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

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

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

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

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

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

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   1. SETTINGS — the private surface scale, corrected at the token
   ══════════════════════════════════════════════════════════════════════════════════════════════
   settingsTokens.ts composes its own accent out of the generated palette (--c-indigo-b9a3ff),
   which inverts to #5d00c3 in light — a saturated violet, and NOT the accent re-derived for this
   theme (#6c3cb8, ~30% less chroma, because a saturated violet on white reads as pigment rather
   than as light). Repointing the two private accent variables at the real tokens fixes every
   consumer at once: the index's leading selection bar, every focus ring, the open chevron, the
   field's focus border, the "add a record" button, the notes' left rules. */
html[data-theme="light"] .set-page {
    --set-accent: var(--accent-solid);
    --set-accent-line: var(--accent-solid);
    /* A tint at light-theme alpha is decoration, not a state. Quiet it to the system's own. */
    --set-accent-wash: var(--accent-tint-strong);
}

/* The sheet is a RAISED card, and it was painting itself DARKER than the page: --set-card is
   `rgba(var(--sf-rgb), 0.028)` and --sf-rgb inverts to the ink colour, so the card measured
   #e9e7ef on a #f0eff5 ground. The alpha is off-ladder and may not be rounded, so light takes a
   real surface + the hairline that §9 requires alongside an elevation. */
html[data-theme="light"] .set-sheet,
html[data-theme="light"] .setp-sheet {
    background: var(--surface-1);
    border-color: var(--border-subtle);
    box-shadow: var(--elevation-1);
}

/* Recesses INSIDE the sheet. --set-well now resolves to --well-a20 (#dbd9e3), which is right for a
   control track but heavy behind prose and behind an opened record's fields; those step one rung
   down from the card instead of four. */
html[data-theme="light"] .set-page .set-open,
html[data-theme="light"] .set-page .set-form,
html[data-theme="light"] .set-page .set-sample {
    background: var(--recess-a05);
}

/* The segmented control. The well is fixed at the token; the SELECTED segment was
   `rgba(var(--sf-rgb), 0.075)` — darker than the well it sits in, i.e. the selection was reading
   as a hole. In light a selected segment is the raised chip: it goes up to white and carries the
   hairline + elevation §9 pairs. The accent underline (::after) is untouched and still marks it. */
html[data-theme="light"] .set-page .set-seg > button.on {
    background: var(--surface-3);
    box-shadow: var(--elevation-1), inset 0 0 0 1px var(--border-subtle);
}
/* The language pair suppresses the underline (it reads as an error squiggle under a flag), so the
   raise and the hairline are the whole mark — they need to be the firmer pair. */
html[data-theme="light"] .set-page .set-seg.lang > button.on,
html[data-theme="light"] .setp-phone .set-seg.lang > button.on {
    background: var(--surface-3);
    box-shadow: var(--elevation-1), inset 0 0 0 1px var(--border-default);
}

/* The switch. The unlit knob was painted with --set-ink-3 — a TEXT token in a fill position — so
   the resting state of every toggle on the page was near-black ink sitting in a pale track. The lit
   track was a 45% violet over the well, which composites to a washed lavender in light; a lit
   control is a filled accent, and the knob on it is the white one. */
html[data-theme="light"] .set-page .set-track > i {
    background: var(--control-knob-off);
}
html[data-theme="light"] .set-page .set-track.on {
    background: var(--accent-solid);
    border-color: var(--accent-solid);
}
html[data-theme="light"] .set-page .set-track.on > i {
    background: var(--control-knob);
}

/* ── Accent discipline ──────────────────────────────────────────────────────────────────────────
   Violet earns the primary action, the focus ring, the selection and the logo. The index's leading
   selection bar (the reference idiom) keeps it; these did not earn it. */

/* "The shop set this rather than inheriting a default" is a fact, not a state, and it was competing
   with the selection bar in the same colour two rows away. */
html[data-theme="light"] .set-page .set-rail-val.set,
html[data-theme="light"] .set-page .setp-val.set {
    color: var(--text-secondary);
}
/* `.on` is the same argument one row down, and it was missed because it is a different word for the
   same non-fact. `--set-pos` prints "På" / "Ansluten" in emerald (rgb(4,120,87)) — MEASURED: three
   of the four toggle rows on the index are on, so green is the ORDINARY case, and it is the only
   hue on the whole page. A colour carried by the majority separates nothing; worse, emerald in this
   app means "logged / succeeded", so a settings switch that is simply in its default position reads
   as an achievement. The word "På" already says it, and "Av" beside it is the contrast.

   It joins `.set` on --text-secondary rather than going all the way to --text. Measured, the rail
   has three ink steps: a plain value at --ink-muted, a shop-set value at --text-secondary, and this
   one. Taking it to --text would have made "På" the DARKEST thing in the rail — trading a colour
   emphasis for a weight emphasis, which is the same mistake in a different channel. `.set` and
   `.on` say the same thing ("the shop decided this"), so they say it the same way. */
html[data-theme="light"] .set-page .set-rail-val.on,
html[data-theme="light"] .set-page .setp-val.on {
    color: var(--text-secondary);
}
/* The page's own eyebrow is decoration. */
html[data-theme="light"] .set-page .set-eyebrow,
html[data-theme="light"] .set-page .setp-eyebrow {
    color: var(--text-muted);
}

/* The one committing action per sheet, and the shared button the nested panels use. Both were a
   pale-violet ink on a 10% wash — legible on near-black, ~2:1 on a white card. A primary is a
   filled accent with --text-on-accent, per §5. */
html[data-theme="light"] .set-page .set-act.key,
html[data-theme="light"] .set-page .btn-astra {
    color: var(--text-on-accent);
    background: var(--accent-solid);
    border-color: var(--accent-solid);
}
html[data-theme="light"] .set-page .set-act.key:hover:not(:disabled),
html[data-theme="light"] .set-page .btn-astra:hover:not(:disabled) {
    color: var(--text-on-accent);
    background: var(--accent-solid-hover);
    border-color: var(--accent-solid-hover);
}
/* You cannot ring accent violet with accent violet (WCAG 2.2 SC 2.4.11) — the inner ring is what
   separates the outline from the fill it lands on. */
html[data-theme="light"] .set-page .set-act.key:focus-visible,
html[data-theme="light"] .set-page .btn-astra:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
    box-shadow: inset 0 0 0 2px var(--focus-ring-inner);
}

