/* tokens.css — the one place a forge colour, font or shape is stated. UX-14.
 *
 * WHY THIS FILE EXISTS (2026-09-13). The palette was copied into ten places — nine pages in ui/ plus the two
 * generators that write pages (tests/report.mjs → ui/tests.html, lib/meta-pages.mjs → /base) — and it had already
 * drifted: `--slate` was #585044 on seven pages and #5A6B70 on the two refusal pages, `--live-soft` was #dceee5 on
 * four and #e4f0ea on the report. Nobody chose either divergence; they are what ten copies do. A skin, a dark mode
 * or a brand swap is theatre while ten files each state their own answer, and a contrast floor that measures ten
 * palettes measures ten different products.
 *
 * THE TWO CONVERSION RULES, from rut's own scars (docs/FORGE-DESIGN-ANALYSIS.md §3.1):
 *   · The token layer goes IN FRONT, never as a fallback at every read. There is not one `var(--x, #hex)` in this
 *     file or in the pages that link it. rut added 212 such fallbacks and spent a day removing them again: a
 *     fallback per read is ten copies wearing a different hat, and it makes a missing token invisible.
 *   · Convert a colour PAIR, never half of one. Every role below states its text and the surface that text sits
 *     on, together, and tests/contrast-floor.test.mjs measures exactly that pair.
 *
 * THREE LAYERS, in DTCG's order (§4.2). Nothing skips a layer:
 *   1. PRIMITIVES — the raw scale. Hex lives here and nowhere else. A primitive has no opinion about meaning.
 *   2. SEMANTIC   — role → value. What a colour MEANS: success, warning, danger, unknown, stale. A page reads
 *                   these, never a primitive.
 *   3. COMPONENT  — the shapes the console actually draws: pill, card, button, hairline.
 * The legacy names (--paper --card --ink --slate --live --idle …) are aliases in the semantic layer, so the
 * pages kept working the moment this file was linked and could be converted one at a time rather than in a
 * single edit nobody could review.
 *
 * VALUES ARE DIRECTION A, MEASURED. Forge's own petrol palette, ruled by the operator 2026-09-13 (`D2 A`);
 * vendoring the Aleris brand tokens stays possible later and is a swap of this layer, not of the pages. Every
 * ratio quoted in a comment here was computed by tests/contrast-floor.test.mjs's own arithmetic, not judged by
 * eye — rut's uncertainty colour sat at 2.01:1 because somebody looked at it and thought it looked fine.
 */

/* ── 1 · PRIMITIVES ────────────────────────────────────────────────────────────────────────────────────
 * The raw scale. The only hex in forge's token layer, and — the CSS structure gate apart — the only hex a
 * page is allowed to inherit. Named by hue and step, never by meaning: #2F6A50 is `--green-700`, and whether
 * green means "online" is the next layer's business.
 */
