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

/* ═════════════════════════════════════════════════════════════════════════════════════════════
   SCOPE

   Every rule below is scoped under `.jobs-wrapper`, the root of BOTH pages in this cluster
   (JobsDesktop.vue and FinishedJobsDesktop.vue) and of no other page in the app. That matters,
   because the two pages share class names with pages outside the cluster — `.tl-event`, `.badge`,
   `.week-track` and `.glass-card` all exist in job-detail and elsewhere — and this file is global.
   Scoping keeps the cluster's corrections inside the cluster.

   Specificity: `html[data-theme="light"] .jobs-wrapper .foo` is (0,3,0), which clears the page
   sheets' (0,1,0) and (0,2,0) rules without needing !important.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── 1. Eyebrows and decorative badges ─────────────────────────────────────────────────────────
   BOKNINGAR in the sidebar and the page eyebrow are violet ink on a violet tint inside a violet
   hairline. They are labels — no action, no state, no status — so in light they are the surface,
   border and text tokens and nothing else. --recess-a05 rather than --surface-3: on a near-white
   sidebar the raised rungs resolve to white and the chip disappears. */
html[data-theme="light"] .jobs-wrapper .badge,
html[data-theme="light"] .jobs-wrapper .cal-sidebar-eyebrow {
    background: var(--recess-a05);
    border-color: var(--border-subtle);
    color: var(--text-muted);
    box-shadow: none;
}

/* NOT here: `.purple-gradient`, the heading highlight on both pages. app.css already carries
   `html[data-theme="light"] .purple-gradient { -webkit-text-fill-color: var(--ink) }`, a
   lead-owned decision that flattens every clipped-gradient heading in light to ink. A cluster
   override repainting it accent would silently disagree with the rest of the app, so it is left
   to that rule. */


/* ── 2. Sidebar ────────────────────────────────────────────────────────────────────────────────
   The sidebar ground is `rgba(--ch-indigo-0e0a1c, .4)`, whose channel flips to 225,222,243 in
   light: a lavender wash down a 260px column, and most of why the page reads violet before a
   single control is drawn. It is a quiet zone inside the calendar shell, so it steps down from the
   card rather than up. */
html[data-theme="light"] .jobs-wrapper .cal-sidebar {
    background: var(--recess-a03);
}

html[data-theme="light"] .jobs-wrapper .mini-cal-month {
    color: var(--text-secondary);
}

/* Month back/forward: secondary buttons. Not violet — surface, hairline, ink. */
html[data-theme="light"] .jobs-wrapper .mini-cal-nav {
    background: var(--surface-3);
    border-color: var(--border-default);
    color: var(--text);
}
html[data-theme="light"] .jobs-wrapper .mini-cal-nav:hover {
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}

html[data-theme="light"] .jobs-wrapper .mini-day:not(.empty):hover {
    background: var(--surface-hover);
    color: var(--text);
}

/* Today is a marker, not a selection: it keeps its amber ring and loses the violet fill. */
html[data-theme="light"] .jobs-wrapper .mini-day.today {
    background: var(--recess-a07);
    color: var(--text);
}

/* The selected day IS a selection, so it keeps the accent — as the quiet wash plus the mandatory
   3px leading bar, not as a filled violet gradient tile. */
html[data-theme="light"] .jobs-wrapper .mini-day.selected {
    background: var(--surface-selected);
    box-shadow: inset 3px 0 0 var(--accent-bar);
    color: var(--text);
}
html[data-theme="light"] .jobs-wrapper .mini-day.selected.has-event::after {
    background: var(--accent-bar);
}

/* "This day has bookings" — information, not accent. */
html[data-theme="light"] .jobs-wrapper .mini-day.has-event::after {
    background: var(--text-muted);
}

/* Scrollbars are chrome, never accent. */
html[data-theme="light"] .jobs-wrapper .upcoming-list,
html[data-theme="light"] .jobs-wrapper .rail-next-list,
html[data-theme="light"] .jobs-wrapper .timeline-scroll,
html[data-theme="light"] .jobs-wrapper .week-grid,
html[data-theme="light"] .jobs-wrapper .cal-grid,
html[data-theme="light"] .jobs-wrapper .lunch-panel {
    scrollbar-color: var(--border-strong) transparent;
}
html[data-theme="light"] .jobs-wrapper .upcoming-list::-webkit-scrollbar-thumb,
html[data-theme="light"] .jobs-wrapper .timeline-scroll::-webkit-scrollbar-thumb,
html[data-theme="light"] .jobs-wrapper .week-grid::-webkit-scrollbar-thumb,
html[data-theme="light"] .jobs-wrapper .cal-grid::-webkit-scrollbar-thumb {
    background: var(--border-strong);
}

html[data-theme="light"] .jobs-wrapper .upcoming-item:hover {
    background: var(--surface-hover);
    border-color: var(--border-default);
}


/* ── 3. Command bar ────────────────────────────────────────────────────────────────────────────
   The active view tab's rule. --fin-accent resolves to #5d00c3 in light, a saturated violet the
   accent re-derivation deliberately moved away from. It is a selection, so it stays accent — at
   the new value. */
html[data-theme="light"] .jobs-wrapper .cmd-mode.active {
    border-bottom-color: var(--accent-solid);
}
html[data-theme="light"] .jobs-wrapper .cmd-mode:focus-visible {
    outline-color: var(--focus-ring);
}

/* Text on the filled primary action. --ink is near-black in light, i.e. dark ink on a deep violet
   fill; --text-on-accent is what that position is for. A ring landing on an accent fill needs the
   inner companion — you cannot ring accent violet with accent violet (SC 2.4.11). */
html[data-theme="light"] .jobs-wrapper .cmd-new {
    color: var(--text-on-accent);
}
html[data-theme="light"] .jobs-wrapper .cmd-new:focus-visible {
    outline-color: var(--focus-ring);
    box-shadow: inset 0 0 0 2px var(--focus-ring-inner);
}

/* Alert chips. A count of mechanics on shift is not an action and not a status — it is a fact, and
   it was wearing the accent. */
html[data-theme="light"] .jobs-wrapper .cal-chip {
    background: var(--recess-a05);
    border-color: var(--border-default);
    color: var(--text-secondary);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .cal-chip:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] .jobs-wrapper .cal-chip .chip-n {
    color: var(--text-strong);
}

/* DELETED 2026-08-11: `.quote-alert` / `.quote-alert-count` and `.cap-pill` / `.day-cap-pill` /
   `.day-cap-item`. Those names exist only in `JobsDesktop.css` — no template under `src/` emits
   them and none is assembled at runtime. Verified `NOT PRESENT IN DOM` on /jobs (probe.mjs,
   THEME=light). The dead sheet in `JobsDesktop.css` is reported upward, not touched here. */


/* ── 4. Month grid ─────────────────────────────────────────────────────────────────────────────
   The per-day count pill. Every populated cell wears one, so this single rule is the largest
   source of violet on the month view — and a count is the plainest possible fact. */
html[data-theme="light"] .jobs-wrapper .cell-count {
    background: var(--recess-a05);
    border-color: var(--border-default);
    color: var(--text-strong);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .cal-cell.today .cell-count {
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
}

/* Out-of-month scaffolding: ink at the floor, not violet at 16%. */
html[data-theme="light"] .jobs-wrapper .cal-day-nil {
    color: var(--text-placeholder);
}

/* DELETED 2026-08-11: `.event-chip.chip-purple`. `.event-chip` is dead sheet in both
   `JobsDesktop.css` and `FinishedJobsDesktop.css` — no template emits it, `NOT PRESENT IN DOM`
   on /jobs. `.more-link` and `.back-link-sm` went with it for the same reason; `.back-link` is
   live and keeps the rule. */
html[data-theme="light"] .jobs-wrapper .back-link {
    color: var(--accent-text);
}


/* ── 5. Day timeline ───────────────────────────────────────────────────────────────────────────
   "Booked" is the commonest status on the busiest page, and it was painted in the accent. Slate —
   the same neutral the week board's --wev-booked-* pair moved to this morning — so the two views
   agree about what a booked car looks like. The bare .tl-event carries the same default. */
html[data-theme="light"] .jobs-wrapper .tl-event,
html[data-theme="light"] .jobs-wrapper .tl-event.status-booked,
html[data-theme="light"] .jobs-wrapper .ovf-item,
html[data-theme="light"] .jobs-wrapper .ovf-item.status-booked {
    background: rgba(var(--wev-booked-wash), 0.5);
    border-color: rgba(var(--wev-booked-edge), 0.28);
    border-left-color: rgb(var(--wev-booked-edge));
}
/* The inset "light catch" reverses in light: --sf-rgb flips to the ink channel and draws a dark
   scar along the top edge of every block. */
html[data-theme="light"] .jobs-wrapper .tl-event {
    box-shadow: var(--elevation-1);
}
html[data-theme="light"] .jobs-wrapper .tl-event:hover {
    box-shadow: var(--elevation-2);
}
html[data-theme="light"] .jobs-wrapper .ovf-item:hover {
    background: rgba(var(--wev-booked-wash), 0.75);
}

/* The hour rules bleed violet into their left end. A rule is a rule. */
html[data-theme="light"] .jobs-wrapper .tl-hour-line {
    background: linear-gradient(to right, var(--border-strong), var(--border-subtle) 60%, transparent);
}

html[data-theme="light"] .jobs-wrapper .tl-overflow,
html[data-theme="light"] .jobs-wrapper .week-overflow {
    background: var(--recess-a05);
    border-color: var(--border-default);
    color: var(--text-secondary);
}
html[data-theme="light"] .jobs-wrapper .tl-overflow:hover,
html[data-theme="light"] .jobs-wrapper .week-overflow:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
}
html[data-theme="light"] .jobs-wrapper .tl-overflow .tl-overflow-count,
html[data-theme="light"] .jobs-wrapper .week-overflow .wov-n {
    color: var(--text-strong);
}


/* ── 6. Week board ─────────────────────────────────────────────────────────────────────────────
   The worst light surface in the cluster, for one reason: every day's numeral is
   `rgba(--ch-indigo-f0eefa, .82)`, whose channel is 240,238,250 in dark and 84,0,182 in light. So
   seven 27px numerals across the head of the board are pure saturated violet, and the week reads
   as violet before a single booking is drawn. They are dates. */
html[data-theme="light"] .jobs-wrapper .week-col-head .wc-num,
html[data-theme="light"] .jobs-wrapper .week-axis-corner .wk-total-num {
    color: var(--text-secondary);
}
html[data-theme="light"] .jobs-wrapper .week-axis-corner .wk-total-lbl {
    color: var(--text-faint);
}
/* Today is the board's one selection, so it is the one date allowed to be accent. The glow is
   dropped: a text-shadow bloom over white smears a numeral instead of lighting it. */
