/* Light-only overrides — boards cluster.
   ─────────────────────────────────────────────────────────────────────────────────────────────
   WHY THIS FILE EXISTS, AND WHY IT IS NOT IN A PAGE STYLESHEET

   A light-only rule cannot live in a page sheet. usePageStyle confines those by CSS nesting when a
   page renders inside a pane, which compiles `html[data-theme="light"] .foo` into a DESCENDANT of
   the page scope — and the root element has no ancestors, so it can never match. Silently. One
   instance of that already shipped.

   It also cannot live in app.css while six agents are working, so the cluster gets its own file.

   HOW IT WINS
   These load after app.css but BEFORE the runtime-injected page sheets, so document order is not on
   their side. They win on SPECIFICITY: `html[data-theme="light"] .foo` is (0,2,0) against a page's
   (0,1,0). Against a two-class page selector, add one more class or an element to stay ahead.

   WHAT BELONGS HERE
   Only rules that must differ in light and whose dark value must not move. Every rule is prefixed
   `html[data-theme="light"]`, so dark cannot match by construction — which is what makes this file
   safe to write in parallel with the dark baseline frozen.

   WHAT DOES NOT BELONG HERE
   Anything that should be true in both themes; that goes in the page sheet. Anything needing a new
   token; that is escalated to the lead, who owns app.css. */

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   1. SELECTION — the pale-lavender chip, in nine dialects
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Every board in this cluster composes its own "applied" state out of `rgba(--ch-violet-7c3aed, α)`
   at α .20–.30, which in dark is a violet glow over near-black and in light is 109,40,217 laid at a
   fifth strength over white — i.e. lavender emulsion. Five of them sit in the same control bar on
   /work-queue, which is what made that page read as a lavender field.

   One answer for all of them, and it is the rulebook's: `--surface-selected` (a deliberately quiet
   wash) plus the 3px `--accent-bar` leading edge that actually carries the state. The bar is the
   carrier — it survives glare, it survives colour-blindness, and it does not multiply into a field
   when nine of them are on screen. The wash only has to say "this one, not that one".

   These cannot be sheet edits: `--surface-selected` is α .16 in dark against these sites' .20–.30,
   so pointing the sheet at it would move dark. */
