/* The look (desk v2 spec › 3, Decision 118: dense-desk's frame and page shapes, portal-panel's bottom bar; DESIGN 1
   to 18): the site's own stylesheet, the only one the base loads. Every rule lives here: the Content-Security-Policy
   allows only this file, no inline style, and only the site's own script files (js/desk.js and its modules, which
   set no style). A frame (the header across the top, the navigation down the left) in the scheme's colours, white with
   hairlines in light and the darkest surface in dark (DESIGN 10), round a canvas of white panels, one accent, the
   system font. Computer first (Decision 102): the
   desk layout from 960 CSS pixels; below 960 the navigation is a bar at the bottom, below 760 a grid's rows are
   cards, the phone the floor at 375. Rests on WCAG 2.2 (SC 1.4.3 and 1.4.11 contrast, 1.4.10 reflow, 2.4.1 bypass
   blocks, 2.4.7 and 2.4.11 focus, 2.5.5 and 2.5.8 target size), the Web Interface Guidelines (tabular figures,
   reduced motion, a skip link) and MDN on `position: sticky`, `:has()` and `<details>`. */

/* --- The tokens: the one place the look lives (desk v2 Task 19, Charlie 13 Oct 21:2x: "an airbnb-like aesthetics in
   terms of font, colours, design hierarchy, emphasis on important stuff ... and a dark version too", the grid "like a
   very nice excel template"). Airbnb's hierarchy: one typeface on a real scale (a large 600 title, the body at 400,
   small muted labels), near-black text on white with a grey for what is secondary, hairline borders, 8 and 12 px
   radii, generous room round the grid and tight inside it, one accent for the primary action and the marks only;
   emphasis by weight and size, never by more colour. The fine spreadsheet's: banded rows, a tinted header band, a
   totals bar at the foot. The typeface is the system's (SF on the Apple devices the co-founders use, Segoe UI on
   Windows): it carries the same hierarchy at no download, no flash of unstyled text and no font licence to vendor;
   a self-hosted Inter was weighed and not taken (tests/test_look.py pins that no font is loaded).

   Every colour a six-digit hex so tests/test_look.py measures each pair with the WCAG relative-luminance formula:
   text, muted text and the accent at least 4.5:1 on every surface a row or panel takes; white on the accent fill
   6.75:1; the control border, the focus ring and the mark at least 3:1; the frame's words 4.5:1 on it; each chip's
   words 4.5:1 on its tint; a grid's link 4.5:1 on every row tone and, in light, 3:1 against the row's text, so it
   may drop its underline at rest (WCAG 1.4.1, G183; in dark no colour meets both, so the customer's link carries its
   weight as the second cue). In dark the surfaces step up in tone with elevation (Material 3's surface tones; Apple's
   base and elevated backgrounds; Decision 121): the frame darkest, then the canvas, the panels and rows, the stripe,
   the header band and the inputs' field; text and borders lighten and the accent softens; the mark (a set filter,
   the open row, a button's edge) is the accent at reduced strength, so nothing set reads as a stuck focus ring. */
:root {
  color-scheme: light dark;
  /* type: one family, a scale of six sizes and three weights (DESIGN 15 and 16) */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --fs-title: 1.75rem;
  --fs-h2: 1.125rem;
  --fs-body: 0.9375rem;
  --fs-table: 0.875rem;
  --fs-small: 0.8125rem;
  --fs-label: 0.75rem;
  --fw-body: 400;
  --fw-medium: 500;
  --fw-strong: 600;
  /* room, corners and lines */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2rem;
  --radius: 8px;
  --radius-lg: 12px;
  --row-h: 2.25rem;
  --shadow: 0 1px 2px rgb(0 0 0 / 0.06), 0 4px 12px rgb(0 0 0 / 0.06);
  --top-h: 3.5rem;
  --nav-w: 12.5rem;
  --bar-h: 3.5rem;
  /* colours, light: near-black on white, two greys, hairlines; the frame white with a hairline */
  --bg: #ffffff;
  --canvas: #f7f7f7;
  --surface: #f2f2f2;
  --field: #ffffff;
  --text: #222222;
  --muted: #6a6a6a;
  --rule: #ebebeb;
  --control: #858585;
  --zebra: #f7f7f7;
  --hover: #f0f4fc;
  --accent: #2453c4;
  --accent-fill: #2453c4;
  --accent-soft: #e8eefb;
  --on-accent: #ffffff;
  --focus: #2453c4;
  --mark: #2453c4;
  --link: #3966d0;
  --chrome: #ffffff;
  --chrome-2: #f2f2f2;
  --on-chrome: #222222;
  --chrome-muted: #6a6a6a;
  --chrome-accent: #2453c4;
  --dealer: #0b4f8a;
  --dealer-bg: #e3effa;
  --end-user: #3a6595;
  --end-user-bg: #f5f8fc;
  --tyre: #24532a;
  --tyre-bg: #e4f3e6;
  --recon: #3d6e45;
  --recon-bg: #f4faf5;
  --check: #6b4f00;
  --check-bg: #fff4cc;
  --wrong: #9b1c1c;
  --wrong-bg: #fde8e8;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --shadow: 0 1px 2px rgb(0 0 0 / 0.4), 0 4px 12px rgb(0 0 0 / 0.3);
    --bg: #1e2024;
    --canvas: #16181b;
    --surface: #2a2d33;
    --field: #2e3137;
    --text: #ececec;
    --muted: #a9adb4;
    --rule: #34373d;
    --control: #80868f;
    --zebra: #25282d;
    --hover: #283246;
    --accent: #8fb4ff;
    --accent-fill: #2453c4;
    --accent-soft: #263a5e;
    --on-accent: #ffffff;
    --focus: #8fb4ff;
    --mark: #6d93e3;
    --link: #8fb4ff;
    --chrome: #0b0c0e;
    --chrome-2: #1f2125;
    --on-chrome: #ececec;
    --chrome-muted: #a0a4ab;
    --chrome-accent: #8fb4ff;
    --dealer: #9cc8f0;
    --dealer-bg: #1e3f5f;
    --end-user: #8db4e0;
    --end-user-bg: #2c3a54;
    --tyre: #9fd6a6;
    --tyre-bg: #1f4528;
    --recon: #8fcc9a;
    --recon-bg: #2a3d31;
    --check: #f0d36b;
    --check-bg: #463c0e;
    --wrong: #f4a3a3;
    --wrong-bg: #5c2828;
  }
}

/* A chosen theme (the header's control, desk v2 Task 19): Light keeps the light tokens above whatever the device;
   Dark takes the same dark values as the block above (tests/test_look.py pins the copy equal), and the browser's
   own controls and scrollbars follow through `color-scheme`. Auto sets nothing and follows the device. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] {
  color-scheme: dark;
  --shadow: 0 1px 2px rgb(0 0 0 / 0.4), 0 4px 12px rgb(0 0 0 / 0.3);
  --bg: #1e2024;
  --canvas: #16181b;
  --surface: #2a2d33;
  --field: #2e3137;
  --text: #ececec;
  --muted: #a9adb4;
  --rule: #34373d;
  --control: #80868f;
  --zebra: #25282d;
  --hover: #283246;
  --accent: #8fb4ff;
  --accent-fill: #2453c4;
  --accent-soft: #263a5e;
  --on-accent: #ffffff;
  --focus: #8fb4ff;
  --mark: #6d93e3;
  --link: #8fb4ff;
  --chrome: #0b0c0e;
  --chrome-2: #1f2125;
  --on-chrome: #ececec;
  --chrome-muted: #a0a4ab;
  --chrome-accent: #8fb4ff;
  --dealer: #9cc8f0;
  --dealer-bg: #1e3f5f;
  --end-user: #8db4e0;
  --end-user-bg: #2c3a54;
  --tyre: #9fd6a6;
  --tyre-bg: #1f4528;
  --recon: #8fcc9a;
  --recon-bg: #2a3d31;
  --check: #f0d36b;
  --check-bg: #463c0e;
  --wrong: #f4a3a3;
  --wrong-bg: #5c2828;
}

/* --- The page and the type scale (DESIGN 15 and 16): the tokens' family, nothing loaded; body --fs-body, tables
   --fs-table, small --fs-small, labels --fs-label, h1 --fs-title; weights 400, 500 and 600. */
*,
*::before,
*::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-body);
  font-weight: var(--fw-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { line-height: 1.25; margin: var(--space-4) 0 var(--space-2); text-wrap: balance; overflow-wrap: anywhere; font-weight: var(--fw-strong); }
h1 { font-size: var(--fs-title); letter-spacing: -0.01em; }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-body); }
small { font-size: var(--fs-small); }
p, ul, ol, dl, form, fieldset, details, article { margin: 0 0 var(--space-3); }
ul, ol { padding-left: 1.25rem; }
main > p { max-width: 75ch; }
main > :first-child { margin-top: 0; }
.muted { color: var(--muted); }
a { color: var(--accent); text-underline-offset: 0.15em; }
a:hover { text-decoration-thickness: 2px; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.875em; }
/* a long refusal message wraps at 375 rather than pushing the page sideways (DESIGN 1) */
mark { color: var(--check); background: var(--check-bg); padding: 0 0.2em; border-radius: 2px; overflow-wrap: anywhere; }
del { color: var(--muted); }
del a { text-decoration-line: line-through; color: var(--muted); } /* a decoration never reaches an inline-flex box
   (CSS Text Decoration 3, 2.1), so a struck linked description is struck here */
