# WFDASH — Multi-theme system Ship 8 selectable themes (5 standard + 3 standout) with a warm/dark mode toggle. Theme selection persists per browser. No FOUC on reload. ## File map ``` src/ ├── lib/ │ ├── themes.ts # 8-theme registry (id, label, fonts, swatches) │ └── use-theme-id.ts # Client hook to read/write active theme ├── styles/ │ └── themes.css # OKLCH token blocks for all 8 themes (warm + dark) ├── components/ │ ├── theme-provider.tsx # next-themes wrapper (manages warm/dark mode) │ ├── theme-bootstrap.tsx # Inline script — prevents FOUC │ ├── theme-picker.tsx # Settings UI — mini preview cards │ └── sidebar.tsx # +Settings nav item └── app/ ├── layout.tsx # +Inter, Space Grotesk, DM Sans, Playfair, │ # Lato, Nunito, Outfit, JetBrains Mono via next/font ├── globals.css # Imports themes.css └── (app)/settings/page.tsx # New Settings page with Appearance section ``` ## The 8 themes | # | ID | Category | Fonts | Radius | Notes | |---|---|---|---|---|---| | 1 | `crisp` | Standard | Inter | 10px | Default. Calm, professional. | | 2 | `midnight` | Standard | Space Grotesk + DM Sans | 12px | Dark control-room. | | 3 | `editorial` | Standard | Playfair Display + Lato | 6px | Warm cream + serif. | | 4 | `bento` | Standard | Inter | 18px | Tinted tiles, big radius. | | 5 | `field-ops` | Standard | Inter | 8px | Dense, navy sidebar, gold CTA. | | 6 | `terminal` | **Standout** | JetBrains Mono | 0px | Green CRT, scanlines, mono. | | 7 | `blueprint` | **Standout** | JetBrains Mono + Inter | 2px | White-on-blue, grid, technical. | | 8 | `confetti` | **Standout** | Outfit + Nunito | 20px | Pastel, playful, oversized radius. | ## How it works **Theme identity** = `data-theme=""` on `` - Set by `theme-bootstrap.tsx` before hydration (prevents flash) - Updated by `useThemeId()` hook when user picks a new theme - Persisted to `localStorage["wfdash-theme"]` **Mode (warm/dark)** = `.warm` or `.dark` class on `` - Managed by `next-themes` (already wired in `theme-provider.tsx`) - Persisted to `localStorage["wfdash-mode"]` - Independent from theme — you can mix any theme with any mode **shadcn/ui adapts automatically** because all components read from CSS vars (`--background`, `--primary`, `--accent`, `--radius`, etc.) that themes.css overrides per theme. ## How to add a new theme 1. Add an entry to `THEMES` in `src/lib/themes.ts` 2. Add a `[data-theme=""]` block in `src/styles/themes.css` (copy an existing theme and adjust tokens). Add `.dark` variant if it works in dark mode. 3. Add `radiusMap[]` in `theme-bootstrap.tsx` 4. Done — picker picks it up automatically ## How to ship a font swap Each theme has `fontHeading` + `fontBody` pointing at a CSS var. Add the font to `src/app/layout.tsx` via `next/font/google` and register its `.variable`. Then add the binding in `src/styles/themes.css`: ```css [data-theme=""] { --font-heading: var(--font-); --font-body: var(--font-); } ``` The body picks up `var(--font-body)` automatically; `font-heading-theme` class applies heading font where you need it. ## Standout theme caveats The 3 standout themes override default component styling: - **Terminal** — sharp 0px radius, uppercase headings, mono everywhere, scanline body overlay - **Blueprint** — sharp 2px radius, uppercase headings, grid overlay, transparent inputs - **Confetti** — 20px radius (capped — shadcn's `--radius-4xl` calc clamps at ~1.6rem) All three are still built on the brand palette (#4C96D7 / #F2AF58 / #E94E49) but applied in different ways. They're tested against the standard shadcn components but may render slightly differently from the 5 standard themes — by design.