/* ============================================================================
   mc-ui.css — Slipcurve shared design system
   ----------------------------------------------------------------------------
   A warm, light, professional canvas. Terracotta accent, sage second accent,
   one restrained alarm red. Caprasimo for display headings ONLY; Figtree for
   everything else. Linked by every module, so the look is defined in one place.

   HOW THIS FILE IS ORGANISED — read this before editing.

   1. @font-face          self-hosted from ../vendor/fonts/ (no CDN, no network)
   2. :root TOKENS        the central ramps: accent 100..900, accent-2
                          100..900, neutral 100..900, alarm 100..700.
   3. :root SEMANTICS     the construction status layer — critical-path,
                          near-critical, float-healthy, overdue, blocked,
                          approved, superseded. Derived from the ramps above,
                          never invented per-module. Then the CATEGORICAL
                          SERIES ramp (--series-1..8 and their fills) for
                          "float path 1..N", disciplines, resource types and
                          every other chart legend that is not a status.
   4. :root LEGACY REMAP  every variable the module pages already use
                          (--canvas --panel --bone --amber --teal --clay …)
                          re-pointed at the tokens above. THIS IS WHY THE WHOLE
                          APP GOES LIGHT FROM ONE FILE. Do not rename these and
                          do not delete them: renaming breaks every page at once
                          and smoke.py cannot see a purely visual break.
   5. everything else     base, type, layout, controls, tables, status chips,
                          compare table, spec list, info panel, floating windows.

   CONTRAST. Every text/background pair shipped here clears WCAG AA (4.5:1 body,
   3:1 large). The muted-on-card pair is the one that always fails elsewhere:
   --bone-faint is neutral-600 (5.24:1 on --panel), NOT neutral-500 (3.39:1).
   neutral-400/500 and accent-500 are BORDER AND FILL COLOURS ONLY — never text.

   NUMBERS. This is a scheduling and cost product. Every numeric surface carries
   font-variant-numeric: tabular-nums so digits line up in a column.
   ============================================================================ */

/* ---- 1. fonts ------------------------------------------------------------ */
/* Vendored under vendor/fonts/ (SIL OFL 1.1 — see vendor/fonts/README.md).
   Paths are relative to THIS file, so they resolve identically from
   slipcurve-shell.html at the root and from every modules/*.html. */
