/* ============================================================================
   dira — design tokens
   Direction: "Bearing" — A3's instrument grammar for the graph, A2's
   typographic calm for the reasoning. The chain tells you what the tool is;
   the prose tells you what was decided.

   Every color token carries an explicit light AND dark value. Dark is designed
   at the same hue and lower lightness, never inverted. No #000, no #fff.

   Consumed by every screen in docs/design/screens/. A hardcoded hex or px in a
   screen is a defect — see DESIGN.md.
   ============================================================================ */

/* ---- the serif, self-hosted (dec-0016) -------------------------------------
   TeX Gyre Pagella, subsetted. Three faces, 60.6 KB total, embedded in the Go
   binary via embed.FS and served from it — no network call, so int-0002's and
   cst-0004's offline guarantee is untouched. dec-0012 narrows to "no NETWORK
   webfont"; this is not one.

   The URL is relative, and deliberately so: `../../assets/fonts/` is the one
   form that resolves in all three places this file is read from.

     docs/design/tokens.css over file://       -> <repo>/assets/fonts/...
     /docs/design/tokens.css over the harness  -> /assets/fonts/...
     /tokens.css served by `dira ui`           -> /assets/fonts/... (RFC 3986
       discards the .. segments that would climb above the root, and the Go
       server registers exactly that path from the embedded copy)

   font-display: block, not swap. Swap paints the fallback first and reflows
   when the real face arrives, which is a layout shift render.mjs would fail —
   and would be a genuine defect besides, since the fallback's metrics are the
   thing this decision exists to stop shipping. Loaded from memory over
   loopback, the block period is not perceptible. */
