﻿/* ==========================================================================
   Kundvagn — the read-only basket (#3025, slice 5).
   Composes btj.css tokens only; no new custom properties. Mobile-first, and
   the row is a grid so cover / text / money re-flow without a media query
   per element.
   ========================================================================== */

.kv { display: grid; gap: var(--sp-5); }

/* --- Head ---------------------------------------------------------------- */

.kv-head h1 { margin: 0 0 var(--sp-1); font-size: var(--fs-2xl); line-height: 1.2; }

.kv-ident {
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-3);
    color: var(--c-text-muted);
}

.kv-alias { font-weight: 600; color: var(--c-text); }

/* Legacy's exemplar count, under the cart's name (CartInfo.ascx). A fact about the basket,
   drawn like the identity line above it and not like a total. NOT `.kv-amount`, which is the
   cart ROW's money-and-quantity panel further down this file — the name was free-looking and is
   not, and a print rule hiding it would have taken every row's price off the sheet. */
.kv-itemcount {
    margin: var(--sp-1) 0 0;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

.kv-message,
.kv-truncated,
.kv-warning {
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-left: 4px solid var(--c-accent);
    border-radius: var(--radius);
    font-size: var(--fs-sm);
}

/* The notice now carries the button that clears it (6a M2), so it holds a stack, not one line. */
.kv-warning { display: grid; gap: var(--sp-3); justify-items: start; }
.kv-warning p { margin: 0; }

.kv-empty {
    margin: 0;
    padding: var(--sp-6) var(--sp-4);
    text-align: center;
    color: var(--c-text-muted);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}

/* --- Booklet filter ------------------------------------------------------ */

.kv-filter {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    font-size: var(--fs-sm);
}

.kv-filter-label { color: var(--c-text-muted); }

.kv-filter ul {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Filter chips: tonal when off, solid when on. The unselected state carries
   no outline — a row of outlined pills reads as a row of buttons, and only
   one of them is the current filter. */
.kv-chip {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-2);
    padding: 3px 12px;
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    text-decoration: none;
}

.kv-chip:hover { background: var(--c-surface-3); color: var(--c-text); }

.kv-chip.is-selected {
    background: var(--c-accent);
    border-color: var(--c-accent);
    color: #fff;
}

.kv-chip-count { color: var(--c-text-muted); font-size: 0.75rem; }
.kv-chip.is-selected .kv-chip-count { color: #fff; }

/* --- Groups and sections ------------------------------------------------- */

.kv-group { display: grid; gap: var(--sp-3); }

.kv-group-head {
    margin: 0;
    font-size: var(--fs-lg);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--c-border);
}

.kv-section { display: grid; gap: var(--sp-2); }

.kv-section-head {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--c-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.kv-group-total {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-4);
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.kv-group-total strong { color: var(--c-text); }

/* --- Rows ---------------------------------------------------------------- */

.kv-rows {
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    overflow: hidden;
}

.kv-row {
    display: grid;
    grid-template-columns: 3.5rem 1fr;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-4);
    /* Tone, not a line (docs/DesignSystem.md): rows are told apart by a hairline in the
       surface stepping rather than by --c-border, which at full strength turned a long
       cart into a ledger. */
    border-top: 1px solid var(--c-surface-3);
}

.kv-row:first-child { border-top: 0; }

/* A row that cannot be ordered says so in its own ground, warm-tinted, so the reason
   chip under the title is not the only sign of it. */
.kv-row:has(.kv-flag-warn) { background: color-mix(in srgb, var(--c-warm-soft) 55%, transparent); }

.kv-cover { grid-row: span 2; }

/* Block, so the picture is the whole of the link and not a glyph on a line: an inline anchor
   leaves the image sitting on a baseline, with a few pixels of descender space under it that
   the shadow then draws a second edge across. */
.kv-cover a { display: block; }

.kv-cover img {
    width: 100%;
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}

.kv-body { min-width: 0; }

.kv-title {
    margin: 0;
    /* The headline face at a size of its own: this is the one thing on the row a reader
       is scanning for, and it was taking the body face at body size. */
    font-family: var(--font-headline);
    font-size: var(--fs-lg);
    font-weight: 600;
    line-height: 1.3;
}
.kv-title a { color: inherit; text-decoration: none; }
.kv-title a:hover { color: var(--c-accent); text-decoration: underline; }
.kv-title a:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

.kv-byline {
    margin: var(--sp-1) 0 0;
    color: var(--c-text-muted);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
}

.kv-facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-2);
    margin: var(--sp-2) 0 0;
    color: var(--c-text-muted);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
}

.kv-facts span:not(:last-child)::after {
    content: "·";
    margin-left: var(--sp-2);
    color: var(--c-border);
}

.kv-facts:empty { display: none; }

.kv-added {
    margin: var(--sp-1) 0 0;
    color: var(--c-text-muted);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
}

/* --- Row states and the rest of the row (M4) ------------------------------ */

.kv-flags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-2);
    margin: var(--sp-2) 0 0;
}

/* The foundation's chip, not a bordered box: a tinted pill is the shop's own way of
   saying "a state worth naming" (btj.css .chip). */
.kv-flag {
    padding: 1px var(--sp-2);
    border: 0;
    border-radius: var(--radius-pill);
    background: var(--c-surface-3);
    color: var(--c-text-muted);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: 1.7;
    white-space: nowrap;
}

/* Legacy puts an error icon beside these two; the warm tint does the same job. */
.kv-flag-warn { background: var(--c-warm-soft); color: var(--c-warm-soft-text); }
.kv-flag-info { background: var(--c-surface-3); color: var(--c-text-muted); }

/* Label/value pairs: one column on a phone, label + value from 560px up. */
.kv-details {
    margin: var(--sp-2) 0 0;
    display: grid;
    gap: var(--sp-1) var(--sp-3);
    font-size: var(--fs-sm);
}

.kv-details {
    grid-template-columns: max-content minmax(0, 1fr);
    font-family: var(--font-label);
}

.kv-details dt {
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.kv-details dd { margin: 0; }

.kv-equipment { margin: 0; padding: 0; list-style: none; }
.kv-equipment-price { color: var(--c-text-muted); }

/* Money sits under the text on a phone and beside it from 560px up. */
.kv-amount {
    grid-column: 2;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-3);
    font-size: var(--fs-sm);
}

.kv-amount:not(.is-editing) {
    justify-content: flex-end;
    align-content: start;
    font-family: var(--font-label);
}

.kv-qty { font-weight: 600; }

/* #3278: the one control a row that can no longer be ordered keeps. A line of its own under the
   amounts rather than another item in the money column's wrap — it is an action on the row, and
   next to "1 st" and "189,00:-" it would read as one more figure. The column is right-aligned
   (.kv-amount:not(.is-editing)), so the button follows that edge. */
.kv-inert-actions {
    flex: 0 0 100%;
    display: flex;
    justify-content: flex-end;
    margin-top: var(--sp-2);
}
.kv-unit { color: var(--c-text-muted); font-size: var(--fs-xs); }

/* What the line costs is the number the caller came for — the only figure on the row set
   above the small print, and the only one in the ink colour. */
.kv-rowtotal {
    font-family: var(--font-headline);
    font-weight: 700;
    font-size: var(--fs-lg);
    white-space: nowrap;
}

.kv-noprice { color: var(--c-danger); font-style: italic; }

/* An editable row carries a whole form (quantity, egen info, the equipment picker, actions) far too
   wide for the right-hand column. At every width it drops to a full-width panel below the cover +
   title, rather than staying crammed in the second column — on a phone that left a tall empty gutter
   under the thumbnail. The ≥560px block only refines the inner alignment. `:has` scopes the cover so
   it no longer reserves the panel's row on a narrow, editable row. */
.kv-amount.is-editing { grid-column: 1 / -1; }
.kv-row:has(.kv-amount.is-editing) .kv-cover { grid-row: 1; }

/* --- Editing a row (6a) --------------------------------------------------- */

/* The editable form is three stacked sections — a top line of short controls, the two disclosure
   panels, and the actions footer — rather than one flat wrap where a full-width panel forced every
   control onto its own line. */
/* THE ORDER PANEL IS A WELL, NOT MORE ROW. Legacy draws the beställningspost surface on
   its own grey ground (CartItemEgenInfoContainer); without that the twenty-odd controls
   read as a continuation of the title block and the row lost its shape. One step down the
   surface stage plus a ghost border — no rule, per the design system. */
/* IT TAKES THE WHOLE LINE, which below 560px it did not (2026-09-01). .kv-amount is a flex
   container down there — only the >=560px block turns it into a stretching grid — so the form was a
   flex item with an auto basis: sized to its CONTENT, sitting at the leading edge of a row it was
   narrower than. Every symptom reported against this well (it "is not aligned", it is "left", it is
   not "centered on the tile") is that one fact, and none of them is fixable by moving it, because
   what was wrong was its width. `1 1 100%` is inert at the widths where .kv-amount is a grid, so it
   is stated once here rather than inside a query. */
.kv-edit {
    display: grid;
    gap: var(--sp-3);
    margin-top: var(--sp-1);
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface-2);
    border-radius: var(--radius);
    box-shadow: var(--ghost-border);
}

/* IT TAKES THE WHOLE LINE, which below 560px it did not. .kv-amount is a flex container down there
   — only the >=560px block turns it into a stretching grid — so the form is a flex item with an auto
   basis: sized to its CONTENT, sitting at the leading edge of a row it is narrower than. Every
   symptom reported against this well on 2026-09-01 (it "is not aligned", it is "left", it is not
   "centered on the tile") is that one fact, and none of them is fixable by moving it, because what
   is wrong is its width.

   BOTH CARTS since 2026-09-04. `1 1 100%` is inert at the widths where .kv-amount is a grid, so it
   needs no query of its own; and on a row whose form already fills the line — most of the shop's —
   it changes nothing on screen. It is the guarantee that a narrow form still takes the width, not a
   visible change to every row. */