@font-face{font-family:'Figtree';font-style:normal;font-weight:300 900;font-display:swap;
  src:url('../vendor/fonts/figtree-0.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Figtree';font-style:normal;font-weight:300 900;font-display:swap;
  src:url('../vendor/fonts/figtree-1.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2190-2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Caprasimo';font-style:normal;font-weight:400;font-display:swap;
  src:url('../vendor/fonts/caprasimo-0.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Caprasimo';font-style:normal;font-weight:400;font-display:swap;
  src:url('../vendor/fonts/caprasimo-1.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2190-2193,U+2212,U+2215,U+FEFF,U+FFFD;}

:root{
  /* ---- 2. TOKENS — the full ramps. Do not invent colours outside these. ---- */
  --color-bg:#f5ead8;
  --color-card:#fbf4e7;
  --color-text:#201e1d;

  --color-accent-100:#f7e4d4; --color-accent-200:#eeceb2; --color-accent-300:#e4b58e;
  --color-accent-400:#d99465; --color-accent-500:#c67139; --color-accent:#c67139;
  --color-accent-600:#a95c2b; --color-accent-700:#8a4a22; --color-accent-800:#6d3a1b;
  --color-accent-900:#532c15;

  --color-accent-2-100:#e9ecdf; --color-accent-2-200:#d5dbc2; --color-accent-2-300:#bec7a3;
  --color-accent-2-400:#9cab7f; --color-accent-2-500:#7a8a5e; --color-accent-2:#7a8a5e;
  --color-accent-2-600:#64734c; --color-accent-2-700:#505c3d; --color-accent-2-800:#3f4930;
  --color-accent-2-900:#303724;

  --color-neutral-100:#efe6d5; --color-neutral-200:#e4d8c3; --color-neutral-300:#d3c5ac;
  --color-neutral-400:#b5a78d; --color-neutral-500:#91836b; --color-neutral-600:#6f6553;
  --color-neutral-700:#554d40; --color-neutral-800:#3c362d; --color-neutral-900:#27231d;

  /* one restrained alarm red, ramped so a tint and an ink both exist */
  --color-alarm-100:#f7dedb; --color-alarm-200:#f0cdc7; --color-alarm-300:#dc9c92;
  --color-alarm-500:#a5281c; --color-alarm-600:#8e2117; --color-alarm-700:#741a13;

  --font-display:'Caprasimo',Georgia,serif;      /* h1 / .display ONLY */
  --font-body:'Figtree',system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;

  --radius-lg:16px; --radius-md:12px; --radius-sm:8px; --radius-pill:999px;
  --shadow-sm:0 1px 3px rgba(32,30,29,.10);
  --shadow-md:0 4px 14px rgba(32,30,29,.12);
  --shadow-lg:0 12px 40px rgba(32,30,29,.22);
  --shadow-xl:0 18px 60px rgba(32,30,29,.55);          /* lifted media */
  --shadow-grab:0 0 0 1.5px rgba(32,30,29,.28),0 2px 6px rgba(32,30,29,.25);

  /* ---- 2b. TRANSLUCENT SURFACES -------------------------------------------
     A scrim, a wash and a drop shadow are --color-text (or an accent) at low
     alpha. Written inline in a module as rgba() they are that token COPIED BY
     VALUE, and they stop following it the day the token moves — verbatim the
     failure CLAUDE.md names. They live here for exactly the reason --shadow-*
     above already does: this is the one file that may define a colour.

     WHY A TOKEN AND NOT var(--color-text) AT THE CALL SITE. CSS cannot apply an
     alpha to a hex custom property without color-mix(), and color-mix() is
     itself a colour literal by the ratchet's own reckoning (see FUNC_COLOUR_RE
     in tools/consistency.py). A named token in THIS file is the only construct
     that puts the value in one place AND keeps every page at zero residue.

     KEEP THE CHANNELS IN STEP BY HAND — nothing checks the correspondence:
       32,30,29    = --color-text        #201e1d
       198,113,57  = --color-accent-500  #c67139
       122,138,94  = --color-accent-2-500 #7a8a5e                            */
  --wash-accent:rgba(198,113,57,.10);    /* accent-500   @10% — hero gradient */
  --wash-accent-2:rgba(122,138,94,.10);  /* accent-2-500 @10% — hero gradient */
  --wash-faint:rgba(32,30,29,.045);      /* non-working day band on a chart   */
  --wash-soft:rgba(32,30,29,.14);        /* hover tint on an unfilled control */
  --rule-ink:rgba(32,30,29,.35);         /* hairline over an UNKNOWN fill —
                                            a material swatch, a photograph   */
  --scrim-soft:rgba(32,30,29,.42);       /* dim the page behind a slide-in rail*/
  --scrim:rgba(32,30,29,.62);            /* modal dim — page still legible    */
  --scrim-deep:rgba(32,30,29,.80);       /* chrome sitting ON a dark canvas   */
  --scrim-media:rgba(32,30,29,.90);      /* lightbox: the image IS the page.
                                            One value for one component — four modules carried .86/.88/.90/.93 of
                                            the same scrim before this.       */
  --scrim-veil:rgba(32,30,29,.94);       /* loader concealing a 3-D screen    */
  --paper:#fff;                          /* the print sheet — see § 22        */
  --cad-outline:var(--color-text); --cad-hidden:var(--bone-dim); --cad-centre:var(--color-accent-700);
  --cad-envelope:var(--st-critical-ink); --cad-hatch:var(--rule-ink); --cad-text:var(--color-text);

  /* ---- 2b-2. THE CONTROL BOUNDARY -----------------------------------------
     A DECORATIVE RULE AND A CONTROL BORDER ARE TWO DUTIES, AND ONE TOKEN WAS
     DOING BOTH. --line (§ 4) is --color-neutral-300 and is right for the first:
     a hairline separating content that POSITION already separates may be faint,
     and 1.4.11 does not apply to it. It is wrong for the second, because a
     control border is the only thing saying the control is there, and 1.4.11
     asks that boundary for 3:1 against every adjacent colour.
     --color-neutral-300 measured 1.554:1 on --color-card and 1.428:1 on
     --color-bg. That is not a boundary, it is a suggestion.

     THE RAMP STEP IS NOT RE-POINTED. Darkening --color-neutral-300 to fix a
     button would darken every table rule, tag outline and scrollbar with it.
     Two duties, two names.

     PICKED BY MEASUREMENT, not by eye. Ratios are WCAG 2.x over sRGB relative
     luminance, computed on the shipped hexes, against every surface a LIVE
     control actually sits on in this tree: --paper, --color-card, --color-bg,
     --color-accent-100 (the info-panel and help-panel heads, § 18 and § 19 —
     both carry a button), --color-neutral-100 (--base / --panel2) and the five register row tints from § 3.

       candidate            hex      on card   on bg    WORST   worst surface
       --color-neutral-300  #d3c5ac   1.554    1.428    1.372   neutral-100
       --color-neutral-400  #b5a78d   2.162    1.988    1.909   neutral-100
       --color-neutral-500  #91836b   3.390    3.116    2.993   neutral-100
       --color-neutral-600  #6f6553   5.242    4.818    4.628   neutral-100

     REJECTED, recorded so they are not re-proposed:
       neutral-300  the value that was shipping. 1.372:1 worst.
       neutral-400  the obvious "one step darker", and 1.909:1 worst is nowhere
                    near. Interpolating 300-400 in sRGB runs 1.21 to 1.68
                    worst-case across all eleven steps, so nothing lighter
                    passes between the rungs either.
       neutral-500  3.390 on --color-card and 3.116 on --color-bg, so it clears
                    3:1 on both surfaces the audit named — and it is 2.993:1 on
                    --color-neutral-100, which is --panel2, a panel that hosts
                    controls. It fails by 0.007, on a glance it looks fine, and
                    it would have shipped. brand/animus/animus.css refused its
                    own .46 alpha at 2.99:1 for exactly this; so does this.
       accent-500   3.30 / 3.03 and 2.91 worst — the same near-miss, and it
                    tints every control on the page terracotta besides.

     So the lightest step on the shipped ramp that clears 3:1 on EVERY live
     surface is neutral-600, at 4.628:1 worst. A var() rather than a hex, so it cannot
     drift off the ramp and a ground that re-points the ramp without re-declaring this
     token still lands somewhere measured.

     A DISABLED CONTROL KEEPS --color-neutral-300, DELIBERATELY: 1.4.11 exempts an
     inactive component by name, and the § 9 disabled rules must NOT reach for this
     token — a dead control drawn at 4.6:1 looks live.

     brand/animus/animus.css DECLARES THIS SAME NAME, and that is the seam: its own
     --line-control at 4.06:1 worst on its own surface stack, loaded after this file on
     every module, so the Animus value wins there.
     (Its channels stay over there, where that colour is declared. A foreign
     triple retyped into a comment HERE is a copy that can go stale, and the
     rgba() check in tools/consistency.py refused it — correctly.) NAMING THE
     TOKEN IS WHAT LETS A THEME SWAP REACH THE DECLARATION rather than having to
     out-specify every rule: without the name, a (0,1,2) selector here punched
     straight through animus.css's (0,0,1) `button` and kept painting a 1.20:1
     border on that ground.                                                   */
  --line-control:var(--color-neutral-600);  /* control border. 4.628:1 worst  */

  /* ---- 2c. TERRAIN RAMPS — a measured surface drawn as a map ---------------
     Sits beside § 2b for the same reason § 2b exists: this is the one file that
     may spell a colour, and a page that draws 12,000 cells cannot reach for a
     literal per cell. A terrain page emits
     fill="var(--terrain-elev-3)" and constructs NO colour string in script —
     the imported page it replaces built sixteen of them with rgb(), which the
     ratchet counts by notation and which a new file may not carry at all.

     These are NOT semantic status (§ 3) and NOT a categorical series (§ 3b).
     They are the drapes of one continuous quantity each, and they are ordered:
     -1 is the low end, -5 (or -3) the high end. Read them by index.

     WHAT THE HILLSHADE DOES WITH THEM. The relief is a second pass of cells
     over the drape, each one --terrain-shade or --terrain-light at an OPACITY
     computed from the survey's own slope and aspect planes. Opacity is a
     number, not a colour, so the shading follows the tokens rather than
     bypassing them; and because the two planes are measured, the relief is the
     ground rather than an estimate of it. (feDiffuseLighting over a bump map
     was the other candidate and was rejected on measurement: a per-cell alpha
     surface is a staircase, so the filter lights the CELL EDGES and not the
     hillside.) Both are opaque inks here — the alpha lives at the call site,
     which is legitimate on a fill, and is exactly what § 2b forbids for text.

     CUT AGAINST FILL IS THE ONE PAIR THAT MUST NEVER COLLAPSE. Cut takes the
     accent ramp and fill takes the slate-blue of --series-2: CIEDE2000 over the
     two mid steps is 46.9 normal, 27.1 deuteranopia, 23.8 protanopia. An
     accent-against-sage pair (the obvious warm/cool choice on this theme) falls
     to 8.1 under protanopia, and a drape carries no word beside it to recover
     the meaning — the legend is somewhere else on the page. The elevation ramp
     runs low green to high pale sand so it also separates on LIGHTNESS, which
     is what survives every dichromacy and a black-and-white print.

     --terrain-nodata IS NOT A VALUE. It is what a cell reads when the survey
     did not measure it — outside the flown swath, or where the packed uint8
     slope saturated. It is deliberately the only unsaturated colour in the set,
     so an unmeasured cell cannot be mistaken for a low reading on any drape
     (trap 10, in a raster). Do not give it a hue.                            */
  --terrain-elev-1:#54683f; --terrain-elev-2:#82914f; --terrain-elev-3:#b8ac6b;
  --terrain-elev-4:#d8a765; --terrain-elev-5:#f2e6cd;
  --terrain-cut-1:#e4b58e;  --terrain-cut-2:#c67139;  --terrain-cut-3:#8a4a22;
  --terrain-fill-1:#a8bcd8; --terrain-fill-2:#5f83b4; --terrain-fill-3:#1d477b;
  --terrain-base:#d3c5ac;             /* ground the earthworks do not touch   */
  --terrain-slope-1:#d5dbc2; --terrain-slope-2:#e4b58e; --terrain-slope-3:#a5281c;
  /* FBP fuel classes in the payload's OWN code order 0..4 — NF, C7, D1, O1a,
     O1b — so the page indexes rather than translates. Assigned BY RULE, never
     surveyed: the page that draws them carries that sentence on the page. */
  --terrain-fuel-1:#a89f92; --terrain-fuel-2:#37542f; --terrain-fuel-3:#7f6a33;
  --terrain-fuel-4:#c2b276; --terrain-fuel-5:#ddca8d;
  --terrain-burn:#8e2117;             /* inside a real mapped fire perimeter  */
  --terrain-shade:#201e1d;            /* relief, dark side  — alpha at use    */
  --terrain-light:#fbf4e7;            /* relief, lit side   — alpha at use    */
  --terrain-nodata:#8d8578;           /* not a reading. See above.            */

  /* spacing scale — use these, not ad-hoc pixel values.

     THIS FILE OWNS THE NAME --sp-*, and the reason is that a spacing step is a
     METRIC, not a colour. A theme is entitled to re-point a colour under the
     name its pages already read; it is not entitled to re-point a number the
     rules were authored AROUND, because that does not restyle those rules, it
     moves them — every gap, pad and margin in the tree at once, with nothing
     throwing and no gate able to see it. brand/animus/animus.css declared a
     second, tighter scale under these same seven names and loads after this
     file everywhere, so every var(--sp-N) anybody had ever written rendered at
     half to two-thirds of the authored number.

     Both scales are wanted — this is a 4px base for a document, the Animus one
     is a tighter base for dense chrome — so both exist under names of their
     own: --sp-1..7 here, --as-0..10 in brand/animus/animus.css. Ask for the one
     you mean. Do not re-declare --sp-* in a later stylesheet, and do not add a
     compatibility alias between the two: an alias is the same collision with a
     longer name. */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:24px; --sp-6:32px; --sp-7:48px;

  --ease:cubic-bezier(.22,.61,.36,1);

  /* text that sits ON a filled accent/status swatch */
  --on-accent:#fdf8ef;

  /* ---- 3. SEMANTIC STATUS LAYER — the construction vocabulary --------------
     Each status is a pair: an INK (text, border, chart stroke) that is AA on
     both --color-card and --color-bg, and a TINT (chip / row background) that
     the ink is AA against. Use these instead of picking a colour per module.  */
  --st-critical-ink:#a5281c;  --st-critical-tint:#f7dedb;  /* on the critical path */
  --st-near-ink:#8a4a22;      --st-near-tint:#f7e4d4;      /* near-critical, watch */
  --st-healthy-ink:#505c3d;   --st-healthy-tint:#e9ecdf;   /* float healthy / on track */
  --st-overdue-ink:#8e2117;   --st-overdue-tint:#f0cdc7;   /* past its date */
  --st-blocked-ink:#554d40;   --st-blocked-tint:#e4d8c3;   /* blocked / held / on hold */
  --st-approved-ink:#3f4930;  --st-approved-tint:#d5dbc2;  /* approved / accepted */
  --st-superseded-ink:#6f6553;--st-superseded-tint:#efe6d5;/* superseded / void */
  /* ROW tints are deliberately lighter than chip tints. A chip only has to
     carry its own ink; a tinted table ROW has to carry EVERY ink that can land
     in it, including --bone-faint (the weakest text token we ship). At chip
     strength, --bone-faint on the overdue tint measures 3.9:1 and fails.
     These five all clear 4.5:1 for --bone-faint and for every status ink. */
  --st-critical-row:#fbebe8; --st-overdue-row:#f9e3df; --st-near-row:#fcf1e6;
  --st-healthy-row:#f1f4e9;  --st-blocked-row:#f2ece0;
  /* a failed feed is NOT a status — it is an absence. See trap 10: it renders
     as an alarm-shaped banner that names the feed, never as a zero. */
  --st-nodata-ink:#8e2117;    --st-nodata-tint:#f7dedb;

  /* ---- 3b. CATEGORICAL SERIES RAMP -----------------------------------------
     For a legend that is NOT a status: float path 1..N, constraint type,
     discipline, resource type, risk band, scenario. Nine modules used to carry
     a private array of hexes here; use these instead, in order, and the ratchet
     stays at zero for the file.

     --series-N      the INK. Stroke, line, dot, outline, and legend TEXT.
     --series-N-fill the FILL. An area, a bar, a filled swatch. Every fill
                     carries --color-text at >= 5.2:1, so a value printed
                     inside a filled bar is legible.

     HOW THESE WERE PICKED — by measurement, not by eye. Every number below is
     CIEDE2000 or a WCAG 2.x contrast ratio, computed over the shipped hexes,
     with dichromacy simulated by the Machado/Oliveira/Fernandes (2009) linear-
     RGB matrices at severity 1.0.

       ink         hex      L*    C*     h     on --color-bg  on --color-card
       1 terracotta #8f5a26 43.2  41.6   65.8      4.82            5.25
       2 slate blue #1d477b 29.9  34.1  278.2      7.88            8.57
       3 teal       #257372 44.0  24.2  195.3      4.68            5.09
       4 bark       #484136 27.9   7.9   83.5      8.46            9.21
       5 sage       #536f58 44.1  18.1  148.1      4.66            5.07
       6 plum       #573148 25.8  22.0  341.8      9.10            9.90
       7 olive      #3a4101 25.8  35.9  109.0      9.11            9.91
       8 periwinkle #54659e 43.7  34.3  286.1      4.73            5.14

     AA. Worst ink is 4.66:1 on --color-bg and 5.07:1 on --color-card, so every
     one clears 4.5:1 as body-size TEXT and as a 1px STROKE on both surfaces.
     The ceiling is real: 4.5:1 on #f5ead8 caps an ink at L* 45.1, which is why
     none of these is light and why there is a separate fill ramp.

     COLOUR VISION. Worst pair, CIEDE2000, over all 28 pairs:
       normal 12.3 · deuteranopia 11.9 · protanopia 11.5 · tritanopia 7.4
     Restricted to series 1..4, which is what most charts here actually use:
       normal 21.3 · deuteranopia 17.8 · protanopia 17.7 · tritanopia 13.6
     Lightness does the work hue cannot: the ramp alternates L* 43-44 and
     L* 26-30, so 2/8 (slate blue L*29.9 vs periwinkle L*43.7 — the worst pair
     at every severity) stays apart on lightness after hue has collapsed.

     THE RED/GREEN PAIR, measured because it is the one that matters here.
     --st-critical-ink #a5281c against --st-healthy-ink #505c3d is CIEDE2000
     38.6 normal, 11.7 deuteranopia, 7.9 PROTANOPIA. That 7.9 is the weakest
     colour distinction anywhere in this file — weaker than any series pair.
     It is acceptable ONLY because the status layer never encodes meaning in
     colour alone: `.st` chips carry the word, `.sdot` sits beside the word.
     Keep it that way. If you are about to draw critical vs healthy as two bare
     swatches with no label, use --series-1 and --series-3 instead (17.7 under
     protanopia), or label them.

     BEYOND SIX SERIES, add a second channel — dash pattern, marker shape, a
     direct label. 11.5 is a usable separation, not a comfortable one, and no
     eight-colour ramp does much better inside an L* 26-45 band.               */
  --series-1:#8f5a26; --series-2:#1d477b; --series-3:#257372; --series-4:#484136;
  --series-5:#536f58; --series-6:#573148; --series-7:#3a4101; --series-8:#54659e;
  /* Fills are lighter siblings at the same hue; fill lightness tracks ink
     lightness so the pair reads as one series. Text-on-fill with --color-text
     measures 5.20-9.24:1. Fills are NOT a categorical channel on their own
     (worst pair 10.1 under protanopia, and only 4.9 under tritanopia) — always
     stroke a filled area with its own --series-N. */
  --series-1-fill:#eeb380; --series-2-fill:#819bd1; --series-3-fill:#8bcdcb;
  --series-4-fill:#9c9589; --series-5-fill:#adc8b1; --series-6-fill:#ac859b;
  --series-7-fill:#91945a; --series-8-fill:#b2bdf7;

  /* ---- 4. LEGACY REMAP — every var the 59 pages already use ----------------
     Keep this block. It is the whole reason a theme swap is one file edit.  */
  --canvas:var(--color-bg);
  --void:var(--color-bg);
  --base:var(--color-neutral-100);
  --panel:var(--color-card);
  --panel2:var(--color-neutral-100);
  --panel-2:var(--color-neutral-100);
  --line:var(--color-neutral-300);
  --line-soft:var(--color-neutral-200);
  --grid:rgba(32,30,29,.045);

  --bone:var(--color-text);              /* primary text            15.2:1 on card */
  --bone-dim:var(--color-neutral-700);   /* secondary text           7.6:1 on card */
  --bone-faint:var(--color-neutral-600); /* tertiary text            5.2:1 on card */

  /* the accent aliases land on the TERRACOTTA ramp, never on a yellow.
     --amber doubles as a fill; --on-accent is the text that goes on top of it. */
  --amber:var(--color-accent-700);
  --amber-bright:var(--color-accent-800);
  --amber-fill:var(--color-accent-500);  /* decorative fills, bars, diamonds     */
  --teal:var(--color-accent-2-700);      /* the "good / linked / official" accent */
  --clay:var(--color-alarm-500);         /* the "bad / critical / late" accent    */

  --disp:var(--font-body);               /* 85 legacy usages — Figtree, NOT Caprasimo */
  --body:var(--font-body);
  /* THIS FILE OWNS THE NAME --mono, and the reason is metrics again, one step
     along: a font's ADVANCE is a number too. Every var(--mono) site in the tree
     reads this file and each was sized against Figtree's tabular digits, so a
     face with a different advance re-flows a dense table authored to fit
     exactly — in EITHER direction, and nothing in this repo's toolchain
     measures a column width. Measured in Chromium at the 15px body size, ten
     tabular digits set 93.453px here and 90.000px in Geist Mono, so the
     re-point brand/animus/animus.css used to make was a 3.7% SQUEEZE on every
     numeric column in the product — not the widening its own note predicted.
     The direction is not the argument; that a theme moved a metric under the
     name the rules were authored against is. That file publishes the real
     monospace under a name of its own — --font-mono, aliased --as-mono — and a
     rule that wants Geist Mono asks by that name. site/site.css already does,
     as var(--font-mono,var(--mono)): that is the pattern, and it is why the
     site kept its monospace labels through the rename without being touched. */
  --mono:var(--font-body);               /* 710 legacy usages — Figtree + tabular-nums */

  --rail-w:296px; --bar-h:60px;
}

/* ---- 5. base ------------------------------------------------------------- */
*{box-sizing:border-box}
html,body{margin:0;background:var(--color-bg);color:var(--color-text);
  font-family:var(--font-body);font-size:15px;line-height:1.55;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}
body{padding:0 0 80px}
a{color:var(--amber);text-underline-offset:2px}
a:hover{color:var(--amber-bright)}
.wrap{max-width:1200px;margin:0 auto;padding:0 var(--sp-5)}
.hide{display:none!important}
.right{text-align:right}.center{text-align:center}
.teal{color:var(--teal)}.clay{color:var(--clay)}.amber{color:var(--amber)}.dim{color:var(--bone-dim)}
.faint{color:var(--bone-faint)}

/* numeric surfaces — digits must line up. */
.mono,.num,.stat,.money,.dur,.qty,.pct,td.num,.card .v,.stat .n,
input[type="number"],input[type="date"],time{font-variant-numeric:tabular-nums}
.mono{font-family:var(--font-body)}
.pre,pre,code,kbd,samp{font-family:var(--font-mono);font-variant-numeric:tabular-nums}

::selection{background:var(--color-accent-200);color:var(--color-text)}
:focus-visible{outline:2px solid var(--color-accent-600);outline-offset:2px;border-radius:4px}
*::-webkit-scrollbar{width:11px;height:11px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:var(--color-neutral-300);border-radius:8px;border:3px solid var(--color-bg)}
*::-webkit-scrollbar-thumb:hover{background:var(--color-neutral-400)}
html{scrollbar-color:var(--color-neutral-300) transparent}

/* ---- 6. header ----------------------------------------------------------- */
header.top{border-bottom:1px solid var(--color-neutral-200);background:var(--color-card);
  position:sticky;top:0;z-index:30}
header.top .wrap{display:flex;align-items:center;gap:15px;height:60px}
.brand{font-family:var(--font-body);font-weight:800;letter-spacing:.14em;font-size:13px;color:var(--color-text)}
.brand .dot{color:var(--color-accent-700)}   /* it is text, so it needs a text-grade ink */

/* THE HEADER MARK IS A MASK, NOT AN IMAGE, AND THAT IS THE WHOLE POINT.
   What stood here was the RETIRED hexagon -- <polygon points="270,80 426,170 ..."> --
   painted in #7a8a5e, an obsolete accent-2 value. It shipped on every page
   that carry class="brand" while brand/slipcurve-mark.svg, brand/slipcurve.ico and
   site/og-image.png had all moved to the wireframe tower. Three marks, and the one a
   user actually saw was the dead one. consistency.py could not catch it: it strips
   data: URIs before counting colour literals, deliberately, because a fixed asset is
   not a theme colour -- which is true right up until the asset is stale.
   A background-image cannot inherit, which is WHY the old one froze a hex. A mask can:
   the paint is background-color:currentColor, so the mark follows --color-text on both
   grounds and stops being a colour that has to be remembered. The cut is the compact
   4-plate one, which brand/ already assigns to 24-40px -- the header slot is 24px.
   Inside a mask the SVG is read as ALPHA, so its strokes are #fff and the ghost
   group's opacity="0.4" survives as 40% of the inherited ink.
   NOT COLOURED WITH THE SPECTRUM, and that was measured rather than chosen: colour on
   this wireframe fails below 40px (see tools/makeicon.py stops_for), and this is 24. */
.brand::before{content:"";display:inline-block;width:24px;height:24px;margin-right:10px;vertical-align:-7px;background-color:currentColor;-webkit-mask:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJNb250ZSBDYXJsbyI+PGcgc3Ryb2tlLXdpZHRoPSIxLjEiIG9wYWNpdHk9IjAuNSI+PHBhdGggZD0iTTIxLjY2IDUgTDEzLjgzIDYuNTUgTDEwLjM0IDMuMDYgTDE4LjE3IDEuNSBaIE0yMi4wOCAxMi44NyBMMjEuNjYgNSBNMTMuNjcgMTQuNTQgTDEzLjgzIDYuNTUgTTkuOTIgMTAuNzggTDEwLjM0IDMuMDYgTTE4LjMzIDkuMTEgTDE4LjE3IDEuNSIvPjwvZz48ZyBzdHJva2Utd2lkdGg9IjEuNCI+PHBhdGggZD0iTTIyLjkxIDI4LjYgTDEzLjM1IDMwLjUgTDkuMDkgMjYuMjMgTDE4LjY1IDI0LjM0IFogTTIyLjQ5IDIwLjczIEwxMy41MSAyMi41MiBMOS41MSAxOC41MSBMMTguNDkgMTYuNzIgWiBNMjIuMDggMTIuODcgTDEzLjY3IDE0LjU0IEw5LjkyIDEwLjc4IEwxOC4zMyA5LjExIFogTTIyLjkxIDI4LjYgTDIyLjQ5IDIwLjczIE0xMy4zNSAzMC41IEwxMy41MSAyMi41MiBNOS4wOSAyNi4yMyBMOS41MSAxOC41MSBNMTguNjUgMjQuMzQgTDE4LjQ5IDE2LjcyIE0yMi40OSAyMC43MyBMMjIuMDggMTIuODcgTTEzLjUxIDIyLjUyIEwxMy42NyAxNC41NCBNOS41MSAxOC41MSBMOS45MiAxMC43OCBNMTguNDkgMTYuNzIgTDE4LjMzIDkuMTEiLz48L2c+PC9zdmc+") center/contain no-repeat;mask:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJNb250ZSBDYXJsbyI+PGcgc3Ryb2tlLXdpZHRoPSIxLjEiIG9wYWNpdHk9IjAuNSI+PHBhdGggZD0iTTIxLjY2IDUgTDEzLjgzIDYuNTUgTDEwLjM0IDMuMDYgTDE4LjE3IDEuNSBaIE0yMi4wOCAxMi44NyBMMjEuNjYgNSBNMTMuNjcgMTQuNTQgTDEzLjgzIDYuNTUgTTkuOTIgMTAuNzggTDEwLjM0IDMuMDYgTTE4LjMzIDkuMTEgTDE4LjE3IDEuNSIvPjwvZz48ZyBzdHJva2Utd2lkdGg9IjEuNCI+PHBhdGggZD0iTTIyLjkxIDI4LjYgTDEzLjM1IDMwLjUgTDkuMDkgMjYuMjMgTDE4LjY1IDI0LjM0IFogTTIyLjQ5IDIwLjczIEwxMy41MSAyMi41MiBMOS41MSAxOC41MSBMMTguNDkgMTYuNzIgWiBNMjIuMDggMTIuODcgTDEzLjY3IDE0LjU0IEw5LjkyIDEwLjc4IEwxOC4zMyA5LjExIFogTTIyLjkxIDI4LjYgTDIyLjQ5IDIwLjczIE0xMy4zNSAzMC41IEwxMy41MSAyMi41MiBNOS4wOSAyNi4yMyBMOS41MSAxOC41MSBNMTguNjUgMjQuMzQgTDE4LjQ5IDE2LjcyIE0yMi40OSAyMC43MyBMMjIuMDggMTIuODcgTTEzLjUxIDIyLjUyIEwxMy42NyAxNC41NCBNOS41MSAxOC41MSBMOS45MiAxMC43OCBNMTguNDkgMTYuNzIgTDE4LjMzIDkuMTEiLz48L2c+PC9zdmc+") center/contain no-repeat}
.mtag{font-size:11px;font-weight:600;color:var(--bone-faint);border:1px solid var(--color-neutral-300);
  border-radius:var(--radius-pill);padding:3px 10px;font-variant-numeric:tabular-nums}
.spacer{flex:1}
/* docked inside the shell: the shell's module bar replaces the module brand strip */
html.mc-embedded header.top{display:none!important}

/* ---- 7. typography ------------------------------------------------------- */
/* Caprasimo is for display headings ONLY. Everything else is Figtree. */
/* FLUID: `Nonconformance` needs 268px at 32px in a 207px column, so the heading painted outside its box on eight pages. Still 32px from 480px up. */
h1,h1.title,.display{font-family:var(--font-display);font-weight:400;letter-spacing:0;
  font-size:clamp(23px,6vw,32px);line-height:1.12;margin:var(--sp-6) 0 var(--sp-1);
  overflow-wrap:break-word;color:var(--color-text)}
h2,h3,h4,h5{font-family:var(--font-body);font-weight:700;letter-spacing:-.005em;color:var(--color-text)}
/* the small uppercase kicker — the one place ALL-CAPS is allowed */
.eyebrow,.kicker,.card-kicker{font-size:11px;font-weight:700;text-transform:uppercase;
  letter-spacing:.09em;color:var(--bone-faint);margin:0 0 var(--sp-2)}
.lede{color:var(--bone-dim);font-size:16px;line-height:1.55;max-width:74ch;margin:0 0 var(--sp-5)}
.secp{color:var(--bone-dim);font-size:14px;line-height:1.55;max-width:80ch}
.note{font-size:12.5px;color:var(--bone-faint);margin-top:var(--sp-2);line-height:1.5}
/* font-family AND line-height ARE DECLARED HERE ON PURPOSE, AND OMITTING THEM
   WAS A REAL BUG. These two selectors are worn by SECTION TITLES, and those
   titles were <p> for most of this tree's life. A <p> inherits the body face
   and the body line-height, so the rule never needed to say either.
   The moment a title becomes a real <h2 class="panel-h"> - which it must, or a
   page exposes exactly one heading to a screen reader - it also becomes an
   h1..h6, and brand/animus/animus.css:587 sets h1..h6 to var(--font-display)
   with a snug line-height. CLASS specificity beats ELEMENT specificity only for
   properties the class actually DECLARES, so .panel-h won on weight and colour
   and SILENTLY LOST THE TYPEFACE: the label rendered in Tektur instead of the
   body face, on all 75 pages, and nothing threw and no gate measures a face.
   Declaring both makes <h2 class="panel-h"> genuinely identical to the <p> it
   replaced, which is what the heading conversion was promised to be. */
.chart-title,.panel-h{font-size:11.5px;font-weight:700;color:var(--bone-faint);
  font-family:var(--font-body);line-height:inherit;
  text-transform:uppercase;letter-spacing:.08em;margin:0 0 var(--sp-3)}
/* a heading row that carries its own info button on the right */
.panel-hd{display:flex;align-items:center;gap:var(--sp-2);margin:0 0 var(--sp-3)}
.panel-hd>.panel-h,.panel-hd>.chart-title,.panel-hd>h4{margin:0;flex:1}
/* the page head: kicker + h1 on the left, the page-level "i" on the right.
   It has to live here and not in header.top, because header.top is hidden
   whenever the module is docked inside the shell. */
.pagehead{display:flex;align-items:flex-end;gap:var(--sp-3);margin:var(--sp-6) 0 var(--sp-4)}
.pagehead>div:first-child{min-width:0}
.pagehead h1,.pagehead h1.title{margin:0}
.pagehead .eyebrow{margin:0 0 var(--sp-1)}
/* a compact control row: label, control, and the control's own "i" */
.ctlrow{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}

/* ---- 8. layout ----------------------------------------------------------- */
.panel,.card,.cardbox{background:var(--color-card);border:1px solid var(--color-neutral-200);
  border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);min-width:0}
.panel{padding:var(--sp-5);margin-top:var(--sp-4)}
.row{display:flex;gap:var(--sp-4);flex-wrap:wrap}.col{flex:1;min-width:0}
.section{margin-top:var(--sp-6)}
hr,.rule{border:none;border-top:1px solid var(--color-neutral-200);margin:var(--sp-5) 0}

/* ---- 9. buttons + inputs ------------------------------------------------- */
/* EVERY BORDER IN THIS SECTION IS A CONTROL BOUNDARY AND CARRIES WCAG 1.4.11 —
   see § 2b-2 for the measurement and for why the decorative ramp step is not
   re-pointed instead. Resting boundaries take --line-control (4.628:1 worst);
   the accent-flavoured ones — hover, the secondary button, focus — take
   --color-accent-600 (#a95c2b, 3.99:1 worst, and 4.00:1 on the accent-100 hover
   fill the control paints under itself). accent-600 is not a new answer: it is
   already the fill of a primary button and the ink of the global :focus-visible
   ring, and it is the lightest step on the accent ramp that clears 3:1 at all.
   accent-300 measured 1.50:1 worst, accent-400 2.02:1 and accent-500 2.91:1, so
   every accent step this section used to reach for was under the floor. */
button,.btn{font-family:var(--font-body);font-size:13px;font-weight:600;color:var(--color-text);
  background:var(--color-card);border:1px solid var(--line-control);
  border-radius:var(--radius-pill);padding:7px 15px;cursor:pointer;min-width:24px;min-height:24px;
  transition:background .14s,border-color .14s,color .14s}
button:hover,.btn:hover{background:var(--color-accent-100);border-color:var(--color-accent-600);
  color:var(--color-accent-800)}
button:active,.btn:active{background:var(--color-accent-200)}
/* A filled primary button is TEXT ON A FILL, so the fill has to be a
   text-grade step. accent-500 measures 3.41:1 under --on-accent and fails AA
   at button sizes; accent-600 measures 4.67:1 and passes. accent-500 stays for
   fills that carry no text — milestone diamonds, progress bars, rules, LEDs. */
button.amber,.btn.amber,.btn-primary,button.primary,.btn.primary{
  background:var(--color-accent-600);color:var(--on-accent);border-color:var(--color-accent-700);font-weight:700}
button.amber:hover,.btn.amber:hover,.btn-primary:hover,button.primary:hover,.btn.primary:hover{
  background:var(--color-accent-700);color:var(--on-accent);border-color:var(--color-accent-800)}
/* The secondary button has NO fill, so its border is the entire control: it is
   the one variant here where 1.4.11 has nothing else to measure. accent-400 was
   2.29:1 on --color-card. */
.btn-secondary{background:transparent;color:var(--color-accent-800);border-color:var(--color-accent-600)}
.btn-secondary:hover{background:var(--color-accent-100)}
/* The alarm ramp has no 400 rung, so the danger boundary steps 300 -> 500.
   alarm-300 #dc9c92 measured 2.08:1 on --color-card and 1.84:1 worst; alarm-500
   (= --st-critical-ink) is 6.57:1 on card and 5.63:1 on its own hover tint. The
   state change survives the two meeting in the middle because hover also swaps
   the fill and the ink, and the border goes one step darker again. */
button.danger,.btn.danger{color:var(--st-critical-ink);border-color:var(--st-critical-ink)}
button.danger:hover,.btn.danger:hover{background:var(--st-critical-tint);color:var(--color-alarm-700);
  border-color:var(--color-alarm-700)}
/* A DISABLED CONTROL MUST STILL BE READABLE, and `opacity` cannot deliver that.
   Opacity composites the WHOLE button toward the page and drags its text down with it, so a
   disabled variant keeps its own foreground — a disabled .amber button held color:var(--on-accent)
   (cream) over a cream page and measured 1.17:1 on module-pay-apps' "Blocked — compliance" chip.
   That chip is the one word telling the user why a draw cannot be submitted, so it is exactly the
   text that must survive being disabled. Two conversion batches hit this independently and each
   patched it locally; this is the shared fix.
   Explicit muted tokens instead of opacity: neutral-700 on neutral-200 measures 5.86:1.
   `!important` on the two colour declarations is deliberate and is the narrowest tool that works:
   a variant class (.btn.amber, 0-2-0) outranks a state pseudo-class (.btn:disabled, 0-1-1), so
   without it the variant's cream foreground silently wins and the label disappears again. */
button:disabled,.btn:disabled,button[disabled],.btn[disabled]{
  cursor:not-allowed;box-shadow:none;
  background:var(--color-neutral-200)!important;
  color:var(--color-neutral-700)!important;
  border-color:var(--color-neutral-300)}
/* The same rule for the OTHER disabled controls, which had none: a disabled
   <select> keeps its option text at full --color-text on --color-card and so
   looked live, and a page dimming it with opacity instead (module-site-plan's
   scrub bar) pushed the READING inside it — "day", the scrub date, the span the
   film covers — to 2.5-3.3:1. A value a control is displaying is content even
   when the control is idle; only a control's own LABEL is WCAG-exempt, and this
   product does not take that exemption either. Muted tokens, never a fade.
   `opacity:1` is not redundant: CHROMIUM'S OWN UA STYLESHEET fades a disabled
   <select> to 0.7, and measured at file:// that dropped module-site-plan's
   step reading ("day") to 3.22:1 all by itself, with no author rule involved.
   A browser default is still a fade on a light canvas. */
input:disabled,select:disabled,textarea:disabled,
input[disabled],select[disabled],textarea[disabled]{
  cursor:not-allowed;opacity:1;background:var(--color-neutral-200);
  color:var(--color-neutral-700);border-color:var(--color-neutral-300)}
input[type="range"]:disabled{accent-color:var(--color-neutral-500)}
/* a quiet icon button — the only place a glyph stands alone */
.iconbtn{width:26px;height:26px;padding:0;display:inline-flex;align-items:center;justify-content:center;
  border-radius:var(--radius-pill);font-size:14px;line-height:1}

input,select,textarea{font-family:var(--font-body);font-size:13.5px;color:var(--color-text);
  background:var(--color-card);border:1px solid var(--line-control);
  border-radius:var(--radius-sm);padding:8px 11px;
  transition:border-color .14s,box-shadow .14s;font-variant-numeric:tabular-nums}
input::placeholder,textarea::placeholder{color:var(--color-neutral-500)}
/* The field is FLUSH with the card it sits in — same fill, so the border is the
   only thing that says a field is there, and 1.4.11 is the whole affordance
   rather than a refinement of it. On :focus the border moves accent-500 ->
   accent-600 for the reason the note below already gives for the ring: 2.91:1
   worst against 3.99:1, and the two are then the same ink. */
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--color-accent-600);
  box-shadow:0 0 0 3px var(--color-accent-100)}
/* KEYBOARD FOCUS ON A FORM CONTROL MUST BE VISIBLE, NOT MERELY TINTED.
   `input:focus{outline:none}` on the line above is specificity (0,1,1) and the
   global ring at § 5 (`:focus-visible`) is (0,1,0), so the rule that exists to
   soften a MOUSE click was silently cancelling the keyboard ring on exactly the
   controls a data-entry product is made of. Measured in Chromium at file:// on
   module-rfis.html, read after the .14s transition settles rather than during
   it (mid-transition the computed value is the interpolated one and reports a
   ring that is not there yet):
     the 3px --color-accent-100 halo  #f7e4d4 on --color-card  = 1.13:1
     the only other signal, the 1px border moving
       --color-neutral-300 -> --color-accent-500                = 2.12:1 state change
   Neither is a focus indicator anyone can find on a dense form. --color-accent-600
   is 4.51:1 on --color-card and 4.15:1 on --color-bg, and is the same ink the
   global :focus-visible ring already uses, so this restores the system's own answer
   rather than a second one; box-shadow is cleared so the halo and the ring do not stack.
   :focus-visible (not :focus) is deliberate: a text field matches it however it
   was focused, while a <select> matches it only from the keyboard, so a mouse
   user keeps the soft look on the control where the soft look was the point. */
input:focus-visible,select:focus-visible,textarea:focus-visible{
  outline:2px solid var(--color-accent-600);outline-offset:1px;box-shadow:none}
input[type="range"],input[type="checkbox"],input[type="radio"]{accent-color:var(--color-accent-500)}
label.fl,.fl{display:block;font-size:11px;font-weight:700;color:var(--bone-faint);
  text-transform:uppercase;letter-spacing:.07em;margin-bottom:var(--sp-1)}

/* ---- 10. metric cards ---------------------------------------------------- */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:var(--sp-3)}
.card{padding:var(--sp-4) var(--sp-4) var(--sp-3)}
.card .k{font-size:10.5px;font-weight:700;color:var(--bone-faint);text-transform:uppercase;letter-spacing:.08em}
.card .v{font-size:26px;font-weight:700;margin-top:var(--sp-1);letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;line-height:1.1}
.card .s{font-size:11.5px;color:var(--bone-faint);margin-top:var(--sp-1)}
.card.good .v{color:var(--st-healthy-ink)}
.card.warn .v{color:var(--st-near-ink)}
.card.bad  .v{color:var(--st-critical-ink)}
.card.good{border-color:var(--color-accent-2-300)}
.card.warn{border-color:var(--color-accent-300)}
.card.bad {border-color:var(--color-alarm-300)}