hr { border: 0; border-top: 1px solid var(--rule); }
.sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* An icon from the sprite (screens/static/screens/icons.svg), drawn in its text's colour beside the words it stands
   for, so it is hidden from assistive technology. */
.icon { flex: none; width: 1.125rem; height: 1.125rem; vertical-align: -0.2em; }

/* The skip link (SC 2.4.1): the keyboard's first stop, out of sight until focused, to `<main id="main">`. */
.skip { position: absolute; left: var(--space-2); top: -4rem; z-index: 30; padding: var(--space-2) var(--space-3); color: var(--accent); background: var(--bg); border-radius: var(--radius); }
.skip:focus { top: var(--space-2); }
main:focus:not(:focus-visible) { outline: none; } /* the skip link moves focus here; a mouse never rings the page */

/* --- The frame (DESIGN 10 and 17; spec › 3): the header across the top holds the name, the search, the record's
   as-of time and Sign out. */
body > header {
  display: flex;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--chrome);
  color: var(--on-chrome);
  border-bottom: 1px solid var(--rule); /* a hairline: the white frame against the page (desk v2 Task 19) */
}
body > header form { margin: 0; }
body > header .name { display: inline-flex; align-items: center; gap: var(--space-2); font-weight: 600; font-size: 1.0625rem; color: var(--on-chrome); text-decoration: none; }
/* The mark: a letter in the accent's fill, its alternative text empty, so a screen reader reads the name once. */
body > header .name::before { content: "A" / ""; display: inline-grid; place-items: center; width: 1.625rem; height: 1.625rem; border-radius: var(--radius); background: var(--accent-fill); color: var(--on-accent); font-size: 0.875rem; font-weight: 600; }
body > header [role=search] { flex: 1 1 auto; flex-wrap: nowrap; align-items: center; min-width: 0; max-width: 30rem; }
body > header .field input { color: var(--on-chrome); background: var(--chrome-2); border-color: var(--chrome-muted); } /* its edge
   at 3:1 on the header (SC 1.4.11; the look panel's M-9) */
body > header .field input::placeholder { color: var(--chrome-muted); opacity: 1; }
body > header .field .icon { color: var(--chrome-muted); }
body > header button.secondary,
body > header .signout button { color: var(--on-chrome); background: transparent; border-color: var(--chrome-muted); }
body > header .signout { margin-left: auto; }
body > header button { white-space: nowrap; }
.asof { display: none; align-items: center; gap: 0.375rem; margin: 0 0 0 auto; font-size: 0.8125rem; color: var(--chrome-muted); white-space: nowrap; }
.asof time { color: var(--on-chrome); }
body > header :focus-visible,
.side :focus-visible,
.bottom-bar :focus-visible { outline-color: var(--chrome-accent); }

/* A search field with its icon inside (the header's): the field takes the room, the button keeps its width. */
.field { position: relative; display: flex; flex: 1 1 auto; min-width: 0; align-items: center; }
.field .icon { position: absolute; left: 0.5rem; pointer-events: none; }
.field input[type=search] { width: 100%; min-width: 0; padding-left: 1.875rem; }

/* The left navigation from 960 pixels, in the frame's colours, grouped Record, Work and Admin (a disclosure open on every page), the
   page shown marked by an accent bar and a lighter fill (aria-current), the role and address at its foot. */
.side {
  display: none;
  flex-direction: column;
  padding: var(--space-3) var(--space-2);
  border-right: 1px solid var(--rule); /* a hairline: in light the white frame, in dark the frame and the canvas are near */
  background: var(--chrome);
  color: var(--on-chrome);
}
.side ul { list-style: none; margin: 0 0 var(--space-2); padding: 0; display: flex; flex-direction: column; gap: 2px; }
.nav-h { margin: var(--space-2) 0.625rem var(--space-1); font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--chrome-muted); }
.side a,
.side summary {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.375rem 0.625rem;
  border-radius: var(--radius);
  color: var(--on-chrome);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 500;
}
.side .icon { color: var(--chrome-muted); }
.side a:hover,
.side summary:hover { background: var(--chrome-2); }
.side a[aria-current=page] { background: var(--chrome-2); box-shadow: inset 3px 0 0 var(--chrome-accent); font-weight: 600; }
.side a[aria-current=page] .icon { color: var(--chrome-accent); }
.side details { margin: var(--space-2) 0 0; padding: 0; border: 0; background: none; }
.side summary { list-style: none; cursor: pointer; }
.side summary::-webkit-details-marker { display: none; }
.side summary::after { content: "▾" / ""; margin-left: auto; font-size: 0.75rem; color: var(--chrome-muted); }
.side details:not([open]) > summary::after { content: "▸" / ""; }
.side details ul { margin: 2px 0 0; padding-left: 1.75rem; }
.side details a { padding-block: 0.25rem; font-weight: 400; color: var(--chrome-muted); }
.side details a:hover,
.side details a[aria-current=page] { color: var(--on-chrome); }
.side .who { margin: auto 0 0; padding: 0.75rem 0.625rem 0; border-top: 1px solid var(--chrome-2); font-size: 0.75rem; color: var(--chrome-muted); overflow-wrap: anywhere; }
.side .who span { color: var(--on-chrome); }
main { flex: 1 0 auto; min-width: 0; padding: var(--space-3); }

/* From 960 pixels the desk: the header sticky across the top, the navigation sticky at the left at the viewport's
   height, the content using the remaining width; the record's as-of time shows in the header. */
.bottom-bar { display: none; }
@media (min-width: 960px) {
  body.desk {
    display: grid;
    grid-template-columns: var(--nav-w) minmax(0, 1fr);
    grid-template-rows: var(--top-h) 1fr;
  }
  /* A focused control scrolled into view clears the sticky header (SC 2.4.11; WCAG Technique C43). */
  html { scroll-padding-top: var(--top-h); }
  body.desk > header { grid-column: 1 / -1; position: sticky; top: 0; z-index: 10; height: var(--top-h); padding-block: 0; }
  body.desk > header .name { min-width: calc(var(--nav-w) - var(--space-3)); }
  body.desk > header .signout { margin-left: 0; }
  body.desk > .side { display: flex; position: sticky; top: var(--top-h); align-self: start; height: calc(100vh - var(--top-h)); overflow-y: auto; }
  body.desk > main { padding: 1.25rem 1.25rem var(--space-5); }
}

/* The record's as-of time shows in the header from 760 pixels (DESIGN 8), where the slim header has the room; on the
   phone it shows on home. */
@media (min-width: 760px) {
  .asof { display: flex; }
  body.desk > header .asof + .signout { margin-left: 0; } /* the as-of time takes the free room */
}

/* Below 960 pixels the navigation is a bar at the bottom of the screen (from portal-panel), in thumb reach (DESIGN 1):
   an icon over a word per screen, the page shown in the frame's accent; Admin a menu whose list opens above the bar.
   The page keeps the bar's height free at its foot, so its last line is never under the bar. */
@media (max-width: 959.98px) {
  body.desk > header .name { font-size: 0; gap: 0; } /* the mark alone: the slim header gives the search the room */
  body.desk > header .name::before { font-size: 0.875rem; }
  body.desk > header { gap: var(--space-2); }
  body.desk > main { padding-bottom: calc(var(--bar-h) + var(--space-4) + env(safe-area-inset-bottom)); }
  /* A focused control scrolled into view clears the bottom bar (SC 2.4.11; WCAG Technique C43). */
  html { scroll-padding-bottom: calc(var(--bar-h) + env(safe-area-inset-bottom)); }
  .desk > .bottom-bar {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    padding: 0 0 env(safe-area-inset-bottom);
    background: var(--chrome);
    border-top: 1px solid var(--chrome-2);
  }
  .bottom-bar > ul { list-style: none; margin: 0; padding: 0; display: flex; }
  .bottom-bar > ul > li { flex: 1 1 0; min-width: 0; }
  .bottom-bar > ul > li > a,
  .bottom-bar summary {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: var(--bar-h);
    padding: 0.25rem 0.125rem;
    color: var(--chrome-muted);
    font-size: 0.6875rem;
    font-weight: 500;
    text-decoration: none;
    list-style: none;
    cursor: pointer;
  }
  .bottom-bar summary::-webkit-details-marker { display: none; }
  .bottom-bar .icon { width: 1.375rem; height: 1.375rem; }
  .bottom-bar span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .bottom-bar a[aria-current=page],
  .bottom-bar details[open] > summary,
  .bottom-bar details:has([aria-current=page]) > summary { color: var(--on-chrome); box-shadow: inset 0 2px 0 var(--chrome-accent); }
  .bottom-bar a[aria-current=page] .icon,
  .bottom-bar details:has([aria-current=page]) > summary .icon { color: var(--chrome-accent); }
  .bottom-bar details { margin: 0; padding: 0; border: 0; background: none; }
  .bottom-bar details[open] > ul {
    position: fixed;
    right: var(--space-2);
    bottom: calc(var(--bar-h) + var(--space-2) + env(safe-area-inset-bottom));
    width: min(16rem, calc(100vw - 2 * var(--space-2)));
    margin: 0;
    padding: var(--space-1);
    list-style: none;
    background: var(--chrome);
    border: 1px solid var(--chrome-2);
    border-radius: var(--radius-lg);
    box-shadow: 0 8px 24px rgb(0 0 0 / 0.3);
  }
  .bottom-bar details[open] > ul a { display: flex; align-items: center; min-height: 2.75rem; padding: 0 0.75rem; border-radius: var(--radius); color: var(--on-chrome); text-decoration: none; }
  .bottom-bar details[open] > ul a[aria-current=page] { background: var(--chrome-2); box-shadow: inset 3px 0 0 var(--chrome-accent); }
}

