/* The design system's tokens: the colour ramps, plus the spacing, radius, type, motion, layering
   and viewport scales.

   NO TEMPLATE LINKS THIS FILE — it is a build input. design-system/scripts/build-assets.mjs copies
   it verbatim to design-system/src/tokens.css, which frontend/src/app/app.css imports through the
   package, so this is the palette the app ships. The public pages link this file directly and add
   marketing.css on top. It also ships to claude.ai/design as tokens/tokens.css, where
   it is the palette every design gets built against, so an edit here is live for design work in the
   same pass.

   Why it exists: the palette it replaced was never designed as a set. Each
   ramp's hue was read off whichever token already shipped, so several landed in the same arc —
   measured against the sienna brand, its --danger-11 sits 0.033 away, where anything under 0.10
   reads as the same colour family.

   Step jobs are Radix's: 1-2 background, 3-5
   component background, 6-8 borders, 9-10 solid fill, 11-12 text.

   ---- SEPARATION ----

   Six chromatic ramps are tested: orange (brand, fixed) plus the five companions. Sand is exempt —
   its chroma is 0.004-0.019 in light and 0.004-0.011 in dark, where hue distance is meaningless; it
   is the *background* the ramps are measured against, not a participant.

   The metric is dH: distance in the OKLab a/b plane. Step 11 is the binding step, because Radix
   solves every step 11 to the same APCA contrast, which compresses lightness out of the comparison
   and leaves hue as the only separator; adjacent hues collide at 11 even when their solids are
   fine. Threshold is dH >= 0.10 for 14 of the 15 pairs, in both themes. The fifteenth —
   crimson/orange — is exempted, and the "ADJACENCY" section below is why.

   step-11 dH — light                    step-11 dH — dark
            crim  gold  gras  blue  purp          crim  gold  gras  blue  purp
   orange  0.076 0.111 0.192 0.251 0.184  orange 0.046 0.103 0.183 0.233 0.175
   crimson    ·  0.187 0.262 0.290 0.200  crimson   ·  0.137 0.198 0.216 0.142
   gold             ·  0.102 0.226 0.212  gold            ·  0.102 0.215 0.205
   grass                  ·  0.160 0.198  grass                 ·  0.151 0.189
   blue                         ·  0.110  blue                        ·  0.102

   step-9 dH, worst four (identical in both themes — step 9 is theme-invariant here):
   orange/crimson 0.076 · gold/grass 0.102 · orange/gold 0.110 · blue/purple 0.115

   No pair has room to spare. Four sit between 0.102 and 0.115, because the sienna brand is a
   high-chroma colour at hue 40 and it occupies the whole amber-through-red arc. The binding
   constraint is always dark step 11, where sRGB caps chroma near 0.13 at the lightness those
   values need.

   ---- ADJACENCY ----

   The floor is weighted by adjacency rather than applied uniformly. What a separation floor
   actually asks is whether two ramps ever have to be told apart *side by side, at a glance*. The
   three status dots do — they share one session row — so they hold the full 0.10, and so does every
   other pair. Danger and the brand do not: they co-occur as a link beside an error sentence, which
   is sequential reading, not a comparison.

   crimson/orange is the single exempted pair, and exempting it is what lets danger be a red at all.
   It sits at 0.046 in dark step 11 (the binding cell), 0.076 at the other three. Everything else in
   this file still clears 0.10, and tokens.test.ts enforces the exemption as a named pair
   holding a floor of its own — so a reseed that pushes crimson further into the brand still fails,
   and a reseed that clears it fails the test that says this is the only exemption.

   ---- HOW CLOSE THIS STAYS TO THE SHIPPED COLOURS ----

   Four of the five companions are the closest ramp to the colour the product ships that satisfies
   every rule above. Seeds were solved, not picked: the coupled groups {crimson, purple, blue} and
   {gold, grass} were searched jointly, minimising each role's worst-case movement rather than the
   total, so no role is sacrificed to buy slack for another.

   The frozen column is each role's step 9 in the ramps of the palette this one replaced. That
   palette declared only the steps it used, so --info-9 (#4d5baa) was not among them; its --info-11
   was #5160af.

   role       frozen   ->  step 9    moved (dE)   note
   brand      #9e3c12       #9e3c12      0.0000     fixed; not in the search
   danger     #c0392b       #c11628      0.0399     the exempted pair — see ADJACENCY
   executing  #a15d08       #7e660d      0.0652     forced (floor 0.0610)
   designing  #4d5baa       #3661aa      0.0288     the frozen --info hue, not a signal blue
   merged     #9877c2       #a56fc3      0.0278     moved only to clear designing
   completed  #4a7c59       #437d5f      0.0095     essentially the frozen sage

   Executing is forced rather than chosen. It is pinned by the *dark* step 11 against orange-11
   (#ff9a73, L 0.78 C 0.133 H 42): at that lightness sRGB caps chroma near 0.13, so clearing 0.10
   needs a hue gap of ~44deg, which pushes executing to hue >= 90 — an olive-gold, not an amber.
   That same arithmetic would cap danger at hue <= 358, and is exactly the constraint the exemption
   lifts.

   Designing barely moves *because* it is anchored on the replaced palette's --info ramp, a muted
   slate-indigo, rather than on a warm status colour. The hue was already in the product, attached
   to a different role.

   ---- CONTRAST ----

   Radix targets APCA, not WCAG 2, and the guarantee it makes is scale-local: Lc 60 against step 2
   *of the same scale*. Neither of those is the question a designer actually asks, which is "can I
   put this step 11 on that surface". Measured against all three neutral surfaces a step 11 can
   land on — sand-1, sand-2, sand-3 — in both themes, there are exactly two soft spots:

   1. gold-11 is text-safe on sand-1 and sand-2 but NOT on sand-3. It is the tightest step 11 in
      the system (4.99 / 4.82 / 4.46 across the three in light), so it is the only one that falls
      under AA on the recessed surface — which is the ordinary ground for a tool row or an inset
      panel. Every other step 11's worst surface still clears: crimson 4.64, grass 4.77, orange
      4.85, blue 4.99, purple 5.14. This is a real restriction, written up as a rule in
      .design-sync/conventions.md and enforced by tokens.test.ts rather than left to be
      rediscovered.

   2. crimson-11 in dark sits just under the APCA Lc 60 body-text line on sand-2, at about 59.5.
      WCAG is unaffected and comfortable at 8.12:1, and APCA and WCAG disagree here in APCA's usual
      direction for light-on-dark. Recorded rather than smoothed over: if Lc 60 has to hold exactly,
      the fix is a darker step 11 traded against contrast. It is not a hue move — every hue is under
      the line at this lightness.

   tokens.test.ts guards the dH invariant above at steps 9 and 11 in both themes, and the WCAG floor
   for every step 11 against all three surfaces (gold excepted on sand-3, per the restriction above)
   — so a future palette edit cannot quietly break either. The invariant is adjacency-weighted:
   crimson/orange holds a floor of its own and every other pair holds 0.10. It does not assert the
   APCA figures: a hand-rolled APCA would be a second implementation to get subtly wrong. The Lc
   numbers here came from colorjs.io.

   ---- WHAT ELSE IS IN HERE ----

   Everything above is about colour separation. Three groups of tokens further down are not colour
   concerns at all, and a reader looking for them will not expect to find them under a header about
   hue distance:

     focus      --n-focus-ring, --focus-ring, --focus-ring-offset. In the semantic block, with the
                inset-vs-outset rule written out there.
     motion     --duration-*, --ease-*, plus a prefers-reduced-motion block that overrides all
                four durations at once. With the spacing and type scales.
     layering   --z-*. Same place.

   They exist because unstyled primitives each need a focus ring, an enter/exit duration and a
   stacking context, and without tokens every component invents its own. */

