twenty tokens is the contract; eleven palettes prove it

Officer now ships fourteen themes. Eleven of them were written by other people, ported
unchanged, and none of them knows that panels, docks, glass, grids or scrollbars exist.

── What changed ──

`base.css` derives every surface from the standard tokens: a panel is `card` at 92%, the
header is `card` at 85%, grid lines are `border` faded, scrollbars are `muted-foreground`.
Those defaults sit on `:root`; a theme sits on `[data-theme='x']` and both land on
<html>, so a theme's own declaration simply outranks them. No machinery.

State tones are defaulted too — shadcn's vocabulary has `destructive` and nothing else,
and green-means-good is a convention rather than a palette decision. That is what drops
the required contract from 29 to 20: exactly the tokens every published shadcn palette
already ships.

So there are now three tiers, and an author picks one:

  palette   20 tokens                  Tokyo Night, Gruvbox, Nord — surfaces derived
  textured  + a few surfaces           glass, blur, radius, a wallpaper of its own
  white-label + assets                 its own logo and background

Duck is tier three and is untouched — verified by probe: it still reports
`panel=#ffffff1f`, `blur=24px` and its own grid, because its declarations outrank the
new defaults.

── Verified before committing, not assumed ──

The whole HSL-triplet design existed to make `bg-card/50` work. Tested it: Tailwind v4
emits `color-mix(in oklab, var(--card) 50%, transparent)` for a HEX token, with a
`@supports` fallback. The unwrapped triplets were never necessary, and the ported
palettes are plain hex.

Six themes probed in the browser: each derives panels from its own card colour,
including the light one (Catppuccin Latte).

── Credit ──

The eleven palettes and the idea of a small standard contract come from the owner's
`vimazing` repository, which solved this more simply than we had. What Officer adds is
the surface layer underneath, so a palette gets a textured app for free.

── One open decision ──

`--logo-src` defaults to empty, so a palette theme shows no mark at all. Correct for a
third-party theme; probably wrong for Officer, where the alternative is defaulting to
the officer.dev logo and letting a white-label theme replace it. One line either way.

tsgo clean, suite unchanged, builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 00:49:13 +00:00
co-authored by Claude Opus 5
parent 1dfadef506
commit b106f67288
14 changed files with 645 additions and 0 deletions
@@ -0,0 +1,45 @@
/*
MATTE BLACK — ported from the published palette, unchanged.
A PALETTE theme: it supplies the standard tokens and nothing else. Panels, the header, the dock, glass,
grid lines and scrollbars are all derived from these by `base.css`, so this file never mentions them.
That is the contract working as intended — a theme anyone has already written, dropped in whole. Compare
`duck-*.css`, which additionally declares a texture.
*/
[data-theme='matte-black'] {
--radius: 0.625rem;
--background: #0a0a0a;
--foreground: #e0e0e0;
--card: #121212;
--card-foreground: #e0e0e0;
--popover: #121212;
--popover-foreground: #e0e0e0;
--primary: #ffffff;
--primary-foreground: #0a0a0a;
--secondary: #2a2a2a;
--secondary-foreground: #e0e0e0;
--muted: #1a1a1a;
--muted-foreground: #9a9a9a;
--accent: #d0d0d0;
--accent-foreground: #0a0a0a;
--destructive: #ff4444;
--destructive-foreground: #e0e0e0;
--border: #2a2a2a;
--input: #2a2a2a;
--ring: #ffffff;
--chart-1: #ffffff;
--chart-2: #cccccc;
--chart-3: #999999;
--chart-4: #666666;
--chart-5: #444444;
--sidebar: #121212;
--sidebar-foreground: #9a9a9a;
--sidebar-primary: #ffffff;
--sidebar-primary-foreground: #0a0a0a;
--sidebar-accent: #1a1a1a;
--sidebar-accent-foreground: #e0e0e0;
--sidebar-border: #2a2a2a;
--sidebar-ring: #ffffff;
}