feat(ui): elegant wedding white/silver/gold redesign
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>
This commit is contained in:
@@ -5,6 +5,21 @@
|
||||
*
|
||||
* 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';
|
||||
@@ -15,32 +30,108 @@
|
||||
* `: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 {
|
||||
/* Brand palette — the blue used for primary buttons, FAB, active tabs. */
|
||||
--color-primary-50: #eff6ff;
|
||||
--color-primary-100: #dbeafe;
|
||||
--color-primary-500: #3b82f6;
|
||||
--color-primary-600: #2563eb;
|
||||
--color-primary-700: #1d4ed8;
|
||||
|
||||
/* Accent for hashtag chips and highlights. */
|
||||
--color-accent-500: #a855f7;
|
||||
--color-accent-600: #9333ea;
|
||||
|
||||
/* Surface scale matches the existing gray-* usage. Listed here so the viewer
|
||||
* picks up the same shade in case Tailwind defaults ever drift. */
|
||||
--color-surface-0: #ffffff;
|
||||
--color-surface-50: #f9fafb;
|
||||
--color-surface-100: #f3f4f6;
|
||||
--color-surface-200: #e5e7eb;
|
||||
|
||||
/* Typography. Keepsake should feel like the live app — same defaults. */
|
||||
--font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
/* ── 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;
|
||||
|
||||
/* Radii — keep cards and bottom sheets consistent. */
|
||||
--radius-card: 0.75rem; /* rounded-card */
|
||||
--radius-sheet: 1.25rem; /* rounded-sheet */
|
||||
/* ── 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
|
||||
@@ -48,13 +139,26 @@
|
||||
* their own backgrounds via `bg-*` utilities, but this catches any gaps. */
|
||||
@layer base {
|
||||
html {
|
||||
background-color: #f9fafb; /* matches bg-gray-50 */
|
||||
color: #111827; /* matches text-gray-900 */
|
||||
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: #030712; /* matches bg-gray-950 */
|
||||
color: #f3f4f6; /* matches text-gray-100 */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user