/* ---- 11. tables ---------------------------------------------------------- */
.tablewrap{overflow:auto;border:1px solid var(--color-neutral-200);border-radius:var(--radius-md);
  background:var(--color-card);max-width:100%;min-width:0}
table{width:100%;border-collapse:collapse;font-size:13px;font-variant-numeric:tabular-nums}
th,td{text-align:left;padding:8px 11px;border-bottom:1px solid var(--color-neutral-200);white-space:nowrap}
thead th{position:sticky;top:0;z-index:1;background:var(--color-neutral-100);
  font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;
  color:var(--bone-dim);cursor:pointer;box-shadow:0 1px 0 var(--color-neutral-300)}
tbody tr:nth-child(even){background:rgba(32,30,29,.022)}
tbody tr:hover{background:var(--color-accent-100)}
td.num,th.num,.num{text-align:right;font-variant-numeric:tabular-nums}
td.mono{font-size:12.5px}
tfoot td{font-weight:700;border-top:2px solid var(--color-neutral-300)}

/* the action column is pinned to the SCROLLER's right: the markup OPEN button sat at x 2,386 in a box ending at 1,301 (round-5 super V-04, V-17) */
th.pin-r,td.pin-r{position:sticky;right:0;z-index:2;background:var(--color-card);
  box-shadow:-1px 0 0 var(--color-neutral-200)}
