/* Light-only overrides — registers 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.

   ─────────────────────────────────────────────────────────────────────────────────────────────
   THE RULE THIS FILE ENFORCES

   Violet earns four things: the primary action, the focus ring, the selection, and the logo.
   Everywhere else in light it was noise — the ground, the chips, the counts, the eyebrows, the
   scrollbar thumbs. A colour spent on everything means nothing, and a saturated violet on white
   reads as pigment rather than as light.

   Every selector below mirrors its page/component selector exactly, prefixed with
   `html[data-theme="light"]`. That prefix is worth (0,1,0), so the override always beats the rule
   it corrects by exactly one step — no doubled classes, no !important, and no guesswork about
   which sheet injected last.

   The cluster: /vehicles, /vehicle-detail, /customers, /customer-detail, and the shared component
   layer (lib/*Style.ts sheets, the modals, the pickers, the chat drawers). The lib sheets reach
   well past this cluster, so their corrections here are the widest-travelling rules in the file. */


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   1. HEADER FURNITURE — the eyebrow, the title, the counts
   Three violet objects stacked at the top of all four register pages. In dark they are a quiet
   glow over near-black; in light they are three saturated lozenges before a single word of the
   page's actual content. None of them is an action, a selection or a focus target.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

/* The eyebrow. A category label, not a control — it takes the neutral chip rung.
   SUPERSEDED by _base.css §1, which now paints `.badge` app-wide at the same (0,2,0) with the
   NEWER vocabulary (--surface-inset / --keyline / --ink-dim) and gives it the badge rectangle. The
   rule that stood here was a bare, un-scoped `.badge` in a cluster file — i.e. it repainted every
   page in the app from the registers' sheet, and it did so with the first-generation tokens, so it
   was silently beating _base on document order for three of its four declarations. Deleted rather
   than re-tokenised: two files declaring one app-wide object is exactly the `.purple-gradient`
   failure this rebuild exists to stop. */

/* The page title. A gradient wordmark is the product's logo; a page heading is not, and at the
   light channel values the two stops are #7c3aed → #6d28d9 — a heavy pigment bar across the top of
   a white page. The heading is the most important text on the register, so it takes the text
   colour that says so. */
/* MOVED to _base.css. `.purple-gradient` is declared by ~14 page sheets across every cluster, so a
   bare rule for it living in ONE cluster file is precisely the drift that made the same class
   violet in six sheets and emerald in a seventh. It is shared vocabulary and belongs in the shared
   file. */

/* The counts. "1 284 fordon" is a fact, not a state — a fact does not get the accent. */
html[data-theme="light"] .stat-pill {
    color: var(--ink-dim);
    background: var(--surface-3);
    border-color: var(--border-default);
    box-shadow: none;
}

/* …except the owner pill, which really is a way into another record. Secondary action: a neutral
   fill with a real border, and a hover that goes darker because light has no headroom above white. */
html[data-theme="light"] .stat-pill.owner-pill:hover:not(:disabled) {
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] .owner-pill-arrow { color: var(--ink-muted); }
html[data-theme="light"] .owner-link-arrow { color: var(--ink-muted); }

/* The two register pages' back link and the owner link in the fact grid. A text link may carry the
   accent, but at --accent-text, which is the accent re-derived for a white ground (~30% less
   chroma) — not the raw dark-theme violet the channel token resolves to. */