.kv-edit { flex: 1 1 100%; }

/* On an unavailable row the well would otherwise vanish into its warm ground. */
.kv-row:has(.kv-flag-warn) .kv-edit { background: var(--c-surface); }

.kv-edit-top,
.kv-edit-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
}

/* The actions sit below a hairline of the well's own ground, so "what I am changing" and
   "what I do about it" are two bands rather than one long wrap. */
.kv-edit-actions {
    gap: var(--sp-2);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--c-surface-3);
}

.kv-row:has(.kv-flag-warn) .kv-edit-actions { border-top-color: var(--c-surface-2); }

/* Egen info and the equipment picker are peers — either can be the taller — so they take equal,
   responsive columns: side by side when the row has room for two 26rem tracks, stacking to one
   column when it does not (auto-fit + min(100%, 26rem), so it never overflows a narrow row and
   needs no width breakpoint). Each panel then fills its column with its own field grid, which is
   what keeps a many-field egen info from collapsing into a single tall column. */
/* auto-FILL, not auto-fit. The difference is the whole reason a row with one disclosure looked
   unlike a row with three: auto-fit COLLAPSES the tracks nothing lands in, so a lone "Egen info till
   beställningspost" — one panel holding one Notis field — was stretched across the full column,
   while its neighbour with three panels drew them at half that. auto-fill keeps the empty track, so
   a panel is one column of a fixed rhythm whatever the row happens to offer, and the width a caller
   reads is the same all the way down the basket. */
.kv-edit-panels {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 26rem), 1fr));
    gap: var(--sp-3) var(--sp-5);
    align-items: start;
}
.kv-edit-panels > details { margin-top: 0; }

/* --- How wide a field is allowed to get -----------------------------------
   A quarter of the CONTENT COLUMN, not of the viewport.

   The first cut said max(25vw, 15rem) and was dead on exactly the screens it was written for.
   `<main class="page">` is capped at --page-max (72rem), so the column is ~70rem however wide the
   monitor is: on a 2560px screen a quarter of the VIEWPORT is 640px — wider than any field track on
   the page — and the cap never applied. Nothing here is ever a share of a wide screen, so measuring
   against one asks a question whose answer is always "no". Derived from --page-max instead, so it
   also follows the page width if that is ever changed.

   There is no matching cap on the panel: auto-fill above already holds one to a single column of the
   rhythm, which is half the content column and narrower than any cap worth writing.

   The media query is the width guard: a quarter of the column (18rem) is NARROWER than a phone's
   whole column, so applying it unconditionally would leave a stray short box in a full-width panel
   rather than capping anything. Above 48rem the column is wide enough for it to be a cap — which is
   also the only place the rule has anything to do. */
@media (min-width: 48rem) {
    .kv-egeninfo-field,
    .kv-eqpicker-field { max-width: calc(var(--page-max) / 4); }
}

/* THE PICKER COMES OFF THE ROW ONCE THE DIALOG CAN HOLD IT (2026-08-31, asked for). confirm-submit.js
   adds .js-confirm-control to <html> only when it is running AND the browser has a real <dialog> to
   move the control into, so this hides nothing that the enhancement then fails to show (#3149's one
   statement). No script, or no <dialog>, and the row keeps the inline picker it has always had.

   Scoped to `.kv-move .kv-select` rather than to the select itself, which is what makes the move
   work with one rule: while the node is inside the dialog it has no .kv-move ancestor, so the same
   selector stops matching and it is visible there without a second, opposing declaration. */
.js-confirm-control .kv-move .kv-select { display: none; }

/* The move controls (Till / Flytta / Kopiera) travel together and sit at the far end, apart from
   the save/remove pair. */
.kv-move {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-left: auto;
}

.kv-qty-label,
.kv-check,
.kv-select {
    font-family: var(--font-label);
    font-size: var(--fs-sm);
}

.kv-qty-label { color: var(--c-text-muted); }

/* The box itself is .antal / .antal-field in btj.css (#3198) — 4rem where this row drew 4.5,
   and with the browser's spinners replaced by a stepper that also exists on a phone. Nothing
   about the control is this sheet's any more. */

.kv-check,
.kv-select {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    color: var(--c-text-muted);
}

/* "Hyllkod Libris" is a text box on the same line as the two dropdowns beside it, so it takes the
   same declaration rather than a lookalike of its own — legacy gives it the same InputRounded class
   its neighbours have. Sized to the values it actually holds ("Bilderbok", "Hcf", "796j" on prd),
   not to the column's 4000 characters. */
.kv-select select,
.kv-holdingcode input {
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-surface);
    color: var(--c-text);
    font: inherit;
    font-family: var(--font-label);
}

.kv-holdingcode input { width: 9rem; }

/* Shut while the row does not order a Libris post — the state legacy paints #E9E9E9 and this app
   takes from the tonal scale (cart-biblpost.js). A value already stored stays legible: it is what the
   order carries until the next save, and greying is not hiding. */
.kv-holdingcode input.is-shut {
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    cursor: not-allowed;
}

.kv-save,
.kv-remove {
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    background: var(--c-surface);
    color: var(--c-text);
    font: inherit;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

/* Saving is the row's one primary act and it was a ghost button among five. The accent
   fill is the same one the checkout link carries, one size down. */
.kv-edit-actions > .kv-save:first-child {
    background: var(--c-accent);
    border-color: var(--c-accent);
    color: #fff;
}

.kv-edit-actions > .kv-save:first-child:hover {
    background: var(--c-accent-dark);
    border-color: var(--c-accent-dark);
    color: #fff;
}

.kv-save:hover { border-color: var(--c-accent); color: var(--c-accent); }

/* Removal is destructive and has no undo — it reads as such before it is clicked, not after. */
.kv-remove { color: var(--c-danger); }
.kv-remove:hover { border-color: var(--c-danger); background: color-mix(in srgb, var(--c-danger) 10%, transparent); }

.kv-save:focus-visible,
.kv-remove:focus-visible,
.kv-select select:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}


/* --- "Egen info" on the row (6a M4) ---------------------------------------
   Legacy hides these behind a JS-toggled panel; a <details> is the same disclosure without a
   script, and it opens itself when the row already carries values. Full width inside the flex
   form, because a column of labelled fields cannot share a line with the quantity box. */
.kv-egeninfo {
    flex-basis: 100%;
    margin-top: var(--sp-1);
    font-size: var(--fs-sm);
}

/* An "Etiketter" panel with nothing open in it. Restated as an author rule for the same reason
   .kv-label-group[hidden] is: the attribute's user-agent rule loses to any author `display`, and one
   arriving on a details in this grid later would silently un-hide every empty panel. */
.kv-egeninfo[hidden] { display: none; }

/* A disclosure that looks like one. The marker is dropped for a chevron the rule can
   rotate, and the summary is a tinted strip so the closed state still reads as a control
   rather than as a stray line of grey text. */
.kv-egeninfo > summary,
.kv-eqpicker > summary {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    background: var(--c-surface-3);
    color: var(--c-text);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
    list-style: none;
}

/* The shop's coarse-pointer floor (btj.css) names buttons, inputs and selects, and a <summary>
   is none of those, so these three strips drew 37px on a phone (#3011, measured 2026-09-16) —
   on both carts, three per row. */
@media (pointer: coarse) {
    .kv-egeninfo > summary,
    .kv-eqpicker > summary {
        min-height: var(--control-min);
    }
}

.kv-egeninfo > summary::-webkit-details-marker,
.kv-eqpicker > summary::-webkit-details-marker { display: none; }

.kv-egeninfo > summary::before,
.kv-eqpicker > summary::before {
    content: "";
    width: 0.4em;
    height: 0.4em;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    transition: transform 120ms ease;
}

.kv-egeninfo[open] > summary::before,
.kv-eqpicker[open] > summary::before { transform: rotate(45deg); }

@media (hover: hover) {
    .kv-egeninfo > summary:hover,
    .kv-eqpicker > summary:hover { background: var(--c-surface-4); }
}

.kv-egeninfo > summary:focus-visible,
.kv-eqpicker > summary:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* Legacy groups the row's fields under two headings — "Etiketter" and "Egen info till
   beställningspost" (CommonCartItemUC.ascx). Both are now the SUMMARY of their own disclosure
   (#3086), so the headings that used to sit inside are gone with the wrapper that held them; what
   remains is the section box the label groups and the shelf-sign sentence stack in. */
.kv-egeninfo-section { margin-top: var(--sp-3); }
.kv-egeninfo-section:first-of-type { margin-top: var(--sp-2); }

/* The fields flow in a multi-column grid that fills the panel — an organisation can configure many
   "egen info" fields, so this must hold at 1 (just Notis) and at 8+. auto-fit collapses the empty
   tracks, so a lone Notis stretches the panel rather than sitting in a lone narrow cell. */
.kv-egeninfo-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--sp-2) var(--sp-3);
    margin-top: var(--sp-2);
}

