/* ============================================================================
   PACRON — component stylesheet
   ----------------------------------------------------------------------------
   WHAT  The CONSULATE components this application needs: the crown, the queue
         band, the page header, the canvas, the white box, section header
         bands, control strips, data grids, field rows and pagination.

   WHY   CONSULATE is a Locked design system. It ships tokens and a written
         specification, not a stylesheet, so each product writes the component
         rules itself against the shared tokens. These rules are transcribed
         from 01-CONSULATE-DESIGN-SYSTEM.md and consulate-reference.html.

   HOW   Every value below is a token from tokens.css. No raw hex, anywhere.
         Load-bearing rules, from 01 section 2:
           1. border-radius is 0 everywhere, forever.
           2. Two border weights only: 2px container, 1px item.
           3. A heading is a FILLED BAND, never floating bold text.
           4. Shadow belongs to the record window and the confirm only.
           5. Status colour is TEXT ONLY. No chips, badges, pills or fills.
   ============================================================================ */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; border-radius: var(--radius); }

body {
  font-family: var(--font);
  font-size: var(--t-body);
  color: var(--c-ink);
  background: var(--c-canvas);
}

button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
a { color: var(--c-link); text-decoration: underline; }
:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }

.app { min-height: 100vh; display: flex; flex-direction: column; }

/* ---------- CROWN ---------------------------------------------------------
   84px total: a 44px identity row and a 40px object-tab row. The active tab
   is 42px tall so it runs 2px past the crown's own border and merges into the
   white band below. That square notch is the signature form. ------------- */
.crown { background: var(--c-crown); border-bottom: 2px solid var(--c-crown-border); flex: none; }
.crown-r1 { height: var(--h-crown-row1); display: flex; align-items: center; padding: 0 var(--gutter); }
.crest {
  width: 26px; height: 26px; background: var(--c-gold); color: var(--c-ink-invert);
  display: grid; place-items: center; font-size: 12px; font-weight: 700; flex: none;
}
.wordmark {
  font-size: var(--t-wordmark); font-weight: 700; letter-spacing: 1.2px;
  color: var(--c-ink-invert); margin-left: 12px;
}
.crown-rule { width: 1px; height: 18px; background: var(--c-crown-rule); margin: 0 14px; }
.desk { font-size: var(--t-secondary); color: var(--c-crown-ink); }
.spacer { flex: 1; }
.util { margin-left: 20px; font-size: 12px; display: flex; align-items: center; gap: 8px; }
.util a { color: var(--c-ink-invert); }
.util .sep { color: var(--c-crown-rule); text-decoration: none; }

/* Card t_20aff642. The Margin lines tickbox sits in the crown's own utility
   row, so it needs the crown's secondary ink colour rather than .tickbox's
   ordinary one, which is tuned for the white canvas below. */
.navtoggle { display: inline-flex; align-items: center; }
.util .tickbox { color: var(--c-crown-ink); font-size: 12px; }
.util .tickbox input { margin: 0; }

.crown-r2 { height: var(--h-crown-row2); display: flex; align-items: stretch; padding: 0 var(--gutter); }
.otab {
  padding: 0 22px; display: grid; place-items: center;
  font-size: var(--t-tab-object); font-weight: 600; color: var(--c-crown-ink);
  text-decoration: none;
}
.otab:hover { background: var(--c-crown-hover); color: var(--c-ink-invert); }
.otab.on {
  background: var(--c-surface); color: var(--c-crown); font-weight: 700;
  height: 42px; align-self: flex-start; position: relative; z-index: 2;
}
.otab.off { color: var(--c-crown-rule); cursor: default; }
.otab.off:hover { background: none; color: var(--c-crown-rule); }

/* ---------- QUEUE TAB BAND ------------------------------------------------
   Level 2 navigation. Every queue visible at rest with a live count. A count
   of zero prints as nothing at all. ------------------------------------- */
.qband {
  height: var(--h-queue-band); background: var(--c-surface);
  border-bottom: 1px solid var(--c-border-container);
  display: flex; align-items: stretch; padding: 0 var(--gutter); flex: none;
}
.qtab {
  padding: 0 18px; display: flex; align-items: center; gap: 6px;
  font-size: var(--t-tab-queue); font-weight: 600; color: var(--c-ink-2);
  border-bottom: 3px solid transparent; text-decoration: none;
}
.qtab .n { font-weight: 400; color: var(--c-ink-3); }
.qtab:hover { color: var(--c-ink); }
.qtab.on { color: var(--c-ink); font-weight: 700; border-bottom-color: var(--c-gold); }

/* ---------- EVIDENCE DROPDOWN + CONFIDENCE SEGMENTED CONTROL ---------------
   Card t_833ba0b1 gave the evidence ladder its own qband, wrapped onto
   however many rows ten tabs needed, with the observed/derived toggle in a
   qband of its own underneath. Card t_db4ba6b4 puts both on the one queue
   band: a <select> (which cannot wrap the way tabs did, so it never needs
   to) beside a segmented control. Two qbands become one. */
.evidenceband { height: auto; min-height: var(--h-queue-band); align-items: center; gap: 14px; padding: 6px var(--gutter); }
.evidenceband-note { border-bottom: 1px solid var(--c-border-container); }

/* The confidence toggle. Same CONSULATE grammar as .atabs - a 2px container
   border, 1px dividers between segments, the active segment a filled band,
   zero border-radius throughout this design - just sized to sit inline
   beside the dropdown instead of spanning the page. */
.seg { display: inline-flex; border: 2px solid var(--c-border-container); height: var(--h-input); flex: none; }
.segopt {
  display: flex; align-items: center; gap: 6px; padding: 0 14px;
  font-size: var(--t-tab-queue); font-weight: 600; color: var(--c-ink-2);
  text-decoration: none; border-right: 1px solid var(--c-border-item);
}
.segopt:last-child { border-right: none; }
.segopt .n { font-weight: 400; color: var(--c-ink-3); }
.segopt:hover { background: var(--c-band-header); color: var(--c-ink); }
.segopt.on { background: var(--c-crown); color: var(--c-ink-invert); }
.segopt.on .n { color: var(--c-crown-ink); }

/* ---------- ASSUMPTIONS TABS — card t_5491e45c ----------------------------
   The Assumptions screen splits into two tabs that hold DIFFERENT KINDS of
   content - arithmetic on one, genuine judgement calls on the other - so
   this does not reuse the .qtab underline idiom above, which switches
   between slices of the SAME list. Filled colour bands instead, the same
   grammar as every section heading (.strip) on this screen: the active tab
   is a filled band, not a highlighted line. Two border weights - a 2px
   container border around the whole strip, a 1px item border between the
   two tabs - and zero border-radius, like everywhere else in this design. */
.atabs {
  display: flex; border: 2px solid var(--c-border-container);
  border-bottom: none; flex: none;
}
.atab {
  flex: 1; padding: 12px 18px; text-align: center;
  font-size: var(--t-header); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.7px; text-decoration: none;
  background: var(--c-band-service); color: var(--c-ink-2);
  border-right: 1px solid var(--c-border-item);
}
.atab:last-child { border-right: none; }
.atab .n {
  font-weight: 400; text-transform: none; letter-spacing: normal;
  margin-left: 6px; color: var(--c-ink-3);
}
.atab:hover { background: var(--c-band-header); color: var(--c-ink); }
.atab.on { background: var(--c-crown); color: var(--c-ink-invert); }
.atab.on .n { color: var(--c-crown-ink); }
.atab-note .note { font-style: italic; color: var(--c-ink-2); }

/* ---------- PAGE HEADER ---------------------------------------------------- */
.phead {
  min-height: var(--h-page-header); background: var(--c-surface);
  border-bottom: 1px solid var(--c-border-container);
  display: flex; align-items: center; padding: 8px var(--gutter); gap: 12px; flex: none;
}
.ptile {
  width: 32px; height: 32px; background: var(--c-crown); color: var(--c-ink-invert);
  display: grid; place-items: center; font-size: 16px; font-weight: 700; flex: none;
}
.eyebrow {
  font-size: var(--t-eyebrow); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.7px; color: var(--c-ink-3);
}
.h1line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
h1 { font-size: var(--t-h1); font-weight: 700; }
.countline { font-size: var(--t-secondary); color: var(--c-ink-2); }
.countline .good { color: var(--c-good); font-weight: 700; }

.btn {
  height: var(--h-button); padding: 0 16px; font-size: var(--t-body);
  background: var(--c-surface); border: 1px solid var(--c-border-control);
  display: grid; place-items: center; text-decoration: none; color: var(--c-ink);
}
.btn.primary {
  background: var(--c-crown); color: var(--c-ink-invert);
  border-color: var(--c-crown); font-weight: 600;
}

/* ---------- CANVAS + BOX --------------------------------------------------
   The whole product is ONE white rectangle on a tinted field. Never put
   content directly on the canvas. ---------------------------------------- */
.canvas { flex: 1; padding: 16px var(--gutter); }

/* Card t_68799fdd. Set on <body> by an inline script the instant the tiers-
   by-distance page decides it has a scroll offset to restore, and taken off
   again by app.js once that offset has been applied — so the page never
   paints at the top before jumping to where the reader actually was. */
.restoring-scroll { visibility: hidden; }

.box {
  background: var(--c-surface); border: 2px solid var(--c-border-container);
  display: flex; flex-direction: column; margin-bottom: 16px;
}

/* A heading is a filled band. Section strips use --c-band-header; column
   headers, group headers and pagination use --c-band-service. */
.strip {
  min-height: var(--h-section-strip); background: var(--c-band-header);
  display: flex; align-items: center; gap: 10px; padding: 0 var(--pad-box);
  font-size: var(--t-header); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.7px; color: var(--c-ink-on-band);
}
.strip .strip-note {
  font-size: var(--t-secondary); font-weight: 400; text-transform: none;
  letter-spacing: normal; color: var(--c-ink-2); margin-left: auto;
}

/* ---------- THE SECTION BAND'S RIGHT-HAND SHELF — card t_6273cc11 ----------
   WHAT  A `.strip-note` that holds SEVERAL things instead of one sentence:
         the counts that state what the screen is a screen of, the standing
         caveat about a number on it, and the folded explanations that used
         to sit in bands of their own between the filters and the table.

   WHY   Joe, about the two bands under the /items filter row: "Also there are
         two boxes underneath. We have to find a different place for it, maybe
         on the side on the top. Make it shorter and put it on the top because
         that column heading should be right under the filter. It's taking up
         too much real estate space again."

         "On the side on the top" is this — the right-hand end of the section
         band that is already there. Nothing new is added to the page's
         height: the band existed, the space to its right was empty, and the
         two `.ghead` bands and two note bands that used to follow the filter
         row are gone from the flow altogether. That is what puts the table's
         column headings directly under the filter row.

         Everything on this shelf is ABOVE the pinned control strip, so it
         all scrolls away like ordinary page content and none of it is
         charged against the height the table gets once the strip pins.

   HOW   A flex row that WRAPS, right-aligned, pushed to the far end of the
         band by `margin-left: auto` exactly as a plain `.strip-note` is. It
         resets the band's uppercase, bold, letter-spaced heading type back
         to ordinary note type for everything inside it, so a sentence reads
         as a sentence and a disclosure arrow reads as a disclosure arrow.

         `min-width: 0` lets the shelf be squeezed narrower than its longest
         line and wrap inside itself rather than forcing the band wider than
         the window at 1280. `align-items: baseline` lines the arrows up with
         the sentences beside them rather than with the middle of the band.
   -------------------------------------------------------------------------- */
.strip .strip-shelf {
  margin-left: auto; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: flex-end;
  gap: 2px 16px; padding: 4px 0; text-align: right;
  font-size: var(--t-secondary); font-weight: 400; text-transform: none;
  letter-spacing: normal; color: var(--c-ink-2);
}

/* A disclosure ON the shelf. Closed — which is how it is at rest — it is one
   more item on a row that already existed and costs the screen no height at
   all, the same bargain `.cstrip .stripdisc` strikes on a control strip. The
   two lines are for the OPEN state: `max-width` stops the paragraph turning
   into one line the width of the window, and `min-width: 0` lets the item be
   squeezed rather than forcing the band wider. The body is left-aligned
   because a wrapped paragraph ragged on the left is very hard to read. */
.strip .strip-shelf .disc { max-width: 560px; min-width: 0; }
.strip .strip-shelf .disc-body { text-align: left; }
.ghead {
  min-height: var(--h-group-header); background: var(--c-band-service);
  display: flex; align-items: center; padding: 4px var(--pad-box);
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.6px; color: var(--c-ink-2);
}

/* ---------- CONTROL STRIP (filters) --------------------------------------- */
.cstrip {
  min-height: var(--h-control-strip); display: flex; align-items: center;
  gap: 10px; flex-wrap: wrap; padding: 6px var(--pad-box);
  border-bottom: 1px solid var(--c-border-item);
}
.cstrip label { font-size: var(--t-control); color: var(--c-ink-2); }
.cstrip .fieldgroup { display: flex; align-items: center; gap: 6px; }
/* The standard-vs-custom filter is the only MULTI-select control in the
   product, so its four tick boxes are enclosed in a 1px item border to read
   as one control rather than four loose ones. No new border weight, no
   radius: the same grammar as everything else. */
.cstrip .clsfilter {
  border: 1px solid var(--c-border-item); padding: 3px 8px; gap: 10px;
  background: var(--c-band-service);
}
.cstrip .clsfilter .tickbox { font-weight: 600; color: var(--c-ink); }

/* ---------- ONE STRIP, TWO JOBS — card t_1eff36c7 -------------------------
   WHAT  Two group boxes for a control strip that carries the FILTERS and the
         ACTIONS on one line. `.cactions` holds the action half; each
         `.cgroup` inside it is ONE action — its label, its control and its
         button — held together so a narrow window wraps whole actions rather
         than shedding a button onto a line away from the tickbox it belongs
         to. That is what happened at 1280 before `.cgroup` existed: "Show
         only items with a suggested match" finished one line and "22 items
         have one. Apply suggested match" began the next.

   WHY   /capacity-gaps had three control strips stacked vertically - the
         search, the capacity dropdown with its Apply, and the suggested-match
         tickbox with its Apply. Joe, scrolled down the queue: "when I scroll,
         everything is, and the whole filter is hidden, all the action items
         are hidden... I think we need to combine this together with the
         action, or at least close to it."

         Pinning three stacked strips to the top of the window would have
         answered the letter of that and cost him a third of the screen. So
         they are one strip, and the space to the right of the search - which
         was empty on every one of those three rows - is where the actions
         went. It costs the page no vertical space at all: three rows became
         one at 1920.

   HOW   `.cstrip` is already a wrapping flex row, so this adds nothing to the
         layout model. `.cactions` is a nested flex row that WRAPS, with the
         same gap as its parent so the controls sit at the same rhythm as the
         ones outside it; each `.cgroup` inside it does NOT wrap, which is what
         keeps one action whole. Both are layout only: no control changes what
         form it belongs to, what it posts, or where it falls in tab order by
         being inside them.

         Measured on the live screen: at 1920 the strip is 87px and the table
         gets 1,032 of a 1,080px window; at 1280 it is 127px and the table
         gets 712 of 800. Before this card the three strips scrolled away
         entirely and the table got 840 and 560.
   -------------------------------------------------------------------------- */
.cstrip .cactions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.cstrip .cgroup {
  display: flex; align-items: center; gap: 10px; flex-wrap: nowrap;
}
.sel, .inp {
  height: var(--h-input); border: 1px solid var(--c-border-control);
  background: var(--c-surface); padding: 0 8px;
  font-size: var(--t-control); font-weight: 600; color: var(--c-ink);
  font-family: var(--font);
}
.inp { font-weight: 400; }
/* Long customer names must not stretch the control strip off the page. */
.sel { max-width: 220px; }
.go {
  height: var(--h-input); padding: 0 16px; background: var(--c-gold);
  color: var(--c-ink-invert); font-size: var(--t-secondary); font-weight: 700;
}