/* --- A page's head (spec › 3): a breadcrumb, the title and a line of facts; the customer's figures as tiles; the
   invoice and the Questions' groups as panels. */
.crumbs ol { list-style: none; margin: 0 0 var(--space-1); padding: 0; display: flex; flex-wrap: wrap; font-size: 0.8125rem; color: var(--muted); }
.crumbs li + li::before { content: "/" / ""; margin: 0 0.375rem; }
.crumbs a { color: var(--muted); }
.page-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-3); margin: 0 0 var(--space-3); }
.page-head h1 { margin: 0; }
.page-head p { margin: 0; }
.page-head .totals { flex: 1 1 100%; font-size: var(--fs-small); }
/* The Lines summary (Task 18's live region, `#grid-summary`, read aloud when the grid updates in place) shows only its
   muted sentence under the title: its counts are in the totals bar at the grid's foot (Task 19), so they are kept for
   a screen reader alone; with no line on the page, and so no totals bar, the sentence goes too (review m-7). */
#grid-summary.totals > span:first-child { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.grid-region:not(:has(tfoot)) #grid-summary.totals > .muted { display: none; }
dl.facts { display: flex; flex-wrap: wrap; gap: var(--space-2) 2rem; margin: 0 0 var(--space-3); }
dl.facts dt { font-size: 0.75rem; color: var(--muted); }
dl.facts dd { margin: 0; overflow-wrap: anywhere; }
dl.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--space-2); max-width: 50rem; margin: 0 0 var(--space-3); }
dl.stats > div,
.tiles > li { padding: 0.625rem 0.875rem; background: var(--bg); border: 1px solid var(--rule); border-radius: var(--radius-lg); }
dl.stats dt { font-size: 0.75rem; color: var(--muted); }
dl.stats dd { margin: 0; font-size: 1.125rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.panel,
.hero { padding: var(--space-3) 1.25rem; background: var(--bg); border: 1px solid var(--rule); border-radius: var(--radius-lg); margin: 0 0 var(--space-3); }
.panel > :last-child,
.hero > :last-child { margin-bottom: 0; }
.panel > h2:first-child { margin-top: 0; }
.panel .page-head .btn .icon { width: 1rem; height: 1rem; }
ul.flags { list-style: none; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.q-actions { align-items: center; margin: 0 0 var(--space-3); }
.q-actions p { margin: 0; }

/* Home: the greeting and the search in a panel, the record's state as tiles, the questions open as links. */
.hero h1 { margin: 0 0 var(--space-3); }
.hero [role=search] input,
.hero [role=search] button { min-height: 2.5rem; }
.tiles { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: var(--space-2); margin: 0 0 var(--space-3); }
.tiles > li { display: flex; flex-direction: column; gap: 2px; }
.tiles strong { font-size: 1.125rem; white-space: nowrap; font-weight: 600; font-variant-numeric: tabular-nums; order: 1; }
.tiles span { font-size: 0.8125rem; color: var(--muted); order: 0; }
.tiles strong + span { order: 2; }

/* --- Tables (DESIGN 5 and 16): each in its own scroll box, so the page never scrolls sideways (SC 1.4.10); from 960
   pixels the box is bounded by the viewport and scrolls both ways, so its header rows stick inside it; dense rows,
   zebra stripes, numbers right-aligned in tabular figures, long text cut to one line (the full text in its title). */
.table-box {
  overflow-x: auto;
  width: max-content;
  max-width: 100%;
  margin: 0 0 var(--space-3);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  /* A shadow at the right edge while there is more to the right (DESIGN 1): the cover travels with the content
     (local) and hides the shadow once the end is reached; CSS only, as the CSP allows no script. */
  background-color: var(--bg);
  background-image: linear-gradient(to left, var(--bg) 30%, transparent),
    radial-gradient(farthest-side at 100% 50%, var(--control), transparent);
  background-position: right center, right center;
  background-size: 2.5rem 100%, 0.75rem 100%;
  background-repeat: no-repeat;
  background-attachment: local, scroll;
}
.table-box.wide { width: 100%; } /* a record grid uses the width, not its content's (the look panel, plan C) */
.table-box :focus { scroll-margin-top: 4.5rem; } /* SC 2.4.11: a focused control clears the sticky header rows */
@media (min-width: 960px) {
  .table-box { overflow: auto; max-height: calc(100vh - var(--top-h) - 2rem); }
}
table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 0.875rem; line-height: 1.35; }
th, td { padding: 0.375rem 0.625rem; text-align: left; vertical-align: top; border-bottom: 1px solid var(--rule); }
thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
  font-weight: 600;
  white-space: nowrap;
  border-bottom: 2px solid var(--control);
}
thead th a { color: var(--text); text-decoration: none; }
.grid thead th { font-size: var(--fs-label); font-weight: var(--fw-strong); letter-spacing: 0.01em; } /* small labels, so no
   label widens a number's column */
.grid thead th a { color: var(--muted); }
.grid thead th[aria-sort] a { color: var(--accent); }
thead th a:hover { color: var(--accent); text-decoration: underline; }
/* The sorted column says so beside its label, in the accent; aria-sort carries it for a screen reader, so the arrow's
   own alternative text is empty. */
thead th[aria-sort] a { color: var(--accent); }
thead th[aria-sort=ascending] a::after { content: " ▲" / ""; font-size: 0.75em; }
thead th[aria-sort=descending] a::after { content: " ▼" / ""; font-size: 0.75em; }
/* The stripes count a table's rows but a lines table's opened rows (`tr.more`, desk v2 spec › 4.4), so they alternate
   line by line whether a row is open or not; every other table keeps its stripes (the `of S` form: Baseline 2023). */
tbody tr:nth-child(even of :not(.more)) { background: var(--zebra); }
tbody tr:last-child > * { border-bottom: 0; }
tfoot th, tfoot td { border-top: 1px solid var(--rule); border-bottom: 0; }
tfoot tr.total > * { font-weight: 600; border-top: 2px solid var(--control); }
td.date { white-space: nowrap; font-variant-numeric: tabular-nums; } /* dates align, as numbers do */
/* The totals bar (desk v2 Task 19, a fine spreadsheet's): the count and the totals under their columns, in the header
   band's tint, pinned at the grid's foot while its rows scroll in the box. */