/* Each "Etiketter" group (bibliografisk etikett, ryggetikett) is its own tonal block: a legend, its
   fields, and the "Hämta från Libris" button cart-libris.js appends into the footer. The blocks flow
   in an auto-fit grid so they sit side by side on a wide row and stack when narrow; the equipment
   picker shows or hides a whole block live (cart-equipment.js). The [hidden] rule must be restated
   because an author `display` overrides the user-agent rule that hides the attribute.

   THE FLOOR IS THE RYGGETIKETT BLOCK'S, NOT THE WIDER-LOOKING ONE'S (reported 2026-09-03 against the
   häfte's cart: the ryggetikett "looks weird", and only there). 16rem was read off the bibliographic
   block, whose fields are `minmax(8rem, 1fr)` and shrink with whatever they are given — so the number
   was true of the block that could not overflow and false of the one that can. The extended
   ryggetikett is four boxes at a STATED width (`max(10ch + padding, 9rem)` = 144px, the comfort floor
   below), two to a line, so its block needs 2 x 144 + --sp-3 + 2 x --sp-3 = 324px = 20.25rem before
   anything about it is flexible. Between 16rem and that, the boxes simply hung out of their own
   tonal block: measured 2026-09-03, +50px of spill in a 560px column, +30 at 600, +10 at 640, clean
   from ~660. The shop's cart row is wider than that at every desktop width, which is why this was a
   häfte-only fault — DH's cart sits in the main column beside the tree — and not a häfte-only rule.

   21rem, so the pair splits only where both blocks fit, with a few pixels of slack. min(21rem, 100%)
   because an auto-fit floor WIDER than its container overflows: the DH cart overrides this grid to
   one column below 36rem, and the shop's does not. */
.kv-label-groups {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(21rem, 100%), 1fr));
    gap: var(--sp-3);
    margin-top: var(--sp-2);
}

/* The tonal block itself, shared by every one of the row's three disclosures (#3086): each label
   group inside "Etiketter", the field grid inside "Egen info till beställningspost", and the
   controls inside "Utrustning". ONE declaration rather than three copies — "same panel as
   Bibliografisk etikett" is the requirement, and three copies is how it stops being true. */
.kv-label-group,
.kv-panel {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-3);
    /* One step below the well the block sits in, or it disappears into it. */
    background: var(--c-surface-3);
    border-radius: var(--radius-sm);
}
.kv-label-group[hidden] { display: none; }

/* The panel is the disclosure's whole body, so it carries the gap under the summary — and its own
   first child must not add a second one on top of it. */
.kv-panel { margin-top: var(--sp-2); }
.kv-panel > .kv-egeninfo-fields,
.kv-panel > .kv-eqpicker-fields { margin-top: 0; }

.kv-label-group-legend {
    margin: 0;
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--c-text-muted);
}

/* The shelf-sign-category sentence (#3073): a statement where a field would otherwise be, so it
   reads as one of the tonal label blocks rather than as a warning. Legacy draws it in red; here the
   accent tint carries the same "this is decided for you" weight without shouting. */
.kv-shelfsign-choice {
    padding: var(--sp-3);
    background: var(--c-surface-3);
    border-left: 3px solid var(--c-accent);
    border-radius: var(--radius-sm);
}
.kv-shelfsign-choice[hidden] { display: none; }

.kv-shelfsign-choice-text {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

/* The ryggetikett fields drop their visible label — it only repeats the legend — but keep it as an
   accessible name for screen readers (clipped, not display:none, so it stays in the a11y tree). */
.kv-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* The group's own fields — two wide bibliographic lines, or the four short ryggetikett rows —
   flow in a compact auto-fit grid inside the block; a validation message spans the whole row. */
.kv-label-group-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
    gap: var(--sp-2) var(--sp-3);
}
.kv-label-group-fields .kv-error { grid-column: 1 / -1; margin: 0; }

/* A RYGGETIKETT IS PRINTED AS LINES, SO IT IS EDITED AS LINES (#3201). The rows of
   "Ryggetikett egen text" are the lines of one label, not fields about one thing — an auto-fit
   flow put rad 3 beside rad 2 and rad 4 under rad 1, so reading order and print order
   disagreed. Two to a line (asked for), which keeps 1 2 / 3 4 intact and halves a block that
   was taller than the row it sits in; each box says which line it is, and that is what makes a
   pair legible where four anonymous boxes were not. Legacy stacks them in one declaration
   (`.CartItemEgenInfoContainer .CartItemEgenInfoExtended { display: block }`), so its flow was
   a parity gap as well as an unclear control; this is that fix with the height paid back.

   Scoped to a group that HAS a second field, so the one-line variant is not laid out in two
   columns — and by :nth-of-type, since a validation <p> can sit between two <label>s. */
.kv-label-group[data-eq-labels="spine"] .kv-label-group-fields {
    grid-template-columns: minmax(0, max-content);
}

.kv-label-group[data-eq-labels="spine"]
    .kv-label-group-fields:has(label.kv-egeninfo-field:nth-of-type(2)) {
    grid-template-columns: repeat(2, max-content);
}

/* THE LIBRIS BUTTON SITS BESIDE THE FIELDS IT FILLS (reported 2026-09-03: the ryggetikett "looks
   weird" on the häfte's cart). Every label block puts the button in its footer — bottom, trailing
   edge — which is right where the fields FILL the block: the bibliografisk pair spans the row, so a
   button under it at the right reads as that block's action. The ryggetikett's boxes do not span
   anything: they are a picture of the printed line (43 characters, or four rows of ten), so the
   block was a short box at the leading edge, a long stretch of nothing, and a button pinned to the
   far corner — two things in one block with no relation drawn between them.

   So this group lays its own children out in a row instead: legend on its own line, then the fields
   and the button side by side, and the button wraps UNDER the fields at the leading edge when the
   two do not fit rather than hanging off the right. `align-items: flex-end` puts it on the boxes'
   own bottom edge; `min-width: 0` on the fields lets the grid inside them shrink, which a flex item
   does not do on its own.

   Only this group, deliberately. The rule the footer states — a block's button belongs at its
   bottom, so side-by-side blocks of unequal height keep their buttons on one line — is still right
   for a block whose fields use the width they are given. */
.kv-label-group[data-eq-labels="spine"] {
    flex-flow: row wrap;
    align-items: flex-end;
    column-gap: var(--sp-3);
}

.kv-label-group[data-eq-labels="spine"] > .kv-label-group-legend { flex-basis: 100%; }
.kv-label-group[data-eq-labels="spine"] > .kv-label-group-fields { flex: 1 1 auto; min-width: 0; }
.kv-label-group[data-eq-labels="spine"] > .kv-libris { margin-top: 0; }

.kv-label-group[data-eq-labels="spine"] .kv-egeninfo-field input {
    /* Sized from the field's own capacity — --kv-chars is its maxlength, `ch` the advance of
       "0" in the face it is typed in — plus this input's own padding and border, stated rather
       than guessed. `ch` is honest here and not merely convenient: measured in Jost at the
       control's size, a lowercase letter averages 0.96 of the zero and a mixed string 0.92, so
       a box of N ch holds its N characters with a little to spare.

       The floor is the part that is a comfort decision rather than a measurement (asked for,
       "make them a bit bigger"): ten characters is ~102px, which is a small target and reads
       as cramped beside a 45-character bibliografisk field. So capacity sets the box wherever
       capacity is the larger number, and below that the box stops shrinking — the ryggetikett
       row stays plainly shorter than the bibliografisk one, which is the comparison that
       carries the meaning, without any single box being uncomfortable to hit. */
    width: max(calc(var(--kv-chars, 20) * 1ch + 2 * var(--sp-2) + 2px), 9rem);
    max-width: 100%;
}

.kv-egeninfo-field {
    display: flex;
    flex-direction: column;
    gap: 2px;
    color: var(--c-text-muted);
}

.kv-egeninfo-field input,
.kv-egeninfo-field select {
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-surface);
    color: var(--c-text);
    font: inherit;
    font-family: var(--font-label);
}

.kv-egeninfo-field input:focus-visible,
.kv-egeninfo-field select:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* --- "Hämta från Libris" auto-fill (6a route B, cart-libris.js) -------------
   Injected by the script after the label field it fills — so it never renders with JS off, where the
   field stays hand-editable. It flows as another cell in the egen-info grid; a small text button, not
   a primary action, since it only pre-fills what the caller could type. */
.kv-libris {
    display: flex;
    flex-direction: column;
    gap: 2px;
    align-self: end;
}

/* Inside a label block the button is the footer: pushed to the block's bottom, so side-by-side
   blocks of unequal height keep their buttons on one line, and to the trailing edge. Two buttons in
   one block (the single-line ryggetikett's SAB + Dewey) stack there. */
.kv-label-group .kv-libris {
    flex-direction: row;
    align-items: baseline;
    gap: var(--sp-2);
    align-self: flex-end;
    margin-top: auto;
}

.kv-libris-btn {
    align-self: start;
    padding: 2px var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    background: var(--c-surface);
    color: var(--c-text-muted);
    font: inherit;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    cursor: pointer;
}

.kv-libris-btn:hover { border-color: var(--c-accent); color: var(--c-accent); }
.kv-libris-btn:disabled { opacity: 0.6; cursor: progress; }

.kv-libris-btn:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* Legacy alert()s the Libris miss; here it is an inline line, so the row is not interrupted and an
   existing value is visibly left alone. */