/* ---------- THE CHECKBOX DROPDOWN — card t_4adc06ee, REVERSED BY t_297d0a26
   Joe: "Maybe the standard-or-custom and cost-of column should also be this
   new idea of having a dropdown with checkboxes next to it." Built once here
   and used for every checkbox-group filter on /items, /margin-lines and
   /tier-distance - freight type, custom cost, tier, evidence level, distance.

   The browser's own <details>/<summary>, the same idiom .disc already uses,
   so it needs no script to open or close and stays keyboard-operable. The
   closed <summary> ALWAYS states where the filter stands - "Tiers: all" or
   "Tiers: 3 of 6" - computed server-side from the filter actually in force,
   never from an unsubmitted tick, so it is correct with JavaScript off too.

   THE OPEN BODY FLOATS OVER THE PAGE - WHEN JAVASCRIPT IS RUNNING. It is
   absolutely positioned against the <details>, so opening or closing a filter
   moves nothing else on the screen by a single pixel. With JavaScript off it
   flows in the document instead and pushes the page apart, which is the older
   behaviour and is deliberate; the JAVASCRIPT OFF paragraph below says why.

   WHAT THIS COMMENT USED TO SAY, AND WHY IT IS NOW WRONG.
   Card t_4adc06ee wrote here that the body deliberately FLOWED IN THE
   DOCUMENT the way .disc-body does, and did not float. The stated reason:
   with ten filters on one control strip the strip wraps onto a second line
   at ordinary widths, so a floating panel opening under a first-line control
   would sit on top of whatever the SECOND line holds - on the margin grid,
   the Go button itself - and a reader who opened the Tier dropdown could no
   longer reach Go without closing it again. Growing the strip's own height
   instead pushed the wrapped line down rather than covering it.

   Joe overruled that in card t_297d0a26: "This new drop-down with the
   checkboxes - the whole screen jumps. It doesn't hover like a drop-down.
   When there's a regular drop-down it hovers, it doesn't move all the
   controllers." He is right, and a native <select> option list has floated
   over the page since the first browser that had one.

   The Go-button objection is answered rather than ignored, by the three
   close behaviours card t_297d0a26 added in app.js's wireCheckboxDropdowns():
   opening a second dropdown closes the first, clicking anywhere outside
   closes it, and Escape closes it. That is exactly what a native dropdown
   does, so a panel is never STUCK over the Go button - it goes away the
   moment the reader reaches for anything underneath it. The old objection
   was really an objection to a panel that could only be dismissed by going
   back to the control that opened it, and that panel no longer exists.

   JAVASCRIPT OFF - THE ONE PLACE THE OLD OBJECTION STILL BITES.
   Every one of those three close behaviours is script. Turn JavaScript off
   and a floating panel has none of them, so it is precisely the panel the old
   objection described: on /tier-distance and /margin-lines the control strip
   wraps, an Evidence level panel opened from the first row lands squarely on
   the Go button, and the only way out is to find the summary again and click
   it a second time. That was measured on the live site, not imagined - a real
   click on the Go button was intercepted by the panel and timed out.

   So the float is scoped under `.js`, a marker an inline script at the top of
   base.html's <head> puts on <html> as the page's very first act. No script,
   no marker, no float: the panel flows in the document exactly as it did
   before card t_297d0a26 and nothing is ever covered. Card t_297d0a26's brief
   allowed that fallback in as many words - "with JS off the panel may fall
   back to pushing the page apart - that is acceptable". The float is a trade:
   nothing on the page moves, paid for by close behaviours. Where the close
   behaviours cannot exist, the trade is not available and is not taken.

   z-index 50 is deliberate and has to stay above 2: the scrolling table
   pane's sticky column headers carry z-index 2 (see .tablescroll below) and
   the active object tab in the crown carries z-index 2 as well, and either
   of those would otherwise paint straight over an open panel. Nothing on the
   path from .ddown-body up to <body> creates a stacking context or clips
   overflow - measured, not assumed - so one z-index here is enough and no
   ancestor has to be given `overflow: visible` to let the panel out.

   No box-shadow: rule 4 in tokens.css reserves that for exactly two surfaces,
   and this dropdown is not one of them. The 2px container border is what
   gives the open list its edge instead. -------------------------------- */
.ddfilter { display: inline-flex; align-items: flex-start; gap: 6px; }
/* The panel's positioning parent. It is the <details> rather than the
   surrounding .ddfilter span so that a right-aligned panel lines up with the
   right edge of its own SUMMARY, not with the far edge of the ↺ Clear link
   sitting beside it. Left unscoped rather than put under `.js`: with the
   panel in flow there is nothing absolutely positioned inside a .ddown for
   this to be the parent of, so with JavaScript off the line changes nothing
   whatsoever. */
.ddown { position: relative; }
.ddown > summary.ddsum {
  height: var(--h-input); border: 1px solid var(--c-border-control);
  background: var(--c-surface); padding: 0 10px; display: flex;
  align-items: center; gap: 6px; white-space: nowrap; cursor: pointer;
  list-style: none; font-size: var(--t-control); font-weight: 600;
  color: var(--c-ink); font-family: var(--font);
}
.ddown > summary.ddsum::-webkit-details-marker { display: none; }
.ddown > summary.ddsum::after { content: "\25BE"; color: var(--c-ink-3); font-size: 10px; }
.ddown[open] > summary.ddsum { border-color: var(--c-crown); }
.ddown[open] > summary.ddsum::after { content: "\25B4"; }
.ddown > summary.ddsum:focus-visible {
  outline: var(--focus-ring); outline-offset: var(--focus-offset);
}
/* The open panel itself. Everything here - its size, its own scrolling, its
   2px container border, its lack of a shadow - is what it looks like in BOTH
   worlds. Only where it sits differs, and that is the .js rule below. This
   base rule is the JavaScript-off one: `margin-top: 2px` and no positioning,
   so the panel flows in the document and grows the control strip, exactly as
   it did before card t_297d0a26. */
.ddown-body {
  margin-top: 2px;
  min-width: 220px; max-width: 340px; max-height: 240px; overflow-y: auto;
  background: var(--c-surface); border: 2px solid var(--c-border-container);
  padding: 8px 10px; display: flex; flex-direction: column; gap: 6px;
}
/* THE FLOAT, and it only happens when JavaScript is actually running. `.js`
   is put on <html> by a one-line inline script at the top of base.html's
   <head> - see the comment there - so it is already on the element before the
   first paint and there is never a flash of the in-flow layout. See the
   JAVASCRIPT-OFF paragraph in the block above for why the float is tied to
   the script this way. */
.js .ddown-body {
  position: absolute; top: calc(100% + 2px); left: 0; z-index: 50;
  margin-top: 0;
}
/* Right-aligned to its summary instead of left-aligned, for a filter sitting
   close enough to the right-hand edge that a left-aligned panel would hang
   off the window. app.js measures each panel as it opens and adds this class
   only when it is actually needed; see flipPanelIfOffscreen(). Scoped under
   .js as well, because only a floating panel can hang off anything and only
   JavaScript ever adds the class. */
.js .ddown-body.ddown-body-right { left: auto; right: 0; }
/* A WIDER panel, for the one dropdown whose contents are sentences rather
   than tick boxes: the sample questions on /ask (card t_5147e2fd). The shared
   rule's 340px was measured against option labels like "Pickup" and "Tier 3";
   a seven-entry list of whole questions inside it wraps three times an entry
   and turns a glance into a reading exercise. This is a modifier rather than
   an edit to .ddown-body because three other screens depend on that width and
   none of them wants this one. max-height goes up with it for the same
   reason — seven questions, each with its own "what this one answers" fold,
   is more than 240px of list — but it is still capped and still scrolls, so
   the panel can never grow past the window.

   The width is set by min-width, not by max-width, and that is not an
   oversight. A floating panel is sized shrink-to-fit inside its positioning
   parent, and the parent here is the .ddown itself — barely wider than the
   summary — so the available width the browser offers the panel is tiny and
   max-width never comes into play at all. min-width is the only one of the
   pair that can make this panel wider; max-width stays as the guard that
   keeps it off a small window. Measured, not assumed: at min-width 320px the
   panel came out exactly 320px wide and the seventh question sat below the
   fold. */
.ddown-body.ddown-body-wide {
  min-width: 480px; max-width: 520px; max-height: 420px;
}
/* The list sits directly under the paragraph above it, which already carries
   the gap the flex column gives every child; the extra top margin .asklist
   has for its life on a page would double it. */
.ddown-body-wide .asklist { margin-top: 0; }
.ddown-body-wide p { margin: 0; }
.ddown-body .tickbox { font-weight: 400; color: var(--c-ink); white-space: nowrap; }
/* The per-group clear. Only ever rendered when that one group is actually
   filtering - see web/templates/_furniture.html's checkbox_dropdown() macro -
   so its presence on screen already means something without reading its
   text. The word "Clear" sits beside the circular-arrow icon rather than the
   icon alone, because a bare circular arrow reads as "reload the page" to a
   lot of people; Joe picked this icon off a sheet of five. */
.ddclear {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: var(--t-control); font-weight: 600; color: var(--c-link);
  text-decoration: underline; white-space: nowrap;
}
.ddclear-icon { font-size: 12px; text-decoration: none; }
/* "Clear all filters", plain text at the end of the row, visible only when
   at least one filter anywhere on the screen is on - see any_filters_on() in
   web/app.py. */
.clearall { font-size: var(--t-control); font-weight: 600; color: var(--c-link); }

/* ---------- DATA GRID -----------------------------------------------------
   No zebra striping and no vertical column rules: both were considered and
   removed by the design system. Rows are 44px on purpose. ---------------- */
.grid { width: 100%; border-collapse: collapse; }
.grid thead th {
  height: var(--h-column-header); background: var(--c-band-service);
  font-size: var(--t-column); font-weight: 700; color: var(--c-ink-2);
  text-align: left; padding: 0 10px; white-space: nowrap;
}
.grid tbody td {
  height: var(--h-row); padding: 4px 10px; font-size: var(--t-body);
  border-bottom: 1px solid var(--c-border-item); vertical-align: middle;
}
.grid tbody tr:hover { background: var(--c-band-service); }
.grid .r { text-align: right; font-variant-numeric: tabular-nums; }
.grid .strongcell { font-weight: 600; }
.grid td.sub { font-size: var(--t-secondary); color: var(--c-ink-2); }
.grid .this-line td { background: var(--c-band-header); font-weight: 600; }

/* ---------- SELECT ALL, IN THE TABLE HEADER — card t_b2f5c626 -------------
   WHAT  The tick column's header cell: a "Select all" tickbox with its label,
         and under it the running count of how many rows are ticked.

   WHY   Joe: "when you click Select All, actually selects all that is on the
         screen." Putting it at the top of the column of ticks is what makes
         that self-evident — a box at the head of a column can only mean the
         column beneath it. The old control floated above the capacity dropdown
         and had to be read to be understood.

         The count matters because the capacity gaps screen no longer shows a
         confirmation before it writes. How many rows are about to be acted on
         has to be legible at the moment the button is pressed, not after.

   HOW   The column is widened from the 28px a bare tickbox needed to fit the
         two words beside it, and the label wraps the tickbox so clicking
         either works. The header cell is `white-space: normal` because the
         grid's default nowrap would otherwise force the count onto the same
         line as the label. Nothing here changes the sticky header: the cell is
         still a plain `th` inside `.tablescroll`, so the rule at the foot of
         this file still pins it.
   -------------------------------------------------------------------------- */
.grid thead th.tickcol {
  width: 86px;
  white-space: normal;
  line-height: 1.25;
  vertical-align: middle;
}
.grid thead th.tickcol .tickall {
  display: flex; align-items: center; gap: 5px;
  font-size: var(--t-column); font-weight: 700; color: var(--c-ink-2);
  cursor: pointer;
}
.grid thead th.tickcol .tickall-count {
  display: block;
  font-size: var(--t-secondary); font-weight: 700; color: var(--c-crown);
}

/* ---------- PRODUCT GROUPING INSIDE ONE PANE — card t_e1bd743a -------------
   WHAT  The heading row that names a product on the tiers-by-distance report,
         and the totals row at the foot of a summary table.

   WHY   That report is read product by product, and each product carries its
         own tiers and its own distance bands underneath it. Drawing one table
         per product would have given the page a scrolling pane and a sticky
         header per product; one table with a filled heading row keeps a single
         pane, a single column header and one reachable sideways scrollbar.

   HOW   A heading is a FILLED BAND in this design system, so the row takes the
         section-header fill and the rule above it is the container weight —
         the heavier of the two — because it separates products rather than
         rows. No radius, no third border weight, no new colour: both rules use
         tokens that were already here. */
.grid tbody tr.prodhead td {
  background: var(--c-band-header);
  border-top: 2px solid var(--c-border-container);
  font-weight: 600;
}
.grid tbody tr.prodhead:hover td { background: var(--c-band-header); }
.grid tbody tr.prodhead .sub { font-weight: 400; }

.grid tfoot tr.totalrow td {
  background: var(--c-band-service);
  border-top: 2px solid var(--c-border-container);
  font-weight: 700;
}

/* Status colour is TEXT ONLY, and it is spent sparingly.
   A negative true margin is the exception worth colouring, so it prints in
   --c-urgent. An ordinary positive margin stays in ordinary ink: a column of
   green numbers is noise, and it would spend the one signal the eye has on
   the rows that are fine. --c-good is reserved for a single terminal
   statement - the coverage headline, and the margin on a drill-down. */
.pos { color: var(--c-good); }
.neg { color: var(--c-urgent); }
.grid .pos { color: var(--c-ink); }
.muted { color: var(--c-ink-3); }
.note { font-size: var(--t-secondary); color: var(--c-ink-2); padding: 10px var(--pad-box); }
.note.tight { padding: 6px var(--pad-box); }
.derived { font-size: var(--t-eyebrow); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.7px; color: var(--c-urgent); }
.actual { font-size: var(--t-eyebrow); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.7px; color: var(--c-ink-3); }

/* ---------- PAGINATION ----------------------------------------------------- */
.pag {
  min-height: var(--h-pagination); border-top: 2px solid var(--c-border-container);
  background: var(--c-band-service); display: flex; align-items: center;
  gap: 6px; flex-wrap: wrap; padding: 4px var(--pad-box);
  font-size: 12px; color: var(--c-ink-2);
}
.pg {
  border: 1px solid var(--c-border-control); background: var(--c-surface);
  padding: 3px 9px; font-size: 12px; font-variant-numeric: tabular-nums;
  text-decoration: none; color: var(--c-ink);
}
.pg.on { background: var(--c-crown); color: var(--c-ink-invert); border-color: var(--c-crown); font-weight: 700; }
.pagination-count { margin-right: 8px; }

/* ---------- FIELD ROWS (component library 5.1) ----------------------------
   Label column 180px, 13px/400, RIGHT-ALIGNED, 12px gutter. The label is not
   above the value; that is Dynamics inheritance and it is deliberate. ---- */
.fields { padding: 0 var(--pad-box); }
.c-field {
  display: flex; align-items: baseline; min-height: 34px; padding: 6px 0;
  border-bottom: 1px solid var(--c-border-field);
}
.c-field:last-child { border-bottom: none; }
.c-field__label {
  width: 220px; margin-right: 12px; flex: none; text-align: right;
  font-size: 13px; font-weight: 400; color: var(--c-ink-2);
}
.c-field__value { flex: 1 1 auto; font-size: var(--t-body); }
.c-field__value .big { font-size: 15px; font-weight: 700; }
.c-help { font-size: 13px; color: var(--c-ink-3); }

/* ---------- ARITHMETIC BLOCK ----------------------------------------------
   The sum, shown as a sum. There is no monospace anywhere in this product,
   so the working is set in the product face with tabular figures. ------- */
.sum { padding: 10px var(--pad-box); }
.sum .line { display: flex; align-items: baseline; gap: 10px; padding: 6px 0;
  border-bottom: 1px solid var(--c-border-field); flex-wrap: wrap; }
.sum .line:last-child { border-bottom: none; }
.sum .lbl { width: 220px; flex: none; text-align: right; font-size: 13px; color: var(--c-ink-2); }
.sum .calc { font-size: var(--t-body); font-variant-numeric: tabular-nums; }
.sum .calc .eq { font-weight: 700; }
.sum .why { font-size: 13px; color: var(--c-ink-3); width: 100%; padding-left: 232px; }

