/* ========================================================================== ReceiptNext — PWA Expense Tracker Stylesheet "The Terminal Mint" dark palette | Mobile-first responsive ========================================================================== */ /* -------------------------------------------------------------------------- 0. CSS Custom Properties (Design Tokens) -------------------------------------------------------------------------- */ :root { /* Colors — "The Terminal Mint" Dark Palette */ --color-primary: #10b981; --color-primary-hover: #34d399; --color-primary-light: #064e3b; --color-primary-dark: #047857; --color-secondary: #334155; --color-secondary-hover: #475569; --color-bg: #0f172a; --color-card: #1e293b; --color-text: #f8fafc; --color-text-muted: #94a3b8; --color-text-light: #64748b; --color-border: #334155; --color-border-focus: #10b981; --color-danger: #ef4444; --color-danger-hover: #f87171; --color-danger-light: #450a0a; --color-success: #10b981; --color-warning: #f59e0b; --color-open-bg: #064e3b; --color-open-text: #6ee7b7; --color-closed-bg: #334155; --color-closed-text: #cbd5e1; /* Shadows — more pronounced on dark bg */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2); --shadow-md: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2); --shadow-lg: 0 4px 6px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2); --shadow-xl: 0 10px 25px rgba(0, 0, 0, 0.4); /* Border radius */ --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-full: 9999px; /* Spacing scale */ --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px; --space-12: 48px; --space-16: 64px; /* Typography */ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.125rem; --text-xl: 1.25rem; --text-2xl: 1.5rem; --text-3xl: 1.875rem; --leading-tight: 1.25; --leading-normal: 1.5; --leading-relaxed: 1.625; --font-normal: 400; --font-medium: 500; --font-semibold: 600; --font-bold: 700; /* Transitions */ --transition-fast: 150ms ease; --transition-base: 200ms ease; --transition-slow: 300ms ease; /* Layout */ --max-width: 1024px; --header-height: 60px; /* Z-index layers */ --z-base: 1; --z-dropdown: 100; --z-modal: 200; --z-toast: 300; } /* -------------------------------------------------------------------------- 1. Reset & Base -------------------------------------------------------------------------- */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html { -webkit-text-size-adjust: 100%; -moz-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; } body { font-family: var(--font-family); font-size: var(--text-base); line-height: var(--leading-normal); color: var(--color-text); background-color: var(--color-bg); min-height: 100vh; min-height: 100dvh; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } img { max-width: 100%; height: auto; display: block; } a { color: var(--color-primary); text-decoration: none; transition: color var(--transition-fast); } a:hover { color: var(--color-primary-hover); } /* -------------------------------------------------------------------------- 2. Typography -------------------------------------------------------------------------- */ h1, h2, h3, h4, h5, h6 { line-height: var(--leading-tight); font-weight: var(--font-semibold); color: var(--color-text); } h1 { font-size: var(--text-2xl); } h2 { font-size: var(--text-xl); } h3 { font-size: var(--text-lg); } p { margin-bottom: var(--space-2); color: var(--color-text-muted); } small, .text-sm { font-size: var(--text-sm); } .text-xs { font-size: var(--text-xs); } .text-muted { color: var(--color-text-muted); } .text-light { color: var(--color-text-light); } .text-center { text-align: center; } .font-medium { font-weight: var(--font-medium); } .font-semibold { font-weight: var(--font-semibold); } .font-bold { font-weight: var(--font-bold); } .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* -------------------------------------------------------------------------- 3. Layout Helpers -------------------------------------------------------------------------- */ .container { width: 100%; max-width: var(--max-width); margin-left: auto; margin-right: auto; padding-left: var(--space-4); padding-right: var(--space-4); } .flex { display: flex; } .flex-col { flex-direction: column; } .flex-wrap { flex-wrap: wrap; } .items-center { align-items: center; } .items-start { align-items: flex-start; } .justify-center { justify-content: center; } .justify-between { justify-content: space-between; } .justify-end { justify-content: flex-end; } .gap-1 { gap: var(--space-1); } .gap-2 { gap: var(--space-2); } .gap-3 { gap: var(--space-3); } .gap-4 { gap: var(--space-4); } .gap-6 { gap: var(--space-6); } .mt-1 { margin-top: var(--space-1); } .mt-2 { margin-top: var(--space-2); } .mt-3 { margin-top: var(--space-3); } .mt-4 { margin-top: var(--space-4); } .mt-6 { margin-top: var(--space-6); } .mt-8 { margin-top: var(--space-8); } .mb-1 { margin-bottom: var(--space-1); } .mb-2 { margin-bottom: var(--space-2); } .mb-3 { margin-bottom: var(--space-3); } .mb-4 { margin-bottom: var(--space-4); } .mb-6 { margin-bottom: var(--space-6); } .mb-8 { margin-bottom: var(--space-8); } .p-2 { padding: var(--space-2); } .p-3 { padding: var(--space-3); } .p-4 { padding: var(--space-4); } .p-6 { padding: var(--space-6); } .py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); } .py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); } .py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); } .py-6 { padding-top: var(--space-6); padding-bottom: var(--space-6); } .px-3 { padding-left: var(--space-3); padding-right: var(--space-3); } .px-4 { padding-left: var(--space-4); padding-right: var(--space-4); } .px-6 { padding-left: var(--space-6); padding-right: var(--space-6); } .w-full { width: 100%; } .hidden { display: none; } /* -------------------------------------------------------------------------- 4. App Shell (Header, Main) -------------------------------------------------------------------------- */ .app-header { position: sticky; top: 0; z-index: var(--z-dropdown); display: flex; align-items: center; justify-content: space-between; height: var(--header-height); padding: 0 var(--space-4); background-color: var(--color-card); border-bottom: 1px solid var(--color-border); box-shadow: var(--shadow-sm); } .app-header__title { font-size: var(--text-lg); font-weight: var(--font-bold); color: var(--color-secondary); } .app-header__title span { color: var(--color-primary); } .app-main { padding: var(--space-6) var(--space-4) var(--space-10); min-height: calc(100vh - var(--header-height)); min-height: calc(100dvh - var(--header-height)); } .main-content { padding: var(--space-5) var(--space-4) var(--space-10); } /* -------------------------------------------------------------------------- 5. Cards -------------------------------------------------------------------------- */ .card { background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-md); transition: box-shadow var(--transition-base), transform var(--transition-base); } .card:hover { box-shadow: var(--shadow-lg); } .card--clickable { cursor: pointer; } .card--clickable:active { transform: scale(0.99); } .card__body { padding: var(--space-4); } .card__footer { display: flex; align-items: center; justify-content: space-between; padding: var(--space-3) var(--space-4); border-top: 1px solid var(--color-border); background-color: var(--color-bg); border-radius: 0 0 var(--radius-lg) var(--radius-lg); } .card__title { font-size: var(--text-base); font-weight: var(--font-semibold); margin-bottom: var(--space-1); } .card__subtitle { font-size: var(--text-sm); color: var(--color-text-muted); } /* Login card — centered single-column */ .login-card { width: 100%; max-width: 420px; margin: var(--space-12) auto; padding: var(--space-10) var(--space-8); border-radius: var(--radius-xl); } .login-card__logo { display: flex; justify-content: center; margin-bottom: var(--space-6); } .login-card__logo svg { width: 48px; height: 48px; color: var(--color-primary); } .login-card__heading { text-align: center; margin-bottom: var(--space-2); } .login-card__subheading { text-align: center; color: var(--color-text-muted); font-size: var(--text-sm); margin-bottom: var(--space-6); } /* -------------------------------------------------------------------------- 6. Event Cards Grid -------------------------------------------------------------------------- */ .events-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-4); } .event-card { position: relative; } .event-card__header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2); } .event-card__meta { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); font-size: var(--text-sm); color: var(--color-text-muted); } .event-card__amount { font-size: var(--text-lg); font-weight: var(--font-bold); color: var(--color-primary); margin-top: var(--space-2); } .event-card__description { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: var(--space-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .event-card__actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); } /* -------------------------------------------------------------------------- 7. Status Badges -------------------------------------------------------------------------- */ .badge { display: inline-flex; align-items: center; gap: var(--space-1); padding: 2px 10px; font-size: var(--text-xs); font-weight: var(--font-medium); line-height: 1.4; border-radius: var(--radius-full); white-space: nowrap; user-select: none; } .badge--open { background-color: var(--color-open-bg); color: var(--color-open-text); } .badge--closed { background-color: var(--color-closed-bg); color: var(--color-closed-text); } .badge--pending { background-color: #fef3c7; color: #92400e; } .badge--approved { background-color: var(--color-primary-light); color: var(--color-primary-dark); } .badge--rejected { background-color: var(--color-danger-light); color: #991b1b; } /* -------------------------------------------------------------------------- 8. Buttons -------------------------------------------------------------------------- */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-3) var(--space-5); font-family: inherit; font-size: var(--text-sm); font-weight: var(--font-medium); line-height: 1.4; border: 1px solid transparent; border-radius: var(--radius-md); cursor: pointer; user-select: none; white-space: nowrap; text-decoration: none; transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast); -webkit-tap-highlight-color: transparent; } .btn:active { transform: scale(0.97); } .btn:disabled, .btn--disabled { opacity: 0.5; cursor: not-allowed; transform: none; } /* Primary */ .btn--primary { background-color: var(--color-primary); color: #ffffff; border-color: var(--color-primary); } .btn--primary:hover:not(:disabled) { background-color: var(--color-primary-hover); border-color: var(--color-primary-hover); } .btn--primary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; } /* Secondary */ .btn--secondary { background-color: var(--color-card); color: var(--color-secondary); border-color: var(--color-border); } .btn--secondary:hover:not(:disabled) { background-color: var(--color-bg); border-color: var(--color-text-light); } .btn--secondary:focus-visible { outline: 2px solid var(--color-secondary); outline-offset: 2px; } /* Danger */ .btn--danger { background-color: var(--color-danger); color: #ffffff; border-color: var(--color-danger); } .btn--danger:hover:not(:disabled) { background-color: var(--color-danger-hover); border-color: var(--color-danger-hover); } .btn--danger:focus-visible { outline: 2px solid var(--color-danger); outline-offset: 2px; } /* Ghost (no background, subtle border) */ .btn--ghost { background-color: transparent; color: var(--color-text-muted); border-color: transparent; } .btn--ghost:hover:not(:disabled) { background-color: var(--color-bg); color: var(--color-text); } /* Sizes */ .btn--sm { padding: var(--space-1) var(--space-3); font-size: var(--text-xs); } .btn--lg { padding: var(--space-3) var(--space-6); font-size: var(--text-base); } /* Full width on mobile */ .btn--block { width: 100%; } /* Icon-only button */ .btn--icon { padding: var(--space-2); min-width: 36px; min-height: 36px; } /* Floating Action Button */ .fab { position: fixed; bottom: var(--space-6); right: var(--space-6); z-index: var(--z-dropdown); display: flex; align-items: center; justify-content: center; width: 56px; height: 56px; background-color: var(--color-primary); color: #ffffff; border: none; border-radius: var(--radius-full); box-shadow: var(--shadow-lg); cursor: pointer; transition: background-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast); -webkit-tap-highlight-color: transparent; } .fab:hover { background-color: var(--color-primary-hover); box-shadow: var(--shadow-xl); transform: translateY(-2px); } .fab:active { transform: scale(0.95); } .fab svg { width: 24px; height: 24px; } /* -------------------------------------------------------------------------- 9. Form Inputs -------------------------------------------------------------------------- */ .form-group { margin-bottom: var(--space-5); } .form-label { display: block; font-size: var(--text-xs); font-weight: var(--font-semibold); color: var(--color-text-muted); margin-bottom: var(--space-1); text-transform: uppercase; letter-spacing: 0.05em; } .form-label--required::after { content: " *"; color: var(--color-danger); } /* Bare input/select/textarea inside form-group get the same styling */ .form-group input:not([type="radio"]):not([type="checkbox"]):not([type="file"]):not([type="hidden"]), .form-group select, .form-group textarea { display: block; width: 100%; padding: var(--space-4) var(--space-4); font-family: inherit; font-size: var(--text-lg); line-height: var(--leading-relaxed); color: var(--color-text); background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); transition: border-color var(--transition-fast), box-shadow var(--transition-fast); -webkit-appearance: none; appearance: none; box-sizing: border-box; } .form-group input:not([type="radio"]):not([type="checkbox"]):not([type="file"]):not([type="hidden"])::placeholder, .form-group textarea::placeholder { color: var(--color-text-light); font-size: var(--text-base); } /* Also keep the class-based selectors for explicit usage */ .form-input, .form-select, .form-textarea { display: block; width: 100%; padding: var(--space-4) var(--space-4); font-family: inherit; font-size: var(--text-lg); line-height: var(--leading-relaxed); color: var(--color-text); background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); transition: border-color var(--transition-fast), box-shadow var(--transition-fast); -webkit-appearance: none; appearance: none; box-sizing: border-box; } .form-input::placeholder, .form-textarea::placeholder { color: var(--color-text-light); font-size: var(--text-base); } .form-group input:not([type="radio"]):not([type="checkbox"]):not([type="file"]):not([type="hidden"]):hover, .form-group select:hover, .form-group textarea:hover, .form-input:hover, .form-select:hover, .form-textarea:hover { border-color: var(--color-text-light); } .form-group input:not([type="radio"]):not([type="checkbox"]):not([type="file"]):not([type="hidden"]):focus, .form-group select:focus, .form-group textarea:focus, .form-input:focus, .form-select:focus, .form-textarea:focus { outline: none; border-color: var(--color-border-focus); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15); } .form-input--error { border-color: var(--color-danger); } .form-input--error:focus { box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15); } .form-error { display: flex; align-items: center; gap: var(--space-1); margin-top: var(--space-1); font-size: var(--text-xs); color: var(--color-danger); } .form-row { display: flex; gap: var(--space-4); } @media (max-width: 480px) { .form-row { flex-direction: column; gap: var(--space-3); } } .form-textarea { min-height: 100px; resize: vertical; } .form-group select, .form-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right var(--space-3) center; padding-right: var(--space-10); cursor: pointer; } /* Prevent zoom on mobile for inputs */ @media screen and (max-width: 768px) { .form-group input:not([type="radio"]):not([type="checkbox"]):not([type="file"]):not([type="hidden"]), .form-group select, .form-group textarea, .form-input, .form-select, .form-textarea { font-size: 16px; /* Prevents iOS zoom on focus */ } } /* -------------------------------------------------------------------------- 10. OTP Input (6-digit) -------------------------------------------------------------------------- */ .otp-container { display: flex; gap: var(--space-2); justify-content: center; margin: var(--space-6) 0; } .otp-input { width: 44px; height: 52px; padding: 0; font-family: inherit; font-size: var(--text-xl); font-weight: var(--font-bold); text-align: center; color: var(--color-text); background-color: var(--color-card); border: 2px solid var(--color-border); border-radius: var(--radius-md); outline: none; transition: border-color var(--transition-fast), box-shadow var(--transition-fast); -moz-appearance: textfield; -webkit-appearance: none; appearance: none; } .otp-input::-webkit-outer-spin-button, .otp-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .otp-input:hover { border-color: var(--color-text-light); } .otp-input:focus { border-color: var(--color-border-focus); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15); } .otp-input--filled { border-color: var(--color-primary); background-color: var(--color-primary-light); } .otp-input--error { border-color: var(--color-danger); animation: shake 0.3s ease-in-out; } /* -------------------------------------------------------------------------- 11. File Input (hidden for camera capture) -------------------------------------------------------------------------- */ .file-input-wrapper { position: relative; } .file-input-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } .file-input-label { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); font-family: inherit; font-size: var(--text-sm); font-weight: var(--font-medium); color: var(--color-text); background-color: var(--color-card); border: 1px dashed var(--color-border); border-radius: var(--radius-md); cursor: pointer; transition: border-color var(--transition-fast), background-color var(--transition-fast); width: 100%; } .file-input-label:hover { border-color: var(--color-primary); background-color: var(--color-primary-light); } .file-input-label:active { transform: scale(0.99); } .file-input-label--has-file { border-style: solid; border-color: var(--color-primary); background-color: var(--color-primary-light); color: var(--color-primary-dark); } .file-input-preview { margin-top: var(--space-2); position: relative; border-radius: var(--radius-md); overflow: hidden; } .file-input-preview img { width: 100%; max-height: 240px; object-fit: cover; border: 1px solid var(--color-border); border-radius: var(--radius-md); } .file-input-preview__remove { position: absolute; top: var(--space-2); right: var(--space-2); background-color: rgba(0, 0, 0, 0.6); color: #ffffff; border: none; border-radius: var(--radius-full); width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: var(--text-sm); transition: background-color var(--transition-fast); } .file-input-preview__remove:hover { background-color: rgba(0, 0, 0, 0.8); } /* -------------------------------------------------------------------------- 12. Tables / Expense List -------------------------------------------------------------------------- */ .expense-list { display: flex; flex-direction: column; gap: var(--space-3); } .expense-item { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); transition: border-color var(--transition-fast), box-shadow var(--transition-fast); } .expense-item:hover { border-color: var(--color-text-light); box-shadow: var(--shadow-sm); } .expense-item__icon { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; background-color: var(--color-primary-light); color: var(--color-primary); border-radius: var(--radius-sm); flex-shrink: 0; } .expense-item__icon svg { width: 20px; height: 20px; } .expense-item__info { flex: 1; min-width: 0; } .expense-item__title { font-size: var(--text-sm); font-weight: var(--font-medium); color: var(--color-text); margin-bottom: 2px; } .expense-item__meta { font-size: var(--text-xs); color: var(--color-text-muted); } .expense-item__amount { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); white-space: nowrap; flex-shrink: 0; } .expense-item__actions { display: flex; align-items: center; gap: var(--space-1); flex-shrink: 0; } /* -------------------------------------------------------------------------- 13. Receipt Form (AI pre-filled edit form) -------------------------------------------------------------------------- */ .receipt-form { background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-6); box-shadow: var(--shadow-md); } .receipt-form__header { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-6); padding-bottom: var(--space-4); border-bottom: 1px solid var(--color-border); } .receipt-form__header svg { width: 24px; height: 24px; color: var(--color-primary); } .receipt-form__title { font-size: var(--text-lg); font-weight: var(--font-semibold); } .receipt-form__ai-badge { display: inline-flex; align-items: center; gap: var(--space-1); padding: 2px 8px; background-color: #ede9fe; color: #5b21b6; font-size: var(--text-xs); font-weight: var(--font-medium); border-radius: var(--radius-full); margin-left: auto; } .receipt-form__ai-badge svg { width: 12px; height: 12px; color: #5b21b6; } .receipt-form__row { display: grid; grid-template-columns: 1fr; gap: var(--space-4); } /* -------------------------------------------------------------------------- 14. Modal / Dialog Overlay -------------------------------------------------------------------------- */ .modal-overlay { position: fixed; inset: 0; z-index: var(--z-modal); display: flex; align-items: flex-end; justify-content: center; background-color: rgba(15, 23, 42, 0.5); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); padding: var(--space-4); opacity: 0; visibility: hidden; transition: opacity var(--transition-base), visibility var(--transition-base); } .modal-overlay--open { opacity: 1; visibility: visible; } .modal { width: 100%; max-width: 500px; max-height: 90vh; max-height: 90dvh; overflow-y: auto; background-color: var(--color-card); border-radius: var(--radius-xl) var(--radius-xl) 0 0; box-shadow: var(--shadow-xl); transform: translateY(100%); transition: transform var(--transition-slow); -webkit-overflow-scrolling: touch; } .modal-overlay--open .modal { transform: translateY(0); } .modal__header { display: flex; align-items: center; justify-content: space-between; padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--color-border); position: sticky; top: 0; background-color: var(--color-card); z-index: 1; } .modal__title { font-size: var(--text-lg); font-weight: var(--font-semibold); } .modal__close { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: none; border: none; border-radius: var(--radius-sm); color: var(--color-text-muted); cursor: pointer; transition: background-color var(--transition-fast), color var(--transition-fast); } .modal__close:hover { background-color: var(--color-bg); color: var(--color-text); } .modal__body { padding: var(--space-6); } .modal__footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-3); padding: var(--space-4) var(--space-6); border-top: 1px solid var(--color-border); background-color: var(--color-bg); } /* Desktop: center modal */ @media (min-width: 768px) { .modal-overlay { align-items: center; } .modal { border-radius: var(--radius-xl); transform: translateY(0) scale(0.95); opacity: 0; transition: transform var(--transition-base), opacity var(--transition-base); } .modal-overlay--open .modal { transform: translateY(0) scale(1); opacity: 1; } } /* -------------------------------------------------------------------------- 15. HTMX Loading States -------------------------------------------------------------------------- */ /* Pulse skeleton for content loading */ .htmx-request .skeleton, .skeleton { background: linear-gradient( 90deg, var(--color-border) 25%, #f1f5f9 50%, var(--color-border) 75% ); background-size: 200% 100%; animation: skeleton-pulse 1.5s ease-in-out infinite; border-radius: var(--radius-sm); color: transparent !important; user-select: none; pointer-events: none; } .skeleton--text { display: inline-block; height: 1em; width: 100%; } .skeleton--heading { height: 1.5em; width: 60%; } .skeleton--avatar { width: 40px; height: 40px; border-radius: var(--radius-full); } .skeleton--card { height: 120px; } /* HTMX spinner */ .htmx-indicator { display: none; opacity: 0; transition: opacity var(--transition-fast); } .htmx-request .htmx-indicator, .htmx-indicator.htmx-request { display: inline-flex; opacity: 1; } /* Spinner animation */ .spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid var(--color-border); border-top-color: var(--color-primary); border-radius: 50%; animation: spin 0.6s linear infinite; } .spinner--sm { width: 14px; height: 14px; border-width: 2px; } .spinner--lg { width: 32px; height: 32px; border-width: 3px; } .spinner--white { border-color: rgba(255, 255, 255, 0.3); border-top-color: #ffffff; } /* Button loading state */ .btn--loading { position: relative; color: transparent !important; pointer-events: none; } .btn--loading::after { content: ""; position: absolute; width: 16px; height: 16px; border: 2px solid rgba(255, 255, 255, 0.3); border-top-color: #ffffff; border-radius: 50%; animation: spin 0.6s linear infinite; } .btn--secondary.btn--loading::after { border-color: var(--color-border); border-top-color: var(--color-primary); } /* HTMX swap transitions: prevent layout shifts */ .htmx-swapping { opacity: 0; transition: opacity var(--transition-fast); } .htmx-settling { opacity: 0; } /* Container for replaced content — reserve space */ .htmx-target-container { position: relative; min-height: 40px; } /* -------------------------------------------------------------------------- 16. Toast / Notification -------------------------------------------------------------------------- */ .toast-container { position: fixed; top: var(--space-4); right: var(--space-4); left: var(--space-4); z-index: var(--z-toast); display: flex; flex-direction: column; gap: var(--space-2); pointer-events: none; } .toast { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); pointer-events: auto; animation: toast-in 0.3s ease-out; } .toast--success { border-left: 4px solid var(--color-success); } .toast--error { border-left: 4px solid var(--color-danger); } .toast--warning { border-left: 4px solid var(--color-warning); } .toast__message { flex: 1; font-size: var(--text-sm); color: var(--color-text); } .toast__dismiss { flex-shrink: 0; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; background: none; border: none; border-radius: var(--radius-sm); color: var(--color-text-light); cursor: pointer; transition: background-color var(--transition-fast), color var(--transition-fast); } .toast__dismiss:hover { background-color: var(--color-bg); color: var(--color-text); } /* Desktop positioning */ @media (min-width: 768px) { .toast-container { left: auto; right: var(--space-6); max-width: 380px; } } /* -------------------------------------------------------------------------- 17. Empty State -------------------------------------------------------------------------- */ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: var(--space-12) var(--space-4); text-align: center; } .empty-state__icon { width: 64px; height: 64px; color: var(--color-text-light); margin-bottom: var(--space-4); } .empty-state__heading { font-size: var(--text-lg); font-weight: var(--font-semibold); color: var(--color-text); margin-bottom: var(--space-2); } .empty-state__text { font-size: var(--text-sm); color: var(--color-text-muted); max-width: 280px; margin-bottom: var(--space-6); } /* -------------------------------------------------------------------------- 18. Login Page Specific -------------------------------------------------------------------------- */ .login-page { display: flex; flex-direction: column; justify-content: center; min-height: 100vh; min-height: 100dvh; padding: var(--space-4); background-color: var(--color-bg); } .login-page__brand { text-align: center; margin-bottom: var(--space-8); } .login-page__brand h1 { font-size: var(--text-3xl); font-weight: var(--font-bold); color: var(--color-secondary); } .login-page__brand h1 span { color: var(--color-primary); } .login-page__brand p { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: var(--space-1); } /* Email step */ .login-step__email, .login-step__otp { transition: opacity var(--transition-base); } /* OTP resend */ .otp-resend { text-align: center; font-size: var(--text-sm); color: var(--color-text-muted); margin-top: var(--space-4); } .otp-resend button { background: none; border: none; color: var(--color-primary); font-family: inherit; font-size: var(--text-sm); font-weight: var(--font-medium); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; transition: color var(--transition-fast); } .otp-resend button:hover { color: var(--color-primary-hover); } .otp-resend button:disabled { color: var(--color-text-light); cursor: not-allowed; text-decoration: none; } /* -------------------------------------------------------------------------- 19. Dashboard Specific -------------------------------------------------------------------------- */ .dashboard-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-6); flex-wrap: wrap; gap: var(--space-3); } .dashboard-header__title { font-size: var(--text-xl); font-weight: var(--font-bold); } .dashboard-header__actions { display: flex; align-items: center; gap: var(--space-2); } /* Filters / tabs */ .filter-tabs { display: flex; gap: var(--space-1); margin-bottom: var(--space-4); padding: var(--space-1); background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow-x: auto; -webkit-overflow-scrolling: touch; } .filter-tab { flex: 1; padding: var(--space-2) var(--space-3); font-family: inherit; font-size: var(--text-sm); font-weight: var(--font-medium); color: var(--color-text-muted); background: none; border: none; border-radius: var(--radius-sm); cursor: pointer; white-space: nowrap; transition: background-color var(--transition-fast), color var(--transition-fast); } .filter-tab:hover { color: var(--color-text); background-color: var(--color-bg); } .filter-tab--active { color: var(--color-primary); background-color: var(--color-primary-light); } /* Summary bar */ .summary-bar { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); margin-bottom: var(--space-6); } .summary-item { padding: var(--space-4); background-color: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); text-align: center; } .summary-item__value { font-size: var(--text-2xl); font-weight: var(--font-bold); color: var(--color-text); } .summary-item__label { font-size: var(--text-xs); color: var(--color-text-muted); margin-top: var(--space-1); } /* -------------------------------------------------------------------------- 20. Receipt Capture Page -------------------------------------------------------------------------- */ .capture-page { padding: var(--space-4); } .capture-preview { margin-bottom: var(--space-6); } .capture-preview__image { width: 100%; max-height: 360px; object-fit: contain; background-color: #000000; border-radius: var(--radius-lg); } .capture-actions { display: flex; flex-direction: column; gap: var(--space-3); } /* -------------------------------------------------------------------------- 21. Utility & Accessibility -------------------------------------------------------------------------- */ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } /* Focus visible for keyboard navigation */ :focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; } :focus:not(:focus-visible) { outline: none; } /* Reduced motion preference */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } html { scroll-behavior: auto; } } /* Selection */ ::selection { background-color: var(--color-primary-light); color: var(--color-primary-dark); } /* Scrollbar styling */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background-color: var(--color-border); border-radius: var(--radius-full); } ::-webkit-scrollbar-thumb:hover { background-color: var(--color-text-light); } /* -------------------------------------------------------------------------- 22. Responsive: Tablet (768px+) -------------------------------------------------------------------------- */ @media (min-width: 768px) { .container { padding-left: var(--space-8); padding-right: var(--space-8); } h1 { font-size: var(--text-3xl); } h2 { font-size: var(--text-2xl); } h3 { font-size: var(--text-xl); } .app-main { padding: var(--space-6) 0 var(--space-12); } /* Events grid: 2 columns */ .events-grid { grid-template-columns: repeat(2, 1fr); } /* Summary bar: 4 columns */ .summary-bar { grid-template-columns: repeat(4, 1fr); } /* Receipt form rows */ .receipt-form__row { grid-template-columns: 1fr 1fr; } /* Login card vertical centering */ .login-card { margin-top: 0; } /* OTP: slightly larger inputs */ .otp-input { width: 48px; height: 56px; font-size: var(--text-2xl); } .fab { bottom: var(--space-8); right: var(--space-8); } /* Dashboard header stack inline */ .dashboard-header { flex-wrap: nowrap; } .empty-state__icon { width: 80px; height: 80px; } } /* -------------------------------------------------------------------------- 23. Responsive: Desktop (1024px+) -------------------------------------------------------------------------- */ @media (min-width: 1024px) { .container { padding-left: var(--space-12); padding-right: var(--space-12); } .app-main { padding: var(--space-8) 0 var(--space-16); } /* Events grid: 3 columns */ .events-grid { grid-template-columns: repeat(3, 1fr); } /* OTP: standard sizing */ .otp-input { width: 52px; height: 60px; } /* Larger FAB */ .fab { width: 60px; height: 60px; bottom: var(--space-10); right: var(--space-10); } .fab svg { width: 28px; height: 28px; } } /* -------------------------------------------------------------------------- 24. Responsive: Large Desktop (1280px+) -------------------------------------------------------------------------- */ @media (min-width: 1280px) { .container { padding-left: var(--space-16); padding-right: var(--space-16); } .events-grid { gap: var(--space-6); } } /* -------------------------------------------------------------------------- 25. Keyframe Animations -------------------------------------------------------------------------- */ @keyframes spin { to { transform: rotate(360deg); } } @keyframes skeleton-pulse { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } @keyframes toast-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } } @keyframes shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-4px); } 40% { transform: translateX(4px); } 60% { transform: translateX(-4px); } 80% { transform: translateX(4px); } } @keyframes fade-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } @keyframes fade-in-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } @keyframes scale-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } /* Utility animation classes */ .animate-fade-in { animation: fade-in var(--transition-base) ease-out; } .animate-fade-in-up { animation: fade-in-up var(--transition-slow) ease-out; } .animate-scale-in { animation: scale-in var(--transition-base) ease-out; } /* Stagger children animations */ .animate-stagger > * { opacity: 0; animation: fade-in-up var(--transition-slow) ease-out forwards; } .animate-stagger > *:nth-child(1) { animation-delay: 0ms; } .animate-stagger > *:nth-child(2) { animation-delay: 60ms; } .animate-stagger > *:nth-child(3) { animation-delay: 120ms; } .animate-stagger > *:nth-child(4) { animation-delay: 180ms; } .animate-stagger > *:nth-child(5) { animation-delay: 240ms; } .animate-stagger > *:nth-child(6) { animation-delay: 300ms; }