.grid tfoot tr.sum > * { position: sticky; bottom: 0; z-index: 1; padding-block: 0.5rem; background: var(--surface); border-top: 1px solid var(--control); border-bottom: 0; font-weight: var(--fw-strong); font-variant-numeric: tabular-nums; white-space: nowrap; text-align: left; }
.grid tfoot tr.sum > .num { text-align: right; }
th.num,
td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
td.wrap { overflow-wrap: anywhere; min-width: 12rem; }
.cut { max-width: 24rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
:not(td, th).cut { display: inline-block; vertical-align: bottom; } /* beats `td a`, so a cut link shows its "…" */
/* A record grid's columns: each cell on one line, narrower padding, a wrong line's reason cut short, each full text
   in its title (a re-saved copy's link to the counted copy stays whole beside its cut words). Below 1280 the
   description and the customer's name are capped, so the box fits at 960 with the navigation open; from 1280 (desk
   v2 Task 19) a lines grid's Customer and Description share the free width instead (a cell's `max-width: 0` takes
   it out of the columns' content widths, so the two take what the other columns leave, each by its share), and a
   name or a description is cut only when the window is narrower than it; a floor keeps a few words of each beside
   the side panel. From 1500 the reason takes the room there is. */
.wide th, .wide td { padding-inline: 0.35rem; white-space: nowrap; }
.wide .cut { max-width: 11rem; }
.wide .cut.why { max-width: 4rem; }
@media not all and (min-width: 1280px) {
  .wide .cut.desc { max-width: 11rem; } /* with its sales arrow beside it */
  .wide .cut.name { max-width: 8rem; }
}
@media (min-width: 1280px) {
  .grid td.c-customer:has(.name) { width: 28%; max-width: 0; min-width: 7rem; }
  .grid td.c-description:has(.desc) { width: 72%; max-width: 0; min-width: 9rem; }
  .grid .cut.name,
  .grid .cut.desc { max-width: 100%; }
}
/* The Customers and Items grids' five or six columns leave their name or description the room of a plain cut, so two
   labels that share their first 11rem still tell apart; at phone width they are cards. */
@media (min-width: 960px) {
  .wide .cut.label { max-width: 24rem; }
}
@media (min-width: 1440px) {
  .wide .cut.why { max-width: 10rem; } /* a wrong line's reason reads from 1440, where the description has room (m-1) */
}
@media (min-width: 1500px) {
  .wide .cut { max-width: 20rem; }
  .wide .cut.why { max-width: 20rem; }
}

/* --- A description and the row that opens (desk v2 spec › 4.4, 4.9, 4.10; plan Task 1). A description is text in its
   lines (`.lines`, never on a cut element: a grid cell keeps one line); its item's sales link is left out of a drag,
   so a drag inside the text selects the text alone: in a grid a quiet arrow at the cell's right edge, held out of the
   text's box (desk v2 Task 19), on the invoice page "sales ›" after the text. A lines table's first column holds a chevron, a native
   disclosure (`<details name="row">`, one open at a time; the APG Disclosure pattern), and the row beneath each line
   shows only while the line's disclosure is open. Its cell wraps within the table (`contain: inline-size` gives the
   panel no width of its own), so no column moves when it opens; where the table scrolls in its box, the panel stays
   at the box's left at the box's width (a container query unit), so its text reads without scrolling sideways. */
.lines { white-space: pre-line; }
.sales { font-size: 0.75rem; white-space: nowrap; -webkit-user-select: none; user-select: none; }
.wide .cut.desc,
.wide .sales { vertical-align: top; } /* level with the row's other cells, whatever the link's height */
/* The "sales ›" link is 24 pixels tall on a mouse (DESIGN 12; SC 2.5.8) and 44 on a touch screen (SC 2.5.5) without
   moving its line: a 1rem line and padding above and below, which a matching negative margin takes back out of the
   line box, so a row stays one line tall and a description's last line keeps its height. */
td a.sales { display: inline-block; min-height: 0; line-height: 1rem; padding-block: 4px; margin-block: -4px; }
/* A grid's arrow: 24 by 24 at the cell's right edge, centred on the row, muted at rest and in the link's colour on
   a hover or a focus; the cell keeps its room, so the text never runs under it. */
.grid td.c-description:has(> .sales) { position: relative; padding-right: 1.625rem; }
.grid tbody tr:not(.more) > td:not([colspan]) > a.sales { position: absolute; top: 50%; right: 0; display: inline-grid; place-items: center; width: 24px; height: 24px; margin: -12px 0 0; padding: 0; line-height: 1; font-size: 0.875rem; color: var(--muted); border-radius: var(--radius); }
.grid tbody tr:not(.more) > td:not([colspan]) > a.sales:hover,
.grid tbody tr:not(.more) > td:not([colspan]) > a.sales:focus-visible { color: var(--link); background: var(--hover); }
/* At rest on a mouse the arrow waits for its row (review I-2: from 1280 a column of arrows beside Qty read as stray
   glyphs): shown while the row is hovered or holds the focus, so a keyboard's Tab still lands on it and shows it, as
   Airtable reveals a row's icons; on a touch screen it always shows, having no hover. */
@media (hover: hover) and (pointer: fine) {
  .grid tbody tr:not(.more, :hover, :focus-within) > td:not([colspan]) > a.sales { opacity: 0; }
}
.grid { container-type: inline-size; }
.grid .c-x { width: 1.625rem; padding-inline: 0.125rem 0; text-align: center; }
.grid td.c-x details,
.grid td.c-x details[open] { margin: 0; padding: 0; border: 0; min-width: 0; background: none; }
.grid td.c-x summary { display: inline-grid; place-items: center; width: 1.5rem; height: 1.5rem; padding: 0; color: var(--muted); border-radius: var(--radius); list-style: none; }
.grid td.c-x summary::-webkit-details-marker { display: none; }
.grid td.c-x summary:hover { color: var(--text); background: var(--rule); }
.grid td.c-x details[open] summary { color: var(--accent); }
.chev { width: 1rem; height: 1rem; }
.grid td.c-x details[open] .chev { transform: rotate(90deg); }
@media (prefers-reduced-motion: no-preference) {
  .grid td.c-x .chev { transition: transform 120ms ease-out; }
}
.grid tbody tr.row:hover { background: var(--hover); }
/* A grid's rows are dense on a fine pointer (DESIGN 16; desk v2 Task 19): 2.25rem at 0.875rem type, every cell
   centred on the row; a coarse pointer's 44 px targets make its rows taller (below). Its links are quiet at rest: no
   underline, the link's colour (above, the tokens), the customer's name a little heavier; a hover or a keyboard focus
   underlines one; the invoice's number keeps its underline, the row's anchor. A struck row's links stay struck. */
@media (min-width: 760px) { /* the table's rows (below 760 they are cards); a cut name or description is a block of
     one line, centred with the row's other cells */
  .grid tbody tr:not(.more) > td:not([colspan]) { height: var(--row-h); padding-block: 0.25rem; vertical-align: middle; }
  .grid tbody tr .cut:is(.name, .desc, .label) { display: block; }
  .grid tbody tr a.cut:is(.name, .label) { line-height: 24px; } /* 24 px tall (SC 2.5.8) */
}
@media (min-width: 1280px) { /* the free width goes to the customer and the description (above) */
  .grid th,
  .grid td { padding-inline: 0.2rem; } /* a spreadsheet's tight cells */
  .grid :is(th, td):last-child { padding-right: var(--space-2); } /* the last cell clear of the box's edge (m-1) */
}
.grid tbody tr:not(.more) > td:not([colspan]) a { color: var(--link); text-decoration: none; }
.grid tbody tr:not(.more) > td:not([colspan]) a:hover,
.grid tbody tr:not(.more) > td:not([colspan]) a:focus-visible { text-decoration-line: underline; }
.grid tbody tr:not(.more) > td:not([colspan]).c-invoice a { text-decoration-line: underline; }
.grid tbody tr:not(.more) > td:not([colspan]) a.cut { font-weight: var(--fw-medium); } /* a customer's or an item's name */
/* In dark no link colour is both AA on every row tone and 3:1 against the text (G183; measured, and confirmed by the
   review), so a name's link carries a real weight step as its non-colour cue (WCAG 1.4.1: bold, review I-5): under
   the device's dark scheme unless Light was chosen, and under a chosen Dark. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .grid tbody tr:not(.more) > td:not([colspan]) a.cut { font-weight: var(--fw-strong); }
}
:root[data-theme="dark"] .grid tbody tr:not(.more) > td:not([colspan]) a.cut { font-weight: var(--fw-strong); }
.grid tbody tr:not(.more) > td:not([colspan]) del a,
.grid tbody tr:not(.more) > td:not([colspan]).c-invoice del a { color: var(--muted); text-decoration-line: line-through; } /* the
   anchor's own underline rule outweighs the first (review I-1) */
.grid tbody tr.row:has(details[open]) { background: var(--accent-soft); }
.grid tr.more { display: none; }
.grid tr.row:has(details[open]) + tr.more { display: table-row; }
.grid tr.more > td { white-space: normal; padding: 0; background: var(--surface); box-shadow: inset 3px 0 0 var(--mark); }
.grid .more-panel { contain: inline-size; position: sticky; left: 0; max-width: 100cqi; padding: var(--space-2) var(--space-3); }
.grid tbody tr.row:nth-last-child(2):not(:has(details[open])) > * { border-bottom: 0; } /* the last line, its panel shut */
/* The grid in place (desk v2 Task 18): while a sort, page or filter loads, the rows dim, a tone and nothing moved;
   the region takes focus to be announced, never ringed for a mouse. */
.grid-region[aria-busy=true] tbody { opacity: 0.55; }
.grid-region:focus:not(:focus-visible) { outline: none; }
.grid-region > p { max-width: 75ch; } /* a message and the hidden sentence keep main > p's measure */
.more-panel p { max-width: 75ch; margin: 0 0 var(--space-2); }
.more-panel .facts { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4); margin: 0 0 var(--space-2); }
.more-panel .facts div { display: flex; gap: var(--space-1); }
.more-panel dl.facts dt { font-size: inherit; color: var(--muted); }
.more-panel dd { margin: 0; }
.more-panel .actions { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin: 0; }

/* --- Chips (DESIGN 13 and 16): a small label per meaning, always holding its words, so colour never carries the
   meaning alone; the Kind chip says "dealer" or "end user" (desk v2 spec › 3). Quiet (desk v2 Task 19): a squared
   tag of medium weight; Kind and Class hold the body's text on their column's tint, one hue a column (Kind blue,
   Class green), the first word filled and the second outlined, so "dealer" and "end user", "tyre" and "recon" read
   apart at a glance; a flag (check, wrong, credit note) keeps its own colour, the row's one alert. A "check" chip is
   the link to its invoice's PDF, its icon saying where it goes. */
