# Foundations — danchilton.com design system Colour tokens, type scale, radius and spacing. Generated from source at build time, so it cannot drift from the code it describes. Rendered board: https://danchilton.com/styleguide/ ## Basis - Design system: **shadcn-svelte v1.5.0**, base colour `zinc` - Radius: `--radius: 0.5rem` (`sm` = r−4px, `md` = r−2px, `lg` = r, `xl` = r+4px, `2xl` = r+8px) - Body face: **Figtree**, fallback `ui-sans-serif, system-ui, sans-serif` - Tailwind **v4**, CSS-first config. Token source of truth: `src/shared/styles/theme.css` - Dark mode is live, class-strategy (`.dark` on the root element) - Spacing is Tailwind's 4px base. In practice: `gap-2`/`gap-3` inside rows, `gap-4` between cards, `px-4 py-2.5` for tappable rows, `min-h-9`/`min-h-11` for touch targets. **Rule:** always use semantic tokens (`bg-background`, `bg-card`, `text-muted-foreground`, `border-border`, `bg-primary`). Never raw ramps like `bg-gray-100` or `bg-blue-600`. The repo currently contains zero raw ramps; keeping it that way is the point. ## Colour tokens Every value is `oklch()`. Both themes are listed; consumers reference the semantic name, never the literal. ### Surfaces | Token | Light | Dark | | --- | --- | --- | | `--background` | `oklch(1 0 0)` | `oklch(0.141 0.005 285.823)` | | `--foreground` | `oklch(0.141 0.005 285.823)` | `oklch(0.985 0 0)` | | `--card` | `oklch(1 0 0)` | `oklch(0.21 0.006 285.885)` | | `--card-foreground` | `oklch(0.141 0.005 285.823)` | `oklch(0.985 0 0)` | | `--popover` | `oklch(1 0 0)` | `oklch(0.21 0.006 285.885)` | | `--popover-foreground` | `oklch(0.141 0.005 285.823)` | `oklch(0.985 0 0)` | | `--muted` | `oklch(0.967 0.001 286.375)` | `oklch(0.274 0.006 286.033)` | | `--muted-foreground` | `oklch(0.552 0.016 285.938)` | `oklch(0.705 0.015 286.067)` | | `--accent` | `oklch(0.967 0.001 286.375)` | `oklch(0.274 0.006 286.033)` | | `--accent-foreground` | `oklch(0.21 0.006 285.885)` | `oklch(0.985 0 0)` | | `--sidebar` | `oklch(0.985 0 0)` | `oklch(0.21 0.006 285.885)` | | `--sidebar-foreground` | `oklch(0.141 0.005 285.823)` | `oklch(0.985 0 0)` | | `--sidebar-accent` | `oklch(0.967 0.001 286.375)` | `oklch(0.274 0.006 286.033)` | | `--sidebar-accent-foreground` | `oklch(0.21 0.006 285.885)` | `oklch(0.985 0 0)` | ### Actions | Token | Light | Dark | | --- | --- | --- | | `--primary` | `oklch(0.21 0.006 285.885)` | `oklch(0.92 0.004 286.32)` | | `--primary-foreground` | `oklch(0.985 0 0)` | `oklch(0.21 0.006 285.885)` | | `--secondary` | `oklch(0.967 0.001 286.375)` | `oklch(0.274 0.006 286.033)` | | `--secondary-foreground` | `oklch(0.21 0.006 285.885)` | `oklch(0.985 0 0)` | | `--destructive` | `oklch(0.577 0.245 27.325)` | `oklch(0.704 0.191 22.216)` | ### Status Project additions on top of stock shadcn. Green = a goal met or a set completed, indigo = AI output, amber = advisory, slate = pending (not started, paused, set aside, below target). These carry information, so they are tokens rather than flattened to muted. Badge has matching success/warning/info/pending variants — use those, never a raw green-*/amber-* ramp. USE EACH TOKEN WITH ITS PAIRED FOREGROUND: only success (3.67:1) and pending (4.77:1) are legible as coloured text on the light card; warning is 2.15:1, below even the 3:1 large-text floor, so amber appears as text only as text-warning-foreground on a bg-warning/N tint. destructive is deliberately not in this group: it means an irreversible action, never a status and never a number. | Token | Light | Dark | | --- | --- | --- | | `--success` | `oklch(0.596 0.145 163.225)` | `oklch(0.696 0.17 162.48)` | | `--success-foreground` | `oklch(0.985 0 0)` | `oklch(0.21 0.006 285.885)` | | `--warning` | `oklch(0.769 0.188 70.08)` | `oklch(0.828 0.189 84.429)` | | `--warning-foreground` | `oklch(0.21 0.006 285.885)` | `oklch(0.21 0.006 285.885)` | | `--info` | `oklch(0.585 0.233 277.117)` | `oklch(0.673 0.182 276.935)` | | `--info-foreground` | `oklch(0.985 0 0)` | `oklch(0.21 0.006 285.885)` | | `--pending` | `oklch(0.554 0.046 257.417)` | `oklch(0.704 0.04 256.788)` | | `--pending-foreground` | `oklch(0.985 0 0)` | `oklch(0.21 0.006 285.885)` | ### Lines | Token | Light | Dark | | --- | --- | --- | | `--border` | `oklch(0.92 0.004 286.32)` | `oklch(1 0 0 / 10%)` | | `--input` | `oklch(0.92 0.004 286.32)` | `oklch(1 0 0 / 15%)` | | `--ring` | `oklch(0.705 0.015 286.067)` | `oklch(0.552 0.016 285.938)` | | `--sidebar-border` | `oklch(0.92 0.004 286.32)` | `oklch(1 0 0 / 10%)` | | `--sidebar-ring` | `oklch(0.705 0.015 286.067)` | `oklch(0.552 0.016 285.938)` | ### Chart ramp | Token | Light | Dark | | --- | --- | --- | | `--chart-1` | `oklch(0.646 0.222 41.116)` | `oklch(0.488 0.243 264.376)` | | `--chart-2` | `oklch(0.6 0.118 184.704)` | `oklch(0.696 0.17 162.48)` | | `--chart-3` | `oklch(0.398 0.07 227.392)` | `oklch(0.769 0.188 70.08)` | | `--chart-4` | `oklch(0.828 0.189 84.429)` | `oklch(0.627 0.265 303.9)` | | `--chart-5` | `oklch(0.769 0.188 70.08)` | `oklch(0.645 0.246 16.439)` | ### Superset Categorical, never semantic: a colour here says "these exercises are one superset" and nothing about state. Hues stay clear of success green, warning amber and destructive red so a stripe is never misread as a status. Picked by index at runtime via inline style="var(--superset-N)" — Tailwind cannot scan a dynamic class name, so there are no utilities for these. | Token | Light | Dark | | --- | --- | --- | | `--superset-1` | `oklch(0.585 0.233 277.117)` | `oklch(0.673 0.182 276.935)` | | `--superset-2` | `oklch(0.6 0.13 195)` | `oklch(0.72 0.12 195)` | | `--superset-3` | `oklch(0.6 0.22 330)` | `oklch(0.72 0.19 330)` | | `--superset-4` | `oklch(0.65 0.17 55)` | `oklch(0.75 0.15 55)` |