/* The search field's focus border composed its own violet from the palette. */
html[data-theme="light"] .set-search:focus-within,
html[data-theme="light"] .setp-search:focus-within {
    border-color: var(--focus-ring);
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   2. /fel — the fault screen, same well defect
   ══════════════════════════════════════════════════════════════════════════════════════════════
   errorPageStyle painted `rgba(var(--ref-shadow-base), α)` as a SURFACE at .28/.4/.3. The channel
   is 0,0,0 in dark (near-invisible on near-black, which is what it was tuned against) and the
   violet-black 23,20,40 in light, so all three were mid-grey slabs. .28 is now --well-a28 in the
   sheet; light steps the resting state one rung up so the hover still has somewhere to go. */
html[data-theme="light"] .fel-contact {
    background: var(--well-a20);
}
html[data-theme="light"] .fel-contact:hover,
html[data-theme="light"] .fel-contact:focus-visible {
    background: var(--well-a28);
    border-color: var(--border-strong);
}
/* Technical details: a paper recess holding monospace text, not a control. */
html[data-theme="light"] .fel-tech-grid {
    background: var(--recess-a07);
}

/* The reference block and its copy button composed their own violet from the palette. The reference
   is DATA, not an action, so its container goes neutral; the copy button is a secondary and takes
   the §5 secondary shape rather than a violet outline. */
html[data-theme="light"] .fel-ref {
    background: var(--recess-a05);
    border-color: var(--border-default);
}
html[data-theme="light"] .fel-ref-copy {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .fel-ref-copy:hover {
    background: var(--surface-3-hover);
}
/* A mailto/tel link is an action, so it keeps the accent — but the accent this theme actually has.
   --astra-accent-soft inverts to #7c3aed, the value the light theme was re-derived away from. */
html[data-theme="light"] .fel-contact-value {
    color: var(--accent-solid);
}

/* The shared error panel MOVED to _base.css. It was never a settings surface — `.errpanel` is the
   one component that can appear on ~20 routes (it is `<ErrorPanel>`, rendered by every board and
   detail page) — and living in a page cluster's sheet is why it only ever got its fill corrected
   and never the radius, the double edge or the measure. The full treatment is in _base.css beside
   the other shared components. */

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   3. /brief — accent discipline, and the card that painted itself darker than the page
   ══════════════════════════════════════════════════════════════════════════════════════════════
   The three MorningBrief views share class names, so one block covers desktop, tablet and phone.
   The unlit toggle knob is fixed in the sheets themselves (--ink-dim and --control-knob-off are the
   same value in dark), which is why it does not appear here. */

/* Decorative violet containers. A page eyebrow and a demo notice are not the primary action, the
   focus ring, the selection or the logo, and at light-theme alphas a violet wash over a violet-white
   ground is the lavender field this programme exists to remove. */
html[data-theme="light"] .brief-eyebrow {
    color: var(--text-secondary);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .brief-demo {
    color: var(--text-secondary);
    background: var(--recess-a05);
    border-color: var(--border-default);
}
/* The day's paragraph earns its rule by being the page's primary content; it does not need to say
   so in the accent as well. */
html[data-theme="light"] .brief-body {
    border-left-color: var(--border-strong);
}
/* The row's "go there" pill is a secondary action. §5's secondary is not violet. */
html[data-theme="light"] .brief-row-go {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}
html[data-theme="light"] .brief-row-go:hover {
    color: var(--text-display);
    background: var(--surface-3-hover);
}
html[data-theme="light"] .brief-line:hover {
    background: var(--surface-hover);
    border-color: var(--border-default);
}
/* The one accent word in the masthead keeps the accent — it just takes the one this theme derived
   rather than the pre-derivation #7c3aed the palette inverts to. */
html[data-theme="light"] .brief-title > span {
    color: var(--accent-solid);
}
html[data-theme="light"] .brief-line:hover .brief-line-go {
    color: var(--accent-solid);
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   4. /message-drafts — the card was a hole, and the page carried five violets
   ══════════════════════════════════════════════════════════════════════════════════════════════
   .md-card / .mdp-card paint `rgba(var(--sf-rgb), 0.032)`; that channel inverts to the ink colour,
   so a RAISED card composited DARKER than the page it sits on. The alpha is off-ladder and may not
   be rounded to a rung, so light takes a real surface plus the hairline §9 pairs with an elevation. */
html[data-theme="light"] .md-card,
html[data-theme="light"] .mdp-card {
    background: var(--surface-1);
    border-color: var(--border-subtle);
    box-shadow: var(--elevation-1);
}
html[data-theme="light"] .md-card:hover,
html[data-theme="light"] .mdp-card:hover {
    border-color: var(--border-default);
}
/* The batch instruction reads as an instruction over the list, which is a surface job, not a hue
   job — in light the violet wash read as a second, louder card. */
html[data-theme="light"] .md-batch,
html[data-theme="light"] .mdp-batch {
    background: var(--recess-a05);
    border-color: var(--border-default);
}
html[data-theme="light"] .md-batch-text,
html[data-theme="light"] .mdp-batch-text {
    color: var(--text-secondary);
}
html[data-theme="light"] .md-eyebrow,
html[data-theme="light"] .mdp-eyebrow {
    color: var(--text-secondary);
    background: var(--surface-3);
    border-color: var(--border-default);
}
/* The speech rule beside the message is typography, not state. */
html[data-theme="light"] .md-text,
html[data-theme="light"] .mdp-text {
    border-left-color: var(--border-strong);
}
html[data-theme="light"] .md-text:hover,
html[data-theme="light"] .mdp-text:hover {
    background: var(--surface-hover);
}
html[data-theme="light"] .md-text:focus,
html[data-theme="light"] .mdp-text:focus {
    background: var(--surface-hover);
    border-left-color: var(--accent-solid);
}
/* Links and the title's accent word: the accent is legitimate here, the palette's inverted #7c3aed
   is not, and rgba(--ch-indigo-c4b5fd, 0.75) was a pale violet on a white card. */
html[data-theme="light"] .md-title > span,
html[data-theme="light"] .mdp-title > span,
html[data-theme="light"] .md-jump,
html[data-theme="light"] .mdp-jump,
html[data-theme="light"] .md-back,
html[data-theme="light"] .mdp-back {
    color: var(--accent-solid);
}
html[data-theme="light"] .md-jump:hover,
html[data-theme="light"] .mdp-jump:hover,
html[data-theme="light"] .md-back:hover,
html[data-theme="light"] .mdp-back:hover {
    color: var(--accent-solid-hover);
}
/* The page composed its own focus ring at 75% alpha, so it could not be deduped into --focus-ring
   in the sheet without moving dark. */
html[data-theme="light"] .md-wrap :focus-visible,
html[data-theme="light"] .mdp-wrap :focus-visible {
    outline-color: var(--focus-ring);
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   5. /phone-bookings — selection, and the accent word
   ══════════════════════════════════════════════════════════════════════════════════════════════
   A selected filter chip was a 28% violet wash carrying #4c1d95 text: violet on violet, and the
   selection was the same weight as the eight unselected chips beside it.

   ⚠ SUPERSEDED 2026-08-12 — the diagnosis stood, the remedy did not. This block was recipe D of the
   four selected-chip idioms catalogued at `_base.css` § THE SELECTED CHIP; it is the only one that
   is a FILL, and it directly contradicts `_base`'s stated rule. Because this file is the LAST of
   the eight theme sheets in `index.html`, it won `.pt-chip.on`, `.pb-sort-btn.on` and
   `.pt-sort-btn.on` outright — while `_base`'s `button.pb-chip.on` (one extra element) kept
   `.pb-chip.on` unfilled. That is the split boards.css § T2 measured and reported: one control,
   two idioms, decided by which shell you were holding.

   The correct answer to "the selection is the same weight as its neighbours" is the house idiom,
   which was written for exactly this: --surface-selected wash PLUS the 3px --accent-bar leading
   edge. The BAR is the unambiguous, glare- and CVD-surviving part; the fill was solving the wash's
   weakness by replacing the whole control, which is how a filter chip ended up wearing the primary
   action's costume beside a real primary. Selection is never a fill. */
html[data-theme="light"] .pb-chip.on,
html[data-theme="light"] .pt-chip.on,
html[data-theme="light"] .pb-sort-btn.on,
html[data-theme="light"] .pt-sort-btn.on {
    color: var(--text);
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}
/* Was `inset 0 0 0 2px var(--focus-ring-inner)`, because ringing an accent FILL with the accent
   fails SC 2.4.11 and the ring had to move inside. With the fill gone the outer ring is legal again
   — and the inset form had become actively wrong, because a single `box-shadow` replaces rather
   than adds: focusing a selected chip erased its 3px leading bar, i.e. tabbing to it took its
   selection off screen. The bar is restated so the state survives the focus. */
html[data-theme="light"] .pb-chip.on:focus-visible,
html[data-theme="light"] .pt-chip.on:focus-visible,
html[data-theme="light"] .pb-sort-btn.on:focus-visible,
html[data-theme="light"] .pt-sort-btn.on:focus-visible {
    box-shadow:
        inset 3px 0 0 var(--accent-bar),
        0 0 0 2px var(--focus-ring);
}
/* `.pb-more` does not exist — the desktop element is `.pb-hl` (PhoneBookingsDesktop.vue:15), so this
   rule silently did nothing and the /phone-bookings h1 kept a clipped two-stop violet gradient.

   A rename alone would ALSO have looked correct and changed nothing: PhoneBookingsDesktop.css:40
   sets `-webkit-text-fill-color: transparent`, which beats `color`. The fill has to be reset too —
   the same pattern jobs.css already uses on `.rail-hero-num`. `.pbp-hl` (phone) is flat `color` and
   needs only the colour, but it rides along so the pair stays together. */
html[data-theme="light"] .pb-hl,
html[data-theme="light"] .pbp-hl {
    background: none;
    color: var(--accent-solid);
    -webkit-text-fill-color: var(--accent-solid);
}
/* The back button on tablet is a navigation exit, not the page's primary action. */
html[data-theme="light"] .pt-back {
    color: var(--text);
    background: var(--surface-3);
    border-color: var(--border-default);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   6. STRUCTURE — settings is an index plus one sheet
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Everything above this line is colour. Everything below is GEOMETRY: type scale, row height,
   divider, radius, badge shape. Light is a different design on the same DOM, and on this page the
   difference is that a settings row is a LIST ROW — divider-separated, one height, label left,
   control right — rather than a card in a stack of cards.

   Dark cannot match any of it: every selector carries html[data-theme="light"].


   ── 6.1 The --set-* ladder, folded onto the main ramp ─────────────────────────────────────────
   settingsTokens.ts declares six private type steps, a three-rung rule ladder, a four-step ink ramp,
   a status triad and a radius pair (`src/lib/settingsTokens.ts:36-87`). In dark that is a deliberate
   second system — the concierge card. In light it is a second system FIGHTING the first, in the same
   way --fin-* was: --set-body 14px against --t-body 13px, --set-small 12.8px against --t-sm 13px,
   --set-hint 12.4px against --t-xs 12px, --set-label 10.56px against --t-micro 11px. Four near-misses
   of the same step is what makes a page read as texture rather than as hierarchy.

   --set-label is the one that is not merely a near-miss: 0.66rem is 10.56px, under the type
   standard's 11px floor for a tracked uppercase micro-label, and it carries every field caption and
   every group heading on the page.

   Folding at the TOKEN is the whole leverage: twenty declarations re-scale every consumer of the
   page's own design system, and dark keeps its ladder byte for byte because this block cannot match
   there.

       --set-display  clamp(24-31.2px)  →  --t-h1      20px
       --set-title    18.9px            →  --t-h2      14px
       --set-body     14px              →  --t-body    13px
       --set-small    12.8px            →  --t-sm      13px
       --set-hint     12.4px            →  --t-xs      12px
       --set-label    10.56px           →  --t-micro   11px   (raised — it was under the floor)

   NOT FOLDED, deliberately: --set-ctl-h (34px). `SettingsPhone.css:237` sets `.setp-phone {
   --set-ctl-h: 44px }` — (0,1,0) — and this block is (0,2,1) on the SAME element, because the phone
   view is `class="set-page setp-phone"`. Folding it to --ctl-h would win that cascade and shrink
   every touch target on the phone from 44px to 32px. Reported rather than worked around. */
html[data-theme="light"] .set-page {
    --set-ink: var(--ink);
    --set-ink-2: var(--ink-dim);
    --set-ink-3: var(--ink-muted);
    /* Four steps to three. Spec §3: light has no sub-AA decorative rung, and --ink-faint aliases
       --ink-muted for exactly this reason. --set-ink-4 carries hints, placeholders and every field
       caption on the page — it is not decoration. */
    --set-ink-4: var(--ink-muted);

    /* A hairline that DIVIDES is --keyline; one that BOUNDS (a control's border, the masthead rule,
       a sheet-head underline) is --keyline-strong. The old ladder had -mid doing both. */
    --set-rule: var(--keyline);
    --set-rule-mid: var(--keyline-strong);
    --set-rule-strong: var(--keyline-strong);

    --set-pos: var(--st-logged);
    --set-warn: var(--st-expected);
    --set-neg: var(--st-breach);

    --set-card: var(--surface);
    --set-card-edge: var(--keyline);
    --set-well: var(--surface-inset);
    --set-hover: var(--surface-inset);

    --set-display: var(--t-h1);
    --set-title: var(--t-h2);
    --set-body: var(--t-body);
    --set-small: var(--t-sm);
    --set-hint: var(--t-xs);
    --set-label: var(--t-micro);

    /* 0.14em is a dark-theme device: tracking buys separation on a dark ground where the counters
       close up. On white the same tracking reads as a stretched word. */
    --set-track: var(--eyebrow-track);

    --set-r: var(--r-card);
    --set-r-sm: var(--r-control);
    --set-row-h: var(--row-h-comfy);
}


/* ── 6.2 The masthead ─────────────────────────────────────────────────────────────────────────
   A page title in light is --t-h1 at BODY weight — spec §8 puts page titles on --fw-body
   deliberately: the title is the largest thing on screen and does not also need to be the heaviest.
   The eyebrow above it drops from 0.22em to the system track.

   `.set-head h1` and `.setp-head h1` are separate selectors in separate files; both are stated. */
html[data-theme="light"] .set-page .set-head h1,
html[data-theme="light"] .set-page .setp-head h1 {
    font-size: var(--t-h1);
    font-weight: var(--fw-body);
    letter-spacing: -0.01em;
}

html[data-theme="light"] .set-page .set-eyebrow,
html[data-theme="light"] .set-page .setp-eyebrow {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
}

/* The search box is the one control in the masthead, and it was a 999px capsule on a tinted fill —
   the same "read-only" signal _base.css §12 removes from every input, applied to the control this
   page is most often driven from. A field is white, with an edge and a floor. */
html[data-theme="light"] .set-page .set-search,
html[data-theme="light"] .set-page .setp-search {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-control);
    box-shadow: var(--field-floor);
}


/* ── 6.3 The index is a list ──────────────────────────────────────────────────────────────────
   Eleven entries, each carrying its current value: that is a ledger, and it should be ruled like
   one. On the desktop rail the entries were separated by a 1px flex gap and nothing else, so the
   values down the right edge had no line to sit on.

   The selected entry keeps its leading accent bar — that is the page's reference idiom and it is
   the one thing on the rail that must survive a glance. The group headings become eyebrows. */
/* --row-h-comfy, not --row-h: below 1000px this rail IS the page (the drill-down), and
   `SettingsDesktop.css:269` opens the entries up for touch there. A 40px rung would win inside that
   media query on specificity and take the target back down. */
html[data-theme="light"] .set-page .set-rail-item {
    min-height: var(--row-h-comfy);
    padding: calc((var(--row-h-comfy) - 20px) / 2) var(--sp-2); /* derived — not a scale step */
    border-bottom: 1px solid var(--keyline);
    border-radius: 0;
}

html[data-theme="light"] .set-page .set-rail-item.on {
    border-radius: 0;
}

html[data-theme="light"] .set-page .set-rail-group,
html[data-theme="light"] .set-page .setp-rail-group,
html[data-theme="light"] .set-page .set-sub-head {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    color: var(--ink-muted);
}


/* ── 6.4 The sheet, and the rows inside it ────────────────────────────────────────────────────
   The sheet keeps its surface and its elevation (§1 above): it is the only card on screen now, so it
   means something again. What it loses is the 14px radius — the widest in the app, and two steps
   above --r-panel.

   A setting is a row: --row-h-comfy tall (it can carry one hint line under the name), vertical
   padding DERIVED from that height, horizontal padding constant, one hairline between. The 0.875rem
   the sheet authored happens to equal the derived value exactly, which is the argument for authoring
   the height rather than the padding: the number was right and nothing guaranteed it.

   The name goes to --ink-dim. One --fw-strong per container is the rule, and in a settings row the
   container's one strong thing is the VALUE in the control on the right, not the caption on the
   left. */
html[data-theme="light"] .set-page .set-sheet,
html[data-theme="light"] .set-page .setp-sheet {
    border-radius: var(--r-panel);
}

html[data-theme="light"] .set-page .set-sheet-head h2,
html[data-theme="light"] .set-page .setp-sheet-head h2 {
    font-size: var(--t-h2);
    font-weight: var(--fw-strong);
    letter-spacing: 0;
}

html[data-theme="light"] .set-page .set-row {
    min-height: var(--row-h-comfy);
    padding: calc((var(--row-h-comfy) - 20px) / 2) 0;       /* derived — do not round to a scale step */
}

html[data-theme="light"] .set-page .set-key > b {
    color: var(--ink-dim);
}

/* The record lists — mechanics, capacity exceptions, admin periods — are the same object one level
   in, so they take the same rung. The phone floors .set-line at 48px already; this agrees with it
   rather than fighting it. */
html[data-theme="light"] .set-page .set-line,
html[data-theme="light"] .set-page .set-line-static {
    min-height: var(--row-h-comfy);
    padding: calc((var(--row-h-comfy) - 22px) / 2) var(--sp-2);  /* derived — not a scale step */
    border-radius: 0;
}


/* ── 6.5 Every field caption is an eyebrow ────────────────────────────────────────────────────
   Four spellings of the same object, each with its own hard-coded 0.07em: the label beside an open
   record's field, the unit inside a number control, the caption on one half of a paired control, and
   the sub-group heading (§6.3 above). --fw-650 goes to the label rung — 600 is reserved for ONE
   element per container, and a form of six 600-weight captions spends that budget on the captions
   instead of on the data they describe. */
html[data-theme="light"] .set-page .set-open-field > label,
html[data-theme="light"] .set-page .set-num > span,
html[data-theme="light"] .set-page .set-inline > span {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    color: var(--ink-muted);
}


/* ── 6.6 Chips take the badge grammar ─────────────────────────────────────────────────────────
   `.set-chip` is the page's status object: ON for a connected/enabled section, WARN for one wanting
   attention, and a plain chip for a stated fact. It was a 999px capsule told apart by hue alone —
   the failure _base.css §1 exists to stop, and this page can show ON and WARN in the same list.

   Rectangle, soft fill, tint border, dark ink, and the mandatory shape-distinct ::before. `.set` is
   NOT given a glyph: "the shop set this rather than inheriting a default" is a fact, not a status,
   and §"Accent discipline" above has already taken its colour away. */
html[data-theme="light"] .set-page .set-chip {
    border-radius: 4px;
    padding: 2px 6px;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    line-height: 1.4;
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    color: var(--ink-dim);
}

html[data-theme="light"] .set-page .set-chip.on {
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .set-page .set-chip.on::before {
    content: "✓";
    font-size: 1em;
    line-height: 1;
}

html[data-theme="light"] .set-page .set-chip.warn {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}
html[data-theme="light"] .set-page .set-chip.warn::before {
    content: "!";
    font-size: 1em;
    line-height: 1;
}

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

/* The segmented control is a control, so it rounds like one — and the track loses its border.
   §8.7 below raises the SELECTED segment on a 1px spread plus an ambient drop, i.e. an elevation.
   settingsTokens.ts:186 gives the track that contains it `border: 1px solid var(--set-rule)`. A
   container with a border whose contents carry an elevation is exactly what _base.css's "DEPTH, NOT
   OUTLINES" rule forbids ("a container has EITHER a border OR an elevation, never both, and
   whatever is inside it has NEITHER"), at the smallest scale on the page: three edges inside 28px.

   The grey fill is what stays. It is the one surviving container type in this theme — a well, read
   by being a step below the card rather than by being drawn around — and it is also what the raised
   white thumb needs to read AS raised. `border: 0`, not `border-color: transparent`: the 1px of
   border box the track was reserving is padding that no longer has a job. */
html[data-theme="light"] .set-page .set-seg {
    border-radius: var(--r-control);
    background: var(--surface-inset);
    border: 0;
}


/* ── 6.7 One primary per pane ─────────────────────────────────────────────────────────────────
   `.set-act.key` is the sheet's one committing action and keeps the filled accent (§1 above).
   `.set-add` and `.set-back` were both bare accent TEXT, which put three violet things on a sheet
   that is allowed one. "Add a record" becomes the secondary shape; "back to the index" stays a text
   control — it leaves the sheet rather than acting on it — but takes the accent on hover only. */
html[data-theme="light"] .set-page .set-add {
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    border-radius: var(--r-control);
    color: var(--ink-dim);
}
html[data-theme="light"] .set-page .set-add:hover:not(:disabled) {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--accent);
}

html[data-theme="light"] .set-page .set-back,
html[data-theme="light"] .set-page .setp-back {
    color: var(--ink-dim);
}
html[data-theme="light"] .set-page .set-back:hover,
html[data-theme="light"] .set-page .setp-back:hover {
    color: var(--accent);
}

/* Destructive stays invisible until the cursor is on it; on white its hover is the measured breach
   triad rather than a red text colour on a red wash. */
html[data-theme="light"] .set-page .set-act.danger:hover:not(:disabled) {
    background: var(--st-breach-soft);
    color: var(--st-breach);
}

/* "The owner sets this" is a statement of fact under the sheet's blurb. It was a violet paragraph
   with a violet rule — the accent doing a job that is not an action, twice. */
html[data-theme="light"] .set-page .set-locked-note {
    color: var(--ink-muted);
    border-left-color: var(--keyline-strong);
}


/* ── 6.8 The scroll fade ──────────────────────────────────────────────────────────────────────
   "There is more below" is drawn as a gradient to `rgba(var(--ch-indigo-050309), 0.75)`. That
   channel inverts to 227,232,248 in light — a pale BLUE that is neither the canvas (#f6f8fa) nor
   the sheet, so the affordance fades the page out to a colour the page does not contain.

   The fade itself is kept: it is a real affordance, and deleting it is how a shop concluded the
   roster had no add row. It just fades to the ground it is actually over. `transparent` is a
   keyword, not a literal. */
html[data-theme="light"] .set-page .set-more,
html[data-theme="light"] .set-page .setp-more {
    background: linear-gradient(to bottom, transparent, var(--canvas));
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   7. THE ADJACENT PAGES — the two shapes they share with settings
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   /brief, /message-drafts and /phone-bookings live in this file because their colour debt was the
   same. Their structural debt is narrower: an eyebrow drawn at a dark-theme tracking, and a row of
   filter capsules. Both shapes are already decided elsewhere, so this is a mapping, not a design.

   The eyebrows keep the surface + border the §3/§4 colour rules gave them — on those pages the
   eyebrow is a boxed chip by design, not a bare label. */
html[data-theme="light"] .brief-eyebrow,
html[data-theme="light"] .md-eyebrow,
html[data-theme="light"] .mdp-eyebrow {
    border-radius: 4px;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
}

/* --r-pill is for true pills. A filter chip in a dense board is a rectangle: a row of nine capsules
   is a row of nine lozenges rather than a control bar, and each one wastes width at both ends. The
   .on state is a filled accent and is already declared in §5 above. */
html[data-theme="light"] .pb-chip,
html[data-theme="light"] .pt-chip,
html[data-theme="light"] .pb-sort-btn,
html[data-theme="light"] .pt-sort-btn {
    border-radius: var(--r-control);
}
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   PHASE 14 — SETTINGS, the light theme.  Destination: public/theme-light/settings.css
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   CLUSTER FILE VERIFIED, not assumed.  SettingsDesktop.vue:2 is `<div class="set-page">` and
   SettingsPhone.vue mounts `class="set-page setp-phone"`; settings.css already carries 687 lines
   rooted on `.set-page` (settings.css:35, :385, :468 …).  The brief's guess was right: these rules
   belong in `public/theme-light/settings.css`, APPENDED AFTER its existing §6.

   APPEND, DO NOT INSERT.  Roughly half the rules below are the same specificity as an existing rule
   in §1/§6 of that file — (0,3,0) against (0,3,0) — so they win on DOCUMENT ORDER only.  Dropping
   them above §6 makes them silently inert, which looks exactly like a rule that was never written.
   Where an existing rule is deliberately superseded the comment says so, per handoff §0.

   src/lib/settingsTokens.ts is this page's PRIVATE token module (603 lines, injected by
   usePageStyle at SettingsDesktop.vue:177).  It is theme-aware only in the sense that its values
   resolve through app.css, and settings.css §6.1 already re-points its twenty type/rule/ink steps
   onto the main ramp.  Nothing below re-declares a --set-* variable; every rule here fights the
   SHAPE those tokens are poured into, not the tokens.

   Every selector carries html[data-theme="light"] — (0,2,0) minimum — so dark cannot match by
   construction.  No colour literals, no !important, no @layer, no color-mix().


   ── DIAGNOSIS, per section, all measured off wide (1900px) screenshots + probe.mjs ─────────────

   THE FRAME (every section)
   · The sheet is a CARD ON A WHITE PAGE carrying BOTH a border and an elevation.  Probed:
     `.set-sheet` background rgb(250,251,253), box-shadow rgba(23,23,23,0.06) 0 3px 6px,
     border-top-width 1px — and `.page` is rgb(255,255,255).  Two faults at once: depth spent twice
     on one container, and a container at all, when `_base.css:1035` ("Cards stand down") already
     stood `.fx-card` / `.lx-sheet` / `.glass-panel` down across the app on the finding that Stripe
     removed its cards on purpose.  `.set-sheet` is the same object under a different name.
   · The scroll-more fade fades to `var(--canvas)` (settings.css:657) = #e3e8ee, over a page that is
     #ffffff.  Visible as a grey band across the bottom of the Öppettider and Rutinpåminnelser
     shots.  The affordance fades the page out to a colour the page does not contain.

   FIELD GROUPS WEARING CARD COSTUMES — 4 distinct classes, on every sheet that has a field
   · `.set-num`   probed background rgba(27,35,61,0.035) + 1px border, wrapping an input that
                  `_base.css:868` has already given the real field grammar.  Renders as a grey slab
                  with a white field punched into it (Priser: KR/TIM, %, KR ×6; Rutiner: DAGAR, TIM,
                  %, CYKLER ×6; Admin/Incheckning: MIN ×2; Feedback: TIMMAR).
   · `.set-input` same wrapper — but its child is a bare `<input>` or `type="url"`, NEITHER of which
                  matches `_base.css:264/868` (a typeless input does not match `input[type="text"]`).
                  So the whole control stays grey.  Probed on Recensionslänk: wrapper
                  rgba(27,35,61,0.035), child background transparent.  A grey fill on white is the
                  universal read-only signal — that field reads as one you cannot fill in, and the
                  number field two rows above it reads as one you can.  Same page, same idiom, two
                  opposite messages.
   · `.msch-quick` (MechanicScheduleEditor.vue:443) fill + border, nested INSIDE `.set-open`'s fill.
                  Its whole content is a label, two time fields and two buttons.
   · `.set-form`  fill + border, same shape.

   THE OPEN RECORD (Mekaniker → Melker)
   · `.set-open` probed rgb(236,237,242) — a 19-step grey slab, on white, holding EIGHT editable
     controls.  The single worst inversion on the page.
   · Nested depth: `.msch-quick` is a second filled, bordered box inside it.  A container inside a
     container, both paying for depth.
   · EIGHT uppercase tracked labels in that one record: NAMN · KOMPETENSNIVÅ · DEN HÄR DAGEN ·
     TIDER · SAMMA TIDER · ENSTAKA UNDANTAG · INLOGGNING · ROLL.

   UPPERCASE TRACKED LABELS — count across the page: 34 rendered instances over 9 classes
     `.set-rail-group` ×3 (index) · `.set-eyebrow` ×1 · `.set-sub-head` ×5 (Priser, Kapacitet ×1,
     Admin ×1, Rutiner ×2) · `.set-open-field > label` ×8 · `.set-num > span` ×15 · `.set-inline >
     span` ×14 (Öppettider ÖPPNAR/STÄNGER ×10, Rutiner FRÅN/TILL ×4) · `.msch-quick-label` ×1 ·
     `.msch-disclose` ×1 · `.cap-past-head` ×1.  Every one of the underlying strings is sentence
     case in the table (localization.ts:2590 'Verkstaden', :2607 'timmar', :2616 'Samma tider',
     :119 'Kompetensnivå'), so `text-transform: none` is sufficient for all of them — EXCEPT
     `settings_badge` (localization.ts:2574 `{ en: 'PREFERENCES', sv: 'INSTÄLLNINGAR' }`), which is
     capitals AT SOURCE and needs the lowercase + ::first-letter recipe.

   COLOUR SPENT WHERE IT CARRIES NOTHING
   · `.mech-skill` probed rgb(154,96,0) — the SKILL LEVEL of a mechanic rendered in the severity
     triad: ENKEL green, AVANCERAD mustard, EXPERT red.  Five of five roster rows are coloured, and
     "Expert" — the best mechanic in the shop — is painted in the breach red.  This is handoff §8
     exactly: a colour on every row has stopped carrying information, and here it is also carrying
     the WRONG information.
   · `.set-note.ok` puts a green rule beside "5 aktiva · kapaciteten kommer från den här listan".
     That is a count, not a success.  `.set-note.info` puts an accent rule beside two informational
     lines on Lösenord and one on Bokningskapacitet — accent doing a job that is not an action.
   · `.set-note.warn` ink is `--c-orange-f0dcae` (settingsTokens.ts:475) and `.set-error` ink is
     `--c-red-f7cccc` (:505) — a pale cream and a pale pink, tuned against near-black.  On white
     those are roughly 1.4:1.  UNSEEN in these captures (no error state was reachable) but they are
     unambiguous from the values.
   · `.msch-day.shop` is a TEAL left border (MechanicScheduleEditor.vue:462) — a hue that appears
     nowhere else in this theme, repeating a word that is already written on the row.
   · `.msch-scope` is accent-coloured uppercase; `.set-rail-val.set` and `.set-eyebrow` were already
     de-accented by settings.css §"Accent discipline".

   TWO PRIMARIES: none found.  Every sheet holds at most one `.set-act.key` — Fortnox is the densest
   (Synka nu filled, Koppla från text, one green chip) and it reads correctly.

   HOVERS THAT VANISH: `.set-rail-item:hover` and `.set-line:hover` resolve to `--set-hover`, which
   settings.css:407 re-points at `--surface-inset` — a real tone step, so these are fine.  The one
   that does not paint is `.set-input:hover` / `.set-num:hover` → `--set-rule-strong` on a wrapper
   that should not have a border at all; the fix below removes the wrapper rather than the hover,
   and the resting state becomes the hover state per the measured input rule.

   GRADIENTS / GLOWS LEFT: one — `.set-more`, above.  `.msch-modal` (MechanicScheduleEditor.vue:530)
   is a two-stop near-black gradient panel and would render as a dark slab in light, but it is a
   confirm dialog I could not reach from the harness; its rule below is marked UNVERIFIED.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── 8.1  THE SHEET STANDS DOWN ────────────────────────────────────────────────────────────────
   CONFIRMED (rendered).  Supersedes settings.css:46-51 and :503-506 for the DESKTOP sheet.

   `_base.css:1035` already made this call for the rest of the app: on a white page a bordered,
   shadowed box around content is what made the theme read as a wireframe.  `.set-sheet` was simply
   not on that list.  Standing it down also retires the nested-box problem by construction — with
   the outer card gone, `.set-open` is no longer a grey box inside a grey box inside a white page.

   The index and the pane are then separated the way `_base.css:1057` separates a ledger from its
   record: one vertical hairline and 24px, not two frames.  It is inside `min-width: 1001px` because
   `SettingsDesktop.css:254` collapses the shell to a single column below that and turns the two
   panes into a drill-down — a left border there would be a stray vertical line down the page.

   `.setp-sheet` (phone) is DELIBERATELY not included.  The phone view is a different layout with
   its own drill-down and I have no phone capture; settings.css:46 keeps it as it was. */
html[data-theme="light"] .set-page .set-sheet {
    background: transparent;
    border: 0;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
}

/* REVISED after looking at it (this block supersedes my own first attempt, which put the rule on
   `.set-pane` as a border-left): a rule on the PANE is only as tall as the open sheet, so on Namn
   och språk — two rows — it rendered as a 210px fragment hanging in white with the index running
   700px past it.  On the RAIL it is always the tall element, it is `position: sticky` so it stays
   with the index it belongs to, and each entry's own bottom rule now meets it at a corner: three
   lines, one colour, one weight, which is the measured table idiom.  No padding is added — the
   shell's own 2.4rem gap is the gutter. */
@media (min-width: 1001px) {
    html[data-theme="light"] .set-page .set-rail {
        border-right: 1px solid var(--keyline);
    }
}

/* The fade fades to the ground it is actually over.  settings.css:657 aimed it at --canvas
   (#e3e8ee) on the reasoning that --canvas is the page; probed, `.page` is #ffffff (= --surface),
   so the affordance was painting a grey band across the bottom of every long sheet.
   CONFIRMED — the band is visible in g-hours-light.png and g-routines-light.png and gone after. */
html[data-theme="light"] .set-page .set-more,
html[data-theme="light"] .set-page .setp-more {
    background: linear-gradient(to bottom, transparent, var(--surface));
}


/* ── 8.2  A FIELD GROUP IS NOT A CARD ──────────────────────────────────────────────────────────
   CONFIRMED (rendered).  `.set-num` and `.set-input` are LAYOUT — a flex row holding a field and,
   for `.set-num`, a unit beside it.  They were given a fill, a border and a radius, which makes the
   unit look like it lives in a grey chip welded to the field, and makes `.set-input` — whose child
   carries no field styling of its own — a grey box you are supposed to type into.

   The :hover and :focus-within variants are restated rather than left to inherit: settingsTokens
   declares them at `.set-page .set-input:focus-within` (0,3,0), and a plain (0,3,0) override here
   would lose the tie on document order for those states only — the resting state would flatten and
   the focused state would go back to a grey box.  Stated at (0,4,0) they cannot. */
html[data-theme="light"] .set-page .set-input,
html[data-theme="light"] .set-page .set-num {
    background: none;
    border-color: transparent;
    border-radius: 0;
    gap: var(--sp-2);
}
html[data-theme="light"] .set-page .set-input:hover,
html[data-theme="light"] .set-page .set-num:hover,
html[data-theme="light"] .set-page .set-input:focus-within,
html[data-theme="light"] .set-page .set-num:focus-within {
    background: none;
    border-color: transparent;
}

/* The field itself, verbatim from the measured grammar (_base.css:868).  The edge is lighting, not
   a border; the `0 0 0 1px rgba(--focus-ring-rgb, 0)` and `0 0 0 2px` slots are pre-declared at
   alpha 0 so `transition: box-shadow` INTERPOLATES instead of hard-cutting, and so the focus ring
   REPLACES the navy hairline instead of adding to it — a focused field has exactly as much edge as
   an unfocused one and nothing moves by a pixel.

   Written against the CHILD, not the wrapper, because that is where the caret is.  `.set-num`'s
   child already gets this from _base.css (it is `input[type=number]`); `.set-input`'s child does
   not, and naming both here is what makes the two look identical.  No :hover — the measured file
   has none on a text input anywhere, and the resting state IS the hover state.

   Height is left alone deliberately: settingsTokens sets `height: 100%` of the 34px wrapper, and
   `SettingsPhone.css:237` re-declares --set-ctl-h at 44px for touch.  Pinning --ctl-h here would
   take every phone target down to 32px — the same trap settings.css:381 already documents. */
html[data-theme="light"] .set-page .set-input > input,
html[data-theme="light"] .set-page .set-num > input {
    padding: 0 var(--ctl-pad-x);
    border: 0;
    border-radius: var(--r-control);
    background-color: var(--surface);
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.16),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0),
        0 1px 1px rgba(0, 0, 0, 0.08);
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}
html[data-theme="light"] .set-page .set-input > input:focus,
html[data-theme="light"] .set-page .set-num > input:focus {
    outline: 0;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0.20),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0.25),
        0 1px 1px rgba(0, 0, 0, 0.08);
}

/* The same shape for the one control the page is most often driven from.  settings.css:449 gave
   the search box `border + --field-floor`, which was the app-wide field idiom at the time; the
   later block at _base.css:868 replaced that with the spread-shadow grammar for every typed input,
   so the search box was left as the only field on the page wearing the old one.

   Padding is NOT touched.  The magnifier and the × live INSIDE this label and the 12px/8.8px
   reserve is what keeps them off the placeholder — the trap that once put a search icon on top of
   its own text.  Removing `border` costs 1px on each side and nothing else. */
html[data-theme="light"] .set-page .set-search,
html[data-theme="light"] .set-page .setp-search {
    border: 0;
    background: var(--surface);
    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;
}
html[data-theme="light"] .set-page .set-search:focus-within,
html[data-theme="light"] .set-page .setp-search:focus-within {
    border-color: transparent;
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0),
        0 0 0 1px rgba(var(--focus-ring-rgb), 0.20),
        0 0 0 2px rgba(var(--focus-ring-rgb), 0.25),
        0 1px 1px rgba(0, 0, 0, 0.08);
}