.chip {
  display: inline-block;
  padding: 0 0.4em;
  border-radius: 4px;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.25rem;
  white-space: nowrap;
}
.chip:not([class*=" chip-"]) { color: var(--text); background: var(--bg); box-shadow: inset 0 0 0 1px var(--control); }
.chip abbr,
abbr.chip { text-decoration: none; cursor: help; } /* the lab's D and E chips are the abbr itself */
del .chip { text-decoration-line: line-through; } /* a chip is an atomic box: a struck row's decoration skips it */
.chip-dealer { color: var(--text); background: var(--dealer-bg); }
.chip-end-user { color: var(--text); background: var(--end-user-bg); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--end-user) 55%, transparent); }
.chip-tyre { color: var(--text); background: var(--tyre-bg); }
.chip-recon { color: var(--text); background: var(--recon-bg); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--recon) 55%, transparent); }
.chip-check { color: var(--check); background: var(--check-bg); }
.chip-wrong { color: var(--wrong); background: var(--wrong-bg); }
a.chip { display: inline-flex; align-items: center; gap: 0.25rem; min-height: 24px; text-decoration: none; }
a.chip .icon { width: 0.875rem; height: 0.875rem; }
.grid tbody tr:not(.more) > td:not([colspan]) a.chip { color: var(--check); }
.grid tbody tr:not(.more) > td:not([colspan]) a.chip:hover,
.grid tbody tr:not(.more) > td:not([colspan]) a.chip:focus-visible { text-decoration-line: underline; box-shadow: inset 0 0 0 1px var(--check); }

/* A grid's pager: a row of links with a gap, the page shown marked (aria-current). */
nav.pager { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-3); margin: 0 0 var(--space-3); font-size: 0.875rem; }
nav.pager [aria-current=page] { font-weight: 600; }

/* The run log's counts: one short line, its parts separated by a dot, the full record one click away. */
.counts > span + span::before { content: " · "; }
.counts { white-space: nowrap; }
td pre { margin: var(--space-1) 0 0; font-size: 0.8rem; white-space: pre-wrap; overflow-wrap: anywhere; max-width: 40rem; }

/* --- The toolbar (desk v2 spec › 3 and 4.1): the grid's one GET form (From and To, Apply, Clear), each control
   keeping its own width, so a date input shows its whole year (#167); on the phone the Filters disclosure. Under it
   the filters in force as chips, each a link that drops its own filter. */
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); margin: 0 0 var(--space-2); }
.filter-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-2) var(--space-3); margin: 0; }
.filter-bar { align-items: center; }
.filter-bar label { flex: 0 0 auto; display: flex; align-items: center; gap: 0.375rem; margin: 0; font-size: 0.8125rem; color: var(--muted); }
.filter-bar label > input { margin: 0; color: var(--text); }
.filter-bar label > input[type=date] { width: auto; }
details.filters { display: none; }
.chips { list-style: none; margin: 0 0 var(--space-2); padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); font-size: 0.8125rem; }
.fchip { display: inline-flex; align-items: center; gap: 0.375rem; min-height: 24px; padding: 0 0.625rem; border: 1px solid var(--mark); border-radius: 999px; background: var(--accent-soft); color: var(--accent); text-decoration: none; }
.fchip:hover { text-decoration: underline; }
.hint { margin: 0 0 var(--space-2); font-size: 0.8125rem; } /* how a number filter reads (the look panel's M-1) */
/* The toolbar's buttons (desk v2 Task 19): Apply the one filled button; Clear, Columns and Side panel one secondary
   style, an outline in the accent's words; every control 2rem tall on a fine pointer, so the row reads as one line. */
.toolbar button { min-height: 2rem; }
.toolbar .filter-bar label > input { min-height: 2rem; padding-block: 0; }
.toolbar .filter-bar > a,
details.columns > summary,
.toolbar .panel-switch button {
  display: inline-flex;
  align-items: center;
  min-height: 2rem;
  padding: 0 0.75rem;
  border: 1px solid var(--control);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text); /* near-black words in a grey outline: the accent is Apply's and the marks' alone (review I-4) */
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.35;
  text-decoration: none;
}
.toolbar .filter-bar > a:hover,
details.columns > summary:hover { background: var(--hover); }
/* From 760 on a mouse the number filters' hint leaves the page: each number filter's title says it on hover and a
   screen reader hears it as the filter's description (review m-2: a note floating over the toolbar hid its buttons).
   On a touch screen, which shows no title, it stays in the flow; below 760 the Filters panel shows it (below). */
@media (min-width: 760px) and (pointer: fine) {
  .hint { display: none; }
}
@media (min-width: 1440px) { /* generous room round the grid where the width allows it */
  body.desk > main { padding: var(--space-5) var(--space-5) 3rem; }
}
/* The filter row under a grid's headers (desk v2 spec › 4.1): one small control a column, joined to the toolbar's
   form by `form="grid"`; each takes its column's width, from a floor that still shows a few characters, so the row
   widens no column but the Customers grid's dates, which stack From over To. A filter holding a value carries the
   accent's edge. The Kind select reads its choice whole once the column is as wide as its "end user" chip. */
thead .filters td { background: var(--surface); padding: 0.2rem; border-bottom: 2px solid var(--control); }

thead .filters input,
thead .filters select { display: block; width: 100%; min-width: 2rem; min-height: 24px; padding: 0 0.2rem; font-size: 0.75rem; }
thead .filters select { min-width: 2.25rem; padding-inline: 0; } /* "any" and its arrow */
.filters select[name=kind],
.filters select[name=class],
.filters select[name=flags] { min-width: 3.5rem; } /* "any" reads whole, a page of unflagged lines too */
thead tr:has(+ tr.filters) th { border-bottom-width: 1px; } /* one heavy rule, under the filters (review M-3) */
.filters input[type=date] { width: auto; }
.filters input[type=date] + input[type=date] { margin-top: 2px; }
.grid tr.filters td.c-apply { display: none; } /* the phone's Filters panel's own Apply, below 760 */
.filters .set,
.filter-bar .set { border-color: var(--mark); box-shadow: inset 0 0 0 1px var(--mark); }
.totals { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4); margin: 0 0 var(--space-2); font-variant-numeric: tabular-nums; }
.answers { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.answers form { margin: 0; }
/* In a wide table a row's answers wrap inside their cell, the why box on its own line when the row is full, and a
   row's flags wrap too, so the buttons stay in the box at 1280; the cut columns keep their one line (review I-1). */
.wide td .answers { white-space: normal; min-width: 12rem; }
.wide td.flags { white-space: normal; min-width: 8rem; }  /* a row's flag chips wrap, a long one inside itself */
.wide td.flags .chip { white-space: normal; border-radius: var(--radius); }
.answers input[name=why] { width: 12rem; }

/* --- Elements styled one by one, since no framework does it: forms, buttons, disclosures, fieldsets, cards. */
label { display: block; margin: 0 0 0.75rem; }
input,
select,
textarea,
button { font: inherit; }
input:not([type=checkbox], [type=radio], [type=hidden]),
select,
textarea {
  max-width: 100%;
  padding: var(--space-1) var(--space-2);
  color: var(--text);
  background: var(--field);
  border: 1px solid var(--control);
  border-radius: var(--radius);
  min-height: 24px;
}
label > input:not([type=checkbox], [type=radio]),
label > select,
label > textarea { display: block; width: 100%; max-width: 32rem; margin-top: var(--space-1); }
[type=checkbox],
[type=radio] { width: 1rem; height: 1rem; margin: 0; accent-color: var(--accent-fill); }
button,
[role=button],
a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-height: 24px;
  min-width: 24px;
  padding: 0.3125rem 0.875rem;
  color: var(--on-accent);
  background: var(--accent-fill);
  border: 1px solid var(--mark); /* dark mode's edge in #6d93e3, 4.82:1 on a panel, the fill being 2.16:1 */
  border-radius: var(--radius);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.35;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
}
/* A secondary button (Find, Filter, sign-out) is an outline; a destructive one (Remove, Revoke) an outline in the
   wrong pair (6.94:1 light, 8.06:1 dark), so neither reads as the page's main action. */
.signout button,
button.secondary { background: transparent; color: var(--accent); border-color: var(--control); }
button.danger { color: var(--wrong); background: var(--wrong-bg); border-color: var(--wrong); }
button:hover,
a.btn:hover { box-shadow: inset 0 0 0 100vmax rgb(0 0 0 / 0.15); }
form.inline { display: inline; margin: 0; }
form > button + button { margin-left: var(--space-1); }
header a,
nav a,
td a,
.way a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  min-width: 24px;
}
details { border: 1px solid var(--rule); border-radius: var(--radius); padding: 0 0.75rem; background: var(--bg); }
details[open] { padding-bottom: var(--space-2); }
summary { padding-block: var(--space-1); cursor: pointer; }
details.panel > summary { font-weight: 600; }
/* A row's correction behind a disclosure (the invoice's Mark wrong): the row stays one line until it is opened. */
td details { margin: 0; border: 0; padding: 0; white-space: nowrap; background: none; } /* a row stays one line tall */
td details[open] { white-space: normal; min-width: 14rem; padding-bottom: 0; }
td details form { margin: var(--space-1) 0 0; }
td details label { margin-bottom: var(--space-1); }
fieldset { border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--space-2) 0.75rem; }
legend { padding: 0 var(--space-1); font-weight: 600; }
/* A radio or checkbox and its label are one target; a count after the text (the customer's names) its own line. */
fieldset label {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: var(--space-2);
  min-height: 24px;
  margin: 0;
}
fieldset label > small { grid-column: 2; }
article { padding: 0.75rem var(--space-3); background: var(--bg); border: 1px solid var(--rule); border-radius: var(--radius-lg); }
article > :last-child { margin-bottom: 0; }