html[data-theme="light"] .back-link { color: var(--accent-text); }
html[data-theme="light"] .back-link:hover { color: var(--accent-solid); }
html[data-theme="light"] .detail-value.owner-link { color: var(--accent-text); }
html[data-theme="light"] .detail-value.owner-link:hover:not(:disabled) { color: var(--accent-solid); }
html[data-theme="light"] .vx-fact-link { color: var(--accent-text); }
html[data-theme="light"] .vx-fact-link:hover { color: var(--accent-solid); }
html[data-theme="light"] .cxm-link { color: var(--accent-text); }
html[data-theme="light"] .cx-phone:hover { color: var(--accent-text); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   2. SCROLLBAR THUMBS
   A translucent accent thumb is decoration on a scrollbar — the one element on the page whose
   entire job is to be a neutral position indicator. Six of them across this cluster.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

html[data-theme="light"] .vx-sheet-scroll,
html[data-theme="light"] .cx-sheet-scroll,
html[data-theme="light"] .vd-scroll,
html[data-theme="light"] .cd-scroll,
html[data-theme="light"] .chat-body,
html[data-theme="light"] .lf-menu,
html[data-theme="light"] .tpf-col { scrollbar-color: var(--border-strong) transparent; }

html[data-theme="light"] .vx-sheet-scroll::-webkit-scrollbar-thumb,
html[data-theme="light"] .cx-sheet-scroll::-webkit-scrollbar-thumb,
html[data-theme="light"] .vd-scroll::-webkit-scrollbar-thumb,
html[data-theme="light"] .cd-scroll::-webkit-scrollbar-thumb,
html[data-theme="light"] .chat-body::-webkit-scrollbar-thumb,
html[data-theme="light"] .lf-menu::-webkit-scrollbar-thumb,
html[data-theme="light"] .ssel-menu::-webkit-scrollbar-thumb,
html[data-theme="light"] .lsel-menu::-webkit-scrollbar-thumb,
html[data-theme="light"] .tgp-list::-webkit-scrollbar-thumb,
html[data-theme="light"] .tpf-col::-webkit-scrollbar-thumb { background: var(--border-strong); }

/* The thread rail's thumb is the loud one — .6 resting, .9 on hover, over a pale track. */
html[data-theme="light"] .chat-threads { scrollbar-color: var(--border-strong) var(--control-track-08); }
html[data-theme="light"] .chat-threads::-webkit-scrollbar-track { background: var(--control-track-08); }
html[data-theme="light"] .chat-threads::-webkit-scrollbar-thumb { background: var(--border-strong); }
html[data-theme="light"] .chat-threads:hover::-webkit-scrollbar-thumb { background: var(--control-knob-off); }
html[data-theme="light"] .chat-conversations { scrollbar-color: var(--border-strong) transparent; }
html[data-theme="light"] .chat-conversations::-webkit-scrollbar-thumb { background: var(--border-strong); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   3. THE SHARED LIB SHEETS
   These travel furthest: boardTableStyle, listFilterStyle, listSearchStyle, detailSectionsStyle,
   quoteCardStyle and capacityStripStyle are injected by pages well outside this cluster, so a
   correction here is the widest-reaching in the file.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── boardTableStyle: the row ──
   A hover tinted with the accent says "this row is chosen" in the same colour the picked row uses.
   In light it is also a violet wash under every pointer move. The state ladder is neutral; the
   accent stays on the picked row's leading bar, which is what actually carries selection. */
/* --surface-inset, not --surface-hover: boards.css §2 unified row hover on the lighter step after
   the same gesture was measured at two different tones (228,230,235 here and on the ledgers,
   243,246,248 on the settings rail). The leading bar stays — it is what tells hover from a row that
   is merely present, and it is neutral rather than accent so it cannot be read as selection. */
html[data-theme="light"] .bt-row:hover {
    background: var(--surface-inset);
    box-shadow: inset 2px 0 0 var(--border-strong);
}
html[data-theme="light"] .bt-row:focus-visible {
    background: var(--surface-active);
    box-shadow: inset 2px 0 0 var(--focus-ring);
}

/* The group rule ran a violet hairline out to the measure. A rule is a rule. */
html[data-theme="light"] .bt-group-head::after {
    background: linear-gradient(to right, var(--border-default), transparent);
}

/* "+N more" is a secondary control: neutral fill, neutral border, text that reads. */
html[data-theme="light"] .bt-more {
    color: var(--ink-dim);
    border-color: var(--border-default);
}
html[data-theme="light"] .bt-more:hover {
    color: var(--text);
    border-color: var(--border-strong);
    background: var(--surface-hover);
}

/* ── listFilterStyle: the toolbar ──
   The pressed filter is a SELECTION, so it takes the selection pattern: the quiet wash plus a 3px
   leading bar. The wash alone at light-theme alpha reads weaker than dark's unselected state; the
   bar is what survives glare and colour blindness, and it is why the wash is allowed to be quiet. */
html[data-theme="light"] .lf-toggle.active {
    color: var(--text);
    background: var(--surface-selected);
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .lf-toggle.takeover-chip.active { border-color: var(--border-default); }

/* The popup itself was a tinted lavender panel. A menu is a menu: the popover rung, a real border
   (a card whose only boundary is a shadow vanishes under forced-colors) and the elevation. */
html[data-theme="light"] .lf-menu {
    background: var(--surface-4);
    border-color: var(--border-default);
    box-shadow: var(--elevation-3);
}
html[data-theme="light"] .lf-item.active {
    color: var(--text);
    background: var(--surface-selected);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}

/* ── listSearchStyle ── the magnifier is furniture, and focus is the ring's job. */
html[data-theme="light"] .ls-ic { color: var(--ink-muted); }
html[data-theme="light"] .list-search:focus-within { border-color: var(--focus-ring); }

/* ── detailSectionsStyle: the drill-down cards ── */
/* The card's leading edge is a category mark; its three named variants are real status colours and
   keep them. The unnamed default was violet for no reason. */
html[data-theme="light"] .dcard-accent { background: var(--border-strong); }
html[data-theme="light"] .dcard:hover { border-color: var(--border-strong); }
html[data-theme="light"] .dcard-chevron { color: var(--ink-muted); }
html[data-theme="light"] .dcard-subtoggle { color: var(--accent-text); }
html[data-theme="light"] .dcard-subtoggle:hover { color: var(--accent-solid); }

/* "Open the job" is one of several controls inside an expanded card — secondary, so not violet. */
html[data-theme="light"] .dcard-open-job {
    background: var(--surface-3);
    border-color: var(--border-default);
    color: var(--text);
}
html[data-theme="light"] .dcard-open-job:hover {
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] .dcard-open-job-arrow { color: var(--ink-muted); }

/* The vehicle chips under a customer. A hover goes darker, not violet. */
html[data-theme="light"] .veh-chip { color: var(--ink-dim); }
html[data-theme="light"] .veh-chip:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] .veh-chip-ic { color: var(--ink-muted); }

/* The spec sheet's filter field and the plate entry: focus is the ring, not a violet border. */
html[data-theme="light"] .specs-search:focus-within {
    border-color: var(--focus-ring);
    color: var(--ink-muted);
}
html[data-theme="light"] .specs-plate-input:focus { border-color: var(--focus-ring); }

/* A meter fill is the accent or the status it means — never a two-stop decorative gradient, which
   in light puts a saturated violet bar on a near-white track. */
html[data-theme="light"] .specs-progress-fill { background: var(--accent-solid); }
html[data-theme="light"] .specs-progress-pct { color: var(--ink-dim); }

/* ── quoteCardStyle: the total tile ──
   The total is the loudest FIGURE on a quote and it already has weight, size and position saying
   so. The violet panel behind it was a third signal for the same fact. */
html[data-theme="light"] .quote-card-total {
    background: var(--surface-3);
    border-color: var(--border-default);
}

/* ── capacityStripStyle ──
   The pressed place is a selection: it keeps --surface-selected from the sheet and gains the bar. */
html[data-theme="light"] .cs-place.on {
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .cs-place:hover { border-color: var(--border-strong); }
html[data-theme="light"] .cs-place-meter > i { background: var(--accent-solid); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   4. THE PANE AND THE FACT GRID
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

/* The label column's tick. A 4px dot with a 7px violet glow behind it, repeated down every row of
   every card on both detail pages — decoration at scale. It keeps the shape (it anchors the pair)
   and loses the colour and the halo. */
html[data-theme="light"] .detail-label { color: var(--ink-muted); }
html[data-theme="light"] .detail-label::before {
    background: var(--border-strong);
    box-shadow: none;
}

/* Row hovers inside the pane's duplicate blocks. */
html[data-theme="light"] .vx-dup-row:hover .vx-dup-arrow { color: var(--ink-dim); }
html[data-theme="light"] .cx-dup-row:hover .cx-dup-arrow { color: var(--ink-dim); }
html[data-theme="light"] .cx-veh-chip:hover .cx-veh-arrow { color: var(--ink-dim); }
html[data-theme="light"] .cx-veh-chip:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
}

/* The notes textarea. Focus is the ring plus an inner line, because you cannot ring accent violet
   with accent violet — WCAG 2.2 SC 2.4.11 wants ≥3:1 against the adjacent colour. */
html[data-theme="light"] .notes-input:focus {
    border-color: var(--focus-ring);
    box-shadow: inset 0 0 0 2px var(--focus-ring-inner),
                0 0 0 3px var(--focus-ring);
}
html[data-theme="light"] .vd-edit-field input:focus { border-color: var(--focus-ring); }
html[data-theme="light"] .cd-edit-field input:focus { border-color: var(--focus-ring); }

/* The rename pill: a secondary icon button beside the heading. Under 48px, so no backdrop-filter
   to lose — but it does need a fill that is not the accent. */
html[data-theme="light"] .vd-edit-pill,
html[data-theme="light"] .cd-edit-pill {
    color: var(--ink-dim);
    border-color: var(--border-default);
}
html[data-theme="light"] .vd-edit-pill:hover,
html[data-theme="light"] .cd-edit-pill:hover { background: var(--surface-3-hover); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   5. THE CHAT DRAWER
   One shape, four hosts: VehicleDetail, CustomerDetail, QuoteChatPanel and the ledger pane. Its
   violet is the densest concentration in the cluster — the trigger, the avatar, the takeover
   toggle, two rows of pills, the send button and every customer bubble.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

/* The trigger. It opens a drawer; the page's primary action is Spara. */
html[data-theme="light"] .chat-pill {
    background: var(--surface-3);
    border-color: var(--border-default);
    color: var(--ink-dim);
    box-shadow: none;
}
html[data-theme="light"] .chat-pill:hover { background: var(--surface-3-hover); }

/* The avatar and the customer's bubble were violet gradients carrying --ink, which in light is
   near-BLACK text on saturated violet. Flat --accent-solid with --text-on-accent is the only
   combination that both reads and keeps the bubble identifiable as the customer's. */
html[data-theme="light"] .chat-avatar {
    background: var(--accent-solid);
    color: var(--text-on-accent);
    box-shadow: none;
}
html[data-theme="light"] .chat-row.customer .chat-bubble {
    background: var(--accent-solid);
    color: var(--text-on-accent);
    box-shadow: none;
}

/* Send IS the primary action of the drawer, so it keeps the accent — as a flat fill with the text
   colour that belongs on one. */
html[data-theme="light"] .chat-send {
    background: var(--accent-solid);
    color: var(--text-on-accent);
}

/* Takeover is a toggle. Off it is secondary; on it is already red and stays red. */
html[data-theme="light"] .chat-takeover {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .chat-takeover:hover:not(:disabled) {
    background: var(--surface-3-hover);
    color: var(--text);
}

/* The two pill rails. An active pill is a selection: quiet wash, leading bar. */
html[data-theme="light"] .chat-thread-pill.active,
html[data-theme="light"] .chat-conv-pill.active,
html[data-theme="light"] .chat-service-pill.active {
    color: var(--text);
    background: var(--surface-selected);
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .chat-service-pill.active .chat-service-more { color: var(--ink-muted); }
html[data-theme="light"] .chat-service-more { color: var(--ink-muted); }

html[data-theme="light"] .chat-input:focus { border-color: var(--focus-ring); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   6. THE PICKERS
   StationSelect, LiftSelect, TagPicker, DatePickerField, TimePickerField, VehiclePicker. All six
   share one shape: a trigger with a violet chevron, a tinted popup, and a violet "on" row.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

/* Chevrons and field icons are furniture. */
html[data-theme="light"] .ssel-chev,
html[data-theme="light"] .lsel-chev,
html[data-theme="light"] .tgp-chev,
html[data-theme="light"] .dpf-ic { color: var(--ink-muted); }

/* The open/hover/focus border is the ring's job, not a decorative violet. */
html[data-theme="light"] .ssel.open .ssel-btn,
html[data-theme="light"] .lsel.open .lsel-btn,
html[data-theme="light"] .tgp.open .tgp-btn { border-color: var(--focus-ring); }
html[data-theme="light"] .lsel-btn:hover:not(:disabled),
html[data-theme="light"] .tgp-btn:hover:not(:disabled) { border-color: var(--border-strong); }
html[data-theme="light"] .tgp-search:focus { border-color: var(--focus-ring); }

/* The popups: the menu rung with a real border, not a lavender wash. */
html[data-theme="light"] .ssel-menu,
html[data-theme="light"] .lsel-menu,
html[data-theme="light"] .tgp-menu {
    background: var(--surface-4);
    border-color: var(--border-default);
    box-shadow: var(--elevation-3);
}

/* A chosen option is a selection. */
html[data-theme="light"] .ssel-opt.active,
html[data-theme="light"] .lsel-opt.active {
    background: var(--surface-selected);
    color: var(--text);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .tgp-opt.active { box-shadow: inset 3px 0 0 var(--accent-bar); }
html[data-theme="light"] .tgp-check { color: var(--accent-solid); }
html[data-theme="light"] .tgp-create { color: var(--accent-text); }

/* The calendar. The chosen day is the one accent object in the popup and it earns it — but as a
   flat fill with text that reads on it, not a two-stop gradient carrying near-black type. */
html[data-theme="light"] .dpf-day.on {
    background: var(--accent-solid);
    color: var(--text-on-accent);
}
html[data-theme="light"] .dpf-day.today { color: var(--accent-text); }
html[data-theme="light"] .dpf-month { color: var(--text); }
html[data-theme="light"] .dpf-nav { color: var(--ink-dim); }
html[data-theme="light"] .dpf-nav:hover { background: var(--surface-3-hover); }
html[data-theme="light"] .dpf-link { color: var(--accent-text); }
html[data-theme="light"] .dpf-link:hover { color: var(--accent-solid); }

/* The time columns, same shape. */
html[data-theme="light"] .tpf-ic-btn { color: var(--ink-muted); background: var(--recess-a05); }
html[data-theme="light"] .tpf-ic-btn:hover,
html[data-theme="light"] .tpf-ic-btn.open { background: var(--surface-3-hover); }
html[data-theme="light"] .tpf-cell.sel {
    background: var(--accent-solid);
    color: var(--text-on-accent);
}

/* VehiclePicker: the car chips are a list of ways in, not eight selected things. */
html[data-theme="light"] .vp-chip {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .vp-chip:hover { background: var(--surface-3-hover); }
html[data-theme="light"] .vp-chip:focus-visible { outline-color: var(--focus-ring); }
html[data-theme="light"] .vp-back { color: var(--accent-text); }
html[data-theme="light"] .vp-back:hover { color: var(--accent-solid); }
html[data-theme="light"] .vp-back:focus-visible { outline-color: var(--focus-ring); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   7. SLOT PILLS
   The booking rail. Its chosen pill and its picked summary both paint --c-indigo-8b5cf6 (which in
   light is #6d28d9) under --ink, which in light is near-black: dark type on a saturated violet
   tile. That is a legibility failure, not only an accent-discipline one.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

html[data-theme="light"] .sp-pill.on,
html[data-theme="light"] .sp-picked-pill,
html[data-theme="light"] .sp-day-picked {
    background: var(--accent-solid);
    border-color: var(--accent-solid);
    color: var(--text-on-accent);
}
html[data-theme="light"] .sp-pill:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] button.sp-day-head:hover { background: var(--surface-hover); }
html[data-theme="light"] .sp-nav-btn:hover:not(:disabled) {
    color: var(--text);
    background: var(--surface-hover);
}
html[data-theme="light"] .sp-nav-range,
html[data-theme="light"] .sp-week-name,
html[data-theme="light"] .sp-picked-label { color: var(--ink-muted); }
/* "Add your own time" is a secondary control beside the pills, not a second primary. */
html[data-theme="light"] .sp-own-add {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .sp-own-add:hover:not(:disabled) { background: var(--surface-3-hover); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   8. THE MODALS
   CheckinModal, FoundWorkModal, EquipmentPickerModal, QuoteRejectModal, CapacityModal, Collapsible,
   RepairOrder, ReachButton. Same three defects each: an accent-tinted pinned header, accent chips
   for every option, and an accent-tinted secondary button.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── CheckinModal ── */
html[data-theme="light"] .ckm-pin {
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .ckm-pin-row > b { color: var(--text); }
html[data-theme="light"] .ckm-result:hover { background: var(--surface-hover); }
/* A fill with no border is invisible in light and nothing errors: --surface-3 against the card it
   sits on has no boundary. In dark the source tag needed none, because a 20% violet wash IS its own
   edge. It is a status tag, so it takes the keyline rather than a bespoke edge. */
html[data-theme="light"] .ckm-sr-source.walkin {
    color: var(--ink-dim);
    background: var(--surface-3);
    border: 1px solid var(--keyline);
}
html[data-theme="light"] .ckm-veh-chip {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .ckm-veh-chip:hover { background: var(--surface-3-hover); }
html[data-theme="light"] .ckm-veh-chip.active {
    color: var(--text);
    background: var(--surface-selected);
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .ckm-veh-more:hover {
    color: var(--text);
    border-color: var(--border-strong);
}
html[data-theme="light"] .ckm-pill:hover { border-color: var(--border-strong); }
html[data-theme="light"] .ckm-pill.on {
    color: var(--text);
    background: var(--surface-selected);
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}

/* ── FoundWorkModal ── the pinned vehicle strip and the time note are context, not accent. */
html[data-theme="light"] .fwm-pin {
    border-left-color: var(--border-strong);
    background: var(--surface-3);
}
html[data-theme="light"] .fwm-pin-veh { color: var(--text); }
html[data-theme="light"] .fwm-pin-cust { color: var(--ink-muted); }
html[data-theme="light"] .fwm-timenote {
    border-left-color: var(--border-strong);
    background: var(--surface-3);
}
html[data-theme="light"] .fwm-timenote-t { color: var(--text); }
html[data-theme="light"] .fwm-line { border-left-color: var(--border-strong); }
/* "Add" is the modal's primary action inside its own row, so it keeps the accent — flat, with the
   text colour that belongs on a filled accent. */
html[data-theme="light"] .fwm-add-btn {
    color: var(--text-on-accent);
    background: var(--accent-solid);
    border-color: var(--accent-solid);
}
html[data-theme="light"] .fwm-add-btn:hover:not(:disabled) {
    background: var(--accent-text);
    color: var(--text-on-accent);
}
html[data-theme="light"] .fwm-pill:hover { border-color: var(--border-strong); }
html[data-theme="light"] .fwm-pill.on {
    color: var(--text);
    background: var(--surface-selected);
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .fwm-note-toggle:hover {
    color: var(--text);
    border-color: var(--border-strong);
}
html[data-theme="light"] .fwm-note-plus { color: var(--ink-muted); }

/* ── EquipmentPickerModal ── a checked box is the accent; a checked ROW is a selection. */
html[data-theme="light"] .eqp-item.on {
    background: var(--surface-selected);
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .eqp-item input:hover { border-color: var(--border-strong); }
html[data-theme="light"] .eqp-item input:focus-visible { box-shadow: 0 0 0 3px var(--focus-ring); }
html[data-theme="light"] .eqp-item input:checked {
    background: var(--accent-solid);
    border-color: var(--accent-solid);
}
html[data-theme="light"] .eqp-units.placed { color: var(--ink-dim); }

/* ── QuoteRejectModal ── the chosen reason keeps the selection pattern. */
html[data-theme="light"] .kind-option.active {
    border-color: var(--border-default);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .reject-field textarea:focus { border-color: var(--focus-ring); }

/* ── CapacityModal ── its own re-declared buttons. The confirm is the primary action of the sheet;
   the tinted-glass version measured as neither a button nor a link on white. */
html[data-theme="light"] .cap-panel button.btn-astra {
    color: var(--text-on-accent);
    background: var(--accent-solid);
    border-color: var(--accent-solid);
}
html[data-theme="light"] .cap-panel button.btn-astra:hover:not(:disabled) { background: var(--accent-text); }
html[data-theme="light"] .cap-panel button.btn-astra-neutral { color: var(--ink-dim); }
html[data-theme="light"] .cap-panel button.btn-astra-neutral:hover:not(:disabled) {
    color: var(--text);
    background: var(--surface-hover);
}
html[data-theme="light"] .cap-status.custom { color: var(--accent-text); }
html[data-theme="light"] .cap-mech-link { color: var(--accent-text); }
html[data-theme="light"] .cap-mech-link:hover { color: var(--accent-solid); }
html[data-theme="light"] .cap-field input:focus {
    border-color: var(--focus-ring);
    background: var(--recess-a05);
}

/* ── Collapsible ── the disclosure's own action pill. */
html[data-theme="light"] .collapsible-count {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .collapsible-chevron { color: var(--ink-muted); }

/* ── RepairOrder ── */
html[data-theme="light"] .ro-invoice-btn {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .ro-invoice-btn:hover {
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] .ro-invoice-btn:focus-visible { outline-color: var(--focus-ring); }
html[data-theme="light"] .full-quote-btn {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .full-quote-btn:hover { background: var(--surface-3-hover); }
html[data-theme="light"] .full-quote-btn:focus-visible { outline-color: var(--focus-ring); }

/* ── ReachButton ── the floating trigger IS a primary action, flat-filled. */
html[data-theme="light"] .reach-act.primary { background: var(--accent-solid); border-color: var(--accent-solid); color: var(--text-on-accent); }
html[data-theme="light"] .reach-pop {
    background: var(--surface-4);
    border-color: var(--border-default);
    box-shadow: var(--elevation-3);
}

/* ── QuoteAssistChat / InvoiceChat ── the two assistant sheets. Their triggers are primary; their
   scope dots, chips and quoted turns are not.
   InvoiceChat is styled in the finance token system (--fin-*), which the money cluster owns, so it
   is touched here ONLY where the raw violet channels leak in — its panel, its card language and its
   wells are left to that cluster. */
html[data-theme="light"] .qa-btn {
    background: var(--accent-solid);
    border-color: var(--accent-solid);
    color: var(--text-on-accent);
}
html[data-theme="light"] .qa-panel {
    background: var(--surface-4);
    border-color: var(--border-default);
    box-shadow: var(--elevation-3);
}
html[data-theme="light"] .qa-scope i { background: var(--border-strong); }
/* The asked-question bubble. Right-alignment is doing most of the work of telling it from the
   answer, but a fill with no border still has no edge of its own on a white card — and a keyline
   costs nothing here. */
html[data-theme="light"] .qa-q,
html[data-theme="light"] .ic-q { background: var(--surface-3); border: 1px solid var(--keyline); }
html[data-theme="light"] .qa-a { border-left-color: var(--border-strong); }
html[data-theme="light"] .ic-a { border-left-color: var(--border-strong); }
html[data-theme="light"] .qa-send {
    border-color: var(--border-default);
    background: var(--surface-3);
    color: var(--text);
}
html[data-theme="light"] .qa-send.writes {
    background: var(--accent-solid);
    border-color: var(--accent-solid);
    color: var(--text-on-accent);
}
/* Only the resting fill, which is a raw violet channel — the .writes state is --fin-accent-deep and
   belongs to the money cluster. */
html[data-theme="light"] .ic-send {
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .qa-input:focus,
html[data-theme="light"] .ic-input:focus { border-color: var(--focus-ring); }
html[data-theme="light"] .qa-chip:hover:not(:disabled) { border-color: var(--border-strong); }
html[data-theme="light"] .qa-undo:hover:not(:disabled) { border-color: var(--border-strong); }
html[data-theme="light"] .qa-btn:focus-visible,
html[data-theme="light"] .qa-x:focus-visible,
html[data-theme="light"] .qa-mode:focus-visible,
html[data-theme="light"] .qa-undo:focus-visible,
html[data-theme="light"] .qa-send:focus-visible { outline-color: var(--focus-ring); }

/* ── WorkQueueChat ── the customer drawer's own bubble/send pair, same failure as the chat drawer:
   near-black type on a saturated violet fill. */
html[data-theme="light"] .cwq-row.biz .cwq-bubble {
    background: var(--accent-solid);
    border-color: var(--accent-solid);
    color: var(--text-on-accent);
}
html[data-theme="light"] .cwq-send {
    background: var(--accent-solid);
    color: var(--text-on-accent);
}
html[data-theme="light"] .cwq-avatar {
    color: var(--ink-dim);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .cwq-input:focus { border-color: var(--focus-ring); }

/* ── The two Customers dialogs (import + new customer) ── */
html[data-theme="light"] .cxm-file-btn {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .cxm-file input:focus-visible + .cxm-file-btn { outline-color: var(--focus-ring); }
html[data-theme="light"] .cxm-field input:focus,
html[data-theme="light"] .cxm-field textarea:focus { border-color: var(--focus-ring); }

/* ── CanceledRecord ── its quoted block and its close/link furniture. */
html[data-theme="light"] .cnr-close:focus-visible { outline-color: var(--focus-ring); }
html[data-theme="light"] .cnr-link { color: var(--accent-text); }
html[data-theme="light"] .cnr-quote-toggle { color: var(--accent-text); }
html[data-theme="light"] .cnr-quote-toggle:hover { color: var(--accent-solid); }
html[data-theme="light"] .cnr-quote-toggle:focus-visible { outline-color: var(--focus-ring); }
html[data-theme="light"] .cnr-quote-text { border-left-color: var(--border-strong); }


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   THE STRUCTURAL PASS — registers
   ═════════════════════════════════════════════════════════════════════════════════════════════
   Everything above this line is the COLOUR pass: it took the violet out. Everything below is the
   pass that made Work Queue read as a Stripe-grade light UI rather than as a de-tinted dark one —
   geometry, density, badge grammar, and the removal of chrome that only exists because near-black
   needs help separating things.

   The worked example is `boards.css` §"WORK QUEUE — the light structural prototype"; the shared
   vocabulary and the recipes are `_base.css`. Nothing here restates either. In particular the whole
   shared ledger idiom — `.lx-row`, `.lx-list`, `.lx-bucket`, `.lx-tab`, `.lx-state` — is already
   done, app-wide, in boards.css §5.1–5.5, and /customers and /vehicles are consumers of exactly
   that stylesheet. So this file's job on the two register BOARDS is only the page-own vocabulary
   the shared idiom does not know about: the plates, the source tags, the twin blocks, the pane.

   THE ONE SENTENCE THAT GOVERNS THIS FILE: a register is a table. Columns that are compared are
   tabular and they agree on an edge; a row is a row, not a card; and the only thing separating two
   rows is one 1px keyline.

   Scoping: `.cx-wrapper` /customers · `.vx-wrapper` /vehicles · `.cd-wrapper` /customer-detail ·
   `.vd-wrapper` /vehicle-detail. None of those four roots is used by any other page (checked), and
   the classes INSIDE them mostly are — `.detail-row`, `.section-title`, `.glass-section`,
   `.detail-value` are each declared by five or six page sheets — so every rule below is anchored
   on a root. A bare selector here would reach into three other agents' clusters.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── R1. THE HEADER PILLS BECOME RECTANGLES, AND THE COUNT STOPS BEING A STATE ─────────────────
   `.stat-pill` is declared only by this cluster's four sheets, so it is genuinely ours. It is a
   999px capsule: consumer-app language, and it wastes width at both ends of every instance in a row
   of three or four of them. --r-control, and the count reads as a fact.

   `.stat-pill.warn` — "42 dubbletter", "6 väntar" — keeps its hue. It is not a per-row verdict
   fired on every record; it is a header count that is usually ZERO, and a value that is normally
   absent has not spent its colour budget. What it does lose is its INK: the sheet paints
   `--c-amber-fcd34d`, a raw dark-theme channel, over the measured `--st-expected-soft` fill. */
html[data-theme="light"] .stat-pill {
    border-radius: var(--r-control);
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}
html[data-theme="light"] .stat-pill.warn {
    color: var(--st-expected);
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
}

/* The four page headers get the rule that says the board begins here — the same single hairline
   Stripe separates a header from its content with, and nothing else. */
html[data-theme="light"] .cx-wrapper > .cx-header,
html[data-theme="light"] .vx-wrapper > .vx-header,
html[data-theme="light"] .cd-wrapper > .cd-header,
html[data-theme="light"] .vd-wrapper > .vd-header {
    padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--keyline);
}


/* ── R2. THE ROW FLAG LOSES ITS GLOW ───────────────────────────────────────────────────────────
   `.lx-flag` is a 6px disc with an 8px coloured glow behind it. A glow needs darkness to read as
   emission; on white it is a smudge around a dot. The dot stays — on these two boards it is a
   leading rule rather than a status carrier, and the state is spelled in words in `.lx-state`
   beside it, which boards.css §5.1 has already turned into a badge with a shape-distinct glyph.

   Scoped to the two register wrappers: `.lx-flag` is the shared ledger's class and five other
   boards render it. */
html[data-theme="light"] .cx-wrapper .lx-flag,
html[data-theme="light"] .vx-wrapper .lx-flag {
    box-shadow: none;
}

/* The lapsed-inspection chip on /customers is the shared badge (boards.css §5.1 gives it the
   rectangle and the neutral dot) with the page's own `.overdue` variant, which that file does not
   know about. Its fill and border are already the measured tokens; what is missing is the second
   carrier. Under deuteranopia this red and the amber `.waiting` beside it collapse — the cross is
   what keeps them apart. */
html[data-theme="light"] .lx-state.overdue::before { content: "✕"; }


/* ── R3. THE PLATE COLUMN, AND EVERY COMPARED FIGURE, GOES TABULAR ─────────────────────────────
   The plate is the whole reason both boards exist in this shape: on /vehicles it is the identity
   column and on /customers it is the right rule. Both are already mono, which fixes the glyph
   WIDTH but not the FIGURE width — the fallback stack ends at the generic `monospace`, and what
   that resolves to is the platform's business, not this design system's. `tnum` pins it, so
   `ABC 123` and `WXY 987` occupy the same box and the column is a real column.

   The date and the money slots take it for the reason _base §5 gives: a number in a compared column
   is read down, not across. */
html[data-theme="light"] .cx-plate,
html[data-theme="light"] .cx-veh-plate,
html[data-theme="light"] .vx-plate,
html[data-theme="light"] .cx-wrapper .lx-when,
html[data-theme="light"] .vx-wrapper .lx-when,
html[data-theme="light"] .cx-wrapper .lx-money,
html[data-theme="light"] .vx-wrapper .lx-money,
html[data-theme="light"] .cx-fact-value,
html[data-theme="light"] .vx-fact-value {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}


/* ── R4. SOURCE TAGS AND ROW TAGS: THE BADGE GRAMMAR, AND THE DATA/STATE SPLIT ─────────────────
   `.cx-src` (where the customer came from) and `.vx-tag` (what the car is) are 999px capsules in
   four and three colours. Both get the badge rectangle from _base §1 — 4px, 11px, --fw-label, soft
   fill, tint border, dark ink — and then they SPLIT, on the distinction boards.css drew for
   `.wq-tag-lead`:

     a DATA VALUE ("Chatt", "Disk", "Vinterdäck") is a noun. It takes the badge's geometry and the
     neutral rung, and it takes NO glyph — a bullet in front of a noun is noise.
     a STATE ("Dubblett", "Huvudbil") is a verdict about the record. It keeps a hue and it MUST
     carry a shape, because hue alone fails for ~1 in 12 mechanics.

   That is why the violet "Chatt" and the teal "Disk" go neutral: they are not two states, they are
   two values of one field, and spending two hues on an origin label is what leaves nothing left for
   the twin marker three columns over. The words already say which is which. */
html[data-theme="light"] .cx-src,
html[data-theme="light"] .vx-tag {
    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);
    border: 1px solid var(--keyline);
    color: var(--ink-dim);
}
html[data-theme="light"] .cx-src.chat,
html[data-theme="light"] .cx-src.walkin,
html[data-theme="light"] .cx-src.taken,
html[data-theme="light"] .vx-tag.tires {
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-dim);
}

/* One person or one car entered twice. This is the only thing on either board that is WRONG rather
   than merely true, so it is the only thing that keeps a hue on the row. */
html[data-theme="light"] .cx-src.dup,
html[data-theme="light"] .vx-tag.dup {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}
html[data-theme="light"] .cx-src.dup::before,
html[data-theme="light"] .vx-tag.dup::before { content: "!"; font-size: 1em; line-height: 1; }

/* The record that survives a merge, and the car that is the customer's main one: good news, so a
   check rather than a bang. */
html[data-theme="light"] .cx-src.keep,
html[data-theme="light"] .vx-tag.primary {
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .cx-src.keep::before,
html[data-theme="light"] .vx-tag.primary::before { content: "✓"; font-size: 1em; line-height: 1; }


/* ── R5. THE PANE IS A SCROLL REGION, SO IT NEEDS AN EDGE ──────────────────────────────────────
   `.lx-sheet` is glass: --surface-1 plus a 28px backdrop blur plus a 1px rgba(--sf-rgb, 0.11)
   border. In light the filter token resolves to `none` and the border resolves to ~11% ink, so what
   is left is a near-white box with a hint of a line, sitting beside a list column that boards.css
   §5.4 has given a real bordered surface. The two halves of the page stop matching.

   Surface AND border, per _base's scroll-region recipe — and boards.css made exactly this call for
   the Work Queue pane, including the reasoning for why two bordered boxes side by side are correct
   here (they are not cards arguing, they are two scroll regions, and each has to show where it
   begins). The radius comes to --r-card, and the shadow goes: it is not floating.

   `.lx-sheet` is the shared ledger's pane class, so this is anchored on the two register roots. */
html[data-theme="light"] .cx-wrapper .lx-sheet,
html[data-theme="light"] .vx-wrapper .lx-sheet {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* The pane's head rule was an 8%-white hairline — invisible on white. */
html[data-theme="light"] .cx-sheet-head,
html[data-theme="light"] .vx-sheet-head { border-bottom-color: var(--keyline); }

/* The pane's own action bar sits at the foot of the record and needs to read as belonging to it. */
html[data-theme="light"] .cx-wrapper .lx-actions,
html[data-theme="light"] .vx-wrapper .lx-actions { border-top-color: var(--keyline); }


/* ── R6. THE FACT LIST IS A TABLE, NOT A STACK OF PAIRS ────────────────────────────────────────
   `.cx-fact` / `.vx-fact` are label-left / value-right pairs on a 5%-white hairline — i.e. on
   nothing, in light. They become rows: the --row-h rung as a min-height with the vertical padding
   DERIVED from it (exempt from the --sp-* scale, per the spec — rounding 9px to 8 here changes the
   height of every fact row on two pages), a real keyline divider, and no divider under the last.

   The label goes to the eyebrow and the value carries the one --fw-strong in the pair. That is the
   whole hierarchy: the reader is looking for the value, and the label is the caption that finds it. */
html[data-theme="light"] .cx-fact,
html[data-theme="light"] .vx-fact {
    min-height: var(--row-h);
    padding: 9px 0;                     /* derived from --row-h — not on the space scale */
    border-bottom: 1px solid var(--keyline);
}
html[data-theme="light"] .cx-fact:last-child,
html[data-theme="light"] .vx-fact:last-child { border-bottom-color: transparent; }

html[data-theme="light"] .cx-fact-label,
html[data-theme="light"] .vx-fact-label,
html[data-theme="light"] .cx-veh-label {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: uppercase;
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}
html[data-theme="light"] .cx-fact-value,
html[data-theme="light"] .vx-fact-value {
    font-weight: var(--fw-strong);
    color: var(--ink);
}

/* Lifetime value was mint green, because everywhere else in this app that slot is money and money
   is drawn in the "work logged" colour. A sum is not a success — it is the same figure whether it
   reads 0 kr or 84 000 kr, and painting it green states an outcome the number does not carry. */
html[data-theme="light"] .cx-fact-value.money { color: var(--ink); }

/* An expired besiktning IS a verdict and keeps red; it just gets the weight the other values have. */
html[data-theme="light"] .vx-fact-value.overdue { color: var(--st-breach); }


/* ── R7. THE TWIN BLOCK ────────────────────────────────────────────────────────────────────────
   The amber-edged panel that reports duplicate records. It stays amber — this is the one genuinely
   wrong thing a register can contain, and it is not present on most records. What changes is its
   shape (a 14px consumer radius to --r-card), its title (a raw amber channel to the measured status
   ink, as an eyebrow), and the merge receipt, which was a raw emerald channel. */
html[data-theme="light"] .cx-dup,
html[data-theme="light"] .vx-dup {
    border-radius: var(--r-card);
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
}
html[data-theme="light"] .cx-dup-title,
html[data-theme="light"] .vx-dup-title {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    color: var(--st-expected);
}
html[data-theme="light"] .cx-dup-row,
html[data-theme="light"] .vx-dup-row {
    min-height: var(--row-h);
    border-radius: var(--r-control);
}
html[data-theme="light"] .cx-dup-row:hover,
html[data-theme="light"] .vx-dup-row:hover { background: var(--surface-inset); }
html[data-theme="light"] .cx-merge-msg,
html[data-theme="light"] .vx-merge-msg { color: var(--st-logged); }
html[data-theme="light"] .cx-merge-msg.error,
html[data-theme="light"] .vx-merge-msg.error { color: var(--st-breach); }

/* The car chips under a customer are a list of ways in. Rectangles at the control rung, a real
   edge, and the row height the rest of the pane is on. */
html[data-theme="light"] .cx-veh-chip {
    min-height: var(--row-h);
    border-radius: var(--r-control);
    background: var(--surface);
    border-color: var(--keyline);
}

/* The pane's free-text note. A well below the card, with an edge — not a slab. */
html[data-theme="light"] .cx-notes,
html[data-theme="light"] .vx-notes {
    border-radius: var(--r-control);
    background: var(--surface-inset);
    border-color: var(--keyline);
}


/* ═════════════════════════════════════════════════════════════════════════════════════════════
   R8. /customer-detail AND /vehicle-detail — THE RECORD IS THE PAGE
   ═════════════════════════════════════════════════════════════════════════════════════════════
   Both pages are a column of `.glass-section` cards, each holding four or five label/value pairs.
   In dark that is how the sections separate: a tint over near-black with a lit top edge. In light it
   is four rounded white rectangles on an off-white ground, each with a 24px blur shadow under it —
   a record wearing a card around every field group, which is the single most consumer-app shape
   left in the cluster.

   A record does not need a card around every field group. Stripe, Linear and Carbon all run a
   record full-bleed and separate its sections with a heading and a rule. The section keeps a
   surface and ONE keyline (it must — a white fill with no border on #f6f8fa is invisible and
   nothing throws), and loses the blur, the shadow, the lit edge and the 20px radius.

   `.glass-section` is also declared by /quote-history-detail, which is the detail cluster's file, so
   both roots are named here and that page is left alone. */
html[data-theme="light"] .cd-wrapper .glass-section,
html[data-theme="light"] .vd-wrapper .glass-section {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* The section heading. It was 10.9px at --ink-faint, which light aliases to --ink-muted — legible,
   but sized below the eyebrow floor and tracked at 1.5px, which at that size is a gap rather than
   tracking. The eyebrow rung, exactly as _base declares it, so a section heading here is the same
   object as a section heading on the Work Queue. */
html[data-theme="light"] .cd-wrapper .section-title,
html[data-theme="light"] .vd-wrapper .section-title {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}

/* The fact rows. Same move as R6 and for the same reason: these are the rows a mechanic reads down
   looking for one value, so they get a height, a divider, and a value that is heavier than its
   caption. The gap between rows goes to zero — the keyline is the separation now, and keeping both
   is the "two separation mechanisms" fault boards.css §4 names. */
html[data-theme="light"] .cd-wrapper .detail-rows,
html[data-theme="light"] .vd-wrapper .detail-rows { gap: 0; }

html[data-theme="light"] .cd-wrapper .detail-row,
html[data-theme="light"] .vd-wrapper .detail-row {
    min-height: var(--row-h);
    padding: 9px 0;                     /* derived from --row-h — not on the space scale */
    border-bottom: 1px solid var(--keyline);
}
html[data-theme="light"] .cd-wrapper .detail-row:last-child,
html[data-theme="light"] .vd-wrapper .detail-row:last-child { border-bottom-color: transparent; }

/* The label column. The colour pass above already took it off a 2.15:1 violet-at-alpha; this puts
   it on the eyebrow rung, and DELETES the tick.

   The tick is a 4px dot before every label. It was violet with a 7px halo (fixed above), and even
   neutral it is a bullet in front of forty captions across two pages — the exact shape _base warns
   about, where a decorative dot reads as a status mark. In the dark design it earns its keep,
   because it anchors a pair whose two halves sit at opposite edges of a wide card and there is no
   rule between them. Here there IS a rule between them, so the anchor is redundant and it is the
   dot that goes, not the rule. */
html[data-theme="light"] .cd-wrapper .detail-label,
html[data-theme="light"] .vd-wrapper .detail-label {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}
html[data-theme="light"] .cd-wrapper .detail-label::before,
html[data-theme="light"] .vd-wrapper .detail-label::before { display: none; }

/* The value is what the row is FOR, so it carries the one strong weight in the pair. */
html[data-theme="light"] .cd-wrapper .detail-value,
html[data-theme="light"] .vd-wrapper .detail-value {
    font-weight: var(--fw-strong);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}
/* Two exceptions. The raw customer/vehicle key is a support artefact, not a fact about the record —
   it stays quiet and stays proportional, because an opaque 36-character token is not a figure being
   compared. And the owner cell is a link, whose colour is set by the accent block above. */
html[data-theme="light"] .cd-wrapper .detail-value.mono,
html[data-theme="light"] .vd-wrapper .detail-value.mono {
    font-weight: var(--fw-body);
    color: var(--ink-muted);
    font-variant-numeric: normal;
}
html[data-theme="light"] .vd-wrapper .detail-value.owner-link { font-weight: var(--fw-label); }

/* THE THIRD EXCEPTION, AND IT IS AN EXCEPTION TO THE EXCEPTION.
   The rule above quiets `.mono` because an opaque 36-character key is a support artefact. The
   registration plate wore the same `detail-value mono` pair (VehicleDetail.vue:74) as `vehicle_id`
   and `customer_id`, so it was quieted with them — leaving the one field that IS the car's identity
   rendering lighter than its model name two rows above. No selector could separate them: the class
   pair was identical.

   So the markup grew one: `vd-plate`, added to that span and to nothing else. That is safe for dark
   BY CONSTRUCTION rather than by luck — a class with no rule declares nothing, and this file is the
   only place `vd-plate` appears in the codebase (grepped). Dark keeps VehicleDetail.css:208 exactly
   as it was.

   What comes back is what the `.mono` rule took away and nothing else: the strong weight, the full
   ink, and tabular figures — a plate is read digit by digit and compared against a screen, which is
   the definition of a figure being compared. The monospace family and the smaller size STAY: they
   are what makes a plate scannable, and they were never the problem. (0,4,0) against the `.mono`
   rule's (0,3,0), so it wins on specificity and not on being nine lines further down. */
html[data-theme="light"] .vd-wrapper .detail-value.mono.vd-plate {
    font-weight: var(--fw-strong);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* Lifetime cost, same call as `.cx-fact-value.money` in R6: a sum is not a success state. */
html[data-theme="light"] .cd-wrapper .detail-value.cost { color: var(--ink); }

/* A booked-in date that has passed IS a verdict, so it keeps red — off the raw `--c-red-fca5a5`
   channel and onto the measured status ink. The "waiting" chip beside it takes the badge grammar. */
html[data-theme="light"] .cd-wrapper .rq-date { font-variant-numeric: tabular-nums; }
html[data-theme="light"] .cd-wrapper .rq-date.overdue { color: var(--st-breach); }
html[data-theme="light"] .cd-wrapper .unavail-chip {
    border-radius: 4px;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-dim);
}
html[data-theme="light"] .cd-wrapper .unavail-chip.waiting {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}

/* The two scrollers. They hold the sections rather than being a bordered region themselves — each
   section already carries its own edge — so they take the neutral thumb and nothing else. */
html[data-theme="light"] .cd-scroll,
html[data-theme="light"] .vd-scroll { scrollbar-color: var(--keyline-strong) transparent; }
html[data-theme="light"] .cd-scroll::-webkit-scrollbar-thumb,
html[data-theme="light"] .vd-scroll::-webkit-scrollbar-thumb { background: var(--keyline-strong); }

/* The rename panel that opens under the title. An inset block inside a bordered section: the inset
   rung with a real edge, at the control radius, so a nested box is smaller-radiused than the box
   containing it. */
html[data-theme="light"] .cd-wrapper .cd-edit-panel,
html[data-theme="light"] .vd-wrapper .vd-edit-panel {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    border-radius: var(--r-control);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   C1 — THE WIDE-VIEWPORT FIX
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The complaint is "content stops two-thirds up the screen" and the received diagnosis was that the
   fault is vertical — a 100dvh shell whose scroll child is missing `min-height: 0`. Measured on the
   real pages, that is not what is happening here. The fault is HORIZONTAL and it is a hard cap:

       Customers   --lx-page  68.5rem = 1096px   ~800px unused at 1900
       Vehicles    --lx-page  71.5rem = 1144px   ~750px unused at 1900
       Work Queue  --lx-page          = 1560px   ~340px unused  <- the page the user signed off

   Both registers cap ~450px narrower than the console that was approved, on the same monitor. That
   is the dead space.

   AND RAISING THE CAP ALONE DOES NOTHING, which is why this needs two declarations rather than one:
   `.lx-split` pins the detail column at `minmax(0, 33rem)` / `minmax(0, 32rem)`, so the pane cannot
   grow into space the wrapper gains. The list would simply sit further left with the same gap to
   its right.

   WHY 1560 AND NOT "REMOVE THE CAP": a cap is right, the number was wrong. Uncapped, the row's
   identity cell and its figure end up a metre apart on a wide monitor and the eye loses the line.
   1560 is not invented here either — it is the measure the console already uses and the one the
   user has looked at and approved.

   The prose cap is untouched. Capping a paragraph at ~34em is about reading measure and is correct;
   it was never the thing making a data surface look empty.

   Light-only, so dark's layout does not move and the baseline stays immutable while this is judged.
   If it survives review it gets applied to both themes as one justified re-baseline. */
/* APPLIED TO BOTH THEMES, 2026-08-11, and deleted here — the cross-theme width sweep found dark had
   never followed, so /customers and /vehicles were 1096/1144 wide in dark against light's 1560.
       `--lx-page: 1560px`  ->  Customers.css:20 / Vehicles.css:20 as `var(--page-measure)` (=1560px)
   The `> .lx-split` pair that stood here (`minmax(340px, var(--lx-col)) minmax(0, 1fr)`) was already
   dead: E1 below overrode it at identical specificity, later in the file. E1's ratio is the one that
   moved to the page sheets. The prose above is kept as the record of why the cap is 1560. */

/* ═════════════════════════════════════════════════════════════════════════════════════════════
   THE COMPLIANCE SWEEP — registers (2026-08-10)
   ═════════════════════════════════════════════════════════════════════════════════════════════
   Everything above was written against the two BOARDS and the two RECORD pages. This pass walks the
   same four pages again looking only for rules that were never applied, and it finds them in the two
   places a page-level pass does not look: the dialogs, and the chat drawer.

   The chat drawer is the biggest single gap in the cluster. §5 above took the VIOLET out of it — the
   trigger, the avatar, the bubbles, the send button — and stopped there, so every one of its shapes
   is still the dark one: a near-black translucent ground, five 999px capsules, a two-stop emerald
   bubble, and six dividers built on `rgba(var(--sf-rgb), 0.08)`, which in light is ink at 8% and
   composites to nothing. It is one component with five hosts (/customer-detail, /vehicle-detail,
   /job-detail, /finished-job-detail, /quote-history-detail), all of which are this file's cluster or
   the detail cluster — both mine — so it is corrected once here, unscoped, exactly as §5 already
   does for `.chat-pill` and `.chat-avatar`. A second declaration in detail.css is the
   `.purple-gradient` failure this rebuild exists to stop.

   CLASS NAMES VERIFIED, all read out of the sheet or the template, none guessed:
     .cxm-panel / .cxm-backdrop            Customers.css:335-341, rendered Customers.vue:315,353
     .chat-drawer .chat-head .chat-foot    VehicleDetail.css:380-573, VehicleDetail.vue:245
     .chat-thread-pill .chat-conv-pill     VehicleDetail.css:517-568 (`.active` is the only variant)
     .chat-conversations                   VehicleDetail.css:536
     .chat-input .chat-send .chat-daysep   VehicleDetail.css:579-641, VehicleDetail.vue:271,286
     .chat-bubble.human / .chat-row.bot    VehicleDetail.css:605, :662
   ═════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── S1. THE TWO CUSTOMERS DIALOGS — RULE 5 ────────────────────────────────────────────────────
   `.cxm-panel` is `linear-gradient(160deg, var(--c-blue-171a24), var(--c-blue-10131b))`. Those two
   channels carry light values (#d4ddee → #d6dff1), so the dialog does not break — it renders as a
   mid-blue-grey slab with a two-stop wash across it, which is the one surface on the page that is
   neither the canvas nor a card. A dialog is the most raised surface in the app: white, one keyline,
   the popover elevation, and the card radius rather than the 16px consumer round.

   Its border is `rgba(var(--sf-rgb), 0.08)` — RULE 10, ink at 8% — and its shadow is
   `rgba(var(--ref-shadow-base), 0.55)` at 60px blur, a dark-ground drop that reads as a smudge on
   white. `--elevation-3` is the measured light popover shadow. */
html[data-theme="light"] .cxm-panel {
    border-radius: var(--r-card);
    background: var(--surface);
    background-image: none;
    border: 1px solid var(--keyline);
    box-shadow: var(--elevation-3);
}

/* The dialog's field labels are the eyebrow, like every other label in this cluster (R6, R8). */
html[data-theme="light"] .cxm-field > span {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}

/* `.cxm-field input` / `textarea` are `--surface-2` (#fcfcfe) on `rgba(var(--sf-rgb), 0.12)` inside a
   white dialog: RULE 8 + RULE 10. _base's form-surface rule reaches the textarea and the typed
   inputs by element, but it keys on `[type="..."]`, and this dialog's fields are typed — so the
   ONLY thing missing is the radius, which the sheet authors at 8px against the control rung. */
html[data-theme="light"] .cxm-field input,
html[data-theme="light"] .cxm-field textarea {
    border-radius: var(--r-control);
}

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


/* ── S2. THE CHAT DRAWER'S GROUND — RULES 5, 10 and 11 ─────────────────────────────────────────
   `rgba(var(--ch-indigo-120c22), 0.82)` over a 32px backdrop blur. In light that channel is
   221,226,242, so the drawer is a translucent pale-blue sheet with the page showing through it — two
   grounds meeting on a line nobody drew, which is RULE 11 stated at the component level. A drawer is
   an overlay: it is opaque, it is the surface, and the one line down its leading edge is what says
   the page continues behind it.

   The 60px `--ref-shadow-base` drop goes to the measured light popover elevation. */
html[data-theme="light"] .chat-drawer {
    background: var(--surface);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-left: 1px solid var(--keyline);
    box-shadow: var(--elevation-3);
}

/* The four horizontal rules inside it, all of them RULE 10 hairlines on the surface channel. */
html[data-theme="light"] .chat-head { border-bottom: 1px solid var(--keyline); }
html[data-theme="light"] .chat-foot { border-top: 1px solid var(--keyline); }
html[data-theme="light"] .chat-threads { border-bottom: 1px solid var(--keyline); }
html[data-theme="light"] .chat-conversations { border-bottom: 1px solid var(--keyline); }

/* `--surface-sunken` is #e4e6eb in light — DARKER than the canvas (#eef2f7) and much darker than the
   drawer it sits inside, so the conversation rail reads as a grey band cut across a white sheet.
   The inset rung is the sanctioned step below a surface, and the keyline above already separates it. */
html[data-theme="light"] .chat-conversations { background: var(--surface-inset); }

/* The head's `--surface-a03` resolves to #fafbfd — a near-white on white with no edge. It has a
   keyline under it now, so it takes the surface and stops pretending to be a tint. */
html[data-theme="light"] .chat-head { background: var(--surface); }


/* ── S3. THE DRAWER'S FIVE CAPSULES — RULE 2 ───────────────────────────────────────────────────
   `.chat-takeover`, `.chat-thread-pill`, `.chat-input`, `.chat-send` and `.chat-daysep span` are all
   999px/50px. Five capsules stacked down a 420px column is the consumer-app read the whole theme is
   trying to leave; and on the input it costs a full em of width at each end of the one control the
   drawer exists to drive.

   Everything here keeps the colour §5 gave it. This is geometry only. */
html[data-theme="light"] .chat-takeover,
html[data-theme="light"] .chat-thread-pill,
html[data-theme="light"] .chat-input,
html[data-theme="light"] .chat-send,
html[data-theme="light"] .chat-daysep span {
    border-radius: var(--r-control);
}

/* The thread rail's resting pill: `--surface-a05` (#fcfcfe) on `rgba(var(--sf-rgb), 0.08)` is a
   white rectangle on a white drawer with a border you cannot see — RULE 8 and RULE 10 in one
   declaration. The chip recipe from _base §2, which its `.active` state (§5) already assumes. */
html[data-theme="light"] .chat-thread-pill {
    background: var(--surface);
    border-color: var(--keyline-strong);
    color: var(--ink-dim);
}
html[data-theme="light"] .chat-thread-pill:hover { background: var(--surface-inset); }

/* Same object one rail down, on `--hair` (#e5edf5) — the palest line token in the system, used here
   as a control's own edge rather than as a divider. */
html[data-theme="light"] .chat-conv-pill {
    background: var(--surface);
    border-color: var(--keyline-strong);
    color: var(--ink-dim);
}
html[data-theme="light"] .chat-conv-pill:hover { background: var(--surface-inset); }

/* The day separator is a label, not a control, so it takes the badge rung and an edge. It sits ON
   the message column with nothing behind it, so without a border it is a white lozenge on white. */
html[data-theme="light"] .chat-daysep span {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    color: var(--ink-muted);
}

/* The composer field. It is a bare `<input>` with no `type` attribute (VehicleDetail.vue:286), so
   _base's form-surface rule — which keys on `input[type="text"]` — does NOT reach it. That is the
   whole reason this needs restating: the fix looks applied at the file level and is not. */
html[data-theme="light"] .chat-input {
    background: var(--surface);
    border: 1px solid var(--keyline);
    box-shadow: var(--field-floor);
}
html[data-theme="light"] .chat-input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--field-ring);
}


/* ── S4. THE THREE BUBBLES — RULES 5 and 8 ─────────────────────────────────────────────────────
   Three speakers, three treatments, and only one of them survived the colour pass.

     customer  flat --accent-solid + --text-on-accent          (§5, done)
     bot       --surface-3, which is #ffffff in light, on `rgba(var(--sf-rgb), 0.08)` — a white
               bubble on a white drawer with an invisible edge. RULE 8.
     human     a two-stop emerald gradient. RULE 5.

   The mechanic's own message has to stay distinguishable from the bot's, so "delete the tint" is not
   available here — it would merge two speakers. One flat token: the inset rung with the strong
   keyline, which is the same "quiet but bounded" pair the neutral badge uses, and it keeps the three
   bubbles three shapes (filled accent / bordered white / bordered inset) rather than three hues. */
html[data-theme="light"] .chat-row.bot .chat-bubble {
    background: var(--surface);
    border: 1px solid var(--keyline);
}

html[data-theme="light"] .chat-bubble.human {
    background: var(--surface-inset);
    background-image: none;
    border: 1px solid var(--keyline-strong);
}

/* The avatar's `inset 0 1px 0 rgba(var(--hl-rgb), 0.25)` lit top edge is already killed by §5's
   `box-shadow: none`. The customer bubble's is killed by §5 too. Nothing further. */


/* ── S5. THE CLOSE BUTTON ──────────────────────────────────────────────────────────────────────
   A 32px round icon button, `--surface-a05` (#fcfcfe) on `--border-default`. It keeps the circle —
   RULE 2 is about chips, tags and badges, and a round icon button is neither — but it does need a
   fill that is not white-on-white. */
html[data-theme="light"] .chat-close {
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-muted);
}
html[data-theme="light"] .chat-close:hover {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   P2 — THE CHAT DRAWER, REBUILT ON OCCLUSION
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Screenshotted side by side against dark, which is the only way this was ever going to show up.

   The earlier pass diagnosed the bot bubble correctly — "a white bubble on a white drawer with an
   invisible edge" — and then fixed it with a hairline. A keyline is 1.18:1 against white; that is
   correct for terminating a card and it is not enough to make a surface EXIST. Dark has headroom
   above its ground, so a tint is a lift there; light has none above white, so the same declaration
   produces nothing. Rendered, the shop's own messages were a column of text with a ghost around it.

   The light-native answer is not a stronger border, it is a different mechanism: give the drawer a
   GROUND and let the bubbles sit ON it. Occlusion instead of outline, which is the whole thesis of
   this theme. Three speakers still get three distinct treatments, and none of them is a hue:

       customer   filled --accent, white ink            — the one saturated mark on the surface
       bot        white, raised off the ground          — it sits on
       mechanic   white, raised, with a leading bar     — same surface, marked as a person

   The radius is deliberately NOT touched. A message bubble is one of the few places roundness
   carries real meaning — it says "speech" — and it is not a control, so the 4px control ceiling does
   not apply. It is also geometry, which is theme-invariant: changing it in light alone would give
   the same conversation two shapes. */

/* The ground. Everything below depends on this one declaration: with the body still white there is
   nothing for a white bubble to be raised off. */
html[data-theme="light"] .chat-drawer .chat-body {
    background: var(--surface-inset);
}

/* The shop's automated replies sit on the ground. No border at all — the contact shadow is what
   says "above", and an edge as well would be depth spent twice on one object. */
html[data-theme="light"] .chat-drawer .chat-row.bot .chat-bubble {
    background: var(--surface);
    border: 0;
    box-shadow: var(--elevation-1);
    color: var(--ink);
}

/* The mechanic's own message is the same surface — it comes from the same side of the conversation —
   marked with a leading bar so the two are never confused. A bar rather than a tint, because a tint
   this light reads weaker than no tint at all, and because the bar is already this theme's mark for
   "this one is different" on rows and selections. */
html[data-theme="light"] .chat-drawer .chat-bubble.human {
    background: var(--surface);
    background-image: none;
    border: 0;
    box-shadow: var(--elevation-1), inset 3px 0 0 0 var(--keyline-strong);
    color: var(--ink);
}

/* Timestamps inside a raised bubble are secondary, not faint: they sit on white now rather than on
   a tint, so the muted rung is legible where the faint one would not have been. */
html[data-theme="light"] .chat-drawer .chat-row.bot .chat-meta,
html[data-theme="light"] .chat-drawer .chat-bubble.human .chat-meta {
    color: var(--ink-muted);
}

/* ── The service list — five framed boxes become five rows ─────────────────────────────────────
   `.chat-svc-item` is a bordered, rounded box, and the active one is a saturated blue fill. Five of
   them stacked inside a panel that is itself inside a drawer is three levels of frame, and the fill
   spends the accent on a list row rather than on an action.

   Rows with one hairline; the active one takes tint PLUS a 3px leading bar. Tint alone is never
   enough in light — a light tint reads weaker than dark's unselected state — which is why selection
   in this theme is always two mechanisms. */
html[data-theme="light"] .chat-drawer .chat-svc-item {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: inset 0 -1px 0 0 var(--keyline);
    color: var(--ink-dim);
}

html[data-theme="light"] .chat-drawer .chat-svc-item:hover {
    background: var(--surface-inset);
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}

html[data-theme="light"] .chat-drawer .chat-svc-item.active {
    background: var(--surface-inset);
    box-shadow: inset 3px 0 0 0 var(--accent), inset 0 -1px 0 0 var(--keyline);
    color: var(--ink);
}

html[data-theme="light"] .chat-drawer .chat-svc-item.active .chat-svc-more,
html[data-theme="light"] .chat-drawer .chat-svc-more {
    color: var(--ink-muted);
}

/* The vehicle panel holding those rows is a container inside a drawer that already has an edge, so
   it carries neither a border nor a fill of its own. */
html[data-theme="light"] .chat-drawer .chat-veh-panel {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   PHASE 9 + 10 — /customers, /customer-detail, /vehicles, /vehicle-detail  (light only)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CLUSTER FILE: all four pages belong in `public/theme-light/registers.css`. Verified by reading the
   roots out of the templates, not by assumption:

       Customers.vue:2        <div class="cx-wrapper">        /customers
       CustomerDetail.vue:2   <div class="cd-wrapper">        /customer-detail
       Vehicles.vue:2         <div class="vx-wrapper">        /vehicles
       VehicleDetail.vue:2    <div class="vd-wrapper">        /vehicle-detail

   registers.css:738 already claims exactly those four roots and carries R1–R8, C1 and S1–S5 for
   them, so this file is an EXTENSION of that section, not a new home. Two grep hits look like they
   contradict that and do not:
     · `.cx-*` in boards.css (87 hits) is the COMPLAINTS board's namespace — `.cx-wrap`,
       `.cx-lane`, `.cx-bucket`, `.cx-tab` (phone.css:498 says so outright). Different page, same
       two letters. Every rule below is anchored on `.cx-wrapper` (never bare `.cx-…`) so it cannot
       reach it.
     · `.vd-*` in money.css (137 hits) is `.invd-*`, the invoice-draft page. Substring, not prefix.

   The ONE shared component here that is not register-only is `Collapsible.vue`, whose hosts are
   /customer-detail, /vehicle-detail and /quote-history-detail (the detail cluster). Every rule for
   it below is scoped to `.cd-wrapper` / `.vd-wrapper`, so quote-history keeps whatever the detail
   cluster decides for it. Correcting it unscoped would be the `.purple-gradient` failure again.

   WHAT WAS RENDERED. Every page was screenshotted in BOTH themes at VIEWPORT=wide (1900) through the
   run-app driver, plus the New-customer dialog, an open disclosure, a selected row and a hovered
   row. /customers cannot load on this tenant at all (see the BUG note at the foot of this file), so
   it was rendered through a harness that rewrites the one failing request; nothing about the page's
   CSS is faked by that.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── E1. THE SPLIT IS THE WRONG WAY ROUND AT 1560px ─────────────────────────────────────────────
   CONFIRMED — rendered, both boards, both themes.

   C1 (registers.css:355) raised `--lx-page` from 1096/1144 to 1560 — correct, that dead space was
   real — and then handed ALL of the new width to the detail pane with `minmax(0, 1fr)`. Measured on
   the shot: the list column is still 34rem/37rem and the pane is now ~990px. The consequences are
   both visible and both bad:

     · In the pane, "Ägare" sits at x=808 and "Theo Montelin ›" ends at x=1704. The page's own
       author had already written the rule this breaks — Customers.css:113 — "across 760px 'Senaste
       besök' and 'Idag' end up at opposite ends of the screen; the eye travels further than the
       information is worth". C1 shipped 990px.
     · In the list, which did NOT grow, "2014 Ford Transit Connect 230 LWB 1.6 TDC…" and "2010
       Volkswagen Passat 1.4 TG…" truncate — while a thousand pixels of pane sits empty beside them.

   So the growth goes to the column that is short of room and the pane returns to the cap its author
   measured. Same total measure, same centred composition; only the ratio changes. `1fr` on the LIST
   rather than a second fixed number, because the list is by definition "the rest" once the pane is
   capped. */
/* APPLIED TO BOTH THEMES, 2026-08-11, and deleted here. Identical values, unprefixed:
       Customers.css:118  .cx-wrapper > .lx-split { minmax(340px, 1fr) minmax(0, 33rem) }
       Vehicles.css:245   .vx-wrapper > .lx-split { minmax(340px, 1fr) minmax(0, 32rem) }
   The argument above is the record of why the ratio is this way round. */

/* The pane sizes to its content (`align-self: start`, listLedgerStyle.ts:231) except for a 15rem
   floor. Before a row is picked its whole content is one sentence, so that floor draws a 240px
   bordered rectangle around a single line — the "page failed to load" read that _base.css:440
   already fixed the TEXT position for but not the BOX. With the floor gone the pane is as tall as
   what it says, and a selected record is unaffected (a dozen fact rows clear 240px on their own).

   THIS RULE IS GONE AND THE FLOOR ITSELF IS GONE. `min-height: 15rem` was removed from
   listLedgerStyle.ts outright, for both themes, rather than being cancelled here for two wrappers —
   the argument above is about a pane that sizes to its content, which is what `align-self: start`
   makes it in EVERY theme. Cancelling a shared length from a light-only file is the fork mechanism,
   and it also left the fault live in dark: MEASURED on /vehicles at 1900, `.lx-sheet` unselected is
   111px in light and 240px in dark, i.e. dark still draws the empty rectangle this fixed.

   The three panes that are `align-self: stretch` (Work Queue, Telefon, and the quote console) are
   unaffected either way — they are 700px+ scroll regions and never met the floor. */


/* ── E2. THE BUCKET HEADINGS ARE STILL SHOUTING ────────────────────────────────────────────────
   CONFIRMED — probed, not guessed. On /vehicles, `.lx-bucket-name` computes
   `text-transform: uppercase; letter-spacing: 0.66px`, i.e. "VOLKSWAGEN", "PEUGEOT", "FORD".

   This is trap 3, exactly as described. THREE rules claim this class and the loser looks like a
   winner:

       _base.css:916    html[data-theme="light"] .lx-bucket .lx-bucket-name   (0,3,1)  sentence case
       boards.css:344   html[data-theme="light"] .lx-bucket .lx-bucket-name   (0,3,1)  UPPERCASE
       boards.css:632   html[data-theme="light"] .wq-wrapper .lx-bucket …     (0,4,1)  sentence case

   _base and boards:344 tie on specificity and _base loads FIRST, so the uppercase one wins
   everywhere. boards:632 then bought Work Queue back out of it with a wrapper class — and Work Queue
   is the page that was signed off. The two registers were never given the same treatment, so the
   app currently disagrees with itself about what a bucket heading is: sentence case on the console,
   caps on the registers.

   Same escape hatch, same specificity (0,4,1), for the two roots this cluster owns. Sentence case is
   safe here without the ::first-letter dance: /vehicles' heading is a make read straight out of
   `04_Customer_Vehicles.make` ("Volkswagen", verified in the REST response) and /customers' is a
   single initial, so `text-transform: none` renders the string as stored. */
html[data-theme="light"] .cx-wrapper .lx-bucket .lx-bucket-name,
html[data-theme="light"] .vx-wrapper .lx-bucket .lx-bucket-name {
    text-transform: none;
    letter-spacing: 0;
}


/* ── E3. THE CAR CHIPS IN THE CUSTOMER PANE ARE BOXES INSIDE A BOX ─────────────────────────────
   R7 gave `.cx-veh-chip` a surface and a `--keyline` border. Rendered, that is a bordered white
   rectangle inside the bordered white pane, one per car — a container whose contents carry an edge
   of their own, which is the depth-spent-twice rule stated at the smallest scale on the page.

   They become rows on one hairline, with the darker inset as the hover. This is not a new idiom for
   this file: P2 (registers.css:659) made exactly this call for `.chat-svc-item`, five framed boxes
   inside a drawer, for exactly this reason. Keeping the two consistent is the point. */
html[data-theme="light"] .cx-wrapper .cx-veh-chip {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: inset 0 -1px 0 0 var(--keyline);
    transition: background-color 0.15s ease;
}
html[data-theme="light"] .cx-wrapper .cx-veh-chip:hover {
    background: var(--surface-inset);
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   E4. THE TWO RECORD PAGES — THE DISCLOSURE CARD
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The single largest thing left on /customer-detail and /vehicle-detail, and the reason it survived
   R8: R8 corrected `.glass-section`, and five of the seven sections on these pages are NOT
   `.glass-section` — they are `<Collapsible>`, a component with `<style scoped>` (Collapsible.vue:29)
   that no light sheet has ever touched beyond its count and its chevron (registers.css:598-604).

   What it declares, and what each of those is:

       border-radius: 20px                                   five times the 4px ceiling
       border: 1px rgba(--sf-rgb, .11)  AND
       box-shadow: 0 4px 24px … , inset 0 1px 0 rgba(--hl-rgb,.18)
                                                             border + elevation + a lit top edge on
                                                             one object; light has no lit edges
       backdrop-filter: blur(28px)                            resolves to `none` in light, dead weight
       background: var(--surface-1)                           reads grey against the white page, so a
                                                              collapsed section is a grey slab

   Rendered at 1900 that is five grey lozenges with drop shadows stacked down /customer-detail, and on
   /vehicle-detail a Collapsible nested INSIDE a Collapsible (Specifikationer › Motor & drivlina) —
   three levels of frame.

   The fix is to make it the same object R8 already made `.glass-section`: one surface, one keyline,
   the card radius, no shadow. A record then reads as sections of one page rather than as a stack of
   cards. Scoped to the two register record roots, so /quote-history-detail keeps the detail
   cluster's call. The scoped-SFC `[data-v-…]` raises the component's own selector to (0,2,0);
   `html[data-theme="light"] .cd-wrapper .collapsible` is (0,3,1) and wins on specificity, not order. */
html[data-theme="light"] .cd-wrapper .collapsible,
html[data-theme="light"] .vd-wrapper .collapsible {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* A disclosure inside a disclosure gets NEITHER, per the container rule — the outer section already
   drew the edge, so the inner ones are rows that open. (0,4,1) beats the rule directly above it. */
html[data-theme="light"] .cd-wrapper .collapsible .collapsible,
html[data-theme="light"] .vd-wrapper .collapsible .collapsible {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}

/* The head is the control. `--surface-sunken` (#e4e6eb) is darker than the page canvas itself, so a
   hover on a white section flashed a mid-grey band; `--surface-inset` is the sanctioned step below a
   surface and is what every other row in this theme hovers to. */
html[data-theme="light"] .cd-wrapper .collapsible-head:hover,
html[data-theme="light"] .vd-wrapper .collapsible-head:hover {
    background: var(--surface-inset);
}

/* The title. 0.68rem = 10.88px, below the 11px label floor, at 1.5px of tracking — which at that
   size is not tracking, it is a gap between letters — and uppercased by CSS. Every string it renders
   is sentence case in the table already (`cd_ongoing` = "Pågående bokningar", localization.ts:1731;
   `cd_routines`, `cd_quotes`, `spec_*` likewise), so `text-transform: none` prints exactly what was
   authored. Swedish compounds are the worst case for caps and this page is full of them. */
html[data-theme="light"] .cd-wrapper .collapsible-title,
html[data-theme="light"] .vd-wrapper .collapsible-title {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}

/* The count is read-only data in a 50px capsule — a shape that says "press me". The badge rectangle
   from _base §1, tabular so a 4 and a 14 occupy the same box down a column of sections.
   registers.css:599 already recoloured it; only the geometry was missed. */
html[data-theme="light"] .cd-wrapper .collapsible-count,
html[data-theme="light"] .vd-wrapper .collapsible-count {
    border-radius: 4px;
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-dim);
    font-variant-numeric: tabular-nums;
}


/* ── E5. THE RECORD'S LABELS, SENTENCE CASE ────────────────────────────────────────────────────
   PROBED: `.cd-wrapper .section-title` and `.cd-wrapper .detail-label` both compute
   `text-transform: uppercase; letter-spacing: 0.66px`. That is R8 (registers.css:254, :287) putting
   them on the eyebrow — written before the sentence-case decision that _base.css:908 and
   boards.css:632 record, and which Work Queue and Finished Jobs now both follow. Thirteen uppercase
   tracked labels on one record page is the loudest thing left on it.

   R8 keeps everything else it said (size, weight, colour); this changes the two declarations the
   decision reversed. Specificity is deliberate: adding the scroller class makes these (0,4,1)
   against R8's (0,3,1), so the correction does not depend on which file loads last. */
html[data-theme="light"] .cd-wrapper .cd-scroll .section-title,
html[data-theme="light"] .cd-wrapper .cd-scroll .detail-label,
html[data-theme="light"] .vd-wrapper .vd-scroll .section-title,
html[data-theme="light"] .vd-wrapper .vd-scroll .detail-label {
    text-transform: none;
    letter-spacing: 0;
}

/* The spec sheet's own captions, which no light rule has ever reached: 0.62rem (9.9px — a full
   point under the floor), uppercase, 0.6px tracked, `--ink-faint` (detailSectionsStyle.ts:163).
   Same rung as every other caption on the page. Strings are sentence case in the table
   (`spec_body` = "Kaross", localization.ts:1767). */
html[data-theme="light"] .vd-wrapper .spec-label {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}


/* The same reversal in the two BOARD panes, which R6 (registers.css:152) put on the eyebrow for the
   same pre-decision reason: "FORDON / SENASTE BESÖK / NÄSTA BESÖK / ÖPPNA OFFERTER / KUND SEDAN" on
   /customers and "ÄGARE / VAR DEN STÅR / DÄCK I FÖRVARING" on /vehicles. A caption in a pane and a
   caption on a record page have to be the same object or the two halves of one register disagree.
   All fourteen strings are sentence case in the table (localization.ts:3518-3586). Anchored on the
   wrapper for (0,3,1) against R6's (0,2,1) — a win on specificity, not on file order. */
html[data-theme="light"] .cx-wrapper .cx-fact-label,
html[data-theme="light"] .cx-wrapper .cx-veh-label,
html[data-theme="light"] .vx-wrapper .vx-fact-label {
    text-transform: none;
    letter-spacing: 0;
}


/* ── E6. THE NEW-CUSTOMER DIALOG'S FIELD LABELS ────────────────────────────────────────────────
   S1 (registers.css:443) put `.cxm-field > span` on the eyebrow; same reversal as E5, same reason.
   "NAMN / TELEFON / E-POST / ANTECKNINGAR" over four fields is the whole dialog shouting its own
   form. The strings are sentence case in the table (`cd_name_label` = "Namn", localization.ts:3539).

   Anchored on `.cxm-panel` so it is (0,4,2) against S1's (0,3,2) — the dialog teleports to <body>
   (trap 1), so a wrapper-scoped selector would parse and match NOTHING; `.cxm-panel` is the
   overlay's own root and is the correct anchor. */
html[data-theme="light"] .cxm-panel .cxm-field > span {
    text-transform: none;
    letter-spacing: 0;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   VERIFICATION LOG — what was seen rendered, and what was only reasoned
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Every rule above was injected into the running app with `page.addStyleTag` and screenshotted at
   VIEWPORT=wide. Status per rule:

     E1  split rebalance          CONFIRMED  /customers + /vehicles, selected row. Model names stop
                                             truncating; the pane's label/value pairs close from
                                             ~950px apart to ~450px.
     E1  .lx-sheet min-height:0   CONFIRMED  /vehicles unselected — the empty pane drops from a
                                             240px box to ~110px of content.
     E2  bucket sentence case     CONFIRMED  /vehicles — "VOLKSWAGEN/PEUGEOT/FORD" now
                                             "Volkswagen/Peugeot/Ford", untracked. On /customers the
                                             heading is a single initial, so the same rule is
                                             visually inert there by construction.
     E3  .cx-veh-chip as a row    CONFIRMED resting (the box is gone, one hairline remains).
                                  UNVERIFIED hover — not photographed; the declaration is copied
                                             verbatim from the confirmed `.chat-svc-item:hover`.
     E4  .collapsible             CONFIRMED  both record pages. Five grey shadowed lozenges become
                                             five white keyline sections.
     E4  nested .collapsible      CONFIRMED  /vehicle-detail, Specifikationer open — the three inner
                                             groups are rows on a hairline.
     E4  head hover               CONFIRMED  incidentally: the probe's click leaves the pointer on
                                             the head, and it computes --surface-inset (243,246,248).
     E4  title / count            CONFIRMED  both pages.
     E5  section-title/detail-label CONFIRMED both record pages (13 shouting captions → sentence case).
     E5  .spec-label              CONFIRMED  /vehicle-detail with the spec sheet open.
     E5b pane fact labels         CONFIRMED  /customers and /vehicles panes.
     E6  dialog field labels      CONFIRMED  the New-customer dialog.

   DARK IS UNMOVED — proved, not asserted. Computed styles for every class this file touches were
   dumped in THEME=dark with and without the block injected, on /customers and /customer-detail:
   both diffs are empty. Structurally guaranteed anyway — `html[data-theme="light"]` cannot match a
   root stamped `dark`.

   ── WAS "NOT FIXED FROM CSS, needs a source change" — NOW DONE ───────────────────────────────
   VehicleDetail.vue:74 rendered the PLATE as `<span class="detail-value mono">`, the identical
   class pair used by `vehicle_id` (:78) and `customer_id` (:82). R8 correctly quiets `.mono` — "an
   opaque 36-character token is not a figure being compared" — and the plate was caught by it, so
   the one field that IS the car's identity rendered quieter and lighter than "Renault" two rows
   above. No selector separated them.

   The source change asked for here has been made. The span now also carries `vd-plate` (named for
   this page rather than the bare `plate` originally proposed, because bare class names in this app
   inherit vendor Bootstrap), and the rule restoring --fw-strong, --ink and tabular figures sits
   with the `.mono` rule it qualifies, above.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── LEAD REWORK ON THE VEHICLE REGISTER ───────────────────────────────────────────────────────
   Three things the block above did not reach, all of them consistency with the pages already built.

   1. THE PAGE EYEBROW. A filled chip around "Fordon", directly above a heading that reads
      "Fordonsregister". It is a label, not a status, so it loses the fill — and the string is
      capitalised in the table, so the case has to come from CSS. `::first-letter` needs a block
      container, which is why the display change is load-bearing rather than cosmetic. Same treatment
      the calendar, routines and job detail eyebrows already got.

   2. "8 att städa" IS AMBER, AND IT IS THE ORDINARY CASE. Eight of eleven vehicles on this tenant
      need tidying — a colour that appears on nearly three quarters of the register has stopped
      carrying information, and there is no good light amber anyway: to clear 3:1 it has to be
      darkened, and a darkened yellow is mustard. It states a count, so it sets as a count. The
      register's health is still legible; it just stops shouting about the majority case.

   3. THE EMPTY PANE IS A BOX AROUND A SENTENCE. With nothing selected, the right pane draws a
      bordered rectangle containing one line of muted type. An empty state is a fact stated where the
      data would have been, not a frame around an apology. */
/* 1's rule has been PROMOTED, not deleted. The reasoning above was right and was the only correct
   eyebrow in the app, but scoping it to `.vx-header` left the other five pages rendering the same
   element four other ways. It now lives on the shared `.badge` in _base.css §1, so this per-page
   copy is redundant and is removed to keep one owner. */

/* GENERALISED from `.stat-pill.warn` to every pill in the header strip, on both registers.
   Un-chipping only the amber one left "11 fordon" — the plain count — still wearing a white fill,
   a 1px border and a 50px radius: control geometry on read-only data, in a strip whose OTHER item
   is now bare text. Three other pages state exactly this fact (how many records the board holds) as
   a hero figure with a word beside it; nowhere does it get a chip. A chip is a thing you can press
   or dismiss, and this one is neither.

   `.warn`'s `--ink-muted` and label weight stay for both: the two pills are the same class of fact
   and the amber-vs-violet distinction they used to carry was the thing being retired.

   SCOPED TO `.header-pills`, not to `.stat-pill`. The same class is a CONTACT chip on the two
   detail pages — CustomerDetail.vue:28 is a `stat-pill cd-phone-chip` with a real `tel:` href, so
   it is genuinely pressable and genuinely owes the control geometry it is wearing. */
html[data-theme="light"] .vx-wrapper .header-pills .stat-pill,
html[data-theme="light"] .cx-wrapper .header-pills .stat-pill {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    color: var(--ink-muted);
    font-weight: var(--fw-label);
}

/* ── THE EMPTY PANE IS NOT A CARD — and the first attempt at this aimed at the wrong element ───
   THE FAULT: on /vehicles with nothing selected, the right-hand pane draws a white, keylined,
   4px-radius rectangle around one sentence — "Välj ett fordon för att se var det står." An empty
   state is a fact stated where the data would have been, not a frame around an apology, and a frame
   makes the absence of data look like a component that failed.

   WHY THE EARLIER SELECTOR MISSED. It stood `.lx-empty` down (the rule this replaces, kept below in
   spirit: those four declarations were no-ops, because `.lx-empty` draws NOTHING — listLedgerStyle
   .ts:251 gives it flex, gap and colour and no surface at all). The box is drawn by the PANE:
   R5 above, `html[data-theme="light"] .vx-wrapper .lx-sheet`, on the reasoning that a scroll region
   needs an edge. Probed: `background rgb(255,255,255)`, `border-top-width 1px`, `border-radius 4px`
   on `.lx-sheet`, and nothing whatsoever on `.lx-empty`.

   R5's reasoning is right and stays — for a pane with a record in it. It is wrong for a pane with
   one sentence in it: there is no scroll region to bound, so the edge is bounding nothing. So this
   qualifies R5 by STATE rather than overturning it.

   THE SELECTOR. `:has(> .lx-empty)` is exactly the state test wanted — the pane is empty precisely
   when Vehicles.vue:134 renders that child (`v-if="selected === null"`), so no new class and no
   markup change is needed, and a pane with a record selected keeps its edge untouched. `:has()`
   takes the specificity of its argument, putting this at (0,4,1) against R5's (0,3,1); it wins on
   specificity, not on being 900 lines further down, so it survives being moved.

   The horizontal padding goes with the frame. `--row-pad-x` was the inset from the box's own edge;
   with no box, it is a 16px indent that lines the sentence up with nothing. Vertical padding stays:
   it is what holds the sentence off the pane's top edge. */
html[data-theme="light"] .vx-wrapper .lx-sheet:has(> .lx-empty) {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

html[data-theme="light"] .vx-wrapper .lx-sheet > .lx-empty {
    padding-left: 0;
    padding-right: 0;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TOUCH PASS — REGISTERS CLUSTER  (phone 412×839 · tablet 1080×810, light only)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Author: registers cluster. Every rule below is prefixed `html[data-theme="light"]`, so dark
   cannot match by construction. Nothing here was written to `public/` or `src/`.

   ── ROOTS VERIFIED (read out of the templates, not assumed) ─────────────────────────────────────
     /customers        Customers.vue:2       <div class="cx-wrapper">     → registers.css  ✔ mine
     /customer-detail  CustomerDetail.vue:2  <div class="cd-wrapper">     → registers.css  ✔ mine
     /vehicles         Vehicles.vue:2        <div class="vx-wrapper">     → registers.css  ✔ mine
     /vehicle-detail   VehicleDetail.vue:2   <div class="vd-wrapper">     → registers.css  ✔ mine

   Ownership double-checked against the other light sheets:
     · boards.css holds exactly TWO `.cx-wrapper` rules (`.badge` / `.stat-pill` box-shadow, :2390)
       and its own note at :3859 draws the `.cx-wrapper` vs `.cx-wrap` line. No collision below —
       every selector here is anchored on a full `.cx-wrapper` / `.vx-wrapper` / `.cd-wrapper` /
       `.vd-wrapper` root, never a bare `.cx-…`.
     · money.css's 90 apparent hits are `.invd-wrapper` (invoice draft). Substring, not prefix.
     · NONE of the four pages is platform-split — there is one `.vue` per page and no
       tablet/phone view. Both touch shells render the DESKTOP view, per the brief's §Scope.

   ── WHAT I COULD ACTUALLY SEE, STATED FIRST ────────────────────────────────────────────────────
     /customers        UNREACHABLE on this tenant — renders the error boundary in BOTH themes.
     /customer-detail  UNREACHABLE TOO, and this was NOT in the brief. Reached via the context
                       rail's "Öppna kund" chip off a real vehicle it renders
                       "Kunden hittades inte." with four 403s in console, in light AND in dark
                       (measured: `.cd-wrapper` 760×810 containing one `.state-msg`, identical in
                       both themes). Same `03_Customer_CRM` column-grant defect. Icarus-side.
     /vehicles         seen, both shells.
     /vehicle-detail   seen, both shells, plus the chat drawer open on both.

     So HALF this cluster was invisible. Nothing below is written against a page I did not render.
     The two customer selectors that DO appear (`.cd-wrapper .detail-row`, `.cd-wrapper
     .cd-edit-pill`, `.cd-wrapper .chat-pill`) are paired verbatim with their vehicle twins, which
     I did render, and they are the only UNVERIFIED rules in the file. They are marked as such.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   DESTINATION: public/theme-light/registers.css   (append at end of file)
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── TR1. THE TWO REGISTERS ARE A BROKEN TWO-COLUMN SPLIT ON THE TABLET ────────────────────────
   CONFIRMED — rendered at 1080×810 in light, MEASURED in light and in dark. This is the whole
   tablet pass for this cluster and it is a LIGHT-ONLY REGRESSION, not a shared shortcoming.

   The shared ledger already solves this itself. `listLedgerStyle.ts:267`:

       @media (max-width: 1179px) { .lx-split { grid-template-columns: minmax(0,1fr); gap: 0; }
                                    .lx-sheet { display: none; } }

   …with the reasoning written into the source: "The threshold clears iPad landscape (1080)
   deliberately — at that width the split renders a 430px list beside a mostly empty pane, which is
   a worse tablet than the plain ledger."

   Light then defeats it. registers.css §E1 (:1549, :1552) writes

       html[data-theme="light"] .vx-wrapper > .lx-split { grid-template-columns: minmax(340px,1fr) minmax(0,32rem) }

   which is (0,3,1) against the media query's (0,1,0) — so it applies at EVERY width, including
   1080 and 412. E1 was measured and argued at 1900px and is right there; it simply had no width
   condition, and a media query cannot out-specify it.

   MEASURED, /vehicles at 1080 (RECT from touch-audit.mjs):
       LIGHT  .lx-split cols = 414px 512px  ·  .lx-list w=414  ·  .lx-sheet 0×0 (display:none)
       DARK   .lx-split w=926 single column ·  .lx-list w=926  ·  .lx-sheet HIDDEN

   So 512px of a 926px row — 55% of the register — was reserved for a pane that is display:none,
   and the list was crushed into 414px. Inside it the row grid `6px minmax(0,22rem) auto 1fr 7rem`
   resolved to `6px 136px 35px 0px 0px 104px`: a 35px MODEL column. Every car on the board read
   "201…", while the plate column kept 136px. That is the trap-14 shape (a track floored at
   min-content) but the cause here is simply that there was no room left to distribute.

   The fix is not a new layout — it is letting the ledger's own tablet decision stand. Same media
   condition, same declarations, same specificity as E1 and later in the file, so it wins on
   document order and E1 keeps the desktop composition it was signed off at, untouched.

   AFTER, measured: `.lx-list` w=926, model names render in full ("2010 Volkswagen Passat 1.4 TGI
   EcoFuel Manuell", "2013 Peugeot 308 1.6 e-HDi 115 FAP Manuell"), the date column lands on the
   right rule. Light now measures identically to dark at this width, which is the strongest form
   this proof takes: the fix does not invent a tablet design, it stops light from overriding one.

   Phone is covered by the same rule (412 < 1179) and was already collapsing there by accident of
   the pane being `display:none` — but the 32rem track was still being reserved, so the phone list
   was being sized against a 512px ghost too. */
/* NOT deleted with the rest of C1/E1 on 2026-08-11: this one has NO unprefixed twin. The page sheets
   guard the same case with `@media (--tablet), (--phone) { .lx-split { … } }` (Customers.css:275,
   Vehicles.css:252) — a BARE `.lx-split`, (0,1,0), against E1's now-unprefixed `.cx-wrapper >
   .lx-split`, (0,2,0). The page-sheet guard therefore loses, and dark is currently crushed below
   1280 for exactly the reason measured above. Until the page sheets scope their own guard, this rule
   is the only thing keeping LIGHT correct there, so it stays. Flagged to the lead. */
@media (max-width: 1179px) {
    html[data-theme="light"] .cx-wrapper > .lx-split,
    html[data-theme="light"] .vx-wrapper > .lx-split {
        grid-template-columns: minmax(0, 1fr);
        gap: 0;
    }
}


/* ── TR2. THE RECORD'S FACT ROWS SIT OFF-CENTRE ON PHONE ───────────────────────────────────────
   CONFIRMED — measured before and after on /vehicle-detail at 412×839.

   AND MY FIRST DIAGNOSIS OF THIS WAS WRONG, which is worth more than the rule. The brief said the
   `--row-h` consumers had "already grown" and that some derived padding would need re-deriving.
   R8's rule is `min-height: var(--row-h); padding: 9px 0`, and 9px derives from the 40px desktop
   rung — so the obvious move was `padding: calc((var(--row-h) - 20px) / 2) 0` per shell. I wrote
   that first. It is a NO-OP, and here is why:

     tablet  `.detail-row` measured h=64, minH=64px, align-items:center. The min-height governs and
             flex centring places the content. Padding is INERT — 9px, 22px or 0 render identically.
             The tablet rows are already correct at the gloved rung; nothing to do.
     phone   `.detail-row` measured h=48, minH=48px, and VehicleDetail.css:696 switches it to
             `flex-wrap: wrap; align-items: baseline` on this shell. Baseline alignment puts the
             line at the START of the cross axis, so the content sat 9px from the top of a 48px box
             with 20px of dead space UNDER it — measured: row y=548 h=48, value y=557 h=19.

   Re-deriving the padding to 14px would only have moved the text from 9px-from-top to
   14px-from-top; still not centred, because it is the ALIGNMENT that is off, not the padding.
   `align-content` is the property that centres flex LINES inside a taller box, and it applies here
   precisely because this shell is the one that sets `flex-wrap: wrap`.

   AFTER, measured: value y=563 inside row 548-596 → 15px above, 14px below. Centred.
   Padding is deliberately left at 9px: it is inert on both touch shells and touching it would
   change the desktop rung for no reason.

   (0,4,1) via `.page[data-shell='phone']` against R8's (0,3,1), so it wins on specificity rather
   than on file order. `data-shell` rather than a width query, per the touch layer's §2 note: it is
   what the app itself decides the shell with. */
html[data-theme="light"] .page[data-shell='phone'] .cd-wrapper .detail-row,
html[data-theme="light"] .page[data-shell='phone'] .vd-wrapper .detail-row {
    /* UNVERIFIED for `.cd-wrapper` — /customer-detail is 403-blocked on this tenant (see header).
       CONFIRMED for `.vd-wrapper`. The pair is identical markup (`.detail-rows > .detail-row`,
       same shared `detailSectionsStyle` idiom, same phone media query in both page sheets). */
    align-content: center;
}


/* ── TR3. THE SQUARE ICON BUTTONS ON THE RECORD HEADER ─────────────────────────────────────────
   CONFIRMED — measured before and after, /vehicle-detail at 1080×810.

   These are exactly the case `_base.css` §2b hands to the cluster: they fail the floor on WIDTH,
   which the blanket `min-height` cannot reach, and the fix is a layout call rather than a token.

       BEFORE (tablet)  .vd-edit-pill  36 × 44      x=861 → 897
                        .chat-pill     36 × 44      x=905 → 941      gap 8px
       AFTER  (tablet)  both           44 × 44      x=845 → 889 · 897 → 941   gap 8px

   Phone was already 44 × 44 (VehicleDetail.css's own phone block sets a min-width there); the
   tablet had no equivalent, which is the shell that most needs it — the bay, gloved, at arm's
   length. That asymmetry is the finding: the page had already accepted the argument for phone and
   never applied it to the harder shell.

   `min-width` only. §2b's honest note is that an invisible hit area is not sufficient on this
   app's tablet, so this GROWS THE PAINT — and it costs nothing here because both buttons sit in a
   right-aligned header group with room to their left, which is why this one did not need the grid
   re-layout /jobs' arrows do.

   The existing 8px gap between them is already exactly `--tap-sep` and survives the change, so no
   separation rule is needed: these two are genuinely adjacent targets and they are already spaced.
   Neither is destructive, so neither takes `--tap-primary`.

   `@media (pointer: coarse)` rather than `.page[data-shell]` because the same header renders
   behind an open drawer and the floor should not depend on which shell string is stamped. */
@media (pointer: coarse) {
    html[data-theme="light"] .vd-wrapper .vd-edit-pill,
    html[data-theme="light"] .vd-wrapper .chat-pill,
    /* UNVERIFIED — /customer-detail is 403-blocked. CustomerDetail.vue renders the identical
       header pair (`.cd-edit-pill` + the shared `.chat-pill`), and registers.css §4 already treats
       `.vd-edit-pill` / `.cd-edit-pill` as one object at :295. */
    html[data-theme="light"] .cd-wrapper .cd-edit-pill,
    html[data-theme="light"] .cd-wrapper .chat-pill {
        min-width: var(--tap-min);
    }
}


/* ── TR4. THE CHAT DRAWER'S CLOSE BUTTON ───────────────────────────────────────────────────────
   CONFIRMED — measured on /vehicle-detail with the drawer open, both shells.

       BEFORE   tablet 32 × 44   ·   phone 44 × 44
       AFTER    tablet 44 × 44   ·   phone 44 × 44 (unchanged)

   Same class of defect as TR3 and the same asymmetry: the phone case was already handled and the
   tablet was not. It is the control that DISMISSES the drawer, i.e. the one whose mistap costs the
   most in a bay, and at 32px wide it sat below the floor on the shell with the glove.

   Rendered after the change: the head still composes — "Ta över" holds its box, the vehicle title
   truncates one character further, nothing collides. Verified on the screenshot, not inferred.

   Scoped `.chat-head .chat-close` rather than bare `.chat-close`, both to stay clear of any other
   `.chat-close` in the app and to sit at (0,3,0). registers.css §S5 already owns `.chat-close`
   unscoped (:1387) for the same one-component-five-hosts reason, so this extends an existing
   owner rather than creating a second one. */
@media (pointer: coarse) {
    html[data-theme="light"] .chat-head .chat-close {
        min-width: var(--tap-min);
    }
}


/* ── TR5. THE NOTES CARD'S "Redigera" IS 36px TALL ON BOTH TOUCH SHELLS ────────────────────────
   CONFIRMED — found on the re-audit WITH `CLICK`, measured before and after, tablet and phone.

   `VehicleDetail.vue:225` and `CustomerDetail.vue:238` both render
   `<button class="btn-astra-glass">{{ loc.t('edit') }}</button>` in the notes card. Measured 92 × 36
   on tablet AND on phone, `min-height: 36px` computed on a coarse pointer.

   IT IS NOT THAT THE FLOOR WAS NEVER WRITTEN — the floor LOSES. `_base.css`'s touch layer §1 is

       @media (pointer: coarse) { html[data-theme="light"] button { min-height: var(--tap-min) } }   (0,1,2)

   and its own control layer, 1400 lines earlier, is

       html[data-theme="light"] .btn-astra-glass { min-height: 36px }                                (0,2,1)

   (0,2,1) beats (0,1,2), so the 36px survives the floor. See the ROOT CAUSE note below — this is
   app-wide and belongs in `_base.css`; the rule here is the cluster-local patch for MY two pages
   so they are not left short if the central fix lands later. DELETE THIS BLOCK once _base's floor
   is specificity-matched: it will then be redundant, and two owners for one control is drift.

   (0,3,1) via the wrapper, which is what it takes to clear the control layer's (0,2,1).
   AFTER, measured: 92 × 44, `min-height: 44px`. Width was never the problem here — the label is a
   word, so the button is 92px wide and only ever failed on height. */
@media (pointer: coarse) {
    html[data-theme="light"] .vd-wrapper .btn-astra-glass,
    /* UNVERIFIED — /customer-detail is 403-blocked. CustomerDetail.vue:238 is character-identical
       markup to VehicleDetail.vue:225 in the same notes card. */
    html[data-theme="light"] .cd-wrapper .btn-astra-glass {
        min-height: var(--tap-min);
    }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   DESTINATION: public/theme-light/phone.css
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   NOTHING. Every phone rule this cluster needs is anchored on a registers root
   (`.cd-wrapper` / `.vd-wrapper`), so it belongs with the cluster and not in the shared phone
   sheet — putting a `.vd-wrapper` selector in phone.css would give one page two owners. TR2 is
   phone-only and still lives above, for that reason.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   DESTINATION: NOT MINE — escalated to the lead, with measurements. Do not paste these blind.
   ═══════════════════════════════════════════════════════════════════════════════════════════════

   ── E-A. `.ctb-threadnav` IS 25.6px WIDE, AND IT IS THE DETAIL CLUSTER'S ─────────────────────
   The chat drawer's vehicle strip is `ChatThreadBar.vue`, whose arrows are
   `width: 1.6rem` (:146) = 25.6px. Under the coarse floor they measure 25.6 × 58 on BOTH touch
   shells: tall enough, and far too narrow. They are the last square-icon button in this cluster's
   render.

   I did NOT write the rule into registers.css, because `.ctb-*` is owned by
   `public/theme-light/detail.css` (:406-431, including a note correcting `.ctb-service-count` to
   `.ctb-service-more`). A second owner for one component is the `.purple-gradient` failure. The
   rule, tested and rendered at 44 × 58 on both shells with no reflow (`.ctb-threads` is
   `flex: 1; min-width: 0`, so it simply narrows by 37px and keeps scrolling):

       @media (pointer: coarse) {
           html[data-theme="light"] .ctb-threadbar .ctb-threadnav { min-width: var(--tap-min); }
       }

   (0,3,0) is deliberate: the component is a scoped SFC, so its own selector is
   `.ctb-threadnav[data-v-…]` = (0,2,0) and its runtime-injected sheet would win a tie. Trap #9.

   ── E-B. THE CONTEXT RAIL'S CHIPS ARE WHITE ON WHITE ON BOTH TOUCH SHELLS ────────────────────
   EXACT REPRO (the lead could not reproduce it, and the reason is the route):

       APP_BASE_URL=http://localhost:5192 THEME=light VIEWPORT=tablet MSYS_NO_PATHCONV=1 \
         CLICK=".lx-row" node .claude/skills/run-app/screenshot.mjs "/vehicles" out.png

   `.crail` is NOT on /vehicles and never was — it is a DETAIL-page component. `CLICK=".lx-row"`
   opens the first vehicle, which navigates to /vehicle-detail, and the rail renders there. A probe
   of /vehicles alone correctly returns NOT PRESENT. Same for VIEWPORT=phone. `probe.mjs` has no
   CLICK support, which is why the route cannot be reached with it at all.

   THE CHIP'S SELECTOR IS `.crow` (ContextRail.vue:14, `<button class="crow">`). Below 1280px
   ContextRail.css:336 turns the fixed rail into this in-flow chip row, so it is effectively a
   touch-shell-only surface.

   MEASURED on /vehicle-detail at 1080×810, light, re-confirmed after your message:
       .crail  w=728 h=96  background rgb(255,255,255)
       .crow   w=183 h=44  background rgb(252,252,254) on border rgba(27,35,61,0.09)   (×6)
   Identical at 412×839.

   A near-white fill on a white card whose only boundary is a 9%-ink hairline — the exact
   "a fill with no border is invisible in light and nothing errors" failure registers.css §8 names
   at :491. Below 1280 the rail stops being a rail and becomes this chip row (ContextRail.css:336),
   so it is effectively a touch-shell surface, and _base.css already claims `.crail` (:660), which
   is why this is the lead's and not mine. The `.crow` inside it has no light rule anywhere.

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

   UNVERIFIED — reasoned and measured, not rendered. It also lands on /job-detail (jobs cluster),
   which is the other reason to route it through the lead.

   ── E-C. §S3 / §P2's CHAT SELECTORS ARE DEAD ON THIS PAGE ────────────────────────────────────
   Not a touch item, but it was found while doing this one and it is the trap-1 shape.
   registers.css §S3 (:1300-1340) and §P2 (:1457-1496) style `.chat-thread-pill`,
   `.chat-conv-pill`, `.chat-svc-item`, `.chat-conversations`. On /vehicle-detail's open drawer,
   `document.querySelectorAll` returns ZERO of them — the component rendered is ChatThreadBar,
   whose own comment (:136) states outright that the legacy `.chat-thread-pill` rules "must never
   restyle this component". Visible consequence on the render: those pills are still 999px capsules
   against the theme's 4px control ceiling, and the four service pills all read "Tjänst".
   Colour/geometry work, not touch — flagged for whoever schedules the next drawer pass.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   LOOKED AT AND DELIBERATELY LEFT ALONE
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   · THE AUDIT IS GREEN ON THE TWO BOARDS ONLY — CORRECTED. `touch-audit.mjs FLOOR=44` reports
     "TARGETS: all clear" on /customers + /vehicles at tablet (20 + 37 controls) and phone (8 + 25).
     That result is real but it is ONLY the boards: `.lx-tab` 56px (`--ctl-h-lg`), `.lx-row` 64px at
     tablet, `.list-search` 50px, the filter chips 50px, `.lf-menu` rows 44px.

     RE-RUN WITH `CLICK=".lx-row"` (i.e. on /vehicle-detail, which is the route these pages actually
     lead to) IT IS NOT GREEN, and my original "audit is green" line was measured on the board the
     unclicked run stayed on. What the clicked run reports (height × width):

         tablet  36x92  button.btn-astra-glass  "Redigera"    → TR5
                 44x36  button.vd-edit-pill                   → TR3
                 44x36  button.chat-pill                      → TR3
                 44x32  button.chat-close                     → TR4
         phone   36x92  button.btn-astra-glass  "Redigera"    → TR5

     All four are fixed above; TR5 was found only by this re-run. /customer-detail cannot be
     audited on this tenant at all — the click lands on "Kunden hittades inte.", so any green there
     would be measured on a not-found page.

   · NO HOVER-ONLY AFFORDANCES in this cluster beyond `.crail`, which _base's touch layer §3
     already stands up. `.lx-row:hover`, `.cx-veh-chip:hover`, `.collapsible-head:hover` and
     `.lf-item` hovers are all decoration over an affordance that is visible at rest.

   · `.lx-tabs` SCROLLS SIDEWAYS AT PHONE — 3 of 5 lanes visible, `scrollbar-width: none`. Left as
     is, twice over: it is `.lx-tab` vocabulary, which boards.css owns app-wide (:490-529), and on
     a touch shell a horizontally scrolled strip is the native gesture with the half-clipped fifth
     tab as its own affordance. It would be a real fault on a desktop shell at this width.

   · `.crail` STRIP OVERFLOWS ITS CARD at both shells (chips clipped mid-word: "Öppn…", "I ver…").
     Deliberate and BOTH-THEMES: ContextRail.css:336 sets `flex-wrap: nowrap; overflow-x: auto`
     below 1280 and argues for it in prose. Not a light regression, not mine.

   · `.cx-fact` / `.vx-fact` / `.cx-dup-row` / `.cx-veh-chip` — the `--row-h` consumers the brief
     pointed at (registers.css:920-1055). They live inside `.lx-sheet`, which the shared ledger
     sets `display: none` below 1179px. They render on NEITHER touch shell. Verified by measurement
     (`.lx-sheet` 0×0 at tablet in both themes), not assumed. Their derived padding is a desktop
     question only.

   · `.lx-row`'s three-line phone layout (name / sub-line / a lone date on row 3) reads untidy at
     412px. It is `listLedgerStyle.ts:275`'s shared phone grid, identical in dark, and it belongs
     to the shared ledger rather than to the two registers. Reported, not patched — patching it
     under `.vx-wrapper` alone would give one ledger a different row from the other five.

   · `registers.css:1878`'s `:has()` — RETRACTED, I was wrong to flag it. I reported
     `html[data-theme="light"] .vx-wrapper .lx-sheet:has(> .lx-empty)` as violating a ban on
     `:has()`. There is no such ban: it is not in the documented trap list, and the repo already
     ships `:has()` at four sites (`registers.css:1878`, `InvoiceDraftDetailDesktop.css:595`,
     `RoutinesDesktop.css:832` and `:1316`, the last a live both-themes layout rule). The rule
     stands as written; nothing to fix. The distinction that does matter is failure mode — an
     unsupported `:has()` drops only its own rule, which is safe for an enhancement (this one is:
     without it the empty pane merely keeps a border) and unsafe only where it is load-bearing.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   THE SPECIFICITY-DEFEATS-A-MEDIA-QUERY MECHANISM — A THIRD INSTANCE, AND IT IS THE WORST
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Asked for by the lead after the money author hit the same shape. It is not in registers.css —
   it is in `_base.css`, it is app-wide, and it voids the touch floor for a whole button family.

       _base.css §CONTROL LAYER   html[data-theme="light"] .btn-astra-glass … min-height: 36px   (0,2,1)
       _base.css §TOUCH LAYER §1  @media (pointer: coarse) {
                                    html[data-theme="light"] button { min-height: var(--tap-min) } }  (0,1,2)

   (0,2,1) beats (0,1,2). A media query buys no specificity, so the 36px wins on every coarse
   pointer and the floor paints nothing for `.btn-astra-neutral`, `.btn-astra-glass`, `.wq-act`,
   `button.pb-chip`, `.pb-sort-btn` and `.fc-sort-btn` — every neutral and secondary button in the
   app, on both touch shells, in light.

   MEASURED, not argued: `.btn-astra-glass` on /vehicle-detail at 1080×810, light, coarse pointer,
   computes `min-height: 36px` and renders 92 × 36.

   The sharpest part is that _base ALREADY KNOWS. Its touch-layer prose names this exact button as
   "the one place light made touch WORSE: 36px in light against >=44px in dark", and it wrote the
   primary/danger floor with the `:not()` chain copied EXACTLY from the primary button's own
   selector, explaining that a comfortable override "scores (0,4,1) and silently loses". That
   defence was given to the primary and destructive buttons and never to the neutral/glass family
   sitting three declarations above them.

   THE CENTRAL FIX — one selector list, matching the control layer's own specificity, appended to
   the touch layer so document order decides the tie (the same technique the primary already uses):

       @media (pointer: coarse) {
           html[data-theme="light"] .btn-astra-neutral,
           html[data-theme="light"] .btn-astra-glass,
           html[data-theme="light"] .wq-act,
           html[data-theme="light"] button.pb-chip,
           html[data-theme="light"] .pb-sort-btn,
           html[data-theme="light"] .fc-sort-btn { min-height: var(--tap-min); }
       }

   UNVERIFIED as written (I did not inject the app-wide form — it reaches five other clusters and is
   not mine to render-test). The cluster-scoped equivalent IS verified: TR5 above takes the same
   button to 44px at (0,3,1) and was measured before and after. Landing the central fix makes TR5
   redundant and it should be deleted then.

   So the count is three, all one mechanism: money's `min-height` over `@media (hover: none)`, my
   §E1 over the ledger's `max-width: 1179px` breakpoint, and this one — a cluster-grade selector
   over a media query in the SAME FILE. The pattern worth naming for the handoff: in this codebase a
   media query is almost always the LOWER-specificity rule, because breakpoint rules are written on
   bare page classes and theme rules carry a mandatory `html[data-theme="light"]` prefix worth
   (0,1,1) before they name anything. Any light rule that sets a property a media query also sets
   must either match that query's condition itself or it will win everywhere.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   BROKEN IN BOTH THEMES  (separate class of finding, called out explicitly per the brief)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   1. /customers renders its error boundary. `select=*` on `03_Customer_CRM` → 403 / 42501.
      Known, Icarus-side, in the brief. Not reported as a design finding.
   2. /customer-detail IS ALSO DOWN, and this was not in the brief. "Kunden hittades inte." with
      four 403s, in light AND dark, reached from a real vehicle's owner chip. Same root cause. Two
      of this cluster's four pages cannot be seen on this tenant.
   3. `.crail` strip clips its chips mid-word below 1280px (see above) — deliberate per its own
      source comment, but it is what a mechanic actually sees on both touch shells.
   4. The chat drawer's four service pills all render the word "Tjänst" with no distinguishing
      label (ChatThreadBar `s.label`). Data/copy, both themes, not CSS.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   DARK IS UNMOVED
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   By construction: every rule is prefixed `html[data-theme="light"]`, so a root stamped `dark`
   cannot match any of them.

   And measured, for the one rule that changes LAYOUT rather than a target size. /vehicles at
   1080×810, THEME=dark, with no override injected:

       .lx-split   x=114 w=926  (single column)
       .lx-list    x=114 w=926
       .lx-sheet   HIDDEN

   Dark already renders exactly what TR1 produces in light. The rule does not move dark and does
   not invent a tablet layout — it removes a light-only override that was defeating one.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ===============================================================================================
   FLOATING CHROME THAT WAS STILL A PALE-BLUE SLAB
   ===============================================================================================
   The report was "that grey subtle fade thing at the bottom of this pop up ... and sharp grey
   contrast on top of white". One mechanism produces all of it, and it is trap #3: the raw colour
   PRIMITIVES flip between themes, so a near-black dark surface read through the light half of a
   flipping channel comes out as a saturated blue-grey. Dark's greys are violet-blue, so the light
   half of the same channel is a pale periwinkle - never white.

   The channels, read straight off app.css's light block:

       --c-blue-1b1f2b   #1b1f2b -> #d2dbed      --c-indigo-14111d  #14111d -> #dce1f1
       --c-blue-12151d   #12151d -> #d6dff0      --c-indigo-0b0910  #0b0910 -> #dfe4f5
       --c-blue-171a24   #171a24 -> #d4ddee      --c-indigo-191428  #191428 -> #dadfef
       --c-blue-10131b   #10131b -> #d6dff1      --c-indigo-100c1a  #100c1a -> #dee3f3
       --ch-indigo-18102b  24,16,43 -> 219,224,240
       --ch-indigo-050309   5, 3, 9 -> 227,232,248
       --ch-indigo-06030e   6, 3,14 -> 226,231,247
       --ch-blue-06080f     6, 8,15 -> 219,228,245

   This is the same fault found and fixed on the notification panel, the toast, the offline banner
   and the wake strip (_base.css, "four surfaces, one channel"), and later on .as-pop (jobs.css),
   .hf-panel (money.css) and .msch-modal (settings.css). Those passes were per-page. This block is
   the sweep of what none of them reached: the popovers, dropdowns, drawers and tooltips that belong
   to no page because they are mounted by shared components.

   THE FIX IS ALWAYS THE SAME SHAPE, from the handoff's section 5.2: an object that floats gets
   --surface (white), ONE separation cue, and 4px. A container has EITHER a border OR an elevation,
   never both. --elevation-2 already CONTAINS a canvas-coloured hairline ring as its first layer, so
   a surface taking elevation-2 sets `border: 0`; a surface taking elevation-3 (pure shadow, no
   ring) keeps a --border-default hairline - which is how .lf-menu and .qa-panel were done above.

   `background`, not `background-color`, on every one of these - trap #18. All but one of the
   originals paint a linear-gradient, and naming only `background-color` leaves the gradient
   rendering: the fix looks applied and does nothing.

   WHY THIS BLOCK IS IN registers.css: it is where .lf-menu, .qa-panel, .cap-panel, .ssel-menu and
   .cwq-* were already handled, so the shared-component floaters stay in one file rather than
   drifting across two. The sheets are plain global CSS; the filename is organisation, not scope.
   =============================================================================================== */

/* -- The date and time picker popovers --------------------------------------------------------
   Both are `linear-gradient(160deg, --c-blue-1b1f2b, --c-blue-12151d)` = #d2dbed -> #d6dff0: a
   periwinkle card floating over a white page, with a `rgba(var(--sf-rgb),0.1)` border that in light
   is ink-over-pale - the "sharp grey contrast on top of white" in the report. The drop is
   `0 18px 44px rgba(var(--ref-shadow-base),0.55)`, a dark-theme amount of shadow that on white
   reads as smoke.

   `.dpf-pop` is `position: fixed; z-index: 201` and `.tpf-pop` is absolute over its own backdrop,
   so nothing scoped to a page wrapper reaches them (trap #21). Matched on their own class.

   `.as-pop` is the third member of this family and is NOT listed here: jobs.css already fixes it,
   scoped `.appt-search .as-pop`, and AppointmentSearch.vue:2 always renders that root - so an
   unscoped duplicate here would be dead weight at lower specificity. */
html[data-theme="light"] .dpf-pop,
html[data-theme="light"] .tpf-pop {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-2);
}

/* The popover headings read --c-indigo-ede9fe, which flips to #1d3995 - a saturated navy. In dark
   that channel is the near-white heading ink; in light it lands on the accent ramp, so the month
   name in the calendar was rendering as a link. Already handled for .dpf-month above; .tpf has no
   heading. Left here as the note, not a rule. */

/* -- The capacity dialog and the check-in dropdown ---------------------------------------------
   `.cap-panel` already had ink and button rules above, but NOTHING ever named its background, so
   the panel itself has been rendering the #d4ddee -> #d6dff1 gradient all along - a fix that looked
   applied and was not, which is trap #18 in its purest form. `.ckm-dropdown` is the one flat
   (non-gradient) case: `background: var(--c-blue-1b1f2b)` = #d2dbed. */
html[data-theme="light"] .cap-panel {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-2);
    color: var(--ink-dim);
}
html[data-theme="light"] .ckm-dropdown {
    background: var(--surface);
    border-color: var(--border-default);
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-3);
}

/* -- The three body-teleported select menus: geometry only -------------------------------------
   Their fill, border and shadow were already corrected above ("The popups: the menu rung with a
   real border"). What that rule did not name is the radius: all three ship 12px, which is dark's
   geometry, and light's ceiling is 4px because softness comes from the shadow. Stated separately
   from the paint rule so the earlier block stays the authority on colour.

   Their `inset 0 1px 0 rgba(var(--hl-rgb),0.12)` lit top edge - a dark-mode device that in light
   paints a dark scar along the top of a menu - is already gone: the earlier rule names box-shadow,
   which replaces every layer at once. Verified by reading it, not assumed. */
html[data-theme="light"] .ssel-menu,
html[data-theme="light"] .lsel-menu,
html[data-theme="light"] .tgp-menu {
    border-radius: var(--r-panel);
}

/* -- The Work Queue chat drawer ----------------------------------------------------------------
   boards.css already stands its shadow down while closed and gives it a transition; it never named
   the fill. `linear-gradient(160deg, --c-blue-171a24, --c-blue-10131b)` = #d4ddee -> #d6dff1, a
   420px periwinkle panel down the right of the Work Queue. A drawer is anchored to an edge, so it
   takes a hairline on that edge and no radius; the depth is the source's own -24px spread, which
   boards.css already governs. */
html[data-theme="light"] .cwq-drawer {
    background: var(--surface);
    border-left: 1px solid var(--keyline);
}

/* -- The invoice assistant, the one member of the assistant family nobody owned -----------------
   Three components ship the identical launcher-plus-panel markup: HermesChat (.hf-*), fixed in
   money.css; QuoteAssistChat (.qa-*), fixed above; and InvoiceChat (.ic-*), fixed nowhere. It fell
   between two clusters - money.css's own comment says InvoiceChat's panel is "left to that
   cluster", and registers.css's says the same in reverse. Both were reading the other as owner.

   Same values as .hf-panel / .hf-btn so the three read as one control. */
html[data-theme="light"] .ic-panel {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-2);
}
html[data-theme="light"] .ic-btn {
    background: var(--accent-solid);
    border: 0;
    color: var(--text-on-accent);
}

/* -- The pale halo under the two remaining floating buttons ------------------------------------
   `box-shadow: 0 8px 26px rgba(var(--ch-indigo-06030e), 0.62)` = rgba(226,231,247,0.62): a 26px
   PALE BLUE HALO under a 56px round button. A shadow lighter than the page it falls on is not a
   shadow, it is a glow - dark's device. `.hf-btn` had this corrected in money.css; `.qa-btn` got
   its fill corrected above but not its shadow, and `.ic-btn` had neither. Two rungs, so the hover
   response survives.

   Specificity: `.qa-btn:hover` is (0,2,0) and so is `html[data-theme="light"] .qa-btn`, so the
   hover rung is written at (0,3,0) rather than relying on document order to break the tie. */
html[data-theme="light"] .qa-btn,
html[data-theme="light"] .ic-btn {
    box-shadow: var(--elevation-2);
}
html[data-theme="light"] .qa-btn:hover,
html[data-theme="light"] .ic-btn:hover {
    box-shadow: var(--elevation-3);
}

/* -- The context rail's hover tooltip ----------------------------------------------------------
   `.crow[data-tip]::after` is `rgba(var(--ch-indigo-18102b), 0.98)` with an a78bfa border - a pale
   periwinkle tip with a violet outline, over a white page, carrying #1d3995 blue text. `.crail`
   itself was fixed in _base.css; its tooltip was not, because a `::after` never appears in a
   class-name grep of the light sheets. That is worth writing down: pseudo-element surfaces are
   structurally invisible to the way this programme has been finding its bugs.

   A tooltip is the one floating surface that is allowed to be dark: it is transient, it is the
   smallest surface in the app, and inverting it is what keeps it from dissolving into the card it
   is explaining. --scrim-strong is the app's own darkest neutral and is defined in both themes.

   Specificity: the source rule is `.crow[data-tip]::after` = (0,2,0); this is (0,3,0). */
html[data-theme="light"] .crow[data-tip]::after {
    color: var(--surface);
    background: var(--scrim-strong);
    border-color: transparent;
    border-radius: var(--r-control);
    box-shadow: var(--elevation-3);
}

/* -- Backdrops ---------------------------------------------------------------------------------
   `rgba(var(--ch-blue-06080f), 0.72)` = rgba(219,228,245,0.72) - a backdrop that BRIGHTENS the page
   behind it. A scrim exists to push the page back; a pale one pushes it forward and leaves the
   dialog with nothing to sit against, which is the second reason these dialogs read as flat grey
   rectangles instead of floating ones.

   `--scrim` / `--scrim-soft` are rgba(20,25,40,.) in light - the same navy the shadows use, so the
   veil and the shadow agree. `.cwq-backdrop` is the lighter one at source (0.5) and stays lighter.

   `--glass-veil-4` is already `none` in light (app.css:2327), so the blur is not in play here.
   `.msch-backdrop` and `.mech-backdrop` are the same value and are handled in settings.css, which
   owns those two components. */
html[data-theme="light"] .cap-backdrop {
    background: var(--scrim);
}
html[data-theme="light"] .cwq-backdrop {
    background: var(--scrim-soft);
}