/* ── 8.3  THE OPEN RECORD IS A ZONE, NOT A SLAB ────────────────────────────────────────────────
   CONFIRMED (rendered).  Supersedes settings.css:56-60 for these three.

   "A zone is carved in; an object sits on."  An opened record is carved in — but it was carved 19
   steps down (probed rgb(236,237,242)), which on white is the read-only fill, and it is full of
   editable controls.  --surface-inset (#f3f6f8) is a 3-step carve: enough to say "this belongs to
   the row above", not enough to say "you cannot touch this".  The white fields inside then read as
   raised out of it, which is the occlusion doing the work the pigment was trying to do.

   The edge is an INSET hairline rather than a border, so the zone gains a boundary without gaining
   a layout box and without a second radius to keep in step with the row above it.  `.set-line.open`
   takes the same fill so the header and its zone are visibly one object.

   `.msch-quick` loses its container entirely — a label, two time fields and two buttons is a row,
   not a card, and it was a filled bordered box inside this fill. */
html[data-theme="light"] .set-page .set-open,
html[data-theme="light"] .set-page .set-form {
    background: var(--surface-inset);
    border-color: transparent;
    border-radius: 0;
    box-shadow: inset 0 1px 0 0 var(--keyline);
}
html[data-theme="light"] .set-page .set-line.open {
    background: var(--surface-inset);
}
html[data-theme="light"] .set-page .msch-quick {
    background: none;
    border-color: transparent;
    border-radius: 0;
    padding-left: 0;
    padding-right: 0;
}