@font-face {
  font-family: "TeX Gyre Pagella";
  src: url("../../assets/fonts/pagella-regular.core.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: "TeX Gyre Pagella";
  src: url("../../assets/fonts/pagella-italic.core.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: block;
}
@font-face {
  font-family: "TeX Gyre Pagella";
  src: url("../../assets/fonts/pagella-bold.core.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

:root {
  color-scheme: light dark;

  /* ---- HUE BUDGET: three saturated hues ----------------------------------
     bearing   the instrument — current focus, brand, links, active intents,
               AND the WITHHELD resolution state (see below).
     converged runtime state only — accepted, converged, achieved
     caught    drift & contradiction ONLY. Never decoration, never a rejected
               alternative (a refusal is a record, not an alarm).
     A fourth hue requires a documented role. A hue with no role fails review.

     ONE DOCUMENTED EXCEPTION, approved 2026-07-30. --bearing carries a second
     meaning: withheld. The rule was one meaning per hue and this breaks it,
     recorded here rather than quietly widened. The defence is that withheld is
     the instrument pointing at something it can name and cannot open — the
     needle is still on it — and that the alternatives were worse: --caught is
     forbidden (withheld is not drift, Law 1), --converged would claim a
     resolution that did not happen, and a fourth hue would have to earn a slot
     for a state whose whole design brief is to be unremarkable.
     The cost, stated: a decision page with a withheld parent now carries
     bearing in four roles at once — the invocation argument, the id chips, the
     links, and this. It is an exception, not a precedent. */
  --bearing:      #8a5f18;
  --bearing-lift: #6d4a12;   /* hover/focus — MORE contrast than rest.
                                In LIGHT that means DARKER; in dark, lighter.
                                A single "lift" direction cannot serve both. */
  --converged:    #1f6d5b;
  --caught:       #a83828;

  /* ---- surfaces: warm paper, hued, never pure ---- */
  --ground:  #f7f4ed;
  --panel:   #fffdf8;
  --sunk:    #ece7db;   /* the chain block, inputs, wells */
  --rule:    #ddd7c8;
  --rule-soft: #ebe6d9;

  /* ---- ink ---- */
  --ink:     #23211d;
  --ink-mid: #55514a;
  --ink-low: #645f55;

  /* ---- type: 2 families + mono ----
     serif  display & prose — the record's voice
     ui     chrome, labels, buttons (never display)
     mono   ids, the chain, numerics */
  /* The embedded face leads. The old system stack stays behind it, unchanged,
     so a build that somehow ships without the woff2 still renders something
     Palatino-shaped rather than nothing (dec-0016). It is a backstop, not the
     design: the ratios below were tuned against these metrics, and Pagella is a
     Palatino metric clone, so the two agree. */
  --serif: "TeX Gyre Pagella", "Palatino", "Palatino Linotype", "Book Antiqua", Georgia, serif;
  --ui: system-ui, -apple-system, "Helvetica Neue", sans-serif;
  --mono: "SF Mono", "Menlo", "Monaco", "Consolas", monospace;

  /* ---- THE TYPE SCALE: 9 values, not 16 ----
     Audited 2026-07-30: one screen carried sixteen distinct sizes, which gave the
     two supporting faces eleven volumes instead of a register. Collapsed to
     four serif, three sans, two mono. A size not on this list is a defect.

     serif  - display / lede / body / small
     sans   - ui / secondary / label
     mono   - code / chip                                                     */
  --t-display: clamp(32px, 5.6vw, 52px);  /* the ruling; the frame's anchor */
  --t-lede:    19px;                      /* the because */
  --t-body:    16.5px;                    /* prose, grounds, alternative names */
  --t-small:   15px;                      /* serif in tight contexts */
  --t-ui:      13.5px;                    /* chrome, buttons, body sans */
  --t-sub:     12.5px;                    /* secondary sans */
  --t-label:   11px;                      /* uppercase, tracked */
  --t-mono:    13px;                      /* the chain, ids */
  --t-chip:    11.5px;                    /* chips, kv rows */

  /* ---- MEASURE: every text block gets one, not just the serif ----
     Audited 2026-07-30: every max-width in the system was on a serif element,
     so the SMALLEST type ran the LONGEST lines - a 12.5px line at 1003px wide,
     and a 121-character caption in the page's smallest text. Measure varied
     39 to 109 characters on a single screen. These are the ceilings. */
  --m-prose:   64ch;   /* serif body */
  --m-lede:    60ch;
  --m-ui:      70ch;   /* 13.5px sans */
  --m-sub:     74ch;   /* 12.5px sans - shorter line, more characters per em */
  --m-mono:    82ch;

  /* ---- spacing scale — nothing off-scale ---- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 18px;
  --s5: 28px; --s6: 40px; --s7: 60px; --s8: 96px;

  /* ---- concentric radii: inner ~= half outer ---- */
  --r-shell: 14px; --r-card: 7px; --r-chip: 4px;

  /* ---- motion: two curves, one signature ---- */
  --ease: cubic-bezier(.32, .72, 0, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --dur: .28s;
}

/* dark scheme — same hues, lower lightness, designed not inverted */
@media (prefers-color-scheme: dark) {
  :root {
    --bearing:      #c99a3e;
    --bearing-lift: #e2b95f;
    --converged:    #45a189;
    --caught:       #d97060;

    --ground:  #0f151c;
    --panel:   #161e27;
    --sunk:    #0b1118;
    --rule:    #27313d;
    --rule-soft: #1d2530;

    --ink:     #e9e6df;
    --ink-mid: #a6aeb8;
    --ink-low: #808a95;
  }
}

/* explicit overrides so a theme toggle wins in BOTH directions */
:root[data-theme="dark"] {
  --bearing:#c99a3e; --bearing-lift:#e2b95f; --converged:#45a189; --caught:#d97060;
  --ground:#0f151c; --panel:#161e27; --sunk:#0b1118; --rule:#27313d; --rule-soft:#1d2530;
  --ink:#e9e6df; --ink-mid:#a6aeb8; --ink-low:#808a95;
}
:root[data-theme="light"] {
  --bearing:#8a5f18; --bearing-lift:#6d4a12; --converged:#1f6d5b; --caught:#a83828;
  --ground:#f7f4ed; --panel:#fffdf8; --sunk:#ece7db; --rule:#ddd7c8; --rule-soft:#ebe6d9;
  --ink:#23211d; --ink-mid:#55514a; --ink-low:#645f55;
}

/* ============================ base ======================================== */

* { box-sizing: border-box; margin: 0; }

body {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--ui);
  font-size: var(--t-ui);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
  font-variant-numeric: tabular-nums;
}

a { color: var(--bearing); }
a:hover { color: var(--bearing-lift); }
:focus-visible {
  outline: 2px solid var(--bearing-lift);
  outline-offset: 2px;
  border-radius: 3px;
}

code, .mono { font-family: var(--mono); font-size: var(--t-mono); }

/* reduced motion: the END state is the base style, so content is always
   visible — print, no-JS and reduced-motion all show the real thing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}

/* ========================= shared components ============================== */

/* --- chrome: one hairline, no blur, no shadow --- */
.topbar {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s3) var(--s5);
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}
.wordmark {
  font-family: var(--serif); font-size: 20px; letter-spacing: .01em;
  color: var(--ink); text-decoration: none;
}
.wordmark b { color: var(--bearing); font-weight: 400; }
.crumb { font-family: var(--mono); font-size: var(--t-chip); color: var(--ink-low); }
.crumb a { color: var(--ink-mid); text-decoration: none; }
.crumb a:hover, .crumb a:focus-visible { color: var(--bearing-lift); text-decoration: underline; }
.topbar .spacer { flex: 1; }

/* --- the invocation line: this IS the CLI --- */
.invoke {
  display: flex; gap: var(--s2); align-items: baseline;
  font-family: var(--mono); font-size: 14px;
  padding-bottom: var(--s4); margin-bottom: var(--s5);
  border-bottom: 1px solid var(--rule);
  overflow-x: auto;
}
.invoke .p { color: var(--converged); flex: none; }
.invoke .c { color: var(--ink); white-space: nowrap; }
.invoke .a { color: var(--bearing); white-space: nowrap; }

/* --- THE CHAIN: the graph as real, selectable, copyable type ---
   Rebuilt 2026-07-30. It previously drew its verticals with box-drawing glyphs
   at line-height 1.9, which meant a 24.7px line box containing a glyph drawn at
   its own height: roughly 12px of dead space between every pair of rows. The
   column of verticals rendered as a DASHED run of tick marks. In a real
   terminal - the exact thing this element claims byte-identity with - those
   rules are unbroken. The signature element of the whole direction was
   rendering broken, and it was invisible at 1x.

   The rules are now a CSS border-left on nested rows. Law 3 survives intact:
   every id, reason, mark and status is still selectable text. The vertical rule
   was never content - it was punctuation for a fixed-width medium, and a border
   is the honest way to draw it in one that is not.

   This also answers the collapse-at-scale question for free: a nested structure
   takes <details>/<summary> for depth with no JavaScript at all. */
.chain {
  background: var(--sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-shell);
  padding: var(--s5);
  font-family: var(--mono); font-size: var(--t-mono); line-height: 1.65;
  overflow-x: auto;
  max-width: var(--m-mono);
}
/* a row and its children; the border IS the connector.
   NOTE: do NOT blank the border on :last-child. That targets the CONTAINER of a
   group, not the last row in it, and removing it deletes the whole vertical --
   which is exactly the bug the border was introduced to fix. The elbow on the
   final row reads as the terminus on its own. */
.chain .node { padding-left: var(--s4); border-left: 1px solid var(--rule); margin-left: 3px; }
.chain .row { display: flex; gap: var(--s2); align-items: baseline; white-space: pre; position: relative; }
/* the elbow: a short horizontal rule meeting the vertical, drawn not typed */
.chain .node > .row::before {
  content: ""; position: absolute; left: calc(var(--s4) * -1); top: .72em;
  width: calc(var(--s4) - 4px); height: 1px; background: var(--rule);
}
.chain .ln { display: block; white-space: pre; }
.chain .tree { color: var(--ink-low); }
.chain .id { color: var(--bearing); }
.chain .id-q { color: var(--ink-mid); }
/* a withheld parent inside the chain: the ref is real, so it is set in the
   instrument hue exactly like any other id. The trailing mark says the body is
   not here. NOTE the row carries no explanatory clause — "private ledger" and
   "⊙ withheld" are one statement, not two; the sentence that explains it lives
   once, in the .arising line, and is not repeated in the chain. */
.chain .id-w { color: var(--bearing); }
.chain .wh-tag { color: var(--bearing); }
.chain .ok { color: var(--converged); }
.chain .no { color: var(--ink-low); }        /* a refusal is a record, not an alarm */
.chain .txt { color: var(--ink); }
.chain .dim { color: var(--ink-mid); }
.chain .lo { color: var(--ink-low); }

/* --- chips: label + hue, never hue alone --- */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--mono); font-size: var(--t-chip); letter-spacing: .03em;
  padding: 2px 7px; border-radius: var(--r-chip); white-space: nowrap;
}
.chip-id {
  color: var(--bearing);
  background: color-mix(in oklab, var(--bearing) 5%, transparent);
  border: 1px solid color-mix(in oklab, var(--bearing) 30%, transparent);
}
.chip-accepted { color: var(--converged); background: color-mix(in oklab, var(--converged) 6%, transparent); }
.chip-open { color: var(--ink-mid); background: color-mix(in oklab, var(--ink-mid) 6%, transparent); }
.chip-staged { color: var(--bearing); background: color-mix(in oklab, var(--bearing) 6%, transparent); }
.chip-date { color: var(--ink-low); }
/* withheld carries a hairline where the other state chips do not, because it is
   the one state that names a thing you cannot open: the border is the edge of
   what this repo will show. Tint stays at 7% — --bearing on a 13% --bearing
   tint over --panel measures 4.11:1 in light, under the floor, and the token
   matrix structurally cannot see that pair. contrast-rendered.mjs can. */
.chip-withheld {
  color: var(--bearing);
  background: color-mix(in oklab, var(--bearing) 7%, transparent);
  border: 1px solid color-mix(in oklab, var(--bearing) 30%, transparent);
}

/* --- section label --- */
.seclabel {
  font-family: var(--ui); font-size: var(--t-label);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-low); font-weight: 600;
  display: flex; align-items: center; gap: var(--s3);
  margin-bottom: var(--s4);
}
.seclabel::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