.kv-libris-status {
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.kv-libris-status.is-error { color: var(--c-danger); }

/* --- The edit form on a phone (2026-09-01) --------------------------------
   THE TOP LINE AND THE ACTIONS ARE ROWS OF CONTROLS, AND A PHONE HAS NO ROW. Both are flex-wrap
   bands written for a well that fits two or three controls across; at ~300px they wrap into a
   ragged stair — a label orphaned from the box it names, "Hyllkod Libris" whose 9rem box is wider
   than what is left of the line, and five action buttons at three different lengths with a hole in
   the middle where .kv-move's auto margin pushes its half to the trailing edge.

   Same reading as the action bar above: what wraps is not made to wrap more tidily, it stops being
   a row. The controls become full-width stacked fields with their label above, which is the one
   arrangement where the label is unambiguously attached to its own box, and the buttons become
   equal pairs — the two acts on THIS row, then the two that move it elsewhere — so the actions read
   as two decisions rather than as four buttons of arbitrary width.

   BOTH CARTS SINCE 2026-09-04 — reported against the shop's: "a big part of hor space is for
   margins, Ryggetikett is 4 margins in". It was written on the häfte and scoped `.dh` because only
   the häfte had been asked about, and the reversal is what that scope was for (the action bar below
   was un-scoped the same way a day earlier). Nothing here is a fact about which cart is drawn: the
   page, the row, the well and the label block nest identically in both, and at 390px they spend
   16 + 16 + 16 + 12 = 60px a side before a field starts — the four margins that were counted. What
   differs is that the shop's row is WIDER at desktop widths, which is why the overflow this block
   fixes was only ever visible on the häfte and the wasted width was visible on both.

   WHAT IS NOT BROUGHT ALONG IS THE PIN, the same carve-out the bar makes: `.dh .kv-bar` keeps its
   scope, because "may a band hold the top of a phone screen permanently" is a different question.

   AND THAT IS WHY THIS BLOCK SITS HERE, after the rules it overrides rather than up in "Editing a
   row" where it was written. `.dh` was carrying a class of specificity as well as a scope: with it,
   `.dh .kv-label-group-fields` (0,2,0) beat the base `.kv-label-group-fields` (0,1,0) from anywhere
   in the file. Without it the two TIE, and a tie is settled by document order — so lifting the
   scope alone silently handed the bibliografisk pair back to the base `repeat(auto-fit,
   minmax(8rem, 1fr))` (measured: two 132px tracks where this block asks for a max-content label
   column and 101/163). Nothing errors and the page still looks plausible, which is the whole
   hazard. Moving it below the base rules is what makes the declarations mean what they say — and
   it is load-bearing: put this block back above "Egen info" and half of it stops applying.

   36rem, matching the bar, because it is the same well and the same question about it. */
@media (max-width: 36rem) {
    /* The well gives back its horizontal padding: --sp-4 either side is ~32px of a ~300px column
       spent on air before a single field starts. It keeps its ground, its radius and its inset —
       the full-bleed band that was tried here (2026-09-01) bought ~32px and read worse for it:
       inside a row that is itself inside a page, an edge-to-edge well stops looking like a well. */
    .kv-edit { padding: var(--sp-3); }


    /* TWO COLUMNS: THE LABEL, THEN THE CONTROL. The stack was six lines of label-over-box centred
       in the well, which spends the height twice over and puts a gutter down both sides of every
       field — the dead space reported 2026-09-01. Beside each other they are three lines with no
       gutter, and the controls all start at one edge, which is the alignment a form is read down.

       SUBGRID is what makes that edge shared. Each field is its own <label> (.kv-select wraps its
       text AND its control) so their label columns are independent boxes; inheriting the parent's
       two tracks is the only way "Antal", "Bibl.post" and "Hyllkod Libris" line up without a magic
       width copied onto each. Where it is unsupported the declaration is dropped and the field
       falls back to one column — label over box, which is where this started and is readable.

       The label column is max-content, i.e. the width of "Hyllkod Libris" and nothing more, so the
       control column gets everything else — ~190px of a ~300px well, which the 152px antal stepper
       and both dropdowns fit inside. */
    .kv-edit-top {
        display: grid;
        grid-template-columns: max-content minmax(0, 1fr);
        align-items: center;
        gap: var(--sp-2) var(--sp-3);
    }

    .kv-edit-top > .kv-select {
        display: grid;
        grid-column: 1 / -1;
        grid-template-columns: subgrid;
        align-items: center;
        gap: inherit;
    }

    /* The boxes take the control column rather than a width of their own: 9rem was measured against
       the values a hyllkod holds, which is the right number on a line it shares with two other
       controls and an arbitrary one on a line of its own. */
    .kv-edit-top .kv-select select,
    .kv-edit-top .kv-holdingcode input { width: 100%; }

    /* The antal box keeps its own width — it is a 2-digit field with a stepper sized in btj.css, and
       stretching it to the control column would be a box that lies about what it takes. It is the
       one cell that does not fill its track. */
    .kv-edit-top > .antal-field { justify-self: start; }


    /* AND THE SAME TWO COLUMNS INSIDE "ETIKETTER" (2026-09-01, reported: the ryggetikett rows
       "are not in the mobile fix"). The block above reaches .kv-edit-top and stops there, so the
       label groups kept the stack it was written to replace — and paid a second price the top line
       does not: .kv-label-group-fields sizes the spine group as `repeat(2, max-content)` over boxes
       with a 9rem floor, which is 288px of track plus the gap inside a ~276px group. max-content
       tracks do not shrink, so that is an overflow rather than a squeeze, and it is why these four
       boxes read as broken where the fields above them merely read as tall.

       ONE COLUMN OF FIELDS, each field label-beside-box on the parent's two tracks — the subgrid
       arrangement .kv-select uses above, for the same reason: the label columns are independent
       boxes otherwise, and inheriting the tracks is what puts "Rad 1".."Rad 4" and both
       bibliografisk lines on one edge without a width copied onto each. 1 2 / 3 4 survives it: the
       #3201 pairing exists to keep reading order and PRINT order the same, and a single column is
       1 2 3 4 straight down.

       The boxes keep their own width rather than filling the control track, which is the antal
       box's rule above and the same argument — a ryggetikett row holds ten characters and a box
       that stretches to 190px lies about that. The bibliografisk lines have no width of their own
       and go on filling, since 45 characters is more than the track has either way.

       NOT the "Egen info till beställningspost" fields beside them: those labels are the
       organisation's own configured text, of no bounded length, so a max-content label column is
       one long parameter name away from leaving the control nothing — they stay stacked, which is
       the arrangement that cannot be starved. */
    .kv-label-groups { grid-template-columns: minmax(0, 1fr); }

    .kv-label-group-fields,
    .kv-label-group[data-eq-labels="spine"] .kv-label-group-fields,
    .kv-label-group[data-eq-labels="spine"]
        .kv-label-group-fields:has(label.kv-egeninfo-field:nth-of-type(2)) {
        grid-template-columns: max-content minmax(0, 1fr);
        align-items: center;
    }

    .kv-label-group-fields > .kv-egeninfo-field {
        display: grid;
        grid-column: 1 / -1;
        grid-template-columns: subgrid;
        align-items: center;
        gap: inherit;
    }

    /* …EXCEPT WHERE THERE IS NO LABEL TO ALIGN, WHICH IS AN OVERFLOW AND NOT A NICETY. The one-line
       ryggetikett's label is .kv-sr-only — `position: absolute`, and an absolutely positioned child
       of a grid container is NOT a grid item — so the input is the field's first in-flow child and
       auto-placement puts it in the LABEL column, which is `max-content`. A box stated as
       `max(43ch + padding, 9rem)` is ~400px, so that track alone is wider than the ~276px group and
       the box hangs out of its own block. Nothing about the rule above says "the first column is for
       labels"; the boxes it was written for happen to have one.

       A block, therefore: no columns to inherit, so the input's max-width resolves against the whole
       field and the box stops at the block's edge. :has(> .kv-sr-only) is the honest test — it asks
       whether the field draws a label, which is exactly the condition the subgrid needs. */
    .kv-label-group-fields > .kv-egeninfo-field:has(> .kv-sr-only) {
        display: block;
    }

    /* TWO PAIRS, NOT FOUR BUTTONS. The grid is what makes the halves equal; .kv-move keeps its own
       element (it carries the Till picker with no script, and that picker must stay attached to the
       two buttons it feeds) and simply becomes the second row of the same rhythm. The auto margin
       goes: with the block at full width there is no slack for it to take. */
    .kv-edit-actions {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: var(--sp-2);
    }

    .kv-move {
        grid-column: 1 / -1;
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: var(--sp-2);
        margin-left: 0;
    }

    /* The picker sits above the pair it feeds — full width, label above box, like every other field
       in the form. With confirm-submit.js running it is display:none here and the row is just the
       two buttons. */
    .kv-move .kv-select {
        grid-column: 1 / -1;
        flex-direction: column;
        align-items: stretch;
        gap: 2px;
    }

    .kv-move .kv-select select { width: 100%; }

    /* --control-min, because this is the one place in the row a thumb has to land, and the buttons
       were --sp-2 tall padding on a --fs-sm label. */
    .kv-edit-actions .kv-save,
    .kv-edit-actions .kv-remove {
        min-height: var(--control-min);
        padding-inline: var(--sp-2);
    }
}


/* --- The equipment overlay (6a M5) -----------------------------------------
   Legacy renders this as a modal overlay whose prices are added up in JavaScript. Here it is the
   same disclosure as "egen info", and every fee beside an option came from the cursor. */
.kv-eqpicker {
    flex-basis: 100%;
    margin-top: var(--sp-1);
    font-size: var(--fs-sm);
}

/* Its <summary> and focus ring are the shared disclosure rules above, beside "egen info" —
   they used to be restated here, after them, and won on colour. */

/* This one summary is not only a heading: it is legacy's "Vald utrustning: ... Ändra" line
   (SelectedEquipmentPanel), so it carries a value as well as a label (#3144). The label keeps the
   disclosure's weight and the selection beside it is set as a value — lighter and muted — so the
   strip still scans as the same control as "Etiketter" and "Egen information" next to it. It wraps
   because a row can carry eight articles, and an unwrapped list would push the chevron and the
   label off the line on a narrow column. */
.kv-eqpicker > summary { flex-wrap: wrap; }

.kv-eqpicker-chosen {
    font-weight: 400;
    color: var(--c-text-muted);
}

/* The picker stacks its two parts: the single-choice dropdowns on one row, the checkbox extras on
   the next — so the six boxes read as one group instead of a tail wrapping around the selects.

   One step looser than the other two panels' grids at every gap. This one holds three kinds of
   control (two dropdowns, six checkboxes and a running total) where they hold rows of like fields,
   and at the tighter spacing the checkbox row read as one run-on line. */
.kv-eqpicker-fields {
    display: grid;
    gap: var(--sp-3);
    margin-top: var(--sp-2);
}

/* The two dropdowns take a fixed, readable width and wrap when the column is narrow. */
.kv-eqpicker-selects {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3) var(--sp-5);
}