/* The sample message is the one place a grey fill is CORRECT — it is genuinely read-only, a quote
   of what the customer will receive.  It keeps the fill and loses the border: the surviving filled
   container in this theme is "a soft grey fill with NO border" (_base.css:1047), and a fill that
   also carries an edge is depth spent twice on a block of text. */
html[data-theme="light"] .set-page .set-sample {
    border-color: transparent;
}


/* ── 8.4  SENTENCE CASE — 34 labels, nine classes, one rule ────────────────────────────────────
   CONFIRMED (rendered).  Supersedes settings.css:479-487 and :541-548, which set these to
   uppercase + --eyebrow-track.  That was the earlier idiom; _base.css:915 has since reversed it on
   measured evidence (the 3.6 MB source contains nine letter-spacing declarations and not one is on
   a section label), and boards.css:3685 and money.css:1553 are the signed-off pages built that way.
   Settings was the last cluster still shouting.

   Sentence case matters more in Swedish than in English: VAD KUNDER FÅR HÖRA is four words of
   capitals above a list that is already carrying eleven values down its right edge.

   Every string here is sentence case at source, so `text-transform: none` is the whole fix and the
   ::first-letter recipe is not needed — see §8.5 for the one that is.

   SPECIFICITY: `.msch-*`, `.mech-*` and `.cap-*` come from `<style scoped>` SFCs, so their own
   selectors are `.msch-quick-label[data-v-x]` = (0,2,0).  Prefixed here at (0,3,0) they cannot lose
   on document order the way a bare (0,2,0) override would. */