/* A search form (the header's and home's): the field and the button side by side; the button keeps its width, since a
   min-width replaces a flex item's automatic minimum (it read "Sea / rch" at 375). */
[role=search] { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-2); }
[role=search] label { flex: 0 1 32rem; margin: 0; }
[role=search] button { flex: none; }

/* --- Focus is always visible (DESIGN 14; SC 2.4.7): a solid ring in the focus token, 6.75:1 light and 8.35:1 dark;
   on the frame its own accent (above). */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* --- Below 760 pixels a grid's rows are cards (DESIGN 1 as amended; spec › 3), so nothing scrolls sideways: a lines
   card holds the invoice, the date and the amount on its first line, then the customer, the description over two
   lines at most, the chips, quantity and price, and the chevron at its lower right, its opened row beneath it; the
   Customers' and Items' cards their name first. The roles table, row and cell in the markup keep the table's meaning
   once its display changes [assumed: holds in VoiceOver on iOS; plan Task 15 checks by hand]. The header row becomes
   a strip of sort pills that scrolls in its own box; the filter row shows when the Filters disclosure is open. An
   invoice's lines become a list, so its amount is never off-screen. */
@media (max-width: 759.98px) {
  .grid.table-box { overflow: visible; max-height: none; border: 0; background: none; }
  .wide.grid > table,
  .wide.grid > table > thead,
  .wide.grid > table > tbody { display: block; }
  .wide.grid thead tr:first-child { display: flex; gap: var(--space-1); overflow-x: auto; padding: 0 0 var(--space-2); }
  .wide.grid thead th { position: static; display: block; padding: 0; background: none; border: 0; }
  .wide.grid thead th.c-x { display: none; }
  .wide.grid thead th a { display: inline-flex; align-items: center; min-height: 2rem; padding: 0 0.75rem; border: 1px solid var(--rule); border-radius: 999px; background: var(--bg); font-weight: 500; font-size: 0.8125rem; white-space: nowrap; }
  .wide.grid thead th[aria-sort] a { border-color: var(--mark); background: var(--accent-soft); }
  .wide.grid tr.filters { display: none; }
  main:has(details.filters[open]) .wide.grid tr.filters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); margin: 0 0 var(--space-2); padding: var(--space-2); background: var(--bg); border: 1px solid var(--rule); border-radius: var(--radius-lg); }
  .wide.grid tr.filters td { display: block; padding: 0; border: 0; background: none; white-space: normal; } /* a long label wraps */
  .wide.grid tr.filters td:empty { display: none; }
  .wide.grid tr.filters input[type=date] { width: 100%; min-width: 0; } /* a 16 px date box keeps to its column */
  .wide.grid tr.filters td[data-label]::before { content: attr(data-label); display: block; margin-bottom: 2px; font-size: 0.75rem; color: var(--muted); } /* the control's name, seen (its aria-label is heard) */
  .wide.grid tr.filters input,
  .wide.grid tr.filters select { min-height: 2.5rem; font-size: max(1rem, 16px); padding-inline: var(--space-2); }
  details.filters { display: block; margin: 0; padding: 0; border: 0; background: none; }
  details.filters > summary { display: inline-flex; align-items: center; gap: 0.375rem; min-height: 2rem; padding: 0 0.75rem; border: 1px solid var(--control); border-radius: var(--radius); background: var(--bg); font-size: 0.875rem; list-style: none; }
  details.filters > summary::-webkit-details-marker { display: none; }
  details.filters[open] > summary { border-color: var(--mark); background: var(--accent-soft); color: var(--accent); }
  /* From and To and the number filters' hint show while Filters is open, so the first purchase or sale on a customer
     or item page sits higher (a date in force still shows as its chip; the look panel's M-2). Apply shows in the
     toolbar only where its form holds a control of its own (the form, not the toolbar: the Columns menu beside it
     holds a checkbox per column) and while Filters is closed; the panel ends in its own Apply, the one Apply while it
     is open (M-4). */
  main:not(:has(details.filters[open])) .filter-bar label:has(> input[type=date]),
  main:not(:has(details.filters[open])) .hint { display: none; }
  .filter-bar:not(:has(label > input)) > button,
  main:has(details.filters[open]) .filter-bar > button { display: none; }
  .wide.grid tr.filters td.c-apply { grid-column: 1 / -1; }
  .wide.grid tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.625rem;
    margin: 0 0 var(--space-2);
    padding: 0.625rem 0.75rem;
    background: var(--bg);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
  }
  .wide.grid tbody tr > td { display: block; padding: 0; border: 0; white-space: normal; }
  .wide.grid tbody .cut { max-width: none; white-space: normal; }
  .wide.grid tbody .cut.desc { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
  .wide.grid tbody tr > .c-invoice,
  .wide.grid tbody tr > .c-name { font-weight: 600; }
  .wide.grid tbody tr > .c-invoice { order: 1; }
  .wide.grid tbody tr > .c-date { order: 2; color: var(--muted); font-size: 0.8125rem; }
  .wide.grid tbody tr > .c-amount { order: 3; margin-left: auto; font-weight: 600; font-size: 0.9375rem; }
  .wide.grid tbody tr > .c-name,
  .wide.grid tbody tr > .c-customer,
  .wide.grid tbody tr > .c-description { order: 4; flex: 1 1 100%; } /* a Customers card's name heads it, alone on its line */
  .wide.grid tbody tr > .c-name { order: 1; }
  .wide.grid tbody td.num { text-align: left; }
  .wide.grid tbody tr > .c-kind,
  .wide.grid tbody tr > .c-class,
  .wide.grid tbody tr > .c-flags { order: 5; }
  .wide.grid tbody tr.row::after { content: ""; order: 6; flex: 1 1 100%; } /* quantity and price start a line */
  .wide.grid tbody tr > .c-qty,
  .wide.grid tbody tr > .c-price,
  .wide.grid tbody tr > .c-invoices,
  .wide.grid tbody tr > .c-first,
  .wide.grid tbody tr > .c-last,
  .wide.grid tbody tr > .c-size,
  .wide.grid tbody tr > .c-brand,
  .wide.grid tbody tr > .c-sold,
  .wide.grid tbody tr > .c-questions { order: 7; color: var(--muted); }
  .wide.grid tbody tr > .c-year { order: 8; color: var(--muted); }
  .wide.grid tbody tr > td.c-x { order: 9; margin-left: auto; width: auto; padding: 0; }
  .wide.grid tbody td.c-qty::before { content: "Qty " / ""; }
  .wide.grid tbody td.c-price::before { content: "at " / ""; }
  .wide.grid tbody td.c-invoices::before { content: "Invoices " / ""; }
  .wide.grid tbody td.c-first::before { content: "First " / ""; }
  .wide.grid tbody td.c-last::before { content: "Last " / ""; }
  .wide.grid[aria-label=Items] tbody td.c-last::before { content: "Last sold " / ""; }
  .wide.grid tbody td.c-sold::before { content: "Sold " / ""; }
  .wide.grid tbody td.c-questions:not(:empty)::before { content: "Questions " / ""; }
  .wide.grid td.c-x summary { width: 2.25rem; height: 2.25rem; border: 1px solid var(--rule); }
  .wide.grid tbody tr.row:has(details[open]) { margin-bottom: 0; border-color: var(--mark); border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
  .wide.grid tbody tr.more { display: none; margin: 0; padding: 0; }
  .wide.grid tbody tr.row:has(details[open]) + tr.more { display: block; margin: 0 0 var(--space-2); border: 1px solid var(--mark); border-top: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg); overflow: hidden; }
  .wide.grid tbody tr.more > td { display: block; }
  .wide.grid tbody tr > td[colspan]:not(.c-x) { flex: 1 1 100%; }
  /* the totals bar under the cards: one band, the count, then each total named */
  .wide.grid > table > tfoot { display: block; }
  .wide.grid tfoot tr.sum { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; padding: 0.75rem; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius-lg); }
  .wide.grid tfoot tr.sum > * { position: static; display: block; padding: 0; border: 0; background: none; }
  .wide.grid tfoot tr.sum > :empty { display: none; }
  .wide.grid tfoot td.c-qty::before { content: "Qty " / ""; font-weight: var(--fw-body); color: var(--muted); }
  .wide.grid tfoot td.c-amount::before { content: "Amount " / ""; font-weight: var(--fw-body); color: var(--muted); }
  /* An invoice's lines: the description full width, then quantity, price and amount at the right, each labelled; the
     totals a line each; the header row visually hidden, read as before. */
  .table-box:has(> table.invoice-lines) { overflow: visible; border: 0; background: none; width: 100%; }
  table.invoice-lines,
  table.invoice-lines > tbody,
  table.invoice-lines > tfoot { display: block; }
  table.invoice-lines > thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  table.invoice-lines tr { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: baseline; gap: 0 0.75rem; padding: 0.5rem; border-bottom: 1px solid var(--rule); background: none; }
  table.invoice-lines tr > * { display: block; padding: 0; border: 0; }
  table.invoice-lines td.desc,
  table.invoice-lines td.correct { flex: 1 1 100%; }
  table.invoice-lines td.qty::before { content: "Qty " / ""; color: var(--muted); }
  table.invoice-lines td.price::before { content: "at " / ""; color: var(--muted); }
  table.invoice-lines td.amount { font-weight: 600; }
  table.invoice-lines tfoot tr { justify-content: space-between; }
  table.invoice-lines td:empty { display: none; }
}