thead th.pin-r{z-index:3;background:var(--color-neutral-100)}
tbody tr:hover td.pin-r{background:var(--color-accent-100)}
@media print{th.pin-r,td.pin-r{position:static;box-shadow:none}}

/* ---- 12. compare table (sticky first column + differing rows lit) ---------
   From the Drone Intelligence compare pane, re-skinned. For scenario vs
   scenario, baseline vs baseline, bid vs bid: the first column and the header
   both stick, and a row whose values DISAGREE is the one you are looking for.
   Mark it with class="diff" on the <tr>.                                     */
.cmp{border-collapse:separate;border-spacing:0}
.cmp th,.cmp td{border-bottom:1px solid var(--color-neutral-200);background:var(--color-card)}
.cmp thead th{position:sticky;top:0;z-index:3;background:var(--color-neutral-100)}
.cmp tbody th,.cmp td.key{position:sticky;left:0;z-index:2;text-align:left;
  background:var(--color-neutral-100);font-weight:700;font-size:12px;
  text-transform:none;letter-spacing:0;color:var(--bone-dim);
  box-shadow:1px 0 0 var(--color-neutral-300);cursor:default}
.cmp thead th:first-child{z-index:4}
.cmp tbody tr.diff td,.cmp tbody tr.diff th{background:var(--st-near-tint)}
.cmp tbody tr.diff td{color:var(--st-near-ink);font-weight:600}
.cmp tbody tr:hover td,.cmp tbody tr:hover th{background:var(--color-accent-100)}
.cmp td.v{text-align:right;font-variant-numeric:tabular-nums}