/* The extras pack to their natural width on one wrapping row. */
.kv-eqpicker-checks {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-5);
}

.kv-eqpicker-field {
    flex: 0 1 15rem;
    display: flex;
    flex-direction: column;
    gap: 2px;
    color: var(--c-text-muted);
}

.kv-eqpicker-field select {
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-surface);
    color: var(--c-text);
    font: inherit;
    font-family: var(--font-label);
}

.kv-eqpicker-field select:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* The extras carry their own text, so each is a centred inline checkbox in the checks row. */
.kv-eqpicker-check { align-items: center; }

/* An option the compatibility rules have ruled out (cart-equipment.js updateFields): dimmed and,
   via the disabled input, unclickable. JS-only — with JS off nothing is disabled and the server
   drops the incompatible codes on save instead. */
.kv-eqpicker-check.is-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* The live equipment subtotal (cart-equipment.js updatePrice). Ships hidden and is revealed by the
   script, so a caller with JS off never sees an empty line. */
.kv-eqpicker-total {
    margin: var(--sp-2) 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--c-accent-soft-text);
}

/* The two notes the picker can carry — the preserved articles and the pending adjustment — are
   statements about the selection, so they sit on it. (A third, the standing "måste ha utrustning"
   line, was dropped with #3086: the rule is answered on the attempt, not on every render.) */
.kv-eqpicker-note {
    margin: var(--sp-2) 0 0;
    padding: var(--sp-2) var(--sp-3);
    background: var(--c-surface-3);
    border-radius: var(--radius-sm);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
}

/* The quantity as text, for the printed sheet only — the box above is the screen's version. */
.kv-print-qty { display: none; }

/* The empty control ("Töm kundvagn", or "Ta bort alla rader i …" under a group heading)
   sits above the rows it would take. */
.kv-group-actions { margin: 0 0 var(--sp-2); }

/* The format note under a field that takes a list (the kvittens addresses). Muted and small:
   it is read with the box through aria-describedby, and must not compete with the label above
   it or the error below it. */
.kv-hint {
    margin: 0;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
    font-weight: 400;
}

/* One row's validation message, beside that row and nowhere else. */
.kv-error {
    flex-basis: 100%;
    margin: 0;
    color: var(--c-danger);
    font-size: var(--fs-sm);
}

/* --- The action bar ------------------------------------------------------ */

/* Save, checkout and the way back to the list, above the rows and ON SCREEN while they are read
   (#3204). Reported from the test round: scrolling through the rows took the save bar off the top
   of the page and checkout was only ever at the very bottom, so acting on the basket meant
   scrolling to one end of it or the other.

   Legacy answers half of that and the half it answers is the composition: its top strip carries
   the checkout link AND the save (Pages/cart/CartInfo.ascx), where this page had the save alone.
   The other half legacy does not answer — .CartInfo is `position: absolute; top: 104px`, so it
   scrolls away too — and what makes that bearable there is that legacy PAGES its rows while this
   page renders the whole cart to CartOptions.MaxRows. So the arrangement is ported and the pin is
   ours.

   A surface, not a strip: it lifts off the page for the same reason .kv-rows and .kv-summary do,
   and once it is stuck over scrolling rows the shadow is what says which is in front. */
.kv-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}

/* The two ways OUT of the page, pushed to the trailing edge so the bar reads as "what you have
   changed" on the left and "where you are going" on the right. margin-left:auto rather than
   space-between, because the save form is absent for a caller who may not edit and space-between
   would then hang these on the leading edge. */
.kv-bar-go {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    margin-left: auto;
}

/* THE PIN IS AT EVERY WIDTH IN THE HÄFTE, above 60rem only in the shop. It was above 60rem for
   both, and the reason given was that below it the header is itself pinned (#3149, both chromes
   since #3189) and 64px is already permanent, so a second pinned row is a question about how much
   of a phone screen the chrome may take. That question is answered for the häfte — yes, and the bar
   pays for it by being slimmer down there (the max-width block below), ~140px of chrome rather than
   ~184px — and left alone for the shop.

   WHAT DIFFERS IS `top`, NOT WHETHER. Below 60rem the header holds the viewport edge, so a bar
   pinned at 0 slides UNDER it and is never seen — the header is z-index 50 against this 20, which
   is the right precedence and not something to invert. Above 60rem nothing else is pinned and
   `top: 0` has the edge to itself.

   AND THE OFFSET IS MEASURED, NOT WRITTEN DOWN. There is no header height to hardcode:
   .site-header-inner sizes to its tallest child plus its padding, the shop's wordmark is 2rem while
   DH's keeps --logo-w, and the caller block grows with the caller's name — so a fixed number is
   either a gap the rows scroll through or a bar tucked under the row. chrome.js publishes
   --site-header-h on <html>; the 4.5rem fallback is btj.css's `scroll-padding-top`, which is
   deliberately OVER the row (an anchor wants clearance) and is therefore the right way to be wrong
   when there is no script: clearing the chrome matters more than a seam. */

/* z-index 20 puts the bar over the row cards it slides across and under the body-level popups and
   the header (both 50). */
@media (min-width: 60rem) {
    .kv-bar {
        position: sticky;
        top: 0;
        z-index: 20;
        box-shadow: var(--shadow);
    }
}

/* Below 60rem the pin is the HÄFTE'S ONLY (asked for 2026-09-01, after the shop's cart was seen
   with it). The narrow rules this file grew that day — the pin, the bar's 50/50 halves, the row
   form's label/control columns — were designed against the DH cart and made the webshop's look
   wrong, so they are scoped rather than kept: the shop keeps exactly the cart it shipped with.

   That is a real divergence in a file whose whole argument is that these two carts are ONE surface
   (#3210, _CartRow and _CartSaveAll are shared partials), and it is written down as a scope on the
   rules rather than as a copy of them, so what differs is visible in one place and the shop can be
   brought along by deleting a selector.

   BOUNDED ABOVE, or it breaks the desktop it was not written for: `.dh .kv-bar` is (0,2,0) and
   would out-specify the generic `top: 0` at every width — offsetting the desktop DH bar by a header
   that is not pinned up there, since --site-header-h is published at all widths. */
@media (max-width: 59.99rem) {
    .dh .kv-bar {
        position: sticky;
        top: var(--site-header-h, 4.5rem);
        z-index: 20;
        box-shadow: var(--shadow);
    }
}

/* NARROW: THE BAR IS STILL A BAR. Reported 2026-09-01 against the häfte's cart — the save and
   "Gå till kassan" were on two rows on a phone — AND ASKED FOR ON THE SHOP'S CART TOO (2026-09-03),
   which is why this block is no longer scoped `.dh`.

   That reverses the scoping two days after it was written, and the reversal is what the scope was
   for: it said the shop had not been brought along yet, not that the two carts want different bars
   ("the shop can be brought along by deleting a selector" is the block above's own sentence). Every
   sentence below is a fact about two buttons in a ~300px well — what the labels measure, that a flex
   line breaks on max-content, that a status line and a way back are not controls — and none of it is
   a fact about which cart is being drawn. The shop's bar is the WIDER case rather than a different
   one: it carries "Alla kundvagnar" as well, which is the case the boundary below was read off.

   WHAT IS NOT BROUGHT ALONG IS THE PIN. The block above keeps its `.dh` scope, because "may a second
   row hold the top of a phone screen permanently" is a different question from "do these two buttons
   share a line", and only the second was asked. The shop's bar still scrolls with the page below
   60rem — which also means the height economy the --control-min note below argues for is bought here
   for a bar that is merely on screen rather than pinned over the rows.

   The bar wraps, and a flex line breaks on its items' MAX-CONTENT sizes: it does not shrink them
   first and wrap only if that fails. So the pair needs ~190px + ~150px + the gap and the bar's own
   padding to stay on one line, which is more than a 390px phone has once the page's padding is off
   it — and the shop's bar carries "Alla kundvagnar" as well, so the widest case wants ~550px. That
   is where the boundary is read off, per the rule the header's three layouts are built on: from
   the labels, with the widest member, not from a device.

   What gives is the two things in the bar that are NOT controls. The live count is a status line
   under the button it belongs to, and the way back is a link under the button it sits beside —
   both keep their block, so nothing moves between owners, and neither has ever earned a share of
   a row a phone is this short of. Then the row may not wrap at all (the buttons shrink and their
   labels take a second line instead), which is what makes "same row" true at every width rather
   than at the widths that happen to fit. The horizontal padding comes off with it: --sp-5 either
   side of two buttons is ~64px of a ~300px well spent on air. */
