/* Default token layer. Every colour, size, radius and font in this deployment resolves
   through one of these custom properties, so an agency's branding is an override of this
   file — never a fork of the markup that uses it.

   The palette is owner-decided (#57, 2 Aug 2026), derived from zig.ai: a warm off-white ground,
   grey-green ink, one deep-green accent and three state tints. It replaces the stackai defaults
   (white / grey / #0099ff blue) this file shipped with. It is still a NEUTRAL base rather than
   the saulera Sunrise brand, so the README's "one deployment per agency, the tool sits inside
   the agency's own client relationship" decision is untouched — this is a warmer neutral, not
   saulera's colours.

   Where a decided value and a contrast gate disagreed, the gate won (#58 AC2). That happened
   exactly once, at --accent, and --accent-strong below is the result.

   The --font-* families now have real .woff2 files behind them (fonts.css, on disk since #8).
   test/tokens.test.js measures every pairing here and fails the suite under the floor,
   including for an agency that swaps a colour. */

:root {
  /* colour. Ratios below are quoted against the three grounds text renders on, in the order
     --background / --surface / --surface-signature, which is the order test/tokens.test.js
     checks them in. */
  --background:   #fdfafa;
  --surface:      #f4efee;   /* one warm step down from the ground: 1.10:1, a tone not an edge */
  --text-primary: #2e3332;   /* 12.37 / 11.27 / 11.59 */
  /* Kept at the 4.5:1 body-text floor through the repalette, for the same reason it was darkened
     from stackai's #8c8c8c in #5: row meta and the note scaffold both sit on --surface, so this
     is body text and not a decorative grey. The warm-neutral equivalent measures 5.65 / 5.15 /
     5.29 — comfortably clear, and it must stay that way through any agency swap.
     NOT usable on --tint-info: 3.75:1 there. See the tints below. */
  --text-muted:   #5c6764;
  /* The owner's decided accent, and DECORATIVE ONLY. Its uses are the focus ring, the link and
     aria-current underlines, .client-row's marker bar and the dragover edge — every one a
     non-text use at the 3:1 floor, which it clears at 3.88 / 3.53 / 3.63. It cannot carry a
     label: 4.03:1 with white and 3.19:1 with the ink, both under the 4.5:1 body-text floor.
     That is what --accent-strong is for. It is also 2.57:1 on --tint-info, which is why
     :focus-visible keeps a --text-primary hairline inside the ring (app.css) rather than
     trusting the accent alone. */
  --accent:       #08906c;
  /* The accent darkened until a white label clears the body-text floor: 5.04:1. This exists
     because #58 AC2 says the gates win over a raw reference value, and .btn-primary puts a label
     directly on its fill. It is the minimum darkening that satisfies the floor, so the decided
     hue survives at every one of the six sites where it is legal. One consumer: .btn-primary. */
  --accent-strong: #087e60;
  /* The label colour on --accent-strong. A token rather than a literal so app.css needs no raw
     hex — which test/chrome.test.js now enforces. */
  --on-accent:    #ffffff;
  --border:       #78827f;   /* 3.82 / 3.48 / 3.58 — the 3:1 non-text floor, held */
  /* Decorative only: card edges, the top bar's rule, dividers whose grouping the surface
     change already carries. Never the sole boundary of a meaningful control — inputs and
     textareas keep --border, which is what holds the 3:1 non-text floor. That is why this
     token is deliberately NOT in test/tokens.test.js's PAIRINGS. 1.25:1 on --background. */
  --border-hairline: #e7e1e0;
  /* Its width, tokenised so component files can draw a hairline without a raw px. */
  --hairline: 1px;
  --danger:       #78350f;   /* unchanged: 8.74 / 7.96 / 8.19 on the new grounds */

  /* The signature surface: one card per candidate page renders on this accent-tinted ground.
     Retinted blue → mint in #58 so the portal does not ship a blue card on a green page in the
     window before #62 restyles it. Every text token holds its floor on it unchanged — the third
     column of every ratio in this file is this surface, and test/tokens.test.js now checks it
     rather than trusting this comment. The border is decorative, same licence as
     --border-hairline: 1.18:1 on its own ground. prep.css:69-74 is the consumer, and #60 may
     replace the pair wholesale with the Canopy chip pattern. */
  --surface-signature: #eaf6f1;
  --border-signature:  #cfe7dd;

  /* The three state tints, named for their ROLE and not their colour (mint / powder / blush),
     because that is what keeps an agency's branding a value swap. These are BACKGROUNDS: #60
     builds the claim chips on them. A chip carries --text-primary or its own mark colour, never
     --text-muted, which is 3.75:1 on --tint-info and under the floor.
       --tint-verified  ink 10.05, --verified 6.24
       --tint-info      ink 8.21   (--border is 2.53:1 on it: a chip here needs a darker edge, or
                                    a border that is genuinely decorative because the chip also
                                    carries a word — this product's existing pattern)
       --tint-warn      ink 11.34, --unverified 5.59, --failed 7.08, --danger 8.01
     --tint-info now has two consumers, both spending it as the info ground #58 promised it for.
     #60 gives it to act 3's open-questions panel (.pack-questions-panel) — the one block of the
     pack that is neither sourced nor unsourced, the recruiter's own list of things to confirm
     before sending. #62 gives it to the notice a dead magic link lands on (login.html). Both
     carry --text-primary (8.21:1) and never --text-muted (3.75:1, under the body-text floor),
     and both draw NO border: --border is 2.53:1 on this tint and an edge in it would sit under
     the 3:1 non-text floor, so each panel is found by its tone and by the words it carries.
     test/tokens.test.js's TINTS table measures all three. */
  --tint-verified: #c4ece1;
  --tint-info:     #b0d4e8;
  --tint-warn:     #fbeeec;

  /* stackai's soft layered card shadow, one token so every card lifts identically. Alpha hex
     is fine here: the contrast gate reads single whole-value hex tokens only, and a shadow is
     not a colour pairing. The two tinted layers carry the ink's own hue, so they moved with it
     from #1d1d1d to #2e3332 — nothing measures this, so it is a manual correspondence. */
  --shadow-card: 0 1px 1px #0000000a, 0 4px 6px #2e333205, 0 20px 60px #2e33320a,
                 0 2px 4px #0000000a;

  /* type. One sans, one mono (#58): Geist carries the whole UI. Both names
     stay pointing at the same stack rather than collapsing into one --font-sans, because they
     have 14 + 3 call sites across app.css and prep.css and prep.css belongs to #62 — a rename
     is a cross-ticket edit for no functional gain. Deliberate, not an oversight.
     Geist ships 400 and 600 only. Nothing may ask for 500: CSS font-matching resolves a request
     between 400 and 500 DOWNWARD, so a 500 heading would silently render at regular weight. */
  --font-ui:   "Geist", system-ui, -apple-system, sans-serif;
  --font-body: "Geist", system-ui, -apple-system, sans-serif;
  --font-mono: "DM Mono", ui-monospace, SFMono-Regular, monospace;

  /* Type ramp: a ~1.2 step from a 14px UI base. The note is 16px because it is prose a person
     writes and rereads, which CRAFT.md asks for on the pack and the note is the same kind of
     text. Nothing anywhere goes below 12px. */
  --text-caption: 12px;   /* row meta, scaffold items */
  --text-body:    14px;   /* UI chrome, labels, buttons */
  --text-note:    16px;   /* the textarea and any rendered note prose */
  --text-h4:      17px;
  --text-h3:      20px;   /* the selected client's name */
  --text-h2:      24px;   /* what the page h1 was until #59 */
  --text-h1:      29px;   /* the next 1.2 step up: the candidate pages' role title */
  /* 29 × 1.2 = 34.8 → 35. The ramp's top step, and the page h1 on every recruiter screen from
     #59 — it was --text-h2 (24px), which put the page title four points above a subheading and
     is not a ramp. The epic's decided direction is big type; this is the step #58's Out of
     Scope reserved for #59 by name, spent on the one element it named. Disciplined rather than
     round: 35, not 40. */
  --text-display: 35px;

  /* provenance status. Whether a claim is sourced is this product's core distinction, so these
     are text colours and are held to the 4.5:1 body-text floor rather than to the 3:1 a
     decorative state colour could take — the mark carries the WORD ("Unverified"), and a word
     is text. Measured on --background / --surface / --surface-signature:
       --verified   7.68:1 / 7.00:1 / 7.20:1
       --unverified 6.10:1 / 5.55:1 / 5.71:1
       --failed     7.72:1 / 7.03:1 / 7.23:1
     They replace aliases of --success (#22c55e, 2.28:1) and --warning (#c68a0b, 2.98:1), which
     both failed the floor. Those two tokens are gone rather than left orphaned: nothing else
     referenced them. test/tokens.test.js is the gate that keeps these numbers true.
     --failed is its own red rather than var(--danger) so it separates from --unverified at
     12px; --danger stays the state-line error colour.
     --verified moved from #166534 to #0b5c46 in #58, and that one is NOT gate-forced: the old
     value cleared every floor (6.87 / 6.26). It moved so the "sourced" green sits inside the
     accent's hue family rather than beside it — a hue-harmony judgment, cheap to revert. */
  --verified:   #0b5c46;
  --unverified: #8a5300;
  --failed:     #9f1239;

  /* 4px grid */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-12: 48px;
  /* The next step up, added by #59 for the rhythm BETWEEN zones: the gap under the page head
     and between the acts. CRAFT.md asks that rhythm between zones be visibly larger than
     spacing within them, and --space-12 could not deliver that while it was also the common
     large gap — it separated the acts AND the page head AND the .visibility fieldset AND
     .editor-remove. 64px is the zone break; 48px goes back to being an ordinary large step. */
  --space-16: 64px;

  /* Decided and settled in #59, closing #58's open question 5, which asked #59 or #60 to pick
     once because the two references disagree — zig.ai is soft, forcanopy tighter. 9px stays:
     the epic assigns zig.ai the FEEL of the build-a-pack flow and gives forcanopy only the
     pattern language of the pack view, so soft is the right answer for the surfaces #59 owns,
     and 9px is already that. If #60 wants a tighter corner for the Canopy claim chips it adds
     a --radius-sm rather than moving this one — this one is on the card, the rail, the pack,
     every input and every button. */
  --radius: 9px;

  /* The tighter corner #60 was told to add rather than move --radius. Its consumers are the
     claim chips and the two counts in the pack's summary, and nothing else: a chip is roughly
     22px tall, where 9px reads as a pill and 4px reads as a corner. 4px because the spacing
     grid is 4px and this is the one number in the file that can sit on it for free. */
  --radius-sm: 4px;

  --max-width: 1024px;

  /* Off the 4px grid deliberately: CRAFT.md's hit-area floor is 44px on touch, and that is not
     a spacing decision that should move when the grid does. --space-8 is 32px and was standing
     in for it, which put every button under the floor. */
  --tap-target: 44px;

  /* The client note's height, taken from the viewport rather than from a row count. The floor is
     what rows="16" already renders — 16 rows at --text-note (16px) and line-height 1.5 is 384px
     of text plus padding — so a window shorter than about 770px is no worse than today, and a
     tall one gets a real writing surface instead of the same 400px box. The ceiling stops the
     box outgrowing the reader on a 27" monitor. Nothing measures this: it is a comfort judgment,
     stated so it can be argued with.
     vh rather than dvh on purpose: a dynamic viewport unit re-lays-out the note box as a phone's
     URL bar hides, which is motion nobody asked for.
     Neither this nor --sticky-save has an entry in test/tokens.test.js, and neither needs one:
     that gate reads `--name: #hex;` declarations only, because what it measures is contrast. */
  --note-height: clamp(400px, 52vh, 640px);

  /* The sticky save row's own height, reserved as scroll padding so a control focused below it
     is not scrolled underneath it. --tap-target (44) + --space-3 above and below (12 + 12) + the
     hairline (1) = 69px, rounded up to the next 4px step. CHECKLIST.md names the top-side
     version of this trap (scroll-padding-top under sticky chrome); this is the bottom-side twin.
     Keep the two in step: change the save row's padding and this moves with it. */
  --sticky-save: 72px;

  /* Motion. One curve, defined once. Ease-out on everything entering or interactive and never
     ease-in, because ease-in delays the first frame — the one the user is watching. */
  --ease-out:   cubic-bezier(0.23, 1, 0.32, 1);
  --duration-1: 150ms;   /* press and hover response */
  --duration-2: 200ms;   /* the one authored moment: the saved-state line arriving */

  /* Focus. Two concentric lines rather than one. --accent clears the 3:1 non-text floor on all
     three grounds (3.53:1 at worst), but it is 2.57:1 on --tint-info and #60 puts focusable
     controls on the tints, so a --text-primary hairline sits inside the accent ring and
     guarantees the indicator is visible on any surface in this palette. */
  --focus-width:  2px;
  --focus-inner:  1px;
}