/* ---- 13. spec detail list (dense right-aligned key/value readout) ---------
   From the Drone Intelligence .specdl pane, re-skinned. Use it wherever a
   panel is really a fact sheet, instead of a two-column table.               */
.specdl{display:grid;grid-template-columns:auto 1fr;gap:0 var(--sp-4);margin:0;font-size:12.5px}
.specdl dt{color:var(--bone-faint);padding:5px 0;border-bottom:1px solid var(--color-neutral-200);
  white-space:nowrap}
.specdl dd{margin:0;padding:5px 0;border-bottom:1px solid var(--color-neutral-200);
  text-align:right;color:var(--color-text);font-weight:600;font-variant-numeric:tabular-nums}
.specdl dd.wide{text-align:left;font-weight:400;color:var(--bone-dim)}

/* ---- 14. status layer: pills, chips and dots -----------------------------
   NO EMOJI. A status is a word plus, where it helps, a coloured dot.         */
.pill,.tag,.st{display:inline-block;font-size:11px;font-weight:600;letter-spacing:.01em;
  padding:2px 10px;border-radius:var(--radius-pill);
  background:var(--color-neutral-200);color:var(--color-neutral-800);white-space:nowrap}
.tag{margin:1px 3px 1px 0}
.tag-accent{background:var(--color-accent-200);color:var(--color-accent-800)}
.tag-accent-2{background:var(--color-accent-2-200);color:var(--color-accent-2-800)}
.tag-neutral{background:var(--color-neutral-200);color:var(--color-neutral-800)}

.st-critical  {background:var(--st-critical-tint);  color:var(--st-critical-ink)}
.st-near      {background:var(--st-near-tint);      color:var(--st-near-ink)}
.st-healthy   {background:var(--st-healthy-tint);   color:var(--st-healthy-ink)}
.st-overdue   {background:var(--st-overdue-tint);   color:var(--st-overdue-ink)}
.st-blocked   {background:var(--st-blocked-tint);   color:var(--st-blocked-ink)}
.st-approved  {background:var(--st-approved-tint);  color:var(--st-approved-ink)}
.st-superseded{background:var(--st-superseded-tint);color:var(--st-superseded-ink)}
.st-nodata    {background:var(--st-nodata-tint);    color:var(--st-nodata-ink)}

/* legacy pill modifiers, re-pointed at the semantic layer */
.pill.crit{background:var(--st-critical-tint);color:var(--st-critical-ink)}
.pill.mile{background:var(--st-near-tint);color:var(--st-near-ink)}
.pill.ok  {background:var(--st-healthy-tint);color:var(--st-healthy-ink)}
.pill.sum {background:var(--color-neutral-200);color:var(--color-neutral-800)}
.tag.dw   {background:var(--st-healthy-tint);color:var(--st-healthy-ink)}
.tag.miss {background:var(--st-critical-tint);color:var(--st-critical-ink)}

/* the coloured dot that replaces an emoji in a dense cell */
.sdot{display:inline-block;width:8px;height:8px;border-radius:50%;background:currentColor;
  margin-right:6px;vertical-align:.5px;flex:none}
.sdot.critical{color:var(--st-critical-ink)}.sdot.near{color:var(--st-near-ink)}
.sdot.healthy{color:var(--st-healthy-ink)}.sdot.overdue{color:var(--st-overdue-ink)}
.sdot.blocked{color:var(--st-blocked-ink)}.sdot.approved{color:var(--st-approved-ink)}
.sdot.superseded{color:var(--st-superseded-ink)}

/* filter chips (selectable) */
.chips{display:flex;gap:var(--sp-2);flex-wrap:wrap;align-items:center}
/* A FILTER CHIP IS A CONTROL, SO IT IS A <button>, AND THE HEIGHT MUST NOT
   DEPEND ON WHICH ELEMENT IT IS. A <span> inherits the body's 1.55 line-height
   (18.6px at this size) while a <button> takes the UA's `line-height:normal`,
   which measured 24px against the span's 28.6px on the same page — same width,
   same paint, 4.6px shorter. That is why module-14-baseline's chips, the only
   ones already built as buttons, sat visibly shallower than every other filter
   row in the product. Stating it here makes the two spellings identical. */