html[data-theme="light"] .set-page .set-rail-group,
html[data-theme="light"] .set-page .setp-rail-group,
html[data-theme="light"] .set-page .set-sub-head,
html[data-theme="light"] .set-page .set-open-field > label,
html[data-theme="light"] .set-page .set-num > span,
html[data-theme="light"] .set-page .set-inline > span,
html[data-theme="light"] .set-page .msch-quick-label,
html[data-theme="light"] .set-page .msch-disclose,
html[data-theme="light"] .set-page .msch-scope,
html[data-theme="light"] .set-page .msch-past-head,
html[data-theme="light"] .set-page .cap-scope,
html[data-theme="light"] .set-page .cap-past-head,
html[data-theme="light"] .set-page .mech-skill,
html[data-theme="light"] .set-page .mech-att-chip,
html[data-theme="light"] .set-page .mech-role-pill,
html[data-theme="light"] .set-page .mech-login-pill,
html[data-theme="light"] .set-page .aiu-table th {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    text-transform: none;
    letter-spacing: 0;
}

/* The unit inside a number control is now a suffix standing on the page rather than a chip.  It
   gets the muted ink and a little air, and nothing else — a unit is the quietest thing in a row. */
html[data-theme="light"] .set-page .set-num > span,
html[data-theme="light"] .set-page .set-inline > span {
    color: var(--ink-muted);
    padding: 0;
}


/* ── 8.5  THE PAGE EYEBROW — WORKAROUND RETIRED, THE STRING WAS FIXED AT SOURCE ───────────────
   This used to read `text-transform: lowercase` plus a `::first-letter` rule raising the initial
   back, plus `display: inline-block` which was load-bearing ONLY because ::first-letter needs a
   block container.  All three are gone: `settings_badge` was `{ en: 'PREFERENCES',
   sv: 'INSTÄLLNINGAR' }` — capitals in the DATA — and localization.ts:2574 is now sentence case.

   Dark-safe by inspection: `.set-eyebrow` and `.setp-eyebrow` each declare
   `text-transform: uppercase` in their own page sheets (SettingsDesktop.css:56,
   SettingsPhone.css:40), unprefixed and so still in force — dark re-shouts the string.

   `text-transform: none` stays and is not redundant: those same page rules are what would otherwise
   reach light too.  §7 above sets the tracking on this pair; `letter-spacing: 0` here supersedes it,
   which is the intended relationship (later rule wins, and this one is the eyebrow decision).

   Still true and still a string-table question, not a CSS one: in Swedish this eyebrow renders
   "Inställningar" directly above an <h1> that says "Inställningar".  The case fix makes it quiet; it
   does not make it say anything. */
html[data-theme="light"] .set-page .set-eyebrow,
html[data-theme="light"] .set-page .setp-eyebrow {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}


/* ── 8.6  THE VERDICT RULE, APPLIED TO SKILL ──────────────────────────────────────────────────
   CONFIRMED (rendered).  `.mech-skill` is `t${clampSkill(level)}` (src/models/skill.ts:28) — the
   classes are `t1 t2 t3`, grepped and read, not guessed.  All three are named so no tier can slip
   back into the triad through a variant this rule forgot.

   Two separate faults, one fix.  First, handoff §8: five of five roster rows carry a hue, so the
   hue has stopped separating anything.  Second, and worse, the hues are the SEVERITY triad — the
   most skilled mechanic in the shop is painted in the same red the app uses for "past the ready
   date".  A skill level is an ordinal fact, and the word already states it.

   The tier survives in ink weight and position, which is what it had before the colour was added. */
html[data-theme="light"] .set-page .mech-skill,
html[data-theme="light"] .set-page .mech-skill.t1,
html[data-theme="light"] .set-page .mech-skill.t2,
html[data-theme="light"] .set-page .mech-skill.t3 {
    color: var(--ink-muted);
}

/* The schedule editor's day markers: violet for "a window is set", teal for "follows shop hours".
   The teal appears nowhere else in this theme, and both facts are written in words on the same row
   — colour repeating type in the one channel a colour-blind mechanic cannot read.  The bar stays
   (it separates three row states at a glance); it goes neutral, with the SET day keeping the firmer
   line because that is the one that departs from the default.  UNVERIFIED: reasoned from
   MechanicScheduleEditor.vue:460-462, the week list was collapsed in every capture. */
html[data-theme="light"] .set-page .msch-day.set {
    border-left-color: var(--keyline-strong);
}
html[data-theme="light"] .set-page .msch-day.shop,
html[data-theme="light"] .set-page .msch-day.off {
    border-left-color: var(--keyline);
}
html[data-theme="light"] .set-page .msch-scope {
    color: var(--ink-muted);
}


