/* ==========================================================================
   Mockup Desk

   Light SaaS surface: near-white ground, white cards, hairline-separated
   rows, soft pastel state pills with a leading dot, letter avatars tinted
   per person. Empire Cove gold stays as the brand thread in the wordmark
   and the active date, so it reads as yours rather than stock.
   ========================================================================== */

/* The browser's own [hidden] rule sits in the UA stylesheet, so ANY author
   `display` beats it — .btn is display:inline-flex, which meant el.hidden =
   true did nothing at all to a button and the control stayed on screen. This
   is what makes `hidden` mean hidden. */
[hidden] { display: none !important; }

:root {
  --ground:  #F5F7FA;
  --surface: #FFFFFF;
  --raised:  #FBFCFD;
  --sunken:  #EFF2F6;
  --hover:   #F7F9FC;

  /* ---------- the ink family ----------
     Aziz, 24 Sep 2026: "the font colors ... seem like greyed kind of color, not
     easy to read on most parts of the system." He was right, and the old
     comment on this line was part of the problem - it claimed the tertiary tone
     was "AA on surface/raised/sunken: 5.01 / 4.88 / 4.46" while quoting 4.46,
     which is under the 4.5 it claimed to pass. Every de-emphasised label in the
     app - SKU sub-lines, field captions, table sub-labels, empty states - sat
     on that token.

     Both secondary tones were darkened once already for that.

     SECOND PASS, 24 Sep 2026. Aziz, on the reorder control strip: "there are
     still colors that are too fade, improve these and where ever required."
     He is right, and no contrast audit was ever going to find it - those
     labels measured 7.63:1, a comfortable AA pass, and every page in the app
     was at zero failures when he said it. The problem is size, not ratio. At
     11.5px there is so little ink on the screen that a ratio which reads as
     solid at 15px reads as a smudge. Contrast is measured per pixel;
     legibility is bought per stroke.

     Two things were wrong and both are fixed here.

     1. --ink-2 and --ink-3 sat at 6.80 and 5.33 on --sunken - 1.28x apart.
        Two tokens that close are not a hierarchy, they are one grey applied
        twice, which is why everything secondary read as a single
        undifferentiated wash. They are now 9.15 and 6.48, 1.41x apart, and
        the whole ladder moved darker with them.
     2. The tokens alone could not fix it, because the labels he pointed at
        were on the wrong token to begin with. A label naming a control the
        buyer is about to operate is not quiet text - it is the name of the
        thing. Those moved to --ink; see "control labels are not quiet text"
        further down this file.

     What each token now means, so this stops being decided rule by rule:
       --ink    content, and the name of any control
       --ink-2  supporting text you are meant to read - captions, sub-lines,
                column totals, help text
       --ink-3  deliberately quiet - timestamps, refs, counts, placeholders,
                chart axes, hover borders

     Figures in the ladder are surface / raised / sunken / ground / hover. */
  --ink:   #1A1D26;   /* 16.83 16.39 14.99 15.68 15.96 */
  --ink-2: #39414F;   /* 10.28 10.01  9.15  9.58  9.75   was #4C5462, 6.80 */
  --ink-3: #4F5766;   /*  7.27  7.08  6.48  6.78  6.90   was #5B6472, 5.33 */

  --line:   #ECEFF4;
  --line-2: #DDE2EA;

  /* brand */
  --gold:        #A07C0C;
  --gold-bright: #E0A800;
  --gold-wash:   #FDF4DC;

  /* primary action + navigation accent.
     #4F5BD5 was the last text colour in the app under 5:1 - measured 24 Sep
     2026 it reads 4.89 as an 11px type tag on --sunken and 5.16 as a pagination
     link. One shade deeper, same hue: 5.75 worst case, and white on the primary
     button goes 5.54 -> 6.52 at the same time, because this token is both the
     ink and the fill. --indigo-soft is unchanged, so the pale wash behind a
     selected card still looks the same. */
  --indigo:      #4650C6;
  --indigo-soft: #EEF0FE;

  /* state pills */
  --s-new:    #7B8496;  --s-new-bg:    #EDF0F5;
  --s-prog:   #4F5BD5;  --s-prog-bg:   #EEF0FE;
  --s-review: #C77A05;  --s-review-bg: #FEF3D9;
  --s-change: #E1445F;  --s-change-bg: #FFE7EB;
  --s-appr:   #0F9F73;  --s-appr-bg:   #D9F6EA;
  /* Darker text forms of amber and green for the Website words (4.5:1 on white). */
  --web-amber-ink: #9A5B00; --web-green-ink: #0B7A57; --web-red-ink: #B42318;

  /* ---------- semantic colour: one red, one green, one amber, one blue ----------
     Before this block the app carried FOUR reds (#BD394F, #BB384F, #b42318,
     #b91c1c), two greens, three ambers and three blues. Two of the reds differ
     by (2,1,0) - the same colour to the eye, a different string to a search -
     and each was re-declared once for light and twice for dark, so saying
     "this one means bad news" took about thirty rules, and they drifted apart.

     Measured 24 Sep 2026: the weakest blue in use, #2563EB, reads 4.40:1 as
     text on the change tint, under AA, while its near-twin #1D4ED8 reads 5.71.
     The difference nobody could see was the difference that failed.

     One token per MEANING, declared in all three theme blocks, so a component
     names what it means and the theme decides the value. The figure beside each
     is its worst case across the ten backgrounds it can land on in this app
     (surface, raised, sunken, ground, hover, indigo-soft and the four tints).
     All clear 4.5:1 with headroom; the values they replace sat at 4.63-5.02.
     Hues are unchanged, so red still reads as red.

     TEXT, BORDERS and rules only. Chart fills and tile washes keep their own
     lighter values on purpose - darkening a bar changes the weight of a chart,
     which is a different decision from making a label legible. */
  --pos:  #0A6F50;   /* worst 5.26 */
  --neg:  #B23148;   /* worst 5.22 */
  --warn: #8A5403;   /* worst 5.34 */
  --info: #1E54CC;   /* worst 5.60 */

  /* left menu: a stack of cards rather than a list of links */
  --nav-card: #FFFFFF;   --nav-line: #E7EBF1;   --nav-tile: #F1F3F8;
  --nav-guide: #C8D1DF;  --nav-dot:  #B4BFCF;   --nav-pill: #EEF1F6;
  /* Was #2563EB. Measured 24 Sep 2026 against its own tints: 4.22:1 on
     --nav-accent-tile, i.e. under AA, and 4.57 on --nav-pill. The failure
     was already known - the comment further down this file records it - but
     only the .tw label was moved off it, so the open section heading and the
     active department tile kept reading at 4.22. Same value as --info now,
     which also means the app stopped having a separate navigation blue:
     5.37 tile / 5.80 pill / 5.96 bg / 6.57 surface / 6.12 ground. */
  --nav-accent: #1E54CC; --nav-accent-bg: #EFF4FF;
  --nav-accent-line: #C9DCFF; --nav-accent-tile: #DCE9FF;

  --lift-1: 0 1px 2px rgba(24,32,52,.04);
  --lift-2: 0 1px 3px rgba(24,32,52,.06), 0 8px 24px rgba(24,32,52,.06);
  --lift-3: 0 12px 40px rgba(24,32,52,.14);
  --ring: 0 0 0 3px color-mix(in srgb, var(--indigo) 24%, transparent);

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;

  /* Avatar tint + text-on-accent live as tokens so no component rule ever
     has to know which theme is active. */
  --av-bg-s: 72%; --av-bg-l: 94%;
  --av-fg-s: 52%; --av-fg-l: 38%;
  --on-accent: #FFFFFF;

  --sans:  Archivo, "Helvetica Neue", Helvetica, sans-serif;
  --serif: Newsreader, Georgia, serif;
  /* THE CODE FACE — one rule, no exceptions.
     Mono is for IDENTIFIERS ONLY: a string somebody reads off this screen and
     types into another system character by character — a SKU or alias, a job
     number, a password. A wrong character there fails silently and costs an
     order, and only a mono face keeps 0/O and 1/l/I apart.
     It is NEVER used for language. An English phrase set in a typewriter face
     reads as a mistake, which is exactly what "not in catalogue" looked like
     in the Say column on /ask.
     Where it is used it is always var(--mono) — never the stack spelled out —
     at the size of the text beside it, weight 500 (600 for a standalone chip),
     colour --ink-2, with tabular-nums and letter-spacing -.01em. The negative
     tracking matters: Plex Mono sets wider than Archivo, so without it the
     code column looks spread out and foreign inside its own row. */
  --mono:  "IBM Plex Mono", ui-monospace, Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:#101218; --surface:#181B22; --raised:#1D212A; --sunken:#232833; --hover:#1F232C;
    /* Same second pass as the light ladder: 12.95 / 9.41 / 6.76 worst case,
       was 12.95 / 7.88 / 5.75. Dark needs the step widened for the same reason
       light did - at 11px a mid-grey on a near-black panel is the harder of the
       two to read, not the easier. */
    --ink:#EEF0F5; --ink-2:#C8CFDA; --ink-3:#A8B0BE;
    --line:#262B35; --line-2:#39404D;
    --gold:#E8C25A; --gold-bright:#E0A800; --gold-wash:#2E2610;
    --indigo:#8B95F0; --indigo-soft:#1D2140;
    --s-new:#98A1B2;  --s-new-bg:#22262F;
    --s-prog:#8B95F0; --s-prog-bg:#1D2140;
    --s-review:#E8C25A; --s-review-bg:#2E2610;
    --s-change:#F58097; --s-change-bg:#361A22;
    --s-appr:#4ECFA0; --s-appr-bg:#0F2E24;
    --web-amber-ink:#E8C25A; --web-green-ink:#4ECFA0; --web-red-ink:#F58097;
    /* worst case on the dark surfaces: 7.49 / 5.86 / 8.56 / 6.03 */
    --pos:#4ECFA0; --neg:#F58097; --warn:#E8C25A; --info:#7BA5FF;
    --nav-card:#181B22; --nav-line:#262B35; --nav-tile:#232833;
    --nav-guide:#39404D; --nav-dot:#4A5361; --nav-pill:#232833;
    --nav-accent:#7BA5FF; --nav-accent-bg:#16203A;
    --nav-accent-line:#284272; --nav-accent-tile:#1D2B4A;
    --av-bg-s:38%; --av-bg-l:22%; --av-fg-s:72%; --av-fg-l:74%;
    --on-accent:#101218;
    --lift-1:0 1px 2px rgba(0,0,0,.4);
    --lift-2:0 2px 8px rgba(0,0,0,.45);
    --lift-3:0 14px 44px rgba(0,0,0,.6);
  }
}
:root[data-theme="dark"] {
  --ground:#101218; --surface:#181B22; --raised:#1D212A; --sunken:#232833; --hover:#1F232C;
  /* Same second pass as the light ladder: 12.95 / 9.41 / 6.76 worst case,
       was 12.95 / 7.88 / 5.75. Dark needs the step widened for the same reason
       light did - at 11px a mid-grey on a near-black panel is the harder of the
       two to read, not the easier. */
  --ink:#EEF0F5; --ink-2:#C8CFDA; --ink-3:#A8B0BE;
  --line:#262B35; --line-2:#39404D;
  --gold:#E8C25A; --gold-bright:#E0A800; --gold-wash:#2E2610;
  --indigo:#8B95F0; --indigo-soft:#1D2140;
  --s-new:#98A1B2;  --s-new-bg:#22262F;
  --s-prog:#8B95F0; --s-prog-bg:#1D2140;
  --s-review:#E8C25A; --s-review-bg:#2E2610;
  --s-change:#F58097; --s-change-bg:#361A22;
  --s-appr:#4ECFA0; --s-appr-bg:#0F2E24;
  --web-amber-ink:#E8C25A; --web-green-ink:#4ECFA0; --web-red-ink:#F58097;
  /* worst case on the dark surfaces: 7.49 / 5.86 / 8.56 / 6.03 */
  --pos:#4ECFA0; --neg:#F58097; --warn:#E8C25A; --info:#7BA5FF;
    --nav-card:#181B22; --nav-line:#262B35; --nav-tile:#232833;
    --nav-guide:#39404D; --nav-dot:#4A5361; --nav-pill:#232833;
    --nav-accent:#7BA5FF; --nav-accent-bg:#16203A;
    --nav-accent-line:#284272; --nav-accent-tile:#1D2B4A;
  --av-bg-s:38%; --av-bg-l:22%; --av-fg-s:72%; --av-fg-l:74%;
  --on-accent:#101218;
  --lift-1:0 1px 2px rgba(0,0,0,.4);
  --lift-2:0 2px 8px rgba(0,0,0,.45);
  --lift-3:0 14px 44px rgba(0,0,0,.6);
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--ground); color: var(--ink);
  font-family: var(--sans); font-size: 14px; line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* ---------- placeholders belong to the palette ----------
   Found 24 Sep 2026 by measuring every input in the app rather than the one
   being worked on. Seven of them - the brand picker and the type filter on
   /reorder, the big question box on /inventory, and all three fields in the
   add-a-member form on /settings - were drawing their placeholder in
   #757575. That colour is not in this stylesheet. It is Chrome's default,
   showing through because four inputs had been given a ::placeholder rule
   one at a time and the rest had been missed, which is what per-element
   styling of a thing every element has always ends up doing.

   It measured 3.50:1 on the dark member form. Nothing caught it: a
   placeholder is an attribute, not a text node, so a contrast audit that
   walks the DOM for text never sees it, and in light mode it looks
   approximately right. It is also, exactly, the complaint - grey text that is
   hard to read.

   One rule for every input there will ever be, on the token that means
   "deliberately quiet". `opacity: 1` because Firefox dims placeholders by
   default on top of the colour, which would put the real value back below
   where this sets it. */
::placeholder { color: var(--ink-3); opacity: 1; }
a { color: inherit; text-decoration: none; }
button, select, input, textarea { font: inherit; color: inherit; }
:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-sm); }
@media (prefers-reduced-motion: reduce) { *,*::before,*::after { animation:none!important; transition:none!important; } }

/* ---------- top bar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--ground) 86%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 12px; padding: 0 22px; height: 70px;
}.spacer { flex: 1; }
.searchform { flex: none; }
.searchbox {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: 999px;
  padding: 9px 16px; font-size: 13px; width: 260px;
  transition: border-color .15s ease, width .2s ease;
}
.searchbox::placeholder { color: var(--ink-3); }
.searchbox:focus { border-color: var(--indigo); width: 300px; }
.me { display: flex; align-items: center; gap: 10px; }
.me-text { display: flex; flex-direction: column; line-height: 1.2; }
.me-text b { font-size: 13px; font-weight: 600; }
.me-text i {
  font-style: normal; font-size: 10.5px; color: var(--ink-3); text-transform: capitalize;
}

/* ---------- buttons ---------- */
.btn {
  font-size: 13px; font-weight: 500;
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink);
  border-radius: var(--r-sm); padding: 8px 15px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  transition: background .14s ease, border-color .14s ease, transform .12s ease;
}
.btn:hover { background: var(--hover); border-color: var(--ink-3); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--indigo); border-color: var(--indigo); color: var(--on-accent); }
.btn.primary:hover { filter: brightness(1.07); background: var(--indigo); }
.btn[disabled] { opacity: .42; cursor: not-allowed; transform: none; }

/* ---------- avatars ---------- */
.avatar {
  --h: 220;
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: inline-grid; place-items: center;
  font-size: 12.5px; font-weight: 700;
  background: hsl(var(--h) var(--av-bg-s) var(--av-bg-l));
  color: hsl(var(--h) var(--av-fg-s) var(--av-fg-l));
}
.avatar.sm { width: 24px; height: 24px; font-size: 10px; }

/* ---------- state pills ---------- */
.chip {
  font-size: 11.5px; font-weight: 600;
  border-radius: 999px; padding: 4px 10px 4px 8px;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  border: 0;
}
.chip::before { content:""; width:6px; height:6px; border-radius:50%; background: currentColor; flex:none; }
.chip.new              { color: var(--s-new);    background: var(--s-new-bg); }
.chip.in_progress      { color: var(--indigo);   background: var(--s-prog-bg); }
.chip.ready_for_review { color: var(--s-review); background: var(--s-review-bg); }
.chip.changes_required { color: var(--s-change); background: var(--s-change-bg); }
.chip.approved         { color: var(--s-appr);   background: var(--s-appr-bg); }
.chip.on_hold, .chip.cancelled { color: var(--ink-3); background: var(--sunken); }

/* ---------- shell ---------- */
/* 272px, not 250: the menu labels are cards now, and at 250 "Low Quantity
   Emails" wrapped onto a second line while its neighbours did not. */
.shell-2col { display: grid; grid-template-columns: 272px minmax(0,1fr); align-items: start; }
@media (max-width: 880px) { .shell-2col { grid-template-columns: minmax(0,1fr); } }

/* ---------- sidebar ---------- */
.sidebar {
  position: sticky; top: 70px; align-self: start; max-height: calc(100dvh - 70px);
  overflow-y: auto; padding: 18px 12px 20px;
  display: flex; flex-direction: column; gap: 10px;
}
.sidebar > nav { margin-bottom: 8px; }
@media (max-width: 880px) { .sidebar { position: static; max-height: none; border-bottom: 1px solid var(--line); } }

.side-empty { font-size: 13px; color: var(--ink-3); padding: 0 10px; }
.sidebar summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: 8px; }
.sidebar summary::-webkit-details-marker { display: none; }
.sidebar .tw { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar b {
  font-weight: 600; font-size: 10.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums; background: var(--sunken);
  padding: 2px 7px; border-radius: 999px; min-width: 24px; text-align: center;
}

.yr > summary {
  font-size: 14.5px; font-weight: 700; color: var(--indigo);
  padding: 9px 12px; border-radius: var(--r-sm); transition: background .13s ease;
}
.yr > summary:hover { background: var(--indigo-soft); }
.yr > summary::before {
  content: "›"; font-size: 16px; color: currentColor; line-height: 1; opacity: .6;
  transition: transform .16s ease; display: inline-block; width: 10px;
}
.yr[open] > summary::before { transform: rotate(90deg); }

.dy {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 500; color: var(--ink);
  padding: 9px 14px 9px 28px; border-radius: var(--r-md); margin: 2px 0;
  transition: background .14s ease, color .14s ease;
}
.dy:hover { background: var(--hover); }
/* Selected date: TVN orange, reversed out. Sits under the navy month pill so
   the two levels read as different things rather than two equal dark blocks. */
.dy[data-active="true"] {
  background: var(--brand-orange); color: #fff; font-weight: 600;
}
.dy[data-active="true"] b {
  background: rgba(255, 255, 255, .22); color: #fff;
}

.side-load {
  margin-top: auto; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 14px; display: flex; flex-direction: column; gap: 9px;
  box-shadow: var(--lift-1);
}
.side-load label { font-size: 11px; font-weight: 600; color: var(--ink-2); }
.side-load .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.side-load select {
  background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 8px; font-size: 12.5px; min-width: 0; cursor: pointer;
}
.side-load .hint { line-height: 1.45; }
/* Inside the Mockup List tree it is one more row of that tree, not a panel
   pinned to the floor of the sidebar. */
.navtree .side-load { margin: 10px 0 4px; padding: 12px; }

/* ---------- filters ---------- */
.col-main { min-width: 0; }
.filters {
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center; padding: 18px 26px 4px;
}
.filter {
  font-size: 12.5px; font-weight: 500;
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink-2);
  border-radius: 999px; padding: 7px 14px; cursor: pointer;
  display: inline-flex; gap: 8px; align-items: center;
  transition: border-color .14s ease, background .14s ease, color .14s ease;
}
.filter:hover { border-color: var(--ink-3); color: var(--ink); }
.filter[data-active="true"] { background: var(--indigo); color: var(--on-accent); border-color: var(--indigo); }
.filter b { font-weight: 700; font-size: 11px; font-variant-numeric: tabular-nums; opacity: .68; }
.scope { margin-left: auto; font-size: 12px; color: var(--ink-3); }

/* ---------- board: day = one card of hairline rows ---------- */
.board {
  --page-max: 1700px;
  padding: 18px max(26px, calc((100% - var(--page-max)) / 2)) 90px;
  display: flex; flex-direction: column; gap: 18px;
}
.daycard {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--lift-1); overflow: hidden;
}
.dayhead {
  display: flex; align-items: center; gap: 10px;
  padding: 15px 20px; border-bottom: 1px solid var(--line);
}
.dayhead .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--indigo); flex: none; }
.dayhead h3 {
  margin: 0; font-size: 12px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-2);
}
.dayhead .n {
  font-size: 10.5px; font-weight: 700; color: var(--ink-3); background: var(--sunken);
  padding: 2px 8px; border-radius: 999px; font-variant-numeric: tabular-nums;
}

.row {
  display: grid; align-items: center; gap: 16px;
  /* Four columns, not five. The leading column held the internal MD-nnnn;
     with that gone the ticket rides inside the name cell like every other
     tag, so there is no gutter standing empty on the jobs nobody has given
     a ticket. */
  grid-template-columns: minmax(0,1fr) 150px 148px 62px;
  padding: 13px 20px; border-bottom: 1px solid var(--line);
  transition: background .13s ease;
}
.row:last-child { border-bottom: 0; }
.row:hover { background: var(--hover); }
.row .chip { justify-self: start; }
@media (max-width: 780px) {
  .row { grid-template-columns: minmax(0,1fr) auto; gap: 10px; }
  .row .count { display: none; }
}
/* The SKU or alias a row is about. It was 11px tertiary grey — the smallest,
   palest thing in a row whose whole subject it is, and a full size below the
   plain word "alias" sitting next to it. Code face spec. */
.ref {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--ink-2); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.who { min-width: 0; }
/* A flex row so a long folder name truncates on its own and any tag beside
   it stays whole — ellipsising the pair together clipped "Urgent" to "Urg…". */
.who .name { font-weight: 600; font-size: 14.5px; letter-spacing: -.005em; display: flex; align-items: center; min-width: 0; }
.who .name > .nametext { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.who .name > .urgenttag, .who .name > .tickettag { flex: none; }
.who .co { font-size: 12.5px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.assignee { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-2); min-width: 0; }
.assignee.none { color: var(--ink-3); }
.assignee span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.count {
  font-size: 11px; color: var(--ink-3); text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.count[data-has="true"] {
  color: var(--ink-2); background: var(--sunken); border-radius: 999px;
  padding: 3px 9px; justify-self: end; font-weight: 600;
}

.empty {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 48px 24px; text-align: center; color: var(--ink-2); box-shadow: var(--lift-1);
}
.empty p { margin: 0 0 16px; font-size: 14.5px; }
.hint { font-size: 11.5px; color: var(--ink-3); }

/* ---------- job detail ---------- */
.job { display: grid; grid-template-columns: minmax(0,1fr) 330px; gap: 34px; padding: 24px 26px 90px; max-width: 1320px; }
@media (max-width: 980px) { .job { grid-template-columns: minmax(0,1fr); gap: 24px; } }
/* A real button, not a whisper of a link — this is how everyone gets back to
   the day they were working through, so it has to be findable at a glance. */
.back {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 600; color: var(--ink-2);
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: 999px; padding: 8px 16px 8px 13px; margin-bottom: 18px;
  box-shadow: var(--lift-1);
  transition: background .14s ease, color .14s ease, border-color .14s ease, transform .12s ease;
}
.back:hover {
  color: var(--indigo); border-color: var(--indigo);
  background: var(--indigo-soft); transform: translateX(-2px);
}
.back:active { transform: translateX(0); }
.back .arrow { font-size: 15px; line-height: 1; }
.job h1 { font-size: clamp(23px,3vw,30px); font-stretch: 106%; font-weight: 700; letter-spacing: -.02em; margin: 0 0 6px; text-wrap: balance; }
/* Code face spec. No markup renders .path today — kept on the spec so it is
   right if it comes back, not left as a second dialect. */
.path {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--ink-2); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
  word-break: break-all; margin: 0 0 20px;
}

/* The edge was --line (#ECEFF4) sitting on --ground (#F5F7FA): 1.07:1, which
   is no edge at all, so seven stacked panels on /lookup were separated by
   nothing but white against near-white. --line-2 measures 1.21 light and
   1.80 dark against the same ground. --line stays for rules INSIDE a panel,
   so the box now has an outline and its contents still have hairlines. */
.panel { background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-lg); padding: 18px; box-shadow: var(--lift-1); }
.panel + .panel { margin-top: 14px; }
/* A panel heading is a band, not a line of text: pulled out to the panel's
   inner edge and given a grey strip with a lighter outline under it, so
   "Jobs this month · 6" reads as the title of the list rather than its
   first row. */
.panel h3 {
  font-size: 14px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--on-bar); display: flex; align-items: center; gap: 9px;
  margin: -18px -18px 16px; padding: 12px 18px;
  background: var(--bar); border-bottom: 1px solid var(--bar);
  border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
}
/* The dot was the indigo accent on a grey strip. On the navy it has to be the
   light one or it disappears into the band. */
.panel h3::before { content:""; width:8px; height:8px; border-radius:50%; background: var(--on-bar-dot); flex:none; }
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field:last-child { margin-bottom: 0; }
.field select, .field input {
  background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 9px 10px; font-size: 13px; cursor: pointer;
}
.statusgrid { display: flex; flex-direction: column; gap: 6px; }
.statusgrid button { width: 100%; justify-content: flex-start; cursor: pointer; padding: 8px 12px; font-size: 12px; transition: transform .12s ease, filter .14s ease; }
.statusgrid button:hover:not([disabled]) { transform: translateX(2px); filter: brightness(.97); }
.statusgrid button[data-on="false"] { background: var(--sunken) !important; color: var(--ink-3) !important; }
.statusgrid button[data-on="false"]::before { opacity: .35; }

/* ---------- comments ---------- */
.thread { display: flex; flex-direction: column; gap: 0; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--lift-1); overflow: hidden; }
.comment { display: flex; gap: 13px; padding: 16px 18px; border-bottom: 1px solid var(--line); }
.comment:last-child { border-bottom: 0; }
.cmain { min-width: 0; flex: 1; }
.chead { display: flex; align-items: baseline; gap: 9px; margin-bottom: 5px; flex-wrap: wrap; }
.chead { margin-bottom: 8px; }
.chead .author { font-weight: 700; font-size: 15px; color: var(--ink); }
.chead .role { font-size: 11px; font-weight: 600; text-transform: capitalize; color: var(--ink-3); background: var(--sunken); padding: 3px 10px; border-radius: 999px; }
.chead .when { font-size: 12.5px; color: var(--ink-3); }

/* What a reviewer wrote is the point of this page, so it is set at reading
   size in the full ink color. It used to be 15.5px of --ink-2, which is the
   grey this app uses for supporting detail — it made the instructions look
   like a caption of themselves. */
.cbody {
  font-family: var(--sans); font-size: 17px; line-height: 1.55;
  color: var(--ink); white-space: pre-wrap; letter-spacing: -.003em;
}

.composer { margin-top: 16px; display: flex; flex-direction: column; gap: 10px; }
.composer textarea {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-md);
  padding: 14px 15px; font-family: var(--sans); font-size: 16px; line-height: 1.55;
  min-height: 104px; resize: vertical; transition: border-color .15s ease;
}
.composer textarea:focus { border-color: var(--indigo); }
.composer textarea::placeholder { font-family: var(--sans); font-size: 14px; color: var(--ink-3); }
.composer .actions { display: flex; gap: 12px; align-items: center; }

.activity { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; }
.activity li { font-size: 12.5px; color: var(--ink-3); line-height: 1.5; }
.activity b { color: var(--ink-2); font-weight: 600; }

/* ---------- sign in ----------
   Split screen: a branded panel carrying the pitch, and a plain white card
   carrying the work. No social sign-in and no sign-up — accounts are created
   by an admin, so offering either would be a dead end. */