@media (max-width: 36rem) {
    .kv-bar {
        flex-wrap: nowrap;
        align-items: flex-start;
        gap: var(--sp-2);
        padding: var(--sp-3);
    }

    /* HALF THE BAR EACH, whatever the labels are (asked for 2026-09-01). At natural widths the two
       buttons are ~190px and ~150px, which on a ~300px well is a ragged pair with an arbitrary gap
       between them; an even split is the one arrangement that does not look like an accident, and
       it also stops the wider label taking room from the narrower one.

       `1 1 0` rather than `1 1 auto`: the basis has to be zero or the split is proportional to the
       labels, which is the ragged pair again. min-width, because a flex item's floor is otherwise
       its own min-content — the buttons inside still stop at THEIR min-content, so a label wraps
       rather than clipping. The auto margin that pushes the way-out block to the trailing edge is
       off here: with both halves growing there is no slack for it to take, and it would only add
       itself between them. */
    .kv-saveall,
    .kv-bar-go {
        flex: 1 1 0;
        min-width: 0;
        margin-left: 0;
    }

    .kv-saveall-count { flex-basis: 100%; }

    /* Under the button rather than beside it, and after it in the flow: `order` is safe here
       because the two are not a reading sequence — the button is the row's job and the link is the
       way back out of the page. */
    .kv-bar-go .kv-link {
        flex-basis: 100%;
        order: 1;
        margin-left: auto;
        align-self: auto;
    }

    /* EQUAL HEIGHT, and it cannot come from align-items. The two buttons live in different flex
       containers (the save is inside its own <form>, the checkout inside .kv-bar-go), so stretching
       the bar's items stretches the BLOCKS and leaves each button sized by its own line count —
       "Spara ändrade rader" takes two lines at this width and "Gå till kassan" one. Nothing in the
       cascade can equalise two boxes that are not siblings, so the height is stated instead.

       IT IS --control-min, AND THAT IS WHAT PAYS FOR THE PIN. The floor was two full lines plus
       padding (~66px), which is generous for a control and expensive for one that is now on screen
       permanently. 44px is the floor a thumb actually needs, and the label still gets both lines
       inside it: the line-height comes down to 1.2 and the block padding to --sp-1, so two lines of
       --fs-sm measure ~42px and the FLOOR sets the height rather than the text. Both buttons draw
       at 44 whether they wrap or not. */
    .kv-bar .kv-btn {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        min-height: var(--control-min);
        padding: var(--sp-1) var(--sp-3);
        font-size: var(--fs-sm);
        line-height: 1.2;
        text-align: center;
    }

    /* The rest of the bar's own height. --sp-3 of padding and a --fs-sm status line were sized for
       a block that scrolls past; pinned, every one of those pixels is spent on every screen of the
       basket. The count keeps its line — it is the only thing that says whether there is anything
       to save — at the size the row's own small print is set in. */
    .kv-bar {
        padding: var(--sp-2) var(--sp-3);
        gap: var(--sp-1);
    }

    .kv-saveall { gap: var(--sp-1) var(--sp-2); }
    .kv-saveall-count { font-size: var(--fs-xs); }
}

/* "Spara ändrade rader" (#3075). Its own block inside the bar rather than the bar itself: it acts
   on the rows, its live count only means anything beside them — and it is the one part of the bar
   that needs a script, so it must be able to be absent without taking the other two with it.
   Hidden until cart-saveall.js can wire it up. */
.kv-saveall {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
}

/* The `display: flex` above is an author rule and `[hidden]`'s `display: none` is the browser's, so
   the attribute alone loses and the bar shipped VISIBLE to a caller with no JavaScript — a disabled
   "Spara ändrade rader" that nothing can ever enable, which is the dead control the partial says it
   exists to avoid. Same one-line restatement .copy-btn, .ks-panel and .site-cart-menu each needed
   for the same reason. */
.kv-saveall[hidden] { display: none; }

.kv-saveall-count { margin: 0; color: var(--c-text-muted); font-size: var(--fs-sm); }

/* A disabled primary button must not still read as the thing to click. */
.kv-saveall button[disabled] {
    background: var(--c-surface-2);
    border-color: var(--c-border);
    color: var(--c-text-muted);
    cursor: default;
}

.kv-saveall button[disabled]:hover {
    background: var(--c-surface-2);
    border-color: var(--c-border);
    color: var(--c-text-muted);
}

/* A row the bar would send. Marked so that "3 ändrade rader" can be checked against the page rather
   than taken on trust — the accent edge is the same one .kv-message uses for "something happened
   here", and it is the only thing separating an edited row from twenty untouched ones. */
.kv-row.is-changed { box-shadow: inset 4px 0 0 var(--c-accent); }

/* --- "Sparad", said on the row (#3129) ------------------------------------ */

/* The save's confirmation is a banner at the top of the page, and the caller is no longer there:
   cart-panels.js keeps them at the row they saved. So the row says it too — and unlike the banner it
   can say WHICH rows, which is the whole question after "Spara ändrade rader" has written twenty.

   Green, not the accent teal: the status ladder in btj.css keeps a cool tint for something under way
   and green for something finished, and this row is finished. It is also what stops the marker
   reading as .kv-row.is-changed's louder cousin — that edge means "this row is still unsaved", the
   exact opposite, and the two can be on screen together after a bulk save the caller has since
   edited again. */
.kv-row.is-saved {
    box-shadow: inset 4px 0 0 var(--c-ok-text);
    background: color-mix(in srgb, var(--c-ok-soft) 55%, transparent);
    /* An animation and not a transition: the class is on the row from the first paint, so a
       transition would run the wrong way round — it would fade the marker IN after the delay
       instead of holding it and then letting it go. Only the way out is animated for the same
       reason: the marker arrives with the page, where a fade-in would draw the eye a beat after it
       has already found the row, and then it leaves quietly, which is what a five-second-old
       confirmation should do. */
    animation: kv-saved-fade 4.6s ease-out forwards;
}

@keyframes kv-saved-fade {
    0%, 72% { box-shadow: inset 4px 0 0 var(--c-ok-text); background: color-mix(in srgb, var(--c-ok-soft) 55%, transparent); }
    100%    { box-shadow: inset 4px 0 0 transparent;      background: transparent; }
}

/* A mark and a word, not a filled pill: the row it sits on is already tinted in this hue, so a
   --c-ok-soft chip on it is a pill nobody can see — measured, not guessed (the first cut drew one
   and the fill vanished into the row). The system's quietest voice instead — the uppercase
   micro-label — which is also the right volume for something that is confirming rather than
   offering, right beside a primary button that is offering. */
.kv-saved-chip {
    align-self: center;
    animation: kv-saved-chip-fade 4.6s ease-out forwards;
    color: var(--c-ok-text);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    /* One line, and never wider than what holds it: "Kopierad till <kundvagn>" names a cart whose
       alias is the member's own to choose, so the long tail is ellipsised rather than allowed to
       push the actions footer wide. Every ordinary name fits. */
    white-space: nowrap;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

.kv-saved-chip::before {
    content: "\2713";                 /* ✓ — the mark reads at a glance where the word has to be read */
    margin-right: 0.35em;
    font-size: 1.15em;
    letter-spacing: 0;
}

@keyframes kv-saved-chip-fade {
    0%, 72% { opacity: 1; }
    100%    { opacity: 0; }
}

/* THE LINE A MOVED ROW LEAVES BEHIND (cart-panels.js). The only marker on this page that is not
   drawn ON something: the row it is about is in another cart now, so what is marked is its PLACE.
   Which is also why it exists at all — a moved row and a removed row leave the same hole, and
   without this the page's answer to "Flytta" is word for word its answer to "Ta bort".

   Row-shaped, in the row's own slot, in the same green as a saved row's ground, and it carries the
   two facts the caller has lost with the row: which title, and which cart took it.

   It COLLAPSES rather than blinking out. Everything else on this page fades in place and leaves the
   layout where it found it; this one is holding a gap open in a list the caller is reading, so the
   gap has to close under a movement they can follow. */
.kv-row-gone {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--c-surface-3);
    background: color-mix(in srgb, var(--c-ok-soft) 55%, transparent);
    box-shadow: inset 4px 0 0 var(--c-ok-text);
    overflow: hidden;
    animation: kv-gone-out 5s ease-out forwards;
}

/* The list draws its rows apart with a top hairline and drops it on the first one — this line takes
   a row's place, so it answers the same rule. */
.kv-row-gone:first-child { border-top: 0; }

.kv-gone-mark {
    color: var(--c-ok-text);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.kv-gone-mark::before {
    content: "\2192";                 /* → — it went somewhere, where the save's ✓ says it is finished */
    margin-right: 0.35em;
    font-size: 1.15em;
    letter-spacing: 0;
}

/* The title in the row's voice but a step quieter: the line should read as the row it stands in for,
   without competing with the rows still on either side of it. */
.kv-gone-title {
    min-width: 0;
    font-family: var(--font-headline);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

@keyframes kv-gone-out {
    0%, 80% { opacity: 1; max-height: 8rem; padding-top: var(--sp-3); padding-bottom: var(--sp-3); }
    100%    { opacity: 0; max-height: 0;    padding-top: 0;           padding-bottom: 0; }
}

/* A caller who has asked for less motion gets the same marker and no fade — the script removes both
   at the same moment either way, so the information is identical and only the exit differs. The
   departed line included: it is removed on the same timer, and without the animation it simply
   stands until then and then goes. */
@media (prefers-reduced-motion: reduce) {
    .kv-row.is-saved, .kv-saved-chip, .kv-row-gone { animation: none; }
}

/* A refused "Spara allt": nothing was written at all, so it is stated in the danger colour rather
   than in the accent one a successful save gets. */
.kv-refused {
    margin: 0;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-danger);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--c-danger) 8%, transparent);
    font-size: var(--fs-sm);
}