/* --- card --- */
.card {
  background: var(--panel); border: 1px solid var(--rule);
  border-radius: var(--r-card); padding: var(--s4);
}
.card > h3 {
  font-family: var(--ui); font-size: 10.5px; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-low);
  font-weight: 600; margin-bottom: var(--s3);
}

/* --- RESOLUTION STATES: three, and exactly one of them is drift -----------
   dec-0011 settles the model; this is its only visual. Every upward edge
   resolves to one of:

     oriented   parent present and readable here        ✓  --converged
     withheld   parent declared, body not published     ⊙  --bearing
     orphan     no parent recorded at all               ⚠  --caught   ← drift

   Law 1 restated on this axis: --caught belongs to orphan and to nothing else
   here. Withheld must read as neither an error nor a warning, which is why it
   is drawn at CHIP weight and never gets a tinted card surface — orphan is the
   only state that changes the colour of the ground it sits on. If withheld
   ever becomes the heaviest thing in a chain, this rule has been broken.
   Grep test: --caught appears only in .drift, .res-orphan and check states. */
.res { display: flex; align-items: baseline; gap: var(--s2); }
.res .mk { font-family: var(--mono); font-size: var(--t-mono); flex: none; color: var(--ink-low); }
.res .nm {
  font-family: var(--ui); font-size: var(--t-label); letter-spacing: .14em;
  text-transform: uppercase; font-weight: 600; color: var(--ink-low);
}
.res-oriented .mk, .res-oriented .nm { color: var(--converged); }
.res-withheld .mk, .res-withheld .nm { color: var(--bearing); }
.res-orphan   .mk, .res-orphan   .nm { color: var(--caught); }