.signin {
  min-height: 100dvh; display: grid; grid-template-columns: 1.05fr 1fr;
  /* The gradient runs the full width — the card floats on it rather than
     sitting beside a colored half. Anchored on the TVN navy. */
  background: linear-gradient(150deg, #041640 0%, #06215c 46%, #0c3486 100%);
}
@media (max-width: 900px) {
  .signin { grid-template-columns: 1fr; }
}.signin-card {
  width: 100%; max-width: 448px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 22px;
  padding: 92px 44px 84px; box-shadow: var(--lift-3);
}
.signin-card h2 {
  font-size: 32px; font-weight: 700; letter-spacing: -.02em; margin: 0 0 8px; text-align: center;
}
.signin-sub {
  /* var(--ink) rather than a literal black: the card turns dark in dark mode,
     where #000 would be unreadable. This is black in light, near-white there. */
  color: var(--ink); font-size: 16px; margin: 0 0 26px; text-align: center; line-height: 1.5;
}
.signin-form { display: flex; flex-direction: column; gap: 12px; }
.signin-form input {
  /* --ink-3 is the dark grey already in the palette, so the outline stays
     legible if the card flips to dark rather than being a fixed grey. */
  background: var(--raised); border: 1px solid var(--ink-3); border-radius: 999px;
  padding: 15px 20px; font-size: 14.5px; transition: border-color .15s ease, background .15s ease;
}
.signin-form input::placeholder { color: var(--ink-3); }
.signin-form input:focus { border-color: var(--indigo); background: var(--surface); }
.forgot {
  /* Same reason as .signin-sub: var(--ink) is black on the light card and
     stays legible when the card goes dark. */
  margin: 2px 0; text-align: center; font-size: 12.5px; color: var(--ink); line-height: 1.5;
}
.signin-form button.primary {
  margin-top: 8px; padding: 15px; justify-content: center;
  border-radius: 999px; font-size: 17px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  background: var(--brand-orange); border-color: var(--brand-orange); color: #fff;
}
.signin-form button.primary:hover:not([disabled]) {
  background: var(--brand-orange); filter: brightness(1.07);
}

.err {
  font-size: 12.5px; color: var(--s-change); background: var(--s-change-bg);
  border-radius: var(--r-sm); padding: 10px 12px; margin: 2px 0 0;
}/* ---------- status panel takes the color of the state it's in ----------
   A job that needs changes should be unmistakable from across the room, so
   the whole panel carries the state rather than one small pill. */
.statuspanel {
  transition: background .22s ease, border-color .22s ease;
}
.statuspanel[data-status="new"]              { background: var(--s-new-bg);    border-color: color-mix(in srgb, var(--s-new)    28%, transparent); }
.statuspanel[data-status="in_progress"]      { background: var(--s-prog-bg);   border-color: color-mix(in srgb, var(--s-prog)   28%, transparent); }
.statuspanel[data-status="ready_for_review"] { background: var(--s-review-bg); border-color: color-mix(in srgb, var(--s-review) 32%, transparent); }
.statuspanel[data-status="changes_required"] { background: var(--s-change-bg); border-color: color-mix(in srgb, var(--s-change) 34%, transparent); }
.statuspanel[data-status="approved"]         { background: var(--s-appr-bg);   border-color: color-mix(in srgb, var(--s-appr)   32%, transparent); }

.statuspanel[data-status="new"] h3::before              { background: var(--s-new); }
.statuspanel[data-status="in_progress"] h3::before      { background: var(--s-prog); }
.statuspanel[data-status="ready_for_review"] h3::before { background: var(--s-review); }
.statuspanel[data-status="changes_required"] h3::before { background: var(--s-change); }
.statuspanel[data-status="approved"] h3::before         { background: var(--s-appr); }

.statuspanel[data-status="new"] h3              { color: var(--s-new); }
.statuspanel[data-status="in_progress"] h3      { color: var(--s-prog); }
.statuspanel[data-status="ready_for_review"] h3 { color: var(--s-review); }
.statuspanel[data-status="changes_required"] h3 { color: var(--s-change); }
.statuspanel[data-status="approved"] h3         { color: var(--s-appr); }

/* The selected state reads as a solid bar, not a tinted pill. */
.statusgrid button[data-on="true"] { color: var(--on-accent); font-weight: 700; }
.statusgrid button[data-on="true"]::before { background: var(--on-accent); opacity: .9; }
.statusgrid button[data-on="true"][data-status="new"]              { background: var(--s-new); }
.statusgrid button[data-on="true"][data-status="in_progress"]      { background: var(--s-prog); }
.statusgrid button[data-on="true"][data-status="ready_for_review"] { background: var(--s-review); }
.statusgrid button[data-on="true"][data-status="changes_required"] { background: var(--s-change); }
.statusgrid button[data-on="true"][data-status="approved"]         { background: var(--s-appr); }

/* Unselected options sit quietly on the tinted panel. */
.statuspanel .statusgrid button[data-on="false"] {
  background: color-mix(in srgb, var(--surface) 72%, transparent) !important;
  color: var(--ink-3) !important;
}
.statuspanel .statusgrid button[data-on="false"]:hover:not([disabled]) {
  background: var(--surface) !important; color: var(--ink-2) !important;
}

/* ---------- notification bell ---------- */
.bell-wrap { position: relative; }
/* Solid red so it is the first thing anyone sees in the header. */
.bell {
  position: relative; width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid var(--s-change); background: var(--s-change); color: var(--on-accent);
  display: grid; place-items: center; cursor: pointer;
  box-shadow: 0 1px 2px color-mix(in srgb, var(--s-change) 40%, transparent);
  transition: filter .14s ease, transform .12s ease, box-shadow .16s ease;
}
.bell:hover { filter: brightness(1.08); transform: translateY(-1px);
  box-shadow: 0 3px 10px color-mix(in srgb, var(--s-change) 45%, transparent); }
.bell:active { transform: translateY(0); }
/* <details> carries its state in the open attribute, not aria-expanded. */
.bell-wrap[open] > .bell { filter: brightness(.94); }
.bell { list-style: none; }
.bell::-webkit-details-marker { display: none; }

/* The count reverses out of the red so it stays readable. */
.bell-count {
  position: absolute; top: -4px; right: -4px; min-width: 19px; height: 19px; padding: 0 5px;
  border-radius: 999px; background: var(--surface); color: var(--s-change);
  font-size: 10.5px; font-weight: 800; line-height: 17px; text-align: center;
  border: 2px solid var(--s-change); font-variant-numeric: tabular-nums;
}.bell-panel {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 60; width: 350px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--lift-3); overflow: hidden;
}
.bell-panel header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 13px 16px; border-bottom: 1px solid var(--line);
}
.bell-panel header b { font-size: 13.5px; }
.linky {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-size: 12px; color: var(--indigo); font-weight: 600;
}
.linky:hover { text-decoration: underline; }
.bell-empty { margin: 0; padding: 26px 16px; text-align: center; color: var(--ink-3); font-size: 13px; }
.bell-panel ul, .notif-list { list-style: none; margin: 0; padding: 0; }
.bell-panel li a, .notif-list li a {
  display: flex; align-items: center; gap: 11px; padding: 12px 16px;
  border-bottom: 1px solid var(--line); transition: background .13s ease;
}
.bell-panel li:last-child a { border-bottom: 0; }
.notif-list li:last-child a { border-bottom: 0; }
.bell-panel li a:hover, .notif-list li a:hover { background: var(--hover); }
.bell-panel li a[data-unread="true"], .notif-list li a[data-unread="true"] { background: var(--indigo-soft); }
.bell-panel li a[data-unread="true"]:hover, .notif-list li a[data-unread="true"]:hover {
  background: color-mix(in srgb, var(--indigo) 12%, transparent);
}
.ndot { width: 8px; height: 8px; border-radius: 50%; background: var(--line-2); flex: none; }
.ndot.hot { background: var(--s-change); }
.ntext { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ntext b { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ntext i { font-style: normal; font-size: 12px; color: var(--ink-3); }
.nwhen { font-size: 11px; color: var(--ink-3); white-space: nowrap; }.bell-all {
  display: block; text-align: center; padding: 12px; font-size: 12.5px; font-weight: 600;
  color: var(--indigo); border-top: 1px solid var(--line); background: var(--raised);
}
.bell-all:hover { background: var(--indigo-soft); }

/* The bell sits in the middle of the header, not at its right edge, so a
   350px panel anchored to the bell hangs off the left of a phone screen.
   Below this width it stops tracking the bell and spans the viewport
   instead — fixed, because the header is sticky and absolute would scroll
   the panel away from the button that opened it. */
@media (max-width: 560px) {
  .bell-panel {
    position: fixed;
    top: 70px; left: 10px; right: 10px;
    width: auto;
    max-height: calc(100vh - 84px);
    overflow-y: auto;
  }
}

/* ---------- notifications page ---------- */
/* Narrower than the rest on purpose: a notification is one line of text, and
   a line that runs the full width of a wide monitor is hard to come back to. */
.notif-page {
  --page-max: 1100px;
  padding: 26px max(26px, calc((100% - var(--page-max)) / 2)) 60px;
  max-width: none;
  display: flex; flex-direction: column; gap: 16px;
}
.notif-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.notif-head h1 { font-size: 26px; font-stretch: 106%; font-weight: 700; letter-spacing: -.02em; margin: 0 0 4px; }
.notif-filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.notif-filters .divider { width: 1px; height: 22px; background: var(--line-2); margin: 0 2px; }.daychips { display: flex; gap: 6px; flex-wrap: wrap; }
.daychip {
  font-size: 11.5px; color: var(--ink-2); background: var(--surface);
  border: 1px solid var(--line); border-radius: 999px; padding: 5px 11px;
  display: inline-flex; gap: 7px; align-items: center;
}
.daychip:hover { border-color: var(--ink-3); }
.daychip[data-active="true"] { background: var(--ink); color: var(--surface); border-color: var(--ink); }
.daychip b { font-size: 10px; font-weight: 700; opacity: .6; }

/* A scroll pane laid out as a flex column must not let its children shrink.
   A card with overflow:hidden reports a min-content height of zero, so flexbox
   is free to squeeze it — the card then swallows its own overflow, and because
   every child shrinks to fit, the pane never overflows and no scrollbar ever
   appears. The clipped rows become unreachable rather than scrollable. Pinning
   the children to their natural height is what makes the pane scroll at all. */
.board > *, .settings > *, .notif-page > * { flex: none; }

/* ---------- app shell: the page itself never scrolls ----------
   Header stays put, sidebar and content scroll independently, so this reads
   as an application rather than a long document. Below the sidebar
   breakpoint the normal document scroll comes back, because two nested
   scrollers on a phone is worse than one. */
@media (min-width: 881px) {
  html, body { height: 100dvh; overflow: hidden; }
  body { display: flex; flex-direction: column; }

  .topbar { position: static; flex: none; }

  .app-body { flex: 1; min-height: 0; display: flex; }
  .app-body > * { flex: 1; min-width: 0; min-height: 0; }

  .shell-2col { height: 100%; }
  .sidebar {
    position: static; max-height: none; height: 100%;
    overflow-y: auto; overscroll-behavior: contain;
  }
  .col-main {
    height: 100%; overflow-y: auto; overscroll-behavior: contain;
    display: flex; flex-direction: column;
  }
  .col-main .board { flex: 1; }

  .job, .notif-page, .settings, .reports { height: 100%; overflow-y: auto; overscroll-behavior: contain; }
  /* Capped and centred like the other pages. Without this the thread sat on
     the far left and the details rail on the far right, with several hundred
     pixels of nothing between them on a wide monitor. The thread column comes
     out around 880px, which is what the 860px cap below was reaching for. */
  .job {
    --page-max: 1240px;
    max-width: none;
    padding-left: max(26px, calc((100% - var(--page-max)) / 2));
    padding-right: max(26px, calc((100% - var(--page-max)) / 2));
  }

  /* The sign-in screen has no header, so it owns the whole viewport. */
  .signin { height: 100%; min-height: 0; overflow-y: auto; }
}

/* Slim, unobtrusive scrollbars for the inner panes. */
@supports (scrollbar-width: thin) {
  .sidebar, .col-main, .job, .notif-page, .settings, .reports { scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
}

/* The designer's hand-back button — the counterpart to "Changes Required". */
.btn.done {
  background: var(--s-appr); border-color: var(--s-appr); color: var(--on-accent);
  font-weight: 600;
}
.btn.done:hover { filter: brightness(1.06); background: var(--s-appr); }

/* ---------- theme toggle ---------- */
.theme-toggle {
  width: 38px; height: 38px; border-radius: 50%; flex: none;
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink-2);
  display: grid; place-items: center; cursor: pointer;
  transition: background .14s ease, color .14s ease, border-color .14s ease, transform .12s ease;
}
.theme-toggle:hover { background: var(--hover); color: var(--ink); border-color: var(--ink-3); }
.theme-toggle:active { transform: scale(.94); }
.theme-toggle svg { display: block; }
.theme-slot { width: 17px; height: 17px; display: block; }

/* ---------- rows that are waiting on someone ----------
   Changes Required is the designer's cue; Ready for Review is the reviewer's.
   Both take a full-row tint mixed from the state color over the card surface,
   so the same rule produces a soft cream row in light and a deep olive one in
   dark without a second set of hard-coded values. */
.row[data-alert] { position: relative; }

.row[data-alert="ready_for_review"] {
  background: color-mix(in srgb, var(--s-review) 16%, var(--surface));
}
.row[data-alert="ready_for_review"]:hover {
  background: color-mix(in srgb, var(--s-review) 24%, var(--surface));
}
.row[data-alert="changes_required"] {
  background: color-mix(in srgb, var(--s-change) 15%, var(--surface));
}
.row[data-alert="changes_required"]:hover {
  background: color-mix(in srgb, var(--s-change) 23%, var(--surface));
}

/* A stripe on the leading edge, so the state survives being skim-read. */
.row[data-alert]::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
}
.row[data-alert="ready_for_review"]::before { background: var(--s-review); }
.row[data-alert="changes_required"]::before { background: var(--s-change); }

/* On a tinted row the soft pill disappears, so it inverts to a solid one.
   In dark the state colors are the light end of the scale, which gives the
   pale pill with dark text; in light it gives a saturated pill with white. */
.row[data-alert] .chip {
  color: var(--on-accent); font-weight: 700;
}
.row[data-alert="ready_for_review"] .chip { background: var(--s-review); }
.row[data-alert="changes_required"] .chip { background: var(--s-change); }

.row[data-alert] .who .name { color: var(--ink); }
.row[data-alert] .who .co,

/* ---------- settings ---------- *//*
 * Page width.
 *
 * These containers are their own scrollers above the sidebar breakpoint, and
 * as flex items a max-width left them pinned to the left — on a 1504px screen
 * Today's work was 1080px of content followed by 424px of nothing.
 *
 * Centring is done with padding rather than auto margins so the container
 * still spans the window: the scrollbar stays at the edge of the screen where
 * people reach for it, while the content sits in the middle. --page-max is
 * the readable ceiling, overridden per page below.
 */
.settings {
  --page-max: 1400px;
  padding: 26px max(26px, calc((100% - var(--page-max)) / 2)) 60px;
  max-width: none;
  display: flex; flex-direction: column; gap: 16px;
}
.settings .panel h3::before { background: var(--on-bar-dot); }

.addrow { display: grid; grid-template-columns: 1.1fr 1.4fr 150px auto; gap: 9px; align-items: center; }
@media (max-width: 820px) { .addrow { grid-template-columns: 1fr; } }
.addrow input, .addrow select {
  background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 10px 11px; font-size: 13.5px; min-width: 0;
}
.addrow input:focus, .addrow select:focus { border-color: var(--indigo); }

.ok {
  font-size: 12.5px; color: var(--s-appr); background: var(--s-appr-bg);
  border-radius: var(--r-sm); padding: 10px 12px; margin: 12px 0 0;
}
.panel .err { margin: 12px 0 0; }

.teamlist { display: flex; flex-direction: column; }
.teamrow {
  display: grid; align-items: center; gap: 14px;
  grid-template-columns: 34px minmax(0,1.3fr) minmax(0,1fr) 74px 96px auto;
  padding: 13px 0; border-bottom: 1px solid var(--line);
}
.teamrow:last-child { border-bottom: 0; }
.teamrow[data-suspended="true"] { opacity: .62; }
@media (max-width: 900px) {
  .teamrow { grid-template-columns: 34px minmax(0,1fr); gap: 10px 12px; }
  .teamrow > :nth-child(n+3) { grid-column: 2; }
}

.teamwho { display: flex; flex-direction: column; min-width: 0; }
.teamwho b { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 7px; }
.teamwho i {
  font-style: normal; font-size: 12.5px; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.youtag {
  font-style: normal; font-size: 9.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: var(--indigo); background: var(--indigo-soft);
  padding: 2px 7px; border-radius: 999px;
}

.teamrole { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.teamrole b { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.teamrole i { font-style: normal; font-size: 11px; color: var(--ink-3); }

.teamjobs { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

.statuslamp {
  font-size: 11.5px; font-weight: 600; border-radius: 999px; padding: 4px 10px 4px 8px;
  display: inline-flex; align-items: center; gap: 6px; justify-self: start; white-space: nowrap;
}
.statuslamp::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
/* The worst text in the app, and it predates this pass: measured 24 Sep 2026
   on /settings, "Active" was --s-appr on --s-appr-bg at 2.94:1 - a green
   word on a green pill, which is the arrangement most likely to look fine
   and read badly. Same trap the chips were pulled out of in an earlier
   round; the lamps were missed because they are not chips. The TINTS stay,
   so the lamp is the same colour it was; only the word darkens. */
.statuslamp.on  { color: var(--pos); background: var(--s-appr-bg); }
.statuslamp.off { color: var(--neg); background: var(--s-change-bg); }

.teamacts { display: flex; gap: 7px; flex-wrap: wrap; justify-content: flex-end; }
.teamacts .btn { padding: 6px 12px; font-size: 12.5px; }
.btn.ghost { background: transparent; border-color: var(--line); color: var(--ink-3); }
.btn.ghost:hover:not([disabled]) { color: var(--s-change); border-color: var(--s-change); background: var(--s-change-bg); }
/* White on --s-change measured 4.04:1 - under AA, on the one button in the
   app whose label must not be misread. --neg is the same hue two shades
   down and takes it to 6.13. */
.btn.danger { background: var(--neg); border-color: var(--neg); color: var(--on-accent); font-weight: 600; }
.btn.danger:hover { filter: brightness(1.06); background: var(--neg); }

/* password controls in settings */
.addrow { grid-template-columns: 1fr 1.3fr 130px 1fr auto; }
@media (max-width: 1020px) { .addrow { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .addrow { grid-template-columns: 1fr; } }
.nopw {
  font-size: 11px; font-weight: 600; color: var(--s-review);
  background: var(--s-review-bg); border-radius: 999px; padding: 4px 9px; white-space: nowrap;
}
.teamrow { position: relative; }

/* The one edit form, folded out under its row.
   A grid rather than a flex row: four fields of very different natural widths
   — a name, an address, a one-word department, a password — laid out by flex
   come out ragged and differently ragged on every row. Fixed tracks make the
   four labels line up down the list, which is what lets an admin scan them. */
.memberedit {
  grid-column: 1 / -1; display: grid; gap: 12px 14px; align-items: end;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.5fr) minmax(0, .8fr) minmax(0, 1.2fr) auto;
  margin-top: 12px; padding: 14px; background: var(--sunken); border-radius: var(--r-md);
}
@media (max-width: 1180px) { .memberedit { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .memberedit { grid-template-columns: minmax(0, 1fr); } }

/* margin-bottom is reset: the base .field spaces a stack, this is a row. */
.memberedit .field { display: flex; flex-direction: column; gap: 5px; min-width: 0; margin-bottom: 0; }
.memberedit .field > span {
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.memberedit input, .memberedit select {
  width: 100%; background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 8px 10px; font-size: 13px;
}
.memberedit input { cursor: text; }
.memberedit input:focus, .memberedit select:focus { border-color: var(--indigo); outline: none; }
/* Readable, and meant to be: an admin opens this to pass a password on. */
.memberedit input[name="password"] { font-family: var(--mono, ui-monospace, monospace); }
.memberedit-acts { display: flex; gap: 8px; align-items: center; }
@media (max-width: 1180px) { .memberedit-acts { grid-column: 1 / -1; } }

/* ---------- brand ----------
   The logo's navy disappears on a dark ground, so the type color comes from
   a token while the orange stays exactly as the artwork draws it. */
:root {
  --logo-ink: #06215c; --brand-orange: #f04e23; --brand-navy: #06215c;
  /* The section bar, in the logo's navy. One token so every panel heading in
     the app moves together — they are one component wearing one color. */
  --bar: #06215c; --on-bar: #FFFFFF; --on-bar-dot: #7FA9F0;
  --charcoal: #36454F;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --logo-ink: #EEF0F5;
    /* #06215c against a #181B22 surface is one dark on another; lifted a
       step so the band still reads as a band. */
    --bar: #123A7A; --on-bar: #EAF0FB; --on-bar-dot: #9CC0F7; }
}
:root[data-theme="dark"] { --logo-ink: #EEF0F5;
    /* #06215c against a #181B22 surface is one dark on another; lifted a
       step so the band still reads as a band. */
    --bar: #123A7A; --on-bar: #EAF0FB; --on-bar-dot: #9CC0F7; }

.brand { display: inline-flex; align-items: center; flex: none; }
/* 46, not 41: this artwork's viewBox carries the Illustrator artboard's
   padding (the ink is 145 of its 193 units tall), so it needs the extra to
   draw the same size the old lockup did at 41. */
.brandmark { height: 46px; width: auto; display: block; }
.brand:hover { opacity: .82; }/* background-sync status */
.scope { display: inline-flex; align-items: center; gap: 10px; }
.synced {
  font-style: normal; font-size: 11px; color: var(--ink-3);
  background: var(--sunken); border-radius: 999px; padding: 3px 9px; white-space: nowrap;
}.panel .ok { margin: 12px 0 0; }

/* ---------- handing out work ---------- */
.handout {
  display: grid; gap: 9px; align-items: center;
  /* Columns are set further down, where the six-field version lives. An
     earlier five-column set used to sit here and was silently overridden. */
}
.handout input, .handout select {
  background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 10px 11px; font-size: 13.5px; min-width: 0;
}
.handout input:focus, .handout select:focus { border-color: var(--indigo); }

.waiting {
  margin-left: 10px; font-size: 10.5px; font-weight: 700; text-transform: none;
  letter-spacing: 0; color: var(--s-review); background: var(--s-review-bg);
  padding: 3px 9px; border-radius: 999px;
}

.handout-list { display: flex; flex-direction: column; }
.handout-row {
  display: grid; align-items: center; gap: 14px;
  grid-template-columns: 8px minmax(0,1.6fr) 150px 150px auto;
  padding: 13px 0; border-bottom: 1px solid var(--line);
}
.handout-row:last-child { border-bottom: 0; }
.handout-row[data-linked="true"] { opacity: .72; }
@media (max-width: 900px) {
  .handout-row { grid-template-columns: 8px minmax(0,1fr); }
  .handout-row > :nth-child(n+3) { grid-column: 2; }
  /* Stacked, a chip would otherwise stretch the whole row and read as a bar
     rather than a label. */
  .handout-row > .chip, .handout-row > .assignee { justify-self: start; }
}
.handout-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.handout-who b { font-size: 14.5px; font-weight: 600; }
.handout-who i { font-style: normal; font-size: 12.5px; color: var(--ink-3); }
.handout-who em { font-style: normal; font-size: 12.5px; color: var(--ink-2); }
.handout-acts { display: flex; gap: 7px; justify-content: flex-end; flex-wrap: wrap; }
.handout-acts .btn { padding: 6px 12px; font-size: 12.5px; }.daypick {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 8px 10px; font-size: 13px; color: var(--ink);
}

/* ---------- the designer's own queue ----------
   Always filled in the brand orange so designers can find it at a glance,
   whether or not anything is outstanding. The count appears only when there
   are folders still to make. */
.myday {
  display: inline-flex; align-items: center; gap: 8px; flex: none;
  font-size: 13.5px; font-weight: 700; letter-spacing: .01em;
  color: #fff; background: var(--brand-orange);
  border-radius: 999px; padding: 9px 18px;
  box-shadow: 0 1px 2px color-mix(in srgb, var(--brand-orange) 40%, transparent);
  transition: filter .14s ease, transform .12s ease, box-shadow .16s ease;
}
.myday:hover {
  filter: brightness(1.07); transform: translateY(-1px);
  box-shadow: 0 3px 10px color-mix(in srgb, var(--brand-orange) 45%, transparent);
}
.myday:active { transform: translateY(0); }
.myday b {
  font-size: 11.5px; font-weight: 800; line-height: 1;
  background: #fff; color: var(--brand-orange);
  border-radius: 999px; padding: 3px 7px; font-variant-numeric: tabular-nums;
}

/* my-day date controls */
.myday-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.myday-tools form { display: flex; align-items: center; }

/* ---------- board sections ----------
   Navy for the reader's own queue, charcoal for the whole day — the quieter
   color on the bigger list, so your own work still leads. Fixed in both
   themes, like the sidebar. */
.sectionbar {
  display: flex; align-items: center; gap: 10px;
  margin: 4px 0 0; padding: 12px 18px; border-radius: var(--r-md);
  font-size: 13px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: #fff;
}
.sectionbar b {
  font-size: 11.5px; font-weight: 800; letter-spacing: 0;
  background: rgba(255, 255, 255, .2); border-radius: 999px;
  padding: 3px 9px; font-variant-numeric: tabular-nums;
}
.sectionbar.mine { background: var(--brand-navy); }
.sectionbar.all { background: var(--charcoal); margin-top: 14px; }

.empty.slim { padding: 26px 22px; }

/* my-day sections */
.sectionbar.revisions { background: var(--s-change); }
.sectionbar.newmockups { background: var(--brand-navy); }
.sectionnote { margin: 0; padding: 14px 18px 0; }
.daycard .handout-list { padding: 0 18px; }
.handout-row.asrow {
  grid-template-columns: 8px minmax(0,1.6fr) 70px 150px auto;
  transition: background .13s ease;
}
.handout-row.asrow:hover { background: var(--hover); }
.handout-row.asrow .ref { font-size: 11.5px; }.sourcetag {
  font-family: var(--sans); font-size: 10px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  border-radius: 999px; padding: 3px 8px; margin-left: 9px; white-space: nowrap;
}/* A reference, not a word: mono so a 6 and a G cannot be confused, and
   quiet so it never outranks the client name it sits in front of. */
.tickettag {
  display: inline-block; margin-right: 6px; padding: 1px 6px;
  font-family: var(--mono);
  font-size: 12px; font-weight: 500; letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2); background: var(--sunken);
  border: 1px solid var(--line-2); border-radius: 5px;
  vertical-align: 1px;
}
/* On the job title it sits against 28px type, so it needs a little more
   body than it does in a list row. */
h1 .tickettag { font-size: 13px; margin-right: 8px; vertical-align: 5px; }
/* The way out of a capped list. Centred under the last day card so it reads
   as the end of the list rather than another row in it. */
.showall {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  flex-wrap: wrap; padding: 18px 0 4px; margin: 0;
}
.showall .hint { margin: 0; }
.sourcetag.direct_email { color: var(--s-prog); background: var(--s-prog-bg); }
.sourcetag.form_email { color: var(--s-review); background: var(--s-review-bg); }

/* Urgent. Deliberately the same red the app uses for "changes required" —
   the color already means "this one needs you" everywhere else, and a new
   hue for a second kind of alarm would only dilute both. */
.urgenttag {
  margin-left: 6px; font-size: 10.5px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .04em; color: var(--s-change); background: var(--s-change-bg);
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
  border: 1px solid color-mix(in srgb, var(--s-change) 35%, transparent);
}

/* An urgent job tints its whole row, so it is findable while scanning a list
   of thirty rather than only once you have read across to the tag. Kept light
   — the text on top still has to meet contrast, and a wash this size reads as
   emphasis where a saturated fill would read as an error state. */
[data-urgent="true"] { background: color-mix(in srgb, var(--s-change) 16%, transparent); }
a.row[data-urgent="true"]:hover,
.handout-row[data-urgent="true"]:hover { background: color-mix(in srgb, var(--s-change) 24%, transparent); }
/* Eight children, so eight columns — one short and the button drops to a
   second line. The ticket takes a fixed track rather than a flexible one:
   references are short and all much the same length, so a track that grows
   with the window only ever adds empty box. */
.handout {
  grid-template-columns:
    136px               /* day it is for      */
    minmax(0, 1.05fr)   /* client folder name */
    minmax(0, 1fr)      /* client email       */
    118px               /* how it came in     */
    100px               /* urgency            */
    128px               /* designer           */
    auto;               /* add to the list    */
}
/* A revision has no "how it came in", so it is one column shorter. */
.handout[data-kind="revision"] {
  grid-template-columns:
    136px               /* day it is for      */
    minmax(0, 1.15fr)   /* client folder name */
    minmax(0, 1fr)      /* client email       */
    110px               /* urgency            */
    132px               /* designer           */
    auto;               /* add to the list    */
}
@media (max-width: 1200px) { .handout { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 700px) { .handout { grid-template-columns: 1fr; } }

/* Inline edit for a handed-out job — the same shape as the add form above it:
   the fields on one row, the note across the width, the buttons under. */
.editrow {
  grid-column: 1 / -1; display: grid; gap: 10px; align-items: center;
  grid-template-columns:
    136px                /* day it is for      */
    minmax(0, 1.3fr)     /* client folder name */
    minmax(0, 1fr)       /* client email       */
    128px                /* how it came in     */
    110px                /* urgency            */
    132px;               /* designer           */
  margin-top: 12px; padding: 14px; background: var(--sunken); border-radius: var(--r-md);
}
.editnotes {
  grid-column: 1 / -1; width: 100%; resize: vertical; min-height: 58px;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 10px 12px; font: inherit; font-size: 13px; color: var(--ink); line-height: 1.5;
}
.editnotes::placeholder { color: var(--ink-3); }
.editnotes:focus { outline: none; box-shadow: var(--ring); }
.editacts { grid-column: 1 / -1; display: flex; gap: 9px; }
@media (max-width: 1200px) { .editrow { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 700px) { .editrow { grid-template-columns: 1fr; } }
.editrow input, .editrow select {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 9px 10px; font-size: 13px; min-width: 0;
}
.editrow input:focus, .editrow select:focus { border-color: var(--indigo); }

/* ==========================================================================
   Team report
   Chart hues validated with the dataviz palette checker: light
   #4F5BD5/#E1445F/#0891B2 and dark #6E79E0/#DE5A73/#1795B8 both pass the
   lightness band, chroma floor, CVD separation and contrast checks. The
   original green failed CVD against the rose (ΔE 4.2, deutan) — cyan
   replaced it. Each chart carries ONE hue; identity comes from the row
   label, so no reader depends on telling hues apart.
   ========================================================================== */
:root { --c-a: #4F5BD5; --c-b: #E1445F; --c-c: #0891B2; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --c-a: #6E79E0; --c-b: #DE5A73; --c-c: #1795B8; }
}
:root[data-theme="dark"] { --c-a: #6E79E0; --c-b: #DE5A73; --c-c: #1795B8; }

.reports { --page-max: 1400px; }   /* .settings supplies the padding */

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
.tile {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 18px 20px; box-shadow: var(--lift-1); display: flex; flex-direction: column; gap: 3px;
}
.tile b {
  font-size: 30px; font-weight: 700; letter-spacing: -.02em; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.tile span { font-size: 12.5px; color: var(--ink-3); }
.tile[data-warn="true"] b { color: var(--s-review); }

.charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; }
.chart {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 18px 20px 20px; box-shadow: var(--lift-1);
}
.chart h2 { margin: 0 0 3px; font-size: 14.5px; font-weight: 700; letter-spacing: -.005em; }
.chart-note { margin: 0 0 16px; font-size: 12px; color: var(--ink-3); line-height: 1.45; }

/* One hue per chart; the row label carries identity. */
.chart[data-hue="a"] .bar-fill { background: var(--c-a); }
.chart[data-hue="b"] .bar-fill { background: var(--c-b); }
.chart[data-hue="c"] .bar-fill { background: var(--c-c); }

.bars { display: flex; flex-direction: column; gap: 2px; }
.bar-row {
  display: grid; grid-template-columns: 88px minmax(0,1fr) 34px;
  align-items: center; gap: 10px; padding: 4px 0;
}
.bar-name {
  font-size: 12.5px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bar-track {
  height: 14px; background: var(--sunken); border-radius: 3px; overflow: hidden; display: block;
}
/* Thin mark, rounded at the data end only, anchored to the baseline. */
.bar-fill {
  display: block; height: 100%; min-width: 3px;
  border-radius: 3px 4px 4px 3px;
  transition: width .25s ease;
}
.bar-value {
  font-size: 12.5px; font-weight: 600; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--ink);
}

.scrollx { overflow-x: auto; }
.reporttable { width: 100%; border-collapse: collapse; font-size: 13px; }
.reporttable th {
  text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 0 14px 10px 0; border-bottom: 1px solid var(--line); white-space: nowrap;
}
.reporttable th + th, .reporttable td + td { text-align: right; }
.reporttable td {
  padding: 11px 14px 11px 0; border-bottom: 1px solid var(--line);
  font-variant-numeric: tabular-nums; color: var(--ink-2);
}
.reporttable td:first-child {
  color: var(--ink); font-weight: 600;
  display: flex; align-items: center; gap: 9px;
}
.reporttable tr:last-child td { border-bottom: 0; }
.suspended {
  font-style: normal; font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--s-change); background: var(--s-change-bg);
  padding: 2px 7px; border-radius: 999px;
}

/* ---------- report header ----------
   The month is the subject of the page, so it takes the heading weight and
   size; "Team report" becomes a small quiet label above it. */
.reports .page-kicker {
  margin: 0 0 2px;
  font-size: 13px;      /* half of the 26px this used to be */
  font-weight: 400;     /* down from 700 */
  color: var(--ink-3);
  letter-spacing: .01em;
}
.reports .page-month {
  margin: 0;
  font-size: 26px; font-weight: 700; letter-spacing: -.02em; line-height: 1.15;
  color: var(--brand-orange);
}/* per-person report */
.personhead { display: flex; align-items: center; gap: 14px; }
.personhead .avatar { width: 46px; height: 46px; font-size: 15px; }
.personlink {
  display: inline-flex; align-items: center; gap: 9px;
  border-bottom: 1px solid transparent; transition: border-color .13s ease, color .13s ease;
}
.personlink:hover { color: var(--indigo); border-bottom-color: currentColor; }
.reporttable td:first-child { gap: 10px; }

/* .settings is a flex column, so an inline-flex child still stretches. */
.reports .back { align-self: flex-start; }

/* ---------- the gear menu ----------
   One menu instead of four header controls. Shares the bell's scrim and
   panel geometry so the two dropdowns behave identically. */
.menu-wrap { position: relative; }
.menu-panel {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 60; min-width: 236px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--lift-3); padding: 6px; overflow: hidden;
}
.menu-panel a,
.menu-panel button {
  width: 100%; display: flex; align-items: center; gap: 11px;
  padding: 11px 13px; border: 0; background: none; border-radius: var(--r-sm);
  font-size: 13.5px; font-weight: 500; color: var(--ink); text-align: left; cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.menu-panel a:hover,
.menu-panel button:hover { background: var(--hover); }
.menu-panel .mi { display: inline-grid; place-items: center; color: var(--ink-3); flex: none; }
.menu-panel a:hover .mi,
.menu-panel button:hover .mi { color: var(--indigo); }

/* ---------- the signed-in person ----------
   A navy chip so the header reads as one identity block rather than an
   avatar floating beside loose text. The circle is a lighter navy so it
   separates from the chip without leaving the same color family. */
.me {
  background: var(--brand-navy);
  border-radius: 999px;
  padding: 5px 16px 5px 6px;
  gap: 9px;
}
.me .avatar {
  background: #2C4C8F;   /* lighter than the chip, same navy family */
  color: #fff;
  width: 30px; height: 30px; font-size: 11.5px;
}
.me-text b { color: #fff; }
.me-text i { color: rgba(255, 255, 255, .72); }

/* ==========================================================================
   Folders-added donut  (team report)
   Slices reuse the board's status colors, so the ring needs no color
   vocabulary of its own. Gaps between slices are the surface showing
   through, which keeps adjacent slices legible without an outline.
   ========================================================================== */

.donut-wrap {
  display: grid; grid-template-columns: minmax(180px, 240px) 1fr;
  gap: 26px; align-items: center;
}
.donut-fig { display: flex; justify-content: center; }
.donut { width: 100%; max-width: 230px; height: auto; overflow: visible; }

.donut-track { fill: none; stroke: var(--sunken); }
.donut-slice {
  fill: none; stroke-linecap: butt;
  transition: opacity .12s ease;
}
.donut:hover .donut-slice { opacity: .45; }
.donut .donut-slice:hover { opacity: 1; }

.donut-slice.new              { stroke: var(--s-new); }
.donut-slice.in_progress      { stroke: var(--s-prog); }
.donut-slice.ready_for_review { stroke: var(--s-review); }
.donut-slice.changes_required { stroke: var(--s-change); }
.donut-slice.approved         { stroke: var(--s-appr); }

/* The hero number sits in the hole — it is the headline this panel exists
   to give, so it wears ink tokens rather than any series color. */
.donut-total {
  text-anchor: middle; fill: var(--ink);
  font-size: 34px; font-weight: 800; letter-spacing: -.02em;
}
.donut-sub {
  text-anchor: middle; fill: var(--ink-3);
  font-size: 11.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
}

.donut-side { display: flex; flex-direction: column; gap: 18px; min-width: 0; }

.donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.donut-legend li {
  display: grid; grid-template-columns: 10px 1fr auto 44px;
  align-items: center; gap: 10px;
  padding: 7px 8px; border-radius: var(--r-sm); font-size: 13.5px;
}
.donut-legend li:hover { background: var(--hover); }
.donut-legend .dl-name { color: var(--ink-2); font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.donut-legend b { font-size: 14.5px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.donut-legend em { font-style: normal; font-size: 12.5px; color: var(--ink-3);
  text-align: right; font-variant-numeric: tabular-nums; }

.donut-sw { width: 10px; height: 10px; border-radius: 3px; }
.donut-sw.new              { background: var(--s-new); }
.donut-sw.in_progress      { background: var(--s-prog); }
.donut-sw.ready_for_review { background: var(--s-review); }
.donut-sw.changes_required { background: var(--s-change); }
.donut-sw.approved         { background: var(--s-appr); }

/* Ownership: one 100% bar, because the question is only ever "how much of
   the month has an owner". */
.ownsplit { border-top: 1px solid var(--line); padding-top: 16px; }
.own-head {
  margin: 0 0 9px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3);
}
.ownbar { display: flex; gap: 2px; height: 14px; border-radius: 4px; overflow: hidden; background: var(--sunken); }
.own { display: block; height: 100%; }
.own.has  { background: var(--s-prog); border-radius: 4px 0 0 4px; }
.own.none { background: var(--line-2); border-radius: 0 4px 4px 0; }

.own-note { margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 6px 20px; font-size: 13px; color: var(--ink-2); }
.own-note span { display: inline-flex; align-items: center; gap: 7px; }
.own-note b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.own-note em { font-style: normal; color: var(--ink-3); font-size: 12.5px; }
.own-key { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.own-key.has  { background: var(--s-prog); }
.own-key.none { background: var(--line-2); }

@media (max-width: 720px) {
  .donut-wrap { grid-template-columns: 1fr; gap: 18px; }
}

/* ==========================================================================
   Comment attachments
   ========================================================================== */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Screenshots already posted, under the comment they belong to. */
.shots { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 0; padding: 0; }
.shots li { margin: 0; }
.shots a {
  display: block; border: 1px solid var(--line-2); border-radius: var(--r-sm);
  overflow: hidden; background: var(--sunken); line-height: 0;
  transition: border-color .13s ease, box-shadow .13s ease;
}
.shots a:hover { border-color: var(--indigo); box-shadow: var(--lift-2); }
/* Height-constrained so a tall screenshot can't push the next comment off the
   page; the width follows the image's own aspect ratio. */
.shots img { height: 132px; width: auto; max-width: 320px; object-fit: cover; display: block; }

/* Remove a screenshot. Sits on the image rather than beside it so a row of
   thumbnails does not grow a column of buttons, and stays faint until the
   thumbnail is hovered — it is a correction, not a primary action. */
.shots li { position: relative; }
.shotdel { position: absolute; top: 5px; right: 5px; margin: 0; line-height: 0; }
.shotdel button {
  width: 24px; height: 24px; border-radius: 50%; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--s-change) 45%, transparent);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  color: var(--s-change); font-size: 15px; line-height: 1; font-weight: 700;
  display: grid; place-items: center; padding: 0;
  opacity: 0; transition: opacity .13s ease, background .13s ease, transform .12s ease;
}
.shots li:hover .shotdel button,
.shotdel button:focus-visible { opacity: 1; }
.shotdel button:hover { background: var(--s-change); color: var(--on-accent); }
.shotdel button:active { transform: scale(.92); }

/* A touch screen has no hover, so there it is simply always visible. */
@media (hover: none) {
  .shotdel button { opacity: 1; }
}

/* The strip of files chosen but not yet posted. */
.shotstrip { list-style: none; display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 0; padding: 0; }
.shotstrip li { position: relative; width: 104px; margin: 0; }
.shotstrip img {
  width: 104px; height: 74px; object-fit: cover; display: block;
  border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--sunken);
}
.shotstrip .shotname {
  display: block; margin-top: 4px; font-size: 10.5px; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shotx {
  position: absolute; top: -6px; right: -6px; width: 20px; height: 20px;
  border-radius: 50%; border: 1px solid var(--line-2); background: var(--surface);
  color: var(--ink-2); font-size: 14px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--lift-2); transition: background .13s ease, color .13s ease;
}
.shotx:hover { background: var(--s-change); border-color: var(--s-change); color: #fff; }

.btn.attachbtn { display: inline-flex; align-items: center; gap: 7px; }
.btn.attachbtn:disabled { opacity: .5; cursor: not-allowed; }

.composer .err { margin: 10px 0 0; }

/* ==========================================================================
   Job page pieces the PHP templates introduce
   ========================================================================== */

.jobtools { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 22px; }

.threadhead {
  margin: 0 0 12px; font-size: 12px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3);
}

/* Label above value: a sidebar column is too narrow for two columns without
   the values wrapping into each other. */
.details { margin: 0; }
.details dt {
  font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3); margin-top: 12px;
}
.details dt:first-child { margin-top: 0; }
.details dd { margin: 2px 0 0; font-size: 13.5px; color: var(--ink); overflow-wrap: anywhere; }
.details dd code { font-size: 11.5px; color: var(--ink-2); }

.assignedto { display: flex; align-items: center; gap: 9px; margin: 0 0 12px; font-size: 14px; }
.assignedto i { color: var(--ink-3); font-style: normal; }

.assignform { display: flex; gap: 8px; align-items: center; }
.assignform select { flex: 1; min-width: 0; }

/* An inline edit form, revealed by its own <details>. */
.editwrap { display: inline-block; }
.editwrap > summary { list-style: none; cursor: pointer; display: inline-flex; }
.editwrap > summary::-webkit-details-marker { display: none; }
.editform {
  position: relative; z-index: 2; margin-top: 8px; padding: 14px;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-md); box-shadow: var(--lift-2); min-width: 260px;
}

.ok {
  margin: 0 0 14px; padding: 10px 13px; border-radius: var(--r-sm);
  background: var(--s-appr-bg); color: var(--s-appr); font-size: 13.5px; font-weight: 600;
}

.sectionnote { padding: 14px 18px 0; margin: 0; }
.daychips { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 18px; }

/* ==========================================================================
   Narrow screens
   --------------------------------------------------------------------------
   The header is one flex row of fixed-width pieces — a 181px logo, a 260px
   search box, an avatar with a name, a button and a menu — which comes to
   roughly 830px before anything can give. Below that it does not wrap, it
   simply pushes the page sideways, so the whole app scrolls horizontally on
   a phone and the gear menu ends up off-screen.

   Three steps down: first the search shrinks, then the person's name and the
   search go entirely, leaving logo, bell, button and menu. Nothing that can
   only be reached from the header is ever dropped.
   ========================================================================== */

@media (max-width: 1100px) {
  .searchbox { width: 190px; }
  .searchbox:focus { width: 220px; }
}

@media (max-width: 900px) {
  .topbar { gap: 9px; padding: 0 14px; }
  .brandmark { height: 38px; }
  .searchbox { width: 150px; }
  /* Growing on focus would push the row wide again at exactly the width
     where there is no room to grow into. */
  .searchbox:focus { width: 150px; }
  .me-text { display: none; }
  .myday { padding: 9px 14px; }
}

@media (max-width: 700px) {
  /* The search has its own page-level filters; the header copy is the first
     thing that can go without losing a route. */
  .searchform { display: none; }
  .brandmark { height: 34px; }
  .topbar { height: 62px; }
}

@media (max-width: 480px) {
  .topbar { padding: 0 10px; gap: 7px; }
  .myday { font-size: 12px; padding: 8px 11px; }
  .bell{ width: 34px; height: 34px; }
  /* The avatar was the last thing pushing the gear button off the right edge
     on a phone — body overflow-x:hidden was quietly clipping it rather than
     showing a scrollbar. Who you are signed in as is in that menu anyway, and
     dropping a route to keep an avatar would be the wrong trade. */
  .me { display: none; }
}

/* 320px — an SE-sized phone. The wordmark is the widest fixed thing left, so
   it gives up height (and with it width) before any control does. */
@media (max-width: 380px) {
  .topbar { padding: 0 8px; gap: 6px; }
  .brandmark { height: 28px; }
  .myday { font-size: 11.5px; padding: 7px 9px; }
}

/* The menu panel is anchored to the right of a button that is itself near the
   right edge; on a narrow screen its own width is what overflows. */
@media (max-width: 560px) {
  .menu-panel { width: min(240px, calc(100vw - 20px)); right: 0; }
}

/* Below the sidebar breakpoint the normal document scroll comes back, so
   these panes must not keep a fixed height. */
@media (max-width: 880px) {
  .job { padding: 20px 16px 60px; }
  .board { padding: 16px 14px 60px; }
  .settings, .notif-page, .reports { padding: 20px 16px 60px; }
  .sidebar { padding: 14px 12px 16px; }

  /* A row is a six-column grid at desktop width; stacked it stays readable
     rather than crushing the folder name to three characters. */
  .row { grid-template-columns: 1fr auto; gap: 6px 10px; padding: 12px 14px; }
  .row .who { grid-column: 1 / -1; grid-row: 2; }
  .row .chip { grid-column: 1; grid-row: 3; justify-self: start; }
  .row .assignee { grid-column: 2; grid-row: 3; justify-self: end; }
  .row .count { grid-column: 2; grid-row: 1; justify-self: end; }

  /* Not one column: the rule at 900px puts every child after the dot into
     column 2, and asking for a single column here left that pin dangling —
     the browser made a phantom column and the row emptied out to the right.
     Keeping the 8px gutter is also simply better; the dot stays beside the
     text instead of sitting alone on its own line. */
  .handout-row { gap: 8px 12px; }
  .handout-acts { justify-content: flex-start; }
  .teamrow { grid-template-columns: auto 1fr; gap: 10px; }
  .teamrole, .teamjobs, .statuslamp, .teamacts { grid-column: 2; }
}

/* A table cannot reflow, so it scrolls inside its own box rather than taking
   the page with it. */
.scrollx { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.scrollx > table { min-width: 640px; }

/* Nothing may push the document sideways: a horizontal scrollbar on the whole
   page is always a bug, never a layout. */
html, body { max-width: 100%; overflow-x: hidden; }

/* ---------- manual sync controls (Settings → Dropbox) ---------- */

.syncrow { display: flex; flex-wrap: wrap; gap: 9px; margin: 14px 0 0; }
.syncrow form { display: contents; }

.syncpick { margin-top: 10px; }
.syncpick > summary { list-style: none; cursor: pointer; display: inline-flex; }
.syncpick > summary::-webkit-details-marker { display: none; }
.syncpick-body {
  display: flex; flex-wrap: wrap; gap: 22px; margin-top: 14px; padding: 16px;
  background: var(--sunken); border: 1px solid var(--line); border-radius: var(--r-md);
}
.syncform { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.syncform .field { margin-bottom: 0; }
.syncform .row2 { display: flex; gap: 8px; }

/* Sync controls on the board.
   They live inside the filter row itself, at the right-hand end. .scope
   already carries margin-left:auto, so everything from the month label
   rightwards is pushed away from the chips as one group. */
/* margin-left:auto so that when the row is too narrow to hold everything and
   these wrap to a second line, they wrap to the RIGHT rather than jumping
   under the chips and looking like a stray toolbar. */
.filtersync { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }

/* The month label takes a line of its own beneath the buttons, right-aligned.
   Given a full-width basis it always wraps, so the layout is identical on a
   laptop and on a wide monitor rather than rearranging itself at some width
   nobody can predict. */
.filters .scope {
  /* justify-content, not text-align: .scope is itself an inline-flex box, so
     text-align has nothing to act on. */
  margin-left: 0; flex-basis: 100%; margin-top: 2px; justify-content: flex-end;
}

/* Sized from .filter rather than .btn: these sit on the same line as
   All / New / In Progress and any difference in height or radius reads as a
   mistake. No line-height of its own either — .filter inherits the body's,
   and setting one here made them four pixels shorter than the chips. */
.filtersync .btn {
  font-size: 12.5px; font-weight: 500;
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink-2);
  gap: 8px; white-space: nowrap;
}
.filtersync .btn:hover {
  border-color: var(--ink-3); color: var(--ink); background: var(--surface);
}
.filtersync .btn.primary {
  background: var(--indigo); border-color: var(--indigo); color: var(--on-accent);
}
.filtersync .btn.primary:hover { filter: brightness(1.07); }

/* The picker drops as a panel rather than growing the row: opening a
   <details> inline would shove the whole board down by 150px. */
.filtersync .syncpick { position: relative; margin: 0; }
.filtersync .syncpick-body {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 30;
  margin: 0; text-align: left; min-width: 330px;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-md); box-shadow: var(--lift-3); padding: 16px;
}

/* The result of a sync gets its own line — far too long for a button row. */
.boardsync { padding: 6px 26px 0; margin: 0; }
.syncnote {
  margin: 0; font-size: 12.5px; padding: 6px 13px;
  border-radius: 999px; display: inline-block;
}

@media (max-width: 880px) {
  .boardsync { padding: 6px 14px 0; }
  /* Below this width the filter row already wraps, so let the panel use the
     full width rather than hanging off the right edge. */
  .filtersync .syncpick-body { right: auto; left: 0; min-width: 0; width: min(330px, 88vw); }
}

/* ==========================================================================
   Sign in — one centred column
   --------------------------------------------------------------------------
   Replaces the earlier two-panel split. Everything below overrides the
   .signin rules further up the file by coming after them, so those can stay
   where they are for reference.
   ========================================================================== */

.signin {
  display: block; grid-template-columns: none;
  min-height: 100dvh; padding: 56px 20px 72px;
  background:
    radial-gradient(120% 90% at 50% 0%, #14356e 0%, transparent 60%),
    linear-gradient(180deg, #0b2a60 0%, #071d47 100%);
  background-color: #0b2a60;
  overflow-y: auto;
}

.signin-stack { width: min(100%, 760px); margin: 0 auto; }

/* ---------- the pitch above the card ---------- */

.signin-head { text-align: center; margin-bottom: 34px; }

/* The white lockup: TVN and MANAGEMENT white, INVENTORY and the V left
   orange. Matches TVN_Inventory_white.svg exactly without shipping a second
   copy of the artwork. */
.signin-head .brandmark {
  --logo-ink: #fff;
  height: 70px; width: auto; margin: 0 auto 30px; display: block;
}

.signin-head h1 {
  margin: 0 0 16px; color: #fff;
  font-size: clamp(30px, 4.4vw, 46px); font-weight: 700;
  font-stretch: 108%; letter-spacing: -.02em; line-height: 1.12; text-wrap: balance;
}
.signin-head p {
  margin: 0 auto; max-width: 620px;
  font-size: 15.5px; line-height: 1.62; color: rgba(255, 255, 255, .78);
  text-wrap: pretty;
}

/* ---------- the card ---------- */

.signin-card {
  /* max-width:none because the two-panel version capped this at 448px and
     that rule is still live further up the file. */
  width: 100%; max-width: none; border: 0;
  background: #fff; border-radius: 26px;
  padding: 56px 40px 46px;
  box-shadow: 0 26px 70px rgba(0, 0, 0, .28);
  text-align: center;
}
.signin-card h2 {
  margin: 0 0 10px; color: #2a2416;
  font-size: clamp(30px, 3.6vw, 42px); font-weight: 700;
  font-stretch: 106%; letter-spacing: -.025em;
}
.signin-card .signin-sub {
  margin: 0 0 30px; font-size: 15px; color: var(--ink-2);
}

/* The fields are narrower than the card, as in the design. */
.signin-form {
  display: flex; flex-direction: column; align-items: stretch;
  width: min(100%, 470px); margin: 0 auto; gap: 14px;
}

.signin-form > input[type="email"],
.signin-form > input[type="password"] {
  width: 100%; box-sizing: border-box;
  background: #e9eefb; border: 1px solid transparent; border-radius: 999px;
  padding: 16px 24px; font-size: 15px;
  /* Literal, NOT var(--ink). The field's background is a hard-coded light blue
     and does not follow the theme, so the text on it must not either: in dark
     mode --ink is #EEF0F5 and what you typed was white on white. Reported
     17 Sep 2026 — the placeholder stayed visible because it is literal too,
     which made it look like the field was simply not accepting input. */
  color: #1A1D26; caret-color: #1A1D26;
  transition: border-color .14s ease, background .14s ease;
}
.signin-form > input::placeholder { color: #9aa3b8; }
.signin-form > input:focus {
  outline: none; background: #fff; border-color: var(--indigo);
}

/* ---------- login ---------- */

.signin-form > .btn.primary {
  width: min(100%, 300px); margin: 22px auto 0; justify-content: center;
  background: var(--brand-orange); border-color: var(--brand-orange); color: #fff;
  border-radius: 999px; padding: 16px 24px;
  font-size: 15px; font-weight: 700; letter-spacing: .1em;
}
.signin-form > .btn.primary:hover { filter: brightness(1.06); background: var(--brand-orange); }

.signin-form .forgot {
  margin: 18px 0 0; text-align: center; font-size: 12.5px; color: var(--ink-2);
}
.signin-form .err { margin: 4px 0 0; text-align: center; }

@media (max-width: 560px) {
  .signin { padding: 34px 16px 48px; }
  .signin-card { padding: 34px 22px 30px; border-radius: 20px; }
}

/* ------------------------------------------------ enquiries from the sheet */

/* ---------------------------------------------- marketing: the bid sheet */

/* The left menu. Plain links rather than the board's collapsing tree — five
   fixed destinations do not need disclosure. */
.sidenav { display: flex; flex-direction: column; gap: 8px; }

.quote-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- collapsible group panels on the templates page ---------- */

/* A <details> wearing .panel, so the bar is the summary and everything else is
   the body. No JavaScript: it opens, closes and takes keyboard focus on its
   own, and works before app.js has loaded. */
.gpanel { padding: 0; overflow: hidden; }
.gbar {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 12px 18px; background: var(--bar); color: var(--on-bar);
  font-size: 14px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  list-style: none;
}
.gbar::-webkit-details-marker { display: none; }
.gbar::before { content:""; width:8px; height:8px; border-radius:50%; background: var(--on-bar-dot); flex:none; }
.gbar-name { flex: none; }
/* The count and the chevron sit at the far right, leaving the name alone on
   the left — the bar then reads as a title with its tally, rather than three
   things crowded into one corner. */
.gbar-count {
  margin-left: auto;
  font-size: 12px; font-weight: 600; letter-spacing: .04em;
  color: color-mix(in srgb, var(--on-bar) 66%, transparent);
}
.gbar .btn.tiny { margin-left: 4px; }
.gbar .btn {
  background: color-mix(in srgb, #fff 14%, transparent);
  border-color: color-mix(in srgb, #fff 30%, transparent); color: var(--on-bar);
  text-transform: none; letter-spacing: 0;
}
.gbar .btn:hover { background: color-mix(in srgb, #fff 26%, transparent); color: var(--on-bar); }
.gchev { flex: none; transition: transform .15s ease; opacity: .85; }
.gpanel[open] > .gbar .gchev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .gchev { transition: none; } }

.gbody { padding: 4px 18px 18px; }

/* A small button for a table row or a bar — the full-size one makes a row
   twice as tall as it needs to be. */
.btn.tiny { padding: 5px 11px; font-size: 12px; }
/* An available destructive action. It wears its color rather than going grey,
   because grey-on-transparent is what a DISABLED control looks like and this
   one is not — it just happens to be the one you press least often. */
.btn.del {
  background: transparent;
  /* The BORDER keeps --s-change: a hairline is not text and the softer red is
     what makes this read as the button you press least. The LABEL moves to
     --neg, because --s-change on white measured 4.04:1 on /vendors. */
  border-color: color-mix(in srgb, var(--s-change) 45%, transparent);
  color: var(--neg); font-weight: 600;
}
.btn.del:hover { background: var(--neg); border-color: var(--neg); color: var(--on-accent); }
.rowacts { display: inline-flex; gap: 6px; align-items: center; justify-content: flex-end; }
.rowacts form { display: inline; margin: 0; }
/* The row whose editor is open below it. */
.savedq tr[data-editing="true"] > td { background: var(--indigo-soft); }

/* ---------- the templates a quote can start from ---------- */

/* A row of them rather than a dropdown: there will be a handful, and a
   dropdown hides which one you are on — which is the only thing this control
   has to say. */
/* The decoration row: the template pills, and Additional Decoration pushed to
   the far end of the same line. Sits under the Decoration heading, which now
   carries the word, so the pills need no label of their own. */
.qdecobar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 20px;
}

/* Black, and the only black control on the page: it is the one button here
   that changes the shape of the quote rather than its numbers. Pushed right so
   it reads as separate from the pills, which are a choice of one. */
.deco-more {
  margin-left: auto; background: #14161c; border-color: #14161c; color: #fff;
  font-weight: 700; font-size: 13px;
}
.deco-more:hover { background: #000; border-color: #000; color: #fff; }
/* Squared off to --r-sm, the same corner the Save Quotation button uses, so
   the decoration row reads as buttons of this page rather than as pills from
   somewhere else. Ink rather than ink-2: these are a choice to be made, not a
   caption, and the muted grey read as already-disabled. */
.qtpl {
  padding: 7px 13px; border-radius: var(--r-sm); font-size: 14px; font-weight: 600;
  color: var(--ink); background: var(--surface); border: 1px solid var(--line-2);
}
/* These are submit buttons now, not links — clicking one posts the page so
   the customer and products already filled in survive the change of
   decoration. A button brings its own font, line-height and cursor, so all
   three are reset to what the links looked like. */
button.qtpl { font-family: inherit; line-height: 1.35; cursor: pointer; -webkit-appearance: none; appearance: none; }
.qtpl:hover { background: var(--hover); color: var(--ink); }
.qtpl[data-active="true"] { background: var(--indigo); border-color: var(--indigo); color: #fff; }


/* Stitch count and quantity, under the four placements. Two fields and the
   answer they produce, on one line — the answer beside the controls that
   chose it, because the sheet below shows it too but not until you have found
   the right row. */
/* Each quotation on the page is a block: product, decorations, sheet. The
   first sits plain; the ones after it are ruled off and titled, so a page of
   three reads as three quotations rather than one long form. */
.qblock + .qblock { margin-top: 28px; padding-top: 4px; border-top: 2px solid var(--line-2); }
.qblock-bar {
  display: flex; align-items: center; gap: 12px; margin: 18px 0 6px;
  font-size: 13px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-2);
}
.qblock-bar .btn { margin-left: auto; text-transform: none; letter-spacing: 0; }
/* Centred, because it belongs to the page rather than to the quotation above
   it — it is where you go when this one is finished, not part of it. */
.quote-add { margin-top: 24px; display: flex; justify-content: center; }
.quote-add .btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 22px; font-size: 14px; font-weight: 600;
  background: var(--brand-orange); border-color: var(--brand-orange); color: #fff;
}
.quote-add .btn:hover {
  background: var(--brand-orange); border-color: var(--brand-orange);
  color: #fff; filter: brightness(1.07);
}

.qdecos { display: flex; flex-direction: column; gap: 12px; margin-bottom: 22px; }
.qstitch {
  display: grid; align-items: end; gap: 14px;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, .8fr) minmax(0, 1.6fr) auto;
}
/* Every block after the first sits in its own well, so a quote carrying two
   decorations reads as two things rather than a longer form. */
.qdecos .qstitch + .qstitch {
  padding: 14px; background: var(--sunken); border-radius: var(--r-md);
}
.q-ddel { align-self: end; margin-bottom: 2px; }
@media (max-width: 1000px) { .qstitch { grid-template-columns: minmax(0,1fr) minmax(0,1fr); } }
@media (max-width: 560px)  { .qstitch { grid-template-columns: minmax(0,1fr); } }
.qstitch .qfield input, .qstitch .qfield select {
  width: 100%; background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 12px 14px;
  font-size: 15px; font-weight: 600; color: var(--ink);
}
.qstitch .qfield select { cursor: pointer; }
.qstitch .qfield input:focus, .qstitch .qfield select:focus { border-color: var(--indigo); outline: none; }

.qstitch-out {
  margin: 0; padding: 12px 14px; border-radius: var(--r-md);
  font-size: 13.5px; color: var(--ink-2); min-height: 1px;
}
.qstitch-out[data-found="true"] { background: var(--indigo-soft); }
.qstitch-out[data-found="true"] b {
  color: var(--indigo); font-size: 16px; font-variant-numeric: tabular-nums;
}
/* Nothing to charge for that quantity is a warning, not a result. */
.qstitch-out[data-found="false"]:not(:empty) {
  background: var(--s-change-bg); color: var(--s-change); font-weight: 600;
}

/* A single field on its own row rather than half of a two-column grid. */
.qcustomer.one { grid-template-columns: minmax(0, 1fr); }

/* ---------- the price grid: the spreadsheet, as it is priced ---------- */

/* Nine columns and eighteen rows, so the cells are tighter than the quote
   sheet's and the row names hold their width while the prices share what is
   left. It scrolls inside its own box rather than taking the page sideways. */
table.quote.pricegrid { min-width: 940px; }
table.quote.pricegrid .qlabel { min-width: 150px; width: 150px; }
table.quote.pricegrid .qnum { width: auto; min-width: 82px; }
table.quote.pricegrid td input { padding: 9px 8px; font-size: 13px; }

/* The headings and the stitch counts are the two things you read the grid BY,
   so they are black and bold on the same white as the prices. The grey strip
   the rest of the app uses for headings made this one look like a report you
   cannot touch. */
table.quote.pricegrid thead th {
  font-weight: 700; font-size: 11px; letter-spacing: .03em;
  padding: 12px 8px; text-align: center;
}
table.quote.pricegrid thead th.qlabel { text-align: left; padding-left: 12px; }
table.quote.pricegrid td.qlabel input,
table.quote.pricegrid td.qlabel .gcell { font-weight: 700; color: var(--ink); }

/* A row that is not being edited is text, not a field — the grid reads as a
   price list until Edit turns one row into boxes. */
.gcell {
  display: block; padding: 9px 8px; font-size: 13px; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
td.qnum .gcell { text-align: center; }
/* Scoped to the grid and specific enough to beat the per-column background
   rules below — without that the open row tinted its price cells and left the
   name and the buttons white, so half a row looked open and half did not. */
table.quote.pricegrid tr[data-editing="true"] > td { background: var(--indigo-soft); }
table.quote.pricegrid tr[data-editing="true"] td input {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
}
table.quote.pricegrid tr[data-editing="true"] td input:focus { border-color: var(--indigo); outline: none; }

/* The buttons column is furniture, not data. */
table.quote.pricegrid .qacts {
  width: 1%; white-space: nowrap; padding: 6px 10px; background: var(--surface);
}
table.quote.pricegrid tr.gridnew td input::placeholder { color: var(--ink-3); }
.gridadd { margin: 14px 0 0; }

/* The bid template under each bar. Sunk into its own well so it reads as a
   second thing under the price grid rather than more of the same table. */
.tplsheet {
  margin-top: 20px; padding: 16px 18px 18px;
  background: var(--sunken); border-radius: var(--r-md); border: 1px solid var(--line-2);
}
.tplsheet .qhead:first-of-type { margin-top: 0; display: flex; align-items: baseline; gap: 10px; }
.tplsheet .sheetnote {
  font-style: normal; font-size: 11px; font-weight: 600; letter-spacing: 0;
  text-transform: none; color: var(--ink-3);
}
.tplsheet table.quote { background: var(--surface); }
.tplsheet .s-live { font-variant-numeric: tabular-nums; }
/* A row added just now, so it is findable in a sheet of eight. */
.tplsheet tr.sheetadded td { background: var(--indigo-soft); }
.tplsheet tr.sheetadded input::placeholder { color: var(--ink-3); }

/* What a vendor is priced for, as a row of small tags rather than a sentence
   — the count is the useful half and it should be findable at a glance. */
.svctag {
  display: inline-flex; align-items: baseline; gap: 5px; margin: 2px 5px 2px 0;
  padding: 3px 9px; border-radius: 999px; font-size: 11.5px;
  background: var(--sunken); color: var(--ink-2); white-space: nowrap;
}
.svctag b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* Add and remove sit on one line: the pair is the whole of vendor admin, and
   putting delete alone would be a door with no way back through it. */
.vendor-admin { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.vendor-admin form { margin: 0; }

/* The service-level controls sit in a row under the grid: add a row, drop the
   whole service, rename its columns. */
.vservice-del { display: inline-block; margin: 0 0 0 8px; }
.gridadd { display: inline-block; margin: 14px 0 0; }

/* The page's own "are you sure?", so the question cannot be switched off the
   way a browser dialog can. */
.askbox {
  border: 0; padding: 0; border-radius: var(--r-lg); max-width: 420px;
  background: var(--surface); color: var(--ink); box-shadow: var(--lift-2);
}
.askbox::backdrop { background: rgba(6, 33, 92, .45); }
.askbox form { padding: 22px 24px 18px; }
.ask-text { margin: 0 0 18px; font-size: 15px; line-height: 1.5; color: var(--ink); }
.ask-acts { display: flex; gap: 10px; justify-content: flex-end; }

/* A vendor's "More stitches?" line is a rule in words, not a price, so it is
   set apart from the numbers above it rather than pretending to be one. */
tr.vnote > td { background: var(--sunken); }
tr.vnote .gcell { font-size: 12px; font-weight: 600; color: var(--ink-2); white-space: normal; }
tr.vnote td.qlabel .gcell { font-weight: 700; }

/* Renaming the columns is a rare, wide-reaching edit, so it is folded away. */
.gcols { margin-top: 14px; }
.gcols > summary { display: inline-flex; list-style: none; cursor: pointer; }
.gcols > summary::-webkit-details-marker { display: none; }
.gcols-form {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-top: 12px; padding: 12px; background: var(--sunken); border-radius: var(--r-md);
}
.gcols-form input {
  flex: 1 1 120px; min-width: 0; background: var(--surface);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 8px 10px; font-size: 12.5px;
}
.gcols-form input:focus { border-color: var(--indigo); outline: none; }

.quote-page .qfield select {
  width: 100%; background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 12px 14px;
  font-size: 15px; font-weight: 600; color: var(--ink); cursor: pointer;
}

/* ---------- the customer a quote is filed under ---------- */

/* Two fields, side by side, above everything: the quote is saved under the
   name, so it is the first thing asked for rather than the last. */
.qcustomer {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px; margin-bottom: 22px;
}
@media (max-width: 640px) { .qcustomer { grid-template-columns: minmax(0, 1fr); } }
.qfield { display: flex; flex-direction: column; gap: 6px; min-width: 0; margin-bottom: 0; }
.qfield > span {
  font-size: 11.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3);
}
.qfield input {
  width: 100%; background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 12px 14px;
  font-size: 15px; font-weight: 600; color: var(--ink);
}
.qfield input:focus { border-color: var(--indigo); outline: none; }
/* :user-invalid, not :invalid — the plain one matches an untouched empty
   field, which painted the form red before anybody had typed a character.
   Where it is unsupported the field simply stays neutral, which is the
   behaviour we want anyway until somebody has had a go at it. */
.qfield input:user-invalid { border-color: var(--s-change); }

/* Repeated under the sheet: by the time the last cell is typed the button in
   the header is off the top of the screen. */
.quote-save {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 18px 0 0;
}
.qsaved-when { font-size: 12.5px; color: var(--ink-3); }
.quote-delete { margin-top: 14px; }

/* ---------- saved quotations ---------- */

.qmonth-sum { font-weight: 600; font-size: 12.5px; color: var(--ink-3); margin-left: 10px; }
table.savedq tbody tr { cursor: pointer; }
table.savedq tbody tr:hover td { background: var(--hover); }
table.savedq a { color: var(--ink); }
table.savedq tbody tr:hover a { color: var(--indigo); }
.qrow-email { display: block; font-style: normal; font-size: 12px; color: var(--ink-3); }
/* A product name is a model, a code and two trademarks — it must not be
   allowed to set the width of the whole table. */
.qrow-product { max-width: 340px; color: var(--ink-2); }
.qrow-when { white-space: nowrap; color: var(--ink-2); }
.qrow-when i { display: block; font-style: normal; font-size: 12px; color: var(--ink-3); }

/* ---------- the bid sheet head: product, then the four placements ---------- */

/* A ruled heading, the way the printed sheet heads its blocks. Not an <h3>:
   the panel already has one level of heading and these sit inside it. */
.qhead {
  margin: 0 0 12px; padding-bottom: 10px; font-size: 14px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink);
  border-bottom: 1px solid var(--line-2);
}
.qhead + .qhead, .qbig + .qhead, .qdeco + .qhead { margin-top: 26px; }

/* The product is one long line — a model name, a code, a trademark — so it
   gets the full width rather than sharing a row. */
.qbig {
  width: 100%; background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 14px 16px;
  font-size: 16px; font-weight: 700; color: var(--ink);
}
.qbig:focus { border-color: var(--indigo); outline: none; }

/* One row per product, each with its own blank-cost line in the sheet. */
.qproducts { display: flex; flex-direction: column; gap: 10px; margin-bottom: 22px; }
.qproduct { display: flex; align-items: center; gap: 10px; }
.qproduct .qbig { flex: 1; min-width: 0; }
.qproduct .q-pdel { flex: none; }
/* The button sits on the heading's rule, so the heading has to make room. */
.qhead { display: flex; align-items: center; gap: 12px; }
.qhead .btn.tiny { margin-left: auto; text-transform: none; letter-spacing: 0; font-weight: 600; }

.qdeco {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px; margin-bottom: 22px;
}
@media (max-width: 900px) { .qdeco { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .qdeco { grid-template-columns: minmax(0, 1fr); } }

/* ---------- the bid table ---------- */

.quote-scroll { overflow-x: auto; margin: 0 -18px; padding: 0 18px; }

table.quote { border-collapse: collapse; width: 100%; }
.quote-panel table.quote { min-width: 720px; }
table.quote th, table.quote td {
  border: 1px solid var(--line); padding: 0; vertical-align: middle;
}
/* Black, not grey: these are the names of the columns you type into, and a
   muted heading over an editable column reads as a caption on something
   fixed. Matches the price grid, which was already black. */
table.quote thead th {
  background: var(--surface); font-size: 11.5px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink);
  padding: 12px; text-align: left; white-space: nowrap;
  /* The heading row carries its own rule now that it shares the body's white
     — without it the column names float free of the table they name. */
  border-bottom: 2px solid var(--line-2);
}
/* Figures sit centred under their own heading; money stays right so the
   decimal points line up down the column. */
table.quote thead th.qnum { text-align: center; }
table.quote .qlabel { min-width: 220px; }
table.quote .qnum   { text-align: center; width: 132px; }

table.quote td input {
  width: 100%; border: 0; background: none; padding: 13px 12px;
  font-size: 14px; color: var(--ink); font-variant-numeric: tabular-nums;
}
table.quote td.qnum input { text-align: center; }
table.quote td input:focus { outline: 2px solid var(--indigo); outline-offset: -2px; }
/* The row names sit on white, like the cells beside them — the grey tint made
   a column of names you can actually type in look like a heading strip you
   cannot. Bold is what marks them as the name of their row. */
table.quote td.qlabel { background: var(--surface); }
table.quote td.qlabel input { font-weight: 700; }

/* Calculated cells are shaded so it is obvious which cells accept typing. */
table.quote td.out {
  padding: 13px 12px; background: var(--sunken); color: var(--ink);
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 14px;
  text-align: right;
}

table.quote tbody tr:hover td { background: var(--hover); }

table.quote tfoot th, table.quote tfoot td { background: var(--raised); }
table.quote tfoot .qtotal th, table.quote tfoot .qtotal td { font-weight: 700; font-size: 15px; }
table.quote tfoot th.qlabel {
  padding: 13px 12px; text-align: left; font-size: 15px; font-weight: 700;
  background: var(--raised);
}
table.quote .qmuted { padding: 13px 12px; color: var(--ink-3); font-size: 13px; text-align: right; }
table.quote tfoot td.out { background: var(--raised); }

/* The two highlights carried over from the sheet: the mark up marketing tune
   per job, and the discount. They are the cells people reach for first, and
   the discount is the louder of the two because it is the last thing changed
   before a quote goes out. */
input.accent-yellow { background: #FFF3B0 !important; color: #4A3B00 !important; font-weight: 700; }
input.accent-yellow.strong { background: #FFE600 !important; color: #3A3200 !important; }
/* The dark variant has to sit behind a media query as well as the attribute,
   or "not light" matches the unset (system) case and paints the cell dark on
   a light screen. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) input.accent-yellow { background: #6B5A12 !important; color: #FFF3B0 !important; }
  :root:not([data-theme="light"]) input.accent-yellow.strong { background: #9A8410 !important; color: #FFFBE0 !important; }
}
:root[data-theme="dark"] input.accent-yellow { background: #6B5A12 !important; color: #FFF3B0 !important; }
:root[data-theme="dark"] input.accent-yellow.strong { background: #9A8410 !important; color: #FFFBE0 !important; }

/* ---------- the three tables under it ---------- */

/* Cost of Goods, the mark up bands and the summary read as one answer — what
   it costs, what we charge over it, what that leaves — so they sit in one
   row. Cost of Goods is the widest because it is the only one with rows. */
/* One panel, three columns — not three panels. The three are one answer in
   three parts, and hairlines between them say that where a gap between boxes
   said the opposite. */
/* Three bands stacked down the panel, each reading left to right across the
   page. A narrow column made a nine-item cost table wrap every label; turned
   on its side the items are the columns, under the same headings the bid
   table uses. */
.lower-row { padding-top: 4px; }
.lower-row + .lower-row { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
/* The running total, held off from the items it sums. */
table.quote .qtot { border-left: 2px solid var(--line-2); }
table.quote.cogs th.qlabel, table.quote.bands th.qlabel {
  background: var(--sunken); font-weight: 700; font-size: 12px;
  text-align: left; padding: 10px 12px; white-space: nowrap;
}
table.quote.cogs thead th.qnum, table.quote.bands thead th.qnum { text-align: center; }
table.quote.cogs .q-coglabel {
  display: block; font-size: 11px; font-weight: 700; color: var(--ink);
  text-transform: none; letter-spacing: 0; line-height: 1.3;
}
table.quote.bands thead th input { text-align: center; font-weight: 700; }
/* The column's own name. Smaller than the panel's bar above it, because it
   names a part of that panel rather than a section of the page. */
.colhead {
  margin: 0 0 10px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--ink-3);
}

table.quote.cogs, table.quote.bands { min-width: 0; }
table.quote.cogs .qlabel, table.quote.bands .qlabel { min-width: 0; }
table.quote.cogs .qnum, table.quote.bands .qnum { width: auto; }
/* Mirrored from the bid rows, so it is text rather than a field — editing it
   here would be editing a copy. */
table.quote.cogs td.qlabel .q-coglabel {
  display: block; padding: 13px 12px; font-size: 13.5px; font-weight: 700;
  color: var(--ink);
  /* break-word, not anywhere: "anywhere" is happy to split Sewing/Heat Press
     as "Sewing/Hea | t Press", which is worse than the overflow it prevents.
     This breaks between words and at the slash, and only splits a word that
     genuinely cannot fit. */
  overflow-wrap: break-word;
}

/* Four figures across, not four rows down — the same left-to-right reading as
   the two tables above it. */
.quote-summary {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0; margin: 0; border: 1px solid var(--line); border-radius: var(--r-md);
  overflow: hidden;
}
.quote-summary > div { padding: 14px 16px; border-left: 1px solid var(--line); min-width: 0; }
.quote-summary > div:first-child { border-left: 0; }
.quote-summary dt { font-size: 12px; font-weight: 600; color: var(--ink-3); }
.quote-summary dd {
  margin: 4px 0 0; font-size: 22px; font-weight: 700; letter-spacing: -.02em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
@media (max-width: 760px) {
  .quote-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .quote-summary > div:nth-child(3) { border-left: 0; }
}

.soon-list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 10px; }
.soon-list li { font-size: 14px; line-height: 1.55; color: var(--ink-2); }
.soon-list b { color: var(--ink); }
.soon-list code {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--ink-2); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
  background: var(--sunken); padding: 2px 6px; border-radius: 4px;
}

/* Printing a quote should give the customer the numbers, not the furniture. */
@media print {
  .topbar, .sidebar, .quote-actions, .hint { display: none !important; }
  .shell-2col { display: block; }
  .quote-scroll { overflow: visible; }
  table.quote td input { border: 0; }
}

/* ------------------------------------------------ the departments menu */

/* A secondary pill beside "Today's work": same height and shape, but a plain
   surface so the orange button stays the one obvious call to action. */
.dept-wrap { position: relative; flex: none; }
.deptbtn {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 14px; border-radius: 999px;
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink-2);
  font-size: 13px; font-weight: 600; white-space: nowrap;
  transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.deptbtn::-webkit-details-marker { display: none; }
.deptbtn:hover { background: var(--hover); color: var(--ink); border-color: var(--ink-3); }
.deptbtn svg { transition: transform .16s ease; }
.dept-wrap[open] > .deptbtn { background: var(--hover); color: var(--ink); border-color: var(--ink-3); }
.dept-wrap[open] > .deptbtn svg { transform: rotate(180deg); }

.dept-panel { width: 244px; }
.dept-panel a { align-items: flex-start; gap: 11px; padding: 10px 13px; }
.depttext { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.depttext b { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.depttext i { font-style: normal; font-size: 11.5px; color: var(--ink-3); }
.dept-panel a:hover .depttext b { color: var(--indigo); }

/* On a phone the label goes and the chevron carries it, so the header still
   fits alongside the bell, the button and the gear. */
@media (max-width: 700px) {
  .deptbtn { padding: 9px 11px; font-size: 0; gap: 0; }
  .deptbtn svg { width: 14px; height: 14px; }
  .dept-panel { width: min(240px, calc(100vw - 20px)); }
}

/* ==========================================================================
   The dashboards

   One layout for every department: five tiles, two charts, two tables. The
   charts are hand-drawn SVG rather than a library, so there is nothing to
   download and nothing that can fail to draw. Everything reads from theme
   tokens, so dark mode follows without a second stylesheet.
   ========================================================================== */

.deptnav { padding: 16px 12px; }
.deptnav .sidenav { gap: 3px; }
/* One weight and one color for every entry, so an open Mockup List does not
   read as more important than My Report or Settings sitting under it. The
   active item is told apart by its filled pill, not by being heavier. */
/* Each section is a card, not a line in a list: at nine sections with three of
   them expandable, a flat list gives no edge to say where one section's
   children stop and the next section starts. */
/* 12.5px and 8px of vertical padding: two off the type and three off the box.
   The padding is cut by less than the type is, so the label keeps room to
   breathe rather than sitting on the border — a shorter row that reads tight
   is worse than the taller one it replaced. */
.navitem {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 13px; border-radius: 12px;
  font-size: 12.5px; font-weight: 550; color: var(--ink);
  background: var(--nav-card); border: 1px solid var(--nav-line);
  transition: background .13s ease, color .13s ease, border-color .13s ease;
}
.navitem:hover { background: var(--hover); color: var(--ink); }
.navitem[data-active="true"] {
  background: var(--nav-accent-bg); border-color: var(--nav-accent-line);
  color: var(--nav-accent);
}
.navitem[data-soon="true"] { color: var(--ink-3); cursor: default; }
.navitem[data-soon="true"]:hover { background: var(--nav-card); color: var(--ink-3); }

/* The icon sits in a tile of its own so the labels start at one x-position
   whatever shape the glyph is. */
.ni {
  display: inline-flex; flex: none; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 9px;
  background: var(--nav-tile); color: var(--ink-2);
}
.navitem:hover .ni { color: var(--ink); }
.navitem[data-active="true"] .ni { background: var(--nav-accent-tile); color: var(--nav-accent); }

/* A row that goes somewhere gets a > ; one that opens gets the ⌄ SVG it
   already carries. Drawn rather than marked up, so four render branches did
   not each need a copy of the same arrow. */
.navitem:not(summary)::after {
  content: ''; margin-left: auto; flex: none;
  width: 6px; height: 6px; transform: rotate(45deg);
  border-right: 2px solid var(--ink-3); border-top: 2px solid var(--ink-3);
  border-radius: 1px;
}
.navitem[data-active="true"]:not(summary)::after {
  border-color: var(--nav-accent);
}

.dash { padding: 24px 26px 70px; max-width: var(--page-max); }
.dash-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.dash-head h1 { font-size: 27px; letter-spacing: -.02em; margin: 0 0 4px; }
.dash-when {
  padding: 9px 14px; border-radius: 999px; font-size: 13px; font-weight: 600;
  color: var(--ink-2); background: var(--surface); border: 1px solid var(--line-2);
}

/* -------------------------------------------------------------- the tiles */

.tilerow {
  display: grid; gap: 14px; margin: 20px 0 16px;
  /* Five equal tiles, because the row is read as one line. auto-fit would
     reflow them 3-then-2 at awkward widths, which reads as two groups. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
}
/* The forms lists show four, not five. Set here rather than by counting
   children, so the row keeps equal columns either way. */
.tilerow[data-count="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* These three carry [data-count] only to match the specificity of the rule
   above. Written as plain `.tilerow` they are (0,1,0) against its (0,2,0), so
   every one of them lost, a four-tile row stayed four-across at 390px — 46px
   of content per tile — and "187,832" rendered as "187,8" on /sales and
   "1,326" lost its last digit on /reorder. The column counts were always
   right; only the specificity was wrong.

   The one-column step moved from 420px to 359px deliberately. Two tiles still
   fit a 360px phone with room to spare (133px of content each against the
   97px the widest number needs at the phone type size below), and seeing all
   four summary figures in one block beats scrolling past four full-width
   cards to reach the table underneath. */
@media (max-width: 1120px) { .tilerow[data-count] { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .tilerow[data-count] { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 359px)  { .tilerow[data-count] { grid-template-columns: minmax(0, 1fr); } }
.tile {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 16px 16px 14px;
}
.tile-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 11px; margin-bottom: 12px;
}
.tile-icon[data-tint="blue"]   { color: #2563eb; background: color-mix(in srgb, #2563eb 12%, transparent); }
.tile-icon[data-tint="green"]  { color: #16a34a; background: color-mix(in srgb, #16a34a 12%, transparent); }
.tile-icon[data-tint="violet"] { color: #7c3aed; background: color-mix(in srgb, #7c3aed 12%, transparent); }
.tile-icon[data-tint="orange"] { color: #ea580c; background: color-mix(in srgb, #ea580c 12%, transparent); }
.tile-icon[data-tint="teal"]   { color: #0d9488; background: color-mix(in srgb, #0d9488 12%, transparent); }
.tile-icon[data-tint="red"]    { color: #dc2626; background: color-mix(in srgb, #dc2626 12%, transparent); }
.tile-label { margin: 0; font-size: 13px; font-weight: 600; color: var(--ink-2); }
.tile-value { margin: 2px 0 6px; font-size: 31px; font-weight: 700; letter-spacing: -.02em; color: var(--ink); }
.tile-value.none { color: var(--ink-3); font-weight: 600; }
/* The tile's change figure. Both colors are literals and were fine while the
   only tiles carrying a delta sat on a light dashboard; putting one on the
   sales tiles showed #dc2626 reading 3.57:1 against the dark ground. The
   light values stay, and dark mode gets the lighter pair the table cells
   already use. */
.tile-delta { margin: 0; font-size: 12px; font-weight: 600; color: var(--pos); }
/* #dc2626 measured 4.50 against the tile — AA to two decimal places and under
   it to three. The table cells settled on #b91c1c for the same reason. */
.tile-delta[data-dir="down"] { color: var(--neg); }
.tile-delta[data-dir="flat"] { color: var(--ink-3); font-weight: 500; }
.tile-delta i { font-style: normal; font-weight: 500; color: var(--ink-3); display: block; }
.tile-note { margin: 0; font-size: 11.5px; color: var(--ink-3); }
.tile[data-empty="true"] { border-style: dashed; }

/* ------------------------------------------------------ charts and tables */

.chartrow { display: grid; gap: 14px; grid-template-columns: minmax(0,1fr) minmax(0,1fr); margin-bottom: 16px; }
@media (max-width: 1000px) { .chartrow { grid-template-columns: minmax(0,1fr); } }

.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); padding: 16px 18px 18px; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.card-head h2 { font-size: 14.5px; font-weight: 700; margin: 0; display: flex; align-items: center; gap: 8px; }
.card-head .ci { color: var(--indigo); font-size: 15px; }
.viewall { font-size: 12.5px; font-weight: 600; color: var(--indigo); }
.yearpick select, .card-head select {
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink);
  border-radius: 9px; padding: 6px 10px; font-size: 12.5px; font-weight: 600;
}
.card-figure { margin: 6px 0 12px; display: flex; align-items: baseline; gap: 9px; }
.card-figure b { font-size: 26px; font-weight: 700; letter-spacing: -.02em; }
.card-figure .unit { font-size: 12.5px; color: var(--ink-3); }
.legend { display: flex; align-items: center; gap: 12px; font-size: 12px; color: var(--ink-2); }
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 5px; vertical-align: -1px; }
.legend i[data-k="fresh"] { background: #2563eb; }
.legend i[data-k="back"]  { background: #16a34a; }

/* The chart scrolls inside itself on a narrow screen rather than pushing the
   page sideways. */
.chart-wrap { overflow-x: auto; }
.barchart { width: 100%; min-width: 460px; height: auto; display: block; }
.barchart .grid { stroke: var(--line); stroke-width: 1; }
.barchart .axis { fill: var(--ink-3); font-size: 11px; font-family: inherit; }
.barchart .bar { fill: color-mix(in srgb, #2563eb 28%, transparent); transition: fill .13s ease; }
.barchart .bar[data-now="true"] { fill: #2563eb; }
.barchart .bar[data-k="back"] { fill: #16a34a; }
.barchart .bar:hover { fill: #2563eb; }
.barchart .bar[data-k="back"]:hover { fill: #15803d; }

/* ---------- report tables that keep their headings ---------- */

/* Sticky has to stick to SOMETHING. The page body scrolls, but .scrollx sets
   overflow-x, and setting overflow on one axis makes the other a scroll box
   too — which leaves `position: sticky; top: 0` pinned to a box that never
   scrolls. So the table gets its own scroll box with a real height, and the
   headings pin to the top of that. Both axes scroll inside it. */
.tablewrap {
  max-height: calc(100vh - 260px);
  min-height: 240px;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: var(--r-md);
}
.tablewrap > table { min-width: 720px; }

/* A sortable heading is a link that fills its cell, so the whole heading is
   the target rather than the few pixels of the word. */
.dtable a.sortlink {
  display: block; color: inherit; font: inherit; text-decoration: none;
  white-space: nowrap;
}
.dtable a.sortlink:hover { color: var(--ink); text-decoration: underline; }
.dtable th[data-sorted="true"] { color: var(--ink); }
.dtable th[data-sorted="true"] a.sortlink { font-weight: 800; }

.dtable.sticky thead th {
  position: sticky; top: 0; z-index: 3;
  /* The header sits over rows as they pass under it, so it needs a solid fill —
     var(--sunken) is translucent in dark mode and the digits show through. */
  background: var(--surface);
  /* Rows pass UNDER this, so its edge has to be visible against a row, not
     merely present: --line is the same hairline the rows use between
     themselves, which made the frozen heading read as just another row. */
  box-shadow: inset 0 -1px 0 var(--line-2);
  border-radius: 0;
}
/* The first column stays put horizontally too: on a wide table the numbers are
   meaningless once the SKU has scrolled off the left. */
.dtable.sticky tbody td:first-child,
.dtable.sticky thead th:first-child {
  position: sticky; left: 0; z-index: 2;
  background: var(--surface);
  /* Same reason as the heading above: at 1440px the reorder table is 1271px
     wide in a 1116px scroller, so 155px of columns slide behind this cell
     and a --line hairline did not say that anything was passing it. */
  box-shadow: inset -1px 0 0 var(--line-2);
}
.dtable.sticky thead th:first-child { z-index: 4; }

/* ---------- the brand filter ---------- */
.brandfilters { align-items: center; margin-bottom: 2px; }
.brandlabel {
  font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3); margin-right: 2px;
}
.brandfilters .filter b { font-weight: 600; color: var(--ink-3); margin-left: 4px; }
.brandfilters .filter[data-active="true"] b { color: inherit; opacity: .8; }

/* The dashboard's cards borrow the report bars. Their fill color normally
   comes from a .chart[data-hue] ancestor, and inside a .card there is none —
   nesting a .chart would draw a second bordered panel inside the first. */
.card .bars { margin-top: 6px; }
.card .bars .bar-fill { background: var(--c-a); }

/* The "see all" link in a card header, opposite its title. */
.cardlink { font-size: 12.5px; font-weight: 600; color: var(--indigo); white-space: nowrap; }
.cardlink:hover { text-decoration: underline; }

/* The reorder controls sit on one line — they are read together, and stacked
   they push the numbers they drive below the fold. */
.reorder-controls { margin-bottom: 16px; }
.reorder-controls .rc-row {
  display: flex; flex-wrap: wrap; gap: 14px 18px; align-items: flex-end;
}
.reorder-controls .field { min-width: 140px; flex: 0 1 auto; margin: 0; }
.reorder-controls .field > span {
  display: block; font-size: 11.5px; font-weight: 600; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: .04em; margin-bottom: 5px;
}
.reorder-controls .field select,
.reorder-controls .field input { min-width: 140px; }
.reorder-controls .btn { margin-left: auto; }

/* The brand picker is the one control that is a list rather than a single
   value, so it gets height instead of a row of its own. */
.reorder-controls .rc-brands { margin-top: 14px; align-items: flex-end; }
.reorder-controls .field-brands { min-width: 280px; flex: 0 1 380px; }
.reorder-controls .field-brands select[multiple] {
  min-width: 230px; height: auto; padding: 6px 8px;
  font-size: 13px; line-height: 1.5;
}
.reorder-controls .field-brands select[multiple] option { padding: 3px 4px; }
.reorder-controls kbd {
  font: inherit; font-size: 11px; padding: 1px 5px; border-radius: 4px;
  background: var(--sunken); border: 1px solid var(--line);
}
.reorder-controls .hint { margin: 12px 0 0; }

/* Right-align every figure in a report table and keep the digits in step, so a
   column of quantities can be scanned rather than read. */
.dtable th.qnum, .dtable td.qnum { text-align: right; font-variant-numeric: tabular-nums; }
.dtable code { font-size: 12px; }

.dtable { width: 100%; border-collapse: collapse; margin-top: 8px; }
.dtable th {
  text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 9px 10px; background: var(--sunken); border-radius: 6px;
}
.dtable td { padding: 11px 10px; font-size: 13.5px; border-bottom: 1px solid var(--line); }
.dtable tr:last-child td { border-bottom: 0; }
.dtable .num { text-align: right; font-variant-numeric: tabular-nums; }
.dtable a { font-weight: 600; }
.dtable tr[data-urgent="true"] td { background: color-mix(in srgb, var(--s-change) 12%, transparent); }
.dempty { color: var(--ink-3); }

/* The collapsible Mockup List in the department menu. */
/* An expandable section is one card holding its own children, so an open
   section reads as a single block instead of leaking rows into the gap
   before the next one. */
.sidenav > .navtree {
  background: var(--nav-card); border: 1px solid var(--nav-line);
  border-radius: 12px; padding: 0 0 2px;
}
.sidenav > .navtree[data-active="true"] {
  background: var(--nav-accent-bg); border-color: var(--nav-accent-line);
}
.sidenav > .navtree > summary.navitem,
.sidenav > .navtree[data-active="true"] > summary.navitem {
  background: none; border: 0; border-radius: 12px;
}
.sidenav > .navtree[data-active="true"] > summary.navitem { color: var(--nav-accent); }
.sidenav > .navtree[data-active="true"] > summary.navitem .ni {
  background: var(--nav-accent-tile); color: var(--nav-accent);
}
.navtree > summary.navitem { list-style: none; cursor: pointer; }
.navtree > summary.navitem::-webkit-details-marker { display: none; }
.navtree > summary .chev { margin-left: auto; transition: transform .16s ease; color: var(--ink-3); }
.sidenav > .navtree[data-active="true"] > summary .chev { color: var(--nav-accent); }
.navtree[open] > summary .chev { transform: rotate(180deg); }

/* ---- rows inside an open card ----
   Dashes and a dot rather than indentation alone: at three levels — All /
   year / month / day — indentation on its own stops saying which row is a
   child of which. The vertical dash stops halfway down the last child so the
   line does not run past the end of the branch. */
.navyr { margin: 0; }
.navyr > summary,
.navmo,
.navdy {
  position: relative; display: flex; align-items: center; gap: 8px;
  margin: 1px 8px 1px 0; padding: 7px 10px 7px 0;
  border-radius: 9px; font-size: 12.5px; color: var(--ink-2);
}
.navyr > summary { list-style: none; cursor: pointer; font-weight: 600; }
.navyr > summary::-webkit-details-marker { display: none; }

.navyr > summary::before,
.navmo::before,
.navdy::before {
  content: ''; position: absolute; top: 0; height: 100%; width: 13px;
  background:
    linear-gradient(var(--nav-guide) 0 4px, transparent 4px 8px) left top / 1px 8px repeat-y,
    linear-gradient(90deg, var(--nav-guide) 0 4px, transparent 4px 8px) left 50% / 8px 1px repeat-x;
}
.navyr:last-child > summary::before,
.navmo:last-child::before,
.navdy:last-child::before { height: 50%; }

/* the bullet, sitting where the two dashed lines meet */
.navyr > summary::after,
.navmo::after,
.navdy::after {
  content: ''; position: absolute; top: 50%; width: 7px; height: 7px;
  border-radius: 50%; background: var(--nav-dot); transform: translateY(-50%);
}

/* indents: All submissions and the year at level one, months under the year,
   days under the month */
.navtree > .navmo, .navyr { padding-left: 22px; }
.navtree > .navmo::before, .navyr > summary::before { left: 22px; }
.navtree > .navmo, .navyr > summary { padding-left: 41px; }
.navtree > .navmo::after, .navyr > summary::after { left: 26px; }

.navyr .navmo { padding-left: 41px; }
.navyr .navmo::before { left: 22px; }
.navyr .navmo::after  { left: 26px; }

.navdy { padding-left: 59px; font-size: 12px; }
.navdy::before { left: 40px; }
.navdy::after  { left: 44px; }

.navyr > summary:hover, .navmo:hover, .navdy:hover { background: var(--hover); color: var(--ink); }

/* the count, always right-aligned so the eye can run down the column */
.navyr > summary b, .navmo b, .navdy b {
  margin-left: auto; font-size: 10.5px; font-weight: 700; color: var(--ink-3);
  background: var(--nav-pill); padding: 2px 8px; border-radius: 999px;
  font-variant-numeric: tabular-nums;
}

/* selected: the row, its bullet and its count all say so together */
.navmo[data-active="true"], .navdy[data-active="true"] {
  background: var(--nav-accent-tile); color: var(--nav-accent); font-weight: 600;
}
.navmo[data-active="true"]::after, .navdy[data-active="true"]::after { background: var(--nav-accent); }
.navmo[data-active="true"] b, .navdy[data-active="true"] b {
  background: var(--nav-accent-line); color: var(--nav-accent);
}
.navtree .side-empty { margin: 6px 0 6px 41px; font-size: 12px; color: var(--ink-3); }

/* One preference to a row, control on the right. */
.prefrow { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.prefrow .hint { margin: 3px 0 0; max-width: 46ch; }
/* The shared .theme-toggle is a 38px circle built for an icon on its own; here
   it carries a label too, so the circle has to be undone or the text spills
   out of it. */
.prefrow .theme-toggle {
  width: auto; height: auto; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 9px 15px; font-size: 13px; font-weight: 600;
  flex: none; white-space: nowrap;
}

/* A value that stays hidden until asked for. */
.reveal { flex: none; }
.reveal > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; }
.reveal > summary::-webkit-details-marker { display: none; }
.reveal[open] > summary { display: none; }
.revealed {
  display: inline-block; padding: 9px 14px; border-radius: var(--r-sm);
  font-family: var(--mono);
  font-size: 14px; font-weight: 600; letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; color: var(--ink);
  background: var(--sunken); border: 1px solid var(--line-2); user-select: all;
}

/* .navdy is styled with the rest of the tree rows above; the board's own .dy
   list keeps its orange selection, which is a different control. */

/* A row that goes somewhere looks like it does. */
.dtable tr[data-href] { cursor: pointer; }
.dtable tr[data-href]:hover td { background: var(--hover); }
.dtable .dempty a { color: var(--indigo); font-weight: 600; }

/* The "still owed elsewhere" line under My assigned tasks. */
.waiting-elsewhere { margin: 10px 2px 4px; }
.waiting-elsewhere a { color: var(--indigo); font-weight: 600; }

/* Sign Out is a form so it can POST, but it should read as one more menu row. */
/* ---------- left-menu group (Hand-out Jobs) ---------- */
/* Reuses .navtree's open/close behaviour; only the children differ — plain
   links rather than the mockup tree's year/month rows. */
.navsub {
  position: relative; display: flex; align-items: center;
  padding: 8px 10px 8px 41px; margin: 1px 8px 1px 0;
  font-size: 13px; font-weight: 600; color: var(--ink-2); border-radius: 9px;
  text-decoration: none;
}
.navsub::before {
  content: ''; position: absolute; left: 22px; top: 0; height: 100%; width: 13px;
  background:
    linear-gradient(var(--nav-guide) 0 4px, transparent 4px 8px) left top / 1px 8px repeat-y,
    linear-gradient(90deg, var(--nav-guide) 0 4px, transparent 4px 8px) left 50% / 8px 1px repeat-x;
}
.navsub::after {
  content: ''; position: absolute; left: 26px; top: 50%; width: 7px; height: 7px;
  border-radius: 50%; background: var(--nav-dot); transform: translateY(-50%);
}
.navsub:hover { background: var(--hover); color: var(--ink); }
.navsub[data-active="true"] {
  background: var(--nav-accent-tile); color: var(--nav-accent);
}
.navsub[data-active="true"]::after { background: var(--nav-accent); }

/* The date tree under TPW Website Forms. Same rows as the mockup tree, one
   level further in, so the two calendars look like the same control. */
.navsubtree > summary {
  display: flex; align-items: center; justify-content: space-between;
  cursor: pointer; list-style: none;
}
.navsubtree[data-active="true"] > summary.navsub { color: var(--nav-accent); }
.navsubtree[data-active="true"] > summary.navsub::after { background: var(--nav-accent); }
.navsubtree > summary::-webkit-details-marker { display: none; }
.navsubtree > summary .chev { transition: transform .15s ease; opacity: .65; }
.navsubtree[open] > summary .chev { transform: rotate(180deg); }
/* One level further in, because this tree hangs off a child row. */
.navsubtree > .navmo, .navsubtree .navyr { padding-left: 34px; }
.navsubtree > .navmo::before, .navsubtree .navyr > summary::before { left: 34px; }
.navsubtree > .navmo, .navsubtree .navyr > summary { padding-left: 53px; }
.navsubtree > .navmo::after, .navsubtree .navyr > summary::after { left: 38px; }
.navsubtree .navyr .navmo { padding-left: 53px; }
.navsubtree .navyr .navmo::before { left: 34px; }
.navsubtree .navyr .navmo::after  { left: 38px; }
.navsubtree .navdy { padding-left: 71px; }
.navsubtree .navdy::before { left: 52px; }
.navsubtree .navdy::after  { left: 56px; }

/* ---------- the reviewer's decisions, above a submission ---------- */
.actionbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px 12px;
  padding: 12px 14px; margin: 14px 0 4px;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--raised);
}
.actionbar form { display: flex; align-items: center; gap: 8px; margin: 0; }
/* Its own line above the controls: the bar wraps, and a status readout that
   drifted in beside the designer picker would read as part of it. */
.ab-linked {
  flex: 0 0 100%; display: flex; align-items: center; gap: 8px; margin: 0;
  padding-bottom: 10px; border-bottom: 1px solid var(--line);
  font-size: 13px; color: var(--ink-2);
}
.ab-linked b { color: var(--ink); font-weight: 600; }
.ab-linked svg { color: var(--ink-3); flex: none; }
/* A line of its own across the top of the bar, so every control below it sits
   on one row underneath. Full ink, not the muted grey the rest of the bar
   uses: it names the decision the whole row exists to make. */
.ab-heading {
  flex: 0 0 100%; margin: 0 0 -4px;
  font-size: 14.5px; font-weight: 600; color: var(--ink);
}
/* Priority keeps its inline caption — it sits beside its own control, and the
   heading above is not about it. */
.ab-hand label { font-size: 14.5px; font-weight: 600; color: var(--ink); }
.ab-hand select {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 7px 9px; font-size: 12.5px; cursor: pointer; min-width: 150px;
}
/* Urgency is a two-word choice; it does not need the designer list's width. */
.ab-hand select#ab-priority { min-width: 104px; }
/* An urgent job says so on the control that set it, so the bar reads the same
   way the row does everywhere else in the app. */
.ab-hand select#ab-priority:has(option[value="urgent"]:checked) {
  border-color: var(--s-change); color: var(--s-change); font-weight: 700;
}
.ab-done { margin: 0; font-size: 13px; color: var(--ink-2); }
/* Part of the same row as the pickers, not floated off to the right edge:
   under one heading, one continuous line of controls that starts where the
   heading starts. */
.ab-marks { display: flex; flex-wrap: wrap; gap: 8px; }

/* The note gets the whole width of the bar, on a line of its own. */
.ab-notes {
  flex: 1 1 100%; width: 100%; resize: vertical; min-height: 62px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 11px 13px;
  font: inherit; font-size: 13.5px; color: var(--ink); line-height: 1.5;
}
.ab-notes::placeholder { color: var(--ink-3); }
.ab-notes:focus { outline: none; border-color: var(--line-2); box-shadow: var(--ring); }

.handout-day { font-variant-numeric: tabular-nums; }

/* A revision on a designer's day, told apart from a new mockup at a glance. */
.kindtag {
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .02em; white-space: nowrap;
  background: var(--s-change-bg); color: var(--s-change);
}

/* The rest of the team's day: present, but visibly not yours to touch. */
.sectionbar.allassigned { background: var(--ink-3); }

/* ---------- the monthly report ---------- */
.dash-tools { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.reptools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.repmonth select {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 9px 11px; font-size: 13px; cursor: pointer;
}
.reptable { width: 100%; border-collapse: collapse; font-size: 13px; }
.reptable th, .reptable td { padding: 9px 10px; border-bottom: 1px solid var(--line); text-align: left; }
.reptable thead th { color: var(--ink-2); font-weight: 600; font-size: 12px; }
/* Figures sit centred under their heading, and tabular so a column can still
   be read down rather than across — which is the only reason to put them in a
   table. The first column of every one of these tables is a name (website,
   how it came in, status, person), and names stay left where reading starts. */
.reptable .num { text-align: center; font-variant-numeric: tabular-nums; }
.reptable tbody tr:last-child td { border-bottom: 0; }
.reptable tfoot td {
  border-top: 1px solid var(--line-2); border-bottom: 0;
  font-weight: 700; color: var(--ink);
}

/* A month in the handouts tree only opens — that page has no month view, so a
   month link would promise one that does not exist. */
.navmonth > summary { list-style: none; cursor: pointer; }
.navmonth > summary::-webkit-details-marker { display: none; }

/* The note on the manual handout form: a row of its own across the whole
   form, matching the one on a submission. */
/* A row of its own beneath the fields — .handout is a grid, so this spans
   every column rather than taking one. */
.handout-notes {
  grid-column: 1 / -1; width: 100%; resize: vertical; min-height: 58px;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 10px 12px;
  font: inherit; font-size: 13px; color: var(--ink); line-height: 1.5;
}
.handout-notes::placeholder { color: var(--ink-3); }
.handout-notes:focus { outline: none; box-shadow: var(--ring); }

/* The one action that produces a file, so it does not read as a third mark. */
.rtfbtn {
  background: var(--ink); border-color: var(--ink); color: var(--ground);
  font-weight: 600;
}
.rtfbtn:hover { background: var(--ink-2); border-color: var(--ink-2); color: var(--ground); }

/* What the reviewer told this designer, on their own list. */
.handout-note {
  display: block; margin-top: 4px; color: var(--ink-2);
  font-style: normal; font-size: 12.5px; line-height: 1.5;
  border-left: 2px solid var(--line-2); padding-left: 9px;
}

/* Off is an offer, on is a statement — so off is outlined and on is filled.
   A pair of buttons that looked the same either way would leave the reviewer
   checking the list to find out whether the click landed. */
.markbtn { font-size: 12.5px; }
/* The app's own state pairs, so both themes are already handled: amber for a
   caution (too few), red for something missing (no artwork). */
.markbtn[data-on="true"].low_qty {
  background: var(--s-review-bg); border-color: var(--s-review);
  color: var(--s-review); font-weight: 600;
}
.markbtn[data-on="true"].no_logo {
  background: var(--s-change-bg); border-color: var(--s-change);
  color: var(--s-change); font-weight: 600;
}
.okline {
  margin: 10px 0 0; padding: 9px 12px; font-size: 13px;
  border-radius: var(--r-sm);
  background: var(--s-appr-bg); color: var(--s-appr);
}

/* The two marks as filter chips: selected, they take their own color rather
   than the indigo the state filters use, so a list narrowed to a mark looks
   different from one narrowed to a state. */
.filter.mark[data-active="true"].lowqty {
  background: var(--s-review); border-color: var(--s-review); color: #fff;
}
.filter.mark[data-active="true"].nologo {
  background: var(--s-change); border-color: var(--s-change); color: #fff;
}
.filter.mark[data-active="true"] b { background: rgba(255, 255, 255, .24); color: #fff; }

/* The same two marks, as tags on a list row. */
.marktag {
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .02em; white-space: nowrap;
}
.marktag.lowqty { background: var(--s-review-bg); color: var(--s-review); }
.marktag.nologo { background: var(--s-change-bg); color: var(--s-change); }

/* A paperclip on a submission the customer attached artwork to. Muted, and the
   same ink as the email under it: it marks the row without competing with the
   name it sits beside. Two thirds of rows carry one, so anything louder would
   turn the list into a wall of icons. */
.fr-clip { display: inline-flex; align-items: center; color: var(--ink-3); flex: none; }
.formrow:hover .fr-clip { color: var(--ink-2); }

/* Readable by a screen reader, invisible on screen — the paperclip's meaning
   is carried by a title attribute, which assistive tech reads unreliably. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- one website submission ---------- */
/* The same page padding as .board, so the back link lines up with the top of
   the Dashboard card and the content starts the same distance from the menu
   as the list does. Without it this page alone hugged the sidebar and sat a
   row higher than everything else. */
.submission {
  --page-max: 1700px;
  padding: 18px max(26px, calc((100% - var(--page-max)) / 2)) 90px;
}
/* A reading measure on the blocks rather than the page: the padding above
   places the column, this keeps the lines short enough to read. */
.submission > * { max-width: 920px; }
.submission h1 { font-size: 27px; margin: 14px 0 10px; }
.submission h2 { font-size: 19px; margin: 26px 0 14px; }
.submission .lede { color: var(--ink-2); line-height: 1.55; margin: 0 0 22px; max-width: 62ch; }
.ticketbox {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--raised); padding: 16px 18px; margin-bottom: 14px;
}
.ticketbox h3 { margin: 0 0 8px; font-size: 15px; }
.ticketbox p { margin: 0 0 4px; }
.ticketbox p:last-child { margin-bottom: 0; }
.ticketbox.state { background: var(--surface); }
.tickettag.lg { font-size: 13px; padding: 3px 9px; }

.subfield.lead { padding: 12px 0; border-bottom: 1px solid var(--line); }
.subfield.lead b { display: block; margin-bottom: 3px; }

/* Two columns, and the label column sized to the longest question rather than
   a guess — these headings run from "Phone" to "Logos/Artwork Placement". */
.subfields {
  display: grid; grid-template-columns: minmax(140px, max-content) minmax(0, 1fr);
  gap: 0 22px; margin: 6px 0 0;
}
.subfields dt {
  padding: 11px 0; color: var(--ink-2); font-weight: 600; font-size: 13px;
  border-bottom: 1px solid var(--line);
}
.subfields dd {
  padding: 11px 0; margin: 0; color: var(--ink-1); font-size: 13px;
  border-bottom: 1px solid var(--line); overflow-wrap: anywhere;
}
.subfields dt:last-of-type, .subfields dd:last-of-type { border-bottom: 0; }
.artshot { display: block; margin-top: 9px; }
.artshot img {
  max-width: 220px; max-height: 140px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: #fff; padding: 8px;
}

.formsearch { flex: none; }

/* ---------- the submissions list ---------- */
.formlist { display: flex; flex-direction: column; gap: 10px; }

/* A card per submission, not rows in a table: at five columns the table rules
   start competing with the row rules for the eye, and the thing a reviewer is
   scanning for is one submission, not a column of them. */
.formrow {
  display: grid; align-items: center;
  grid-template-columns: 148px minmax(0, 1fr) 108px 148px 150px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; padding: 14px 18px;
  text-decoration: none; color: var(--ink);
  transition: border-color .13s ease, box-shadow .13s ease;
}
.formrow:hover { border-color: var(--line-2); box-shadow: var(--lift-1); }
.formrow[data-state="dismissed"] { opacity: .6; }

/* A rule between the columns rather than around them — enough to separate,
   not so much that the card turns back into a table. */
.formrow > * + * { border-left: 1px solid var(--line); padding-left: 18px; }

.fr-ticket { display: flex; flex-direction: column; align-items: flex-start; gap: 7px; }
.fr-ticket .tickettag { font-size: 12.5px; padding: 4px 10px; }
.fr-date {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; color: var(--ink-3); white-space: nowrap;
}

.fr-who { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fr-name { display: flex; align-items: center; gap: 8px; min-width: 0; }
.fr-name b {
  font-size: 15px; font-weight: 700; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fr-email {
  font-size: 12.5px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The three right-hand columns are all centred so their contents line up down
   the list even when the middle column wraps differently row to row. */
.fr-cell { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; }
.fr-lbl { font-size: 11px; color: var(--ink-3); }
.fr-val { font-size: 14px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }

.fr-assignee { flex-direction: row; gap: 9px; }
.fr-assignee .avatar.sm { width: 30px; height: 30px; font-size: 12px; }
.fr-aname {
  font-size: 13.5px; color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fr-assignee.none { color: var(--ink-3); }

/* Narrow: the five columns become two, and the rules between them would then
   be cutting across nothing. */
@media (max-width: 1080px) {
  .formrow { grid-template-columns: 148px minmax(0, 1fr); gap: 10px 14px; }
  .formrow > * + * { border-left: 0; padding-left: 0; }
  .formrow > .fr-cell { align-items: flex-start; grid-column: 2; }
}

.navform { margin: 0; }
.navform .navitem {
  width: 100%; text-align: left; font-family: inherit;
  background: none; border: 0; cursor: pointer;
}
.navitem[data-signout="true"]:hover { background: color-mix(in srgb, var(--s-change) 14%, transparent); color: var(--neg); }
.navitem[data-signout="true"]:hover .ni { color: currentColor; }

/* ------------------------------------------------------------------ search */

/* Full width like the lists it shows, not the reading measure of a document:
   both sections are made of the same wide rows they have on their own pages,
   and narrowing them here would reflow every column. */
.searchpage {
  --page-max: 1700px;
  padding: 18px max(26px, calc((100% - var(--page-max)) / 2)) 90px;
}
.searchpage h1 { font-size: 27px; margin: 14px 0 10px; }
.searchpage .lede { color: var(--ink-2); line-height: 1.55; margin: 0 0 26px; }
.searchpage .lede b { color: var(--ink); }

.searchsec { margin-bottom: 34px; }
.searchsec h2 {
  display: flex; align-items: center; gap: 9px;
  font-size: 13px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-2); margin: 0 0 12px;
}
/* The count beside the heading, so "no folder yet" is legible before reading
   the list — that absence is the thing worth seeing on this page. */
.searchsec .cnt {
  font-size: 11.5px; letter-spacing: 0; padding: 2px 8px; border-radius: 999px;
  background: var(--sunken); color: var(--ink-2); font-weight: 600;
}
.searchsec .empty { padding: 30px 22px; text-align: left; font-size: 13.5px; }

/* ---------- direct jobs: two bars, a list under each ---------- */

/* .sectionbar sits tight to what follows it on My Day, where a .daycard
   supplies its own gap. Here the lists are bare .formlists, so the spacing
   has to come from the bar. */
.board > .sectionbar { margin: 26px 0 12px; }
.board > .sectionbar:first-of-type { margin-top: 14px; }
/* Read-only rows: no pointer, no lift on hover — the eye should not be
   invited to click somebody else's job. */
.board > .sectionbar.allassigned + .formlist .formrow { cursor: default; }

/* ---------- a handout, in full ---------- */

/* The name carries the link, so it needs to look like one without turning the
   row into a wall of blue: inherits its weight from the <b> around it and
   shows the underline only under the pointer. */
.handout-open { color: inherit; text-decoration: none; }
.handout-open:hover, .handout-open:focus-visible { text-decoration: underline; }

/* A note is the part written for this designer, so it reads as prose rather
   than as another field: full measure, and the line height of a paragraph. */
.notebox p { margin: 0; line-height: 1.6; white-space: normal; }
.submission .notebox { max-width: 920px; }

/* ---------- a note that was changed after the fact ---------- */

.noteedit {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 10px; font-size: 12px; color: var(--ink-3);
}
/* Amber rather than red: the note changing is worth noticing, not a fault. */
.editedtag {
  font-size: 10.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px;
  background: var(--s-review-bg); color: var(--s-review);
}
.notewrap { margin-top: 14px; }
.notewrap > summary { display: inline-flex; width: auto; }
.notewrap .editform { margin-top: 12px; }
.notewrap textarea {
  width: 100%; resize: vertical; font: inherit; line-height: 1.55;
  padding: 10px 12px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
}
.notewrap textarea:focus { outline: none; border-color: var(--line-2); box-shadow: var(--ring); }
/* In a list the stamp rides beside the note rather than above it. */
.editedtag.sm { font-size: 9.5px; padding: 2px 6px; align-self: flex-start; }

/* The note as it was handed out, folded under the current one. */
.origwrap { margin-top: 12px; }
.origwrap > summary { display: inline-flex; width: auto; }
.orignote {
  margin-top: 10px; padding: 12px 14px;
  border: 1px dashed var(--line-2); border-radius: var(--r-sm);
  background: var(--sunken);
}
.orignote p:last-child { margin-bottom: 0; }
/* Grey, not amber: this one is history rather than something to act on. */
.editedtag.was { background: var(--sunken); color: var(--ink-2); border: 1px solid var(--line); }

/* ---------- the shop tiles on Low Quantity / No Logo ---------- */

/* These three are the filter, so they behave like one: a pointer, a lift under
   it, and a ring on the one in force. Everything a plain tile looks like is
   kept — they are still the number first. */
a.tile {
  text-decoration: none; color: inherit; display: block;
  transition: border-color .14s ease, box-shadow .14s ease, transform .14s ease;
}
a.tile:hover { border-color: var(--line-2); box-shadow: var(--lift-1); transform: translateY(-1px); }
a.tile:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--line-2); }
/* Chosen: a ring in the ink rather than a fill, so the number stays the
   loudest thing on the tile. */
a.tile[data-active="true"] {
  border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink);
}
a.tile[data-active="true"] .tile-label { color: var(--ink); }

/* ---------- sync now, on the website-form lists ---------- */

/* The board keeps its picker in the sidebar where a block can grow. Here it
   hangs off the end of the filter row, so the body floats above the list
   instead of stretching the row it lives in. */
.filters .filtersync { position: relative; }
.filters .syncpick { margin-top: 0; }
.filters .syncpick-body {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
  width: max-content; max-width: min(420px, 90vw);
  flex-direction: column; gap: 14px;
  background: var(--raised); box-shadow: var(--lift-2);
}
.filters .syncpick-body .hint { margin: 0; max-width: 34ch; }

/* ---------- the designers' dashboard ---------- */

.dcards { display: flex; flex-direction: column; gap: 16px; }
.dcard-head { align-items: center; gap: 12px; }
.dcard-head .who { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
.dcard-name { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.dcard-name b { font-size: 15px; color: var(--ink); }
.dcard-name i { font-style: normal; font-size: 12px; color: var(--ink-3); }

/* This month in words, under the name. Wraps rather than scrolls: on a narrow
   screen these become two lines, which reads fine, where a scrollbar would
   hide half of them. */
.dcard-now {
  display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 0 0 4px;
  font-size: 12.5px; color: var(--ink-2);
}
.dcard-now b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
/* Amber on the two that mean somebody is waiting. */
.dcard-now span[data-warn="true"] b { color: var(--s-change); }

/* A shorter chart than the dashboard's: there are several on the page and the
   full height would turn it into scrolling rather than comparing. */
.dcard .chart-wrap { margin-top: 4px; }
.dcard .barchart { max-height: 180px; }

/* A heading over a second tile row. */
.rowhead {
  margin: 26px 0 -4px; font-size: 13px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
}

/* ---------- the four departments on the admin's dashboard ---------- */

/* Two across, and only two. These are the whole top of the admin's page now
   rather than a strip beneath the tiles, so each has the width to show its
   three figures at a size worth reading, with the note that says what the
   department is next to the name instead of wrapping under it. */
.deptcards {
  display: grid; gap: 16px; margin: 12px 0 20px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 780px) { .deptcards { grid-template-columns: minmax(0, 1fr); } }

/* One hue per department, set once on the card and used by the head wash, its
   rule, the arrow and the hover — so four cards read as four places rather
   than four copies of the same card. */
.deptcard[data-tint="blue"]   { --dept: #2563eb; }
/* Re-stepped from #7c3aed: against the blue next to it that violet came out
   at ΔE 0.4 under deuteranopia and 12.4 to normal vision — two cards a reader
   could not tell apart by color. This one measures 9.7 / 22.6. Only the
   department cards use it; the shared violet tile tint is untouched. */
.deptcard[data-tint="violet"] { --dept: #a21caf; }
.deptcard[data-tint="orange"] { --dept: #ea580c; }
.deptcard[data-tint="teal"]   { --dept: #0d9488; }
.deptcard[data-tint="green"]  { --dept: #16a34a; }
.deptcard[data-tint="red"]    { --dept: #dc2626; }

/* The card IS the link — bands run edge to edge, so the padding lives on the
   three sections rather than here, and overflow is clipped to the radius. */
.deptcard {
  display: flex; flex-direction: column; color: inherit;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); overflow: hidden;
  transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.deptcard:hover {
  border-color: color-mix(in srgb, var(--dept) 45%, var(--line));
  box-shadow: 0 6px 20px color-mix(in srgb, var(--dept) 15%, transparent);
  transform: translateY(-1px);
}
.deptcard:focus-visible { outline: 2px solid var(--dept); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .deptcard { transition: none; } .deptcard:hover { transform: none; } }

.deptcard-head {
  display: flex; align-items: center; gap: 13px; padding: 15px 18px;
  background: color-mix(in srgb, var(--dept) 7%, var(--surface));
  border-bottom: 1px solid color-mix(in srgb, var(--dept) 20%, var(--line));
}
/* The chip goes back to the card surface here: on a tinted band a tinted chip
   disappears into it. Same weight as the tile rule it overrides, and later in
   the file, which is what decides it. */
.deptcard-head .tile-icon {
  margin-bottom: 0; flex: none; width: 44px; height: 44px;
  background: var(--surface); color: var(--dept);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dept) 22%, transparent);
}
.deptcard-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.deptcard-name b { font-size: 17px; letter-spacing: -.01em; color: var(--ink); }
.deptcard-name i { font-style: normal; font-size: 12.5px; color: var(--ink-3); line-height: 1.45; }

/* Three columns, divided rather than merely spaced: at this width a plain gap
   lets "Forms submitted 126" and "Ready for review 0" run together as one line
   of words. The rules say where one figure ends and the next begins. */
.deptstats {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0;
}
.deptstats > div {
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
  padding: 15px 18px 14px; border-left: 1px solid var(--line);
}
.deptstats > div:first-child { border-left: 0; }
.deptstats dd {
  margin: 0; font-size: 29px; font-weight: 700; letter-spacing: -.025em;
  line-height: 1.05; color: var(--ink); font-variant-numeric: tabular-nums;
}
.deptstats dt { font-size: 12px; color: var(--ink-3); line-height: 1.35; }
/* Amber only when there is something to act on — a zero backlog is good news
   and should not wear a warning color. */
.deptstats dd[data-warn="true"] { color: var(--s-change); }
/* A measure that has nothing behind it yet: greyed, and the reason under the
   label so nobody reads the dash as a nought. */
.deptstats dd.none { color: var(--ink-3); font-weight: 600; }
.deptstats .dnote { display: block; font-style: normal; font-size: 10.5px; color: var(--ink-3); opacity: .8; }

/* ---------- the chart on each department card ---------- */

/* Six months of one measure. Sunk into its own well so the card reads as a
   headline, its figures, then the shape behind them — rather than as a chart
   with some numbers loose above it. */
/* ---------------------------------------------------------------------------
   Department sparkline

   Replaced a 248px bar chart. Four of those made the department row 71% of the
   page; the shape of six months reads just as well at 40px, and the number a
   reader wants — the delta — is now the loudest thing in the block.
   --------------------------------------------------------------------------- */
.deptspark {
  margin: 0 18px 14px; padding: 10px 12px 8px;
  background: var(--sunken); border-radius: var(--r-md);
}
.deptspark-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin: 0 0 6px;
}
.deptspark-head span {
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
/* Text tokens, never the series color — the line carries identity, the words
   carry the reading. */
.deptspark-head i {
  font-style: normal; font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--ink-3);
}
.deptspark-head i[data-dir="up"]   { color: var(--pos); }
.deptspark-head i[data-dir="down"] { color: var(--neg); }

.sparkline { display: block; width: 100%; height: 40px; }
/* vector-effect keeps the stroke 2px after preserveAspectRatio="none" has
   stretched the viewBox — without it the line thins as the card widens. */
.sparkline .sline {
  fill: none; stroke: var(--dept); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.sparkline .sfill { fill: color-mix(in srgb, var(--dept) 14%, transparent); stroke: none; }
.sparkline .sdot  { fill: var(--dept); stroke: var(--sunken); stroke-width: 2; vector-effect: non-scaling-stroke; }
.sparkline .sdot.open { fill: var(--surface); stroke: var(--dept); }

.deptspark-foot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; margin: 4px 0 0;
  font-size: 10.5px; color: var(--ink-3);
}
.deptspark-foot b {
  font-size: 12px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* A series with nothing in it gets this instead of a flat line along the
   floor — which is not data, and reads as "this department did nothing". */
.deptflat {
  margin: 0 18px 14px; padding: 11px 12px;
  background: var(--sunken); border-radius: var(--r-md);
  font-size: 11.5px; color: var(--ink-3);
}

/* ---------------------------------------------------------------------------
   Growth and pace

   Two cards above the departments. Every other figure on this page is a count
   of now; these two are the only ones that say which way it is going.
   --------------------------------------------------------------------------- */
.growthrow {
  display: grid; gap: 16px; margin: 12px 0 24px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 780px) { .growthrow { grid-template-columns: minmax(0, 1fr); } }

.gcard {
  display: flex; flex-direction: column;
  padding: 16px 18px 14px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.gcard-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin: 0 0 10px;
}
.gcard-head span {
  font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-2);
}
.gcard-head i { font-style: normal; font-size: 11.5px; color: var(--ink-3); }

.gcard-figure { margin-bottom: 10px; }
/* The hero number. One per card, and the only thing on the page set this big,
   so it is read first and read as the answer. */
.gbig {
  margin: 0; font-size: 30px; line-height: 1.05; font-weight: 700;
  letter-spacing: -.02em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.gbig[data-dir="up"]   { color: var(--pos); }
.gbig[data-dir="down"] { color: var(--neg); }

.gsub { margin: 4px 0 0; font-size: 12px; color: var(--ink-2); }
.gsub b { font-weight: 700; font-variant-numeric: tabular-nums; }
.gsub b[data-dir="up"]   { color: var(--pos); }
.gsub b[data-dir="down"] { color: var(--neg); }

.gline { display: block; width: 100%; height: 46px; margin-top: auto; }
.gline .sline {
  fill: none; stroke: var(--brand-navy); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.gline .sfill { fill: color-mix(in srgb, var(--brand-navy) 12%, transparent); stroke: none; }
.gline .sdot  { fill: var(--brand-navy); stroke: var(--surface); stroke-width: 2; vector-effect: non-scaling-stroke; }
.gline .sdot.open { fill: var(--surface); stroke: var(--brand-navy); }

/* How much of the month has gone. The bar is the month, not the job count —
   a bar that filled with jobs would need a target nobody has set. */
.pacebar {
  position: relative; height: 8px; margin-top: auto;
  background: var(--sunken); border-radius: 999px; overflow: hidden;
}
.pacefill {
  display: block; height: 100%; border-radius: 999px;
  background: var(--brand-navy);
}

.gfoot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; margin: 8px 0 0;
  font-size: 10.5px; color: var(--ink-3);
}

@media (prefers-color-scheme: dark) {
  /* #a21caf measures 2.73:1 on the dark surface — below the 3:1 floor, which
     for a 2px line means a mark you have to hunt for. Re-stepped to #b833c9:
     3:1 clear, and 6.2 ΔE from the blue under protanopia, which is legal
     because every card is titled in words as well as colored. */
  :root:not([data-theme="light"]) .deptcard[data-tint="violet"] { --dept: #b833c9; }
  :root:not([data-theme="light"]) .gcard .sline { stroke: #7fa9f0; }
  :root:not([data-theme="light"]) .gcard .sfill { fill: color-mix(in srgb, #7fa9f0 14%, transparent); }
  :root:not([data-theme="light"]) .gcard .sdot  { fill: #7fa9f0; }
  :root:not([data-theme="light"]) .gcard .sdot.open { fill: var(--surface); stroke: #7fa9f0; }
  :root:not([data-theme="light"]) .pacefill { background: #7fa9f0; }
}

:root[data-theme="dark"] .deptcard[data-tint="violet"] { --dept: #b833c9; }
:root[data-theme="dark"] .gcard .sline { stroke: #7fa9f0; }
:root[data-theme="dark"] .gcard .sfill { fill: color-mix(in srgb, #7fa9f0 14%, transparent); }
:root[data-theme="dark"] .gcard .sdot  { fill: #7fa9f0; }
:root[data-theme="dark"] .gcard .sdot.open { fill: var(--surface); stroke: #7fa9f0; }
:root[data-theme="dark"] .pacefill { background: #7fa9f0; }

/* Pushed to the bottom so the four footers line up however the notes above
   them wrap. Not a button: the whole card is the target, and this only names
   where it goes. */
.deptcard-go {
  margin-top: auto; display: flex; align-items: center; gap: 7px;
  padding: 12px 18px; border-top: 1px solid var(--line);
  font-size: 12.5px; font-weight: 700; color: var(--dept);
}
.deptcard-go svg { transition: transform .12s ease; }
.deptcard:hover .deptcard-go svg { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) { .deptcard-go svg { transition: none; } }

/* ---------------------------------------------------------------------------
   The Product field's catalogue picker

   A suggestion list under the product name on the quote page, offering what
   Arclight stocks with the price listed on the website. It overlays the sheet
   below it rather than pushing it down: a list that reflows the whole quote
   every time somebody types is unusable.
   --------------------------------------------------------------------------- */

/* .qproduct is a flex row and used to hold the input directly; the input now
   sits one level down, so the wrapper takes the growth and passes the width on. */
.qpick { position: relative; flex: 1 1 auto; min-width: 0; }
.qpick .qbig { width: 100%; }

.qpick-list {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 340px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(15, 23, 42, .16);
}

.qpick-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  text-align: left;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
}

.qpick-opt:last-child { border-bottom: 0; }
.qpick-opt:hover,
.qpick-opt.on { background: var(--hover); }

.qpick-opt img,
.qpick-noimg {
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  border-radius: 6px;
  object-fit: cover;
  background: var(--ground);
  border: 1px solid var(--line);
}

.qpick-txt { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }

.qpick-name {
  font-weight: 600;
  font-size: 13px;
  /* Titles here run to sixty characters and would otherwise wrap to three
     lines each, turning eight results into a page of text. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.qpick-sub {
  font-size: 11.5px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.qpick-price {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  flex: 0 0 auto;
  font-weight: 700;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.qpick-stk { font-weight: 500; font-size: 11px; color: var(--ink-3); }
.qpick-stk.out,
.qpick-stock.out { color: var(--neg); }

.qpick-none {
  margin: 0;
  padding: 10px 12px;
  font-size: 12.5px;
  color: var(--ink-2);
}

/* What was picked, kept under the field so the price in the sheet can always be
   traced back to a SKU rather than looking like it appeared on its own. */
.qpick-chip {
  margin: 4px 0 0;
  font-size: 13.5px;
  color: var(--ink-2);
}

.qpick-chip strong { color: var(--ink); font-variant-numeric: tabular-nums; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .qpick-list {
    box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
  }
}

:root[data-theme="dark"] .qpick-list { box-shadow: 0 10px 28px rgba(0, 0, 0, .5); }

/* ---------------------------------------------------------------------------
   Inventory Check

   The catalogue as a list rather than a picker. A quotation's Product field
   collapses to the one thing you chose; this keeps every match on screen,
   because the question here is "how many have we got" and the answer is the
   whole row.
   --------------------------------------------------------------------------- */
.invsearch { margin-bottom: 4px; }
.invsearch .qbig { width: 100%; }
.invsearch-note { margin: 6px 0 0; font-size: 12px; color: var(--ink-3); }
.invsearch-note code {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--ink-2); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
  background: var(--sunken); padding: 1px 5px; border-radius: 4px;
}

.invhint  { margin: 16px 0 4px; }
.invcount { margin: 16px 0 8px; font-size: 12px; font-weight: 600; color: var(--ink-2); }

/* The table scrolls inside its own box: a long product name must not push the
   page sideways. */
.invtable-wrap { overflow-x: auto; }
.invtable { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.invtable th {
  text-align: left; padding: 8px 10px; white-space: nowrap;
  font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink); border-bottom: 1px solid var(--line-2);
}
.invtable td { padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.invtable tbody tr:last-child td { border-bottom: 0; }
.invtable tbody tr:hover { background: var(--hover); }
.invtable .num  { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.invtable .mono {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--ink-2); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
  white-space: nowrap;
}

.invname { display: flex; align-items: center; gap: 10px; min-width: 260px; }
.invname img, .invname .qpick-noimg {
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: 6px; object-fit: cover;
  background: var(--ground); border: 1px solid var(--line);
}
.invname span { display: flex; flex-direction: column; min-width: 0; }
.invname b { font-weight: 600; }
.invname i { font-style: normal; font-size: 11.5px; color: var(--ink-3); }

/* Sold out is the thing somebody rang up to find out, so it is a word as well
   as a color — never color alone. */
.instock { font-weight: 700; }
.instock.out { font-weight: 600; color: var(--neg); }


/* The shortlist being built up, above the results that feed it. Hidden until
   something is on it — a heading over an empty table has to be explained. */
.invchecked {
  margin: 18px 0 4px; padding: 14px 16px 6px;
  background: var(--sunken); border-radius: var(--r-md);
}
.invchecked-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin: 0 0 6px;
}
.invchecked-head h4 {
  margin: 0; font-size: 14px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink);
}
.invchecked-head h4 span { color: var(--ink-3); }
.invchecked .invtable th { border-bottom-color: var(--line-2); }
.invchecked .invtable tbody tr:hover { background: transparent; }

/* A result row is a toggle: click adds it to the list, click again takes it
   off. The cursor says so before the click does. */
.invpick { cursor: pointer; }
.invpick[data-on="true"] { background: color-mix(in srgb, var(--indigo) 9%, transparent); }
.invpick[data-on="true"]:hover { background: color-mix(in srgb, var(--indigo) 14%, transparent); }
.invpick[data-on="true"] .inv-add { border-color: var(--indigo); color: var(--indigo); }

/* Shopify holds no cost for this variant. A dash rather than $0.00, which
   would read as "it costs us nothing". */
.nocost { color: var(--ink-3); }

/* Shopify holds no cost for the variant just picked, so Cost each was left for
   somebody to fill in. Said in the chip rather than left as a silent blank. */
.qpick-nocost { color: var(--s-change); }

/* A saved quotation, locked. Every cell is still a field — the sheet has to
   read as the same document either way — but a flat one, so it reads as a
   record rather than a form waiting to be filled in. */
.quote-page[data-locked="true"] input:not([type=hidden]),
.quote-page[data-locked="true"] textarea,
.quote-page[data-locked="true"] select {
  background: transparent; border-color: transparent;
  cursor: default; box-shadow: none;
}
.quote-page[data-locked="true"] input.accent-yellow,
.quote-page[data-locked="true"] input.accent-yellow.strong {
  background: transparent !important; color: var(--ink) !important;
}
.quote-page[data-locked="true"] select { appearance: none; -webkit-appearance: none; }
/* Fields keep their value but stop advertising that they can be typed in. */
.quote-page[data-locked="true"] input:focus,
.quote-page[data-locked="true"] textarea:focus { outline: none; }

/* Who changed it, and when. */
.qhistory-list { list-style: none; margin: 0; padding: 0; }
.qhistory-list li {
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.qhistory-list li:last-child { border-bottom: 0; }
.qhistory-list b { font-weight: 600; color: var(--ink); min-width: 160px; }
.qhistory-list span { color: var(--ink-2); font-variant-numeric: tabular-nums; }
/* The row the quote started as, set apart from the changes made to it. */
.qhistory-first b, .qhistory-first span { color: var(--ink-3); }

/* A day inside a month of quotations. The heading is a rule with the day's own
   count and value on it, so a month reads as the days it was made of rather
   than as one long table. */
.qday + .qday { margin-top: 18px; }
/* Saved Quotations gives each day a card of its own, so the separation between
   days is white space rather than a rule inside one long panel.
   The padding a panel already has is right; only the gap needs saying, and it
   ADDS to the 16px flex gap .settings puts between its children rather than
   replacing it — 6 + 16 is the 22px the days want between them. */
.qdaycard + .qdaycard { margin-top: 6px; }
.qday-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 0 0 6px; padding-bottom: 5px;
  border-bottom: 1px solid var(--line-2);
}
.qday-head span { font-size: 13px; font-weight: 700; color: var(--ink); }
.qday-head i {
  font-style: normal; font-size: 11.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* The history line: who, when, and what they did. */
.qhistory-list em {
  font-style: normal; margin-left: auto;
  font-size: 12px; color: var(--ink-2);
}

/* Where a quotation came from: the website enquiry it answers, one click from
   what the customer actually wrote. */
.qfrom {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  margin: 0 0 18px; padding: 10px 12px;
  background: var(--sunken); border-radius: var(--r-md);
  font-size: 13px;
}
.qfrom-tag {
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3);
}
.qfrom i { font-style: normal; color: var(--ink-2); font-size: 12px; }
.qfrom form { display: inline; }
/* The "is this from a form?" variant asks rather than states, so its buttons
   sit on one line with the question. */
.qfrom-ask { gap: 6px; }

/* Make Quote / already quoted, on a website submission. The one orange
   button in the action bar: the same orange as "Add Quotation for Another
   Product", because it is the same act on the same job. */
.quotebtn {
  font-weight: 600;
  background: var(--brand-orange); border-color: var(--brand-orange); color: #fff;
}
.quotebtn:hover {
  background: var(--brand-orange); border-color: var(--brand-orange);
  color: #fff; filter: brightness(1.07);
}
.quoted {
  background: var(--sunken); border-color: var(--line-2);
  color: var(--ink-2); font-size: 12px; font-weight: 600;
}

/* Where a job has got to, and where it can go next. Stated above the sheet
   because the stage is what somebody opens a saved quote to check. */
.qstage {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  margin: 0 0 18px; padding: 10px 14px;
  background: var(--sunken); border-radius: var(--r-md);
}
.qstage-now {
  font-size: 12px; font-weight: 700; letter-spacing: .03em;
  padding: 4px 10px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line-2); color: var(--ink);
}
/* One step per color, so the stage reads before the words do. */
.qstage-now[data-stage="invoice"]   { border-color: #2563eb; color: #1d4ed8; }
.qstage-now[data-stage="booked"]    { border-color: #ea580c; color: #c2410c; }
.qstage-now[data-stage="completed"] { border-color: #15803d; color: #15803d; }

.qstage-inv { font-size: 12.5px; color: var(--ink-2); }
.qstage-inv b {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.qstage i { font-style: normal; font-size: 12px; color: var(--ink-3); }

/* The move sits at the far end: it is the action, not the status. */
.qstage-move { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.qstage-move select, .qstage-move input { font-size: 13px; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .qstage-now[data-stage="invoice"]   { border-color: #60a5fa; color: #93c5fd; }
  :root:not([data-theme="light"]) .qstage-now[data-stage="booked"]    { border-color: #fb923c; color: #fdba74; }
  :root:not([data-theme="light"]) .qstage-now[data-stage="completed"] { border-color: #4ade80; color: #86efac; }
}
:root[data-theme="dark"] .qstage-now[data-stage="invoice"]   { border-color: #60a5fa; color: #93c5fd; }
:root[data-theme="dark"] .qstage-now[data-stage="booked"]    { border-color: #fb923c; color: #fdba74; }
:root[data-theme="dark"] .qstage-now[data-stage="completed"] { border-color: #4ade80; color: #86efac; }

/* One job on one line: who it is for, the three records it is made of, and
   where it has got to. A table would have made the trail three more columns
   to read across; this keeps the story in the order it happened. */
.jobrow {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
  padding: 12px 0; border-bottom: 1px solid var(--line);
}
.jobrow:last-child { border-bottom: 0; }

.jobrow-who { display: flex; flex-direction: column; min-width: 190px; }
.jobrow-who b { font-weight: 600; font-size: 13.5px; }
.jobrow-who i { font-style: normal; font-size: 11.5px; color: var(--ink-3); }

/* The name and, when there is one, the mark that says we have done work for
   these people before. Baseline-aligned rather than centred: the mark is an
   aside to the name, not a second thing of equal weight. */
.jobrow-name { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: 4px 7px; }
.repeatmark {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  font-size: 10.5px; font-weight: 700; letter-spacing: .02em;
  padding: 2px 7px 2px 6px; border-radius: 999px;
  color: var(--indigo); background: var(--indigo-soft);
  cursor: help; white-space: nowrap;
}
/* Indigo, not one of the four status colors: this is not a stage the job is
   at, and borrowing a status color would make it read like one. */
.repeatmark svg { flex: none; opacity: .85; }

.jobrow-trail { display: flex; align-items: stretch; gap: 8px; flex-wrap: wrap; flex: 1 1 auto; }

/* Each record in the trail: what it is, and the one fact worth seeing without
   opening it. */
.joblink {
  display: flex; flex-direction: column; gap: 1px;
  padding: 6px 10px; min-width: 132px;
  background: var(--sunken); border: 1px solid transparent; border-radius: var(--r-sm);
  color: inherit;
}
a.joblink:hover { border-color: var(--line-2); background: var(--hover); }
.joblink span { font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
                text-transform: uppercase; color: var(--ink-3); }
.joblink i { font-style: normal; font-size: 12px; color: var(--ink); }
.joblink i.mono {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
/* A record that does not exist says so rather than leaving a gap. */
.joblink.none { opacity: .55; }
.joblink.none i { color: var(--ink-3); }

.jobrow-act { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.jobrow-act form { display: inline; }

@media (max-width: 900px) {
  .jobrow-act { margin-left: 0; }
}

/* ---------- Today's List: the row ---------- */

/*
 * The client column is a fixed width, so the three boxes start at the same
 * place on every row and read as columns down the page. Left to size itself
 * they began wherever the customer's name happened to end, which turned a
 * scannable table into a ragged edge — and the name of a company is exactly
 * the thing that varies most.
 *
 * Scoped to this list. Clients and Production share .jobrow and want their
 * trail tight against the name, because those rows carry fewer boxes.
 */
.todaylist .jobrow { gap: 16px 28px; }
.todaylist .jobrow-who { flex: 0 0 clamp(180px, 21%, 300px); min-width: 0; }
.todaylist .jobrow-who i { overflow-wrap: anywhere; }

/* Air between the boxes as well as before them: three of them shoulder to
   shoulder read as one block rather than three facts. */
.todaylist .jobrow-trail { gap: 12px; }
/* Equal thirds rather than shrink-to-fit. Sized to their own text the boxes
   stepped left and right by a designer's name length, so column two never
   started in the same place twice — the fixed client column fixed one edge
   and left the other two ragged. */
.todaylist .joblink { flex: 1 1 0; min-width: 0; padding: 8px 12px; }
.todaylist .joblink i { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* One button wide, always. The stage chip lives under the customer's name
   instead, so a quoted row does not squeeze its own boxes narrower than its
   neighbours' — and the two button labels are given a common width so the
   column edge does not move between "Make Quote" and "Quote Sent". */
.todaylist .jobrow-act { flex: 0 0 auto; justify-content: flex-end; }
.todaylist .quotebtn { min-width: 132px; justify-content: center; }

/* The stage, under the name. Small and quiet: it is context for the customer,
   not the thing you came to the row to do. */
.rowstage {
  align-self: flex-start; margin-top: 5px;
  font-size: 11px; padding: 2px 8px;
}

/* Below the breakpoint the fixed column stops helping — there is no room for
   columns — so the name goes back to taking the width it needs. */
@media (max-width: 900px) {
  .todaylist .jobrow { gap: 12px 14px; }
  .todaylist .jobrow-who { flex: 1 1 100%; }
  /* Columns are pointless once the row is a stack, and holding three across a
     phone clipped every one of them to an ellipsis. Let them wrap and say
     what they say in full. */
  .todaylist .joblink { flex: 1 1 150px; }
  .todaylist .joblink i { white-space: normal; overflow: visible; }
}

/* Quoted already: the same button, finished. Green rather than the brand
   orange, which on this page means "there is work to do here". */
.quotebtn.sent {
  background: var(--s-appr); border-color: var(--s-appr); color: #fff;
}
.quotebtn.sent:hover { filter: brightness(.95); }

/* Written but not yet gone. Outlined rather than filled: it is not a job to
   start, so it should not shout like one, but it is not finished either. */
.quotebtn.waiting {
  background: var(--surface); border-color: var(--s-review);
  color: var(--s-review); font-weight: 600;
}
.quotebtn.waiting:hover { background: var(--s-review-bg); }

/* ---------- the sent tick, on the quotation itself ---------- */

.qsent {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12.5px; font-weight: 600; color: var(--s-appr);
  background: var(--s-appr-bg); border-radius: 999px; padding: 5px 12px;
}
/* A link, not a button, because undoing a tick is a footnote to it — a second
   button here would read as a second thing to do. */
.qsent-undo {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-weight: 500; font-size: 11.5px;
  color: var(--s-appr); opacity: .75; text-decoration: underline;
}
.qsent-undo:hover { opacity: 1; }
.qsent-btn { font-size: 12.5px; font-weight: 600; }

/* ---------- Today's List: the day picker ---------- */

/* Arrow, date, arrow — the smallest control that answers "and yesterday?".
   The date input rather than a month tree: this page is about one day, and a
   tree would invite browsing where the point is checking. */
.dayjump { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dayjump form { display: inline-flex; margin: 0; }
.dayjump input[type="date"] {
  font: inherit; font-size: 13px;
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 7px 10px;
}
.dayjump input[type="date"]:focus { border-color: var(--indigo); outline: none; }
/* The forward arrow on today: kept in place rather than removed, so the row
   does not reflow as you step back and forward through the days. */
.dayjump [aria-disabled="true"] { opacity: .35; pointer-events: none; }

/* ---------- All Clients: a month per fold ---------- */

/* The month band, wearing the same clothes .panel h3 wears — same navy, same
   uppercase, same dot — because it IS that band; what has changed is that it
   now opens and shuts. Anything else would read as a different kind of
   heading from the one on Booked Jobs and Invoices. */
.monthfold > summary {
  display: flex; align-items: center; gap: 9px;
  font-size: 14px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--on-bar); background: var(--bar);
  padding: 12px 18px; border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  cursor: pointer; list-style: none;
  transition: filter .14s ease;
}
.monthfold > summary::-webkit-details-marker { display: none; }
.monthfold > summary:hover { filter: brightness(1.12); }
.monthfold > summary::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--on-bar-dot); flex: none;
}
/* The count sits at the far end, quieter than the label but on the same navy,
   so it has to be dimmed rather than greyed. */
.monthfold .mb-sum {
  margin-left: auto; font-size: 12.5px; font-weight: 600;
  letter-spacing: .02em; text-transform: none;
  color: var(--on-bar); opacity: .72;
  font-variant-numeric: tabular-nums;
}
.monthfold .mb-chev {
  flex: none; margin-left: 12px; opacity: .8;
  transition: transform .15s ease;
}
.monthfold[open] .mb-chev { transform: rotate(180deg); }

/* Open, the band and its first day card are one object again — square where
   they meet — which is exactly how the band reads on the lists that do not
   fold. Shut, it is a bar on its own and fully rounded. */
.monthfold[open] > summary { border-radius: var(--r-lg) var(--r-lg) 0 0; }
.monthfold[open] > .monthfold-body > .qdaycard:first-child {
  border-top-left-radius: 0; border-top-right-radius: 0;
}

/* ---------- production: a year per panel, a month per fold ---------- */

/* The month bar reads as the day bar does on the marketing lists — same rule,
   same weights — so a job looks like the same job all the way down the
   pipeline. What it adds is a chevron, because unlike a day this one opens. */
.prodmonth > summary {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 0; padding: 8px 0 5px;
  border-bottom: 1px solid var(--line-2);
  cursor: pointer; list-style: none;
}
.prodmonth > summary::-webkit-details-marker { display: none; }
.prodmonth > summary:hover span { color: var(--indigo); }
.prodmonth > summary span { font-size: 13px; font-weight: 700; color: var(--ink); }
.prodmonth > summary i {
  font-style: normal; font-size: 11.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
/* Drawn on the label rather than added as an element, so the flex row stays
   two items and the count keeps its place at the far end. */
.prodmonth > summary span::after {
  content: ''; display: inline-block; margin-left: 8px;
  width: 6px; height: 6px; vertical-align: 2px;
  border-right: 1.8px solid currentColor; border-bottom: 1.8px solid currentColor;
  transform: rotate(45deg); transform-origin: 60% 60%;
  transition: transform .15s ease;
  opacity: .55;
}
.prodmonth[open] > summary span::after { transform: rotate(-135deg); }
.prodmonth + .prodmonth { margin-top: 4px; }
.prodmonth > .jobrow:last-child { border-bottom: 0; }

/* The reviewer's note, on the row. Not a link — it is the whole of what there
   is to read, and the handout beside it is where the rest lives. */
.joblink.note { flex: 1 1 240px; max-width: 420px; min-width: 0; }
.joblink.note i {
  color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* One customer, everything on record. */
.qstage-hist { font-size: 12.5px; color: var(--nav-accent); }

.custjob { padding: 18px 0; border-bottom: 1px solid var(--line); }
.custjob:last-child { border-bottom: 0; }
.custjob:first-child { padding-top: 4px; }

/* Three things across: what the job was, where it has got to, what it came
   to. The title takes the slack so the stage and the money stay pinned to the
   right edge however long a product name runs. */
.custjob-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px 14px; margin-bottom: 16px;
}
.custjob-title { flex: 1 1 260px; min-width: 0; }
.custjob-title > a {
  display: block; font-size: 17.5px; font-weight: 700; color: var(--ink);
  line-height: 1.3; overflow-wrap: anywhere;
}
.custjob-title > a:hover { color: var(--indigo); }

/* The number the job is filed under. Mono, because it gets read out and typed
   back in, and a 1 that looks like an l costs a phone call. */
.custjob-inv { margin: 6px 0 0; font-size: 13.5px; color: var(--ink-3); }
.custjob-inv b {
  font-family: var(--mono); font-size: 13px; font-weight: 600;
  color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.custjob-inv.none { font-style: italic; }

.custjob-total {
  font-size: 24px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums; margin-left: auto; white-space: nowrap;
}

/* Below 560px the money would be pushed onto a line of its own with the stage
   chip stranded beside the title; keeping them together reads better. */
@media (max-width: 560px) {
  .custjob-total { margin-left: 0; font-size: 19px; }
}

/* The job's life as four dates. Fixed four columns so a stage not yet reached
   is visibly a gap rather than something left out. */
.custtrail { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; margin: 0; padding: 0; }
.custtrail li {
  flex: 1 1 128px; padding: 10px 13px;
  background: var(--sunken); border-radius: var(--r-sm);
  display: flex; flex-direction: column; gap: 3px;
}
.custtrail span {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.custtrail i { font-style: normal; font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.custtrail li[data-done="false"] { opacity: .5; }
.custtrail li[data-done="false"] i { color: var(--ink-3); }

.custenq { padding: 12px 0; border-bottom: 1px solid var(--line); }
.custenq:last-child { border-bottom: 0; }
.custenq-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 6px; }
.custenq-src {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-3);
}
.custenq-body { display: flex; flex-wrap: wrap; gap: 18px; margin: 0; }
.custenq-body dt {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.custenq-body dd { margin: 0; font-size: 13px; }
/* What the customer actually wrote, set apart from the fields around it. */
.custenq-msg {
  margin: 8px 0 0; padding: 8px 10px;
  background: var(--sunken); border-radius: var(--r-sm);
  font-size: 13px; color: var(--ink-2); white-space: pre-wrap;
}

/* ---------- the permission checklist on Team & Settings ----------
   One column per row rather than a wrapped grid: these are sentences with a
   consequence attached, and a reader scanning for "can they see costs" should
   not have to hunt across a row. */
.permbox {
  grid-column: 1 / -1; margin: 6px 0 0; padding: 14px 16px;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--sunken);
}
.permbox legend {
  padding: 0 6px; font-size: 12px; font-weight: 600;
  letter-spacing: .02em; text-transform: uppercase; color: var(--ink-2);
}
.permopt { display: flex; gap: 10px; align-items: flex-start; padding: 7px 0; cursor: pointer; }
.permopt + .permopt { border-top: 1px solid var(--line); }
.permopt input { margin-top: 3px; flex: none; }
.permopt span { display: flex; flex-direction: column; gap: 1px; font-size: 13.5px; color: var(--ink); }
.permopt small { font-size: 12px; color: var(--ink-2); }

/* ---------- reorder: search, brand checklist, view legend ---------- */

.searchclear {
  margin-left: 10px; font-size: 12.5px; color: var(--ink-2);
  white-space: nowrap; text-decoration: none;
}
.searchclear:hover { color: var(--s-change); }

.field-brands > span small { font-weight: 400; color: var(--ink-2); }

.brandfilter {
  width: 100%; box-sizing: border-box; margin: 0 0 6px;
  padding: 7px 11px; font-size: 13px;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
}

/* A fixed-height scroller: 129 brands must not push Recalculate off screen. */
.brandlist {
  max-height: 168px; overflow-y: auto;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface);
}
/* .brandopt is a <tr> in the matrix and a <label> in a plain list. It must be
   scoped to the label form: `display:flex` on a table row collapses the whole
   table — the header's first cell measured 534px against the body's 20px and
   every heading slid right of its column. The row form is styled with .matrix
   rules further down; only the hide and hover behaviour is shared. */
label.brandopt {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 11px; font-size: 13px; cursor: pointer;
  /* text-align is inherited from the centred panel above, which otherwise
     pushed every brand name into the middle of its row. */
  text-align: left;
}
label.brandopt + label.brandopt { border-top: 1px solid var(--line); }
label.brandopt:hover { background: var(--hover); }
label.brandopt input { flex: none; margin: 0; }
label.brandopt span { flex: 1 1 auto; text-align: left; color: var(--ink); }
label.brandopt b { font-weight: 500; font-size: 12px; color: var(--ink-2); }
.brandopt[hidden] { display: none !important; }

.brandacts {
  display: flex; align-items: center; justify-content: flex-start;
  gap: 10px; margin-top: 6px;
}
.brandcount { font-size: 12px; color: var(--ink-2); }

.brandjump { margin-left: auto; }
.brandjump select {
  padding: 7px 12px; font-size: 13px; border-radius: 999px;
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink);
  max-width: 230px;
}

/* The legend under the pills: says what the open view actually contains. */
.viewnote {
  margin: 2px 0 14px; font-size: 13px; color: var(--ink-2); line-height: 1.5;
}
.viewnote b { color: var(--ink); }

.filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* A checkbox is not a text field.
   `.field select, .field input` gives every control in the panel a background,
   a border and 9px of padding, and `.reorder-controls .field input` sets
   min-width:140px on top — which turned each tick box into a 140px-wide slab
   and pushed the brand name into the middle of its row. Checkboxes opt out. */
.reorder-controls .field .brandopt input[type="checkbox"],
.permopt input[type="checkbox"] {
  min-width: 0; width: auto; flex: none;
  margin: 0; padding: 0; border: 0; border-radius: 0;
  background: none; accent-color: var(--indigo);
}

/* ---------- the sheet-freshness bar ----------
   Always present, never a modal. Its job is to make "how old is this" the
   first thing you read, not something you have to go and check. */
.syncbar {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 10px 14px;
  border: 1px solid var(--line); border-left-width: 3px;
  border-radius: var(--r-sm); background: var(--surface);
  font-size: 13px; color: var(--ink-2);
}
.syncbar b { color: var(--ink); font-weight: 600; }
.syncbar form { margin-left: auto; }
.syncdot { width: 8px; height: 8px; border-radius: 50%; flex: none; }

.syncbar[data-tone="fresh"] { border-left-color: var(--s-appr); }
.syncbar[data-tone="fresh"] .syncdot { background: var(--s-appr); }
.syncbar[data-tone="aging"] { border-left-color: var(--s-review); }
.syncbar[data-tone="aging"] .syncdot { background: var(--s-review); }
.syncbar[data-tone="stale"] { border-left-color: var(--s-change); }
.syncbar[data-tone="stale"] .syncdot { background: var(--s-change); }
.syncbar[data-tone="busy"]  { border-left-color: var(--indigo); }
.syncbar[data-tone="busy"] .syncdot {
  background: var(--indigo); animation: syncpulse 1.1s ease-in-out infinite;
}
@keyframes syncpulse { 0%, 100% { opacity: 1 } 50% { opacity: .25 } }

@media (prefers-reduced-motion: reduce) {
  .syncbar[data-tone="busy"] .syncdot { animation: none; }
}

/* ---------- IOS type: a filter beside the brands, and a tag in the table ---------- */

.field-types { min-width: 230px; }
.field-types > span small { font-weight: 400; color: var(--ink-2); }
.typelist {
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface);
}
.typenote {
  margin: 7px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--ink-2);
  max-width: 260px; text-align: left;
}
.reorder-controls .field .typelist input[type="checkbox"] {
  min-width: 0; width: auto; flex: none;
  margin: 0; padding: 0; border: 0; border-radius: 0;
  background: none; accent-color: var(--indigo);
}

/* Color carries the meaning at a glance: Core is what we mean to hold, so it
   is the only one that reads as "normal". The rest are exceptions. */
.typetag {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; letter-spacing: .02em; white-space: nowrap;
  background: var(--sunken); color: var(--ink-2);
}
.typetag[data-t="core"]         { background: var(--s-appr-bg);   color: var(--s-appr); }
.typetag[data-t="extra"]        { background: var(--s-review-bg); color: var(--s-review); }
/* --s-prog (#4F5BD5) on --s-prog-bg measured 4.89:1 here on 24 Sep 2026 -
   the last text in the app under 5:1, and the same "colour on its own tint"
   trap the other three type tags were pulled out of in an earlier round.
   The TINT is untouched, so the tag is the same blue object; only the word
   takes the darker --indigo, giving 5.75. */
.typetag[data-t="return"]       { background: var(--s-prog-bg);   color: var(--indigo); }
.typetag[data-t="discontinued"] { background: var(--s-change-bg); color: var(--s-change); }

/* ---------- the brand × type matrix in the reorder controls ----------
   One table instead of two lists. The counts are links, so reading "Richardson
   has 108 Extra" and filtering to exactly that is the same gesture. */

.field-matrix { flex: 1 1 100%; min-width: 0; }
.field-matrix > span small { font-weight: 400; color: var(--ink-2); }

.matrixhead {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 6px;
}
.matrixhead .brandfilter { flex: 1 1 200px; margin: 0; }

.typechips { display: flex; gap: 6px; flex-wrap: wrap; }
.typechip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line-2); background: var(--surface);
  font-size: 12.5px; color: var(--ink-2); white-space: nowrap;
}
.typechip span { line-height: 1; }
.reorder-controls .field .typechip input[type="checkbox"] {
  min-width: 0; width: auto; flex: none;
  margin: 0; padding: 0; border: 0; border-radius: 0;
  background: none; accent-color: var(--indigo);
}
/* :has is what lets a ticked box color its own chip without script. */
.typechip:has(input:checked) { border-color: var(--indigo); color: var(--ink); background: var(--indigo-soft); }

.matrixwrap { max-height: 260px; overflow: auto; }
.matrix { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.matrix thead th {
  position: sticky; top: 0; z-index: 1;
  padding: 7px 10px; text-align: right; white-space: nowrap;
  background: var(--sunken); color: var(--ink-2);
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  border-bottom: 1px solid var(--line-2);
}
.matrix thead th.mx-brand, .matrix thead th.mx-pick { text-align: left; }
.matrix tbody tr + tr td, .matrix tbody tr + tr th { border-top: 1px solid var(--line); }
.matrix tbody tr:hover td, .matrix tbody tr:hover th { background: var(--hover); }
.matrix td, .matrix th { padding: 6px 10px; text-align: right; }
/* `tbody` is doing real work in this selector, so do not drop it.

   Aziz, 24 Sep 2026: "the text color of brand is not as the same of other
   column headers in the same table." Measured in the DOM, every heading in
   that row was the identical colour, rgb(26,29,38). What differed was weight:
   Pick, Total, Core, Extra, Return and Disc. were all 700 and Brand alone was
   500, and a lighter stroke at 11px reads as a paler colour - which is what he
   was seeing and reporting accurately in the only vocabulary the eye gives you
   for it.

   The cause was this rule. `th.mx-brand` is both the column heading in `thead`
   AND the row heading on all 73 brand rows in `tbody`, where 500 is correct -
   a row label must not shout over the figures beside it. Unscoped, its
   specificity (0,2,1) beat `.matrix thead th { font-weight: 700 }` (0,1,2) and
   the body weight leaked up into the header.

   Scoped to tbody rather than fixed with another override on top, so the two
   cases stop fighting over one selector. */
.matrix tbody th.mx-brand {
  text-align: left; font-weight: 500; color: var(--ink);
  max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.matrix th.mx-brand label { cursor: pointer; }
.matrix th.mx-brand em {
  margin-left: 7px; font-style: normal; font-size: 10.5px;
  color: var(--ink-3); text-transform: uppercase; letter-spacing: .04em;
}
.matrix td.mx-pick, .matrix th.mx-pick { width: 1%; text-align: left; }
.matrix td.mx-total { font-weight: 600; color: var(--ink); }
.matrix td a { color: var(--indigo); text-decoration: none; }
.matrix td a:hover { text-decoration: underline; }
/* Carries `tbody` for one reason: the rule above it now does. Scoping the brand
   cell to `tbody th.mx-brand` took it from (0,2,1) to (0,2,2), which would have
   out-specified this and quietly returned every not-stocked brand to full ink -
   a fix for the header silently undoing the signal on the rows. Matched back to
   (0,2,2) so source order decides again, as it did before. */
.matrix tbody tr[data-nostock="1"] td,
.matrix tbody tr[data-nostock="1"] th { color: var(--ink-3); }
.matrix tr[hidden] { display: none !important; }

.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- the IOS type table, beside the brand matrix ---------- */

.rc-brands { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }
.field-matrix { flex: 3 1 460px; }
.field-typetable { flex: 1 1 320px; min-width: 300px; }
.field-typetable > span small { font-weight: 400; color: var(--ink-2); }
.field-typetable .matrixwrap { max-height: none; }
.field-typetable .matrix th.mx-brand { max-width: none; white-space: normal; }
.field-typetable .matrix th.mx-brand em {
  display: block; margin: 2px 0 0 0; font-size: 10.5px; text-transform: none;
  letter-spacing: 0; color: var(--ink-3);
}
.matrix tr.mx-foot td, .matrix tr.mx-foot th {
  background: var(--sunken); font-weight: 600; color: var(--ink);
}

/* ---------- pager for the long lists ---------- */
.pager {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 10px 0; font-size: 12.5px; color: var(--ink-2);
}
.pager a { color: var(--indigo); text-decoration: none; }
.pager a:hover { text-decoration: underline; }
.pager .pagenow { color: var(--ink); font-weight: 600; }
.pager .perpick { display: inline-flex; gap: 7px; align-items: center; }
.pager .perpick b { color: var(--ink); }
.pager .csvlink {
  margin-left: auto; padding: 5px 13px; border-radius: 999px;
  border: 1px solid var(--line-2); white-space: nowrap;
}
.pager .csvlink:hover { border-color: var(--indigo); background: var(--indigo-soft); text-decoration: none; }

/* ---------- the stock-it verdict ---------- */
.verdict {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 600; white-space: nowrap;
  background: var(--sunken); color: var(--ink-3);
}
.verdict[data-v="stock"]    { background: var(--s-appr-bg);   color: var(--s-appr); }
.verdict[data-v="consider"] { background: var(--s-review-bg); color: var(--s-review); }
.verdict[data-v="lumpy"]    { background: var(--s-prog-bg);   color: var(--indigo); }

/* ---------- the custom-work breakdown ---------- */
.cbhead {
  margin: 26px 0 2px; font-size: 15px; font-weight: 700; color: var(--ink);
  display: flex; align-items: baseline; gap: 10px;
}
.cbhead small { font-size: 11.5px; font-weight: 500; color: var(--ink-2); }
.cbnote { margin: 0 0 10px; max-width: 70ch; }

/* ---------- the order sheet ---------- */
.potot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 26px;
  margin: 0 0 18px; padding: 16px 20px;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface);
}
.potot > div { display: flex; flex-direction: column; gap: 2px; }
.potot span { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-2); }
.potot b { font-size: 21px; font-weight: 700; color: var(--ink); line-height: 1.1; }
.potot b.bad { color: var(--s-change); }
.potot small { font-size: 11.5px; color: var(--ink-2); }
.potot .csvlink {
  margin-left: auto; padding: 8px 16px; border-radius: 999px;
  border: 1px solid var(--line-2); color: var(--indigo);
  font-size: 12.5px; text-decoration: none; white-space: nowrap;
}
.potot .csvlink:hover { border-color: var(--indigo); background: var(--indigo-soft); }

/* Rows for things we do not yet carry read as a proposal, not an order. */
.po-new td { background: color-mix(in srgb, var(--s-appr-bg) 45%, transparent); }
.podl { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
.podl .csvlink { margin-left: 0; }

/* ---------- column totals, inside the heading ----------
   A second header row would have to be stuck at exactly the first row's height,
   which breaks the moment a heading wraps. One cell, two lines instead, so the
   figure always travels with the label it belongs to. */
.dtable thead th .colsum {
  display: block; margin-top: 3px;
  font-size: 11px; font-weight: 700; letter-spacing: 0;
  text-transform: none; color: var(--ink);
  white-space: nowrap;
}
/* [data-sorted] without a value matches data-sorted="false" too, so the
   emphasis reserved for the column the table is actually sorted by was
   being painted on EVERY sortable column: measured 24 Sep 2026 on
   /sales?tab=sku, all six numeric totals resolved to --ink with nothing
   sorted. The signal that tells a buyer which column he ordered by had no
   way to show itself. */
.dtable thead th[data-sorted="true"] .colsum { color: var(--ink); }
.dtable thead th:not(.qnum) .colsum { font-weight: 500; color: var(--ink-2); }

/* ---------- table layout conventions ----------
   Stated once so it stops being decided per table.

   A table column sizes to its content unless something absorbs the slack. The
   IOS-type table had no such column, so its 20px checkbox cell inflated to
   167px and left a gap before every chip, while the brand table next to it —
   same markup, more columns — looked fine. The label column takes the slack in
   both, so numeric and control columns stay tight to their content. */
.matrix th.mx-brand, .matrix td.mx-brand { width: 100%; }
.matrix th.mx-pick,  .matrix td.mx-pick { width: 1px; white-space: nowrap; }
.matrix thead th, .matrix td, .matrix th { vertical-align: middle; }

/* Two panels side by side must start level. Their headers are one line each,
   the same height, whatever is in them. */
.rc-brands { align-items: stretch; }
.field-matrix, .field-typetable { display: flex; flex-direction: column; }
.fieldhead {
  display: flex; align-items: center; gap: 14px;
  min-height: 34px; margin-bottom: 6px;
}
.fieldhead em {
  font-style: normal; white-space: nowrap;
  font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-2);
}
.fieldhead .brandfilter { flex: 0 1 240px; margin: 0; }
.field-matrix .brandlist, .field-typetable .brandlist { flex: 1 1 auto; }

/* ---------- make column widths actually mean something ----------
   `table-layout: auto` treats a declared cell width as a suggestion and then
   redistributes the slack however it likes: the IOS-type table's checkbox
   column was declared at 1px and rendered at 167px, which is what put the gap
   in front of every chip. Fixed layout makes the declared widths binding, and
   the one column left undeclared — the label — absorbs the remainder. */
.matrix { table-layout: fixed; }
.matrix th.mx-pick { width: 34px; }
.matrix thead th:not(.mx-pick):not(.mx-brand) { width: 64px; }
.matrix th.mx-brand, .matrix td.mx-brand { width: auto; }
.matrix td { overflow: hidden; text-overflow: ellipsis; }

/* Both panel headers occupy exactly one line of the same height, so the two
   tables beside each other start level whatever their labels say. */
.fieldhead { height: 36px; min-height: 0; overflow: hidden; }
.fieldhead em, .fieldhead em small { white-space: nowrap; }

/* ---------- the two control panels, scoped to beat .reorder-controls .field ----------
   `.reorder-controls .field { flex: 0 1 auto }` is specificity 0,2,0 and every
   `.field-matrix` rule written to size these panels was 0,1,0 — so none of them
   ever applied and the row wrapped to a stack. Scoped to match, once, here. */
.reorder-controls .field-matrix    { flex: 3 1 520px; min-width: 0; }
.reorder-controls .field-typetable { flex: 1 1 360px; min-width: 320px; }

/* ---------- final word on the two control panels ----------
   `.rc-row { align-items: flex-end }` is right for a row of labelled inputs and
   wrong for two panels of unequal height: it hangs them from their bottoms, so
   their tables started 17px apart. Stretch, scoped so it actually wins.
   The header is one non-wrapping line: the label gives way with an ellipsis
   rather than pushing the filter onto a second line that then gets clipped. */
.reorder-controls .rc-brands { align-items: stretch; }
.reorder-controls .fieldhead {
  display: flex; align-items: center; gap: 14px;
  height: 34px; flex: none; flex-wrap: nowrap; overflow: visible;
}
.reorder-controls .fieldhead em {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.reorder-controls .fieldhead .brandfilter { flex: 0 0 230px; height: 30px; padding: 4px 11px; }

/* ---------- control panel headers: the specificity that actually wins ----------
   `.reorder-controls .field > span` is 0,3,0 and sets display:block. Every
   attempt above at `.fieldhead` (0,1,0) and `.reorder-controls .fieldhead`
   (0,2,0) lost to it silently — the flex row was never a flex row, which is why
   the filter box sat on its own line at full width. Matched at 0,3,1 here.
   Lesson worth keeping: when a rule has no effect, read the cascade before
   writing another rule. */
.reorder-controls .field > span.fieldhead {
  display: flex; align-items: center; gap: 14px;
  height: 34px; flex-wrap: nowrap; margin-bottom: 6px;
}
.reorder-controls .field > span.fieldhead em {
  min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis;
}
.reorder-controls .field > span.fieldhead .brandfilter {
  flex: 0 0 230px; width: 230px; height: 30px; margin: 0; padding: 4px 11px;
}
/* Stretch is for the two panels, not for the button beside them. */
.reorder-controls .rc-brands > .btn { align-self: flex-end; flex: none; }

/* ---------- sales reports: the date row ---------- */
.daterow { display: flex; flex-direction: column; gap: 14px; }
.presets { display: flex; flex-wrap: wrap; gap: 8px; }
.customrange { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px; }
.customrange .field { flex: 0 0 auto; }
.customrange input[type="date"] {
  background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 9px 11px; font-size: 13px; color: var(--ink); color-scheme: dark light;
}

/* ---------- checkboxes inside any matrix, and a visible selected row ----------
   The reset was scoped to `.brandopt`, which is the brand table's row class.
   The IOS-type table's rows do not carry it, so its checkboxes kept the panel's
   input styling — `min-width: 140px` plus padding and a border — inside a 34px
   fixed column with overflow hidden, which rendered them invisible. Scoped to
   the table instead of the row, so a new table cannot repeat it. */
.reorder-controls .field .matrix input[type="checkbox"] {
  min-width: 0; width: 15px; height: 15px; flex: none;
  margin: 0; padding: 0; border: 0; border-radius: 0;
  background: none; accent-color: var(--indigo); cursor: pointer;
}

/* Ticked reads as ticked before you press Recalculate: the box alone was too
   small a signal on a row this wide. */
.matrix tbody tr:has(input[type="checkbox"]:checked) > td,
.matrix tbody tr:has(input[type="checkbox"]:checked) > th {
  background: var(--indigo-soft);
}
.matrix tbody tr:has(input[type="checkbox"]:checked) th.mx-brand { font-weight: 600; }
.matrix tbody tr:has(input[type="checkbox"]:checked) > td:first-child {
  box-shadow: inset 3px 0 0 var(--indigo);
}

/* ---------- the brand jump: an input, not a 147-item select ---------- */
.brandjump { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.brandjump .brandpick {
  width: 220px; padding: 7px 12px; font-size: 13px; border-radius: 999px;
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink);
}
.brandjump .brandpick:focus { outline: none; border-color: var(--indigo); }
.brandjump .btn { padding: 7px 14px; font-size: 12.5px; }

/* The checkbox column was 34px against 46px of content — a 15px box plus 20px
   of cell padding plus the border — so every one of them was clipped by 12px.
   Sized to what it actually holds. */
.matrix th.mx-pick { width: 46px; }
.matrix td.mx-pick, .matrix th.mx-pick { padding-left: 12px; padding-right: 8px; }

/* ---------- type: legibility, measured ----------
   Three text styles were failing WCAG AA at 2.45–2.63:1 against their own
   backgrounds — the page hints, the control labels and every sortable table
   heading. All three used --ink-3, the tertiary tone, for text people read
   rather than glance at. Those three moved to --ink-2, because a heading is not
   incidental text. The tertiary token itself was darkened on 24 Sep 2026 - the
   figures this comment used to quote (5.01 / 4.88 / 4.46) are the OLD ones, and
   the last of them was under AA while the sentence said it passed. The current
   ladder is recorded on the tokens themselves at the top of this file, which is
   the only place it cannot go stale. */
.hint { font-size: 12.5px; line-height: 1.55; color: var(--ink-2); }

/* ---------- control labels are not quiet text ----------
   This is the rule Aziz was actually pointing at when he said "there are still
   colors that are too fade": the reorder strip's label row - Measure demand
   over, Order every, Lead time (days), Safety buffer (days), Round up to,
   Slow-mover cover. They measured 11.5px / 600 / --ink-2, which is 7.63:1 and
   passes AA comfortably, which is exactly why a contrast audit kept reporting
   zero failures on a page he could see was washed out.

   Darkening the token helps them, but it does not fix the mistake underneath,
   which is that they were on a secondary token at all. Everything else wearing
   --ink-2 is commentary - a caption, a sub-line, a column total, a note under
   a figure. These six are not commentary. They are the names of the six
   controls that decide every number on the page, they are the first thing read
   on arrival, and on a phone they are most of what fits above the fold. The
   name of a control gets the same ink as content.

   Listed here as a family rather than patched on the one row he screenshotted,
   because the next person to add a control panel should inherit the decision
   instead of re-litigating it. The `small` inside these labels stays secondary
   - it is genuinely an aside, and the contrast between the two is the point. */
.reorder-controls .field > span,
.memberedit .field > span,
.qfield > span,
.side-load label,
.permbox legend { color: var(--ink); }
.reorder-controls .field > span { font-size: 11.5px; }
.dtable thead th .sortlink,
.dtable thead th { color: var(--ink-2); font-size: 11.5px; }

/* Figures line up column to column: tabular numerals give every digit the same
   width, so 1,111 and 9,999 occupy the same space and a column of numbers can
   be compared by eye without reading each one. */
.dtable .qnum, .dtable td b, .matrix td, .matrix .mx-total,
.colsum, .potot b, .card .value, .tile .value {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Archivo renders heavy on macOS without this; the weight it was drawing made
   small text muddier than its color alone explains. */
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }

/* ---------- small colored labels, measured against their own tint ----------
   The state chips are 11px text on a pale tint of their own hue, which is the
   arrangement most likely to look fine and read badly: green on green measured
   3.02:1, amber 3.56, red 3.55. Darkened until each clears 4.5:1 on the exact
   background it sits on — the hue is unchanged, so the color still means what
   it meant. Chips only; the full-size status panels keep the brighter tone. */
.typetag, .verdict, .chip, .marktag {
  font-weight: 650; letter-spacing: .015em;
}
.typetag[data-t="core"],  .verdict[data-v="stock"]    { color: var(--pos); }
.typetag[data-t="extra"], .verdict[data-v="consider"] { color: var(--warn); }
.typetag[data-t="discontinued"]                       { color: var(--neg); }
/* These were literals a shade or two off --ink-2, which meant they did not
   move when the ink family was darkened on 24 Sep 2026 and would have been
   left as the greyest text on the page. */
.typetag[data-t="none"]                               { color: var(--ink-2); }

/* The type descriptions sat at 10.5px and 4.42:1 — under on both counts. */
.field-typetable .matrix th.mx-brand em { font-size: 11px; color: var(--ink-2); }

/* A flat tint rather than color-mix(): same appearance, but it resolves to a
   plain rgb() that contrast tooling can actually read. */
.po-new td { background: rgba(214, 243, 231, .5); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .po-new td { background: rgba(15, 46, 36, .55); }
}
:root[data-theme="dark"] .po-new td { background: rgba(15, 46, 36, .55); }

/* Red numerals: an out-of-stock count, a lost-sales figure. #E1445F is the
   panel/border red and measures 3.5:1 as 13.5px text on white — fine as a
   border, not as a number somebody reads. */
.bad, td.bad, b.bad { color: var(--neg); }

/* The active sidebar label was #2563EB on its own pale pill: 4.22:1. */
.nav .tw[data-on="true"], .navitem[data-on="true"] .tw, .tw { color: var(--info); }

/* ---------- amending an order quantity ----------
   The engine's number and the buyer's sit side by side, and the buyer's is the
   one that ships. Never overwrite the engine figure with theirs: the pair is
   the whole point — it is what tells you the model runs short on an item. */
/* The suggested figure stays quieter than the box beside it: the buyer's number
   is the one that ships, so it should draw the eye. */
.engineqty { color: var(--ink-2); font-variant-numeric: tabular-nums; }

.qtycell { min-width: 132px; }
.qtybox {
  width: 84px; padding: 5px 9px; text-align: right;
  font-size: 13.5px; font-variant-numeric: tabular-nums;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
}
.qtybox:focus { outline: none; border-color: var(--indigo); box-shadow: 0 0 0 3px var(--indigo-soft); }
/* An amended line reads as amended before you look at the history under it. */
.qtybox.amended { border-color: var(--s-review); font-weight: 600; }
.qtyhist {
  display: block; margin-top: 4px; text-align: right;
  font-size: 11px; line-height: 1.45; color: var(--ink-2);
}

.qtysave {
  position: sticky; bottom: 0; z-index: 3;
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px;
  margin-top: 22px; padding: 16px 20px;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--lift-2);
}
.qtysave .field { flex: 1 1 320px; min-width: 0; }
.qtysave .field input { width: 100%; }
.qtysave .hint { flex: 1 1 100%; margin: 0; }
.dtable thead th.qnum { white-space: normal; }

/* ======================================================================
   TABLES: one alignment rule, stated last so it wins
   ======================================================================
   Numbers centred, text left — Aziz, 17 Sep 2026. Numeric columns also hug
   their content (`width: 1%`) instead of sharing the table's slack equally:
   that sharing is what spread five figures across 900px on the brand summary
   and left a canyon between the brand name and its first number. The first
   text column absorbs the remainder instead. */
.dtable th.qnum, .dtable td.qnum,
.dtable thead th.qnum .sortlink,
.dtable thead th.qnum .colsum {
  text-align: center;
}
.dtable th.qnum, .dtable td.qnum { width: 1%; white-space: nowrap; }
.dtable td.qtycell { text-align: center; }
.dtable td.qtycell .qtybox { text-align: center; }
.dtable td.qtycell .qtyhist { text-align: center; }

/* Text columns read left, always — including their headings. */
.dtable th:not(.qnum), .dtable td:not(.qnum),
.dtable thead th:not(.qnum) .sortlink,
.dtable thead th:not(.qnum) .colsum {
  text-align: left;
}

/* Readability: 13.5px body text in a dense grid was the smallest thing on the
   page that people actually read line by line. */
.dtable { font-size: 14px; }
.dtable td { padding-top: 9px; padding-bottom: 9px; line-height: 1.45; }
.dtable thead th { font-size: 12px; }
.dtable thead th .colsum { font-size: 11.5px; }
.hint { font-size: 13px; }
.viewnote { font-size: 13.5px; }
.matrix { font-size: 13px; }

/* ---------- filter menus that live in a column heading ---------- */
th.colfilter { vertical-align: top; }
.colfilter-lab {
  display: block; margin-bottom: 4px;
  font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-2);
}
th.colfilter select {
  max-width: 100%; padding: 3px 6px; font-size: 11.5px;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink-2); cursor: pointer;
  text-transform: none; letter-spacing: 0; font-weight: 400;
}
th.colfilter select:hover { border-color: var(--indigo); color: var(--ink); }
.colfilter-multi {
  display: block; margin-top: 3px; font-size: 10.5px; font-style: normal;
  color: var(--indigo); text-transform: none; letter-spacing: 0;
}

/* A type chip that filters reads as a control, not a label. */
a.typetag { text-decoration: none; cursor: pointer; }
a.typetag:hover { filter: brightness(.92); text-decoration: underline; }

/* Superseded — the single .dtable.compact rule lives further down. `width:
   auto` never shrank to fit, and keeping a second rule here meant two blocks
   setting the same three properties, with the later one silently cancelling
   this one's min-width. */
/* (the cap that never bound; the sizing now lives with the single rule below) */

/* The save bar is sticky to the bottom and the table header is sticky to the
   top; on the order sheet they meet. The header sits at z-index 3, so the bar
   has to clear it or column names print through the note field. The form also
   reserves room underneath, or the bar covers its own last rows. */
.qtyform { padding-bottom: 8px; }
.qtysave { z-index: 12; }

/* ======================================================================
   Summary tables size to content — deterministically
   ======================================================================
   `width: auto` did not shrink-to-fit here and `max-width` on a cell is
   advisory in auto table layout, so the brand column kept 835px of a 1176px
   table. `width: max-content` is not advisory: the table is exactly as wide as
   its widest row needs, capped at the container.

   The floor matters as much as the cap. An earlier version set `min-width: 0`
   and the table then shrank to the narrowest thing it could render: 726px
   inside an 839px container, every figure column at its minimum and every
   heading broken over three lines. Sizing to content is right; collapsing
   below what the content reads well at is not. */
.dtable.compact {
  /* `fixed`, because under `auto` nothing here binds. The cap was tried as
     `max-width: 260px` on the cell (ignored outright) and then as
     `width: 230px` (treated as a preference — surplus width is still handed to
     the widest column, so the brand column went on rendering at 323px, 38% of
     the table, with seven figure columns sharing what was left). Under fixed
     layout a declared width IS the width, and the remainder is split evenly
     across the columns that declare none. */
  table-layout: fixed;
  width: 100%;
  /* Capped, because the original complaint was the opposite failure: six
     columns stretched across 1500px put a canyon between a brand and its
     first figure. */
  max-width: 1080px;
  min-width: min(840px, 100%);
}
.tablewrap > table.compact { min-width: min(840px, 100%); }

/* The name column takes a fixed share and the figures divide the rest equally,
   whether there are five of them or seven. */
.dtable.compact th:first-child { width: 230px; }
/* `.dtable th.qnum` declares `width: 1%` to collapse figure columns under auto
   layout. Under fixed layout that 1% would be taken literally — eight-pixel
   columns — so it is handed back to the even split. */
.dtable.compact th.qnum, .dtable.compact td.qnum { width: auto; }
.dtable.compact th:not(.qnum), .dtable.compact td:not(.qnum) { max-width: none; }

/* Headings wrap; they do not clip.
   A `white-space: nowrap` was added here on the belief that the headings were
   three lines deep. They were not — that reading came from dividing the cell
   height by the line height without subtracting the padding. What nowrap
   actually did, once the columns became a fixed 87px, was truncate "Selling
   days" to "SELLING DA". A heading that wraps to two lines is readable; one
   that is cut off mid-word is not.

   The total underneath stays on its own line and never wraps, because a
   thousands separator is not a break opportunity worth taking. */
.dtable.compact thead th .colsum { white-space: nowrap; }

/* Column headings read as headings: --ink-2 is right for a caption and too
   quiet for the label on a column of figures. */
.dtable thead th,
.dtable thead th .sortlink,
.matrix thead th {
  color: var(--ink);
  font-weight: 600;
}
.dtable thead th .colsum { color: var(--ink-2); font-weight: 600; }

/* ---------- draggable column widths ---------- */
.dtable th { position: relative; }
.dtable th .grip {
  position: absolute; top: 0; right: -3px; width: 7px; height: 100%;
  cursor: col-resize; user-select: none; touch-action: none;
  background: transparent; border: 0; padding: 0; z-index: 4;
}
.dtable th .grip::after {
  content: ""; position: absolute; top: 20%; bottom: 20%; left: 3px;
  width: 1px; background: var(--line-2); opacity: 0; transition: opacity .12s ease;
}
.dtable th .grip:hover::after,
.dtable th .grip:focus-visible::after { opacity: 1; background: var(--indigo); }
.dtable th .grip:focus-visible { outline: 2px solid var(--indigo); outline-offset: -2px; }
body.col-resizing { cursor: col-resize; user-select: none; }

/* ---------- column filter: the funnel and its panel ---------- */
th.colfilter { position: relative; }
.colfilter-head { display: flex; align-items: center; gap: 6px; }
.colfilter-lab {
  font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink); white-space: nowrap;
}
.filtbtn {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 4px; border: 1px solid transparent; border-radius: var(--r-sm);
  background: none; color: var(--ink-2); cursor: pointer; line-height: 1;
}
.filtbtn:hover { color: var(--indigo); border-color: var(--line-2); background: var(--surface); }
.filtbtn i {
  font-style: normal; font-size: 10px; font-weight: 700;
  padding: 0 4px; border-radius: 99px; background: var(--indigo); color: #fff;
}
th.colfilter.on .filtbtn { color: var(--indigo); }
th.colfilter.on .colfilter-lab { color: var(--indigo); }

.filtpop {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 30;
  width: 236px; max-width: 78vw; padding: 10px;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--lift-3);
  text-transform: none; letter-spacing: 0; font-weight: 400;
}
.filtpop.flip { left: auto; right: 0; }
.filtsearch {
  width: 100%; box-sizing: border-box; margin-bottom: 7px;
  padding: 6px 9px; font-size: 13px;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
}
.filtopts { max-height: 216px; overflow-y: auto; margin-bottom: 8px; }
.filtopt {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 4px; font-size: 13px; color: var(--ink); cursor: pointer;
  border-radius: var(--r-sm);
}
.filtopt:hover { background: var(--hover); }
.filtopt[hidden] { display: none !important; }
.dtable th .filtopt input[type="checkbox"] {
  width: 14px; height: 14px; min-width: 0; margin: 0; padding: 0;
  border: 0; background: none; accent-color: var(--indigo); flex: none;
}
.filtacts { display: flex; gap: 8px; justify-content: flex-end; }
.filtacts .btn { padding: 5px 12px; font-size: 12.5px; }

/* ---------- the last two small labels on a tint of their own hue ----------
   Same fault as the type chips: 11.5px and 10.5px text at 2.94:1 and 3.06:1
   against their own pale background. Darkened to the same set already proven
   against these tints, hue unchanged, so a red chip still reads as a warning. */
.chip.changes_required { color: var(--neg); }
.chip.ready_for_review  { color: var(--warn); }
.chip.approved          { color: var(--pos); }
.chip.new, .chip.on_hold, .chip.cancelled { color: var(--ink-2); }
.marktag.lowqty { color: var(--warn); }
.marktag.nologo { color: var(--neg); }

/* Both were also the smallest text on the page. */
.chip { font-size: 12px; font-weight: 600; }
.marktag { font-size: 11px; font-weight: 600; }

/* .chip.new / .on_hold / .cancelled had a dark override of their own pinned to
   the old --ink-2 literal; they now use the token, which inverts itself, so the
   override is gone rather than left to drift. .in_progress keeps its own pair:
   #3A46B8 was picked to clear AA on the pale progress tint and --indigo (#4F5BD5)
   is lighter, so pointing it at the token would have been a regression. */
.chip.in_progress { color: #3A46B8; }
:root[data-theme="dark"] .chip.in_progress { color: #8B95F0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .chip.in_progress { color: #8B95F0; }
}

/* The count badge sits on --indigo, which is a DARK blue in light mode and a
   LIGHT one in dark mode. White text works on the first and not the second. */
.filtbtn i { color: #fff; }
:root[data-theme="dark"] .filtbtn i { color: #101218; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .filtbtn i { color: #101218; }
}

/* ---------- only these / everything except ---------- */
.filtmode {
  display: flex; gap: 4px; margin-bottom: 8px; padding: 3px;
  background: var(--sunken); border-radius: var(--r-sm);
}
.filtmode label {
  flex: 1 1 0; display: flex; align-items: center; justify-content: center;
  padding: 4px 6px; border-radius: calc(var(--r-sm) - 2px);
  font-size: 11.5px; color: var(--ink-2); cursor: pointer; text-align: center;
}
.filtmode input { position: absolute; opacity: 0; pointer-events: none; }
.filtmode label:has(input:checked) {
  background: var(--surface); color: var(--ink); font-weight: 600;
  box-shadow: var(--lift-1);
}
.filtmode label:has(input:focus-visible) { outline: 2px solid var(--indigo); outline-offset: 1px; }

/* An excluding filter is marked differently from a narrowing one: the count
   alone would read as "3 brands shown" when it means "3 brands removed". */
.filtbtn i.ex { background: var(--s-change); }
th.colfilter.ex .filtbtn, th.colfilter.ex .colfilter-lab { color: var(--s-change); }

/* An excluding filter never reads as a narrowing one. */
.brandcount.excl { color: var(--neg); font-weight: 600; }

/* A ticked row that is being REMOVED is struck through, so the checklist cannot
   be read as "these are the ones shown". */
.matrix tbody tr.row-excluded th.mx-brand { text-decoration: line-through; opacity: .75; }
.matrix tbody tr.row-excluded > td, .matrix tbody tr.row-excluded > th { background: var(--s-change-bg); }
.matrix tbody tr.row-excluded > td:first-child { box-shadow: inset 3px 0 0 var(--neg); }

/* The excluding column heading was 4.04:1 on white — the panel red is a border
   color, not a text color. */
th.colfilter.ex .filtbtn, th.colfilter.ex .colfilter-lab { color: var(--neg); }
/* --on-accent is white in light mode and the dark ground in dark mode, so one
   rule serves both themes instead of a pair that can drift. */
.filtbtn i.ex { background: var(--neg); color: var(--on-accent); }

/* ---------- the sales date range, on one line ---------- */
.customrange { align-items: center; gap: 12px; }
.daterange {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13.5px; color: var(--ink);
}
.daterange b { font-weight: 600; color: var(--ink); white-space: nowrap; }
.daterange input[type="date"] { margin: 0; }

/* ───────────────────────────────────────────────────────── Item Lookup ──
   One SKU: how it sells, who has it, what to offer instead.

   The palette carries the whole meaning of the availability column, so it is
   defined once here as tokens and used by both the badge and the item header.
   Ranks match alt_availability(): 3 on our shelf, 2 in transit, 1 vendor
   only, 0 nobody. */
.lookup .lk-search { margin-bottom: 14px; }
.lk-searchrow { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.lk-field { display: flex; flex-direction: column; gap: 5px; min-width: 150px; }
.lk-field > span { font-size: 12px; font-weight: 600; color: var(--ink-2); }
.lk-grow { flex: 1 1 320px; }
.lk-field input, .lk-field select {
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 9px 11px;
  font: inherit; font-size: 13.5px; color: var(--ink);
}
.lk-field input:focus, .lk-field select:focus { outline: none; box-shadow: var(--ring); }
.lk-check {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 11px; font-size: 12.5px; color: var(--ink-2);
  max-width: 62ch;
}
/* The box aligns with the first line of the label, not the middle of a
   two-line one. */
.lk-check input[type="checkbox"] { margin-top: 1px; flex: none; }
.lk-check small {
  display: block; margin-top: 3px; font-size: 11.5px;
  line-height: 1.5; color: var(--ink-3);
}

/* The item itself. A heading and its state on one line, because "Richardson
   112, nobody has it" is one fact and reads worse split over two rows. */
.lk-itemhead {
  display: flex; flex-wrap: wrap; gap: 12px;
  align-items: flex-start; justify-content: space-between;
}
.lk-itemhead h2 { margin: 0; font-size: 19px; letter-spacing: -.01em; }
.lk-itemsub { margin: 4px 0 0; font-size: 12.5px; color: var(--ink-2); }
.lk-itemstate {
  padding: 6px 12px; border-radius: 999px;
  font-size: 12.5px; font-weight: 600; white-space: nowrap;
}
/* Same four tint pairs as .lk-avail below - the item state and the alternate
   rows are the same judgement about the same thing and must not be two
   different greens. */
.lk-itemstate[data-rank="3"] { background: var(--s-appr-bg);   color: var(--pos); }
.lk-itemstate[data-rank="2"] { background: var(--indigo-soft); color: var(--info); }
.lk-itemstate[data-rank="1"] { background: var(--s-review-bg); color: var(--warn); }
.lk-itemstate[data-rank="0"] { background: var(--s-change-bg); color: var(--neg); }

.lk-facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px; margin-top: 16px;
}
.lk-fact {
  display: flex; flex-direction: column; gap: 2px;
  padding: 11px 13px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--ground);
}
.lk-factlabel { font-size: 11.5px; font-weight: 600; color: var(--ink-2); }
.lk-fact > b { font-size: 23px; font-weight: 700; color: var(--ink);
               font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.lk-factnote { font-size: 11px; color: var(--ink-3); }

/* Tables. Product and color are names and stay left; every figure is centred
   under its heading, which is what the rest of the reports do. */
.lookup .reptable td, .lookup .reptable th { vertical-align: top; }
/* The SKU under a product name is not decoration - it is the string the
   buyer copies into a vendor site. Table DATA reads at secondary strength
   at the quietest, never tertiary. */
.lk-sku {
  display: block; margin-top: 2px;
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--ink-2); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.lk-size { font-size: 11px; color: var(--ink-3); }
.lk-days { display: block; font-size: 11px; color: var(--ink-3); font-weight: 400; }
.lk-na { color: var(--ink-3); }
.lk-up   { color: var(--neg); }
.lk-down { color: var(--pos); }
/* A de-emphasised row is still a row of figures somebody may need to read. */
.lk-quiet td { color: var(--ink-2); }

.lk-avail {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
}
/* The availability pill used to carry a fifth palette of its own - eight
   light values and eight dark ones that matched nothing else on the page,
   so "in stock" here and "core" two panels down were different greens.
   It now wears the same tint pairs as every other chip in the app, which
   also collapses sixteen rules into four: the tints already have their own
   dark values, so the theme block does the inverting. */
.lk-avail[data-rank="3"] { background: var(--s-appr-bg);   color: var(--pos); }
.lk-avail[data-rank="2"] { background: var(--indigo-soft); color: var(--info); }
.lk-avail[data-rank="1"] { background: var(--s-review-bg); color: var(--warn); }
.lk-avail[data-rank="0"] { background: var(--s-change-bg); color: var(--neg); }
.lk-covers {
  display: block; margin-top: 3px;
  font-size: 11px; font-weight: 600; color: var(--pos);
}
.lk-fit { font-weight: 700; color: var(--ink-2); }
.lk-empty { margin: 4px 0 0; }
.lk-blank h3 { margin-top: 0; }


/* The watchlist that stands in for a blank search. The cover picker sits beside
   the heading rather than under it: it changes what the list means, so it reads
   as part of the title, not as a control bar over the table. */
.lk-watchhead {
  display: flex; flex-wrap: wrap; gap: 10px;
  align-items: flex-start; justify-content: space-between;
}
.lk-watchhead h3 { margin-top: 0; }
.lk-coverpick { flex: none; }

/* The ad-hoc window picker on the assortment view. Labelled, because the
   control that looks like it above this one saves for everyone and this one
   does not — the difference has to be visible, not just true. */
.nswindow { align-items: center; margin: 10px 0 6px; }
.nswindow-label { margin: 0; flex: none; }

/* ────────────────────────────────────────────────── Ask for a report ──
   A sentence in, a report out. The box is the page, so it is sized like the
   thing you came to use rather than like a filter on something else. */
.ask-row { display: flex; gap: 10px; align-items: stretch; flex-wrap: wrap; }
.ask-input {
  flex: 1 1 420px; min-width: 0;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 13px 15px;
  font: inherit; font-size: 15px; color: var(--ink);
}
.ask-input::placeholder { color: var(--ink-3); }
.ask-input:focus { outline: none; box-shadow: var(--ring); border-color: var(--indigo); }
.ask-row .btn { flex: none; padding-inline: 20px; font-size: 14px; }

.ask-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.ask-chip {
  padding: 5px 11px; border-radius: 999px; font-size: 12.5px;
  background: var(--raised); border: 1px solid var(--line); color: var(--ink-2);
}
.ask-chip b { color: var(--ink); font-weight: 600; }
.ask-chip-main {
  background: var(--indigo); border-color: var(--indigo);
  color: var(--on-accent); font-weight: 600;
}
.ask-ignored { border-left: 2px solid var(--line-2); padding-left: 10px; }
.ask-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }

/* Near neighbours, offered rather than explained: the label carries the name
   and the line under it carries the difference. */
.ask-alts, .ask-examples {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px;
}
.ask-alt {
  display: flex; flex-direction: column; gap: 3px;
  padding: 12px 14px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--ground);
  text-decoration: none; color: inherit;
}
.ask-alt:hover { border-color: var(--ink-3); background: var(--hover); }
.ask-alt b { font-size: 13.5px; color: var(--ink); }
.ask-alt span { font-size: 12px; color: var(--ink-2); line-height: 1.45; }

.ask-example {
  padding: 11px 13px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--ground); font-size: 13px; color: var(--ink-2);
  text-decoration: none; line-height: 1.45;
}
.ask-example:hover { border-color: var(--indigo); color: var(--ink); }
/* NOT a code. "not in catalogue" and "lost sale events" are English you say to
   the box at the top of this page, not a string you type character by
   character. It was in the mono face, which made a plain phrase look like a
   machine token and put a third typeface inside an ordinary table row. It is
   language, so it takes the UI face at the size of the cell beside it. */
.ask-say { font-family: var(--sans); font-size: 13px; color: var(--ink-2); }

/* The figures that answer the question on the page it was asked on. Sized
   below the heading numbers elsewhere in the app: this is a confirmation that
   the question was read correctly, not the report itself. */
.ask-figures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin: 14px 0 2px;
}
.ask-figure {
  display: flex; flex-direction: column; gap: 2px;
  padding: 11px 13px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--ground);
}
.ask-figlabel { font-size: 11.5px; font-weight: 600; color: var(--ink-2); }
.ask-figure > b {
  font-size: 21px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.ask-fignote { font-size: 11px; color: var(--ink-3); }

/* The message to the customer. A textarea rather than a block of text: it is
   meant to be edited before it goes, and editing in place beats copying,
   pasting and then fixing it somewhere else. */
.ask-msg {
  width: 100%; resize: vertical; margin-top: 4px;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 12px 14px;
  font: inherit; font-size: 13.5px; line-height: 1.6; color: var(--ink);
}
.ask-msg:focus { outline: none; box-shadow: var(--ring); }

/* Understood but unusable — louder than the ignored-words note, because the
   report will silently answer a broader question than the one asked. */
.ask-dropped { margin-top: 12px; line-height: 1.6; }

/* The period-comparison switch and the change cells it turns on. */
.cmptoggle { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 2px 0 6px; }
.cmpnote { margin: 0; }
.delta { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.delta[data-dir="up"]   { color: var(--pos); }
.delta[data-dir="down"] { color: var(--neg); }
.delta[data-dir="flat"] { color: var(--ink-3); font-weight: 500; }

/* ======================================================================
   Frozen headings on every report table
   ======================================================================
   Aziz, 19 Sep 2026: "table headers should be freeze for all the reports."
   `.dtable.sticky` already did this; `.reptable` and `.invtable` did not, so
   the Ask page, Item Lookup and Inventory Check lost their column names as
   soon as a list ran past a screen — which is exactly when a column of bare
   figures stops meaning anything.

   The fill has to be opaque. `--sunken` is translucent in dark mode and the
   digits of the row underneath show through the heading as it passes. */
.reptable thead th,
.invtable thead th,
table.dtable:not(.sticky) thead th {
  position: sticky; top: 0; z-index: 3;
  background: var(--surface);
  box-shadow: inset 0 -1px 0 var(--line);
}

/* A panel clips a sticky child to its own rounded corner unless the heading is
   allowed to sit at the very top of the scrolling box. */
.reptable thead th:first-child { border-top-left-radius: inherit; }
.reptable thead th:last-child  { border-top-right-radius: inherit; }

/* Totals under a result table. Heavier than a row and separated from them, so
   it reads as a summary rather than one more line of data. */
.reptable tfoot td {
  border-top: 2px solid var(--line-2); border-bottom: 0;
  font-weight: 700; color: var(--ink); padding-top: 11px;
}
.tfootnote {
  display: block; font-size: 11px; font-weight: 500; color: var(--ink-3);
}

/* ======================================================================
   A long SKU must not decide the width of every other column
   ======================================================================
   Measured on /sales?tab=sku&brands=Richardson&style=112, 19 Sep 2026:

     SKU    237px   max-content 117px
     Item   108px   max-content 351px, wrapping to 5 lines, rows 116px tall
     table 1011px   inside an 788px box, so Dropship onwards was cut off

   The cause is one row. `RIC-112-Mink_Beige/Charcoal/Amber_Gold` is 38
   characters with no break opportunity a browser will take by default, so the
   column's *minimum* was 237px. Under auto layout a column can never go below
   its minimum, so that one code pushed the table 223px past its container and
   took the room out of the item description, which is the column people
   actually read.

   Letting the code break fixes both ends at once: the minimum drops to a few
   characters and the slack returns to the description. `anywhere` rather than
   `break-word` because it counts for min-content, which is the number that
   was doing the damage. */
.dtable td code,
.reptable td code,
.invtable td code { overflow-wrap: anywhere; }

/* The description column asks for the remainder. Columns carrying figures
   already hug their contents (`width: 1%`), and the SKU and brand columns are
   bounded below, so this is the only one with an appetite. It is a preference,
   not a cap: auto layout will still give it less when the window is narrow. */
.dtable td.item, .dtable th.item,
.reptable td.item, .reptable th.item { width: 34%; min-width: 220px; }

/* Breaking the code must not send it down five lines either: 38 characters in
   a 74px column made the tallest row on the page taller than the one it
   replaced. A floor of 132px holds a normal SKU on one line and the longest
   colorway names on two. */
.dtable.sticky th:first-child,
.dtable.sticky td:first-child { min-width: 132px; }

/* ======================================================================
   The brand filter lives in the Brand column
   ======================================================================
   Aziz, 24 Sep 2026: "apply a filter on the Brand column." It used to sit on
   the label line above the panel, where it read as a control over the whole
   field rather than over the one column it narrows.

   The heading is already `position: sticky` inside the 168px scroller, so the
   box stays put while 73 brands scroll under it — which is the only reason
   putting it here works at all.

   24 Sep 2026, second pass. Aziz: "can we move the filter brand field next to
   the heading - Brand?" Stacked, the two of them made a 68.1px header band
   against the 32.5px one on the type table beside it, so the brand list
   started a row and a half lower than the type list and the two panels only
   looked level at their outer edges. On one line the band is 36px and the two
   lists start at the same height.

   Why this is done with inline boxes and not flexbox, which is the obvious
   way to put two things on a line: `display: flex` on a `<th>` stops it being
   a table-cell. The browser wraps it in an anonymous cell, the declared column
   widths below stop binding, and `position: sticky` loses the containing block
   that makes it stick. The header would have stayed put visually and come
   apart structurally. Inline-block keeps the cell a cell.

   The input is sized in percent, not pixels, because this column is the one
   that absorbs the table's slack — a fixed width would either overflow at the
   narrow end or sit short of the edge at the wide one. It is 129.5px at 1440,
   where the two panels sit side by side, and the column is 197.5px.

   It needs a ceiling as well as a floor, which measuring found and reading the
   CSS would not have. Below about 1300 the two panels stop sharing the row and
   the brand panel goes full width: the column becomes 520px and an uncapped
   box rendered at 452px, which stops reading as a filter and starts reading as
   somewhere to type a paragraph. 260px is about as wide as the longest brand
   in the list — "Yupoong / FlexFit / YP Classics" — and a box does not need to
   be wider than the longest thing that will ever be typed into it.

   The floor is `min(84px, 100%)` rather than a flat 84px for the phone case.
   Fixed table layout gives the declared columns their pixels first and hands
   this one whatever is left, and on a 390px screen there is nothing left — the
   column computes to 0 and the table scrolls sideways instead. A flat floor
   then holds an 84px box inside a 0px cell, so it spilled across Total and
   Core and got cut in half by the overflow rule. `min()` lets the floor give
   way when there is no room for it. NOTE: the brand column collapsing to zero
   at phone width is older than this rule and is still true — see the handover
   note, it needs a decision about what this table does on a phone, not a
   patch here. */
.matrix thead th.mx-brand { white-space: nowrap; overflow: hidden; }
.matrix thead th.mx-brand .mx-brandhead {
  display: inline-block; vertical-align: middle; margin: 0;
}
.matrix thead th.mx-brand .brandfilter {
  display: inline-block; vertical-align: middle;
  width: calc(100% - 48px); min-width: min(84px, 100%); max-width: 260px;
  height: 24px; margin: 0 0 0 9px; padding: 2px 8px;
  font-size: 11.5px; font-weight: 400; letter-spacing: 0; text-transform: none;
  line-height: 18px;
  background: var(--surface);
}
/* The background is here because the box sat on --raised inside a header band
   painted --sunken, which is 1.09:1 - no edge at all - leaving a 1px --line-2
   border at 1.27:1 to say on its own that this is somewhere you can type. On
   --surface it reads as a well sunk into the band in light mode and, because
   --surface is darker than --sunken in dark mode, as a well there too: the
   same convention in both themes.

   Its placeholder was the other half of the problem, and it turned out not to
   be this box's problem alone - see "placeholders belong to the palette" up at
   the top of this file. */

/* Both header bands are pinned to the same height rather than being padded
   until they happen to agree. Padding agreement is what broke the first time:
   it holds only while the taller side's content stays exactly the height it
   was, and the taller side here contains a form control, whose height moves
   with the platform's font. A stated height is a table-cell minimum, so each
   band is 36px whatever is inside it, and the two lists start level. */
.field-matrix .matrix thead th,
.field-typetable .matrix thead th {
  height: 36px; padding-top: 4px; padding-bottom: 4px;
}

/* ======================================================================
   Lost sales, split in two
   ======================================================================
   Aziz, 24 Sep 2026, on why the split exists: being out of stock on something
   we hold is a buying failure; being out on something we do not hold is an
   assortment one. Three quarters of the units lost are the second kind, so
   listed together the hundred items a buyer can fix sit under fourteen
   hundred he never had.

   The three cards are a filter, not a summary — each one narrows the tables
   below it, which is why the chosen one is outlined rather than merely
   colored. */
.lostsplit {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px; margin: 14px 0 20px;
}
@media (max-width: 900px) { .lostsplit { grid-template-columns: minmax(0, 1fr); } }
.lostcard {
  display: block; padding: 14px 16px; text-decoration: none;
  border: 1px solid var(--line-2); border-radius: var(--r-md);
  background: var(--surface); color: var(--ink);
}
.lostcard:hover { border-color: var(--indigo); text-decoration: none; }
.lostcard.on {
  border-color: var(--indigo); box-shadow: inset 0 0 0 1px var(--indigo);
  background: var(--indigo-soft);
}

/* The split is the whole point of these three cards and it was carried only by
   the wording: three identical cards, three identical figures, and the reader
   had to finish the sentence to learn that one of them is the one he can fix
   today. A 3px edge says it before the sentence does - red for the buying
   failure, amber for the assortment question, nothing for the total, which is
   not a verdict. Inset, so the bar costs no layout and the selected and
   unselected cards keep their text on the same pixel.

   Keyed off the href because the bucket already lives there; there is no class
   to hang it on and inventing one would mean editing reorder.php for a border.
   If the query parameter is ever renamed, rename it here too. */
.lostcard[href*="bucket=stocked"]      { box-shadow: inset 3px 0 0 var(--neg); }
.lostcard[href*="bucket=unstocked"]    { box-shadow: inset 3px 0 0 var(--warn); }
.lostcard.on[href*="bucket=stocked"]   { box-shadow: inset 3px 0 0 var(--neg),  inset 0 0 0 1px var(--indigo); }
.lostcard.on[href*="bucket=unstocked"] { box-shadow: inset 3px 0 0 var(--warn), inset 0 0 0 1px var(--indigo); }
.lostcard-h {
  display: block; font-size: 11.5px; font-weight: 600; color: var(--ink-2);
  text-transform: uppercase; letter-spacing: .04em;
}
.lostcard b {
  display: block; margin: 4px 0 1px; font-size: 26px; font-weight: 700;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.lostcard-s { display: block; font-size: 12px; color: var(--ink-2); }
.lostcard em {
  display: block; margin-top: 7px; font-style: normal;
  /* --ink-3 on the selected card's indigo wash measured 4.42:1 on 24 Sep 2026,
     under AA. --ink-2 on the same wash is 5.43. This line is the one that says
     whether the bucket is a buying problem or an assortment one, so it is read,
     not glanced at. */
  font-size: 11.5px; line-height: 1.5; color: var(--ink-2);
}
.losthead {
  margin: 26px 0 2px; font-size: 15px; font-weight: 650; letter-spacing: -.01em;
}

/* ======================================================================
   Column heading vs column total, and finding your row
   ======================================================================
   Stated last, for the same reason the alignment rule above is stated last:
   these two facts about a table heading were being decided in four places.

   1. The label and the total were the same thing to look at.
      Measured 24 Sep 2026 on /sales?tab=sku at 1440px: the word "UNITS" and
      the figure "47,810" underneath it both resolved to 11.5px / 600 /
      #1A1D26 - identical in size, weight and colour. Eleven columns of that
      is twenty-two pieces of type with no order to them, which is why the
      heading band reads as noise however carefully the words were chosen.

      A label names a column; a total is a fact about it. They are different
      jobs and now look different: the label is a small structural caption in
      full ink, the total is a figure - larger, quieter, tabular, so a column
      of them can be compared down the header row the way the cells below can.

      Sizes settled at 12px for both once the uppercase came off (see the block
      below): sentence case at 12px is NARROWER than caps at 11px with .05em
      tracking, so the heading band grew by about 1.5px in height and lost width
      - and it is one band per table, not per row. Density is the feature here;
      legibility is not paid for with rows. */
.dtable thead th .sortlink,
.dtable thead th > a {
  font-size: 12px; font-weight: 700; letter-spacing: 0; color: var(--ink);
}
.dtable thead th .colsum {
  font-size: 12px; font-weight: 600; letter-spacing: 0;
  color: var(--ink-2); font-variant-numeric: tabular-nums;
}
/* The sorted column is the one exception: its total is the number the buyer
   ordered the table by, so it keeps full ink. See the [data-sorted="true"]
   fix further up - before that, every column claimed to be the sorted one. */
.dtable thead th[data-sorted="true"] .sortlink { font-weight: 800; }
.dtable thead th[data-sorted="true"] .colsum   { color: var(--ink); }

/*  2. On a table whose rows are not the same height, the row is hard to hold.
      Measured 24 Sep 2026: rows on /sales?tab=sku render at 57.6px, 58.1px
      and 77.7px depending on whether the SKU wraps - a 20px swing, so the eye
      cannot fall into a rhythm and carry a figure back to its item. Trimming
      the SKU would fix the rhythm by hiding the one string a buyer copies, so
      the pitch stays and the row gets a highlight instead.

      Only rows with data-href had one, because only those were clickable.
      Reading a row and clicking it are different acts and both need to know
      where the row is. */
.dtable tbody tr:hover td { background: var(--hover); }
/* Urgent rows keep their own tint on hover - losing it would mean the row
   stopped being urgent at the moment somebody looked straight at it. */
.dtable tbody tr[data-urgent="true"]:hover td {
  background: color-mix(in srgb, var(--s-change) 18%, transparent);
}
/* The frozen first column is painted --surface so scrolled columns cannot show
   through it, and that fill is more specific than the hover above. Without
   these two lines the row lights up and its SKU cell stays white, which breaks
   the row in exactly the place it is being read from. */
.dtable.sticky tbody tr:hover td:first-child { background: var(--hover); }
.dtable.sticky tbody tr[data-urgent="true"]:hover td:first-child {
  background: color-mix(in srgb, var(--s-change) 18%, var(--surface));
}

/* ======================================================================
   Sentence case everywhere a label names something
   ======================================================================
   Aziz, 24 Sep 2026: "Most fields specially the column headers have all caps
   font, change to proper case."

   The labels were never written in capitals - the PHP says "Selling days",
   "On hand", "Last sold". Forty-four CSS rules were shouting them. Turning the
   transform off in one place, rather than editing forty-four rules, is what
   makes this reviewable and reversible: one block to read, one block to delete
   if he changes his mind.

   Three things travel with it, and all three are necessary:

   1. The tracking goes. Letter-spacing between .02em and .12em was there to
      stop capitals closing up. On sentence case it opens words into loose
      strings of letters, which is worse than the caps were.

   2. The hierarchy has to be put back. Capitals were doing the work of
      separating a label from the data under it; removing them without
      replacing that signal would leave a heading that reads as just another
      cell. Weight and full ink do it instead - which is also the cheaper
      signal, because it costs no width and no height.

   3. Column widths were measured after, not assumed. Sentence case is
      narrower than caps with tracking, so headings that previously forced a
      column wider now fit - the change can only reduce table width, never add
      to it.

   What keeps its capitals: the short badge tokens (YOU, URGENT, SUSPENDED,
   EDITED, and the quote source tag) and the sign-in button. Those are not
   labels naming a field - they are one-word marks where the capitals ARE the
   shape, and set in sentence case they stop reading as badges at all. */
.dayhead h3,
.panel h3,
.memberedit .field > span,
.sectionbar,
.handout-row.asrow .ref,
.reporttable th,
.donut-sub,
.own-head,
.threadhead,
.details dt,
.gbar,
.qblock-bar,
.qfield > span,
.qhead,
table.quote thead th,
.colhead,
.brandlabel,
.reorder-controls .field > span,
.dtable th,
.dtable thead th,
.dtable thead th .sortlink,
.dtable thead th .colsum,
.searchsec h2,
.rowhead,
.deptspark-head span,
.gcard-head span,
.invtable th,
.invchecked-head h4,
.joblink span,
.monthfold > summary,
.custtrail span,
.custenq-body dt,
.permbox legend,
.matrix thead th,
.matrix th.mx-brand em,
.potot span,
.fieldhead em,
.colfilter-lab,
.lostcard-h {
  text-transform: none;
  letter-spacing: 0;
}

/* Hierarchy, restored without capitals and without extra space.

   A column heading and the figures beneath it are now told apart by weight and
   ink alone, so both have to be unambiguous: 700 against the 400 of a data
   cell, and full --ink against the --ink-2 of a column total. Tested on
   /sales?tab=sku and /reorder, where a heading band of eleven and fifteen
   columns is the hardest case in the app. */
.dtable thead th,
.matrix thead th,
.reporttable th,
.invtable th,
table.quote thead th {
  font-weight: 700; color: var(--ink);
}
/* The filter caption above a column heading stays a caption: same sentence
   case, but secondary ink and normal weight, or two labels compete inside one
   cell. */
.colfilter-lab { font-weight: 600; color: var(--ink-2); }

/* Section and panel headings carried .06em to .12em tracking for their
   capitals. Without the capitals that tracking reads as a gap between every
   letter, so they take a slight negative instead - the same treatment the
   large figures in this file already use. */
.panel h3,
.sectionbar,
.gbar,
.qblock-bar,
.searchsec h2,
.dayhead h3,
.qhead,
.monthfold > summary,
.invchecked-head h4 {
  letter-spacing: -.005em;
}

/* ======================================================================
   The green "go" pill  —  .btn.pill-go
   ======================================================================
   Aziz, 24 Sep 2026: "Refresh Now button should be in light green pill with
   green text as you have for on our shelf." The badge he means is
   .lk-avail[data-rank="3"] on the item page, so this takes that badge's fill,
   its ink, its 999px radius and its 600 weight - it should read as the same
   object, not as a second green.

   Where it must differ, and why: the badge is a statement and this is a
   control, so it needs a rest state, a hover, a focus ring and a disabled
   state the badge never needed.

   Measured 24 Sep 2026, --pos on --s-appr-bg at 13px:
     rest     5.38 light / 7.49 dark
     hover    6.17 light / 9.59 dark
     disabled 5.33 light / 5.75 dark
   all clear of 4.5.

   HOVER INVERTS rather than deepening the tint, and that is a measurement, not
   a preference. Green text on a green fill is the classic AA failure, and it
   fails on the way DOWN: mixing --pos into the tint at 16% drops the label to
   4.30:1, at 10% to 4.68 - i.e. every tint-deepening hover makes the button
   harder to read at the moment the pointer is on it. Filling solid with --pos
   and flipping the label to --on-accent goes the other way, to 6.17, and
   --on-accent already inverts per theme so one rule serves both. */
.btn.pill-go {
  background: var(--s-appr-bg);
  border-color: color-mix(in srgb, var(--pos) 26%, transparent);
  color: var(--pos);
  border-radius: 999px;
  font-weight: 600;
  transition: background .14s ease, border-color .14s ease, color .14s ease, transform .12s ease;
}
.btn.pill-go:hover:not([disabled]) {
  background: var(--pos); border-color: var(--pos); color: var(--on-accent);
}
/* The global :focus-visible rule sets border-radius: var(--r-sm), which squares
   a pill off the moment it is tabbed to - the button changes SHAPE to show it
   has focus. The ring itself stays the app's indigo, because a second focus
   colour would be a second thing to learn. */
.btn.pill-go:focus-visible { border-radius: 999px; }
/* A disabled green pill that is merely faded still looks like a green pill you
   can press, and this one disables itself for the seconds a sync is running
   while its label changes to "Refreshing...". So it stops being green: neutral
   fill, secondary ink, and opacity back to 1 so the state is a real colour that
   can be measured rather than a translucent guess. */
.btn.pill-go[disabled] {
  opacity: 1;
  background: var(--sunken); border-color: var(--line-2); color: var(--ink-3);
}

/* ==========================================================================
   Phone layout — 24 Sep 2026
   --------------------------------------------------------------------------
   Every rule below sits inside a max-width query, so nothing at 881px and up
   moves. That is deliberate: the desktop view was measured and signed off in
   rounds 1 and 2, and a phone fix that shifts it is a regression, not a fix.

   The faults these answer were all measured at 390px and 360px in the live
   DOM, not guessed from the CSS.
   ========================================================================== */

/* ---------- the summary tiles: type scale ----------
   The column-count fix is up at .tilerow. This is the second half of it. Two
   tiles on a 360px phone give 133px of content each; "187,832" set at the
   desktop 31px measures 112px, which fits but leaves nothing for a longer
   figure — one more digit and it clips again. At 27px the same number is 97px
   and an eight-digit one still fits. The hierarchy survives: 27px against a
   12.5px label is the same jump the desktop makes at 31 against 13. */
@media (max-width: 560px) {
  .tile { padding: 13px 13px 12px; }
  .tile-icon { width: 32px; height: 32px; border-radius: 9px; margin-bottom: 9px; }
  .tile-value { font-size: 27px; margin: 1px 0 5px; }
  .tile-label { font-size: 12.5px; }
  .tile-delta, .tile-note { font-size: 11.5px; }
}

/* ---------- the column-resize grips ----------
   `.dtable th .grip` is a 7px drag handle sitting at `right: -3px` with
   `z-index: 4`. Two things follow on a touch screen. It cannot be used, because
   nobody drags a 7px target with a thumb; and it covers the right 7px of every
   header cell, so it swallows taps meant for the sort link underneath it.

   It is also the whole explanation of the "clipped table headings" reported
   with this job. Every one of them overflowed by exactly 3px — "Change vs
   prior" 116 against 113, "Per day" 65 against 62, "In IOS" 58 against 55 —
   and 3px is the grip's overhang, not truncated text. The headings were never
   clipped. Measured on /sales?tab=sku: each heading's own content stops 10px
   inside its cell's right edge while the grip ends 3px outside it.

   Width-based rather than `(pointer: coarse)`, so a touchscreen laptop at
   desktop width keeps its draggable columns. */
@media (max-width: 880px) {
  .dtable th .grip { display: none; }
}

/* ---------- the brand matrix ----------
   What this panel is for: picking which brands go into the reorder run. The
   brand name and the tick box are the job. Total / Core / Extra / Return /
   Disc. are reference — useful, not the decision.

   It was unusable at 390px. `table-layout: fixed` with `th.mx-pick` at 46px
   and five numeric columns at 64px declares 366px inside a 322px scroller,
   and the brand column is `width: auto` — so it computed to 0 and the panel
   showed a tick box and five columns of numbers with no brand names at all.

   The fix gives Brand a real 150px and lets the table be wider than its
   scroller, which is what the scroller is for. At rest you see the tick box,
   the name and Total — the decision and its headline number, 260px of the
   322px on screen. Extra, Return and Disc. are one swipe right.

   Deliberately NOT done two other ways. Stacking the numbers under the name
   costs a `display: block` rewrite of the table, which breaks the sticky head
   and the round-2 column work for a panel that is already only 168px tall.
   Hiding the numbers behind a tap hides data, and a brand list that quietly
   drops its figures is how somebody orders against a number they never saw.
   Nothing here is hidden; it is ordered by what the panel is for. */
@media (max-width: 560px) {
  .field-matrix .matrix { min-width: 516px; }
  .field-matrix .matrix th.mx-brand,
  .field-matrix .matrix td.mx-brand { width: 150px; }
  /* 150px is narrower than the longest name on the sheet ("Yupoong / FlexFit
     / YP Classic" needs 200px), so the name wraps to a second line instead of
     being cut. A wrapped name is readable; an ellipsed one is a guess. */
  .field-matrix .matrix tbody th.mx-brand {
    max-width: none; white-space: normal; overflow: visible; line-height: 1.3;
  }
  /* The filter box shares the heading cell with the word "Brand" (round 2).
     At 150px there is no room for both on one line, so it takes its own. */
  .field-matrix .matrix thead th.mx-brand { white-space: normal; }
  .field-matrix .matrix thead th.mx-brand .brandfilter {
    width: 100%; max-width: none; margin: 4px 0 0; display: block;
  }
}

/* ---------- the view pills ----------
   /reorder offers eleven views and /sales three, all as wrapped pills. At
   390px the eleven wrap to five ragged rows and push the data most of a
   screen down the page.

   One scrolling row instead, with the active pill pinned first by `order: -1`.
   That ordering is the part worth arguing about, so here is the argument: in a
   `nowrap` strip the active pill can land off-screen — you arrive at
   ?view=dead and the strip shows "Order sheet" with no sign of where you
   actually are. Pinning it first means the view you are on is always the first
   thing you read, and it is always in the same place. The cost is that the
   other ten reshuffle between views, which matters less on a phone, where you
   are picking from a list you are looking at rather than reaching for a
   position you remember.

   `-webkit-overflow-scrolling: touch` and `scrollbar-width: none` because a
   scrollbar drawn over a 42px strip reads as a broken row. */
@media (max-width: 880px) {
  .filters {
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; padding: 14px 14px 6px;
  }
  .filters::-webkit-scrollbar { display: none; }
  .filters .filter { flex: none; }
  .filter[data-active="true"] { order: -1; }
  /* The brand jump is a control, not a view, so it goes to the end of the
     strip rather than competing with the pills for first place. `margin-left:
     auto` does nothing in a nowrap flex row, so it is set explicitly. */
  .filters .brandjump { order: 99; flex: none; margin-left: 12px; }
  .filters .scope { order: 98; flex: none; margin-left: 12px; }
}

/* ---------- tap targets ----------
   Measured at 390px. Everything below came in under about 36px square, which
   is the point at which a thumb starts hitting its neighbour. Sizes here are
   set in the space around the control, not on the control's own box, so the
   type and the ink stay exactly where the desktop put them. */
@media (max-width: 880px) {
  /* 15px and 13px tick boxes. 20px with the cell padding around it gives a
     44px row to aim at, and the <label> on the brand name is already part of
     the same target. */
  .reorder-controls .field .matrix input[type="checkbox"] { width: 20px; height: 20px; }
  .matrix td.mx-pick, .matrix th.mx-pick { padding-top: 10px; padding-bottom: 10px; }
  .settings input[type="checkbox"] { width: 20px; height: 20px; }

  /* Page numbers and page-size links were 14–31px wide and 19px tall, set
     shoulder to shoulder. */
  .pager { gap: 4px 10px; row-gap: 10px; }
  .pager a { display: inline-block; padding: 10px 8px; min-width: 24px; text-align: center; }
  .pager .csvlink { padding: 10px 14px; }

  /* The column funnel was 22 x 18 inside a table heading. */
  .filtbtn { min-width: 32px; height: 30px; }

  /* The report links in the sidebar tree were 33.4px tall — just under. */
  .navmo { padding-top: 11px; padding-bottom: 11px; }
}

/* ---------- narrow data tables get crushed, wide ones do not ----------
   Found at 390px on /reorder?view=oos and /sales?tab=brand. The wide list
   tables declare their column widths and simply scroll, which is correct and
   intended. The SHORT summary tables do not, so auto layout hands the whole
   362px to the first column and squeezes the rest: on ?view=oos the Brand
   column took 230px and each of "Units missed", "SKUs", "Days hit", "Lines"
   and "Stocked" was left with 26px — heading text of 84px, 43px, 56px, 41px
   and 57px cut to about two characters each.

   680px is a floor, not a width. Measured live at 390px: at 680 every heading
   fits (0 of 21 clipped, against 5 before) while the two wide tables on the
   same page stay at 850px and 701px, exactly where their own declared columns
   put them — the floor is under them and never lifts them.

   `min-width: max-content` was tried first and rejected: it took those same
   two tables to 9431px, because it sizes to the Item descriptions in the body
   rather than to the headings. A floor that tracks content is not a floor.
   `white-space: nowrap` on the headings was tried second and does nothing at
   all here — the table is width-constrained by its wrapper, so the headings
   have no way to push it wider; it simply went back to 362px with ten
   headings cut instead of five.

   One floor is not enough either. 700 suits a six-column summary, but the
   sales-by-brand table carries nine, and at 700 each of its numeric columns
   gets 56px: "Selling days" lost 33px of itself, the "vs prior 30 days"
   sub-label lost 50px, and the 187,832 column total lost 9px. So the floor
   rises with the column count, which `:has()` can read directly — it is
   already used a few hundred lines up for the ticked-row highlight, so this
   is not a new dependency. Measured at 390px: 0 of 9 headings clipped on
   /sales?tab=brand (was 9) and 0 of 21 on /reorder?view=oos (was 5), with the
   wide list tables untouched at 850px and 701px, where their own declared
   columns put them. */
@media (max-width: 880px) {
  .tablewrap > table.dtable { min-width: 700px; }
  .tablewrap > table.dtable:has(thead th:nth-child(7)) { min-width: 900px; }
  .tablewrap > table.dtable:has(thead th:nth-child(9)) { min-width: 1120px; }
}

/* ---------- dashboard bar labels ----------
   `.bar-row` gives the name a fixed 88px. "Pacific Headwear" needs 96px and
   "Zapped Headwear" 102px, so both were ellipsed — and a vendor chart whose
   vendor names are cut is a chart of anonymous bars. The card is full-width on
   a phone with room to spare, so the label column takes what it needs and the
   bar keeps the rest. Left at 88px above 560px, because the desktop layout was
   measured and signed off at that figure. */
@media (max-width: 560px) {
  .bar-row { grid-template-columns: 116px minmax(0,1fr) 34px; }
}

/* ---------- the reorder control strip on a narrow phone ----------
   Only shows up at 360px, not at 390. `.rc-row` is a wrapping flex row and
   `.field` carries `min-width: 140px`, so two fields plus the 18px gap ask for
   298px inside a 294px row. They did not wrap — they overflowed, and the
   "Brands & IOS types" row lost 26px off its right edge, taking 8px of the
   control panel with it.

   One field per line below 560px. The fields in this strip are a date range,
   a cover figure and two whole tables; side by side they were already the
   tighter reading at 390px, and at 360px they do not fit at all. */
@media (max-width: 560px) {
  .rc-row > .field { flex: 1 1 100%; min-width: 0; }
}

/* ---------- the sign-in logo ----------
   The lock-up carries an explicit 338.9px width, so at 360px it sat 11px
   outside its own 328px header and the wordmark was cut on the right — the
   first thing anybody signing in on an Android phone would see. Capped to the
   column and left to scale. Above 560px there is room for its natural size,
   so nothing changes there. */
@media (max-width: 560px) {
  .signin-head svg { max-width: 100%; height: auto; }
}

/* Refresh now, running: a thin bar along the foot of the freshness strip. */
.syncbar { position: relative; }
.syncprog {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--line); border-radius: 0 0 var(--r-sm) var(--r-sm); overflow: hidden;
}
.syncprog span {
  display: block; height: 100%; width: 0; background: var(--indigo);
  transition: width .4s ease;
}
.sync-failed { color: var(--s-change); }
@media (prefers-reduced-motion: reduce) { .syncprog span { transition: none; } }

/* Download Excel, wherever it sits in a hint line: the same pill as the pager's. */
.hint .csvlink {
  display: inline-block; margin-left: 8px; padding: 3px 12px; border-radius: 999px;
  border: 1px solid var(--line-2); white-space: nowrap; font-weight: 500;
}
.hint .csvlink:hover { border-color: var(--indigo); background: var(--indigo-soft); text-decoration: none; }

/* IOS Reorder > Website. Above the table, a strip of counts for this tab,
   each a click that shows just those items. In the column, the answer in words:
   quiet green when on sale everywhere, red when nowhere, amber when a store
   is missing it, with which store and why on a second line. */
.webstrip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 4px 0 14px; padding: 10px 14px; border: 1px solid var(--line);
  border-radius: 12px; background: var(--surface, transparent);
}
.webstrip-lab { font-weight: 600; margin-right: 4px; }
.webchip {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px;
  border-radius: 999px; border: 1px solid var(--s-review); background: var(--s-review-bg);
  color: var(--ink); font-weight: 500; white-space: nowrap; text-decoration: none;
}
.webchip b { color: var(--web-amber-ink); font-variant-numeric: tabular-nums; }
.webchip.worst { border-color: var(--s-change); background: var(--s-change-bg); }
.webchip.worst b { color: var(--web-red-ink); }
.webchip:hover { text-decoration: none; filter: brightness(0.97); }
.webchip.on { box-shadow: 0 0 0 2px var(--indigo); }
.webchip.none { border-color: var(--line-2); background: transparent; color: var(--ink-3); }
.webchip.none b { color: var(--ink-3); }
.webstrip-ok { color: var(--web-green-ink); font-weight: 500; margin-left: 4px; }
.webstrip-note { color: var(--ink-3); }
.webstrip-clear { margin-left: auto; font-weight: 500; }
.webcell { white-space: nowrap; }
.webword { display: block; font-weight: 600; line-height: 1.3; background: none; padding: 0; }
.webword.w-ok    { color: var(--web-green-ink); font-weight: 500; }
.webword.w-gap   { color: var(--web-amber-ink); }
.webword.w-worst { color: var(--web-red-ink); }
.webwhy { display: block; font-size: 12px; color: var(--ink-3); line-height: 1.3; margin-top: 2px; }

/* Orders > Daily Orders Export: the build row. */
.orders-build { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 6px 0 18px; }
.orders-build .hint { flex: 1 1 320px; }
/* A three-row summary: the table's usual 240px floor left a gap under it. */
.tablewrap.orders-sum { min-height: 0; }
.orders-file { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 12px 0 10px; }

/* Orders > Daily Tab. The sheet's colours, kept as the team knows them. */
.dtab-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin: 6px 0 12px; }
.dtab-tiles .dtab-tile { border: 1px solid var(--line); border-radius: 12px; padding: 10px 14px; background: var(--surface); }
.dtab-tiles .dtab-tile b { display: block; font-size: 22px; font-variant-numeric: tabular-nums; }
.dtab-tiles .dtab-tile span { color: var(--ink-3); font-size: 12.5px; }
.dtab td { vertical-align: top; }
.dtab .mono, .dtab-pick .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }
.dtab .nowrap { white-space: nowrap; }
.dtab .muted, .dtab-pick .muted { color: var(--ink-3); }
.dtab-gap td { height: 8px; padding: 0; background: var(--line); }
.dtab-notes { max-width: 240px; font-size: 12px; white-space: normal; }
.dtab-notes > div { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.dtab td.dtab-li { max-width: 260px; min-width: 200px; }
.dtab td.mono { max-width: 230px; overflow-wrap: anywhere; }
.dtab-mark { color: var(--ink-3); font-size: 11px; }
/* Painted as an inset shadow, not a background, so neither the row hover nor
   the processed-row colour can cover a flag (Theo, 11 Oct). */
.dtab .fl-pack   { box-shadow: inset 0 0 0 999px #FFE599; color: #1f1f1f; }
.dtab .fl-custom { box-shadow: inset 0 0 0 999px #B6D7A8; color: #1f1f1f; }
.dtab .fl-egift  { box-shadow: inset 0 0 0 999px #85DCC1; color: #1f1f1f; }
.dtab .fl-dup    { box-shadow: inset 0 0 0 999px #F4CCCC; color: #1f1f1f; }
.dtab .fl-nosize { box-shadow: inset 0 0 0 999px #9BC2E6; color: #1f1f1f; }
.dtab .fl-risk   { box-shadow: inset 0 0 0 999px #FF4D4F; color: #fff; font-weight: 600; }
.dtab .fl-short  { box-shadow: inset 0 0 0 999px #CC4125; color: #fff; }
.dtab .fl-neg    { box-shadow: inset 0 0 0 999px #A61C00; color: #fff; }
.dtab-pick { columns: 2 320px; padding-left: 22px; }

/* Orders > Daily Tab, worked in place (11 Oct 2026): sheets made from a date
   range, the team's columns typed into, rows coloured by who processed them. */
.dtab-make { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px; margin: 6px 0 8px; }
.dtab-make > label:not(.dtab-check) { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.dtab-make input[type="date"], .dtab-make input[type="text"], .dtab-bar input[type="search"] {
  font: inherit; font-size: 13.5px; background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); padding: 7px 10px;
}
.dtab-make input[type="text"] { width: 140px; }
.dtab-make input:focus, .dtab-bar input[type="search"]:focus { border-color: var(--indigo); outline: none; }
.dtab-make .dtab-check { padding-bottom: 8px; }
.dtab-make-hint { flex: 1 1 100%; margin: 2px 0 0; }
.dtab-check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--ink-2); cursor: pointer; }
.dtab-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin: 10px 0 6px; }
.dtab-bar input[type="search"] { flex: 0 1 300px; min-width: 0; }
.dtab-bar-links { display: flex; flex-wrap: wrap; gap: 8px; margin-left: auto; }
.dtab-note { margin: 4px 0 8px; }
.dtab-note .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.7; }
.dtab-early { display: inline-block; font-size: 11px; font-weight: 600; line-height: 1.5; padding: 0 6px; border-radius: 999px;
  background: var(--web-amber-ink); color: var(--on-accent); vertical-align: 1px; }

.dtab td { padding-top: 5px; padding-bottom: 5px; }
.dtab .dcell { box-sizing: border-box; width: 100%; min-width: 0; font: inherit; font-size: 12.5px; color: inherit;
  background: var(--raised); border: 1px solid var(--ink-3); border-radius: 4px; padding: 3px 6px; }
.dtab .dcell:focus { border-color: var(--indigo); outline: 1px solid var(--indigo); }
.dtab td.dtab-sku { min-width: 260px; }
.dtab td.dtab-sku .dcell { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.dtab td.dtab-rew .dcell { text-decoration: underline dotted; text-underline-offset: 3px; }
.dtab td.dtab-n { width: 62px; }
.dtab .dnum { text-align: right; width: 58px; }
.dtab td.dtab-po { min-width: 150px; }
.dtab td.dtab-vo { min-width: 130px; }
.dtab td.dtab-cm { min-width: 210px; }
.dtab td.dtab-inc { min-width: 90px; }
.dtab .dcell.dsaving { opacity: .55; }
.dtab .dcell.dbad { border-color: var(--web-red-ink); outline: 1px solid var(--web-red-ink); }
.dtab thead th.dtab-ed { color: var(--ink); box-shadow: inset 0 -3px 0 var(--indigo); }
/* Who processed it: the whole row in that person's colour, as the team fills
   rows in the sheet. Kept at zero specificity so the sheet's cell colours
   (pack, custom, risk…) still show on top, as conditional formats do there. */
:where(.dtab tbody tr[data-by]:not([data-by=""])) > td { background: var(--rowc); color: #1f1f1f; }
.dtab.sticky tbody tr[data-by]:not([data-by=""]) > td:first-child { background: var(--rowc); }
.dtab tbody tr[data-by]:not([data-by=""]) .dcell { background: rgba(255, 255, 255, .6); color: #1f1f1f; border-color: rgba(0, 0, 0, .2); }
.dtab tbody tr[data-by]:not([data-by=""]) .muted { color: #555; }
.dtab-legend { margin: 12px 0 4px; }
.dtab-swatch { display: inline-block; margin: 2px 4px 2px 0; padding: 1px 8px; border-radius: 6px; background: var(--rowc);
  color: #1f1f1f; font-size: 12.5px; }
.dtab-status { position: fixed; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); z-index: 60;
  max-width: min(440px, calc(100vw - 32px)); padding: 10px 14px; border-radius: 10px; font-size: 13.5px;
  background: var(--ink); color: var(--surface); box-shadow: 0 6px 24px rgba(0, 0, 0, .18); }
.dtab-status[data-tone="bad"] { background: var(--web-red-ink); color: #fff; }
/* NOTES is the first column but not the one to keep in view: unpinned, so the
   order columns are not hidden behind it on a laptop. */
.dtab.sticky tbody td:first-child { position: static; min-width: 0; box-shadow: none; }
.dtab.sticky thead th:first-child { left: auto; min-width: 0; box-shadow: none; }
.dtab.sticky tbody tr.dtab-gap > td:first-child { background: var(--line); }
/* Theo, 11 Oct: a hovered processed row keeps its colour; a flagged cell's
   input reads on the flag in both themes; short tables are not forced wide on
   a phone; the export page's mode pills wrap and its dates match the Daily Tab. */
.dtab tbody tr[data-by]:not([data-by=""]):hover > td { background: var(--rowc); }
.dtab td[class*="fl-"] .dcell { background: rgba(255, 255, 255, .6); color: #1f1f1f; border-color: rgba(0, 0, 0, .3); }
.tablewrap.dtab-narrow > table.dtable { min-width: 0; }
.filters.wrap { flex-wrap: wrap; overflow-x: visible; }
.orders-build input[type="date"] {
  font: inherit; font-size: 13.5px; background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); padding: 7px 10px;
}
.orders-build input[type="date"]:focus { border-color: var(--indigo); outline: none; }
/* The date pickers' calendar icon follows the theme (dark icon on a dark box otherwise). */
.dtab-make input[type="date"], .orders-build input[type="date"] { color-scheme: light dark; }

/* Daily Tab: Order # stays in view while the sheet scrolls sideways (Aziz, 12 Oct). */
.dtab tbody td.dtab-ord, .dtab thead th.dtab-order_ref { position: sticky; left: 0; z-index: 2; background: var(--surface);
  box-shadow: inset -1px 0 0 var(--line-2); }
.dtab thead th.dtab-order_ref { z-index: 4; }
.dtab tbody tr[data-by]:not([data-by=""]) > td.dtab-ord { background: var(--rowc); }
.dtab tbody tr:hover > td.dtab-ord { background: var(--hover); }
.dtab tbody tr[data-by]:not([data-by=""]):hover > td.dtab-ord { background: var(--rowc); }
.dtab-early.dtab-added { background: var(--indigo); }
.dtab-status.dtab-more { bottom: calc(70px + env(safe-area-inset-bottom, 0px)); background: var(--indigo); color: var(--on-accent); }
.dtab-status.dtab-more a { color: var(--on-accent); text-decoration: underline; }
/* The "added" pill sits under the order number, so the frozen column stays narrow. */
.dtab td.dtab-ord .dtab-added { display: table; margin-top: 3px; }

/* Orders > Live Orders (12 Oct 2026). */
.live-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin: 4px 0 8px; }
.live-head .filters { margin: 0; padding: 0; }
.live-head button.filter { font: inherit; cursor: pointer; }
.live-move { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 8px 0; padding: 10px 12px;
  border: 1px solid var(--line-2); border-radius: 10px; background: var(--surface); position: sticky; top: 0; z-index: 5; }
.live-move #lv-count { font-weight: 600; min-width: 140px; }
.live-move label { font-size: 13px; color: var(--ink-2); }
.live-move select, .live-move input[type="text"] { font: inherit; font-size: 13.5px; background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); padding: 0 10px; height: 38px; box-sizing: border-box; }
.live-move select:focus, .live-move input[type="text"]:focus { border-color: var(--indigo); outline: none; }
.live-done { margin: 6px 0 12px; padding: 10px 14px; border-radius: 10px; border: 1px solid var(--line-2); background: var(--surface); line-height: 1.6; }
.live .live-tick { width: 36px; text-align: center; }
.live .live-tick input { width: 17px; height: 17px; cursor: pointer; }
.live .nowrap { white-space: nowrap; }
.live .muted { color: var(--ink-3); }
.live-st { display: inline-block; font-size: 12px; font-weight: 600; line-height: 1.6; padding: 0 8px; border-radius: 999px; }
.live-new { background: var(--web-amber-ink); color: var(--on-accent); }
a.live-st.live-in { background: var(--indigo); color: var(--on-accent); text-decoration: none; }
a.live-st.live-in:hover { text-decoration: underline; }
.live-tab { background: var(--sunken); color: var(--ink-2); border: 1px solid var(--line-2); }
.live-x { background: var(--web-red-ink); color: var(--on-accent); }
.live tr.live-fresh > td { box-shadow: inset 3px 0 0 var(--web-amber-ink); }
.live tr.live-fresh > td ~ td { box-shadow: none; }
.live tr.live-cancel > td { color: var(--ink-3); }
.tablewrap.live-wrap > table.dtable { min-width: 0; }
@media (max-width: 880px) { .tablewrap.live-wrap > table.dtable.dtable { min-width: 0; } }
/* Cancelled sits under the status, so it stays in view on a phone (Theo r3). */
.live .live-x { display: table; margin-top: 3px; }
/* Live Orders: tags kept to two lines; the whole list on hover. */
.live td.live-tags { min-width: 180px; max-width: 280px; font-size: 12.5px; white-space: normal; }
.live td.live-tags > div { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* Theo, 12 Oct: "8 min ago" under the time so the table fits at 1440; the new-sheet box shows its example in full. */
.live td .muted { display: block; }
.live-move input[type="text"] { width: 210px; max-width: 100%; }

/* Daily Tab grid (Univer), 12 Oct 2026: tall enough to work in, inside the page. */
.dtab-grid { position: relative; height: calc(100vh - 150px); min-height: 520px; border: 1px solid var(--line-2);
  border-radius: 10px; overflow: hidden; background: #fff; margin: 8px 0 18px; }
.dtab-page .dtab-bar .hint { flex: 1 1 420px; }
/* The Daily Tab page, tightened so the grid starts near the top (Theo, 12 Oct). */
.dtab-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; margin: 0 0 6px; }
.dtab-head h1 { margin: 0; font-size: 22px; }
.dtab-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; margin: 0 0 4px; font-size: 13.5px; color: var(--ink-2); }
.dtab-strip b { color: var(--ink); font-variant-numeric: tabular-nums; }
.dtab-strip-links { display: flex; flex-wrap: wrap; gap: 6px; margin-left: auto; }
.dtab-tip { margin: 2px 0 6px; font-size: 12.5px; }
.dtab-grid { height: calc(100dvh - 70px); min-height: 480px; }
.dtab-page #dtab-narrow { color: var(--web-red-ink); }   /* 5.6:1, Theo */
/* Live Orders: a filter under each heading (12 Oct). */
.live thead tr.live-filters th { padding: 4px 6px 8px; background: var(--surface); }
.live-filters input, .live-filters select { box-sizing: border-box; width: 100%; min-width: 64px; font: inherit; font-size: 12.5px;
  padding: 4px 6px; border: 1px solid var(--line-2); border-radius: 6px; background: var(--raised); color: var(--ink); }
.live-filters input:focus, .live-filters select:focus { border-color: var(--indigo); outline: none; }
/* Theo, 12 Oct: the filter row pinned under the headings, and wide enough for its choices. */
.live thead tr.live-filters > th { top: var(--lv-head, 55px); z-index: 3; }
.live-filters input, .live-filters select { height: 29px; }
.live-filters #lf-status { min-width: 150px; }
.live-filters #lf-day { min-width: 104px; }
.live-filters #lf-fin { min-width: 108px; }
.live-filters #lf-ful { min-width: 112px; }
.live-filters #lf-store { min-width: 70px; }
/* US date boxes (mm/dd/yyyy) with a calendar button, 12 Oct 2026. */
.usdate { position: relative; display: inline-flex; align-items: stretch; }
.usdate input[data-usdate] { width: 130px; padding-right: 34px !important; font-variant-numeric: tabular-nums; }
.usdate .usdate-pick { position: absolute; right: 4px; top: 50%; transform: translateY(-50%); width: 28px; height: 28px; display: grid;
  place-items: center; border: 0; border-radius: 6px; background: transparent; color: var(--ink-2); cursor: pointer; }
.usdate .usdate-pick:hover, .usdate .usdate-pick:focus-visible { background: var(--hover); color: var(--ink); outline: none; }
.usdate .usdate-native { position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; opacity: 0; pointer-events: none; border: 0; padding: 0; }
.orders-build .usdate input[data-usdate] { font: inherit; font-size: 13.5px; background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); padding: 7px 10px; }
/* NTO and carts under the Daily Tab grid (12 Oct 2026). */
.dtab-carts { display: grid; gap: 8px; margin: 6px 0 18px; }
.dtab-cart { border: 1px solid var(--line-2); border-radius: 10px; background: var(--surface); padding: 8px 12px; }
.dtab-cart > summary { cursor: pointer; font-size: 14px; color: var(--ink-2); }
.dtab-cart > summary b { color: var(--ink); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.dtab-cart .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }
/* Send to the Google Sheet (admins), 12 Oct 2026. */
.dtab-push { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 2px 0 6px; font-size: 13px; }
.dtab-push label { color: var(--ink-2); }
.dtab-push input[type="text"] { width: 110px; font: inherit; font-size: 13px; padding: 5px 8px; border: 1px solid var(--line-2);
  border-radius: var(--r-sm); background: var(--raised); color: var(--ink); }
/* Theo, 12 Oct: dates keep their label; a wrong date says so; carts read across. */
.date-pair { display: inline-flex; align-items: center; gap: 8px; }
.usdate { flex-wrap: wrap; }
.usdate .usdate-bad { border-color: var(--web-red-ink) !important; outline: 1px solid var(--web-red-ink); }
.usdate .usdate-msg { flex-basis: 100%; margin-top: 3px; font-size: 12px; color: var(--web-red-ink); }
.usdate .usdate-pick { top: 19px; }
.dtab-h2row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.dtab-h2row h2 { margin-bottom: 4px; }
.dtab-carts .tablewrap > table.dtable { width: auto; min-width: 560px; }
.dtab-carts .dtable th.qnum, .dtab-carts .dtable td.qnum { width: 90px; }
.dtab-push input[type="text"] { height: 32px; box-sizing: border-box; }
.dtab-push .btn.small { height: 32px; display: inline-flex; align-items: center; }
/* Theo r2: the date message sits under the box without moving anything; carts scroll inside their box on a phone. */
.usdate { flex-wrap: nowrap; }
.usdate .usdate-msg { position: absolute; top: 100%; left: 0; white-space: nowrap; margin-top: 2px; }
.dtab-cart { min-width: 0; }
.dtab-cart .tablewrap { max-width: 100%; overflow-x: auto; }
.usdate:has(.usdate-bad) { margin-bottom: 22px; }   /* room for the date warning (Theo r3) */
/* Sales Reports: the US date boxes styled as the old date boxes were (12 Oct 2026). */
.customrange .usdate input[data-usdate] { background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 9px 34px 9px 11px; font-size: 13px; color: var(--ink); }
.customrange .usdate input[data-usdate]:focus { border-color: var(--indigo); outline: none; }

/* Sheet Archive (12 Oct 2026): saved Google Sheet tabs, and the Daily Orders Export send. */
.arc-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 12px 0; }
.arc-tools input[type="search"] { min-width: 0; width: 260px; max-width: 100%; height: 38px; }
@media (max-width: 600px) { .arc-tools input[type="search"] { font-size: 16px; } }
.live-st.arc-safe { background: var(--web-green-ink); color: var(--on-accent); }
.err-inline { color: var(--web-red-ink); font-size: 12.5px; }
.arc-day { margin-left: 6px; font-size: 12px; }
.tablewrap.arc-list-wrap > table.dtable.arc-list { min-width: 680px; table-layout: auto; }
.arc-list td { vertical-align: middle; }
.arc-list .live-st, .arc-list td.arc-saved, .arc-list td.arc-checked, .arc-list td.arc-links { white-space: nowrap; }
.arc-h2 { font-size: 15px; margin: 24px 0 6px; }
.arc-exports { margin: 0; padding-left: 18px; line-height: 1.8; }
.orders-push { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 4px 0 12px; }
/* The saved tab itself: a plain grid like the sheet, light in both themes because the tab's own fills are. */
.arc-wrap { max-height: max(320px, calc(100vh - 360px)); overflow: auto; border: 1px solid var(--line-2); border-radius: 8px; }
.tablewrap.arc-wrap > table { min-width: 0; }
.arc-grid { border-collapse: separate; border-spacing: 0; font: 12.5px/1.35 Arial, Helvetica, sans-serif; background: #fff; color: #000; }
.arc-grid th, .arc-grid td { border-right: 1px solid #e2e2e2; border-bottom: 1px solid #e2e2e2; padding: 3px 6px; white-space: nowrap; max-width: 320px; overflow: hidden; text-overflow: ellipsis; text-align: left; }
.arc-grid thead tr:first-child th { position: sticky; top: 0; z-index: 3; background: #f3f3f3; color: #555; font-weight: 600; text-align: center; }
.arc-grid thead tr.arc-head th { position: sticky; top: 23px; z-index: 2; background: #fff; font-weight: 400; }
.arc-grid .arc-rn { position: sticky; left: 0; z-index: 1; background: #f3f3f3; color: #555; text-align: center; min-width: 34px; }
.arc-grid thead tr th.arc-rn { z-index: 5; background: #f3f3f3; }
.arc-grid td.arc-note { box-shadow: inset -6px 6px 0 -3px #f4b400; }

.live-done a { color: var(--indigo); text-decoration: underline; text-underline-offset: 2px; }
.arc-list td:first-child { white-space: nowrap; }
.live-done .tablewrap { min-height: 0; }
.tablewrap > table.dtable.arc-diff { min-width: 0; }
.arc-diff td { white-space: normal; overflow-wrap: anywhere; vertical-align: top; }
.arc-diff td code { white-space: pre-wrap; }
/* Column filters: one-click remove beside an active filter (13 Oct 2026). */
.filtx { display: inline-grid; place-items: center; min-width: 24px; min-height: 24px;
  border-radius: 99px; font-size: 14px; line-height: 1; color: var(--ink-2); text-decoration: none; }
.filtx:hover, .filtx:focus-visible { background: var(--hover); color: var(--ink); }
/* Clear and Apply stay in view when the panel scrolls inside itself. */
.filtpop .filtacts { position: sticky; bottom: -10px; margin: 0 -10px -10px; padding: 8px 10px 10px; background: var(--surface); border-top: 1px solid var(--line); }
@media (pointer: coarse) { .filtx { min-width: 32px; min-height: 32px; } }
/* The heading whose panel is open sits above its neighbours, so their own
   headings and sums never paint across the panel (Theo, 13 Oct). */
.dtable thead th.colfilter:has(> .filtpop:not([hidden])) { z-index: 39; }
/* While a filter panel is open, the table's own scrollbar strip cannot take taps across it (Theo, 13 Oct). */
.tablewrap:has(.filtpop:not([hidden])) { scrollbar-width: none; }
.tablewrap:has(.filtpop:not([hidden]))::-webkit-scrollbar { display: none; }

/* Stores vs S&S (14 Oct 2026). */
.vs-fresh { margin: 6px 0 14px; padding: 10px 14px; border-radius: 10px; border: 1px solid var(--line-2); background: var(--surface); line-height: 1.6; font-size: 13.5px; }
.vs-fresh p { margin: 0; }
.vs-fresh p + p { margin-top: 6px; }
.vs-fresh.vs-old { border-color: var(--web-amber-ink); }
.vs-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin: 6px 0 10px; }
.vs-tools select, .vs-tools input[type="search"] { max-width: 100%; }
.vs-tools input[type="search"] { width: 240px; }
.vs-table td.vs-prod { min-width: 220px; max-width: 340px; white-space: normal; }
.vs-table td.vs-zero { color: var(--web-red-ink); font-weight: 600; }
.vs-table .live-st { white-space: nowrap; }

/* Live Orders: an order opened (15 Oct 2026). */
.lv-open { display: inline-flex; align-items: center; gap: 6px; padding: 2px 4px; margin: -2px -4px; border: 0; border-radius: 6px; background: none; color: inherit; font: inherit; cursor: pointer; }
.lv-open:hover b, .lv-open:focus-visible b { color: var(--indigo); text-decoration: underline; }
.lv-caret { color: var(--ink-3); font-size: 11px; width: 10px; }
.live tr.lv-detail > td { background: var(--sunken); padding: 10px 12px 14px; }
.lv-sum { margin: 0 0 8px; padding: 8px 12px; border-radius: 8px; border: 1px solid var(--line-2); background: var(--surface); line-height: 1.5; }
.lv-sum.lv-sum-ios { border-color: var(--web-green-ink); }
.tablewrap.lv-lines-wrap > table.dtable { min-width: 640px; margin-top: 0; background: var(--surface); }
.lv-lines td, .lv-lines th { padding: 7px 10px; font-size: 13px; }
.lv-lines td.lv-ok { color: var(--web-green-ink); font-weight: 700; }
.lv-lines td.lv-short { color: var(--web-red-ink); font-weight: 600; }
.lv-lines tr.lv-noship td { color: var(--ink-3); }
.lv-sug { display: inline-block; padding: 0 7px; border-radius: 999px; border: 1px solid var(--line-2); font-size: 12px; font-weight: 600; white-space: nowrap; }
.lv-sug.lv-sug-ios { background: var(--web-green-ink); border-color: transparent; color: var(--on-accent); }
.lv-foot { margin: 6px 0 0; font-size: 12px; }
/* The opened order stays in view while the wide list scrolls sideways, and its lines wrap. */
.lv-detail-box { position: sticky; left: 12px; width: min(1040px, calc(100vw - 340px)); max-width: 100%; }
@media (max-width: 880px) { .lv-detail-box { width: calc(100vw - 56px); } }
.lv-lines td.lv-name { white-space: normal; min-width: 200px; max-width: 360px; }
.lv-lines td code { white-space: nowrap; }
.lv-sum .lv-sum-note { display: block; margin-top: 2px; font-size: 12.5px; color: var(--ink-2); }
.lv-sum.lv-sum-done { border-style: dashed; }
.lv-why { margin: 4px 0 0; padding-left: 18px; font-size: 12.5px; color: var(--ink-2); }
/* Theo r1 (15 Oct): the order number reads as a link; parts of the suggestion one per line; no nested scroll; phone cards. */
.lv-open b { color: var(--indigo); }
.lv-caret { font-size: 13px; color: var(--ink-2); }
.lv-sum .lv-sum-part { display: block; }
.tablewrap.lv-lines-wrap { min-height: 0; max-height: none; }
@media (max-width: 600px) {
  .tablewrap.lv-lines-wrap { overflow: visible; }
  .tablewrap.lv-lines-wrap > table.dtable.lv-lines, .lv-lines tbody { display: block; min-width: 0; width: 100%; }
  .lv-lines thead { display: none; }
  .lv-lines tbody tr { display: grid; grid-template-columns: repeat(3, auto) 1fr; align-items: center; gap: 4px 12px; padding: 8px 10px; border-bottom: 1px solid var(--line-2); }
  .lv-lines.dtable tbody td { display: block; padding: 0; border: 0; text-align: left; min-width: 0; max-width: none; white-space: nowrap; }
  .lv-lines tbody td:first-child, .lv-lines tbody td.lv-name { grid-column: 1 / -1; }
  .lv-lines tbody td.lv-name { white-space: normal; color: var(--ink-2); font-size: 12px; }
  .lv-lines tbody td:nth-child(2)::before { content: "Qty "; color: var(--ink-3); font-weight: 400; }
  .lv-lines tbody td:nth-child(3)::before { content: "IOS "; color: var(--ink-3); font-weight: 400; }
  .lv-lines tbody td:nth-child(4):not(:empty)::before { content: "Bin "; color: var(--ink-3); }
  .lv-lines tbody td:nth-child(6)::before { content: "S&S "; color: var(--ink-3); }
  .lv-lines tbody td:empty { display: none; }
}
.lv-plan { margin-top: 2px; font-size: 12px; color: var(--web-amber-ink); font-weight: 600; white-space: nowrap; }

/* Live Orders' order dropdown in pure black (Aziz, 15 Oct 2026: "make the
   orders drop down colors pure black color. Everything should be
   accessible"); pure white on the dark theme. The IOS numbers keep their
   green/red meaning in deeper shades, and nothing is smaller than 13px. */
:root { --lv-ink: #000000; --lv-ok: #05603A; --lv-short: #A0180C; --lv-amber: #6B3A00; --lv-pill-line: #000000; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --lv-ink: #FFFFFF; --lv-ok: #7EF0C2; --lv-short: #FFA3A3; --lv-amber: #FFD86B; --lv-pill-line: #FFFFFF; }
}
:root[data-theme="dark"] { --lv-ink: #FFFFFF; --lv-ok: #7EF0C2; --lv-short: #FFA3A3; --lv-amber: #FFD86B; --lv-pill-line: #FFFFFF; }
.live tr.lv-detail > td, .live tr.lv-detail > td *:not(.lv-sug-ios) { color: var(--lv-ink); }
.live tr.lv-detail .lv-sum { border-color: var(--lv-ink); }
.live tr.lv-detail .lv-lines thead th { color: var(--lv-ink); font-weight: 700; }
.live tr.lv-detail .lv-lines td, .live tr.lv-detail .lv-lines th { font-size: 14px; }
.live tr.lv-detail .lv-sum .lv-sum-note, .live tr.lv-detail .lv-why, .live tr.lv-detail .lv-foot,
.live tr.lv-detail .muted, .live tr.lv-detail .lv-plan { font-size: 13px; }
.live tr.lv-detail .lv-lines td.lv-ok { color: var(--lv-ok); }
.live tr.lv-detail .lv-lines td.lv-short { color: var(--lv-short); }
.live tr.lv-detail td div.lv-plan { color: var(--lv-amber); }
.live tr.lv-detail .lv-sug { border-color: var(--lv-pill-line); font-size: 13px; }
.live tr.lv-detail .lv-sug.lv-sug-ios { background: var(--lv-ok); color: var(--surface); border-color: transparent; }
.live tr.lv-detail .lv-lines td code { font-size: 13px; }
.live tr.lv-detail .lv-lines tbody td::before { color: var(--lv-ink); }
.live tr.lv-detail .lv-lines td.qnum span.muted { display: inline; }

/* ---------- Vendor Sources (15 Oct 2026) ---------- */
.live-st.src-off { background: var(--sunken); color: var(--ink-2); }
.src-alert a { font-weight: 600; }
.src-ok { margin: 6px 0 12px; padding: 8px 12px; border-radius: 10px; background: var(--surface); border: 1px solid var(--web-green-ink); font-size: 13.5px; }
.src-table td { vertical-align: top; }
.src-table td .hint { margin-top: 2px; }
.src-table form { margin: 0; }
tr.src-just td { background: var(--hover); }
.src-statusline { margin: 0 0 12px; line-height: 1.9; }
.src-statusline .live-st { margin-right: 6px; }
.src-mark { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
.src-mark input[type="text"] { flex: 1 1 260px; }
.src-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 18px; margin: 0; font-size: 13.5px; }
.src-facts dt { color: var(--ink-2); }
.src-facts dd { margin: 0; overflow-wrap: anywhere; }
.src-edit textarea { width: 100%; }
@media (max-width: 640px) { .src-facts { grid-template-columns: 1fr; } .src-facts dd { margin-bottom: 6px; } }
/* The two Vendor Sources text boxes outside `.field select, .field input`
   kept the browser's white box, so on the dark theme typed text was near-white
   on white (measured 1.14:1). Same look as every other field. */
.src-edit .field textarea, .src-mark input[type="text"] {
  background: var(--raised); color: var(--ink); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 9px 10px; font: inherit; font-size: 13px;
}