html[data-theme="light"] .jobs-wrapper .week-col-head.today .wc-num {
    color: var(--accent-solid);
    text-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .week-col-head.today .wc-name {
    color: var(--accent-text);
}

/* The day panes. `rgba(--ch-indigo-c4b5fd, .085)` is a violet at 139,92,246 in light, tiled seven
   times down the full height of the board — the lavender field itself. A pane is a quiet zone
   inside the board, so it steps down; weekend and past step less. */
html[data-theme="light"] .jobs-wrapper .week-track {
    background: var(--recess-a03);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}
html[data-theme="light"] .jobs-wrapper .week-track.weekend,
html[data-theme="light"] .jobs-wrapper .week-track.past {
    background: var(--surface-a03);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}
/* Today keeps the accent, as the selection wash plus its bar — not as a radial violet bloom, which
   over white is a stain rather than a light. */
html[data-theme="light"] .jobs-wrapper .week-track.today {
    background: var(--surface-selected);
    box-shadow: inset 0 0 0 1px var(--accent-hairline), inset 3px 0 0 var(--accent-bar);
}
html[data-theme="light"] .jobs-wrapper .week-track.today:hover {
    box-shadow: inset 0 0 0 1px var(--accent-hairline), inset 3px 0 0 var(--accent-bar);
}

/* Closed hours have to read as recessed. `rgba(--ch-indigo-030108, .26)` inverts to 231,228,250 — a
   pale violet LIGHTER than the pane it is meant to cut into, so the shop's closed hours currently
   read as its open ones. */
html[data-theme="light"] .jobs-wrapper .week-closed {
    background: var(--recess-a07);
}
html[data-theme="light"] .jobs-wrapper .week-hline {
    border-top-color: var(--border-subtle);
}

/* Booking blocks already run on --wev-*; only the inverted top-edge highlight and the violet-black
   drop shadow need light values. */
html[data-theme="light"] .jobs-wrapper .week-ev {
    box-shadow: inset 3px 0 0 rgba(var(--wev-edge), 0.85), var(--elevation-1);
}
html[data-theme="light"] .jobs-wrapper .week-ev:hover {
    box-shadow: inset 3px 0 0 rgb(var(--wev-edge)), var(--elevation-2);
}
html[data-theme="light"] .jobs-wrapper .week-ev:focus-visible {
    outline-color: var(--focus-ring);
}
/* The car's name on a block: 84,0,182 in light. It is the block's title, not an accent. */
html[data-theme="light"] .jobs-wrapper .week-ev .wev-veh {
    color: var(--text-strong);
}
/* The admin tail is the booking's own shadow fading out; violet turns it into a second object. */
html[data-theme="light"] .jobs-wrapper .week-admin {
    background: linear-gradient(to bottom, var(--recess-a07), transparent);
}
/* The "now" chip rides a near-black plate in dark; that channel inverts to near-white in light and
   the chip dissolves into the board. */
html[data-theme="light"] .jobs-wrapper .week-now-chip {
    background: var(--surface-4);
    box-shadow: var(--elevation-1), inset 0 0 0 1px var(--border-default);
}

/* The day's load meter genuinely measures progress toward the day's capacity, so the accent is the
   right fill — but the track was `rgba(--sf-rgb, .09)`, which inverts to white on white and stops
   being a track at all. */
html[data-theme="light"] .jobs-wrapper .wk-meter {
    background: var(--control-track-08);
}
html[data-theme="light"] .jobs-wrapper .wk-meter .wk-meter-fill,
html[data-theme="light"] .jobs-wrapper .wk-meter.busy .wk-meter-fill {
    background: var(--accent-solid);
    box-shadow: none;
}


/* ── 7. Day rail ───────────────────────────────────────────────────────────────────────────────
   The hero tile is a violet-tinted panel holding a count. The count is the content; the tint is
   decoration, and a column of decorated panels is where the rail's lavender comes from. */
html[data-theme="light"] .jobs-wrapper .rail-hero {
    background: var(--recess-a05);
    border-color: var(--border-default);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .rail-hero .rail-hero-num {
    background: none;
    color: var(--text-display);
    -webkit-text-fill-color: var(--text-display);
}
html[data-theme="light"] .jobs-wrapper .rail-stat .rail-stat-num,
html[data-theme="light"] .jobs-wrapper .rail-cap-val {
    color: var(--text-strong);
}
html[data-theme="light"] .jobs-wrapper .rail-next:hover {
    background: var(--surface-hover);
    border-color: var(--border-default);
}
html[data-theme="light"] .jobs-wrapper .rail-next-count,
html[data-theme="light"] .jobs-wrapper .rail-next-rel {
    background: var(--recess-a07);
    color: var(--text-secondary);
}
/* Every section header in the rail — ÖVERSIKT, KAPACITET, LEDIGA LUCKOR — is violet at 75%. They
   are eyebrows; three of them stacked down a 300px column is a colour scheme, not an accent. */
html[data-theme="light"] .jobs-wrapper .rail-eyebrow {
    color: var(--text-muted);
}
html[data-theme="light"] .jobs-wrapper .rail-cap-edit {
    color: var(--text-muted);
}
html[data-theme="light"] .jobs-wrapper .rail-cap-seg:hover {
    background: var(--surface-hover);
    border-color: var(--border-default);
}
/* Booked hours against the day's ceiling — progress toward the primary goal, so accent stays, flat
   rather than as a gradient between two violets. */
html[data-theme="light"] .jobs-wrapper .rail-cap-fill {
    background: var(--accent-solid);
}
/* The load profile: how many cars are in the shop across the day. Same meaning, same accent, but a
   tint under a solid top rule rather than a 55%-opaque violet slab. */
html[data-theme="light"] .jobs-wrapper .util-step {
    background: var(--accent-tint-strong);
    border-top-color: var(--accent-solid);
}
html[data-theme="light"] .jobs-wrapper .util-scrub-label.busy {
    background: var(--accent-solid);
    border-top-color: var(--accent-solid);
    color: var(--text-on-accent);
}
html[data-theme="light"] .jobs-wrapper .util-ceiling {
    border-top-color: var(--border-strong);
}


/* ── 8. Booking sheet, search and the lunch editor ──────────────────────────────────────────────
   DELETED 2026-08-11: `.add-booking-btn` (+ :hover) — dead sheet in `JobsDesktop.css`, no template
   emits it, `NOT PRESENT IN DOM` on /jobs. */
html[data-theme="light"] .jobs-wrapper .mode-tab.active::after {
    background: var(--accent-solid);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .mode-tab:hover {
    color: var(--text);
}

html[data-theme="light"] .jobs-wrapper .search-result:hover {
    background: var(--surface-hover);
    border-color: var(--border-default);
}
html[data-theme="light"] .jobs-wrapper .sr-source.agent,
html[data-theme="light"] .jobs-wrapper .bk-row-source.agent {
    background: var(--recess-a05);
    border-color: var(--border-default);
    color: var(--text-secondary);
}
/* DELETED 2026-08-11: the four `.veh-option` rules — dead sheet in `JobsDesktop.css`, the name
   appears in no template under `src/`. */

html[data-theme="light"] .jobs-wrapper .time-pill,
html[data-theme="light"] .jobs-wrapper .status-badge.booked,
html[data-theme="light"] .jobs-wrapper .lunch-status.custom,
html[data-theme="light"] .jobs-wrapper .lunch-slot-tag.own {
    background: var(--recess-a05);
    border-color: var(--border-default);
    color: var(--text-secondary);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .glass-card.status-booked {
    border-color: rgba(var(--wev-booked-edge), 0.3);
}
/* The label column's tick: a glowing violet dot before every label in every card. */
html[data-theme="light"] .jobs-wrapper .detail-label {
    color: var(--text-muted);
}
html[data-theme="light"] .jobs-wrapper .detail-label::before {
    background: var(--border-strong);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .lunch-slot.custom {
    background: var(--recess-a05);
    border-color: var(--border-default);
}
html[data-theme="light"] .jobs-wrapper .lunch-slot.open {
    border-color: var(--border-default);
}
html[data-theme="light"] .jobs-wrapper .lunch-slot-dot {
    background: var(--text-muted);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .lunch-slot-chev,
html[data-theme="light"] .jobs-wrapper .lunch-card-lbl,
html[data-theme="light"] .jobs-wrapper .lunch-sec-label {
    color: var(--text-muted);
}
html[data-theme="light"] .jobs-wrapper .lunch-dur input:focus,
html[data-theme="light"] .jobs-wrapper .lunch-slot-name:focus {
    border-color: var(--focus-ring);
}
/* The lunch toggle. A lit track takes the accent; the knob takes the knob token, never ink. */
html[data-theme="light"] .jobs-wrapper .lunch-switch.on {
    color: var(--text);
}
html[data-theme="light"] .jobs-wrapper .lunch-switch.on .lunch-switch-track {
    background: var(--accent-solid);
    border-color: transparent;
}
html[data-theme="light"] .jobs-wrapper .lunch-switch-knob {
    background: var(--control-knob);
    box-shadow: var(--elevation-1);
}


/* ── 9. Finished jobs ──────────────────────────────────────────────────────────────────────────
   The card board is emerald by its own design and is left alone — it is not violet. What is violet
   is the calendar view it shares with Jobs (covered by the .week-* / .mini-* rules above) and the
   invoice button, which is that page's primary action. */
/* "Avslutade Kalender" highlights its second word with the same clipped two-violet gradient the
   page headings use — but through a bare `.cal-title span`, so app.css's light rule for
   `.purple-gradient` never reaches it. Same treatment, so the two headings agree: ink.
   (`:not(.purple-gradient)` keeps this off anything app.css already owns.) */
html[data-theme="light"] .jobs-wrapper .cal-title span:not(.purple-gradient) {
    background: none;
    color: var(--text);
    -webkit-text-fill-color: var(--text);
}

/* Week/Month segmented control. A filled violet pill inside a track is the loudest object in a
   header whose content is a month of empty cells; selection is the wash plus its bar. */
html[data-theme="light"] .jobs-wrapper .mode-switch {
    background: var(--recess-a05);
}
html[data-theme="light"] .jobs-wrapper .mode-switch .mode-tab.active {
    background: var(--surface-3);
    color: var(--text-strong);
    box-shadow: inset 3px 0 0 var(--accent-bar), var(--elevation-1);
}

/* Month back/forward, the same secondary-button treatment the mini-calendar's pair gets. */
html[data-theme="light"] .jobs-wrapper .cal-nav-btn {
    background: var(--surface-3);
    border-color: var(--border-default);
    color: var(--text);
}
html[data-theme="light"] .jobs-wrapper .cal-nav-btn:hover {
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}

html[data-theme="light"] .jobs-wrapper .invoice-btn {
    background: var(--accent-solid);
    color: var(--text-on-accent);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .invoice-btn:hover {
    background: var(--accent-solid-hover);
    box-shadow: none;
}
/* DELETED 2026-08-11: `.glass-card.canceled-card .fj-canceled-open` — both names are dead sheet in
   `FinishedJobsDesktop.css`; neither appears in any template under `src/`. */


/* ── 10. Two rules that reach in from lib/calendarTokens.ts ────────────────────────────────────
   The month ledger's day figures are styled by the shared calendar sheet, not by either page in
   this cluster, so they are corrected here rather than at source — the lib file is not ours and the
   same sheet serves pages in other clusters. Both are scoped to .jobs-wrapper for that reason, and
   both are reported upward.

   `rgba(--ch-indigo-f5f3f9, .22)` has channel 245,243,249 in dark and 84,0,182 in light, so every
   day outside the month is a saturated lavender numeral. It is scaffolding. */
html[data-theme="light"] .jobs-wrapper .cal-day.is-outside .cal-daynum {
    color: var(--text-placeholder);
}
/* Today's figure and its rule take --fin-accent, which resolves to #5d00c3 in light. Today is the
   grid's selection, so it stays accent — at the re-derived value, not the finance sheet's. */
html[data-theme="light"] .jobs-wrapper .cal-day.is-today .cal-daynum {
    color: var(--accent-solid);
}
html[data-theme="light"] .jobs-wrapper .cal-day.is-today .cal-daynum::after {
    background: var(--accent-bar);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   THE STRUCTURAL PASS — the Work Queue prototype mapped onto the jobs cluster
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Everything above this line is the COLOUR pass: it stopped the cluster from reading violet. This
   section is the STRUCTURAL one, and it is what the signed-off Work Queue (boards.css, "the light
   structural prototype") did to that page, applied here to `.jobs-wrapper`'s own class names.

   Light is not a re-skin. Dark carries state in coloured text and glow; light carries it in a filled
   badge with a shape-distinct glyph, a 1px keyline, and an off-white canvas under white surfaces.
   Those are different mechanisms, and a mechanism lives in geometry, not only in hex.

   Every selector below was read out of `src/pages/jobs/JobsDesktop.css`,
   `src/pages/finished-jobs/FinishedJobsDesktop.css` or the two templates. Nothing here is guessed:
   the prototype lost four rules to guessed selectors (one painted a whole row amber, one keyed on an
   `<a>` that is a `<button>`), and a wrong selector in this file is silent — it neither errors nor
   shows up in a build.

   Recipes used, in the order the brief states them: badge grammar · the verdict rule · rows on the
   height rungs · rectangles and underlines · eyebrows · one primary per pane · meter tokens · scroll
   regions keep an edge · gradients and glows deleted · figures tabular.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── S1. THE PAGE HEADER GETS ITS RULE ─────────────────────────────────────────────────────────
   One hairline under the header, so the board reads as beginning somewhere. Both pages declare
   `.jobs-header`; the compact variant is the same element. */
html[data-theme="light"] .jobs-wrapper .jobs-header {
    border-bottom: 1px solid var(--keyline);
    padding-bottom: var(--sp-3);
}


/* ── S2. STATUS BADGES — the grammar, with the glyph that carries colour vision ─────────────────
   `.status-badge` is rendered on both pages from `jobStatusBadgeClass()` (src/models/jobStatus.ts),
   which emits exactly: in-progress · awaiting-pickup · booked · canceled · finished ·
   awaiting-checkin. FinishedJobsDesktop.css additionally declares `.ongoing`, `.pending` and
   `.no-show` from an earlier vocabulary; they are mapped too, at no cost, so a stale render cannot
   fall through to a dark-theme tint.

   `_base.css` already gives every `.status-badge` the rectangle, the fill and the neutral ink. What
   it cannot know is which of THESE words means what, so the hue and the glyph are assigned here.

   The glyphs are shape-distinct on purpose and it is not decoration: under deuteranopia the app's
   emerald and red collapse to two olives at dE 0.040 on TEXT. Generated content is not in the
   accessibility tree, so the word keeps sole ownership of the accessible name. */
html[data-theme="light"] .jobs-wrapper .status-badge {
    border-radius: 4px;
    padding: 2px 6px;
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    text-transform: none;
    box-shadow: none;
}

/* Being worked. Same token and same glyph as the Work Queue's `.lx-flag.f-progress`, so a mechanic
   learns one vocabulary across the two boards. */
html[data-theme="light"] .jobs-wrapper .status-badge.in-progress,
html[data-theme="light"] .jobs-wrapper .status-badge.ongoing {
    background: var(--st-logged-soft);
    border: 1px solid var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .jobs-wrapper .status-badge.in-progress::before,
html[data-theme="light"] .jobs-wrapper .status-badge.ongoing::before { content: "\25B8"; }

/* Done and gone: the one true success on this page. */
html[data-theme="light"] .jobs-wrapper .status-badge.finished {
    background: var(--st-logged-soft);
    border: 1px solid var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .jobs-wrapper .status-badge.finished::before { content: "\2713"; }

/* Work done, car still on the lot — an outstanding errand for the shop, not a success and not a
   fault. Neutral, and the bang is what separates it from "Bokad" beside it. There is no good dark
   amber: to clear 3:1 on a light ground an amber must be darkened, and a darkened yellow is
   mustard. That is a fact about the hue, not a failure to find the right one. */
html[data-theme="light"] .jobs-wrapper .status-badge.awaiting-pickup,
html[data-theme="light"] .jobs-wrapper .status-badge.awaiting-checkin {
    background: var(--surface-inset);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .status-badge.awaiting-pickup::before,
html[data-theme="light"] .jobs-wrapper .status-badge.awaiting-checkin::before { content: "!"; }

/* A future slot. The commonest word on the busiest board — it is the ordinary case and it spends no
   hue at all. */
html[data-theme="light"] .jobs-wrapper .status-badge.booked,
html[data-theme="light"] .jobs-wrapper .status-badge.pending {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .status-badge.booked::before,
html[data-theme="light"] .jobs-wrapper .status-badge.pending::before { content: "\2022"; }

/* Called off. The cross is the carrier, not a hue — on the Finished Jobs "canceled" lane EVERY row
   is a cancellation, and a colour that is on every row has stopped carrying information. The dark
   sheet reached the same conclusion by a different route (it went slate so it could not be misread
   as the violet "Bokad" beside it). */
html[data-theme="light"] .jobs-wrapper .status-badge.canceled {
    background: var(--surface-inset);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .status-badge.canceled::before { content: "\2715"; }

/* A customer who never turned up IS the thing that went wrong, and it is rare. It keeps the hue. */
html[data-theme="light"] .jobs-wrapper .status-badge.no-show {
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
    color: var(--st-breach);
}
html[data-theme="light"] .jobs-wrapper .status-badge.no-show::before { content: "\2715"; }


/* ── S3. TABS AND CHIPS — rectangles, and an active tab is an UNDERLINE ────────────────────────
   `--r-pill` is for true pills. A filter chip or a view tab in a dense board is a rectangle: a row
   of capsules reads as consumer-app and wastes width at both ends of every one. And an active tab is
   an underline, which is the strongest available "you are here" that spends no fill and no accent
   area — the filled capsule is the loudest object in a header whose content is a month of cells.

   Three tab sets exist in this cluster and they must agree with each other:
     .view-toggle / .view-tab   Cards|Calendar        (FinishedJobsDesktop)
     .lane-bar   / .lane-tab    awaiting|collected|canceled  (FinishedJobsDesktop)
     .mode-switch / .mode-tab   Week|Month            (FinishedJobsDesktop; §9 above tints it) */
html[data-theme="light"] .jobs-wrapper .view-toggle,
html[data-theme="light"] .jobs-wrapper .mode-switch {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
    gap: var(--sp-2);
}

html[data-theme="light"] .jobs-wrapper .view-tab,
html[data-theme="light"] .jobs-wrapper .mode-switch .mode-tab,
html[data-theme="light"] .jobs-wrapper .lane-tab {
    min-height: var(--ctl-h);
    padding: 0 var(--ctl-pad-x);
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    background: none;
    box-shadow: none;
    font-weight: var(--fw-label);
    color: var(--ink-muted);
}

html[data-theme="light"] .jobs-wrapper .view-tab.active,
html[data-theme="light"] .jobs-wrapper .mode-switch .mode-tab.active,
html[data-theme="light"] .jobs-wrapper .lane-tab.active,
html[data-theme="light"] .jobs-wrapper .lane-tab.awaiting.active,
html[data-theme="light"] .jobs-wrapper .lane-tab.collected.active,
html[data-theme="light"] .jobs-wrapper .lane-tab.canceled.active {
    background: none;
    border-bottom-color: var(--accent);
    box-shadow: none;
    color: var(--ink);
}

/* The lane dot is the board's legend — "blue = still here, green = gone" — and it is the one place
   on the strip where a hue is doing work no word repeats. It is `background: currentColor` in the
   sheet, so neutralising the tab's text would have silently deleted it. Each lane gets its meaning
   back explicitly, off the status family. */
html[data-theme="light"] .jobs-wrapper .lane-tab .lane-dot { opacity: 1; }
html[data-theme="light"] .jobs-wrapper .lane-tab.awaiting .lane-dot { background: var(--st-info); }
html[data-theme="light"] .jobs-wrapper .lane-tab.collected .lane-dot { background: var(--st-logged); }
html[data-theme="light"] .jobs-wrapper .lane-tab.canceled .lane-dot { background: var(--ink-muted); }

/* A tab's count is a count. Rectangle, inset well, muted ink, tabular so the three read as a column
   of figures rather than three different-width lozenges. */
html[data-theme="light"] .jobs-wrapper .lane-count,
html[data-theme="light"] .jobs-wrapper .lane-tab.active .lane-count {
    border-radius: 4px;
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    color: var(--ink-muted);
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-label);
}
html[data-theme="light"] .jobs-wrapper .lane-tab.active .lane-count {
    border-color: var(--keyline-strong);
    color: var(--ink-dim);
}

/* The shared filter toolbar (src/lib/listFilterStyle.ts, `.lf-*`) is re-tinted emerald by this page.
   Scoped to `.fj-toolbar` so only this cluster's copy moves. */
html[data-theme="light"] .jobs-wrapper .fj-toolbar .lf-toggle,
html[data-theme="light"] .jobs-wrapper .fj-toolbar .lf-tag {
    border-radius: var(--r-control);
}
html[data-theme="light"] .jobs-wrapper .fj-toolbar .lf-toggle.active,
html[data-theme="light"] .jobs-wrapper .fj-toolbar .lf-item.active {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--ink);
}

/* The no-show chip on an archive row. Rectangle, badge grammar, and the cross — same shape the badge
   above uses for the same fact, so the row does not say it two different ways. */
html[data-theme="light"] .jobs-wrapper .fj-chip {
    border-radius: 4px;
    font-weight: var(--fw-label);
    letter-spacing: 0;
}
html[data-theme="light"] .jobs-wrapper .fj-chip.warn {
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
    color: var(--st-breach);
}

/* The month cell's count pill on both boards. A count, in a rectangle, tabular. */
html[data-theme="light"] .jobs-wrapper .cell-count,
html[data-theme="light"] .jobs-wrapper .cal-day-count {
    border-radius: 4px;
    padding: 1px 6px;
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
    box-shadow: none;
}


/* ── S4. THE ARCHIVE LEDGER — a divided list, and a divider you can actually see ────────────────
   `.fj-group` is the surface, `.fj-row` the row, and the separator between rows is
   `inset 0 1px 0 rgba(var(--hl-rgb), 0.045)`. `--hl-rgb` is `255,255,255` in BOTH themes by design
   (it is the highlight channel), so in light that separator is white at 4.5% on white: the list
   loses every divider it has and nothing errors. This is the exact failure mode the brief names —
   a fill with no border is invisible and silent. A real keyline replaces it.

   The group keeps its surface and one hairline; the shadow goes (it is not floating) and the radius
   comes down to the card rung. */
html[data-theme="light"] .jobs-wrapper .fj-group {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
}

/* 48px floor with 10px of derived vertical padding — DERIVED, and exempt from the spacing scale:
   these rows carry a second metadata line, so the floor is what a one-line row lands on and the
   padding is what a two-line row grows from. Horizontal padding is the constant 16px, because a row
   that gets shorter must not also get narrower or the columns stop agreeing between densities. */
html[data-theme="light"] .jobs-wrapper .fj-row {
    min-height: var(--row-h-comfy);
    padding: 10px var(--row-pad-x);
    grid-template-columns: 16px minmax(0, 1fr) auto 10.5rem;
}

html[data-theme="light"] .jobs-wrapper .fj-row + .fj-row {
    box-shadow: none;
    border-top: 1px solid var(--keyline);
}

/* The divider is the separation mechanism, so hover must not introduce a second one. */
html[data-theme="light"] .jobs-wrapper .fj-row:hover {
    background: var(--surface-inset);
}

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

/* ── The row flag becomes a badge with a glyph ──
   `.fj-flag` is a 6px dot with a coloured glow. Both halves are dark devices: a glow needs darkness
   to read as emission, and a coloured dot is colour working alone — the amber "unbilled" and the red
   "no-show" dots collapse to one olive under deuteranopia. It becomes a 16px rounded square carrying
   a shape-distinct glyph, and the row's first column widens from 6px to 16px to match (that column
   width is why the grid is restated above).

   A clean row renders `.fj-flag` with no modifier and no fill — the empty box is invisible, which is
   the "nothing to do here" signal the sheet intends. The base therefore paints nothing. */
html[data-theme="light"] .jobs-wrapper .fj-flag {
    width: 16px;
    height: 16px;
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid transparent;
    box-shadow: none;
    font-size: 10px;
    font-weight: var(--fw-label);
    line-height: 1;
}

/* Collected but never invoiced. It is the ordinary outstanding case — it fires on every unbilled
   row the moment a shop connects Fortnox — so it goes neutral. There is no good dark amber: to
   clear 3:1 on a light ground an amber must be darkened, and a darkened yellow is mustard. */
html[data-theme="light"] .jobs-wrapper .fj-flag.due {
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .fj-flag.due::before { content: "!"; }

/* Nobody turned up. Rare, and genuinely wrong — this is what the hue is being saved for. */
html[data-theme="light"] .jobs-wrapper .fj-flag.late {
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    color: var(--st-breach);
}
html[data-theme="light"] .jobs-wrapper .fj-flag.late::before { content: "\2715"; }

/* ── The bucket heading becomes an eyebrow, and its count stops being a status ── */
html[data-theme="light"] .jobs-wrapper .fj-bucket-name {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}
html[data-theme="light"] .jobs-wrapper .fj-bucket-n {
    font-weight: var(--fw-label);
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}
html[data-theme="light"] .jobs-wrapper .fj-bucket::after {
    background: var(--keyline);
}

/* The result line above the ledger. "· 4 fakturerade" was emerald: a number is not a status. */
html[data-theme="light"] .jobs-wrapper .fj-result-n,
html[data-theme="light"] .jobs-wrapper .fj-result-inv {
    color: var(--ink-muted);
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}

/* ── The row's own figures and its trailing action ── */
html[data-theme="light"] .jobs-wrapper .fj-when b {
    color: var(--ink);
    font-weight: var(--fw-label);
}
html[data-theme="light"] .jobs-wrapper .fj-meta .cost {
    color: var(--ink);
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}

/* "Fakturerad #1042" is a state; "Fakturera" is a button. They looked alike and read alike. The
   first takes the badge grammar with the tick; the second becomes the secondary control it is —
   this pane's one filled primary is the lot card's invoice button, not a per-row action repeated
   thirteen times down a list. */
html[data-theme="light"] .jobs-wrapper .fj-inv {
    border-radius: 4px;
    font-weight: var(--fw-label);
}
html[data-theme="light"] .jobs-wrapper .fj-inv.yes {
    background: var(--st-logged-soft);
    border: 1px solid var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .jobs-wrapper .fj-inv.yes::before {
    content: "\2713";
    margin-right: 4px;
}
html[data-theme="light"] .jobs-wrapper .fj-inv.btn {
    border-radius: var(--r-control);
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .fj-inv.btn:hover {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--accent);
}


/* ── S5. THE LOT STRIP — the wait verdict, and a meter that has a track again ───────────────────
   These cards are cars standing on the floor, and the only question the lane answers is who to ring
   first. The card is the wait.

   The card itself: glass off, shadow off (a bordered card on an off-white canvas does not also
   float), radius to the card rung, and no lift on hover — a hover that moves the card makes a
   thirteen-card grid twitch as the cursor crosses it. */
html[data-theme="light"] .jobs-wrapper .lot-card {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
html[data-theme="light"] .jobs-wrapper .lot-card:hover {
    transform: none;
    border-color: var(--keyline-strong);
    background: var(--surface-inset);
}
html[data-theme="light"] .jobs-wrapper .lot-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* THE VERDICT RULE, and this is the clearest instance of it in the cluster. Every card in the lane
   prints a wait, so "3 dagar" wearing amber means the amber is on nearly every card — a colour that
   is always on screen has stopped carrying information. The ordinary case goes to ink; only the
   seven-day card, the one that is actually wrong, keeps a hue. The figure is printed in words
   beside the bar either way, so nothing is lost with the colour. */
html[data-theme="light"] .jobs-wrapper .lot-wait-text {
    font-weight: var(--fw-label);
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
}
html[data-theme="light"] .jobs-wrapper .lot-card.wait-due .lot-wait-text {
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .lot-card.wait-late .lot-wait-text {
    color: var(--st-breach);
    font-weight: var(--fw-strong);
}

/* The meter. Its track was `rgba(var(--sf-rgb), 0.07)` — the surface channel, which inverts to the
   INK channel in light, so the "empty" part of the bar rendered as a grey smear on white with no
   edge; and the fills came off the status TEXT family, which owes 4.5:1 and therefore overshoots a
   bar (owed 3:1 under SC 1.4.11) into olive-brown and washed pink. Inset track with a keyline, and
   the --meter-* family for the fills.

   The track keeps --r-pill: on a 4px-tall bar a full round IS the correct terminal shape. The rule
   is "no capsules for controls", not "no round anywhere". */
html[data-theme="light"] .jobs-wrapper .lot-meter {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    box-sizing: border-box;
}
html[data-theme="light"] .jobs-wrapper .lot-card.wait-fresh .lot-meter-fill {
    background: var(--meter-ok);
}
html[data-theme="light"] .jobs-wrapper .lot-card.wait-due .lot-meter-fill {
    background: var(--meter-warn);
}
html[data-theme="light"] .jobs-wrapper .lot-card.wait-late .lot-meter-fill {
    background: var(--meter-bad);
}

/* The phone number is the card's real errand, but it is a link, not the pane's primary: ink, and it
   takes the accent on hover, which is where a link's colour earns its keep. */
html[data-theme="light"] .jobs-wrapper .lot-call {
    color: var(--ink-dim);
    font-variant-numeric: tabular-nums;
}
html[data-theme="light"] .jobs-wrapper .lot-call:hover {
    color: var(--accent);
}
html[data-theme="light"] .jobs-wrapper .lot-veh {
    color: var(--ink);
    font-weight: var(--fw-strong);
}

/* ONE PRIMARY PER PANE. `.invoice-btn` is the terminal action on the card — it is what closes the
   job out — so it stays filled (§9 above already gives it the flat accent). Here it only loses the
   capsule and the lift. `.invoice-pill` is the STATE that replaces it once the job is billed, so it
   is a badge and not a second button wearing the same shape. */
html[data-theme="light"] .jobs-wrapper .invoice-btn {
    border-radius: var(--r-control);
    font-weight: var(--fw-label);
}
html[data-theme="light"] .jobs-wrapper .invoice-btn:hover {
    transform: none;
}
html[data-theme="light"] .jobs-wrapper .invoice-pill {
    border-radius: 4px;
    padding: 2px 6px;
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    background: var(--st-logged-soft);
    border: 1px solid var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .jobs-wrapper .invoice-pill::before {
    content: "\2713";
    margin-right: 4px;
}


/* ── S6. SECTION HEADINGS BECOME EYEBROWS ──────────────────────────────────────────────────────
   A coloured, letterspaced 700-weight section heading is a dark device: on white it reads as a
   highlighter mark, and it spends the accent budget on something that is not an action and cannot be
   clicked. Every one of these becomes the same eyebrow — 11px, uppercase, tracked, muted — so the
   sidebar, the rail, the booking sheet and the lunch editor stop each having their own idea of what
   a heading looks like. The §2/§7 rules above already corrected their COLOUR; this is the shape. */
html[data-theme="light"] .jobs-wrapper .sidebar-section-title,
html[data-theme="light"] .jobs-wrapper .cal-sidebar-eyebrow,
html[data-theme="light"] .jobs-wrapper .rail-eyebrow,
html[data-theme="light"] .jobs-wrapper .rail-next-eyebrow,
html[data-theme="light"] .jobs-wrapper .bk-zone-head,
html[data-theme="light"] .jobs-wrapper .lunch-card-lbl,
html[data-theme="light"] .jobs-wrapper .lunch-sec-label,
html[data-theme="light"] .jobs-wrapper .modal-field > label {
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* The page eyebrow chip loses its capsule. It is a label, not a status, so it gets NO glyph — the
   `_base.css` note is explicit that a bare `.badge` is often a plain label and that attaching a dot
   to the base selector puts a bullet in front of the page title. */
html[data-theme="light"] .jobs-wrapper .badge,
html[data-theme="light"] .jobs-wrapper .cal-sidebar-eyebrow {
    border-radius: 4px;
    padding: 2px 6px;
    font-weight: var(--fw-label);
}


/* ── S7. SCROLL REGIONS KEEP AN EDGE, AND A LIST OF CARDS BECOMES A LIST ───────────────────────
   A column that scrolls independently must show where it begins and ends: white on #f6f8fa with no
   hairline has no boundary at all, and a row half-clipped at the top of an unbordered region looks
   like a rendering fault rather than like scrolling. This is the recipe that produces no error when
   it is missed.

   The two board panes and the two rail lists get a surface and a keyline. The archive ledger and the
   lot grid deliberately do NOT: their contents (`.fj-group`, `.lot-card`) are already bordered
   surfaces, and a bordered box around bordered boxes is a box in a box. */
html[data-theme="light"] .jobs-wrapper .timeline-scroll,
html[data-theme="light"] .jobs-wrapper .upcoming-list,
html[data-theme="light"] .jobs-wrapper .rail-next-list {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
}

/* The sidebar's upcoming list was eight little cards with their own radius, border and 8px gap —
   card-per-row, which at this size is eight objects where there is one list. Rows on the compact
   rung with a single divider. */
html[data-theme="light"] .jobs-wrapper .upcoming-item {
    margin-bottom: 0;
    border: 0;
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    background: none;
    min-height: var(--row-h);
    padding: var(--sp-2) var(--sp-3);
    align-items: center;
}
html[data-theme="light"] .jobs-wrapper .upcoming-item:last-child {
    border-bottom: 0;
}
html[data-theme="light"] .jobs-wrapper .upcoming-item:hover {
    background: var(--surface-inset);
    border-color: var(--keyline);
    transform: none;
}
html[data-theme="light"] .jobs-wrapper .upcoming-meta {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

/* The rail's "next in" cards keep their card shape — there are at most three and each is a target —
   but they stop lifting off the page. */
html[data-theme="light"] .jobs-wrapper .rail-next:hover {
    transform: none;
}

/* The finished-jobs month grid is 35 glass cards with a 20px radius, a 28px blur, a three-part
   shadow and a 3px hover lift, drawn identically whether the day holds eight jobs or none. (The
   Jobs page's own month ledger was already reduced to hairlines — this is the copy that was not.) */
html[data-theme="light"] .jobs-wrapper .cal-cell {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
html[data-theme="light"] .jobs-wrapper .cal-cell:not(.other-month):hover {
    transform: none;
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .cal-cell.today {
    border-color: var(--accent);
}
html[data-theme="light"] .jobs-wrapper .cell-num {
    font-variant-numeric: tabular-nums;
}


/* ── S8. METERS, GRADIENTS AND GLOWS ───────────────────────────────────────────────────────────
   In light these are deleted rather than restyled. A rule that fades to transparent exists because a
   hard line reads as a scratch on near-black; on white it reads as a rendering artefact. A glow
   needs darkness to read as emission; on white it is a smudge. And a two-stop gradient on a 2px bar
   reads as a lit bar in dark and as a smear here.

   The week board's day-load gauge. Its track is `rgba(var(--sf-rgb), 0.09)` — the SURFACE channel,
   which inverts to the ink channel in light, so the unfilled part of the gauge was a grey dash with
   no edge. Inset track with a keyline, and the fills come off --meter-*, never off the --st- text
   family: a bar owes 3:1 under SC 1.4.11, and the text tokens overshoot that into olive-brown and
   washed pink when laid down as solid fill. The four glows go. */
html[data-theme="light"] .jobs-wrapper .wk-meter {
    background: var(--surface-inset);
    box-shadow: inset 0 0 0 1px var(--keyline);
}
html[data-theme="light"] .jobs-wrapper .wk-meter .wk-meter-fill,
html[data-theme="light"] .jobs-wrapper .wk-meter.busy .wk-meter-fill {
    background: var(--accent);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .wk-meter.quiet .wk-meter-fill {
    background: var(--meter-ok);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .wk-meter.full .wk-meter-fill {
    background: var(--meter-warn);
    box-shadow: none;
}
html[data-theme="light"] .jobs-wrapper .wk-meter.over .wk-meter-fill {
    background: var(--meter-bad);
    box-shadow: none;
}

/* THE VERDICT RULE, applied to the shared calendar sheet's day-load marker (lib/calendarTokens.ts,
   `.cal-page .cal-load-marker`). Its default is --st-expected, so every populated day on the month
   ledger carried an amber tick — the ordinary case wearing the warning colour. Neutral by default;
   "every hour accounted for" keeps its green and "past the ceiling" keeps its red, and both of those
   are already stated a second time in geometry and in words.

   Corrected here rather than at source: the lib sheet is not this cluster's and serves other pages.
   Reported upward. */
html[data-theme="light"] .jobs-wrapper .cal-load-marker {
    background: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .cal-load.is-done .cal-load-marker {
    background: var(--meter-ok);
}
html[data-theme="light"] .jobs-wrapper .cal-load.is-over .cal-load-marker {
    background: var(--meter-bad);
}

/* The "now" line, on both the day timeline and the week board. It is drawn with a `border-image`
   gradient from pink to indigo, which on white is a smear across the board. One flat rule; red is
   the calendar convention for now and it is the one thing on the board that must be found in a
   glance. The dots lose their two-layer bloom and take a ring in the SURFACE colour instead, so the
   dot punches through the rule rather than sitting in a haze on top of it. */
html[data-theme="light"] .jobs-wrapper .tl-now,
html[data-theme="light"] .jobs-wrapper .week-now {
    border-image: none;
    border-top-color: var(--st-breach);
}
html[data-theme="light"] .jobs-wrapper .tl-now-dot,
html[data-theme="light"] .jobs-wrapper .week-now-dot {
    background: var(--st-breach);
    box-shadow: 0 0 0 2px var(--surface);
}
html[data-theme="light"] .jobs-wrapper .tl-now-label {
    background: var(--surface);
    border: 1px solid var(--keyline);
    color: var(--st-breach);
    font-family: inherit;
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}

/* The hour rules. §5 above already replaced the violet with a neutral fade; the fade itself is the
   dark device. On white a rule is just a rule. */
html[data-theme="light"] .jobs-wrapper .tl-hour-line {
    background: var(--keyline);
}

/* The load profile's scrub label. Three two-stop gradients, one per reading, each with an arrow that
   inherits its border colour — so a flat token has to be set on BOTH halves or the arrow keeps the
   old hue. "Busy" is progress toward the day's capacity and stays on the accent (section 7 above);
   the other two go flat. */
html[data-theme="light"] .jobs-wrapper .util-scrub-label.free {
    background: var(--st-logged-soft);
    border-top-color: var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .jobs-wrapper .util-scrub-label.lunch {
    background: var(--surface-inset);
    border-top-color: var(--keyline-strong);
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .util-scrub-label {
    border-radius: 4px;
    font-weight: var(--fw-label);
    box-shadow: var(--elevation-1);
}
html[data-theme="light"] .jobs-wrapper .util-step.over {
    background: var(--st-breach-soft);
    border-top-color: var(--st-breach);
}

/* A free slot is a fact about the day, offered as a chip: rectangle, quiet, and NOT a success hue —
   emerald on nine chips in a column is a colour scheme. */
html[data-theme="light"] .jobs-wrapper .free-slot {
    border-radius: var(--r-control);
    background: var(--surface-inset);
    border-color: var(--keyline-strong);
    color: var(--ink-dim);
    box-shadow: none;
    font-variant-numeric: tabular-nums;
}

/* The lunch editor's provenance tags: four capsules in four hues on one row. Rectangles, and only
   the one that means something is missing keeps a hue. */
html[data-theme="light"] .jobs-wrapper .lunch-slot-tag {
    border-radius: 4px;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    color: var(--ink-muted);
}
html[data-theme="light"] .jobs-wrapper .lunch-slot-tag.none {
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    color: var(--st-breach);
}

/* Scrollbars are chrome, never accent and never a status hue. Section 2 above covers the Jobs page's
   own; these two are the finished-jobs sheet's emerald ones. */
html[data-theme="light"] .jobs-wrapper .lot-grid,
html[data-theme="light"] .jobs-wrapper .fj-ledger {
    scrollbar-color: var(--keyline-strong) transparent;
}
html[data-theme="light"] .jobs-wrapper .lot-grid::-webkit-scrollbar-thumb,
html[data-theme="light"] .jobs-wrapper .fj-ledger::-webkit-scrollbar-thumb {
    background: var(--keyline-strong);
}

/* The week block's hover bloom on the finished-jobs board (the Jobs board's own is handled in
   section 6 above). A booking is a fixed rectangle of time; it must not lift or glow. */
html[data-theme="light"] .jobs-wrapper .week-ev:hover {
    transform: none;
}


/* ── S9. FIGURES ───────────────────────────────────────────────────────────────────────────────
   Money, time and counts go tabular wherever they can be compared down a column — and only there.
   Proportional figures are better in prose, and tnum in a sentence looks like a ransom note.
   `_base.css` covers `.fig`, `.money`, `.amount` and `.num`; these are this cluster's own names. */
html[data-theme="light"] .jobs-wrapper .wc-num,
html[data-theme="light"] .jobs-wrapper .wk-total-num,
html[data-theme="light"] .jobs-wrapper .cal-day-hours,
html[data-theme="light"] .jobs-wrapper .rail-hero-num,
html[data-theme="light"] .jobs-wrapper .rail-stat-num,
html[data-theme="light"] .jobs-wrapper .rail-cap-val,
html[data-theme="light"] .jobs-wrapper .rail-next-time,
html[data-theme="light"] .jobs-wrapper .wev-time,
html[data-theme="light"] .jobs-wrapper .tl-event-time,
html[data-theme="light"] .jobs-wrapper .week-axis-lbl,
html[data-theme="light"] .jobs-wrapper .tl-hour-label,
html[data-theme="light"] .jobs-wrapper .lunch-slot-when {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   S10 — THE COMPLIANCE SWEEP
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   A rule-by-rule pass over the two pages against the signed-off Work Queue, looking only for places
   the rules were never applied. Everything below is a coverage gap, not a new decision: each block
   names the rule it answers, and every class was read out of the page sheet AND confirmed present
   in a template, because a wrong selector in this file is silent — it neither errors nor shows up
   in a build.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── S10.1 THE PAGE TITLE IS LEFT-ALIGNED ──────────────────────────────────────────────────────
   `.jobs-header` is `align-items: center; text-align: center` (FinishedJobsDesktop.css:18) and it is
   the only centred page title in the app — every other board starts its heading at the left edge of
   its own content column. A centred title also puts the h1 on a different vertical from the lane
   bar, the toolbar and the ledger, all three of which are `max-width: var(--fj-col)` centred blocks,
   so the page has two left edges.

   The fix is not `text-align: left` alone: that would drop the title to the WRAPPER's left edge,
   1rem in, while the data column starts ~330px further right on a wide screen. It takes the same
   measure the three blocks below it already use, so the page gains one left edge rather than a
   third one. The header rule from S1 then runs the width of the data, which is what it is for.

   `.jobs-header` is declared by both sheets in the cluster but rendered by FinishedJobsDesktop.vue
   alone — JobsDesktop.vue has no header element at all, so its copy of the rule is dead sheet and
   this cannot reach the Jobs board. The `.compact` variant is the same element and inherits it. */
html[data-theme="light"] .jobs-wrapper .jobs-header {
    align-items: flex-start;
    justify-content: flex-start;
    text-align: left;
    width: 100%;
    max-width: var(--fj-col);
    margin-left: auto;
    margin-right: auto;
    padding-left: 0;
    padding-right: var(--topright-safe);
}


/* ── S10.2 THE LOT GRID STOPS SHOUTING — RULE 4, TWO PRIMARIES ─────────────────────────────────
   The worst instance of the rule in this cluster, and this file introduced it rather than inherited
   it: section 9 above sets `.jobs-wrapper .invoice-btn` to the solid accent at (0,3,0), which beats
   the page sheet's own `.lot-foot .invoice-btn` at (0,2,0) — and that page rule had DELIBERATELY
   made the lot card's copy secondary, for exactly the reason the rule states ("on five cards it read
   as five calls to action rather than a list of cars", FinishedJobsDesktop.css:357).

   So the light theme silently reverted a correct decision, and "Väntar på hämtning" renders as a
   grid of cards each carrying a filled blue button. A repeated per-card action is never the primary:
   there is no terminal action on this pane at all, because the pane is a list.

   Scoped to `.lot-foot` rather than to `.invoice-btn`, because the collected ledger's copy is
   already handled as `.fj-inv.btn` above and the class is genuinely primary elsewhere. */
html[data-theme="light"] .jobs-wrapper .lot-foot .invoice-btn {
    background: var(--surface);
    background-image: none;
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    box-shadow: none;
    font-weight: var(--fw-label);
    border-radius: var(--r-control);
}
html[data-theme="light"] .jobs-wrapper .lot-foot .invoice-btn:hover {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--accent);
    transform: none;
    box-shadow: none;
}

/* The card's foot rule is `rgba(var(--sf-rgb), 0.07)` — the SURFACE channel, which inverts to the
   ink channel in light, so the line above the phone number is a grey smear rather than a hairline.
   The same failure as the ledger's row separator in S4, on the other half of the page. */
html[data-theme="light"] .jobs-wrapper .lot-foot {
    border-top: 1px solid var(--keyline);
}


/* ── S10.3 CAPSULES THAT WERE ONLY RECOLOURED — RULE 2 ─────────────────────────────────────────
   Sections 3 and 7 above corrected the COLOUR of these chips and left the geometry, so the command
   bar and the day rail are still rows of lozenges. `--r-pill` is for true pills; a chip in a dense
   board is a rectangle at the control rung and a badge is a 4px rectangle.

   Every class here was confirmed rendered. The neighbouring `.quote-alert`, `.overbook-alert`,
   `.lunch-status`, `.cell-full-tag`, `.time-pill` and `.add-booking-btn` rules in the page sheet are
   deliberately NOT touched: those names appear in no template under `src/`, so they are dead sheet —
   and sections 3 and 8 above already spend four light overrides on two of them. Reported upward. */
html[data-theme="light"] .jobs-wrapper .cal-chip,
html[data-theme="light"] .jobs-wrapper .lunch-chip {
    border-radius: var(--r-control);
}

html[data-theme="light"] .jobs-wrapper .rail-next-rel,
html[data-theme="light"] .jobs-wrapper .bk-row-source {
    border-radius: 4px;
    font-weight: var(--fw-label);
}

/* The difficulty badge on a booking card, in the month cell and in the overflow list. The tier is
   printed in words beside its own tint and `skillTierClass()` (src/models/skill.ts:28) emits exactly
   t1 · t2 · t3 for SKILL_LEVELS = 3, so only the shape is wrong here — the --diff-* family is
   already a measured, light-capable set and is left alone. */
html[data-theme="light"] .jobs-wrapper .diff-badge {
    border-radius: 4px;
    letter-spacing: var(--eyebrow-track);
    box-shadow: none;
}


/* ── S10.4 THE CAPACITY BAR'S OTHER THREE FILLS — RULES 5 AND 9 ────────────────────────────────
   Section 7 above flattened `.rail-cap-fill` to the accent and stopped there, but the bar has three
   more states and each is its own two-stop gradient off the raw palette: teal, amber→orange, red.
   So the ordinary bar is a flat token and the three readings that actually mean something are
   smears — the inverse of what the rule wants.

   The --meter-* family, never --st-*: a bar owes 3:1 under SC 1.4.11 where the status TEXT tokens
   are built for 4.5:1 and land as bottle green and washed pink when laid down as 5px of solid fill.
   `.lift` is a fact about what the day needs rather than a fault, so it takes the ok fill. */
html[data-theme="light"] .jobs-wrapper .rail-cap-fill.lift {
    background: var(--meter-ok);
}
html[data-theme="light"] .jobs-wrapper .rail-cap-fill.full {
    background: var(--meter-warn);
}
html[data-theme="light"] .jobs-wrapper .rail-cap-fill.over {
    background: var(--meter-bad);
}

/* The figure beside that bar. `.full` is `--c-amber-fde68a`, a pale dark-theme amber that measures
   under 2:1 on white — the word is effectively invisible in light — and its `em` is an amber capsule
   on an amber tint inside an amber hairline. A day at capacity is the shop's GOAL, not a fault, so
   per the verdict rule it goes neutral rather than hunting for an amber that clears contrast: to
   clear 3:1 an amber must be darkened, and a darkened yellow is mustard.

   `.over` is the one that is actually wrong, and it keeps the hue. */
html[data-theme="light"] .jobs-wrapper .rail-cap-val.full {
    color: var(--ink-dim);
}
html[data-theme="light"] .jobs-wrapper .rail-cap-val.over {
    color: var(--st-breach);
}
html[data-theme="light"] .jobs-wrapper .rail-cap-val em {
    border-radius: 4px;
    background: var(--surface-inset);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    letter-spacing: var(--eyebrow-track);
}


/* ── S10.5 THE GLOWS UNDER THE MONTH CELL'S DIFFICULTY DOT — RULE 6 ────────────────────────────
   DELETED 2026-08-11. The three `.cell-diff-dot.t1/.t2/.t3` glow-killers never ran: `cell-diff-dot`
   is dead sheet in `JobsDesktop.css` and `NOT PRESENT IN DOM` on /jobs (probe.mjs, THEME=light).
   The reasoning is kept because it is a general one — a `0 0 8px rgba(<hue>,.7)` glow under a 10px
   disc needs darkness to read as emission; on white it is a smudge nearly as wide as the dot. Apply
   it to the marker that IS rendered if one is ever found. */


/* ── S10.6 FIGURES THE TWO LISTS COMPARE DOWN A COLUMN — RULE 10 ───────────────────────────────
   `.fj-when` is already tabular and right-aligned in the page sheet, which is right. Its second line
   is not (`em`, the slot time), and neither are the bucket counts, the lane counts, or the lot
   card's customer line, which on this lane carries the plate as often as a name. */
html[data-theme="light"] .jobs-wrapper .lot-cust,
html[data-theme="light"] .jobs-wrapper .fj-when em,
html[data-theme="light"] .jobs-wrapper .lane-count,
html[data-theme="light"] .jobs-wrapper .fj-result-inv {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* ── THE CALENDAR SHELL: ONE SURFACE, NOT TWO GROUNDS ─────────────────────────────────────────
   The white-to-grey seam down the middle of /jobs. Three compounding causes, none of them a token:

     .cal-root    background: var(--surface-sunken)      the console's ground
     .cal-sidebar background: rgba(var(--ch-indigo-0e0a1c), 0.4)

   The rail paints a 40% tint of a channel that flips, which in light resolves near-WHITE. The main
   area paints nothing at all and shows the sunken ground underneath. So the two halves of one
   console sit on two different grounds, meeting on a line nobody drew — which reads as a rendering
   seam rather than as a division.

   In dark this was invisible and correct: both values are near-black, the 40% tint IS the rail's
   separation, and there is nothing to notice. It only becomes a fault when the ground inverts.

   The fix is to stop having two grounds. The console is ONE white surface; the rail is divided from
   the calendar by a keyline, which is the same thing every other split in this theme uses.

   Two more off-system values here, both from the glass era and both wrong once it is gone:
   a 24px radius against light's 8px panel rung, and a 50px-blur 50%-opacity drop shadow — a
   marketing lift on a console that fills the viewport. */
html[data-theme="light"] .jobs-wrapper .cal-root {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-2);
}

html[data-theme="light"] .jobs-wrapper .cal-sidebar {
    background: transparent;
    border-right: 1px solid var(--keyline);
}

html[data-theme="light"] .jobs-wrapper .cal-sidebar-header {
    border-bottom: 1px solid var(--keyline);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   P1 — THE MONTH LEDGER, REBUILT ON THE MEASURED SHELL
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Everything before this section corrected COLOUR on this page. The screenshot says colour was never
   the problem: the page is a white card with a 24px radius and a 50px shadow, floating on a ground,
   containing a second bordered card (the sidebar), containing a third set of bordered cards (the
   upcoming list). Four edges arguing about one object, which is the definition of a wireframe.

   The rules below are structural. They supersede any earlier declaration in this file that they
   name — later in the file wins, and that is deliberate, not an oversight to tidy back. */

/* ── 1. THE PAGE STOPS BEING A CARD ────────────────────────────────────────────────────────────
   `.cal-root` is `border-radius: 24px` + `--surface-sunken` + a border + `0 20px 50px` of shadow.
   Every one of those is a dark-mode device: on near-black a 50px shadow is how a surface separates
   from a ground that is almost the same value. On white it is a box drawn around the whole page,
   and `detect-light.mjs` flags it as D7 FLOATING on six routes.

   Radius 24 is also six times the measured ceiling — nothing in the shipped Stripe stylesheet
   exceeds 4px, because softness there comes from the shadow and never from the corner.

   So the frame goes. The page IS the surface; the calendar sits on it; structure is carried by the
   three lines that are actually doing work — the sidebar edge, the header rule, and the grid's own
   partitions. Depth is spent once, and on this page it is spent on nothing at all, because nothing
   here floats. */
html[data-theme="light"] .jobs-wrapper .cal-root {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* 20px, the measured Chrome padding. The page sheet's 1.5rem/1rem made the calendar start late on
   the left and end early at the top, against a rhythm that is supposed to descend from 20. */
html[data-theme="light"] .jobs-wrapper {
    padding: 20px;
}

/* The sidebar is a REGION, not a card: no fill of its own, one rule where it meets the calendar.
   In dark the tint change WAS the boundary — two near-black planes at different alphas. In light two
   flat fills abutting is just an edge nobody drew, so the edge has to be drawn. */
html[data-theme="light"] .jobs-wrapper .cal-sidebar {
    background: none;
    border-right: 1px solid var(--keyline);
    padding: 0 20px 16px 0;
}

html[data-theme="light"] .jobs-wrapper .cal-sidebar-header {
    padding: 0 0 16px;
    border-bottom: 1px solid var(--keyline);
}

/* The command bar earns the header rule — the single hairline that separates chrome from data. */
html[data-theme="light"] .jobs-wrapper .cal-cmd {
    border-bottom: 1px solid var(--keyline);
    padding-bottom: 16px;
    margin-bottom: 16px;
}

/* ── 2. EYEBROWS GO SENTENCE CASE ──────────────────────────────────────────────────────────────
   This reverses the uppercase-tracked block earlier in this file, and the evidence is blunt: the
   entire measured stylesheet contains NINE letter-spacing declarations and not one is on a table
   header or a section label. The reference header is 11px/500 sentence case, and their shell is
   actively shrinking the old 13px uppercase header down to it. The tracked eyebrow is the idiom
   being retired, not the target.

   It matters more in Swedish than in English: VÄNTANDE OFFERT is two words of caps sitting beside a
   figure, on a row whose whole job is to be glanceable. */
html[data-theme="light"] .jobs-wrapper .sidebar-section-title,
html[data-theme="light"] .jobs-wrapper .cal-sidebar-eyebrow,
html[data-theme="light"] .jobs-wrapper .rail-eyebrow,
html[data-theme="light"] .jobs-wrapper .rail-next-eyebrow,
html[data-theme="light"] .jobs-wrapper .bk-zone-head,
html[data-theme="light"] .jobs-wrapper .lunch-card-lbl,
html[data-theme="light"] .jobs-wrapper .lunch-sec-label,
html[data-theme="light"] .jobs-wrapper .modal-field > label,
html[data-theme="light"] .jobs-wrapper .cal-day-lbl {
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

/* The page eyebrow is a LABEL, so it stops wearing a chip. A filled rectangle around a word is the
   costume of a status; this one states which page you are on, which the heading beneath it already
   does. Strip the fill and it becomes what it is — a line of small type. */
html[data-theme="light"] .jobs-wrapper .cal-sidebar-eyebrow {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
}

/* ── 3. THE ALERT ROW — three controls that were dressed as decoration ─────────────────────────
   All three of these are `<button>` (JobsDesktop.vue:91, :94, :98): one routes to the quote queue,
   one jumps to the first overbooked day, one opens the capacity editor. I first read them as a mixed
   row of one control and two read-only facts and styled them accordingly, which stripped the
   affordance off two live buttons. The template settles it — grep, then read the binding.

   They were violet capsules at `border-radius: 50px` with 1.4px of tracking. The capsule is the one
   shape that means "this is a control", so on that count it was right; everything else about it was
   dark's vocabulary. They become rectangles at the 4px ceiling with the measured control edge — a
   1px navy spread shadow rather than a border, plus the pre-declared `0 0 0 0 transparent` layer
   that lets `transition: box-shadow` interpolate instead of hard-cutting when the deep layer arrives
   on hover. Colour does not change and nothing moves. */
html[data-theme="light"] .jobs-wrapper .cal-chip {
    padding: 0 12px;
    min-height: var(--ctl-h);
    border: 0;
    border-radius: var(--r-control);
    background: var(--surface);
    background-image: none;
    color: var(--ink-dim);
    font-size: 0.8125rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    /* Load-bearing. The page sheet uppercases this chip, and that declaration reaches light too —
       dropping it from the rewrite left the strings shouting even after they were fixed at source.
       Dark keeps its uppercase; light reads the string as written. */
    text-transform: none;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

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

/* The figure is the thing being counted, so it takes the ink and the tabular figures; the noun
   beside it stays secondary. One saturated mark per surface, and this surface does not get one —
   the count is emphasis, not status. */
html[data-theme="light"] .jobs-wrapper .cal-chip .chip-n {
    color: var(--ink);
    font-weight: var(--fw-label);
    font-variant-numeric: tabular-nums;
}

/* ── 4. THE GRID IS NOTHING BUT ITS LINES ──────────────────────────────────────────────────────
   A month is not a list, it is a partition, and the line doing the partitioning is not the same line
   that terminates a card. At the card keyline the whole month mushes into one plane and stops
   reading as a grid at all — which is exactly what it did, and worse on a bay tablet under glare
   than on the monitor it was checked on. */
html[data-theme="light"] .jobs-wrapper .cal-month,
html[data-theme="light"] .jobs-wrapper .cal-day-labels,
html[data-theme="light"] .jobs-wrapper .cal-day-lbl,
html[data-theme="light"] .jobs-wrapper .cal-day {
    border-color: var(--keyline-strong);
}

/* Days outside this month recede; days the shop is CLOSED only whisper. Both were painted
   `--surface-sunken` (#e4e6eb), which is the canvas value — heavy enough that the weekend columns
   read as disabled and the last two columns of the month looked switched off. A closed day is still
   a day you can click to book into. */
html[data-theme="light"] .jobs-wrapper .cal-day.is-off {
    background: var(--surface-inset);
}

/* The nil mark. A middot in every empty cell is texture on near-black and debris on white — twenty
   dots scattered across a page whose whole job is to show where the work is NOT. An empty day says
   it is empty by being empty. */
html[data-theme="light"] .jobs-wrapper .cal-day-nil {
    display: none;
}

/* ── 5. THE LOAD METER — the one graphic in a cell, and it carries the day's verdict ───────────
   THE SIGNATURE OF THIS PAGE. A cell holds a date, a count and a bar; the bar is the only thing
   that answers the question a shop owner actually opens this page with, which is "which day can I
   still take work on". So it gets to be the one mark that carries hue, and everything else in the
   cell stays ink.

   A meter owes 3:1 under SC 1.4.11, not the 4.5:1 that text owes, so the fill comes from the
   --meter-* family and NEVER from the --st-* text tokens: laid down as solid fill those overshoot
   into olive-brown and bottle green.

   And the verdict rule decides which states get hue at all. A colour on nearly every row has stopped
   carrying information, so busy and quiet — the ordinary days — go neutral, and the hue is kept for
   full and over, which is the case that is actually worth a second look. */
html[data-theme="light"] .jobs-wrapper .cal-load-track {
    background: var(--surface-inset);
    box-shadow: inset 0 0 0 1px var(--keyline);
    border-radius: var(--r-pill);
}

html[data-theme="light"] .jobs-wrapper .cal-load-ceiling {
    background: var(--keyline-strong);
}

html[data-theme="light"] .jobs-wrapper .cal-load .cal-load-marker {
    background: var(--ink-dim);
}

html[data-theme="light"] .jobs-wrapper .cal-load.is-full .cal-load-marker {
    background: var(--meter-warn);
}

html[data-theme="light"] .jobs-wrapper .cal-load.is-over .cal-load-marker {
    background: var(--meter-bad);
}

/* `Fullt` is the exception, not the ordinary case — one day in this month carries it — so this is
   precisely where hue belongs, and painting it grey (as the first pass did, over-applying the
   verdict rule) threw away the only signal on the cell. It becomes a badge rather than a shouted
   word: fill, tint border, dark ink, sentence case. */
html[data-theme="light"] .jobs-wrapper .cal-full-tag {
    display: inline-flex;
    align-items: center;
    border-radius: 4px;
    padding: 1px 5px;
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--meter-warn);
    background: var(--surface-inset);
    box-shadow: inset 0 0 0 1px var(--keyline-strong);
}

/* The count is a fact about the day, not a status, so it loses the chip it was wearing and sets in
   ink beside the date. */
html[data-theme="light"] .jobs-wrapper .cal-day-count {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    color: var(--ink-dim);
}

/* ── 6. THE UPCOMING LIST IS A LIST ────────────────────────────────────────────────────────────
   Six bordered, rounded boxes stacked with gaps, inside the sidebar, inside the page card. The
   nesting rule is absolute: a container has EITHER a border OR an elevation, never both, and
   whatever is inside it has NEITHER. These are rows. They separate with a hairline and the category
   bar they already carry.

   The bar itself is an inline :style binding driven by --cat-1…--cat-6, so it is data rather than
   theme and cannot be reached from here — the light block re-derives those six hues centrally in
   app.css, which is the correct seam for it. */
html[data-theme="light"] .jobs-wrapper .upcoming-item {
    background: none;
    border: 0;
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    box-shadow: none;
}

html[data-theme="light"] .jobs-wrapper .upcoming-item:hover {
    background: var(--surface-inset);
    border-color: var(--keyline);
}

html[data-theme="light"] .jobs-wrapper .upcoming-list > .upcoming-item:last-child {
    border-bottom: 0;
}


/* ── P1b. WHAT THE FIRST SCREENSHOT SHOWED ─────────────────────────────────────────────────────
   Four faults that only a rendered page could have told me, which is the whole argument for looking
   rather than reasoning about the declaration.

   1. `text-transform: none` did NOT lower-case two of the labels, because those strings are
      CAPITALISED IN THE STRING TABLE — "BOKNINGAR" and "VÄNTANDE OFFERT" arrive already shouting, so
      removing the CSS transform changed nothing at all. The transform has to run the other way:
      lowercase the whole string, then raise the first letter. Single-word and short-phrase labels
      only; this would mangle an acronym, so it is applied by name and never broadly.

      The strings themselves are the real fix and they belong to `ui-copy` and the bilingual table —
      changing them here would move DARK, which is not on offer. This is the theme-local answer.

      Scoped to `.cal-sidebar-eyebrow` alone. It does NOT work on the alert chips, because their
      first character is the count — `::first-letter` lands on the digit and the word behind it stays
      lowercase, turning "VÄNTANDE OFFERT" into "väntande offert". Those two strings are fixed at
      source instead (`quote_alert_one` / `quote_alert_many` in localization.ts), which is dark-safe
      because dark's `.cal-chip` carries `text-transform: uppercase` and re-shouts them unchanged. */
html[data-theme="light"] .jobs-wrapper .cal-sidebar-eyebrow {
    text-transform: lowercase;
}

html[data-theme="light"] .jobs-wrapper .cal-sidebar-eyebrow::first-letter {
    text-transform: uppercase;
}

/* 2. The upcoming list is still a bordered white box — the "a scroll region needs an edge" recipe,
      correctly applied when this page was a card floating on a grey ground, and wrong the moment
      the sidebar became a region with a rule of its own. The list is now the only thing in that
      region; a border around it is a box inside a box, which is the exact fault this pass exists to
      remove. The sidebar's edge bounds it, and the rows' own dividers say where each one ends.

      Superseding the block at the top of this file, deliberately. */
html[data-theme="light"] .jobs-wrapper .upcoming-list {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
}

/* 3. Today's ring in the mini-calendar came through amber, and it was the only warm mark on an
      otherwise navy page — a leftover from dark, where amber on near-black is a legible marker.
      There is no good light amber: to clear 3:1 on a light track it must be darkened, and a darkened
      yellow is mustard. Today is a marker, so it takes ink; selected is a selection, so it keeps the
      accent. Two different jobs, and now two different mechanisms rather than two hues. */
html[data-theme="light"] .jobs-wrapper .mini-day.today {
    background: none;
    border-color: var(--ink);
    box-shadow: inset 0 0 0 1px var(--ink);
    color: var(--ink);
    font-weight: var(--fw-label);
}

/* 4. The meter's own edge was drawn at --keyline, which is 1.18:1 against white — correct for a card
      edge, invisible on a 4px track. The track is an instrument, not a container: it has to show its
      own extent or the marker has nothing to be positioned within. */
html[data-theme="light"] .jobs-wrapper .cal-load-track {
    box-shadow: inset 0 0 0 1px var(--keyline-strong);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   P1c — THE DAY AND THE WEEK
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Both views have the same disease as the month did, in a different costume: FILLS AND FRAMES WHERE
   THERE SHOULD BE LINES. The week is seven filled, rounded, bordered slabs standing side by side —
   seven cards, containing nothing, arguing about a grid. The day rail is five stacked grey boxes,
   and a grey fill on a white surface is the universal signal for read-only, so a rail whose whole
   job is to be edited reads as a rail you cannot touch.

   The unifying correction is the one from the measured spec: a zone is CARVED IN, an object SITS ON,
   and a track is neither — it is a partition, and a partition is a line. */

/* ── 1. THE WEEK IS A GRID, NOT SEVEN CARDS ────────────────────────────────────────────────────
   `.week-track` carries `--wk-radius`, a two-stop violet gradient, and an inset ring. The gradient
   is the tell: a fill that fades exists because a flat fill reads as a dark rectangle on near-black.
   On white a fading fill is a smear, and seven of them is a slat wall.

   Flat surface, one partition line, no radius, no gap — the columns meet, which is what makes the
   hour lines read straight across the week instead of restarting seven times. */
html[data-theme="light"] .jobs-wrapper .week-track {
    background: var(--surface);
    background-image: none;
    border-radius: 0;
    box-shadow: inset -1px 0 0 0 var(--keyline-strong);
}

html[data-theme="light"] .jobs-wrapper .week-body,
html[data-theme="light"] .jobs-wrapper .week-heads {
    gap: 0;
    background: none;
    background-image: none;
}

/* THE STICKY WEEK HEADER WAS A BLUE-GREY SLAB, AND THE RULE ABOVE COULD NOT REACH IT. The fill is
   not on `.week-heads` — it is on `.week-heads::before`, a pseudo-element the rule above never
   names, so `background: none` there cleaned an element that was already clean while the band went
   on painting. Measured in light: `linear-gradient(rgba(225,230,246,.5) 30%, rgba(225,230,246,.26)
   68%, rgba(225,230,246,0))` — `--ch-indigo-080412` is `8,4,18` in dark and `225,230,246` in light,
   so a scrim built to darken near-black inverts into a lavender-grey band laid across white, fading
   out at its lower edge. That fade is exactly the "grey fade at the bottom" fault: on white a scrim
   must fade to WHITE, never to a tinted grey.

   `background-image` has to be named as well as `background` — the slab is a gradient, and a rule
   that only supersedes `background-color` leaves the gradient rendering and looks applied while
   changing nothing. The blur goes with it: a 16px backdrop blur over white content smears type for
   no gain, since an opaque white scrim already hides what scrolls under it. The mask stays, so the
   white still dissolves at its lower edge rather than ending on a hard line, and one hairline draws
   the boundary the fill used to imply. The hairline goes on `.week-heads`, NOT on the pseudo — the
   pseudo's own mask erases anything at its lower edge, which is where a hairline has to be. */
html[data-theme="light"] .jobs-wrapper .week-heads::before {
    background: var(--surface);
    background-image: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

html[data-theme="light"] .jobs-wrapper .week-heads {
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}

html[data-theme="light"] .jobs-wrapper .week-grid {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

/* A day the shop is closed only whispers; the selected day gets tint PLUS a 3px leading bar. Tint
   alone is not enough in light — a light tint reads weaker than dark's UNSELECTED state, which is
   why the selection rule in this app is always two mechanisms rather than one. The bar stacks into
   the same shadow list as the partition, so it costs no layout. */
html[data-theme="light"] .jobs-wrapper .week-track.weekend {
    background: var(--surface-inset);
}

html[data-theme="light"] .jobs-wrapper .week-track.today {
    background: var(--surface-inset);
    box-shadow: inset 3px 0 0 0 var(--accent), inset -1px 0 0 0 var(--keyline-strong);
}

/* The weekday name: 0.22em of tracking plus a matching text-indent to re-centre what the tracking
   pushed off. Both go. Sentence case needs neither. */
html[data-theme="light"] .jobs-wrapper .wc-name {
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-indent: 0;
    text-transform: none;
    color: var(--ink-muted);
}

/* ── 2. THE DAY RAIL STOPS BEING A STACK OF CARDS ──────────────────────────────────────────────
   Five nested boxes: a violet gradient hero at radius 16, then three grey cards at 13 and 14, then
   a filled lunch tile. Every radius above 4 is off the measured ceiling and every fill is a second
   surface inside a region that already has an edge.

   The rail becomes what it is — a column of sections, separated by the same hairline the rest of
   the page uses, with the eyebrow doing the labelling. Nothing in it floats, because nothing in it
   is on top of anything. */
html[data-theme="light"] .jobs-wrapper .rail-hero,
html[data-theme="light"] .jobs-wrapper .rail-stat,
html[data-theme="light"] .jobs-wrapper .rail-cap,
html[data-theme="light"] .jobs-wrapper .rail-lunch {
    background: none;
    background-image: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
}

html[data-theme="light"] .jobs-wrapper .rail-cap,
html[data-theme="light"] .jobs-wrapper .rail-free,
html[data-theme="light"] .jobs-wrapper .rail-lunch {
    border-top: 1px solid var(--keyline);
    padding-top: 16px;
}

html[data-theme="light"] .jobs-wrapper .day-rail {
    border-left: 1px solid var(--keyline);
}

/* The hero figure sets on the page rather than inside a tile. It is the largest thing in the rail
   and it should not have to shout through a fill to say so. */
html[data-theme="light"] .jobs-wrapper .rail-hero-num {
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* `Redigera` and `Lyftar och utrustning` are links, not section headings. They were uppercase,
   tracked and violet — a coloured label that cannot be clicked is the worst of both, and here they
   CAN be clicked, so they take the link treatment instead: sentence case, secondary ink, accent on
   hover only. One saturated mark per surface, and the surface does not spend it at rest. */
html[data-theme="light"] .jobs-wrapper .rail-cap-edit {
    font-size: 0.75rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
    transition: color 0.08s ease-in;
}

html[data-theme="light"] .jobs-wrapper .rail-cap-edit:hover,
html[data-theme="light"] .jobs-wrapper .rail-cap-edit.lift:hover {
    color: var(--accent);
}

html[data-theme="light"] .jobs-wrapper .rail-cap-edit.lift {
    color: var(--ink-muted);
}

/* The capacity bars. Four two-stop gradients — indigo, teal, orange, red — which on near-black read
   as lit bars and on white read as smears with a hue change in the middle of the measurement.

   Flat fills from the --meter-* family, because a bar owes 3:1 under SC 1.4.11 and the --st-* text
   tokens overshoot into olive-brown when laid down as solid fill. And the ordinary case is neutral:
   a shop at 0/5 or 3/5 is simply a shop, so hue is held back for full and over. */
html[data-theme="light"] .jobs-wrapper .rail-cap-bar {
    background: var(--surface-inset);
    box-shadow: inset 0 0 0 1px var(--keyline-strong);
}

html[data-theme="light"] .jobs-wrapper .rail-cap-fill,
html[data-theme="light"] .jobs-wrapper .rail-cap-fill.lift {
    background: var(--ink-dim);
    background-image: none;
}

html[data-theme="light"] .jobs-wrapper .rail-cap-fill.full {
    background: var(--meter-warn);
    background-image: none;
}

html[data-theme="light"] .jobs-wrapper .rail-cap-fill.over {
    background: var(--meter-bad);
    background-image: none;
}

/* ── 3. FREE SLOTS ARE CONTROLS, NOT GOOD NEWS ─────────────────────────────────────────────────
   Emerald fill, emerald border, emerald text, radius 12. Green here is being spent to say "this time
   is free", which is true of most of most days — a colour that appears on nearly every row has
   stopped carrying information. And the thing it is actually for is booking into, so what it needs
   is the affordance of a control, not the reassurance of a status.

   The measured control edge, ink text, 4px. It presses like every other button in the app. */
html[data-theme="light"] .jobs-wrapper .free-slot {
    background: var(--surface);
    background-image: none;
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink-dim);
    font-weight: var(--fw-label);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

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

/* ── 4. LUNCH STOPS SHOUTING ───────────────────────────────────────────────────────────────────
   Five diagonally-hatched blue blocks across the timeline, each labelled in tracked uppercase accent
   — the loudest thing on the page, spent on the least consequential fact on it. Hatching is a
   texture device: it exists to give a block presence on near-black without a fill bright enough to
   compete. On white it is a moiré.

   A flat whisper tone and a hairline. Lunch is a hole in the day; it should look like one. */
html[data-theme="light"] .jobs-wrapper .tl-lunch-block,
html[data-theme="light"] .jobs-wrapper .week-admin,
html[data-theme="light"] .jobs-wrapper .tl-admin-block {
    background: var(--surface-inset);
    background-image: none;
    border: 0;
    border-radius: var(--r-control);
    box-shadow: inset 0 0 0 1px var(--keyline);
}

html[data-theme="light"] .jobs-wrapper .tl-lunch-lbl {
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

html[data-theme="light"] .jobs-wrapper .tl-lunch-who {
    color: var(--ink-dim);
}

/* ── 5. THE EMPTY DAY ──────────────────────────────────────────────────────────────────────────
   "Inget bokat idag." centred in the middle of a nine-hour timeline reads as a page that failed to
   load. An empty state is a fact, stated where the data would have been — which on a timeline is
   the top left, at the first hour, where the first booking of the day would sit. */
html[data-theme="light"] .jobs-wrapper .tl-empty {
    text-align: left;
    padding: 12px 16px;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

/* ── P1d. THE LAST OF THE SHOUTING, AND AN EMPTY STATE THAT STILL CENTRED ──────────────────────
   `.tl-empty` sets `justify-content: center` on a flex container, so `text-align: left` had nothing
   to align — the property was correct and the mechanism was the wrong one. A reminder that on a
   flex parent the text property is inert.

   The remaining three are the same uppercase-tracked label the rest of this pass has been retiring,
   in the three places the earlier sweeps did not reach. */
html[data-theme="light"] .jobs-wrapper .tl-empty {
    justify-content: flex-start;
}

html[data-theme="light"] .jobs-wrapper .wk-total-lbl,
html[data-theme="light"] .jobs-wrapper .rail-lunch-edit,
html[data-theme="light"] .jobs-wrapper .util-axis-lbl {
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

/* The utilisation plot's ceiling is a dashed rule at 34% white — invisible on white, and it is the
   reference line the whole plot is read against. A dashed line is right here (it marks a limit
   rather than a boundary); the value was the dark one. */
html[data-theme="light"] .jobs-wrapper .util-ceiling {
    border-top-color: var(--keyline-strong);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   P1e — THE OVERLAYS
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Structurally invisible to the capture harness — it never opens anything — so every fault here was
   found by driving the page with a click selector and looking.

   NOTHING IN THIS SECTION IS SCOPED TO `.jobs-wrapper`, AND THAT IS THE POINT. The first version of
   this block was, and every rule in it was dead: the booking sheet, the lunch editor and the
   overflow popover are SIBLINGS of the wrapper, not descendants — the wrapper closes above them in
   the template. The page renders, the CSS parses, nothing warns, and the screenshot is the only
   thing that says so. They are scoped to their own root class instead, which is what a dialog
   actually is: its own surface, not part of the page behind it. */

/* ── 1. THE SCRIM — MEASURED, AND ALREADY CORRECT ──────────────────────────────────────────────
   Recorded because I got this wrong from the screenshot and nearly "fixed" a working rule.

   `.mdl-backdrop` genuinely WAS broken and is now fixed in `_base.css` — it was missing from the
   scrim list, so every dialog on the shared modal shell kept `rgba(var(--ch-blue-06080f), 0.66)`, a
   channel that inverts in light and turns a scrim into a pale wash.

   `.modal-backdrop` was NOT broken. It computes to `rgba(20, 25, 40, 0.42)` — the light `--scrim`,
   applied correctly. Over a WHITE page that composites to about rgb(156,158,165), which reads much
   lighter than the same token does over near-black, and I read "correct scrim on a light page" as
   "pale wash". A scrim on a light theme is simply less dramatic than on a dark one.

   Settled with `probe.mjs` (added alongside `screenshot.mjs`) rather than by eye. A screenshot can
   tell you a surface looks wrong; only the computed style tells you whether a rule applied. */

/* ── 1b. THE GREY BAND ALONG THE BOTTOM OF EVERY DIALOG ────────────────────────────────────────
   This is the fault the shop owner actually pointed at, on the booking sheet: the sheet is white,
   and its action bar is a distinctly greyer strip under it.

   `modalShellStyle.ts:179` paints `.mdl-foot` with `var(--well-a14)` — `rgba(0,0,0,0.14)` in dark,
   where it does exactly what its comment says (the floor reads a step below the form). In light
   `--well-a14` is not an alpha at all, it is the flat `#e2e4ea`; probed on the open sheet it
   computes to `rgb(226, 228, 234)`. That is a blue-grey slab welded to the bottom of a white
   dialog, and because the body above it is `rgb(255,255,255)` the join is a hard horizontal edge —
   the "sharp grey contrast on top of white" complaint, in one declaration.

   In light the floor does not need a fill to read as a floor. `_base.css` §2 already gives it a
   `--keyline` top border; a hairline plus the padding the shell already reserves is the whole
   separation, which is how Stripe's own dialogs do it. The dialog then spends its depth once, at
   its outer edge, and carries no second surface inside itself.

   NOT SCOPED, DELIBERATELY, AND NOT LEFT TO `_base.css`. `.mdl-foot` is the shared frame, so the
   booking sheet (this cluster's dialog) cannot be fixed without fixing every sheet built on the
   shell — they all inherit the identical band. This file loads directly after `_base.css` and no
   later theme sheet declares a background on `.mdl-foot`, so this is the last word on it. */
html[data-theme="light"] .mdl-foot {
    background: var(--surface);
    background-image: none;
}

/* ── 1c. THE LEGACY PANEL'S GRADIENT, NAILED DOWN WHERE IT IS DECLARED ─────────────────────────
   `JobsDesktop.css:1619` paints `.modal-panel` — the lunch editor and the overflow popover — with
   `linear-gradient(160deg, var(--c-blue-171a24), var(--c-blue-10131b))`. Both stops invert: in
   light that is `#d2dbed`-family, a blue-grey slab where a dialog should be white.

   It is not rendering today, because `_base.css` §2's `html[data-theme="light"] .modal-panel` wins
   on specificity AND uses the `background` shorthand, which resets `background-image` to none as a
   side effect. Both of those are accidents of how the winning rule happens to be written: raise the
   page-local declaration's specificity by one class, or switch the base rule to `background-color`,
   and the gradient is back with nothing to warn you. This restates the kill on the two panels this
   cluster owns, naming `background-image` explicitly rather than relying on a shorthand's side
   effect. Cheap, and it is the exact trap that has already cost this programme a full pass. */
html[data-theme="light"] .modal-panel.lunch-panel,
html[data-theme="light"] .modal-panel.ovf-panel {
    background-color: var(--surface);
    background-image: none;
}

/* ── 2. THE BUTTON THAT WOULD NOT STAND DOWN, AND WHY ──────────────────────────────────────────
   `Stäng` stayed filled through two attempts because `_base.css`'s primary selector is

       html[data-theme="light"] .btn-astra:not(.btn-astra-neutral):not(.btn-astra-glass):not(.btn-astra-danger)

   and `:not()` CONTRIBUTES the specificity of its argument. That is four classes plus the attribute
   — (0,5,1) — where a comfortable-looking three-class page override scores (0,4,1) and silently
   loses. The rule below carries five classes and the attribute, and sits in a sheet that loads after
   `_base.css`, so it wins on both counts. Worth remembering for every "one primary per pane" pass in
   this theme: the primary button is harder to out-specify than it looks. */
html[data-theme="light"] .modal-panel.lunch-panel .lunch-foot .btn-astra.lunch-btn-big {
    background: var(--surface);
    background-image: none;
    color: var(--ink-dim);
}

html[data-theme="light"] .modal-panel.lunch-panel .lunch-foot .btn-astra.lunch-btn-big:hover {
    background: var(--surface);
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

/* ── 2. A REGRESSION I CAUSED, AND THE RULE THAT CAUSED IT ─────────────────────────────────────
   The shared field rule in `_base.css` sets `padding: 4px 7px 2px` on every text input — measured,
   and correct for a plain field. `.bk-search input` is NOT a plain field: it carries an absolutely
   positioned magnifier at `left: var(--sp-4)`, and the page reserved room for it with a large left
   padding. Overriding padding globally reclaimed that room, so the icon rendered ON TOP of its own
   placeholder: "Sö🔍namn, telefon eller regnr…".

   The lesson generalises past this input: a global control rule cannot know which fields carry an
   adornment. Anywhere an icon sits inside a field, the reserve has to be restated. */
html[data-theme="light"] .bk-search input {
    padding: 0 16px 0 44px;
    min-height: 48px;
    font-size: 0.9375rem;
}

/* ── 3. A DIALOG TITLE IS THE LARGEST THING IN ITS DIALOG ──────────────────────────────────────
   `Ny manuell bokning` was set at the eyebrow size — the same 11px as the field labels beneath it —
   so the sheet opened with nothing to read first. A dialog is a page with one job; its title says
   what the job is. */
html[data-theme="light"] .mdl-sheet .mdl-head .mdl-title,
html[data-theme="light"] .modal-panel .modal-title {
    font-size: 1rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
}

/* ── 4. THE LUNCH EDITOR — two grey cards, three primaries and a pink capsule ──────────────────
   `.lunch-card` is a filled grey box wrapping a time field and a save button. A grey fill on a white
   surface is the universal signal for read-only, so the two blocks a mechanic is meant to EDIT read
   as the two that are locked. It is also a box inside a dialog that is already a surface.

   A field group is a label and some space. The controls inside it have edges because they are
   controls; the group around them does not. */
html[data-theme="light"] .lunch-panel .lunch-card,
html[data-theme="light"] .lunch-panel .lunch-sec {
    background: none;
    background-image: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
}

html[data-theme="light"] .lunch-panel .lunch-sec {
    border-top: 1px solid var(--keyline);
    padding-top: 16px;
    margin-top: 16px;
}

/* The section labels were uppercase, tracked and ACCENT-COLOURED. A coloured heading is a dark
   device — on white it reads as a highlighter mark — and it spends the accent budget on something
   that is not an action and cannot be clicked. */
html[data-theme="light"] .lunch-panel .lunch-card-lbl,
html[data-theme="light"] .lunch-panel .lunch-sec-label {
    font-size: 0.6875rem;
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

/* Five bordered, rounded white boxes inside a white dialog. They are rows in a list: one hairline
   each, and the last drops its rule so the list does not end on a line with nothing beneath it. */
html[data-theme="light"] .lunch-panel .lunch-slot {
    background: none;
    border: 0;
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    box-shadow: none;
}

html[data-theme="light"] .lunch-panel .lunch-slots > .lunch-slot:last-child {
    border-bottom: 0;
}

html[data-theme="light"] .lunch-panel .lunch-slot-head:hover {
    background: var(--surface-inset);
}

/* ── 5. ONE PRIMARY PER PANE ───────────────────────────────────────────────────────────────────
   Three filled blue buttons on one dialog — two `Spara` and a `Stäng` — which is no primary at all.
   The terminal action is saving; `Stäng` is the way out and takes the neutral control treatment. */
html[data-theme="light"] .lunch-panel .lunch-foot .lunch-btn-big {
    background: var(--surface);
    background-image: none;
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
}

html[data-theme="light"] .lunch-panel .lunch-foot .lunch-btn-big:hover {
    background: var(--surface);
    color: var(--ink);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

/* `Ingen lunch denna dag` is a pink capsule with red ink. It is not destructive — it skips one lunch
   on one day, reversible from this same dialog — so it was borrowing the vocabulary of deletion. The
   capsule is wrong on its own terms too: a full round is reserved for the one interactive filter
   shape, and a capsule that is not a filter reads as a button you cannot press. A quiet control, and
   the red kept for something that genuinely cannot be undone. */
html[data-theme="light"] .lunch-panel .btn-astra-danger {
    background: var(--surface);
    background-image: none;
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
}

html[data-theme="light"] .lunch-panel .btn-astra-danger:hover {
    background: var(--surface);
    color: var(--st-breach);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}

/* ── 6. THE OVERFLOW POPOVER ───────────────────────────────────────────────────────────────────
   The "+N more" list. Rows, not cards, on the same reasoning as everything above. */
html[data-theme="light"] .ovf-panel .ovf-item {
    background: none;
    border: 0;
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
    box-shadow: none;
}

html[data-theme="light"] .ovf-panel .ovf-list > .ovf-item:last-child {
    border-bottom: 0;
}

html[data-theme="light"] .ovf-panel .ovf-item:hover {
    background: var(--surface-inset);
}
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   FINISHED JOBS — the completed-work board (/finished-jobs)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   READ THIS FIRST — WHERE THIS BLOCK BELONGS AND WHY IT LOOKS LATE

   This page's root element is `.jobs-wrapper` (FinishedJobsDesktop.vue:2), the SAME wrapper class
   the /jobs board uses, so the cluster that already owns most of it is `theme-light/jobs.css` —
   sections S4, S5, S10.1-S10.6 there were written against this page and they are good work. This
   block is a SECOND pass over what those sections could not reach or got wrong once rendered, and
   it is written for `boards.css` only because boards.css loads AFTER jobs.css: at equal
   specificity, later file wins. If it is moved into jobs.css it must go at the END of that file.

   Every rule below names the defect it answers. Four of them are structural rather than colour —
   a row that collapses into its own divider, an empty state that is a centred hero, a page with two
   left edges in calendar view, and a dialog with no light rule at all — and none of those is
   reachable by tuning a token.

   THE ONE PRIMARY ON THIS BOARD is "Boka om" in the cancelled record. The board itself has no
   terminal action: the per-row and per-card "Fakturera" is a repeated action on a list, and
   jobs.css:1424 and :951 already made both of them secondary, correctly.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── FJ.1 THE ROW COLLAPSE — a light-only rendering fault, not a style ─────────────────────────
   THE DEFECT, and it is the worst thing on the page: on the "Avbokade" lane every row's second line
   is struck through by the divider of the row below it. Sixteen rows of text with a line drawn
   through the customer's name. Dark is clean; this is light-only.

   THE MECHANISM. `.jobs-wrapper` is `height: 100vh; overflow: hidden` (FinishedJobsDesktop.css:8-9)
   and `.fj-ledger` is `flex: 1; min-height: 0; overflow-y: auto` (:378-386) — a fixed-height scroll
   container. `.fj-group` is a column flex container inside it (:426) and the rows are its flex
   items. A flex item's automatic minimum size is `auto`, which is content-based, so in DARK the
   rows physically cannot be squeezed below their content.

   jobs.css:861-865 then gives `.fj-row` an explicit `min-height: var(--row-h-comfy)`. That
   REPLACES the content-based `auto` floor with a hard 48px — and the row's real content is a 22px
   title plus a 19px meta line plus 20px of padding, about 65px. The moment the ledger overflows,
   every row shrinks to exactly 48 and its text spills through the boundary where the next row's
   `border-top` is drawn. A correct-looking density rule turned into a clipping bug because of what
   `min-height` means to a flex item.

   The fix is to say what was always true: rows in a scrolling ledger do not shrink. The scroll
   container is what absorbs the overflow. `flex-shrink: 0` on both the group and the row, so the
   rule holds whichever of the two the pressure lands on.

   MOVED OUT 2026-08-11: the two `flex: 0 0 auto` declarations this block carried now live in
   `src/pages/finished-jobs/FinishedJobsDesktop.css`, unprefixed, for both themes — a length in a
   theme file cannot be agreed on by construction, and "rows in a scroll container do not shrink"
   is true of dark too (dark only escapes today because it declares no min-height). What stays here
   is what is genuinely light's: the min-height floor, whose token exists only in this theme, and
   the flush padding, which belongs to FJ.2's un-carded table below. */
html[data-theme="light"] .jobs-wrapper .fj-group .fj-row {
    /* And the floor stops being a ceiling. 48px was the intent for a one-line row; these rows carry
       two lines, so the height is DERIVED from 12px of padding (the rhythm's fourth step) and the
       content decides the rest. --row-h-comfy stays as the minimum a short row lands on. */
    min-height: var(--row-h-comfy);
    padding: 12px 0;
}


/* ── FJ.2 THE ARCHIVE IS A TABLE, NOT A CARD FULL OF ROWS ─────────────────────────────────────
   jobs.css:850-856 kept `.fj-group` as a surface with a keyline, which was the right call while the
   page still had a grey ground. The ground is white now (_base.css, "THE SHELL, INVERTED"), so a
   white bordered box on a white page contributes exactly one thing: an outline. On the "Hämtade"
   lane it is an outline drawn around a SINGLE row.

   A list that terminates itself with row dividers does not also need a frame — that is the border-
   OR-elevation rule applied to a container that needs neither. Stripe ships fourteen first-party
   screenshots and every table in them sits directly on the page.

   Un-carding costs the scroll region its edge, which _base.css warns about. It is paid back in the
   same breath: the bucket heading and its hairline mark where the list starts, and the last row's
   divider (kept, deliberately — see below) marks where it ends. */
html[data-theme="light"] .jobs-wrapper .fj-ledger .fj-group {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    overflow: visible;
}

/* The divider becomes the house mechanism: an INSET SHADOW, not a border. Three reasons, all of
   them load-bearing here — it occupies no layout box (so it cannot re-open FJ.1 by adding a pixel
   per row), a selection or focus bar stacks into the same declaration without disturbing anything,
   and it is the same declaration the Work Queue rows use, so two boards draw one line.

   The last row KEEPS its rule. With the card gone it is the table's bottom edge; dropping it leaves
   the list trailing off into white. */
html[data-theme="light"] .jobs-wrapper .fj-group .fj-row {
    border-top: 0;
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}

/* Every state re-declares the divider. A hover that only sets a background silently deletes the
   rule under the pointer, which is how a list develops a gap that follows the cursor. */
html[data-theme="light"] .jobs-wrapper .fj-group .fj-row:hover {
    background: var(--surface-inset);
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}

html[data-theme="light"] .jobs-wrapper .fj-group .fj-row:focus-visible {
    background: var(--surface-inset);
    box-shadow: inset 3px 0 0 0 var(--accent), inset 0 -1px 0 0 var(--keyline);
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* ONE LEFT EDGE. With the card gone there is no 1px border and no 16px inset left to justify: the
   row's first glyph now lands on the same vertical as the lane tabs, the filter chips and the
   result count, all of which are flush to the `--fj-col` measure. The hover fill spans the full
   measure, which is what a table row does. The bucket heading joins the same line — it was 14px in,
   which was invisible while the card was there and would read as a 14px stagger now. */
html[data-theme="light"] .jobs-wrapper .fj-bucket {
    padding-left: 0;
    padding-right: 0;
}

html[data-theme="light"] .jobs-wrapper .fj-bucket:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--r-control);
}


/* ── FJ.3 THE BUCKET HEADING GOES SENTENCE CASE ───────────────────────────────────────────────
   "DENNA MÅNAD" is a date bucket, not a shout. jobs.css:926-931 gave it the uppercase tracked
   eyebrow, which was this programme's standard at the time; the measured evidence overturned it —
   the entire 3.6 MB Stripe stylesheet holds NINE letter-spacing declarations and not one is on a
   section label or a table header, and their shell is actively shrinking the old 13px uppercase
   header to 11px/500 sentence case.

   The uppercase here is CSS, not the string: `bucketOf()` returns loc.t('qh_period_month') =
   "Denna månad" (localization.ts:2481) and FinishedJobsDesktop.css:412 uppercases it. So
   `text-transform: none` is enough and the lowercase/::first-letter trick is not needed. Checked
   against all five labels this function can return — today_word, yesterday_word, qh_period_week,
   qh_period_month, qh_group_earlier, qh_group_undated — every one is sentence case in the table.

   Sentence case also stops a Swedish interface shouting: "UTAN DATUM" is two words of caps sitting
   above rows that are already carrying a badge. */
html[data-theme="light"] .jobs-wrapper .fj-bucket .fj-bucket-name {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-muted);
}


/* ── FJ.4 EMPTY STATES: A FACT WHERE THE DATA WOULD HAVE BEEN ─────────────────────────────────
   Search for something this lane does not hold and the answer is a single line of muted type
   floating in the middle of a 1248px-wide white rectangle, 48px below the filters. That is the
   marketing empty state _base.css §4 exists to kill, and _base's `text-align: left` is INERT here
   for the reason the brief names: `.fj-noresult` is `display: flex; align-items: center`
   (FinishedJobsDesktop.css:233), so the cross-axis alignment centres the line whatever the text
   alignment says.

   Left-aligning alone would drop the line to the WRAPPER's edge, 1rem in, while the data column
   starts ~330px further right — the same third-left-edge trap jobs.css:1389 documents for the page
   title. So it takes the measure the rest of the board is composed on, and the line lands exactly
   where the first row would have been.

   REACH, stated because it is not obvious: `.state-msg` under `.jobs-wrapper` also matches the
   /jobs board's loading and empty lines. That is intended — the same treatment is right there —
   and the cap is inert on that page: `--fj-col` is declared on `.jobs-wrapper` by
   FinishedJobsDesktop.css:5 only, and `usePageStyle` scopes each page's sheet to its own page, so
   on /jobs the variable is undefined and `max-width` falls back to `none`. */
html[data-theme="light"] .jobs-wrapper .state-msg,
html[data-theme="light"] .jobs-wrapper .state-msg.fj-noresult {
    width: 100%;
    max-width: var(--fj-col);
    margin: 0 auto;
    padding: var(--sp-4) 0;
    align-items: flex-start;
    text-align: left;
    color: var(--ink-muted);
}

/* The sidebar's own empty line is inside a 260px rail, not on the board measure — it must not
   inherit the 78rem cap or it forces the rail open. `.small` is the variant the rail uses
   (FinishedJobsDesktop.vue:330). */
html[data-theme="light"] .jobs-wrapper .cal-sidebar .state-msg.small {
    max-width: none;
    padding: var(--sp-2) var(--sp-3);
}


/* ── FJ.5 THE WAIT METER STOPS BEING RED ON EVERY CARD — the verdict rule ─────────────────────
   THE VERDICT RULE, applied to the last place on this page that was still spending a hue on the
   ordinary case. jobs.css:1013-1025 correctly neutralised the wait TEXT — muted, then --ink-dim at
   the amber tier, breach only past seven days — and then left the bar underneath it running the
   full --meter-* ladder. So the figure says "ordinary" in ink while the bar under it says
   "something is wrong" in pigment, on the same card, about the same number.

   Rendered, three cars waiting five days each produce three cards each carrying a 70%-full
   #c2410c bar. A colour on every card has stopped carrying information — and it is worse than
   neutral, because --meter-warn (#c2410c) and --meter-bad (#dc2626) are 14 ΔE apart on the red
   axis: at a glance across the shop the "attention" tier and the "wrong" tier are the same bar.
   There is no good light amber to reach for instead; to clear 3:1 on a white track an amber must
   be darkened, and a darkened yellow is mustard — which is exactly how #c2410c got picked.

   So the ordinary tiers draw the bar in the neutral edge colour, and the one card that is actually
   wrong is the only coloured thing on the lane. Nothing is lost: the number is printed in words
   directly above the bar either way, and the bar keeps its full length as the quantitative
   carrier.

   THE FILL IS --ink-muted, NOT --keyline-strong, and the first attempt at this rule got it wrong.
   --keyline-strong (#adbaca) is a LINE colour: laid down as a 3px bar 70% of the way across a
   #f3f6f8 track it stops reading as a meter and starts reading as a section rule that failed to
   reach the end — verified on the capture, and it looked like a rendering fault. --ink-muted
   (#697386) sits at 4.6:1 on the track, well over the 3:1 a bar owes under SC 1.4.11, and reads
   unambiguously as a filled quantity. Neutral is the point; invisible is not. */
html[data-theme="light"] .jobs-wrapper .lot-card.wait-fresh .lot-meter-fill,
html[data-theme="light"] .jobs-wrapper .lot-card.wait-due .lot-meter-fill {
    background: var(--ink-muted);
}

html[data-theme="light"] .jobs-wrapper .lot-card.wait-late .lot-meter-fill {
    background: var(--meter-bad);
}


/* ── FJ.6 THE CALENDAR VIEW HAS TWO LEFT EDGES ────────────────────────────────────────────────
   Switch Kort → Kalender and the page splits down the middle: the title and the lane bar stay
   capped at `--fj-col` (78rem, centred, starting ~328px in) while `.cal-root` runs the full
   monitor from ~44px. Measured at VIEWPORT=wide: the heading's left edge is 328, the calendar
   sidebar's is 44, and the two right edges are 1573 and 1836. One page, two compositions, and the
   whole thing jumps sideways every time the view is switched.

   The measure is right for the CARD views — a ledger row spanning 1900px puts the date a screen
   away from the car it belongs to, which is why FinishedJobsDesktop.css:5 introduced it. It is
   simply wrong for the console, which is a fixed layout that wants the glass.

   The hook already exists and needs no markup change: the template puts `.compact` on both the
   header and the lane bar exactly when the calendar is showing (FinishedJobsDesktop.vue:6 and :32),
   so the cap can be lifted on precisely the state that needs it. Specificity: this is (0,5,1)
   against jobs.css:1397's (0,4,1) — it wins on its own count, not on load order.

   `.jobs-header` and `.lane-bar` are declared in JobsDesktop.css too but rendered only by
   FinishedJobsDesktop.vue (jobs.css:1394 verified this and it still holds — JobsDesktop.vue has no
   header element), so this cannot reach the /jobs board. */
html[data-theme="light"] .jobs-wrapper .jobs-header.compact,
html[data-theme="light"] .jobs-wrapper .lane-bar.compact {
    max-width: none;
    margin-left: 0;
    margin-right: 0;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   FJ.7 — THE CANCELLED RECORD SHEET, which had no light rule at all
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The dialog a row on the "Avbokade" lane opens into (CanceledRecord.vue, teleported to <body> —
   FinishedJobsDesktop.vue:449 — so none of it is reachable from a `.jobs-wrapper` scope).

   IT HAS ZERO LIGHT OVERRIDES TODAY, and the reason is a class name nobody checked against the
   markup: _base.css:548 and :561 name `.cnr-scrim` and `.cnr-panel`. Neither string exists anywhere
   under `src/`. The real classes are `.cnr-backdrop` and `.cnr-sheet` (CanceledRecord.css:14 and
   :28). This is the identical failure the `.mdl-backdrop` comment in _base.css was written to
   record, repeated on the next dialog along — a rule that parses cleanly, applies to nothing, and
   produces no error anywhere. **Those two dead selectors in _base.css should be corrected or
   deleted; this block does not depend on them.**

   What it looks like rendered is precisely the failure _base.css §C0.5 predicts. Measured with
   probe.mjs:

     .cnr-backdrop   rgba(225, 230, 246, 0.72)   a 72% PALE wash + a 6px blur
     .cnr-sheet      rgba(220, 225, 241, 0.96)   near-white, radius 20px,
                                                 0 24px 70px rgba(20,25,40,0.60)
                                                 + inset 0 1px 0 rgba(255,255,255,0.1)

   Both are built on `--ch-indigo-*` channels, which are theme-VARIANT (handoff §9.3), so the scrim
   inverts into milk and the sheet inverts into a second sheet of milk. The page reads straight
   through the backdrop, the dialog has no ground of its own, and every dark device is still bolted
   on: a 20px radius (five times the ceiling), a 70px marketing shadow, and a lit top edge that
   needs darkness under it to mean anything.

   A dialog is the one thing in this theme that genuinely floats, so it is also the one place the
   full elevation is correct — and per the border-OR-elevation rule it takes the elevation and
   drops the border. */
html[data-theme="light"] .cnr-backdrop {
    background: var(--scrim);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

html[data-theme="light"] .cnr-sheet {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-3);
}

/* The close button becomes a control: the edge is lighting, hover goes DOWN the ramp (there is no
   headroom above white — the page sheet's hover resolves `--c-indigo-e9e4ff`, which is a near-white
   ink in light, so the glyph currently disappears at the moment it is pointed at). */
html[data-theme="light"] .cnr-sheet .cnr-close {
    border-radius: var(--r-control);
    color: var(--ink-muted);
}

html[data-theme="light"] .cnr-sheet .cnr-close:hover {
    background: var(--surface-inset);
    color: var(--ink);
}

html[data-theme="light"] .cnr-sheet .cnr-close:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ── FJ.7.1 The head ──
   `.cnr-eyebrow` measures 9.28px, letter-spacing 2px, rgba(92,128,246,0.75) — below the 11px type
   floor (handoff §7.4: arm's length, under glare), tracked twice as hard as anything else in the
   theme, and a pale blue that lands near 2.6:1 on white. `.cnr-title` measures rgb(0,42,182): a
   saturated blue heading, which is the accent spent on something that is not an action and cannot
   be clicked.

   THE STRING TRAP, and this is the one the brief warns about: `fj_cx_eyebrow` is
   'AVBOKAD BOKNING' / 'CANCELED BOOKING' — UPPERCASE IN THE TABLE (localization.ts:2290). So
   `text-transform: none` changes nothing at all. `lowercase` plus a `::first-letter` override is
   the only fix available from CSS, and it needs `display: inline-block` because ::first-letter
   requires a block container. The first character is a letter in both languages, so it works. */
html[data-theme="light"] .cnr-sheet .cnr-eyebrow {
    display: inline-block;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: lowercase;
    color: var(--ink-muted);
}

html[data-theme="light"] .cnr-sheet .cnr-eyebrow::first-letter {
    text-transform: uppercase;
}

html[data-theme="light"] .cnr-sheet .cnr-title {
    color: var(--ink);
}

/* The badge. A capsule on read-only data reads as a button you cannot press — Stripe's own runtime
   error says it: "Chip must have at least one callback… you might want Badge". Rectangle at 4px,
   and the MANDATORY shape-distinct glyph, because this app's status fills measure ΔE 0.027 apart
   under deuteranopia and the icon is the second carrier rather than decoration.

   "Avbokad" is the tab's own name repeated — it is the ordinary case in here, so it goes neutral.
   "Anlände aldrig" is the one that is actually wrong, and it keeps the hue. Same split, same
   tokens and the same two glyphs as the row flag two clicks away (jobs.css:910-923), so the sheet
   and the ledger say one thing one way. */
html[data-theme="light"] .cnr-sheet .cnr-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border-radius: 4px;
    padding: 2px 6px;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
}

html[data-theme="light"] .cnr-sheet .cnr-badge.canceled {
    background: var(--surface-inset);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
}

html[data-theme="light"] .cnr-sheet .cnr-badge.canceled::before {
    content: "\2022";
    line-height: 1;
}

html[data-theme="light"] .cnr-sheet .cnr-badge.no-show {
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
    color: var(--st-breach);
}

html[data-theme="light"] .cnr-sheet .cnr-badge.no-show::before {
    content: "\2715";
    line-height: 1;
}

/* ── FJ.7.2 The notice — the one figure this record is read for ──
   Four tones, and three of them are wrong in light for the same reason. `.ample` is a pale blue,
   `.short` renders as #c68a12 mustard on a 5% wash, `.after` is a pale red. All three come off the
   dark palette's TEXT ramp, and `.short` in particular is the amber the handoff §8 retired on the
   user's explicit call after seeing it rendered.

   The verdict rule decides the split, and the caption is what makes it free: the block already
   spells the tier out in words underneath the figure — "i förväg" / "sent återbud" / "efter tiden"
   (localization.ts:2297-2300) — so colour is the SECOND carrier here, never the only one. Every
   record shows one of these, which means ample and short are the ordinary cases and go neutral,
   separated by a rung of ink so the tier is still legible; `.after` is the one where nobody warned
   anybody, and it is what the hue was being saved for.

   The wash goes with them: a tinted panel inside a dialog is a box inside a box. Whatever is inside
   an elevated container gets NEITHER a border nor an elevation, so this is a carved zone —
   `--surface-inset`, one hairline, radius at the ceiling. */
html[data-theme="light"] .cnr-sheet .cnr-notice,
html[data-theme="light"] .cnr-sheet .cnr-notice.ample,
html[data-theme="light"] .cnr-sheet .cnr-notice.short,
html[data-theme="light"] .cnr-sheet .cnr-notice.unknown {
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
}

html[data-theme="light"] .cnr-sheet .cnr-notice.after {
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
}

/* The rule between the figure and the two timestamps is a gradient that fades to transparent at
   both ends. A rule fades on near-black because a hard line there reads as a scratch; on white a
   fading rule just looks like a rendering artefact. Flat keyline. */
html[data-theme="light"] .cnr-sheet .cnr-notice::before {
    background: var(--keyline);
}

html[data-theme="light"] .cnr-sheet .cnr-notice .cnr-figure-value,
html[data-theme="light"] .cnr-sheet .cnr-notice.ample .cnr-figure-value {
    color: var(--ink-muted);
}

html[data-theme="light"] .cnr-sheet .cnr-notice.short .cnr-figure-value {
    color: var(--ink-dim);
}

html[data-theme="light"] .cnr-sheet .cnr-notice.after .cnr-figure-value {
    color: var(--st-breach);
}

html[data-theme="light"] .cnr-sheet .cnr-notice.unknown .cnr-figure-value {
    color: var(--ink-muted);
}

/* The caption under the figure, and the two timestamp labels beside it. All three were 9.6-9.9px
   with 1.4px of tracking — under the type floor, and tracked micro-caps at that size stop being
   words and become texture. 11px, the label weight, no tracking, sentence case. The strings are
   sentence case in the table, so `text-transform: none` is sufficient here (unlike the eyebrow
   above — the difference is worth noticing rather than assuming). */
html[data-theme="light"] .cnr-sheet .cnr-figure-label,
html[data-theme="light"] .cnr-sheet .cnr-notice.ample .cnr-figure-label,
html[data-theme="light"] .cnr-sheet .cnr-notice.short .cnr-figure-label,
html[data-theme="light"] .cnr-sheet .cnr-notice.after .cnr-figure-label,
html[data-theme="light"] .cnr-sheet .cnr-when-row dt {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

html[data-theme="light"] .cnr-sheet .cnr-when-row dd {
    color: var(--ink-dim);
}

/* ── FJ.7.3 The sections and the ledger rows ──
   Section titles: 9.6px, 2px of tracking, uppercase. Same treatment as everything else — 11px,
   sentence case. "Bokningen" and "Offerten" are sentence case in the table (localization.ts:2291,
   :2292), so the transform alone does it.

   Every hairline in the sheet is `rgba(var(--sf-rgb), 0.045-0.09)` — the surface channel, which
   inverts to the INK channel in light, so the section rules and the row dividers are grey smears
   at three different strengths rather than one line at one weight. A Stripe surface draws one
   colour at one weight, and here that is `--keyline`. */
html[data-theme="light"] .cnr-sheet .cnr-section-title {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

html[data-theme="light"] .cnr-sheet .cnr-section,
html[data-theme="light"] .cnr-sheet .cnr-foot {
    border-top-color: var(--keyline);
}

html[data-theme="light"] .cnr-sheet .cnr-row {
    border-bottom-color: var(--keyline);
}

html[data-theme="light"] .cnr-sheet .cnr-row dt {
    color: var(--ink-muted);
}

/* The values. `--c-indigo-ece8ff` and its neighbours are theme-variant primitives, so a record's
   entire right-hand column — customer, phone, length, price, quote totals — renders in saturated
   blue. That is the accent budget spent on read-only data, on every line of the sheet. Ink. */
html[data-theme="light"] .cnr-sheet .cnr-row dd,
html[data-theme="light"] .cnr-sheet .cnr-line,
html[data-theme="light"] .cnr-sheet .cnr-line-amount,
html[data-theme="light"] .cnr-sheet .cnr-price,
html[data-theme="light"] .cnr-sheet .cnr-total {
    color: var(--ink);
}

html[data-theme="light"] .cnr-sheet .cnr-row-wide dd,
html[data-theme="light"] .cnr-sheet .cnr-bd-row {
    color: var(--ink-dim);
}

html[data-theme="light"] .cnr-sheet .cnr-bd-row.muted,
html[data-theme="light"] .cnr-sheet .cnr-muted,
html[data-theme="light"] .cnr-sheet .cnr-hint,
html[data-theme="light"] .cnr-sheet .cnr-quote-meta {
    color: var(--ink-muted);
}

/* The two links in the sheet ARE actions — the customer id and the phone number open something —
   so this is where the accent legitimately goes, and it is the only place on the sheet other than
   "Boka om" that gets any. */
html[data-theme="light"] .cnr-sheet .cnr-link,
html[data-theme="light"] .cnr-sheet .cnr-quote-toggle {
    color: var(--accent);
}

html[data-theme="light"] .cnr-sheet .cnr-quote-toggle:hover {
    color: var(--accent-hover);
}

html[data-theme="light"] .cnr-sheet .cnr-quote-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: var(--r-control);
}

/* ── FJ.7.4 The quote block ──
   A bordered, rounded, tinted card sitting inside an elevated dialog — a box in a box, at radius
   13. Depth is spent once, at the outermost object that genuinely floats, and on this screen that
   is the sheet. So the quote becomes a carved zone: the inset ground, no frame at all, and the
   quote body keeps only its leading rule because that rule is what says "this is what was sent",
   which is a real distinction and the cheapest possible way to draw it. */
html[data-theme="light"] .cnr-sheet .cnr-quote {
    background: var(--surface-inset);
    border: 0;
    border-radius: var(--r-card);
}

html[data-theme="light"] .cnr-sheet .cnr-quote-text {
    background: transparent;
    border-left: 2px solid var(--keyline-strong);
    border-radius: 0;
    color: var(--ink-dim);
}

html[data-theme="light"] .cnr-sheet .cnr-quote-status {
    border-radius: 4px;
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    font-weight: var(--fw-label);
}

html[data-theme="light"] .cnr-sheet .cnr-breakdown,
html[data-theme="light"] .cnr-sheet .cnr-total {
    border-top-color: var(--keyline);
}

/* "Nothing further to show" is a quiet rail, not a warning — its left rule is a blue channel that
   inverts. And the money note was emerald: what is still owed is a fact, not a success state. */
html[data-theme="light"] .cnr-sheet .cnr-note {
    border-left-color: var(--keyline-strong);
    color: var(--ink-muted);
}

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

/* The one thing that did fail. `--c-red-fca5a5` is the dark palette's pale red and it is unreadable
   on white; an error message is the last string in the app that may be hard to read. */
html[data-theme="light"] .cnr-sheet .cnr-err {
    color: var(--st-breach);
}


/* ── FJ.8 THE FAILED-INVOICE NOTICE ───────────────────────────────────────────────────────────
   `.invoice-error` is `color: var(--c-amber-fde68a)` on a 10% amber wash inside a 28% amber border
   (FinishedJobsDesktop.css:114-124). `--c-amber-fde68a` is the dark theme's pale amber and it
   measures under 2:1 on white — jobs.css:1301 found the same token doing the same thing on the
   capacity figure and called it "effectively invisible in light". This is the message that tells a
   mechanic why the invoice they just pressed did not open, so it is the worst possible string to
   lose.

   An invoice press that failed is not the ordinary case, so unlike everything else in this block it
   KEEPS a hue — it just takes the one that has a readable light value. Radius to the ceiling, and
   the dismiss × becomes ink rather than a third amber.

   UNVERIFIED BY SCREENSHOT: this state needs a live Fortnox compose failure and the harness cannot
   drive one. The rule is written from the declared token values, which are checkable; the geometry
   is not. */
html[data-theme="light"] .jobs-wrapper .invoice-error {
    border-radius: var(--r-control);
    background: var(--st-breach-soft);
    border: 1px solid var(--st-breach-line);
    color: var(--st-breach);
}

html[data-theme="light"] .jobs-wrapper .invoice-error-x {
    color: var(--ink-muted);
}

html[data-theme="light"] .jobs-wrapper .invoice-error-x:hover {
    color: var(--ink);
}


/* ── FJ.9 THE CALENDAR RAIL'S SMALL GEOMETRY ──────────────────────────────────────────────────
   Two leftovers above the 4px ceiling in the month rail. `.mini-day` is a 6px-radius tap target
   thirty-one times over, which at 24px square is a visible squircle grid; the cell count chip is
   already correct at 4px (jobs.css:833) and these are the same family of object.

   `.mini-cal-grid`, `.mini-day` and `.mini-cal-nav` are rendered by BOTH boards
   (JobsDesktop.vue:31, FinishedJobsDesktop.vue:318) and both roots are `.jobs-wrapper`, so this
   moves /jobs' rail too — deliberately: they are one component with two call sites, and a radius
   that differs between them is exactly the drift these files exist to prevent. */
html[data-theme="light"] .jobs-wrapper .mini-day {
    border-radius: var(--r-control);
}

/* ── THE SEARCH BOX WAS TWO FIELDS NESTED ──────────────────────────────────────────────────────
   `.as-field` is a wrapper holding a magnifier and an `<input>`. Both of them ended up dressed as a
   control: the wrapper carries its own fill and border from the component, and the input is matched
   by the shared field rule in `_base.css`, which gives it a white fill and the measured spread edge.
   So the page drew a box, then drew a second box inside it, and on focus the outer one lit a ring
   around an inner rectangle that was already outlined. Two edges, two fills, one control.

   THE RULE, and it is the same one this whole rebuild keeps applying: depth is spent ONCE, at the
   outermost object. Here that is the wrapper — it is what the user perceives as the field, it is
   what holds the icon, and it is what the focus ring belongs on. The input inside it is not a
   surface at all; it is the text.

   So the wrapper takes the control grammar and the input gives up everything: no fill, no edge, no
   shadow, no radius. Its horizontal padding stays, because that is the reserve the absolutely
   positioned magnifier sits in — the same reserve a global padding override has already destroyed
   twice elsewhere in this theme.

   Specificity note: `_base.css` matches the input at (0,2,2) via `html[data-theme="light"]
   input[type="text"]`. Two classes plus the theme attribute is (0,3,1), which wins. */
html[data-theme="light"] .appt-search .as-field {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-control);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.16),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0),
        0 1px 1px rgba(0, 0, 0, 0.08);
    transition: box-shadow 0.08s ease-in;
}

/* The focus ring REPLACES the edge rather than adding to it — the navy hairline goes to alpha 0 as
   the cyan comes up, so a focused field has exactly as much edge as an unfocused one and nothing
   shifts by a pixel. That is why the resting state pre-declares both cyan layers at alpha 0. */
html[data-theme="light"] .appt-search .as-field:focus-within,
html[data-theme="light"] .appt-search .as-field.open {
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0.20),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0.25),
        0 1px 1px rgba(0, 0, 0, 0.08);
}

/* MEASURED: there is no `:hover` on a text input anywhere in the reference. The resting state IS the
   hover state — a field you can type into does not need to announce itself as the pointer crosses. */
html[data-theme="light"] .appt-search .as-field:hover {
    background: var(--surface);
}

/* The input stops being a surface. Only the horizontal padding survives, and it is load-bearing. */
html[data-theme="light"] .appt-search .as-input {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    color: var(--ink);
}

html[data-theme="light"] .appt-search .as-ic {
    color: var(--ink-muted);
}

/* The input's padding goes too. `.as-field` is a FLEX ROW — the magnifier is a `flex-shrink: 0`
   sibling, not an absolutely positioned overlay — so there is no icon reserve to protect here, and
   the component deliberately gave the input none. What it inherited instead was the shared field
   rule's `4px 7px 2px`, which is measured for a standalone input: the asymmetric vertical is an
   optical correction for line-height 1.6, and inside a flex row that centres its children it just
   pushes the text off its own baseline. The wrapper owns the padding now, so the input has none. */
html[data-theme="light"] .appt-search .as-input {
    padding: 0;
}


/* ══════════════════════════════════════════════════════════════════════════════════════════════
   THE COMMAND BAR AT TOUCH WIDTH — a light-only collision, with a light-only cause
   ══════════════════════════════════════════════════════════════════════════════════════════════
   MEASURED, both themes, tablet (1080x810), via `touch-audit.mjs RECT=`:

     light   .cmd-title  right=561   .cmd-period  x=557   ->  4px OVERLAP, the month sits under `‹`
     dark    .cmd-title  right=550   .cmd-period  x=559   ->  9px clear

   Same markup, same grid, same font-size. The difference is divergence #2: Inter Variable ships in
   light only, and it sets "Augusti 2026" 8px wider than the system stack does in dark. So this is
   not a shared defect that light happens to expose — light's own webfont is the whole cause, and
   the fix belongs here rather than in the page sheet.

   IT IS DELIBERATELY NOT FIXED BY MAKING THE TITLE CLIP. `.cmd-title` already carries
   `overflow: hidden` + `text-overflow: ellipsis`, and the reason it does not fire is that `.cmd-id`
   is a `justify-self: start` grid item, so it is sized to fit-content and fit-content floors at the
   nowrap min-content — 153px against a 131.8px track. Forcing it to the track (`width: 100%`) would
   make the ellipsis work, and it would ALSO clip dark, whose title is 145px against a 136.8px
   track. That trades a light-only collision for a both-themes truncation of the page's own title.
   Strictly worse.

   One step down the type scale instead: 1.5rem -> 1.25rem measures ~127px, inside the 131.8px
   track, so the title stays whole, the collision is gone and nothing is truncated in either theme.
   A denser heading on a 1080px touch bar is a defensible reading of the same design rather than a
   patch — it is the one place this bar is genuinely short of room.

   THE REAL FAULT: at 1080 the bar holds roughly 1010px of content in 658px of track, and ALL THREE
   columns overflow in BOTH themes — `.cmd-actions` wants 359px and is given 131.8px.

   RESOLVED STRUCTURALLY INSTEAD, AND THIS FILE THEREFORE ADDS NO RULE.

   The type step above was written, rendered and then withdrawn. Measuring dark to check the fix had
   not cost it anything turned up a far bigger fault on the same bar: `.appt-search` overlapping
   `.cmd-period` by 205px, in DARK, at HEAD, before anything in this pass was touched. One row
   cannot hold this bar at 1080 in either theme, and the 4px light collision was the same
   over-constrained grid showing a second symptom.

   `JobsDesktop.css` now reflows the bar to two rows under `pointer: coarse`, for both themes. That
   gives the title a ~299px track against the 153px it wants, so light clears without a type change
   and dark clears too. Shrinking light's heading on top of that would be an unforced divergence:
   two themes with different heading sizes, to solve a problem neither of them still has.

   Kept as a comment rather than deleted because the measurement is the useful part — if the bar is
   ever returned to one row, this is the collision that returns with it, and the webfont is why
   light hits it first. */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TOUCH PASS — JOBS CLUSTER  (/jobs and /finished-jobs)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Everything in this file is DESTINED FOR public/theme-light/jobs.css, appended at the end of that
   file. Nothing here belongs in phone.css: phone.css §11 (P1–P5) already did the structural pass on
   this cluster's two phone views and it holds up under measurement — see the report.

   WHAT WAS MEASURED FIRST, so the rules below are answers rather than guesses.

   `touch-audit.mjs` at both shells, both routes, floor 44: TARGETS all clear, and no HOVER-ONLY
   reveals anywhere in the cluster. That is a genuine result and it is also the trap. The auditor
   walks `button, a[href], input, [role=...], [tabindex]` — and the calendar's densest control is a
   plain `<div @click>`:

       .mini-day   28x30 on /jobs, 28x28 on /finished-jobs, 42 of them per page

   Forty-two date targets, on the app's default landing route, at two thirds of the floor's area,
   and the audit reports the page clean. Everything below that is not a press state comes from
   measuring the div-clickables by hand (`RECT=` on `.mini-day|.upcoming-item|.cal-cell|.fj-row|…`).

   THE SHELLS ARE NOT SYMMETRIC HERE. Both phone views (JobsPhone `.jp-*`/`.jpa-*`/`.jpb-*`,
   FinishedJobsPhone `.fjp-*`) measure clean at 412px: `.jp-tab` 49x51, `.jpa-card` 364x120,
   `.fjp-lane` 119x60, `.fjp-lot` 364x258, every button >= 44. Neither is a stub — they are real,
   complete phone views — they were simply already done. So this file is almost entirely TABLET
   work: neither page has a tablet view file, so both render their DESKTOP view at 1080x810 with a
   finger, and that is where the mouse-sized geometry surfaces.

   NOT REPEATED HERE: `.mini-cal-nav`, `.cmd-step`, `.cal-cmd`'s two-row reflow and `.jp-nav-btn`
   are already fixed for BOTH themes in the page sheets by the lead. This file assumes them.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── T1. THE MINI-CALENDAR BECOMES A TOUCHABLE MONTH GRID ──────────────────────────────────────
   CONFIRMED — rendered at 1080x810 on /jobs and on /finished-jobs' calendar view, measured after.

   The month picker in the sidebar is 42 `<div class="mini-day">` cells at 28x30 (and 28x28 on
   /finished-jobs, whose own sheet is a near-copy of JobsDesktop.css and does not carry its
   `min-height: 30px`). It is the smallest target in the cluster and it is invisible to the auditor,
   which is why it survived the pass that fixed `.mini-cal-nav` right next to it.

   The arithmetic is the whole design problem. The grid is 7 columns inside a 260px sidebar; with
   the sheet's own 16px side padding it gets 207px, so a column is 29.6px and 7x44 = 308px does not
   fit. Three ways out, and why this one:

     a) widen `.cal-sidebar` to 340px on coarse — gives exactly 44.0px columns, and costs
        `.cal-main` 80px of its 658. The month grid's columns drop 94 -> 82px, which is where
        "18.5/32 tim" and the `Fullt` chip live. Rejected: it buys the picker a floor by breaking
        the view the picker is a shortcut INTO.
     b) leave it and note it. Rejected: 28x30 is 40% of the floor's area on the landing route.
     c) spend the gutter and the gaps, which is what this does.

   The grid runs edge to edge in the sidebar (`.mini-cal` loses its side padding, `.mini-cal-header`
   takes it back so the month name keeps the sidebar's one left edge) and the 2px inter-cell gaps
   go. That is 32 + 12 = 44px recovered, and the cell becomes 34x44: the height floor is met, the
   width is 34.

   34 IS STATED AS A SHORTFALL, NOT CLAIMED AS A PASS. What it is not is a 34px island: with the
   gaps gone the 7x6 grid is contiguous, so there is no dead space between two dates and the error a
   near-miss produces is the neighbouring day rather than nothing — which is the failure mode a
   calendar recovers from in one press. Area goes 840 -> 1496px², an eightfold better proposition
   than the 26px arrow beside it that this programme has already agreed to grow.

   The flush grid does break the sidebar's "one edge, one rhythm" rule (JobsDesktop.css:3364) for
   this one block. Measured on the render, the first column's NUMERAL centres at x=135 against the
   16px gutter's text edge at x=134 — so what the eye reads as the left edge is unchanged, and it is
   the empty cell padding, not the type, that crosses the gutter.

   `display: flex` + `flex-direction: column` rather than `align-items` alone: the "this day has
   bookings" dot is a `::after` with `margin: 2px auto 0`, i.e. it is stacked UNDER the numeral. A
   row-direction centre would have put the dot beside the date. Verified on the render (Aug 4/5). */
@media (pointer: coarse) {
    html[data-theme="light"] .jobs-wrapper .mini-cal {
        padding-left: 0;
        padding-right: 0;
    }
    html[data-theme="light"] .jobs-wrapper .mini-cal-header {
        padding: 0 var(--sp-4);
    }
    html[data-theme="light"] .jobs-wrapper .mini-cal-grid {
        gap: 0;
    }
    html[data-theme="light"] .jobs-wrapper .mini-day {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        min-height: var(--tap-min);
        padding: 0;
        border-radius: var(--r-control);
    }
}


/* ── T2. THE MONTH ARROWS ON /finished-jobs ────────────────────────────────────────────────────
   CONFIRMED — rendered and measured: 24x44 before, 44x44 after.

   `.mini-cal-nav` measures 44x44 on /jobs and 24x44 on /finished-jobs, in the same theme, on the
   same shell, from the same markup. The cause is the cluster's real structure: BOTH pages root on
   `.jobs-wrapper`, but each has its OWN 1600/3400-line page sheet declaring the same unscoped
   global names — `.mini-cal-nav`, `.mini-day`, `.cal-nav-btn`, `.upcoming-item` — and only
   `JobsDesktop.css` received the coarse-pointer fix. `?inline` injection means whichever page is
   mounted supplies the definition, so /finished-jobs never sees it.

   Fixed here as a `min-width` rather than a `width`, which is what makes it inert on /jobs (already
   44) and corrective on /finished-jobs. The height floor is already supplied by _base.css's control
   floor — this is the WIDTH half that the base layer deliberately does not blanket.

   REPORTED SEPARATELY, NOT FIXED HERE: `.cal-nav-btn` (the `‹ Juli 2026 ›` month stepper in
   /finished-jobs' calendar topbar) measures 30x44 and needs the identical rule. It is deliberately
   left alone because that bar is ALREADY overlapping in both themes and widening the stepper makes
   the collision worse by 28px. See the report. */
@media (pointer: coarse) {
    html[data-theme="light"] .jobs-wrapper .mini-cal-nav {
        min-width: var(--tap-min);
    }
}


/* ── T3. THE 36px BUTTONS THE CONTROL FLOOR CANNOT REACH — A SPECIFICITY DEFECT ────────────────
   CONFIRMED as a defect by measurement (36x77 / 36x121 at 1080 with `pointer: coarse` matching and
   `min-height` COMPUTING to 36px); the fix below is CONFIRMED to compute 44px on the same render.

   THIS IS NOT A JOBS BUG. It is trap #6 firing inside the touch layer itself, and it is worth the
   lead's attention before this file is:

       _base.css:845  html[data-theme="light"] .btn-astra-glass, .wq-act,
                      button.pb-chip, .pb-sort-btn, .fc-sort-btn   { min-height: 36px }   (0,2,1)
       _base.css:2253 @media (pointer: coarse) html[data-theme="light"] button { … }      (0,1,1)

   The control layer's 36px scores HIGHER than the coarse floor, so every one of those six selectors
   is pinned at 36px on both touch shells while the audit reports the page clean — the auditor reads
   the rendered box, and `.btn-astra-neutral` simply never grows. The primary escapes only because
   _base.css:2291 copies its exact `:not()` chain and sits later in the file; the SECONDARY buttons
   have no such twin. The real fix is one line in _base.css's control floor (add these class names
   beside `button`), and it fixes `.wq-act`, `.pb-chip`, `.pb-sort-btn` and `.fc-sort-btn` in three
   other clusters at the same time.

   Written here scoped to this cluster's own dialogs so /jobs is correct whether or not that lands,
   and harmless if it does (both resolve to 44). (0,3,1) beats the (0,2,1) it has to override.
   Reached with `@media (pointer: coarse)` and NOT a page-wrapper scope, because `.mdl-sheet`,
   `.lunch-panel` and `.ovf-panel` are teleported out of `.jobs-wrapper` (trap #3). */
@media (pointer: coarse) {
    html[data-theme="light"] .mdl-sheet .btn-astra-neutral,
    html[data-theme="light"] .mdl-sheet .btn-astra-glass,
    html[data-theme="light"] .lunch-panel .btn-astra-neutral,
    html[data-theme="light"] .lunch-panel .btn-astra-glass,
    html[data-theme="light"] .ovf-panel .btn-astra-neutral,
    html[data-theme="light"] .ovf-panel .btn-astra-glass {
        min-height: var(--tap-min);
    }
}


/* ── T4. THE TIME PICKER'S ICON BUTTON ─────────────────────────────────────────────────────────
   CONFIRMED as a defect (44x30, measured in the lunch editor at 1080); the rule is UNVERIFIED on a
   render — the control is inside a modal reached by two presses and the width is arithmetic rather
   than composition, so it was not re-shot.

   `.tpf-ic-btn` (src/components/TimePickerField.vue:140) is the clock button that opens the picker.
   The base control floor gave it its 44px height; its width is 30px. Same square-icon case as the
   mini-calendar arrows, and the same answer.

   Scoped to `.lunch-panel` deliberately: `TimePickerField` is a SHARED component and other clusters
   mount it. A global rule is very likely right — the button is 30px wide everywhere — but it is not
   mine to write blind, and it is called out in the report so one author can take it globally rather
   than six taking it locally. */
@media (pointer: coarse) {
    html[data-theme="light"] .lunch-panel .tpf-ic-btn {
        min-width: var(--tap-min);
    }
}


/* ── T5. SEPARATION FOR THE DESTRUCTIVE CONTROL — DIAGNOSED, MEASURED, AND NOT NEEDED ──────────
   NO RULE. Kept as a note because the wrong version of this was written first.

   The cluster has exactly one control that `--tap-sep` is for: the lunch editor's
   `Ingen lunch denna dag` (`.btn-astra-danger`), which sits directly beside `Spara` and which
   deletes the shop's lunch for the day if mistapped. `margin-left: var(--tap-sep)` was written for
   it, and then the panel was actually opened and measured: `Spara` ends at x=320 and the danger
   button starts at x=337. There is already 17px between them, against a token that asks for a
   MINIMUM of 8. The rule would have been a spacing preference dressed up as an accessibility fix.

   The other candidate — `.btn-astra-danger` is 56px tall (--tap-primary) — is already satisfied by
   _base.css's primary/destructive rule. Nothing to do on either half. */


/* ── T5b. FOUR CLASSES THIS FILE STYLES THAT NO TEMPLATE EMITS ─────────────────────────────────
   NO RULE, and a note for whoever prunes. Press states were drafted for `.cap-pill`,
   `.day-cap-item`, `.quote-alert` and `.add-booking-btn` before checking, and then
   `grep -rl … src/ --include=*.vue` returned nothing for any of the four. They are dead names —
   jobs.css:174-208 and :451 style controls that were removed from the markup. Trap #1 in reverse:
   presence in the stylesheet is not presence in the DOM either. `.search-result` (jobs.css:464) is
   alive, but only in RoutinesDesktop.vue and InspectionBooking.vue — another cluster's control
   being styled from this cluster's sheet. */


/* ── T6. NO HOVER: EVERY PRESS ACKNOWLEDGES ITSELF ────────────────────────────────────────────
   public/theme-light/jobs.css carries 47 selector lines with `:hover` on them and ZERO `:active`
   rules. On the two touch shells that is a whole feedback layer that can never fire, and nothing in
   its place: on a page where pressing a date reloads the entire calendar, a press currently
   produces no acknowledgement at all until the new data lands. The auditor cannot see this — a
   `:hover` that STYLES something is not a `:hover` that REVEALS something, so its reveal check
   (correctly) reports the cluster clean. This is the systematic finding of the pass, and it is
   almost certainly true of the other five cluster sheets too.

   Each rule below is the SAME DECLARATION as that element's existing hover, so this introduces no
   new design decision — it re-points a signed-off state at the input this shell actually has.
   `.week-ev` is the one deliberate omission: its light rule only cancels a transform, so there is
   nothing to mirror, and a week block is drawn at a size where a finger covers the whole thing.

   Under `@media (pointer: coarse)` so the mouse desktop is untouched: `:active` fires on a mouse
   too, and the desktop design was signed off without a press state.

   VERIFICATION. The SELECTORS are confirmed against the live DOM (every element measured by
   `RECT=` in this pass). The PAINT is confirmed by injecting the same declarations with `:active`
   stripped and photographing the result — the values render as the intended darker inset on white.
   The pseudo-class itself was not driven from a synthetic touch event; that is the one gap, and it
   is a gap in the harness rather than in the rule.

   THE DIVIDER TRAP, which cost the hover rules a fix already: `.fj-group .fj-row:hover` re-declares
   `inset 0 -1px 0 0 var(--keyline)` because a state that only sets a background silently deletes
   the separator under the pointer. The `:active` twin has to re-declare it too, or the ledger grows
   a gap that follows the finger. */
@media (pointer: coarse) {

    /* The month picker (T1) — the densest press surface on the page. */
    html[data-theme="light"] .jobs-wrapper .mini-day:not(.empty):active {
        background: var(--surface-hover);
        color: var(--text);
    }

    /* The sidebar's upcoming list, and the month grid's day cell. Both jump the view. */
    html[data-theme="light"] .jobs-wrapper .upcoming-item:active {
        background: var(--surface-inset);
        border-color: var(--keyline);
    }
    html[data-theme="light"] .jobs-wrapper .cal-cell:not(.other-month):active {
        background: var(--surface-inset);
        border-color: var(--keyline-strong);
    }

    /* The command bar's alert chips ("1 Väntande offert", "5 Mek") — both are presses that navigate.
       `.cap-pill`, `.day-cap-item` and `.quote-alert` were drafted here too and cut: see T5b. */
    html[data-theme="light"] .jobs-wrapper .cal-chip:active {
        background: var(--surface-hover);
        border-color: var(--border-strong);
    }

    /* Month/week/day navigation. */
    html[data-theme="light"] .jobs-wrapper .mini-cal-nav:active {
        background: var(--surface-3-hover);
        border-color: var(--border-strong);
    }
    html[data-theme="light"] .jobs-wrapper .cal-nav-btn:active {
        background: var(--surface-3-hover);
        border-color: var(--border-strong);
    }
    html[data-theme="light"] .jobs-wrapper .mode-tab:active {
        color: var(--text);
    }

    /* The day timeline and the week board: a booking block, and the "+N more" collapse. */
    html[data-theme="light"] .jobs-wrapper .tl-event:active {
        box-shadow: var(--elevation-2);
    }
    html[data-theme="light"] .jobs-wrapper .tl-overflow:active,
    html[data-theme="light"] .jobs-wrapper .week-overflow:active {
        background: var(--surface-hover);
        border-color: var(--border-strong);
    }

    /* The day rail: the next-up cards and the capacity segments.
       `.free-slot` is deliberately ABSENT. It was in the first draft, on the strength of a
       `:hover` rule at jobs.css:2083 — and `JobsDesktop.vue:294` renders it as a `<span>` with no
       handler. It is a label of when the bay is free, not a control, and a press state on it would
       have promised an action that does not exist. (Its hover is also a shadow lift onto a white
       fill that is already white, i.e. a mouse affordance with nothing behind it.) */
    html[data-theme="light"] .jobs-wrapper .rail-next:active {
        background: var(--surface-hover);
        border-color: var(--border-default);
    }
    html[data-theme="light"] .jobs-wrapper .rail-cap-seg:active {
        background: var(--surface-hover);
        border-color: var(--border-default);
    }

    /* The booking sheet's customer and vehicle rows. */
    html[data-theme="light"] .mdl-sheet .bk-row:active {
        background: var(--surface-hover);
        border-color: var(--border-default);
    }

    /* The overflow popover's list, and the lunch editor's per-mechanic disclosure. */
    html[data-theme="light"] .ovf-panel .ovf-item:active,
    html[data-theme="light"] .lunch-panel .lunch-slot-head:active {
        background: var(--surface-inset);
    }

    /* /finished-jobs — the archive ledger. The divider is re-declared: see the trap above. */
    html[data-theme="light"] .jobs-wrapper .fj-row:active {
        background: var(--surface-inset);
    }
    html[data-theme="light"] .jobs-wrapper .fj-group .fj-row:active {
        background: var(--surface-inset);
        box-shadow: inset 0 -1px 0 0 var(--keyline);
    }

    /* /finished-jobs — the lot strip: the card, its phone link, and the two invoice actions. */
    html[data-theme="light"] .jobs-wrapper .lot-card:active {
        background: var(--surface-inset);
        border-color: var(--keyline-strong);
    }
    html[data-theme="light"] .jobs-wrapper .lot-call:active {
        color: var(--accent);
    }
    html[data-theme="light"] .jobs-wrapper .fj-inv.btn:active,
    html[data-theme="light"] .jobs-wrapper .lot-foot .invoice-btn:active {
        background: var(--surface-inset);
        border-color: var(--accent);
        color: var(--accent);
    }
    /* The filled accent action keeps its fill and deepens, the same way its hover does. */
    html[data-theme="light"] .jobs-wrapper .invoice-btn:active {
        background: var(--accent-solid-hover);
    }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   NOT FIXED HERE — three things this file deliberately does not contain
   ═══════════════════════════════════════════════════════════════════════════════════════════════

   N1. /finished-jobs' CALENDAR TOPBAR DRAWS THE SEARCH FIELD THROUGH THE Vecka/Månad CONTROL.
       BOTH THEMES. EVERY WIDTH. Not a touch bug, not a light bug, and the worst thing found in
       this cluster. `.cal-topbar` is `minmax(0,1fr) auto minmax(0,1fr)`; `.tb-left` holds the h1
       AND `<AppointmentSearch>` and overflows its track, so it paints over the centred
       `.mode-switch`. Measured:

         tablet  1080  light   .appt-search 588→828   .mode-switch 638→777   control fully buried
         tablet  1080  dark    .appt-search 579→819   .mode-switch 637→777   control fully buried
         desktop 1280  light   .appt-search 514→754   .mode-switch 701→840   53px, "Vecka" buried
         desktop 1280  dark    .appt-search 505→745   .mode-switch 700→840   45px, "Vecka" buried

       Same shape as the `.cal-cmd` collision already fixed this pass, in the sheet nobody looked
       at, and it swallows a control rather than a title. It belongs in
       `src/pages/finished-jobs/FinishedJobsDesktop.css` as a both-themes fix — most likely the same
       answer, wrapping the bar so the search drops to its own row. No light-only rule is written
       for it here, because a light-only patch would leave the two themes visibly different on a bar
       that is broken in both.

   N2. `.cal-nav-btn` IS 30x44 ON /finished-jobs AND IS LEFT AT 30. It is the `‹ Juli 2026 ›` month
       stepper, and it wants the same `min-width: var(--tap-min)` T2 gives `.mini-cal-nav`. It lives
       in `.tb-right`, i.e. in the bar N1 describes: widening the stepper by 28px pushes it 28px
       further left, INTO the collision. Blocked on N1, and it is one line the moment N1 lands.

   N3. `.tl-event` HEIGHT IS DATA. A booking block in the day timeline is sized by its duration
       through an inline `:style` (trap #10), so a 15-minute job is a ~12px target and no stylesheet
       can raise it. The overflow popover (`.tl-overflow` -> `.ovf-panel`) is the existing escape
       hatch and its rows are comfortably above the floor; a short block is reachable, just not
       reliably first time. Genuinely needs a layout decision about minimum block height in the
       page sheet, for both themes. Not attempted. */




/* ══════════════════════════════════════════════════════════════════════════════════════════════
   DESTINATION: public/theme-light/jobs.css       §  THE SEARCH DROPDOWN (the other half of a
                                                     block that was already half-written)
   ══════════════════════════════════════════════════════════════════════════════════════════════
   WHY jobs.css. `AppointmentSearch.vue` has exactly three hosts and all three are jobs-cluster
   pages: `JobsDesktop.vue:78`, `FinishedJobsDesktop.vue:353`, `FinishedJobsPhone.vue:134`. jobs.css
   already owns the component — its §"THE SEARCH BOX WAS TWO FIELDS NESTED" (line ~2999) styles
   `.appt-search .as-field`, `:focus-within`, `:hover`, `.as-input` and `.as-ic` in full.

   IT STOPPED AT THE FIELD. `.as-pop` — the panel that opens when you type, which is the thing the
   user is actually looking at when they say "why is the design blue" — has never had a light rule.
   That is why the field is a clean white Stripe control and the list under it is a blue slab: they
   were written by two different passes and only one of them happened.

   Append directly after that existing block. The `.appt-search` ancestor is kept on every selector
   to match its idiom and to keep the specificity margin over the scoped styles.

   CHECKED ON BOTH HOSTS — /jobs and /finished-jobs. Same component, same computed values.
*/

/* ── S1. The dropdown is a white panel with ONE depth. ─────────────────────────────────────────
   CONFIRMED — probed on /jobs AND /finished-jobs.

     background-image  linear-gradient(160deg, rgb(210,219,237), rgb(214,223,240))  ->  none
     background-color  (transparent, the gradient was the paint)                    ->  rgb(255,255,255)
     border-radius     12px                                                         ->  4px
     box-shadow        rgba(20,25,40,0.55) 0 18px 44px                              ->  --elevation-2

   Four faults in six lines of the component:

     1. A gradient again, on the two `--c-blue-*` primitives. Same disease as the sheet, same cure:
        `background` shorthand so the image is CLEARED, not merely under-painted.
     2. `border: 1px solid rgba(var(--sf-rgb), 0.1)` AND an 18/44 drop shadow — both edges at once.
        A popover genuinely floats, so it keeps the elevation and gives up the border.
        `--elevation-2` in light already carries its own `0 0 0 1px rgb(227,232,238)` ring, so the
        hairline the panel needs is inside the elevation token and setting `border: 0` does not lose
        it. This is why the either/or rule works in light and is not just an aesthetic preference.
     3. `0 18px 44px` at 55 % black is a DARK-theme drop — the amount of shadow you need to separate
        a near-black panel from a near-black page. On white it reads as smoke. `--elevation-2` is the
        measured house value for a floating panel.
     4. 12px radius against a 4px ceiling.

   The floating panel gets the depth; every row inside it gets NONE. That is S2. */
html[data-theme="light"] .appt-search .as-pop {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-2);
}

/* ── S2. The rows are rows, not cards. ─────────────────────────────────────────────────────────
   CONFIRMED — probed. `.as-veh` colour rgb(29, 57, 149) -> rgb(6, 27, 49).

   `.as-veh` is the plate — the primary text of every result — and it renders in #1d3995, a deep
   blue, because `--c-indigo-ede9fe` (a near-white lavender in dark, chosen to be the BRIGHT ink on a
   dark row) flips. It is the clearest single answer to "why is the design blue": the headings are.
   A result title is ordinary text and takes ordinary ink.

   `.as-meta` already resolves to `--ink-muted` (probed rgb(105,115,134)) and needs nothing.

   The 9px row radius goes to 4. A row inside a panel arguably wants no radius at all, but the rows
   carry a hover fill and an un-rounded fill inside a 4px panel clips visibly at the first and last
   row — 4px matches the container and the corners disappear into it. */
html[data-theme="light"] .appt-search .as-veh {
    color: var(--ink);
}

html[data-theme="light"] .appt-search .as-result {
    border-radius: var(--r-card);
}

/* ── S3. Hover goes DARKER, and does not go blue. ──────────────────────────────────────────────
   CONFIRMED — probed with HOVER on `.as-result`: rgba(58, 100, 237, 0.14) -> rgb(228, 230, 235).

   `rgba(var(--ch-indigo-a78bfa), 0.14)` is a lift: on a near-black row a 14 % violet wash reads as
   "lighter, and selected". In light it is a blue wash on white, which is both the tint the user
   objected to and the wrong DIRECTION — in light, states go darker, because there is no headroom
   above white. `--surface-hover` (#e4e6eb) is the token that exists for exactly this and is the most
   common single bug in this codebase.

   Keyboard note: this component has no `:focus-visible` state on `.as-result` in either theme, so
   the same fill is given to focus. A dropdown you can only see with a mouse is not a dropdown, and
   this costs nothing in dark because dark does not match the selector. */
html[data-theme="light"] .appt-search .as-result:hover,
html[data-theme="light"] .appt-search .as-result:focus-visible {
    background: var(--surface-hover);
}

/* ── S4. Selection is a tint PLUS a leading bar. ───────────────────────────────────────────────
   UNVERIFIED — the component has no persistent selected state to render (`pick()` closes the
   dropdown immediately), so this fires only on the keyboard-focused row, which is why it is folded
   onto `:focus-visible` rather than given a `.is-selected` hook that does not exist. Do not invent
   the class; if a future pass adds real keyboard arrow-navigation, this is the rule it inherits.

   The house rule is that a tint alone is not selection — the 3px leading bar is what makes it
   readable to someone who cannot separate the tint from the ground. `inset` box-shadow rather than a
   border so the row does not shift by 3px when it takes focus. */
html[data-theme="light"] .appt-search .as-result:focus-visible {
    box-shadow: inset 3px 0 0 var(--accent-bar);
    outline: none;
}

/* ── S5. The empty state's suggestion chip. ────────────────────────────────────────────────────
   UNVERIFIED — needs a query that matches nothing AND is close enough to a real string for
   `DidYouMean` to offer a correction; I could not reliably force both at once against live data.

   SCOPED TO `.as-empty` ON PURPOSE. `.dym` is a shared component and I do not own it — this rule
   reaches only the instance inside the dropdown I do own. If the lead would rather fix `.dym` once,
   globally, DELETE THIS BLOCK rather than keeping both; two owners for one surface with the later
   file winning silently is the exact drift these files exist to prevent.

   `.dym` is a real ACTION (it rewrites the query), so it is allowed accent — but as a bordered
   control, not as a 14 % blue slab: `rgba(var(--ch-violet-7c3aed), 0.14)` over
   `rgba(var(--ch-indigo-a78bfa), 0.6)` resolves to a blue fill inside a blue edge, which in a panel
   we have just made white is the last blue rectangle left on the surface. Surface + hairline, and
   the accent moves onto the corrected TERM, which is the part the user has to read. */
html[data-theme="light"] .as-empty .dym {
    background: var(--surface);
    border-color: var(--keyline-strong);
}

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

html[data-theme="light"] .as-empty .dym-ic,
html[data-theme="light"] .as-empty .dym-term {
    color: var(--accent-solid);
}


/* ══════════════════════════════════════════════════════════════════════════════════════════════
   WHAT I DID NOT FIX, AND WHY
   ══════════════════════════════════════════════════════════════════════════════════════════════

   1. `.ms-w-flag` — the "delvis" / "kapad" flag on a running window. Probed at background
      rgba(154, 96, 0, 0.14), colour rgb(138, 85, 0): a muddy brown, which is the known "there is no
      good light amber" problem, not a blue one. It is out of both complaints' scope and fixing it
      properly is a status-token decision for whoever owns the light warn ramp, not a paint patch.
      Reporting it rather than guessing at it.

   2. `.as-pop`'s WIDTH (320px) and `.appt-search`'s (240px) are geometry, so under this pass's one
      rule they belong in a shared sheet and in BOTH themes — not here. They measure identically in
      light and dark, so there is no divergence to close; I left them alone.

   3. WRONG DIAGNOSIS, CORRECTED — see §A7. I first recorded HermesChat as unreachable because
      `Features.hermes` is env-gated, and wrote it into this section as a gap. It renders in this
      environment, it is inside `.ms-scrim`, and it was the third blue surface on the panel. Now
      fixed and confirmed. Keeping the correction visible because the reasoning that produced the
      mistake — inferring a feature flag's value instead of probing for the element — is the kind
      that will recur.

   4. `.as-result` has NO `:focus-visible` state in either theme, and `.dym`'s is a raw-channel
      outline. S3/S4 give light a keyboard state; dark still has none. That is a real gap and it is
      not light's to fix — a dropdown you can only operate with a mouse is broken in both themes.

   5. The sheet's `--fin-*` interior — rules, ink ramp, figures, the `.ms-g-bar` track — is already
      correct: money.css §5 repoints `--fin-rule` to `--keyline` and §1 repoints `--fin-well` to
      `--well-a22`, and all of it probes neutral against the new white ground. Nothing to add, and
      re-declaring any of it here would create a second owner.
*/


/* ══════════════════════════════════════════════════════════════════════════════════════════════
   BROKEN IN BOTH THEMES — three findings that are NOT light-theme faults, reported separately
   because they cannot be fixed from `public/theme-light/` and the lead asked for them explicitly.
   ══════════════════════════════════════════════════════════════════════════════════════════════

   B1. THE "MER NEDANFÖR" CUE ONLY APPEARS AFTER YOU HAVE ALREADY SCROLLED.
       `MetricSheet.vue:341-346` computes `hasMore` in `measureScroll()`, which is driven by a
       `ResizeObserver` on `panelEl` plus the panel's own `@scroll`. The ResizeObserver watches the
       ASIDE, whose box is `height: 100%` and therefore never changes size — what changes is its
       CONTENT, as the windows, the chart and the breakdown resolve one async read at a time. So the
       observer fires once, early, while the sheet is still short, `hasMore` latches false, and
       nothing re-measures until the user scrolls.

       I hit this as a measurement problem — the cue was reproducibly absent from the DOM on a sheet
       that visibly overflowed by hundreds of pixels, and only appeared once I scripted a
       `scrollTop = 300`. It is the same in dark; I diffed it. The cue exists to tell you there is
       more below, and it only shows up once you have found out for yourself.

       Fix is one line and it is not mine to write: observe the sheet's CONTENT rather than the
       sheet, or call `measureScroll()` in the `watch`/`then` of the two async loaders.

   B2. `/jobs` HAS NO VISIBLE `<h1>` IN DARK, AND DOES IN LIGHT.
       Found by accident: `probe.mjs` and `screenshot.mjs` both gate on `page.locator('h1').first()
       .waitFor()`, and that wait times out at 30 s on `/jobs` with `THEME=dark` while succeeding in
       light on the same server, same session, same route. `.cal-title` is the h1. Whatever hides or
       suppresses it in dark also means **the standard capture tooling cannot photograph `/jobs` in
       dark at all** — every dark `/jobs` shot in this programme either came from a run that got
       lucky or was never taken. Worth someone's attention before the next dark/light comparison is
       trusted on that page. Not investigated further; it is outside both of my surfaces.

   B3. `AppointmentSearch` ON `/finished-jobs` IS BEHIND THE "Kalender" TAB.
       Not a defect, a note for the next person measuring it: `/finished-jobs` lands on "Kort",
       which has its OWN separate search field, and `AppointmentSearch` only mounts on "Kalender".
       A probe that types into `.as-input` on that route without clicking through first times out
       and looks like the component is missing. Both hosts were verified for this file
       (`CLICK="text=Kalender"` then type).
*/