/* ---------- TWO COLUMNS OF BOXES ------------------------------------------ */
.cols { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.cols > * { flex: 1 1 420px; min-width: 0; }

/* ---------- A ROOMIER PAIR OF COLUMNS — card t_5247398f -------------------
   WHAT  The same two-column helper above, with a wider point at which the
         boxes give up and stack.

   WHY   The 420px basis is right for a box of field rows, which reads fine in
         a narrow column. A box holding a data TABLE needs more room than that
         before splitting the screen is an improvement rather than a squeeze.
         /tiers is the first screen to put two table boxes side by side.

   HOW   One number changes: the flex basis. Two 700px columns plus the 16px
         gap need 1416px, so on a 1920 window the pair sits side by side and on
         a 1280 window it wraps and stacks in DOM order. Nothing else about the
         helper changes and there is no second grid system. ---------------- */
.cols.roomy > * { flex: 1 1 700px; }

/* ---------- /distance: THE TALL COLUMN COMES DOWN — card t_638d3434 --------
   WHAT  `.cols.roomy.levelfit` makes a two-column row whose height is set by
         the FIRST column alone, and fits the second column into exactly that
         height, letting the table inside it scroll. Two helper classes go with
         it: `.levelfit-driver` is the column that sets the height, and
         `.levelfit-follower` is the column that is fitted to it.

   WHY   Joe, of /distance: "take this box… and move it up right under the box
         which distance — it's empty space. And the box next to it, make the
         same height, it should end the same place where this new box is going
         to end, so it looks good on the screen, doesn't look lopsided."

         On /distance the TALL column is the right one — the band ladder is
         about 1045px of rows, and the left column is about 440px even after
         the no-band box moves into it. So the plain answer, `align-items:
         stretch`, is the WRONG answer here: it makes every column as tall as
         the tallest, which would stretch the short left column down and leave
         some six hundred pixels of dead white inside it. The ends would match
         and the screen would look worse. Joe asked for the opposite — the
         bands box comes DOWN to where the left column ends.

         This is why /tiers must NOT use this modifier. There the tall column
         is the left one, it is full of controls, and the right box growing to
         meet it costs nothing; /tiers uses `.levelstretch` below.

   HOW   Three parts, and no fixed pixel or `vh` height anywhere — a hard-coded
         `calc(100vh - 240px)` on a whole column is the exact bug that broke
         /tier-distance and it is not coming back.

           1. A CONTAINER QUERY, not a media query. `.cols.roomy` gives each
              child `flex: 1 1 700px`, so the row is side by side only while
              the wrapper itself is at least 700 + 16 + 700 = 1416px wide, and
              below that it wraps and stacks. Asking the wrapper its own width
              tests exactly that condition, whatever the page gutter happens to
              be, which a window-width media query can only approximate.
           2. Inside the query the row becomes a stretch container, and the
              follower column's box is taken OUT OF FLOW (`position: absolute;
              inset: 0` inside a `position: relative` column). An out-of-flow
              box contributes no height, so the flex line's height is the
              driver column's own content height, and the follower is then
              stretched to exactly that and its box fills it.
           3. The follower's `.tablescroll` gives up the shared height ceiling
              and becomes the one part of that box that flexes, so the ladder
              scrolls INSIDE the box and the last band and the "Above the
              bands" row stay reachable.

         Outside the query — i.e. as soon as the row wraps — every one of those
         rules is off. Nothing is absolutely positioned, nothing is fitted, the
         boxes are full width in document order and the pane goes back to the
         ordinary ceiling. A browser too old for container queries lands in the
         same place, which is today's screen. ------------------------------- */
.cols.roomy.levelfit { container-type: inline-size; container-name: levelfit; }

/* The driver column is an ordinary stack of boxes. Its last box drops its
   bottom margin only in the fitted layout, so the two columns finish on the
   same pixel rather than 16px apart. */
.levelfit-driver > .box:last-child { margin-bottom: 16px; }

@container levelfit (min-width: 1416px) {
  .levelfit-driver > .box:last-child { margin-bottom: 0; }

  /* `align-self` and not `align-items` on the wrapper. A container query can
     only restyle the container's DESCENDANTS - the container itself is off
     limits, because a rule that changed its own size would be circular - so
     the stretch has to be asked for by the column that wants it. It is the
     same result: the follower is given the flex line's height, and the line's
     height is the driver's, because an out-of-flow box contributes none. */
  .levelfit-follower { position: relative; align-self: stretch; }
  .levelfit-follower > .box {
    position: absolute; inset: 0; margin: 0;
  }
  /* Everything except the scrolling pane keeps its natural height; the pane
     takes whatever is left. `min-height: 0` is what lets a flex child actually
     shrink below its content — without it the box would grow past the column
     it is supposed to match. */
  .levelfit-follower > .box > * { flex: 0 0 auto; }
  .levelfit-follower > .box > .tablescroll {
    flex: 1 1 auto; min-height: 0; max-height: none;
  }
}

/* ---------- /tiers: THE SHORT COLUMN GROWS — card t_638d3434 ---------------
   WHAT  `.cols.levelstretch` makes every column of the row as tall as the
         tallest one, so a row of two boxes finishes level at the bottom.

   WHY   Joe, of /tiers: "or stretch this box a little bit". On /tiers the tall
         column is the LEFT one — the band editor, which is all controls — and
         the right one, the tiers in force, is a short table. Growing the short
         table to meet the editor adds a little quiet space under five rows and
         costs nothing.

         /distance must NOT use this modifier. There the tall column is the
         band ladder and stretching would leave hundreds of pixels of dead
         white in the short column, which is the opposite of what was asked
         for. /distance uses `.levelfit` above.

   HOW   One line: `align-items: stretch` in place of the `flex-start` that
         `.cols` sets. No container query is needed here and none is used. Once
         the row wraps there is one box per flex line, a line is only as tall
         as the box on it, and stretching a box to its own height changes
         nothing — so the narrow layout is untouched by construction rather
         than by a second rule. ------------------------------------------- */
.cols.levelstretch { align-items: stretch; }

/* ---------- A TABLE THAT DOES NOT JUSTIFY ITS COLUMNS — card t_5247398f ---
   WHAT  Two modifiers on `.grid`. `.grid.hug` makes a table only as wide as
         its own cells need, instead of stretching to its container.
         `.grid.hug-last` keeps the table full width but lets the LAST column
         swallow every spare pixel, so each column before it is sized to its
         own content and they sit together at the left.

   WHY   `.grid` is `width: 100%`, which is right for a table of many columns
         filling a screen and wrong for a table of three. On /tiers the band
         editor's three cells were justified across 1880px: the tier name at
         the far left, the "From" box near the middle of the screen and the
         "Up to" box out at the right edge, with a blank gutter of several
         hundred pixels between them. Joe: "It's too much empty space on this
         page." The empty space was INSIDE the boxes, not around them, so
         halving the box width on its own would have made it worse.

   HOW   `width: auto` hands the table back to the browser's automatic layout,
         which sizes each column to its content and then stops. `hug-last`
         gets the same effect for a table whose final column is a sentence
         rather than a figure: a cell asked for 100% of the table takes
         whatever is left once the other columns have taken what they need,
         which pulls the narrow columns together at the left and gives the
         prose the remainder. Numbers are untouched by either rule — they keep
         their right alignment and their tabular figures, and nothing here can
         make a figure fold. ---------------------------------------------- */
.grid.hug { width: auto; }
.grid.hug-last th:last-child,
.grid.hug-last td:last-child { width: 100%; }
/* The row's NAME is not spare space. Asking the last column for 100% makes the
   browser squeeze every other column towards its narrowest possible width, and
   the first column is the one with a name in it — "Tier 1" came out folded onto
   two lines and "In no tier" onto three. Holding the name on one line hands
   that column its natural width back. A `.sub` note underneath the name is a
   sentence and is allowed to fold as sentences do. */
.grid.hug-last th:first-child,
.grid.hug-last td:first-child { white-space: nowrap; }
.grid.hug-last td:first-child .sub { white-space: normal; }

.breadcrumb { font-size: var(--t-secondary); color: var(--c-ink-2); }
.breadcrumb a { color: var(--c-link); }

/* ---------- PHASE 2 COMPONENTS --------------------------------------------
   Everything below is built from the same tokens and the same five rules as
   the components above: no radius, two border weights, headings are filled
   bands, no shadow outside the record window, and status colour is text only.
   Nothing here introduces a chip, a pill or a coloured row. -------------- */

/* A refusal is a heading band like any other heading band. The only thing
   that marks it out is the ink, because status colour in CONSULATE is text. */
.strip.refused { color: var(--c-overdue); }
.bigrefusal { font-size: 15px; font-weight: 700; color: var(--c-ink); }

/* ---------- ITEMS SCREEN --------------------------------------------------
   Rows here are taller than a normal 44px data row because each one carries
   two editable values, both of their originals, and an audit trail. The row
   height token still governs the minimum; the content decides the rest. */
.grid.items tbody td { vertical-align: top; padding-top: 10px; padding-bottom: 10px; }
.grid.items tbody tr:hover { background: none; }

.valuenow { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
/* "your correction" is a sentence fragment in accent ink, not a badge. */
.yours {
  font-size: var(--t-eyebrow); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.7px; color: var(--c-gold); margin-left: 8px;
}
.grid td .sub { font-size: var(--t-secondary); color: var(--c-ink-2); }
.grid td .sub.warn { color: var(--c-ink-3); }

/* An edit control sits inside the cell it edits, on its own line. */
.editrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.editrow .go:disabled,
.editrow .inp:disabled { opacity: 0.45; cursor: not-allowed; }
.tickbox { font-size: var(--t-control); color: var(--c-ink-2); display: flex; align-items: center; gap: 4px; }
.btn.small { height: var(--h-input); padding: 0 12px; font-size: var(--t-secondary); }

/* ---------- ASSUMPTIONS ---------------------------------------------------
   A field row per rule. The label column carries the rule's own title, so it
   is a little wider than the standard 220px and reads left-aligned. */
.c-field.assumption { align-items: flex-start; padding: 10px 0; }
.c-field.assumption .c-field__label {
  width: 280px; text-align: left; font-weight: 700; color: var(--c-ink);
}
.c-field.assumption .c-field__label .sub {
  font-size: var(--t-eyebrow); font-weight: 400; color: var(--c-ink-3);
  text-transform: uppercase; letter-spacing: 0.7px;
}
.ruledetail { margin-top: 6px; }

/* ---------- PHASE 3: THE QUESTION BOX -------------------------------------
   Same five rules as everything above: no radius, two border weights,
   headings are filled bands, no shadow outside the record window, status
   colour is text only. Nothing here is a chat bubble — an answer is rendered
   in the product's own field rows, arithmetic blocks and data grids, because
   the answer IS the product's data and should look like it. */

/* The question field is the one control on the page, so it takes the room the
   filter controls elsewhere have to share. */
.askfield { flex: 1 1 520px; }
.askfield .inp { width: 100%; min-width: 240px; }

/* The example questions and the follow-ups are a plain list of links. They are
   not buttons: pressing one is asking a question, which is navigation. */
.asklist { padding-left: 18px; margin-top: 6px; }
.asklist li { padding: 3px 0; }

/* The fact packet, printed in full. Whitespace is preserved because it is JSON
   and its shape carries meaning, but the face is the product's own: there is
   no monospace font anywhere in this product and this is not the exception. */
.packet {
  font-family: var(--font);
  font-size: 12px;
  line-height: 1.5;
  color: var(--c-ink-2);
  background: var(--c-band-service);
  border-top: 1px solid var(--c-border-item);
  padding: 12px var(--pad-box);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 460px;
  overflow: auto;
}

/* ---------- SCREEN 8: THE RAW TABLE BROWSER --------------------------------
   Built from the same five rules as everything above. Nothing new is
   invented here: the grid is the product's grid, the filter row sits inside
   the column-header band because it belongs to the columns, and the only
   genuinely new thing is horizontal scrolling — a table with 32 columns has
   to go somewhere, and the alternative is hiding columns, which is exactly
   what this screen must not do. */

/* The table picker holds every table in the database, so it needs more room
   than the 220px a filter select gets. */
.sel.wide { max-width: 420px; }

/* The capacity dropdown on the gaps screen. Card t_046c6208 change 2 put the
   code, its description from Pacron's capacity sheet and its units per truck
   in every option, which is a long line; at 420px the description was cut off
   at the very moment it was added, which would have defeated the point of the
   change. */
.sel.widest { max-width: 640px; }

/* One table may be wider than the screen. The box does not scroll; the grid
   inside it does, so the section band, the control strip and the pagination
   all stay put. The rules that make that pane work — the fixed height, the
   column header that stays put, the second scrollbar above it — are shared by
   every screen and live in the SCROLLING TABLE PANE section at the foot of
   this file. */

/* Column headings carry the real database name under the readable one. The
   heading is a sort link, so it is underlined like every other link in the
   product. */
.grid.raw { --rawhead-h: 46px; }
.grid.raw thead th {
  vertical-align: top; padding-top: 6px; padding-bottom: 6px;
  /* A fixed height, not `auto`, because the filter row underneath is pinned
     at exactly this offset when the pane is scrolled. See the SCROLLING TABLE
     PANE section at the foot of this file. */
  height: var(--rawhead-h);
}
.grid.raw thead th a { color: var(--c-ink-2); }
.colname {
  font-size: var(--t-eyebrow); font-weight: 400; color: var(--c-ink-3);
  text-transform: none; letter-spacing: normal; margin-top: 2px;
}
.sortmark { color: var(--c-gold); font-weight: 700; }

/* The filter row is part of the column-header band, not part of the data. */
.grid.raw .filterrow td {
  background: var(--c-band-service); height: auto; padding: 4px 6px;
  border-bottom: 1px solid var(--c-border-container); white-space: nowrap;
}
.sel.tiny, .inp.tiny { height: 24px; font-size: 12px; padding: 0 4px; }
.sel.tiny { max-width: 118px; }
.inp.tiny { width: 118px; }

/* Raw rows are single-line so a wide table stays scannable. The full text of
   anything trimmed is in the cell's tooltip. */
.grid.raw tbody td {
  height: auto; padding: 7px 10px; white-space: nowrap;
  max-width: 520px; overflow: hidden; text-overflow: ellipsis;
}
.grid.raw tbody td.r { font-variant-numeric: tabular-nums; }

/* A numeric column's heading is right-aligned, so its filter control belongs
   under the heading rather than at the far left of the same cell. */
.grid.raw .filterrow td.r { text-align: right; }

/* ---------- SCREEN 8: THE COLUMN PICKER -----------------------------------
   Same five rules as everything else. The picker is part of the page, not a
   drawer and not a modal, because CONSULATE has neither. It is a list of
   items, so it gets the 1px item border and nothing heavier. */

.picker {
  max-height: 460px; overflow-y: auto;
  border-bottom: 1px solid var(--c-border-container);
}
.pickrow {
  display: flex; align-items: center; gap: 10px;
  padding: 5px var(--pad-box);
  border-bottom: 1px solid var(--c-border-field);
  font-size: var(--t-secondary);
}
.pickrow:last-child { border-bottom: none; }
/* A column that is currently out of the way is shown in secondary ink while
   "Show hidden" is on, so the eye can tell it from the ones already in play.
   Status colour in CONSULATE is text, never a fill, so this is ink only. */
.pickrow.was-hidden .pickname { color: var(--c-ink-3); }
.pickname { font-weight: 600; min-width: 220px; }
.picktype { color: var(--c-ink-3); font-size: var(--t-eyebrow);
  text-transform: uppercase; letter-spacing: 0.7px; min-width: 190px; }
.picknote { color: var(--c-ink-2); }

/* The filter list for columns that are not in the grid. Same controls as the
   inline filter row, laid out as a wrapping list rather than a table row. */
.offfilters {
  display: flex; flex-wrap: wrap; gap: 8px 16px;
  padding: 8px var(--pad-box);
  border-bottom: 1px solid var(--c-border-item);
}
.offrow { display: flex; align-items: center; gap: 6px; }
.offname { font-size: var(--t-control); color: var(--c-ink-2); font-weight: 600; }

/* A statement that something is affecting the numbers off screen. Status
   colour is TEXT ONLY - no fill, no chip, no coloured row. */
.warnline { color: var(--c-overdue); }
.warnline strong { color: var(--c-overdue); }

/* The list of reasons a save was refused - card t_68f81c25. One line per
   fault, each naming the exact numbers that are wrong. Status colour is TEXT
   ONLY, in keeping with the rule above: no fill, no chip, no coloured row. */
.reasons { margin: 6px 0 0; padding-left: 18px; }
.reasons li { margin: 3px 0; }

.sep-dot { color: var(--c-ink-3); }

/* ---------- BREADCRUMB TRAIL (change 2) -----------------------------------
   WHAT  The row of clickable steps across the top of every entity screen,
         sitting between the page header and the canvas.
   WHY   The client asked to walk down from an order line into a Bill of
         Lading, an item, a capacity record, a salesperson or a price tier and
         climb back out the way he came. The trail is the path he actually
         took, so it has to sit where a path belongs: above the content, on
         every one of those screens, at rest.
   HOW   Built from the tokens like everything else. It is a service band, the
         same pale surface the pagination and the column headers use, with a
         1px item rule under it. No radius, no chip, no fill behind the
         current step - the step you are standing on is simply heavier ink,
         because in CONSULATE state is expressed as type, never as colour. */
.crumbs {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0 8px;
  padding: 7px var(--pad-box);
  background: var(--c-band-service);
  border-bottom: 1px solid var(--c-border-item);
  font-size: var(--t-secondary);
}
.crumbs .crumb { color: var(--c-ink-2); }
.crumbs a.crumb { color: var(--c-link); }
.crumbs .crumb.on { color: var(--c-ink); font-weight: 700; }
.crumb-sep { color: var(--c-ink-3); }

/* A step in the address that could not be used says so, in the same service
   band, in status ink. Never a fill, and it never repeats the bad text back. */
.crumb-note {
  padding: 6px var(--pad-box);
  background: var(--c-band-service);
  border-bottom: 1px solid var(--c-border-item);
  font-size: var(--t-secondary);
  color: var(--c-overdue);
}

/* The "open the thing this figure came from" link that sits in a heading
   band. It is a link in a band, so it inherits the band's type and only the
   colour changes. */
.strip-note a { color: var(--c-link); }

/* ---------- SCROLLING TABLE PANE ------------------------------------------
   WHAT  Turns any data table wrapped in `.tablescroll` into a pane with its
         own scrollbars: the column header stops at the top of the pane and
         stays there while the rows slide underneath it, and a horizontal
         scrollbar sits at the foot of the pane instead of at the foot of the
         page.

   WHY   The client asked for exactly this. On a wide table — the source-table
         browser can be fifty-odd columns — he lost the column heading as
         soon as he scrolled, so he could no longer tell which column he was
         reading, and the only way to go left or right was to scroll past
         several hundred rows to reach the bottom of the page. Both of those
         are the page scrolling when the TABLE should have been scrolling.

   HOW   Three parts, all CSS, none of them a JavaScript table library:
           1. `.tablescroll` gets a height ceiling and `overflow: auto`, which
              makes it a scroll container in both directions. The rows scroll
              inside it; the section band, the control strip and the
              pagination around it never move.
           2. `position: sticky; top: 0` on every header cell pins the header
              to the top of that pane. Sticky is pinned on the TOP axis only,
              so when the pane is scrolled sideways the header travels with
              the columns and stays over its own data. That is the whole
              reason for using sticky rather than a floating copy of the row.
           3. A sticky cell is painted over live rows, so it needs a solid
              fill of its own or the text underneath shows through it. Every
              header cell gets the service band colour explicitly, and its
              bottom rule is drawn as an inset shadow because a collapsed
              table border belongs to the table, not to the cell, and would
              be left behind when the cell sticks.

         The ceiling here is expressed against the viewport so the pane is as
         tall as the screen allows, minus the crown, the queue band, the page
         header and the box furniture above it. Nothing is clipped: everything
         the pane cannot show is reachable by scrolling the pane, and when the
         pane runs out of rows the wheel carries on scrolling the page as
         usual.

         240px is a rough allowance and it is only good enough for the small
         secondary panes on these screens — a five-row tier ladder, a history
         list. The one big grid on a screen is marked `.screenpane` instead
         and gets a measured ceiling; see the block immediately below.
   -------------------------------------------------------------------------- */
.tablescroll {
  overflow: auto;
  max-height: calc(100vh - 240px);
  /* A sideways flick on a trackpad must not be read as a "go back" gesture
     once the pane has run out of columns. Vertical chaining is deliberately
     left alone so the page still scrolls on when the rows run out. */
  overscroll-behavior-x: contain;
}

/* ---------- THE PANE THE SCREEN IS BUILT AROUND — card t_191d4a18 ---------
   WHAT  The one big grid on a screen. It is an ordinary `.tablescroll` pane —
         rows scroll inside it, its column header pins to its own top, its
         horizontal scrollbar is at its own foot — with a ceiling MEASURED
         from the two things that have to share the window with it: the
         control strip that pins above it, and the pagination below it. The
         result is that the pane ends before the bottom of the window and the
         pager sits under it, in view, at the same time as the column header.

   WHY   Joe, 1 September 2026, on four screens in a row: "This screen has a
         problem because it doesn't have a button. I have to scroll to the
         bottom until I reach the bottom part, which is a problem. This should
         have been visible, like the other screens." And on the margin grid,
         which still worked: "See, you see the part of the footer and the
         header you see on the same screen. I'm just rolling within this
         window. That's how it needs to be done. So if I roll here, then I go
         outside of the rolling part, I can move up and down this part so I
         can quickly reach the top part."

         What he is describing is a pane with a ceiling. The four screens had
         lost theirs: `.pagepane`, which card t_da7aabcc pass 2 introduced,
         took the ceiling off so the WINDOW did the scrolling and the document
         grew as tall as the rows — 11,345px on /items. The pager was then
         ten thousand pixels below the fold. This class is the ceiling coming
         back, and `.pagepane` is gone.

         Pass 1 of t_da7aabcc is the OTHER way to get this wrong, and it is
         ruled out here as firmly. It sized the pane so the whole page fitted
         the window exactly, which froze the top of the page: "Nothing scrolls
         up. The header doesn't scroll up. I want it to scroll up until where
         my line is." So the pane is NOT sized to what is above it today; it
         is sized to what must still be above it once the page has scrolled as
         far as it goes.

   HOW   One declaration and two measured numbers, both written by app.js and
         neither of them a constant:

           --top-h   from the TOP of the pinned control strip to the top of
                     the pane. That is the strip's own height plus anything
                     between the two, and it is 0px on a screen that has no
                     pinned strip. It is what must still be on screen when the
                     strip has reached the top of the window.

           --foot-h  from the foot of the pane to the foot of the furniture
                     underneath it — the pager, and the page-size chooser
                     where there is one. That is what must be on screen under
                     the pane.

         The pane is `100vh` minus the two. The arithmetic that falls out of
         that is the whole point of the class, so it is worth writing down.
         Call A the height of the page title and summary ABOVE the strip:

           - the document ends up exactly `100vh + A` tall, so the page
             scrolls by A and by nothing more;
           - scrolled to that end, the strip is at the top of the window, the
             title has gone, the pane fills everything between the strip and
             the pager, and the pager is on the bottom edge. Nothing overlaps
             and nothing is wasted.

         Both of Joe's requirements are then true at once, and they are true
         because of each other: the page scrolls exactly as far as its own
         title band, which is what "I can move up and down this part so I can
         quickly reach the top part" asks for, and the pane is exactly as tall
         as the window leaves, which is what "you see the part of the footer
         and the header on the same screen" asks for.

         A is not free. Every pixel of pane height above "the whole page fits
         the window" is a pixel the page has to scroll before the footer comes
         into view, and there is no third option: a document that is exactly
         one window tall is a document with a frozen top. So on a screen whose
         title band is taller than its pager, the pager starts just below the
         fold and one short scroll — a third of a screen, against the ten
         thousand pixels it replaces — settles the page for good. That is
         measured per screen in REPORT_t_191d4a18.md.

         Both fall back to something sane, because neither is load-bearing:
         with JavaScript off `--top-h` is 0px and `--foot-h` is 240px, which
         is the plain `.tablescroll` ceiling above, and the pane behaves as it
         did before this class existed.
   -------------------------------------------------------------------------- */
.tablescroll.screenpane {
  max-height: calc(100vh - var(--top-h, 0px) - var(--foot-h, 240px));
}

/* The control strip that pins. Sticky, not fixed: before the page has
   scrolled that far the strip sits in the flow exactly where it always did,
   which is the whole difference between this and pass 1 of card t_da7aabcc.
   Its z-index of 40 is above the header cells' 2, so rows pass UNDER it
   rather than through it; the floating filter panels of card t_297d0a26 are
   inside it and so travel with it. */
.cstick {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--c-surface);
}

.tablescroll > .grid > thead > tr > th,
.tablescroll > .grid > thead > tr > td {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--c-band-service);
  box-shadow: inset 0 -1px 0 var(--c-border-item);
}

