/* Shared design tokens for the live app and the offline HTML-viewer export. * Both `frontend/src/app.css` and `frontend/export-viewer/src/app.css` import this file * so the keepsake stays visually in sync with the live app. Edit tokens here, rebuild * the export-viewer, and re-commit `backend/static/export-viewer/`. * * Tailwind v4 reads `@theme` blocks to populate utility classes; everything declared * here becomes a `bg-primary`, `text-accent`, `rounded-card`, etc. * * ── DESIGN LANGUAGE: "Warm & celebratory" ────────────────────────────────── * The whole product exists to relive an event together, so the palette is a * wedding-invitation one: warm ivory paper, warm-charcoal ink, a dusty-rose * brand and champagne-gold accent. Headings are set in Fraunces (an elegant * display serif); body copy in Inter. Both are self-hosted (see @font-face * below) because the CSP is `font-src: 'self'` and the keepsake runs offline. * * The bulk of the app was authored in raw Tailwind classes (`bg-gray-900`, * `text-blue-600`, …). Rather than touch 900+ class usages, we REMAP the * underlying Tailwind ramps here so the warmth cascades everywhere: * gray → warm "sand" neutrals * blue → dusty-rose brand * purple → champagne gold (hashtag chips / accents) * Semantic red / green / amber keep their meaning and stay close to default. */ @import 'tailwindcss'; /* Class-based dark variant. Tailwind v4 defaults to `prefers-color-scheme`; we want * the user's explicit selection (saved in `theme-store.ts`) to win, so we re-bind * the `dark:` variant to apply whenever `` has the `dark` class. * `:where(...)` keeps the specificity low so existing utilities still override. */ @custom-variant dark (&:where(.dark, .dark *)); /* ── Self-hosted fonts (variable woff2, latin subset covers German umlauts) ── */ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 100 900; font-display: swap; src: url('/fonts/Inter.woff2') format('woff2'); } @font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 100 900; font-display: swap; src: url('/fonts/Fraunces.woff2') format('woff2'); } @theme { /* ── Type ─────────────────────────────────────────────────────────────── */ --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; --font-display: 'Fraunces', ui-serif, Georgia, 'Times New Roman', serif; --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', 'Courier New', monospace; /* ── Brand: champagne / antique gold — the "signal" colour (remaps `blue-*`) * Wedding research: treat gold as the signal (brand, primary CTA, active * state, key highlights); let silver/greys carry the system. `600` is a deep * antique gold so white button text clears WCAG AA. */ --color-blue-50: #faf6ea; --color-blue-100: #f3ead0; --color-blue-200: #e8d6a3; --color-blue-300: #d8bd73; --color-blue-400: #c6a24a; --color-blue-500: #ab8433; --color-blue-600: #8a6a2b; /* primary buttons / FAB / active tab — white text passes AA */ --color-blue-700: #6f5523; --color-blue-800: #59441e; --color-blue-900: #493819; --color-blue-950: #29200d; /* Semantic aliases so new work can use `bg-primary` intentionally. Full ramp * (mirrors the gold `blue-*` values) so `@apply` in the component layer can * reference any shade — a partial ramp hard-fails `@apply bg-primary-900`. */ --color-primary-50: #faf6ea; --color-primary-100: #f3ead0; --color-primary-200: #e8d6a3; --color-primary-300: #d8bd73; --color-primary-400: #c6a24a; --color-primary-500: #ab8433; --color-primary-600: #8a6a2b; --color-primary-700: #6f5523; --color-primary-800: #59441e; --color-primary-900: #493819; --color-primary-950: #29200d; /* ── Accent: same champagne gold (remaps `purple-*` / `violet-*`) so hashtag * chips + badges share the one signal colour rather than a competing hue. */ --color-purple-50: #faf6ea; --color-purple-100: #f3ead0; --color-purple-200: #e8d6a3; --color-purple-300: #d8bd73; --color-purple-400: #c6a24a; --color-purple-500: #ab8433; --color-purple-600: #8a6a2b; --color-purple-700: #6f5523; --color-purple-800: #59441e; --color-purple-900: #493819; --color-violet-500: #ab8433; --color-violet-600: #8a6a2b; --color-accent-500: #ab8433; --color-accent-600: #8a6a2b; /* ── Neutrals: pearl → silver → graphite (remaps `gray-*`). Whisper-warm * pearl at the light end (research: "warm pearl, not stark white"), turning * neutral-cool through the mids/darks so structure reads as silver, not sand. */ --color-gray-50: #faf9f7; /* soft pearl — light page background */ --color-gray-100: #f2f1ef; /* light silver — muted surfaces */ --color-gray-200: #e5e4e1; /* silver borders / dividers */ --color-gray-300: #d1cfcc; --color-gray-400: #a6a4a1; /* silver — placeholder / disabled icon */ --color-gray-500: #767471; /* secondary text (AA on pearl) */ --color-gray-600: #545350; --color-gray-700: #3b3a38; /* graphite — dark-mode borders */ --color-gray-800: #262524; /* dark-mode cards */ --color-gray-900: #1a1918; /* ink (light) / dark surface */ --color-gray-950: #100f0f; /* darkest — dark-mode page background */ /* Surface aliases (kept from the original tokens for the export viewer). */ --color-surface-0: #ffffff; --color-surface-50: #faf9f7; --color-surface-100: #f2f1ef; --color-surface-200: #e5e4e1; /* ── Radii — softer, keepsake-y corners ───────────────────────────────── */ --radius-card: 1rem; /* rounded-card */ --radius-sheet: 1.5rem; /* rounded-sheet */ /* ── Elevation — soft, cool-neutral shadows (remaps Tailwind shadow ramp) ─ */ --shadow-2xs: 0 1px 2px 0 rgba(28, 28, 35, 0.05); --shadow-xs: 0 1px 3px 0 rgba(28, 28, 35, 0.06); --shadow-sm: 0 2px 6px -1px rgba(28, 28, 35, 0.07), 0 1px 2px -1px rgba(28, 28, 35, 0.05); --shadow-md: 0 6px 16px -4px rgba(28, 28, 35, 0.09), 0 2px 6px -2px rgba(28, 28, 35, 0.06); --shadow-lg: 0 12px 28px -6px rgba(28, 28, 35, 0.1), 0 4px 10px -4px rgba(28, 28, 35, 0.07); --shadow-xl: 0 24px 48px -12px rgba(28, 28, 35, 0.16), 0 8px 16px -8px rgba(28, 28, 35, 0.09); } /* Baseline body background + text colour so pages that haven't been re-themed yet * at least don't render light-on-light or dark-on-dark. Pages and cards still set * their own backgrounds via `bg-*` utilities, but this catches any gaps. */ @layer base { html { background-color: #faf9f7; /* soft pearl (bg-gray-50) */ color: #1a1918; /* ink (text-gray-900) */ color-scheme: light; font-family: var(--font-sans); -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } html.dark { background-color: #100f0f; /* graphite near-black (bg-gray-950) */ color: #f2f1ef; /* light silver (text-gray-100) */ color-scheme: dark; } /* Headings wear the display serif by default. Individual headings can still * opt out with `font-sans`; the brand wordmark opts in via `.font-display`. */ h1, h2, h3 { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.015em; } }