html[data-theme="light"] .fc-chip.on,
html[data-theme="light"] .fc-sort-btn.on,
html[data-theme="light"] .lx-tab.active,
html[data-theme="light"] .rt-chip.active,
html[data-theme="light"] .rt-cell.active,
html[data-theme="light"] .rt-cell.due.active,
html[data-theme="light"] .cx-chip.on,
html[data-theme="light"] .wq-pill.on,
html[data-theme="light"] .wq-seg-btn.on,
html[data-theme="light"] .vs-tab.active,
html[data-theme="light"] .vs-dens-btn.on,
html[data-theme="light"] .vs-place-mini.on,
html[data-theme="light"] .vs-eq-unit.on,
html[data-theme="light"] .vs-chip-btn.on,
html[data-theme="light"] .do-chip.on,
html[data-theme="light"] .dt-chip.on,
html[data-theme="light"] .dp-chip.on {
    color: var(--text);
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
/* The counts riding inside a selected chip follow its ink rather than carrying a second violet. */
html[data-theme="light"] .fc-chip.on i,
html[data-theme="light"] .cx-chip.on > b,
html[data-theme="light"] .rt-chip.active .rt-chip-n,
html[data-theme="light"] .rt-seg button.active .rt-chip-n,
html[data-theme="light"] .do-chip.on i,
html[data-theme="light"] .dt-chip.on i,
html[data-theme="light"] .cx-lane.on > b {
    color: var(--text-muted);
    background: none;
}
/* (`.vs-tab.active` is in that list because it was a SOLID accent gradient with a violet drop-shadow
   — a selected tab wearing the primary action's whole costume, louder than the "Checka in bil"
   beside it that is the page's actual primary.) */

/* The complaint clock's keys already carry a 3px status edge (fresh / watch / over) on the leading
   side, so they get the wash and an accent hairline instead of a second bar in the same place. */
html[data-theme="light"] .cx-key.on {
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}
html[data-theme="light"] .cx-key.on .cx-key-label { color: var(--text); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   2. THE LEDGER ROW — hover is not a selection
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   lib/listLedgerStyle is consumed by six boards, so its hover wash is the most-repeated violet in
   the app. In dark, `rgba(--ch-indigo-a78bfa, .06)` over near-black is a lift; in light the same
   channel is 124,58,237 and every row a cursor crosses tints lavender. The state tokens go DARKER,
   which is the direction light actually has. The leading edge stays accent — that is the one thing
   on a row allowed to be violet, and it is what tells hover from picked. */
/* ── ONE TONE FOR ROW HOVER, and one mechanism ────────────────────────────────────────────────
   MEASURED: `.lx-row:hover` and `.bt-row:hover` came up rgb(228,230,235) (--surface-hover) while
   /settings' `.set-rail-item:hover` came up rgb(243,246,248) (--surface-inset). Two tones for one
   gesture, and the app has no way to tell a user that the difference means nothing. --surface-inset
   is the base file's own choice for `.lx-row:hover` (_base.css §rows) and it is the lighter, calmer
   step: on white, hover is an acknowledgement, not a selection, and 228 is close enough to
   --surface-selected to read as one. --surface-hover keeps its meaning on controls, where a press
   target genuinely does need the deeper step.

   `box-shadow: none` is the second half and it is NOT redundant. _base.css's `.lx-row:hover`
   re-declares `inset 0 -1px 0 --keyline` — correct under _base's idiom, where the divider IS the
   shadow and hover must not drop it. But boards.css:398 below switched `.lx-row` to a real
   `border-bottom`, and a rule at equal specificity only overrides the properties it NAMES, so this
   line's `background` won and _base's `box-shadow` survived underneath it: hovering a row drew the
   shadow divider ON TOP of the border and the rule thickened to 2px under the pointer. boards.css
   owns `.lx-row`'s boundary, so boards.css cancels the shadow. */
html[data-theme="light"] .lx-row:hover {
    background: var(--surface-inset);
    box-shadow: none;
}
/* The tablet drop-off board keeps its own row, same wash by copy. */
html[data-theme="light"] .dt-row-main:hover { background: var(--surface-inset); }
html[data-theme="light"] .dt-row-main:focus-visible { background: var(--surface-active); }
/* Same tone as `.lx-row` above — one gesture, one step. */
html[data-theme="light"] .cx-row:hover,
html[data-theme="light"] .cx-row:active { background: var(--surface-inset); }
/* Group hairlines: a rule that separates two groups is furniture, not an accent. */
/* FLAT, not a fade — corrected in the compliance sweep. A rule that dissolves into the ground exists
   because on near-black a hard hairline reads as a scratch; on white the fade is what looks like a
   rendering artefact, a line the renderer gave up on halfway across. (_base, GRADIENT HYGIENE.) */
html[data-theme="light"] .dt-group-head::after {
    background: var(--keyline);
}
html[data-theme="light"] .lx-row:focus-visible { background: var(--surface-active); }
html[data-theme="light"] .lx-row.picked { background: var(--surface-selected); }
/* Scrollbar furniture is not a state. */
html[data-theme="light"] .lx-list { scrollbar-color: var(--border-strong) transparent; }
html[data-theme="light"] .lx-list::-webkit-scrollbar-thumb { background: var(--border-strong); }
html[data-theme="light"] .lx-sheet.wq-pane { scrollbar-color: var(--border-strong) transparent; }
html[data-theme="light"] .lx-sheet.wq-pane::-webkit-scrollbar-thumb { background: var(--border-strong); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   3. SECONDARY ACTIONS — violet does not mean "button"
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   "Logga arbete" is the most repeated control on /work-queue (one per live row) and it is painted
   from an indigo gradient. Its own stylesheet says, correctly, that every row action wears the
   SECONDARY look whatever it does — but secondary in this app is a surface and a hairline, not a
   quieter violet. Fifteen quiet violets still add up to a violet page.

   Sheet edits are unavailable: dark's gradient is the identity of that control on the dark board. */
html[data-theme="light"] .wq-rowact.go,
html[data-theme="light"] .wq-rowact.claim,
html[data-theme="light"] .rt-dueact.primary,
html[data-theme="light"] .wqt-chip,
html[data-theme="light"] .wq-act.reach,
html[data-theme="light"] .wq-counter-open,
html[data-theme="light"] .dt-preset,
html[data-theme="light"] .dp-preset,
html[data-theme="light"] .dt-more,
html[data-theme="light"] .dp-more {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
    box-shadow: none;
}
html[data-theme="light"] .wq-rowact.go:hover:not(:disabled),
html[data-theme="light"] .wq-rowact.claim:hover:not(:disabled),
html[data-theme="light"] .rt-dueact.primary:hover:not(:disabled),
html[data-theme="light"] .wqt-chip:active:not(:disabled),
html[data-theme="light"] .wq-act.reach:hover:not(:disabled),
html[data-theme="light"] .wq-counter-open:hover,
html[data-theme="light"] .dt-preset:hover:not(:disabled),
html[data-theme="light"] .dt-more:hover {
    color: var(--text);
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}
/* The disclosure that opens the estimate correction: an open row is a state, and it was saying so in
   violet on a sheet that already has a violet primary at its foot. */
html[data-theme="light"] .wq-inlinetoggle:hover:not(:disabled),
html[data-theme="light"] .wq-inlinetoggle[aria-expanded="true"] {
    color: var(--text);
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] .wq-inlinetoggle .chev {
    color: var(--text-secondary);
    background: var(--well-a12);
}
html[data-theme="light"] .wq-history-row:hover {
    background: var(--surface-2-hover);
    border-color: var(--border-strong);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   4. DECORATIVE VIOLET — panels, notices, eyebrows, counts
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   None of these is an action, a focus, a selection or the logo, so none of them is allowed the
   accent. In dark each is a faint bloom on black; in light each is a lavender slab. */
/* The preview-mode notice — a full-measure lavender band across /complaints. It is a NOTICE, and the
   theme has a token for exactly that meaning. */
html[data-theme="light"] .cx-demo {
    color: var(--st-info);
    background: var(--st-info-soft);
    border-color: var(--st-info-line);
}
/* Storage: the "no stations yet" banner, the header count pill and the occupancy meter. Occupancy is
   not progress toward the shop's goal — a full shop is not a win — so the meter carries no accent. */
html[data-theme="light"] .vs-nostations {
    background: var(--surface-1);
    border-color: var(--border-default);
}
html[data-theme="light"] .vs-total {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .vs-hero-meter > i { background: var(--text-secondary); }
/* A place's name is a group heading, not a state: "GARAGE 1" printed in accent violet, with an
   accent hairline running off it, repeated once per station down the board. */
html[data-theme="light"] .vs-group-head { color: var(--text-muted); }
/* Flat, for the same reason as .dt-group-head::after above. */
html[data-theme="light"] .vs-group-head::after {
    background: var(--keyline);
}
html[data-theme="light"] .vs-places-label { color: var(--text-muted); }
/* Routines: the year strip's month counts are a count, not a state, and they sat in accent chips
   above an axis that is already carrying two categorical season hues. */
html[data-theme="light"] .rt-strip-n b {
    color: var(--text-secondary);
    background: var(--well-a12);
}
/* "Due soon" is a STATUS. It was coded in the accent — which put the page's most repeated data value
   in the one colour reserved for the primary action, on a board where the primary action ("+ Ny
   rutin") is six inches away in the same violet. --st-info is the theme's own token for exactly this
   meaning: a notice, not a warning. Red still outranks it for overdue, so the ordering holds. */
html[data-theme="light"] .rt-row.soon .rt-row-when b { color: var(--st-info); }
html[data-theme="light"] .rt-row.soon .rt-row-when em { color: var(--st-info); }
html[data-theme="light"] .rt-cell.due b { color: var(--text); }
/* Eyebrows over the drop-off quick-log pad: an uppercase caption is not an accent site. Both are
   painted with --astra-accent-soft, which in light is still the PRE-re-derivation #7c3aed — see the
   escalation note at the foot of this file. */
html[data-theme="light"] .dt-quick-eyebrow,
html[data-theme="light"] .dp-quick-eyebrow { color: var(--text-muted); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   ESCALATIONS — not fixable from this file, listed so they are not re-discovered
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   1. `--astra-accent-soft` resolves to `#7c3aed` in the light block of app.css — the accent value
      that was retired this morning in favour of `#6c3cb8` (~30% less chroma). It is consumed as
      accent TEXT at roughly twenty sites in this cluster alone (.do-rowbtn:hover, .do-pacebtn-go,
      .do-arrival-state, .do-detail-customer.link, .do-penbtn, .do-infobtn, .do-daychip.own,
      .wq-pcfg-btn:hover, .wq-daychip.own, .vs-*, .rt-*, plus lib/errorPageStyle). Repointing that one
      declaration at `--accent-solid` fixes all of them at once and cannot move dark, where the token
      is `#a78bfa` and shared with --accent-bar/--focus-ring. Only the lead can make that edit.
      Its focus-ring and selection-bar uses have already been migrated to --focus-ring / --accent-bar
      in this cluster's sheets, which IS dark-identical.
   2. `.btn-astra-glass` — the app's SECONDARY button — is an indigo gradient with indigo ink in
      light (app.css). It is the "Historik" button on /work-queue and the "Logga tid" on the drop-off
      tablet board, i.e. the loudest remaining violet on both. Per the accent rule a secondary is
      `--surface-3` + `--border-default` + `--text`. This is a shared component in app.css and an
      app-wide decision; diverging one page for it would be worse than leaving it.
   3. `.badge` (the page eyebrow pill) is likewise a violet-tinted app.css component and reads as a
      lavender chip at the top of all five pages in this cluster.
   4. `.rt-season.summer` / `.rt-strip-run.summer` are the routines board's CATEGORICAL season pair
      (winter cyan / summer violet), not accent misuse — but the summer half is indistinguishable
      from the accent in light. That is a --cat-* mapping decision, not a page decision (§10.6). */
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   5. STRUCTURE — the boards stop being a re-skin (light-only, 2026-08-10)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Everything above this line is a COLOUR correction: the same shapes, repainted. Everything below
   changes geometry — heights, radii, borders, type size, case, tracking — because the two themes
   carry the same information by different mechanisms, and a mechanism lives in shape.

   Dark builds a board out of tint over near-black: a coloured heading, a glowing dot, a filled
   capsule. Every one of those survives on near-black because a saturated pixel there is the only
   light source in its neighbourhood. On white the identical device is PIGMENT — an amber heading
   reads as a highlighter mark, a filled capsule reads as a consumer-app lozenge, and a row of nine
   of them reads as a toy. So light carries the same facts as: a bordered badge with a shape-distinct
   icon, an uppercase muted eyebrow, a rectangle, an underline, and a 1px keyline.

   All of it composes with _base.css rather than restating it: the badge recipe, the density tokens
   and the eyebrow tokens are declared there, and this file only extends their selector lists onto
   the class names these five boards actually use. */

/* ── 5.1 STATUS-AS-COLOURED-TEXT → BADGES ──────────────────────────────────────────────────────
   The clearest instances in the cluster, all of them "the state, spelled in a hue":
     .lx-state.s-attention / .s-ready   the work-queue row's one word (amber / emerald text)
     .lx-state.waiting                  the shared ledger's own tone, six boards deep
     .cx-row-state.warn / .pending      the complaints row — PLAIN TEXT tinted amber, no chip at all
     .wq-sched-chip.today / .overdue    the schedule verdict
   None of these is a class _base.css targets, so the selector list is extended here. The RECIPE is
   not duplicated: fill + tint border + dark ink + a ::before, exactly _base §1, at the same 11px and
   4px, so a badge on a board is the same object as a badge anywhere else.

   The icon is the second carrier and is mandatory, for the reason _base states: under deuteranopia
   the success and danger fills collapse to two olives at ΔE 0.027 and the TEXT at 0.040. Four
   coloured dots would reintroduce exactly that; check / bang / cross are shape-distinct. */
html[data-theme="light"] .lx-state,
html[data-theme="light"] .cx-row-state,
html[data-theme="light"] .wq-sched-chip,
html[data-theme="light"] .wq-tag-lead {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border-radius: 4px;
    padding: 2px 6px;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    line-height: 1.4;
    letter-spacing: 0;
    text-transform: none;
    background: var(--surface-inset);
    color: var(--ink-dim);
    border: 1px solid var(--keyline);
}
/* Neutral takes the dot. .wq-tag-lead is deliberately NOT in this list: it is the leading FINDING
   ("Bromsar"), a data value rather than a state, and a bullet in front of a noun is noise. It keeps
   the badge's geometry and loses the badge's glyph. */
html[data-theme="light"] .lx-state::before,
html[data-theme="light"] .cx-row-state::before,
html[data-theme="light"] .wq-sched-chip::before {
    content: "•";
    font-size: 1em;
    line-height: 1;
}
html[data-theme="light"] .lx-state.s-ready {
    color: var(--st-logged);
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
}
html[data-theme="light"] .lx-state.s-ready::before { content: "✓"; }

html[data-theme="light"] .lx-state.waiting,
html[data-theme="light"] .lx-state.s-attention,
html[data-theme="light"] .cx-row-state.warn,
html[data-theme="light"] .cx-row-state.pending,
html[data-theme="light"] .wq-sched-chip.today,
html[data-theme="light"] .wq-mark {
    color: var(--st-expected);
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
}
html[data-theme="light"] .lx-state.waiting::before,
html[data-theme="light"] .lx-state.s-attention::before,
html[data-theme="light"] .cx-row-state.warn::before,
html[data-theme="light"] .cx-row-state.pending::before,
html[data-theme="light"] .wq-sched-chip.today::before { content: "!"; }

html[data-theme="light"] .wq-sched-chip.overdue {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
}
html[data-theme="light"] .wq-sched-chip.overdue::before { content: "✕"; }

/* .wq-mark is already a shape-carrier by construction — an 18px disc holding a glyph the markup
   supplies, with an accessible name. It keeps its circle (that IS its distinctness against the
   square badges beside it) and only takes the amber badge's three values, above. */

/* ── 5.2 SECTION HEADINGS → THE EYEBROW ────────────────────────────────────────────────────────
   The work-queue ledger prints its three bucket headings in amber, neutral and emerald, at reading
   size and sentence case. That is the single loudest use of colour-as-label in the cluster, and the
   page sheet says so itself: "the hue belongs to the WORD, and the row's flag below only repeats
   it". On near-black that is a quiet tint; on white the page opens with a green word and an amber
   word before a single row of data.

   The eyebrow replaces it: 11px, uppercase, tracked, --ink-muted. The state has not been lost — it
   is still on the row's flag, still in the row's badge, and still in the heading's own words. What
   is gone is the third statement of it, in the one channel a light UI cannot afford. */
html[data-theme="light"] .lx-bucket .lx-bucket-name,
html[data-theme="light"] .lx-bucket.b-customer .lx-bucket-name,
html[data-theme="light"] .lx-bucket.b-us .lx-bucket-name,
html[data-theme="light"] .lx-bucket.b-progress .lx-bucket-name,
html[data-theme="light"] .cx-bucket .cx-bucket-name,
html[data-theme="light"] .vs-group-head,
html[data-theme="light"] .dt-group-head,
html[data-theme="light"] .vs-places-label,
html[data-theme="light"] .cx-eyebrow,
html[data-theme="light"] .dt-quick-eyebrow,
html[data-theme="light"] .dp-quick-eyebrow {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: uppercase;
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}
/* The count and the sum that ride on a heading are FACTS ABOUT the section, not states of it. The
   sum was mint green (--st-logged) because everywhere else in the app that slot is money; here it
   is bay hours, and hours are not a success. */
html[data-theme="light"] .lx-bucket .lx-bucket-n,
html[data-theme="light"] .lx-bucket .lx-bucket-sum,
html[data-theme="light"] .cx-bucket .cx-bucket-n,
html[data-theme="light"] .cx-bucket .cx-bucket-sev {
    color: var(--ink-muted);
    font-weight: var(--fw-label);
}
/* The heading's trailing rule is furniture: a keyline, at keyline weight, not a fading tint. */
html[data-theme="light"] .lx-bucket::after,
html[data-theme="light"] .cx-bucket::after { background: var(--keyline); }

/* The windowed list's sticky heading. Its dark value is a 72% near-black fill plus a blur, so light
   resolves it to a pale blue slab with a filter that is `none` — i.e. a bar of a colour nothing else
   on the page uses, and no boundary. It becomes the surface it sits on, with a keyline under it,
   which is the only thing that has to be true of a sticky heading: that rows pass BEHIND it. */
html[data-theme="light"] .wq-ledger.windowed .lx-bucket {
    background: var(--surface);
    border-bottom: 1px solid var(--keyline);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* ── 5.3 THE ROWS ──────────────────────────────────────────────────────────────────────────────
   Heights are authored from the --row-h* rungs and the vertical padding is DERIVED from them; the
   horizontal padding holds at --row-pad-x across every board so the columns of two different boards
   still agree. These rows are 2–3 grid lines tall, so the rung is a MIN-height rather than a height:
   a fixed height on a row whose third line appears only on live jobs would clip it.

   DERIVED PADDING — exempt from the --sp-* scale, and labelled so per the spec. Rounding 6px to 8
   here silently changes the height of every row on five boards.

   The divider is --keyline at 1px and it is the whole boundary: no per-row radius, no per-row
   shadow, no card. That was already the idiom's intent in dark ("no card, no box"); light just needs
   the hairline to be a real hairline rather than a 5.5% white wash that composites to nothing. */
/* `.lx-row`'s SIZE LEFT THIS FILE. `min-height: var(--row-h-comfy)` and `padding: 6px
   var(--row-pad-x)` are now listLedgerStyle.ts, unprefixed, for both themes — the reasoning above
   is about a dense ledger row, not about a light one, and stating it here made the shared component
   render at two densities. MEASURED before the move, the same markup on the same route:

       /vehicles        light 48px    dark 71px
       /quote-drafts    light 58px    dark 80px
       /phone-bookings  light 61px    dark 73px

   `--row-h-comfy` and `--row-pad-x` are declared in _base.css, a light-only file, so the shared
   rule reads them through `var(--…, 48px)` / `var(--…, 16px)` fallbacks: light keeps the ladder
   (including the 56/64px touch-shell bumps at _base.css:2470) and dark gets the same 48/16 desktop
   numbers without this file having to exist for it. Lifting the tokens themselves into app.css is
   the tidier end state and is flagged rather than done — they serve six components, not just this
   one.

   The BORDER stays: a 1px keyline is paint, and it is the whole reason this block was written. */
html[data-theme="light"] .lx-row,
html[data-theme="light"] .cx-row,
html[data-theme="light"] .rt-row {
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    box-shadow: none;
}
/* The complaints row carries severity on a 2px leading edge, which must survive the reset above. */
html[data-theme="light"] .cx-row { border-left: 2px solid transparent; }
/* The last hairline in a bordered column would double with the column's own edge. */
html[data-theme="light"] .lx-list > .lx-row:last-child,
html[data-theme="light"] .rt-list > .rt-row:last-child { border-bottom-color: transparent; }
/* Hover and pick keep the leading bar as the carrier (§2 above) and lose the row-level lift. */
/* --surface-inset per §2's one-tone rule. The leading bar stays: on the routines ledger it is the
   only thing distinguishing hover from the row simply being there. */
html[data-theme="light"] .rt-row:hover { background: var(--surface-inset); box-shadow: inset 2px 0 0 var(--keyline-strong); }
html[data-theme="light"] .rt-row:focus-visible { background: var(--surface-active); box-shadow: inset 2px 0 0 var(--focus-ring); }
html[data-theme="light"] .rt-row.picked { background: var(--surface-selected); box-shadow: inset 3px 0 0 var(--accent-bar); }

/* ── 5.4 A BOARD'S LEFT COLUMN NEEDS A BOUNDARY ────────────────────────────────────────────────
   On /work-queue the ledger column (.lx-list.wq-ledger) has no surface, no border and no fill at
   all — in dark it reads because the rows' hairlines sit on a near-black ground and the pane beside
   it is glass. In light the pane is a bordered white card and the list is nothing: rows floating on
   the canvas next to a framed object, which reads as an unfinished half of the page.

   THE CHOICE MADE, and why. Two answers were available — self-separating rows on the bare canvas
   (Linear's inbox), or a bordered surface (Stripe's list). The surface wins here for one structural
   reason: this column SCROLLS INDEPENDENTLY of the pane beside it. A scroll region with no edge has
   no visible start or end, so a row half-clipped at the top rail looks like a rendering fault rather
   than like more list. The border is where the scroll begins.

   And it is a surface WITH a border, deliberately: white on #f6f8fa with no hairline is the light
   theme's silent failure — nothing throws and the element is simply absent. */
html[data-theme="light"] .lx-list,
html[data-theme="light"] .rt-list {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    padding-bottom: 0;
}
/* The complaints list already had a surface; it only needed the boundary raised to a real keyline
   and the fill taken to the surface rung, so it matches the pane it sits beside. */
html[data-theme="light"] .cx-list {
    background: var(--surface);
    border-color: var(--keyline);
    border-radius: var(--r-card);
}
/* Inside a bordered column a heading no longer needs a 24px air gap to say "new section" — the rule
   and the case do that. The first one loses its indent entirely. */
/* THE BUCKET HEADING'S SPACING ALSO LEFT THIS FILE — same fault, same fix. `margin-top: var(--sp-4)`
   / `padding: 10px var(--row-pad-x) 6px` / `:first-child { margin-top: 0 }` are listLedgerStyle.ts
   now, unprefixed. The argument written above ("inside a bordered column a heading no longer needs a
   24px air gap") is about a heading sitting in a ledger, and the shared component's own 32px gap was
   authored before the ledger had buckets at all — it was never a dark decision, only an unrevised
   one. Light's numbers are the ones that were looked at, so they are the ones that ship, for both.

   Only the bucket's PAINT stays in this file (§5.2 above: case, tint, and the ::after rule). */

/* ── 5.5 CHIPS AND TABS — rectangles, and an underline for "you are here" ──────────────────────
   _base §2 puts the filter chips on --r-control; these are the two objects it does not reach: the
   ledger's lane pills and the complaints board's segmented lane control, both 999px capsules with a
   FILLED active state.

   An underline rather than a fill, for the reason _base gives: it is the strongest available "you
   are here" that spends no fill area and no accent budget. Note this deliberately reverses the
   complaints sheet's own comment ("the active lane is a filled pill, not an underline") — that
   comment is about DARK, where a fill is the only thing that separates from near-black, and it stays
   true there. On white a filled capsule is the loudest object in a bar that also holds the page's
   real primary action.

   This also supersedes the §1 SELECTION treatment for .lx-tab.active / .vs-tab.active specifically:
   a wash plus a leading bar is right for an applied FILTER (one of nine, any number on) and wrong
   for a TAB (one of three, exactly one on). Later in the file at equal specificity, so it wins. */
html[data-theme="light"] .lx-tabs,
html[data-theme="light"] .cx-lanes,
html[data-theme="light"] .vs-tabs {
    gap: 0;
    padding: 0;
    border: 0;
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    background: none;
}
html[data-theme="light"] .lx-tab,
html[data-theme="light"] .cx-lane,
html[data-theme="light"] .vs-tab {
    display: inline-flex;
    align-items: center;
    min-height: var(--ctl-h-lg);
    padding: 0 var(--ctl-pad-x);
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    margin-bottom: -1px;                    /* the tab's rule sits ON the strip's keyline */
    background: none;
    box-shadow: none;
    font-size: var(--t-sm);
    font-weight: var(--fw-label);
    color: var(--ink-muted);
}
html[data-theme="light"] .lx-tab:hover,
html[data-theme="light"] .cx-lane:hover,
html[data-theme="light"] .vs-tab:hover { background: none; color: var(--ink); }
html[data-theme="light"] .lx-tab.active,
html[data-theme="light"] .cx-lane.on,
html[data-theme="light"] .vs-tab.active {
    color: var(--ink);
    background: none;
    border-color: transparent;
    border-bottom-color: var(--accent);
    box-shadow: none;
}
html[data-theme="light"] .lx-tab.active .lx-tab-count,
html[data-theme="light"] .cx-lane.on > b { color: var(--ink-muted); background: none; }

/* The density pair on the storage board is a true segmented control (two values of one property),
   so it stays a bordered group — but a rectangle group, with the pressed half filled from the inset
   rung rather than from a violet at 30%. */
html[data-theme="light"] .vs-density {
    border-radius: var(--r-control);
    border-color: var(--keyline-strong);
    background: var(--surface);
}
html[data-theme="light"] .vs-dens-btn { border-radius: 2px; }
html[data-theme="light"] .vs-dens-btn.on {
    color: var(--ink);
    background: var(--surface-inset);
    box-shadow: none;
}

/* ── 5.6 CONTROLS ──────────────────────────────────────────────────────────────────────────────
   The row action, the presets and the counter-offer's opener: one height, one radius, one weight,
   and a secondary that is --surface plus a real 1px --keyline-strong. Not a tint — §3 above already
   took the violet out of them, and a quieter violet is still a violet; what a secondary needs on
   white is an EDGE. */
html[data-theme="light"] .wq-rowact,
html[data-theme="light"] .rt-dueact,
html[data-theme="light"] .dt-preset,
html[data-theme="light"] .dp-preset,
html[data-theme="light"] .dt-more,
html[data-theme="light"] .dp-more,
html[data-theme="light"] .wq-counter-open,
html[data-theme="light"] .cx-chip,
html[data-theme="light"] .vs-place-mini {
    min-height: var(--ctl-h);
    padding: 0 var(--ctl-pad-x);
    border-radius: var(--r-control);
    font-size: var(--t-sm);
    font-weight: var(--fw-label);
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    box-shadow: none;
}
html[data-theme="light"] .wq-rowact:hover:not(:disabled),
html[data-theme="light"] .rt-dueact:hover:not(:disabled),
html[data-theme="light"] .dt-preset:hover:not(:disabled),
html[data-theme="light"] .dp-preset:hover:not(:disabled),
html[data-theme="light"] .dt-more:hover,
html[data-theme="light"] .dp-more:hover,
html[data-theme="light"] .wq-counter-open:hover,
html[data-theme="light"] .cx-chip:hover:not(:disabled) {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
}
/* On the touch shells the row action is a real target; the coarse rung, not a re-typed 48. */
@media (max-width: 1279px) {
    html[data-theme="light"] .wq-rowact { min-height: var(--row-h-comfy); }
}

/* Work Queue: the "more below" cue and the counter-offer quote block. */
html[data-theme="light"] .wq-morecue-pill {
    color: var(--text-secondary);
    background: var(--surface-4);
    border-color: var(--border-default);
}
html[data-theme="light"] .wq-counter {
    background: var(--recess-a03);
    border-color: var(--border-default);
}
html[data-theme="light"] .wq-counter-label { color: var(--text-muted); }
html[data-theme="light"] .wq-daychip.own {
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}
/* Ledger rows on a drag target and the log-entry rail: violet marking a list item's ordinary state. */
html[data-theme="light"] .wq-logentry { border-left-color: var(--border-strong); }
html[data-theme="light"] .wq-tl-dot { background: var(--text-secondary); box-shadow: none; }
html[data-theme="light"] .wq-tl-row:not(:last-child)::before { background: var(--border-default); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   WORK QUEUE — the light structural prototype
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   This is the worked example the rest of the light theme gets mapped onto. Everything below is
   light-only: `html[data-theme="light"]` is (0,2,0) against the page sheet's (0,1,0), so it wins on
   specificity without !important, and dark cannot match it by construction.

   The page is dense and it is read on a bay tablet at arm's length. Every change below is either
   "replace a dark-mode device with a light-mode one" or "let the data have the room".
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. THE LEDGER COLUMN GETS A BOUNDARY ─────────────────────────────────────────────────────
   The fault that made the point: with glass removed, the list column had NO surface at all — it
   floated on the page while the record beside it was a bordered card. In dark it separated as a
   tint over near-black; here white-on-white has no edge, and nothing errors.

   A surface and one vertical keyline. Not a card: the ledger IS the page's left half. */
/* §5.4 above already gives `.lx-list` a bordered surface, and its reasoning is right: this column
   scrolls independently, and a scroll region with no edge has no visible start or end. So the
   ledger needs nothing further here.

   That argument applies just as much to the pane BESIDE it, which also scrolls independently — so
   the pane keeps its border too. An earlier draft of this file stripped it on the grounds that two
   bordered boxes side by side are "two cards arguing". That was wrong: they are not cards, they are
   two scroll regions, and each one needs to show where it begins. What the pane does lose is the
   shadow (it is not floating) and the violet scrollbar, which was the last saturated thing on the
   page. */
html[data-theme="light"] .wq-wrapper .lx-sheet.wq-pane {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    scrollbar-color: var(--keyline-strong) transparent;
}

html[data-theme="light"] .wq-wrapper .lx-sheet.wq-pane::-webkit-scrollbar-thumb {
    background: var(--keyline-strong);
}

/* ── 2. SECTION HEADINGS BECOME EYEBROWS ──────────────────────────────────────────────────────
   "Väntar på kund" was amber and "Pågår" was emerald. Colour as a section label is a dark device:
   on white it reads as a highlighter mark, and it spends the accent budget on something that is
   not an action and cannot be clicked. The status still lives on every row's own badge, which is
   where it belongs — a heading does not need to repeat it in hue.

   The sticky heading also loses its indigo wash and its blur, for a plain surface and a hairline. */
html[data-theme="light"] .wq-wrapper .lx-bucket .lx-bucket-name,
html[data-theme="light"] .wq-wrapper .lx-bucket.b-customer .lx-bucket-name,
html[data-theme="light"] .wq-wrapper .lx-bucket.b-us .lx-bucket-name,
html[data-theme="light"] .wq-wrapper .lx-bucket.b-progress .lx-bucket-name {
    /* SENTENCE CASE, 2026-08-10, superseding the tracked uppercase this block shipped with.
       This page was signed off BEFORE the sentence-case decision existed, and approving a page does
       not freeze a rule that had not been made yet. The measured evidence is one-sided: nine
       letter-spacing declarations in 3.6 MB of shipped stylesheet, not one of them on a section
       label, with the tracked uppercase header actively being retired. Swedish compounds suffer
       worst in caps. And a shared class meaning two different things on two pages is exactly the
       drift that made `.purple-gradient` violet in six sheets and emerald in a seventh — Finished
       Jobs now reads sentence case, so Work Queue had to follow or the app would disagree with
       itself about what a bucket heading is. */
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}

html[data-theme="light"] .wq-ledger.windowed .lx-bucket {
    background: var(--surface);
    border-bottom: 1px solid var(--keyline);
}

html[data-theme="light"] .wq-wrapper .lx-bucket::after {
    background: var(--keyline);
}

/* The bucket's hour sum was emerald. A number is not a status. */
html[data-theme="light"] .wq-wrapper .lx-bucket .lx-bucket-sum {
    color: var(--ink-muted);
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}

/* ── 3. THE STATUS FLAG BECOMES A BADGE WITH A GLYPH ──────────────────────────────────────────
   The most important change on this page, and the one that generalises furthest.

   `.lx-flag` is a 6px dot with a coloured glow. Both halves are dark devices: a glow needs darkness
   to read as emission, and a coloured dot is colour doing a job alone. Under deuteranopia the amber
   and the emerald dots collapse — roughly 1 in 12 mechanics, in a trade that skews male, unable to
   tell "waiting on the customer" from "being worked on".

   In light it becomes a 16px rounded square carrying a SHAPE-distinct glyph, and the grid's first
   column widens to match. The glyph is generated content, so it needs no markup change, cannot leak
   into dark, and stays out of the accessibility tree — the row's text keeps sole ownership of the
   accessible name. */
/* DELIBERATELY LEFT LIGHT-ONLY, 2026-08-11, after being proposed for both themes and rejected on
   evidence. The proposal was sound in principle: the deuteranopia argument above ("colour alone
   cannot tell 'waiting on the customer' from 'being worked on'") is a fact about eyes, not about
   themes, so dark's 6px hue-only dot fails 1-in-12 mechanics exactly as light's did.

   It cannot be executed as a GEOMETRY move, which is what this pass is allowed to do. Dark's flag
   fills are saturated and live unprefixed in the page sheet (`WorkQueueDesktop.css:294-297`:
   `--st-expected`, `--st-logged`, `--st-breach`). Light pairs its 16px badge with a per-state INK
   (`color:` on each `.f-*` rule below). Widening the box in both themes without that ink would put a
   `currentColor` glyph on a saturated square — an unreadable badge, and a worse outcome than the dot.
   Supplying the ink means changing dark's `color`, which is exactly what `compare.mjs` records.

   So this is a dark PAINT decision, not a width fork, and it needs an explicit re-baseline rather
   than being smuggled in under geometry parity. Recorded here so the next reader inherits the
   argument and the reason it stopped, rather than rediscovering both. */
html[data-theme="light"] .wq-wrapper .wq-row {
    grid-template-columns: 18px minmax(0, 22rem) auto 1fr 7rem;
}

html[data-theme="light"] .wq-wrapper .wq-row .lx-flag {
    width: 16px;
    height: 16px;
    border-radius: 4px;
    margin-top: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: none;
    font-size: 10px;
    font-weight: var(--fw-label);
    line-height: 1;
    border: 1px solid transparent;
}

html[data-theme="light"] .wq-wrapper .wq-row .lx-flag::before {
    content: "•";
}

/* Waiting on the customer: amber, with a bang. The amber stays — that is explicit instruction. */
html[data-theme="light"] .wq-wrapper .wq-row .lx-flag.f-customer {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}

html[data-theme="light"] .wq-wrapper .wq-row .lx-flag.f-customer::before {
    content: "!";
}

/* Ours, unstarted. A backlog item is not a status, so it earns no hue — only a boundary. */
html[data-theme="light"] .wq-wrapper .wq-row .lx-flag.f-us {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink-muted);
}

/* Being worked. */
html[data-theme="light"] .wq-wrapper .wq-row .lx-flag.f-progress {
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
    color: var(--st-logged);
}

html[data-theme="light"] .wq-wrapper .wq-row .lx-flag.f-progress::before {
    content: "▸";
}

/* Overdue outranks the bucket: it is a fact about the card, not about where it sits. */
html[data-theme="light"] .wq-wrapper .wq-row .lx-flag.f-breach,
html[data-theme="light"] .wq-wrapper .lx-flag.overdue {
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    color: var(--st-breach);
}

html[data-theme="light"] .wq-wrapper .wq-row .lx-flag.f-breach::before,
html[data-theme="light"] .wq-wrapper .lx-flag.overdue::before {
    content: "✕";
}

html[data-theme="light"] .wq-wrapper .lx-flag.waiting {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}

html[data-theme="light"] .wq-wrapper .lx-flag.waiting::before {
    content: "!";
}

/* ── 4. ROWS: A DIVIDED LIST, NOT A STACK OF CARDS ────────────────────────────────────────────
   The divider is the separation mechanism, so the hover must not introduce a second one. The violet
   wash and the inset accent bar both go: hover is a quiet fill. Selection keeps a bar, because
   selection genuinely needs a carrier that survives a glance down a long column. */
/* THE SIZE DECLARATIONS ARE GONE from this rule too, and this one was a fork INSIDE light as well as
   between the themes: `min-height: var(--row-h)` (40px) contradicted §5's own `--row-h-comfy` (48px)
   for the same class, 360 lines apart in one file, and `padding: var(--sp-2) var(--row-pad-x)` was
   already being beaten by WorkQueueDesktop.css — measured, the light row computes 12px/16px, not
   8px/16px, so that half of this declaration has never once applied. A row floor BELOW the shared
   ledger's is not a decision anyone made; it is the older number surviving.

   Nothing moves visually: the Work Queue row carries three lines and a meter and renders ~100px, so
   it has never met either floor. What changes is that the two themes now agree about what the floor
   IS, and the page sheet's padding is the only padding, for both. */
html[data-theme="light"] .wq-wrapper .lx-row {
    border-bottom: 1px solid var(--keyline);
}

html[data-theme="light"] .wq-wrapper .lx-row:hover {
    background: var(--surface-inset);
    box-shadow: none;
}

html[data-theme="light"] .wq-wrapper .lx-row:focus-visible {
    background: var(--surface-inset);
    box-shadow: inset 2px 0 0 var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

html[data-theme="light"] .wq-wrapper .lx-row.picked {
    background: var(--surface-inset);
    box-shadow: inset 2px 0 0 var(--accent);
}

/* ── 5. FIGURES ───────────────────────────────────────────────────────────────────────────────
   The money slot was emerald at weight 700. On this page it carries bay hours rather than money,
   and a duration is not a success state. Ink, tabular, one weight down. */
html[data-theme="light"] .wq-wrapper .lx-money {
    color: var(--ink);
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}

html[data-theme="light"] .wq-wrapper .lx-when,
html[data-theme="light"] .wq-wrapper .wq-band-figures,
html[data-theme="light"] .wq-wrapper .wq-band-pct {
    font-variant-numeric: tabular-nums;
}

/* ── 6. TAGS: RECTANGLES ──────────────────────────────────────────────────────────────────────
   --r-pill is for true pills. A finding chip in a dense ledger is a rectangle; a row of capsules
   reads as consumer-app and wastes width at both ends of every one. */
html[data-theme="light"] .wq-wrapper .wq-tag-lead {
    border-radius: 4px;
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-dim);
    font-weight: var(--fw-label);
}

/* ── 7. EMPTY STATES: A LINE, NOT A HERO ──────────────────────────────────────────────────────
   A centred block of muted type in the middle of an 1100px pane is a marketing device. In an ops
   tool an empty state is a fact, stated where the data would have been — and it should not reserve
   18rem of vertical space to say that nothing is there. */
html[data-theme="light"] .wq-wrapper .wq-pane .lx-empty {
    align-items: flex-start;
    justify-content: flex-start;
    text-align: left;
    min-height: 0;
    height: auto;
    margin: 0;
    padding: var(--sp-5) var(--row-pad-x);
}

html[data-theme="light"] .wq-wrapper .wq-pane .lx-empty svg {
    display: none;
}

html[data-theme="light"] .wq-wrapper .wq-pane-empty-head {
    font-size: var(--t-h2);
    font-weight: var(--fw-strong);
    color: var(--ink);
}

html[data-theme="light"] .wq-wrapper .wq-bucket-empty {
    color: var(--ink-muted);
    padding: var(--sp-2) var(--row-pad-x) var(--sp-3);
}

/* ── 8. THE CONTROL BAR: SORT BUTTONS ARE RECTANGLES TOO ──────────────────────────────────────
   The filter chips took the rectangle treatment from _base.css, but the SORT buttons did not — they
   are `.fc-sort-btn`, a different class in the same bar, so "Äldsta" was left as the one capsule on
   a line of rectangles. A control bar has to agree with itself. */
html[data-theme="light"] .fc-sort-btn {
    border-radius: var(--r-control);
    font-weight: var(--fw-label);
    color: var(--ink-muted);
}

html[data-theme="light"] .fc-sort-btn.on {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
}

html[data-theme="light"] .fc-sort-label {
    font-size: var(--eyebrow-size);
    text-transform: uppercase;
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}

/* ── 9. COMPOSITION: THE LEDGER'S MEASURE — MOVED OUT, 2026-08-11 ─────────────────────────────
   `--lx-col: 34rem` lived here, in a PAINT file, against `27rem` in the page sheet. That is the
   mechanism that made one component render at two widths: measured at 1900, the same `.lx-split`
   resolved `544 | 1220` in light and `432 | 1332` in dark. A width in a theme file forks the themes
   by construction, and it re-forks them the next time somebody edits the page sheet.

   The 34rem VALUE was right and survives — it now lives in `WorkQueueDesktop.css`, unprefixed,
   governing both themes, with the derivation that used to be here (22rem name cap + 7rem figure +
   the 18px badge column + the row's 16px padding + the gaps).

   The five-track row grid that stood here went with it, and it was carrying a real defect:
   `18px minmax(0,24rem) auto 1fr 7rem` resolved to `18 · 204 · 109 · 0 · 112`. Columns 4 and 5 held
   NOTHING — the page sheet places every child in tracks 1–3 — so 132px of a 523px row, a quarter of
   it, was reserved for children that do not exist, and the vehicle name paid for it. That is why the
   identity truncated to "2010 Volkswag…" inside a 544px ledger with 1220px of empty pane beside it.
   Both replacements are in the page sheet so the two themes cannot disagree about them again. */

/* ── 10. THE HEADER RULE ──────────────────────────────────────────────────────────────────────
   One hairline under the page header, so the board reads as beginning somewhere. Stripe separates
   its header from its content with exactly this and nothing else. */
html[data-theme="light"] .wq-wrapper .wq-header {
    border-bottom: 1px solid var(--keyline);
    padding-bottom: var(--sp-3);
}

/* ── 11. SECTION EYEBROWS INSIDE THE RECORD ───────────────────────────────────────────────────
   `.wq-eyebrow` is a label followed by a gradient hairline that fades to nothing. A gradient rule
   is a dark-theme device — it fades because on near-black a hard line reads as a scratch. On white
   a rule is just a rule, and a fading one looks like a rendering artefact. Flat keyline, and the
   label drops from weight 700 to the label rung. */
html[data-theme="light"] .wq-wrapper .wq-eyebrow > span {
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}

html[data-theme="light"] .wq-wrapper .wq-eyebrow > i {
    background: var(--keyline);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   WORK QUEUE — the 1% pass
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   A review pass against the rendered page. The mechanisms below are adopted wholesale; three of the
   review's proposed HEX values were rejected on measurement and the reasons are recorded inline, so
   nobody re-proposes them:

     - amber badge text #B54708 on #FFF8EB measures 5.14:1 against the shipping 6.37:1. More
       luminous, less legible. This is read at arm's length under glare.
     - muted ink #64748B measures 4.76:1 on white against the shipping #5D6B80 at 5.08:1 on canvas,
       so the "microcopy is too light" finding is already answered by a DARKER value than proposed.
     - amber solid fill #E39B0A measures 2.34:1 on white. A progress bar is a meaningful graphical
       object and owes 3:1 under SC 1.4.11, so that value fails as a fill. Solid fills use the
       status tokens, which are measured.

   The accent stays BLUE (#2f4fd0, hue 228). The review specifies Stripe's blurple #533afd, which is
   hue 253 — violet — and the standing rule is that everything violet becomes blue in light.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── 12. FORM SURFACES: A FIELD IS WHITE, OR IT READS AS DISABLED ─────────────────────────────
   The strongest point in the review. A grey fill on a white card is the universal signal for
   read-only, so a form of grey inputs reads as a form you cannot fill in. In dark the same fill is
   a lift — it is lighter than its surround — which is exactly the inversion this rebuild exists to
   catch: the same declaration means "raised" on one ground and "disabled" on the other.

   White fill, structural keyline, and an inset micro-shadow so the field still has a floor. The
   focus ring darkens the border and adds a 2px halo rather than glowing, because a glow on white is
   a smudge. */
html[data-theme="light"] .wq-wrapper input[type="text"],
html[data-theme="light"] .wq-wrapper input[type="number"],
html[data-theme="light"] .wq-wrapper input[type="search"],
html[data-theme="light"] .wq-wrapper input[type="tel"],
html[data-theme="light"] .wq-wrapper textarea,
html[data-theme="light"] .wq-wrapper select,
html[data-theme="light"] .wq-wrapper .modal-field input,
html[data-theme="light"] .wq-wrapper .modal-field textarea {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-control);
    box-shadow: var(--field-floor);
    color: var(--ink);
}

html[data-theme="light"] .wq-wrapper input:hover:not(:disabled),
html[data-theme="light"] .wq-wrapper textarea:hover:not(:disabled) {
    border-color: var(--keyline-strong);
}

html[data-theme="light"] .wq-wrapper input:focus,
html[data-theme="light"] .wq-wrapper input:focus-visible,
html[data-theme="light"] .wq-wrapper textarea:focus,
html[data-theme="light"] .wq-wrapper textarea:focus-visible,
html[data-theme="light"] .wq-wrapper select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--field-ring);
}

/* A field that IS disabled now has to say so, since grey no longer means it by default. */
html[data-theme="light"] .wq-wrapper input:disabled,
html[data-theme="light"] .wq-wrapper textarea:disabled {
    background: var(--surface-inset);
    color: var(--ink-muted);
    box-shadow: none;
}

/* ── 13. THE LAST OF THE PILLS ────────────────────────────────────────────────────────────────
   `.wq-pill` and the search box were still fully rounded. A capsule is consumer-app language and it
   costs width at both ends of every instance.

   DELIBERATE EXCEPTION, so it is not "fixed" later: the progress TRACKS (.wq-row-track,
   .wq-progress-track) keep --r-pill. They are 4-6px tall bars, where a full round IS the correct
   terminal shape — a 4px radius on a 4px-tall bar is a rectangle with damaged corners. The rule is
   "no capsules for controls", not "no round anywhere". */
html[data-theme="light"] .wq-pill,
html[data-theme="light"] .wq-seg-btn {
    border-radius: var(--r-control);
}

/* ── 14. THE SEARCH BAR ───────────────────────────────────────────────────────────────────────
   It was a grey floating capsule with no edge — the same "disabled" signal as the inputs above,
   applied to the control the page is most often driven from. */
html[data-theme="light"] .wq-wrapper .list-search {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: var(--field-floor);
}

html[data-theme="light"] .wq-wrapper .list-search:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--field-ring);
}

html[data-theme="light"] .wq-wrapper .list-search .ls-ic {
    color: var(--ink-muted);
}

/* ── 15. THE EMOJI TRAP ───────────────────────────────────────────────────────────────────────
   `WorkQueueDesktop.vue:213` renders a raw 🔒 next to "TID SOM FAKTURERAS". An OS emoji renders as
   a different picture on Windows, macOS, Android and iOS — full-colour on three of them — so one
   data row is drawn by the operating system rather than by this design system.

   The markup cannot be touched: that file is shared with dark, and editing it would move dark. So
   the glyph is suppressed and replaced with a monochrome mask that inherits `currentColor`, which
   keeps it exactly the colour of the text it sits beside. Light-only, no markup change, no network
   request.

   The `font-size: 0` is what hides the emoji; the mask is drawn on the ::after box. */
html[data-theme="light"] .wq-wrapper .wq-bf-lock {
    font-size: 0;
    display: inline-flex;
    align-items: center;
}

html[data-theme="light"] .wq-wrapper .wq-bf-lock::after {
    content: "";
    width: 12px;
    height: 12px;
    background-color: currentColor;
    -webkit-mask-image: var(--icon-lock);
    mask-image: var(--icon-lock);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: contain;
    mask-size: contain;
}

/* ── 16. THE TIMELINE ─────────────────────────────────────────────────────────────────────────
   The dots were solid violet with a violet glow ring — a dark device twice over. A ring made of the
   SURFACE colour reads as engineered rather than drawn: the dot punches through the connector line
   instead of sitting on top of it.

   The connector drops to the keyline. A timeline's line is a guide, not a wall. */
html[data-theme="light"] .wq-wrapper .wq-tl-dot {
    background: var(--ink-dim);
    box-shadow: 0 0 0 2px var(--surface);
}

html[data-theme="light"] .wq-wrapper .wq-tl-row:not(:last-child)::before {
    background: var(--keyline-strong);
}

/* ── 17. THE STICKY ACTION BAR ────────────────────────────────────────────────────────────────
   The footer needs to be a footer: a rule above it, so the actions read as belonging to the record
   rather than floating after it.

   The secondary action stops being blue. Two blue things side by side is two primaries; the text
   link goes to ink and picks up the accent only on hover, which is where a link's colour actually
   earns its keep. */
html[data-theme="light"] .wq-wrapper .wq-detail-actions {
    border-top: 1px solid var(--keyline);
    padding-top: var(--sp-3);
}

html[data-theme="light"] .wq-wrapper .wq-detail-actions .wq-act:not(.primary) {
    background: none;
    border-color: transparent;
    color: var(--ink-dim);
}

html[data-theme="light"] .wq-wrapper .wq-detail-actions .wq-act:not(.primary):hover {
    color: var(--accent);
    background: none;
}

html[data-theme="light"] .wq-wrapper .wq-act.primary {
    border-radius: var(--r-control);
    padding: 8px 16px;
    font-weight: var(--fw-label);
}

/* ── 18. THE NOTIFICATION DOT — MOVED OUT ─────────────────────────────────────────────────────
   `.ntf-badge` belongs to NotificationCenter, which MainLayout mounts on every route, so it was
   never a boards-cluster surface — and the split left the badge's geometry in _base.css and its
   ring here, two files for one 18px object. The ring is now folded into the _base.css rule (§A7/A9,
   "the counts keep their hue"), with the order-tie audit written up there. */

/* ── 19. DATA LABELS ──────────────────────────────────────────────────────────────────────────
   The field labels above inputs are eyebrows and should be sized like them. The review asks for
   weight 600; they stay on the label rung, because 600 is reserved for ONE element per container
   (here, the value the label describes) and a form of six 600-weight labels spends that budget on
   the captions instead of on the data. */
html[data-theme="light"] .wq-wrapper .wq-bf-label,
html[data-theme="light"] .wq-wrapper .modal-field > label {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* The value the label describes IS the one 600 in that block. */
html[data-theme="light"] .wq-wrapper .wq-bf-val {
    font-weight: var(--fw-strong);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   WORK QUEUE — the 1% pass, second round (against the opened record)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The first round was written against the list view, which meant half the review's findings were
   about a pane I had not photographed. These are the ones only visible with a job open. */

/* ── 20. THE STICKY FOOTER BAND ───────────────────────────────────────────────────────────────
   `.wq-detail-foot` is a sticky bar over a tinted scrim. In dark the tint is what separates it from
   the record scrolling underneath; in light it renders as a pale blue slab across the bottom of the
   pane, which reads as a selected state rather than as chrome.

   Surface plus a rule. The rule is the separation — that is the whole premise of this theme. */
html[data-theme="light"] .wq-wrapper .wq-detail-foot {
    background: var(--surface);
    border-top: 1px solid var(--keyline);
}

/* ── 21. THE CLOSING ACTION ───────────────────────────────────────────────────────────────────
   56px tall and 200px wide is a bay-tablet floor and it is correct THERE — a gloved hand needs
   15mm. On a mouse-driven desktop it is a billboard. The height comes down to the comfortable rung
   on pointer-fine devices only, so the tablet keeps its target.

   Radius to the control rung, weight to the label rung. */
@media (pointer: fine) {
    html[data-theme="light"] .wq-wrapper .wq-detail-actions .wq-act.big {
        min-height: var(--row-h-comfy);
        min-width: 0;
        padding: 0 var(--sp-5);
        font-size: var(--t-sm);
    }
}

html[data-theme="light"] .wq-wrapper .wq-act.big,
html[data-theme="light"] .wq-wrapper .wq-act.primary,
html[data-theme="light"] .wq-wrapper .wq-act.claim {
    border-radius: var(--r-control);
    font-weight: var(--fw-label);
}

/* "Öppna bilens jobb" sits beside the primary as a blue underlined link — two blue things in one
   footer is two primaries. It goes to ink and picks the accent up on hover, which is where a
   link's colour actually does work. */
html[data-theme="light"] .wq-wrapper .wq-detail-foot a,
html[data-theme="light"] .wq-wrapper .wq-detail-actions a {
    color: var(--ink-dim);
    text-decoration: none;
    font-weight: var(--fw-label);
}

html[data-theme="light"] .wq-wrapper .wq-detail-foot a:hover,
html[data-theme="light"] .wq-wrapper .wq-detail-actions a:hover {
    color: var(--accent);
    text-decoration: underline;
}

/* ── 22. THE PROGRESS METER ───────────────────────────────────────────────────────────────────
   Every fill is a two-stop gradient built from the raw palette — a dark device: on near-black a
   gradient reads as a lit bar. On white it reads as a smear, and the behind/overdue variants land
   as dirty mustard and washed pink.

   Flat status fills. The review proposed #E39B0A for the amber; it measures 2.34:1 on white and a
   progress meter is a meaningful graphical object owing 3:1 under SC 1.4.11, so the measured status
   token is used instead. The track goes to the inset rung so the unfilled portion is a recess
   rather than a grey bar. */
html[data-theme="light"] .wq-wrapper .wq-progress-track,
html[data-theme="light"] .wq-wrapper .wq-row-track {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .wq-wrapper .wq-progress-track i,
html[data-theme="light"] .wq-wrapper .wq-row-track b {
    background: var(--st-logged);
}

html[data-theme="light"] .wq-wrapper .wq-progress-track i.done {
    background: var(--st-logged);
}

html[data-theme="light"] .wq-wrapper .wq-progress-track i.behind {
    background: var(--st-expected);
}

html[data-theme="light"] .wq-wrapper .wq-progress-track i.overdue {
    background: var(--st-breach);
}

/* ── 23. THE PACE BADGE ───────────────────────────────────────────────────────────────────────
   "1H 30M EFTER PLAN" was a filled amber capsule. It becomes the badge recipe: soft fill, tint
   border, dark ink, rectangle. Same treatment as every other status in the app, which is the point
   — a mechanic should not have to learn two badge shapes. */
/* NOT `.wq-band-pace` — that is the flex ROW the badge sits in, and painting it amber put a band
   across the whole line. Caught on the second screenshot. The badge is `.wq-act.pace`. */
html[data-theme="light"] .wq-wrapper .wq-act.pace,
html[data-theme="light"] .wq-wrapper .wq-act.pace.unset {
    border-radius: 4px;
    background: var(--st-expected-soft);
    border: 1px solid var(--st-expected-line);
    color: var(--st-expected);
    font-weight: var(--fw-label);
}

/* ── 24. THE FREE-TEXT NOTE ───────────────────────────────────────────────────────────────────
   The note box at the bottom of the record kept a tinted fill, so it read as a quote block rather
   than as an editable field. Same rule as every other input: white, keyline, inset floor. */
html[data-theme="light"] .wq-wrapper .wq-note,
html[data-theme="light"] .wq-wrapper .wq-detail-note,
html[data-theme="light"] .wq-wrapper .wq-freetext {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-control);
    box-shadow: var(--field-floor);
    color: var(--ink-dim);
}

/* ── 25. THE WORK LOG ROW ─────────────────────────────────────────────────────────────────────
   The logged-hours row carried a pale blue band with every word at one weight, so the figure — the
   only thing on the row anybody scans for — had no anchor. The band goes to the inset rung (it is a
   recess in the record, not a highlight) and the figure takes the one strong weight in the block. */
html[data-theme="light"] .wq-wrapper .wq-logreceipt {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    border-radius: var(--r-control);
}

html[data-theme="light"] .wq-wrapper .wq-logreceipt b,
html[data-theme="light"] .wq-wrapper .wq-logreceipt strong {
    font-weight: var(--fw-strong);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* ── 26. THE CONTEXT RAIL ─────────────────────────────────────────────────────────────────────
   The collapsed rail down the right edge was still a violet-indigo column — the last saturated
   surface on the page, and the one furthest from the work. It is chrome: surface, keyline, muted
   glyphs. It earns colour when it is opened, not while it is a 60px strip. */
html[data-theme="light"] .crail {
    background: var(--surface);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .crail:hover,
html[data-theme="light"] .crail:focus-within {
    background: var(--surface);
    border-color: var(--keyline-strong);
    box-shadow: var(--elevation-3);
}

/* ── 27. THE RECORD HEADING ───────────────────────────────────────────────────────────────────
   The record's status ("Pågår") is still a coloured dot plus coloured text at the top right. It
   takes the badge treatment like every other status on the page. */
/* Geometry only on the base — `.lx-state` has s-attention / s-ready variants that already carry the
   right soft fill and tint border. What they do NOT have is light-legible INK: both spend a pale
   dark-theme hue (--c-emerald-a7f3d0, --c-amber-fde68a) that measures under 2:1 on its own fill. So
   the shape is set once here and each variant gets its ink corrected, rather than one blanket rule
   flattening a distinction the page is making on purpose. */
html[data-theme="light"] .wq-wrapper .lx-state {
    border-radius: 4px;
    padding: 2px 6px;
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
}

html[data-theme="light"] .wq-wrapper .lx-state.s-ready {
    color: var(--st-logged);
}

html[data-theme="light"] .wq-wrapper .lx-state.s-attention {
    color: var(--st-expected);
}

/* ── 28. THE METERS TAKE BAR-WEIGHT STATUS ────────────────────────────────────────────────────
   Overrides §22 above, which reused the status TEXT tokens and produced an olive-brown bar. See the
   --meter-* block in app.css for the measurement. */
html[data-theme="light"] .wq-wrapper .wq-progress-track i,
html[data-theme="light"] .wq-wrapper .wq-row-track b,
html[data-theme="light"] .wq-wrapper .wq-progress-track i.done {
    background: var(--meter-ok);
}

html[data-theme="light"] .wq-wrapper .wq-progress-track i.behind {
    background: var(--meter-warn);
}

html[data-theme="light"] .wq-wrapper .wq-progress-track i.overdue {
    background: var(--meter-bad);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   WORK QUEUE — round 3: the amber verdict, the second primary, the timeline
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── 29. THE PACE VERDICT STOPS SPENDING A HUE ────────────────────────────────────────────────
   "1h 30m efter plan" appears on very nearly every card, and a colour that is always on screen has
   stopped carrying information. Stripe's own position is that restricting colour is an accessibility
   measure rather than a taste — so the common case goes neutral and the hue is saved for the case
   that is actually wrong.

   There is also no good dark amber, and that is a fact about the hue rather than a failure to find
   the right one: to clear 3:1 on a light track an amber has to be darkened, and a darkened yellow
   IS mustard. The previous value sat at hue 35, squarely in the yellow-brown band.

   So: BEHIND is a neutral slate badge at 9.45:1. OVERDUE keeps red — it is the one that means
   something is wrong. AHEAD keeps green. The warning tier is preserved; it just no longer fires on
   the ordinary case.

   This overrides the "keep the amber" instruction from the original brief, on the user's explicit
   later call. Recorded here so it is not restored by someone reading only the older document. */
html[data-theme="light"] .wq-wrapper .wq-pchip.behind {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink-dim);
}

html[data-theme="light"] .wq-wrapper .wq-pchip.ontrack {
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-muted);
}

html[data-theme="light"] .wq-wrapper .wq-pchip.overdue {
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    color: var(--st-breach);
}

html[data-theme="light"] .wq-wrapper .wq-pchip.ahead {
    color: var(--st-logged);
}

/* Rectangle, not capsule — the round-2 rule guessed the wrong class and this is the real one. */
html[data-theme="light"] .wq-wrapper .wq-pchip,
html[data-theme="light"] .wq-wrapper .wq-pcfg-btn {
    border-radius: 4px;
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
}

html[data-theme="light"] .wq-wrapper .wq-pcfg-btn,
html[data-theme="light"] .wq-wrapper .wq-band-takt.unset {
    color: var(--ink-muted);
}

/* ── 30. ONE PRIMARY PER PANE ─────────────────────────────────────────────────────────────────
   "Logga" and "Markera klar" were both filled blue. Two primaries is no primary — and they are not
   peers: logging an hour is routine and repeated, closing out a job is terminal. Recolouring the
   button would only have moved the problem, so the hierarchy is what changes.

   `.btn-astra` is the app's shared primary, so this is scoped to the log form rather than to the
   class — the same button elsewhere is genuinely primary. */
html[data-theme="light"] .wq-wrapper .wq-logform .wq-wp-btn,
html[data-theme="light"] .wq-wrapper .wq-logform .btn-astra {
    background: var(--surface);
    background-image: none;
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    box-shadow: none;
    font-weight: var(--fw-label);
    border-radius: var(--r-control);
}

html[data-theme="light"] .wq-wrapper .wq-logform .wq-wp-btn:hover:not(:disabled),
html[data-theme="light"] .wq-wrapper .wq-logform .btn-astra:hover:not(:disabled) {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--accent);
}

/* "Öppna bilens jobb" is a <button> styled as a link, which is why the round-2 rule keyed on `a`
   never matched it. It leaves the card rather than acting on it, so it stays a link — but a quiet
   one, in ink, taking the accent only on hover. */
html[data-theme="light"] .wq-wrapper .wq-openjob-link {
    color: var(--ink-dim);
    text-decoration: none;
    font-weight: var(--fw-label);
}

html[data-theme="light"] .wq-wrapper .wq-openjob-link:hover:not(:disabled) {
    color: var(--accent);
    text-decoration: underline;
}

/* ── 31. THE RECORD'S STATE BADGE ─────────────────────────────────────────────────────────────
   `.wq-state-badge`, not `.lx-state` — the round-2 guess was the ledger row's class, not the
   record head's. Same badge grammar as everywhere else: rectangle, soft fill, tint border, dark ink.
   The `s-` variants carry the meaning, so only the ink is corrected per state. */
html[data-theme="light"] .wq-wrapper .wq-state-badge {
    border-radius: 4px;
    padding: 2px 8px;
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    border: 1px solid var(--keyline);
    background: var(--surface-inset);
    color: var(--ink-dim);
}

/* The variant names are the REAL state enum, not a plausible-sounding one. An earlier version of
   this block invented `.s-progress`, `.s-customer`, `.s-ready`, `.s-attention`, `.s-breach` and
   `.s-overdue`; the class is bound as `s-${card.state}` (WorkQueueDesktop.vue:175 and :560) and
   WorkQueueState is exactly awaiting_approval | approved | in_progress | awaiting_pickup |
   unreachable | done | declined. So all six selectors were dead: the record head rendered neutral
   for every state and `declined` never went red, with nothing to see in the source and nothing to
   fail in a build.

   `.s-ready` and `.s-attention` do exist in this app — on `.lx-state`, a different element, mapped
   correctly further up this file. That near-miss is exactly what made the wrong names look right.

   Only two states are good news and two are wrong. The other three — awaiting_approval,
   in_progress, awaiting_pickup — are the ordinary case and keep the neutral base above, per the
   verdict rule: a hue that fires on the common state stops carrying information. */
html[data-theme="light"] .wq-wrapper .wq-state-badge.s-approved,
html[data-theme="light"] .wq-wrapper .wq-state-badge.s-done {
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
    color: var(--st-logged);
}

html[data-theme="light"] .wq-wrapper .wq-state-badge.s-declined,
html[data-theme="light"] .wq-wrapper .wq-state-badge.s-unreachable {
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    color: var(--st-breach);
}

/* ── 32. THE TIMELINE BECOMES A SEQUENCE ──────────────────────────────────────────────────────
   Eight-pixel solid discs read as a column of identical blobs: every event weighted the same, so
   the eye has nowhere to land and the list has no head.

   Hollow for what has happened, filled for where the job stands now. The ring is drawn with an
   inset box-shadow rather than a border so the dot's box does not change size and the connector
   still meets it exactly. The connector drops to a keyline and stops at the dot's edge — a
   timeline's line is a guide, not a wall. */
html[data-theme="light"] .wq-wrapper .wq-tl-dot {
    width: 6px;
    height: 6px;
    background: var(--surface);
    box-shadow: inset 0 0 0 1.5px var(--keyline-strong);
}

/* The most recent event is the last row, and it is the only filled dot on the column. */
html[data-theme="light"] .wq-wrapper .wq-tl-row:last-child .wq-tl-dot {
    background: var(--ink);
    box-shadow: none;
}

html[data-theme="light"] .wq-wrapper .wq-tl-row:not(:last-child)::before {
    background: var(--keyline);
    left: 6px;
}

/* ── 33. THE METER'S WARN FILL ────────────────────────────────────────────────────────────────
   Hue 35 (yellow-brown) to hue 17 (true orange) — see the --meter-* block in app.css. The bar keeps
   a hue where the badge no longer does, because a 6px bar cannot carry a word and colour is the
   only channel it has. */
html[data-theme="light"] .wq-wrapper .wq-progress-track i.behind {
    background: var(--meter-warn);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   ROUTINES — the compliance sweep (2026-08-10)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   /routines was the least-covered board in this cluster: §1–§5 above reached its chips, its rows and
   its two hero verbs, and stopped. Everything the ledger says about a routine below the row — the
   bucket headings, the season and tag chips, the `Byt` press, the record's fact band, the visit
   history, the add sheet's foot — was still the dark design repainted.

   Nothing below is a new decision. Every rule maps onto a recipe already written in _base.css or
   already worked out on Work Queue, and each block names which one.

   CLASS NAMES VERIFIED, not guessed. The runtime-assembled ones and their real enums:
     .rt-bucket    + `it.bucket.tone`   -> overdue | now | plain        (RoutinesDesktop.vue:206, :2092)
     .rt-row       + `r.dueStatus`      -> overdue | soon | ok | none   (:220, models/routine.ts:22)
     .rt-season    + `r.season`         -> winter | summer              (:256)
     .rt-due       + `dueBadge().cls`   -> overdue | soon | ok | none   (:765, :2573)
     .rt-job-state + `jobPhase(r)`      -> booked | awaiting | collected | canceled | no_show (:928, :3215)
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── R1. THE BUCKET HEADING STOPS BEING A COLOUR ──────────────────────────────────────────────
   RULE 1. "FÖRSENAT" prints in --c-red-fca5a5 and "NU" in --c-indigo-c4b5fd, and the count beside
   each takes the same hue (RoutinesDesktop.css:555-556). On near-black those are two tints; on white
   the ledger opens with a red word and a violet word before a single row of data — and the fact is
   already carried by the rows underneath, each of which has its own flag and its own date.

   The count and the "5 bokade" tally go muted with it. .rt-bucket-booked was --st-logged: a number is
   not a status, and "how many of these need nobody" is a fact about the section. */
/* SENTENCE CASE, superseding the uppercase this block shipped with. The measured stylesheet contains
   nine letter-spacing declarations in 3.6 MB and not one is on a section label; the tracked uppercase
   eyebrow is the idiom being retired, not the target.

   Corrected HERE rather than overridden later, and that is the point of this edit. A `.rt-wrapper`-
   scoped override appended at the end of the file scores (0,3,1) and loses to the `.rt-bucket.overdue`
   branch above at (0,4,1) — which is exactly why "Försenat" kept shouting while every month beside it
   went quiet. The months only match the 3-class branch and tie, so source order settled them; the
   overdue bucket matches the 4-class branch and never consulted source order at all. Two labels, one
   class, two different outcomes, from a `.overdue` modifier in the selector. */
html[data-theme="light"] .rt-bucket .rt-bucket-name,
html[data-theme="light"] .rt-bucket.overdue .rt-bucket-name,
html[data-theme="light"] .rt-bucket.now .rt-bucket-name {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}

html[data-theme="light"] .rt-bucket .rt-bucket-n,
html[data-theme="light"] .rt-bucket.overdue .rt-bucket-n,
html[data-theme="light"] .rt-bucket.now .rt-bucket-n,
html[data-theme="light"] .rt-bucket .rt-bucket-booked {
    color: var(--ink-muted);
    font-weight: var(--fw-label);
}

/* The heading's trailing rule and the closed bucket's own hairline are furniture — rgba(--sf-rgb,
   .09) is dark ink at 9% on white, i.e. very nearly nothing. Both go to the real keyline. */
html[data-theme="light"] .rt-bucket::after { background: var(--keyline); }
html[data-theme="light"] .rt-bucket.closed { border-bottom-color: var(--keyline); }

/* ── R2. THE OTHER LABEL TIERS ────────────────────────────────────────────────────────────────
   RULE 1, continued. Four more label voices on this page carry a hue: the rail's section heads and
   the record's zone heads in --astra-accent-soft, the .rt-mini-label tier and the fact band's keys in
   --c-indigo-c4b5fd. All four are already 11px uppercase tracked — they are eyebrows wearing paint.
   Only the colour changes.

   .rt-deleted-toggle is in the list because it is the same tier (an uppercase caption that happens to
   be pressable), and .rt-add-total span / .rt-edf span because a form caption is an eyebrow. */
html[data-theme="light"] .rt-rail-head,
html[data-theme="light"] .rtx-zone-head,
html[data-theme="light"] .rt-mini-label,
html[data-theme="light"] .rt-fact-k,
html[data-theme="light"] .rt-detail-meta > div > span,
html[data-theme="light"] .rt-add-total span,
html[data-theme="light"] .rt-edf span,
html[data-theme="light"] .rt-deleted-toggle {
    color: var(--ink-muted);
    letter-spacing: var(--eyebrow-track);
}

/* ── R3. THE CAPSULES ─────────────────────────────────────────────────────────────────────────
   RULE 2. Eight --r-pill objects on this page are chips and badges, not true pills: the chip count,
   the "Bokad" chip, the shop's own tags, the season chip, the record's due badge, the visit's phase
   badge, the visit count and the "more below" cue. A row can carry a season chip, a tag, a "+2" and a
   Bokad chip at once — four lozenges on one line is what makes a dense ledger read as a toy.

   DELIBERATE EXCEPTIONS, listed so they are not "fixed" later, all of them terminal shapes on objects
   2-10px in one dimension where a 4px radius would be a rectangle with damaged corners:
   .rt-strip-run (the 5px season bar), .rt-strip-today (a 2px cap), .rt-row-flag (a 6px dot) and
   .rt-tagmgr-dot (a 10px colour swatch). Same carve-out as the Work Queue tracks in §13. */
html[data-theme="light"] .rt-chip-n,
html[data-theme="light"] .rt-booked-chip,
html[data-theme="light"] .rt-tag,
html[data-theme="light"] .rt-season,
html[data-theme="light"] .rt-due,
html[data-theme="light"] .rt-job-state,
html[data-theme="light"] .rt-jobs-count,
html[data-theme="light"] .rt-more-pill {
    border-radius: 4px;
}

/* ── R4. `Byt` STOPS WEARING A STATUS ─────────────────────────────────────────────────────────
   RULE 3, and the loudest instance in the cluster. .rt-act is the press that bills a customer, and it
   is painted from --ch-orange-f59e0b at .08 fill / .42 border, going red on an overdue row. That is a
   status hue on an ACTION: forty of them down a due-day ledger, each one restating in colour what the
   row's date column already says in words.

   An action is not a status. It takes the secondary shape from _base's ONE PRIMARY PER PANE recipe —
   the same shape .wq-rowact takes two hundred lines up, so the two boards' row actions are one object.
   The overdue row keeps its verdict: on the flag, on the date, and on the bucket it sits in. */
html[data-theme="light"] .rt-act,
html[data-theme="light"] .rt-row.overdue .rt-act {
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    font-weight: var(--fw-label);
    border-radius: var(--r-control);
    box-shadow: none;
}

html[data-theme="light"] .rt-act:hover,
html[data-theme="light"] .rt-row.overdue .rt-act:hover {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--accent);
}

html[data-theme="light"] .rt-act:active,
html[data-theme="light"] .rt-row.overdue .rt-act:active { background: var(--surface-inset); }

/* ── R5. THE ROW'S VERDICT ────────────────────────────────────────────────────────────────────
   RULE 7. `soon` is the ordinary state of a routine on a board a shop opens BECAUSE work is coming:
   it fires on most of the ledger, and a colour that is always on screen has stopped carrying
   information. `overdue` is the one that means something is actually wrong, and it keeps red.

   This supersedes §4 above, which routed .rt-row.soon to --st-info. That was the right call against
   the accent it replaced — the point there was that "due soon" must not wear the primary action's
   violet — and the wrong one against the verdict rule, which is that the ordinary case wears no hue
   at all. Later in the file at higher specificity, so it wins.

   The flag loses its glow with it (RULE 6): 0 0 8px of colour is emission, and emission needs
   darkness. On white a coloured halo is a printing fault. The overdue dot keeps its red and loses the
   halo; the soon dot goes to the strong keyline, which is a mark without being an alarm. */
html[data-theme="light"] .rt-row.soon .rt-row-when b { color: var(--ink-dim); }
html[data-theme="light"] .rt-row.soon .rt-row-when em { color: var(--ink-muted); }
html[data-theme="light"] .rt-row.soon .rt-row-flag {
    background: var(--keyline-strong);
    box-shadow: none;
}
html[data-theme="light"] .rt-row.overdue .rt-row-flag {
    background: var(--st-breach);
    box-shadow: none;
}
html[data-theme="light"] .rt-strip-today { box-shadow: none; }

/* The record's due badge is the same verdict in badge form. `ok` and `none` are the quiet majority of
   the book; `soon` is the ordinary case; only `overdue` is wrong. Badge grammar throughout: soft fill,
   tint border, dark ink, rectangle (R3 above already set the radius). */
html[data-theme="light"] .rt-due,
html[data-theme="light"] .rt-due.soon,
html[data-theme="light"] .rt-due.ok,
html[data-theme="light"] .rt-due.none {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .rt-due.overdue {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
}

/* ── R6. THE VISIT'S PHASE ────────────────────────────────────────────────────────────────────
   RULES 7 + 8. `booked` was amber ink on an amber fill, `awaiting` mint on mint, and all five variants
   carried a background with NO border — invisible on the record's white surface, and nothing errors.
   Booked, awaiting and collected are the three ordinary outcomes of a routine visit; a cancellation
   and a no-show are the two that are actually wrong. */
html[data-theme="light"] .rt-job-state,
html[data-theme="light"] .rt-job-state.booked,
html[data-theme="light"] .rt-job-state.awaiting,
html[data-theme="light"] .rt-job-state.collected {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .rt-job-state.canceled,
html[data-theme="light"] .rt-job-state.no_show {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
}

/* ── R7. THE CHIPS THAT ARE FACTS ABOUT A ROW ─────────────────────────────────────────────────
   RULE 8. .rt-chip-n and .rt-jobs-count are --surface-2 with no border, which in light is #fcfcfe on
   white — the element is simply absent. Same fix as every other borderless fill in this theme: the
   inset rung, plus the hairline that gives it an edge.

   .rt-booked-chip keeps its green: "the car is already coming in" is the one piece of GOOD news the
   action column ever states, it is not the common case, and it is what stops a mechanic ringing a
   customer who has already booked. It only needed the rectangle R3 gave it. */
html[data-theme="light"] .rt-chip-n,
html[data-theme="light"] .rt-jobs-count {
    color: var(--ink-muted);
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

/* The season chip's fills are the CVD-validated categorical pair and stay (see escalation 4 at the
   head of this file — a --cat-* mapping is not a page decision). What it does not get to keep is a
   fill with no boundary once the radius is square. */
html[data-theme="light"] .rt-season.winter,
html[data-theme="light"] .rt-season.summer { border-color: var(--keyline); }

/* ── R8. THE SEGMENTED CONTROLS AND THE RAIL ──────────────────────────────────────────────────
   RULE 8, and the §1 selection treatment finishing its own list. .rt-seg button.active and
   .rt-rail-modes button.active are rgba(--ch-violet-7c3aed, .26) with no border — a lavender slab with
   no edge. Both are "one of N, exactly one on", which is the segmented case §5.5 settled on the
   storage board: the inset rung, filled, inside a bordered group.

   The rail itself is rgba(--sf-rgb, .022) on rgba(--sf-rgb, .055) — 2% ink over #f6f8fa. It is a
   region beside the ledger and it needs a real surface and a real edge. */
html[data-theme="light"] .rt-seg {
    background: var(--surface);
    border-color: var(--keyline-strong);
    border-radius: var(--r-control);
}

html[data-theme="light"] .rt-seg button.active,
html[data-theme="light"] .rt-rail-modes button.active {
    color: var(--ink);
    background: var(--surface-inset);
    box-shadow: none;
}

html[data-theme="light"] .rt-rail,
html[data-theme="light"] .rt-board.rail-open .rt-rail,
html[data-theme="light"] .rt-rail:focus-within {
    background: var(--surface);
    border-color: var(--keyline);
}

/* A facet that is filtering is a SELECTION, so it takes the §1 treatment rather than a violet wash. */
html[data-theme="light"] .rt-facet.on .ssel-btn {
    color: var(--ink);
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}

/* ── R9. THE GRADIENTS ────────────────────────────────────────────────────────────────────────
   RULE 5. Three on this page, and each one is a dark device rather than a decoration:

   · .rt-fbtn-primary — the record's terminal press, a 135° violet gradient under a violet bloom. It
     is genuinely the one primary on that floor (RULE 4 is satisfied: .rt-fbtn beside it is already a
     ghost), so it stays filled — flat, in the accent, with no halo. RULE 6 takes the glow.
   · .rt-toast.ok — a mint gradient with --c-emerald-04231a ink, a token with no light value at all,
     so the toast rendered near-black-on-mint. Badge grammar instead.
   · .rt-more — the "there is more below" scrim, built from --ch-blue-0f1119. The FADE is the mechanism
     (it sits over the last line of content rather than clipping it), so it keeps two stops — but both
     are tokens, and it fades to the sheet's own surface instead of to a blue slab. */
html[data-theme="light"] .rt-fbtn-primary {
    background: var(--accent);
    background-image: none;
    border: 1px solid var(--accent);
    color: var(--text-on-accent);
    box-shadow: none;
}

html[data-theme="light"] .rt-fbtn-primary:hover:not(:disabled) {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    box-shadow: none;
}

html[data-theme="light"] .rt-toast.ok {
    color: var(--st-logged);
    background: var(--st-logged-soft);
    background-image: none;
    border: 1px solid var(--st-logged-line);
    box-shadow: var(--elevation-3);
}

html[data-theme="light"] .rt-toast.error {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    box-shadow: var(--elevation-3);
}

html[data-theme="light"] .rt-more {
    background: linear-gradient(to bottom, transparent, var(--surface));
}

html[data-theme="light"] .rt-more-pill {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

/* ── R10. THE HAIRLINES ───────────────────────────────────────────────────────────────────────
   RULE 5's second clause and RULE 8's cousin. This page draws no boxes — its whole structure is
   hairlines — and every one of them is rgba(--sf-rgb, .045-.09), i.e. dark ink at under a tenth over
   an off-white canvas. That is the light theme's silent failure: the page loses its structure and
   nothing throws. One flat keyline for all of them.

   .rt-fact keeps a vertical because the fact band's cells are separated by one; it just becomes a line
   you can see. */
html[data-theme="light"] .rt-hero-cells,
html[data-theme="light"] .rt-detail-meta,
html[data-theme="light"] .rt-tagfield,
html[data-theme="light"] .rt-summary,
html[data-theme="light"] .rt-auto,
html[data-theme="light"] .rt-jobs,
html[data-theme="light"] .rt-history,
html[data-theme="light"] .rt-deleted,
html[data-theme="light"] .rt-qtotal,
html[data-theme="light"] .rtx-zones > .rtx-zone + .rtx-zone { border-top-color: var(--keyline); }

html[data-theme="light"] .rt-facts,
html[data-theme="light"] .rt-auto-line,
html[data-theme="light"] .rt-hentry,
html[data-theme="light"] .rt-confirm-car,
html[data-theme="light"] .rt-search-result { border-bottom-color: var(--keyline); }

html[data-theme="light"] .rt-fact { border-left-color: var(--keyline); }
html[data-theme="light"] .rtx-hl { border-bottom-color: var(--keyline); }

/* ── R11. THE BORDERLESS AND NEAR-BORDERLESS BLOCKS ───────────────────────────────────────────
   RULE 8. Six blocks inside the record are rgba(--sf-rgb, .028-.045) on rgba(--sf-rgb, .07) — a fill
   and an edge that both composite to nothing on a white sheet. They are recesses inside the card, so
   they take the inset rung and a real keyline.

   .rt-batch is the exception in shape: it is the one FLOATING object the page has, sticky over the
   rows it is about to bill, so it keeps the card surface and gains a contact shadow rather than the
   violet-bordered slab it was. */
html[data-theme="light"] .rt-plan,
html[data-theme="light"] .rt-quote,
html[data-theme="light"] .rt-edln,
html[data-theme="light"] .rt-lockrow,
html[data-theme="light"] .rt-job,
html[data-theme="light"] .rt-filter {
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .rt-filter { color: var(--ink-dim); }

/* A statutory deadline against a shop's own interval is a CATEGORY, and it was marked in amber. There
   is no good dark amber — to clear 3:1 on white an amber has to be darkened, and a darkened yellow is
   mustard — and this is not the case that is wrong. Both leading edges become weight, not hue. */
html[data-theme="light"] .rt-plan { border-left-color: var(--keyline-strong); }
html[data-theme="light"] .rt-plan.deadline { border-left-color: var(--ink-dim); }

html[data-theme="light"] .rt-batch {
    background: var(--surface);
    border-color: var(--keyline);
    box-shadow: var(--elevation-3);
}

/* A bulk run in flight is a progress statement, not a warning. It was an orange fill on an orange
   border — RULE 7's "no good amber" applied to a message that fires on every bulk press. */
html[data-theme="light"] .rt-bulk-progress {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

/* Toggles that are ON are selections, not fills. Same treatment as every other applied state (§1). */
html[data-theme="light"] .rt-liftbox.on,
html[data-theme="light"] .rt-stillhere.on,
html[data-theme="light"] .rt-lockrow.on {
    color: var(--ink);
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}

/* The customer-search dropdown is --c-blue-1b1f2b, a raw palette entry with NO light override in
   app.css — so in light it renders a near-black panel hanging under a white field. Surface, keyline,
   and the shared elevation, like every other floating list in the theme. */
html[data-theme="light"] .rt-search-dropdown {
    background: var(--surface);
    border-color: var(--keyline);
    box-shadow: var(--elevation-3);
}

/* ── R12. THE FIGURES ─────────────────────────────────────────────────────────────────────────
   RULE 1's last clause: a number is not a status. Eight money slots on this page print in
   --c-emerald-6ee7b7 or --c-indigo-ede9fe because "money is mint" is the app's dark habit. On a
   routine's card the figure is what the visit costs — it is neither a success nor an action. */
html[data-theme="light"] .rt-batch-sum,
html[data-theme="light"] .rt-fact.money .rt-fact-v,
html[data-theme="light"] .rt-fact.booked .rt-fact-v,
html[data-theme="light"] .rt-qtotal b,
html[data-theme="light"] .rt-edtotal b,
html[data-theme="light"] .rt-total strong,
html[data-theme="light"] .rt-add-total.set b,
html[data-theme="light"] .rt-hentry-prices .rt-hentry-total {
    color: var(--ink);
}

html[data-theme="light"] .rt-batch-missing { color: var(--ink-muted); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   COMPLAINTS — the compliance sweep (2026-08-10)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   §1–§5 above already reached this board's row, its list surface, its lanes, its chips, its bucket
   headings and its row-state words. What was left is the header (a red capsule and an accent count),
   the recovery clock's status edges, the record pane, and the three chip-shaped labels inside the
   record.

   CLASS NAMES VERIFIED:
     .cx-key.{band}       `:class="[k.band, …]"`      -> fresh | watch | over  (ComplaintsDesktop.vue:31, :274-277)
     .cx-hero-oldest.{b}  `:class="oldestBand"`       -> fresh | watch | over  (:23, :269; band() in complaintFormat.ts:21)
     .cx-row-when.{clock} `:class="clock"`            -> '' | fresh | watch | over  (ComplaintRow.vue:6, :35)
     .cx-row.sev-*        `` :class="`sev-${f.severity ?? 'none'}`" ``
     .cx-quote.sev-*      same template literal       -> sev-low | sev-medium | sev-high | sev-none
                                                         (ComplaintRow.vue:2, ComplaintRecord.vue:9,
                                                          ComplaintSeverity in models/serviceFeedback.ts:22)
   NOTE the fourth severity: `sev-none`, produced by the `?? 'none'` fallback. Neither sheet declares
   it, and it needs nothing — it correctly falls through to the transparent/neutral base.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── C1. THE PAGE EYEBROW IS A RED CAPSULE ────────────────────────────────────────────────────
   RULES 1 + 2 in one object. `.cx-eyebrow` is `--c-red-fca5a5` ink on an `--ch-red-f87171` fill and
   border, at `--r-pill` — the page opens with a red lozenge. §5.2 already muted its ink; the fill,
   the border and the shape are what is left. It labels the page; it does not report a fault. */
/* REVISED: half-measure retired. Neutralising the fill still left /complaints as the one page whose
   eyebrow is a chip while /vehicles' is bare text — the eyebrow rendered five different ways across
   six pages, four of them off `.badge` and this one off its own class. _base.css §1 now gives every
   `.badge` eyebrow bare text; `.cx-eyebrow` is the same element under a different name, so it takes
   the same treatment rather than a quieter version of the wrong one. It is a label, not a status.
   `padding` and `display` are written out because ComplaintsDesktop.css:44-49 sets both. */
html[data-theme="light"] .cx-eyebrow {
    display: inline-block;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}

/* The count inside the title was `--c-indigo-a78bfa` — the accent, spent on a number in an h1. */
html[data-theme="light"] .cx-title > span { color: var(--ink); }

/* ── C2. THE RECOVERY CLOCK'S BANDS ───────────────────────────────────────────────────────────
   RULE 7. The three keys are a legend AND a filter, and their 3px leading edges are green / amber /
   red. `fresh` is where most complaints sit and `watch` is the middle of the ordinary range; only
   `over` means the shop has missed its own recovery window. There is no good dark amber — a darkened
   yellow is mustard — so the middle band does not get one.

   The same three bands appear as the age of the worst one (`.cx-hero-oldest`) and as each row's age
   (`.cx-row-when`), which is three restatements of one clock. All three go the same way. */
html[data-theme="light"] .cx-key.fresh { border-left-color: var(--keyline-strong); }
html[data-theme="light"] .cx-key.watch { border-left-color: var(--ink-dim); }
html[data-theme="light"] .cx-key.over { border-left-color: var(--st-breach); }

html[data-theme="light"] .cx-hero-oldest.watch { color: var(--ink-dim); }
html[data-theme="light"] .cx-row-when.watch { color: var(--ink-dim); }

/* ── C3. SEVERITY ON A LEADING EDGE ───────────────────────────────────────────────────────────
   RULE 7 again. `medium` is the middle of a three-step scale a model assigns to every complaint, so
   it fires on a large share of the board — and it fired in `--c-orange-fbbf24`, the amber this theme
   does not have a good value for. `high` is the one that means something is actually wrong and keeps
   red. `low` and the `sev-none` fallback keep the neutral edge they already have, raised to a
   keyline so it is a line rather than a smudge. */
html[data-theme="light"] .cx-row.sev-medium:not(.picked) { border-left-color: var(--keyline-strong); }
html[data-theme="light"] .cx-quote.sev-medium { border-left-color: var(--keyline-strong); }
html[data-theme="light"] .cx-quote.sev-low { border-left-color: var(--keyline); }
html[data-theme="light"] .cx-quote { border-left-color: var(--keyline); }
html[data-theme="light"] .cx-quote.sev-high { border-left-color: var(--st-breach); }

/* ── C4. THE RECORD'S CHIP-SHAPED LABELS ──────────────────────────────────────────────────────
   RULES 2 + 8. `.cx-work` (what the visit was) and `.cx-tag` (category / severity words) are
   `--r-pill` capsules on `rgba(--sf-rgb, .05)` — 5% ink over a white pane, i.e. a fill with no
   substance under a rounded end. Rectangles on the inset rung with a real keyline. */
html[data-theme="light"] .cx-work,
html[data-theme="light"] .cx-tag {
    border-radius: 4px;
    background: var(--surface-inset);
    border-color: var(--keyline);
}

/* `.cx-tag.ok` keeps its green: "resolved" is genuinely good news and is not the common state of the
   board. It only needs the rectangle above. */

/* The resolution's date caption was `--st-logged` — RULE 1's last clause, a heading in a status hue.
   The green stays on the resolution's own leading rule, which is where it belongs. */
html[data-theme="light"] .cx-resolution-when { color: var(--ink-muted); }

/* ── C5. THE PREVIEW SWITCH ───────────────────────────────────────────────────────────────────
   RULE 2. The switch's TRACK and KNOB are true pills and stay — a 18px×34px toggle is the one shape
   in this theme where a full round is correct. The BUTTON wrapped around them is a control, so it
   takes the control rung like every other control in the bar. */
html[data-theme="light"] .cx-demo-toggle { border-radius: var(--r-control); }

/* ── C6. THE RECORD PANE ──────────────────────────────────────────────────────────────────────
   RULES 6 + 8. `.cx-pane` is `--surface-1` on an `rgba(--sf-rgb, .11)` border, under a shadow that
   ends in `inset 0 1px 0 rgba(--hl-rgb, .14)` — a lit top edge, which is a dark device: it exists so
   a near-black card catches light along its rim. On white it is a pale scratch across the top of the
   panel. Surface, keyline, and the shared contact shadow.

   This is the record beside a scrolling list, so it keeps its border for the reason §5.4 gives: two
   regions side by side both have to show where they begin. */
html[data-theme="light"] .cx-pane {
    background: var(--surface);
    border-color: var(--keyline);
    box-shadow: var(--elevation-2);
}

/* ── C7. THE HAIRLINES AND THE BUCKET STRIP ───────────────────────────────────────────────────
   RULE 5's second clause / RULE 8. Every rule on this page is `rgba(--sf-rgb, .055-.09)` — dark ink
   at under a tenth over white, which composites to nothing. The month heading is the same problem
   with a fill: `rgba(--sf-rgb, .035)` inside the list panel has no edge at all. */
html[data-theme="light"] .cx-bar,
html[data-theme="light"] .cx-sheet-head { border-bottom-color: var(--keyline); }

html[data-theme="light"] .cx-acts { border-top-color: var(--keyline); }

html[data-theme="light"] .cx-bucket {
    background: var(--surface-inset);
    border-bottom: 1px solid var(--keyline);
}

html[data-theme="light"] .cx-bucket:hover { background: var(--surface-hover); }

/* The clock key and the empty state are the last two fills without a boundary. */
html[data-theme="light"] .cx-key {
    background: var(--surface);
    border-color: var(--keyline);
}

html[data-theme="light"] .cx-empty {
    background: var(--surface);
    border-color: var(--keyline);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   DROP-OFFS — the compliance sweep, all three shells (2026-08-10)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   §1, §3, §5.2 and §5.6 already reached this cluster's chips, presets, "more" buttons, quick-log
   eyebrows and the tablet row's hover. What is left is the occupancy meter (a gradient on a bar),
   the week strip, the hero's two counts, and the amber that marks EVERY waiting car on all three
   shells.

   CLASS NAMES VERIFIED:
     .do-chip / .dt-chip / .dp-chip + `` `c-${v}` `` -> c-decide | c-held | c-ontrack | c-done |
        c-unset   (BOARD_VIEWS, useDropoffsBoard.ts:21; bound at DropoffsDesktop.vue:103,
        DropoffsTablet.vue:69, DropoffsPhone.vue:55)
     .do-hero-meter + `meterState`  -> ok | full | over   (useDropoffs.ts:104-108, bound at :48)
     .do-daychip + `dayChipState(i)` -> own | off | inherit  (useDropoffs.ts:863-866, bound at :445)
     .dt-group-note / .dp-group-note + `g.tone` -> breach | expected | '' (useDropoffsBoard.ts:146)
     .do-hero-stat + `{ breach: …, lit: …, on: … }` (DropoffsDesktop.vue:59, :64)

   DEAD SELECTORS FOUND — no rule written for any of them, because a rule on a class no markup
   produces is a silent no-op. `.do-arrival` / `.do-arrival-state` / `.do-arrival-action` (incl. the
   `.go` gradient and its glow), `.do-done-badge`, `.do-detail-progress`, `.do-logdel`, `.do-sort` /
   `.do-sort-btn` / `.do-sort-label`, `.do-penbtn`, `.do-infobtn`, `.do-detail-block`, `.do-tools`,
   `.do-loglist` / `.do-logentry*`, `.do-hm-row` / `.do-hm-field`, `.do-detail-meta`,
   `.do-danger-btn`, `.do-meta-*`. `.do-pmeter` survives only inside a COMMENT in
   `src/pages/dropoffs/sheetStyle.ts:173`. All are listed in the sweep note; three of them
   (`.do-arrival-action.go`, `.do-pmeter i.done`, `.do-hero-meter` variants) are exactly the
   gradient/glow violations an audit would otherwise "fix" into nothing.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── D1. THE OCCUPANCY METER ──────────────────────────────────────────────────────────────────
   RULES 5 + 9. Three two-stop gradients, one per state, and a track with no boundary. A gradient on
   a 6px bar reads as a lit tube on near-black and as a smear on white; the amber pair in particular
   (`--c-amber-fcd34d` → `--c-orange-fbbf24`) measures 2.34:1 as a solid, under the 3:1 a meaningful
   graphical object owes under SC 1.4.11.

   Flat `--meter-*` fills, never `--st-*` — the status TEXT tokens are tuned for 4.5:1 and render as
   olive-brown when laid down as solid bar. The base state is NEUTRAL rather than `--meter-ok`, for
   the reason §4 already gave the storage board's identical meter: occupancy is not progress toward
   the shop's goal, and a full shop is not a win. */
html[data-theme="light"] .do-hero-meter {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .do-hero-meter > i {
    background: var(--ink-dim);
    background-image: none;
}

html[data-theme="light"] .do-hero-meter.full > i {
    background: var(--meter-warn);
    background-image: none;
}

html[data-theme="light"] .do-hero-meter.over > i {
    background: var(--meter-bad);
    background-image: none;
}

/* ── D2. THE HERO'S TWO COUNTS ────────────────────────────────────────────────────────────────
   RULE 1's last clause. `.do-hero-stat.lit` prints the ready-to-collect count in `--st-logged` and
   `.dt-hero-stat.logged` does the same on the tablet. A number is not a status, and "cars finished"
   is not a success state — it is a count of work waiting to be handed over.

   `.breach` stays: "needs a decision" is the one that means somebody has to do something. */
html[data-theme="light"] .do-hero-stat.lit,
html[data-theme="light"] .dt-hero-stat.logged { color: var(--ink); }

/* Applied filter, same treatment as every other applied state (§1) rather than a violet wash. */
html[data-theme="light"] .do-hero-stat.on {
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}

/* ── D3. THE WAITING CAR IS NOT A WARNING ─────────────────────────────────────────────────────
   RULE 7, and it fires on all three shells at once. Every car in the intake queue — the whole
   "Väntar" group, on every board — is marked amber four times over: the row's dashed
   `--st-expected-line` bottom rule, the expected-arrival time in `--c-orange-f0dcae`, the group's
   own note in `--st-expected`, and the `c-held` filter chip.

   A colour that is true of an entire group has stopped carrying information, and there is no good
   dark amber to spend on it anyway: to clear 3:1 on white an amber must be darkened, and a darkened
   yellow is mustard. The DASH survives — that is the shape carrier, and it is what actually says
   "this row is not like the ones above it". */
html[data-theme="light"] .do-row.waiting,
html[data-theme="light"] .dt-row.waiting { border-bottom-color: var(--keyline-strong); }
html[data-theme="light"] .dp-row.waiting { border-color: var(--keyline-strong); }

html[data-theme="light"] .do-c-when,
html[data-theme="light"] .dt-when,
html[data-theme="light"] .dp-when { color: var(--ink-dim); }

html[data-theme="light"] .dt-group-note.expected,
html[data-theme="light"] .dp-group-note.expected { color: var(--ink-dim); }

/* The held view's chip: amber fill, amber border, amber ink, on a control. It rejoins the selection
   idiom the other four `c-*` chips already use through §1. */
html[data-theme="light"] .do-chip.c-held.on,
html[data-theme="light"] .dt-chip.c-held.on,
html[data-theme="light"] .dp-chip.c-held.on {
    color: var(--ink);
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}

/* ── D4. THE REMAINING CAPSULES ───────────────────────────────────────────────────────────────
   RULE 2. `.do-chip` is already on `--r-control` through _base §2; the two controls sitting in the
   same bar as it are not — `.do-defbtn` (the shop-takt link) and `.do-viewbtn` (the sort trigger)
   are still `--r-pill`. A control bar has to agree with itself, which is the same finding §8 made
   about `.fc-sort-btn` on Work Queue. */
html[data-theme="light"] .do-defbtn,
html[data-theme="light"] .do-viewbtn { border-radius: var(--r-control); }

/* Open is a state of the trigger, not a selection: the inset rung and a strong keyline, not a wash. */
html[data-theme="light"] .do-viewbtn.open {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
}

/* RULE 8 on a menu row. The chosen option was `rgba(--ch-violet-7c3aed, .18)` with no border at all.
   A full box around one row of a menu would read as a card, so the carrier is the theme's own 3px
   selection bar — the same one §1 puts on an applied chip. */
html[data-theme="light"] .do-viewmenu-item.active {
    color: var(--ink);
    background: var(--surface-selected);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}

/* ── D5. THE WEEK STRIP ───────────────────────────────────────────────────────────────────────
   RULE 8. `.do-daychip` is `--surface-1` (#fafbfd) on `rgba(--sf-rgb, .09)` — seven near-white tiles
   with a near-invisible edge, in a grid where the whole point is telling three states apart. Surface
   and a real keyline; the overridden day takes the selection treatment rather than a violet fill.

   The `.sel` ring keeps its accent: a 1px ring is a focus-adjacent affordance, not a glow. */
html[data-theme="light"] .do-daychip {
    background: var(--surface);
    border-color: var(--keyline);
}

html[data-theme="light"] .do-daychip:hover:not(:disabled) { background: var(--surface-inset); }

html[data-theme="light"] .do-daychip.own {
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}

html[data-theme="light"] .do-dayedit {
    background: var(--surface-inset);
    border-color: var(--keyline);
}

/* ── D6. THE PACE DISCLAIMER ──────────────────────────────────────────────────────────────────
   RULE 7. An amber sentence behind an amber rail, standing on every pace sheet that has not been
   configured — which is most of them. It is an explanation, not a fault. Ink and a keyline rail. */
html[data-theme="light"] .do-pc-disclaimer {
    color: var(--ink-dim);
    border-left-color: var(--keyline-strong);
}

/* ── D7. THE PHONE HEADING'S GRADIENT ─────────────────────────────────────────────────────────
   RULE 5. `.dp-hl` is `--purple-gradient` under another name: a two-stop violet clipped to the
   heading's text. `-webkit-text-fill-color` is the load-bearing line — it beats `color`, so setting
   `color` alone leaves the clipped gradient rendering and the fix looks applied while changing
   nothing. Same three declarations _base uses for `.purple-gradient`. */
html[data-theme="light"] .dp-hl {
    background: none;
    -webkit-background-clip: border-box;
    -webkit-text-fill-color: var(--ink);
    color: var(--ink);
}

/* ── D8. THE HAIRLINES ────────────────────────────────────────────────────────────────────────
   RULE 5's second clause. The tablet row's divider is `rgba(--sf-rgb, .055)` and the phone card's
   edge is `.09` — both composite to nothing on white, which on the phone means the CARD disappears
   and the facts float on the canvas. */
html[data-theme="light"] .dt-row { border-bottom-color: var(--keyline); }

html[data-theme="light"] .dp-row {
    background: var(--surface);
    border-color: var(--keyline);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   VEHICLE STORAGE — the compliance sweep (2026-08-10)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   §1, §4, §5.5 and §5.6 already reached the tab strip, the density pair, the place chips, the filter
   chips, the group headings and the no-stations banner. What is left is the whole CHIP VOCABULARY on
   a car — status, season, due, over-time — plus two meters, the record's zone heads, and one
   remaining gradient button.

   CLASS NAMES VERIFIED:
     .vs-status + `badgeOf(item).cls` -> ready | working  — EXACTLY TWO, not the four a reader would
        guess from the three return statements (useVehicleStorageDesktop.ts:594-598, bound at :344, :394)
     .vs-due + `dueBadge().cls`       -> overdue | soon | ok | none  (:301, :587)
     .vs-season-chip + `heldSeason`   -> winter | summer             (:298, :576)
     .vs-hero-meter + `{ over: … }`   (:48)   .vs-hero-stat + `{ lit: … }` / `{ breach: … }` (:52, :56)
     .vs-lift-cap + `{ warn: liftCapacity === 0 }` (:631)

   DEAD SELECTORS FOUND, no rule written: `.vs-lift-chip` (an amber capsule), `.vs-equnit-chip` (a
   violet capsule), `.vs-card` / `.vs-card-*` (a card with a lit top edge AND a violet hover glow —
   i.e. three rule-6 violations that render nowhere), `.vs-manage-arch`. Grepped across every .vue
   and .ts in src/.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── S1. THE TWO METERS ───────────────────────────────────────────────────────────────────────
   RULES 5 + 9. §4 above already flattened the shop meter's BASE fill to `--text-secondary`, on the
   correct reasoning that occupancy is not progress toward a goal. It left the `over` variant, which
   is still a two-stop red gradient, and it left both TRACKS without a boundary — a 4px `--control-
   track-08` bar on white has no edge, so an empty meter is invisible rather than empty.

   `.vs-pmeter` is a genuine job-progress bar and takes `--meter-ok`, not the blue gradient and not a
   `--st-*` token: a bar owes 3:1 under SC 1.4.11 rather than text's 4.5:1, so the text tokens
   overshoot into olive-brown when laid down as solid fill. */
html[data-theme="light"] .vs-hero-meter,
html[data-theme="light"] .vs-pmeter {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .vs-hero-meter.over > i {
    background: var(--meter-bad);
    background-image: none;
}

html[data-theme="light"] .vs-pmeter > i {
    background: var(--meter-ok);
    background-image: none;
}

/* ── S2. THE HERO'S READY COUNT ───────────────────────────────────────────────────────────────
   RULE 1. `.vs-hero-stat.lit` prints "waiting to be collected" in `--st-logged`. A number is not a
   status, and a car nobody has fetched is not a success. `.breach` stays — that one is overdue. */
html[data-theme="light"] .vs-hero-stat.lit { color: var(--ink); }

/* ── S3. THE CAR'S CHIPS ──────────────────────────────────────────────────────────────────────
   RULE 2. Six capsule families ride on one car row: the kind chip, the status pill, the over-time
   chip, the season chip, the due badge, and the lift-capacity note. Six lozenges on a table row is
   the shape rule 2 exists to stop.

   Rectangles for all of them. `.vs-chip` is deliberately unchanged apart from the radius: it is an
   OUTLINE with no fill, which is the geometry answer this page already reached for the deuteranopia
   collision recorded in its own sheet — a kind is not a status, so it does not wear a status's
   clothes. That decision is preserved. */
html[data-theme="light"] .vs-chip,
html[data-theme="light"] .vs-status,
html[data-theme="light"] .vs-state-over,
html[data-theme="light"] .vs-season-chip,
html[data-theme="light"] .vs-due,
html[data-theme="light"] .vs-place-more { border-radius: 4px; }

html[data-theme="light"] .vs-chip { border-color: var(--keyline); }

/* RULE 7. `working` is what nearly every car on the board is — it is returned by two of `badgeOf`'s
   three branches — so a blue fill on it says nothing except "this is a car". `ready` is the minority
   and the good news, and it keeps its green: that is the row a mechanic is looking for. */
html[data-theme="light"] .vs-status.working {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

/* The rack's due badge, same verdict rule as /routines: `ok` and `none` are the quiet majority of a
   tire hotel, `soon` is the ordinary case, `overdue` is the one that is actually wrong. The amber on
   `soon` has no good dark value — a darkened yellow is mustard — so it does not get one. */
html[data-theme="light"] .vs-due.soon,
html[data-theme="light"] .vs-due.ok,
html[data-theme="light"] .vs-due.none {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .vs-due.overdue {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
}

/* The season pair is CATEGORICAL and stays (escalation 4 at the head of this file). What it does not
   keep is `summer` borrowing the amber ramp — that is the same hue the due badge just gave up, so on
   a rack row "Sommar" and "om 3 dagar" were two amber chips side by side saying different things.
   Both halves take the neutral chip ground and the WORD carries the season, which is what the
   routines sheet already says is legal for this pair. */
html[data-theme="light"] .vs-season-chip.winter,
html[data-theme="light"] .vs-season-chip.summer {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

/* ── S4. THE LIFT-CAPACITY NOTE ───────────────────────────────────────────────────────────────
   RULES 7 + 8. The read-out is a violet slab standing above the lifts it counts — a notice wearing
   the accent. Its `warn` state fires when the shop has NO lifts configured, which is a real fault
   rather than a middle band, so it takes breach rather than the amber it had. */
html[data-theme="light"] .vs-lift-cap {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .vs-lift-cap.warn {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
}

/* ── S5. THE STAGED-LIFT COMMIT ───────────────────────────────────────────────────────────────
   RULES 5 + 6. `.vs-lift-save` is a 135° violet gradient with `inset 0 1px 0 --hl-rgb` — a lit top
   edge, which exists so a near-black button catches light along its rim and reads as a scratch on
   white. It IS the terminal action of that drawer, so it stays filled: flat accent, no rim. */
html[data-theme="light"] .vs-lift-save {
    background: var(--accent);
    background-image: none;
    color: var(--text-on-accent);
    border: 1px solid var(--accent);
    box-shadow: none;
}

html[data-theme="light"] .vs-lift-save:hover:not(:disabled) {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    box-shadow: none;
}

/* ── S6. THE RECORD'S ZONE HEADS AND ITS HIGHLIGHT STRIP ──────────────────────────────────────
   RULE 1 for the heads (`--astra-accent-soft`, i.e. a violet section label), RULE 5's second clause
   for the strip: `--hair` is `#e5edf5` in light, which is a line you cannot see between four cells
   whose whole job is to be separate facts. */
html[data-theme="light"] .vsx-zone-head {
    color: var(--ink-muted);
    letter-spacing: var(--eyebrow-track);
}

html[data-theme="light"] .vsx-hl {
    background: var(--surface-inset);
    border-bottom-color: var(--keyline);
}

html[data-theme="light"] .vsx-hl-cell { border-right-color: var(--keyline); }

html[data-theme="light"] .vs-manage-add { border-top-color: var(--keyline); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   CUSTOMERS BOARD — the compliance sweep (2026-08-10)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   NAMESPACE WARNING, because it cost time to establish and will cost it again: `src/pages/
   Customers.css` and `src/pages/complaints/ComplaintsDesktop.css` BOTH use the `cx-` prefix and they
   overlap on `.cx-sheet-head` and `.cx-veh`. Page sheets are injected only while their page is
   mounted so they never collide at runtime — but a rule in THIS file is global, so anything written
   for one lands on the other. Every selector below was checked against both sheets; `.cx-sheet-head`
   is deliberately shared (§C7 already gives it the keyline, and it is a head-with-a-rule on both).

   Verified live in `src/pages/Customers.vue`: `.stat-pill` (+ `.warn`, :20-24), `.cx-src` (+ `chat` /
   `walkin` / `taken` / `dup` / `keep`, :144-146, :179-180, :204), `.cx-dup*`, `.cx-fact-value.money`,
   `.cxm-file-btn`, `.cxm-panel`, `.cx-veh-chip`, `.badge`, `.purple-gradient`.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── X1. THE HEADER PILLS ─────────────────────────────────────────────────────────────────────
   RULES 2 + 6 + 7. `.badge` and `.stat-pill` are `border-radius: 50px` violet capsules, each under an
   `inset 0 1px 0 rgba(--hl-rgb, .1)` lit top edge. _base §1 already squares `.badge` and repaints it;
   what it does not reach is the rim, and `.stat-pill` it does not reach at all.

   `.warn` is the amber "someone is owed something" count. It is a COUNT — rule 1's last clause — and
   amber has no good dark value, so it goes neutral and the words carry the meaning. */
/* Scoped to `.cx-wrapper` (Customers.vue:2). `.badge` is app.css vocabulary and _base §1 owns it —
   a bare rule here would reach every page in the app, which is the lead's call, not this file's.
   `.stat-pill` is likewise a bare name that other page sheets declare. */
html[data-theme="light"] .cx-wrapper .badge,
html[data-theme="light"] .cx-wrapper .stat-pill { box-shadow: none; }

html[data-theme="light"] .cx-wrapper .stat-pill {
    border-radius: 4px;
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .cx-wrapper .stat-pill.warn {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
}

/* ── X2. THE SOURCE CHIPS ─────────────────────────────────────────────────────────────────────
   RULE 2 (`border-radius: 999px`), and RULE 7 on one of the five variants.

   `.chat` / `.walkin` are the CATEGORICAL pair — violet for the assistant's chat, teal for someone at
   the counter, the same two this app uses for the same distinction on the quote history. They keep
   their hues; only the shape changes. `.taken` and `.keep` are already correct.

   `.dup` is amber, and it is the third amber thing this board prints for the same fact (the header's
   warn pill, this chip, and the whole `.cx-dup` block in the pane). Neutral, with the count `2×`
   doing the work it was already doing. */
html[data-theme="light"] .cx-src { border-radius: 4px; }

html[data-theme="light"] .cx-src.dup {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
}

/* The duplicate block in the pane: an amber panel under an amber uppercase heading. RULES 1 + 7 —
   a section heading does not carry a hue, and there is no good amber to give this one. It is a
   finding the shop should look at, not a fault, and its words already say so. */
html[data-theme="light"] .cx-dup {
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .cx-dup-title {
    color: var(--ink-muted);
    letter-spacing: var(--eyebrow-track);
}

/* ── X3. MONEY IS NOT A SUCCESS ───────────────────────────────────────────────────────────────
   RULE 1. `.cx-fact-value.money` is `--st-logged`, on the reasoning that "money wears the app's
   settled-figure mint". A customer's lifetime spend is a figure, not a state — and green here is the
   same token the board uses to mean "resolved" two panes over. */
html[data-theme="light"] .cx-fact-value.money { color: var(--ink); }

/* ── X4. THE PANE'S HAIRLINES AND ITS CAR ROWS ────────────────────────────────────────────────
   RULE 8. `.cx-fact` divides on `rgba(--sf-rgb, .05)` and `.cx-veh-chip` is `--surface-a03` on
   `rgba(--sf-rgb, .08)` — a list of cars that, on a white pane, is a list of unbounded text. */
html[data-theme="light"] .cx-fact { border-bottom-color: var(--keyline); }

html[data-theme="light"] .cx-veh-chip {
    background: var(--surface);
    border-color: var(--keyline);
}

html[data-theme="light"] .cx-veh-chip:hover {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
}

/* ── X5. THE IMPORT DIALOG ────────────────────────────────────────────────────────────────────
   RULE 5. `.cxm-panel` is `linear-gradient(160deg, --c-blue-171a24, --c-blue-10131b)`. Both stops DO
   have light values (#d4ddee / #d6dff1), so this does not render black — it renders a pale-blue
   two-stop slab, which is a colour nothing else in the light theme uses, on the app's own dialog.

   RULE 8 for the file button: a violet fill with no relationship to the primary beside it. It is the
   secondary shape from _base's ONE PRIMARY PER PANE recipe. */
html[data-theme="light"] .cxm-panel {
    background: var(--surface);
    background-image: none;
    border-color: var(--keyline);
    box-shadow: var(--elevation-3);
}

html[data-theme="light"] .cxm-file-btn {
    color: var(--ink-dim);
    background: var(--surface);
    border-color: var(--keyline-strong);
}

html[data-theme="light"] .cxm-file-btn:hover {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--accent);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   PHONE BOOKINGS — the compliance sweep (2026-08-10)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The call console runs on the shared ledger, so §5.1–§5.4 already reach its rows, its list surface
   and its bucket geometry. Its own vocabulary — the chase chips, the three lane headings, the result
   line's five tones and the row tag — is untouched.

   CLASS NAMES VERIFIED:
     .lx-bucket + `` `b-${b.key}` `` -> b-needs_callback | b-awaiting_answer | b-accepted
        (PHONE_LANES, models/phoneQuote.ts:33; bound at PhoneBookingsDesktop.vue:78)
     .pb-chip + `` `c-${s}` `` -> c-stale | c-expiring | c-inshop | c-duplicate
        (SHOW_OPTIONS, usePhoneBookings.ts:149; bound at :45-46). The sheet only declares `.c-stale.on`
        and `.c-expiring.on`; the other two fall through to `.pb-chip.on`, correctly.
     .pb-result-urgent + `` `u-${resultLine.tone}` `` -> u-idle | u-dead | u-go | u-warn | u-call |
        u-wait (triage tones, usePhoneBookings.ts:100-120; bound at :59). `u-idle` is declared by
        nothing and needs nothing — it is the "board is clear" case.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── P1. THREE COLOURED LANE HEADINGS ─────────────────────────────────────────────────────────
   RULE 1, and the same fault §5.2 fixed on Work Queue: the ledger opens with an amber word, a cyan
   word and a mint word before a single row. The page's own comment says the hue is never what
   carries the state — the bucket's WORDS carry it — which is precisely the argument for taking the
   hue off in a theme where it reads as a highlighter mark.

   §5.2's generic `.lx-bucket .lx-bucket-name` rule already outranks these at (0,3,1) against (0,3,0),
   so this is belt and braces: named explicitly so the intent survives a future edit to §5.2. */
html[data-theme="light"] .lx-bucket.b-needs_callback .lx-bucket-name,
html[data-theme="light"] .lx-bucket.b-awaiting_answer .lx-bucket-name,
html[data-theme="light"] .lx-bucket.b-accepted .lx-bucket-name { color: var(--ink-muted); }

/* The sticky heading's backing is `rgba(--ch-indigo-130f1e, .94)` — a near-black at 94% in dark, and
   in light a channel-inverted slab of a colour nothing else on the page uses. A sticky heading only
   has to be opaque enough that rows pass BEHIND it: the surface, and a keyline under it. */
html[data-theme="light"] .pb-wrapper .lx-bucket {
    background: var(--surface);
    border-bottom: 1px solid var(--keyline);
}

html[data-theme="light"] .pb-wrapper .lx-bucket::after { background: var(--keyline); }

/* ── P2. THE RESULT LINE'S FIVE TONES ─────────────────────────────────────────────────────────
   RULE 7. One sentence, painted in five different hues depending on which triage branch fired —
   amber, mint, violet, cyan or red. Four of the five are ordinary states of a call board: someone to
   ring, someone to book, someone to wait on. Only `dead` (an offer whose slots have expired) is the
   case where something has actually gone wrong, and it keeps red.

   `.u-warn` is the amber one, and it does not get a replacement amber for the reason recorded in §29:
   to clear contrast on white an amber must be darkened, and a darkened yellow is mustard. */
html[data-theme="light"] .pb-result-urgent.u-warn,
html[data-theme="light"] .pb-result-urgent.u-go,
html[data-theme="light"] .pb-result-urgent.u-call,
html[data-theme="light"] .pb-result-urgent.u-wait { color: var(--ink-dim); }

html[data-theme="light"] .pb-result-urgent.u-dead { color: var(--st-breach); }

/* ── P3. THE CHASE CHIPS AND THE SORT BUTTONS ─────────────────────────────────────────────────
   RULES 2 + 8. `.pb-chip` is the one filter-chip dialect _base §2 does not name, so it is still a
   `--r-pill` capsule on `rgba(--sf-rgb, .045)` over a `.05` border — a fill and an edge that both
   composite to nothing on white. Same recipe as every other chip in the app, and `.on` rejoins the
   §1 selection treatment instead of a violet wash.

   `.pb-sort-btn` is the same finding §8 made about `.fc-sort-btn`: the sort control sitting in a bar
   of rectangles was the one capsule left on the line. */
html[data-theme="light"] .pb-chip {
    border-radius: var(--r-control);
    min-height: var(--ctl-h);
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}

html[data-theme="light"] .pb-chip:hover {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
}

html[data-theme="light"] .pb-chip.on,
html[data-theme="light"] .pb-chip.c-expiring.on {
    color: var(--ink);
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}

html[data-theme="light"] .pb-chip.on i { color: var(--ink-muted); }

/* `.c-stale.on` keeps breach: a dead offer is the one filter on this bar that means something is
   wrong. `.c-expiring.on` was the amber twin of it and is in the selection list above instead.

   Restated here rather than left to the page sheet, and that is load-bearing: the `.pb-chip.on` rule
   above is (0,3,1) against the page's (0,3,0), so without this the stale chip would silently lose its
   red to the selection wash. Same trap as `.wq-band-pace` in §23 — a correct-looking rule quietly
   overriding a narrower one. */
html[data-theme="light"] .pb-chip.c-stale.on {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    box-shadow: inset 3px 0 0 var(--st-breach);
}

html[data-theme="light"] .pb-sort-btn { border-radius: var(--r-control); }

html[data-theme="light"] .pb-sort-btn.on {
    color: var(--ink);
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
}

/* ── P4. THE ROW TAG AND THE ASSIST EYEBROW ───────────────────────────────────────────────────
   RULE 2 for both, RULE 8 for the tag: `--surface-2` is #fcfcfe in light and `--hair` is #e5edf5, so
   the tag is a white rectangle on a white row with a border you cannot see. */
html[data-theme="light"] .pb-tag {
    border-radius: 4px;
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .pb-assist-eyebrow {
    border-radius: var(--r-control);
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .pb-assist-eyebrow svg { color: var(--ink-muted); }

/* ── P5. THE DIALOG, THE TOAST AND THE FEEDBACK LINE ──────────────────────────────────────────
   RULE 5 for the first two — a 160° pale-blue two-stop on the dialog panel, a mint two-stop on the
   toast whose ink (`--c-emerald-04231a`) has no light value at all — and RULE 8 for the third:
   `--surface-1` with no border is #fafbfd on white.

   `.pb-hl` is `--purple-gradient` under another name, on the page's own h1. `-webkit-text-fill-color`
   is the load-bearing declaration: it beats `color`, so setting `color` alone leaves the clipped
   gradient rendering and the fix looks applied while changing nothing. */
html[data-theme="light"] .pb-hl {
    background: none;
    -webkit-background-clip: border-box;
    -webkit-text-fill-color: var(--ink);
    color: var(--ink);
}

/* Targeted at `.pb-sheet`, not at `.modal-panel`: the element carries BOTH classes
   (PhoneBookingsDesktop.vue:130), and `.modal-panel` is a bare shared name that four other page
   sheets in this app declare independently. A global rule on it would reach into other clusters'
   files, which is exactly the drift `.purple-gradient` is the cautionary tale for. */
html[data-theme="light"] .pb-sheet {
    background: var(--surface);
    background-image: none;
    border-color: var(--keyline);
    box-shadow: var(--elevation-3);
}

html[data-theme="light"] .pb-toast.ok {
    color: var(--st-logged);
    background: var(--st-logged-soft);
    background-image: none;
    border: 1px solid var(--st-logged-line);
    box-shadow: var(--elevation-3);
}

html[data-theme="light"] .pb-toast.error {
    color: var(--st-breach);
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    box-shadow: var(--elevation-3);
}

/* Scoped to this sheet, and `:not(.error)` so the error variant keeps its own measured fill. A bare
   `.feedback-msg` rule here would land on /routines' dialogs too — same shared-name trap. */
html[data-theme="light"] .pb-sheet .feedback-msg:not(.error) {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

/* ── ROUTINES: THE SEASON CHIP IS A CATEGORY, NOT A STATUS ────────────────────────────────────
   `.rt-season.winter` is a cyan fill and `.rt-season.summer` a violet one — two saturated capsules
   on every row of the page, marking a fact that is true of half the rows either way. A category
   label earns no hue and no capsule; it takes the neutral badge rectangle like every other tag in
   the app.

   The season is still legible: it says "Vinter" or "Sommar" in words. Colour was never carrying it.

   NOT FIXED, and not fixable from CSS: the tag chip beside it (`.rt-tag`) is painted by
   `:style="tagChipStyle(t)"` — an inline style, which outranks any stylesheet short of !important.
   Those colours are chosen by the shop in the tag manager, so they are DATA rather than theme, and
   overriding them would be overriding the user. Left alone deliberately. */
html[data-theme="light"] .rt-season,
html[data-theme="light"] .rt-season.winter,
html[data-theme="light"] .rt-season.summer {
    border-radius: 4px;
    padding: 2px 6px;
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    font-style: normal;
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    color: var(--ink-dim);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TELEFON — rebuilt on the light shell
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   This page is a dispatch desk: someone is on the line and the job is to price them and book them
   before they hang up. Everything on it should be scannable in one pass and nothing should compete
   with the two things that matter — who is waiting, and what it costs. */

/* The title's second word is a `.pb-hl` span carrying the accent, so the page name reads as half
   heading and half link. A page title is one object. The rail already says where you are. */
html[data-theme="light"] .pb-wrapper .pb-hl {
    background: none;
    color: var(--ink);
    -webkit-text-fill-color: var(--ink);
}

html[data-theme="light"] .pb-wrapper .pb-header h1 {
    font-size: 1.75rem;
    font-weight: var(--fw-label);
    letter-spacing: -0.01em;
    color: var(--ink);
}

html[data-theme="light"] .pb-wrapper .pb-header {
    border-bottom: 1px solid var(--keyline);
    padding-bottom: 16px;
    margin-bottom: 24px;
}

html[data-theme="light"] .pb-wrapper .pb-flow-hint {
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

/* ── The filter bar ───────────────────────────────────────────────────────────────────────────
   Nine capsules in a row, one of them filled accent-blue. The filled one is not an action — it is
   a statement about which subset you are looking at — so it was wearing the primary button's whole
   costume for a fact. Rectangles, one quiet fill for the active one, and the count stays muted. */
html[data-theme="light"] .pb-wrapper .pb-chip,
html[data-theme="light"] .pb-wrapper .pb-sort-btn {
    border-radius: var(--r-control);
    min-height: var(--ctl-h);
    font-size: 0.8125rem;
    font-weight: var(--fw-body);
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}

html[data-theme="light"] .pb-wrapper .pb-chip.on,
html[data-theme="light"] .pb-wrapper .pb-sort-btn.on {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
    font-weight: var(--fw-label);
    box-shadow: none;
}

/* ── The ledger ───────────────────────────────────────────────────────────────────────────────
   Money goes to ink. A price is the fact this page exists to produce; painting it mint-green says
   "good" about a number that is neither good nor bad, and it was the only green on the screen so it
   read as a status the row does not have. Tabular, so a column of quotes compares straight down. */
html[data-theme="light"] .pb-wrapper .lx-money {
    color: var(--ink);
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}

/* The waiting age is a fact until it is a problem. Red on every row is red on no row. */
html[data-theme="light"] .pb-wrapper .lx-when {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

/* The pane is a scroll region beside another scroll region: keyline, no float. */
html[data-theme="light"] .pb-wrapper .lx-sheet {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
}

/* `--lx-page: 100%` STOOD HERE AND IS GONE. It was the last surviving instance of the exact
   mechanism this whole programme exists to remove: a LENGTH declared in a THEME file, which forks
   the two themes by construction because only one of them can ever read it.

   PhoneBookingsDesktop.css:35-40 already carries a comment claiming this override was retired —
   it was not, and the claim held only because the fork is invisible at the widths anyone audits.
   MEASURED, `.pb-wrapper` on /phone-bookings:

       1900   light 1820 @ x=40    dark 1820 @ x=40     agree
       2560   light 2512 @ x=24    dark 1852 @ x=354    660px apart

   Below the cap the viewport is the binding constraint in both themes, so 1440 and 1900 report a
   clean match and every page-by-page pass signed it off. On a 27" monitor light runs full-bleed to
   the glass while dark sits centred in its measure — one page, two compositions, decided by a
   colour preference.

   `--lx-page: 1820px` in app.css is now the only declaration, and 1820 was set FROM this page's
   light rendering, so light keeps the composition that was signed off and dark joins it. */

/* ── TELEFON: USE THE WIDTH, AND STOP THE SLABS MELTING ───────────────────────────────────────
   Two faults, and the first is an unfinished change of mine rather than a taste call.

   1. THE WIDTH WENT NOWHERE. The shell's column was widened to 1400, but `.lx-split` pins its
      first track to `var(--lx-col)` (27rem) and its second to a fixed measure, so the extra width
      landed in empty page rather than in the ledger or the record. Widening a container does
      nothing if the grid inside it is fixed. The ledger gets a real measure and the record takes
      the rest.

   2. THE RECORD MELTS. Inside the pane, the quote text sits in a grey slab and the totals in a
      blue-tinted one, stacked directly on white with no borders and near-identical tints. That is
      the same failure this theme was already fixed for at page level — a fill with no edge — showing
      up again INSIDE a card because the earlier pass only ever looked at page-level surfaces.

      The quote is a DOCUMENT: white, keyline, and the text left to be read. The totals are a
      SUMMARY of it: the inset rung, so it reads as a step below the document rather than as a
      second, competing panel. One of them recedes, which is what makes the other one legible.

   FAULT 1 IS NO LONGER LIGHT'S TO FIX. `--lx-col: 34rem` and the `.lx-split` track pair both stood
   here as light-only declarations, and PhoneBookingsDesktop.css:25 and :48 now declare exactly the
   same two things unprefixed, for both themes — its own comment records that light's 34rem was
   what dark was raised TO. Two identical declarations, one of them theme-scoped, is not
   belt-and-braces: it is a fork waiting for someone to edit the page sheet and wonder why light
   did not move. The lengths live in the page sheet; only paint stays below. */

html[data-theme="light"] .pb-wrapper .pc-quote-text {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    color: var(--ink-dim);
}

html[data-theme="light"] .pb-wrapper .pc-lines,
html[data-theme="light"] .pb-wrapper .pc-total {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .pb-wrapper .pc-line,
html[data-theme="light"] .pb-wrapper .pc-quote-row {
    border-bottom: 1px solid var(--keyline);
}

html[data-theme="light"] .pb-wrapper .pc-total,
html[data-theme="light"] .pb-wrapper .grand {
    font-weight: var(--fw-strong);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* ── One primary in the footer ────────────────────────────────────────────────────────────────
   Three buttons, two of them tinted: "De sa nej" in red wash, "Erbjud igen" in grey, "De sa ja" in
   accent. The customer said yes or no — one of those closes the call, and the other two are what
   you do when they did not. Only the closing action stays filled; the rest go to the secondary
   shape and keep their meaning in the word. */
html[data-theme="light"] .pb-wrapper .pc-actions .btn-astra-neutral,
html[data-theme="light"] .pb-wrapper .pc-actions .no,
html[data-theme="light"] .pb-wrapper .pc-actions .ghost {
    background: var(--surface);
    background-image: none;
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    box-shadow: none;
}

html[data-theme="light"] .pb-wrapper .pc-actions .no:hover:not(:disabled) {
    border-color: var(--st-breach-line);
    color: var(--st-breach);
    background: var(--st-breach-soft);
}

/* Section titles inside the record follow the eyebrow, so the record has one heading grammar. */
/* NOT .pc-brow — that is a TOTALS ROW (label + figure), not a section eyebrow. Adding it here
   uppercased every money line including the currency ("2 912 KR"). Named like an eyebrow, used as
   a row: the exact guessed-selector mistake this file warns agents about twice. */
html[data-theme="light"] .pb-wrapper .pc-sec-title {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* The timeline: hollow past, filled head, keyline connector — the same grammar as Work Queue's. */
html[data-theme="light"] .pb-wrapper .pc-tl-dot {
    background: var(--surface);
    box-shadow: inset 0 0 0 1.5px var(--keyline-strong);
}

html[data-theme="light"] .pb-wrapper .pc-tl-row:last-child .pc-tl-dot {
    background: var(--ink);
    box-shadow: none;
}

/* ── TELEFON: THE RECORD, FINISHED ────────────────────────────────────────────────────────────
   Supersedes the block above, which gave the quote and the totals their own borders. That produced
   exactly the nested-box look the theme is being pulled out of: a shadowed pane, containing a
   bordered document, containing a bordered summary. Depth belongs to the OUTERMOST object only.

   The quote is now a plain white document with air around it — no frame, because it is already
   inside one. The totals recede onto the inset ground with a single top rule, which is the one
   line that earns its place: it separates a summary from the thing it summarises. */
html[data-theme="light"] .pb-wrapper .pc-quote-text {
    background: var(--surface);
    border: 0;
    box-shadow: none;
    padding: 0 4px;
    color: var(--ink-dim);
    line-height: 1.65;
}

html[data-theme="light"] .pb-wrapper .pc-lines,
html[data-theme="light"] .pb-wrapper .pc-total {
    background: transparent;
    border: 0;
    border-top: 1px solid var(--keyline);
    border-radius: 0;
    box-shadow: none;
    padding-top: 12px;
    margin-top: 16px;
}

html[data-theme="light"] .pb-wrapper .pc-line,
html[data-theme="light"] .pb-wrapper .pc-quote-row {
    border-bottom: 0;
}

/* ── The header figure and the phone number ───────────────────────────────────────────────────
   The price was in a bordered box and the number in a filled blue capsule — two chips competing
   with the vehicle name for the top of the record. The price is the loudest FACT here, so it gets
   size and ink and nothing else. The number is a phone number: it is dialled, not clicked, so it
   reads as text set in the figure face. */
html[data-theme="light"] .pb-wrapper .pc-quote-total,
html[data-theme="light"] .pb-wrapper .pc-price {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    font-size: 1.375rem;
    font-weight: var(--fw-strong);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

html[data-theme="light"] .pb-wrapper .pc-call {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    color: var(--ink-dim);
    font-weight: var(--fw-body);
    font-variant-numeric: tabular-nums;
}

/* ── Tags ─────────────────────────────────────────────────────────────────────────────────────
   "Döda tider" was a red-outlined capsule on every row of a two-row list. It is a real exception,
   so it keeps its hue — but as the badge rectangle at badge weight, not as an outlined pill that
   outweighs the vehicle beside it. */
/* NOT .pb-result-urgent — that is the count line above the ledger ("2 samtal · döda tider"), a
   sentence fragment, and boxing it turned prose into a badge. Tags only. */
html[data-theme="light"] .pb-wrapper .lx-flag-tag,
html[data-theme="light"] .pb-wrapper .lx-tag {
    border-radius: 4px;
    padding: 1px 6px;
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
    color: var(--st-breach);
}

/* ── The pane's own actions ───────────────────────────────────────────────────────────────────
   "Redigera detaljer" and "Ändra med AI" were tinted capsules sitting beside a section eyebrow —
   two more chips on a record that already had four. Quiet rectangles; the eyebrow leads. */
html[data-theme="light"] .pb-wrapper .pc-editbtn,
html[data-theme="light"] .pb-wrapper .pc-aibtn,
html[data-theme="light"] .pb-wrapper .btn-astra-glass {
    background: var(--surface);
    background-image: none;
    border: 1px solid var(--keyline-strong);
    border-radius: var(--r-control);
    box-shadow: none;
    color: var(--ink-dim);
    font-weight: var(--fw-label);
    font-size: 0.8125rem;
    min-height: var(--ctl-h);
}

/* ── The footer ───────────────────────────────────────────────────────────────────────────────
   Three buttons stretched to a third of the pane each. A button is sized by its word, not by the
   space available — full-width actions are a phone pattern and on a 1900px record they read as a
   toolbar rather than a decision. Right-aligned, natural width, one filled. */
html[data-theme="light"] .pb-wrapper .pc-actions {
    justify-content: flex-end;
    gap: 8px;
    border-top: 1px solid var(--keyline);
    padding-top: 16px;
}

/* GEOMETRY MOVED TO PhoneCallPane.vue (`.pc-actions`, `.pc-actions > button`, `.pc-decide`),
   unprefixed, so BOTH themes get it. It was forking the footer by ~300px per button: dark rendered
   371/404/404 x 45, light 94/111/89 x 36. `min-height` stays here — 44px is the coarse-pointer floor
   and this pane also renders on the bay tablet in dark, so the two themes disagree on height by
   design until a media-guarded shared rule is written. */
html[data-theme="light"] .pb-wrapper .pc-actions > * {
    min-height: 36px;
    border-radius: var(--r-control);
    font-size: 0.8125rem;
    font-weight: var(--fw-label);
}


/* The totals slab is .pc-money — the container, not the rows inside it. Targeting .pc-lines and
   .pc-total left the tinted rounded panel exactly where it was. It is a summary of the document
   above it, so it recedes onto the inset ground with one rule and no frame of its own. */
html[data-theme="light"] .pb-wrapper .pc-money {
    background: var(--surface-inset);
    border: 0;
    border-radius: var(--r-card);
    box-shadow: none;
    padding: 12px 16px;
    margin-top: 20px;
}

html[data-theme="light"] .pb-wrapper .pc-money .pc-brow.grand {
    border-top: 1px solid var(--keyline-strong);
    margin-top: 8px;
    padding-top: 10px;
    color: var(--ink);
}

/* The record scrolls under a sticky footer, so it owes the footer its own height in padding or the
   last row is permanently half-hidden — which is what was clipping "Totalt (inkl. moms)". */
html[data-theme="light"] .pb-wrapper .lx-sheet {
    padding-bottom: 76px;
}

/* The action bar is sticky over a scrolling record, so it must be OPAQUE. It carried a translucent
   tint — fine over near-black, where anything behind it goes dark anyway; on white it let two rows
   of the totals show through and read as a band laid across the numbers. Surface, one rule above,
   and it stops being a smear. */
html[data-theme="light"] .pb-wrapper .pc-actions {
    background: var(--surface);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    padding-bottom: 16px;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TELEFON — the action bar, the notice, and putting some life back in
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. THE ACTION BAR STOPS COVERING THE FORM ────────────────────────────────────────────────
   `.pc-actions` is `position: sticky; bottom: 0` inside the record's own scroll container, so it
   is pinned over whatever is last — and what is last is the note field. Scrolling to the bottom
   put the buttons ON TOP of the input you were trying to fill in. A pinned bar is only correct
   when the thing behind it is scrollable PAST it; here it was the end of the form.

   It flows at the end of the record instead. Nothing to overlay, nothing to clear, and the note is
   reachable. The rule above it is what separates it now — the same single hairline the rest of the
   page uses, rather than a gradient scrim over content. */
/* THE LAYOUT HALF MOVED OUT, 2026-08-12 — see PhoneCallPane.vue. The argument above is right and it
   is THEME-INDEPENDENT: "a pinned bar is only correct when the thing behind it is scrollable PAST
   it" is a fact about this form's structure, not about a colour scheme. Held light-only it left DARK
   still covering its own note field, and forked the footer's position by 1256px — measured at wide
   with a record open, `.pc-actions` y=886 in dark against y=2142 in light. Only paint stays here. */
html[data-theme="light"] .pb-wrapper .pc-actions {
    background: none;
    background-image: none;
    border-top: 1px solid var(--keyline);
}

/* Buttons sized by their word, not stretched to fill. `flex: 1` on three actions is a phone
   pattern; on a wide record it makes every choice look equally weighted.
   The `flex` half of that argument now lives in PhoneCallPane.vue, unprefixed — it was true of dark
   too, and keeping it here forked the footer by 300px per button. Only paint and the 36px rung stay. */
html[data-theme="light"] .pb-wrapper .pc-actions > button,
html[data-theme="light"] .pb-wrapper .pc-decide {
    min-height: 36px;
    border-radius: var(--r-control);
    font-size: 0.875rem;
    font-weight: var(--fw-label);
}

/* The yes/no decisions were tinted green and red fills. Red is reserved for STATUS in this system,
   and a decision is not a status — saying "they said no" is not an error. Both take the neutral
   control shape; the word carries the meaning, and only the affirmative keeps the fill. */
html[data-theme="light"] .pb-wrapper .pc-decide.no {
    background: var(--surface);
    border: 0;
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
}

html[data-theme="light"] .pb-wrapper .pc-decide.no:hover:not(:disabled) {
    color: var(--st-breach);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

html[data-theme="light"] .pb-wrapper .pc-decide.yes {
    background: var(--accent);
    border: 0;
    color: #fff;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 0 0 0 transparent;
}

html[data-theme="light"] .pb-wrapper .pc-decide.yes:hover:not(:disabled) {
    background: var(--accent);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.30),
        0 4px 9px rgba(var(--edge-rgb), 0.20);
}

/* ── 2. THE NOTICE ────────────────────────────────────────────────────────────────────────────
   It was a pink box with a red border and a filled red disc — three carriers for one sentence, and
   the fill was doing the shouting. Measured: Stripe's severity is a 5% wash of the hue over an
   opaque white layer, with NO border and NO bar. Their warning fill lands at 1.065:1 against white,
   which is a WEAKER departure from white than plain grey is — severity is carried by hue at
   near-zero chroma, never by intensity.

   The icon stays, but as ink rather than a filled disc. Stripe puts the alarming glyph on the FIELD
   error, not on the page banner; here the sentence is genuinely about a booking that broke, so it
   keeps a mark — just not a badge. */
html[data-theme="light"] .pb-wrapper .pc-alert {
    border: 0;
    border-radius: var(--r-control);
    padding: 8px 12px;
    font-size: 0.875rem;
    color: var(--ink-dim);
    background-image:
        linear-gradient(rgba(205, 61, 100, 0.05), rgba(205, 61, 100, 0.05)),
        linear-gradient(#fff, #fff);
}

html[data-theme="light"] .pb-wrapper .pc-alert-mark {
    background: none;
    color: var(--st-breach);
}

/* The action inside a notice is never severity-coloured — measured, and it is why a Stripe banner
   reads as information rather than as an alarm with a button on it. */
html[data-theme="light"] .pb-wrapper .pc-alert-act {
    color: var(--accent);
    background: none;
    border: 0;
    font-weight: var(--fw-label);
}

/* ── 3. NO BOX INSIDE A BOX ───────────────────────────────────────────────────────────────────
   The note field sat in its own bordered well inside a bordered section inside the record. Three
   frames for one textarea. The field keeps its own edge — it is a control and controls have edges
   — and everything around it stands down to spacing. */
html[data-theme="light"] .pb-wrapper .pc-notewrap,
html[data-theme="light"] .pb-wrapper .pc-sec,
html[data-theme="light"] .pb-wrapper .pc-block {
    background: none;
    border: 0;
    box-shadow: none;
    padding-left: 0;
    padding-right: 0;
}

/* ── 4. PUTTING THE LIFE BACK ─────────────────────────────────────────────────────────────────
   The note that this reads flatter than dark is fair, and the fix is not more colour — it is the
   response machinery Stripe actually uses. Dark felt alive because everything glowed; light gets
   the same aliveness from surfaces that answer the pointer.

   Rows lift on hover with the same three-layer shadow the buttons use, so the whole interface
   responds in one grammar. Nothing moves — measured, `transform` appears on zero controls in
   Stripe's file — the light changes, not the position. */
html[data-theme="light"] .pb-wrapper .lx-row {
    transition: box-shadow 0.08s ease-in, background-color 0.15s ease;
}

html[data-theme="light"] .pb-wrapper .lx-row:hover {
    background-color: #f7fafc;
    box-shadow:
        inset 0 -1px 0 0 var(--keyline),
        0 1px 1px rgba(0, 0, 0, 0.04),
        0 3px 9px rgba(var(--edge-rgb), 0.06);
}

/* The selected row is the one place the ledger spends colour: a solid accent edge, the same move
   the rail makes for the current page. One saturated mark per surface. */
html[data-theme="light"] .pb-wrapper .lx-row.picked {
    background-color: #f7fafc;
    box-shadow: inset 3px 0 0 0 var(--accent), inset 0 -1px 0 0 var(--keyline);
}

/* The steppers. A +/- pair is a control, so it gets the control shape rather than a tinted chip. */
html[data-theme="light"] .pb-wrapper .pc-step,
html[data-theme="light"] .pb-wrapper .pc-dur-btn {
    min-width: 28px;
    min-height: 28px;
    border: 0;
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .pc-step:hover:not(:disabled),
html[data-theme="light"] .pb-wrapper .pc-dur-btn:hover:not(:disabled) {
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

/* Offered time slots are choices, so they answer the pointer like every other control here. */
html[data-theme="light"] .pb-wrapper .pc-slot {
    border: 0;
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .pc-slot:hover:not(:disabled) {
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

html[data-theme="light"] .pb-wrapper .pc-slot.on,
html[data-theme="light"] .pb-wrapper .pc-slot[aria-pressed="true"] {
    background: var(--accent);
    color: #fff;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TELEFON — the job-details panel
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The worst box-in-box on the page, and every fault in it is the same fault: a FIELD GROUP was
   given the costume of a CARD. `.lift-toggle` and `.diff-toggle` are bordered, filled frames whose
   only content is a switch and three buttons; `.pc-admin-box` is a bordered box wrapping an input
   that has had its own border removed to fit inside it. So the page draws a frame, then draws
   another frame inside it, then removes the edge from the control that actually needed one.

   The rule, from the measured spec: a control has an edge because it is a control. The group AROUND
   controls is a label and some space. Nothing else on this panel earns a frame. */

/* The group frames stand down. */
html[data-theme="light"] .pb-wrapper .lift-toggle,
html[data-theme="light"] .pb-wrapper .diff-toggle {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
}

html[data-theme="light"] .pb-wrapper .lift-toggle:hover,
html[data-theme="light"] .pb-wrapper .diff-toggle:hover {
    background: none;
    border-color: transparent;
}

html[data-theme="light"] .pb-wrapper .lift-label,
html[data-theme="light"] .pb-wrapper .diff-label,
html[data-theme="light"] .pb-wrapper .pc-field > span {
    font-size: 0.8125rem;
    font-weight: var(--fw-label);
    color: var(--ink-muted);
    letter-spacing: 0;
    text-transform: none;
}

/* ── The difficulty segments ──────────────────────────────────────────────────────────────────
   Three buttons where the chosen one was mint-green. Green means SUCCESS in this system, and
   picking "Enkel" is not a success — it is a selection. One accent fill marks the choice, which is
   the same move the nav rail makes for the current page and the ledger makes for the open row: one
   saturated mark per surface, always meaning "this one". */
html[data-theme="light"] .pb-wrapper .diff-segments {
    display: inline-flex;
    gap: 4px;
}

html[data-theme="light"] .pb-wrapper .diff-seg {
    min-height: 36px;
    padding: 6px 16px;
    border: 0;
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink-dim);
    font-size: 0.875rem;
    font-weight: var(--fw-label);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .diff-seg:hover:not(:disabled) {
    background: var(--surface);
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

html[data-theme="light"] .pb-wrapper .diff-seg.on,
html[data-theme="light"] .pb-wrapper .diff-seg[aria-pressed="true"],
html[data-theme="light"] .pb-wrapper .diff-seg.active {
    background: var(--accent);
    color: #fff;
}

/* Same shape for the Snabb / Avancerad mode switch — it is the same kind of decision, so it should
   not look like a different kind of control. */
html[data-theme="light"] .pb-wrapper .pc-mode {
    display: inline-flex;
    gap: 4px;
}

html[data-theme="light"] .pb-wrapper .pc-mode-btn {
    min-height: 36px;
    padding: 6px 16px;
    border: 0;
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink-dim);
    font-size: 0.875rem;
    font-weight: var(--fw-label);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .pc-mode-btn.active {
    background: var(--accent);
    color: #fff;
}

/* ── The fields themselves ────────────────────────────────────────────────────────────────────
   `.pc-field input` and `.pc-field textarea` carried their own grey fill and border at (0,2,0),
   which beat the shared field recipe — so every input on this panel rendered as a grey well while
   every other input in the theme was white. Re-pointed at the measured recipe: white, a 1px navy
   SPREAD shadow for the edge, 4px radius, and the cyan ring pre-declared at alpha 0 so focus
   cross-fades rather than snapping. */
html[data-theme="light"] .pb-wrapper .pc-field input,
html[data-theme="light"] .pb-wrapper .pc-field textarea,
html[data-theme="light"] .pb-wrapper .pc-admin-box {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink);
    font-size: 0.875rem;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.16),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0),
        0 1px 1px rgba(0, 0, 0, 0.08);
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .pc-field input:focus,
html[data-theme="light"] .pb-wrapper .pc-field textarea:focus,
html[data-theme="light"] .pb-wrapper .pc-admin-box:focus-within {
    outline: 0;
    border-color: transparent;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0.20),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0.25),
        0 1px 1px rgba(0, 0, 0, 0.08);
}

/* The admin-time box IS the field now, so the input inside it carries no second edge and the unit
   sits in it as a suffix rather than as a grey band running the width of the row. */
html[data-theme="light"] .pb-wrapper .pc-admin-box input {
    box-shadow: none;
    background: none;
    padding-right: 4px;
}

html[data-theme="light"] .pb-wrapper .pc-admin-box em {
    color: var(--ink-muted);
    font-size: 0.8125rem;
}

/* The hint under a field is a fact about the default, not a warning about it. */
html[data-theme="light"] .pb-wrapper .pc-hint {
    color: var(--ink-muted);
    font-size: 0.8125rem;
}

/* The mechanic picker is a select — a select is styled as a button in the measured spec (pressable
   things float, typable things sit), so it keeps one edge and drops the wrapper's. */
html[data-theme="light"] .pb-wrapper .mp-wrap,
html[data-theme="light"] .pb-wrapper .mp-root {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TELEFON — the job-details panel
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The worst box-in-box on the page, and every fault in it is the same fault: a FIELD GROUP was
   given the costume of a CARD. `.lift-toggle` and `.diff-toggle` are bordered, filled frames whose
   only content is a switch and three buttons; `.pc-admin-box` is a bordered box wrapping an input
   that has had its own border removed to fit inside it. So the page draws a frame, then draws
   another frame inside it, then removes the edge from the control that actually needed one.

   The rule, from the measured spec: a control has an edge because it is a control. The group AROUND
   controls is a label and some space. Nothing else on this panel earns a frame. */

/* The group frames stand down. */
html[data-theme="light"] .pb-wrapper .lift-toggle,
html[data-theme="light"] .pb-wrapper .diff-toggle {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
}

html[data-theme="light"] .pb-wrapper .lift-toggle:hover,
html[data-theme="light"] .pb-wrapper .diff-toggle:hover {
    background: none;
    border-color: transparent;
}

html[data-theme="light"] .pb-wrapper .lift-label,
html[data-theme="light"] .pb-wrapper .diff-label,
html[data-theme="light"] .pb-wrapper .pc-field > span {
    font-size: 0.8125rem;
    font-weight: var(--fw-label);
    color: var(--ink-muted);
    letter-spacing: 0;
    text-transform: none;
}

/* ── The difficulty segments ──────────────────────────────────────────────────────────────────
   Three buttons where the chosen one was mint-green. Green means SUCCESS in this system, and
   picking "Enkel" is not a success — it is a selection. One accent fill marks the choice, which is
   the same move the nav rail makes for the current page and the ledger makes for the open row: one
   saturated mark per surface, always meaning "this one". */
html[data-theme="light"] .pb-wrapper .diff-segments {
    display: inline-flex;
    gap: 4px;
}

html[data-theme="light"] .pb-wrapper .diff-seg {
    min-height: 36px;
    padding: 6px 16px;
    border: 0;
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink-dim);
    font-size: 0.875rem;
    font-weight: var(--fw-label);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .diff-seg:hover:not(:disabled) {
    background: var(--surface);
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

html[data-theme="light"] .pb-wrapper .diff-seg.on,
html[data-theme="light"] .pb-wrapper .diff-seg[aria-pressed="true"],
html[data-theme="light"] .pb-wrapper .diff-seg.active {
    background: var(--accent);
    color: #fff;
}

/* Same shape for the Snabb / Avancerad mode switch — it is the same kind of decision, so it should
   not look like a different kind of control. */
html[data-theme="light"] .pb-wrapper .pc-mode {
    display: inline-flex;
    gap: 4px;
}

html[data-theme="light"] .pb-wrapper .pc-mode-btn {
    min-height: 36px;
    padding: 6px 16px;
    border: 0;
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink-dim);
    font-size: 0.875rem;
    font-weight: var(--fw-label);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .pc-mode-btn.active {
    background: var(--accent);
    color: #fff;
}

/* ── The fields themselves ────────────────────────────────────────────────────────────────────
   `.pc-field input` and `.pc-field textarea` carried their own grey fill and border at (0,2,0),
   which beat the shared field recipe — so every input on this panel rendered as a grey well while
   every other input in the theme was white. Re-pointed at the measured recipe: white, a 1px navy
   SPREAD shadow for the edge, 4px radius, and the cyan ring pre-declared at alpha 0 so focus
   cross-fades rather than snapping. */
html[data-theme="light"] .pb-wrapper .pc-field input,
html[data-theme="light"] .pb-wrapper .pc-field textarea,
html[data-theme="light"] .pb-wrapper .pc-admin-box {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink);
    font-size: 0.875rem;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.16),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0),
        0 1px 1px rgba(0, 0, 0, 0.08);
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .pb-wrapper .pc-field input:focus,
html[data-theme="light"] .pb-wrapper .pc-field textarea:focus,
html[data-theme="light"] .pb-wrapper .pc-admin-box:focus-within {
    outline: 0;
    border-color: transparent;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0.20),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0.25),
        0 1px 1px rgba(0, 0, 0, 0.08);
}

/* The admin-time box IS the field now, so the input inside it carries no second edge and the unit
   sits in it as a suffix rather than as a grey band running the width of the row. */
html[data-theme="light"] .pb-wrapper .pc-admin-box input {
    box-shadow: none;
    background: none;
    padding-right: 4px;
}

html[data-theme="light"] .pb-wrapper .pc-admin-box em {
    color: var(--ink-muted);
    font-size: 0.8125rem;
}

/* The hint under a field is a fact about the default, not a warning about it. */
html[data-theme="light"] .pb-wrapper .pc-hint {
    color: var(--ink-muted);
    font-size: 0.8125rem;
}

/* The mechanic picker is a select — a select is styled as a button in the measured spec (pressable
   things float, typable things sit), so it keeps one edge and drops the wrapper's. */
html[data-theme="light"] .pb-wrapper .mp-wrap,
html[data-theme="light"] .pb-wrapper .mp-root {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   P3 — RUTINER
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The page a previous audit called the worst offender still standing, and rendered it is mostly one
   fault repeated: things that are LABELS wearing the costume of things that are CONTROLS or STATUSES.
   Ten uppercase tracked labels, a gradient rule, two coloured section headings, a capsule on a
   read-only tag, and a bordered card around a list that is already bounded by its own rows. */

/* ── 1. THE LABELS STOP SHOUTING ───────────────────────────────────────────────────────────────
   Rutiner, Sommar, Sep./Okt./Maj, Sommar/Vinter/Sommar, Försenat, September, Oktober, Maj — and
   then Vinter and Sommar again on every row. In a Swedish interface that is a lot of caps on a page
   whose whole job is to be scanned down a date column. 11px/500 sentence case, measured. */
html[data-theme="light"] .rt-board .rt-bucket-name,
html[data-theme="light"] .rt-board .rt-strip-season,
html[data-theme="light"] .rt-board .rt-strip-zone,
html[data-theme="light"] .rt-board .rt-mini-label,
html[data-theme="light"] .rt-board .rt-season {
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

/* ── 2. THE SEASON STRIP LOSES ITS HUES, AND THAT FIXES A REAL DEFECT ──────────────────────────
   Two problems in one element. The runs are a two-stop gradient — a fade exists because a hard line
   reads as a scratch on near-black, and on white it is a smear. And the seasons were carried by hue
   alone, teal against blue-violet, which is the pair a previous colour-vision pass measured at a
   ΔE that two of the app's status hues also failed on.

   Both go, and the second is the interesting one: THE STRIP DOES NOT NEED HUE. Every run is already
   labelled in words directly beneath it. Colour was repeating what the type had said, and repeating
   it in the one channel that a colour-blind mechanic cannot read. Neutral tones for the runs, ink
   for the season you are actually in, and the labels do the naming. */
html[data-theme="light"] .rt-board .rt-strip-track {
    background: var(--surface-inset);
    background-image: none;
    box-shadow: inset 0 0 0 1px var(--keyline);
}

html[data-theme="light"] .rt-board .rt-strip-run {
    background: var(--keyline-strong);
    background-image: none;
}

html[data-theme="light"] .rt-board .rt-strip-run.sommar,
html[data-theme="light"] .rt-board .rt-strip-run.summer {
    background: var(--ink-dim);
    background-image: none;
}

html[data-theme="light"] .rt-board .rt-strip-today {
    background: var(--accent);
    background-image: none;
}

/* ── 3. THE LIST IS A LIST ─────────────────────────────────────────────────────────────────────
   A bordered, rounded container around rows that already terminate themselves with hairlines. On a
   white page that frame is a box drawn for its own sake — depth spent on the one object here that
   is not raised off anything. */
html[data-theme="light"] .rt-board .rt-list,
html[data-theme="light"] .rt-board .rt-board-main {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

html[data-theme="light"] .rt-board .rt-row {
    border-bottom: 0;
    box-shadow: inset 0 -1px 0 0 var(--keyline);
    transition: background-color 0.15s ease;
}

html[data-theme="light"] .rt-board .rt-row:hover {
    background: var(--surface-inset);
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}

/* ── 4. THE TAG IS NOT A CONTROL ───────────────────────────────────────────────────────────────
   `Däck` is a read-only category, and it was wearing a capsule. Roundness carries meaning: the
   measured system ships a pill on exactly one component, its interactive filter Chip, and its own
   runtime error says so — "Chip must have at least one callback. If you are trying to render
   read-only data, you might want Badge or Signpost." A capsule that cannot be pressed reads as a
   button that is broken.

   ONLY the radius is touched. The tag's fill and ink come from `tagChipStyle(t)` as an inline
   `:style` binding — they are DATA, chosen per tag by the shop, not theme — and an inline style
   cannot be overridden from a stylesheet at all. Reaching for `!important` here would be fighting
   the shop's own colour choice, which is not this pass's to make. */
html[data-theme="light"] .rt-board .rt-tag,
html[data-theme="light"] .rt-board .rt-tag-static {
    border-radius: var(--r-control);
}

/* ── 5. THE RAIL TOGGLE STOPS FLOATING ─────────────────────────────────────────────────────────
   A small bordered box parked in the margin to the right of the list, holding one glyph. It reads as
   a fragment of some other panel that failed to render. It is a control, so it takes the control
   grammar — the measured 1px navy spread shadow rather than a border, with the deep layer
   pre-declared at alpha 0 so the hover interpolates instead of snapping. */
html[data-theme="light"] .rt-board .rt-rail-toggle,
html[data-theme="light"] .rt-board .rt-rail:not(.rail-open) {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .rt-board .rt-rail-toggle:hover {
    background: var(--surface);
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

/* ── 6. THE PAGE EYEBROW ───────────────────────────────────────────────────────────────────────
   A filled chip around the word "Rutiner", directly above a heading that says "Kund rutiner". A
   label, not a status, so it loses the fill — and the string itself is capitalised in the table, so
   the case has to be corrected in CSS the same way the calendar's was. `::first-letter` needs a
   block container, which is why the display change is load-bearing rather than cosmetic. */
html[data-theme="light"] .rt-board .rt-header .badge {
    display: inline-block;
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
    text-transform: lowercase;
}

html[data-theme="light"] .rt-board .rt-header .badge::first-letter {
    text-transform: uppercase;
}

/* ── P3b. THE ANCESTOR WAS WRONG ───────────────────────────────────────────────────────────────
   Half the block above reached nothing: the page root is `.rt-wrapper`, and `.rt-board` wraps only
   the list beneath the strip. The bucket headings changed and everything in the header and the
   season strip did not, which is exactly the shape of a scoping miss — the rules parse, the page
   renders, and only a screenshot says which half landed.

   Restated against the real root. Kept as a correction rather than edited in place, because the
   pattern is worth leaving visible: a light rule that changes nothing looks identical to a light
   rule that was never written. */
html[data-theme="light"] .rt-wrapper .rt-bucket-name,
html[data-theme="light"] .rt-wrapper .rt-strip-season,
html[data-theme="light"] .rt-wrapper .rt-strip-zone,
html[data-theme="light"] .rt-wrapper .rt-strip-n,
html[data-theme="light"] .rt-wrapper .rt-mini-label,
html[data-theme="light"] .rt-wrapper .rt-season {
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

/* The season labels under the strip were also ACCENT-COLOURED — a coloured section label, which on
   white reads as a highlighter mark and spends the accent on something that cannot be clicked. The
   figure beside each month keeps the ink; the word does not need it. */
html[data-theme="light"] .rt-wrapper .rt-strip-n .fig {
    color: var(--ink);
    font-weight: var(--fw-label);
}

/* The strip itself. Gradient runs go flat, and the hues go entirely — every run is already named in
   words directly beneath it, so colour was repeating the type in the one channel a colour-blind
   mechanic cannot read. */
html[data-theme="light"] .rt-wrapper .rt-strip-track {
    background: var(--surface-inset);
    background-image: none;
    box-shadow: inset 0 0 0 1px var(--keyline);
}

html[data-theme="light"] .rt-wrapper .rt-strip-run {
    background: var(--keyline-strong);
    background-image: none;
}

html[data-theme="light"] .rt-wrapper .rt-strip-run.sommar,
html[data-theme="light"] .rt-wrapper .rt-strip-run.summer {
    background: var(--ink-dim);
    background-image: none;
}

html[data-theme="light"] .rt-wrapper .rt-strip-today {
    background: var(--accent);
    background-image: none;
}

/* The page eyebrow: a chip around a word, sitting directly above a heading that says the same thing.
   A label, not a status. The string is capitalised at source, so the case is corrected here the same
   way the calendar's was — and `::first-letter` needs a block container, which is why the display
   change is load-bearing rather than cosmetic. */
html[data-theme="light"] .rt-wrapper .rt-header .badge {
    display: inline-block;
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
    text-transform: lowercase;
}

html[data-theme="light"] .rt-wrapper .rt-header .badge::first-letter {
    text-transform: uppercase;
}

/* The current-season tag in the hero gets the same treatment: it states a fact, so it is type. */
html[data-theme="light"] .rt-wrapper .rt-hero-sub .rt-season {
    display: inline-block;
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
    text-transform: lowercase;
}

html[data-theme="light"] .rt-wrapper .rt-hero-sub .rt-season::first-letter {
    text-transform: uppercase;
}

/* The rail toggle, parked in the right margin as a small bordered box holding one glyph — it reads
   as a fragment of a panel that failed to render. It is a control, so it takes the control grammar. */
html[data-theme="light"] .rt-wrapper .rt-rail-toggle {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .rt-wrapper .rt-rail-toggle:hover {
    background: var(--surface);
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

html[data-theme="light"] .rt-wrapper .rt-rail:not(.rail-open) {
    background: none;
    border: 0;
    box-shadow: none;
}

/* Two last corrections, both from reading the markup after the screenshot disagreed with me.

   TWO CORRECTIONS TO ME, both established with the computed-style probe rather than by reading, and
   both worth recording because I had each of them wrong in a different way.

   1. `Försenat` was not capitalised at source. `localization.ts:3992` reads
      `routines_bk_overdue: { sv: 'Försenat' }` — already sentence case. The shouting was entirely
      CSS, from the `.rt-bucket.overdue .rt-bucket-name` branch further up this file at (0,4,1),
      which my (0,3,1) override could never reach. Fixed at that block instead, and the
      lowercase + `::first-letter` machinery deleted: it was solving a problem that did not exist.

      The tell was in the computed style all along — `letter-spacing: 0.66px`, which is
      `--eyebrow-track` at 11px. My rule asked for 0 and 0 is not what rendered, so the rule was
      losing rather than mis-scoped. That single number would have saved two guesses.

   2. The rail's frame was never a survival. `.rt-rail` computes with no background and no shadow;
      the box in the margin IS the toggle button, wearing the 1px control ring I deliberately gave it
      forty lines above. My `:not(.rail-open)` note was true CSS trivia about the wrong element.

      The ring stays. A button gets an edge because it is a button, and retracting it would leave a
      bare glyph with no affordance — the fault I read into the screenshot was isolation, not the
      ring, and isolation is a layout question this pass is not opening. */
html[data-theme="light"] .rt-wrapper .rt-rail-toggle {
    letter-spacing: 0;
    text-transform: none;
    font-size: 0.6875rem;
}
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   PHASES 11-13 — COMPLAINTS · DROP-OFFS · VEHICLE STORAGE (light only)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CLUSTER FILE: all three belong in `public/theme-light/boards.css`. Verified two ways —
     · roots are `.cx-wrap` (ComplaintsDesktop.vue:2), `.dropoffs-wrapper` (DropoffsDesktop.vue:2)
       and `.vs-wrapper` (VehicleStorageDesktop.vue:2), all board shells, none of them `.jobs-*`;
     · boards.css ALREADY carries a section for each — "COMPLAINTS — the compliance sweep" (:1869),
       "DROP-OFFS" (:1994), "VEHICLE STORAGE" (:2176). This block is the DESIGN pass that sits
       after them. Nothing here restates a rule those sections already made.

   NAME COLLISION, READ BEFORE MOVING ANY RULE. The `.cx-` prefix is shared by TWO pages:
   Customers is `.cx-wrapper` (Customers.vue:2) and Complaints is `.cx-wrap`. `.cx-header` and
   `.cx-sheet-head` are declared in BOTH sheets. `.cx-wrap` cannot match `.cx-wrapper` (class
   selectors match whole tokens), so scoping complaints rules to `.cx-wrap` is what keeps this
   block off the customers board. Every rule below that touches a possibly-shared name is scoped.

   The shared board-table primitive is `.bt-*` (`.bt-cols`, `.bt-row`, `.bt-group-head`,
   `.bt-flag`, `.bt-more`) and it is used by BOTH drop-offs and vehicle storage — declared in
   DropoffsDesktop.css and VehicleStorageDesktop.css, nowhere else (grep --include=*.css).
   Rules on it are written once, in the SHARED section, not twice.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   F0. THE SHARED BOARD TABLE — `.bt-*`, drop-offs and vehicle storage both
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── F0.1 THE COLUMN HEADS ────────────────────────────────────────────────────────────────────
   MEASURED, not reasoned: `.bt-cols` computes `text-transform: uppercase; letter-spacing: 1.1px;
   font-size: 11px` and `border-radius: 4px 4px 0 0`.

   Two faults in one element. The tracked uppercase head is the idiom Stripe is retiring — their
   own header is 11px/500 SENTENCE CASE at letter-spacing 0, and the entire 3.6 MB shipped sheet
   contains nine letter-spacing declarations, none on a table header. And the top-rounded corners
   are the last trace of a card that no longer exists: a header rule sitting directly on a white
   page with two rounded shoulders reads as the lid of a box that was deleted.

   `text-transform: none` is enough here because the STRINGS are already sentence case —
   `storage_col_plate: 'Reg.nr'`, `storage_col_vehicle: 'Fordon'`, `storage_col_work: 'Arbete'`
   (localization.ts:3765-3768). No ::first-letter trick needed, and none is written: it would be
   a no-op that later reads as load-bearing. */
html[data-theme="light"] .bt-cols {
    text-transform: none;
    letter-spacing: 0;
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    color: var(--ink-muted);
    border-radius: 0;
    border-bottom-color: var(--keyline);
}

/* ── F0.2 THE BUCKET HEADING ──────────────────────────────────────────────────────────────────
   `.bt-group-name` is 13px/600 in `--ink-dim` — a heading that competes with the row content
   under it. _base's superseding section-heading rule (`_base.css:916`) names `.lx-bucket
   .lx-bucket-name` and nothing else, so every board that does not use the ledger's class names
   was left out of it. This is that rule, applied to the shared board's own name.

   The COUNT beside it goes `--ink-muted` and stays there: _base's own clause — "counts and sums
   beside a heading are --ink-muted, never a status colour: a number is not a status". */
html[data-theme="light"] .bt-group-head .bt-group-name {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}

/* ── F0.3 THE ROW FLAG STILL GLOWS ────────────────────────────────────────────────────────────
   MEASURED: `.bt-flag.expected` computes `box-shadow: rgba(184,124,5,0.45) 0 0 8px` and
   `.vs-row .lx-flag.overdue` computes `rgba(185,28,28,0.5) 0 0 8px`. A coloured glow needs
   darkness to read as emission; on white it is a smudge with a hard dot in the middle of it.

   boards.css:684 already rebuilt `.lx-flag` as a 16px glyph badge, but scoped it to
   `.wq-wrapper .wq-row` — so the identical dot on these two boards kept the dark geometry. The
   full badge rebuild is NOT copied here: `.bt-flag` is a bare <span> occupying a grid column
   sized for a 6px dot (`.bt-cols` and `.bt-row` share one grid template), so widening it to 16px
   moves every column on both boards. Killing the glow is the part that is both correct and safe;
   the badge promotion is written up as an escalation instead. */
html[data-theme="light"] .bt-flag,
html[data-theme="light"] .vs-row .lx-flag,
html[data-theme="light"] .do-row .lx-flag {
    box-shadow: none;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   F1. DROP-OFFS — the takt cell, and the verdict stated three times
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   THE PAGE'S ONE PRIMARY is the hero line: "3 bilar inne" over the occupancy meter. Everything
   below it is a table. There is no second thing competing for it, and the header composition
   holds at 1900px — so the whole design job on this page is the takt column.

   AND THE TAKT COLUMN IS THE CANONICAL FAILURE. Measured off the live board:

       .tk.behind .tk-fill   background-color: rgb(138, 85, 0)     ← the bar
       .tk.behind .tk-word   color:            rgb(152, 94, 0)     ← "1tim efter plan"
       .bt-flag.expected     background-color: rgb(138, 85, 0)     ← the row's leading dot

   That is #8a5500 — mustard. It is the `--st-*` text ramp used as a FILL, which the handoff warns
   about in two separate places (§8: "a bar owes 3:1 under SC 1.4.11, not the 4.5:1 text owes, so
   the --st-* text tokens overshoot into olive-brown"), and it is the amber the programme has
   already ruled has no good light value. Both rows on the board are `behind`. A colour on every
   row carries nothing, and this one carries nothing in mustard. */

/* TaktCell.vue is `<style scoped>`, so its own selectors carry `[data-v-*]`: `.tk.behind
   .tk-fill[data-v-x]` is (0,4,0), which TIES `html[data-theme="light"] .tk.behind .tk-fill` —
   and the scoped sheet is injected at runtime, so on a tie it wins and the override silently
   does nothing (trap 5, and trap 11 in the handoff).

   `:not(.done)` is the fourth compound. It costs (0,1,0) and takes these to (0,5,0), and it is
   semantically free: `done` and `behind` are mutually exclusive states of the same enum
   (PaceState), so the guard can never exclude a row it was meant to reach. This is deliberately
   NOT scoped to `.dropoffs-wrapper` — the same component renders on the tablet and phone shells
   under different roots, and one mustard bar is one too many on any of them. */

/* The track had no boundary at all: `rgba(27, 35, 61, 0.09)` on white is a suggestion of a bar.
   An empty meter has to read as empty rather than as absent. */
html[data-theme="light"] .tk:not(.done) .tk-track {
    background: var(--surface-inset);
    box-shadow: inset 0 0 0 1px var(--keyline-strong);
}

/* THE ORDINARY CASE GOES NEUTRAL. "Behind the pace" is the normal state of a multi-day car that is
   part-way through its own estimate — it is the board's baseline, not its alarm. The bar keeps
   saying HOW MUCH (the fill width against the tick), which is geometry and survives any colour
   vision; it stops saying it in a hue.

   `held` goes with it, and for a stronger reason: held means the shop has declared it does not own
   the delay, so marking it in a warning colour states the opposite of what it means.

   `--ink-muted` and not `--ink-dim`: at `--ink-dim` (#273951) the bar rendered as the DARKEST object
   in the row — darker than the plate, darker than the customer's name — so neutralising the hue
   simply moved the shouting from colour to weight. Seen and corrected on the first injected render.
   #697386 reads unambiguously against the #f3f6f8 track and stays behind the row's own words. */
html[data-theme="light"] .tk.behind:not(.done) .tk-fill,
html[data-theme="light"] .tk.held:not(.done) .tk-fill {
    background: var(--ink-muted);
}

html[data-theme="light"] .tk.behind:not(.done) .tk-word,
html[data-theme="light"] .tk.held:not(.done) .tk-word {
    color: var(--ink-dim);
}

/* Overdue is the one worth acting on and keeps the hue — but as a METER token, not the text one.
   `--meter-bad` is #dc2626 in light and is tuned for the 3:1 a graphical object owes; `--st-breach`
   is tuned for 4.5:1 text and renders a bar a step too dark and a shade off the word beside it. */
html[data-theme="light"] .tk.overdue:not(.done) .tk-fill {
    background: var(--meter-bad);
}

/* Ahead and finished are good news and a minority, so they keep green — again the meter token, not
   the text one, for the same 3:1-vs-4.5:1 reason. */
html[data-theme="light"] .tk.ahead:not(.behind) .tk-fill,
html[data-theme="light"] .tk.done:not(.behind) .tk-fill {
    background: var(--meter-ok);
}

/* The row's leading dot is the third statement of the same verdict on the same row. It keeps its
   position (the scan column) and gives up its hue for the ordinary state, exactly as the fill did.
   `overdue` is left alone: that dot is the one the eye is scanning FOR. */
html[data-theme="light"] .bt-flag.expected {
    background: var(--keyline-strong);
}

/* ── F1.2 THE HEADLINE COUNT, TWICE ───────────────────────────────────────────────────────────
   "2 kräver beslut" is printed in `--st-breach` in the hero (`.do-hero-stat.breach`) and again
   130px below it as "· 2 kräver beslut" in the result line (`.do-result-urgent`). One fact, one
   colour, two places, both above the fold.

   The hero keeps it — that is the page's verdict and its one loud thing. The result line is a
   description of what the filter left standing, i.e. a count, and takes ink. */
html[data-theme="light"] .do-bar-result .do-result-urgent {
    color: var(--ink-dim);
}

/* ── F1.3 THE READY-BY DATE IS NOT A VERDICT ──────────────────────────────────────────────────
   `.do-c-ready.over` reprints the overdue state as a red DATE. The date is data — "mån 10 aug." is
   true whether or not the car is late — and the takt cell two columns to its left has already said
   it in a word, a width and a colour. Ink, tabular, and the column reads as a column again. */
html[data-theme="light"] .do-row .do-c-ready.over {
    color: var(--ink-dim);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   F2. VEHICLE STORAGE
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   THE PAGE'S ONE PRIMARY is "Checka in bil", filled, top right — correct, and the only filled
   button on the surface. The composition holds at 1900px.

   THE FAULT IS THE VERDICT, and it is worse here than on drop-offs because every row on the board
   is in the same state. "Över tid" is stated FIVE times per row-group:

       .vs-row .lx-flag.overdue   red dot + red glow          (left scan column)
       .vs-state-over             the badge — already neutral, red bullet only
       .vs-c-time.over            rgb(185,28,28) — "11d"      (right edge)
       .vs-group-over             rgb(185,28,28) — "3 över tid" on the group heading
       .vs-hero-stat.breach       rgb(185,28,28) — "4 över sin tid" in the hero

   Two carriers survive: the flag (colour at the scan edge) and the hero count (the page's verdict).
   The two in between are a DURATION and a COUNT — neither is a status. */

/* ── F2.1 THE SEARCH ICON SITS ON TOP OF THE PLACEHOLDER — a real light-only bug ───────────────
   VISIBLE IN THE SCREENSHOT and confirmed by computed style. `.vs-search-bar input` is a bare
   `input[type="text"]`, so it is matched by _base.css:868's control-layer rule at (0,2,1):

       html[data-theme="light"] input[type="text"] { padding: 4px 7px 2px; ... }

   The page's own `.vs-search-bar input { padding: var(--sp-3) var(--sp-6); }`
   (VehicleStorageDesktop.css:766) is (0,1,1) and loses. That `--sp-6` left padding is the reserve
   the absolutely-positioned `.vs-search-ic` sits in — so in light, and only in light, the
   magnifier is drawn through the word "Sök". Restore the reserve; the control layer's edge,
   radius, fill and transition are untouched. */
html[data-theme="light"] .vs-search-bar input[type="text"] {
    padding: var(--sp-3) var(--sp-6);
}

/* ── F2.2 THE LABEL CASE ──────────────────────────────────────────────────────────────────────
   Five families of uppercase tracked label on one board, all measured:

       .vs-col-h        13px / uppercase / 0.78px   ← and 13px is BODY size for a column head
       .vs-group-head   11px / uppercase / 0.66px
       .vs-places-label 11px / uppercase / 0.66px
       .vs-sort-label   11px / uppercase / 1.10px
       .vs-chip         11px / uppercase / 0.40px

   All of them take the same 11px sentence-case treatment. The strings are already sentence case in
   the table (`storage_col_plate: 'Reg.nr'`, `storage_places_label: 'Platser'`,
   `storage_sort_label: 'Sortera'`, localization.ts:3762-3773), so `text-transform: none` is
   sufficient and the ::first-letter dance is not needed — except for one, handled below.

   `.vs-group-head` also carries DATA ("Garage 1"), which is why it must be `none` and not
   `lowercase`: lowercasing a bay's name is a change to the shop's own vocabulary. */
html[data-theme="light"] .vs-col-h,
html[data-theme="light"] .vs-group-head,
html[data-theme="light"] .vs-places-label,
html[data-theme="light"] .vs-sort-label {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}

/* `.vs-group-name` is the bay's name and is the one thing on that line worth reading, so it keeps
   a step of ink over the count and the flag beside it. Not a size step — a heading that is bigger
   AND darker than its own count is two emphases for one job. */
html[data-theme="light"] .vs-group-head .vs-group-name {
    color: var(--ink-dim);
}

/* THE ONE THAT NEEDS THE TRICK. `storage_dropoff_chip` is `'inlämning'` — lower case in the source
   (localization.ts:3803) — so `text-transform: none` would render "inlämning" mid-row. Lowercase
   plus a capitalised ::first-letter gives sentence case without touching the string table.
   `display: inline-block` is load-bearing: `.vs-chip` is an <i>, and ::first-letter only applies
   inside a block container. The first character is 'i', a letter, so the pseudo-element resolves. */
html[data-theme="light"] .vs-chip {
    display: inline-block;
    text-transform: lowercase;
    letter-spacing: 0;
}

html[data-theme="light"] .vs-chip::first-letter {
    text-transform: uppercase;
}

/* ── F2.3 THE VERDICT, TWICE REMOVED ──────────────────────────────────────────────────────────
   A dwell figure is a duration. "11d" is how long the car has stood here — a fact that is equally
   true of a car that is on time — and printing it in breach red makes the right-hand column look
   like a column of alarms rather than a column of numbers. It also destroys the one thing a figure
   column is for: being compared down its own length.

   The count on the group heading goes for the reason _base states outright: a number is not a
   status, and a count beside a heading is `--ink-muted`. Folding a group summarises what is inside
   it; the summary does not need to shout to do that. */
html[data-theme="light"] .vs-row .vs-c-time.over {
    color: var(--ink-dim);
}

html[data-theme="light"] .vs-group-head .vs-group-over {
    color: var(--ink-muted);
    text-transform: none;
    letter-spacing: 0;
}

/* ── F2.3b THE FLAG GOES TOO — the "two carriers survive" call above was one carrier too many ──
   F2.3 kept `.lx-flag.overdue` as the scan-edge carrier on the reasoning that a status belongs at
   the left rail. That is right when overdue is the exception. MEASURED on the live board it is not:
   4 of 4 rows render `i.lx-flag.overdue` at `background rgb(185,28,28)` (listLedgerStyle.ts:189,
   `--st-breach`). A hue on 100% of rows separates nothing from nothing — it is a red column, not a
   flag — and with the other three carriers already neutralised each row now says "över tid" four
   times, three of them quietly and one of them in alarm red, which reads as the three quiet ones
   being a different and lesser fact.

   `--ink-dim` rather than removing the dot: the dot's OTHER job is marking which rows have a due
   state at all (`.lx-flag.waiting` exists for 'soon', and a row with neither renders no <i>), and
   that distinction is still real. It just does not owe a colour.

   SCOPED TO `.vs-row` ON PURPOSE. On the Work Queue the same class IS the exception — boards.css
   :733 gives `.wq-wrapper .lx-flag.overdue` the breach treatment and that stays exactly as it is.
   The colour is wrong here because of what this board's data looks like, not because the token is
   wrong. `.vs-hero-stat.breach` also keeps `rgb(185,28,28)`: "4 över sin tid" is the page's verdict
   and the one place the fact is stated ABOUT the board rather than repeated on every row of it. */
html[data-theme="light"] .vs-row .lx-flag.overdue {
    background: var(--ink-dim);
}

/* ── F2.4 THE HERO METER IS A RULE ACROSS THE PAGE ────────────────────────────────────────────
   MEASURED: `.vs-hero-meter` renders 1208px wide (the entire page measure — and now 1560, which
   makes it worse) at 4px tall, with the fill in raw ink rather than on the meter ramp. Its twin on
   /dropoffs is 260 x 6. Two occupancy meters, one idiom, two shapes.

   The 260 is not arbitrary and the argument is already written, at DropoffsDesktop.css:151: "a lot
   that is 3% full shows a 3% fill of something you can see the end of. Across the full measure the
   same fill was a 40px scratch at the far left of a 1240px rule, which reads as damage rather than
   as a reading." /vehicle-storage is the page that proves it — 4 cars of 180 places is 2.2%, so at
   full measure the fill IS a scratch, and at 4px tall it is a scratch one pixel thinner than the
   drop-offs one. Sized to the phrase it belongs to, both boards state occupancy the same way.

   The fills come off `--meter-*`, per D1: the base state stays NEUTRAL (occupancy is not progress
   toward the shop's goal, and a full shop is not a win) and only the breach state spends a hue.
   `background-image: none` is required, not tidy — the page sheet paints a two-stop gradient and a
   `background` shorthand would be overridden by nothing if the image were left standing. */
html[data-theme="light"] .vs-hero-meter {
    width: 260px;
    max-width: 100%;
    height: 6px;
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .vs-hero-meter > i {
    background: var(--ink-dim);
    background-image: none;
}

html[data-theme="light"] .vs-hero-meter.over > i {
    background: var(--meter-bad);
    background-image: none;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   F3. COMPLAINTS — two boxes on a white page, and six tracked labels inside them
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   THE PAGE'S ONE PRIMARY is "Markera åtgärdat" in the record pane — correct, filled, terminal, and
   the only filled button on the surface. Nothing needs doing to it.

   WHAT IS BOXED THAT SHOULD BE RULED — measured, both of them:

     .cx-list   background rgb(240,241,245) · border 1px · radius 4    ← a GREY panel on a white page
     .cx-pane   border 1px · box-shadow --elevation-2 · padding 32px   ← a border AND an elevation

   `.cx-pane` breaks the hard rule outright: a container has EITHER a border OR an elevation. And
   `.cx-list` is a frame around a list whose rows already terminate themselves with hairlines —
   the exact thing the vocabulary says not to draw.

   The `.cx-list` panel is not a scroll region, so the "two scroll regions both keep their border"
   recipe does not apply to it: it has `overflow: hidden` and no height cap
   (ComplaintsDesktop.css:234), and the page scrolls, not the list. Its stated reason for existing
   is in its own comment — "without one, the animated violet ground shows straight through the
   rows" — and light deleted the orbs, so the condition it was built for no longer exists here. */
html[data-theme="light"] .cx-wrap .cx-list {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

/* TRAP 8 COMPENSATION, and it is why this is two rules rather than one. `.cx-list > :last-child`
   drops the final hairline because the panel's own edge used to close the list
   (ComplaintsDesktop.css:242). With the panel gone that leaves the last row hanging into white
   with nothing under it — the list stops rather than ends. The rule comes back. */
html[data-theme="light"] .cx-wrap .cx-list > :last-child {
    border-bottom: 1px solid var(--keyline);
}

/* The record sheds the box too, and the pair separates the way _base already separates the shared
   ledger split (`_base.css:1065`): one vertical keyline down the middle, and the record's own
   padding measured off it. 32px of card padding becomes the rhythm's 20px — the gap between two
   REGIONS, which is the same step as the page gutter and one the eye already knows from every
   other page in this theme. */
html[data-theme="light"] .cx-wrap .cx-pane {
    background: transparent;
    border: 0;
    box-shadow: none;
    border-radius: 0;
    border-left: 1px solid var(--keyline);
    padding: 0 0 0 20px;
}

/* ── F3.2 THE ROW ─────────────────────────────────────────────────────────────────────────────
   MEASURED `padding: 6px 16px`. The vocabulary's floor is 12px, and 6px is what makes this list
   read as a wall of text rather than as rows — the vertical space inside a row is what tells the
   eye where one customer's words end and the next begins, and at 6px it is smaller than the gap
   between the two lines INSIDE a row. */
html[data-theme="light"] .cx-wrap .cx-row {
    padding-top: 12px;
    padding-bottom: 12px;
    border-bottom-color: var(--keyline);
}

/* States go DARKER in light. The page rule is `rgba(--ch-indigo-a78bfa, 0.05)` — an accent channel
   that inverts, i.e. a 5% wash of a PALE violet over white, which paints very nearly nothing. And
   Stripe's measured behaviour is that hover never changes colour at all: the background is
   re-declared and a tone step appears. */
html[data-theme="light"] .cx-wrap .cx-row:hover {
    background: var(--surface-inset);
}

/* ── F3.3 SIX FAMILIES OF TRACKED UPPERCASE LABEL ─────────────────────────────────────────────
   Counted on the rendered record: TOLKAT SOM · VAD SOM HÄNT · VAD SOM GJORDES ÅT DET ·
   BYTE AV STÖTDÄMPARE · ALLVARLIGT · JOBBET · ÄLDRE ÄN EN VECKA. Seven strings, five classes.

   `.cx-bucket-name` is the instructive one: boards.css:348 DOES list it, in the §5 eyebrow block —
   but that block predates the sentence-case reversal, and _base's superseding rule (`_base.css:916`)
   names `.lx-bucket .lx-bucket-name` only. So the complaints bucket kept the retired idiom because
   the supersession was written against one page's class names. Exactly trap 3.

   All the strings behind these are sentence case in the table (`complaints_note_label:
   'Vad som gjordes åt det'`, `complaints_triage: 'Read as' / 'Tolkat som'`, localization.ts:561,
   568), so `text-transform: none` does the work. */
html[data-theme="light"] .cx-wrap .cx-sec-label,
html[data-theme="light"] .cx-wrap .cx-triage-label,
html[data-theme="light"] .cx-wrap .cx-note-label,
html[data-theme="light"] .cx-wrap .cx-resolution-when,
html[data-theme="light"] .cx-wrap .cx-bucket .cx-bucket-name,
html[data-theme="light"] .cx-wrap .cx-tag,
html[data-theme="light"] .cx-wrap .cx-work {
    font-size: var(--eyebrow-size);
    text-transform: none;
    letter-spacing: 0;
}

/* `.cx-work` is the JOB TITLE ("Byte av stötdämpare") — data, and sentence case as stored, so it
   only needed the case rule above. `.cx-tag` carries category and severity words which may arrive
   lower case from `categoryLabel()`; the ::first-letter pair is cheap insurance and the class is
   a <span>, so it needs the block context stated. */
html[data-theme="light"] .cx-wrap .cx-tag {
    display: inline-block;
}

html[data-theme="light"] .cx-wrap .cx-tag::first-letter {
    text-transform: uppercase;
}

/* ── F3.4 THE PAGE EYEBROW — WORKAROUND RETIRED, THE STRING WAS FIXED AT SOURCE ───────────────
   This used to read `text-transform: lowercase` with a `::first-letter` rule raising the initial
   back, because `complaints_eyebrow` was `{ en: 'WHAT CUSTOMERS SAID', sv: 'VAD KUNDERNA SA' }` —
   capitals in the DATA, which `text-transform: none` cannot touch.

   localization.ts:447 is now sentence case, so `none` is enough and both halves of the workaround
   are gone. The change is dark-safe by inspection AND by construction: `.cx-eyebrow` declares
   `text-transform: uppercase` in the page sheet (ComplaintsDesktop.css:46), which is unprefixed and
   therefore still in force in dark — dark re-shouts the string and renders exactly as before.

   `text-transform: none` is still REQUIRED here and must not be deleted as redundant: §5.2 above
   lists `.cx-eyebrow` among the (0,2,0) eyebrow group that sets `uppercase` for light as well, so
   without this (0,3,0) rule the light page would shout a string that is no longer shouting. */
html[data-theme="light"] .cx-wrap .cx-eyebrow {
    text-transform: none;
    letter-spacing: 0;
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
}

/* ── F3.5 THE EMPTY STATE IS A BOX ────────────────────────────────────────────────────────────
   With no complaints on the board the page is a bordered white rectangle containing two lines,
   drawn on a white page — a frame around nothing, which reads as a panel that failed to load.
   An empty state is a fact stated where the data would have been. Left, unframed, in the row
   position the first row would have occupied.

   `align-items: flex-start` is written as well as `text-align: left`, because `.cx-empty` is a
   flex column and `text-align` cannot move a box the cross-axis has already centred (trap 9). */
html[data-theme="light"] .cx-wrap .cx-empty {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    align-items: flex-start;
    text-align: left;
    padding: 16px 0;
}

html[data-theme="light"] .cx-wrap .cx-empty-title {
    font-size: 0.8125rem;
    font-weight: var(--fw-label);
    color: var(--ink-dim);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   F4. THE OVERLAYS — the drop-off sheet and the check-in modal
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Neither of these appears in a resting screenshot, which is why both were still wearing the dark
   vocabulary after two compliance sweeps of the same pages. Both were opened with CLICK and
   photographed before any rule below was written.

   TRAP 1 APPLIES TO ONE OF THEM AND NOT THE OTHER, and getting it backwards is a silent no-op:

     · `CheckinModal.vue:8` is a `<Teleport to="body">` — its DOM is NOT inside `.vs-wrapper` or
       `.dropoffs-wrapper`. A rule scoped to either page root parses cleanly and matches NOTHING.
       Every `.ckm-*` / `.mdl-*` rule below is therefore scoped to the sheet's own class.
     · `DropoffSheet.vue` renders inline under the page root (no Teleport in the file), so `.dsx-*`
       could be page-scoped — it is not, because the same sheet is reachable from the tablet shell
       under a different root.

   FILE PLACEMENT — DONE, not pending. This block used to carry `.ckm-*` selectors alongside the
   `.dsx-*` ones with a note saying they belonged elsewhere. `CheckinModal.vue` is imported by
   dropoffs (desktop/tablet/phone), vehicle storage AND job detail (JobDetailDesktop.vue,
   JobDetailPhone.vue) — five pages across three clusters — so by the file's own rule ("if you find
   yourself writing the same rule in two cluster files, it belongs in _base.css") they were moved.

   **The three `.ckm-*` rules now live in _base.css, section "CheckinModal — moved out of
   boards.css F4".** The reasoning that produced them moved with them verbatim; what is left below
   is drop-offs only, which is what boards.css is for. Anything about the check-in modal's shape
   goes in _base.css from here on. (`.ckm-*` COLOUR rules also exist in registers.css:481-516 and
   detail.css:1347-1384 — they were out of scope for this move and are still there.) */

/* ── F4.1 COLOURED SECTION HEADINGS, IN BOTH SHEETS ───────────────────────────────────────────
   -> boards.css (.dsx-) · the matching `.ckm-zone-head` rule moved to _base.css

   `.dsx-zone-head` and `.ckm-zone-head` are both `color: var(--astra-accent-soft)` at
   `font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase`
   (sheetStyle.ts:147, CheckinModal.vue:360). That is the accent spent on something that is not an
   action and cannot be pressed — three of them across the top of the check-in modal, printed in
   the same blue as the "Checka in" button 300px below it.

   The strings are sentence case at source (`checkin_zone_who: 'Kund & fordon'`,
   localization.ts:3788-3790), so `none` is enough. */
html[data-theme="light"] .dsx-zone-head {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}

/* ── F4.2 THIRTEEN TRACKED UPPERCASE LABELS IN ONE DIALOG ─────────────────────────────────────
   -> boards.css (.dsx-, .do-) · the matching `.ckm-field > label` rule moved to _base.css

   Counted off the opened drop-off sheet: ANKOM · BERÄKNAD KLAR · MEKANIKER · PLATS · INNE ·
   ARBETET · TJÄNST · ANTECKNING · FRAMSTEG · VÄNTAR PÅ NÅGOT? · LOGGA ARBETE · LOGG OCH TAKT ·
   ÄNDRA UPPSKATTNING. A dialog is where a Swedish interface shouts loudest: the labels are long
   and there are more of them per square inch than anywhere else in the app.

   `.mdl-label` — the SHARED modal frame's field label (modalShellStyle.ts:199) — is NOT in this
   list, and checking that was worth the minute it cost: `detail.css:1274` already declares
   `html[data-theme="light"] .mdl-sheet .mdl-label` with these five declarations, character for
   character. Restating it here would have been a duplicate that looks like coverage and is drift.

   `.do-inlinetoggle` is the "Ändra uppskattning" disclosure (sheetStyle.ts:47). It is a BUTTON, so
   it is easy to miss when sweeping for labels — but it is set as one, 11px/700/0.1em uppercase,
   and it was the single label still shouting after the first pass over this sheet. */
html[data-theme="light"] .dsx-hl-label,
html[data-theme="light"] .dsx-fact-label,
html[data-theme="light"] .dsx-progress-label,
html[data-theme="light"] .do-inlinetoggle {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}

/* ── F4.3 VERTICAL COLUMN RULES IN THE FACT BAND ──────────────────────────────────────────────
   -> boards.css

   `sheetStyle.ts:115` gives `.dsx-hl-cell` a `border-right: 1px solid var(--hair)`, so the sheet's
   five-fact header band is drawn as five cells with four dividers between them. A Stripe table
   draws three kinds of line and ZERO vertical column rules — columns are separated by gutter and
   nothing else. The band already HAS the gutter (`padding-right` / `margin-right: --sp-5`), so the
   rule is doing no work the space is not already doing, and four verticals inside a dialog that is
   itself a floating surface is the wireframe reading in miniature.

   The horizontal rule under the band stays: that one separates two regions of the sheet. */
html[data-theme="light"] .dsx-hl .dsx-hl-cell {
    border-right: 0;
}

/* ── F4.4 THE CAPSULES ────────────────────────────────────────────────────────────────────────
   -> boards.css (.dsx-) · the matching `.ckm-pill` rule moved to _base.css

   `.dsx-chip` ("Inne i verkstaden", the sheet's status), `.dsx-hold-chip` (Väntar på delar / på
   kunden / på underleverantör / Pausad) and `.ckm-pill` (Behöver lyft / Wheel rack / Bay) are all
   `--r-pill`. _base §2 already made every board chip a rectangle; these three were missed because
   they only exist inside an overlay.

   `.dsx-chip` is the worst of them: a read-only STATE at --r-pill, in accent ink on an accent
   wash — a capsule saying "press me" about a fact. Badge shape, badge ground, accent given back. */
html[data-theme="light"] .dsx-chip {
    border-radius: 4px;
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .dsx-hold-chip {
    border-radius: var(--r-control);
}

/* The drop-off sheet's own chip has no such rule anywhere, so it gets one. It was an accent border
   on hover (`rgba(--ch-indigo-a78bfa, 0.4)`) — a pale violet channel that INVERTS in light, so the
   hover resolved to very nearly nothing on white. States go DOWN the ramp. */
html[data-theme="light"] .dsx-hold-chip.on {
    color: var(--ink);
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
}

html[data-theme="light"] .dsx-hold-chip:hover:not(:disabled) {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   F5. THE TYRE-RACK TAB — the last two carriers the sweep left behind
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Found by driving the tab strip, not by reading it.

   boards.css:2277 already neutralised the COLOUR of `.vs-season-chip.winter/.summer` on the correct
   reasoning — "the WORD carries the season". It left the case, so the word carrying the season is
   shouting it: VINTER / SOMMAR, 11px, tracked, on every rack row. `storage_season_winter` is
   sentence case at source, so `none` is enough. */
html[data-theme="light"] .vs-season-chip {
    text-transform: none;
    letter-spacing: 0;
}

/* The same sweep neutralised `.vs-due.soon` as "the ordinary case of a tyre hotel" — but the ROW
   FLAG for that identical state is still mustard. `.lx-flag.waiting` renders #8a5500 beside a
   `.vs-due.soon` badge that has already gone neutral, so one row states one verdict in two
   different vocabularies, one of them in a hue the theme has ruled it has no good value for.

   `overdue` is untouched on purpose: that dot is the one the eye is scanning for, and the badge
   beside it kept its red for exactly the same reason. */
html[data-theme="light"] .vs-row .lx-flag.waiting {
    background: var(--keyline-strong);
}



/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TOUCH PASS — BOARDS CLUSTER.  Phases 15 + 16.
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Routes owned, roots VERIFIED against the markup rather than assumed (index.ts + the root element
   of every view file), because the lead has had this wrong twice:

     route             desktop root      tablet root        phone root      phone is…
     /work-queue       .wq-wrapper       .wqt-wrapper *     .wqp-wrapper    READ-ONLY STUB
                       .wqe-wrapper (employee desktop; no touch shell resolves to it)
     /phone-bookings   .pb-wrapper       .pt-wrap     *     .pbp-wrap       READ-ONLY STUB
     /complaints       .cx-wrap          .cx-wrap     *     .cx-wrap        REAL VIEW
     /dropoffs         .dropoffs-wrapper .dt-wrap     *     .dp-wrap        REAL VIEW
     /routines         .rt-wrapper       (desktop fallback) .rtp-wrap       READ-ONLY STUB
     /vehicle-storage  .vs-wrapper       (desktop fallback) .vsp-wrapper    READ-ONLY STUB

     * = a real, bespoke tablet view (`*Tablet.vue` + `*Tablet.css`), not a squeezed desktop.

   FOUR OF SIX PHONE VIEWS ARE DELIBERATE STUBS. work-queue, phone-bookings, routines and
   vehicle-storage each render a few numbers and a sentence naming the shell where the work is done
   (`phone-bookings/index.ts:4` states it as product intent; the other three say the same in their
   own header comments). They are not under-built — they are the product. No touch UI is invented
   for them below; they are only checked for correctness and legibility. Only /complaints and
   /dropoffs have a real phone view, and both were already built to a 48/56px floor by their authors.

   THE COLLISION, HANDLED. `.cx-` is TWO unrelated pages: Complaints is `.cx-wrap` (mine) and
   Customers is `.cx-wrapper` (registers.css, another author). `.cx-sheet-head` and `.cx-veh` are
   declared in BOTH pages' markup. Every `.cx-` rule below is scoped under `.cx-wrap`, which can
   never contain `.cx-wrapper`, so nothing here can reach Customers. (See the report for two
   pre-existing bare `.cx-` rules in registers.css that DO cross into Complaints today.)

   Destination: public/theme-light/boards.css — APPEND at the end of the file. Several rules below
   deliberately supersede an earlier rule in that same file at equal or lower specificity and win on
   document order; inserting them mid-file makes them silently inert.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   T1. THE CONTROL LAYER TOOK 20px OFF THE ONE BUTTON THAT BILLS A CUSTOMER
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CONFIRMED — measured, both themes, same viewport.

   `/dropoffs` on tablet, the "Logga tid" button (`.dt-act`):

       dark   min-height 56px    the page's own declaration: DropoffsTablet.css, "it bills a visit"
       light  min-height 36px    _base.css's `html[data-theme="light"] .btn-astra-glass`

   The same measurement on `/dropoffs` phone (`.dp-act`, 36px) and `/vehicle-storage` tablet
   ("Hantera platser", 36px). This is the touch layer's own "divergence #1" — 36px was argued for
   the bay tablet and then set below every touch floor there — but the mechanism is worth naming
   precisely, because the blanket floor in `_base.css` §1 was supposed to have caught it and does
   not:

       @media (pointer: coarse) { html[data-theme="light"] button { min-height: var(--tap-min) } }
           -> (0,1,2):  one attribute, two elements
       html[data-theme="light"] .btn-astra-glass { min-height: 36px }
           -> (0,2,1):  one attribute + one CLASS, one element

   A class beats an element. The blanket floor loses to the control layer on every `.btn-astra-*`
   in the app, on every coarse shell, silently — the audit reports the box and nothing throws.

   THE REAL FIX BELONGS IN `_base.css`, not here: add the four `.btn-astra-*` class selectors to the
   coarse floor block so it ties on specificity and wins on document order (it is already last in
   the file). That is the lead's file, so this block is the cluster-scoped version — correct, but a
   patch on six pages rather than a fix on one selector. See the report.

   Scoped by page root so it scores (0,3,1) and clears the control layer by one class. */
@media (pointer: coarse) {
    html[data-theme="light"] .wqt-wrapper .btn-astra-glass,
    html[data-theme="light"] .wqt-wrapper .btn-astra-neutral,
    html[data-theme="light"] .pt-wrap .btn-astra-glass,
    html[data-theme="light"] .pt-wrap .btn-astra-neutral,
    html[data-theme="light"] .cx-wrap .btn-astra-glass,
    html[data-theme="light"] .cx-wrap .btn-astra-neutral,
    html[data-theme="light"] .dt-wrap .btn-astra-glass,
    html[data-theme="light"] .dt-wrap .btn-astra-neutral,
    html[data-theme="light"] .dp-wrap .btn-astra-glass,
    html[data-theme="light"] .dp-wrap .btn-astra-neutral,
    html[data-theme="light"] .rt-wrapper .btn-astra-glass,
    html[data-theme="light"] .rt-wrapper .btn-astra-neutral,
    html[data-theme="light"] .vs-wrapper .btn-astra-glass,
    html[data-theme="light"] .vs-wrapper .btn-astra-neutral {
        min-height: var(--tap-min);
    }

    /* The two that bill. Both views set 56px in their own sheet and say why ("the press that bills
       a customer" / "one 56px action on every card"); light is restoring their number, not
       inventing one. Placed AFTER the 44px list: same (0,3,1) score, document order decides. */
    html[data-theme="light"] .dt-wrap .dt-act.btn-astra-glass,
    html[data-theme="light"] .dp-wrap .dp-act.btn-astra-glass {
        min-height: var(--tap-primary);
    }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   T2. TELEFON ON THE BAY TABLET — the signed-off desktop decisions never crossed the shell
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CONFIRMED — /phone-bookings screenshotted at 1900 (desktop, signed off) and at 1080x810 (tablet),
   both THEME=light, side by side, plus computed styles for every value below.

   This is not a redesign and not a preference. `/phone-bookings` is one of the two pages the user
   has signed off, and the tablet renders SIX of its decisions the other way round, because the
   tablet view is a different DOM: the desktop is `.pb-*` and the tablet is `.pt-*`, and this file's
   TELEFON section only ever named `.pb-*`.

       what                     desktop (signed off)              tablet (measured, light)
       ─────────────────────────────────────────────────────────────────────────────────────────
       selected filter chip     white, ink, firmer edge, NO fill  FILLED #2f4fd0, white text
       lane headings            grey eyebrow over --keyline       #c68a12 mustard / cyan / green
       money                    --ink, tabular                    #047857 bottle green
       waiting age              --ink-muted                       #8a5500 mustard / red
       "Döda tider" tag         4px badge rectangle               999px outlined capsule
       bucket sum               --ink-muted                       --st-logged green

   The chip is the sharpest case and it is a rule-collision rather than an omission. `_base.css`
   declares `button.pb-chip.on` (0,3,1) — "a chip that is ON is stating a fact, never a fill" —
   while `settings.css` §5 declares `.pb-chip.on, .pt-chip.on` (0,3,0) as a filled accent. On
   desktop the higher-specificity no-fill rule wins. On tablet the class is `.pt-chip`, which
   `_base.css` never names, so the fill rule runs unopposed. One control, two selection idioms, one
   page — decided by which shell you are holding.

   (Aside worth recording: `.pb-*`/`.pt-*` rules live in `settings.css` today, which is how a
   phone-bookings decision ended up in the settings cluster with nothing to diff it against. Not
   moved here — that is a reconciliation for the lead, not a change to make mid-pass.) */

/* The title's second word. Seventh divergence, caught only on the verification render: the desktop
   sets `.pb-wrapper .pb-hl` to ink ("a page title is one object; the rail already says where you
   are") and the tablet's span is `.pt-hl`, which nothing names — so on the bay tablet the page is
   still called "Telefon offerter" with the second word in accent blue, reading as half heading and
   half link. `-webkit-text-fill-color` is the load-bearing line: it beats `color`, so setting
   `color` alone leaves the clipped gradient rendering and the fix looks applied while changing
   nothing. */
html[data-theme="light"] .pt-wrap .pt-hl {
    background: none;
    color: var(--ink);
    -webkit-text-fill-color: var(--ink);
}

/* Lane headings. `.pt-bucket-head.b-{key}` is assembled at runtime from the bucket key, so the
   three modifier classes below do not appear as literals in the tablet's markup — they are
   `b-needs_callback`, `b-awaiting_answer`, `b-accepted` (grep the composable, not the template).
   Each is (0,3,0) and beats the page sheet's (0,2,0). */
html[data-theme="light"] .pt-wrap .pt-bucket-head,
html[data-theme="light"] .pt-wrap .pt-bucket-head.b-needs_callback,
html[data-theme="light"] .pt-wrap .pt-bucket-head.b-awaiting_answer,
html[data-theme="light"] .pt-wrap .pt-bucket-head.b-accepted {
    color: var(--ink-muted);
    border-bottom-color: var(--keyline);
}

/* A count and a sum beside a heading are numbers, not statuses. */
html[data-theme="light"] .pt-wrap .pt-bucket-n,
html[data-theme="light"] .pt-wrap .pt-bucket-sum {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

/* Money goes to ink — the desktop's own words: "a price is the fact this page exists to produce;
   painting it mint-green says 'good' about a number that is neither good nor bad". */
html[data-theme="light"] .pt-wrap .pt-money {
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* The waiting age. Red on every row is red on no row; and #8a5500 is mustard, which is what a light
   amber always becomes once it is darkened far enough to clear 3:1. */
html[data-theme="light"] .pt-wrap .pt-age,
html[data-theme="light"] .pt-wrap .pt-age.amber,
html[data-theme="light"] .pt-wrap .pt-age.red {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

/* The tag keeps its hue — a dead offer IS the exception — but as the badge rectangle the desktop
   settled on, not as a 999px outlined capsule that outweighs the vehicle beside it. */
html[data-theme="light"] .pt-wrap .pt-tag,
html[data-theme="light"] .pt-wrap .pt-tag.dead {
    border-radius: 4px;
    padding: 1px 6px;
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
    color: var(--st-breach);
}

/* The selected chip and the selected sort button. (0,4,0) — one class clear of settings.css §5's
   (0,3,0), which is the rule being superseded. Same treatment as `.pb-chip.on` above in this file.
   The 44px floor these controls already meet is untouched: only fill, edge and ink are named. */
html[data-theme="light"] .pt-wrap .pt-chip.on,
html[data-theme="light"] .pt-wrap .pt-sort-btn.on {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
    font-weight: var(--fw-label);
    box-shadow: none;
}

/* Resting chips take the shared control edge rather than a hairline over a wash. */
html[data-theme="light"] .pt-wrap .pt-chip,
html[data-theme="light"] .pt-wrap .pt-sort-btn {
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}

/* The result line's five tones. Only the amber is neutralised, and only because there is no good
   light amber — the others are one word in one sentence fragment and this file has already recorded
   that boxing or bleaching that fragment was wrong once. */
html[data-theme="light"] .pt-wrap .pt-urgent.u-warn {
    color: var(--ink-muted);
}

/* The row's dot. `--st-logged`/`--st-expected` are TEXT tokens; as an 8px solid disc they render as
   bottle green and mustard. A dot is a fill, so it owes 3:1 and takes the meter family. */
html[data-theme="light"] .pt-wrap .pt-flag.f-accepted { background: var(--meter-ok); }
html[data-theme="light"] .pt-wrap .pt-flag.f-needs_callback { background: var(--meter-warn); }


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   T3. TELEFON TABLET — THE DIALOG FLOATS IN MILK.  The single worst thing found this pass.
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CONFIRMED — opened `+ Ny offert` on /phone-bookings at 1080x810, THEME=light, screenshotted, and
   read the computed styles off the live nodes:

       .pt-backdrop   background rgba(219, 228, 245, 0.7)      a 70% PALE wash
       .pt-dialog     background-image linear-gradient(160deg, #d4ddee, #d6dff1)
                      box-shadow none

   This is exactly the failure `_base.css` §C0.5 was written to prevent, and it was missed for
   exactly the reason that block warns about: the fix is a LIST OF CLASS NAMES, and `.pt-backdrop`
   and `.pt-dialog` are not on it. This dialog does not use the shared `.mdl-*` frame — it is the
   only overlay in this cluster that does not — so every rule in §C0.5 reaches past it.

   The scrim is built on `rgba(var(--ch-blue-06080f), 0.7)`, a CHANNEL variable, and a channel
   inverts with the theme. A scrim's entire job is to push back what is behind it; inverted, it
   becomes a pale veil that the page reads straight through. The dialog then resolves to almost the
   same pale blue as the scrim and carries no elevation, so there is no edge anywhere between the
   page, the scrim and the sheet — you are typing a registration number into a form with no ground
   under it. `--scrim` exists for precisely this and was simply never adopted here.

   NOT written inside `@media (pointer: coarse)` and it does not need to be: `.pt-backdrop` is a
   sibling of `.pt-wrap` in the tablet view's own template (PhoneBookingsTablet.vue:95), not a
   teleport to <body>, and `.pt-*` exists on no other page in the app. */
html[data-theme="light"] .pt-backdrop {
    background: var(--scrim);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* A dialog is the one thing in this theme that genuinely floats, so it keeps level 3 — the same
   call `_base.css` §C0.5 made for `.mdl-sheet`. `background-image` has to be named as well as
   `background-color`: the gradient is what is actually painting, and setting `background-color`
   alone leaves it rendering while the rule looks applied. The bottom corners stay square because
   the sheet is docked to the bottom edge; only the top pair take the panel radius. */
html[data-theme="light"] .pt-dialog {
    background-color: var(--surface);
    background-image: none;
    border: 1px solid var(--keyline);
    border-bottom: 0;
    border-radius: var(--r-panel) var(--r-panel) 0 0;
    box-shadow: var(--elevation-3);
}

/* The toast is a two-stop emerald gradient with near-black ink — a lit bar, which reads on
   near-black and smears on white. One flat token, the badge grammar. */
html[data-theme="light"] .pt-toast.ok {
    background: var(--st-logged-soft);
    background-image: none;
    border: 1px solid var(--st-logged-line);
    color: var(--st-logged);
}

html[data-theme="light"] .pt-toast.error {
    background: var(--st-breach-soft);
    background-image: none;
    border: 1px solid var(--st-breach-line);
    color: var(--st-breach);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   T4. THE SQUARE ICON BUTTONS — the width failures the central floor deliberately does not fix
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   `_base.css` §2b names these as per-cluster work with the evidence rather than the assertion, and
   says the answer is a layout decision, not a token. Mine, measured at 1080x810 (the auditor prints
   minHEIGHT x minWIDTH — worth stating, because read the other way round these look like height
   failures and the fix would be the wrong one):

       .rt-rail-toggle   h48 w32   /routines      the season rail's open/close
       .vs-dens-btn      h44 w32   /vehicle-storage  the density pair (two icon buttons)
       .vs-place-more    h44 w43   /vehicle-storage  the "+2" overflow chip — one pixel short
       .rt-strip-zone    h44 w25   /routines      NOT FIXED, see below

   All three fixes GROW THE PAINT rather than hanging an invisible hit area off it. The touch
   layer's note is right and it is the bay argument: at arm's length under glare, a 32px painted
   arrow with a 44px invisible target is still a 32px thing to aim at. */
/* THE THREE GEOMETRY FIXES MOVED OUT OF THIS FILE, 2026-08-12, and are now unprefixed in the page
   sheets that own the layout: `.rt-rail` padding + `.rt-rail-toggle` min-width in
   `src/pages/routines/RoutinesDesktop.css`, `.vs-dens-btn` and `.vs-place-more` min-width in
   `src/pages/vehicle-storage/VehicleStorageDesktop.css`. The reasoning above travelled with them
   verbatim; only the theme prefix was dropped.

   WHY: they were correct fixes on the wrong half of a theme fork. The floor they complete now lives
   in `public/app.css` and applies in both themes, and the shell these controls exist for — the bay
   tablet — runs DARK. A width fix that only lands in light fixes the theme nobody is holding.

   What stays here is the one line of it that is genuinely PAINT: the "+2" chip is a 999px dashed
   capsule in dark, and "a capsule means this is a control" is a rule this theme spends carefully. */
@media (pointer: coarse) {
    html[data-theme="light"] .vs-wrapper .vs-place-more {
        border-radius: var(--r-control);
    }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   T4b. THE TOUCH FLOOR BROKE A GROUP HEADING — a light-only regression the floor itself caused
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CONFIRMED — /vehicle-storage at 1080x810, measured in both themes on the same node:

       .vs-group-head    dark  49px tall      light  80px tall
       .vs-group-toggle  dark  13px tall      light  44px tall (min-height from the coarse floor)

   On the render the chevron leaves the heading and sits alone on a second line under "Garage 1",
   which reads as a rendering fault rather than as a fold control.

   It is NOT a wrap, and the first diagnosis — "the heading is too long for the track" — was wrong.
   `.vs-group-toggle` is `display: flex; align-items: baseline`, and `.vs-group-chev` is the one
   child that opts out with `align-self: center`. At the natural 13px height those two positions are
   the same place. The coarse floor raises the box to 44px without giving the text any more height,
   so the baseline-aligned children stay pinned at the top of the box and the centre-aligned chevron
   drops 16px below them. The floor is correct and the alignment is correct; they are simply
   incompatible, and nothing about that is visible until a control that was 13px tall becomes 44.

   Worth flagging as a CLASS of bug rather than one instance: any `align-items: baseline` flex row
   that the coarse floor grows will separate an `align-self: center` child from its siblings. This
   is the only occurrence in this cluster — grepped every `align-self: center` in all six pages'
   stylesheets against every `align-items: baseline` container, and the other five are either a
   `::after` hairline on a non-control heading (`.dt-bucket-head`, `.vs-group-head`) or a grid item
   (`.wqp-lane-count`), none of which the floor touches. Other clusters should look for it. */
/* MOVED, 2026-08-12, to `src/pages/vehicle-storage/VehicleStorageDesktop.css`, unprefixed. The
   diagnosis above is unchanged and is now MORE load-bearing than when it was written: the floor
   applies in dark as of this date, so the 13px toggle becomes 44px there too and the chevron drops
   in dark unless the alignment moves with it. A light-only fix would have left the bay tablet — the
   dark one — showing the fault this block was written to remove. */


/* `.rt-strip-zone` (h44 w25) is DELIBERATELY NOT FIXED, and the first diagnosis of it was wrong.
   It looks like a 25px button. It is an absolutely-positioned invisible hit region laid over the
   season timeline, and its width IS its date range — a 25px zone is a short season, not a small
   button. Widening it to 44px would make it overlap its neighbour and select the wrong season, i.e.
   it would trade a hard-to-hit target for a target that hits the wrong thing. The honest fix is a
   larger minimum zone computed in `ledgerWindow.ts` with the overlap resolved there; that is a
   composable change, not a CSS one, and it is handed over rather than guessed at. */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   T5. COMPLAINTS ON PHONE — two idioms for one control, six pixels apart
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CONFIRMED — /complaints at 412x839, THEME=light.

   `ComplaintsPhone.css:61` declares `.cx-tab, .cx-chip` in ONE rule at `--r-pill`. `_base.css`'s
   chip list names `.cx-chip` and not `.cx-tab`, so half of that declaration was rectangled and half
   was not: the screen now shows a strip of three capsules (Öppna / Avklarade / Beröm) sitting
   directly above a strip of two rectangles (Utan ägare / Allvarliga), and below that a strip of
   three more rectangles (`.cx-key`) — the same control drawn two ways within 60px of itself.

   Rectangles, matching the `.cx-chip` beneath them exactly — that is the whole fix, and it is the
   cheaper half of "make the tab an underline", which on a horizontally-scrolling thumb strip would
   remove the only edge telling you where one target ends and the next begins.

   A GUESS THAT WAS WRONG, recorded because it is the useful half: the first version of this comment
   said "the tablet and desktop draw the same `.cx-tab` as an underline". They do not. `.cx-tab`
   exists ONLY in `ComplaintsPhone.vue` — the other two shells use `.cx-lane` inside `.cx-lanes`
   (ComplaintsTablet.vue:48, ComplaintsDesktop.vue:57), which this file already styles. Three views
   sharing one root class (`.cx-wrap`) makes it very easy to assume they share the inner ones too.
   Caught by probing `.cx-tab` at 1080 and getting n=0.

   SCOPED UNDER `.cx-wrap` AND `[data-shell='phone']`. `.cx-wrap` is the load-bearing half — it keeps
   this off Customers (`.cx-wrapper`, registers.css), the collision this cluster was warned about.
   `[data-shell='phone']` is belt and braces given the finding above: it costs nothing and it means
   the rule stays correct if a future tablet view ever adopts `.cx-tab`. */
html[data-theme="light"] .page[data-shell='phone'] .cx-wrap .cx-tab {
    border-radius: var(--r-control);
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}

html[data-theme="light"] .page[data-shell='phone'] .cx-wrap .cx-tab.on {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--ink);
}

/* The count inside a selected tab inherits `--c-indigo-ede9fe` (near-white) from the page sheet's
   `.cx-tab.on > b { color: inherit }`. Once the fill is gone that is white on white. Named
   explicitly because a superseding rule only overrides the properties it writes. */
html[data-theme="light"] .page[data-shell='phone'] .cx-wrap .cx-tab.on > b {
    color: var(--ink-muted);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   T6. THE ROUTINES RAIL IS ALWAYS WEARING ITS CLOSED COSTUME — a latent scoping miss
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CONFIRMED — opened the rail on /routines at 1080x810 with this rule injected and screenshotted
   before and after. Before: the 240px open panel carries the closed control's white fill, 4px radius
   and contact shadow, so a rail full of grouped filter options reads as one very large button.
   After: the panel is flat and the toggle inside it keeps the control costume, which is correct —
   the toggle IS the control, the rail is not.

   This file's §5 "THE RAIL TOGGLE STOPS FLOATING" is written as

       html[data-theme="light"] .rt-board .rt-rail:not(.rail-open) { …surface, radius, shadow… }

   `.rail-open` is on `.rt-board`, not on `.rt-rail` (RoutinesDesktop.css:291 —
   `.rt-board.rail-open .rt-rail-toggle`). So `.rt-rail:not(.rail-open)` is true at all times and
   the OPEN rail — a 15rem panel — is also being painted as a small floating control with the
   closed state's radius and shadow.

   Restated against the real ancestor. Left as a correction rather than edited in place, because the
   file already keeps one of these visible (§P3b) and the pattern is the point: a light rule that
   matches too much looks exactly like a light rule that matches correctly until something opens. */
html[data-theme="light"] .rt-board.rail-open .rt-rail {
    background: transparent;
    border-radius: 0;
    box-shadow: none;
}



/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   PART B — PAINT + LIGHT-ONLY COMPOSITION   →   public/theme-light/boards.css
   Every rule below is prefixed. Append to the Work Queue section.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── B1. THE ROW WAS SPENDING A QUARTER OF ITS WIDTH ON AN EMPTY GRID TRACK  [CONFIRMED] ──────
   This is the single largest finding on the page and it is why the vehicle truncated at a width
   that should have been ample.

   boards.css:899 overrides the row grid to `18px minmax(0, 24rem) auto 1fr 7rem`. MEASURED, that
   resolves to:
       18px · 203.95px · 109.05px · 0px · 112px      inside a 523px row
   Columns 4 and 5 hold NOTHING — the page sheet places every child of `.wq-row` in columns 1–3
   (`.lx-flag` 1, `.lx-name`/`.lx-sub`/`.lx-tags`/`.wq-row-meter` 2, `.lx-money`/`.lx-when`/
   `.wq-rowact` 3). So 112px of empty track plus its 20px gap — 132px, 25% of the row — was reserved
   for children that do not exist, and the name track was squeezed to 204px to pay for it. Result:
   "2010 Volkswagen Passat 1.4 T…" in a 544px column with 1220px of empty pane beside it.

   The page sheet's own grid (`6px minmax(0,1fr) auto`) is correct and does not have this fault; the
   only thing light legitimately needs to change is the first track, because §3 of this file replaced
   the 6px dot with an 18px glyph badge. So: same shape as the page sheet, wider flag column.

   MEASURED, after (with A1 applied):
       light   18px · 339.95px · 109.05px     name track 204 → 340px  (+67%)
       dark     6px · 385.98px · 104.02px     name track 274 → 386px
   "2010 Volkswagen Passat 1.4 TGI EcoFuel Manuell" now renders in full in both themes; it was
   truncated in both before.

   REPLACES boards.css:899-901 in place. Do not add it as a later rule: the block it replaces carries
   the derivation for 24rem, which is now wrong and must not be left in the file. */
html[data-theme="light"] .wq-wrapper .wq-row {
    grid-template-columns: 18px minmax(0, 1fr) auto;
}

/* ── B2. THE GREY CENSUS, AND WHAT IT COSTS ───────────────────────────────────────────────────
   The user's words were "all that grey and crammed thing". Counted off the rendered record with
   probe.mjs, these are the distinct fills stacked inside ONE white pane:

       .lx-sheet.wq-pane     #ffffff                     + 1px keyline   ← the container. Correct.
       .wq-band              rgba(27,35,61,0.024)        + 1px border    ← fill AND border, nested
       .wq-pchip             #f3f6f8                     + 1px border    ← inside .wq-band
       .wq-pcfg-btn          transparent                 + 1px border    ← inside .wq-band
       .wq-finding-chip      rgba(27,35,61,0.05)         + 1px border
       .wq-inlinetoggle      #fafbfd                     + 1px border
       .wq-logentry          rgba(27,35,61,0.035)        + 2px left bar
       .wq-detail-note       #ffffff                     + inset floor   ← correct (it is a field)

   FIVE distinct greys, four of them within three percent luminance of each other. They cannot be a
   depth ordering — nobody can rank 0.024 against 0.035 against 0.05 — so they are not structure,
   they are mottle. And `.wq-band` reaches three levels deep: a grey chip, inside a grey slab,
   inside the white pane, each with its own border. That is the exact nested-box failure §"TELEFON:
   THE RECORD, FINISHED" (boards.css:2866) already fixed once on the reference page.

   None of these fills is authored for light. They are all `rgba(var(--sf-rgb), α)` written for dark,
   where the same declaration is a LIFT (lighter than its surround) and in light is a RECESS that
   reads as read-only. The theme flip is the whole bug.

   The rule applied below is Telefon's, unchanged: the container keeps the frame, everything inside
   it separates by a hairline and space. */

/* B2a. THE TIME BAND  [CONFIRMED]
   The record's headline instrument. It does not need a box — it is already inside one, and it is the
   first thing under the title, so nothing above it needs separating. One rule underneath, which is
   the only line that earns its place: it divides the instrument from the work below it. Exactly the
   treatment `.pc-lines` / `.pc-total` got on Telefon. */
html[data-theme="light"] .wq-wrapper .wq-band {
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    box-shadow: none;
    padding: 0 0 var(--sp-4);
    margin-bottom: var(--sp-5);
}

/* The `finished` variant carries a real state and keeps its tint — but as a tint on the RULE, not as
   a slab, so the neutral and the finished band are the same object in two states rather than two
   different-looking panels. */
html[data-theme="light"] .wq-wrapper .wq-band.finished {
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--st-logged-line);
}

/* B2b. THE FINDINGS  [CONFIRMED]
   "vindruta byte" — the found work, i.e. the subject of the whole record — was a grey-filled chip,
   which is how this theme renders a disabled control. It is not a control at all. Outline only:
   still a discrete token, no longer a fill competing with the pane. */
html[data-theme="light"] .wq-wrapper .wq-finding-chip {
    background: transparent;
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}

/* B2c. THE DISCLOSURE  [CONFIRMED]
   "Arbete kvar" rendered #fafbfd on white with a keyline and a chevron in a circle — a grey field,
   which §12 of this file already established reads as read-only. It is a BUTTON, and the one thing
   the mechanic clicks in that column. White fill, structural keyline, and the state goes DARKER
   (open is a state, not a hover, so it takes the inset rung and holds it).

   Supersedes boards.css:173-177, which set the hover/open pair but left the resting fill grey. */
html[data-theme="light"] .wq-wrapper .wq-inlinetoggle {
    background: var(--surface);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .wq-wrapper .wq-inlinetoggle:hover:not(:disabled) {
    background: var(--surface-hover);
    border-color: var(--keyline-strong);
}

html[data-theme="light"] .wq-wrapper .wq-inlinetoggle[aria-expanded="true"] {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
}

/* The chevron sat in a filled violet-derived disc — a second fill inside the control. It is a
   direction indicator; it needs ink, not a puck. */
html[data-theme="light"] .wq-wrapper .wq-inlinetoggle .chev {
    background: transparent;
    color: var(--ink-muted);
}

/* B2d. THE WORK LOG  [CONFIRMED]
   Each logged installment was a grey slab with a 2px leading bar. Two faults in one row: a list of
   peers separated by FILL rather than by rule, and a leading bar on every row — this theme reserves
   the leading bar for selection (3px, `--accent-bar`), so a bar that is always on cannot say
   "selected" any more. Same collision as the tablet card in PART D.

   Rows separate by a hairline, which is the ledger idiom this whole app is built on. The last row
   drops its rule so the block does not end in a dangling line.

   NOTE: this supersedes boards.css:604, which repointed the left bar's COLOUR to --border-strong and
   left the bar itself in place. Trap #4 in the brief, in the wild: recolouring a device is not the
   same as deciding whether the device belongs. */
html[data-theme="light"] .wq-wrapper .wq-logentry {
    background: transparent;
    border-left: 0;
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    padding: var(--sp-2) 0;
}

html[data-theme="light"] .wq-wrapper .wq-logentry:last-child {
    border-bottom: 0;
}

/* B2e. THE PACE BADGE, ON A GROUND THAT IS NOW WHITE  [CONFIRMED]
   `.wq-pchip.ontrack` and `.wq-pchip.behind` fill with `--surface-inset`. That was legible while the
   band underneath was a slab; with B2a the band is white, so the inset chip is now the ONLY grey
   rectangle left in the top third of the record and it re-reads as a disabled control.

   Both are the ordinary case, so by the verdict rule they go neutral — but neutral by OUTLINE, not
   by fill. `.overdue` and `.ahead` are untouched: those are the cases that mean something. */
html[data-theme="light"] .wq-wrapper .wq-pchip.ontrack,
html[data-theme="light"] .wq-wrapper .wq-pchip.behind {
    background: transparent;
    border: 1px solid var(--keyline-strong);
}

/* B2f. THE FACT LINE  [CONFIRMED]
   The SIXTH grey, and the clearest case of the theme flip doing the damage. The page sheet's own
   comment above `.wq-detail-facts` (WorkQueueDesktop.css:910-916) says these facts "get LOOKED UP,
   not read" and asks for "one dot-separated line, quiet … no fill beyond a hairline wash". In dark
   `rgba(var(--sf-rgb), 0.05)` IS a hairline wash. In light the same declaration is a grey fill, so
   "Expert", "Behöver lyft" and the plate render as three filled chips — louder than the note under
   them and identical in treatment to the disabled-looking controls above. The author's stated intent
   is satisfied by removing the fill, not by adding anything.

   Outlined, matching B2b, so the record has ONE chip vocabulary rather than two. The plate keeps its
   accent ink (`.mono`) — it is the handle a mechanic scans for and it is set that way app-wide. */
html[data-theme="light"] .wq-wrapper .wq-detail-facts > span {
    background: transparent;
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}

/* "Ingen klardag satt" is an ABSENCE and already had no fill in dark. Keep it borderless in light
   too, or the neutralised chips above would make it the only bare item on a line of outlines. */
html[data-theme="light"] .wq-wrapper .wq-detail-facts .muted {
    background: transparent;
    border-color: transparent;
}

/* ── B3. THE LEDGER ROW GETS THE AIR THE WIDTH BOUGHT  [CONFIRMED] ────────────────────────────
   MEASURED: light row height 96px against dark's 111px on identical content. Light is the DENSER of
   the two themes on the same page, which is backwards — light has less contrast to work with, so it
   needs more space, not less. This is the second half of "crammed", and the +260px this pass bought
   is what pays for it.

   Vertical padding only. The row's grid, its gaps and its column widths are all untouched, so this
   cannot move the windowing arithmetic that `.wq-row:has(.wq-row-meter)` depends on — every row
   moves by the same amount.

   MOVED OUT, 2026-08-11, and the diagnosis above is why. It correctly identified a light-vs-dark
   row-height fork and then RESOLVED IT BY FORKING HARDER: adding padding to one theme only left the
   two at 104 light / 99 dark instead of 96 / 111 — closer, still forked, and now forked in the
   opposite direction. "Light is the denser of the two themes on the same page, which is backwards"
   is an argument about a LEDGER ROW, not about a colour scheme; a row that needs air needs it in
   both themes. The declaration now lives unprefixed in `WorkQueueDesktop.css` and both themes
   measure the same. Kept as a note because the measurement is the useful part. */



/* ── D1. THE GREEN BAR ON EVERY CARD  [UNVERIFIED — reasoned from source, see the note] ───────
   `src/pages/work-queue/WorkQueueTablet.css:42` declares:

       .wqt-card { background: var(--glass-bg);
                   border: 1px solid var(--glass-border);
                   border-left: 3px solid var(--st-logged);      ← unconditional
                   box-shadow: var(--glass-shadow); }

   Three faults, in order of severity:
     1. The green bar is UNCONDITIONAL. `.wqt-card` is used for exactly one zone — the bench, i.e.
        "what I am working on" — so every card in that zone is green and the colour distinguishes
        nothing. The verdict rule: a colour on every row has stopped carrying information.
     2. It is a 3px leading bar, which is the SELECTION idiom in light (`inset 3px 0 0
        var(--accent-bar)`). With it always on, selection has no vocabulary left on this view.
     3. The card carries a border AND an elevation AND a fill. Depth is spent once, at the outermost
        surface; a card sitting on the page is one or the other.

   ⚠ REPORTED, NOT FIXED IN DARK: the declaration is in the PAGE STYLESHEET, so the green bar is in
   dark too and the same argument applies to it. Removing it there is a paint change to dark and
   would trip compare.mjs, so it is out of scope for this pass by the brief's rule. The lead should
   decide separately. Light-only removal below.

   Marked UNVERIFIED because the tablet view could not be re-rendered with the change applied within
   this pass; the selector, the declaration and the render are all confirmed, the outcome is reasoned.
   Everything else in this file is CONFIRMED against a render. */
html[data-theme="light"] .wqt-card {
    border-left: 1px solid var(--keyline);
    box-shadow: none;
}




/* ── THE PANE'S RIGHT EDGE, LIGHT SIDE — the "sharp cut off" ──────────────────────────────────
   `WorkQueueDesktop.css` states this for both themes at (0,3,0) and DARK took it (measured
   `24px 32px 24px 24px`). Light did not: it kept `24px 0px 24px 20px`, i.e. zero right padding, so
   the meter's `40%`, the Tidslinje timestamps and `1h loggat totalt` still sat hard against the
   pane's own border while dark had breathing room. A geometry fix that lands in one theme only is
   worse than no fix — it is exactly the divergence this whole pass exists to remove.

   Restated here at (0,3,1) so it wins in light too. The values are written as the same tokens, not
   as literals, and they deliberately resolve differently per theme: `--sp-5` is 20px in light and
   24px in dark. That is the light theme's own tighter rhythm and it stays — what has to match is
   that BOTH themes have a real right gutter, not that both have the same number of pixels in it. */
html[data-theme="light"] .wq-wrapper .lx-sheet.wq-pane {
    padding: var(--sp-5) var(--sp-6) var(--sp-5) var(--sp-5);
}


/* ── THE ACTUAL "SHARP CUT OFF": A CLOSED DRAWER THAT NEVER STOPPED PAINTING ──────────────────
   The complaint was reported twice, and the padding fix above did not answer it, because the fault
   was never inside the pane. It was in the gutter beside it.

   `WorkQueueChat.vue` mounts `.cwq-overlay` unconditionally and parks `.cwq-drawer` off-canvas at
   `transform: translateX(100%)` — x = 1900…2320 at the width the shop owner uses. The drawer
   carries `box-shadow: -24px 0 60px rgba(var(--ref-shadow-base), 0.5)`, and a shadow with a
   NEGATIVE x-offset points back the way the drawer came. So a closed drawer kept casting a soft
   dark ramp across the last ~110px of the viewport, on every Work Queue render, always.

   MEASURED off the composited pixels at y=700, 1900px wide, record open (light):
       x=1784  255,255,255      x=1840  240,240,241
       x=1808  253,253,253      x=1864  212,213,216
       x=1824  249,249,250      x=1888  174,176,181   ← 32% darker than the page it sits on
   The page's own white runs unbroken from x=0 to x=1784. So the composition did end in a hard
   vertical boundary with dead grey beyond it — precisely what was described — and no amount of
   padding INSIDE the pane could ever have reached it.

   Why clipping is not the fix, in case it is tried again: `.cwq-overlay` is `inset: 0`, and the
   VISIBLE part of the shadow falls inside that box, not outside it. `overflow: hidden` and
   `clip-path: inset(0)` were both measured on the live page and both left the ramp untouched
   (255→188 at x=1880 either way). Only standing the shadow down removes it.

   Why this is the light sheet's business and not the page's: a closed drawer's elevation is PAINT.
   Declaring it unprefixed would move dark's `.cwq-drawer` box-shadow and trip compare.mjs, which is
   the one thing this rebuild may not do. Dark carries the same ramp — measured 39,30,62 → 13,11,21
   against 39,30,62 → 19,15,29 with the shadow off — but on a dark violet ground a 6-value drop at
   the extreme edge is invisible, which is why only the light theme ever produced a complaint.
   ⚠ REPORTED, NOT FIXED IN DARK: same declaration, same argument; the lead should decide.

   `.open` is the class `WorkQueueChat.vue` puts on the overlay while the drawer is out, so the open
   drawer keeps its full elevation over the page — nothing about the chat's real appearance changes.
   The transition covers the 0.24s slide so the shadow arrives with the drawer rather than snapping
   on ahead of it. */
html[data-theme="light"] .cwq-overlay:not(.open) .cwq-drawer {
    box-shadow: none;
}
html[data-theme="light"] .cwq-drawer {
    transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.24s cubic-bezier(0.4, 0, 0.2, 1);
}