/* ---------- TWO HEADER ROWS, BOTH PINNED ---------------------------------
   WHAT  The source-table browser is the one grid with TWO header rows: the
         column names and, under them, the filter controls that belong to
         those columns. Both stick to the top of the pane, one under the
         other.

   WHY   Card t_451d2114. On a 57-column table a reader who has scrolled a
         few rows down is looking at columns of bare values with nothing
         naming them, which is unreadable. Joe: "when you scroll up, that part
         becomes sticky together with this."

   HOW   The filter row stops one column-name row's height down. `--rawrow-h`
         is that row's own measured height, written by app.js on load, on
         resize and whenever a fold on the page opens or shuts — the three
         things that change it. `--rawhead-h` is the fallback for a reader
         with JavaScript off, and it is the same 46px the row is drawn at, so
         nothing moves if the measurement never arrives.

         Both rows are measured against the PANE, because the pane is a scroll
         container again — card t_191d4a18 gave it its ceiling back. That is
         why `--strip-h`, `--rail-h` and the `.nopan` clipping this rule used
         to be tangled up with have all gone: they existed to pin these rows
         to the WINDOW while the pane was as tall as the whole document, and
         the pane no longer is.
   -------------------------------------------------------------------------- */
.tablescroll > .grid.raw > thead > tr.filterrow > td {
  top: var(--rawrow-h, var(--rawhead-h));
  z-index: 1;
  box-shadow: inset 0 -1px 0 var(--c-border-container);
}

/* ---------- THE SCROLLBARS THEMSELVES --------------------------------------
   WHAT  Draws the scrollbars on the table panes, and on the strip above them,
         as permanently visible grey bars in the product's own palette.

   WHY   Several browsers now default to an OVERLAY scrollbar: an outline that
         floats over the content, takes up no room and fades away when you
         stop scrolling. That is the wrong behaviour for this screen — a bar
         you cannot see is a bar the client cannot find, and the whole point of
         the change is that he can see, at a glance, that the table goes
         further left and right than the window does. Asking for a classic
         scrollbar makes the strip above the table honest: its bar shows both
         where he is in the table and how much of it he is looking at.

   HOW   `scrollbar-width` and `scrollbar-color` are the standard properties;
         the `::-webkit-scrollbar` rules do the same job in Chrome and Edge,
         where naming the pseudo-element is also what switches the overlay
         scrollbar off. Both are built from existing tokens: the track is the
         service band the column header sits on and the bar is the control
         border grey, so nothing new enters the palette and nothing gains a
         radius or a shadow.
   -------------------------------------------------------------------------- */
.tablescroll {
  scrollbar-width: auto;
  scrollbar-color: var(--c-border-control) var(--c-band-service);
}
.tablescroll::-webkit-scrollbar { width: 14px; height: 14px; }
.tablescroll::-webkit-scrollbar-track { background: var(--c-band-service); }
.tablescroll::-webkit-scrollbar-thumb {
  background: var(--c-border-control);
  border: 3px solid var(--c-band-service);
}
.tablescroll::-webkit-scrollbar-corner { background: var(--c-band-service); }

/* ---------- DISCLOSURE — card t_90896ad6 -----------------------------------
   WHAT  The little arrow that folds an explanation away, leaving a one-line
         headline that still carries the meaning. Built on the browser's own
         <details>/<summary>, styled into the CONSULATE language.

   WHY   Joe: "there's a lot of words there... maybe it should be a little
         arrow where you can click and see what's under that." The words are
         WHY the client can trust the number, so none of them are deleted -
         they are folded. Fewer words at rest, not fewer words.

   HOW   No radius, no shadow, no animation - just show and hide. The marker
         is a plain triangle drawn in text, rotated 90 degrees when open, so
         no image and no icon font enters the product. The headline reads at
         --t-secondary in secondary ink like every other note on these
         screens, and the folded body is set off by a single 1px item-weight
         rule on its left edge, which is the existing grammar for "these are
         items inside something", not a new one.

         The default browser marker is switched off in both dialects
         (list-style on the summary, and the WebKit pseudo-element) so there
         is never a second arrow beside ours.
   -------------------------------------------------------------------------- */
.disc { margin: 0; }
.disc + .disc { margin-top: 2px; }

.disc > summary.disc-head {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: var(--t-secondary);
  color: var(--c-ink-2);
  padding: 4px 0;
}
.disc > summary.disc-head::-webkit-details-marker { display: none; }
.disc > summary.disc-head:hover { color: var(--c-ink); }
.disc > summary.disc-head:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* The marker. A text triangle, so it inherits ink and size and needs no asset.
   `flex: none` keeps it from being squeezed when the headline wraps. */
.disc > summary.disc-head::before {
  content: "\25B8";
  flex: none;
  color: var(--c-ink-3);
  font-size: 11px;
  line-height: 1.4;
  transform: translateY(-1px);
}
.disc[open] > summary.disc-head::before { content: "\25BE"; }
.disc[open] > summary.disc-head { color: var(--c-ink); font-weight: 600; }

/* The folded half. Indented past the marker and ruled on the left so it reads
   as belonging to the headline above it rather than as the next paragraph. */
.disc-body {
  font-size: var(--t-secondary);
  color: var(--c-ink-2);
  border-left: 1px solid var(--c-border-item);
  padding: 2px 0 6px 12px;
  margin: 0 0 0 5px;
}
.disc-body p { margin: 0 0 6px 0; }
.disc-body p:last-child { margin-bottom: 0; }

/* ---------- A DISCLOSURE ON A CONTROL STRIP — card t_da7aabcc -------------
   WHAT  The one arrow that sits ON a control strip, in the row of controls,
         rather than in a note band of its own underneath it.

   WHY   Joe, about the two bands that used to sit under this strip: "some
         more words which I don't want there... You can put it after the
         Apply, between Apply and Open. There's enough space to push in. In
         other words you don't need to use space for the screen." Closed —
         which is how it is at rest — the arrow and its headline are one more
         item on a row that already existed, so the explanation costs the
         screen no height whatsoever. That was the whole point of the move.

   HOW   Nothing at all is needed to make the closed state work: the strip is
         a wrapping flex row and a <details> is just another item on it. The
         two lines below are for the OPEN state. `max-width` stops the
         paragraph from stretching to the width of the window and turning
         into one long line, and `min-width: 0` lets the item be squeezed
         narrower than its longest word rather than forcing the strip wider
         than the box.

         When it is opened the row gets taller, exactly as it would if a
         filter wrapped onto a second line, so the pinned strip above the
         grid gets taller too. Card t_191d4a18: the grid's own column header
         is pinned to the top of the PANE, which starts below the strip
         whatever height it is, so nothing has to be re-offset for that. What
         IS re-measured on every fold toggle is `--top-h` — a taller strip
         leaves the pane less room — see `.screenpane` above.
   -------------------------------------------------------------------------- */
.cstrip .stripdisc { max-width: 520px; min-width: 0; }

/* A disclosure sitting inside a .note block must not inherit the note's own
   padding a second time, or every folded section would step further right
   than the sentence above it. */
.note > .disc, .note > .disc-body { padding-left: 0; }

/* ---------- THE TIERS-BY-DISTANCE GRID — card t_0f3ca42b --------------------
   WHAT  The grid the client drew: the price tiers across the top with Outlier
         and Total beside them, the distance bands down the side, and inside
         every box a stack of figures — Margin %, Revenue, Orders, Quantity —
         with the names of those figures printed ONCE in a narrow gutter at the
         left edge of the row rather than repeated inside all seven boxes.

   WHY   The screen this replaced was a list of 2,356 rows repeating "Tier 1 …
         Tier 5" under every distance band. The client: "the tiers are gonna be
         the columns … and then on the side you're gonna have the distance …
         you made everything in rows."

         The gutter is what makes a box readable without a label in it. Every
         box on a row holds the same lines in the same order, so once the eye
         has read "Margin %, Revenue, Orders, Quantity" down the left edge it
         can read the numbers straight across without re-reading a word.

   HOW   Nothing new enters the palette and nothing gains a radius. The stacked
         lines inside a box are plain blocks at the secondary type size with a
         fixed line height, so a box for a tier a product never shipped in —
         which prints an em dash on every line — is exactly as tall as the box
         beside it and the rows stay level.

         Rows are `height: auto` here, unlike every other grid in the product:
         a box holding four figures cannot fit the fixed 44px data row, and
         forcing it would clip the fourth line. The 44px minimum is kept, so a
         one-figure grid looks like every other table on the screen.

         The Total column is separated by the CONTAINER border weight — the
         heavier of the two — because it is a different kind of thing from the
         tier columns beside it, not another tier. That is the existing
         grammar, not a new one, and no third weight is introduced.
   -------------------------------------------------------------------------- */