/* --- Targets fit the pointer (DESIGN 12, Decision 102): at least 24 by 24 CSS pixels everywhere (SC 2.5.8, AA),
   above; on a coarse pointer (a phone or a touch screen) buttons, inputs and links that stand alone at least 44 by 44
   (SC 2.5.5), the phone's sort pills and the filter chips among them. A link inside a sentence keeps its line, so no
   rule sizes a bare a. */
@media (pointer: coarse) {
  button,
  [role=button],
  a.btn,
  header a,
  nav a,
  td a,
  th a,
  .way a,
  .fchip {
    display: inline-flex;
    min-height: 44px;
    min-width: 44px;
  }
  input:not([type=checkbox], [type=radio], [type=hidden]),
  select,
  summary,
  fieldset label { min-height: 44px; }
  .grid td.c-x summary,
  .wide.grid td.c-x summary { width: 44px; height: 44px; } /* DESIGN 12; the cards' 2.25rem has this weight, so the
     later rule wins (review I-1) */
  .wide.grid thead th a { min-height: 44px; } /* the phone's sort pills, past the cards' 2rem (spec › 3) */
  .wide.grid tr.filters input,
  .wide.grid tr.filters select { min-height: 44px; } /* the Filters panel's controls, past the cards' 2.5rem */
  .toolbar a,
  .chips a { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; } /* Clear, Clear all */
  td a.sales { padding-block: 14px; margin-block: -14px; }
  /* desk v2 Task 19's heavier toolbar and grid rules, named again so the coarse 44 wins at equal weight: the toolbar's
     buttons and fields, the grid's sales arrow (its cell keeps the room), the check chip's hit area past its tag */
  .toolbar button,
  .toolbar .filter-bar > a,
  .toolbar .filter-bar label > input,
  .toolbar .panel-switch button { min-height: 44px; }
  .grid td.c-description:has(> .sales) { padding-right: 2.875rem; }
  .grid tbody tr:not(.more) > td:not([colspan]) > a.sales { width: 44px; height: 44px; margin-top: -22px; }
  .grid tbody tr:not(.more) > td:not([colspan]) a.chip { position: relative; }
  .grid tbody tr:not(.more) > td:not([colspan]) a.chip::after { content: ""; position: absolute; inset: -10px -4px; }
  details.filters > summary { min-height: 44px; }
  th, td { padding-block: 0.5rem; }
  /* A form control is at least 16 pixels on a touch screen, so iOS Safari never zooms the page when one takes focus
     (the look panel's S-1); the viewport never blocks zoom (SC 1.4.4). The rules that size a control smaller are
     named here, after them, so at equal weight this one wins. */
  input:not([type=checkbox], [type=radio], [type=hidden]),
  select,
  textarea,
  .filter-bar label > input,
  thead .filters input,
  thead .filters select,
  .wide.grid tr.filters input,
  .wide.grid tr.filters select { font-size: max(1rem, 16px); }
}

/* --- Forced colours (Windows High Contrast): box-shadow and the author's colours are dropped, so the page shown in
   the navigation and a filter holding a value keep a mark of their own, an outline in the system's Highlight. */
@media (forced-colors: active) {
  .side a[aria-current=page],
  .bottom-bar a[aria-current=page],
  .bottom-bar details:has([aria-current=page]) > summary,
  .filters .set,
  .filter-bar .set { outline: 2px solid Highlight; outline-offset: -2px; }
}

/* --- Task 13, same words (desk v2 spec › 4.3): the badge beside its switch while it is on, in the accent so it reads
   apart from the line chips; its words say it, so colour never carries it alone (DESIGN 13). */
.filter-bar .badge { color: var(--accent); box-shadow: inset 0 0 0 1px var(--mark); }

/* --- Desk v2 Task 8 (spec › 4.7, 4.8): the Columns menu, a disclosure under the toolbar holding one POST form, and
   the header's switch that narrows the navigation to an icon rail from 960 pixels: each word kept as the link's
   accessible name and tooltip (the template marks it `sr`), Admin a popover menu beside the rail (the Popover API,
   no script). Below 960 the navigation is the bottom bar, so the switch is not offered. */
details.columns, details.columns[open] { margin: 0; padding: 0; border: 0; background: none; } /* a button that opens a panel, not the bordered disclosure */
details.columns > summary { cursor: pointer; } /* its look is the toolbar's secondary style (above) */
@media (pointer: coarse) { details.columns > summary { min-height: 44px; } } /* DESIGN 12: the site's coarse rule, which the line above outranks */
details.columns form { width: max-content; max-width: 100%; margin: var(--space-1) 0 0; padding: var(--space-2) var(--space-3); border: 1px solid var(--rule); border-radius: var(--radius); background: var(--bg); }
details.columns fieldset { margin: 0 0 var(--space-2); padding: 0; border: 0; }
details.columns ol { list-style: none; margin: 0; padding: 0; }
details.columns li { display: flex; align-items: center; gap: var(--space-1); }
details.columns li label { flex: 1 1 auto; margin: 0 var(--space-2) 0 0; }
details.columns li button { padding-inline: var(--space-2); }
details.columns li button:disabled { opacity: 0.4; cursor: default; box-shadow: none; } /* the first can go no higher, the last no lower */
.nav-switch { margin: 0; }
@media (max-width: 959px) {
  .nav-switch { display: none; }
}
@media (min-width: 960px) {
  /* From 960 the open menu floats under its button, so the toolbar and the table do not move. */
  details.columns { position: relative; }
  details.columns form { position: absolute; top: 100%; left: 0; z-index: 20; max-width: none; box-shadow: 0 8px 24px rgb(0 0 0 / 0.18); }
  body.desk.narrow { --rail-w: 3.5rem; grid-template-columns: var(--rail-w) minmax(0, 1fr); }
  body.desk.narrow > header .name { min-width: 0; }
  body.desk.narrow > .side { padding-inline: 0.375rem; }
  .narrow .side ul { margin-bottom: var(--space-3); }
  .narrow .side a,
  .narrow .side .nav-admin { justify-content: center; padding: 0.5rem 0; }
  .narrow .side .icon { width: 1.25rem; height: 1.25rem; }
  .side .nav-admin { display: flex; align-items: center; width: 100%; margin: 0; border: 0; border-radius: var(--radius); background: none; color: var(--on-chrome); font: inherit; cursor: pointer; }
  .side .nav-admin:hover,
  .side .nav-admin.here { background: var(--chrome-2); box-shadow: none; }
  .side .nav-admin.here .icon { color: var(--chrome-accent); }
  .side .nav-pop:not(:popover-open) { display: none; } /* `.side ul`'s flex would outrank the browser's own hiding */
  .side .nav-pop:popover-open {
    position: fixed; inset: auto; top: var(--top-h); left: var(--rail-w); margin: 0; min-width: 13rem; padding: var(--space-2);
    list-style: none; background: var(--chrome); color: var(--on-chrome); border: 1px solid var(--chrome-2); border-radius: var(--radius);
  }
  .side .nav-pop a { justify-content: flex-start; padding: 0.375rem 0.625rem; color: var(--on-chrome); }
}

/* --- The invoice popup (desk v2 Task 9; spec › 4.5, DESIGN 3 and 18): the page's one modal <dialog id="sheet">,
   which js/sheet.js fills with the invoice's body part (no script, no popup: the link opens the page). From 760 CSS
   pixels a panel over the dimmed grid, at most 64rem wide, the × at its top right; below 760 it fills the screen, an
   invoice's lines a list (the phone rules above), its footer padded for the home indicator. Its body scrolls inside
   it and never chains to the grid behind (`overscroll-behavior: contain`). The dialog has no padding of its own, so a
   click on the dialog element itself is a click on the backdrop, which closes it (APG Dialog (Modal) pattern). */