/* ── 8.7  NOTES: SEVERITY IS HUE AT NEAR-ZERO CHROMA, AND A COUNT IS NOT A SUCCESS ─────────────
   CONFIRMED for .info and .ok (rendered); the .warn and .error inks are UNVERIFIED — no error or
   warning state was reachable from the harness — but their values are unambiguous:
   settingsTokens.ts:475 is `--c-orange-f0dcae` and :505 is `--c-red-f7cccc`, a pale cream and a
   pale pink chosen against near-black.  On white they are roughly 1.4:1, i.e. invisible.

   `.set-note` is a good device — one line, one hairline on the left, no box — and it is kept.  What
   goes is the hue on the two variants that are not severities.  "5 aktiva · kapaciteten kommer från
   den här listan" is a COUNT wearing `.ok`, and it was drawing a green rule; two lines on Lösenord
   and one on Bokningskapacitet wear `.info` and were drawing an accent rule beside a sentence
   nobody can click.  Both go to the structural hairline; the words are unchanged and still carry
   everything they carried.

   .warn and .error keep their hue, because those ARE the case that is actually wrong — repointed at
   the measured light triad, whose 10% fills sit within 0.007 of each other so severity is carried
   by hue rather than by intensity. */
html[data-theme="light"] .set-page .set-note,
html[data-theme="light"] .set-page .set-note.info,
html[data-theme="light"] .set-page .set-note.ok {
    border-left-color: var(--keyline-strong);
    color: var(--ink-muted);
}
html[data-theme="light"] .set-page .set-note.warn {
    border-left-color: var(--st-expected-line);
    color: var(--st-expected);
}
html[data-theme="light"] .set-page .set-error {
    border-left-color: var(--st-breach-line);
    color: var(--st-breach);
}
html[data-theme="light"] .set-page .set-msg {
    color: var(--st-breach);
}
html[data-theme="light"] .set-page .set-msg.saved {
    color: var(--st-logged);
}


/* ── 8.8  THE SEGMENTED CONTROL — one mark for one selection ───────────────────────────────────
   CONFIRMED (rendered).  Supersedes settings.css:66-76.

   That block raised the selected segment with `--elevation-1` PLUS an inset ring — a border and an
   elevation on the same 28px object, which is the rule §5.2 exists to stop, at the smallest scale
   on the page.  Replaced with the single control statement: a 1px spread in navy and a 1px ambient
   drop, one declaration, no second edge.

   The accent underline goes.  With a raised white chip the selection is already unambiguous, and
   the underline was a SECOND mark for the same state — settings.css:71 had already suppressed it on
   the language pair for reading as an error squiggle under a flag, which is an argument that
   applies to all four of them.  One rule, one behaviour, and `.set-seg.lang` no longer needs to be
   a special case. */
html[data-theme="light"] .set-page .set-seg > button.on,
html[data-theme="light"] .set-page .set-seg.lang > button.on,
html[data-theme="light"] .setp-phone .set-seg.lang > button.on {
    background: var(--surface);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08);
}
html[data-theme="light"] .set-page .set-seg > button.on::after {
    display: none;
}


/* ── 8.9  THE INDEX — selection is tint plus a leading bar, and rows are ruled by a shadow ─────
   CONFIRMED (rendered).  Supersedes settings.css:468-477.

   The selected entry probed rgb(228,230,235) — a neutral grey heavy enough that on a white page it
   reads as the DISABLED entry rather than the open one.  Selection in this theme is a tint of the
   accent plus a leading bar, and the bar is the thing that survives a glance; --accent-tint is
   rgba(47,79,208,0.045), which is quieter than the grey it replaces and unambiguously blue.

   The divider becomes an inset shadow rather than a border, per _base.css:938: it occupies no
   layout box, so the leading bar and the rule stack into one declaration without the row changing
   height.  The bar width is declared on the RESTING row as well, transparent, so selecting a row
   cannot shift its label by a pixel. */
html[data-theme="light"] .set-page .set-rail-item {
    border-bottom: 0;
    border-left-width: 3px;
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}
html[data-theme="light"] .set-page .set-rail-item:hover {
    background: var(--surface-inset);
}
html[data-theme="light"] .set-page .set-rail-item.on {
    background: var(--accent-tint);
    box-shadow: inset 0 -1px 0 0 var(--keyline);
}


/* ── 8.10  THE CONFIRM DIALOG INSIDE THE SCHEDULE EDITOR ───────────────────────────────────────
   UNVERIFIED — reasoned only.  MechanicScheduleEditor.vue:528-532 is a two-stop near-black gradient
   panel over an `rgba(var(--ch-blue-06080f), 0.72)` backdrop, opened by "rensa dagen".  I could not
   reach it: it needs the week list expanded, a day put into edit, and then the danger button, and
   the harness ran out of clicks before the state existed.

   It is NOT teleported (it is a plain `v-if` inside the component, unlike DatePickerField.vue:18 /
   StationSelect.vue:12 which are), so scoping it to `.set-page` is correct and will match — but the
   rule below has not been seen rendered and should be screenshotted before it ships.

   `--r-panel` is 4px in light, so no radius is stated separately; the elevation carries its own
   hairline ring, so no border either. */
html[data-theme="light"] .set-page .msch-backdrop {
    background: var(--scrim);
    backdrop-filter: none;
}
html[data-theme="light"] .set-page .msch-modal {
    background: var(--surface);
    background-image: none;
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-3);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   PHASE 15/16 — TOUCH PASS, SETTINGS CLUSTER.   Destination: public/theme-light/settings.css
   APPEND AFTER the existing §8.10 (end of file). Do not insert above it.
   ═══════════════════════════════════════════════════════════════════════════════════════════════

   ROOTS VERIFIED, NOT ASSUMED
   · /settings desktop+tablet  `SettingsDesktop.vue:2`     → `.set-page`
   · /settings phone           `SettingsPhone.vue`         → `.set-page.setp-phone` (probed: the
                                                             element carries BOTH classes)
   · /message-drafts desktop+tablet `MessageDraftsDesktop.vue` → `.md-wrap` / `.md-*`
   · /message-drafts phone     `MessageDraftsPhone.vue:2`  → `.mdp-wrap` / `.mdp-*`
   All four already live in this file (§1/§4/§6/§8), so the brief's assignment is correct. /brief,
   /fel and /phone-bookings also live here but belong to other clusters this pass and are untouched.

   WHAT THIS PASS FOUND, IN ONE LINE EACH (evidence in each block):
   1. /settings on phone audits clean at 44px and still does — nothing here raises a phone target.
   2. The TABLET was where the work was: `--set-ctl-h` is 34px there while the blanket coarse floor
      makes the inputs INSIDE those wrappers 44px, so every number field overflowed its own wrapper.
   3. The search box has been a box-inside-a-box on all three shells, and on the touch shells the
      inner box overflows the outer one by 5px top and bottom. Most visible defect in the cluster.
   4. Every WELL in this cluster inverts on the touch shells, because `_base.css:1125` paints the
      page white only on `data-shell='desktop'`. MEASURED, not argued (numbers in §9.4).
   5. §8.1's scroll-fade fix ("fade to --surface") is correct on desktop and wrong on touch, for the
      same reason as 4.
   6. `.btn-astra-neutral` never reaches the coarse floor anywhere in the app — `_base.css:844`
      declares `min-height: 36px` at (0,2,0) and the floor is (0,1,1). App-wide finding, fixed here
      only for this cluster's two pages.

   Nothing below re-declares a --set-* value on a shell where it would SHRINK a control; the
   `--set-ctl-h` trap documented at settings.css:397 and :900 is respected and re-argued in §9.1.

   ⚠ READ §9.9 BEFORE VERIFYING ANY OF THIS ON A SCREENSHOT. `screenshot.mjs` captures with
   `fullPage: true`, and on any route whose DOCUMENT is taller than the viewport that capture stops
   matching `@media (pointer: coarse)`. Every coarse rule in this file — and in the TOUCH LAYER
   block, and in the other five authors' work — photographs as a no-op on those routes. It cost this
   pass an hour and one wrong finding; the workaround is in §9.9.

   Every selector carries html[data-theme="light"]. Dark cannot match by construction. No !important,
   no :has(), no color-mix(), no @layer, no colour literals.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── 9.1  THE CONTROL HEIGHT TOKEN, ON COARSE POINTERS ONLY ────────────────────────────────────
   CONFIRMED (rendered + probed, tablet).

   MEASURED on tablet, before: `.set-num` wrapper h=34, its `> input` h=44 at x=817 y=218 against a
   wrapper at y=223 — the input sticks 5px out of the top and bottom of the box that lays it out, on
   every number field on the page (Priser ×6, Rutiner ×6, Admin ×2, Feedback ×1). It does not paint
   a visible fault only because §8.2 already made those wrappers transparent; the row's line box is
   still 10px shorter than the control inside it, which is a collision waiting for a denser row.

   The cause is that the blanket floor in `_base.css` reaches `input` but not `--set-ctl-h`, which
   settingsTokens.ts:83 fixes at 34px and which `.set-open-field`, `.set-inline`, `.set-seg` and the
   time fields all consume as `height`.

   WHY THIS IS NOT THE TRAP settings.css:397 AND :900 DOCUMENT. Those say: do not fold --set-ctl-h
   onto --ctl-h, because `SettingsPhone.css:237` declares `--set-ctl-h: 44px` on `.setp-phone` at
   (0,1,0), a light block on the same element scores higher, and --ctl-h is 32px on a fine pointer —
   so the fold would win the cascade and SHRINK every phone target from 44 to 32. That reasoning is
   intact and this rule does not violate it:

     · the value here is --tap-min, which IS 44px — the same number SettingsPhone.css:237 already
       declares, so on phone this changes nothing (44 -> 44, verified on the phone render);
     · it is inside `@media (pointer: coarse)`, so the signed-off DESKTOP page keeps 34px;
     · --ctl-h is not mentioned. The two ladders stay separate.

   In other words the phone measurement that set the app's floor at 44 is now simply applied to the
   OTHER touch shell, which is what it was measured for. */
@media (pointer: coarse) {
    html[data-theme="light"] .set-page {
        --set-ctl-h: var(--tap-min);
    }
}


