

  @font-face {
    font-family: "Zilla Slab";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/zilla-slab-600-v1.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+20AC, U+2122, U+2191, U+2193,
      U+2212, U+2215, U+FEFF, U+FFFD;
  }

  :root {
    color-scheme: light dark;

    --paper: #f9f9f7;
    --surface: #fcfcfb;
    --ink: #0b0b0b;
    --ink-secondary: #52514e;
    --ink-muted: #6b6a64;
    --thread: #8a5a2b;
    --thread-strong: #6b4420;
    --good: #0a7d1f;
    --bad: #c0272a;
    --warn: #b85325;
    --line: #e1e0d9;
    --line-strong: #c3c2b7;

    /* A step below --surface for a chip or code span that reads as set INTO
       the page rather than floating on it (admin-shell.ts's code element).
       Between --paper and --line, not a copy of either — a wash needs to be
       its own step or it disappears into whichever neighbour it matches. */
    --surface-sunken: #edece7;

    /* Pale washes for a table row's own verdict (budget diff added/changed) —
       a tint of --good/--warn low enough that --ink stays legible on it
       without a second colour token, derived from the semantic colour rather
       than picked free-hand so a palette change carries the wash with it. */
    --good-wash: #e9f2e9;
    --warn-wash: #f8f2ee;

    /* The ink that sits ON a thread-coloured ground (primary button, the
       account initial, the active preference chip). A literal #fff cannot
       follow a palette: in dark the thread is a light tan, and white on it
       reads at ~2.2:1. This token is the pair, not the colour. */
    --ink-on-thread: #ffffff;

    /* Chart fills live here rather than in chart.ts, because a palette only
       one file can see is a palette only one file can follow — and that is
       exactly why the chart had no dark mode. The light values are the mock's,
       unchanged. */
    --chart-stockholm: #2b3831;
    --chart-london: #e8dbcc;
    --chart-oslo: #282016;
    --chart-new-york: #8e7d6d;
    --chart-chip-edge: #8e7d6d;

    /* Series marks — up to six people on one chart.
       A LIGHTNESS LADDER first, hue second. Six hues alone cannot do this:
       under deuteranopia a hue-only set collapses to two or three
       distinguishable marks, which chart-palette.test.ts measures by
       simulating protan/deutan/tritan and holding every pair apart by CIE76
       ΔE. Stepping the lightness is what survives that simulation, because
       brightness is the one channel colour-vision deficiency leaves intact.
       The renderers add a third channel on top: each series has its own dash
       pattern. */
    --chart-series-1: #22346f;
    --chart-series-2: #762806;
    --chart-series-3: #67385d;
    --chart-series-4: #3c7a58;
    --chart-series-5: #1a919c;
    --chart-series-6: #a58b48;

    /* Single-subject marks: a store's own bars, the day still filling, the
       day being looked at, the budget pace, and the store line a person is
       measured against. */
    --chart-bar: #33473f;
    --chart-bar-partial: #7f948b;
    --chart-emphasis: #8a5a2b;
    --chart-pace: #52514e;
    --chart-reference: #5f5e57;

    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;

    --font-display: "Zilla Slab", Georgia, "Times New Roman", serif;
    --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

    --stitch-line: repeating-linear-gradient(to right, var(--line-strong) 0 6px, transparent 6px 10px);
    --stitch-thread: repeating-linear-gradient(to right, var(--thread) 0 6px, transparent 6px 10px);
  }

  /* Dark palette. Only colour tokens are redefined — spacing, fonts and the
     stitch gradients are palette-independent, and the gradients follow because
     they are built from var(--line-strong) and var(--thread).

     Every light value above is left untouched: this block only ADDS a
     conditional override, so a reader in light mode sees exactly the page
     they saw before. That is pinned by a test over the light token block.

     The chart fills are not the light ones darkened. Two of them (the dark
     green and the espresso) are chosen to sit on paper; on a dark ground they
     would disappear into it, so the dark set re-picks them for the same job —
     four fills that stay apart from each other and from the background — with
     the contrast pinned by test rather than by eye.

     The OS preference is the only input; there is no manual toggle. That is a
     consequence rather than an omission — a switch needs a control and
     somewhere to persist the choice, and this app ships zero client-side
     JavaScript. Adding one is a decision to give that up, not an afternoon's
     work, so it belongs to whoever is willing to make that trade. */
  @media (prefers-color-scheme: dark) {
    :root {
      --paper: #131311;
      --surface: #1c1c18;
      --ink: #f4f2ec;
      --ink-secondary: #bcb9b0;
      --ink-muted: #8f8d84;
      --thread: #d09154;
      --thread-strong: #e5aa6e;
      --good: #56c26f;
      --bad: #ff7376;
      --warn: #ff9d5f;
      --line: #302f2a;
      --line-strong: #4e4c44;

      /* Re-derived against the dark ground rather than the light set
         inverted — a light wash inverted would go darker than --surface and
         read as a shadow, not a chip. */
      --surface-sunken: #262521;
      --good-wash: #233022;
      --warn-wash: #372b21;

      --ink-on-thread: #131311;

      /* Spaced by lightness, not just by hue. The first attempt kept the
         light set's hues and only lifted them off the ground, which put the
         green and the gold within 1.03:1 of each other — four fills that
         read as two. The contrast test caught it; these four are stepped so
         each is distinguishable from the others and from the ground. */
      --chart-stockholm: #4a9a7c;
      --chart-london: #f2e9dc;
      --chart-oslo: #d9b45c;
      --chart-new-york: #6b5644;
      --chart-chip-edge: #7d766a;

      /* Designed against the dark ground, not the light set inverted: the
         same ladder, re-derived so series 1 is the darkest mark that still
         clears 3:1 on --surface and series 6 the lightest. Inverting the
         light values would put the two darkest series below the ground and
         lose them entirely. Same test, same thresholds, both palettes. */
      --chart-series-1: #4562bf;
      --chart-series-2: #c25a2a;
      --chart-series-3: #a76b99;
      --chart-series-4: #81c19c;
      --chart-series-5: #63cbd9;
      --chart-series-6: #e9c87f;

      --chart-bar: #6d9b8a;
      --chart-bar-partial: #5a7267;
      --chart-emphasis: #e8a765;
      --chart-pace: #bcb9b0;
      --chart-reference: #8f8d84;
    }
  }

  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; }
  body {
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-body);
  }

  a { color: var(--thread); }
  a:hover { color: var(--thread-strong); }

  :focus-visible {
    outline: 2px solid var(--thread);
    outline-offset: 2px;
  }

  .stitch { height: 2px; background-image: var(--stitch-line); }
  .stitch--thread { background-image: var(--stitch-thread); }

  /* Nothing in this brand is rounded. Set to 0 rather than simply omitted:
     iOS Safari's UA stylesheet rounds buttons and several input types, so
     dropping the declaration would hand the corners back on exactly the
     devices the managers use. */
  button, input, select, textarea {
    font: inherit; color: inherit; border-radius: 0;
  }

  .btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
    min-height: 44px; padding: 0 var(--space-4);
    border: 1px solid var(--line-strong); border-radius: 0;
    background: var(--surface); color: var(--ink);
    font-size: 14px; text-decoration: none; cursor: pointer;
  }
  .btn:hover { border-color: var(--ink-muted); }
  .btn--primary {
    background: var(--thread-strong); border-color: var(--thread-strong); color: var(--ink-on-thread);
  }
  .btn--primary:hover {
    background: var(--thread); border-color: var(--thread); color: var(--ink-on-thread);
  }
  .btn--danger { color: var(--bad); border-color: var(--bad); }
  .btn:disabled { color: var(--ink-muted); cursor: not-allowed; }

  .field { display: flex; flex-direction: column; gap: var(--space-1); }
  .field label { font-size: 12px; color: var(--ink-secondary); }
  .field input, .field select, .field textarea {
    min-height: 44px; padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line-strong); border-radius: 0;
    background: var(--surface);
  }
  .field textarea { min-height: 160px; font-family: var(--font-mono); font-size: 13px; resize: vertical; }

  .notice { padding: var(--space-3) var(--space-4); font-size: 13px; }
  .notice--error { border: 1px solid var(--bad); color: var(--bad); }
  .notice--ok { border: 1px solid var(--good); color: var(--good); }
  .notice--warn { border: 1px solid var(--warn); color: var(--warn); }

  @media print {
    .no-print { display: none !important; }
    body { background: #fff; }
  }


  .shell { background: var(--surface); border-bottom: 1px solid var(--line); }
  .shell-inner { max-width: 900px; margin: 0 auto; padding: var(--space-3) var(--space-4) 0; }
  .shell-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
  .shell-controls { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }

  .freshness { margin: var(--space-1) 0 0; font-size: 11px; color: var(--ink-muted); }

  .wordmark {
    font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--ink-muted); text-decoration: none; white-space: nowrap;
  }
  .wordmark:hover { color: var(--ink-secondary); }

  /* The anchor picker. Stepping a day is one tap on a 44px target; picking
     an arbitrary date is one more, inside the disclosure. Phone-first: the
     motion a manager makes twenty times a week is the cheap one. */
  .anchor { position: relative; display: flex; align-items: center; }
  .anchor-step {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; min-height: 44px;
    font-size: 18px; line-height: 1; text-decoration: none; color: var(--ink-secondary);
  }
  .anchor-step:hover { color: var(--ink); }
  .anchor-step--off { color: var(--line-strong); }
  .anchor-menu > summary {
    display: inline-flex; align-items: center; gap: var(--space-1);
    min-height: 44px; padding: 0 var(--space-2);
    cursor: pointer; list-style: none; white-space: nowrap;
  }
  .anchor-menu > summary::-webkit-details-marker { display: none; }
  .anchor-menu > summary::after { content: "▾"; font-size: 9px; vertical-align: 2px; color: var(--ink-muted); }
  .anchor-label {
    font-size: 13px; font-weight: 600; color: var(--ink);
    border-bottom: 2px solid transparent;
  }
  .anchor-menu[open] > summary .anchor-label { border-bottom-color: var(--thread); }
  .anchor-panel {
    position: absolute; right: 0; top: 100%; z-index: 6;
    width: 244px; max-width: calc(100vw - 2 * var(--space-2));
    background: var(--surface); border: 1px solid var(--line-strong);
    padding: var(--space-3);
    display: flex; flex-direction: column; gap: var(--space-2);
  }
  /* Explicit rather than relying on <details>'s own closed-state behaviour:
     a modern engine keeps the collapsed panel's box laid out (for its own
     open/close transition) even though nothing paints — invisible to a
     reader, but not to a bounding-rect measurement. */
  .anchor-menu:not([open]) > .anchor-panel { display: none; }
  .anchor-form { display: flex; flex-direction: column; gap: var(--space-2); }
  .anchor-field { font-size: 12px; color: var(--ink-secondary); }
  .anchor-input {
    min-height: 44px; width: 100%; padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line-strong); border-radius: 0;
    background: var(--surface);
  }
  .anchor-go { width: 100%; }
  .anchor-today { font-size: 13px; min-height: 44px; display: inline-flex; align-items: center; }

  .account { position: relative; }
  .account > summary {
    display: inline-flex; align-items: center; gap: var(--space-2);
    min-height: 44px; padding: 0 var(--space-2);
    font-size: 12px; color: var(--ink-secondary); cursor: pointer; list-style: none;
  }
  .account > summary::-webkit-details-marker { display: none; }
  /* Square, not a circle — the brand has no rounding, and that applies to
     the avatar chip too. */
  .account-initial {
    display: inline-grid; place-items: center; width: 26px; height: 26px;
    background: var(--thread); color: var(--ink-on-thread); font-size: 12px; font-weight: 600;
  }
  .account-email { display: none; }
  .account-panel {
    position: absolute; right: 0; top: 100%; z-index: 6;
    width: 244px; max-width: calc(100vw - 2 * var(--space-2));
    background: var(--surface); border: 1px solid var(--line-strong);
    padding: var(--space-3);
    display: flex; flex-direction: column; gap: var(--space-3);
  }
  .account-panel-email { margin: 0; font-size: 12px; color: var(--ink-secondary); word-break: break-all; }
  .account-pref { display: flex; flex-direction: column; gap: var(--space-2); }
  .account-pref-label {
    margin: 0; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--ink-muted);
  }
  .account-link { font-size: 13px; min-height: 44px; display: inline-flex; align-items: center; }

  .lens { display: flex; flex-wrap: wrap; gap: 2px; }
  .lens-option { margin: 0; }
  .lens-tab {
    font: inherit; font-size: 12px; letter-spacing: 0.02em; cursor: pointer;
    min-height: 32px; padding: 2px 10px; border: 1px solid var(--line);
    background: var(--surface); color: var(--ink-secondary);
  }
  .lens-tab:hover { color: var(--ink); }
  .lens-tab--active {
    color: var(--ink-on-thread); background: var(--thread);
    border-color: var(--thread); font-weight: 600;
  }

  /* The primary nav is HQ-only and three items wide at most (Stores, Store,
     Admin), so it no longer needs the wrap the seven-tab version did — but
     it keeps it: a wrapped row is still reachable where a scroll box is not. */
  .nav { display: flex; gap: var(--space-5); flex-wrap: wrap; }
  .nav-tab {
    position: relative; display: inline-flex; align-items: center;
    min-height: 44px; font-size: 14px; text-decoration: none; color: var(--ink-secondary);
  }
  .nav-tab:hover { color: var(--ink); }
  .nav-tab--active { color: var(--ink); font-weight: 600; }
  .nav-tab .stitch { position: absolute; left: 0; right: 0; bottom: 0; }

  /* The Store nested menu — same disclosure technique and panel styling as
     .account above, so a reader who has already met one zero-JS menu in this
     shell recognises the second. */
  .nav-store-menu { position: relative; }
  .nav-store-menu > summary { list-style: none; cursor: pointer; }
  .nav-store-menu > summary::-webkit-details-marker { display: none; }
  .nav-store-menu > summary::after { content: "▾"; margin-left: 4px; font-size: 9px; vertical-align: 2px; }
  .nav-store-menu-panel {
    position: absolute; left: 0; top: 100%; z-index: 5;
    max-width: calc(100vw - 2 * var(--space-2));
    min-width: 200px;
    background: var(--surface); border: 1px solid var(--line-strong);
    padding: var(--space-2) 0;
  }
  .nav-store-menu:not([open]) > .nav-store-menu-panel { display: none; }
  .nav-store-menu[open] > .nav-store-menu-panel { display: flex; flex-direction: column; }
  .nav-store-menu-item {
    display: flex; align-items: center; min-height: 44px;
    padding: 0 var(--space-3);
    font-size: 14px; text-decoration: none; color: var(--ink-secondary);
  }
  .nav-store-menu-item:hover { color: var(--ink); }
  .nav-store-menu-item--active { color: var(--ink); font-weight: 600; }

  /* A page's own tabs (Overview | Team, Budgets | Access | People). Sits
     under the page title, inside <main> — the second stacked nav bar the
     admin console used to grow is gone, and this is what replaced it. */
  .tabrow {
    display: flex; flex-wrap: wrap; gap: var(--space-4);
    margin: 0 0 var(--space-5); border-bottom: 1px solid var(--line);
  }
  .tabrow-tab {
    position: relative; display: inline-flex; align-items: center;
    min-height: 44px; font-size: 14px; text-decoration: none; color: var(--ink-secondary);
  }
  .tabrow-tab:hover { color: var(--ink); }
  .tabrow-tab--active { color: var(--ink); font-weight: 600; }
  .tabrow-tab .stitch { position: absolute; left: 0; right: -1px; bottom: -1px; }

  /* Thumb-sized by rule, not by luck: four options a manager switches
     between one-handed, on a phone, while the shop is open. */
  .period { display: flex; flex-wrap: wrap; gap: 2px; margin: 0 0 var(--space-4); }
  .period-opt {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; min-width: 64px; padding: 0 var(--space-3);
    border: 1px solid var(--line);
    font-size: 13px; text-decoration: none;
    background: var(--surface); color: var(--ink-secondary);
  }
  .period-opt:hover { color: var(--ink); border-color: var(--line-strong); }
  .period-opt--active {
    color: var(--ink-on-thread); background: var(--thread);
    border-color: var(--thread); font-weight: 600;
  }

  .page { max-width: 900px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-7); }

  @media (min-width: 640px) {
    .account-email { display: inline; }
    .account-initial { display: none; }
  }
  @media (max-width: 600px) {
    .shell-inner { padding: var(--space-2) var(--space-2) 0; }
    .page { padding: var(--space-4) var(--space-2) var(--space-6); }
    .nav { gap: var(--space-4); }
    /* A floating panel anchored to the menu's OWN left edge (like above)
       cannot promise it stays inside a 390px viewport — the Store menu can
       land anywhere in the wrapped nav row, not just near an edge. Below
       the panel's own min-width, drop the float and let it open in normal
       flow instead, full width, the same way a native <select> would rather
       than clipping or scrolling sideways. */
    .nav-store-menu-panel { position: static; max-width: none; width: 100%; margin-top: var(--space-2); }
  }
  @media print {
    .page { max-width: none; padding: 0; }
  }

  /* tests/geometry/fixture-server.ts's nav-check fixture only — scoped to
     classes no real page's markup carries, so these can never reach a
     production page. min-height gives the shell's chrome room to lay out
     with no report content below it; the heading needs the display font
     explicitly because nothing else on this fixture page requests it, and
     document.fonts.check would otherwise (correctly) read "not loaded". */
  .nav-fixture-body { min-height: 300px; }
  .nav-fixture-heading { font-family: var(--font-display); }


  /* Store/Team/Stores/People all give a table its own horizontal scroll box
     rather than letting the page scroll sideways. Store additionally puts a
     border and overscroll containment on its own (see STORE_CSS's
     ".store .table-scroll" — a genuine per-page difference, not drift).

     The background is the CSS-only "scrolling shadows" recipe: a solid
     --paper cover sits on each edge and scrolls WITH the content
     (background-attachment: local), painted on top of a shadow gradient
     fixed to the box (background-attachment: scroll). A cover exactly
     occludes its shadow whenever there is nothing left to scroll to on
     that side — including a table that fits and never scrolls at all,
     since then both covers already sit on both edges — so a shadow only
     ever shows where a column is actually hidden off-screen. No JS, no
     ResizeObserver. */
  .table-scroll {
    overflow-x: auto;
    background-image:
      linear-gradient(to right, var(--paper) 30%, rgba(0, 0, 0, 0)),
      linear-gradient(to left, var(--paper) 30%, rgba(0, 0, 0, 0)),
      radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0)),
      radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0));
    background-position: left, right, left, right;
    background-repeat: no-repeat;
    background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
    background-attachment: local, local, scroll, scroll;
  }

  /* A sortable column header, used by Stores/Team/People. Team's hover state
     also gets an underline (TEAM_CSS's ".page--team .sortlink:hover") — the
     rest of this rule is identical everywhere else, so only that delta stays
     page-local. */
  .sortlink { color: inherit; text-decoration: none; }
  .sortlink:hover { color: var(--ink); }
  .sortlink--active { color: var(--ink); }

  /* A denominator set under a rate, in the mono/uppercase treatment every
     "N of M" figure in this app uses (Stores/Team). */
  .den {
    font-family: var(--font-mono); font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.06em; color: var(--thread-strong);
  }

  /* Screen-reader-only content (Stores/Team/People). */
  .visually-hidden {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }

  /* A titled block of content and its heading, used identically by Store
     Overview and Team (T-something's period block, Team's UPT/adjustments
     blocks). */
  .block { margin: 0 0 var(--space-6); }
  .block-heading {
    font-size: 15px; font-weight: 600; margin: 0 0 var(--space-3); color: var(--ink);
  }

  /* The store name heading Store Overview and Team both render (Team's own
     comment used to say so explicitly: "Same shape and same values as the
     Store Overview's own block furniture, so the two tabs of one store do
     not render one window heading two ways"). Store additionally shrinks
     this at phone width (STORE_CSS's ".store .store-title" media rule) —
     Team does not, and that asymmetry is existing behaviour, not a bug this
     file should paper over. */
  .store-title { font-family: var(--font-display); font-size: 24px; margin: 0 0 var(--space-2); font-weight: 600; }

  /* A lede paragraph and a caption/legend line, used identically by Team and
     People. Team also tightens the legend's own margins for its narrower
     layout (TEAM_CSS's ".page--team .legend") — that stays page-local. */
  .lede, .legend {
    margin: 0 0 var(--space-4); max-width: 62ch;
    color: var(--ink-secondary); font-size: 13px; line-height: 1.5;
  }


  .admin { max-width: 760px; margin-top: var(--space-5); }
  .admin h1 { font-family: var(--font-display); font-size: 22px; margin: 0 0 var(--space-2); font-weight: 600; }
  .admin h2 { font-size: 16px; margin: var(--space-6) 0 var(--space-2); }
  .admin .hint { font-size: 13px; color: var(--ink-secondary); margin: 0 0 var(--space-4); }
  .admin section { margin-bottom: var(--space-6); }
  .admin form { display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }
  .admin .actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
  .admin .field { width: 100%; }
  .admin code { background: var(--surface-sunken); padding: 1px 4px; font-family: var(--font-mono); }

  /* A row wide enough to need it (a long store list, a long email) must
     scroll inside its own box rather than push the whole page sideways —
     the same technique people-page.ts's own tables already use. */
  .admin .table-scroll { overflow-x: auto; }
  .admin table { border-collapse: collapse; width: 100%; font-size: 13px; margin: var(--space-3) 0; }
  .admin th, .admin td {
    padding: var(--space-2); border-bottom: 1px solid var(--line);
    text-align: left; vertical-align: top;
  }
  .admin th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--ink-secondary); }
  .admin td.num { text-align: right; font-variant-numeric: tabular-nums; }

  .errors { color: var(--bad); font-size: 13px; margin: var(--space-2) 0; padding-left: 18px; }
  .warnings { color: var(--warn); font-size: 13px; margin: var(--space-2) 0; padding-left: 18px; }
  .counts { font-size: 13px; color: var(--ink-secondary); }
  tr.diff-add td { background: var(--good-wash); }
  tr.diff-change td { background: var(--warn-wash); }


  .card-page { display: grid; place-items: center; min-height: 100vh; padding: var(--space-5); }
  .card {
    width: 100%; max-width: 420px;
    background: var(--surface); border: 1px solid var(--line);
    padding: var(--space-6);
    text-align: center;
  }
  .card .stitch { margin-bottom: var(--space-5); }
  .card h1 { font-family: var(--font-display); font-size: 19px; margin: 0 0 var(--space-3); }
  .card p { font-size: 14px; line-height: 1.5; color: var(--ink-secondary); margin: 0 0 var(--space-2); }
  .card .muted { font-size: 12px; color: var(--ink-muted); }
  .card form { margin-top: var(--space-4); }


  .card .eyebrow {
    font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--ink-muted); margin: 0 0 var(--space-2);
  }
  .card .lede { font-family: var(--font-display); font-size: 18px; color: var(--ink); line-height: 1.4; }
  .card .notice { margin: var(--space-4) 0; text-align: left; }
  .card .btn { width: 100%; }

  .google-btn { background: #fff; border-color: #dadce0; color: #3c4043; font-weight: 500; }
  .google-btn:hover { background: #f8f9fa; border-color: #d2d5d9; }
  .g-mark { flex: none; }


  .help { max-width: 640px; }
  .help-header { margin-bottom: var(--space-6); }
  .help h1 {
    font-family: var(--font-display); font-size: 26px; font-weight: 600;
    margin: 0 0 var(--space-2);
  }
  .help-intro { margin: 0; font-size: 15px; line-height: 1.5; color: var(--ink-secondary); }

  .help-toc { margin: var(--space-4) 0 var(--space-6); }
  .help-toc ol { margin: 0; padding-left: var(--space-5); columns: 2; column-gap: var(--space-5); }
  .help-toc li { font-size: 14px; line-height: 1.7; break-inside: avoid; }

  .help-section { margin-bottom: var(--space-6); }
  .help-section h2 {
    font-family: var(--font-display); font-size: 18px; font-weight: 600;
    margin: 0 0 var(--space-2); padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--line);
  }
  .help-lede { margin: 0 0 var(--space-4); font-size: 14px; line-height: 1.55; color: var(--ink-secondary); }

  .help-list { margin: 0; }
  .help-entry { margin-bottom: var(--space-4); }
  .help-entry dt { font-size: 14px; font-weight: 600; margin-bottom: var(--space-1); }
  .help-entry dd { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink-secondary); }

  @media (max-width: 600px) {
    .help h1 { font-size: 22px; }
  }


  .stores h1 { font-family: var(--font-display); font-size: 24px; margin: 0 0 var(--space-4); font-weight: 600; }

  .stores-table { width: 100%; border-collapse: collapse; font-size: 13px; }
  .stores-table th, .stores-table td {
    text-align: left; padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--line); white-space: nowrap;
  }
  .stores-table thead th { white-space: normal; }
  .stores-table thead th {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-secondary); font-weight: 600; vertical-align: bottom;
  }
  .stores-table tbody th { font-weight: 400; }
  .stores-table tbody th a { color: var(--ink); text-decoration: none; }
  .stores-table tbody th a:hover { text-decoration: underline; }
  .stores-table .num { text-align: right; font-variant-numeric: tabular-nums; }

  .row--total th, .row--total td {
    border-top: 1px solid var(--line-strong); border-bottom: none; font-weight: 600;
  }

  .stores-note {
    font-size: 12px; color: var(--ink-secondary); margin: var(--space-3) 0 0;
  }

  /* "Closed" is a state, not a figure, so it does not wear the tabular-nums
     of the money it sits among. Token colour only. */
  .stores-table .cell--closed { color: var(--ink-muted); font-variant-numeric: normal; }

  @media (max-width: 600px) {
    .stores h1 { font-size: 20px; }
  }


  .store-scope { font-size: 12px; color: var(--ink-muted); margin: 0 0 var(--space-3); }

  .store-empty {
    font-size: 13px; color: var(--ink-secondary); border: 1px solid var(--line);
    background: var(--surface); padding: var(--space-3); margin: 0 0 var(--space-4);
  }

  /* Store Overview's own tiles, distinct from Person's (person-page.ts's
     PERSON_CSS) — a denser phone-first grid at a smaller value size, rather
     than Person's auto-fit KPI-summary look. Scoped under .store (the page's
     own root wrapper, store-page.ts's renderStoreOverviewBody) because both
     pages reuse the bare .tiles/.tile/.tile-label/.tile-value/.tile-note
     names — see tests/unit/css-collisions.test.ts.

     Phone first: two by two at 390, wider as the room appears. The reserved
     slot for a fifth tile (footfall conversion) costs nothing here — the grid
     reflows. */
  .store .tiles {
    display: grid; gap: var(--space-3); margin: 0 0 var(--space-4);
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .store .tile { border: 1px solid var(--line); background: var(--surface); padding: var(--space-3); }
  .store .tile-label {
    margin: 0; font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em;
    color: var(--ink-secondary);
  }
  .store .tile-value {
    margin: 6px 0 4px; font-size: 20px; font-variant-numeric: tabular-nums; color: var(--ink);
  }
  .store .tile-note { margin: 0; font-size: 11px; color: var(--ink-muted); }
  /* The catch-up line is the one note on this page that is asking for a
     decision, so it carries the emphasis colour the rest of the notes do not.
     Token only — it follows the palette into dark mode. */
  .store .tile-note--catchup { margin-top: 2px; color: var(--ink); font-weight: 600; }

  /* The base .table-scroll (render/primitives.ts) is shared; this is Store's
     own addition — a border and overscroll containment the other pages don't
     have. Scoped for the same reason the tiles above are. */
  .store .table-scroll { overscroll-behavior-x: contain; border: 1px solid var(--line); }
  .days { border-collapse: collapse; width: 100%; font-size: 13px; }
  .days-caption {
    caption-side: bottom; text-align: left; padding: var(--space-2) var(--space-3);
    font-size: 11px; color: var(--ink-muted);
  }
  .days th, .days td { padding: 8px var(--space-3); white-space: nowrap; text-align: left; }
  .days thead th {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em;
    color: var(--ink-secondary); border-bottom: 1px solid var(--line-strong); font-weight: 600;
  }
  .days tbody th { font-weight: 400; color: var(--ink-secondary); }
  .days tbody tr + tr th, .days tbody tr + tr td { border-top: 1px solid var(--line); }
  .days .num { text-align: right; font-variant-numeric: tabular-nums; }

  @media (min-width: 700px) {
    .store .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  }
  @media (max-width: 600px) {
    /* Store's own shrink at phone width — Team (which shares .store-title's
       base rule via primitives.ts) has no equivalent override, and this
       stays scoped so it doesn't gain one by accident. */
    .store .store-title { font-size: 20px; }
  }


  /* .store-title, .block/.block-heading and the base .lede/.legend rule are
     shared with Store Overview and/or People — see render/primitives.ts.
     What's below is Team's own DELTA on top of the shared .legend rule
     (tighter margins for its narrower layout) plus its own .empty, both
     scoped under .page--team (shell.ts's bodyVariant) because a bare
     .empty here collided with People's own, differently-valued .empty —
     see tests/unit/css-collisions.test.ts. */
  .page--team .legend { margin-top: var(--space-3); margin-bottom: var(--space-5); }
  .page--team .empty { color: var(--ink-secondary); font-size: 14px; max-width: 62ch; line-height: 1.5; }

  /* A3: the column definitions a phone cannot reach via the header's
     title= — a native <details> so it needs no script and opens with a tap. */
  .col-definitions { margin: 0 0 var(--space-3); font-size: 13px; }
  .col-definitions summary {
    cursor: pointer; color: var(--ink-secondary); min-height: 32px; display: flex;
    align-items: center;
  }
  .col-definitions summary:hover { color: var(--ink); }
  .col-definitions .help-list { margin: var(--space-2) 0 0; }
  .col-definitions .help-entry { margin-bottom: var(--space-2); }
  .col-definitions .help-entry dt { font-weight: 600; margin-bottom: 2px; }
  .col-definitions .help-entry dd { margin: 0; color: var(--ink-secondary); line-height: 1.5; }

  /* The chart's KPI switch. Same shape as the shell's period control so the
     two read as one family of view choices rather than two inventions. */
  .kpiswitch { display: flex; flex-wrap: wrap; gap: 2px; margin: 0 0 var(--space-3); }
  .kpi-opt {
    display: inline-flex; align-items: center; min-height: 32px; padding: 0 var(--space-3);
    border: 1px solid var(--line); font-size: 12px; text-decoration: none;
    color: var(--ink-secondary); background: var(--surface);
  }
  .kpi-opt:hover { color: var(--ink); border-color: var(--line-strong); }
  .kpi-opt--active {
    color: var(--paper); background: var(--ink); border-color: var(--ink); font-weight: 600;
  }

  .upt-board {
    margin: 0 0 var(--space-5); padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line); max-width: 62ch;
  }
  .upt-board h2 {
    font-family: var(--font-display); font-size: 14px; margin: 0 0 var(--space-2);
  }
  .upt-empty { margin: 0; color: var(--ink-secondary); font-size: 13px; line-height: 1.5; }
  .upt-table { width: 100%; border-collapse: collapse; font-size: 13px; }
  .upt-table th, .upt-table td {
    text-align: left; padding: var(--space-1) var(--space-2);
  }
  .upt-table thead th {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-secondary); font-weight: 600;
  }
  .upt-table tbody th { font-weight: 400; }
  .upt-table .num { text-align: right; font-variant-numeric: tabular-nums; }
  .upt-rank { font-weight: 700; }
  .upt-note {
    margin: var(--space-2) 0 0; color: var(--ink-muted); font-size: 11.5px;
  }

  .team { width: 100%; border-collapse: collapse; font-size: 13px; }
  .team th, .team td {
    text-align: left; padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--line); white-space: nowrap;
  }
  /* Headers and names wrap; figures never do. Measured: the two honest column
     labels are the widest things in the table — "Refunds they processed" and
     "Identified customers" — and on one line they pushed the table 90px past
     its box, clipping the last column at every desktop width. Shortening them
     was the other option and the wrong one: "Refunds they processed" says
     workload rather than quality on purpose (T7), so the words are the
     contract and the line count is not. */
  .team thead th, .team tbody th { white-space: normal; }
  .team thead th {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-secondary); font-weight: 600;
  }
  .team tbody th { font-weight: 400; }
  .team .num { text-align: right; font-variant-numeric: tabular-nums; }

  /* Team's own delta on the shared .sortlink:hover rule (primitives.ts) — an
     underline, which Stores/People don't get. */
  .page--team .sortlink:hover { text-decoration: underline; }

  .person-link { color: inherit; text-decoration: none; border-bottom: 1px solid var(--line-strong); }
  .person-link:hover { color: var(--thread-strong); border-bottom-color: var(--thread-strong); }

  .row--bucket th { font-style: italic; color: var(--ink-secondary); }
  .row--unidentified th { color: var(--ink-muted); font-style: italic; }

  /* The identified cell stacks its denominator under the value so the number
     and the sample it came from are never read apart. */
  .team .coach { line-height: 1.3; }
  .coach-of {
    display: block; font-size: 10.5px; color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
  }

  .adjust { margin-top: var(--space-6); }
  .adjust h2 { font-family: var(--font-display); font-size: 15px; margin: 0 0 var(--space-2); }
  .adjust-note { margin: 0 0 var(--space-3); color: var(--ink-secondary); font-size: 12px; max-width: 62ch; }
  .adjust-list { list-style: none; margin: 0; padding: 0; }
  .adjust-list li {
    display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3);
    padding: var(--space-2) 0; border-bottom: 1px solid var(--line); font-size: 13px;
  }
  .adjust-label { color: var(--ink-secondary); }
  .adjust-value { font-variant-numeric: tabular-nums; }
  .adjust-gap { color: var(--warn); font-size: 12px; }

  @media (max-width: 600px) {
    .team th, .team td { padding: var(--space-2); }
  }


  .crumbs { margin: 0 0 var(--space-2); font-size: 12px; color: var(--ink-secondary); }
  .crumbs a { color: var(--ink-secondary); }
  .crumbs a:hover { color: var(--ink); }

  /* Which store this colleague's figures come from. An HQ reader can reach
     the same name from more than one store's Team tab, so the page has to
     say. */
  .person-store { margin: 0 0 var(--space-3); font-size: 12px; color: var(--ink-muted); }

  /* Person's own tiles, distinct from Store Overview's (store-page.ts's
     STORE_CSS) — an auto-fit KPI-summary look at the display font size,
     rather than Store's denser phone-first grid. Scoped under .page--person
     (shell.ts's bodyVariant) because the bare .tiles/.tile/.tile-label/
     .tile-value/.tile-note names collided with Store's — see
     tests/unit/css-collisions.test.ts. */
  .page--person .tiles {
    display: grid; gap: var(--space-3);
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    margin: 0 0 var(--space-2);
  }
  .page--person .tile { border: 1px solid var(--line); padding: var(--space-3); background: var(--surface); }
  .page--person .tile-label {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-secondary); font-weight: 600;
  }
  .page--person .tile-value {
    font-family: var(--font-display); font-size: 22px; line-height: 1.2;
    margin-top: var(--space-1); font-variant-numeric: tabular-nums;
  }
  /* The store's own figure, deliberately quiet: it is context, not a target,
     and colouring it against the person's would turn a comparison into a
     verdict. */
  .page--person .tile-store {
    margin-top: 2px; font-size: 11.5px; color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
  }
  .page--person .tile-note { margin: 0 0 var(--space-5); color: var(--ink-secondary); font-size: 12px; }

  .section-h { font-family: var(--font-display); font-size: 15px; margin: var(--space-5) 0 var(--space-2); }

  .refunds { margin-top: var(--space-6); }
  .refunds h2 { font-family: var(--font-display); font-size: 15px; margin: 0 0 var(--space-2); }
  .refunds-figure { margin: 0 0 var(--space-2); font-variant-numeric: tabular-nums; font-size: 15px; }
  .refunds-note {
    margin: 0; color: var(--ink-secondary); font-size: 12px; max-width: 62ch; line-height: 1.5;
  }


  .page--me .me-store { margin: 0 0 var(--space-3); font-size: 12px; color: var(--ink-muted); }

  .page--me .me-stores { display: flex; flex-wrap: wrap; gap: 1px; margin: 0 0 var(--space-3); }
  .page--me .me-store-opt {
    border: 1px solid var(--line); padding: 4px var(--space-2);
    font-size: 12px; color: var(--ink-secondary); text-decoration: none;
    background: var(--surface);
  }
  .page--me .me-store-opt:hover { color: var(--ink); }
  .page--me .me-store-opt--active {
    background: var(--ink); color: var(--surface); border-color: var(--ink);
  }

  /* Why the page is on this day (§S9). Quiet, but not a footnote: on a day
     off it is the sentence that explains the whole page. */
  .page--me .me-anchor-note {
    margin: 0 0 var(--space-3); font-size: 12.5px; color: var(--ink-secondary);
  }

  .page--me .me-empty {
    margin: 0 0 var(--space-4); font-size: 14px; line-height: 1.55;
    color: var(--ink-secondary); max-width: 62ch;
  }

  /* Explicit column counts rather than auto-fit, because the counts here are
     FIXED and auto-fit gets them wrong: six tiles in a 900px shell resolve to
     five columns, which strands the sixth alone on a second row. Six wants
     3x2 and the store-only block's four wants 4x1, and neither is something
     a track size can be asked to work out. */
  .page--me .tiles {
    display: grid; gap: var(--space-3);
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin: 0 0 var(--space-2);
  }
  @media (min-width: 600px) {
    .page--me .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .page--me .tiles--store-only { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  }
  .page--me .tile { border: 1px solid var(--line); padding: var(--space-3); background: var(--surface); }
  .page--me .tile-label {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-secondary); font-weight: 600;
  }
  .page--me .tile-value {
    font-family: var(--font-display); font-size: 22px; line-height: 1.2;
    margin-top: var(--space-1); font-variant-numeric: tabular-nums;
  }
  /* The store's figure, deliberately quiet: context, not a target. Colouring
     it against the reader's own would turn a comparison into a verdict. */
  .page--me .tile-store {
    margin-top: 2px; font-size: 11.5px; color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
  }
  .page--me .tile-sub {
    margin-top: var(--space-1); font-size: 11px; line-height: 1.35; color: var(--ink-muted);
  }
  .page--me .tile-note { margin: 0 0 var(--space-5); color: var(--ink-secondary); font-size: 12px; }

  /* .empty is scoped per page throughout this stylesheet — Team's and
     People's already carry different values for it, so a bare one here would
     be a third claim on the same name. .section-h, .upt-board, .team,
     .kpiswitch and .coach-of are deliberately NOT redeclared: they are the
     shared unscoped rules PERSON_CSS and TEAM_CSS already publish, and this
     page uses the same components. */
  .page--me .empty {
    color: var(--ink-secondary); font-size: 14px; max-width: 62ch; line-height: 1.5;
  }

  .page--me .board-window {
    margin: 0 0 var(--space-3); font-size: 12px; color: var(--ink-secondary); max-width: 62ch;
  }
  .page--me .board-list { list-style: none; margin: 0; padding: 0; }
  .page--me .board-row {
    display: grid; grid-template-columns: 3rem 1fr auto; gap: var(--space-2);
    align-items: baseline; padding: var(--space-2) 0; border-bottom: 1px solid var(--line);
    font-variant-numeric: tabular-nums;
  }
  .page--me .board-row--me { font-weight: 600; }
  .page--me .board-rank { color: var(--ink-muted); font-size: 13px; }
  .page--me .board-name { font-size: 14px; }
  /* Same nameless-placeholder treatment as the Team board's row--unidentified,
     but scoped to this board's markup: an <li>/<span> board, not a <table>,
     so employees-page.ts's ".row--unidentified th" rule never reaches it. */
  .page--me .row--unidentified .board-name { color: var(--ink-muted); font-style: italic; }
  .page--me .board-me-tag {
    margin-left: var(--space-2); font-size: 10.5px; text-transform: uppercase;
    letter-spacing: 0.04em; color: var(--ink-muted); font-weight: 600;
  }
  .page--me .board-upt { font-size: 14px; }
  .page--me .board-you {
    margin: var(--space-3) 0 0; font-size: 13px; line-height: 1.5;
    font-variant-numeric: tabular-nums;
  }

  .page--me .me-footer {
    margin-top: var(--space-6); padding-top: var(--space-4); border-top: 1px solid var(--line);
    display: flex; flex-direction: column; gap: var(--space-2);
  }
  .page--me .me-footer-link { font-size: 13px; color: var(--ink-secondary); }
  .page--me .me-footer-link:hover { color: var(--ink); }



  /* Same heading treatment every other Admin tab gets (.admin h1) — People
     isn't wrapped in that container (its tables need the shell's full width),
     so it states the rule for itself instead of inheriting it silently.
     Scoped under .page--people (shell.ts's bodyVariant): a bare h1/h2 rule
     here would restyle every OTHER page's heading too now that every page's
     CSS ships in one file — see tests/unit/css-collisions.test.ts. */
  .page--people h1 { font-family: var(--font-display); font-size: 22px; margin: 0 0 var(--space-2); font-weight: 600; }
  .page--people h2 { font-size: 16px; margin: var(--space-6) 0 var(--space-2); }

  /* The heading can be an email address, which is one unbreakable word — at
     phone width it ran 7px past the viewport and gave the whole page a
     horizontal scrollbar. Measured in webkit at 390: hiding the h1 took
     documentElement.scrollWidth from 397 back to 390, and nothing else on the
     page moved it. The list escapes this only because its addresses sit
     inside the table's own scroll box. */
  .person-subject { overflow-wrap: anywhere; }

  /* .lede and .legend's base rule are shared with Team — see
     render/primitives.ts. .empty and .hint stay here: .empty's own value
     differs from Team's (Team's own is font-size 14px with no margin; this
     is 13px with one), so a bare rule collided — see
     tests/unit/css-collisions.test.ts. */
  .page--people .empty, .page--people .hint {
    margin: 0 0 var(--space-4); max-width: 62ch;
    color: var(--ink-secondary); font-size: 13px; line-height: 1.5;
  }
  .count { margin: var(--space-2) 0 0; font-size: 12px; color: var(--ink-muted); }
  .back { margin: 0 0 var(--space-3); font-size: 13px; }
  /* Scoped: a bare .notice here would add this margin to BASE_CSS's .notice
     everywhere (the sign-in page's error banner, Budgets' save/failure
     notices, …), not just People's own. */
  .page--people .notice { margin: 0 0 var(--space-4); }

  /* Three controls and a button. They wrap rather than shrink: a search box
     narrower than the address it holds is not a control anybody can use. */
  .filters {
    display: flex; flex-wrap: wrap; align-items: flex-end;
    gap: var(--space-3); margin-bottom: var(--space-4);
  }
  .filters .field { flex: 0 1 auto; min-width: 12ch; }
  .filters .field:first-child { flex: 1 1 22ch; max-width: 32ch; }

  .add { margin-top: var(--space-6); max-width: 42ch; }
  .add form { display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }
  .add .field { width: 100%; }

  .people, .ladder { width: 100%; border-collapse: collapse; font-size: 13px; }
  .people th, .people td, .ladder th, .ladder td {
    text-align: left; padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--line);
  }
  /* The ladder is the table with fixed-shape values in it (a rung number, a
     status word), so it keeps the nowrap rule. Headers and the "what it
     decided" prose still wrap. */
  .ladder th, .ladder td { white-space: nowrap; }
  .ladder thead th, .ladder tbody th, .ladder td:last-child { white-space: normal; }

  /* The People table has NO figures in it — four text columns, three of which
     carry annotations. MEASURED at 1280 with the widest fixture row: nowrap
     made the table 98px wider than its box, so the State column's blocker sat
     off the right edge behind a scrollbar nobody would think to drag. Cells
     wrap; the annotations inside them do not, so a date never breaks across
     two lines. */
  .people th, .people td { white-space: normal; }
  .people .src, .people .state { white-space: nowrap; }
  /* An address is one unbreakable word and would otherwise set the column's
     width on its own. */
  .people tbody th { overflow-wrap: anywhere; }
  .people thead th, .ladder thead th {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-secondary); font-weight: 600;
  }
  .people tbody th, .ladder tbody th { font-weight: 400; }
  .people .num, .ladder .num { text-align: right; font-variant-numeric: tabular-nums; }

  .src { font-size: 11px; color: var(--ink-muted); }
  /* Scoped: error-page.ts's CARD_CSS already has its own, differently-scoped
     ".card .muted" — this bare rule would otherwise add its declaration to
     that page's .muted text too (same colour today, but a bare rule here is
     what makes that coincidence load-bearing instead of accidental). */
  .page--people .muted { color: var(--ink-muted); }
  .blocked { color: var(--warn); }
  /* The one cell allowed to be two lines: see stateCell's comment. Everything
     else in the table stays nowrap so figures and dates never break. */
  .people td .blocked { display: block; white-space: normal; font-size: 11px; }

  /* Its own line under the badge, and a real tap target: it sits in a table
     cell, which is where a 30px-tall button usually comes from. */
  .clear-invite { margin-top: var(--space-2); }
  .clear-invite .btn { font-size: 13px; padding: 0 var(--space-3); }

  /* Square, like every other badge in this app. */
  .state {
    display: inline-block; padding: 2px 8px;
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
    border: 1px solid var(--line-strong); color: var(--ink-secondary);
  }
  .state--active { border-color: var(--good); color: var(--good); }
  .state--pending { border-color: var(--warn); color: var(--warn); }
  .state--expired { border-color: var(--line-strong); color: var(--ink-muted); }
  .state--disabled { border-color: var(--bad); color: var(--bad); }

  .identity {
    display: grid; grid-template-columns: minmax(9ch, auto) 1fr;
    gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-6);
    font-size: 13px; max-width: 62ch;
  }
  .identity > div { display: contents; }
  .identity dt { color: var(--ink-secondary); }
  .identity dd { margin: 0; overflow-wrap: anywhere; }

  .actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: flex-start; }

  /* Neutral on purpose: the disclosure only opens a panel, and dressing it
     like the destructive button inside put two identical red Disables on
     screen at once. */
  .confirm > summary {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 0 var(--space-3); cursor: pointer;
    border: 1px solid var(--line-strong); border-radius: 0;
    color: var(--ink); font-size: 13px; list-style: none;
  }
  .confirm > summary::-webkit-details-marker { display: none; }
  .confirm-panel {
    margin-top: var(--space-2); padding: var(--space-3);
    border: 1px solid var(--line-strong);
    background: var(--surface);
  }
  .confirm-panel p { margin: 0 0 var(--space-3); font-size: 13px; max-width: 62ch; }
  .confirm-panel .actions { flex-direction: row; }

  /* Visible exactly while the select still shows the role they hold, so the
     control opens saying that saving it now would change nothing. */
  .role-unchanged { display: none; margin: 0 0 var(--space-3); font-size: 13px;
    color: var(--ink-secondary); max-width: 62ch; }
  .role-form:has(option[data-current]:checked) .role-unchanged { display: block; }

  .verdict {
    margin: 0 0 var(--space-4); padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line-strong); font-size: 14px; max-width: 62ch;
  }
  .verdict--none { border-color: var(--warn); color: var(--warn); }

  .rung--not_reached th, .rung--not_reached td { color: var(--ink-muted); }
  .rung--decided th, .rung--decided td { font-weight: 600; }

  /* ── P2: the Sitoo link surface ──────────────────────────────────────── */

  .page--people h3 { font-size: 14px; margin: var(--space-5) 0 var(--space-2); }

  /* A GUID is an identifier, so it is set as one — never as a name. */
  .guid {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 12px; color: var(--ink-secondary); overflow-wrap: anywhere;
  }

  .link-card {
    border: 1px solid var(--line); padding: var(--space-3);
    margin: 0 0 var(--space-3); max-width: 62ch;
  }
  .link-card h3 { margin: 0 0 var(--space-2); display: flex; flex-wrap: wrap;
    gap: var(--space-2); align-items: center; }
  .link-card--revoked { opacity: 0.7; }
  /* The metadata line is the widest thing in the card (an address, a store, a
     date and a record id), and it is prose — it wraps rather than scrolling,
     because there is no box here to scroll inside. */
  .link-meta {
    margin: 0 0 var(--space-3); font-size: 12px; color: var(--ink-secondary);
    line-height: 1.5; overflow-wrap: anywhere;
  }
  .link-card .hint { margin: var(--space-2) 0 0; }
  .link-card .blocked { font-size: 13px; line-height: 1.5; max-width: 52ch; }

  .state--link-approved { border-color: var(--good); color: var(--good); }
  .state--link-proposed { border-color: var(--warn); color: var(--warn); }
  .state--link-revoked { border-color: var(--line-strong); color: var(--ink-muted); }

  .propose { margin-top: var(--space-5); }
  .staff-search {
    display: flex; flex-wrap: wrap; align-items: flex-end;
    gap: var(--space-3); margin-bottom: var(--space-3);
  }
  .staff-search .field { flex: 1 1 22ch; max-width: 32ch; }

  .candidates, .queue, .assignments {
    width: 100%; border-collapse: collapse; font-size: 13px;
  }
  .candidates th, .candidates td,
  .queue th, .queue td,
  .assignments th, .assignments td {
    text-align: left; padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--line); vertical-align: top;
  }
  .candidates thead th, .queue thead th, .assignments thead th {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-secondary); font-weight: 600;
  }
  .candidates tbody th, .queue tbody th, .assignments tbody th { font-weight: 400; }
  /* MEASURED at 390: overflow-wrap:anywhere on the whole cell broke NAMES
     mid-word ("Alva Lindqvi / st"), because the column collapses to the
     narrowest thing it is allowed to be. A min-width floor fixes the breaking
     and costs 40px of DOCUMENT scroll — a scroll box does not contain a table
     whose cells declare a minimum, measured at 390 both ways — so the rule is
     narrowed instead: the address is the one genuinely unbreakable word in
     the cell, and it is the only part allowed to break anywhere. */
  .candidates tbody th .src, .queue tbody th .src { overflow-wrap: anywhere; }
  /* Same rule the People table states: cells wrap, the annotations inside
     them do not, so a date or a badge never breaks across two lines. */
  .candidates .src, .queue .src, .assignments .src { white-space: normal; }
  .queue .state, .candidates .state { white-space: nowrap; }
  /* The refusal sentences ("not an employee record yet") are prose in a cell
     and are the reason the action column has to be allowed to wrap. */
  .candidates td .blocked, .queue td .blocked {
    display: block; font-size: 12px; line-height: 1.4; max-width: 34ch;
  }
  /* It sits in a table cell, which is where a 30px-tall button usually comes
     from — the same fix .clear-invite needed. */
  .candidates .btn, .queue .btn, .assignments .btn {
    font-size: 13px; padding: 0 var(--space-3);
  }
  /* "Manage in retail" carries its own one-line reason. Inline it would sit
     beside a 44px button and set the column's width on its own; under it, the
     control still reads as one thing. */
  .candidates td form .src, .queue td form .src {
    display: block; margin-top: var(--space-2); max-width: 28ch; line-height: 1.4;
  }

  .assign {
    display: flex; flex-wrap: wrap; align-items: flex-end;
    gap: var(--space-3); margin-top: var(--space-4);
  }
  .assign .field { flex: 0 1 auto; min-width: 14ch; }

  .queue-section { margin-top: var(--space-6); }
  .queue-section .count { margin-bottom: var(--space-4); max-width: 62ch; line-height: 1.5; }


  .page--storecal .storecal { margin-top: var(--space-5); }
  .page--storecal .storecal h1 {
    font-family: var(--font-display); font-size: 24px; margin: 0 0 var(--space-2); font-weight: 600;
  }
  .page--storecal .backlink { font-size: 13px; margin: 0 0 var(--space-2); }
  .page--storecal .hint {
    font-size: 13px; color: var(--ink-secondary); margin: 0 0 var(--space-3); max-width: 62ch;
    line-height: 1.5;
  }
  .page--storecal .notice { margin: 0 0 var(--space-4); }
  .page--storecal table { border-collapse: collapse; width: 100%; font-size: 13px; margin: var(--space-3) 0; }
  .page--storecal th, .page--storecal td {
    padding: var(--space-2); border-bottom: 1px solid var(--line);
    text-align: left; vertical-align: top;
  }
  .page--storecal th {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--ink-secondary);
    white-space: nowrap;
  }
  .page--storecal td.num { text-align: right; font-variant-numeric: tabular-nums; }
  .page--storecal td.rowactions { white-space: nowrap; }
  .page--storecal td.rowactions form { display: inline-block; margin-left: var(--space-2); }
  .page--storecal .storelist td { vertical-align: middle; }

  /* One form per weekday, laid out as a row that wraps to a stack on a phone
     rather than a table — a <form> cannot be a child of <tr>, and the
     alternative (one form outside the table per row, wired by form=
     attributes) puts the control and the thing it changes in different
     places in the DOM for no gain. */
  .page--storecal .hours { display: flex; flex-direction: column; gap: var(--space-2); }
  .page--storecal .hoursrow { border-bottom: 1px solid var(--line); padding-bottom: var(--space-2); }
  .page--storecal .hoursrow form {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3);
  }
  .page--storecal .dayname { font-weight: 600; min-width: 96px; align-self: center; }
  .page--storecal .daystate {
    font-size: 12px; color: var(--ink-secondary); min-width: 120px; align-self: center;
  }
  /* Quiet on purpose: a weekday nobody has stated is not a fault, it trades —
     this marks it for B6's confirmation pass without dressing it as a
     problem. */
  .page--storecal .unconfirmed {
    display: block; font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
    letter-spacing: 0.04em; color: var(--ink-muted);
  }
  .page--storecal .confirmline { color: var(--ink-muted); }
  .page--storecal .field--inline { min-width: 108px; }
  .page--storecal .field--wide { grid-column: 1 / -1; }

  .page--storecal .formgrid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-3); margin: var(--space-3) 0;
  }
  .page--storecal .specialform {
    border: 1px solid var(--line); padding: var(--space-4); margin-top: var(--space-4);
  }
  .page--storecal .formheading { font-size: 14px; margin: 0; font-weight: 600; }
  .page--storecal .formactions { display: flex; gap: var(--space-3); align-items: center; }
  .page--storecal .pastdates { margin: var(--space-3) 0; }
  /* A flex summary drops the UA disclosure marker, so the affordance is
     stated rather than lost — a summary that looks like a caption is one
     nobody presses. */
  .page--storecal .pastdates > summary {
    min-height: 44px; display: flex; align-items: center; gap: var(--space-2);
    cursor: pointer; font-size: 13px; color: var(--ink-secondary);
    list-style: none;
  }
  .page--storecal .pastdates > summary::-webkit-details-marker { display: none; }
  .page--storecal .pastdates > summary::before { content: "▸"; }
  .page--storecal .pastdates[open] > summary::before { content: "▾"; }
  .page--storecal .weightform { display: flex; gap: var(--space-2); align-items: center; }
  /* Pinned, not broken: an edit replaces the row it was opened on, so the
     date is shown rather than offered. */
  .page--storecal .specialform input[readonly] {
    background: var(--surface-sunken); color: var(--ink-secondary);
  }
  .page--storecal .weightform input { width: 108px; min-height: 44px; padding: var(--space-2); border: 1px solid var(--line-strong); background: var(--surface); }

  .page--storecal .cal-state--closed { color: var(--bad); }
  .page--storecal .cal-state--open { color: var(--ink); }
  /* Neutral, not warn: a multiplier can mean busier (2x) or quieter (0.5x) —
     the warn/bad palette read as an error regardless of which (A26). */
  .page--storecal .cal-mult {
    display: inline-block; background: var(--surface-sunken); color: var(--ink);
    border: 1px solid var(--line-strong);
    padding: 1px var(--space-2); font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  }

  /* Seven columns at every width. The grid is a shape, not a table: at 390 the
     cells get narrow rather than the page getting wide, which is why each cell
     stacks its own lines instead of sharing a row with six others. */
  .page--storecal .calgrid {
    display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px;
    background: var(--line); border: 1px solid var(--line); margin: var(--space-3) 0;
  }
  .page--storecal .calhead {
    background: var(--surface); padding: var(--space-2); font-size: 11px; text-transform: uppercase;
    letter-spacing: 0.03em; color: var(--ink-secondary); text-align: center;
  }
  .page--storecal .calday {
    background: var(--surface); padding: var(--space-2); display: flex; flex-direction: column;
    gap: 2px; min-height: 64px; font-size: 11px; overflow-wrap: anywhere;
  }
  .page--storecal .calday--closed { background: var(--surface-sunken); color: var(--ink-secondary); }
  .page--storecal .calday--today { outline: 2px solid var(--thread-strong); outline-offset: -2px; }
  .page--storecal .calday--past { background: transparent; }
  .page--storecal .caldate { font-weight: 600; color: var(--ink); }
  .page--storecal .calstate { color: var(--ink-secondary); }
  .page--storecal .calshare { font-variant-numeric: tabular-nums; color: var(--ink-secondary); }

  @media (max-width: 520px) {
    .page--storecal .calday { min-height: 56px; font-size: 10px; padding: 4px; }
    /* The hours text ("10:00—19:00") is what does not fit at this width —
       "Closed" is short and is the one word this cell exists to show
       (A13), so only the trading-hours case hides. */
    .page--storecal .calstate:not(.calstate--closed) { display: none; }
    /* The narrowest the weights table can be and still put "In use" — the
       column the page is opened to read — inside the scroll box rather than
       behind it. */
    .page--storecal .weightform input { width: 64px; }
  }


  .chart { margin: 0 0 var(--space-5); }

  /* The charts are drawn at their intrinsic size and do not scale — an SVG
     scaled down to a phone scales its type with it. Each renderer therefore
     emits TWO canvases, and this box is the container the query below picks
     between: the wide one only when the box can actually hold it, the phone
     one otherwise. That is why the switch is a container query and not a
     media query — what matters is how much room the CHART has, which is not
     the same question as how wide the window is. */
  /* The two inner shadows are a scroll affordance, not decoration: a chart
     silently cut off at the screen edge reads as a chart that ends there.
     The local-attachment gradients scroll with the content and cover the
     shadows when there is nothing more in that direction, so each shadow
     shows only while there IS something to scroll to — no JavaScript, and
     nothing to keep in sync. */
  .chart-box {
    container-type: inline-size;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    background:
      linear-gradient(to right, var(--surface) 40%, transparent) left center / 40px 100% no-repeat local,
      linear-gradient(to left, var(--surface) 40%, transparent) right center / 40px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%, var(--line-strong), transparent) left center / 12px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%, var(--line-strong), transparent) right center / 12px 100% no-repeat scroll,
      var(--surface);
    border: 1px solid var(--line);
    padding: var(--space-3) 0 var(--space-2);
  }
  /* Centred with auto margins rather than flex: a flex-centred child that is
     wider than its scroll box has its left edge cut off and cannot be
     scrolled back to. Auto margins collapse to zero when there is no room,
     which is exactly the behaviour a scroll box needs. */
  .chart-svg { display: block; margin: 0 auto; }

  /* tests/geometry/chart-fixtures.ts only — a synthetic page that puts every
     chart renderer on one screen for the geometry suite to measure. Scoped
     to a class no real page's markup carries, so this can never reach a
     production page (checked by the geometry suite's own screenshots
     staying unchanged). It exists here, rather than as an inline <style> in
     the fixture, because renderDocument links one stylesheet for every page
     it renders (R3d) and this fixture is rendered through the real
     renderDocument for realism. */
  .chart-fixture-page h1 {
    font-family: var(--font-display); font-weight: 600; font-size: 24px; margin: 0 0 var(--space-5);
  }
  .chart-fixture-page h2 {
    font-family: var(--font-display); font-weight: 600; font-size: 16px; margin: 0 0 var(--space-3);
  }
  .chart-fixture-page .fixture-section { margin-bottom: var(--space-7); }

  /* Phone canvas first, so a browser that does not understand the query below
     still gets the variant that fits rather than the one that does not. It is
     fluid up to its own design width: at 372px and above it draws
     1:1, and below that it scales down rather than growing a scrollbar on a
     screen that has no room for one. */
  .chart-svg--narrow { width: 100%; max-width: 372px; height: auto; }
  .chart-svg--wide { display: none; }

  /* The desktop canvas appears only once the box can hold all of it, so the
     wide chart is never the reason a reader has to scroll. */
  @container (min-width: 720px) {
    .chart-svg--narrow { display: none; }
    .chart-svg--wide { display: block; }
  }

  .chart-caption {
    margin-top: var(--space-2);
    font-size: 13px;
    color: var(--ink-secondary);
    line-height: 1.5;
  }
  .chart-caption-link { white-space: nowrap; }

  /* Figures are the one place in this app where digits stack vertically and
     have to line up. */
  .chart-num { font-variant-numeric: tabular-nums; }

  .chart-mark { cursor: default; }
  .chart-mark:focus { outline: none; }
  .chart-mark:focus-visible,
  .chart-legend-item:focus-visible,
  .chart-box:focus-visible {
    outline: 2px solid var(--thread);
    outline-offset: 2px;
  }

  /* Hover and focus raise the same mark, so a pointer and a keyboard reach
     the same state rather than two different-looking ones. */
  .chart-mark:hover .chart-bar,
  .chart-mark:focus-visible .chart-bar { fill-opacity: 1; }
  .chart-mark:hover .chart-point,
  .chart-mark:focus-visible .chart-point { r: 5; }

  /* Off used to be struck through, which reads as "gone from the company"
     on a person's own name rather than "hidden from this chart" (persona
     QA B3). Opacity alone still marks it as off without saying that. */
  .chart-legend-item { cursor: pointer; }
  .chart-legend-item--off { opacity: 0.5; }

  /* Toggled off client-side. The series is still in the document, which is
     what lets the legend turn it back on without asking the server. */
  .chart-series--off { display: none; }

  .chart-tip {
    position: fixed;
    z-index: 40;
    top: 0;
    left: 0;
    max-width: 280px;
    padding: var(--space-2) var(--space-3);
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--line-strong);
    font-family: var(--font-body);
    font-size: 12px;
    line-height: 1.5;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
    opacity: 0;
    transform: translateY(3px);
    transition: opacity 90ms ease-out, transform 90ms ease-out;
  }
  .chart-tip[data-open="1"] { opacity: 1; transform: none; }

  @media (prefers-reduced-motion: reduce) {
    .chart-tip { transition: none; }
  }

  @media print {
    .chart-box { overflow: visible; border-color: var(--line-strong); }
    .chart-tip { display: none; }
  }
