/* Light-only overrides — money 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. The sheet ═════════════════════════════════════════════════════════════════════════════════
   The single most valuable correction in this cluster, and the reason the invoice did not read as a
   document.

   `--fin-sheet` is `rgba(var(--sf-rgb), 0.032)`. `--sf-rgb` is the surface channel and correctly
   inverts, so in light that is the INK at 3.2% — it composites to #e9e8ef on a #f0eff5 ground:

       sheet #e9e8ef vs ground #f0eff5  =  1.065 : 1        the plane is DARKER than the page
       edge  (--fin-sheet-edge, ink @ .10) vs sheet = 1.09 : 1   the border is not there

   So every .fx-card, every .tile, every metric card and the whole invoice document were a faint grey
   rectangle with no visible boundary — text floating on the page rather than sitting on a plane. A
   light sheet has to be LIGHTER than the ground with an edge that can be seen; that is what candidate
   C is built on, and it is available here because light has headroom upward from a tinted ground where
   dark has headroom upward from black.

       sheet var(--surface-2) #fdfcfe vs ground #f0eff5  =  1.117 : 1   and the direction is right
       edge  var(--border-default) composites to #dedee2 on the sheet — a real hairline

   .032 lands on no rung, and the rulebook forbids rounding a token because rounding moves dark. It
   does not apply here: this file cannot match in dark, so the dark .032 is untouched and light is free
   to take the light value that works.

   Elevation is added below, per §9: the ladder above --surface-3 has no lightness headroom left, so a
   light card separates by shadow and border rather than by being lighter still. */
html[data-theme="light"] .fin-page,
html[data-theme="light"] .invq-wrapper,
html[data-theme="light"] .invd-wrapper,
html[data-theme="light"] .an-page,
html[data-theme="light"] .cal-page,
html[data-theme="light"] .deck-wrapper {
    --fin-sheet: var(--surface-2);
    --fin-sheet-edge: var(--border-default);

    /* ── The well ──
       A meter track, a chart plot, a donut ring's groove. Dark is `rgba(var(--sh-rgb), 0.22)`, i.e.
       black at 22% — a real recess. Light's half is `rgba(76, 29, 149, 0.06)`, which on a white card
       is nothing at all, and it is why the Verkstad utilisation meter draws a 97% row and a 131% row
       identically: there is no track behind either fill. --well-a22 is the SAME dark value and a real
       light recess (#d7d5e0), so the track exists in both themes. */
    --fin-well: var(--well-a22);

    /* ── Accent, by role ──
       Light resolves --fin-accent through the codemod's violet neutral to #5d00c3 — HSL saturation
       1.00, and not the accent this theme was re-derived to on 2026-08-10. --fin-accent-deep resolves
       to #6d28d9, which is the accent as it stood BEFORE that re-derivation. Both are repointed at the
       tokens that carry the current value, so the primary action, the focus ring and the accent ink on
       these six pages are one colour again instead of three near-misses. */
    --fin-accent: var(--accent-solid);
    --fin-accent-deep: var(--accent-solid);

    /* Chart series identity IS legitimately an accent — the exception financeTokens' own series rule
       names — so the primary series is repointed rather than neutralised, and its wash follows it. */
    --fin-series: var(--accent-solid);
    /* Composed from the accent rather than restated as an rgba literal: there is no accent CHANNEL
       token to feed rgba() with, and a hand-typed 108,60,184 is exactly the copy that goes stale the
       next time the accent is re-derived — which has already happened once this week. */
    --fin-series-soft: rgba(var(--accent-rgb), 0.13);
    --fin-fill-primary: rgba(var(--accent-rgb), 0.20);
    /* The second series is a step of the INK ramp, which is right — but .46 of the light ink over a
       #fdfcfe card measures 2.85:1, under the 3:1 floor for a non-text mark, and it is drawn as whole
       distribution bars on Analys ("Säsongen framför er") and as the funnel's declined segment. .55
       measures 3.71:1. The ramp step, not the mechanism, was wrong. */
    --fin-series-2: rgba(var(--fin-ink-rgb), 0.55);
    /* The tail slice and the 0-30 day ageing band. #7b7686 measures 3.34:1 on the worst light card —
       it clears the mark floor but only just, and it is a legend key as well as a segment. */
    --fin-series-other: #6b6478;

    /* The donut ramp has MOVED to app.css, declared in both theme blocks beside the dark five it was
       always meant to sit next to. It lived here as five colour literals in a cluster file — which
       the audit flagged, correctly — only because the dark half had never been declared anywhere at
       all: DonutChart.vue carried it as var() fallbacks, which is why the ramp could not invert and
       step 1, the LARGEST slice, measured 1.05:1 in light. */
}

/* Every surface carrying an elevation must also carry a border (§9), and each of these already takes
   --fin-sheet-edge above. The shadow is what turns "slightly lighter rectangle" into a plane. */
html[data-theme="light"] .fin-page .fx-card,
html[data-theme="light"] .an-page .fx-card,
html[data-theme="light"] .cal-page .fx-card,
html[data-theme="light"] .invq-wrapper .fx-card,
html[data-theme="light"] .an-page .an-state,
html[data-theme="light"] .invd-wrapper .invd-doc,
html[data-theme="light"] .tile,
html[data-theme="light"] .mc {
    box-shadow: var(--elevation-1);
}


/* ══ 2. Accent discipline ═════════════════════════════════════════════════════════════════════════
   Violet earns the primary action, the focus ring, the selection and the logo. Every rule below moves
   one violet off a role it had not earned. */

/* ── The status dot that is not a status ──
   StatTile paints its dot from the tone. On /invoices that puts a VIOLET dot fourth in a row of
   amber / green / red status dots ("SKICKAT SENASTE TIDEN"), and on Ekonomi's Översikt it does the
   same beside OFAKTURERAT and RESULTAT. Violet is the interaction colour on these pages; it cannot
   also be a state, or the row has four dots and only three of them mean anything. --st-info is the
   neutral notice. The tile's sparkline keeps the accent — that is a series, and a series identity is
   allowed to be one. */
html[data-theme="light"] .tile.primary .tile-dot {
    background: var(--st-info);
}

/* ── Selection is never tint alone (§3) ──
   These are selected filter states drawn as a lavender wash. At light-theme alpha a wash reads weaker
   than dark's UNSELECTED state, so the 3px leading bar is what actually carries the selection through
   glare and through colour-blindness. */
html[data-theme="light"] .chip.active,
html[data-theme="light"] .aging-cell.active {
    background: var(--surface-selected);
    border-color: var(--accent-hairline);
    box-shadow: inset 3px 0 0 var(--accent-bar);
}

/* Hover on a row goes DARKER in light — there is no headroom above white — and the violet tint it
   used instead is a decorative accent on the commonest interaction in the cluster.
   Every one of these rows sits on a --surface-2 sheet, so the step is --surface-2-hover, not the
   --surface-1 rung: a hover has to be one step below the plane it is actually on. */
html[data-theme="light"] .invq-row:hover,
html[data-theme="light"] .led-row:hover,
html[data-theme="light"] .todo-row:hover,
html[data-theme="light"] .invd-line.reading:hover {
    background: var(--surface-2-hover);
}
/* An opened row is held, not hovered — one rung further down so it stays distinct while the pointer
   moves over its neighbours. */
html[data-theme="light"] .led-row.open {
    background: var(--surface-hover);
}
html[data-theme="light"] .invq-row:focus-visible,
html[data-theme="light"] .invd-line.reading:focus-visible {
    background: var(--surface-active);
}
/* The opened ledger row's own cell carried a violet tint in light. It is not the surface — the .detail
   panel inside it is, and that panel is a --well-a18 recess. Tinting the cell too puts a second plane
   between the card and the well, and the three of them land within 1.05:1 of each other. The cell goes
   back to the card it sits on, so the recess has something to be recessed FROM. */
html[data-theme="light"] .detail-row td {
    background: transparent;
}

/* ── Focus is --focus-ring, and a border that only says "focused" is not an accent ── */
html[data-theme="light"] .invq-find input:focus,
html[data-theme="light"] .fin-range input:focus,
html[data-theme="light"] .search input:focus,
html[data-theme="light"] .invd-lookup-input:focus,
html[data-theme="light"] .invd-disc-input:focus,
html[data-theme="light"] .cell-desc:focus,
html[data-theme="light"] .cell input:focus,
html[data-theme="light"] .cell select:focus,
html[data-theme="light"] .invd-field input:focus {
    border-color: var(--focus-ring);
}

/* ── Secondary is not violet (§5) ──
   Four controls in the invoice editor are secondary actions wearing an accent tint and accent ink:
   add a line, apply a discount preset, run the org-number lookup. A secondary button is a neutral
   surface, a default border and body ink.

   DELETED 2026-08-11 from this group and four others below: every `.invd-assist-*` selector. The
   assist row was removed from `InvoiceDraftDetailDesktop.vue` — the names survive only in that
   page's own CSS, appear in no template under `src/`, and are not assembled at runtime. */
html[data-theme="light"] .invd-add,
html[data-theme="light"] .invd-disc-chip,
html[data-theme="light"] .invd-lookup-go {
    color: var(--text);
    background: var(--surface-3);
    border: 1px solid var(--border-default);
}
html[data-theme="light"] .invd-add:hover,
html[data-theme="light"] .invd-disc-chip:hover,
html[data-theme="light"] .invd-lookup-go:hover:not(:disabled),
html[data-theme="light"] .invd-add.open {
    background: var(--surface-3-hover);
    border-color: var(--border-strong);
}

/* ── The primary action is a flat accent fill, never a gradient (§2, §5) ──
   Both of these are `linear-gradient(135deg, #6d28d9, #6c27d8)` in light: two shades of the PREVIOUS
   accent, and a gradient on the control that books a legal invoice. `color: var(--ink)` on them is the
   trap .invd-btn.go already documents — the ink token flips, so light painted near-black text on a
   deep violet fill. */
html[data-theme="light"] .invd-disc-go {
    background: var(--accent-solid);
    color: var(--text-on-accent);
}
html[data-theme="light"] .invd-disc-go:hover:not(:disabled),
html[data-theme="light"] .invd-btn.go:hover:not(:disabled),
html[data-theme="light"] .fx-btn.go:hover {
    background: var(--accent-solid-hover);
}
html[data-theme="light"] .invd-btn.go,
html[data-theme="light"] .fx-btn.go {
    color: var(--text-on-accent);
    border-color: var(--accent-solid);
}

/* Decorative violet, on furniture that is not interactive: a scrollbar thumb and the panels the
   assistant and the discount picker sit in. */
html[data-theme="light"] .fin-page {
    scrollbar-color: var(--border-strong) transparent;
}
html[data-theme="light"] .fin-page::-webkit-scrollbar-thumb {
    background: var(--border-strong);
}
html[data-theme="light"] .invd-discount {
    background: var(--surface-sunken);
    border-color: var(--border-subtle);
}

/* The preview switch reads "on" through an accent-tinted track. A track is a control part, not an
   accent surface (§4) — the KNOB and the fill carry the state. */
html[data-theme="light"] .an-demo-toggle.on {
    border-color: var(--accent-solid);
}
html[data-theme="light"] .an-demo-toggle.on .an-demo-track {
    background: var(--accent-solid);
}
html[data-theme="light"] .invq-railtab.active .invq-railcount,
html[data-theme="light"] .mc.pinnedOn {
    border-color: var(--accent-solid);
}


/* ══ 3. Slabs, tracks and one broken hatch ════════════════════════════════════════════════════════
   `rgba(var(--ref-shadow-base), α)` used as a SURFACE is a shadow instruction, not a colour: in light
   the base flips to the ink and the "shadow" becomes a mid-grey slab sitting on a near-white card.
   The one occurrence in this cluster (FinanceInvoicing's opened row) was fixed at source — it is
   dark-identical to --well-a18 — so nothing about it needs to live here. --fin-well above covers every
   track, plot and groove that reaches for a recess through financeTokens.

   What remains is the revenue-mix and repeat-flow hatch. Its base resolves to #8b53ff in light — a
   brighter violet than the accent it sits beside — and its hatch lines to #8b5cf6 over it, so the
   pattern that is supposed to be the non-colour carrier of "parts" has almost no contrast against its
   own base. Pale base, accent hatch: the stripes read, and the segment cannot be confused with the
   solid labour bar next to it. */
html[data-theme="light"] .mix-parts,
html[data-theme="light"] .key.parts,
html[data-theme="light"] .cu-mix-new,
html[data-theme="light"] .cu-key.new {
    background-color: rgba(var(--accent-rgb), 0.22);
    background-image: repeating-linear-gradient(-45deg,
        var(--accent-solid) 0 3px,
        transparent 3px 7px);
}
/* The funnel's "still open" segment is drawn outlined rather than filled, because it has not happened
   yet — so the OUTLINE is the mark, and in light it resolved to the saturated codemod violet at 50%
   (~3.0:1 on a white card, i.e. exactly on the floor for a 1px rule). The accent is 6.6:1 and is the
   same colour the won segment beside it is filled with, which is the point: one hue, two treatments. */
html[data-theme="light"] .fn-seg.open,
html[data-theme="light"] .fn-key.open {
    background: rgba(var(--accent-rgb), 0.14);
    box-shadow: inset 0 0 0 1px var(--accent-solid);
}

html[data-theme="light"] .mix-labour,
html[data-theme="light"] .key.labour,
html[data-theme="light"] .cu-mix-ret,
html[data-theme="light"] .cu-key.returning {
    background: var(--accent-solid);
}

/* The donut's groove is the one place --fin-well sits directly BEHIND the ramp rather than behind a
   solid fill, and the ramp's darkest-to-lightest walk ends at #8e66f1 — 2.72:1 against the #d7d5e0
   well, under the mark floor, on the smallest slice. One step up the recess ladder puts it at 3.36:1
   and still reads as a groove inside the card. */
html[data-theme="light"] .dn-track {
    stroke: var(--recess-a05);
}

/* A search field is a FIELD, not a track. Both of these paint themselves from --fin-well, which is now
   a real recess — and --fin-ink-4 placeholder text on it lands at 4.12:1. On --surface-1 the same text
   clears AA, and the field stops reading as a disabled control. */
html[data-theme="light"] .ms-field,
html[data-theme="light"] .an-range input {
    background: var(--surface-1);
}

/* The metric search's clear button. `filter: invert(1)` was written for a white UA glyph on a dark
   field; in light it inverts a dark glyph to a black block on a pale one — the black square sitting in
   the search box on every Analys tab. Nothing to invert on this side. */
html[data-theme="light"] .ms-field input::-webkit-search-cancel-button {
    filter: opacity(0.45);
}

/* The `/` hint in the metric search reads as a solid black square on every Analys tab, and it is not a
   theme bug: vendor Bootstrap styles the bare `kbd` element with `background-color: var(--bs-body-color)`
   — near-black — and .ms-kbd never declares a background, so nothing overrides it. On the dark ground
   the same block is invisible and the defect has never been seen. Light only, because naming a
   background on .ms-kbd itself would change the resolved dark value. */
html[data-theme="light"] .ms-kbd {
    background: none;
}

/* The action bar is fixed chrome over a scrolling ledger, and deliberately opaque. Its dark value is a
   near-black; light's half of that channel is a lavender band across the bottom of the document. */
