/* The hub's design tokens — one file, every surface.
   ============================================================================
   Lifted out of hub.css on 2026-09-15. Until then the same 79 custom properties
   were defined in four :root blocks inside a 2,290-line stylesheet, and the
   three sibling pages (JV Builds, the Flow Mapper, the Close Calendar) each
   carried a set of their own — four token sets across four surfaces, drifting.

   ⚠️ GENERATED — DO NOT EDIT. Every value comes from the JV-Tools design
   system's master, jv-panel `design/system/tokens.json`; the structure comes
   from jv-panel `design/system/surfaces/hub.css`. Change the master, then run
       node tools/design-sync/sync.js
   in a jv-panel checkout that sits beside this repo (tools/verify.sh runs the
   check when it does). The pages that cannot link a stylesheet (they are opened
   as downloaded single files) carry an EMBEDDED COPY of everything below,
   between a marked begin/end pair. Re-embed with
       python3 hub/design-tokens/sync_tokens.py
   after this file changes — `--check` fails when a copy has drifted.

   Three themes on one set of names, from the JV-Tools design system
   (design/system/tokens.json): Stone Mauve (data-theme="light", the shipped
   default and what an unset root draws), Dusk and Dark, the plum family. The
   Dark set is written TWICE on purpose: CSS cannot alias a block, the media
   query covers "nothing saved yet, the OS says dark", and the attribute block
   covers the switch (localStorage jvhub_theme: light · dusk · dark, stamped
   onto <html> before first paint by the inline script in each page's head).
   ============================================================================ */


  /* ---- Light tokens (default) ---- */
  :root {
    /* Brand ramp — the design system's communication blue (2026-08-24). It
       exists to derive the accent aliases and the dark filled button. */
    --brand-40:#0A3960; --brand-60:#0E4C82; --brand-80:#0F6CBD; --brand-90:#2886DE;
    --brand-100:#479EF5; --brand-130:#96C6FA; --brand-150:#CFE4FA; --brand-160:#EBF3FC;

    /* Stone Mauve — the shipped light theme (design system, 11 Oct): a
       mauve-grey page ground under white cards, plum-grey ink, pastel state
       grounds with firm inks. Every text pair holds 4.5:1 or better.
       ⚠️ --fg-4 (#837A82) is 3.5:1 on white — under WCAG AA for small text.
       It stays for NON-TEXT ink only: dots, list markers, chevrons/arrows,
       dotted underlines and close glyphs. Don't put text on it. */
    --fg-1:#231D22; --fg-2:#413A40; --fg-3:#5A525A; --fg-4:#837A82; --fg-inv:#FFFFFF;
    --bg-canvas:#F1ECF0; --bg-1:#FFFFFF; --bg-2:#F9F6F8; --bg-3:#F4EFF3;
    --bg-top:#FFFFFF; --bg-top-fg:#231D22;
    /* One hairline weight for every border; --stroke-1 is the control edge,
       --stroke-strong a pressed or open edge. */
    --stroke-1:#B2A5AF; --stroke-2:#CFC3CC; --stroke-3:#E0D7DE; --stroke-strong:#93848E;

    /* The one accent. */
    --accent:#0B5EA6; --accent-strong:#0E4C82; --accent-ink:#0A3960; --accent-hover:#0B5192;
    --accent-soft:#E1EAF5; --accent-soft-stroke:#A9C5E8;

    /* States: ink on a soft ground with a stroke, always beside a glyph and a word. */
    --success-fg:#0C5E0C; --success-bg:#DFEFDF; --success-stroke:#8ECB8E;
    --warning-fg:#9A3D05; --warning-bg:#F8E4D6; --warning-stroke:#EDB184;
    --danger-fg:#A10C19; --danger-bg:#F7DEE2; --danger-stroke:#E69AA2;
    --idea-fg:#5E4089; --idea-bg:#EFE7F6; --idea-stroke:#B9A6D6;
    --exp-fg:#0B6A60; --exp-bg:#DCF0EC; --exp-stroke:#7CC4B9;
    --branch:#8764B8;

    /* Grounds a page draws behind something rather than under text: the
       browser's selection, a dimmed backdrop behind a dialog, and the
       print sheet (always black ink on white paper, whatever the theme). */
    --selection:color-mix(in oklab, var(--accent) 28%, transparent);
    --bg-overlay:rgba(35,29,34,0.40);
    --print-bg:#FFFFFF; --print-fg:#000000; --print-muted:#555555; --print-line:#999999;

    /* Coverage / status fills (always shipped with a text label) */
    --cov-automated:var(--accent); --cov-partial:#DE9A45;
    --cov-candidate:#2F8F53; --cov-manual:#808080;

    /* The system stack, weights 400 and 600 (design system typography). No
       font file ships: Segoe UI is local on his Windows machine. */
    --font-sans:"Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
    --font-mono:Consolas, "SF Mono", ui-monospace, monospace;

    /* ---- Type scale (added 2026-09-17) ----------------------------------
       Six steps and no half-pixels. Every value is one a live page already
       renders, so adopting a token moves nothing on screen.
         xs   fine print: a timestamp, a source note
         sm   supporting: a caption, a field label, a count
         base everything a reader reads
         md   a step name, a table header, one emphasised line
         lg   a region heading
         xl   the page title, once
       Code and values sit in the mono family at base or sm only. The paired
       line height carries the same name. */
    --fs-xs:11px;   --lh-xs:16px;
    --fs-sm:12px;   --lh-sm:17px;
    --fs-base:13px; --lh-base:20px;
    --fs-md:15px;   --lh-md:22px;
    --fs-lg:19px;   --lh-lg:26px;
    --fs-xl:26px;   --lh-xl:32px;

    /* ---- Spacing scale (added 2026-09-17) -------------------------------
       One 4px base, six steps, nothing between them. */
    --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px;
    --space-5:24px; --space-6:32px;

    /* ---- Motion (added 2026-09-17) ---------------------------------------
       Nothing moves by default. A fold, a chip or a drawer takes --dur-fast;
       a palette or a toast entering takes --dur-base; a canvas "Fit" takes
       --dur-slow. Reduced motion zeroes all three below. */
    --dur-fast:100ms; --dur-base:200ms; --dur-slow:300ms;
    --ease-standard:cubic-bezier(0.4, 0, 0.2, 1); --ease-decelerate:cubic-bezier(0, 0, 0.2, 1);

    /* ---- Controls, targets, measure, stacking (added 2026-09-17) ----------
       One control height and one large one; the WCAG 2.2 target floor (2.5.8);
       the prose line cap three stylesheets each spelled differently; and the
       three stacking levels the site actually has. */
    --control-h:32px; --control-h-lg:40px; --target-min:24px;
    --measure:75ch;
    --z-top:40; --z-overlay:50; --z-toast:60;

    /* ---- Shape: two radii, one circle -----------------------------------
       A control is 4px, a surface is 8px, a dot is round. The five older
       names (--radius-s/m/l/xl/pill) were aliases until 2026-09-17, when every
       page that wrote them was rewritten onto these three. */
    --radius-control:4px; --radius-surface:8px; --radius-circle:999px;

    /* ---- Elevation: one resting shadow, one for a floating overlay -------
       Regions on a page do not float, so there are two. The older names
       (--shadow-1/2/6/hero) were aliases until 2026-09-17, when every page
       that wrote them was rewritten onto these two. */
    --shadow:0 0 2px rgba(35,29,34,0.12), 0 1px 2px rgba(35,29,34,0.16);
    --shadow-overlay:0 0 2px rgba(35,29,34,0.12), 0 4px 8px rgba(35,29,34,0.14);

    /* ---- The one filled button ------------------------------------------
       Light: white on the accent. Dark: white on the dark accent measures
       2.81:1, under the floor, so the dark theme swaps to the pairing the
       design brief measured at 6.65:1 — the brand's darkest ink on the
       lighter accent fill. Both spellings live here so no rule carries a
       colour of its own. */
    --btn-fill:var(--accent); --btn-fill-hover:var(--accent-hover); --btn-ink:var(--fg-inv);

    --ring:0 0 0 2px rgba(11,94,166,0.60);

    /* ---- Emphasis ladder (added 2026-10-11, design system round 14) --------
       Attention is a level, not a colour. A page spends emphasis once:
         lead     the one recommended press on the page (the asset page's
                  Copy / Open press) — an accent edge and a soft ring
         surface  a card: its ground and the resting shadow
         quiet    flat on the canvas with a hairline
       They derive from the theme's own tokens, so no theme re-colours them,
       and a page carries at most one lead. */
    --em-lead-bg:var(--accent-soft); --em-lead-line:1.5px solid var(--accent);
    --em-lead-shadow:0 0 0 3px color-mix(in oklab, var(--accent) 14%, transparent), var(--shadow);
    --em-surface-bg:var(--bg-1); --em-surface-line:1px solid var(--stroke-2); --em-surface-shadow:var(--shadow);
    --em-quiet-bg:transparent; --em-quiet-line:1px solid var(--stroke-2); --em-quiet-shadow:none;

    --maxw:1180px;
  }

  /* ---- Ultrawide: let the page use the screen (his 2026-08-13 note) ----
     The shell steps up with the viewport instead of pinning every monitor to
     one 1180px column; laptops and iPads keep the old measure untouched.
     Prose blocks keep their ch caps — a wider page buys more columns and
     breathing room, never 40-word lines. */
  @media (min-width:1500px){ :root { --maxw:1400px; } }
  @media (min-width:1800px){ :root { --maxw:1660px; } }
  @media (min-width:2240px){ :root { --maxw:1980px; } }

  /* ---- Dark tokens: the saved switch wins; with nothing saved, the OS preference ---- */
  /* Dark is the plum family, the same ink family as the light theme (design
     system round 14: canvas #17141A, cards #221E26 at 1.18:1, sunk #2C2731,
     track #37313D; accent #86BDF5). Values come from the design system's
     tokens/colors.css, written under this site's token names. Every text pair the
     asset page and the Library draw is held at 4.5:1 or better. Fluent's grey
     dark is gone; nothing reads its values.
     One set of values, written twice because CSS cannot alias a block: the media
     query covers "nothing saved yet, OS says dark", the attribute block covers
     the Dark switch (persisted as localStorage jvhub_theme and applied to <html>
     before first paint by the inline script in index.html's head). The brand
     ramp is unchanged; the neutrals, the semantic sets and the accent aliases move. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]):not([data-theme="dusk"]) {
    color-scheme:dark;
    --accent:#86BDF5; --accent-strong:#9ECBFA; --accent-ink:#B8D8FA; --accent-hover:#9ECBF8;
    --accent-soft:rgba(134,189,245,0.14); --accent-soft-stroke:rgba(134,189,245,0.5);
    --fg-1:#F2EEF2; --fg-2:#D4CED4; --fg-3:#BEB7BE; --fg-4:#857D87; --fg-inv:#FFFFFF;
    --bg-canvas:#17141A; --bg-1:#221E26; --bg-2:#2C2731; --bg-3:#37313D;
    --bg-top:#1C181F; --bg-top-fg:#F2EEF2;
    --stroke-1:#554D5C; --stroke-2:#352F3B; --stroke-3:#2E2934; --stroke-strong:#716876;

    --success-fg:#96D196; --success-bg:#2A3330; --success-stroke:#5D7F62;
    --warning-fg:#EDAD7C; --warning-bg:#3A2E2C; --warning-stroke:#8D6A55;
    --danger-fg:#F4A3AA; --danger-bg:#3B2C33; --danger-stroke:#91646C;
    --idea-fg:#C7AAE8; --idea-bg:#30263A; --idea-stroke:#5F4F75;
    --exp-fg:#8FDCCF; --exp-bg:#223733; --exp-stroke:#3C6B62;
    --branch:#C7AAE8;

    --cov-partial:#E8974A; --cov-candidate:#54B054; --cov-manual:#5C5C5C;

    --shadow:0 0 2px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.5);
    --shadow-overlay:0 0 2px rgba(0,0,0,0.5), 0 4px 8px rgba(0,0,0,0.6);

    --btn-fill:var(--accent-strong); --btn-fill-hover:var(--brand-150); --btn-ink:var(--brand-40);

    --ring:0 0 0 2px #221E26, 0 0 0 4px #86BDF5;
    --bg-overlay:rgba(0,0,0,0.55);
    }
  }
  :root[data-theme="dark"] {
    color-scheme:dark;
    --accent:#86BDF5; --accent-strong:#9ECBFA; --accent-ink:#B8D8FA; --accent-hover:#9ECBF8;
    --accent-soft:rgba(134,189,245,0.14); --accent-soft-stroke:rgba(134,189,245,0.5);
    --fg-1:#F2EEF2; --fg-2:#D4CED4; --fg-3:#BEB7BE; --fg-4:#857D87; --fg-inv:#FFFFFF;
    --bg-canvas:#17141A; --bg-1:#221E26; --bg-2:#2C2731; --bg-3:#37313D;
    --bg-top:#1C181F; --bg-top-fg:#F2EEF2;
    --stroke-1:#554D5C; --stroke-2:#352F3B; --stroke-3:#2E2934; --stroke-strong:#716876;

    --success-fg:#96D196; --success-bg:#2A3330; --success-stroke:#5D7F62;
    --warning-fg:#EDAD7C; --warning-bg:#3A2E2C; --warning-stroke:#8D6A55;
    --danger-fg:#F4A3AA; --danger-bg:#3B2C33; --danger-stroke:#91646C;
    --idea-fg:#C7AAE8; --idea-bg:#30263A; --idea-stroke:#5F4F75;
    --exp-fg:#8FDCCF; --exp-bg:#223733; --exp-stroke:#3C6B62;
    --branch:#C7AAE8;

    --cov-partial:#E8974A; --cov-candidate:#54B054; --cov-manual:#5C5C5C;

    --shadow:0 0 2px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.5);
    --shadow-overlay:0 0 2px rgba(0,0,0,0.5), 0 4px 8px rgba(0,0,0,0.6);

    --btn-fill:var(--accent-strong); --btn-fill-hover:var(--brand-150); --btn-ink:var(--brand-40);

    --ring:0 0 0 2px #221E26, 0 0 0 4px #86BDF5;
    --bg-overlay:rgba(0,0,0,0.55);
  }

  /* ---- Dusk: the mid-dark theme (design system, 11 Oct) ---------------------
     Plum slate, not black: the canvas lifts to #252128 and cards sit a step
     above it, so depth comes from value, not glow — the same mauve family as
     Stone Mauve, a stop darker. Signal inks lighter and less saturated than
     Dark's. The filled button keeps Dark's light fill with dark ink. Only the
     switch reaches it (jvhub_theme "dusk"); an operating system that says dark
     gets Dark, so Dusk has no media-query copy. Every text pair the asset page
     and the Library draw holds 4.5:1 or better. */
  :root[data-theme="dusk"] {
    color-scheme:dark;
    --accent:#7CB6F3; --accent-strong:#96C6FA; --accent-ink:#B4D6FA; --accent-hover:#96C6F7;
    --accent-soft:rgba(124,182,243,0.12); --accent-soft-stroke:rgba(124,182,243,0.5);
    --fg-1:#EEEAEE; --fg-2:#CFC9CF; --fg-3:#BCB5BC; --fg-4:#847C85; --fg-inv:#FFFFFF;
    --bg-canvas:#252128; --bg-1:#34303A; --bg-2:#3E3944; --bg-3:#4A4450;
    --bg-top:#2C2830; --bg-top-fg:#EEEAEE;
    --stroke-1:#655E6C; --stroke-2:#484250; --stroke-3:#403A47; --stroke-strong:#7F7786;

    --success-fg:#8FCD8F; --success-bg:#3F4344; --success-stroke:#627F65;
    --warning-fg:#EAA876; --warning-bg:#4A3E41; --warning-stroke:#8F6C58;
    --danger-fg:#F29CA4; --danger-bg:#4B3D47; --danger-stroke:#93666F;
    --idea-fg:#C3A6E6; --idea-bg:#3D3448; --idea-stroke:#625278;
    --exp-fg:#8FDCCF; --exp-bg:#2F4542; --exp-stroke:#3F6F66;
    --branch:#C3A6E6;

    --cov-partial:#E8974A; --cov-candidate:#54B054; --cov-manual:#5C5C5C;

    --shadow:0 0 2px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.36);
    --shadow-overlay:0 0 2px rgba(0,0,0,0.36), 0 4px 8px rgba(0,0,0,0.4);

    --btn-fill:var(--accent-strong); --btn-fill-hover:var(--brand-150); --btn-ink:var(--brand-40);

    --ring:0 0 0 2px #34303A, 0 0 0 4px #7CB6F3;
    --bg-overlay:rgba(0,0,0,0.50);
  }

  /* Kind dots — one dot, no badges. Names match the design-system tokens.
     Ten kinds (the four local-script kinds read only when a script of that kind is published). Six since 2026-09-17: a SharePoint list is its own kind and used to
     borrow a --fit-* colour. --node-bottleneck and --node-opportunity went the
     same day: nothing on the site read them (the estate says the same thing
     with --cov-*). */
  :root {
    --node-script:#0F6CBD; --node-flow:#8764B8; --node-query:#0E700E;
    --node-bundle:#616161; --node-setup:#BC4B09; --node-list:#3E7CB1;
    --node-python:#8A6A00; --node-powershell:#2B3F94; --node-shell:#00717A; --node-batch:#8E4F63;
    --node-manual:#616161; --node-automated:#0F6CBD; --node-decision:#8764B8;
  }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]):not([data-theme="dusk"]) { --node-script:#479EF5; --node-flow:#B18FDB; --node-query:#54B054;
      --node-bundle:#A8A8A8; --node-setup:#E8974A; --node-list:#7FB2DE;
      --node-python:#E3BE4B; --node-powershell:#93A3EC; --node-shell:#58C4CC; --node-batch:#D993A8;
      --node-manual:#A8A8A8; --node-automated:#479EF5; --node-decision:#B18FDB; }
  }
  :root[data-theme="dusk"] { --node-script:#479EF5; --node-flow:#B18FDB; --node-query:#54B054;
    --node-bundle:#A8A8A8; --node-setup:#E8974A; --node-list:#7FB2DE;
    --node-python:#E3BE4B; --node-powershell:#93A3EC; --node-shell:#58C4CC; --node-batch:#D993A8;
    --node-manual:#A8A8A8; --node-automated:#479EF5; --node-decision:#B18FDB; }
  :root[data-theme="dark"] { --node-script:#479EF5; --node-flow:#B18FDB; --node-query:#54B054;
    --node-bundle:#A8A8A8; --node-setup:#E8974A; --node-list:#7FB2DE;
    --node-python:#E3BE4B; --node-powershell:#93A3EC; --node-shell:#58C4CC; --node-batch:#D993A8;
    --node-manual:#A8A8A8; --node-automated:#479EF5; --node-decision:#B18FDB; }


  /* ---- Added 2026-09-15: what the pictures of the close need -------------
     Three groups, none of them new colours — each one is a literal that was
     already on a page, given a name so a picture and a legend can agree.

     GATE CLASSES. What a step on the close is waiting for. The four values are
     the Close Calendar's own, unchanged; one set for both themes, because they
     are read as swatches beside a text label, never as text.
       time    a date on the calendar        task   another deliverable
       team    somebody on the team          partner  somebody outside it

     EDGES. How two things on a map are joined. The Flow Mapper's values,
     unchanged, light and dark.
       --edge         one thing calls another (solid)
       --edge-system  a system feeds it (dashed)
       --edge-feed    a partner or a calendar feeds it (dashed, lighter)

     CONNECTORS. The product colour of the tool an action runs in — Excel green,
     SharePoint teal, and so on. On a page it appears in exactly one place: the
     7px dot on a connector chip (brief R8, refined 2026-09-17 from his
     Designer v6 mockup). A step card's edge is its control role (--node-* via
     the build page's .k-* rules), never its tool, and the drawn flow's tiles
     stay neutral — since 2026-09-20 a tile may carry the product's OWN mark,
     used as shipped, but its background is still --bg-3 and never the tool's
     colour. Documentation figures and demos may use these freely.

     LANES are not here. A lane's colour IS its meaning and it already has a
     token set: the Yes lane is --success-*, a Switch case is --idea-*, and the
     No lane is the same ink drawn dashed. A second name for those would be a
     second answer. */
  :root {
    --gate-time:#2A78D6; --gate-task:#EB6834; --gate-team:#1BAF7A; --gate-partner:#EDA100;
    --edge:#9AA0A6; --edge-system:#5B6470; --edge-feed:#B8BCC4;
    --conn-excel:#107C41; --conn-sp:#038387; --conn-mail:#0F6CBD; --conn-onedrive:#0364B8;
    --conn-teams:#6264A7; --conn-ai:#B146C2; --conn-data:#A47BD6; --conn-ctrl:#8A8A8A;
  }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]):not([data-theme="dusk"]) { --edge:#7F868E; --edge-system:#A3ABB5; --edge-feed:#5C6470; }
  }
  :root[data-theme="dusk"] { --edge:#7F868E; --edge-system:#A3ABB5; --edge-feed:#5C6470; }
  :root[data-theme="dark"] { --edge:#7F868E; --edge-system:#A3ABB5; --edge-feed:#5C6470; }

  /* ---- Reduced motion: every duration is zero (added 2026-09-17) ---- */
  @media (prefers-reduced-motion:reduce) {
    :root { --dur-fast:0ms; --dur-base:0ms; --dur-slow:0ms; }
  }

  /* ---- The code panel (added 2026-09-20) --------------------------------
     A code panel is dark on both themes — that was already true, as three hex
     literals sitting in hub.css. They are named here instead, and six reading
     roles join them so a script, a query or a payload can be read rather than
     scanned.

     One set of values, no dark variant, because the panel does not change with
     the theme; the same reason --gate-* carries one set.

     The six roles borrow the dark node palette rather than opening a seventh
     colour family: a keyword is the flow violet, a number the setup amber, a
     type the list blue. What the page already means by a colour, the code
     panel means too. A function name is the one new value — SharePoint teal
     lifted to read on near-black. */
  :root {
    --code-bg:#141310; --code-fg:#EDEBE6; --code-stroke:#2C2A24;
    --code-btn:rgba(255,255,255,0.14); --code-btn-hover:rgba(255,255,255,0.26);
    --code-comment:#8E887A; --code-string:#9BC27B; --code-key:#B18FDB;
    --code-num:#E8974A; --code-type:#7FB2DE; --code-fn:#5FC9CE;

    /* The white square a product mark sits on in dark mode. Microsoft's marks
       may not be recoloured, so the chip puts a backing behind the artwork
       rather than touching it — the rule was already in force in two places
       as a literal #FFFFFF; this names it once. */
    --mark-backing:#FFFFFF;
  }
