All pages

Foundations

Colors

Curation's palette divides along one line. Surfaces, hairlines and hover plates are a single base colour plus an opacity modifier, so they invert cleanly between themes. Text names a rung of a fixed ladder, because alpha does not survive the flip where legibility is the point. Every swatch below is painted with the live token — use the theme toggle above to read both palettes.

Surfaces

The canvas is a mid grey, not an off-white — one hue (213) at 16% saturation across every step, which is why the greys cohere. Containers rise above it: a card is lighter than the page in light mode. Dark keeps the legacy app's warm near-black unchanged, so the two themes are deliberately in different hue families.

TokenLightDarkRole
--canvas#e2e5ea#110f0dThe page ground.
--canvas-raised#f1f3f5#131a16Cards and containers, a step above the page.
--canvas-deep#d6dbe1#0d110fRecessed wells and controls.

The ink ladder

--ink is deliberately not white or black: in light mode text-white would have to mean near-black. Ink names the role, and reads correctly in both themes. Text takes a rung, not an alpha — black at 40% on light reaches 2.4:1 where white at 40% on dark reaches 3.8:1, so a shared alpha cannot hold a legibility contract. The dark column lists the old alpha equivalents. Those colours are now mixed with the canvas into opaque paint, preserving their tone on the page while keeping inherited SVG strokes uniform where paths overlap.

TokenLightDarkContrast (light / dark)
--ink#15191e#ffffff13.98 / 19.13 — primary text.
--ink-strong#22282fwhite / 85%11.77 / 13.82 — emphatic secondary text.
--ink-secondary#3e4956white / 65%7.25 / 8.34 — body and supporting copy.
--ink-tertiary#4f5d6dwhite / 40%5.33 / 3.82 — inactive-but-clickable labels; held above AA on light on purpose.
--ink-quiet#8693a3white / 28%2.48 / 2.46 — captions and incidental marks.
--ink-inverse#f8f9fa#121212Text on a plate of solid ink.

The ink ladder, top rung

Body copy sits one rung down

Inactive but clickable

A quiet caption

<p className="text-ink">Primary</p>
<p className="text-ink-secondary">Secondary</p>
<p className="text-ink-tertiary">Tertiary</p>
<p className="text-ink-quiet">Quiet</p>

Tints and hairlines

Everything that is not text stays on the alpha model: a white tint raising a surface off a dark ground becomes a black tint recessing it into a light one. The working strengths are legacy's own — bg-ink/[0.03] for a resting plate, bg-ink/5 for a hover, border-ink/10 for a hairline. Dark's hairlines are finer (--border drops to 5%), because a white tint on near-black reads heavier than a black tint on grey at the same alpha.

ink / 3%

ink / 6%

border

<div className="rounded-lg bg-ink/[0.03]" />   // resting plate
<div className="rounded-lg bg-ink/[0.06]" />   // hover / highlighted
<div className="rounded-lg border-[0.09rem] border-ink/10" />

Brand

The brand is black, as the mobile app's is — black primary actions and neutral emphasis, inverting to white in dark mode. It is named brand, not accent, because shadcn reserves accent for its subtle hover surface. It has three jobs: a decoration tone for tints, a foreground (mobile's accent) and a plate (mobile's inverse). The plate sits a step under the ink on light, so a button and the type beside it need not be one black.

TokenLightDarkJob
--brand#15191e#ffffffDecoration and tints — the ink itself.
--brand-ink#15191e#ffffffThe brand as a foreground — links, active marks, the focus ring.
--brand-fill#22282f#ffffffThe brand as a solid plate. Two names because a plate and a letterform need not stay in step.
--brand-foreground#f8f9fa#121212The on-colour of the plate, so it flips with it.
--brand-fill-sheen#22282f#ffffffThe lighter stop of the marketing CTA gradient — the plate itself, so the CTA draws flat.

Publish

A brand-coloured link

A brand tint

<button className="bg-brand-fill text-brand-foreground">Publish</button>
<a className="text-brand-ink">A brand-coloured link</a>
<span className="bg-brand/20 text-brand-ink">A brand tint</span>

Two further display tones — --ink-warm and --brand-soft — belong to the marketing home alone: a quieter ink for the hero headline and a gentler brand for the manifesto, each quieter than its base in either theme.

Status

The reds differ by theme for the same reason the greys do: legacy's #f44e4e is tuned against near-black and reaches only 3.3:1 on a light ground, too thin for the error copy it carries. The warning tone is stated at reading strength rather than as an amber to be faded — it is a text colour.

TokenLightDarkRole
--danger#be2f27#f44e4eErrors and destructive actions.
--success#1f9d55#27ae60Confirmations.
--warning#7a5008#a29357Caution notes — “couldn't verify”, “upload failed”.
--vip#8a7649#ae9c75Membership marks.

The semantic layer

shadcn/ui components are written against a fixed set of semantic names. Those names are mapped onto Curation's colours in app/globals.css, so an unmodified shadcn component already belongs. The tints are derived from --ink rather than restated per theme — one definition that follows whichever palette is live.

Semantic tokenResolves to
--background / --foreground--canvas / --ink
--card--canvas-raised
--popover / --panelA step off the canvas, not a tint of it — these float above their own scrim, and a translucent tint would show the page through.
--primary--brand-fill, with --brand-foreground on it.
--secondary / --accentink / 5% — shadcn's accent is the subtle hover surface, not the brand.
--mutedink / 3%, with --ink-tertiary as its foreground.
--destructive--danger
--border / --inputink / 10% on light, ink / 5% on dark.
--ring--brand-ink