html[data-theme="light"] .invd-bar {
    background: var(--surface-3);
    border-top: 1px solid var(--border-default);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   4. STRUCTURE — the light money surface is a statement, not a dashboard
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Everything above this line is colour. Everything below is GEOMETRY: type scale, alignment, row
   height, divider, badge shape. Light is a different design on the same DOM, and on the money
   surfaces the difference is mostly typographic — this is the most number-dense cluster in the app
   and the only one where a misaligned decimal is a defect rather than a blemish.

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


/* ── 4.1 The --fin-* ladder, folded onto the main ramp ────────────────────────────────────────
   financeTokens.ts declares its own type scale (`src/lib/financeTokens.ts:104-121`) and ~250 sites
   read it. In dark that is a second, wider ramp and it is deliberate: the statement surface wants a
   hero figure. In light it is a second ramp FIGHTING the first — --fin-body 13.9px against --t-body
   13px, --fin-small 12.4px against --t-sm 13px and --t-xs 12px, --fin-label 11px against --t-micro
   11px. Three near-misses of the same step is what makes a page read as texture rather than as
   hierarchy.

   Folding at the TOKEN, not at the 250 sites, is the whole leverage: eight declarations re-scale
   every consumer, and dark keeps its own ladder byte for byte because this block cannot match there.

   The fold, and what each rung lands on:
       --fin-hero     clamp(38.4-54.4px)  →  --t-display  20px   (0 consumers — folded for hygiene)
       --fin-display  clamp(27.2-36px)    →  --t-h1       20px   (3: page titles)
       --fin-fig-xl   24px                →  --t-h1       20px   (1: the invoice grand total)
       --fin-fig-lg   18.9px              →  --t-h2       14px   (7: band totals, the bar total)
       --fin-fig      16px                →  --t-body     13px   (30: a row's own money)
       --fin-body     13.9px              →  --t-body     13px   (55: cells, prose)
       --fin-small    12.4px              →  --t-xs       12px   (156: meta, the workhorse)
       --fin-label    11px                →  --t-micro    11px   (50: column heads, eyebrows)

   Two rungs (hero/display, display/fig-xl) collapse onto one value. That is reported rather than
   worked around: light's ramp has exactly one step above --t-h2, by design, and inventing a rung
   between them would be inventing a token.

   WEIGHTS: there are none to fold. The --fin-* ladder carries no weight token — its sites read the
   app's own --fw-650 / --fw-700 rungs directly, and the light block already pins both to 600
   (`app.css:487-488`). The weight half of this package was delivered by that pinning; nothing here
   needs to restate it. */
html[data-theme="light"] .fin-page,
html[data-theme="light"] .invq-wrapper,
html[data-theme="light"] .invd-wrapper,
html[data-theme="light"] .an-page,
html[data-theme="light"] .cal-page,
html[data-theme="light"] .deck-wrapper {
    --fin-hero: var(--t-display);
    --fin-display: var(--t-h1);
    --fin-fig-xl: var(--t-h1);
    --fin-fig-lg: var(--t-h2);
    --fin-fig: var(--t-body);
    --fin-body: var(--t-body);
    --fin-small: var(--t-xs);
    --fin-label: var(--t-micro);

    /* The eyebrow track. 0.135em 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. */
    --fin-track: var(--eyebrow-track);

    /* Geometry. --fin-r/--fin-r-sm are a private radius pair that predates --r-card/--r-control and
       sits two steps above them in light (10/6 against 6/4). A card and a control on this surface
       round like every other card and control in the theme. */
    --fin-r: var(--r-card);
    --fin-r-sm: var(--r-control);

    /* Horizontal padding is constant across the theme; --fin-pad's 1.55rem (24.8px) is the widest
       gutter in the app and it is what makes the money cards read as marketing panels. */
    --fin-pad: var(--row-pad-x);
}

/* The three page titles are raw clamps rather than tokens (`FinanceDesktop.css:90`,
   `InvoicesDesktop.css:61`, `InvoiceDraftDetailDesktop.css:632`), so the fold above cannot reach
   them. A page title in light is --t-h1 at body weight — §8 of the spec puts page titles on
   --fw-body deliberately: the title is the largest thing on screen and does not also need to be the
   heaviest. */
html[data-theme="light"] .fin-page .fin-head h1,
html[data-theme="light"] .invq-wrapper .invq-head h1,
html[data-theme="light"] .invd-wrapper .invd-header h1 {
    font-size: var(--t-h1);
    font-weight: var(--fw-body);
    letter-spacing: -0.01em;
}


/* ── 4.2 The eyebrow ──────────────────────────────────────────────────────────────────────────
   Five spellings of the same object across this cluster, each with its own size, its own tracking
   and (for three of them) an accent colour. The eyebrow is 11px uppercase, tracked at
   --eyebrow-track, in --ink-muted, and it is never coloured: a coloured section label on white is a
   highlighter mark, and it spends the accent budget on something that is not an action.

   .invq-cols and .led th are the same object doing a column-header job, so they take the same
   treatment and stay on the same rhythm as the rows underneath them. */
html[data-theme="light"] .fin-page .fin-eyebrow,
html[data-theme="light"] .invq-wrapper .invq-eyebrow,
html[data-theme="light"] .fin-page .fx-label,
html[data-theme="light"] .an-page .fx-label,
html[data-theme="light"] .cal-page .fx-label,
html[data-theme="light"] .invd-wrapper .invd-doc-label,
html[data-theme="light"] .invd-wrapper .invd-bar-label,
html[data-theme="light"] .fin-page .aging-label,
html[data-theme="light"] .invq-wrapper .invq-cols,
html[data-theme="light"] .fin-page .led th,
html[data-theme="light"] .an-page .led th,
html[data-theme="light"] .fin-page .dm-led th,
html[data-theme="light"] .an-page .dm-led th {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    color: var(--ink-muted);
}


/* ── 4.3 Figures — tabular, and right-aligned where they are compared down a column ───────────
   _base.css already does this for .fig / .money / .amount / .num. Every class below is a figure on
   this surface under some other name; the selector list is extended rather than the recipe
   re-written.

   The split is deliberate. TABULAR is for anything that is a number: it stops "1" and "7" from
   changing a row's width as the data updates. RIGHT-ALIGNED is only for a number that sits in a
   column above another number — a decimal edge is the single strongest reading aid on a statement,
   and right-aligning a figure that stands alone in a flex row just pushes it away from its label. */
html[data-theme="light"] .invq-wrapper .invq-amt,
html[data-theme="light"] .invq-wrapper .invq-num,
html[data-theme="light"] .invq-wrapper .invq-band-sum,
html[data-theme="light"] .invq-wrapper .invq-railcount,
html[data-theme="light"] .invd-wrapper .invd-bar-total,
html[data-theme="light"] .invd-wrapper .invd-group-sum,
html[data-theme="light"] .invd-wrapper .invd-group-hours,
html[data-theme="light"] .invd-wrapper .invd-date,
html[data-theme="light"] .invd-wrapper .cell-amt,
html[data-theme="light"] .invd-wrapper .trow td,
html[data-theme="light"] .fin-page .head-fig,
html[data-theme="light"] .fin-page .gross-fig,
html[data-theme="light"] .fin-page .gross-value,
html[data-theme="light"] .fin-page .won-fig,
html[data-theme="light"] .fin-page .won-money,
html[data-theme="light"] .fin-page .todo-amt,
html[data-theme="light"] .fin-page .aging-fig,
html[data-theme="light"] .fin-page .aging-total,
html[data-theme="light"] .fin-page .aging-count,
html[data-theme="light"] .fin-page .sent-total,
html[data-theme="light"] .fin-page .rate-pct,
html[data-theme="light"] .fin-page .pace-rate,
html[data-theme="light"] .fin-page .led td,
html[data-theme="light"] .an-page .led td,
html[data-theme="light"] .an-page .dm-led td,
html[data-theme="light"] .an-page .cu-head-fig,
html[data-theme="light"] .an-page .cu-stage-val,
html[data-theme="light"] .an-page .dm-head-fig,
html[data-theme="light"] .an-page .dm-ta-amount,
html[data-theme="light"] .an-page .fl-flow-val,
html[data-theme="light"] .an-page .rb-value,
html[data-theme="light"] .an-page .ms-w-val,
html[data-theme="light"] .an-page .fn-amount {
    font-variant-numeric: tabular-nums lining-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
}

/* The columns where a decimal edge exists. .invq-amt already sits in a grid cell whose `.end` class
   pushes it right; stating text-align as well is what makes the DIGITS line up rather than the box.
   `.led td.num` and `.trow td` are table cells, where the alignment is the whole contract. */
html[data-theme="light"] .invq-wrapper .invq-amt,
html[data-theme="light"] .invd-wrapper .cell-amt,
html[data-theme="light"] .invd-wrapper .trow td + td,
html[data-theme="light"] .invd-wrapper .invd-group-sum,
html[data-theme="light"] .fin-page .led td.num,
html[data-theme="light"] .an-page .led td.num,
html[data-theme="light"] .an-page .dm-led td.num,
html[data-theme="light"] .fin-page .led th.num,
html[data-theme="light"] .an-page .led th.num,
html[data-theme="light"] .an-page .dm-led th.num {
    text-align: right;
}


/* ── 4.4 Rows and tables ──────────────────────────────────────────────────────────────────────
   Heights are AUTHORED and vertical padding is DERIVED. The ledger row was authored the other way
   round — `min-height: 52px` with `padding: 0.6rem 0` — which is how this cluster ended up with a
   52px row on the invoice queue, a 49px row in the .led table (0.85rem × 2 + line box) and a 44px
   row on the overview to-do list: three lists on two pages, none of them agreeing.

   --row-h-comfy (48px) rather than --row-h (40px): every row here carries a two-line identity cell
   (buyer over document number), and 40px clips the descenders of the second line.

   The divider is one hairline. No zebra — settled decision; the divider does the work, and on a
   money surface a striped background is one more thing competing with the amber "unbilled" tint.

   Derived vertical padding is exempt from the spacing scale, and is labelled so here per §11. */
html[data-theme="light"] .invq-wrapper .invq-row {
    min-height: var(--row-h-comfy);
    padding: calc((var(--row-h-comfy) - 34px) / 2) 0;   /* derived — do not round to a scale step */
    border-bottom: 1px solid var(--keyline);
}
html[data-theme="light"] .invq-wrapper .invq-cols {
    padding: 0 0 8px;
    border-bottom: 1px solid var(--keyline-strong);
}

/* The .led table, on all four of its hosts. A table cell's padding is its row height, so the
   vertical value is derived from --row-h and the horizontal one holds at --row-pad-x — a row that
   gets shorter must not also get narrower, or the columns stop agreeing between the tables. */
html[data-theme="light"] .fin-page .led td,
html[data-theme="light"] .an-page .led td,
html[data-theme="light"] .an-page .dm-led td {
    padding: calc((var(--row-h) - 20px) / 2) var(--row-pad-x);  /* derived — not a scale step */
    border-bottom: 1px solid var(--keyline);
}
html[data-theme="light"] .fin-page .led th,
html[data-theme="light"] .an-page .led th,
html[data-theme="light"] .an-page .dm-led th {
    padding: 0 var(--row-pad-x) 8px;
    border-bottom: 1px solid var(--keyline-strong);
}
/* The first and last cells sit flush with the card's own gutter, so the table reads as ruled
   columns inside the sheet rather than as a box inset within a box. */
html[data-theme="light"] .fin-page .led td:first-child,
html[data-theme="light"] .fin-page .led th:first-child,
html[data-theme="light"] .an-page .led td:first-child,
html[data-theme="light"] .an-page .led th:first-child { padding-left: 0; }
html[data-theme="light"] .fin-page .led td:last-child,
html[data-theme="light"] .fin-page .led th:last-child,
html[data-theme="light"] .an-page .led td:last-child,
html[data-theme="light"] .an-page .led th:last-child { padding-right: 0; }

/* The overview's two lists, on the same rhythm as everything else. */
html[data-theme="light"] .fin-page .todo-row {
    min-height: var(--row-h);
    padding: calc((var(--row-h) - 20px) / 2) 0;         /* derived — not a scale step */
    border-bottom: 1px solid var(--keyline);
}
html[data-theme="light"] .fin-page .lad-row {
    min-height: var(--row-h-compact);
    padding: calc((var(--row-h-compact) - 18px) / 2) 0; /* derived — not a scale step */
    border-bottom: 1px solid var(--keyline);
}
html[data-theme="light"] .fin-page .lad-row:last-child,
html[data-theme="light"] .fin-page .todo-row:last-child { border-bottom: 0; }

/* The card. --fin-pad now carries the gutter, so the only thing left to state is that the vertical
   padding matches it rather than being three separate hand-tuned values (1.35 / 1.5 / 1.45rem). */
html[data-theme="light"] .fin-page .fx-card,
html[data-theme="light"] .an-page .fx-card,
html[data-theme="light"] .cal-page .fx-card {
    padding: var(--fin-pad);
}


/* ── 4.5 Status is a badge, not coloured text ─────────────────────────────────────────────────
   This is the clearest case in the app: `.invq-pay-state.paid` is green TEXT, `.overdue` is red
   text at a heavier weight, `.unpaid` is grey text — three words in a column, told apart by hue
   alone, read by a trade where roughly 1 in 12 people cannot separate the first two. Contrast is
   not the failure; colour vision is. (`InvoicesDesktop.css:252-254`.)

   The recipe is _base.css §1 — fill + tint border + dark ink + a shape-distinct ::before. The
   selector list is extended here rather than the recipe duplicated; the glyphs are the one thing
   that must be restated, because _base keys them off .is-ok/.is-bad/.is-warn class names these
   elements do not carry, and a badge with no icon is exactly the failure the icon exists to stop.

   Fill/border/ink come from the --st-* triads, which are declared in both themes with the light
   half already measured (`app.css:438-450`) — so no literal is written and no token is invented. */
html[data-theme="light"] .invq-wrapper .invq-pay-state,
html[data-theme="light"] .invq-wrapper .invq-tag,
html[data-theme="light"] .invd-wrapper .badge,
html[data-theme="light"] .invd-wrapper .invd-addition-tag,
html[data-theme="light"] .invd-wrapper .invd-status {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border-radius: 4px;              /* a rectangle, not a pill */
    padding: 2px 6px;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    line-height: 1.4;
    letter-spacing: 0;
    text-transform: none;
    background: var(--surface-inset);
    border: 1px solid var(--keyline);
    color: var(--ink-dim);
}

/* The mandatory second carrier. Shape-distinct — check / cross / bang / dot — never four coloured
   dots, which would reintroduce the exact failure. Generated content is not in the accessibility
   tree, so the word keeps sole ownership of the accessible name. */
html[data-theme="light"] .invq-wrapper .invq-pay-state::before,
html[data-theme="light"] .invq-wrapper .invq-tag::before,
html[data-theme="light"] .invd-wrapper .badge::before,
html[data-theme="light"] .invd-wrapper .invd-addition-tag::before,
html[data-theme="light"] .invd-wrapper .invd-status::before {
    content: "•";
    font-size: 1em;
    line-height: 1;
}

html[data-theme="light"] .invq-wrapper .invq-pay-state.paid,
html[data-theme="light"] .invd-wrapper .badge.sent,
html[data-theme="light"] .invd-wrapper .invd-status.ok {
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .invq-wrapper .invq-pay-state.paid::before,
html[data-theme="light"] .invd-wrapper .badge.sent::before,
html[data-theme="light"] .invd-wrapper .invd-status.ok::before { content: "✓"; }

/* `.badge.sent` KEEPS THE CHIP — the one bare-`.badge` in the app that is a real status.
   _base.css §1 now flattens `.badge` to bare text, because every other bare `.badge` in the source
   is a page eyebrow. This one is not: it reports whether the draft has been sent or voided
   (InvoiceDraftDetailDesktop.vue:17), which is exactly what the status-badge recipe is for. The
   rule above already wins the FILL on specificity ((0,4,0) against (0,2,0)) — but a superseding
   rule only overrides the properties it names, so without these four the chip would have kept its
   green ground and lost the padding, border and radius that make it a chip: a green wash behind a
   word, floating on the header with no edge. `display: inline-flex` restores the glyph gap the
   flatten's `inline-block` would have collapsed.

   NOT VERIFIED ON A LIVE RENDER: this tenant has 0 sent drafts ("Skickade 0" on /invoices), so the
   class cannot be produced. It is a restoration of the values the shared badge layer already gives
   every other status badge, not a new treatment. */
html[data-theme="light"] .invd-wrapper .badge.sent {
    display: inline-flex;
    padding: 2px 6px;
    /* border-WIDTH and -STYLE only, never the `border` shorthand: this rule sits BELOW the fill rule
       at the same (0,4,0), so a shorthand would reset `border-color` to its initial value and throw
       away the `--st-logged-line` the rule above just set. */
    border-width: 1px;
    border-style: solid;
    border-radius: 4px;
    text-transform: none;
}

html[data-theme="light"] .invq-wrapper .invq-pay-state.overdue,
html[data-theme="light"] .invq-wrapper .invq-tag.void,
html[data-theme="light"] .invd-wrapper .invd-status.error {
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    color: var(--st-breach);
    font-weight: var(--fw-label);
}
html[data-theme="light"] .invq-wrapper .invq-pay-state.overdue::before,
html[data-theme="light"] .invq-wrapper .invq-tag.void::before,
html[data-theme="light"] .invd-wrapper .invd-status.error::before { content: "✕"; }

/* "Unpaid" is not a fault — the invoice is simply still out. Neutral fill, neutral ink, and the dot
   the base recipe gives it, so it reads as the third member of the set rather than as an absence. */
html[data-theme="light"] .invq-wrapper .invq-pay-state.unpaid {
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-dim);
}

/* "Tillägg" — a line added mid-job. It is a warning-tier fact on the invoice (it is what the
   customer did not originally approve), and it was violet text, i.e. the interaction colour. */
html[data-theme="light"] .invd-wrapper .invd-addition-tag {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}
html[data-theme="light"] .invd-wrapper .invd-addition-tag::before { content: "!"; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   5. THE SECOND PASS — what §4 folded at the type scale, folded at every other private family
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   §4.1 folded the eight TYPE rungs. financeTokens declares four more private families on the same
   six wrappers — an ink ramp, a rule ladder, a status triad and a radius pair — and each of them is
   a near-miss of a system family rather than a different idea:

       --fin-ink / -2 / -3 / -4   four steps of rgba(27,35,61, .82/.72/.63)  vs the navy ink ladder
       --fin-rule / -mid / -strong  three alphas of the INK channel           vs --keyline(-strong)
       --fin-pos / -neg / -warn     #047857 / #9e0000 / #8a5500              vs the --st-* triad

   Two of the three status values are already byte-identical to their --st-* twin in light, which is
   the tell: they were re-derived to the same measurement independently. Pointing them at the token
   means the WORD on a row and the BADGE beside it can never drift again — today they only agree by
   coincidence.

   The ink fold is the one with a design decision inside it: light collapses four steps to three.
   That is the spec's own §3 rule (`--ink-faint` aliases `--ink-muted`; light has no sub-AA
   decorative rung), and --fin-ink-4 at .63 is exactly such a rung — it carries every column head and
   unit note on four money pages at ~4.9:1, under --ink-muted's 5.08:1.

   --fin-ink-rgb itself is NOT folded and must not be: it is a CHANNEL, consumed inside rgba() by
   --fin-series-2 and --fin-fill-muted above, and a channel cannot hold a hex. */
html[data-theme="light"] .fin-page,
html[data-theme="light"] .invq-wrapper,
html[data-theme="light"] .invd-wrapper,
html[data-theme="light"] .an-page,
html[data-theme="light"] .cal-page,
html[data-theme="light"] .deck-wrapper {
    --fin-ink: var(--ink);
    --fin-ink-2: var(--ink-dim);
    --fin-ink-3: var(--ink-muted);
    --fin-ink-4: var(--ink-muted);

    /* A hairline that divides is --keyline; a hairline that BOUNDS something (a control's border, a
       column head's underline, a double rule over a total) is --keyline-strong. The old ladder split
       at the wrong place: -mid was doing both jobs. */
    --fin-rule: var(--keyline);
    --fin-rule-mid: var(--keyline-strong);
    --fin-rule-strong: var(--keyline-strong);

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


/* ── 5.1 The page eyebrow is not a status badge ───────────────────────────────────────────────
   A CORRECTION to §4.5. `.badge` on the invoice draft is TWO different objects in one class name:

       InvoiceDraftDetailDesktop.vue:156   <span class="badge">FAKTURAUNDERLAG</span>   — a page eyebrow
       InvoiceDraftDetailDesktop.vue:17    <span class="badge sent">SKICKAD</span>      — a real status

   §4.5 gave both the badge shape and a leading "•", so the released document read "• FAKTURA-
   UNDERLAG". That is the exact failure _base.css §1 records from the Work Queue prototype ("•
   ARBETSKÖ") — the reason the glyph there is attached only to explicit status classes.

   So the bare badge goes back to being the eyebrow the page sheet designed, and only `.sent` keeps
   the badge grammar. The glyph selector below re-states §4.5's list minus the bare `.badge`; the
   later rule wins on document order at equal specificity. */
html[data-theme="light"] .invd-wrapper .invd-header > .badge:not(.sent) {
    display: inline;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    color: var(--ink-muted);
}

html[data-theme="light"] .invd-wrapper .invd-header > .badge:not(.sent)::before {
    content: none;
}


/* ── 5.2 Figures, second pass ─────────────────────────────────────────────────────────────────
   Every class below was read out of the sheet that declares it, not guessed. They are the figures
   §4.3 did not reach: the tile block (the four facts at the top of three tabs), the overview's
   result ladder, the revenue mix, the workshop rate column and the invoice's grouped subtotals.

   The right-alignment list is separate and shorter, on the same rule as §4.3: a figure is
   right-aligned only where another figure sits directly above or below it. `.lad-row dd` and
   `.mix-legend dd` are the two `<dl>` columns on Ekonomi; both stack. */
html[data-theme="light"] .fin-page .tile-value,
html[data-theme="light"] .an-page .tile-value,
html[data-theme="light"] .invq-wrapper .tile-value,
html[data-theme="light"] .fin-page .tile-delta,
html[data-theme="light"] .an-page .tile-delta,
html[data-theme="light"] .invq-wrapper .tile-delta,
html[data-theme="light"] .fin-page .lad-row dd,
html[data-theme="light"] .fin-page .mix-legend dd,
html[data-theme="light"] .fin-page .todo-amt,
html[data-theme="light"] .fin-page .pace-note,
html[data-theme="light"] .fin-page .invq-band-n,
html[data-theme="light"] .invq-wrapper .invq-age,
html[data-theme="light"] .invq-wrapper .invq-band-n,
html[data-theme="light"] .invq-wrapper .invq-plate,
html[data-theme="light"] .invd-wrapper .invd-date b,
html[data-theme="light"] .invd-wrapper .invd-ro-line .num,
html[data-theme="light"] .invd-wrapper .invd-bar-block {
    font-variant-numeric: tabular-nums lining-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
}

html[data-theme="light"] .fin-page .lad-row dd,
html[data-theme="light"] .fin-page .mix-legend dd,
html[data-theme="light"] .fin-page .rate-pct,
html[data-theme="light"] .invd-wrapper .invd-group-sum {
    text-align: right;
}


/* ── 5.3 The verdict rule, on the money surfaces ──────────────────────────────────────────────
   The most transferable decision from the Work Queue, and this cluster has four instances of it.
   A colour that appears on nearly every row has stopped carrying information, so the ORDINARY case
   goes neutral and the hue is kept for the case that is actually wrong.

       .invq-stuck   the reason a queue row is still there. Every row on that list has one — that is
                     what the column is FOR — so an amber column is an amber list.
       .invq-age.late  age in days. Age is not a fault; .stale (past the point of chasing) is.
       .pace-gap.behind  "1 500 kr efter takten" on nearly every period. The Work Queue case exactly.
       .tile.warn .tile-value  the unbilled figure. A number is not a status: the tile's dot already
                     carries the tone, and a figure recoloured amber reads as an error in the figure.

   `.aging-total` and `.aging-fig.warn` are the same argument — a SUM is not a status. `.aging-fig.bad`
   keeps the hue, because the oldest band genuinely is the thing to act on. */
html[data-theme="light"] .invq-wrapper .invq-stuck,
html[data-theme="light"] .invq-wrapper .invq-age.late,
html[data-theme="light"] .fin-page .pace-gap.behind {
    color: var(--ink-dim);
}

html[data-theme="light"] .fin-page .tile.warn .tile-value,
html[data-theme="light"] .an-page .tile.warn .tile-value,
html[data-theme="light"] .invq-wrapper .tile.warn .tile-value,
html[data-theme="light"] .fin-page .aging-total,
html[data-theme="light"] .fin-page .aging-fig.warn {
    color: var(--ink);
}


/* ── 5.4 Status is a badge — the marks §4.5 did not reach ─────────────────────────────────────
   `.led-mark` is Ekonomi's invoicing tab saying, per row, whether the job can be billed: no draft
   yet / needs fixing / ready to send. Three words in one column told apart by hue and a border
   tint, which is the same failure `.invq-pay-state` had, on the tab beside it.

   It takes the _base.css §1 grammar. The warn tier KEEPS the amber here — it is a genuine exception
   on a minority of rows, not a routine verdict, which is the line §5.3 draws — and the measured
   --st-expected triad is what carries it.

   `.tile-delta` gets the badge geometry but keeps its hues: the arrow is already in the markup
   (StatTile.vue:8 renders ↑ / ↓), so the second, shape-distinct carrier the recipe demands is
   present and no ::before is needed. Adding one would put two glyphs in a 4-character chip. */
html[data-theme="light"] .fin-page .led-mark {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 6px;
    border-radius: 4px;
    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"] .fin-page .led-mark::before {
    content: "•";
    font-size: 1em;
    line-height: 1;
}

html[data-theme="light"] .fin-page .led-mark.ready {
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
    color: var(--st-logged);
}
html[data-theme="light"] .fin-page .led-mark.ready::before { content: "✓"; }

html[data-theme="light"] .fin-page .led-mark.warn {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}
html[data-theme="light"] .fin-page .led-mark.warn::before { content: "!"; }

html[data-theme="light"] .fin-page .tile-delta,
html[data-theme="light"] .an-page .tile-delta,
html[data-theme="light"] .invq-wrapper .tile-delta {
    border-radius: 4px;
    border: 1px solid var(--keyline);
    background: var(--surface-inset);
    font-weight: var(--fw-label);
}

html[data-theme="light"] .fin-page .tile-delta.up,
html[data-theme="light"] .an-page .tile-delta.up,
html[data-theme="light"] .invq-wrapper .tile-delta.up {
    background: var(--st-logged-soft);
    border-color: var(--st-logged-line);
    color: var(--st-logged);
}

html[data-theme="light"] .fin-page .tile-delta.down,
html[data-theme="light"] .an-page .tile-delta.down,
html[data-theme="light"] .invq-wrapper .tile-delta.down {
    background: var(--st-breach-soft);
    border-color: var(--st-breach-line);
    color: var(--st-breach);
}


/* ── 5.5 More eyebrows ────────────────────────────────────────────────────────────────────────
   §4.2 caught the five spellings on Ekonomi and Fakturor. These are the invoice document's own —
   the column heads of a legal document, the field captions, the group headings and the ageing-band
   headings — each carrying its own tracking value (0.11em, 0.135em, 0.22em) for the same job. */
html[data-theme="light"] .invd-wrapper .invd-cols span,
html[data-theme="light"] .invd-wrapper .invd-ro-cols span,
html[data-theme="light"] .invd-wrapper .invd-field > span,
html[data-theme="light"] .invd-wrapper .invd-group-name,
html[data-theme="light"] .invd-wrapper .invd-date > span,
html[data-theme="light"] .fin-page .detail-facts dt,
html[data-theme="light"] .invq-wrapper .invq-band-name {
    font-size: var(--t-micro);
    font-weight: var(--fw-label);
    letter-spacing: var(--eyebrow-track);
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* The rule that runs from a band heading to the sheet's edge fades to transparent. A gradient
   hairline is a dark device — it fades because on near-black a hard line reads as a scratch. On
   white a fading rule just looks like a rendering fault. */
html[data-theme="light"] .invq-wrapper .invq-band-head::after {
    background: var(--keyline);
}


/* ── 5.6 The remaining rows ───────────────────────────────────────────────────────────────────
   §4.4 put the invoice queue, the .led tables and the overview's two lists on the height ladder.
   These are the three lists it missed. Heights authored, vertical padding derived, one hairline,
   no zebra. `.mix-legend div` and `.invd-line.reading` are both two-figure rows and take the
   default rung; the ageing cells are a card grid rather than a list and only take the surface. */
html[data-theme="light"] .fin-page .mix-legend div {
    min-height: var(--row-h);
    padding: calc((var(--row-h) - 20px) / 2) 0;        /* derived — not a scale step */
    border-top: 1px solid var(--keyline);
}

/* --row-h-comfy, not --row-h: the desktop file is also what the TABLET mounts (there is no tablet
   view — see the platform-views register), and its @media (hover: none) block already floors this
   row at 48px for a gloved hand. Authoring 40px here would win on specificity inside that media
   query and quietly shrink the touch target. */
html[data-theme="light"] .invd-wrapper .invd-line.reading {
    min-height: var(--row-h-comfy);
    padding: calc((var(--row-h-comfy) - 22px) / 2) 0;  /* derived — not a scale step */
}

/* An ageing band is a pressable cell, so it is a control surface: white, a structural hairline, and
   the inset only when it is the chosen one. It was a 2.2%-ink fill, which on a white card is
   nothing at all — a button with no edge. */
html[data-theme="light"] .fin-page .aging-cell {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
}
html[data-theme="light"] .fin-page .aging-cell:hover {
    border-color: var(--keyline-strong);
}


/* ── 5.7 One primary per pane ─────────────────────────────────────────────────────────────────
   The terminal action on this cluster is unambiguous: `.invd-btn.go` releases the invoice and
   `.fx-btn.go` opens it. Both keep the filled accent (§2 above already gives them the flat fill).

   Everything else on those surfaces is a secondary and takes the one secondary shape: surface,
   --keyline-strong, --ink-dim, accent on hover only. The list is long because this cluster had
   eleven differently-drawn quiet buttons — three outlined in an accent tint, four in a rule alpha,
   two as bare accent text, two as accent-underlined links.

   `.invq-link`, `.link` and `.invd-discard` stay TEXT controls — they leave the page rather than
   acting on it — but they lose the accent at rest and pick it up on hover, which is where a link's
   colour actually earns its keep. */
html[data-theme="light"] .fin-page .fx-btn:not(.go),
html[data-theme="light"] .fin-page .more,
html[data-theme="light"] .fin-page .icon,
html[data-theme="light"] .fin-page .fx-pager-buttons button,
html[data-theme="light"] .invq-wrapper .invq-pager-btns button,
html[data-theme="light"] .invd-wrapper .invd-pdf,
html[data-theme="light"] .invd-wrapper .invd-btn.ghost {
    background: var(--surface);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    box-shadow: none;
}

html[data-theme="light"] .fin-page .fx-btn:not(.go):hover,
html[data-theme="light"] .fin-page .more:hover,
html[data-theme="light"] .fin-page .icon:hover:not(:disabled),
html[data-theme="light"] .fin-page .fx-pager-buttons button:hover:not(:disabled),
html[data-theme="light"] .invq-wrapper .invq-pager-btns button:hover:not(:disabled),
html[data-theme="light"] .invd-wrapper .invd-pdf:hover:not(:disabled),
html[data-theme="light"] .invd-wrapper .invd-btn.ghost:hover:not(:disabled) {
    background: var(--surface-inset);
    border-color: var(--accent);
    color: var(--accent);
}

/* The delete control in the costs ledger is the one exception: it is destructive, so its hover is
   the breach tier rather than the accent. It is still quiet at rest. */
html[data-theme="light"] .fin-page .icon:not(.add):hover:not(:disabled) {
    border-color: var(--st-breach-line);
    color: var(--st-breach);
    background: var(--st-breach-soft);
}

html[data-theme="light"] .fin-page .link,
html[data-theme="light"] .invq-wrapper .invq-link,
html[data-theme="light"] .invd-wrapper .invd-discard,
html[data-theme="light"] .invd-wrapper .invd-copy b {
    color: var(--ink-dim);
}

html[data-theme="light"] .fin-page .link:hover,
html[data-theme="light"] .invq-wrapper .invq-link:hover,
html[data-theme="light"] .invd-wrapper .invd-copy:hover b {
    color: var(--accent);
}

/* "2 att åtgärda" in the sticky bar is a filled amber chip and the loudest object on a document
   whose primary action sits 40px to its right. It is a COUNT that navigates — the badge grammar,
   at the warning tier, not a second filled button. */
html[data-theme="light"] .invd-wrapper .invd-bar-block {
    background: var(--st-expected-soft);
    border: 1px solid var(--st-expected-line);
    color: var(--st-expected);
    font-weight: var(--fw-label);
}
html[data-theme="light"] .invd-wrapper .invd-bar-block:hover {
    background: var(--st-expected-soft);
    border-color: var(--st-expected);
}


/* ── 5.8 Tracks and marks ─────────────────────────────────────────────────────────────────────
   --fin-well already gives every track a real recess (§1). What the tracks still need is the
   boundary a light recess owes — a 6px fill on a white card with no edge has no floor — and the
   pace mark needs its ring to be the colour of the card it punches through.

   `.pace-mark` rings itself with `var(--fin-card, var(--c-indigo-0d0b12))`. --fin-card is declared
   NOWHERE (grep: one reference, no declaration), so the fallback is what renders: a raw palette step
   that inverts to #dfe4f4 in light. The ring's whole job is to match the surface behind it, which is
   --surface. Reported rather than fixed at the token: declaring --fin-card here would be inventing
   one. */
/* The edge is an INSET SHADOW rather than a border, deliberately: these tracks are 5-10px tall and
   authored at a fixed height, so a 1px border either side would eat 40% of a 5px bar's fill. An
   inset shadow draws the same hairline and changes no box. */
html[data-theme="light"] .fin-page .pace-track,
html[data-theme="light"] .fin-page .rate-track,
html[data-theme="light"] .fin-page .mix-track,
html[data-theme="light"] .an-page .rate-track {
    box-shadow: inset 0 0 0 1px var(--keyline);
}

html[data-theme="light"] .fin-page .pace-mark {
    background: var(--ink);
    box-shadow: 0 0 0 2px var(--surface);
}


/* ── 5.9 Empty states are a line, not a hero ──────────────────────────────────────────────────
   `.invq-state` and `.invq-clear-state` reserve 3rem of vertical space and centre their type. In an
   ops tool an empty state is a fact, stated where the data would have been. The "nothing owing"
   state keeps its heading — that one is genuinely good news and the page is right to say so — but
   it says it on the left, at the section rung, in ink rather than in green.  */
html[data-theme="light"] .invq-wrapper .invq-state,
html[data-theme="light"] .invq-wrapper .invq-clear-state {
    text-align: left;
    padding: var(--sp-4) 0;
    font-size: var(--t-sm);
    color: var(--ink-muted);
}

html[data-theme="light"] .invq-wrapper .invq-clear-title {
    font-size: var(--t-h2);
    font-weight: var(--fw-strong);
    color: var(--ink);
}

html[data-theme="light"] .fin-page .empty,
html[data-theme="light"] .fin-page .empty-cell,
html[data-theme="light"] .fin-page .caveat,
html[data-theme="light"] .fin-page .note,
html[data-theme="light"] .an-page .empty {
    text-align: left;
    font-size: var(--t-xs);
    color: var(--ink-muted);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   6. THE COMPLIANCE SWEEP — the coverage §§2-5 did not reach
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   A rule-by-rule walk of Ekonomi (Översikt · Verkstad · Kostnader · Fakturering), Fakturor and the
   invoice draft, looking only for surfaces the rules were never applied to. Nothing below is a new
   decision: each block names the rule it answers and restates an argument §5 already made somewhere
   else on the same cluster. Every class was read out of the sheet that declares it AND matched to
   the line of template that renders it, because a wrong selector in this file is silent.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── 6.1 THE PREVIEW SWITCH — RULES 2 AND 3, AND AN OUTRIGHT LEGIBILITY FAULT ──────────────────
   §2 corrected `.an-demo-toggle`, the Analys copy of this control, and never touched `.fin-demo*`,
   the Ekonomi copy — the same control, one file away, on the busier page.

   It is not only a rule-3 violation (an action is not a status). It is illegible: the "on" state is
   `--c-amber-fde68a` INK — a pale dark-theme amber, near #fde68a — on `rgba(--ch-orange-fbbf24, .1)`
   over white. That is roughly 1.3:1. A page of invented money whose only warning that it IS invented
   cannot be read is the worst failure this surface has.

   So the banner and the switch move onto the MEASURED --st-expected triad, which is what the badge
   grammar uses for the warning tier and clears AA on a light ground. This is the one place the sweep
   reaches for the warning hue at all: preview mode is a genuine, rare exception rather than a
   routine verdict, so §5.3's "no good amber" argument does not apply — that argument is about hues
   that fire on the ordinary case, and it never says the measured token is unusable. */
html[data-theme="light"] .fin-page .fin-demo {
    background: var(--st-expected-soft);
    border: 1px solid var(--st-expected-line);
    color: var(--st-expected);
    font-weight: var(--fw-label);
}

html[data-theme="light"] .fin-page .fin-demo-toggle {
    border-radius: var(--r-control);
}

html[data-theme="light"] .fin-page .fin-demo-toggle.on {
    background: var(--st-expected-soft);
    border-color: var(--st-expected-line);
    color: var(--st-expected);
}

html[data-theme="light"] .fin-page .fin-demo-toggle.on .fin-demo-track {
    background: var(--st-expected);
    border-color: var(--st-expected);
}

/* The knob is the part that has to be found against the track it slides on, so it takes the knob
   token rather than the tone — the same rule the jobs cluster's lunch switch runs on. */
html[data-theme="light"] .fin-page .fin-demo-toggle.on .fin-demo-knob {
    background: var(--control-knob);
}


/* ── 6.2 A NUMBER IS NOT A STATUS — RULE 1 ────────────────────────────────────────────────────
   §5.3 made this argument for `.aging-total` and `.tile.warn .tile-value` and stopped there. Four
   more figures and labels on the same three tabs are still a status colour doing the job of a
   figure:

     .fin-tab-badge   the count on a tab, a solid --fin-warn slab with a raw-palette ink. --fin-warn
                      folds to --st-expected (§5), i.e. a status TEXT token laid down as a FILL —
                      which is also the rule-9 fault, on a badge instead of a bar.
     .sent-total      a sum of invoiced money, in green. The identical argument as .aging-total.
     .invq-band-flag  "3 blockerade" beside a band heading. Rule 1 is explicit: counts and sums
                      beside a heading are --ink-muted.
     .invq-cap        the "showing 50 of 214" note under the queue, in amber. It is a caption about
                      the LIST, not a fault in the data. */
html[data-theme="light"] .fin-page .fin-tab-badge {
    background: var(--surface-inset);
    border: 1px solid var(--keyline-strong);
    color: var(--ink-dim);
    font-weight: var(--fw-label);
}

html[data-theme="light"] .fin-page .sent-total {
    color: var(--ink);
}

html[data-theme="light"] .invq-wrapper .invq-band-flag,
html[data-theme="light"] .invq-wrapper .invq-cap {
    color: var(--ink-muted);
    font-weight: var(--fw-label);
}


/* ── 6.3 THE VERDICT RULE, TWO MORE INSTANCES — RULE 7 ────────────────────────────────────────
   `.led td.num.warn` on Verkstad is bound as `:class="{ warn: cust.unbilled > 0 }"`
   (FinanceWorkshop.vue:137). In a shop that bills at the end of a visit that is true of most
   customers most of the time, so the column is an amber column — §5.3's case exactly, one tab over.

   `.stamp.late` is the due date of an overdue invoice, in amber, on a row whose `.invq-pay-state`
   already says OVERDUE in red. One fact, stated twice, in two different hues, so neither reads as
   the authority. The date goes to the row's own tier — a date is not a second status. */
html[data-theme="light"] .fin-page .led td.num.warn {
    color: var(--ink);
}

html[data-theme="light"] .fin-page .stamp.late {
    color: var(--st-breach);
}


/* ── 6.4 THE AGEING RAMP IS A METER — RULE 9 ──────────────────────────────────────────────────
   The four-step ramp under "Utestående" is the clearest rule-9 instance in the cluster, and the page
   sheet says so itself: its comment records the measurement as "against the flattened card
   (var(--c-indigo-0d0b12))" — the DARK card. Every number in that comment is void here, and the top
   step is `--fin-warn`, which §5 folds to `--st-expected`: a status TEXT token, built for 4.5:1 on
   type, laid down as 9px of solid fill where 3:1 is owed. It renders as mustard.

   The ramp's shape is right and is kept: a1 is neutral on purpose ("work billed inside a week is not
   a problem"), and lightness must rise monotonically with age or the ramp stops reading as a ramp.
   So the two middle steps become tints of the measured meter warn and the oldest band — the one the
   shop actually has to act on, and the one `.aging-fig.bad` already paints breach — becomes the
   meter's bad fill. `color-mix` rather than two new tokens; §3 above already uses it for the mix
   hatch, so it is in this file's vocabulary.

   The legend key must move with the bar or the ramp becomes decoration, which is the whole reason
   the key exists. */
html[data-theme="light"] .fin-page .aging-seg.a2,
html[data-theme="light"] .fin-page .aging-key.a2 {
    background: var(--meter-warn-55);
}

html[data-theme="light"] .fin-page .aging-seg.a3,
html[data-theme="light"] .fin-page .aging-key.a3 {
    background: var(--meter-warn-80);
}

html[data-theme="light"] .fin-page .aging-seg.a4,
html[data-theme="light"] .fin-page .aging-key.a4 {
    background: var(--meter-bad);
}

/* The bar's segments are separated by a 2px gap that shows the card THROUGH the bar, which is how
   two adjacent bands stay distinct. On white that gap is invisible, so the ramp reads as one solid
   object again. A hairline under the whole bar gives it a floor and gives the gaps an edge to be
   read against — the same fix §5.8 applied to the other four tracks on this page. */
html[data-theme="light"] .fin-page .aging-bar {
    box-shadow: inset 0 0 0 1px var(--keyline);
}


/* ── 6.5 THE INVOICE EDITOR'S CONTROLS ARE RECTANGLES — RULE 2 ────────────────────────────────
   §2 and §5.7 corrected the COLOUR of every one of these and left `border-radius: 50px` on all of
   them, so the document that closes out a job is trimmed with six capsules — including the one that
   releases it. A control in a dense light data view is a rectangle with a hairline; --r-pill is for
   true pills.

   `.invd-btn` covers `.go`, `.ghost` and `.ghost.danger` — a single element with modifier classes,
   confirmed at InvoiceDraftDetailDesktop.css:443 — so one rule reaches the whole action bar without
   disturbing which of them is the filled primary. */
html[data-theme="light"] .invd-wrapper .invd-btn,
html[data-theme="light"] .invd-wrapper .invd-add,
html[data-theme="light"] .invd-wrapper .invd-disc-chip,
html[data-theme="light"] .invd-wrapper .invd-disc-go,
html[data-theme="light"] .invd-wrapper .invd-lookup-go {
    border-radius: var(--r-control);
    font-weight: var(--fw-label);
}


/* ── 6.6 THE EDITOR'S OWN MESSAGE LINES — RULE 7 AND THE PALE-AMBER FAULT ─────────────────────
   Six status lines in the editor are written in raw dark-theme palette steps rather than in the
   --fin-* / --st-* families the rest of the cluster was folded onto (§5), so they were invisible to
   every correction made so far. `--c-amber-fcd34d` and `--c-emerald-a7f3d0` are both pale
   dark-theme tints: on white they measure around 1.5-1.9:1, which is to say the assistant's error
   message cannot be read at all in light.

   Each keeps its MEANING — ok is good news, error is a fault — and only moves onto the measured
   token for that meaning. `.invd-disc-error` and `.invd-lookup-msg.error` are errors despite the
   amber they were written in; an error is a fault, so they take breach.

   `.invd-assist-msg` (both tiers), `.invd-status.ok` and `.invd-blocker` are NOT included: the
   desktop template renders `.invd-status` only ever as `error`, the phone view roots on
   `.invdp-wrapper` and re-declares its own, and no template under `src/` emits the other two. They
   are dead sheet, so a rule for them here would be a rule that can never fire. Reported upward. */
html[data-theme="light"] .invd-wrapper .invd-lookup-msg.ok {
    color: var(--st-logged);
}

html[data-theme="light"] .invd-wrapper .invd-disc-error,
html[data-theme="light"] .invd-wrapper .invd-lookup-msg.error,
html[data-theme="light"] .invd-wrapper .invd-status.error,
html[data-theme="light"] .invd-wrapper .cell-amt.neg,
html[data-theme="light"] .invd-wrapper .cell-del:hover {
    color: var(--st-breach);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   EKONOMI — rebuilt on the light shell
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   The page now sits beside a standing rail in a bounded column, so it stops being a full-bleed
   dashboard and starts being a document about one period. Everything below follows from that. */

/* ── The header ───────────────────────────────────────────────────────────────────────────────
   The eyebrow said EKONOMI directly above a title that says Ekonomi. Two of the same word, one in
   a bordered chip, before a single figure. The rail already names the page — that is what a
   standing navigation is FOR — so the chip is deleted rather than restyled. Removing chrome is the
   cheapest thing on this page and the most effective.

   24px at 600. The old size was display-scale, which is a marketing device: it makes the page name
   the loudest thing on a screen whose whole job is the numbers below it. */
html[data-theme="light"] .fin-page .fin-eyebrow {
    display: none;
}

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

html[data-theme="light"] .fin-page .fin-head {
    border-bottom: 1px solid var(--keyline);
    padding-bottom: 16px;
    margin-bottom: 32px;
}

/* ── THE SIGNATURE: the tiles become instruments ──────────────────────────────────────────────
   Each stat tile carried a filled sparkline — an SVG area under a 2px stroke, washed in a tinted
   gradient. Four of them in a row read as four coloured blobs, and the gradient is doing what a
   gradient always does on white: implying depth that is not there.

   The fill goes; the stroke stays. What is left is four precise 2px readings on a common baseline
   — the same information, drawn like an instrument rather than a chart. `fill` is an SVG
   presentation attribute, so CSS wins over the element's own `fill="url(#…)"` without touching the
   component, and dark keeps its washes.

   This is the one deliberate flourish on the page. Everything around it is kept quiet so it lands. */
html[data-theme="light"] .fin-page .tile-spark svg path[fill] {
    fill: none;
}

/* Shorter band: an instrument reading, not a chart. The tile reserves exactly this much and no
   more — see the padding rule below, which is what stops the stroke running through the note. */
html[data-theme="light"] .fin-page .tile-spark {
    height: 2.2rem;
    opacity: 1;
}

/* ── The tiles themselves ─────────────────────────────────────────────────────────────────────
   A figure is not a status. RESULTAT was mint-green and INTJÄNAT was accent-blue, so the two
   numbers a shop owner compares first were saying "good" and "primary" rather than saying what they
   are. The dot beside the label already carries tone; the number goes to ink and gets read.

   24px, down from display scale. On a page of four figures, size is not what distinguishes them —
   position and label are. */
html[data-theme="light"] .fin-page .tile {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    /* The bottom reserve is NOT decoration: .tile-spark is absolutely positioned into it. An
       earlier version of this rule set a flat 16px and the stroke ran straight through the note. */
    padding: 16px 16px 2.6rem;
}

/* (0,4,0): StatTile.vue is a SCOPED style, so its .tile.pos .tile-value carries a [data-v-*] and
   ties a three-class override — at which point document order decides and the runtime-injected
   component sheet wins. One more class settles it. */
html[data-theme="light"] .fin-page .tile.pos .tile-value,
html[data-theme="light"] .fin-page .tile.neg .tile-value,
html[data-theme="light"] .fin-page .tile.warn .tile-value,
html[data-theme="light"] .fin-page .tile .tile-value {
    font-size: 1.5rem;
    font-weight: var(--fw-label);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}

html[data-theme="light"] .fin-page .tile-label {
    /* SENTENCE CASE, 2026-08-10 — same ruling as the Work Queue bucket heading, same reasoning.
       Ekonomi was signed off before the sentence-case decision existed. Invoices now reads sentence
       case, and the two pages share both the idiom and, for `.tile-label`, the actual class. */
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
}

html[data-theme="light"] .fin-page .tile-note {
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

/* ── Rhythm ───────────────────────────────────────────────────────────────────────────────────
   The thing most missing. Sections were stacked at card-gap distance, so the page read as one
   continuous strip of boxes with no argument. 16px inside a group, 32px between groups — the
   spacing scale's own steps, and enough air that each block reads as a separate statement. */
html[data-theme="light"] .fin-page .tiles {
    gap: 16px;
    margin-bottom: 32px;
}

html[data-theme="light"] .fin-page .fx-card {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    padding: 20px;
    margin-bottom: 32px;
}

html[data-theme="light"] .fin-page .fx-card-head,
html[data-theme="light"] .fin-page .hero-title {
    /* SENTENCE CASE, 2026-08-10 — see the note on `.tile-label` above. */
    font-size: var(--eyebrow-size);
    font-weight: var(--fw-label);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-muted);
    margin-bottom: 16px;
}
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   7 + 8. FAKTUROR (/invoices) AND THE INVOICE DRAFT EDITOR — the light pass
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   APPEND THIS BLOCK TO THE END OF public/theme-light/money.css.

   Everything here is scoped `html[data-theme="light"] .invq-wrapper` / `.invd-wrapper`, so it cannot
   move Ekonomi, cannot move Analys, and cannot move dark by construction.

   WHY IT COMES AFTER §§1-6 RATHER THAN EDITING THEM
   §§1-6 are the FIRST light pass on this cluster — written before the Stripe transcription landed in
   _base.css. They are correct about colour and about which token carries which role, and they are
   out of date on two structural decisions the transcription overturned:

     · the eyebrow. §4.2 and §5.5 set every section label and column head to 11px UPPERCASE tracked at
       --eyebrow-track. _base.css:907-928 records the measurement that reverses it — the whole 3.6 MB
       Stripe stylesheet carries NINE letter-spacing declarations and not one of them is on a table
       header or a section label; their header is 11px/500 SENTENCE CASE at letter-spacing 0, and
       their current shell is actively shrinking the old uppercase header to that. Uppercase tracked
       labels are the thing being retired, not the target. On this cluster it matters more than
       anywhere else: the draft editor carries ~30 of them on one screen, which is what makes the
       document read as texture rather than as hierarchy.

     · depth. §1 gives .invd-doc and .invq-wrapper's .fx-card an --elevation-1 on top of a border they
       already carry. The rule now is EITHER a border OR an elevation, never both. The Ekonomi rebuild
       at the foot of this file already picked the side — border, no shadow — so these two pages join
       it rather than inventing a third answer.

   Every string on both pages was checked in src/services/localization.ts and is authored in sentence
   case ('Fakturering', 'Sammanfattning', 'Fakturadatum', 'Fordon', 'Varför', 'Namn', 'Adress',
   'Fakturautkast'). The capitals are all CSS, so `text-transform: none` is sufficient and none of the
   ::first-letter machinery is needed here.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ══ 7. FAKTUROR — /invoices ══════════════════════════════════════════════════════════════════════

   DIAGNOSIS (light, VIEWPORT=wide, band expanded)
     primary element   the draft list. There is no filled button on the page at all, which is right:
                       nothing here commits anything, every row is a way in. Nothing to demote.
     boxed vs ruled    one box — .fx-card.invq-list — carrying an --elevation-1 and NO horizontal
                       padding (probe: `box-shadow rgba(23,23,23,.06) 0 3px 6px`, `padding 16px 0px`).
                       So the shadow hugs the text with nothing between them. It is also the only
                       shadowed object in a cluster whose signed-off page uses a border.
     uppercase labels  four kinds, all CSS: FAKTURERING (.invq-eyebrow), UTKAST (.fx-label),
                       FORDON/KÖPARE/VARFÖR/VÄNTAT/BELOPP (.invq-cols span) and the band heading
                       "1–4 VECKOR" (.invq-band-name). Probe confirms uppercase + 0.66px on all four.
     capsules          none. The filter chips are already rectangles (_base §2). Clean.
     vanishing hovers  none vanish — but .invq-row:hover paints an accent 2px LEADING BAR
                       (InvoicesDesktop.css:288), i.e. selection grammar fired by the pointer. Caught
                       with HOVER=".invq-row"; the harness never drives it, so it had never been seen.
     gradients/glows   one, .invq-band-head::after, already flattened by §5.5.
     figure columns    VÄNTAT and BELOPP both right-aligned and tabular. No fault.
     other             the four StatTile figures are still at display scale and still coloured green /
                       red / ink — the single loudest thing on the page and the one §5.3 half-fixed.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── 7.1 The header ───────────────────────────────────────────────────────────────────────────
   §4.1 folded the page title to --t-h1 at --fw-body (20px/400, per probe). The Ekonomi rebuild then
   settled the money cluster's title at 1.75rem/--fw-label and that is the one the user signed off,
   so Fakturor takes the same one rather than being the odd page out at 20px.

   The eyebrow stays — unlike Ekonomi's, which was deleted because it said EKONOMI directly above a
   title saying Ekonomi. "Fakturering" over "Fakturor" is a category over a name, not the same word
   twice. It only loses the shouting. */
html[data-theme="light"] .invq-wrapper .invq-head h1 {
    font-size: 1.75rem;
    font-weight: var(--fw-label);
    letter-spacing: -0.01em;
    color: var(--ink);
}

html[data-theme="light"] .invq-wrapper .invq-eyebrow {
    text-transform: none;
    letter-spacing: 0;
}

/* The head rule is a divider, so it is --keyline. It was --fin-rule-mid, which §5 folds to
   --keyline-strong — the BOUNDING weight, reserved for a column-head underline or a card edge. */
html[data-theme="light"] .invq-wrapper .invq-head {
    border-bottom-color: var(--keyline);
}


/* ── 7.2 The tiles — a figure is not a status ─────────────────────────────────────────────────
   The page opens with four figures and two of them are coloured: "0 kr" in green and "53 252 kr" in
   red. That is the argument §5.3 made for `.tile.warn .tile-value` and stopped there — .pos and .neg
   were never neutralised on any page, and the Ekonomi rebuild only ever named `.fin-page`.

   Colouring a number says the NUMBER is wrong. What is actually being said is that the tile is about
   a bad tier, and the dot beside the label already says that — which is exactly what the dot is for.
   Red on "53 252 kr" also collides with the red field errors in the editor one click away.

   (0,4,1): StatTile.vue is a SCOPED style, so its `.tile.neg .tile-value` carries a [data-v-*] and
   ties a three-class override — at which point document order decides and the runtime-injected
   component sheet wins. The wrapper class is the fourth. Same trap the Ekonomi block records.

   1.5rem, down from 30.4px, for the same reason Ekonomi took it: on a row of four figures it is
   position and label that tell them apart, never size. */
html[data-theme="light"] .invq-wrapper .tile.pos .tile-value,
html[data-theme="light"] .invq-wrapper .tile.neg .tile-value,
html[data-theme="light"] .invq-wrapper .tile.warn .tile-value,
html[data-theme="light"] .invq-wrapper .tile.primary .tile-value,
html[data-theme="light"] .invq-wrapper .tile .tile-value {
    font-size: 1.5rem;
    font-weight: var(--fw-label);
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}

/* The tile surface. Ekonomi's four tiles are white with a keyline; these four are --tile-fill grey
   with neither. Two spellings of one object, one page apart, is the drift this directory exists to
   stop — and the white one is also the correct one now the page ground is white: a grey tile on a
   white page is a second plane doing nothing, where a keyline is a boundary doing everything. */
html[data-theme="light"] .invq-wrapper .tile {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    padding: 16px;
}

/* THE BOTTOM RESERVE IS NOT DECORATION. `.tile-spark` is `position: absolute`
   (StatTile.vue:114-115), so the padding is the space the stroke is drawn into. Flattening it once
   already ran a sparkline straight through the note on Ekonomi — the comment at the foot of this
   file records it. Two of these four tiles carry a spark.

   Scoped to `:not(.flat)` because InvoicesDesktop.vue:24 sets `.flat` when Icarus returns no curves,
   and reserving 2.6rem under four sparkless tiles is 42px of nothing, four times. */
html[data-theme="light"] .invq-wrapper .invq-tiles:not(.flat) .tile {
    padding: 16px 16px 2.6rem;
}

/* The same instrument treatment Ekonomi's tiles took, so the two pages' tiles read as one component:
   the filled area under the stroke goes, the 2px reading stays. `fill` is an SVG presentation
   attribute, so CSS beats the element's own `fill="url(#…)"` without touching the component, and
   dark keeps its washes. */
html[data-theme="light"] .invq-wrapper .tile-spark svg path[fill] {
    fill: none;
}

html[data-theme="light"] .invq-wrapper .tile-spark {
    height: 2.2rem;
    opacity: 1;
}


/* ── 7.3 The list is a card with a border, not a shadow ───────────────────────────────────────
   §1 gave it --elevation-1 and _base's `.content .fx-card` had already zeroed its horizontal padding,
   so the shadow ran along the text with no gutter between them. It is also the only shadowed
   container in the cluster: Ekonomi's fx-card is `--surface` + 1px --keyline + `box-shadow: none` +
   20px, which is the signed-off answer and is what this takes.

   One depth per object, spent at the outermost thing that floats. This does not float; it is where
   the list lives. */
html[data-theme="light"] .invq-wrapper .fx-card {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
    padding: 20px;
    margin-bottom: 32px;
}


/* ── 7.4 Sentence case ────────────────────────────────────────────────────────────────────────
   Four labels, one rule. `.invq-cols` carries the transform on the CONTAINER (§4.2) and the page
   sheet carries it again on the SPAN (InvoicesDesktop.css:263), so both are named — killing only one
   leaves the other rendering and the fix looks applied while changing nothing.

   A note for whoever reads this next: the band heading is "1–4 veckor" in the string table and is
   uppercased purely by CSS, so `text-transform: none` is enough. Had the string itself been capitals,
   the ::first-letter recipe would NOT have worked on it — the first character is a digit. */
html[data-theme="light"] .invq-wrapper .fx-label,
html[data-theme="light"] .invq-wrapper .invq-cols,
html[data-theme="light"] .invq-wrapper .invq-cols span,
html[data-theme="light"] .invq-wrapper .invq-band-name {
    text-transform: none;
    letter-spacing: 0;
}


/* ── 7.5 Hover is a tone step. The bar means selected. ────────────────────────────────────────
   `.invq-row:hover` is `box-shadow: inset 2px 0 0 var(--fin-accent)` plus a violet wash
   (InvoicesDesktop.css:288). §2 replaced the wash and left the bar, so in light the pointer draws an
   accent leading bar down whichever row it happens to be over — the exact mark this theme uses for
   SELECTION, fired by mouse position. Two rows cannot both be the selected one, and a list where the
   selection follows the cursor has no selection.

   Measured behaviour is that hover never changes colour and nothing moves: the background steps one
   rung down the ramp and that is the whole response. Focus-visible keeps the bar, at the theme's 3px,
   because keyboard focus IS "you are here" and needs a mark that survives glare and colour-blindness.

   (0,4,1) against the page sheet's (0,2,0) and §2's (0,3,1); both are named so both are beaten. */
html[data-theme="light"] .invq-wrapper .invq-row:hover {
    background: var(--surface-inset);
    box-shadow: none;
}

html[data-theme="light"] .invq-wrapper .invq-row:focus-visible {
    outline: none;
    background: var(--surface-inset);
    box-shadow: inset 3px 0 0 0 var(--accent);
}


/* ── 7.6 The double rule at the foot of the list — a real bug, worked around ───────────────────
   `.invq-band:last-child .invq-row:last-child { border-bottom: none }` (InvoicesDesktop.css:290) can
   never fire. `.invq-foot` is a `<p>` sibling AFTER the `<section class="invq-band">` elements
   (InvoicesDesktop.vue:156), so no band is ever its parent's last child. The last row therefore keeps
   its divider and `.invq-foot` adds its own `border-top` 16px later: two hairlines, 19px apart,
   under the last row. Visible in the capture.

   `:last-of-type` is the correct selector — `.invq-band` is a `<section>` and `.invq-foot` is a `<p>`,
   so type is what actually distinguishes them. Fixed here for light only because the same edit at
   source would move dark; the source line should be corrected in both themes and is reported. */
html[data-theme="light"] .invq-wrapper .invq-band:last-of-type .invq-row:last-child {
    border-bottom: 0;
}


/* ══ 8. THE INVOICE DRAFT EDITOR ══════════════════════════════════════════════════════════════════

   DIAGNOSIS (light, VIEWPORT=wide, draft opened from the queue)
     primary element   `.invd-btn.go` — "Bokför och skicka" in the fixed bar. Correct and already the
                       only filled control in the bar (§5.7). The discount panel's "Tillämpa" is a
                       second filled accent, but it is the commit of a sub-form that only exists while
                       open, not a competing page verb. Left alone.
     boxed vs ruled    `.invd-doc` carries BOTH a 1px border and --elevation-1 (probe). `.invd-footer`
                       is a second card with a 16px radius and a LINEAR-GRADIENT fill
                       (InvoiceDraftDetailDesktop.css:410-415) that no light pass has touched.
     uppercase labels  approximately THIRTY on one screen: 3 section labels (.invd-doc-label), 4 date
                       captions, 6 ledger column heads, 3 group names, 8 buyer field captions, the
                       "REDIGERA ALLA RADER" toggle, the FAKTURAUTKAST eyebrow, the bar's ATT BETALA,
                       and 4 "TILLÄGGSARBETE" tags. This is the page's dominant texture problem.
     capsules          none left — §6.5 already un-pilled the six editor controls.
     vanishing hovers  none vanish; `.invd-line.reading:hover` steps down correctly (§2).
     gradients/glows   one, `.invd-footer`'s fill. Deleted below.
     figure columns    ANTAL / ENHET / À-PRIS / MOMS / BELOPP all align, all tabular. The totals block
                       aligns on its own right edge under a 3px double rule — printed-invoice
                       convention, deliberate (InvoiceDraftDetailDesktop.css:688), and left alone: a
                       totals row is allowed to be heavier, and this is a document, not a screen.
     other             `.invd-addition-tag` is `grid-column: 1 / -1`, so §4.5's badge grammar rendered
                       it as a FULL-WIDTH mustard slab across the ledger, four times.
                       `.invd-note.written` is `rgba(var(--ch-amber-fcd34d), .9)` — a pale dark-theme
                       amber channel — set as the caution paragraph under the title.

   THE DOCUMENT IS EXEMPT FROM THE DENSITY SYSTEM and nothing below compresses it. Every rule here is
   about hue, capitals, and how many edges one object draws.
   ═════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── 8.1 Sentence case, everywhere on the sheet ───────────────────────────────────────────────
   The one change that does the most on this page. Ten selectors because ten different classes were
   each given their own tracking value for the same job — 0.11em, 0.135em, 0.22em, 0.6px, 0.7px,
   0.8px, 1px, 1.2px — which is itself the evidence that nobody was working from one label.

   Each of these is already at 11px / --fw-label / --ink-muted from §4.2, §5.5 or §5.1. All that is
   left to take off is the shouting.

   `.invd-header > .badge:not(.sent)` restates §5.1's own selector exactly so it wins on document
   order: it is (0,4,1) because `:not()` contributes its argument's specificity, and a three-class
   override would silently lose. */
html[data-theme="light"] .invd-wrapper .invd-doc-label,
html[data-theme="light"] .invd-wrapper .invd-date > span,
html[data-theme="light"] .invd-wrapper .invd-cols span,
html[data-theme="light"] .invd-wrapper .invd-ro-cols span,
html[data-theme="light"] .invd-wrapper .invd-ro-desc em,
html[data-theme="light"] .invd-wrapper .invd-field > span,
html[data-theme="light"] .invd-wrapper .invd-group-name,
html[data-theme="light"] .invd-wrapper .invd-editall,
html[data-theme="light"] .invd-wrapper .invd-bar-label,
html[data-theme="light"] .invd-wrapper .invd-copy em,
/* `.invd-blockers-title` and `.invd-assist-note-label` deleted from this group 2026-08-11: both are
   dead sheet in `InvoiceDraftDetailDesktop.css`, emitted by no template. */
html[data-theme="light"] .invd-wrapper .invd-header > .badge:not(.sent) {
    text-transform: none;
    letter-spacing: 0;
}


/* ── 8.2 One sheet, one edge ──────────────────────────────────────────────────────────────────
   `.invd-doc` composites at rgb(252,252,254) on a white page — 1.01:1, i.e. the fill is not there —
   so its whole boundary is the border, and §1 then added a shadow on top of it. Two devices for one
   edge, and the page sheet's own comment says which one it wants: "a document lies on the page, it
   does not float over it".

   So: white like the page, one keyline, no shadow — the same object Ekonomi's fx-card is. The
   near-white tint is dropped rather than deepened because a 1% fill is a plane nobody can see, and
   deepening it would put a grey rectangle under a legal document. */
html[data-theme="light"] .invd-wrapper .invd-doc {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
}

/* The send panel below the sheet. A 170deg two-stop gradient built out of --sf-rgb: the channel
   inverts, so in light it is a pale smear rather than the lift it draws on near-black, and a
   gradient on white always reads as a rendering artefact. Flat token, card radius, one hairline —
   and no shadow, because the sheet above it does not have one either. */
html[data-theme="light"] .invd-wrapper .invd-footer {
    background: var(--surface);
    border: 1px solid var(--keyline);
    border-radius: var(--r-card);
    box-shadow: none;
}

/* Panels that open INSIDE the sheet get no frame of their own — they are part of the document, not a
   second document. They separate with the inset ground, which is what --surface-inset is for. §2
   gave them --surface-sunken with a border; the border is the box-inside-a-box. */
html[data-theme="light"] .invd-wrapper .invd-discount {
    background: var(--surface-inset);
    border: 0;
    border-radius: var(--r-control);
}


/* ── 8.3 "Tilläggsarbete" — a tag, not a band ─────────────────────────────────────────────────
   `.invd-addition-tag` is `grid-column: 1 / -1` (InvoiceDraftDetailDesktop.css:168), a full-width row
   of its own above the line it belongs to. §4.5 correctly gave it badge grammar and thereby stretched
   a filled badge across the entire ledger — four times on this one draft. That is the loudest object
   on a document whose actual verb is 900px below it.

   Two corrections, and only the first is geometry:

   1. `justify-self: start` shrinks the grid item to its content. A badge is the width of its word.

   2. THE HUE GOES. It fires on four of six lines here, and it fires on every line of any visit where
      the mechanic found work — which is the normal case, because that is what a repair order IS. A
      colour on nearly every row has stopped carrying information; §5.3 draws that line for
      `.invq-stuck` and `.pace-gap.behind` and this is the same verdict one page over. There is also
      no good light amber: --st-expected resolves to #8a5500, and four mustard bars down a legal
      document is the one thing this page cannot afford.

      The FACT is not lost. The "!" from §4.5 stays — it is the shape-distinct carrier the badge
      recipe demands, and with the fill neutral it is the only carrier, which is the correct weight
      for a flag that is true half the time. */
html[data-theme="light"] .invd-wrapper .invd-addition-tag {
    justify-self: start;
    width: max-content;
    background: var(--surface-inset);
    border-color: var(--keyline);
    color: var(--ink-dim);
}


/* ── 8.4 The provenance note ──────────────────────────────────────────────────────────────────
   `.invd-note.written` — "the job differs from the quote, so the lines were rewritten" — is
   `rgba(var(--ch-amber-fcd34d), 0.9)`, a PALE dark-theme amber channel. §6.6 swept six of these raw
   palette lines in the editor and missed the two on `.invd-note`, which are the two the mechanic
   reads first.

   It is a genuine caution and it keeps its weight, but not as coloured prose: two lines of mustard
   text is the least legible way to state anything on white, and the sentence is long enough to be a
   block rather than a highlight. So it becomes a block — a quiet wash, no border, no left bar, per
   the severity recipe — and the words go to reading ink where they can actually be read.

   `.quote` is the OPPOSITE case and gets no wash at all: "these lines came from the approved quote"
   is the reassuring default, and it was drawn in a pale emerald that measures under 2:1 on white. A
   fact that means "nothing to worry about" is a caption. */
html[data-theme="light"] .invd-wrapper .invd-note.written {
    color: var(--ink-dim);
    background: var(--surface-inset);
    border-radius: var(--r-control);
    padding: 8px 10px;
}

html[data-theme="light"] .invd-wrapper .invd-note.quote {
    color: var(--ink-muted);
}


/* ── 8.5 Editing a line is a selection ────────────────────────────────────────────────────────
   `.invd-line.editing` is `inset 2px 0 0 rgba(var(--ch-indigo-b9a3ff), .5)` and NOTHING else — no
   tint — so the open row draws a short violet stub floating beside the fields, detached from
   anything, at half alpha on a channel that inverts. In the capture it reads as a rendering fault
   rather than as "this row is open".

   The theme already has one mark for this and it is used identically on the queue above: tint PLUS a
   3px leading bar. The bar alone is not selection — it is half of it.

   The reading row's focus ring goes to the same 3px for the same reason: two widths of the same mark
   on one page is two marks. */
html[data-theme="light"] .invd-wrapper .invd-line.editing {
    background: var(--surface-inset);
    box-shadow: inset 3px 0 0 0 var(--accent);
}

html[data-theme="light"] .invd-wrapper .invd-line.reading:focus-visible {
    box-shadow: inset 3px 0 0 0 var(--accent);
}


/* ── 8.6 The PDF button's radius — working around an undeclared token ─────────────────────────
   `.invd-pdf` is `border-radius: var(--r-sm)` (InvoiceDraftDetailDesktop.css:1005) and `--r-sm` is
   DECLARED NOWHERE. A grep over the whole of src/ and public/ returns exactly two references — that
   line and InvoiceDraftDetailPhone.css:109 — and zero declarations, so the custom property is
   invalid at computed-value time and `border-radius` falls back to its initial value: 0. Both
   buttons have square corners in both themes and always have.

   It is not fixable at source without moving dark, so light works around it. The phone view roots on
   `.invdp-wrapper` and is outside this cluster's selectors, so its copy of the same fault stays
   square in light too — reported rather than papered over from here.

   This only shows on a RELEASED invoice, so it is unverified by capture: the seeded shop has no sent
   invoice to open. The rule is safe regardless — it replaces an invalid value, so it cannot lose. */
html[data-theme="light"] .invd-wrapper .invd-pdf {
    border-radius: var(--r-control);
}

/* ── LEAD REWORK ON THE INVOICES BOARD ─────────────────────────────────────────────────────────
   Two corrections, both consistency rather than correctness — the block above is right on its own
   terms and wrong against the five pages already built.

   1. THE TILES GO BACK TO THE FILL. They were given white + a keyline, which is a defensible
      reading of "a surface needs an edge". But the measured position is narrower than that: Stripe
      REMOVED its cards on purpose, and the one container that survived the cull is "a soft grey
      fill with NO border", used for stat tiles and nothing else. `_base.css` already encodes that as
      `--tile-fill`. A bordered tile re-adds the frame the cull existed to remove, and four of them
      in a row is four frames on a page whose other surfaces now carry none.

   2. THE LIST STOPS BEING A CARD. Jobs, Routines, Job detail and the repair order all dropped their
      list frames in this rebuild: on a white page, a border around rows that already terminate
      themselves with hairlines is a box drawn for its own sake. Keeping one here would leave the
      invoice board as the only list in the app still in a box.

   Neither of these is a defect in the work above. They are the difference between a page that is
   right and a page that is right IN THIS APP, which is the one thing a per-page author cannot see
   and the reason the integration step exists. */
html[data-theme="light"] .invq-wrapper .tile {
    background: var(--tile-fill);
    border: 0;
    box-shadow: none;
    border-radius: var(--r-card);
}

html[data-theme="light"] .invq-wrapper .fx-card.invq-list {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding-left: 0;
    padding-right: 0;
}

/* `.invd-bar-block` — the "1 att åtgärda →" jump in the action bar — is an amber capsule, and both
   halves of that are wrong here.

   The amber first: there is no good light amber. To clear 3:1 on a light track it has to be
   darkened, and a darkened yellow IS mustard — which is why the whole theme retired it. This control
   also fires whenever the invoice has any unfilled required field, which on a Swedish invoice with
   no address on file is most drafts. A colour that appears on nearly every draft has stopped
   carrying information.

   Then the capsule: a full round is reserved for the one interactive filter shape. This is a jump
   link, not a filter, and it sits beside three rectangular buttons in the same bar — one capsule
   among them reads as a different KIND of thing than it is.

   It is a control, so it takes the control grammar, and the hue it needs is the breach ink on hover,
   where the intent is already formed. */
html[data-theme="light"] .invd-wrapper .invd-bar-block {
    background: var(--surface);
    background-image: none;
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink-dim);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.08),
        0 0 0 0 transparent;
    transition: box-shadow 0.08s ease-in, color 0.08s ease-in;
}

html[data-theme="light"] .invd-wrapper .invd-bar-block:hover {
    background: var(--surface);
    color: var(--st-breach);
    box-shadow:
        0 0 0 1px rgba(var(--edge-rgb), 0.10),
        0 1px 1px rgba(0, 0, 0, 0.10),
        0 3px 9px rgba(var(--edge-rgb), 0.10);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   TOUCH PASS — MONEY CLUSTER
   DESTINATION: public/theme-light/money.css  ·  APPEND TO THE END OF THE FILE
   ═══════════════════════════════════════════════════════════════════════════════════════════════

   NOTHING IN THIS FILE BELONGS IN phone.css OR _base.css. Every rule below is scoped to a class
   this cluster owns (.fin-page / .invq-wrapper / .invd-wrapper / .invd-bar / .finp-* / .invqp-*),
   so there is no shared-layer work to hand back to the lead. Stated explicitly because the brief
   asks for the split and the answer here is "the split is empty".

   ── THE ROOTS, VERIFIED MYSELF (the lead has got this wrong twice) ──────────────────────────────
   Routes read out of src/router.ts:72/82/83; roots read out of the templates, not guessed.

     /finance               → src/pages/finance/index.ts
                              desktop/tablet  FinanceDesktop.vue      root .fin-page
                              phone           FinancePhone.vue        root .fin-page.finp
     /invoices              → src/pages/invoices/index.ts
                              desktop/tablet  InvoicesDesktop.vue     root .fin-page.invq-wrapper
                              phone           InvoicesPhone.vue       root .fin-page.invqp-wrapper
     /invoice-draft-detail  → src/pages/invoice-draft-detail/index.ts
                              desktop/tablet  InvoiceDraftDetail-
                                              Desktop.vue             root .fin-page.invd-wrapper
                              phone           …Phone.vue              root .fin-page.invdp-wrapper

   All six belong to this cluster. Two corrections to the assumptions this file already carries:

   1. EVERY PHONE VIEW ALSO CARRIES `.fin-page`. money.css's Ekonomi rebuild block is written
      `html[data-theme="light"] .fin-page .tile { … }` and therefore ALREADY FIRES ON THE PHONE
      /invoices VIEW, which is the only phone view in the cluster that mounts <StatTile>. The lead's
      later rework is scoped `.invq-wrapper .tile` and does NOT reach phone. So today the same
      component is grey-with-no-border on the tablet board and white-with-a-keyline-and-42px-of-
      sparkline-reserve one shell down. Fixed in §M4.

   2. NONE OF THE THREE TABLET VIEWS IS A TABLET VIEW. All three index.ts files resolve
      `shell === 'phone' ? Phone : Desktop`, so 1080×810 mounts the DESKTOP file — which is what the
      §5.6 comment already says, and it is right. The consequence nobody had written down is in §M2:
      at 1080 the invoice editor mounts in a SPLIT PANE beside the queue and gets 616px, not 1080.

   ── THE PHONE VIEWS ARE DELIBERATE READ-ONLY STUBS ─────────────────────────────────────────────
   All three phone templates say so in their own header comment ("this tier answers 'what is the
   state of this', not 'change it'"). The touch auditor sees FIVE interactive elements on the whole
   of /finance and /invoices at 412×839 and every one of them clears 44px. So the phone work here is
   three consistency corrections and nothing else — no touch UI has been invented for a page the
   product deliberately does not offer on a phone.

   ── WHAT THE 64px TABLET RUNG ACTUALLY DID, HAVING LOOKED ──────────────────────────────────────
   Measured, not assumed, on /finance?tab=invoicing, ?tab=workshop and /invoices at 1080×810:

     .invq-row          min-height var(--row-h-comfy) → 64px, renders 64px.   ALREADY CORRECT.
                        Its derived padding assumes 34px of content and the real cell is one 24px
                        line, so min-height is what carries it and the derivation is inert. Left
                        alone: it lands on the rung, and re-deriving an inert value buys nothing.
     .invd-line.reading min-height 64px, renders 91–114px because the description wraps in a 125px
                        column inside the split pane. ALREADY CORRECT — over the rung, by content.
     .todo-row/.lad-row/.mix-legend div — on the rung. ALREADY CORRECT.
     .led td            RENDERS 82px AGAINST A 64px RUNG. This is the one that needed re-deriving,
                        and §M1 is that fix.

   ── DOES ANY TABLE NOW OVERFLOW 1080px? NO. ────────────────────────────────────────────────────
   Measured every table head in the cluster at 1080:
     Ekonomi/Verkstad  6 columns, 147→1007, all inside the 860px content box, no clip.
     Ekonomi/Fakturering  5 columns, 147→1007, no clip.
     /invoices  the desktop sheet's own `@media (--tablet)` already drops the buyer column at this
                width (verified firing: KÖPARE is absent from the rendered head), 4 columns, no clip.
     Invoice editor  7 grid tracks summing 475px inside a 523px box in the split pane, no clip.
   So no column needs to drop or wrap. The clipped "1 792 k" visible in the first tablet capture of
   the editor is NOT a table overflow — it is the floating chat FAB parked over the BELOPP column.
   That is app-wide chrome, not this cluster, and it is reported rather than fixed here.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ── M1. THE .led TABLE, RE-DERIVED AGAINST THE CONTENT HEIGHT THIS SHELL ACTUALLY HAS ─────────
   CONFIRMED — rendered at 1080×810, measured before and after, and looked at.

   §4.4 authors `padding: calc((var(--row-h) - 20px) / 2)`. 20px is the height of ONE line, and on
   the desktop rung (40px) that is right for every row on the page. The tablet rung is 64px and the
   arithmetic is unchanged, so the padding grew to 22px a side — but the cell it is padding is a
   TWO-LINE identity cell on two of the four tables ("3 aug." over "För 7 dagar sedan" on Fakturering,
   "Monte" over "6 dagar in" on Verkstad), whose real content box measures 38px. 22 + 38 + 22 = 82px.

   Measured: .led-row td = 860×82 at tablet, against a 64px rung. 28% over, on the most row-dense
   surface in the app, and — worse than the slack itself — INCONSISTENT: a single-line row on the
   same table lands on 64 exactly, so one page draws two row heights and neither is the rung.

   THE FIX IS NOT A SECOND DERIVATION, because there is no single content height to derive from: the
   same selector styles one-line and two-line cells. `height` on a table cell is a MINIMUM in table
   layout (CSS 2.1 §17.5.3 — the used height is the larger of the specified height and the content),
   which is exactly the `min-height` semantics a <td> otherwise refuses. So the height is authored
   from the token and the padding is derived against the TALLER of the two contents:

       two-line:  13 + 38 + 13 = 64  → equals the rung, height adds nothing
       one-line:  13 + 20 + 13 = 46  → below the rung, height floors it to 64

   Both land on 64px. Verified: every td on both tables measures exactly 64 after this rule, and the
   two-line cell is not cramped (rendered and looked at — the descenders of "6 dagar in" clear).

   SCOPED TO data-shell='tablet' AND NOT TO pointer:coarse, matching the density ladder's own scope-2
   argument in _base.css: the ladder is what created this and the ladder is per-shell. Desktop keeps
   the signed-off derivation byte for byte. Phone never renders `.fin-page .led` at all (the phone
   view is `.finp-stat`, a <dl>), so there is no phone half of this rule.

   `.an-page` is UNVERIFIED-by-render — Analys is outside my two routes — but it is the same class
   taking the same declaration from the same §4.4 block, so it is the same defect or no defect. */
html[data-theme="light"] .page[data-shell='tablet'] .fin-page .led td,
html[data-theme="light"] .page[data-shell='tablet'] .an-page .led td,
html[data-theme="light"] .page[data-shell='tablet'] .an-page .dm-led td {
    height: var(--row-h);                                   /* a MINIMUM on a table cell */
    padding: calc((var(--row-h) - 38px) / 2) var(--row-pad-x);  /* derived — not a scale step */
}


/* ── M2. THE ACTION BAR'S GHOST BUTTONS — A BOTH-THEMES BUG, FIXED IN LIGHT ONLY ───────────────
   CONFIRMED — rendered, measured 40px before and 48px after, and the primary re-checked at 56px.

   THE AUDITOR NEVER SAW THIS and neither would anyone running it as written: `touch-audit.mjs
   "/finance,/invoices"` reports "TARGETS: all clear" on both routes at both shells, because the
   invoice editor only exists once a queue row has been opened and the auditor has no CLICK. It has
   to be reached with `CLICK=".invq-band-head,.invq-row"`, and behind that click two of the three
   controls that close out a legal invoice are 40px tall on a touch shell.

   THE CAUSE IS TRAP #5, IN THE WILD, AND IT IS PRESENT IN DARK TOO.
   InvoiceDraftDetailDesktop.css declares, in this order:

       :922   .invd-bar-acts .invd-btn      { min-height: 40px; }        (0,2,0)  no media query
       :950   @media (hover: none) {
       :956       .invd-btn                 { min-height: 48px; }        (0,1,0)
       :958       .invd-bar-acts .invd-btn.go { min-height: 56px; }      (0,3,0)
              }

   A media query contributes NOTHING to specificity. So :956 — the page author's own, deliberate,
   commented "the bay standard is 48px for a target" rule — loses to :922 for every button in the
   bar, and has never once fired there in either theme. Only `.go` clears it, at (0,3,0), which is
   precisely why the primary measures 56 and its two neighbours measure 40: the evidence that the
   touch block works everywhere except where an earlier modifier rule outranks it.

   48px, NOT --tap-min's 44. This is not a new floor and it does not contradict the 44px derivation
   in _base.css: it is the page's OWN :956 value, restored to the elements it was written for. Every
   other control on this document already has it — .cell input 48, .cell .ssel-btn 48, .invd-line
   .reading 48, .invd-discard 48, .invd-editall 48, .invd-bar-block 48 (all measured). Dropping the
   bar's two buttons to 44 would make them the only sub-48 targets on a sheet that is otherwise
   uniformly 48, which is a new inconsistency in exchange for token purity.

   `:not(.go)` IS LOAD-BEARING AND I GOT THIS WRONG ON THE FIRST RENDER. Without it the selector
   scores (0,4,0) and beats the page's (0,3,0) `.invd-bar-acts .invd-btn.go` — so my "fix" silently
   demoted the release button from 56px to 48px. Trap #6 exactly: `:not()` contributes its argument's
   specificity, taking this to (0,5,0), and it is that raise that keeps the ghosts at 48 while
   leaving the primary's 56 to the rule that owns it. Caught only because the render was measured.

   Scoped `.invd-bar` rather than `.invd-wrapper`: the bar is `position: fixed` and full-viewport-
   width, so a wrapper-descendant assumption is the kind of thing that is true today and silently
   false after a teleport. In `@media (pointer: coarse)` for the reason _base.css gives for its own
   scope 1 — a fixed bar has no `.page[data-shell]` obligation to keep. */
@media (pointer: coarse) {
    html[data-theme="light"] .invd-bar .invd-bar-acts .invd-btn:not(.go) {
        min-height: 48px;   /* the page's own :956 value, restored to the elements it was written for */
    }
}


/* ── M3. THE INVOICE BOARD'S TILES — 3 + 1 BECOMES 2 × 2 ON THE TABLET ─────────────────────────
   CONFIRMED — rendered at 1080×810, measured, and looked at.

   `.invq-tiles` is `repeat(auto-fit, minmax(230px, 1fr))` with a 0.9rem gap. Measured content box
   at 1080 is 926px, which fits exactly three 299px tracks and leaves the fourth tile alone on a
   second row beside 600px of nothing. Ekonomi, one route away, renders its four tiles 2×2 at 443px
   each on the same shell. Two money boards, same object, different composition.

   Two of these four tiles are LINKS (they carry a chevron and navigate), so this is a target
   question as well as a composition one: 2×2 takes them from 299px to 456px wide.

   A HARD `1fr 1fr` IS THE FIX THIS PAGE HAS ALREADY REJECTED, and the sheet says so in its own
   comment at InvoicesDesktop.css:92 — "A hard `1fr 1fr` was what clipped the figures when the pane
   was narrower than two tiles." That is a real constraint, because §M2's finding is that the tablet
   shell mounts this board in a SPLIT PANE next to an open draft, where the board keeps only ~440px.

   So the track FLOOR is raised instead and auto-fit is left to do its job. The arithmetic, off the
   measured 926px box and 14.4px gap:

       3 tracks need 3×min + 28.8 ≤ 926  →  min ≤ 303.9   so any min above 304 falls to two
       2 tracks at min 310 need 634.4    →  a pane narrower than that still falls to one

   310px keeps every safety the auto-fit grid was chosen for and only removes the three-across rung,
   which is the one that does not fit four tiles. Rendered: cols = 455.8px 455.8px, four tiles, two
   rows, no clipping, and the board now composes like Ekonomi.

   `min(310px, 100%)` RATHER THAN A BARE 310px, AND THIS IS THE SECOND THING I GOT WRONG BY RENDER.
   A bare `minmax(310px, 1fr)` was written first and it walked straight into the failure the sheet's
   comment describes, just by a different route: with a draft open the board is squeezed to 277px,
   auto-fit falls to ONE track, and `minmax(310px, 1fr)` resolves that track to its 310px MIN — a
   33px overflow out of the pane. The measurement caught it (`.invq-tiles 277px wide, cols=310px`),
   the arithmetic above would not have.

   Clamping the min to the container is the standard idiom for exactly this, and it makes the floor
   an intention rather than a promise: raise the rung when there is room for it, collapse when there
   is not. Rendered in BOTH states — 277px pane → one 276.7px track, no overflow; 926px board →
   two 455.8px tracks. `min()` is Chrome 79 and is not on the banned list (@layer / color-mix() /
   :has()), which was checked rather than assumed. */
html[data-theme="light"] .page[data-shell='tablet'] .invq-wrapper .invq-tiles {
    grid-template-columns: repeat(auto-fit, minmax(min(310px, 100%), 1fr));
}


/* ── M4. THE PHONE BOARD'S TILES — ONE COMPONENT, ONE SKIN ────────────────────────────────────
   CONFIRMED — rendered at 412×839, measured 103px after against ~155px before, and looked at.

   InvoicesPhone.vue is the only phone view in the cluster that mounts <StatTile>, and its root is
   `.fin-page.invqp-wrapper`. `.fin-page` is what makes the Ekonomi rebuild's `.fin-page .tile` rule
   apply to it, and `.invq-wrapper` is what stops the lead's later rework from applying. Net effect
   on the phone board today:

     · white with a 1px keyline, where the tablet board's identical tiles are --tile-fill with none;
     · `padding: 16px 16px 2.6rem`, i.e. 42px reserved at the foot of each tile for an absolutely-
       positioned `.tile-spark` — and InvoicesPhone passes no curve at all, so `.tile-spark` does not
       exist in the DOM. Four tiles × 42px of nothing, on the 412px screen that can least afford it.

   The desktop board already solved exactly this with `.invq-tiles:not(.flat) .tile`. The phone
   board needs no conditional: it NEVER has a spark, so the reserve is unconditionally wrong here.

   (0,5,0) against the Ekonomi block's (0,3,0), so the fill, the edge and the padding are all taken
   cleanly. The values are copied from the lead's `.invq-wrapper .tile` rework rather than re-chosen,
   which is the whole point — this rule exists to make one component have one skin. */
html[data-theme="light"] .page[data-shell='phone'] .invqp-wrapper .tile {
    background: var(--tile-fill);
    border: 0;
    box-shadow: none;
    border-radius: var(--r-card);
    padding: 16px;              /* no sparkline reserve: this view never renders a .tile-spark */
}


/* ── M5. THE PHONE EYEBROWS ───────────────────────────────────────────────────────────────────
   CONFIRMED — both rendered at 412×839 and looked at.

   `.finp-eyebrow` and `.invqp-eyebrow` are both `--fin-label` / --fw-700 / letter-spacing 0.22em /
   uppercase / `color: var(--fin-accent)`. §1 of this file repoints --fin-accent to --accent-solid,
   so what actually paints on the phone is a BLUE, tracked, capitalised section label — which is
   both halves of what §4.2 forbids ("it is never coloured: a coloured section label on white is a
   highlighter mark, and it spends the accent budget on something that is not an action") and what
   §7.4 and §8.1 retired everywhere else in the cluster. Neither rule could reach these: §7.1 names
   `.invq-eyebrow`, and the phone class is `.invqp-eyebrow`.

   THE TWO ARE NOT THE SAME CASE, and the desktop pass already drew the line between them:

   `.finp-eyebrow` is DELETED, because it reads "EKONOMI" directly above an <h1> reading "Ekonomi".
   That is the identical argument the Ekonomi rebuild used to delete `.fin-eyebrow` on desktop — the
   same word twice, once shouted — and it is if anything stronger here, because at 412px it is the
   first line on the screen.

   `.invqp-eyebrow` is KEPT and de-shouted, because "Fakturering" over "Fakturor" is a category over
   a name, not the same word twice — again exactly what §7.1 decided for the desktop board.

   `display: none` rather than removing it from the template: this file cannot touch src/. */
html[data-theme="light"] .page[data-shell='phone'] .finp-head .finp-eyebrow {
    display: none;
}

html[data-theme="light"] .page[data-shell='phone'] .invqp-head .invqp-eyebrow {
    text-transform: none;
    letter-spacing: 0;
    font-weight: var(--fw-label);
    color: var(--ink-muted);
}


/* ── M6. A FIGURE IS NOT A STATUS — THE PHONE COPY OF THE RULE ────────────────────────────────
   CONFIRMED — rendered at 412×839 and looked at.

   `.finp-fig.warn / .pos / .neg` paint the phone overview's figures in --fin-warn / --fin-pos /
   --fin-neg, which §5 folds to the --st-* triad, which in light resolves --st-expected to #8a5500.
   So the phone shows "5 002 kr" in mustard-brown and "5 002 kr" in green, one under the other.

   This is the argument §5.3 and §7.2 already made twice for `.tile-value`, and it is STRONGER on
   this view rather than weaker, which is the reason it is worth a rule on a read-only stub: on the
   desktop and tablet tiles the coloured figure is redundant, because `.tile-dot` beside the label
   carries the tone independently. THIS VIEW HAS NO DOT. `.finp-stat` is a bare <dt>/<dd> pair, so
   the hue is the ONLY carrier — colour alone, on a trade where deuteranopia collapses the green and
   the brown toward one olive. The page sheet's own comment ("Amber = money not yet billed;
   green/red = the result's own sign") is a statement of intent that light cannot honour: there is
   no good light amber, and the phone view has nowhere to put a second carrier.

   Neutralising to --ink and not to --ink-dim, per the verdict rule's own footnote: moving the
   shouting from hue to weight is not neutralising it. The label to the left keeps the meaning. */
html[data-theme="light"] .page[data-shell='phone'] .finp-stat .finp-fig.warn,
html[data-theme="light"] .page[data-shell='phone'] .finp-stat .finp-fig.pos,
html[data-theme="light"] .page[data-shell='phone'] .finp-stat .finp-fig.neg {
    color: var(--ink);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   PROOF THAT DARK IS UNMOVED — rendered, not argued
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Every rule above carries `html[data-theme="light"]`, so the prefix argument alone is sufficient.
   It was checked anyway, because §M1 changes a LAYOUT property on the densest table in the app and
   a prefix argument is only as good as the prefix actually being there.

   /finance?tab=invoicing at 1080×810, THEME=dark, with and without this block injected:

       .led-row td   204×66.3281  pad 13.6px 11.2px   (without)
       .led-row td   204×66.3281  pad 13.6px 11.2px   (with)      — byte-identical, all five cells
       .tiles        902×145.453  cols 214.703 ×4     both

   Byte-identical. Noted in passing: dark's own tablet row is 66.3px, so §M1 does not merely stop
   light overshooting — it brings the two themes onto the same row height on this table for the
   first time, which was not the goal but is the right outcome.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   WHERE I DREW THE DOCUMENT / SCREEN BOUNDARY, AS ASKED
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   THE SCREEN, and therefore in scope for the density and target system:
     · /invoices in full — the tiles, the tabs, the filter chips, the search, the band heads and the
       queue rows. §M3 and §M4 act on it.
     · the invoice draft editor's CHROME: `.invd-bar` and everything in it, `.invd-add`,
       `.invd-disc-*`, `.invd-lookup-*`, `.invd-assist-*`, `.invd-editall`, `.invd-discard`,
       `.invd-pdf`, `.invd-back`, and the EDITING state of a ledger line (`.cell-desc`, `.cell
       input`, `.cell .ssel-btn`, `.cell-del`) — these are form controls that happen to sit on a
       document. §M2 acts on the one of them that fails.
     · /finance in full.

   THE DOCUMENT, and therefore exempt — nothing below is touched by anything above:
     · `.invd-doc` itself and its resting typography: the date block, `.invd-cols`, the READING
       state of `.invd-line`, `.invd-group-*`, `.invd-ro-*`, the totals block under its 3px double
       rule, `.invd-footer`, `.invd-note`. §8's own diagnosis already fixes this line ("a totals row
       is allowed to be heavier, and this is a document, not a screen") and §8's closing paragraph
       states the exemption outright. I have not compressed, re-rung or re-derived any of it.

   THE ONE EDGE CASE AND HOW IT WAS DECIDED: `.invd-line.reading`. It is a row of the document AND
   it is the target that opens the editor. §5.6 already floors it at --row-h-comfy and the page
   sheet floors it at 48px under `hover: none`. Measured at 91–114px on the tablet, i.e. governed by
   its content, not by either floor. Left exactly as it is: it clears every target requirement
   without any rule of mine, so the document keeps its own rhythm and the exemption costs nothing.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */



/* ══════════════════════════════════════════════════════════════════════════════════════════════
   DESTINATION: public/theme-light/money.css      §  ANALYS — THE METRIC DETAIL SHEET
   ══════════════════════════════════════════════════════════════════════════════════════════════
   WHY money.css AND NOT A NEW FILE. The brief asked me to say if analytics has no obvious owner. It
   does: money.css already owns Analys outright. It declares `html[data-theme="light"] .an-page` at
   lines 55, 105 and 375, it repoints the whole `--fin-*` token set that this sheet reads
   (`--fin-rule` -> `--keyline` at :729, `--fin-accent` -> `--accent-solid` at :75, `--fin-sheet-edge`
   -> `--border-default` at :59, `--fin-well` -> `--well-a22` at :67), and it ALREADY reaches inside
   this very component at line 485 (`html[data-theme="light"] .an-page .ms-w-val`). MetricSheet is
   rendered by Analytics.vue and nothing else, so it has exactly one cluster. No new file.

   Append as a new section at the end of the file's Analys block, after the existing `.ms-field` /
   `.ms-kbd` rules so the reader meets the search box and the sheet together.
*/

/* ── A1. The scrim darkens. ────────────────────────────────────────────────────────────────────
   CONFIRMED — probed at rgba(227, 232, 248, 0.62) before, rgba(20, 25, 40, 0.42) after.

   `.ms-scrim` was MISSED by the scrim sweep in _base.css §1. That block fixed `.mdl-backdrop`,
   `.modal-backdrop`, `.sheet-backdrop`, `.drawer-backdrop` and `.nav-backdrop` and explicitly warned
   that backdrops built on channel variables invert; this is the sixth one, built on
   `rgba(var(--ch-indigo-050309), 0.62)`, and it does exactly what that block describes — a 62 % PALE
   wash, so the grid behind the sheet bleeds through at full strength and the sheet has no ground.

   `backdrop-filter: none` for the same reason the other five have it: `--glass-veil-3` is a blur
   tuned for a dark ground, it costs a compositor layer on every open, and on a white page it does
   nothing a 42 % scrim has not already done. */
html[data-theme="light"] .ms-scrim {
    background: var(--scrim);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* ── A2. The sheet is a white surface with ONE depth. ──────────────────────────────────────────
   CONFIRMED — probed. background-image `linear-gradient(168deg, rgb(220,225,241), rgb(223,228,245))`
   before; `none` over `rgb(255,255,255)` after.

   Three separate things are wrong in the one declaration:

     1. It is a GRADIENT. Gradient hygiene in this theme is: delete, do not restyle. A vertical wash
        across a 620px-wide, full-height panel is the single largest painted area on the screen and
        there is no version of it that is not a tint.
     2. Both stops are flipping indigo primitives, which is where the blue comes from.
     3. It carries `border-left: 1px solid var(--fin-sheet-edge)` AND sits over a scrim — two edges
        for one plane.

   `background`, not `background-color`, is load-bearing: the shorthand is what CLEARS the
   `background-image`. Naming `background-color` alone leaves the gradient painting on top of it and
   the fix looks applied while changing nothing — the same failure mode _base.css:322 documents for
   `-webkit-text-fill-color`.

   Depth: `--elevation-3` and NO border, following the nav drawer precedent set in _base.css §B1
   verbatim ("Depth is spent once, at the outermost thing that genuinely floats — and a drawer over a
   42 % scrim needs no hairline to find its edge"). This is the same shape: a full-height panel docked
   to one edge over the same 42 % scrim. Two full-height drawers in one app should not disagree about
   whether they have a hairline.

   The `.ms-scrim` anchor is the collision guard documented in the header. */
html[data-theme="light"] .ms-scrim > .ms {
    background: var(--surface);
    border-left: 0;
    box-shadow: var(--elevation-3);
}

/* ── A3. The "there's more below" fade. ────────────────────────────────────────────────────────
   CONFIRMED — probed after scrolling the panel (see the both-themes note at the foot of this file
   for why that step is needed): `linear-gradient(rgba(223,228,245,0), rgba(223,228,245,0.94) 62%)`
   before, `linear-gradient(rgba(255,255,255,0), rgba(255,255,255,0.94) 62%)` after.

   `rgba(var(--ch-indigo-0b0910), …)` resolves to rgba(223, 228, 245, 0.94) in light — a pale blue
   band across the bottom of the panel, and it is the SAME channel as the sheet's own gradient end,
   which is the whole point of the rule. It fades to the sheet's ground, so when the ground becomes
   white the fade must become white or it becomes a visible blue bar instead of an invisible fade.

   Written as `rgba(255, 255, 255, …)` rather than through `--surface` because `--surface` is an
   opaque hex in light and a gradient needs a transparent first stop — there is no token that gives
   both ends, and `color-mix()` is banned (trap #7). If A2's `--surface` ever stops being #ffffff,
   these two literals move with it. */
html[data-theme="light"] .ms-morecue {
    background: linear-gradient(to bottom,
        rgba(255, 255, 255, 0),
        rgba(255, 255, 255, 0.94) 62%);
}

/* ── A4. The "more below" pill stops being an accent chip. ─────────────────────────────────────
   CONFIRMED — probed after the same scroll. Fill rgba(40,81,217,0.16) -> rgb(255,255,255); ink
   rgba(0,42,182,0.82) -> rgb(105,115,134); shadow -> the --elevation-2 triple.

   In light it resolves to fill rgba(40, 81, 217, 0.16), border rgba(58, 100, 237, 0.30), ink
   rgba(0, 42, 182, 0.82) — a blue capsule. It is `pointer-events: none`, so it is not a control and
   has no claim on the accent channel: it is a HINT, and the house says a hint is quiet. Surface,
   hairline, muted ink, contact shadow.

   The shadow was `0 4px 14px rgba(var(--ch-blue-06080f), 0.5)`, another flipping channel — light
   gives rgba(219, 228, 245, 0.5), a pale blue GLOW under a pale blue pill. Elevation-2 instead.

   Radius left alone deliberately: 50px is geometry, it is what makes the cue read as a floating
   marker rather than a box, and the 4px ceiling is about surfaces — not about this. */
html[data-theme="light"] .ms-morecue-pill {
    background: var(--surface);
    border-color: var(--keyline-strong);
    color: var(--ink-muted);
    box-shadow: var(--elevation-2);
}

/* ── A5. "VAD DET BESTÅR AV" — the weight bars go neutral. ─────────────────────────────────────
   CONFIRMED — probed at rgb(47, 79, 208) before, rgb(105, 115, 134) after.

   These are the bars in the user's screenshot. The fill is `var(--fin-accent)`, which money.css §1
   correctly repoints to `--accent-solid` (#2f4fd0) — so this is NOT a flipping-channel bug like the
   rest of this file. It is a design call, and the verdict rule decides it: this bar is the group's
   WEIGHT, drawn on EVERY row, and a colour that appears on every row has stopped carrying
   information. There is no verdict in "this mechanic is 31 % of the sample". Accent on it spends the
   loudest thing on the page saying nothing, on the panel the user is complaining is too blue.

   `--ink-muted` (#697386) on the `--well-a22` track (#d5d8e0) measures 3.2:1 — over the 3:1 floor a
   meter owes, and deliberately NOT `--ink-dim`: when you neutralise a hue you must not replace it
   with weight, or the shouting just changes channels.

   The track is left exactly as it is. money.css §1 already made `--fin-well` a real recess on
   purpose, and it reads correctly against white. */
html[data-theme="light"] .ms-scrim .ms-g-bar i {
    background: var(--ink-muted);
}

/* ── A6. What is DELIBERATELY left blue on this sheet. ─────────────────────────────────────────
   Not a rule — a note, so the next pass does not "finish the job" and remove the three marks that
   are supposed to be there. In light, accent is for ACTIONS, and these three are actions:

     .ms-dim.on              probed rgb(47, 79, 208)  — the SELECTED breakdown dimension. A chosen
                             tab in a tablist is the textbook case for accent.
     .ms-go                  --fin-accent             — "öppna sidan ›". A link.
     .ms-close:focus-visible --fin-accent outline      — a focus ring. Required, not optional.

   All three are correct as they stand and none of them is a surface. */

/* ── A7. The assistant launcher and its panel — the third surface inside the scrim. ────────────
   I INITIALLY GOT THIS WRONG and am leaving the correction visible. I wrote HermesChat off as
   unreachable because `Features.hermes` reads `VITE_FEATURE_HERMES === 'true'` and I assumed the
   default. It renders: the round blue button in the bottom-right of the sheet screenshot is it. It
   sits INSIDE `.ms-scrim`, so it is part of the surface the user is pointing at, and it carries the
   same flipping-primitive disease as everything else here.

   MEASURED, light:
     .hf-btn  background-image  linear-gradient(150deg, rgb(56,97,230), rgb(74,114,247))
     .hf-btn  color             rgb(0, 42, 182)      <- BLUE ICON ON A BLUE FILL, about 2:1
     .hf-btn  box-shadow        rgba(226,231,247,0.62) 0 8px 26px   <- a pale glow, not a shadow

   The launcher IS an action, so it is entitled to the accent — as a FILL, flat, with white on it.
   Three separate corrections:

     - the gradient goes (`--c-indigo-6d4bd8` / `--c-indigo-4c31a5`, both flipping) and becomes
       `--accent-solid`. Gradient hygiene: in light, delete rather than restyle;
     - the ink becomes white. `#fff` on `#2f4fd0` measures 6.4:1, and a 24px glyph owes 3:1 as a
       graphical object — it currently misses that by half;
     - the shadow was `rgba(var(--ch-indigo-06030e), 0.62)`, which flips to a PALE value: a light
       halo under a button, which is a glow, and this theme does not glow.

   The border goes with the gradient. A solid accent fill does not need an edge, and the edge it had
   was `rgba(var(--ch-indigo-a78bfa), 0.35)` — a fourth flipping channel on one 56px control.

   `.hf-btn.open` needs nothing: it already reads `--fin-well` / `--fin-rule-mid` / `--fin-ink-2`,
   all three of which money.css §1 and §5 have already repointed. Left alone deliberately. */
html[data-theme="light"] .hf-btn {
    background: var(--accent-solid);
    border: 0;
    color: #ffffff;
    box-shadow: var(--elevation-2);
}

html[data-theme="light"] .hf-btn:hover {
    box-shadow: var(--elevation-3);
}

/* The chat panel itself — `linear-gradient(168deg, var(--c-indigo-191428), var(--c-indigo-100c1a))`
   over `border: 1px solid rgba(var(--ch-indigo-a78bfa), 0.22)` over
   `box-shadow: 0 18px 48px rgba(var(--ch-indigo-04020a), 0.68)`. Same three faults as `.as-pop`
   (§S1): a flipping gradient, a border AND an elevation, and a dark-calibrated 48px drop. It is a
   popover, so it keeps the elevation and gives up the border; `--elevation-2`'s built-in 1px ring is
   the hairline. Radius to the 4px ceiling.

   `.hf-head`'s divider is `--fin-rule`, already `--keyline` in light. Nothing to add. */
html[data-theme="light"] .hf-panel {
    background: var(--surface);
    border: 0;
    border-radius: var(--r-panel);
    box-shadow: var(--elevation-2);
}