.grid.tdgrid tbody td { height: auto; min-height: var(--h-row); vertical-align: top;
  padding-top: 8px; padding-bottom: 8px; }
/* THE HEADING ROW'S OWN HEIGHT. Card t_7e5026d2's measured fidelity pass
   found this: the approved mockup gives every heading cell height:40px and
   padding-bottom:8px, and the port carried only the vertical-align. Live, the
   heading row stood 30px against the mockup's 40 and the words sat hard on the
   rule beneath them. It matters more than 10px sounds, because "Total" now has
   a second line of small type under it (Spread / Margin / Markup) and the
   padding is the air that keeps that trio off the first row of figures. */
.grid.tdgrid thead th { height: 40px; padding-bottom: 8px; vertical-align: bottom; }

/* THE RANGE UNDER A TIER'S NAME. Card t_5621d246.
   Joe approved a quiet second line under "Tier 1" saying which margin
   percentages that tier covers - `75–100%`. It has to read as a note on the
   heading, never as a second heading, and three things do that work:

   * 11px, which is SMALLER than the heading above it. The obvious token,
     --t-secondary, is 13px and the column heading itself is --t-column at
     12px, so the note would have come out BIGGER than the name it belongs to.
     11px is not invented either: it is exactly what the other second line in
     this same heading row already uses - `th.tdtotal .tot3`, the Spread /
     Margin / Markup trio - so the two second lines in this head are one size.
   * weight 400 against the heading's 700, which is the whole difference
     between a label and a note.
   * --c-ink-2, the app's own muted ink, the same token .countline uses for
     small grey type everywhere else in the product. No new hex value.

   `display: block` puts it on its own line; the 2px is the only air it needs
   inside a heading cell that already stands 40px tall. It inherits the
   heading's `white-space: nowrap` and its right alignment, so it sits under
   the last letter of the tier's name and can never wrap the column wider than
   the widest of its two lines. */
.grid.tdgrid thead th.r .tsub {
  display: block; font-size: 11px; font-weight: 400; color: var(--c-ink-2);
  margin-top: 2px; letter-spacing: 0.01em;
}

/* One figure inside a box, and one label in the gutter. Both use the same line
   height so the labels sit exactly on their numbers. */
.grid.tdgrid .mline {
  font-size: var(--t-body); line-height: 20px; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.grid.tdgrid .mline.none { color: var(--c-ink-3); }
.grid.tdgrid .mlabel {
  font-size: var(--t-control); color: var(--c-ink-2); text-align: left;
  font-variant-numeric: normal;
}

/* The whole box is the link to the order lines behind it, so the click target
   is the figures rather than a word beside them. It is still a link, so it is
   still underlined — on hover only, because seven underlined boxes on every
   row would be a wall of rules. */
.grid.tdgrid a.cellwrap { display: block; color: var(--c-ink); text-decoration: none; }
.grid.tdgrid a.cellwrap:hover { text-decoration: underline; color: var(--c-link); }

/* The two left-hand columns are given fixed widths so the gutter sits at the
   LEFT EDGE of the row, right beside the band name, rather than being pushed
   across the page by a long item description. Everything left over goes to the
   tier columns, whose figures are right-aligned and so stay level whatever
   width they end up at. */
.grid.tdgrid .tdname { width: 300px; max-width: 300px; }
.grid.tdgrid .tdname .sub { white-space: normal; }
.grid.tdgrid .tdgutter { width: 118px; white-space: nowrap; }
.grid.tdgrid .tdband { padding-left: 34px; color: var(--c-ink-2); font-weight: 600; }
.grid.tdgrid .tdtotal { border-left: 2px solid var(--c-border-container); }

/* CARD t_de6a87d1 — the group row.
   Measured, not eyeballed: driving the approved mockup and this screen into
   the three grouped states and reading the browser's own computed styles off
   both showed a GROUP row here rendering as an ordinary product summary row —
   weight 600 where the mockup says 700, and carrying the 2px container rule
   above it that separates one PRODUCT from the next.

   The mockup is deliberate about that. `tr.prodhead` is a product's summary
   line and gets the rule above it. `tr.grouprow` is a heading over a set of
   products, so it is heavier and it does NOT take a product separator: the
   rule would be drawing a product boundary where there is no product. The
   template puts both classes on a group row (`prodhead grouprow`), so the
   prodhead rules land on it first and these three take it back.

   Mockup lines 2160-2169. Kept in the mockup's own order and wording. */
.grid.tdgrid tr.grouprow td {
  background: var(--c-band-header);
  border-top: 0;
  font-weight: 700;
  height: var(--h-group-header);
}
.grid.tdgrid tr.grouprow.lvl2 td { background: var(--c-band-service); }
.grid.tdgrid tr.grouprow .lvltag {
  font-weight: 400; color: var(--c-ink-2);
  font-size: var(--t-secondary); margin-right: 6px;
}
/* The mockup indents a group's NAME by 8px, not the 10px an ordinary cell
   takes, because the fold triangle beside it already carries the indent. */
.grid.tdgrid tr.grouprow td.tdname { padding-left: 8px; }
.grid.tdgrid tr.grouprow .fold { margin-right: 4px; }

/* The fold control on a product's summary line. It is a form button because it
   WRITES — the open products are remembered against the reader — so it must
   not be a link the browser may prefetch. It is drawn as the triangle the
   disclosures elsewhere in the product use, and nothing else. */
.grid.tdgrid .foldform { display: inline; }
.grid.tdgrid .fold {
  border: 1px solid var(--c-border-control); background: var(--c-surface);
  color: var(--c-ink-2); font-family: var(--font); font-size: 12px;
  width: 22px; height: 22px; line-height: 1; padding: 0; margin-right: 6px;
  vertical-align: baseline;
}
.grid.tdgrid .fold:hover { background: var(--c-band-service); color: var(--c-ink); }

/* ---------- THE SIGN-IN SCREENS — card t_81a598e6 --------------------------
   WHAT  The login screen, the "forgotten your password" screen and the
         "choose a password" screen. One box, centred on the canvas, carrying
         the crest and almost nothing else.

   WHY   Joe asked for "a regular login screen — username and password", and
         the card's design rule was that it must look like it belongs to this
         application: same crest, same palette, almost no words. So these
         screens use the same navy, the same cyan crest tile, the same square
         corners and the same two border weights as everything else. They do
         NOT use the crown, because the crown is a navigation bar for screens
         a stranger may not see.

   HOW   Every value below is a token. No raw hex, no radius, no third border
         weight — the same rules that govern the rest of this file. The box is
         2px --c-border-container because it is a container, and the strip
         across its top is the crown navy, so the login screen reads as the
         top of the application rather than as a different product.
   -------------------------------------------------------------------------- */
.gatepage {
  min-height: 100vh; background: var(--c-canvas);
  display: flex; align-items: center; justify-content: center;
  padding: var(--gutter);
}
.gatebox {
  width: 380px; max-width: 100%;
  background: var(--c-surface); border: 2px solid var(--c-border-container);
}
.gatecrown {
  height: var(--h-crown-row1); background: var(--c-crown);
  border-bottom: 2px solid var(--c-crown-border);
  display: flex; align-items: center; padding: 0 var(--pad-box);
}
.gateform { padding: var(--pad-window); }
.gatelabel {
  display: block; font-size: var(--t-control); color: var(--c-ink-2);
  margin-bottom: 4px;
}
.gateinp { width: 100%; margin-bottom: 14px; }
.gatego { width: 100%; margin-top: 4px; }
/* Status colour is TEXT ONLY here, exactly as everywhere else in the product:
   no fill, no chip, no coloured row. */
.gateproblem {
  font-size: var(--t-body); font-weight: 700; color: var(--c-urgent);
  margin-bottom: 14px;
}
.gatenote { font-size: var(--t-body); color: var(--c-ink); margin-bottom: 14px; }
.gatehelp { font-size: var(--t-secondary); color: var(--c-ink-2); margin-top: 14px; }

/* Sign out sits in the crown's utility row, so it is drawn as one of that
   row's small white links rather than as a button — but it IS a button,
   because signing out is a POST and must not be a link a browser may
   prefetch or another site may embed. */
.signout {
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font); font-size: 12px; color: var(--c-ink-invert);
  text-decoration: underline;
}
.signout:hover { color: var(--c-gold); }

/* ---------------------------------------------------------------------------
   THE STAGING BAND — card t_cd17aaa3.

   WHAT  A full-width amber strip across the very top of every page on the
         staging copy of the system.

   WHY   Staging carries a copy of production data and looks identical to the
         live site. The client is sent the staging URL to review changes, so
         somebody is always one tab away from editing the wrong system. Amber
         on black is used nowhere else in CONSULATE, which is the point: it
         cannot be mistaken for part of the product.

   HOW   Static, not fixed: it occupies real space at the top of the document
         and pushes the navy crown down rather than covering it. Fixed
         positioning would need the page to reserve a matching offset, and any
         offset would be a guessed number.

         There is deliberately NO height property. The band is one line of text
         with padding around it, so on a narrow window the sentence wraps and
         the band grows to hold it. A fixed height would clip the wrapped line
         and the warning would be the thing that broke. Joe's standing rule:
         never a fixed pixel height in CSS.

         This rule ships in production's stylesheet too and costs nothing
         there, because production never renders an element with this class —
         the markup is absent, not hidden.
   --------------------------------------------------------------------------- */
.staging-banner {
  background: #f0a500;
  color: #1a1a1a;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: center;
  padding: 8px 16px;
  border-bottom: 3px solid #1a1a1a;
}
.staging-banner strong {
  font-weight: 800;
  letter-spacing: 0.08em;
}

/* ---------------------------------------------------------------------------
   THE SANDBOX BAND — card t_5d36cfb1.

   WHAT  The same strip as the one above, in magenta, on the sandbox copy.

   WHY   There are three Pacron sites on this box now: production, the staging
         copy Joe demonstrates from, and a SANDBOX with its own branch, its own
         database and its own public address. The sandbox was wearing no band
         at all, which is the hole the staging band exists to close - reopened
         by a new environment rather than by anybody editing that rule.

         MAGENTA AND NOT MORE AMBER, and this is the whole point of a second
         rule rather than a second sentence in the first one. Amber already
         means "staging" to everybody who uses this system. A sandbox wearing
         amber would answer "am I on production" and leave "am I on the machine
         Joe demonstrates from" exactly as dangerous as it was. Magenta on
         white is used nowhere else in this product and nowhere near the
         navy-and-gold chrome, so the three sites are one glance apart.

   HOW   Every structural decision is the staging band's, deliberately, so the
         two cannot drift into looking like different kinds of thing: static
         rather than fixed, so it occupies real space and pushes the navy crown
         down; no height property, so a wrapped sentence grows the band instead
         of being clipped; and the same 3px black rule underneath.

         It ships in production's stylesheet too and costs nothing there,
         because production never renders an element with this class - the
         markup is absent, not hidden.
   --------------------------------------------------------------------------- */
.sandbox-banner {
  background: #a3006d;
  color: #ffffff;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: center;
  padding: 8px 16px;
  border-bottom: 3px solid #1a1a1a;
}
.sandbox-banner strong {
  font-weight: 800;
  letter-spacing: 0.08em;
}

/* ---------- ITEM CLASSES — card t_04f1fcde, REBUILT BY t_759e1003 ----------
   WHAT  The shapes this one screen needs that nothing else in the design
         system already had: the rule fold and its hand-drawn arrow, the rule
         as editable lines, the waiting list under the item rows, the amber
         note that appears while a history date is being viewed, and the three
         floating windows (name a class, the expanded editor, settings).

   WHY   Card t_759e1003 rebuilt this screen to the approved mockup in
         docs/item_classes_mockup_v8.html. Every rule below is that mockup's
         own declaration, kept word for word so the drawing and the screen
         cannot drift apart. Everything else on the screen is `.box`, `.strip`,
         `.cstrip`, `.grid`, `.btn` and `.go` exactly as the tier and distance
         screens use them — there is no new grammar here and no framework.

         The four shapes phase 1 and 2 declared here and this rebuild no longer
         uses — `.classpick`, `.condrow`, `.aibox` and their children — are gone
         with the markup that used them. `.rulesay` stayed, because the rule
         SAID BACK IN PLAIN ENGLISH is a settled decision and still appears in
         two places: folded inside the Rule fold, and in the window that asks
         for a name, which is the last thing read before anything is saved.

   NOTE THE HEIGHTS. Two rules below set a pixel height and both are
         deliberate: the search sentence box must NOT grow as you type, because
         growing it pushed the whole page down and was rejected, and the AI
         rule box inside the fold is the one Joe asked to be "a little bit
         longer". Everything else is as tall as its content or bounded in vh.
   -------------------------------------------------------------------------- */

/* The rule, said back in plain English. It is what the reader is being asked
   to agree to, so it is set at body size on the service band rather than as
   another grey note. */
.rulesay {
  background: var(--c-band-service);
  border-left: 3px solid var(--c-crown);
  padding: 10px var(--pad-box);
  font-size: var(--t-body); color: var(--c-ink);
}
.rulesay .rulesay-label {
  display: block; font-size: var(--t-eyebrow); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.7px; color: var(--c-ink-2);
  margin-bottom: 4px;
}
.rulesay.empty { border-left-color: var(--c-border-control); color: var(--c-ink-2); }

/* ---------- THE WAITING BANNER -------------------------------------------
   Card t_ff6676e3. The "Asking the model" line used to live on the control
   strip beside Save class, at the FOOT of the item list. On a first search
   that is fine. On a second one it is not: Joe searched, got 400 rows, then
   scrolled back to the sentence box to refine his request and pressed Find -
   and the line that says something is happening was measured 17,265 pixels
   below the bottom of his screen. He pressed the button and the screen said
   nothing, so he could not tell working from hung.

   Joe: "this flies down to the bottom, see here now it's on the bottom, you
   can't see it" and "it'll be on top now, otherwise I have to scroll down".

   So the message is a banner pinned to the top of the window while the model
   is being asked. Fixed rather than sticky: sticky is relative to a scroll
   container and would still be lost inside a page eighteen thousand pixels
   long. It is only in the document while there is something to say, so it
   costs the screen nothing at rest - Joe's "a screen has to be almost empty"
   rule is not suspended for it.

   The blink is his own prescription: "I would like this to be to blink, if
   it's still empty to blink, then it's working ... so maybe it should blink
   green, on and off, on and off." Green is --c-good, the product's own. The
   blink is the WAITING state only, which is what "if it's still empty"
   means: once the answer is on screen the banner goes plain and then goes
   away. prefers-reduced-motion turns the flashing off and leaves the banner,
   because the information is the banner, not the flashing. */
.waitbar {
  /* Below `.shade` at 50, deliberately. A floating window is a question being
     asked of the reader and nothing may sit on top of it - if the banner is
     still standing when the naming window opens, it belongs behind the dim,
     with everything else on the page. */
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: none; align-items: center; gap: 10px;
  padding: 10px var(--pad-box);
  background: var(--c-surface); color: var(--c-ink);
  border-bottom: 2px solid var(--c-border-control);
  box-shadow: var(--shadow-window);
  font-size: var(--t-body);
}
.waitbar.on { display: flex; }
/* The lamp. A shape that blinks, so the blinking is not the words themselves
   flashing - text that flickers is hard to read and Joe has to be able to
   read the sentence while it works. */
.waitbar .lamp {
  width: 12px; height: 12px; flex: none; background: var(--c-good);
}
.waitbar.waiting {
  border-bottom-color: var(--c-good);
  animation: waitpulse 1s steps(1, end) infinite;
}
.waitbar.waiting .lamp { animation: waitpulse 1s steps(1, end) infinite; }
@keyframes waitpulse {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: .25; }
}
@media (prefers-reduced-motion: reduce) {
  .waitbar.waiting, .waitbar.waiting .lamp { animation: none; }
}
/* The page is pushed down by exactly the banner's height while it is there,
   so a pinned banner never covers the tab bar or the first control the
   reader was about to use. The height is written back by the script that
   shows it, because the sentence can wrap to two lines. */
