@tailwind base; @tailwind components; @tailwind utilities; :root { --control-radius: 8px; --page-title-size: 38px; --page-title-line-height: 48px; } @media (max-width: 1199px) { :root { --page-title-size: 30px; --page-title-line-height: 40px; } } @media (max-width: 767px) { :root { --page-title-size: 28px; --page-title-line-height: 36px; } } /* Dodo IS reference: 264px sidebar, neutral surfaces, 8–12px corners. */ @layer components { .app-shell { min-height: 100vh; } .app-sidebar { @apply sticky top-0 z-40 flex flex-col; background: #f0f0f0; } .sidebar-brand-row { @apply flex items-center justify-between px-4 py-3; } .sidebar-brand { @apply flex items-center gap-2 text-lg font-bold text-gray-900; } .brand-logo { display: block; width: 168px; height: 34px; object-fit: contain; } .brand-mark { @apply flex h-8 w-8 shrink-0 items-center justify-center rounded-lg; background: #ff6900; } .sidebar-menu-button { @apply flex h-11 w-11 items-center justify-center rounded-lg hover:bg-black/[0.04] lg:hidden; } .sidebar-content { @apply hidden min-h-0 flex-col px-4 pb-4; max-height: calc(100dvh - 68px); overflow-y: auto; } .sidebar-content.is-open { display: flex; } .sidebar-account { @apply flex items-center justify-between gap-3 rounded-xl bg-white px-3 py-2 text-sm; min-height: 56px; } .city-selector { position: relative; margin-top: 12px; } .sidebar-city-card { @apply flex w-full items-center justify-between gap-3 rounded-xl bg-white px-3 py-2 text-sm transition-colors hover:bg-gray-50; min-height: 64px; } .city-emblem { @apply relative flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary-50 text-lg; overflow: hidden; } .city-emblem > img { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; } .city-emblem-fallback { @apply flex h-full w-full items-center justify-center; } .city-emblem.is-small { @apply h-8 w-8 text-base; } .city-emblem.is-large { @apply h-14 w-14 rounded-xl text-3xl; } .city-switcher { position: absolute; z-index: 50; top: calc(100% + 8px); left: 0; right: 0; min-width: 232px; padding: 8px; border-radius: 12px; background: #fff; box-shadow: 0 12px 32px #00000026, 0 2px 8px #0000000d; } .city-switcher-heading { @apply px-3 pb-2 pt-1 text-xs uppercase tracking-wide text-gray-500; } .city-switcher-option { @apply flex min-h-[44px] w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-sm text-gray-800 hover:bg-gray-50; } .city-switcher-divider { height: 1px; margin: 6px 4px; background: #f0f0f0; } .city-switcher-add { @apply text-primary-700; } .sidebar-group-title { @apply px-3 pb-2 pt-5 text-xs font-normal uppercase tracking-wide text-gray-500; } .sidebar-link { @apply flex min-h-[44px] items-center gap-3 rounded-lg px-3 py-3 text-sm text-gray-600 transition-colors hover:bg-black/[0.04]; } .sidebar-link.is-active { @apply bg-black/[0.04] font-semibold text-gray-900; } .sidebar-footer { @apply mt-auto pt-8; } .app-main { @apply min-w-0 px-4 py-6 sm:px-6 sm:py-8; } .app-content { @apply mx-auto w-full; max-width: 1206px; } .app-main.app-main-wide { padding: 24px; } .app-main-wide .app-content { max-width: 1608px; } .profile-menu { position: fixed; z-index: 60; padding: 8px; background: #fff; border-radius: 12px; box-shadow: 0 12px 32px #00000026, 0 2px 8px #0000000d; } .profile-menu-item { @apply flex w-full items-center gap-4 px-4 py-4 text-left text-sm text-gray-900 hover:bg-gray-50; border-radius: var(--control-radius); min-height: 56px; } .profile-menu-item svg { flex-shrink: 0; } .profile-menu-divider { margin: 4px 16px 4px 56px; height: 1px; background: #f0f0f0; } .route-badge { display: flex; flex: 0 0 auto; align-items: center; justify-content: center; min-width: 48px; max-width: 96px; min-height: 48px; padding: 8px; border-radius: var(--control-radius); color: #fff; font-size: 16px; font-weight: 600; line-height: 24px; overflow-wrap: anywhere; } .skip-link { @apply fixed left-4 top-4 z-50 -translate-y-24 rounded-lg bg-white px-4 py-3 text-primary-600 focus:translate-y-0; } .segmented-control { @apply flex w-fit max-w-full flex-wrap gap-1 rounded-lg bg-black/[0.04] p-1; } .segmented-option { @apply min-h-[44px] rounded-md px-3 py-2 text-sm text-gray-600 transition-colors hover:text-gray-900; } .segmented-option[aria-pressed="true"] { @apply bg-white font-medium text-gray-900 shadow-sm; } .stat-card { @apply flex h-full flex-col; } .stat-card-icon { @apply flex h-10 w-10 items-center justify-center rounded-lg border border-gray-200 text-gray-600; } .stat-card-link { @apply mt-auto flex items-center justify-between border-t border-gray-100 pt-3 text-sm text-primary-600; } .dashboard-intro { @apply flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between; } .cities-page { @apply space-y-6; } .cities-heading { @apply flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between; } .cities-heading > div > p { @apply mt-2 text-gray-500; } .city-form { @apply space-y-5; } .city-form h2 { @apply text-lg font-semibold; } .city-form h2 + p { @apply mt-1 text-sm text-gray-500; } .city-form-grid { @apply grid gap-4 sm:grid-cols-2; } .city-form-hint { @apply mt-1 block text-xs text-gray-500; } .city-form-error { @apply rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700; } .city-form-notice { @apply rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800; } .city-avatar-field { @apply flex flex-col gap-3; } .city-avatar-preview { @apply h-20 w-20 rounded-xl border border-gray-200 bg-gray-50 object-cover; } .city-center-picker { @apply space-y-2 sm:col-span-2; } .city-center-map { height: 320px; overflow: hidden; border: 1px solid #e5e7eb; border-radius: 12px; } .city-center-coordinates { @apply text-sm text-gray-600; } .city-center-coordinates strong { @apply text-gray-900; } .city-avatar-editor { @apply ml-auto flex shrink-0 flex-col items-end gap-2; } .city-avatar-editor .btn-secondary { @apply inline-flex items-center gap-2; } .city-avatar-editor-error { @apply max-w-[200px] text-right text-xs text-red-700; } .cities-grid { @apply grid gap-4 md:grid-cols-2; } .city-network-card { @apply flex flex-wrap items-center gap-4; min-height: 104px; } .city-network-card.is-active { border-color: #0958d9; box-shadow: inset 0 0 0 1px #0958d9; } .city-network-card h2 { @apply text-lg font-semibold; } .city-network-card p { @apply mt-1 text-sm text-gray-500; } .city-current { @apply inline-flex items-center gap-1 rounded-full bg-primary-50 px-2 py-1 text-xs font-medium text-primary-700; } .city-isolation-note { @apply flex items-start gap-3 rounded-xl bg-primary-50 p-4 text-sm text-primary-700; } .city-isolation-note svg { @apply mt-0.5 shrink-0; } .login-surface { display: grid; min-height: 100dvh; place-items: start center; padding: max(32px, calc((100dvh - 595px) / 2)) 16px; background: #fff; } .login-layout { width: min(100%, 332px); } .login-card { width: 100%; padding: 40px 40px 32px; border-radius: 12px; background: #fff; box-shadow: 0 24px 30px rgb(0 10 28 / 15%); } .login-brand { display: flex; width: 130px; height: 44px; align-items: flex-start; justify-content: center; margin: 0 auto 28px; padding-bottom: 14px; } .brand-logo.login-brand-logo { width: 130px; height: 27px; } .login-methods { width: 100%; } .login-password-choice, .login-passkey-button, .login-password-submit { display: inline-flex; width: 100%; align-items: center; justify-content: center; border: 0; font-size: 15px; font-weight: 400; line-height: 16px; transition: background-color 160ms ease, box-shadow 160ms ease, opacity 160ms ease; } .login-password-choice { height: 44px; padding: 0 16px; border-radius: 8px; color: #000; background: #f2f3f7; } .login-password-choice:hover:not(:disabled) { background: #e8ebf0; } .login-divider { position: relative; height: 1px; margin: 28px 0 44px; background: #8c98a9; } .login-divider span { position: absolute; top: 50%; left: 50%; padding: 0 16px; color: #8c98a9; background: #fff; font-size: 15px; line-height: 19px; transform: translate(-50%, -50%); } .login-passkey-button { height: 48px; gap: 4px; padding: 1px 16px; border-radius: 8px; color: #fff; background: #61a246; } .login-passkey-button:hover:not(:disabled) { background: #518b3a; } .login-passkey-button img, .login-passkey-button svg { width: 24px; height: 24px; flex: 0 0 24px; } .login-passkey-button:disabled, .login-password-choice:disabled, .login-password-submit:disabled { opacity: .55; } .login-passkey-hint { margin: 28px 0 0; color: #8c98a9; font-size: 15px; line-height: 19.2px; text-align: center; } .login-error { display: flex; align-items: flex-start; gap: 8px; margin: 0 0 16px; padding: 10px 12px; border: 1px solid #fecaca; border-radius: 8px; color: #b42318; background: #fef2f2; font-size: 13px; line-height: 18px; } .login-error svg { width: 18px; height: 18px; flex: 0 0 18px; } .login-password-form { display: grid; gap: 16px; } .login-password-heading { display: grid; grid-template-columns: 32px 1fr 32px; align-items: center; margin: 0 0 4px; } .login-password-heading h2 { margin: 0; color: #000; font-size: 16px; font-weight: 500; line-height: 20px; text-align: center; } .login-back-button { display: grid; width: 32px; height: 32px; place-items: center; border: 0; border-radius: 8px; color: #536174; background: transparent; } .login-back-button:hover:not(:disabled) { background: #f2f3f7; } .login-back-button svg { width: 20px; height: 20px; } .login-field { display: grid; gap: 6px; color: #536174; font-size: 13px; line-height: 16px; } .login-input-wrap { position: relative; display: block; } .login-input-wrap svg { position: absolute; top: 50%; left: 12px; width: 18px; height: 18px; color: #8c98a9; transform: translateY(-50%); } .login-input-wrap input { width: 100%; height: 44px; padding: 0 12px 0 40px; border: 1px solid #c9cfd8; border-radius: 8px; color: #000; background: #fff; font-size: 15px !important; line-height: 20px; } .login-input-wrap input::placeholder { color: #8c98a9; } .login-input-wrap input:focus { border-color: #0958d9; outline: none; box-shadow: 0 0 0 2px rgb(9 88 217 / 15%); } .login-password-submit { height: 44px; gap: 8px; margin-top: 4px; border-radius: 8px; color: #fff; background: #ff6900; font-weight: 500; } .login-password-submit:hover:not(:disabled) { background: #e95f00; } .login-password-submit svg { width: 18px; height: 18px; } .login-device-card { display: flex; min-height: 104px; align-items: flex-start; gap: 16px; margin-top: 16px; padding: 28px 40px; border-radius: 12px; color: #000; background: #fff; box-shadow: 0 24px 30px rgb(0 10 28 / 15%); } .login-device-card img { width: 72px; height: 45px; flex: 0 0 72px; } .login-device-card p { margin: -1px 0 0; font-size: 15px; line-height: 20.8px; } .login-footer { min-height: 64px; margin-top: 16px; color: #8c98a9; font-size: 13px; line-height: 20px; text-align: center; } .login-footer p { margin: 0; } @media (max-width: 374px) { .login-card { padding: 28px 24px; } .login-device-card { padding: 24px; } } @media (max-width: 767px) { .app-main.app-main-wide { padding: 24px 16px; } } @media (min-width: 1024px) { .app-sidebar { position: fixed; inset: 0 auto 0 0; width: 264px; padding: 16px; } .sidebar-brand-row { padding: 8px 8px 24px; } .sidebar-content { display: flex; flex: 1; max-height: none; padding: 0; } .app-main { margin-left: 264px; padding: 56px 40px; } } @media (min-width: 1536px) { .app-main { padding-inline: 80px; } } } @layer base { button { border-radius: var(--control-radius); } :where(a, button, input, select, textarea):focus-visible { outline: 2px solid #0958d9; outline-offset: 3px; } button:disabled { cursor: not-allowed; } body { @apply text-gray-900; background: #f5f5f5; -webkit-tap-highlight-color: transparent; } /* Prevent zoom on input focus on iOS */ input, select, textarea { font-size: 16px !important; } @media (min-width: 640px) { input, select, textarea { font-size: inherit !important; } } } @layer components { .btn-primary { @apply bg-primary-500 text-white px-4 py-2.5 rounded-lg font-medium; @apply transition-all duration-200 ease-out; @apply hover:bg-primary-700; @apply active:scale-[0.98]; @apply text-sm sm:text-base; } .btn-secondary { @apply bg-black/[0.04] text-gray-900 px-4 py-2.5 rounded-lg font-medium; @apply transition-all duration-200 ease-out; @apply hover:bg-black/[0.08]; @apply active:scale-[0.98]; @apply text-sm sm:text-base; } .btn-danger { @apply bg-red-600 text-white px-4 py-2.5 rounded-lg font-medium; @apply transition-all duration-200 ease-out; @apply hover:bg-red-700 hover:shadow-md hover:-translate-y-0.5; @apply active:scale-[0.98]; @apply text-sm sm:text-base; } .btn-primary, .btn-secondary, .btn-danger, .input { border-radius: var(--control-radius); } .card { @apply bg-white rounded-xl border border-transparent p-4 sm:p-6; @apply transition-all duration-300 ease-out; } .card-hover { @apply hover:border-gray-200; } .input { @apply border border-gray-300 rounded-lg px-3 py-2.5 w-full; @apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent; @apply transition-all duration-200 ease-out; @apply text-base sm:text-sm; } .label { @apply block text-sm font-medium text-gray-700 mb-1; } /* Mobile-friendly touch targets */ .touch-target { @apply min-h-[44px] min-w-[44px]; } /* Page titles */ .page-title { @apply font-semibold text-gray-900; font-size: var(--page-title-size); line-height: var(--page-title-line-height); letter-spacing: -0.6px; overflow-wrap: anywhere; } /* Modal styles with animations */ .modal-overlay { @apply fixed inset-0 bg-black/50 flex items-end sm:items-center justify-center z-50; animation: fadeIn 0.2s ease-out; } .modal-content { @apply bg-white w-full sm:max-w-lg sm:mx-4 sm:rounded-lg rounded-t-2xl max-h-[90vh] overflow-hidden; animation: slideUp 0.3s ease-out; } @media (min-width: 640px) { .modal-content { animation: scaleIn 0.2s ease-out; } } /* Safe area for bottom navigation */ .safe-bottom { padding-bottom: env(safe-area-inset-bottom, 0); } /* Animated list items */ .list-item-animated { @apply transition-all duration-200 ease-out; animation: fadeSlideIn 0.3s ease-out backwards; } /* Stagger animation delays */ .stagger-1 { animation-delay: 0.05s; } .stagger-2 { animation-delay: 0.1s; } .stagger-3 { animation-delay: 0.15s; } .stagger-4 { animation-delay: 0.2s; } .stagger-5 { animation-delay: 0.25s; } .stagger-6 { animation-delay: 0.3s; } /* Pulse animation for loading states */ .pulse-soft { animation: pulseSoft 2s ease-in-out infinite; } /* Shimmer loading effect */ .shimmer { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; } /* Icon animation */ .icon-bounce { @apply transition-transform duration-200; } .icon-bounce:hover { animation: iconBounce 0.4s ease-out; } /* Floating action button */ .fab { @apply fixed bottom-20 right-4 lg:bottom-8 lg:right-8; @apply w-14 h-14 rounded-full shadow-lg; @apply flex items-center justify-center; @apply bg-primary-600 text-white; @apply transition-all duration-300 ease-out; @apply hover:shadow-xl hover:scale-110; @apply active:scale-95; @apply disabled:opacity-50 disabled:cursor-not-allowed; animation: fabIn 0.4s ease-out; z-index: 40; } /* Secondary FAB */ .fab-secondary { @apply fixed bottom-36 right-4 lg:bottom-24 lg:right-8; @apply w-12 h-12 rounded-full shadow-md; @apply flex items-center justify-center; @apply bg-white text-gray-700 border border-gray-200; @apply transition-all duration-300 ease-out; @apply hover:shadow-lg hover:scale-105; @apply active:scale-95; animation: fabIn 0.4s ease-out 0.1s backwards; z-index: 39; } /* Empty state */ .empty-state { @apply text-center py-12 px-4; } .empty-state-icon { @apply h-16 w-16 sm:h-20 sm:w-20 text-gray-300 mx-auto mb-4; animation: fadeSlideIn 0.5s ease-out; } .empty-state-text { @apply text-gray-500 text-sm sm:text-base mb-4; animation: fadeSlideIn 0.5s ease-out 0.1s backwards; } /* Touch feedback for list items */ .list-item-touch { @apply relative overflow-hidden; -webkit-tap-highlight-color: transparent; } .list-item-touch::before { content: ''; @apply absolute inset-0 bg-gray-900/5 opacity-0 transition-opacity duration-150; pointer-events: none; } .list-item-touch:active::before { @apply opacity-100; } /* Mobile card improvements */ @media (max-width: 640px) { .card { @apply rounded-xl; } .btn-primary, .btn-secondary, .btn-danger { @apply py-3; } } /* Pull to refresh indicator */ .pull-indicator { @apply flex items-center justify-center py-4 text-gray-400; animation: fadeIn 0.2s ease-out; } /* Swipeable item */ .swipe-item { @apply relative; touch-action: pan-y; } /* Bottom sheet handle */ .sheet-handle { @apply w-10 h-1 bg-gray-300 rounded-full mx-auto mb-2; } /* iOS-style segment control */ .segment-control { @apply flex rounded-lg bg-gray-100 p-1 relative; } .segment-item { @apply flex-1 py-2 px-3 rounded-md text-sm font-medium transition-colors relative z-10; @apply flex items-center justify-center; } .segment-item-active { @apply text-gray-900; } .segment-item-inactive { @apply text-gray-500; } .segment-indicator { @apply absolute top-1 bottom-1 bg-white rounded-md shadow-sm transition-all duration-300 ease-out; } /* Status indicator */ .status-online { @apply w-2 h-2 bg-green-500 rounded-full; animation: pulseSoft 2s ease-in-out infinite; } .status-offline { @apply w-2 h-2 bg-gray-400 rounded-full; } /* Quick action buttons */ .quick-action { @apply flex flex-col items-center justify-center p-3 rounded-xl; @apply bg-gray-50 hover:bg-gray-100 active:bg-gray-200; @apply transition-all duration-200; @apply min-w-[72px]; } .quick-action-icon { @apply w-10 h-10 rounded-lg flex items-center justify-center mb-1; @apply transition-transform duration-200; } .quick-action:active .quick-action-icon { @apply scale-90; } .quick-action-label { @apply text-xs text-gray-600 font-medium; } /* Badge animation */ .badge-pulse { @apply relative; } .badge-pulse::after { content: ''; @apply absolute inset-0 rounded-full bg-current opacity-40; animation: badgePulse 1.5s ease-out infinite; } /* Skeleton loading */ .skeleton { @apply bg-gray-200 rounded; animation: skeleton 1.5s ease-in-out infinite; } /* Tooltip animation */ .tooltip { animation: tooltipIn 0.15s ease-out; } /* Success checkmark animation */ .success-check { animation: successCheck 0.4s ease-out; } /* Shake animation for errors */ .shake { animation: shake 0.5s ease-out; } /* Slide transitions */ .slide-enter { animation: slideIn 0.3s ease-out; } .slide-exit { animation: slideOut 0.2s ease-in forwards; } /* Rotate animation for refresh icons */ .spin-once { animation: spinOnce 0.5s ease-out; } /* Number counter animation */ .counter { @apply tabular-nums; transition: all 0.3s ease-out; } /* Progress bar */ .progress-bar { @apply h-1 bg-primary-600 rounded-full; animation: progressGrow 0.5s ease-out; } /* Ripple effect */ .ripple { @apply relative overflow-hidden; } .ripple::after { content: ''; @apply absolute inset-0 bg-white/30 rounded-full scale-0; transition: transform 0.4s ease-out, opacity 0.4s ease-out; } .ripple:active::after { @apply scale-150 opacity-0; } } /* Keyframes */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes slideUp { from { opacity: 0; transform: translateY(100%); } to { opacity: 1; transform: translateY(0); } } @keyframes slideDown { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(100%); } } @keyframes scaleIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } @keyframes fadeSlideIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(20px); } } @keyframes pulseSoft { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } @keyframes iconBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } } @keyframes fabIn { from { opacity: 0; transform: scale(0) rotate(-180deg); } to { opacity: 1; transform: scale(1) rotate(0); } } @keyframes badgePulse { 0% { transform: scale(1); opacity: 0.4; } 100% { transform: scale(2); opacity: 0; } } @keyframes skeleton { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } @keyframes tooltipIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } @keyframes successCheck { 0% { transform: scale(0); } 50% { transform: scale(1.2); } 100% { transform: scale(1); } } @keyframes shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-8px); } 40% { transform: translateX(8px); } 60% { transform: translateX(-4px); } 80% { transform: translateX(4px); } } @keyframes spinOnce { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes progressGrow { from { width: 0; } } @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } } @keyframes wiggle { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-5deg); } 75% { transform: rotate(5deg); } } /* Leaflet map container */ .leaflet-container { height: 100%; width: 100%; border-radius: 0.5rem; z-index: 1; } /* Fix leaflet controls on mobile */ .leaflet-control-zoom { border: none !important; box-shadow: 0 2px 6px rgba(0,0,0,0.15) !important; } .leaflet-control-zoom a { width: 36px !important; height: 36px !important; line-height: 36px !important; transition: background-color 0.2s ease; } /* Custom scrollbar for mobile */ @media (max-width: 640px) { ::-webkit-scrollbar { width: 4px; height: 4px; } ::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 4px; } } /* Hide scrollbar but keep functionality */ .hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } .hide-scrollbar::-webkit-scrollbar { display: none; } /* Smooth scroll on iOS */ .smooth-scroll { -webkit-overflow-scrolling: touch; } /* Pull to refresh indicator space */ @supports (padding-top: env(safe-area-inset-top)) { .safe-top { padding-top: env(safe-area-inset-top); } } /* Page transition wrapper */ .page-transition { animation: pageIn 0.3s ease-out; } @keyframes pageIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* Blob animation for login page */ @keyframes blob { 0%, 100% { transform: translate(0, 0) scale(1); } 33% { transform: translate(30px, -50px) scale(1.1); } 66% { transform: translate(-20px, 20px) scale(0.9); } } .animate-blob { animation: blob 7s infinite; } .animation-delay-2000 { animation-delay: 2s; } .animation-delay-4000 { animation-delay: 4s; } /* Shake animation for errors */ @keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); } 20%, 40%, 60%, 80% { transform: translateX(10px); } } /* Reduced motion */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } }