extract the visual layer into its own workspace
The application no longer knows what it looks like. It imports one stylesheet and
mounts one provider; every colour, surface and radius lives in `src/workspaces/theme`.
Proven both directions, because either alone would be worthless:
Duck renders IDENTICALLY to before — checked against screenshots taken before
anything was touched, not asserted.
Plain renders as white with black ink, no component edits — panels turn white, the
teal avatar turns black, the grid fills vanish.
── What moved ──
`globals.css` held three unrelated things: Tailwind wiring, the token contract, and
the Duck brand's hex values. Split along those seams. `base.css` is structure and
names no colour; a theme file is values and nothing else, scoped to
`[data-theme='<id>']`. Adding a theme is a CSS file plus one registry line.
Light and dark are separate themes rather than a mode — the owner's call, and it
removes a concept from the app. Each is written out in full: a theme that only makes
sense as a diff against another cannot be authored by a stranger or sold to a
customer. `.dark` survives only as a per-theme flag, because 396 `dark:` utilities in
33 components resolve against it; retiring those retires the flag.
The `duck-*` utility names are kept for now and pointed at neutral variables, so a
non-Duck theme can define them sensibly. Renaming 1,470 occurrences in the same commit
as the extraction would make any visual difference impossible to attribute. Worth
recording that `duck-dark` was never a brand colour — `#14532d` in light, near-white
in dark, used by 946 components to mean "text". It is `--ink` now.
Surfaces are complete CSS values rather than HSL triplets: `--panel-bg` has to be able
to be a gradient or an image, or the glass the app already has would be unthemeable.
The old provider is a re-export, so its six call sites are untouched and the
extraction stays one variable.
── Bug found by the Plain theme ──
`useThemeSync` stamped the server's `appearance.colorMode` over the local theme on
every load, so choosing Plain and reloading silently reverted it. Diagnosed by asking
the page what it was wearing rather than reasoning: `stored` said `duck-dark` when the
script had written `plain`. The server value is now a starting point for a browser
that has never chosen, not a continuous instruction.
── Tools ──
Two, both of which lied to me before they worked. The snapshot script photographed
light twice and called it a clean run (one browser context, accumulated init scripts);
it now warns when a light/dark pair is identical. The preview server returned the
shell for missing assets, so the logo and wallpaper vanished and read as regressions
in the comparison — it falls through to upstream now.
Dead Tailwind v3 residue deleted: `components/ui/tailwind.{config.ts,css}`, referenced
by nothing, with content globs pointing at a directory that does not exist.
── Not done, deliberately ──
`THEMES-NOTES.md` has the full list. The headlines: the reference repo has not been
read, so the switching mechanism is deliberately the dumbest thing that works; the
wallpaper, panel radius and logo are still component-owned and Plain proves it; and
~985 raw Tailwind palette usages remain, worst in Soulseek.
tsgo clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
DUCK — DARK
|
||||
============================================================================================
|
||||
|
||||
A sibling of Duck Light, not a variant of it. The owner's decision: light and dark are two themes, which
|
||||
removes "colour mode" from the application as a concept.
|
||||
|
||||
It is written out in full rather than as a diff against light. Overriding would be shorter and would
|
||||
reintroduce exactly the coupling this refactor removes — a theme that only makes sense in the presence of
|
||||
another is not something a third party can author or a customer can buy. A complete file is also the only
|
||||
way an author can see what they have to supply.
|
||||
|
||||
`applyDarkClass` is set for this theme in the registry, so the 396 `dark:` utilities still written into
|
||||
components keep resolving. That is a compatibility shim with a known end date, not part of the design.
|
||||
|
||||
Values are the `.dark` block of the original `globals.css`, unchanged. Where light defined something that
|
||||
the original dark block did not override (`--duck-yellow`, `--duck-orange`), the light value is repeated
|
||||
here — that is what the browser was computing before, made explicit.
|
||||
*/
|
||||
|
||||
[data-theme='duck-dark'] {
|
||||
/* ── Brand — lightened for contrast on a dark ground ──────────────────────────────────────────
|
||||
`--ink` flipping to near-white is the clearest evidence that `duck-dark` was never a brand colour. */
|
||||
--brand-primary: #22d3ee;
|
||||
--brand-accent: #f4c430;
|
||||
--brand-warm: #ea580c;
|
||||
--brand-deep: #4ade80;
|
||||
--ink: #f1f5f9;
|
||||
--surface-warm: #292524;
|
||||
|
||||
/* ── The page ─────────────────────────────────────────────────────────────────────────────────── */
|
||||
--page-bg: hsl(222.2 84% 4.9%);
|
||||
--pixel-grid-color: rgba(200, 230, 210, 0.08);
|
||||
|
||||
/* ── Surfaces ─────────────────────────────────────────────────────────────────────────────────── */
|
||||
--glass-bg: rgba(0, 0, 0, 0.2);
|
||||
--glass-border: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--panel-bg: rgba(0, 0, 0, 0.2);
|
||||
--panel-border: rgba(255, 255, 255, 0.1);
|
||||
--panel-radius: 0.75rem;
|
||||
--panel-header-bg: rgba(0, 0, 0, 0.15);
|
||||
--panel-header-fg: hsl(var(--foreground));
|
||||
--panel-accent: var(--brand-primary);
|
||||
--panel-grid-color: rgba(200, 230, 210, 0.06);
|
||||
|
||||
--dock-bg: rgba(0, 0, 0, 0.3);
|
||||
--dock-border: rgba(255, 255, 255, 0.08);
|
||||
--dock-tooltip-bg: rgba(0, 0, 0, 0.85);
|
||||
|
||||
--card-bg: rgba(10, 20, 15, 0.9);
|
||||
--card-grid-color: rgba(200, 230, 210, 0.06);
|
||||
|
||||
/* ── Semantic colour ──────────────────────────────────────────────────────────────────────────── */
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 211 82% 64%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--success: 142.1 76.2% 36.3%;
|
||||
--success-foreground: 355.7 100% 97.3%;
|
||||
|
||||
--brand: 145 60% 45%;
|
||||
--brand-foreground: 0 0% 100%;
|
||||
--brand-muted: 145 30% 25%;
|
||||
|
||||
--warning: 43 96% 56%;
|
||||
--warning-foreground: 0 0% 0%;
|
||||
|
||||
/* Lifted well above the light value — 50% lightness violet on a near-black background reads as muddy
|
||||
grey at 12px, which is exactly the size it is used at. */
|
||||
--info: 263 90% 70%;
|
||||
--info-foreground: 0 0% 100%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
|
||||
/* The original dark block never overrode these, so the light values were in force. Written out because
|
||||
a theme that silently inherits from another is the thing this file exists to stop. */
|
||||
--cta: 211 74.77% 45.58%;
|
||||
--cta-foreground: 0 0% 100%;
|
||||
--ctahover: 211 91.53% 34.82%;
|
||||
|
||||
--naturegreen: 145 79% 38%;
|
||||
--naturegreen-foreground: 0 0% 100%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* ── Data visualisation and status ────────────────────────────────────────────────────────────── */
|
||||
--chart-primary: 211 82% 64%;
|
||||
--chart-secondary: 211 82% 84%;
|
||||
--status-success: 142 76% 36%;
|
||||
--status-warning: 43 96% 56%;
|
||||
--status-in-progress: 211 82% 64%;
|
||||
|
||||
/* ── Chrome ───────────────────────────────────────────────────────────────────────────────────── */
|
||||
--scrollbar-thumb: #444;
|
||||
--scrollbar-thumb-hover: #555;
|
||||
--scrollbar-thin-thumb: rgb(128 128 128 / 0.3);
|
||||
--scrollbar-thin-thumb-hover: rgb(128 128 128 / 0.5);
|
||||
}
|
||||
Reference in New Issue
Block a user