body.haswait { padding-top: var(--waitbar-h, 44px); }

/* The microphone. It changes colour while it is recording, because a
   microphone that is listening and one that is not must never look the same. */
.micbtn.recording {
  background: var(--c-overdue); border-color: var(--c-overdue);
  color: var(--c-ink-invert);
}

/* What a model call cost and how long it took, printed beside the answer.
   Joe is paying per call and is entitled to see the meter. */
.aicost { color: var(--c-ink-3); font-size: var(--t-secondary); }

/* ---------- THE RULE FOLD -------------------------------------------------
   The rule is collapsed behind an arrow under Save class, below the item list.
   `.disc` above already folds things, but it folds a BLOCK the width of the
   box; this one sits inline on a control strip beside a button, so it is its
   own shape rather than a variant of that one. */
.rulefold { font-size: var(--t-secondary); }
.rulefold summary {
  cursor: pointer; color: var(--c-link); font-weight: 600;
  height: var(--h-button); display: inline-flex; align-items: center;
  padding: 0 6px; list-style: none;
}
/* This file hides the native disclosure marker on every fold in the product,
   so the arrow has to be drawn back on. Joe: "you took off the little arrow
   here that shows that you can actually expand it, so put it back." A text
   triangle, so it inherits ink and size and needs no asset. */
.rulefold summary::-webkit-details-marker { display: none; }
.rulefold summary::before {
  content: "\25B8"; display: inline-block; margin-right: 6px;
  transition: transform .12s;
}
.rulefold[open] > summary::before { transform: rotate(90deg); }
.rulefold p { color: var(--c-ink-2); margin: 6px 0 0; max-width: 72ch; }
/* CARD t_5d36cfb1 MOVED THE RULE FOLD ONTO `.disc` and this line is what it
   kept. The paragraphs inside that fold are long explanations - every revision
   the rule has had, every dated run - and they have always been held to 72
   characters, which is the measure this product reads at. `.disc-body p` sets
   only a margin, so without this they would have run the full width of the
   box. Nothing else about the fold's inside changed. */
.disc-body.rulebody p { color: var(--c-ink-2); margin: 6px 0 0; max-width: 72ch; }

/* The rule as editable lines: one row per condition, fields not a sentence,
   because the person has to be able to change a line he got slightly wrong. */
.ruleedit {
  border: 1px solid var(--c-border-item); background: var(--c-band-service);
  padding: 10px; margin-top: 6px; min-width: min(680px, 90vw);
}
.rulerow {
  display: flex; gap: 6px; align-items: center; margin-bottom: 6px;
  flex-wrap: wrap;
}
/* An AI rule has no fixed fields to edit - the model reads the sentence afresh
   on every run - so it is a textarea, and Joe asked for it "a little bit
   longer, since we have the space". Draggable taller from there. */
.ruleedit textarea.airule-text {
  width: 100%; height: 110px; padding: 8px; resize: vertical; line-height: 1.45;
}

/* An item the search found that is already active in ANOTHER class. The
   database refuses a second active membership, so the row is drawn in tertiary
   ink with its box disabled and the class it belongs to named in its
   description - the refusal has to be visible before Save, not at Save. */
.grid tbody tr.taken td { color: var(--c-ink-3); }

/* ---------- THE WAITING LIST ---------------------------------------------
   Below the item rows, never above them. The 2px rule is the container weight
   because it separates one list from another, not one row from another. */
.pending { border-top: 2px solid var(--c-border-container); margin-top: 10px; }
/* `.btn` in this file is a grid box that centres its label, which wraps
   "Reject (0)" onto two lines inside a 34px button. The two actions under the
   waiting list must each stay on one line. */
/* They also take the same height. `.go` is 30px everywhere else in the product
   and `.btn` is 34px, which is invisible when they are apart and reads as a
   mistake when they are the pair Accept/Reject side by side. The approved
   mockup draws them level, so here they are level. */
.pending .cstrip .btn, .pending .cstrip .go {
  white-space: nowrap; width: auto; height: var(--h-button);
  display: inline-flex; align-items: center; justify-content: center;
}

/* Shown only while a history date is being viewed, because in that mode Save
   class REMOVES whatever has been unticked, and nobody should discover that by
   accident. The amber is the approved mockup's own, and matches the staging
   banner's warning register rather than any status token - status colour in
   this design system is TEXT ONLY and never a band. */
.histnote {
  background: #fdf3e0; border-top: 1px solid #f0dcb0;
  border-bottom: 1px solid #f0dcb0; padding: 8px var(--pad-box);
  font-size: var(--t-secondary); color: #7a5200;
}

/* CARD t_818359db. The line that says what Update class is about to do, sitting
   directly above the two buttons it is about to describe. Deliberately NOT the
   amber .histnote band: this is not a warning, it is the caption on a control,
   and giving it the warning register would tell a reader that updating their
   own class is something to be careful of. So it is quiet ink at secondary size
   on the page's own background, the same register .countline uses, with the
   class name allowed to be bold because naming the right class is the whole
   point of the sentence. */
.updatesay {
  padding: 8px var(--pad-box) 0; font-size: var(--t-secondary);
  color: var(--c-ink-2);
}
.updatesay b { color: var(--c-ink); }

/* ===========================================================================
   ADD AND REMOVE — card t_5d36cfb1.

   Four shapes: the welded plus-and-minus pair, the Find button wearing its
   direction's colour, the row a Remove run wants taken out, and the row
   somebody took out by hand.

   NO NEW COLOURS. Green is --c-good #25AF56 and red is --c-overdue #7A1710,
   both already in tokens.css, and both are the ones the approved mockups draw.
   --c-overdue rather than --c-urgent for the reason pass 1 gave: urgent means
   a deadline inside four hours, which this is not, and the darker red carries
   white text at button weight. There is still no dedicated destructive token
   in this product; this card did not invent one.
   =========================================================================== */

/* THE WELDED PAIR. Two buttons with no gap between them, so they read as one
   control with two states rather than as two controls. They are one flex item
   on the strip - the span around them - which is what stops a line break ever
   falling between the plus and the minus.

   Measured at 34px each, 68px welded, 78px including the strip's own 10px gap.
   That is the whole cost of the direction control, against the 333px History
   gave back when it moved to the strip above. */
.dirpair { display: inline-flex; align-items: center; }

.dirbtn {
  height: var(--h-button); width: var(--h-button);
  border: 1px solid var(--c-border-control);
  background: var(--c-surface); color: var(--c-ink-2);
  font-family: var(--font); font-size: 16px; font-weight: 700;
  line-height: 1; cursor: pointer; padding: 0;
}
/* Welded: the second one lends its left border to the first one's right. */
.dirbtn + .dirbtn { margin-left: -1px; }
.dirbtn:hover { color: var(--c-ink); }

/* THE LIT ONE CARRIES ITS OWN DIRECTION'S COLOUR, and it is the same colour
   the button immediately beside it wears. The strip says one thing twice
   rather than two things once. */
.dirbtn.plus.on {
  background: var(--c-good); border-color: var(--c-good);
  color: var(--c-ink-invert);
}
.dirbtn.minus.on {
  background: var(--c-overdue); border-color: var(--c-overdue);
  color: var(--c-ink-invert);
}
.dirbtn:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }

/* THE BUTTON TAKES THE DIRECTION AS ITS NAME AND ITS COLOUR. `.go` is the gold
   Go button every screen in this product uses; these two are that button in
   the direction's own colour and nothing else about it changes. With no class
   open neither class is on it and it is plain gold Find, exactly as it was
   before this card. */
.go.dir-add    { background: var(--c-good); }
.go.dir-remove { background: var(--c-overdue); }

/* THE LINE UNDER THE STRIP. `.updatesay` is the register app.css already
   reserves for the caption on a control rather than for a warning, which is
   what this is: it says what the lit glyph means, it is always visible, and it
   is not telling anybody to be careful. The strip around it carries no top
   border, so it reads as belonging to the row above it rather than as a band
   of its own. */
.dirsay-strip { padding-top: 0; padding-bottom: 6px; min-height: 0; }
.dirsay { padding-top: 0; }
.dirsay-glyph {
  display: inline-block; width: 14px; font-weight: 700; color: var(--c-ink-2);
}

/* THE AMBER BAND ASKING WHETHER THE DIRECTION IS THE WRONG WAY ROUND. It is
   `.histnote`, this screen's own warning furniture, and no new kind of box was
   invented for it. The only thing added here is room under the sentence for
   the two buttons. */
.dirwarn .cstrip { padding-left: 0; padding-right: 0; min-height: 0; }

/* ---------- A ROW A REMOVE RUN WANTS TAKEN OUT ----------------------------
   THREE SIGNALS, and that is deliberate. The tick alone is too small to scan a
   hundred rows for; the tint alone is too close to the zebra the eye already
   ignores. The stripe is what you actually see.

   NOTHING HAS HAPPENED TO THIS ROW YET. It is still a member of the class and
   one click from staying, which is the whole of Joe's reading (a): "it should
   show the ones that it's planning to remove as unticked so they can see what
   it's removing and decide, and when they click save, that's when it
   happens." */
.grid tbody tr.leaving > td { background: var(--c-band-service); }
.grid tbody tr.leaving > td:first-child {
  box-shadow: inset 4px 0 0 0 var(--c-overdue);
}

/* ---------- A ROW SOMEBODY TOOK OUT BY HAND -------------------------------
   Grey ink, a grey stripe, and NO CHECKBOX AT ALL — a dash where one would be.
   The rule does not get a vote on this row, and a checkbox would say that it
   does. This is the distinction the whole feature rests on, so it gets its own
   treatment rather than a variation on unticked. */
.grid.takenout tbody tr.byhand > td { color: var(--c-ink-3); }
.grid.takenout tbody tr.byhand > td:first-child {
  box-shadow: inset 4px 0 0 0 var(--c-border-control);
}
.grid.takenout td.nobox { color: var(--c-ink-3); text-align: center; }
/* The Add button on the row is an ordinary secondary button and is NOT green.
   Green on this screen means "the next prompt adds items", which is a
   direction the rule runs in; this is a person putting one named row back, and
   dressing it in the direction's colour would tie the two together. */
.grid.takenout td .putback { white-space: nowrap; }

/* ---------- THE ACTION DOCK ----------------------------------------------
   Card t_1f4fb8ee. Update class, its escape hatch and the line describing
   them live at the FOOT of the item list, under the rows. That is the right
   place for them while the list is short. It is the wrong place for them on
   a real answer: Joe ran a rule on an open class, 243 rows were drawn, and
   the Update button landed 11,155 pixels down a 11,228 pixel page inside a
   643 pixel window - about seventeen screens below the fold. He read his
   screen, saw only the Saved classes strip, and reported that the feature had
   never been built. "I don't see what you showed me in the picture, Update
   class or Save as new." The bigger the answer the further the controls run
   away, so there is no scrolling habit that fixes this.

   So the box holding those three is fixed to the bottom of the window while
   there is something pending. FIXED, NOT STICKY, for the reason already
   written above on `.waitbar`: sticky is relative to a scroll container and
   is still lost inside a page this long. This is `.waitbar`'s answer to
   `.waitbar`'s problem, applied at the other end of the window - the bottom
   deliberately, so a waiting banner and a pending action never stack in the
   same hundred pixels, and so the actions never cover the Saved classes
   strip the reader is already looking at.

   z-index 40 is `.waitbar`'s, and below `.shade` at 50 for `.waitbar`'s
   reason: a floating window is a question being asked of the reader, and
   nothing may stand on top of it. When the naming window opens, the dock
   belongs behind the dim with everything else on the page.

   AT REST IT IS NOT A BAR AT ALL. No `.docked`, no fixed position, no
   background and no border - an ordinary control on the strip, in the place
   it has always been, costing the screen nothing. Empty screens stay empty
   is Joe's first rule for this screen and it is not suspended here. The
   sentence inside is hidden in that state by the script, so the resting strip
   is byte for byte the strip that was there before this card. */
.actiondock {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
}
.actiondock.docked {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  padding: 10px var(--pad-box);
  background: var(--c-surface); color: var(--c-ink);
  border-top: 2px solid var(--c-border-control);
  box-shadow: var(--shadow-window);
}
/* The sentence takes a whole row of its own, so it sits ABOVE the two buttons
   it describes rather than beside them - which is where card t_818359db put
   it and why. The strip's own side padding is dropped because the docked bar
   brings its own; kept, the line would be indented twice. */
.actiondock .updatesay { flex: 1 1 100%; padding: 0; }
/* Exactly the bar's own height is reserved under the page while it is docked,
   so a bar standing at the foot of the window cannot cover the last rows of
   the item list. The height is written back by the script that docks it,
   because the sentence wraps to two lines on a narrow window. */
body.hasdock { padding-bottom: var(--actiondock-h, 96px); }

/* ---------- THE FLOATING WINDOWS -----------------------------------------
   Three of them: name this class, the expanded editor both sentence boxes
   share, and settings. S2 in the design system, so --shadow-window is the one
   shadow they are allowed and there is no radius anywhere. */
.shade {
  display: none; position: fixed; inset: 0; background: rgba(14, 23, 65, .45);
  /* A window opens NEAR THE TOP, not dead centre. Card t_7e5026d2's measured
     fidelity pass found the approved mockup uses flex-start with 4vh of air
     above; the port centred it instead. The difference shows on the Grouping
     window, which grows a row taller with every level added - centred, the
     whole window jumps upward as you build a stack, and the Add-a-level button
     you are aiming at moves out from under the pointer. Anchored at the top it
     grows downward and the controls stay put. */
  align-items: flex-start; padding-top: 4vh;
  justify-content: center; z-index: 50;
}
.shade.open { display: flex; }
.win {
  background: var(--c-surface); border: 2px solid var(--c-crown-border);
  box-shadow: var(--shadow-window); width: min(520px, 92vw);
}
.wintitle {
  background: var(--c-crown); color: var(--c-ink-invert);
  height: var(--h-window-title); display: flex; align-items: center;
  padding: 0 var(--pad-window); font-size: var(--t-window-title); font-weight: 700;
}
/* The × close in a window's own title bar, and the spacer that pushes it to
   the far end. Card t_7e5026d2's measured fidelity pass found the approved
   mockup carries a × on every window and the port carried none, so a reader
   who reaches for the corner found nothing there. It closes and changes
   nothing, exactly like Cancel. */
.wintitle .spacer { flex: 1; }
.wintitle .xbtn {
  border: none; background: transparent; color: var(--c-ink-invert);
  font-family: var(--font); font-size: 18px; line-height: 1; cursor: pointer;
  padding: 0 2px;
}
.winbody { padding: var(--pad-window); }
/* The expanded editor: wide and tall, floating over the page, because a long
   request typed into a two-line slot cannot be read back. */
.winbig { width: min(860px, 94vw); }
.winbig .bigtext {
  width: 100%; height: min(46vh, 340px); padding: 8px; resize: vertical;
  line-height: 1.45;
}
/* ---------- THE NAMING WINDOW MUST FIT ON THE SCREEN ---------------------
   Card t_ff6676e3. This is the root cause behind two of Joe's four
   complaints, and they turned out to be one bug wearing two hats.

   "Name this class" had no height bound. It is as tall as its contents, and
   its contents include the rule said back in plain English. When the model
   answers with a long enumerated rule - Joe's was ninety-nine item-number
   ranges spelled out one at a time, "or whose code contains -200-, or whose
   code contains -201-" and so on - the window grows to about 1,400 pixels.
   A centred window taller than the window it floats in overflows at BOTH
   ends at once. The name box goes off the top and the Save button goes off
   the bottom. Measured in Joe's own recording: the name box was clipped to a
   sliver at the very top edge of the browser, and Save was roughly 700
   pixels below the bottom of the screen.

   That is exactly what he reported, in his own words: "I can't rewrite the
   name, see how it's hidden from the screen" - 4:08 - and "I can't even save
   it, I don't know the save button" - 4:19. NOTHING WAS MISSING. Both
   controls existed and both worked; a long rule had pushed them off the
   glass. So the fix is geometry, not new features.

   Three rules do it, and each is Joe's own prescription:

     * The window is capped at 88vh and lays out as a column, so it can never
       be taller than the screen no matter what the model writes.
     * The RULE is the part that scrolls, not the window - "so this has to be
       a scrolling box and smaller", 4:08. It is the only part that can grow
       without limit, so it is the only part that gets a scrollbar, and the
       name box and the buttons stay put above and below it where he is
       looking.
     * The name box and the buttons are flex:none, so a long rule can never
       squeeze either of them, and the button strip is pinned to the foot of
       the window on its own surface. Save is on screen the instant the
       window opens, whatever the rule says. */