/* ── 9.2  THE SEARCH BOX IS A FIELD INSIDE A FIELD ─────────────────────────────────────────────
   CONFIRMED (rendered on wide, tablet and phone; probed on all three).

   `.set-search` is a `<label>` wrapping a magnifier, an `<input>` and a clear button. settings.css
   §8.2 gave the LABEL the measured field grammar (white, 1px spread, 1px ambient drop) — correct,
   because the icons live inside it and the whole box is the control. But the `<input>` also matches
   `_base.css:955`'s `input[type="search"]` list at (0,2,1), so it draws the SAME white fill and the
   SAME 1px ring a second time, inside the first.

   MEASURED:
     wide    label x=1368 w=250 h=34 · input x=1404 w=205 h=34   — a second vertical rule appears
                                                                   beside the placeholder, and the
                                                                   inner ring lands within 1px of
                                                                   the outer one on the right.
     tablet  label y=44  h=34       · input y=39  h=44           — the coarse floor raises the INPUT
     phone   label      h=44        · input      h=44              but not its wrapper, so on tablet
                                                                   the inner field hangs 5px out of
                                                                   the outer field, top and bottom.
                                                                   Plainly visible in both touch
                                                                   captures as two offset rounded
                                                                   rectangles.

   Two rules, and they are deliberately different in scope. Un-ringing the input is true on EVERY
   shell (the doubled edge is a desktop fault too, it is just quieter there), so it is unscoped.
   Flooring the label is a touch decision, so it is in the media query.

   The label is what a finger hits — clicking anywhere in a `<label>` focuses the input it wraps —
   so flooring the label is flooring the target, not decorating around one. The input keeps the
   blanket 44px floor from `_base.css`, so both boxes are 44 and neither overflows the other; the
   auditor still measures a 44px `<input>` and stays green.

   Padding on the input is zeroed rather than left at the UA's 4px/7px: the LABEL owns the field's
   padding (12px left for the magnifier, 8.8px right for the ×, trap #13 — do not touch those), and
   the input's own padding was pushing the caret a further 7px in from the icon reserve.

   Specificity: `.set-page .set-search input` is (0,3,1) against _base's (0,2,1). It cannot lose. */
html[data-theme="light"] .set-page .set-search input,
html[data-theme="light"] .set-page .setp-search input {
    background: none;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
}

@media (pointer: coarse) {
    html[data-theme="light"] .set-page .set-search,
    html[data-theme="light"] .set-page .setp-search {
        min-height: var(--tap-min);
    }
}


/* ── 9.3  THE SCROLL FADE, ON THE GROUND THE TOUCH SHELLS ACTUALLY HAVE ────────────────────────
   CONFIRMED (rendered + pixel-sampled, tablet).

   THIS SUPERSEDES §8.1's `.set-more` rule ON COARSE POINTERS ONLY, and §8.1 is not wrong — it is
   right about the shell it was measured on and wrong about the other two.

   §8.1 probed `.page` as #ffffff and pointed the fade at --surface. That white comes from
   `_base.css:1125`, which is scoped `.page[data-shell='desktop']`. On tablet and phone `.page` is
   transparent (probed) and the painted ground is --canvas.

   PIXEL-SAMPLED off the tablet capture, before: ground rgb(227,232,238), fade band rgb(249,250,251)
   — a white band across the bottom of every long sheet, i.e. exactly the fault §8.1 set out to
   remove, reintroduced one shell over. After: fade band rgb(226,231,237) against a ground of
   rgb(227,232,238). Gone.

   Written as a media query rather than `.page[data-shell]` so the pair of selectors stays identical
   to §8.1's and the supersession is legible; `.set-more` is not teleported, so either would reach
   it. */
@media (pointer: coarse) {
    html[data-theme="light"] .set-page .set-more,
    html[data-theme="light"] .set-page .setp-more {
        background: linear-gradient(to bottom, transparent, var(--canvas));
    }
}


/* ── 9.4  EVERY WELL IN THIS CLUSTER INVERTS ON A TOUCH SHELL ──────────────────────────────────
   CONFIRMED (rendered + pixel-sampled, tablet). This is the largest finding of the pass and it is
   a MEASUREMENT, not a preference.

   The whole light surface scale is built on "the page is white, and a well is a step below it".
   That premise holds only where `_base.css:1125` paints it — `.page[data-shell='desktop']`. On the
   two touch shells the page is transparent over --canvas, so every token that means "one step BELOW
   the card" lands ABOVE the ground and the recess becomes a raised slab.

   PIXEL-SAMPLED off the tablet capture of /settings, before:

       ground (--canvas)                    rgb(227,232,238)
       .set-seg track (--surface-inset)     rgb(243,246,248)   +16 — the well is the brightest thing
       .set-seg > button.on (--surface)     rgb(255,255,255)   +12 over its own track

   So the segmented control read as a raised white slab with a slightly whiter chip on it: the
   selection and the track were 12 units apart where the design gives them a well plus an elevation.
   The same inversion applies to `.set-chip`, `.set-sample`, `--set-well` and `--set-hover`, all of
   which resolve through the same two tokens.

   FIXED AT THE TOKEN, ON THE PAGE, ON COARSE ONLY — twelve consumers, two declarations, and the
   desktop page (where the premise is true) is untouched. The values are chosen to reproduce the
   DESKTOP relationship rather than picked by eye: white -> --surface-inset is a delta of (12,9,7),
   and --canvas -> --well-a18 is (7,10,8). The measured result after the change:

       ground                               rgb(227,232,238)
       .set-seg track                       rgb(220,222,230)   a well again
       .set-seg > button.on                 rgb(255,255,255)   raised out of it, unambiguously

   ESCALATION FOR THE LEAD, stated rather than worked around: this is not a settings bug. Any page
   with a recess has it on both touch shells. The one-line version is to extend `_base.css:1125` to
   `.page[data-shell='tablet']` and `[data-shell='phone']`, which would make the premise true
   everywhere and let this block be deleted. I did not write that, for two reasons: `_base.css` is
   the lead's file, and on the tablet the nav rail is ALSO --surface, so a white page would erase
   the boundary between chrome and content that the grey ground currently draws. That trade is a
   composition call above my cluster. */
@media (pointer: coarse) {
    html[data-theme="light"] .set-page {
        --surface-inset: var(--well-a18);
        --recess-a05: var(--well-a18);
    }
}

/* /message-drafts carries the same inversion in one place: the batch instruction bar takes
   --recess-a05 from §4 and measured rgb(236,237,242) against the same rgb(227,232,238) ground — a
   second, brighter card floating above the list it is meant to sit under. Written per element
   rather than as a token override because `.md-wrap` / `.mdp-wrap` do not scope the card list the
   way `.set-page` scopes settings, and a token override would be reaching further than the evidence.
   UNVERIFIED at the pixel level after the change (rendered, and the token is the same one §9.4
   measured); the before value is measured. */
@media (pointer: coarse) {
    html[data-theme="light"] .md-batch,
    html[data-theme="light"] .mdp-batch {
        background: var(--well-a18);
    }
}


/* ── 9.5  THE NEUTRAL BUTTON NEVER REACHED THE FLOOR — APP-WIDE, FIXED HERE FOR TWO PAGES ──────
   CONFIRMED (auditor + rendered).

   `touch-audit.mjs` on /message-drafts at tablet: `36x70 n=3 button.btn-astra-neutral "Släng"` —
   three discard buttons 36px tall on a shell whose floor is 44. /settings audits clean at both
   shells, so this is the cluster's only failing target.

   The cause is not this page. `_base.css:844` declares `min-height: 36px` on
   `.btn-astra-neutral, .btn-astra-glass, .wq-act, button.pb-chip, .pb-sort-btn, .fc-sort-btn` at
   (0,2,0), and the coarse floor at `_base.css:2254` is `html[data-theme="light"] button` = (0,1,1).
   A higher-specificity declaration beats a media query every time — the media query changes nothing
   about specificity — so NONE of those six classes has ever reached the floor, on any page, on
   either touch shell. The TOUCH LAYER block's own note ("`.btn-astra` is the one place light made
   touch WORSE: 36px in light against >=44px in dark") diagnosed the symptom on the primary button;
   this is where the 36 actually lives, and it is six selectors wide.

   Fixed here at (0,3,0) for the two pages this cluster owns. The general fix belongs in `_base.css`
   beside the declaration that causes it — either add the six classes to the floor's selector list,
   or wrap the 36px in `@media (pointer: fine)`. Flagged rather than reached for.

   SEPARATION, the second half of the pair. `.md-acts` puts the discard button immediately beside
   the send button; measured gap ~5px, under --tap-sep. This is the exact case the touch layer names
   as always qualifying — a destructive control abutting the one action you must not confuse it with
   — so the send button takes --tap-sep of air. The phone view already separates them (the send
   button is on its own row, `MessageDraftsPhone.vue:57`) and is left alone. */
