Reskin the whole app via design tokens in tailwind-theme.css (remapped color ramps) plus a define-once component layer in lib/styles/components.css (.btn, .card, .input, .chip, .badge, .sheet, …). Buttons are muted/outlined gold rather than flat fills. Self-host Inter + Fraunces (woff2) under the existing font-src 'self' CSP. Restyle the shared components and the account, admin, host, join, recover and upload screens against the new tokens. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
165 lines
7.2 KiB
CSS
165 lines
7.2 KiB
CSS
/* 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 `<html>` 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;
|
|
}
|
|
}
|