/* The Post-Redirect-Get banner. Same shape as .kv-message so a save and a redirect read alike. */
.kv-saved {
    margin: 0;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-accent);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--c-accent) 8%, transparent);
    font-size: var(--fs-sm);
}

/* --- The cart list (/kundvagnar) ----------------------------------------- */

.kv-list {
    display: grid;
    gap: var(--sp-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.kv-card {
    display: grid;
    gap: var(--sp-2);
    padding: var(--sp-4) var(--sp-5);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}

/* Two columns where there is room (asked for 2026-08-27): the name and the count on the left, the
   sum and the two ways on beside them on the right, rather than three stacked bands. The card
   then costs one line-pair of height instead of three, which is what a list of thirty is read
   down, and the pair lines up with the cart it belongs to instead of trailing under it.

   Placed explicitly rather than by source order, because the actions must span BOTH rows to
   centre against them — auto-placement would put them in row 3 of column 2. Stacked below
   40rem, where a 22rem-wide button pair and a name cannot share a line; the breakpoint is read
   off THIS content (name + "Antal artiklar i kundvagn: N" against the pair's own width), not
   borrowed from the header's, per the rule docs/SearchAnatomy.md §7.10 sets out. */
@media (min-width: 40rem) {
    .kv-card {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
        column-gap: var(--sp-5);
    }

    .kv-card-head { grid-column: 1; grid-row: 1; }
    .kv-card-facts { grid-column: 1; grid-row: 2; }

    .kv-card > .kv-actions {
        grid-column: 2;
        grid-row: 1 / span 2;
        justify-content: flex-end;
    }
}

/* AND UNDER IT THE SUM TAKES ITS OWN LINE, SO THE PAIR STAYS A PAIR (reported 2026-09-03: the two
   buttons "look weird" on mobile).

   Stacked, the action row is the sum plus both ways on in one wrapping flex line, and at 390px it
   has ~326px to spend: "1218:-" ~60, "Ändra i kundvagnen" ~174, "Till kassan" ~125, plus two --sp-3
   gaps. The sum and the first button fit; the second wraps alone and sits under the sum, left of the
   button it belongs with — a pair drawn as a ragged stair, which is what was reported.

   The two buttons alone fit that line (~311 of 326), so the fix is to take the SUM out of it rather
   than to stack all three: it is a fact about the cart and not one of the ways on, and above 40rem
   it already leads a row that has room for all three. One card in a list of thirty therefore costs
   one more line, not two.

   The buttons then GROW into what is left, so the row ends on the card's own edge instead of
   trailing off mid-line — and a cart offering only one of them (an anonymous caller's edit, a cart
   awaiting acceptance; see _CartHandoff.cshtml) gets one full-width button rather than a short one
   parked at the left. `text-align: center` because these are anchors: growing a block-level <a>
   moves its box and leaves the label at the start of it.

   Below ~320px of row the pair still wraps, and it wraps into two full-width buttons, which is the
   shape a narrow phone wants anyway. */
@media (max-width: 39.99rem) {
    .kv-card > .kv-actions > .kv-card-total { flex-basis: 100%; }
    .kv-card > .kv-actions > .kv-btn { flex: 1 1 auto; text-align: center; }
}

/* The caller's default cart, marked where it stands. Legacy moves it to the top of the list
   instead, which changes the order between requests (Carts.aspx.cs:52-62). A bare 1px accent
   border was teal-on-grey at one pixel — all but invisible next to the neutral cards, so the
   mark read as missing. It now carries the same "called out" treatment as .kv-message above: a
   left accent bar and a faint accent wash (the color-mix idiom .kv-saved already uses), which
   reads as highlighted at a glance without turning the whole card into a coloured block. */
.kv-card.is-default {
    border-color: var(--c-accent);
    border-left: 4px solid var(--c-accent);
    background: color-mix(in srgb, var(--c-accent) 6%, var(--c-surface));
}

.kv-card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-3);
}

.kv-card-name { margin: 0; font-size: var(--fs-lg); line-height: 1.3; }
.kv-card-name a { text-decoration: none; }
.kv-card-name a:hover { text-decoration: underline; }
.kv-card-name a:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

.kv-badge {
    padding: 0 var(--sp-2);
    border: 1px solid var(--c-accent);
    border-radius: var(--radius-sm);
    color: var(--c-accent);
    font-size: 0.75rem;
    font-weight: 600;
}

.kv-card-facts {
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-3);
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

/* In the action row, not in the facts line — so it is sized like the summary's own grand total
   (.kv-total dd) rather than like the muted counts it used to stand among. nowrap for the same
   reason that one has it: "9 544:-" must not break before its ":-". */
.kv-card-total {
    color: var(--c-text);
    font-weight: 700;
    font-size: var(--fs-xl);
    line-height: 1.2;
    white-space: nowrap;
}

/* The buttons and the total share a line, so the row centres rather than stretching: a taller
   number would otherwise sit at the top of its own flex item. Scoped to the card — .kv-actions
   on the summary and the cart page holds buttons only. */
.kv-card > .kv-actions { align-items: center; }

.kv-card-empty { color: var(--c-text-muted); font-style: italic; }

/* --- Summary ------------------------------------------------------------- */

.kv-summary {
    display: grid;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.kv-totals { display: grid; gap: var(--sp-2); margin: 0; }

.kv-totals > div {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-4);
}

.kv-totals dt { color: var(--c-text-muted); }
.kv-totals dd { margin: 0; font-weight: 600; }

.kv-total {
    padding-top: var(--sp-2);
    border-top: 1px solid var(--c-border);
}

.kv-total dt { color: var(--c-text); font-weight: 600; }
.kv-total dd { font-size: var(--fs-xl); font-weight: 700; white-space: nowrap; }

.kv-vat,
.kv-note { margin: 0; color: var(--c-text-muted); font-size: var(--fs-sm); }

.kv-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
}

/* --font-label, like every other control in the design system: an <a> inherits <body>, so
   while the shop had a serif body face these anchors were the only buttons on the page set
   in Source Serif. That is no longer possible — one face since 2026-09 — and the declaration
   stays because it states what the control IS rather than which of two faces it wanted, and
   because `font: inherit` on the <button> below would still reset the family
   (docs/DesignSystem.md — "UI furniture" is --font-label). */
.kv-btn {
    padding: var(--sp-2) var(--sp-5);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    background: var(--c-surface);
    color: var(--c-text);
    font-family: var(--font-label);
    font-weight: 600;
    text-decoration: none;
}

/* "Skriv ut" is the one <button> among the anchors, so it needs what a button does not inherit.
   font: inherit resets font-family as part of the shorthand, so the family is restated after it. */
button.kv-btn { font: inherit; font-family: var(--font-label); cursor: pointer; }

.kv-btn:hover { border-color: var(--c-accent); color: var(--c-accent); }

.kv-btn-primary {
    background: var(--c-accent);
    border-color: var(--c-accent);
    color: #fff;
}