#sheet { width: min(64rem, calc(100vw - 2rem)); max-width: none; max-height: calc(100dvh - 2rem); padding: 0; color: var(--text); background: var(--bg); border: 1px solid var(--rule); border-radius: var(--radius-lg); overflow: hidden; }
#sheet[open] { display: flex; flex-direction: column; }
#sheet::backdrop { background: rgb(15 27 45 / 0.55); }
#sheet .sheet-close { position: absolute; top: var(--space-2); right: var(--space-2); z-index: 1; padding: 0.125rem 0.625rem; font-size: 1.25rem; line-height: 1.2; color: var(--accent); background: var(--bg); border-color: var(--control); }
#sheet .sheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
#sheet .sheet-status { margin: 0; padding: var(--space-4) 1.25rem; padding-right: 3.5rem; color: var(--muted); }
#sheet .invoice.panel { margin: 0; border: 0; border-radius: 0; padding-bottom: 0; }
#sheet .page-head { padding-right: 3rem; } /* clear of the × */
#sheet .page-head h2 { margin: 0; font-size: 1.375rem; }
#sheet .table-box { max-height: none; }
#sheet .sheet-foot { position: sticky; bottom: 0; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); margin: var(--space-3) -1.25rem 0; padding: var(--space-2) 1.25rem calc(var(--space-2) + env(safe-area-inset-bottom)); background: var(--bg); border-top: 1px solid var(--rule); }
#sheet .sheet-foot p { margin: 0 auto 0 0; }
#sheet a.btn.secondary { color: var(--accent); background: transparent; border-color: var(--control); }
/* Below 760, written as "not 760 and wider" so the coarse-pointer block above stays the last rule at the cards'
   width (tests/test_look.py's 16 px pin); the sheet sets no control's size. */
@media not all and (min-width: 760px) {
  #sheet { width: 100%; height: 100%; max-height: none; margin: 0; border: 0; border-radius: 0; }
  #sheet .invoice.panel { display: flex; flex-direction: column; min-height: 100%; padding-inline: var(--space-3); }
  #sheet .sheet-foot { margin-inline: calc(-1 * var(--space-3)); margin-top: auto; padding-inline: var(--space-3); } /* at the foot, in thumb reach */
}

/* --- The side panel (desk v2 Task 10; spec › 4.4), the second prototype beside the opened row: a non-modal
   <aside id="panel"> holding the invoice's body part, which the server renders from `?open=` and js/panel.js fills
   where it runs. From 1280 CSS pixels a third column of the desk at the right, at the viewport's height under the
   header, the grid narrowed beside it (the table box scrolls inside itself, as at any width); minimised (`data-min`) a
   strip at the right edge, its invoice's number written down it. Below 1280 a sheet from the bottom over the page,
   above the phone's bottom bar, the page given its height at the foot so the last rows scroll clear of it; minimised a
   bar. Marks in --mark; every colour a token. */
#panel[hidden] { display: none; }
body.desk { --panel-w: min(32rem, 36vw); --strip-w: 3.25rem; } /* on the body, whose grid reads them */
#panel { display: flex; flex-direction: column; min-width: 0; color: var(--text); background: var(--bg); }
#panel .panel-bar { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); padding: var(--space-2) var(--space-3); background: var(--surface); border-bottom: 1px solid var(--rule); }
#panel .panel-bar a.btn.secondary { color: var(--accent); background: transparent; border-color: var(--control); }
#panel .panel-close { font-size: 1.25rem; line-height: 1; padding-inline: 0.625rem; }
#panel .panel-restore { margin-right: auto; }
#panel .panel-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
#panel .sheet-status { margin: 0; padding: var(--space-4) 1.25rem; color: var(--muted); }
#panel .invoice.panel { margin: 0; border: 0; border-radius: 0; padding-bottom: 0; }
#panel .page-head h2 { margin: 0; font-size: 1.25rem; }
#panel .table-box { max-height: none; }
#panel .sheet-foot { position: sticky; bottom: 0; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); margin: var(--space-3) -1.25rem 0; padding: var(--space-2) 1.25rem calc(var(--space-2) + env(safe-area-inset-bottom)); background: var(--bg); border-top: 1px solid var(--rule); }
#panel .sheet-foot p { margin: 0 auto 0 0; }
#panel .sheet-foot a.btn.secondary { color: var(--accent); background: transparent; border-color: var(--control); }
#panel tr.here { background: var(--accent-soft); } /* the line opened from, in the panel and in the grid */
.grid tbody tr.row[aria-current] { background: var(--accent-soft); }
.grid tbody tr.row[aria-current] > :first-child,
#panel tr.here > :first-child { box-shadow: inset 3px 0 0 var(--mark); }
@media not all and (min-width: 760px) { /* the phone's invoice lines are flex rows, its grid rows cards: the bar on the row */
  #panel tr.here { box-shadow: inset 3px 0 0 var(--mark); }
  #panel tr.here > :first-child,
  .grid tbody tr.row[aria-current] > :first-child { box-shadow: none; }
  .wide.grid tbody tr.row[aria-current] { border-color: var(--mark); box-shadow: inset 3px 0 0 var(--mark); }
}
#panel[data-min] .panel-body,
#panel:not([data-min]) .panel-restore { display: none; }
.panel-switch { margin: 0; }
.panel-switch button[aria-pressed=true] { background: var(--accent-soft); border-color: var(--mark); box-shadow: inset 0 0 0 1px var(--mark); } /* on: marked, as a set filter is */
@media (min-width: 1280px) {
  body.desk:has(> #panel:not([hidden])) { grid-template-columns: var(--nav-w) minmax(0, 1fr) var(--panel-w); }
  body.desk.narrow:has(> #panel:not([hidden])) { grid-template-columns: var(--rail-w) minmax(0, 1fr) var(--panel-w); }
  body.desk:has(> #panel[data-min]:not([hidden])) { --panel-w: var(--strip-w); }
  #panel { grid-column: 3; grid-row: 2; position: sticky; top: var(--top-h); align-self: start; height: calc(100vh - var(--top-h)); border-left: 1px solid var(--rule); }
  #panel[data-min] .panel-bar { flex-direction: column; flex: 1 1 auto; justify-content: flex-start; padding-inline: var(--space-1); border-bottom: 0; }
  #panel[data-min] .panel-restore { order: 1; margin: 0; padding: var(--space-3) var(--space-2); writing-mode: vertical-rl; }
  #panel[data-min] .panel-min { display: none; }
}
@media not all and (min-width: 1280px) {
  #panel { position: fixed; left: 0; right: 0; bottom: 0; z-index: 15; max-height: 60dvh; border-top: 1px solid var(--rule); border-radius: var(--radius-lg) var(--radius-lg) 0 0; box-shadow: 0 -8px 24px rgb(0 0 0 / 0.18); }
  #panel .panel-bar { border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
  #panel[data-min] .panel-bar { border-bottom: 0; }
  #panel[data-min] .panel-min { display: none; }
  body.desk:has(> #panel:not([hidden], [data-min])) > main { padding-bottom: calc(60dvh + var(--space-4)); }
}
@media (min-width: 960px) and (max-width: 1279.98px) {
  #panel { left: var(--nav-w); }
  body.desk.narrow > #panel { left: var(--rail-w); }
}
@media (max-width: 959.98px) {
  #panel { bottom: calc(var(--bar-h) + env(safe-area-inset-bottom)); }
  body.desk:has(> #panel:not([hidden], [data-min])) > main { padding-bottom: calc(60dvh + var(--bar-h) + var(--space-4) + env(safe-area-inset-bottom)); }
}

/* --- The theme control (desk v2 Task 19; Charlie, 13 Oct 22:4x "allow us to toggle between light and dark mode"):
   one menu in the header beside Sign out, a native disclosure (the APG Disclosure pattern; keyboard and no-script
   for free), its summary the state's icon and word (Auto, Light or Dark, named "Theme: …"), its panel one form of
   three buttons, the chosen one pressed and ticked. A menu rather than three buttons side by side: one control that
   fits the desk's header and the phone's, where only an icon has room. Below 760 the summary and the Search button
   show their icons alone, their words kept for a screen reader, so the search field keeps its room at 375. */
details.theme,
details.theme[open] { position: relative; margin: 0; padding: 0; border: 0; background: none; }
details.theme > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-height: 2rem;
  padding: 0 0.75rem;
  color: var(--on-chrome);
  border: 1px solid var(--chrome-muted);
  border-radius: var(--radius);
  font-size: var(--fs-table);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  list-style: none;
  cursor: pointer;
}
details.theme > summary::-webkit-details-marker { display: none; }
details.theme > summary:hover,
details.theme[open] > summary { background: var(--chrome-2); }
.theme-form {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 13rem;
  margin: 0;
  padding: var(--space-1);
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.theme-form > button,
.theme-form > button + button {
  justify-content: flex-start;
  gap: 0.5rem;
  margin: 0;
  padding: 0.375rem 0.625rem;
  color: var(--text);
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  font-weight: var(--fw-medium);
  text-align: left;
}
.theme-form > button small { color: var(--muted); font-weight: var(--fw-body); }
.theme-form > button:hover { background: var(--hover); box-shadow: none; }
.theme-form > button[aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); }
.theme-form > button[aria-pressed="true"]::after { content: "✓" / ""; margin-left: auto; }
.theme-form :focus-visible { outline-color: var(--focus); }
body > header [role=search] button .icon { display: none; }
@media not all and (min-width: 760px) {
  body > header [role=search] button .icon { display: inline; }
  body > header :is(details.theme > summary, [role=search] button) .word { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  body > header [role=search] button,
  details.theme > summary { padding-inline: 0; min-width: 2rem; }
}
@media (pointer: coarse) { /* DESIGN 12: the site's coarse rule, which the lines above outrank */
  details.theme > summary,
  body > header [role=search] button { min-height: 44px; min-width: 44px; } /* the phone's icon-only Search too (T-1) */
  .theme-form button { min-height: 44px; }
}