/* --- the ONLY red on any screen --- */
.drift {
  border: 1px solid color-mix(in oklab, var(--caught) 42%, var(--rule));
  background: color-mix(in oklab, var(--caught) 3%, var(--panel));
  border-radius: var(--r-card); padding: var(--s4);
}
.drift h3, .drift .label { color: var(--caught); }
/* body copy in the drift card is INK, not caught. The hue lives in the label
   and the border; making the sentence red too both failed contrast (2.66:1 as
   composited) and spent the one alarm hue on ordinary prose. */
.drift p { font-size: var(--t-ui); color: var(--ink); max-width: var(--m-ui); }
.drift p b { color: var(--caught); }
.drift b { color: var(--caught); font-weight: 600; }

/* --- buttons --- */
.btn {
  font-family: var(--ui); font-size: var(--t-ui); font-weight: 500;
  padding: 9px 16px; border-radius: var(--r-chip);
  border: 1px solid var(--rule); background: var(--panel); color: var(--ink);
  cursor: pointer; min-height: 40px;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
  /* .btn is also applied to <summary> (distill.gohtml's no-JS edit
     disclosure). <button> centers its own text and hides list markers by
     UA default; <summary> does neither (display: list-item, left-aligned).
     Make the class itself own this instead of relying on the element.
     appearance: none strips the native control rendering <button> gets by
     default (form controls use platform text metrics for their box, not
     plain CSS line-height) so both elements size their content box the
     same way; without it a <button>'s native height and a <summary>'s
     CSS-computed height disagreed by ~1px even once both used the same
     line-height value. line-height is pinned explicitly for the same
     reason: 'normal' resolves per-element rather than to one shared
     number. */
  display: inline-flex; align-items: center; justify-content: center;
  text-align: center; list-style: none; line-height: 1.2;
  -webkit-appearance: none; appearance: none;
}
.btn::-webkit-details-marker { display: none; }
.btn:hover { border-color: var(--ink-low); background: var(--sunk); }
.btn-primary {
  background: var(--converged); border-color: var(--converged);
  color: var(--panel);
}
.btn-primary:hover { background: color-mix(in oklab, var(--converged) 84%, black); }
.btn-quiet { background: transparent; color: var(--ink-mid); }
.btn-quiet:hover { color: var(--ink); background: var(--sunk); }
@media (max-width: 767px) { .btn { min-height: 44px; padding: 11px 18px; } }

/* --- attribution: the loop, quietly --- */
.made {
  margin-top: var(--s8); padding-top: var(--s4);
  border-top: 1px solid var(--rule);
  font-family: var(--ui); font-size: var(--t-sub); color: var(--ink-low);
  max-width: var(--m-sub);
  display: flex; gap: var(--s2); flex-wrap: wrap; align-items: baseline;
}
.made a { color: var(--ink-mid); text-decoration: none; border-bottom: 1px solid var(--rule); }
.made a:hover, .made a:focus-visible { color: var(--bearing-lift); border-color: var(--bearing-lift); }
.made .sep { color: var(--rule); }