:root {
  /* petrol — the Aleris ground note, and forge's accent */
  --petrol-900: #002E34;
  --petrol-800: #004851;
  --petrol-600: #386970;
  --petrol-400: #5F8F95;
  --petrol-050: #D9E1E2;

  /* sand and bark — paper, hairlines and body text */
  --white:      #FFFFFF;
  --sand-050:   #FAF8F6;
  --sand-100:   #ECE8E3;
  --sand-200:   #D7D2CB;
  --bark-600:   #5F5A54;
  --bark-700:   #585044;

  /* slate — the quiet, nothing-to-do-here grey */
  --slate-100:  #D9E1E2;
  --slate-700:  #4F5A66;

  /* status hues */
  --green-700:  #2F6A50;
  --green-050:  #dceee5;
  --rust-700:   #8A4A14;
  --rust-100:   #FBD1C0;
  --red-700:    #A83430;
  --red-050:    #fbeaea;
  --amber-700:  #7A5300;
  --amber-050:  #F6EFE1;

  /* coral — the one warm call to action */
  --coral-500:  #F58C61;

  /* OTHER PEOPLE'S MARKS (2026-09-29). The official colours of the places an app is moved from, each used
     ONLY to fill that brand's own mark on "Move an app here" — never as a colour of ours. From Simple Icons'
     data, which cites each brand's guidelines; OpenAI is not in it and its mark is black by its own rules.
     All clear 3:1 on a white card, the floor for a graphic (Claude, the lowest, 3.12:1). Lovable has none
     here: no published mark was found, and a colour guessed for someone else's brand is not theirs. */
  --mark-claude:    #D97757;
  --mark-openai:    #000000;
  --mark-github:    #181717;
  --mark-bitbucket: #0052CC;
  --mark-git:       #F03C2E;

  /* autumn — the season's skin (2026-09-29, "an autumn skin with lucide icons, and falling leaves"; the falling
     leaves and their two colours were removed 2026-10-01). Used for a warmer paper and the one still leaf beside
     the landing heading: no text is ever set in these, so the contrast floor is untouched. */
  --autumn-paper:   #FBF5EC;
  --autumn-rust:    #B5522B;

  /* type */
  --font-sans:  system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-serif: 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
  --font-mono:  ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* ── 2 · SEMANTIC ─────────────────────────────────────────────────────────────────────────────────────
 * Role → value. Four facets per role, named the same way every time, so a component can ask for a role
 * rather than for a colour:
 *   -bg       the page ground the role is read against
 *   -surface  the ground the role paints for itself (a pill, a badge, a card)
 *   -border   its hairline
 *   -text     the ink, and the thing the floor measures against -surface and against both grounds
 *
 * STATUS ROLES NEVER CHANGE WITH A SKIN (§4.2). A future theme may repaint neutral and accent; success,
 * warning, danger, unknown and stale are load-bearing meaning and stay where they are.
 *
 * UNKNOWN AND STALE ARE ROLES, not the absence of one. Principle 1: unknown is a state. Principle 2: a
 * reading turns stale when overdue and NEVER green. Before today forge had no way to say either, so it said
 * the nearest thing it had, which was usually the quiet grey that means "nothing to do here".
 */
:root {
  /* neutral — paper, cards, hairlines, body text. 7.49:1 on paper, 7.94:1 on card. */
  --role-neutral-bg:      var(--sand-050);
  --role-neutral-surface: var(--white);
  --role-neutral-border:  var(--sand-200);
  --role-neutral-text:    var(--bark-700);

  /* brand — the petrol the mark and the headings are made of. 9.69:1 on paper. */
  --role-brand-bg:        var(--sand-050);
  --role-brand-surface:   var(--white);
  --role-brand-border:    var(--petrol-050);
  --role-brand-text:      var(--petrol-800);

  /* accent — brand used as an interactive colour, with its own soft ground. 7.73:1 text on surface. */
  --role-accent-bg:       var(--sand-050);
  --role-accent-surface:  var(--petrol-050);
  --role-accent-border:   var(--petrol-050);
  --role-accent-text:     var(--petrol-800);

  /* cta — the one warm call to action. Ink is petrol-900, not white: white on coral is 2.4:1. 6.11:1. */
  --role-cta-bg:          var(--sand-050);
  --role-cta-surface:     var(--coral-500);
  --role-cta-border:      var(--coral-500);
  --role-cta-text:        var(--petrol-900);

  /* info — a state that asks nothing of the reader. The quiet one. 5.30:1 text on surface. */
  --role-info-bg:         var(--sand-050);
  --role-info-surface:    var(--slate-100);
  --role-info-border:     var(--slate-100);
  --role-info-text:       var(--slate-700);

  /* success — it is running, it passed, it is well. 5.28:1 text on surface. */
  --role-success-bg:      var(--sand-050);
  --role-success-surface: var(--green-050);
  --role-success-border:  var(--green-050);
  --role-success-text:    var(--green-700);

  /* warning — a decision awaits the reader. Amber is reserved for that and for nothing else. 6.46:1 on paper. */
  --role-warning-bg:      var(--sand-050);
  --role-warning-surface: var(--rust-100);
  --role-warning-border:  var(--rust-100);
  --role-warning-text:    var(--rust-700);

  /* danger — it failed, or it is about to be destroyed. 5.65:1 text on surface. */
  --role-danger-bg:       var(--sand-050);
  --role-danger-surface:  var(--red-050);
  --role-danger-border:   var(--red-050);
  --role-danger-text:     var(--red-700);

  /* unknown — FORGE DOES NOT KNOW. Not "fine", not "broken": the read did not happen or did not answer.
   * Deliberately warm-neutral and deliberately NOT the info grey, because "nothing to do here" and "nobody
   * looked" are the two states this estate has most often collapsed into one. 5.60:1 text on surface. */
  --role-unknown-bg:      var(--sand-050);
  --role-unknown-surface: var(--sand-100);
  --role-unknown-border:  var(--sand-200);
  --role-unknown-text:    var(--bark-600);

  /* stale — there IS a reading and it is past its interval. Amber-adjacent, never green, never alarm red:
   * an old reading asks for a second look, it does not announce a fault. 5.99:1 text on surface. */
  --role-stale-bg:        var(--sand-050);
  --role-stale-surface:   var(--amber-050);
  --role-stale-border:    var(--amber-050);
  --role-stale-text:      var(--amber-700);

  /* the three spaces. Marks, not text — the floor holds these at 3:1, and dk is the tight one at 3.39:1. */
  --role-space-se:        var(--petrol-800);
  --role-space-no:        var(--petrol-600);
  --role-space-dk:        var(--petrol-400);

  /* ── the legacy names, aliased ──────────────────────────────────────────────────────────────────────
   * Every name the nine copies used, pointed at the role it always meant. They keep working so that
   * linking this file and deleting the copies were one reviewable step rather than a rewrite of every
   * rule on every page. New CSS should reach for a role or a component token; these stay until the pages
   * are converted, and the CSS structure gate will notice if one is dropped while still in use.
   * Two divergences were settled here rather than preserved: --slate is #585044 (the seven-page value;
   * the refusal pages' #5A6B70 rose from 5.26:1 to 7.49:1) and --live-soft is #dceee5 (the four-page
   * value; the report's .tally reads 5.28:1 rather than 5.44:1, both well clear). */
  --paper:       var(--role-neutral-bg);
  --card:        var(--role-neutral-surface);
  --ink:         var(--role-brand-text);
  --slate:       var(--role-neutral-text);
  --hair:        var(--role-neutral-border);
  --hair-2:      var(--role-accent-surface);
  --accent:      var(--role-accent-text);
  --accent-soft: var(--role-accent-surface);
  --cta:         var(--role-cta-surface);
  --cta-ink:     var(--role-cta-text);
  --live:        var(--role-success-text);
  --live-soft:   var(--role-success-surface);
  --idle:        var(--role-info-text);
  --idle-soft:   var(--role-info-surface);
  --attn:        var(--role-warning-text);
  --attn-soft:   var(--role-warning-surface);
  --crit:        var(--role-danger-text);
  --crit-soft:   var(--role-danger-surface);
  --soon:        var(--role-stale-text);
  --soon-soft:   var(--role-stale-surface);
  --se:          var(--role-space-se);
  --no:          var(--role-space-no);
  --dk:          var(--role-space-dk);
  --sans:        var(--font-sans);
  --serif:       var(--font-serif);
  --mono:        var(--font-mono);
}

/* ── 3 · COMPONENT ────────────────────────────────────────────────────────────────────────────────────
 * The four shapes the console actually draws. A component token names a DECISION about a part — what a
 * card is made of, how round a pill is — so that changing the decision is one edit rather than a search.
 * Only what is used is here: §4.3's full component set is UX-15's and UX-16's work, and a token nobody
 * reads is a token nobody maintains.
 *
 * SIZES ARE STILL px HERE. §4.2 wants rem and the CSS structure gate ratchets the px list downwards; the
 * conversion is a visual change and belongs in its own landing, not smuggled into the one that moves the
 * colours. The gate records the count so the debt is visible rather than remembered.
 */
:root {
  /* pill — the status word. Never a colour alone; the word is the state and the colour agrees with it. */
  --pill-radius:     999px;
  --pill-pad:        3px 9px;
  --pill-font:       var(--font-mono);
  --pill-font-size:  11px;
  --target-min:      44px;    /* the smallest a control that is pressed may be, on either axis: floor-visual measures against 44, not WCAG 2.2's 24, by choice */

  /* card — an app, a rule, a panel. */
  --card-surface:    var(--role-neutral-surface);
  --card-border:     var(--role-neutral-border);
  --card-radius:     14px;

  /* button — the quiet one and the one call to action. */
  --button-surface:         var(--role-neutral-surface);
  --button-text:            var(--role-brand-text);
  --button-border:          var(--role-neutral-border);
  --button-radius:          9px;
  --button-primary-surface: var(--role-cta-surface);
  --button-primary-text:    var(--role-cta-text);

  /* hairline — the rule that separates two things without shouting about it. */
  --hairline:      1px solid var(--role-neutral-border);
  --hairline-soft: 1px solid var(--role-accent-surface);
}

/* ── 3b · THE SEASON ──────────────────────────────────────────────────────────────────────────────────
   A skin is a season, not a setting: the console sets data-skin="autumn" on <html> between 22 September and
   20 December. It changes the paper and nothing a sentence is written in. */
:root[data-skin="autumn"] {
  --paper: var(--autumn-paper);
}

/* ── 4 · THE MOTION FLOOR ──────────────────────────────────────────────────────────────────────────────
   Seven motion declarations existed across the console and not one `prefers-reduced-motion` guard, so a
   person who has asked their operating system to stop moving things was moved anyway. That setting is not
   a preference about taste; for vestibular disorders and for migraine it is the difference between using
   a screen and leaving it, and a clinician reads this console on a bad day like anyone else.

   It sits HERE rather than beside each transition because the floor is a property of the platform, not of
   a page. Every surface links this file before anything it paints with (UX-14 check 3), so a page written
   next month inherits the guard without its author knowing the rule exists — which is the only version of
   a floor that holds.

   `0.01ms` rather than `0`: a duration of zero makes some browsers skip the `transitionend` event that
   script may be waiting on, so the motion is removed and the code that ran after it still runs. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