.win.winfit {
  display: flex; flex-direction: column; max-height: 88vh;
}
.win.winfit .winbody {
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
}
/* The name box, and the button strip, never scroll and never shrink. */
.win.winfit .winbody > .icname,
.win.winfit .winbody > .winfoot { flex: none; }
/* THE RULE IS THE SCROLLING BOX. min-height:0 is what actually lets a flex
   child shrink below its content and scroll - without it the box refuses to
   shrink and the window grows again, which is the bug coming straight back. */
.win.winfit .rulesay {
  flex: 1 1 auto; min-height: 60px; overflow-y: auto;
}
/* The buttons sit on their own strip at the foot, with a rule above them, so
   the end of a scrolling rule never reads as the end of the window. */
.win.winfit .winfoot {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--c-border-field);
}

/* THE NAME BOX ITSELF. Joe: "this has to be a scrolling box and smaller".
   A single-line input already scrolls its own content horizontally as you
   type past the end - what it must not do is set the window's width from the
   length of what is typed into it. Explicitly bounded here so a long class
   name can never widen the window, in the same spirit as the 220px cap on
   `.sel` a few hundred lines above. */
.icname { width: 100%; max-width: 100%; }

/* One plain-English row of the settings window. */
.setrow {
  display: flex; align-items: center; gap: 14px; padding: 12px 0;
  border-bottom: 1px solid var(--c-border-field);
}
.setrow > div:first-child { flex: 1; }
.setrow:last-of-type { border-bottom: none; }
/* `.sel` is capped at 220px so a long value cannot stretch a control strip off
   the page. Inside a 520px window there is no strip to stretch, and a schedule
   that reads "No schedule — only when some" is worse than a wide box. */
.setrow select.sel { max-width: none; }

/* THE SEARCH SENTENCE BOX. A fixed two-line box that does NOT resize as you
   type: an earlier round made it grow, which pushed the whole page down, and
   Joe rejected it. Long requests belong in the expanded editor, which is what
   the button next to it is for.

   IT IS THE ONE ELASTIC CONTROL ON ITS ROW — card t_22570acb, second pass.
   Everything else on the Find the items strip is the width of the words
   printed on it and has nothing to give up. This box has, and it is the widest
   thing on the line, so it is the one that pays when the row runs out of room:
   the width it ASKS for is now the same as the smallest width it will accept,
   and it takes back every inch the row does not need. A tight window spends
   its last inches on the box instead of breaking the line; a roomy one still
   hands the box the room, which is what Joe sees at 1680 and above.

   Asking for more than it needs bought nothing and cost the whole row. With
   the Prompt builder button on the strip the line broke at 1500 — a normal
   window — and left New / Clear sitting alone underneath the Keyword box,
   while 124px of slack sat unused inside this box. 240px is the floor this box
   was already given and it is untouched: about eight words to a line, which is
   what keeps a typed sentence readable. */
textarea.asksentence {
  flex: 1 1 240px; min-width: 240px; height: 52px; padding: 6px 8px;
  resize: none; overflow: auto; line-height: 1.4;
  font-family: var(--font); font-size: var(--t-control);
}


/* ===========================================================================
   TIERS BY DISTANCE — THE APPROVED CONTROL BAR AND GRID. Card t_e4a6f09d.

   Eleven rounds of mockup, signed off by Joe on 10 September 2026. Every rule
   below is carried across from `/tmp/gen_tier_distance_mockup_v11.py`, which
   was itself drawn in this stylesheet's own tokens — so nothing here is a new
   look, it is the same look in the real screen.

   Scoped hard. The bar rules hang off `.mainbar` and the grid rules off
   `.grid.tdgrid`, both of which exist on this one screen, so /items and
   /margin-lines are untouched by every line of it.
   =========================================================================== */

/* --- THE MAIN BAR. TWO LINES, NEVER THREE. ---------------------------------
   Round 5, settled. Joe: "I'm going to agree with you, we should have the
   two-line control bar."

     line 1  the query and the filters
     line 2  the controls, hairlined off above

   The filter panel is NOT a member of this container. It is a sibling that
   sits BETWEEN the two bars in the document, which is the only way a block
   can land between two lines of controls without either line moving. Round 5
   merged the two into one flex container and the panel got pushed under both
   lines; Joe: "It should open up right under this, in between the grouping and
   the first line." Two containers, panel between them. */
.mainbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px;
  /* Less air above than below, so the Filters button rides up. */
  padding: 7px var(--pad-box) 11px;
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-border-item);
}
.mbrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.mbrow.mbend { margin-left: auto; }
.mbrow-q { order: 1; }
.mbrow-tbl { order: 2; }
.mbrow-ctl { order: 3; flex-basis: 100%; }
.mainbar.ctlbar { padding-top: 10px; }

/* ---- CARD t_88ec9e11, ITEM 1. ONE HEIGHT ON THE WHOLE CONTROL BAR --------
   Joe's Loom, 0:09-0:31: "You see the height of this, and you see the height
   of the filters. It should be the same height ... This is a certain height,
   but the save view and grouping is higher. It shouldn't. It should always be
   the same." Then, panning to the row below: "look here, everything looks
   nicely the same."

   THE HEIGHT IS READ OFF THAT ROW, NOT INVENTED. The row he called nice is
   the query/table row, and every control on it is `--h-input` = 30px: the
   Item box (`.inp`), the View picker (`.inp sel`), "Show in every box"
   (`.ddsum`), Expand and Collapse (`.btn small`). Only three controls broke
   it, and all three for the same reason - they are `.btn`/`.foldbtn`, which
   are `--h-button` = 34px: FILTERS, GROUPING and SAVE VIEW. Those are the
   exact three he named.

   So this rule does not set a number; it puts the bar's buttons on the SAME
   TOKEN the rest of the bar already uses. `--h-button` is untouched - it is
   still what a page-level button is elsewhere in the application - because
   this is a statement about the control bar, not about buttons everywhere.

   Scoped to `.mainbar` so nothing outside the bar moves. */
.mainbar .btn,
.mainbar .foldbtn { height: var(--h-input); }

/* "I would push up the filter button a little bit." The bar's padding is
   already top-light; the button WAS lifted a further 3px off it.

   CARD t_88ec9e11: that lift existed because Filters was 34px on a row of
   30px controls, so centring it left it overhanging both sides and Joe wanted
   the overhang taken off the top. Now that it is 30px like everything beside
   it, `align-items: center` alone lands it exactly on its neighbours' line,
   and keeping the 3px would push it 3px ABOVE them - reintroducing, upwards,
   the very mismatch item 1 is about. Measured both ways before removing it. */

.mbfield { display: flex; align-items: center; gap: 6px; min-width: 0; }
.mbfield > label { font-size: var(--t-control); color: var(--c-ink-2); white-space: nowrap; }
.mbfield .inp.itemq { width: 172px; }

/* THE FILTERS CONTROL. It is an <a> on the real screen rather than the
   mockup's <button>, because the panel's open state is one thing in the
   address and a link is what changes an address — see the template. It is
   drawn as a button, because that is what it is to look at. */
.foldbtn {
  font-family: var(--font); font-size: var(--t-body); font-weight: 700;
  height: var(--h-button); border: 1px solid var(--c-border-control);
  background: #fff; padding: 0 12px; cursor: pointer; display: inline-flex;
  align-items: center; gap: 8px; white-space: nowrap;
  color: var(--c-ink); text-decoration: none;
  /* The mockup drew this as a <button>, which centres its own text; the app
     draws it as a real <a> because the panel's open/shut state is a
     server-side preference and a link is what carries it. An <a> inherits
     text-align:start instead, which card t_7e5026d2's fidelity pass measured
     as a difference from the approved screen. Stated outright so the two
     agree regardless of which element carries the class. */
  text-align: center;
}
.foldbtn:hover { border-color: var(--c-crown); }
.foldbtn .arw { display: inline-block; transition: transform .12s; }
/* CARD t_de6a87d1, with the glyph change in the template above: the mockup
   turns its one triangle rather than swapping it for a second character. */
.foldbtn.open .arw { transform: rotate(90deg); }
.foldbtn .n { font-weight: 400; color: var(--c-ink-3); }

/* Card t_7e5026d2, stage 2. The Grouping button's own count badge - how many
   levels are stacked - drawn the same muted way #fbtn's Filters count is. */
#groupingbtn .n { font-weight: 400; color: var(--c-ink-3); }

/* --- THE GROUPING DIALOG ---------------------------------------------------
   One row per stacked level, built client-side onto `.win .winbody` - see
   #gshade in tier_distance.html. Reuses `.sel` (the app's own <select>) and
   `.btn small` (its own small button) rather than inventing either. */
.glevel {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 6px 0;
}
.glevel .rank {
  font-size: var(--t-secondary); color: var(--c-ink-2); width: 62px;
  flex: 0 0 auto;
}
.glevel .sel { min-width: 150px; }
.glevel .lbl { font-size: var(--t-control); color: var(--c-ink-2); }

/* CARD t_88ec9e11, ITEM 2. THE LEVEL'S OWN ×.
   Joe: "Remove should be a little icon here with the X. When I click on it,
   it takes it away."

   This is `.chip .x` restated for the one place it is not inside a `.chip` -
   same size, same muted ink, same red on hover - rather than a second removal
   icon with its own look. The only addition is `margin-left: auto`, which
   parks it at the right-hand end of its level row: on a chip the × follows
   the text immediately, but a level row is a full-width line and an × halfway
   along it reads as belonging to the sort dropdown beside it. */
.glevel .x {
  border: none; background: transparent; font-family: var(--font);
  font-size: 14px; line-height: 1; color: var(--c-ink-3); cursor: pointer;
  padding: 0 4px; text-decoration: none; margin-left: auto;
}
.glevel .x:hover { color: var(--c-overdue); }

/* THE CHIPS. Round 2, item 2. Every applied filter, named and droppable
   without opening the panel — which is what lets the panel collapse on Show
   without the reader losing sight of what is filtering his figures. */
.chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; height: 26px;
  padding: 0 4px 0 8px; border: 1px solid var(--c-border-control);
  background: var(--c-band-service); font-size: var(--t-secondary);
  color: var(--c-ink); white-space: nowrap;
}
.chip b { font-weight: 700; }
.chip .x {
  border: none; background: transparent; font-family: var(--font);
  font-size: 14px; line-height: 1; color: var(--c-ink-3); cursor: pointer;
  padding: 0 4px; text-decoration: none;
}
.chip .x:hover { color: var(--c-overdue); }
.chipclear {
  font-size: var(--t-secondary); color: var(--c-link); cursor: pointer;
  text-decoration: underline; background: none; border: none; font-family: var(--font);
}

/* --- THE FILTER PANEL: ONE FLAT GRID --------------------------------------
   Round 3, item 1. Joe: "between the two filters, I definitely like B better,
   actually, here, B, OneGrid." Alternative A — the same filters under four
   captions — was deleted with its switcher. What is left obeys the three
   rules both alternatives obeyed: every filter is the SAME control except the
   two that cannot be (a pair of dates, and a single-choice dropdown); every
   control is the same width under its own label; related filters stand
   together and the ORDER does that job rather than a caption, so the panel
   spends no words on itself at all. */