/* ...AND BECAUSE IT IS A CONTROL, ITS BORDER IS A CONTROL BOUNDARY. An UNSELECTED
   chip carries the card's own fill, so the border is the only thing saying the
   chip is there and 1.4.11 applies to it in full — --line-control, § 2b-2. The
   SELECTED state is a fill and is measured as one: accent-600 is 4.51:1 against
   --color-card and 4.15:1 against --color-bg, so a selected chip is legible as a
   shape whatever its border does. */
.chip{font-size:12px;font-weight:600;color:var(--bone-dim);background:var(--color-card);
  border:1px solid var(--line-control);border-radius:var(--radius-pill);
  padding:4px 12px;cursor:pointer;user-select:none;font-variant-numeric:tabular-nums;
  line-height:1.55;font-family:var(--font-body);
  transition:background .14s,border-color .14s,color .14s}
.chip:hover{background:var(--color-accent-100);border-color:var(--color-accent-600);color:var(--color-accent-800)}
.chip.on{color:var(--on-accent);background:var(--color-accent-600);border-color:var(--color-accent-700)}
/* The count is a NUMBER THE USER READS, so it gets an ink, not a fade. `opacity:.72` here
   failed AA on every filter chip in the product at once — see the note on button:disabled
   above for why opacity and a light canvas cannot coexist in text. On .chip.on the terracotta
   fill already carries --on-accent, and dimming that is what produced cream-on-terracotta. */
.chip .n{margin-left:6px;color:var(--color-neutral-700)}
.chip.on .n{color:var(--on-accent)}

/* row tints for a register table — use the -row tokens, never the chip tints */
tr.row-overdue td{background:var(--st-overdue-row)}
tr.row-critical td{background:var(--st-critical-row)}
tr.row-near td{background:var(--st-near-row)}
tr.row-healthy td{background:var(--st-healthy-row)}
tr.row-blocked td{background:var(--st-blocked-row)}
/* A muted row is still a row of DATA. opacity:.7 drops --bone-dim to 3.61:1
   and --bone-faint to 2.89:1 on a card — every cell in it below AA at once.
   A recessed tint plus a muted ink says "de-prioritised" and stays readable. */
tr.row-muted td{background:var(--st-blocked-row);color:var(--bone-dim)}
/* a tinted row still needs a hover state, or the table stops responding to the
   pointer exactly on the rows a user is most likely to be reaching for */
tr.row-overdue:hover td,tr.row-critical:hover td,tr.row-near:hover td,
tr.row-healthy:hover td,tr.row-blocked:hover td{background:var(--color-accent-100)}

/* ---- 15. correctness banners — trap 10 ----------------------------------
   A failed feed, a blocked score or a stale data date NEVER renders as zero
   and NEVER hides behind an info button. It renders here, on the page, naming
   the feed and saying what to do.                                            */
.banner{display:flex;gap:var(--sp-3);align-items:flex-start;border-radius:var(--radius-md);
  padding:var(--sp-3) var(--sp-4);margin:var(--sp-3) 0;font-size:13px;line-height:1.5;
  border:1px solid var(--color-alarm-300);background:var(--st-nodata-tint);color:var(--color-alarm-700)}
/* THE AUTOMATIC MINIMUM, and it is why three pages ran past a 390px viewport.
   A flex item's min-width defaults to `auto` and a grid `1fr` is
   `minmax(auto,1fr)`, so neither shrinks below its content -- and an input's
   content width is its `size`, not its text. min-width:0 on the item,
   minmax(0,1fr) on the track. Invisible on a desktop; a scrollbar on a
   phone (WCAG 1.4.10). */
.banner>*{min-width:0}
/* ...AND WHAT THAT DID TO THE LABEL: at 390px banner-t shrank to 40px while
   still PAINTING "NO SITE COORDINATES" over the body. The boxes never intersect,
   so an overlap probe and a11y both pass it — only a rendered screenshot found
   it, on trap 10's own surface. The label keeps its width now. */
.banner{flex-wrap:wrap}
.banner>.banner-t{flex:0 0 auto;max-width:100%}
.banner>*:not(.banner-t){flex:1 1 220px}
.banner b{color:var(--color-alarm-700)}
.banner.warn{border-color:var(--color-accent-300);background:var(--st-near-tint);color:var(--color-accent-800)}
.banner.warn b{color:var(--color-accent-800)}
.banner.info{border-color:var(--color-accent-2-300);background:var(--st-healthy-tint);color:var(--color-accent-2-800)}
.banner.info b{color:var(--color-accent-2-800)}
.banner .banner-t{font-weight:700;text-transform:uppercase;letter-spacing:.07em;font-size:10.5px;
  display:block;margin-bottom:2px}
/* The "what to fix" clause inside a banner. A correctness notice that names the
   failure and stops is only half of trap 10 — "say what to do" is the other
   half — and three modules had each grown their own copy of this rule. */
.banner .dfix{display:block;margin-top:6px;padding-left:11px;
  border-left:2px solid var(--color-neutral-300)}
.banner .dfix b{font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  display:block;margin-bottom:3px}

/* ---- 15a. THE PERSISTENT WORKSPACE NOTICE — trap 10, for storage and sync ----
   Rendered by engine/mc-ui.js (renderNotice) into every page in the tree, and
   ONLY when something is actually wrong: the last write to browser storage
   failed, or the workspace is configured against a server it is not currently
   agreeing with. It is fixed to the top on purpose. A workspace that is not
   being saved, or a register showing yesterday's rows, is exactly the situation
   in which a notice should be in the way, and the measured failure it replaces
   was a six-second toast that fired ONCE, hours before the user noticed.
   It has no close button. See engine/mc-ui.js for why that is deliberate.
   It carries no state colours of its own — .mcn-bad is the alarm ramp and
   .mcn-warn the accent ramp, the same two the .banner rules above use.        */
.mcn{position:fixed;top:0;left:0;right:0;z-index:60;display:flex;gap:var(--sp-3);
  align-items:baseline;padding:var(--sp-2) var(--sp-4);font-family:var(--font-body);
  font-size:12.5px;line-height:1.45;border-bottom:1px solid var(--color-alarm-300);
  background:var(--st-nodata-tint);color:var(--color-alarm-700);box-shadow:var(--shadow-sm)}
.mcn-warn{border-bottom-color:var(--color-accent-300);background:var(--st-near-tint);
  color:var(--color-accent-800)}
.mcn .mcn-t{flex:0 0 auto;font-weight:700;text-transform:uppercase;letter-spacing:.09em;
  font-size:10px;white-space:nowrap;padding-top:1px}
.mcn .mcn-b{flex:1 1 auto;min-width:0}
.mcn .mcn-h{font-weight:700}
.mcn .mcn-f{font-style:italic}
@media print{.mcn{position:static}}
/* A BASIS STATEMENT — quieter than a banner, but still on the page.
   "Project baseline: not set — Baseline* fields read as they stand" (trap 14) is
   not an alarm, so it must not shout like one; it IS a statement about what the
   numbers beside it mean, so it may not sit in an info panel and it may not sit
   in header.top, which the shell hides on every docked module. Three schedule
   pages state the same sentence, so the rule lives here rather than in each. */
.basisline{display:block;margin:0 0 var(--sp-3);font-size:12.5px;color:var(--bone-dim);
  border-left:3px solid var(--color-neutral-300);padding:2px 0 2px var(--sp-3);
  font-variant-numeric:tabular-nums}
.basisline.unset{border-left-color:var(--color-accent-500);color:var(--color-accent-800)}

/* ---- 16. intake ---------------------------------------------------------- */
/* A DROP TARGET IS A CONTROL and its dashed edge is the only thing drawing it —
   the fill is --color-neutral-100, which is a panel surface this product already
   uses for inert content. So the edge carries 1.4.11 like any other boundary.
   Measured against the fill each state paints under itself — neutral-100 at
   rest, accent-100 on :hover and on .hot: neutral-400 1.91:1, accent-400 2.03:1,
   accent-500 2.92:1. All three states were under the floor. .hot takes accent-700
   (5.51:1) rather than accent-600 so the drag-over state still reads as a step
   above :hover now that :hover has moved up to accent-600. */
.drop{border:1.5px dashed var(--line-control);border-radius:var(--radius-md);
  padding:var(--sp-5);text-align:center;cursor:pointer;transition:.15s;background:var(--color-neutral-100)}
.drop:hover{border-color:var(--color-accent-600);background:var(--color-accent-100)}
.drop.hot{border-color:var(--color-accent-700);background:var(--color-accent-100);
  box-shadow:0 0 0 3px var(--color-accent-200)}
.drop .big{font-weight:700;font-size:15px}
.drop .sub{color:var(--bone-faint);font-size:12px;margin-top:var(--sp-1)}
.btns{display:flex;gap:var(--sp-2);flex-wrap:wrap;margin-top:var(--sp-3);justify-content:center}
.filechip{display:none;align-items:center;gap:var(--sp-2);font-size:12.5px;font-weight:600;
  color:var(--st-healthy-ink);margin-top:var(--sp-3)}
.filechip.on{display:inline-flex}
/* once the drop zone is gone the panel held only the file chip and read as an empty section (super V-16) */
.mc-intake-done{border:0!important;background:none!important;box-shadow:none!important;padding:0!important;margin:0 0 var(--sp-3)!important}

/* ---- 17. misc ------------------------------------------------------------ */
.flash{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);
  background:var(--color-neutral-900);border:1px solid var(--color-neutral-900);
  color:var(--on-accent);font-size:12.5px;font-weight:600;padding:10px 16px;
  border-radius:var(--radius-pill);opacity:0;pointer-events:none;transition:.2s;z-index:50;
  box-shadow:var(--shadow-md)}
.flash.on{opacity:1}
.bar{height:8px;background:var(--color-neutral-200);border-radius:var(--radius-pill);overflow:hidden}
.bar>i{display:block;height:100%;background:var(--color-accent-500)}
.bar>i.good{background:var(--color-accent-2-500)}
.bar>i.bad{background:var(--color-alarm-500)}
svg{display:block;max-width:100%}
.empty{border:1px dashed var(--color-neutral-300);border-radius:var(--radius-md);padding:var(--sp-5);
  color:var(--bone-faint);font-size:13px;text-align:center;background:var(--color-neutral-100)}

