diff --git a/docs/superpowers/specs/2026-05-03-m7-362-theme-toggle-design.md b/docs/superpowers/specs/2026-05-03-m7-362-theme-toggle-design.md index b92f00f2..2d312af6 100644 --- a/docs/superpowers/specs/2026-05-03-m7-362-theme-toggle-design.md +++ b/docs/superpowers/specs/2026-05-03-m7-362-theme-toggle-design.md @@ -61,19 +61,19 @@ Current shape (flat): } ``` -New shape: +New shape (dark values are the current `tokens.json` values, preserved): ```json { "colors": { "dark": { - "obsidian": "#0E1013", - "iron": "#14171A", - "slate": "#1C1F23", - "pewter": "#2C313A", - "parchment": "#E8E2D2", - "vellum": "#C9C2AE", - "ash": "#8A8474" + "obsidian": "#14171A", + "iron": "#1E2228", + "slate": "#2C313A", + "pewter": "#3F4651", + "parchment": "#E8E4D8", + "vellum": "#C2BFB4", + "ash": "#9C9A92" }, "light": { "obsidian": "#F8F5EE", @@ -85,14 +85,14 @@ New shape: "ash": "#5C6068" }, "flat": { - "moss": "#5A7563", - "bronze": "#8C6A3F", - "oxblood": "#7A2E2E", - "warning": "#C99A4A", - "error": "#A04848", - "info": "#5A7A8A", + "moss": "#4A5D3F", + "bronze": "#8B7355", + "oxblood": "#6B2118", + "warning": "#8B6F1E", + "error": "#C04A1F", + "info": "#3D5A6E", "accent": "#4A6B5C", - "on-action": "#E8E2D2" + "on-action": "#E8E4D8" } } } @@ -110,9 +110,20 @@ New shape: ### `--fs-on-action` token -A new flat token, value `#E8E2D2` (frozen at the dark-mode parchment value). Used as the label color on action buttons whose backgrounds (`--fs-moss`, `--fs-bronze`, `--fs-oxblood`) do not flip. Without this, action button text would invert in light mode and lose contrast against the mid-tone earth backgrounds. +A new flat token, value `#E8E4D8` (frozen at the dark-mode parchment value). Used as the label color on action buttons whose backgrounds (`--fs-moss`, `--fs-bronze`, `--fs-oxblood`) do not flip. Without this, action button text would invert in light mode and lose contrast against the mid-tone earth backgrounds. -Tailwind utility: `text-fs-on-action`. +Tailwind exposes it as `text-action-fg` via a new entry in `tailwind.config.js`: + +```js +action: { + primary: 'var(--fs-moss)', + secondary: 'var(--fs-bronze)', + destructive: 'var(--fs-oxblood)', + fg: 'var(--fs-on-action)' // new +} +``` + +The audit step replaces `text-text-primary` with `text-action-fg` on every site where it pairs with `bg-action-primary` / `bg-action-secondary` / `bg-action-destructive`. ### Theme store @@ -176,21 +187,21 @@ Full token map with values: | Token | Dark (current) | Light (new) | Role | |---|---|---|---| -| `--fs-obsidian` | `#0E1013` | `#F8F5EE` | page bg | -| `--fs-iron` | `#14171A` | `#ECE6D5` | card/surface bg | -| `--fs-slate` | `#1C1F23` | `#DCD3BD` | hover surface | -| `--fs-pewter` | `#2C313A` | `#B8AE94` | borders | -| `--fs-parchment` | `#E8E2D2` | `#14171A` | primary text | -| `--fs-vellum` | `#C9C2AE` | `#2C313A` | secondary text | -| `--fs-ash` | `#8A8474` | `#5C6068` | muted text | -| `--fs-moss` | `#5A7563` | *(unchanged)* | secondary action bg | -| `--fs-bronze` | `#8C6A3F` | *(unchanged)* | warning action bg | -| `--fs-oxblood` | `#7A2E2E` | *(unchanged)* | destructive action bg | -| `--fs-warning` | `#C99A4A` | *(unchanged)* | semantic marker | -| `--fs-error` | `#A04848` | *(unchanged)* | semantic marker | -| `--fs-info` | `#5A7A8A` | *(unchanged)* | semantic marker | +| `--fs-obsidian` | `#14171A` | `#F8F5EE` | page bg | +| `--fs-iron` | `#1E2228` | `#ECE6D5` | card/surface bg | +| `--fs-slate` | `#2C313A` | `#DCD3BD` | hover surface | +| `--fs-pewter` | `#3F4651` | `#B8AE94` | borders | +| `--fs-parchment` | `#E8E4D8` | `#14171A` | primary text | +| `--fs-vellum` | `#C2BFB4` | `#2C313A` | secondary text | +| `--fs-ash` | `#9C9A92` | `#5C6068` | muted text | +| `--fs-moss` | `#4A5D3F` | *(unchanged)* | secondary action bg | +| `--fs-bronze` | `#8B7355` | *(unchanged)* | warning action bg | +| `--fs-oxblood` | `#6B2118` | *(unchanged)* | destructive action bg | +| `--fs-warning` | `#8B6F1E` | *(unchanged)* | semantic marker | +| `--fs-error` | `#C04A1F` | *(unchanged)* | semantic marker | +| `--fs-info` | `#3D5A6E` | *(unchanged)* | semantic marker | | `--fs-accent` | `#4A6B5C` | *(unchanged)* | brand accent | -| `--fs-on-action` | `#E8E2D2` | *(unchanged)* | action button label | +| `--fs-on-action` | `#E8E4D8` *(new)* | *(unchanged)* | action button label | Design rationale: light palette is "parchment / aged paper" — warm cream surfaces, dark text. Reads as an old book opened on a daylit desk; aligns with the FabledSword mythic register and preserves the bookish tone in light mode. Action and semantic colors are mid-tone earth/jewel hues that read on both light and dark backgrounds. @@ -200,17 +211,18 @@ Design rationale: light palette is "parchment / aged paper" — warm cream surfa 1. `web/src/lib/styles/tokens.json` — restructure `colors` into `dark`/`light`/`flat` sub-maps; add `on-action` to `flat`. 2. `web/scripts/tokens-to-css.js` — emit dual `:root` + `[data-theme="light"]` blocks for colors; preserve other token sections in `:root`. -3. `web/src/app.html` — add inline FOUC script in ``. -4. `web/src/routes/settings/+page.svelte` — add Appearance card with 3-way theme selector. -5. Action button label class audit — find every site using `text-fs-parchment` on a `bg-fs-moss` / `bg-fs-bronze` / `bg-fs-oxblood` button and replace with `text-fs-on-action`. Expected count: 5–10 sites. +3. `web/tailwind.config.js` — add `colors.action.fg = 'var(--fs-on-action)'` entry. +4. `web/src/app.html` — add inline FOUC script in ``; remove the static `class="dark"` on `` (theme is now driven by `data-theme`). +5. `web/src/routes/settings/+page.svelte` — add Appearance card with 3-way theme selector. +6. Action button label class audit — find every site using `text-text-primary` paired with `bg-action-primary` / `bg-action-secondary` / `bg-action-destructive` and replace `text-text-primary` with `text-action-fg`. Verified count via grep: ~25 sites. **Create:** -6. `web/src/lib/stores/theme.svelte.ts` — theme preference + resolved-theme rune store with matchMedia listener and localStorage persistence. +7. `web/src/lib/stores/theme.svelte.ts` — theme preference + resolved-theme rune store with matchMedia listener and localStorage persistence. **Generated (do not hand-edit):** -7. `web/src/lib/styles/tokens.generated.css` — regenerated by `npm run tokens` (or whatever script invokes `tokens-to-css.js`). +8. `web/src/lib/styles/tokens.generated.css` — regenerated by `npm run tokens`. ## Edge cases