.filterpanel {
  padding: 8px var(--pad-box) 12px; background: var(--c-surface);
  border-bottom: 1px solid var(--c-border-item);
}
.fpgrid {
  display: grid; grid-template-columns: repeat(4, minmax(0, 240px));
  gap: 12px 16px; padding: 4px 0 2px; justify-content: start;
}
.fpcell { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.fpcell > label { font-size: var(--t-control); color: var(--c-ink-2); white-space: nowrap; }
.fpcell .ddown, .fpcell .sel, .fpcell .inp { width: 100%; box-sizing: border-box; }
.fpcell .ddsum::after { margin-left: auto; }
.fpcell.span2 { grid-column: span 2; }
.fpdates { display: flex; align-items: center; gap: 6px; }
.fpdates .inp { width: auto; flex: 1 1 0; min-width: 0; }
.fpdates span { font-size: var(--t-control); color: var(--c-ink-2); }
/* Card t_7e5026d2's fidelity pass measured 14px here against the approved
   mockup's 10px. The Show button and the Clear link sit in this row; the wider
   gap pushed Clear further from the button it belongs beside. */
.fpactions { display: flex; align-items: center; gap: 10px; padding-top: 12px; }

/* The summary's text lives in its own span, because app.js rewrites it on
   every tick and the arrow this stylesheet draws with `::after` must not be
   rewritten with it. It is one line, clipped rather than allowed to widen the
   control: this sits in a fixed-width panel cell and a customer name is
   longer than the cell. */
.ddsum .ddtext { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ddown.ddpanel .ddsum { width: 100%; box-sizing: border-box; }

/* --- GROUPING AND THE STATUS LINE ----------------------------------------- */
.groupsay { font-size: var(--t-secondary); color: var(--c-ink-2); }
.groupsay b { color: var(--c-ink); font-weight: 700; }

/* --- ROWS PER PAGE --------------------------------------------------------
   This stylesheet styles `.fieldgroup` only inside `.cstrip`; this control
   lives in `.pag` instead. These are that rule's own declarations, applied
   where the control actually stands — not a new look, the same look in a
   second place. */
.pag .fieldgroup { display: flex; align-items: center; gap: 6px; margin-right: 8px; }
/* CARD t_de6a87d1. The saved-view picker sits in a `.fieldgroup` FORM on this
   screen, because loading a view writes. A form is a block, and a block with
   no display rule of its own left the <select> inside it computing
   `inline-block` where the mockup computes `block`. Measured on all six
   states. The mockup's picker is a bare <select> in a flex row; matching it
   means laying the form out as that row. */
#viewpickform { display: flex; align-items: center; gap: 6px; }
#viewpickform > #f-view { display: block; }
.pag .fieldgroup label { font-size: var(--t-control); color: var(--c-ink-2); }
.pag .fieldgroup .sel { max-width: 92px; height: 24px; font-size: 12px; padding: 0 4px; }
/* The Go button beside it is for a browser with no JavaScript. With the
   script running, app.js submits on change and this would be a second way to
   do a thing that has already happened. */
.js .pag .nojsonly { display: none; }

/* --- THE TOTAL CELL: SPREAD, MARGIN, MARKUP -------------------------------
   Round 10, changes 2, 3 and 4. Joe picked sample F, then dictated the order:
   "the spread put before... so we see spread, marginal markup, flip it
   around. And then put something that says there is spread."

   THE ALIGNMENT IS REAL, NOT APPROXIMATE. The heading's three words and every
   row's three figures are laid on the SAME inline grid, three columns wide,
   and the three widths are one set of numbers held on <body> as `--sl-sp`,
   `--sl-mg` and `--sl-mk`. app.js measures the widest real text in each slot
   after the page is laid out and writes those three numbers, so a word stands
   over its own figure at any wording and any font the browser is set to —
   rather than lining up on the one screen it was eyeballed against. Round 10
   measured the worst remaining misalignment at 1.6px. */
.tot3 {
  display: inline-grid;
  grid-template-columns: var(--sl-sp, max-content) var(--sl-mg, max-content)
                         var(--sl-mk, max-content);
  column-gap: 8px; justify-items: end; align-items: baseline;
  line-height: 18px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.tot3 > .sl { white-space: nowrap; }
/* The spread and the markup are QUALIFIERS on the margin — a range of it, and
   the same profit seen from the other side — so both are smaller and grey and
   neither can be mistaken for the total. In the CELLS only: in the heading all
   three are the same kind of thing, three names, and sizing two of them down
   made "Margin" read as the odd one out of its own trio. */
.grid.tdgrid td .tot3 .sl-sp, .grid.tdgrid td .tot3 .sl-mk {
  font-size: var(--t-control); font-weight: 400; color: var(--c-ink-2);
}
.tot3 .sl-mg { font-weight: inherit; }
.grid.tdgrid tr.prodhead td.tdtotal .tot3 .sl-mg { font-weight: 700; }
.grid.tdgrid th.tdtotal { white-space: nowrap; width: 1px; }
/* "Total" is the name of the WHOLE cell, not of the last figure in it, so it
   is centred over the three slots rather than right-aligned with the column
   like every other heading. Right-aligned it landed directly over the word
   Markup and read as though the markup were the total. */
.grid.tdgrid th.tdtotal .totlabel { display: block; text-align: center; }
.grid.tdgrid th.tdtotal .tot3 {
  font-size: 11px; font-weight: 400; letter-spacing: 0.4px;
  text-transform: none; color: var(--c-ink-3); line-height: 1.2;
  /* Each slot is exactly as wide as the widest figure that will stand in it,
     so a heading word CENTRED in its slot sits over the middle of its own
     column of numbers. Right-aligning the words instead lined their right
     edges up but pushed a word whose width differs from its figures' off to
     one side — which is how "Spread", the longest word over the widest
     figure, ended up reading as though it belonged to the margin. The figures
     themselves stay right-aligned, because they are numbers. */
  justify-items: center;
}

/* --- THE PERCENTAGE COLUMN ------------------------------------------------
   Right-aligned like every other figure, and last, because it is a different
   animal from the six tier boxes and the row's own total: it is what this row
   is worth against everything else, not what this row earns.

   Round 6, Joe: "a column heading, definitely a column heading, it should be."
   So the heading is the ONLY place the mode is said. Round 7 banned the word
   "share"; it is not in this stylesheet, not in a class name, and not in the
   template, so there is nowhere left for it to come back from. */
.grid.tdgrid th.tdpct { width: 1px; height: auto; white-space: nowrap; vertical-align: bottom; }
.grid.tdgrid th.tdpct .shh { display: inline-block; text-align: right; line-height: 1.25; }
/* ROUND 10, CHANGE 1. "Take the word full and percentage, put it on top here,
   on top of rev, so it really narrows the column." The last word goes on its
   own line, so the column takes what the wider of two short lines needs
   instead of what one long line needed. */
.grid.tdgrid th.tdpct .pctl { display: block; text-align: right; line-height: 1.2; white-space: nowrap; }
/* ROUND 11, CHANGE 2. "The button, move it over to here, this side, because
   this side, right under the top row... It doesn't take up more space. It
   actually fits in here, which takes up less space." The second line is one
   short word — "rev." — so there was empty space beside it inside a width the
   first line had already claimed. The switch sits in that space: a flex row,
   button LEFT of the word, both lines still ending flush right, so its 22px
   stops widening the column. */
.grid.tdgrid th.tdpct .pctrow {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 5px; line-height: 1.2; white-space: nowrap;
}
.grid.tdgrid th.tdpct .pctrow .pctl { display: inline; }
.grid.tdgrid td.tdpct { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.grid.tdgrid tr.prodhead td.tdpct { font-weight: 700; }

/* THE SWITCH. Joe: "there should be a small indicator on top of that column.
   When he pushes it, it switches to the filtered or to the full revenue." It
   is a link rather than a button on the real screen, for the same reason the
   Filters control is: the mode is one thing in the address. `margin-left` is
   deliberately 0 — the flex row above spaces it, and a margin here would only
   widen the column again. */
.modebtn {
  font-family: var(--font); font-size: 13px; line-height: 18px; height: 20px;
  width: 22px; margin-left: 0; padding: 0; vertical-align: middle;
  flex: 0 0 auto; text-align: center; text-decoration: none;
  border: 1px solid var(--c-border-control); background: var(--c-surface);
  color: var(--c-ink-2); cursor: pointer; text-transform: none;
  letter-spacing: normal; display: inline-block;
}
.modebtn:hover { color: var(--c-ink); border-color: var(--c-crown); }

/* "And there's maybe a hover over it or some type of indication what that
   does." Placed by app.js at the top level of the page: the table is a
   scrolling pane, and a card drawn inside it would be cut off at the pane's
   own edge.

   ROUND 11, CHANGE 1. Joe: "The message could be cut in half. I only need the
   first part of the message, not the 'press... and they are worked out...
   full' — the last part. Just the first part." The card is ONE paragraph. It
   says what the percentages currently mean and stops; it says nothing about
   what pressing the switch would do. */
.modecard {
  display: none; position: fixed; z-index: 90; width: 330px;
  background: var(--c-surface); border: 2px solid var(--c-crown-border);
  box-shadow: var(--shadow-window); padding: 9px 11px;
  font-size: var(--t-secondary); font-weight: 400; color: var(--c-ink);
  line-height: 1.45; text-align: left; text-transform: none; letter-spacing: normal;
}

/* THE FOOT OF THE PERCENTAGE COLUMN has to be ON the screen and not at the
   bottom of a pane you have to scroll to: it is the confirmation signal — in
   filtered mode, and only in filtered mode, it reads 100.0% — and a signal you
   have to go looking for is not one. So it sticks to the bottom of the pane,
   which is the move this design system already makes with the column header on
   the top axis, and it needs a fill and an inset rule for the same reason the
   header does: a collapsed table border belongs to the table, not to the cell,
   and would be left behind when the cell sticks. */
.grid.tdgrid tfoot tr.totalrow td {
  position: sticky; bottom: 0; z-index: 3;
  background: var(--c-band-service); border-top: none;
  box-shadow: inset 0 2px 0 var(--c-border-container);
}

/* "Show in every box", on line 2 of the control bar. It is the product's own
   dropdown holding four tick boxes, NOT four loose boxes: four loose boxes on
   this line stack into four rows and make the control bar three lines tall,
   which is do-not-regress 4 and the regression that keeps coming back. Fixed
   width, so the bar does not shift as the reader ticks and unticks. */
.mbfield .ddown.f-meas { width: 150px; }
.mbfield .ddown.f-meas .ddsum { width: 100%; box-sizing: border-box; }
.mbfield .ddown.f-meas .ddsum::after { margin-left: auto; }

/* Card t_7e5026d2, stage 3. Short descriptions, Expand, the level dropdown
   and Collapse all sit in .mbrow-tbl, the far end of line 1 (decision A4).
   This stylesheet only styles `.fieldgroup` inside `.cstrip` and `.pag` -
   see the note above .pag .fieldgroup - so it needs its own rule here too,
   or the button and the level dropdown inside the Expand form would stack
   instead of sitting side by side. */
.mbrow-tbl .fieldgroup { display: flex; align-items: center; gap: 6px; }

/* Decision H1: a permanent control, not a mockup toggle, beside Expand. */
.shortdesc {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--t-control); color: var(--c-ink-2);
  white-space: nowrap; cursor: pointer;
}
.shortdesc input { margin: 0; }

/* Decision F10: the Expand level dropdown. Narrow - the longest thing it
   says is a column name - and it right-aligns itself the same way every
   other checkbox dropdown on this strip does, the moment app.js measures it
   running off the window (flipPanelIfOffscreen()): this sits at the
   right-hand end of the line, where a left-aligned panel would hang off it. */
.ddown.f-lvl { width: 150px; }
.ddown.f-lvl .ddsum { width: 100%; box-sizing: border-box; }
.ddown.f-lvl .ddsum::after { margin-left: auto; }

/* --------------------------------------------------------------------------
   Card t_7e5026d2, stage 3, decision H2. The description, the two treatments
   that survived round 3, item 3:
     A  hover only - the row carries the code, the description arrives in a
        card when the pointer rests on it. Nothing on screen at rest. This is
        where the screen always opens; grouprow() draws it on every item row.
     C  one clipped line under the code as well, full text still on hover.
        This is what the Short descriptions checkbox turns on - grouprow()
        only draws `.desline` when `short_desc` is true.
   -------------------------------------------------------------------------- */
.deschover { position: relative; display: inline-block; }
.deschover .card {
  display: none; position: absolute; left: 0; top: 100%; z-index: 40;
  width: 320px; background: var(--c-surface);
  border: 2px solid var(--c-crown-border); box-shadow: var(--shadow-window);
  padding: 8px 10px; font-size: var(--t-secondary); color: var(--c-ink);
  font-weight: 400; white-space: normal; line-height: 1.4;
}
.deschover:hover .card { display: block; }
/*  Card t_de6a87d1. The hover card's third line - the band count - in the
    muted ink the mockup uses for it, under the code and the description. */
.deschover .card .cardmuted { color: var(--c-ink-3); }
.desline {
  font-size: var(--t-secondary); color: var(--c-ink-2); font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 280px; display: block;
}

/*  THE "WHAT'S NEW" VERSION BADGE - card t_ad26b19b
    --------------------------------------------------------------------------
    WHAT  The small teal pill carrying the version number, sitting against the
          What's new link in the crown utility bar.

    WHY   Joe picked variant B of the approved mockup over the plain link:
          "I like the second version with the 1.2." The number is the point -
          when we ship again it changes, and a reader who has already read
          1.02 can see at a glance that there is something new.

    HOW   Copied verbatim from the approved mockup so what he signed off is
          what he gets. It carries its own font and its own radius on purpose:
          this is the one badge in the product, it is not a CONSULATE
          component, and matching the mockup byte for byte was the brief.
    -------------------------------------------------------------------------- */
.wnver { font:600 10px/1 system-ui,sans-serif; padding:2px 5px; margin-left:4px;
         border-radius:9px; background:#2f8f9d; color:#fff; vertical-align:1px; }

/*  SHARE A SAVED VIEW WITH EVERYONE - card t_3cab4df0
    --------------------------------------------------------------------------
    WHAT  Two rules. The small share button beside the View picker on the
          tiers-by-distance control bar, and the one tick-box line inside the
          window it opens.

    WHY   Joe approved the drawing at 13:33 on 18 September 2026 and chose
          icon 1, the Android share mark. These are lifted VERBATIM from the
          generator that drew what he approved,
          scripts/_david_shareicon_mockup_t_a318f729.py, so what he signed off
          is what he gets and nothing was re-derived here.

    HOW   The button is square and exactly the height of the other controls on
          the row, so it cannot be the thing that makes the row taller - the
          bar staying on ONE line is do-not-regress 4 on this screen. The line
          inside the window is the SAME .tickbox this screen's filters already
          use; the two rules below only stop it inheriting the filter panel's
          block layout, and they lay the body out as a single COLUMN so a
          second option ("share with someone specific") can be added
          underneath later without anything above it moving.
    -------------------------------------------------------------------------- */
.svicon { display:grid; place-items:center; width:30px; height:30px;
          padding:0; flex:none; color:var(--c-ink-2); }
.svicon svg { width:14px; height:14px; fill:currentColor; }
.svicon:hover { color:var(--c-ink); }

.svline { display:flex; align-items:center; gap:8px; white-space:nowrap;
          font-size:var(--t-control); }
.svline input { margin:0; }

/*  A DEAD BUTTON LOOKS DEAD, ON EVERY SCREEN - card t_b7c8d315
    --------------------------------------------------------------------------
    WHAT  Three selectors that give every disabled .go, .btn and .inp on the
          system the greyed, not-pressable look that until now only controls
          inside an .editrow had.

    WHY   Joe, 13:43 on 18 September 2026: "The Insert and Copy should be
          disabled if there's nothing to copy or insert." They already WERE -
          _prompt_builder.html sets insBtn.disabled and copyBtn.disabled the
          moment there is no draft rule, and a press does nothing. What was
          missing was the look: the only :disabled appearance in this
          stylesheet was `.editrow .go:disabled, .editrow .inp:disabled`, and
          the Prompt builder's button row is not an .editrow, so the two
          buttons stayed full-strength and invited a press that could not work.

    HOW   The SAME two declarations the .editrow rule has already used since
          the overrides screen was built - opacity 0.45 and cursor not-allowed.
          Nothing new is invented, the appearance is simply unscoped so it
          reaches the control wherever it lives.

          The .editrow rule above is DELIBERATELY LEFT WHERE IT IS rather than
          folded into this one. It is more specific and says exactly the same
          thing, so it cannot disagree, and this stylesheet is read off disk by
          a running service - removing a selector is a live change with nothing
          to gain.

          Only .go, .btn and .inp. Close and New session in the Prompt builder
          are never disabled and so are never touched; the green Send button is
          .pbsendgo and already carries its own disabled look in
          promptbuilder.css, which this does not override.
    -------------------------------------------------------------------------- */
.go:disabled,
.btn:disabled,
.inp:disabled { opacity: 0.45; cursor: not-allowed; }

/*  THE SHARE GLYPH IS CENTRED, AND THE SHARE WINDOW HANGS OFF ITS BUTTON
    - card t_14327bd8
    --------------------------------------------------------------------------
    WHAT  Four things. The share button's padding, so the glyph sits in the
          middle of it. A positioning parent round that button. The share
          window turned from a full-screen overlay into a small panel anchored
          under the button with their right edges flush. And the width that
          makes it small.

    WHY   Joe filmed the control at 15:59 on 18 September 2026: "this icon
          should be centered - it's like towards the right side, should be
          centered", and "when I click this button it pops up on the top. No,
          it shouldn't. It should pop up right under this button ... It doesn't
          even have to be so long ... a small popup right under the button,
          with the two right sides aligning between each other."

          THE GLYPH WAS NOT THE PROBLEM. Measured on live staging before
          anything was touched: svg.getBBox() gives x 0.9, width 13.6, so the
          artwork's own centre is 7.7 in a 16-unit box - a third of a unit LEFT
          of centre, the opposite of what Joe sees. The displacement was the
          BUTTON'S LAYOUT. `.btn.small` sets `padding: 0 12px` and carries two
          classes; `.svicon` sets `padding: 0` and carries one, so `.btn.small`
          won on specificity no matter which came last in the file. That left a
          30px button with a 4px content box, and a 14px glyph laid out from
          that content box's left edge overflowed to the right: 13px of gap on
          the left, 3px on the right. Ten pixels, exactly what he saw.

          So the fix is one selector carrying all three classes, which is the
          CAUSE - the padding - and not a nudge margin or a translate.

    HOW   `.svanchor` is the positioning parent, the same job `.ddown` does for
          every checkbox drop-down on this bar, and it is left unscoped for the
          same reason `.ddown` is: with the panel not floating there is nothing
          absolutely positioned inside it for this to be the parent of, so with
          JavaScript off the line changes nothing at all. It shrink-wraps the
          button so the control bar cannot tell the difference - the bar was
          measured at 915px used of 1593px on one line before this card and
          915px of 1593px on one line after it.

          `.shade.svpop` is the window. Its SIZE is unscoped, because a small
          window is a small window in both worlds. Only WHERE IT SITS is scoped
          under `.js`, exactly as `.ddown-body`'s float is, and for the same
          reason: `.js` is put on <html> by the inline script at the top of
          base.html's head, before the first paint. It does not actually matter
          here - this window is opened only by an onclick handler, so with
          JavaScript off it never opens at all and no reader ever sees either
          version - but the two panels on this bar should not answer the same
          question two different ways.

          `right: 0` is the DEFAULT, not a fallback. That is the whole of "the
          two right sides aligning between each other": the panel's right edge
          is the button's right edge, measured equal to within 0.5px. The
          `.svpop-left` modifier is the flip for a window narrow enough that a
          right-aligned panel would hang off the left; the template's
          tdPlaceShareView() adds it only when measuring says it is needed and
          only when it actually helps, which is flipPanelIfOffscreen()'s own
          shape, mirrored.

          The width is shrink-to-fit with a guard: `width: max-content` sizes
          the window to the view's name, the X, six words and a tick box, and
          `max-width` stops a long view name turning it back into a wide
          window. The window measured 460px before this card and 235px after
          it, on a 1665px page. `.win`'s base `width: min(520px, 92vw)` has to
          be overridden for that, so it is, on the class - the base rule is
          untouched and the other two windows still use it. The title bar gets
          nowrap and an ellipsis for the same reason the max-width exists: a
          name longer than the guard has to stop somewhere rather than push the
          × off the end.
    -------------------------------------------------------------------------- */
.btn.small.svicon { padding: 0; }

.svanchor { position: relative; display: inline-flex; align-items: center; }

.shade.svpop .win { width: max-content; max-width: min(280px, 90vw); }
.shade.svpop .wintitle { gap: 8px; }
.shade.svpop .wintitlename {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.js .shade.svpop {
  position: absolute; inset: auto 0 auto auto; top: calc(100% + 2px);
  padding-top: 0; background: none; justify-content: flex-end; z-index: 50;
  width: max-content;
}
.js .shade.svpop.svpop-left { right: auto; left: 0; justify-content: flex-start; }