/* ---- 17a. mc-directory.js — a directory reference, on any register --------
   MCDIR.badge() emits these, and it emits them into ~25 different registers, so
   they live here rather than in any one module's <style>. The chip that rides
   beside the name when a company's insurance or licence has lapsed is a
   .st/.st-* from § 14 — no colour is chosen here, and none may be.

   .dirref is the resolved NAME. It is deliberately NOT a link colour: a name in
   a cell is data, and colouring every vendor in the tree terracotta would make
   the compliance chip beside it the quiet thing on the row, which is backwards.
   .dirsub is the person's company, one tier down. Both take a text-grade ink —
   de-emphasis uses an ink, never a fade (§ 2d of docs/UI-CONVERSION.md). */
.dirref{font-weight:600;color:var(--color-text)}
.dirsub{font-size:11px;color:var(--bone-faint)}
.dirref+.st,.dirsub+.st{margin-left:var(--sp-1)}

/* ---- 17b. mc-customfields.js — a company's own field, on any register -----
   MCCF.headCells()/rowCells()/formHTML() emit these, into every register that
   accepts custom fields, so they live here for the same reason § 17a does — a
   copy in each module's <style> is six copies of one decision, and the day one
   of them drifts a stale value stops reading as stale on that page only.

   .cfstale and .cfreq are the two CORRECTNESS markers and neither is
   decoration: .cfstale says the value on the record is no longer one the field
   offers (it was kept, not blanked), and .cfreq says a required field has never
   been answered. Both take the overdue ink — a word AND a colour, so the
   meaning survives a black-and-white print (§ 2c of docs/UI-CONVERSION.md).
   .cfhelp is the field's own help text under an input, one tier down in an INK,
   never a fade. */
.cfstale{font-weight:600;color:var(--st-overdue-ink)}
.cfreq{font-style:italic;color:var(--st-overdue-ink)}
.cfreqm{color:var(--st-overdue-ink)}
.cfhelp{font-size:11px;color:var(--bone-dim);margin-top:var(--sp-1)}
.cfsetn{font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--bone-dim);font-weight:600}
.cfset{margin-top:var(--sp-2)}
.cfck{display:flex;gap:var(--sp-2);align-items:center;font-size:12.5px;color:var(--color-text)}
.cfck input{width:auto}
/* The container a wired register drops MCCF.formHTML() into. One layout for
   every register, so a custom field looks the same wherever it was added. */
.cfgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));
  gap:var(--sp-3);align-items:end}
.cfgrid .full{grid-column:1/-1}
.cfgrid input,.cfgrid select,.cfgrid textarea{width:100%}

/* ---- 18. legacy help tab (MCUI.helpTab) ---------------------------------- */
#helpFab{position:fixed;right:16px;bottom:16px;z-index:9000;width:38px;height:38px;border-radius:50%;
  border:1.5px solid var(--color-accent-400);background:var(--color-accent-100);color:var(--color-accent-800);
  font-family:Georgia,'Times New Roman',serif;font-style:italic;font-size:17px;font-weight:700;
  cursor:pointer;box-shadow:var(--shadow-md)}
#helpFab:hover{background:var(--color-accent-300)}
#helpPanel{position:fixed;top:0;right:-360px;width:340px;height:100%;z-index:9001;
  background:var(--color-card);border-left:1px solid var(--color-neutral-200);
  transition:right .25s ease;display:flex;flex-direction:column;box-shadow:var(--shadow-lg)}
#helpPanel.on{right:0}
#helpPanel .hp-head{display:flex;justify-content:space-between;align-items:center;padding:13px 16px;
  border-bottom:1px solid var(--color-neutral-200);background:var(--color-accent-100);
  font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--color-accent-800)}
/* THIS RULE IS WHY § 2b-2 IS A TOKEN AND NOT A PATCH TO THE `button` RULE. It is
   specificity (0,1,2) and brand/animus/animus.css's own `button` rule is (0,0,1),
   so the Animus control ink never reached this control: it stayed on
   --color-neutral-300, which resolves to #243040 on that ground and measured
   1.20:1. The border also has no fill behind it — `background:none` over the
   accent-100 panel head — so it is the entire affordance. */
#helpPanel .hp-head button{background:none;border:1px solid var(--line-control);border-radius:var(--radius-pill);
  color:var(--color-text);cursor:pointer;padding:3px 10px}
#helpPanel .hp-body{padding:14px 16px;overflow-y:auto;color:var(--bone-dim);font-size:13.5px;line-height:1.6}
#helpPanel .hp-body p{margin:0 0 12px}
#helpPanel .hp-body b{color:var(--color-text)}

/* ---- 19. mc-info.js — the "i" button and its dialog ---------------------- */
.info-btn{width:24px;height:24px;flex:0 0 auto;border-radius:var(--radius-pill);cursor:pointer;
  border:1.5px solid var(--color-accent-400);background:var(--color-accent-100);
  color:var(--color-accent-800);font-family:Georgia,'Times New Roman',serif;font-style:italic;
  font-weight:700;font-size:14px;line-height:1;padding:0;align-self:center;transition:background .12s}
.info-btn:hover{background:var(--color-accent-300)}
/* THE SMALL "i" IS 19px OF GLYPH AND 24px OF TARGET, AND THE SPLIT IS THE POINT.
   MCINFO.attach() takes `opts.small !== false` (mc-info.js:191), so the small
   variant is the DEFAULT: of 367 attach() calls across the tree, exactly one
   passes small:false. So 366 controls sat at 19x19 against WCAG
   2.5.8's 24px floor - and MCINFO is otherwise the best-built widget in the
   tree, with a real focus trap, focus return, aria-modal and Escape-to-close.
   One number was the only thing wrong with the product's declutter mechanism.
   The GLYPH stays 19px. Growing the box to 24 would reflow every dense header
   and table cell these sit in on 75 pages, which is a large visual change to
   buy a property that is about the POINTER, not the picture. The ::after
   extends the hit area symmetrically to 24x24 without entering layout - it is
   part of the button, so a click on it is a click on the control. */
.info-btn.sm{width:19px;height:19px;font-size:11.5px;border-width:1px;vertical-align:1px;
  position:relative}
.info-btn.sm::after{content:"";position:absolute;left:50%;top:50%;width:24px;height:24px;
  transform:translate(-50%,-50%)}
.info-overlay{position:fixed;inset:0;z-index:5000;background:rgba(32,30,29,.42);
  display:flex;align-items:center;justify-content:center;padding:24px}
.info-overlay[hidden]{display:none}
.info-card{background:var(--color-card);color:var(--color-text);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-lg);max-width:620px;width:100%;max-height:84vh;
  display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--color-neutral-200)}
.info-head{display:flex;align-items:center;gap:12px;padding:16px 20px;
  border-bottom:1px solid var(--color-neutral-200);background:var(--color-accent-100)}
.info-head h2{margin:0;flex:1;font-family:var(--font-display);font-weight:400;font-size:20px;
  color:var(--color-accent-800)}
.info-x{border:none;background:transparent;font-size:22px;line-height:1;cursor:pointer;
  color:var(--bone-faint);width:30px;height:30px;border-radius:var(--radius-sm);padding:0}
.info-x:hover{background:var(--color-neutral-200);color:var(--color-text)}
.info-body{overflow-y:auto;padding:6px 20px 20px}
.info-body h3{font-family:var(--font-body);font-weight:700;font-size:13px;text-transform:uppercase;
  letter-spacing:.07em;color:var(--color-accent-800);margin:16px 0 4px}
.info-body .info-sec{font-size:13.5px;line-height:1.6;color:var(--bone-dim)}
.info-body p{margin:4px 0 8px}
.info-body b{color:var(--color-text)}
.info-body ul{margin:4px 0 8px;padding-left:18px}
.info-body li{margin:3px 0}
.info-body .k{display:inline-block;min-width:15px;height:15px;border-radius:4px;margin:0 5px -2px 0;
  vertical-align:-2px;border:1px solid var(--color-neutral-300)}
.info-body code{background:var(--color-neutral-100);border-radius:4px;padding:1px 5px;font-size:12px}

/* ---- 20. mc-windows.js — resizable grid gutters + floating windows -------- */
:root{--win-gut:7px}
.win-gutter{position:relative;z-index:5;background:transparent;touch-action:none}
.win-gutter-col{cursor:col-resize}
.win-gutter-row{cursor:row-resize}
.win-gutter::after{content:"";position:absolute;background:var(--color-neutral-300);
  border-radius:3px;transition:background .12s}
.win-gutter-col::after{top:50%;left:50%;transform:translate(-50%,-50%);width:3px;height:42px}
.win-gutter-row::after{top:50%;left:50%;transform:translate(-50%,-50%);height:3px;width:42px}
.win-gutter:hover::after,.win-gutter.dragging::after{background:var(--color-accent-500)}
.win-gutter-col:hover,.win-gutter-col.dragging{background:linear-gradient(90deg,transparent 45%,var(--color-accent-200) 45% 55%,transparent 55%)}
.win-gutter-row:hover,.win-gutter-row.dragging{background:linear-gradient(0deg,transparent 45%,var(--color-accent-200) 45% 55%,transparent 55%)}
/* overflow:visible so the edge/corner resize handles (negative offsets) are not
   clipped; the bar and body carry the corner rounding instead */
.floatwin{box-shadow:var(--shadow-md);border-radius:var(--radius-md);overflow:visible;
  min-width:150px;display:flex;flex-direction:column;background:var(--color-card);
  border:1px solid var(--color-neutral-200)}
.floatwin-bar{display:flex;align-items:center;gap:8px;cursor:move;user-select:none;
  padding:5px 6px 5px 12px;background:var(--color-accent-100);
  border-radius:var(--radius-md) var(--radius-md) 0 0;
  border-bottom:1px solid var(--color-neutral-200);touch-action:none;flex:0 0 auto}
.floatwin-bar.dragging{background:var(--color-accent-200)}
.floatwin-title{flex:1;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--color-accent-800);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.floatwin-btn{border:none;background:transparent;color:var(--color-accent-800);cursor:pointer;
  font-size:15px;line-height:1;width:22px;height:20px;border-radius:5px;padding:0;font-weight:700}
.floatwin-btn:hover{background:var(--color-accent-200)}
.floatwin-body{flex:1 1 auto;overflow:auto;min-height:0;padding:9px 11px;
  border-radius:0 0 var(--radius-md) var(--radius-md)}
