/* THE STRUCTURE OF THE LOOK — never its values. ============================================================================================ This file says WHAT can be themed. A theme file says what it looks like. Nothing here names a colour, and nothing here belongs to any particular theme: swapping every theme in the repository must not require editing this file, and adding a variable here is a change to the contract that every theme has to answer. The split exists because it is the whole feature. Before it, `globals.css` mixed three things — Tailwind wiring, the token contract, and the Duck brand's hex values — so "change how the app looks" and "change how theming works" were the same edit, and neither could be done by anyone who did not own the app. ── How a value reaches a component ── component .bg-surface @theme (here) --color-surface: var(--surface) theme file --surface: 0 0% 100% The indirection through `var()` is load-bearing: Tailwind resolves `@theme` at BUILD time, so a theme that set `--color-surface` directly could never be swapped without a rebuild. Resolving to a custom property means the browser reads it at paint, and changing `data-theme` on re-paints the app. ── Two spellings, and why ── Colours are HSL TRIPLETS without the function (`0 0% 100%`), so `hsl(var(--x) / 0.5)` gives opacity for free — that is what makes `bg-surface/50` work, and it is why every shadcn-derived token in this file looks like that. Surfaces (panel, dock, card, page) are COMPLETE CSS VALUES instead, because a theme needs to set things a triplet cannot express: `rgba()`, a gradient, an image, `none`. A panel background that could only ever be a flat colour would rule out the glass look the app has today. */ @import 'tailwindcss'; @plugin 'tailwindcss-animate'; /* Dark is still a CLASS, even though light and dark are now separate themes. Two themes was the simplification the owner chose, and it is the right one — but 396 `dark:` utilities are written into 33 components, and they all resolve against this variant. A theme that wants them active declares `applyDarkClass` in its manifest and the provider adds the class; a theme that does not simply never sees them. So the concept is gone from the app while the utilities keep working, and removing those 396 becomes a later cleanup rather than a prerequisite. */ @custom-variant dark (&:is(.dark *)); @utility container { margin-inline: auto; padding-inline: 2rem; @media (width >= --theme(--breakpoint-sm)) { max-width: none; } @media (width >= 1400px) { max-width: 1400px; } } /* Values are HEX, not unwrapped HSL triplets. ============================================================================================ This block used to read `--color-card: hsl(var(--card))`, which requires every theme to publish an unwrapped triplet (`0 0% 100%`). That was done so `bg-card/50` could get opacity — and it turned out to be unnecessary: Tailwind v4 emits `color-mix(in oklab, var(--card) 50%, transparent)` for any colour format, with a `@supports` fallback. Tested before changing it. It was also actively wrong the moment a theme arrived in hex: `hsl(#6e6a86)` is invalid, so every semantic utility silently fell back to white or transparent. Eleven ported palettes looked correct only because panels and the page read their variables directly rather than through a utility class. */ @theme { /* ── Semantic colour, the whole vocabulary a component is allowed to use ──────────────────────── */ --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); --color-background: var(--background); --color-foreground: var(--foreground); --color-brand: var(--brand); --color-brand-foreground: var(--brand-foreground); --color-brand-muted: var(--brand-muted); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-info: var(--info); --color-info-foreground: var(--info-foreground); /* The sixth state, and the reason it exists: `running` is neither good news nor a warning. The chat tray, the send button and the queue all wore amber for it, and the token set had nothing to offer — `--status-in-progress` is blue, and `warning` would make a working job look like a failing one. `-ink` is the readable-on-a-tint variant: the fill is bright enough to sit behind black text, which makes it far too light to BE text. Every tone will want this eventually; busy needed it first. */ --color-busy: var(--busy); --color-busy-foreground: var(--busy-foreground); --color-busy-ink: var(--busy-ink); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-cta: var(--cta); --color-cta-foreground: var(--cta-foreground); --color-ctahover: var(--ctahover); --color-naturegreen: var(--naturegreen); --color-naturegreen-foreground: var(--naturegreen-foreground); /* ── The brand slots ──────────────────────────────────────────────────────────────────────────── These are the six utilities that were `duck-*`, kept under their old names for now so ~1,470 existing usages keep compiling, but pointed at variables a theme owns rather than at brand hex. They are DEPRECATED as spellings. `duck-dark` is the clearest case of why: it is `#14532d` in light and near-white in dark, so it was never a brand green — it is ink, and 946 components are using it to mean "text colour". A theme called anything other than Duck cannot sensibly define `--duck-dark`. The rename to semantic names is the largest mechanical piece of this refactor and is deliberately not done in the same change as the extraction: one of them can be verified by screenshot, and doing both at once would make a visual difference impossible to attribute. */ --color-duck-yellow: var(--brand-accent); --color-duck-orange: var(--brand-warm); --color-duck-teal: var(--brand-primary); --color-duck-forest: var(--brand-deep); --color-duck-dark: var(--ink); --color-duck-beige: var(--surface-warm); --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); --animate-accordion-down: accordion-down 0.2s ease-out forwards; --animate-accordion-up: accordion-up 0.2s ease-out forwards; @keyframes accordion-down { from { height: 0; } to { height: var(--radix-accordion-content-height); } } @keyframes accordion-up { from { height: var(--radix-accordion-content-height); } to { height: 0; } } } /* Tailwind v4 changed the default border colour to `currentcolor`. Kept so an element with a border and no explicit colour looks as it did under v3 — removing it means auditing every bordered element in the app. */ @layer base { *, ::after, ::before, ::backdrop, ::file-selector-button { border-color: var(--color-gray-200, currentcolor); } * { @apply border-border; } /* The page itself is a themed surface, not just a colour. `--page-bg` is a complete `background` value so a theme can be a photograph, a gradient, or nothing at all. Duck is a wallpaper; Plain is white. Both are the same line here. */ body { @apply text-foreground; background: var(--page-bg); background-attachment: fixed; background-size: cover; } /* NATIVE CONTROLS FOLLOW THE THEME. ------------------------------------------------------------------------------------------ Nothing here set `color-scheme`, so the browser drew every control it renders itself as if the page were light — on a dark theme, a `