.kv-btn-primary:hover { background: var(--c-accent-dark); border-color: var(--c-accent-dark); color: #fff; }

/* The hand-off pair (_CartHandoff.cshtml), told apart by WEIGHT and not by hue since 2026-09
   ("change color of these button to something that fits the theme?"). It had been green for
   "stay and edit" and warm for "the way out" — a role pair from 2026-08-27, when warm was the
   header band's orange-pink and the green was --c-sage-soft. The reskin took both legs away: the
   whole --c-warm-* family now ALIASES --c-accent, so the checkout leg was already burgundy, and
   the green survived only by borrowing --c-ok-soft, which in this palette means "levererad" on a
   badge (docs/DesignSystem.md — the signal colours are listed exceptions, and a shop with one
   brand hue does not spend a status green on a toolbar button). What was left was one on-brand
   button beside one green one, which reads as a state rather than as a pair.

   So the pair is the brand's own two weights, which is what every other control in the shop
   already uses: "Ändra i kundvagnen" is an OUTLINED burgundy button on the card's own surface,
   "Till kassan" is a FILLED one on the palette's primary container (--c-accent-soft under
   --c-accent-soft-text, the same container/ink pair .kv-badge and .chip-accent take). Outline
   against fill is what keeps them distinguishable on /kundvagnar, where NEITHER carries
   kv-btn-primary — the role is still legible, it is simply spent on weight instead of on a
   second hue.

   kv-btn-primary is solid --c-accent with white ink for whichever leg the page marks, exactly as
   .kv-btn-primary does for every other button in this sheet. Only one leg is ever primary
   (CartHandoff.EditPrimary is Primary && ShowEdit, CheckoutPrimary its complement), so the two
   rules below can be one selector without the pair collapsing into one appearance. Both remain
   (0,2,0), so they win over .kv-btn-primary's own declaration earlier in the file regardless of
   source order. Every ink is AA on its own ground. */
.kv-btn-edit {
    background: var(--c-surface);
    border-color: var(--c-accent);
    color: var(--c-accent);
}

.kv-btn-edit:hover {
    background: var(--c-accent-soft);
    border-color: var(--c-accent-dark);
    color: var(--c-accent-soft-text);
}

.kv-btn-checkout {
    background: var(--c-accent-soft);
    border-color: var(--c-accent);
    color: var(--c-accent-soft-text);
}

.kv-btn-checkout:hover {
    background: color-mix(in srgb, var(--c-accent) 18%, var(--c-accent-soft));
    border-color: var(--c-accent-dark);
    color: var(--c-accent-soft-text);
}

.kv-btn-edit.kv-btn-primary,
.kv-btn-checkout.kv-btn-primary {
    background: var(--c-accent);
    border-color: var(--c-accent);
    color: #fff;
}

.kv-btn-edit.kv-btn-primary:hover,
.kv-btn-checkout.kv-btn-primary:hover {
    background: var(--c-accent-dark);
    border-color: var(--c-accent-dark);
    color: #fff;
}

/* A link among buttons — "Alla kundvagnar" is a way back, not an action on this basket. */
.kv-link { align-self: center; font-size: var(--fs-sm); }

.kv-btn:focus-visible,
.kv-chip:focus-visible,
.kv-title a:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* --- Checkout and receipt (/kassa, /kvittens — 6b M10/M11b) ---------------

   Both pages shipped ahead of their styles and rendered as bare HTML inside styled chrome. They
   share one vocabulary on purpose: a receipt is the same list of titles and the same money block as
   the checkout that produced it, so the two must not look like different products. Nothing new is
   introduced that the basket does not already have — the same surface, the same money alignment,
   the same muted-label/strong-value pairing in a <dl>.

   The one visual difference is deliberate: checkout carries a form and a primary button, the
   receipt carries neither, so the receipt reads as a statement rather than as something to fill in.
   -------------------------------------------------------------------------- */

.kv-page { display: grid; gap: var(--sp-5); }

.kv-page > h1 { margin: 0; font-size: var(--fs-2xl); line-height: 1.2; }

.kv-sub { margin: 0; color: var(--c-text-muted); }

/* The database refused, or the write could not proceed. Same shape as .kv-message so a notice reads
   the same wherever it comes from; the accent turns to danger because this one blocks. */
.kv-notice {
    display: grid;
    gap: var(--sp-2);
    justify-items: start;
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-left: 4px solid var(--c-accent);
    border-radius: var(--radius);
    font-size: var(--fs-sm);
}

.kv-notice p { margin: 0; }
.kv-notice-error { border-left-color: var(--c-danger); }

.kv-checkout-summary,
.kv-receipt-rows,
.kv-receipt-summary {
    display: grid;
    gap: var(--sp-3);
    padding: var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.kv-checkout-summary > h2,
.kv-receipt-rows > h2 { margin: 0; font-size: var(--fs-lg); line-height: 1.3; }

/* One line per title. A grid rather than inline text so the count and the money keep a column of
   their own — the same reason .kv-row is a grid. */
.kv-checkout-lines {
    display: grid;
    gap: var(--sp-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.kv-checkout-lines > li {
    display: grid;
    gap: var(--sp-1);
    padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--c-border);
}

.kv-checkout-lines > li:last-child { padding-bottom: 0; border-bottom: 0; }

.kv-checkout-title { font-weight: 600; line-height: 1.35; }
.kv-checkout-byline,
.kv-row-facts { color: var(--c-text-muted); font-size: var(--fs-sm); }
.kv-row-facts { margin: 0; }

/* Configuration, quantity and money on one wrapping line. The two amounts sit at the end so a
   column of rows lines up without a per-row grid template. */
.kv-row-line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-3);
    margin: 0;
    font-size: var(--fs-sm);
}

.kv-checkout-count { color: var(--c-text-muted); }
.kv-row-config { color: var(--c-text-muted); }
.kv-row-unit { color: var(--c-text-muted); }
.kv-row-total { margin-left: auto; font-weight: 700; color: var(--c-text); }

/* What a RECEIPT row states about itself: dagsleverans, bibliografisk post, equipment. Quiet by
   default — they are facts about an order that is already placed, not things to act on. The
   checkout line states the same three as pairs in .kv-checkout-info below, because there they sit
   beside the egen info and the whole row is one table; the receipt keeps the paragraphs. */
.kv-row-flag,
.kv-row-equipment {
    margin: 0;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

/* The checkout line's shelf-sign sentence (#3073) — same voice as the equipment line above it. */
.kv-row-shelfsign {
    margin: 0;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

.kv-row-info {
    display: grid;
    gap: var(--sp-1);
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

/* The checkout line's egen info: every value the order carries, as label/value columns rather than
   as "Label: Värde" sentences behind a "Visa N till" disclosure. The label column is sized to its
   own content (max-content) and capped, so the values line up under each other without the longest
   label — "Bibl. etikett rad 2" — pushing them off the line; below the cart's own narrow breakpoint
   the pair stacks, because two columns in a phone-width card leave the value about six characters.
   Same colour split the .kv-row-facts line uses: the label is the quiet half, the value is what the
   caller is checking. */
.kv-checkout-info {
    display: grid;
    grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
    gap: var(--sp-1) var(--sp-3);
    margin: 0;
    font-size: var(--fs-sm);
}

.kv-checkout-info > dt {
    color: var(--c-text-muted);
    /* A long label wraps inside its own column instead of widening it. */
    overflow-wrap: break-word;
}

.kv-checkout-info > dd {
    margin: 0;
    color: var(--c-text);
    overflow-wrap: break-word;
}

@media (max-width: 30rem) {
    .kv-checkout-info {
        grid-template-columns: minmax(0, 1fr);
        gap: 0;
    }

    .kv-checkout-info > dt { margin-top: var(--sp-1); }
    .kv-checkout-info > dt:first-child { margin-top: 0; }
}

/* Money. Same two-column dl the basket totals use, so checkout, receipt and basket agree. */
.kv-checkout-money {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--sp-2) var(--sp-4);
    margin: 0;
    padding-top: var(--sp-3);
    border-top: 1px solid var(--c-border);
}

.kv-checkout-money dt { color: var(--c-text-muted); }
.kv-checkout-money dd { margin: 0; font-weight: 600; text-align: right; }

.kv-checkout-money dt.kv-checkout-total { color: var(--c-text); font-weight: 600; }
.kv-checkout-money dd.kv-checkout-total {
    font-size: var(--fs-xl);
    font-weight: 700;
    white-space: nowrap;
}

.kv-vat-notice {
    margin: 0;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
    text-align: right;
}

/* --- The checkout form --------------------------------------------------- */

.kv-checkout-form {
    display: grid;
    gap: var(--sp-4);
    padding: var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.kv-field { display: grid; gap: var(--sp-1); }

.kv-field > label { font-weight: 600; }

.kv-field input[type="text"] {
    padding: var(--sp-2) var(--sp-3);
    font: inherit;
    font-family: var(--font-label);
    color: var(--c-text);
    background: var(--c-bg);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
}

.kv-field input[type="text"]:focus-visible,
.kv-field input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* A checkbox and its label belong on one line, and the label is the click target — so the label
   sits beside the box rather than above it, unlike every other field. */
.kv-field-check {
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-2);
}

.kv-field-check > label { font-weight: 400; }
.kv-field-check > .kv-error { grid-column: 1 / -1; }

/* --- The receipt --------------------------------------------------------- */

.kv-receipt > h2 { margin: 0; font-size: var(--fs-lg); line-height: 1.3; }

/* "Bibliografisk post skickas via …" — a promise about what happens next, so it is stated rather
   than muted. */
.kv-receipt-biblpost { margin: 0; }

/* Order number, date, customer number, reference. Label-and-value pairs, aligned like the money
   block but left-aligned: these are identifiers, not amounts. */
.kv-receipt-facts {
    display: grid;
    grid-template-columns: minmax(8rem, max-content) 1fr;
    gap: var(--sp-2) var(--sp-4);
    margin: 0;
}

.kv-receipt-facts dt { color: var(--c-text-muted); }
.kv-receipt-facts dd { margin: 0; font-weight: 600; }

.kv-receipt-summary { gap: var(--sp-2); }
.kv-receipt-summary .kv-checkout-money { padding-top: 0; border-top: 0; }

/* --- Wider screens -------------------------------------------------------- */

@media (min-width: 560px) {
    .kv-checkout-summary,
    .kv-receipt-rows,
    .kv-receipt-summary,
    .kv-checkout-form { padding: var(--sp-5); }

    /* The money block is not the width of the page — it is the width of its longest label plus its
       amount, pushed right, exactly as .kv-totals is on the basket. */
    .kv-checkout-money { min-width: 22rem; margin-left: auto; }

    .kv-field input[type="text"] { max-width: 30rem; }

    .kv-row {
        grid-template-columns: 4.5rem 1fr auto;
        align-items: start;
    }
    .kv-cover { grid-row: 1; }
    .kv-amount {
        grid-column: 3;
        display: grid;
        justify-items: end;
        gap: var(--sp-1);
        text-align: right;
    }
    /* An editable row carries a whole form — quantity, egen info, the equipment picker and its
       action buttons — which is far too wide to share a line with the title. Left in the auto-sized
       third column it collapses the 1fr title column to one character per line. So an editable row
       drops the form to a full-width panel below the cover + title instead. */
    .kv-amount.is-editing {
        grid-column: 1 / -1;
        justify-items: stretch;
        text-align: left;
    }
    /* The unit price and row total still read right, at the end of that panel. */
    .kv-amount.is-editing .kv-unit,
    .kv-amount.is-editing .kv-rowtotal { justify-self: end; }

    .kv-details {
        grid-template-columns: minmax(8rem, max-content) 1fr;
        align-items: baseline;
    }
    .kv-summary { justify-content: end; }
    .kv-totals { min-width: 22rem; margin-left: auto; }
    .kv-actions { justify-content: flex-end; }
    .kv-vat,
    .kv-note { text-align: right; }
}

/* --- Paper (M5) — moved to kundvagn-print.css (#3060) ----------------------

   The cart's paper rules are no longer an @media block here: the page links them `media="print"`
   and its print view (?utskrift=1) links the same file `media="all"`, so the sheet is what that
   tab shows on screen. See btj-print.css for why that needs a file rather than a block.
   -------------------------------------------------------------------------- */