@media (pointer: coarse) {
    html[data-theme="light"] .md-acts .btn-astra-neutral,
    html[data-theme="light"] .mdp-acts .btn-astra-neutral,
    html[data-theme="light"] .set-page .btn-astra-neutral {
        min-height: var(--tap-min);
    }

    html[data-theme="light"] .md-acts .btn-astra {
        margin-left: var(--tap-sep);
    }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   9.6  THE SHEET WIDTH CALL — EXECUTED, RENDERED, AND ANSWERED "NO"
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   THERE IS NO RULE IN THIS SECTION. That is the finding, and it is written here so the next pass
   does not re-open it from the same premise.

   THE BRIEF'S CALL: `.set-sheet` still caps at 820px (SettingsDesktop.css:215) while the page
   measure moved to the shared 1560px token (`_base.css:2131`), leaving ~450px unused; raise the cap
   light-only, because the 820 was chosen against a 1180px page and the premise changed.

   MEASURED FIRST: at wide (1900) `.set-measure` is 1560, `.set-rail` 264, the shell gap 2.4rem,
   `.set-pane` 1257.6 and `.set-sheet` 820 — so 437.6px of the pane is unpainted. The premise is
   exactly right.

   EXECUTED: `max-width: none` and `max-width: 1040px`, both rendered at wide on the two sheets that
   represent the page's two row shapes — Priser och avgifter (label/control rows) and Mekaniker
   (record list). Both are worse, and for a reason more specific than the original comment gives:

   `.set-row` is a flex row with exactly TWO children and `justify-content: space-between`
   (settingsTokens.ts:93-114). There is no third column. So every pixel the sheet gains is spent,
   in its entirety, on the GAP between a setting's name and its control. Nothing gets bigger, nothing
   gets more legible, and nothing new fits.

       820   "Timpris" x=472, its field x=1516-820+…  — name and control ~490px apart
       1040  same row                                 — ~800px apart
       none  same row                                 — ~1040px apart, and the control cluster is
                                                        now hard against the page's right edge with
                                                        no margin at all

   Mekaniker settles it in the other direction: at 820 the roster rows are ALREADY underfilled —
   "Melker  Avancerad" on the left and a disclosure chevron ~800px away on the right, with nothing in
   between. Every widening makes that row worse. No sheet on the page is content-limited at 820; the
   sheet is not short of width, the rows are short of content.

   SO WHAT IS THE 437px? It is not the sheet's. `--page-measure: 1560px` was raised to give the app
   ONE LEFT EDGE (`_base.css:2116`); the left edge is what it buys, and the right edge is a
   by-product. /settings is the one page in that set whose content is prose and controls rather than
   a data table, which is the same distinction `_base.css:738` draws when it refuses to cap the
   desktop content column ("a reading measure is for PROSE. This is an operations console"). 820 is
   a reading measure and it is doing its job.

   Centring the sheet in the pane was considered and rejected without rendering: `_base.css:731`
   already argues that case — centring a column in the leftover space "detaches the content from the
   navigation it belongs to and leaves two uneven margins instead of one intentional one."

   THE WIDTH PROBLEM IS REAL, IT IS JUST ON THE OTHER SHELL. At tablet the sheet is 600px, i.e.
   220px BELOW its own measure, because the 264px rail and the 2.4rem gap take 302 of the 950
   available. Measured on the Priser sheet, the Moms row needs 339 (key) + 278 (control) + gap = 634
   against 600, so its control cluster wraps to a second line and left-aligns while every other row
   on the sheet stays right-aligned. Two fixes were built and rendered and BOTH ARE REJECTED, with
   the evidence, so they are not tried again:

     · rail 264 -> 232 and gap 2.4rem -> 1.5rem. Buys the sheet 70px, does not stop the wrap, and
       truncates the index: "Meddela kunder att bilen är klar" became "Meddela kunder att bilen är …"
       and "Business 1 Aut…" lost another character. The index is a status sheet — its values are
       the reason it is on screen — so this trades the thing that works for the thing that doesn't.
     · `.set-key { max-width: 30ch }` on the tablet shell. Does not stop the wrap either (the row
       clears by ~3px and still breaks), and costs two-line hints on half the rows.

   WHAT WOULD WORK, AND WHY I DID NOT SHIP IT: making the tablet a DRILL-DOWN, i.e. extending
   `SettingsDesktop.css:254`'s existing `@media (max-width: 1000px)` treatment to
   `.page[data-shell='tablet']`. Rendered, and it is clearly better for the sheet: the sheet reaches
   its full 820 measure, every row holds its line, Moms stops wrapping, and the "← Alla
   inställningar" return is already built and already touch-sized. The cost is that the index is no
   longer on screen beside the sheet, and on a bay tablet that index is a live status board
   (Bokningskapacitet 4, Fortnox Ansluten, Öppettider 09:00–17:00). That is a product call about what
   a tablet's settings page is FOR, not a styling call, so it is handed over rather than taken. The
   exact block that produced the rendered result:

       html[data-theme="light"] .page[data-shell='tablet'] .set-shell {
           grid-template-columns: minmax(0, 1fr);
           gap: 1rem;
       }
       html[data-theme="light"] .page[data-shell='tablet'] .set-rail { position: static; }
       html[data-theme="light"] .page[data-shell='tablet'] .set-shell[data-view='rail'] .set-pane {
           display: none;
       }
       html[data-theme="light"] .page[data-shell='tablet'] .set-shell[data-view='pane'] .set-rail {
           display: none;
       }
       html[data-theme="light"] .page[data-shell='tablet'] .set-back {
           display: inline-flex; align-items: center; gap: 0.45rem; margin-bottom: 0.85rem;
           padding: 0.4rem 0.2rem; border: none; background: none; font-family: inherit;
           font-size: var(--t-sm); font-weight: var(--fw-label); cursor: pointer;
       }

   (`data-view` is set by the component on every shell — only the CSS is width-gated — so the
   drill-down state machine already works at 1080. `.set-back`'s own styling lives inside the
   media query, which is why it has to be restated.)
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── 9.7  NO HOVER — AUDITED, AND THIS CLUSTER IS ALREADY CLEAN ────────────────────────────────
   NO RULE. Recorded so it is not re-audited.

   One hover-revealed affordance exists in the cluster: `.set-line-static .set-row-x` (the row's
   delete ×) sits at `opacity: 0` until `:hover` or `:focus-within`. It is already handled at source
   — `settingsTokens.ts:406` carries `@media (hover: none) { .set-page .set-line-static .set-row-x
   { opacity: 1 } }` — so it is visible at rest on both touch shells, in both themes. The
   app-wide auditor's `:hover`-reveal sweep reports nothing else on /settings or /message-drafts.

   `.md-text` / `.mdp-text` reveal a background on `:hover` and `:focus` only; the resting state is
   a readable field with a left rule, so nothing is hidden behind the hover. */


/* ── 9.8  NOT FIXED, AND WHY ───────────────────────────────────────────────────────────────────
   · A DIAGNOSIS OF MINE THAT WAS WRONG, recorded because the brief asks for these and because the
     next author will see the same picture. I filed "BROKEN IN BOTH THEMES — the draft message body
     is empty": `.md-text` rendered as an empty hairline stub in the light tablet capture, the light
     phone capture AND a THEME=dark control capture, on a page whose whole purpose is reading the
     message. Three shots, two themes, one conclusion — and it was wrong. `.md-text` is a
     `<textarea rows="1">` autosized from `scrollHeight` (MessageDraftsDesktop.vue:52, :107), and the
     fullPage capture described in §9.9 re-lays the page out AFTER autosize has run, collapsing every
     textarea to its unsized height. Captured with `fullPage: false`, all three drafts show their
     text at both shells ("Hej Thomas, vi har inte hört av oss på några dagar…"). Nothing is broken.
     The lesson is the one §9.9 states: a fullPage capture of an overflowing route is not evidence.
   · The /message-drafts masthead is a 48px title with an accent word and an uppercase tracked
     "ATT SKICKA" chip above it, unchanged at tablet and phone. That is the page's desktop design
     rendering on a touch shell; it is not a touch defect and the page has had no light design pass,
     so restyling it here would be inventing one inside a touch pass.
   · `--set-row-h` (52px, settingsTokens.ts:84) is not re-rung per shell. It is superseded by
     settings.css §6.4's `min-height: var(--row-h-comfy)` on `.set-row`, which the TOUCH LAYER
     already re-rings to 64px on tablet and 56px on phone — verified, `.set-row` probes at
     `min-height: 64px` on tablet. Nothing to do; noted because the 52px literal looks like an
     unhandled hard-coded row height and is not one.
   · No square icon button in this cluster fails on width. The cluster's only sub-floor target was
     §9.5's, and it failed on height. */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   9.9  HARNESS DEFECT — `screenshot.mjs` CANNOT SEE `@media (pointer: coarse)` ON A SCROLLING ROUTE
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   NO RULE. This is a note for the lead and for the other five authors, and it is the most expensive
   thing found this pass.

   `.claude/skills/run-app/screenshot.mjs` ends with `page.screenshot({ path: out, fullPage: true })`.
   When the DOCUMENT is taller than the emulated viewport, Chromium's fullPage path re-emulates the
   page at the full content height — and in that re-emulation the touch/mobile emulation the device
   descriptor set is not in force, so `@media (pointer: coarse)` STOPS MATCHING. The capture is of a
   fine-pointer render of a touch shell.

   PROVEN, not inferred, by bisection on /message-drafts at VIEWPORT=tablet:

     injected CSS                                                     result vs the plain capture
     html[data-theme="light"] .md-batch { background: red }           DIFFERENT   (rule applied)
     @media (pointer: coarse) { … .md-batch { background: red } }      BYTE-IDENTICAL (no-op)
     the same coarse rule with `fullPage: false`                      DIFFERENT   (rule applied)

   And by computed style: `probe.mjs`-style probing of the same route, same device profile, same
   injected file reports `.md-batch` at rgb(220,222,230) — the rule IS matching in the live page.
   Only the fullPage screenshot disagrees.

   WHY /settings HID IT, and why the phone shells hid it too: `SettingsDesktop.css:18` sets
   `.set-page { height: 100vh; overflow-y: auto }`, so /settings' document is exactly the viewport
   and fullPage takes the ordinary path. Every scrolling route — /message-drafts, and by inspection
   most boards — takes the broken one. So an author whose page happens to own its scroll will see
   coarse rules work, and an author whose page scrolls the document will see the identical rule do
   nothing, on the same shell, in the same session. That is a very good way to talk somebody out of
   a correct rule.

   WHAT TO DO UNTIL IT IS FIXED
     · Verify a coarse rule with a viewport-sized capture, not a fullPage one. A one-line change to
       `screenshot.mjs` is enough:
           await page.screenshot({ path: out, fullPage: !process.env.NO_FULLPAGE });
       and then `NO_FULLPAGE=1` on any capture that is meant to show a coarse rule.
     · `touch-audit.mjs` is NOT affected — it measures live boxes rather than a capture, so its
       "all clear" results stand.
     · A second, quieter casualty of the same path: JS-autosized textareas collapse in a fullPage
       capture (see §9.8). Any "the content is missing" finding taken off a fullPage shot of a
       scrolling route should be re-taken before it is believed.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ===============================================================================================
   THE MECHANIC CONFIRM DIALOG - the twin of .msch-modal, missed because it has a different prefix
   ===============================================================================================
   Section 8 above found `.msch-modal` (MechanicScheduleEditor.vue:527) and fixed it. It did not
   find `.mech-modal` (MechanicSettings.vue:812), which is the SAME thirteen declarations under a
   different class prefix - three of these dialogs mount from MechanicSettings alone (the issued-code
   sheet, revoke, and delete/delete-all). A grep for one prefix cannot see the other, which is why
   this one survived a pass that was explicitly looking for it.

   The faults are the flipping-primitive family (trap #3):

     background   linear-gradient(160deg, --c-blue-171a24, --c-blue-10131b)
                  = #d4ddee -> #d6dff1, a periwinkle slab where the design calls for white
     backdrop     rgba(var(--ch-blue-06080f), 0.72) = rgba(219,228,245,0.72), a veil that
                  BRIGHTENS the page behind it - a scrim exists to push the page back
     border       --set-rule-mid, plus a 0 24px 60px 55%-black drop: a border AND an elevation,
                  which section 5.2 allows exactly one of
     radius       12px against light's 4px ceiling

   `background`, not `background-color`: the source paints a gradient and naming only the colour
   would leave it rendering (trap #18). `background-image: none` is stated as well, matching how
   `.msch-modal` was written above, so the intent survives anyone later switching the shorthand.

   NOT scoped to `.set-page`, unlike `.msch-modal`. The `.mech-*` prefix is unique to
   MechanicSettings.vue, and scoping buys nothing while costing a match if the component is ever
   mounted outside the settings shell. `--glass-veil-4` is already `none` in light (app.css:2327),
   so the backdrop's blur is not in play; `backdrop-filter: none` is restated for the same
   defensive reason as `background-image`.
   =============================================================================================== */
html[data-theme="light"] .mech-backdrop {
    background: var(--scrim);
    backdrop-filter: none;
}
html[data-theme="light"] .mech-modal {
    background: var(--surface);
    background-image: none;
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-3);
}