:root {
  color-scheme: light dark;


  /* sand — neutral surfaces and text.

     Radix Sand is a warm gray in its *dark* steps only — it reaches chroma 0.0102 by step 9, but
     steps 1-3 are near-achromatic, and steps 1-3 are the page, the panels and the insets. Stock,
     sand-2 is chroma 0.0013 and sand-3 is 0.0017, seven to ten times less tint than the frozen
     palette's --bg (0.0098) and --bg-alt (0.017) carry, on the three surfaces that cover almost all
     of a screen. Designs built from stock steps turn the product's cream into an off-white while
     the mid-ramp borders and step-11 text stay warm enough to read as inconsistent rather than
     neutral. Stock hue also wanders 68 to 107 down the light ramp, so sand-2 and sand-3 tint in
     opposite directions and a recessed panel shifts hue against the page around it.

     So: every step's L is Radix Sand's, unchanged to three decimals — nothing here moves contrast.
     H is pinned to 88 on all twelve steps in both themes, the product's own neutral hue (the frozen
     --bg 87.5, --bg-alt 88, --border 87.5); that tightness is part of why the app reads as one
     material. C traces a chroma-against-lightness curve rather than a flat tint — sand-2 is the
     frozen --bg's 0.0098 exactly, the curve peaks at 0.0185 in the border band, and the text steps
     taper. Dark carries roughly half of light's chroma: tint reads stronger against a dark ground,
     and the frozen dark ground is 0.004.

     Two steps barely move, correctly. sand-11 dark is Radix's own value, 0.0087 at hue 92. sand-12
     light is held down at 0.0040 — the darkest text step should not be the warmest thing on the
     page.

     Each value is solved at its existing L and H 88 for the target C, then quantised to 8-bit. At
     these chromas one channel step is a large fraction of the tint, so measured hue lands between
     82 and 95 rather than on 88. That is rounding, not drift. */
  --sand-1: light-dark(#fffdf9, #12110f);
  --sand-2: light-dark(#fcf9f2, #1a1916);
  --sand-3: light-dark(#f4f0e7, #23221f);
  --sand-4: light-dark(#ede8dd, #2b2a26);
  --sand-5: light-dark(#e6e1d5, #32312d);
  --sand-6: light-dark(#ded9cc, #3c3a35);
  --sand-7: light-dark(#d3cec1, #4a4842);
  --sand-8: light-dark(#c0baae, #63605a);
  --sand-9: light-dark(#908c82, #706d66);
  --sand-10: light-dark(#858178, #7d7b74);
  --sand-11: light-dark(#66625b, #b5b3ad);
  --sand-12: light-dark(#21201e, #efeeeb);

  /* orange — brand — links, primary buttons.

     Generated by radix-ui.com/colors/custom seeded with the product's own burnt sienna, #9e3c12,
     against the generator's default gray (#8b8d98) and backgrounds (#ffffff / #111111). Step 9 is
     the seed exactly in both themes; every other step is solved around it. Not stock Radix orange:
     its step 9 is a brighter, more generic #f76b15, which is not the product's colour and tints the
     low steps for a colour the app never used.

     Dark step 9 is the same deep sienna as light rather than the frozen palette's lighter dark
     accent (#b87848). Seeding dark with #b87848 puts a near-white label on it at 3.11:1, below AA;
     the deep sienna holds 5.81:1. */
  --orange-1: light-dark(#fffdfc, #160f0d);
  --orange-2: light-dark(#fef8f6, #1f1511);
  --orange-3: light-dark(#fcece6, #38180c);
  --orange-4: light-dark(#fddfd5, #4f1600);
  --orange-5: light-dark(#fad2c4, #5f1d01);
  --orange-6: light-dark(#f6c3b1, #6f2b0f);
  --orange-7: light-dark(#efb099, #883b1d);
  --orange-8: light-dark(#e59578, #af4c25);
  --orange-9: light-dark(#9e3c12, #9e3c12);
  --orange-10: light-dark(#8d2c00, #833719);
  --orange-11: light-dark(#ae4a24, #ff9a73);
  --orange-12: light-dark(#4e2e22, #ffd6c7);

  /* crimson — errors and destructive actions. Seeded #c11628 — hue 24, chroma 0.200.

     With the brand fixed at hue 40, the window of step-11 hues clearing 0.10 from BOTH orange-11
     and purple-11 in both themes is about one degree wide, at 358 — and every red a reader
     recognises as an error colour is outside it. Measured at step 11 dark against orange: hue 8
     gives 0.084, hue 16 gives 0.064, hue 24 gives 0.046, hue 29 gives 0.035. So the rule gives
     rather than the colour; see ADJACENCY in the header for what is relaxed and why.

     Why 24 and not further into the red: at hue 29 the ramp starts reading as *related to* the
     sienna in dark rather than distinct from it, which is the collision the floor exists to
     prevent. 24 is the textbook error red — near enough to the classic #c1121f — and the last hue
     that still holds its own beside the brand.

     Step 11 clears AA on all three neutral surfaces in both themes (5.20 / 5.02 / 4.64 light,
     8.72 / 8.12 / 7.35 dark), the near-white label on step 9 measures 6.06:1 light and 5.30:1 dark,
     and the closed Badge's step-11-on-step-3 is 4.57 light / 7.48 dark.

     Dark step 11 is a light salmon rather than a red, and no seed changes that: it is a tint at
     L 0.78, where sRGB caps chroma near 0.13, and every red is pinkish there. The fix, if it is
     ever wanted, is a darker step 11 traded against contrast. */
  --crimson-1: light-dark(#fffcfc, #190d0d);
  --crimson-2: light-dark(#fef7f7, #231111);
  --crimson-3: light-dark(#ffeae8, #420d0f);
  --crimson-4: light-dark(#ffdddb, #5b0007);
  --crimson-5: light-dark(#fdcfcb, #6c000d);
  --crimson-6: light-dark(#f6c0bb, #7f0b17);
  --crimson-7: light-dark(#eeada8, #9c1d24);
  --crimson-8: light-dark(#e59690, #c9232e);
  --crimson-9: light-dark(#c11628, #c11628);
  --crimson-10: light-dark(#af001b, #a3252a);
  --crimson-11: light-dark(#ce2833, #ff918f);
  --crimson-12: light-dark(#671819, #ffd0cf);

  /* gold — in-progress states (session executing). Seeded #7e660d, from the frozen palette's
     --warning-9 #a15d08. An olive-gold, not an amber: the amber arc belongs to the brand. */
  --gold-1: light-dark(#fefdfb, #13110a);
  --gold-2: light-dark(#fefaf0, #1b180e);
  --gold-3: light-dark(#fef3d1, #2c2302);
  --gold-4: light-dark(#faeab9, #392a00);
  --gold-5: light-dark(#f5e0a2, #463400);
  --gold-6: light-dark(#e8d394, #544100);
  --gold-7: light-dark(#d9c486, #67530d);
  --gold-8: light-dark(#c7ae60, #836b15);
  --gold-9: light-dark(#7e660d, #7e660d);
  --gold-10: light-dark(#705800, #6d5915);
  --gold-11: light-dark(#846c18, #f6dc8e);
  --gold-12: light-dark(#3f3927, #ffebae);

  /* grass — success and completion. Seeded #437d5f, all but identical to the frozen palette's
     --success-9 #4a7c59. The one role the constraints left alone. */
  --grass-1: light-dark(#fbfefc, #0d130f);
  --grass-2: light-dark(#f5fbf7, #131b17);
  --grass-3: light-dark(#e5f7ec, #162c21);
  --grass-4: light-dark(#d6f1e1, #193a29);
  --grass-5: light-dark(#c5e8d3, #204733);
  --grass-6: light-dark(#b1dcc3, #2b553f);
  --grass-7: light-dark(#99cbae, #36664d);
  --grass-8: light-dark(#75b693, #417b5d);
  --grass-9: light-dark(#437d5f, #437d5f);
  --grass-10: light-dark(#356f52, #3d6d54);
  --grass-11: light-dark(#3b7557, #8cc8a7);
  --grass-12: light-dark(#274033, #b9eecf);

  /* blue — informational, session designing. Seeded #3661aa, from the frozen palette's --info ramp
     (its --info-11 is #5160af) rather than from a warm status colour. A muted slate-indigo, not a
     signal blue. */
  --blue-1: light-dark(#fcfdfe, #0b111b);
  --blue-2: light-dark(#f6f9fe, #101824);
  --blue-3: light-dark(#ecf2fd, #142645);
  --blue-4: light-dark(#dfeafd, #16315e);
  --blue-5: light-dark(#d0e0fa, #1e3c70);
  --blue-6: light-dark(#bed3f6, #274880);
  --blue-7: light-dark(#a7c1ec, #315694);
  --blue-8: light-dark(#87a9e0, #3b66af);
  --blue-9: light-dark(#3661aa, #3661aa);
  --blue-10: light-dark(#28529a, #2f5491);
  --blue-11: light-dark(#3a66af, #8bb6ff);
  --blue-12: light-dark(#213455, #d2e2fd);

  /* purple — merged pull requests. Seeded #a56fc3, from the frozen palette's --plum-9 #9877c2.
     Moved only far enough to clear blue. */
  --purple-1: light-dark(#fefcfe, #130f16);
  --purple-2: light-dark(#fbf8fd, #1c1520);
  --purple-3: light-dark(#f5edfb, #2d1e35);
  --purple-4: light-dark(#efe2f7, #3b2547);
  --purple-5: light-dark(#e7d6f2, #452d53);
  --purple-6: light-dark(#ddc8ea, #523860);
  --purple-7: light-dark(#cfb5df, #654876);
  --purple-8: light-dark(#be9bd3, #835e98);
  --purple-9: light-dark(#a56fc3, #a56fc3);
  --purple-10: light-dark(#9764b4, #9863b6);
  --purple-11: light-dark(#81519c, #d3a3ef);
  --purple-12: light-dark(#462757, #eadaf4);

  /* The app sits on sand-2 rather than sand-1, which leaves a step in both directions and keeps
     every surface on the scale — no invented white.

     The step numbers swap between themes because the ramp direction does: raising a surface means
     going brighter, which is *down* the scale on light and *up* on dark. Same for recessing.

     Light elevation is subtle on purpose — sand-1 against sand-2 is dL 0.012, where the frozen
     palette's white-on-cream is 0.030. That is Radix's model: steps 1-2 are both background steps
     and are meant to be near-identical, with panels defined by their border (steps 6-8) rather
     than their fill. It holds here because every raised surface is already bordered — the sidebar
     has a border-right, menus a border and a shadow, inputs a 1px border. Dark gains separation,
     0.039 against the frozen palette's 0.023. */
  --raised: light-dark(var(--sand-1), var(--sand-3));
  --recessed: light-dark(var(--sand-3), var(--sand-1));

  /* ---- Dimension. 4px base: the near-universal choice, and what a code agent assumes. ----

     The scale is the design system's own: a component that needs a gap picks a step from it rather
     than a pixel, which is what keeps rhythm consistent across parts drawn by different hands. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  /* A square that was chosen, and the reason it is a token rather than a literal 0: nothing else in
     the scale can say "deliberately not rounded", so a bare `border-radius: 0` reads the same as a
     radius somebody forgot. A full-width block that meets its column is not a card. */
  --radius-none: 0;
  --radius-sm: 2px;
  --radius-md: 4px;
  --radius-lg: 8px;
  --radius-xl: 12px; /* reserved — no consumer yet; kept for scale completeness */
  --radius-full: 9999px;

  /* Type: families. Fallbacks are the generic each font actually belongs to, so a failed webfont
     degrades to the right shape rather than to Times.

     --font-family-* rather than --font-*, for the same reason --font-size-* is not --text-*: a
     --font-sans sitting beside a --font-size-body would put one prefix on two different kinds of
     thing. The four extra characters buy a prefix that means one thing.

     The literal family names stay literal inside the @font-face rules in fonts.css — that is the
     one place a family name cannot be a token, because it is the declaration of the name. */
  --font-family-sans: "DM Sans", sans-serif; /* all UI text */
  --font-family-serif: "Newsreader", serif; /* the conversation's reading voice + editorial headings; italic is the house emphasis */
  --font-family-mono: "JetBrains Mono", monospace; /* code, and uppercase micro-labels */
  --font-family-script: "Caveat", cursive; /* handwritten accent, used sparingly */

  /* Type: a 1.2 modular scale from a 16px base, named by role.

     --font-size-* rather than --text-*: the palette this one replaced spelt --text, --text-muted and
     --text-faint as colours, so --text-body would have meant a size and --text a colour. Same
     prefix, two meanings, and the spelling stays so the prefix never means two things again.

     The shipped screens are drawn with optically tuned sizes, half-pixel steps included, which a
     modular scale will not reproduce — worth knowing before adopting this. */
  --font-size-caption: 0.694rem;
  --font-size-small: 0.833rem;
  --font-size-body: 1rem;
  --font-size-lead: 1.2rem;
  --font-size-h3: 1.44rem;
  --font-size-h2: 1.728rem;
  --font-size-h1: 2.074rem;

  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-loose: 1.7;

  /* ---- Motion. Four steps, each a value the app already converged on, named for intent. ----

     The infinite animations — spinner, ripple, text pulse — keep their own literal durations and
     are deliberately outside this scale: they are not transitions, and a reduced-motion override
     that stopped them would break the only signal that something is running.

     --duration-theme is a separate step rather than an alias of --duration-slow because it is the
     one duration that is not interaction feedback: it exists so the page cross-fades on a theme
     switch. It belongs on background, border and color only, never on transform. */
  --duration-fast: 120ms /* @kind other */; /* colour and opacity feedback: hover, focus, active */
  --duration-normal: 150ms /* @kind other */; /* transforms and small enter/exit */
  --duration-slow: 250ms /* @kind other */; /* size and layout change, and overlay entrances */
  --duration-theme: 300ms /* @kind other */; /* the theme cross-fade, on background/border/color only */

  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1) /* @kind other */; /* entrances */
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1) /* @kind other */; /* size and position change */

  /* ---- Layering. Steps of 100 above the overlay line, so a one-off can be slotted between two
     named layers without renumbering. ----

     Two layers on one step means stacking falls through to DOM order, which no component controls,
     so every value here is distinct and tokens.test.ts asserts that.

     The order above the overlay line is the part that carries reasoning:
       menu above overlay  — a dropdown or combobox inside a dialog is normal.
       tooltip above menu  — a menu item can own a tooltip; the reverse never happens.
       toast above all     — a toast reports something that already happened, so being occluded by
                             a modal defeats it.
     Scrim and dialog content deliberately share --z-overlay: they are one layer, ordered between
     themselves by DOM order within the same portal, which IS controlled by the component. */
  --z-chrome: 10 /* @kind other */; /* sidebar, session header */
  --z-sticky: 100 /* @kind other */; /* sticky rows inside a scroll container */
  --z-overlay: 1000 /* @kind other */; /* dialog scrim and dialog content */
  --z-menu: 1100 /* @kind other */; /* dropdown, popover, combobox, hover card */
  --z-tooltip: 1200 /* @kind other */; /* above menus: a menu item can own a tooltip */
  --z-toast: 1300 /* @kind other */; /* above everything: must stay readable over a dialog */

  /* ---- Viewports. Reference constants, not knobs. ----

     Media queries cannot read var(), so these are never consumed directly: copy the literal into
     the query and cite the token in a comment beside it. Exactly two breakpoints exist — a screen
     uses zero, one, or both, never a new value. Components never carry a viewport media query at
     all; what a layout does at a width is the screen's decision, not the part's.

     Design target 1440×900 · minimum 360 · no upper bound — ultrawide is supported. */
  --breakpoint-mobile: 640px /* @kind other */; /* at and below: mobile. 360–640 */
  --breakpoint-desktop: 1024px /* @kind other */; /* below: narrow/tablet. at or above: desktop */
}

/* Reduced motion, once, for every consumer — including every component built on these tokens.
   Without this, each component owns the concern separately and any one of them can forget it.

   1ms rather than 0s — measured in headless chromium:

     transition-duration 0s    -> transitionend NEVER fires
     transition-duration 1ms   -> transitionend fires
     animation-duration  0s    -> animationend fires
     animation-duration  1ms   -> animationend fires

   Anything that waits on transition completion to unmount (Radix's Presence does exactly this)
   would leave exit-animated components mounted forever for reduced-motion users at 0s. The
   animation path is safe either way; the transition path is not, so 1ms is the value that is
   correct for both. Re-check if the component library's unmount strategy ever changes.

   KNOWN AND DELIBERATE: _ds_manifest.json reports all four durations as 1ms rather than their base
   values. Its parser is last-declaration-wins and ignores media-query context, so it records these
   overrides instead of the declarations above. The CSS is correct — browsers resolve 120ms
   normally. Do NOT "fix" this by restructuring the cascade: a :root:root override or a calc()
   multiplier would game that parser and read like a bug to the next person. The manifest value is
   the thing that is wrong, and it is advisory only. */
@media (prefers-reduced-motion: reduce) {
  :root {
    /* These carry @kind too, and that is not redundant: the compiler attaches a kind to the LAST
       declaration it sees, which is this block, so annotating only the base values above leaves all
       four unclassified. Same last-declaration-wins parser as the 1ms misreport noted above. */
    --duration-fast: 1ms /* @kind other */;
    --duration-normal: 1ms /* @kind other */;
    --duration-slow: 1ms /* @kind other */;
    --duration-theme: 1ms /* @kind other */;
  }
}

/* The theme override. color-scheme is the switch that every light-dark() in this file reads, so
   these two rules are all that is needed — no per-token duplication and no second set of values.

   It is also inheritable, so it works on any subtree, not just :root — setting it on a wrapper
   renders that subtree in the other theme, which is how a side-by-side theme comparison is built
   on one page. */
:root[data-theme="light"] {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}

/* Semantic layer. Prefer these tokens; use a scale step only when no semantic token fits. */
:root {
  --n-bg: var(--sand-2);
  --n-bg-alt: var(--recessed);
  --n-border-subtle: var(--sand-6);
  --n-border: var(--sand-7);
  --n-text-muted: var(--sand-11);
  --n-text: var(--sand-12);
  --n-surface-raised: var(--raised);

  --n-accent: var(--orange-9);
  --n-accent-hover: var(--orange-10);
  --n-accent-text: var(--orange-11);

  /* Inline code in prose. Its own ink rather than the link step, because a design document names
     identifiers constantly and at that density accent chips compete with the page's real accents.
     The chip's ground drops to --n-bg-alt and only the ink stays warm.

     Neither step carries both themes, which is why this is a pair and not one value: orange-12 in
     light is #4e2e22 at chroma 0.052 — effectively near-black, and the warmth that made it code is
     gone; orange-11 in dark IS --n-accent-text, so every identifier would read at link strength,
     which is the complaint this token answers. Same construction as --n-focus-ring and
     --n-accent-contrast below: a role whose right step differs per theme is a token, so a call
     site never hand-writes the pair. Worst case across the three neutral surfaces is 4.85:1 in
     light (on --n-bg-alt, the ground it is drawn on) and 11.89:1 in dark. */
  --n-code-text: light-dark(var(--orange-11), var(--orange-12));

  /* The label on an accent fill. Near-white on both sides — the sienna solid is dark enough in
     either theme to carry one (6.65:1 light, 5.82:1 dark), which is also what Radix's generator
     returns as this scale's --accent-contrast. It is a light-dark() of two scale steps rather than
     one, because sand-1 and sand-12 are the near-white end of the neutral ramp in their respective
     themes; picking either alone would flip to near-black on the other side. */
  --n-accent-contrast: light-dark(var(--sand-1), var(--sand-12));

  --n-danger: var(--crimson-11);
  --n-danger-solid: var(--crimson-9);

  /* Three distinct hues, because these render as dots side by side on the session list. The frozen
     palette drew two of them from one ramp two steps apart, --warning-11 and --warning-9, which are
     0.013 apart in the a/b plane. */
  --n-status-designing: var(--blue-9);
  --n-status-executing: var(--gold-9);
  --n-status-completed: var(--grass-9);

  /* The completed word. Grass-11 clears AA on all three neutrals in both themes, so unlike
     executing it can be a token: there is deliberately no --n-status-executing-text, see
     guides/palette.md on the gold restriction. */
  --n-status-completed-text: var(--grass-11);

  /* The running mark's rings: the running badge's static edge, and the ripple that the badge and a
     plan row's status box draw out of it. */
  --n-status-executing-border: var(--gold-6);
  --n-status-executing-ring: var(--gold-8);

  /* The plan's relation rails, relative to the open task: what it waits on, and what waits on it.
     The open row's own rail is --n-accent. */
  --n-relation-upstream: var(--blue-8);
  --n-relation-downstream: var(--purple-8);

  --n-pr-merged: var(--purple-9);
  --n-pr-closed: var(--crimson-9);

  /* ---- Alpha. Composites over whatever it lands on, which is the whole point. ----

     A solid step cannot do either of these jobs. An overlay has to dim the page while leaving it
     visible, and no opaque value does that. A tint has to preserve elevation: solid --orange-3 over
     --raised and over --recessed resolves to #fcece6 both times, so the surface stops reading as
     raised at all. These composite instead — 8% accent lands on #f7eee7 over --raised and #ede2d6
     over --recessed, keeping the two 0.0367 apart where the bare surfaces differ by 0.0393.

     The percentages differ per theme because the headroom does. Dark needs doubling to 16/32 to
     land at light's 8/16 perceptual weight: at 8% over --raised in dark the shift is only dE
     0.0213, borderline for a large flat fill, where 16% gives 0.0434 — within a hair of light's
     0.0412. Strong is 0.0863 against light's 0.0850, and the two tints stay 0.044/0.043 apart from
     each other.

     Only these three exist by design. Radix's hue-matched neutral alpha ramps are not needed here:
     black at 6% over the warm ground leaves chroma at 0.0098 -> 0.0099. It darkens the surface
     without neutralising it, so there is nothing for a hue-matched ramp to correct. */
  --n-overlay: light-dark(rgb(0 0 0 / 0.32), rgb(0 0 0 / 0.60));
  --n-accent-tint: light-dark(
    color-mix(in srgb, var(--n-accent) 8%, transparent),
    color-mix(in srgb, var(--n-accent) 16%, transparent)
  );
  --n-accent-tint-strong: light-dark(
    color-mix(in srgb, var(--n-accent) 16%, transparent),
    color-mix(in srgb, var(--n-accent) 32%, transparent)
  );

  /* ---- Elevation. ----

     Panels are border-defined, but a dialog over content and a dropdown over a list have to read
     as detached, which a 1px border cannot do.

     light-dark() resolves to a <color>, not to a whole box-shadow — a light-dark() in a box-shadow
     slot makes the declaration invalid and the shadow silently does not render. So the geometry is
     written once and only the colours switch.

     Dark is not "more black": black on near-black is invisible. It gets a heavier ambient shadow
     for separation plus a 1px top rim highlight, which is what actually reads as a lifted edge.
     --n-shadow-rim is fully transparent in light, so the same rule is correct in both themes. */
  --n-shadow-key: light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.32));
  --n-shadow-ambient: light-dark(rgb(0 0 0 / 0.10), rgb(0 0 0 / 0.44));
  --n-shadow-rim: light-dark(rgb(255 255 255 / 0), rgb(255 255 255 / 0.06));

  --n-shadow-sm: 0 1px 2px var(--n-shadow-key), inset 0 1px 0 var(--n-shadow-rim);
  --n-shadow-md: 0 2px 4px var(--n-shadow-key), 0 8px 16px -4px var(--n-shadow-ambient),
    inset 0 1px 0 var(--n-shadow-rim);
  --n-shadow-lg: 0 4px 8px var(--n-shadow-key), 0 24px 48px -12px var(--n-shadow-ambient),
    inset 0 1px 0 var(--n-shadow-rim);

  /* ---- Focus. ----

     Reusing the brand for focus is deliberate: the separation matrix above shows the palette has
     no room for another hue, and a focus ring is the one role that can share a colour with the
     brand without ambiguity — nothing else in the system is a 2px outline.

     Only the ring's *lightness* is theme-dependent. --n-accent is the same sienna in both themes on
     purpose, which is right for a fill (its label is what must contrast) and wrong for a ring,
     whose contrast is measured against the surface behind it:

       orange-9  #9e3c12 on sand-2 light   6.42:1   passes
       orange-9  #9e3c12 on sand-2 dark    2.60:1   FAILS WCAG 1.4.11's 3:1 floor
       orange-11 #ff9a73 on sand-2 dark    8.47:1   passes

     So dark takes orange-11 — already the brand-as-text step, no new value. Worst case across all
     three neutral surfaces in both themes is 5.94:1; on a selected row tinted with
     --n-accent-tint-strong it is still 5.17:1.

     --focus-ring is a whole `outline` value, like --n-shadow-* are whole box-shadows. Apply it
     directly. Never wrap it in light-dark() at the call site: that function only accepts colours,
     and the declaration would be dropped silently. */
  --n-focus-ring: light-dark(var(--orange-9), var(--orange-11));
  --focus-ring: 2px solid var(--n-focus-ring);

  /* The DEFAULT, not the universal rule. Inset is right for anything whose edges meet its
     container — rows, nav and menu items, list items, inputs inside a bordered block.

     A control with a filled background needs +2px instead, so the ring lands on the surface
     around it rather than on the fill. Inset on a filled control measures ring against fill:

       ring on --n-accent      light 1.00:1   dark 3.26:1
       ring on --n-danger-solid light 1.09:1  dark 2.97:1

     Light is a hard failure on both. Dark scrapes past on the accent and fails on the destructive
     button, so this is not a light-mode-only problem — an inset ring on a destructive filled
     button is invisible in *both* themes. One offset rule for all filled controls, no theme
     conditional. */
  --focus-ring-offset: -2px;
}