.floatwin-h{position:absolute;z-index:6;touch-action:none}
.floatwin-h-n{top:-3px;left:8px;right:8px;height:8px;cursor:ns-resize}
.floatwin-h-s{bottom:-3px;left:8px;right:8px;height:8px;cursor:ns-resize}
.floatwin-h-e{right:-3px;top:8px;bottom:8px;width:8px;cursor:ew-resize}
.floatwin-h-w{left:-3px;top:8px;bottom:8px;width:8px;cursor:ew-resize}
.floatwin-h-ne{top:-3px;right:-3px;width:14px;height:14px;cursor:nesw-resize}
.floatwin-h-nw{top:-3px;left:-3px;width:14px;height:14px;cursor:nwse-resize}
.floatwin-h-se{bottom:-3px;right:-3px;width:14px;height:14px;cursor:nwse-resize}
.floatwin-h-sw{bottom:-3px;left:-3px;width:14px;height:14px;cursor:nesw-resize}

/* ---- 21. the deliverable document — mc-export.js ------------------------- */
/* Screen styling for the four blocks a printed deliverable is built from. They
   are ordinary panels on screen; § 22 is what turns them into paper. */
.doccover{border-left:3px solid var(--color-accent-500);background:var(--color-card);
  border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:26px 26px 30px;
  margin-bottom:var(--sp-4)}
.doccover .dc-kick{font-size:11px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  color:var(--color-accent-800)}
.doccover .dc-name{font-family:var(--font-display);font-weight:400;font-size:31px;
  letter-spacing:-.015em;line-height:1.06;margin:8px 0 4px}
.doccover .dc-sub{font-size:12.5px;color:var(--bone-dim);margin:0}
.docsec{margin-bottom:var(--sp-4)}
.docsec .docsec-n{font-size:11px;color:var(--color-accent-800);font-weight:700;letter-spacing:.06em}
/* The provenance block. It is not decoration and it never moves into an
   MCINFO panel: it is the part of the document that says what the numbers
   were computed from. */
.provblock{border:1px solid var(--color-neutral-200);border-radius:var(--radius-sm);
  background:var(--color-neutral-100);padding:14px 16px;margin:var(--sp-3) 0}
.provblock .specdl{margin:6px 0 0}
.provblock .specdl dt{color:var(--bone-dim)}
.provblock .banner{margin-top:var(--sp-3)}
.provblock .banner ul{margin:6px 0 0;padding-left:18px}
.provblock .banner li{margin:3px 0}
/* A one-line stamp for a page footer or a card corner. */
.docstamp{font-size:10.5px;color:var(--bone-faint);letter-spacing:.03em}

/* ---- 22. reduced motion + print ------------------------------------------ */
@media (prefers-reduced-motion: reduce){
  *{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;scroll-behavior:auto!important}
}
/* PRINT IS AN OUTPUT FORMAT HERE, NOT AN AFTERTHOUGHT.
   docs/UI-CONVERSION.md § 6b established that a print block whose only job is
   colour is dead weight once the theme is light ink on cream — measured, four
   modules, identical pagination. What it did NOT establish is what a print
   stylesheet should do INSTEAD, and the answer is: page geometry, repeated
   table headers, break control, and getting the interactive chrome out of the
   way. Those are the four things that separate a usable PDF from a screenshot
   of a web page, and none of them is a colour.
   Everything below is layout. The one colour declaration is `background:#fff`
   on the page box, because a printer drops backgrounds by default and the
   cream canvas would otherwise be a wash of ink on the machines that do not. */
@media print{
  @page{size:A4;margin:14mm}
  html,body{background:var(--paper)!important}
  header.top,#helpFab,#helpPanel,.flash,.noprint,.info-btn,.info-overlay,
  .win-gutter,.floatwin-h{display:none!important}
  .wrap{max-width:none;margin:0;padding:0}
  .panel,.card{box-shadow:none;border-color:var(--color-neutral-300)}
  /* THE BIG ONE. A table that spans pages must repeat its header, or page 4
     of a 300-row activity register is nine unlabelled columns of dates.
     `position:sticky` has to go first — a sticky thead is painted once, at the
     top of page one, and every later page loses it entirely. */
  thead{display:table-header-group}
  tfoot{display:table-footer-group}
  thead th{position:static!important}
  table{break-inside:auto;page-break-inside:auto;width:100%}
  tr,th,td{break-inside:avoid;page-break-inside:avoid}
  /* A scroll container is a screen affordance. On paper it clips the document
     to one viewport's worth of rows and the rest is simply not printed —
     silently, which is the worst kind. */
  .tablewrap{overflow:visible!important;max-height:none!important;border:none!important}
  /* ...but overflow:visible only rescues ROWS. A table wider than the page box
     is clipped at the right margin, and Chrome does not paginate horizontally,
     so columns past it are gone with nothing saying so. Worst measured case is
     a legally filed form: the 18-column OSHA 300 log measured 1721px inside a
     794px page and printed (A) through (F), with (G) Death, (H) Days away,
     (I) Transfer, (J) Other, (K), (L) and M1..M6 nowhere in the document. A
     clipped column is trap 10 in ink: no table may lose one.

     THREE SCREEN AFFORDANCES MAKE A TABLE OVERFLOW, all meaningless on paper
     and so all dropped for print:
       - `white-space:nowrap`, which sets a cell's minimum width to its whole
         string. Ten of twelve over-wide tables fit from restoring wrapping;
       - a pixel column width chosen for a screen. The pay-app SOV grid commits
         1020px on a 744px page before its last two columns; auto layout sizes
         from the content, which is what a printed table wants;
       - a min-width holding a column open against auto layout.
     Then a register of NINE OR MORE columns takes the smaller type, tighter
     padding, and the one rule that costs something: `overflow-wrap:anywhere`,
     which lets a column narrow past its longest token, so "$41,420" can land
     across two lines. That is a real cost on a money column and it is confined
     to the dense registers on purpose — everything else gets `break-word`,
     which never splits a token that fits on a line of its own. Measured: with
     `break-word` everywhere, eight registers of 12 to 18 columns still run off
     the page; with `anywhere` from nine columns up, none does. A split figure
     is legible and a missing column is not there at all, so where the two
     conflict the column wins.

     Over the whole module tree with the demo store seeded: 12 tables ran off the page
     before, 0 do after.

     Thirteen or more columns additionally take a LANDSCAPE leaf of their own,
     because 18 columns across 744px is legible but miserable and the 300 log is
     a landscape form by law. Named pages and :has() are Chromium features and
     Chromium at file:// is the shipping target; where either is unsupported the
     rule is ignored and the reflow above still fits the table. */
  th,td{white-space:normal!important;overflow-wrap:break-word;
    width:auto!important;min-width:0!important;max-width:none!important}
  table:has(> * > tr > *:nth-child(9)){font-size:8pt}
  table:has(> * > tr > *:nth-child(9)) th,
  table:has(> * > tr > *:nth-child(9)) td{padding:2px 3px;overflow-wrap:anywhere}
  @page wide{size:A4 landscape;margin:10mm}
  .tablewrap:has(> table > * > tr > *:nth-child(13)),
  table:has(> * > tr > *:nth-child(13)){page:wide}
  /* A COLLAPSED <details> PRINTED AS NOTHING AT ALL — not even its summary.
     `details>summary{display:none}` hid the summary for EVERY details and only
     `details[open]` put it back, so a section the reader happened to leave
     closed came out as an empty box the height of its own padding, and on a
     block with no border as literally nothing: measured height 0, innerText "".
     That is strictly worse than having no print rule at all, because on screen
     the summary is at least there to click and on paper there is nothing to
     click.

     It also breaks trap 10 / § 2b in the one direction that matters, since a
     correctness banner inside a collapsed <details> is supposed to be FORCED
     OPEN. Measured across the module tree with the demo store seeded, seven
     collapsed blocks print content they did not print before, and three of them
     carry a `.banner`: the schedule log on module-10 and module-11 (68px ->
     1057px — the open-ends, out-of-sequence, constraint and negative-float list
     a reviewer checks first), module-settings' cache section, and
     module-submittals' spec-reader panel. None of them was on paper at all.

     So: the summary always prints, and the content is forced visible with it.
     Chrome hides a closed details' content through ::details-content's
     content-visibility, not through `display` on the host, which is why the
     `details{display:block!important}` above never revealed anything on its
     own. A renderer without ::details-content still gets the summary, which is
     the stated minimum. */
  details{display:block!important}
  details>summary{display:list-item}
  details::details-content{content-visibility:visible!important;
    block-size:auto!important;height:auto!important;overflow:visible!important}
  /* Never split a card, a status banner or a provenance block across a page.
     A correctness warning cut in half reads as a different warning. */
  .card,.banner,.provblock,.specdl{break-inside:avoid;page-break-inside:avoid}
  .panel{break-inside:auto;page-break-inside:auto}
  /* An in-page deliverable: cover on its own leaf, one section per run. */
  .doccover{break-after:page;page-break-after:always;box-shadow:none}
  .docsec{break-inside:auto;page-break-inside:auto}
  .docsec+.docsec{break-before:page;page-break-before:always}
  h1,h2,h3,.panel-h{break-after:avoid;page-break-after:avoid}
  /* Tinted rows and status chips only survive if the reader ticks background
     graphics; the words in them survive regardless, which is why every status
     in this system is a word as well as a colour (UI-CONVERSION § 2c). */
  .st,.pill,.tag,tr[class*="row-"]{-webkit-print-color-adjust:exact;print-color-adjust:exact}
}

.toolmenu{position:relative;flex:none}
.toolmenu>summary{list-style:none;cursor:pointer;padding:6px 12px;border:1px solid var(--color-neutral-400);color:var(--color-text);font:inherit;font-size:12px;letter-spacing:.06em;text-transform:uppercase;background:var(--color-neutral-100)}
.toolmenu>summary::-webkit-details-marker{display:none}
.toolmenu>summary::after{content:" ▾"}
.toolmenu[open]>summary{border-color:var(--color-accent-500)}
.toolmenu-b{position:absolute;right:0;top:calc(100% + 4px);z-index:30;min-width:200px;display:flex;flex-direction:column;background:var(--color-neutral-100);border:1px solid var(--color-neutral-400);box-shadow:var(--shadow-md)}
.toolmenu-b>button{text-align:left;border:0;border-bottom:1px solid var(--color-neutral-300);background:transparent;padding:8px 12px;font:inherit;font-size:12.5px;color:var(--color-text);cursor:pointer}
.toolmenu-b>button:last-child{border-bottom:0}
.toolmenu-b>button:hover{background:var(--color-accent-100)}
