﻿/* ==========================================================================
   BTJ webshop — new-design foundation (slice #3014, re-skinned #3026).

   Hand-written, mobile-first. Design tokens as custom properties; every
   window slice builds on these. No framework, no build step.

   The palette, the type roles and the surface ladder come from Böcker i
   serie (Btj.BockerISerie, src/web/apps/web-public/src/app/globals.css),
   which in turn tracks the BTJ brand theme in BTJ-appen
   (Btj.App.Front/btj-titelblad/src/theme/btj.theme.light.ts). Three apps,
   one brand: teal primary, orange accent, pink header band.

   Two rules carried over from that system, because they are what make it
   look like itself rather than like a bootstrap page:

   - Tonal layering, not lines. A boundary is a change of background, not a
     1px rule. Borders that remain are "ghost" borders — felt, not seen.
   - One type role. Jost (a Futura stand-in, the brand headline face) carries
     everything — headings, labels, dense UI furniture and prose alike. It was
     a SPLIT role until 2026-09, with Source Serif 4 for prose; the
     Designmanual för Butiken forbids a serif anywhere in the shop, so what is
     left of the split is four token names pointing at one stack.

   Token names are unchanged from the red-era sheet on purpose — the seven
   slice stylesheets reference --c-accent / --c-border / --shadow ~500 times
   between them, so the re-skin is a change of values, not a rename.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Webfonts. Self-hosted: the shop's CSP is `font-src 'self'` (ShopCsp.cs),
   and a page that renders database HTML has no business also reaching out to
   fonts.gstatic.com. ONE face since the Designmanual för Butiken (2026-09),
   which forbids a serif anywhere in the shop — Source Serif 4 carried prose
   here until then and its four blocks and four .woff2 files went with it.
   Jost is variable (weight 300–700) so one file per style covers the whole
   scale; latin + latin-ext only — Swedish lives in latin, and latin-ext is
   what carries the odd imported title.

   THE MANUAL ASKS FOR FUTURA, WHICH IS LICENSED, AND JOST IS THE STAND-IN.
   Jost is a Futura revival and --font-headline already names "Futura PT" and
   Futura behind it, so the seam is: buy the licence, drop the .woff2 files in
   wwwroot/fonts/, uncomment the block below, and move the family to the head
   of the stack in --font-headline. Nothing else in the product changes — one
   file, one rule, one token. Self-hosting is not optional; the CSP above is
   why.

   @font-face {
       font-family: "Futura PT";
       font-style: normal;
       font-weight: 300 700;
       font-display: swap;
       src: url("/fonts/futura-pt-latin.woff2") format("woff2");
   }
   -------------------------------------------------------------------------- */

@font-face {
    font-family: "Jost";
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url("/fonts/jost-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Jost";
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url("/fonts/jost-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Jost";
    font-style: italic;
    font-weight: 300 700;
    font-display: swap;
    src: url("/fonts/jost-italic-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Jost";
    font-style: italic;
    font-weight: 300 700;
    font-display: swap;
    src: url("/fonts/jost-italic-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    /* --- Brand ---------------------------------------------------------- */
    /* --c-accent is the shop's action colour: the search and buy buttons, the
       active tab, the cart badge, every "this is the thing to click". It has
       been BTJ red (#c8102e), then the brand teal (#00635e); since the
       Designmanual för Butiken (2026-09) it is BURGUNDY. There is no second
       voice any more — the manual names an ink and a ground, and the orange
       that existed to shout over a teal page has nothing left to shout over.

       THE MANUAL NAMES TWO COLOURS; THAT IS NOT THE SAME AS TWO VALUES. A tint
       and a shade of the burgundy are derived from it, not new hues, and a
       system needs a container fill and a hover step whatever a brief says.
       Everything below is either #6a1a3e moved along the L axis at a fixed hue
       and saturation (HSL 333° / 61 %), or the manual's Smultron verbatim.

       CONTRAST IMPROVED EVERYWHERE IT MATTERS, which is worth recording because
       a reskin is usually the other way: white on the accent is 11.5:1 where
       the teal was 7.6:1, the accent on Smultron is 10.0:1, and the worst
       pairing in the product — a link on --c-surface-4 — is 8.3:1. */
    --c-accent: #6a1a3e;          /* Burgundy, the manual's, verbatim */
    --c-accent-dark: #4e132d;     /* same hue at L 19 %: hover, and the money button */
    /* THE "SELECTED" FILL IS SMULTRON (2026-09-11, drawn from the designer's
       screenshots): a picked chip, the current tab, the campaign caption and the
       carousel arrows are the header band's own colour, so the shop has one
       ground and one ink and a selection is written in the pair. It is a var()
       and not a second copy of the hex, so that moving the band moves it too.
       What it costs: on a --c-pink ground this fill is invisible, so a control
       drawn there must take another fill (.sok-adv-empty is the case). */
    --c-accent-soft: var(--c-pink);
    --c-accent-soft-text: #3e0f24;

    /* --c-warm WAS THE SECOND HUE AND IS NOW A ROLE NAME. The four names stay
       because the slice sheets spell them ~44 times and a rename buys nothing
       — the same reason this file kept every name through the red-to-teal
       reskin before it. They are written as var() rather than as a second copy
       of the hex, so that the file SAYS there is one action colour instead of
       holding two names that happen to agree.

       If a second voice is ever wanted again this is the token to move, and the
       question to ask first is whether the thing needs a HUE or simply more
       WEIGHT. Every place that was asked during this reskin answered weight.

       --c-warm-soft IS THE ROSE, and it is the one warm name that is not an
       alias: the burgundy at L 82 %, one step stronger than Smultron. It is what
       "needs attention" is drawn in — the status rung, a flagged cart row, DH's
       checkout review and reference card, a refusal inside a Smultron block —
       and it stays apart from --c-accent-soft because "selected" is now Smultron
       and the two must not collapse into one value: a flagged row that looked
       like a picked chip would say nothing. */
    --c-warm: var(--c-accent);
    --c-warm-dark: var(--c-accent-dark);
    --c-warm-soft: #e6bccf;
    --c-warm-soft-text: var(--c-accent-soft-text);

    /* SMULTRON: the manual's second colour — the header band, the ground for
       anything decorative, and (through --c-accent-soft) the "selected" fill. */
    --c-pink: #fdece8;

    /* --- Status tints --------------------------------------------------- */
    /* Order states and hit markers. The brand now has ONE hue and one ground
       and neither of them says "finished", so the ladder is: the palest brand
       ground for something under way, the rose for something that needs
       attention, green for done, grey for cancelled.

       Green is the one colour here that is not in the brand, and it stays —
       the manual governs BRAND colour, not signal colour, and a delivered order
       has to be legible as delivered in the corner of the eye, which a second
       burgundy would not be. It is listed with the other signal exceptions in
       DesignSystem.md rather than argued for again here.

       THE RUNGS ARE TOLD APART BY HUE AND NOT BY WEIGHT, which is not a
       regression: three of the four sat within .07 of each other in luminance
       under the teal palette too. Every badge states its own state as a WORD.
       Nobody should "fix" this by saturating a rung — the words are the
       mechanism, and the colour is the fast path to a fact already written.

       --c-tint-cool IS NOW A LIE, AND IS KEPT ANYWAY. There is no cool hue left
       for "under way" to take, so it holds the palest brand ground; the name
       means "the lightest rung", and renaming it is a slice-sheet sweep that
       buys nothing. */
    --c-tint-cool: #fdece8;
    --c-tint-cool-text: #4e132d;
    --c-ok-soft: #e4f1e4;
    --c-ok-text: #1f6b2e;

    /* A field the caller is meant to *change* rather than fill in — the
       customer-number picker is the case: it looks like every other input and
       is the one that reloads the page.

       THE YELLOW SURVIVED THE RESKIN, AND NOT FOR THE REASON IT WAS INVENTED.
       It was chosen because neither of the two brand hues could say "pick here"
       without also saying "submit" (teal) or "warning" (orange); with one hue
       that argument is gone, and a stronger one took its place — Smultron is
       now the page's own decorative ground AND its "selected" fill, so marking a
       field with it would read as already picked, and the rose means "needs
       attention" everywhere else. So it is a signal colour, listed
       beside the green. Kept faint on purpose: it marks one control on a page
       of controls, so it has to be legible next to a white input and invisible
       from across the page. A saturated yellow reads as a validation error. */
    --c-field-highlight: #fdfbe6;

    /* THERE IS NO THIRD OR FOURTH HUE, and the two that existed are worth a
       line each rather than nothing — this file's convention for a retired
       colour, the same one the gold below is recorded in.

       --c-indigo / -dark / -soft / -text (#46468c / #2f2f66 / #eeeef8 /
       #2b2b5c) was added 2026-08-18 as the colour for an AREA rather than a
       control, on the explicit argument that "teal (act) and orange (do not
       miss this) are both spoken for by controls, so a block that has to read
       as its own region had no colour to take". Removed 2026-09 with the
       Designmanual, which dissolves that premise by naming Smultron as the
       sanctioned decorative ground: there is now a brand colour for a region,
       so the third hue has no argument left. Its two live callers — the
       landing's ad grid and the puff aside — take --c-pink.

       --c-sage / -dark / -soft / -text (#4b7f5e / #2f5b41 / #e6f0ea / #234a35)
       was added the same day for the landing's shortcuts rail. THE RAIL HAD
       ALREADY STOPPED USING IT — it takes --c-accent, and "sage" survived in
       start.css as two comment lines and no declaration — so by the time of the
       reskin its only live callers were the cart's flytta/kopiera pair, where
       it meant "green, as opposed to red". That is --c-ok-soft's job, and those
       two buttons do it there now. A value with one caller is not a system.

       If a region ever wants a colour again: --c-pink is the ground the manual
       gives for exactly that, and the surface ladder is the other answer. A
       fifth hue would have to argue past both. */

    /* --- Surface ladder ------------------------------------------------- */
    /* Nested surfaces, lightest on top: a card is --c-surface on --c-bg, and
       a well inside that card is --c-surface-2. No borders needed. */
    --c-bg: #faf7f6;
    --c-surface: #ffffff;
    --c-surface-2: #f3eeec;
    --c-surface-3: #ece5e2;
    --c-surface-4: #e2d9d5;
    /* THE LADDER HAS NO COOL STEP, and like the gold above it that is worth a
       line. --c-surface-cool (#e9ede9) was added 2026-09-01 as a green-grey rung
       for the advanced form's block and removed the same day: the block takes
       --c-pink, the header band's own colour. Both tokens were born for one
       control and died for the same reason -- a value with one caller is not a
       system, it is that caller's local decision wearing a token's clothes.

       If a cool surface is ever wanted again, the question to ask first is
       whether the block needs a SURFACE or a HUE: --c-surface-2..4 answer the
       first (warm, and the shop's own ground), and the four named hues answer
       the second. This token was the attempt to have neither. */

    /* --- Ink ------------------------------------------------------------ */
    --c-text: #1a1a1a;
    --c-text-muted: #4d4d4d;
    --c-outline: #666666;         /* micro-labels, icon glyphs */
    --c-border: #c8c4c2;          /* outline-variant — the only line worth drawing */
    --c-link: #6a1a3e;
    --c-danger: #d32f2f;
    --c-badge-bg: #f3eeec;

    /* --- Type ----------------------------------------------------------- */
    /* FOUR NAMES, ONE FACE. This used to be a split role — Jost for headings,
       labels and every piece of UI furniture, Source Serif 4 for prose, "the
       system speaks sans, the content speaks serif". The Designmanual för
       Butiken (2026-09) forbids a serif anywhere in the shop, so the split is
       gone and the names are what is left of it: --font-headline for headings
       and the brand mark, --font-label for uppercase micro-labels and all UI
       furniture, --font-sans as the alias the slice sheets already spell, and
       --font-body for prose.

       --font-BODY IS KEPT ON PURPOSE, and the objection to keeping it is real:
       a role alias that resolves to the same face is a name that can rot, and
       the next developer will set it and see nothing change. It stays because
       `body` is where the role is DECLARED, and deleting it would leave the
       document with no font-family at all — depending on the UA default, which
       is the implicit dependency this file exists to prevent. It is also the
       one line a licensed Futura for prose would arrive at. */
    --font-headline: "Jost", "Futura PT", Futura, "Trebuchet MS", system-ui, sans-serif;
    --font-label: var(--font-headline);
    --font-sans: var(--font-headline);
    --font-body: var(--font-headline);

    --fs-xs: 0.75rem;
    --fs-sm: 0.875rem;
    --fs-base: 1rem;
    --fs-lg: 1.125rem;
    --fs-xl: 1.5rem;
    --fs-2xl: clamp(1.6rem, 1.2rem + 1.5vw, 2.1rem);

    /* --- Spacing & shape ------------------------------------------------ */
    --sp-1: 0.25rem;
    --sp-2: 0.5rem;
    --sp-3: 0.75rem;
    --sp-4: 1rem;
    --sp-5: 1.5rem;
    --sp-6: 2rem;
    --sp-8: 3rem;
    /* Crisp, not soft. Big radii read as "app"; this shop is a catalogue. */
    --radius: 6px;
    --radius-sm: 3px;
    --radius-pill: 999px;
    /* Ambient, not lifted: a wide, faint shadow tinted with the ink colour
       rather than black, so a card sits on the page instead of above it. */
    --shadow: 0 1px 2px rgba(45, 52, 53, 0.04), 0 12px 32px rgba(45, 52, 53, 0.06);
    --ghost-border: inset 0 0 0 1px rgba(173, 179, 180, 0.15);

    /* THE DROPDOWN CHEVRON, drawn once. Any control that turns `appearance` off
       has to paint its own arrow, and a data: URI copied per sheet is a copy free
       to drift in stroke, colour and viewBox from the others — the same argument
       one renderer settles for markup, applied to the one bit of "markup" a
       stylesheet can hold. Stroke is --c-outline's #666 spelled out, because a
       var() cannot reach inside a url(). */
    --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23666666' stroke-width='1.5'><path d='M4 6l4 4 4-4'/></svg>");

    --page-max: 72rem;
    --logo-w: 2.375rem;           /* the wordmark's box (DH's; the shop draws 2rem) */

    /* --- Touch controls ------------------------------------------------- */
    /* Comfortable finger targets on phones. --control-min is the WCAG/iOS
       44px; --fs-control-min is the 16px floor below which iOS zooms a field
       on focus. Both are opt-in per control, not a global reset.

       --target-min is the other kind of number: not a comfort but the LEGAL
       floor, WCAG 2.2 SC 2.5.8's 24px minimum target, which the shared
       coarse-pointer checkbox rule below applies to every box at once (#3341).
       44px is a guideline to be weighed per surface; 24px is not. */
    --control-min: 2.75rem;       /* 44px */
    --target-min: 1.5rem;         /* 24px — WCAG 2.2 SC 2.5.8 minimum */
    --fs-control-min: 1rem;       /* 16px — the iOS no-zoom floor */

    /* --- Motion ---------------------------------------------------------- */
    /* THE FIRST MOTION TOKENS IN THE FILE (asked for, 2026-09-01: make the nav's
       animations smoother "so it feels more modern"). Until now every transition
       in here picked its own number and its own curve — .15s, 0.22s, 150ms, 120ms,
       all of them `ease` — which is four opinions about how fast this product
       moves and no answer.

       THE CURVE IS THE PART THAT READS AS MODERN, not the duration. `ease` is very
       nearly symmetric: it accelerates and decelerates by about as much, so a
       control appears to hesitate before it commits and the whole gesture feels
       heavy. --ease-out leaves immediately and settles slowly, which is what makes
       a response feel like a response — the thing has already happened by the time
       the eye follows it. Every browser and platform that has been asked this
       question in the last decade has arrived at the same shape; this one is
       cubic-bezier(0.2, 0, 0, 1).

       THREE DURATIONS, BY WHAT IS MOVING, not by where it is:
         --dur-1  ink and grounds — a colour has no distance to travel, so anything
                  slower than this reads as a lag rather than a fade.
         --dur-2  a shape changing size or turning over: the tab indicator, a
                  chevron, a panel's few pixels of slide.
         --dur-3  a surface arriving from off-screen. The drawer's own 0.22s,
                  rounded to the ladder.

       Nothing here is decoration, and the global prefers-reduced-motion rule
       further down zeroes every one of them — so no STATE may be carried by
       motion alone. The tab indicator still appears with motion off; it simply
       appears rather than growing. */
    --ease-out: cubic-bezier(0.2, 0, 0, 1);
    --dur-1: 120ms;
    --dur-2: 180ms;
    --dur-3: 240ms;
}

*, *::before, *::after { box-sizing: border-box; }

/* Native checkboxes/radios in the brand teal instead of the browser blue.
   They stay native controls — accent-color tints the check/dot, no custom
   ::before drawing. */
input[type="checkbox"],
input[type="radio"] {
    accent-color: var(--c-accent);
}

/* On touch/coarse pointers only, give every interactive control a finger-sized
   target. Gated on `pointer: coarse` so the deliberately-dense desktop cart
   rows and mina-sidor toolbars keep their compact mouse spacing. */
@media (pointer: coarse) {
    input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
    select,
    textarea,
    button,
    [type="submit"] {
        min-height: var(--control-min);
    }

    /* 24px, not the 20px this was (#3341). 20px was under the legal floor, and
       nothing else sized a box: every checkbox in the shop and the häfte measured
       exactly this rule, so a label whose words are shorter than the box (DH's
       "Sök i flera häften" at --fs-xs, 21px) was a sub-24px target. Raising it
       here is the whole compliance fix.

       THE 4px COMES OUT OF THE BROWSER'S MARGIN, NOT OUT OF THE ROW. Chrome
       draws a checkbox with 3px above and below, so a 20px box was a 26px
       footprint and a bare 24px one is 30px — which grew the shop's search band
       by 4px on every phone page. 1px each way keeps the footprint at 26px, so a
       row the box set the height of (the search filters, "Kom ihåg mig") is
       exactly as tall as it was; only a row whose box a slice had already
       zeroed the margin of grows, by the pixels compliance costs. */
    input[type="checkbox"],
    input[type="radio"] {
        min-width: var(--target-min);
        min-height: var(--target-min);
        margin-block: 1px;
    }

    /* A 44px HIT AREA DOES NOT NEED A 44px BOX — and on the densest rows it
       cannot have one without making the row taller, which on the search band
       is height taken from every phone page. So a checkbox label listed here
       reaches past its own box with a transparent ::after, by amounts its own
       sheet sets beside the gaps they are measured from:

         --reach-start / --reach-end   how far above / below the label the tap
                                       still counts (default 0)

       THE RULE FOR THOSE AMOUNTS: HALF THE GAP TO THE NEIGHBOUR, NEVER MORE.
       A reach that crosses the midpoint takes taps aimed at the next control —
       and this pseudo-element is positioned, so it paints over an in-flow
       neighbour and wins the hit test even where it overlaps one. The label is
       what a tap on it activates, so the ::after needs no script and no ARIA.

       Vertical only: every listed label is already far wider than 44px.
       Coarse pointer only, like the rest of this block — a mouse does not need
       the reach, and a desktop hit area larger than its hover state is odd. */
    :is(.site-search-options label, .dh-search-multi, .dh-comment-org) {
        position: relative;
    }

    :is(.site-search-options label, .dh-search-multi, .dh-comment-org)::after {
        content: "";
        position: absolute;
        inset: calc(-1 * var(--reach-start, 0px)) 0 calc(-1 * var(--reach-end, 0px));
    }
}

/* THE SCROLLBAR IS RESERVED, SO THE PAGE DOES NOT MOVE SIDEWAYS BETWEEN PAGES
   (reported 2026-09-01: "it jumps when you click through the nav").

   Every band in this design is a --page-max column centred with `margin: 0 auto`,
   so the whole layout is positioned relative to the viewport's width — and the
   viewport loses ~15px the moment a page is long enough to scroll. Click from a
   mediegrupp with two rows of results to one with ten and the header, the search
   band and the page under them all shift left by half a scrollbar. Nothing about
   it is per-page CSS; it is the browser answering a question the content changed.

   THIS IS OLDER THAN THE HEADER WORK and is the only line here that reaches past
   the chrome — it moves short pages by ~8px, which is the cost of the fix and is
   paid once rather than on every navigation.

   It also settles a second shift the drawer had: `body.has-open-drawer` sets
   `overflow: hidden`, which took the scrollbar away and slid the page under the
   panel. With the gutter reserved there is nothing to take away.

   A browser that does not know the property keeps today's behaviour. */
html {
    scrollbar-gutter: stable;
}

/* TWO NUMBERS HERE ANSWER TO THE FACE, and both moved when prose stopped being
   a serif (2026-09). Jost is a Futura derivative: small x-height, geometric
   bowls, low stroke contrast, and a lighter overall colour than Source Serif 4
   at the same nominal size.

   line-height 1.5, down from 1.55. Leading follows x-height — a smaller x-height
   at the same point size gives a shorter visual line, so the same leading reads
   airier and the paragraph loosens. The instinct to give a sans MORE air is the
   wrong way round here.

   AND -webkit-font-smoothing: antialiased IS GONE, which is the single most
   useful line in the type change and is a deletion. On macOS and iOS it thins
   glyph stems; that was harmless under a strong-stemmed serif and makes a light
   geometric sans at 16px visibly anaemic — on exactly the platform where this
   shop's mobile traffic lives.

   --fs-base is deliberately NOT raised. It has 28 call sites and is the base of
   a ladder whose other rungs are tuned against it; if prose still reads small in
   a browser, the lever is `html { font-size: 106.25% }`, which moves the whole
   ladder coherently, and not this one number. */
body {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-base);
    line-height: 1.5;
    color: var(--c-text);
    background: var(--c-bg);
    -webkit-text-size-adjust: 100%;
    overflow-wrap: break-word;
}

/* Headings are the brand face, not the body face. */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-headline);
    font-weight: 600;
    line-height: 1.25;
}

/* The system half of the split: anything the customer operates rather than
   reads. Set once here so no slice sheet has to remember it — form controls
   don't inherit font by default anyway, and a data table is furniture. */
input, select, textarea, button,
table, th, td,
legend, label, summary,
.site-header, .site-nav, .site-footer {
    font-family: var(--font-label);
}

/* WHAT A BUTTON IS, BY DEFAULT (the Designmanual för Butiken, 2026-09):
   burgundy, white on it, rounded. There is no .btn in this system -- each slice
   names its own button -- so the alternative was the same four declarations in
   eleven sheets, which is eleven chances to disagree.

   THIS IS A FLOOR, NOT A RESET, and the specificity is the whole mechanism.
   `button` alone, deliberately NOT `button, [type="submit"]`: there is not one
   <input type="submit"> in the whole Features tree, and the attribute selector
   is (0,1,0), which would beat rules it ought to lose to. At (0,0,1) this loses
   to every class-level declaration in the product -- the ~25 deliberate
   `border-radius: 0` on drawer rows and bands, the pills on the tabs and chips,
   the three DH button roles, the tinted containers, and every borderless
   control that declares `background: none` (the copy button, the drawer toggle,
   the antal steppers). Nothing that was styled by decision changes.

   IT SHIPPED WITH THE RADIUS ALONE AND THAT WAS HALF A FIX (found in review,
   2026-09-02). The argument for a floor -- there is no .btn, so the rule has to
   live once in the foundation -- is exactly as true of the FILL, which is the
   half the manual actually names; and a <button> with no rule does not fall back
   to nothing, it falls back to the browser's grey. A slice rule that styles a
   form's section and its heading and never the control ships a Windows button,
   and nothing about that reads as a missing rule in review.

   `border: 0` belongs here for the same reason -- a UA border drawn over a
   burgundy fill is the mismatched half of a control nobody styled -- and so does
   the padding, because the UA's ~1px is what makes an unstyled filled button
   look broken rather than plain. Every base button rule in the product sets its
   own border and padding, so at (0,0,1) none of them notices this. */
button {
    padding: var(--sp-2) var(--sp-4);
    border: 0;
    border-radius: var(--radius);
    background: var(--c-accent);
    color: #fff;
    cursor: pointer;
}

img { max-width: 100%; height: auto; }

/* THE ERROR PAGE, which has no chrome on purpose (Features/Errors/Error.cshtml):
   the header reads the caller's carts, and a read may be what brought the caller
   here. So the page is layout-less and these are the only rules it gets — a
   readable column, and nothing that can fail. */
.error-page {
    max-width: 34rem;
    margin: 0 auto;
    padding: var(--sp-8) var(--sp-4);
}

.error-page .error-request { color: var(--c-text-muted); font-size: var(--fs-sm); }

/* The 404's ways onward. A plain list of links rather than buttons: none of the three is
   the action the reader came for — they came for a page that is not here — so promoting
   one of them to a filled control would be guessing which. */
/* The no-shop-access notice (#3138). Drawn on the cart list and inside the article page's buy
   bar, so it may not assume either page's ground: a tinted well with no border, in the tonal
   idiom the rest of the shop uses instead of lines. It carries an editor-authored body in one of
   its two branches, hence the flow rules for what that body may contain. */
.shop-access {
    background: var(--c-surface-2);
    border-radius: var(--radius);
    padding: var(--sp-4);
    margin: var(--sp-4) 0 0;
    max-width: 40rem;
}
.shop-access p { margin: 0; }
.shop-access p + p { margin-top: var(--sp-2); }
.shop-access a { color: var(--c-link); }

.error-page .error-links { list-style: none; margin: var(--sp-4) 0 0; padding: 0; }
.error-page .error-links li + li { margin-top: var(--sp-2); }

a { color: var(--c-link); }
a:hover { color: var(--c-accent-dark); }

/* One focus treatment for the whole shop: a thick ring, held off the shape
   so it is legible against both the pink band and a white card. */
*:focus-visible {
    outline: 3px solid var(--c-accent);
    outline-offset: 3px;
}

/* The rose, not the "selected" fill: Smultron on white is too faint a step to
   show which text is highlighted. */
::selection { background: var(--c-warm-soft); }

.page {
    max-width: var(--page-max);
    margin: 0 auto;
    padding: var(--sp-4);
}

/* --------------------------------------------------------------------------
   Shared vocabulary. Four things every slice ends up needing, named once so
   the seven slice sheets stop re-inventing them one hex code at a time.
   -------------------------------------------------------------------------- */

/* A micro-label: the uppercase, tracked, tiny sans line that titles a block
   or annotates a number. The system's quietest voice. */
.label {
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--c-outline);
}

/* A chip: a pill of tonal colour carrying a micro-label. Status, count,
   category. .badge (article detail) is the same idea and shares the shape. */
.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    font-family: var(--font-label);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-decoration: none;
}

.chip-accent { background: var(--c-accent-soft); color: var(--c-accent-soft-text); }
.chip-warm { background: var(--c-warm-soft); color: var(--c-warm-soft-text); }

/* A border you feel rather than see, for the cards that would otherwise
   float shapelessly on a tonal background. */
.ghost-border { box-shadow: var(--ghost-border); }

/* Skip link — a keyboard user should not have to tab the whole header. */
.skip-link {
    position: fixed;
    left: var(--sp-4);
    top: var(--sp-4);
    z-index: 1000;
    transform: translateY(-200%);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius-sm);
    background: var(--c-accent);
    color: #fff;
    font-family: var(--font-label);
    font-weight: 700;
    text-decoration: none;
}

.skip-link:focus { transform: translateY(0); color: #fff; }

/* "Laddar…" — a navigation is under way (#3254, /js/navigation-progress.js).
   A MITIGATION, not a fix: DH's pages are slow because of PL/SQL (#3253), and this only stops a
   slow page reading as a hung one. Site-wide rather than DH's, because a caller cannot tell which
   route is slow today.

   THE WORD IS THE INFORMATION AND THE SPINNER IS DECORATION — the rule .btj-confirm.is-busy states
   for a dialog, at page level. The global prefers-reduced-motion rule below zeroes every animation
   on the page, so the ring stops turning for a caller who asked for less motion and the sentence is
   all that is left; it therefore has to be a sentence and never a bare bar.

   Top-centre and fixed, over the header rather than pushing it: the pill is transient and a
   navigation indicator that reflowed the page it is reporting on would move the very link that was
   just clicked. z-index clears the drawer and its scrim (40/50) and stays under the skip link
   (1000), which a keyboard caller must still be able to reach. pointer-events: none because it is
   drawn where a control might be and is not one. */
.nav-busy {
    position: fixed;
    top: var(--sp-3);
    left: 50%;
    transform: translateX(-50%);
    z-index: 900;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-pill);
    background: var(--c-surface);
    box-shadow: var(--shadow);
    color: var(--c-text);
    font-size: var(--fs-xs);
    font-weight: 600;
    pointer-events: none;
}

/* Rendered hidden and revealed by /js/navigation-progress.js — the [hidden] attribute loses to the
   display above without this, and an author `display` beats the UA sheet's [hidden] whatever the
   specificity, so the pill would sit on EVERY page for ever. Reported 2026-09-03 ("I get the
   'laddar' all the time. always on"), which is the fourth outing of this exact trap after
   .site-adv-panel, .site-cart-menu and .copy-btn — every one of them a control drawn hidden by the
   server and revealed by script. The tests that were supposed to cover it asserted the `hidden`
   ATTRIBUTE, which was true throughout: an attribute is not a computed style. */
.nav-busy[hidden] { display: none; }

.nav-busy-spinner {
    width: .85em;
    height: .85em;
    border: 2px solid var(--c-accent-soft);
    border-top-color: var(--c-accent);
    border-radius: var(--radius-pill);
    animation: nav-busy-spin 700ms linear infinite;
}

@keyframes nav-busy-spin {
    to { transform: rotate(360deg); }
}

/* The other half of the answer, and the half that is where the caller is already looking: the pill
   is at the top of the viewport and may be nowhere near the link that was clicked. */
html[data-navigating], html[data-navigating] a { cursor: progress; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ==========================================================================
   CHANGING TAB IS A CHANGE OF DOCUMENT (reported 2026-09-01: "the switching nav
   is not smooth")

   THE REASON NOTHING COULD BE DONE ABOUT IT IN THE TAB'S OWN RULES. Every
   transition in this file animates a change WITHIN one document, and clicking a
   media group is not one: the server renders a new page, the browser throws the
   old one away, and the indicator does not move from Böcker to Musik -- one
   document's indicator stops existing and another's is painted where it always
   was. There is no state change for a `transition` to observe, which is why the
   strip animates beautifully on hover and not at all on the click that matters.

   Cross-document view transitions are the mechanism that closes that: the browser
   keeps a snapshot of the outgoing page, matches elements by `view-transition-name`
   across the two documents, and tweens between them. BOTH documents must opt in,
   which decides the blast radius for free -- a navigation to a page still served
   by the WebForms shop through YARP simply does not transition, because that page
   carries none of this. No JS, and nothing to undo when the last legacy page goes.

   EXACTLY ONE THING IS NAMED, and the list was shorter after it was tried.

   The current tab's indicator MORPHS. Only one tab is `.is-current` per document,
   so the name is unique in both, and matching it across them turns two unrelated
   bars into one bar that slides and resizes from the old tab to the new one. That
   is the animation the strip has always described in its own comment (scaleX from
   the centre, "arriving") finally applied to the event that means it.

   `.site-header` WAS NAMED FOR HALF A DAY and is not any more -- it made the header
   a permanent stacking context and a containing block for fixed descendants, which
   dropped the account panel under the page. The full reasoning is on `.site-header`
   itself, where the next person to reach for the property will be standing. What it
   leaves behind is the rule for choosing names here: the band is nearly the same
   pixels in both documents, so cross-fading it at --dur-1 is a fade between two
   near-identical images and costs nothing. NAME WHAT MOVES BETWEEN THE TWO PAGES,
   not what stays the same on both; a name is worth its side effects only for an
   element whose position or size actually differs across the navigation.

   THE HOUSE RULE STILL HOLDS: nothing is carried by this. Both pages are fully
   drawn at both ends, and `prefers-reduced-motion` turns the whole thing off --
   which needs saying explicitly, because the blanket rule above does not reach
   here: `*` does not match `::view-transition-*`, so the opt-in is what has to be
   inside the media query. With motion reduced the navigation is what it is today,
   an instant repaint.

   --dur-1, AND EVERY GROUP, NOT JUST THE ROOT (reported 2026-09-01: "animation too
   slow"). Two separate mistakes, and the second was the one being felt.

   THE ROOT IS NOT THE WHOLE TRANSITION. Naming an element takes it OUT of the root
   snapshot and gives it a group of its own, with its own animation -- so retiming
   `::view-transition-old(root)` alone left the header and the indicator, the two
   things this change exists for, running at the browser's default 250ms while the
   page under them faded in 180. The parts that were retimed were the parts nobody
   was watching. The selectors below take `*`, which is every group including root,
   so there is one duration here and adding a third name cannot reintroduce the
   split.

   AND THE NUMBER ITSELF WAS TOO BIG. --dur-2 is the token for a shape changing
   size, which is the right description of the indicator and the wrong one of a
   navigation: a page transition is not a thing being watched, it is the seam
   between a click and its answer, and any length a reader can perceive as a
   duration is a delay dressed up. --dur-1 is the file's shortest and is what the
   grounds and inks use -- long enough that the frame is not a cut, short enough
   that it is over before it is noticed. It is deliberately NOT --dur-3, the token
   for "a surface arriving from off-screen", even though a whole page arriving
   sounds like exactly that: the drawer earns 240ms by travelling the width of the
   screen, and nothing here travels at all. */
@media (prefers-reduced-motion: no-preference) {
    @view-transition {
        navigation: auto;
    }
}

/* The pseudo tree inherits from the document element, so the tokens resolve.
   `group(*)` covers the morph of a named element (the indicator sliding between
   tabs); `old`/`new` cover the cross-fade of its contents and of the root. */
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
    animation-duration: var(--dur-1);
    animation-timing-function: var(--ease-out);
}

/* ==========================================================================
   Site chrome (header, nav, footer) — every slice inherits this shell

   TWO bands, and not one line between them: the pink brand row — wordmark, the
   six mediegrupp tabs, the caller, the cart, all on one line — and then the page,
   which opens with the search. The tone change *is* the boundary; that is the
   "no lines" rule from Böcker i serie, and it is most of why the shop reads as
   the same product as the app.

   The shape settled in two steps on 2026-08-19. #3140 took the search out of the
   brand row, where it had been wedged between the wordmark and the login/cart
   controls, and gave it a band of its own. Then the tabs joined the brand row and
   the search band took the PAGE's grey (--c-bg) instead of a colour of its own —
   so the chrome is one stripe, not two, and the search reads as the top of the
   page rather than as more furniture above it.

   That is also what keeps the tabs working as tabs. A tab is only a tab if there
   is a sheet behind it: they sit on the brand row's BOTTOM edge, and the current
   one is filled in --c-bg, so it breaks the pink exactly where the grey below
   starts and the tab and the page become one shape.
   ========================================================================== */

/* NO view-transition-name HERE, AND IT IS NOT AN OMISSION (reported 2026-09-01:
   "the user popup has disappeared under content").

   `view-transition-name` is not a transition-time property. An element carrying one
   generates a STACKING CONTEXT and a CONTAINING BLOCK for absolutely and fixed
   positioned descendants — permanently, on every page, whether or not a navigation
   is happening. Naming this element to hold the band still across a navigation
   therefore rewrote the paint order and the coordinate system of everything inside
   the header, which is where this chrome keeps three things that depend on both:

     - the account panel (`.site-account-menu`, z-index 40) — above 64rem the header
       is static, so before the name the panel competed at the document's own
       stacking level and won against the page. Trapped inside a new stacking
       context whose root is a static element painted before `<main>`, its z-index
       became a private ordering among its siblings and the page drew over it. This
       is the reported symptom, and note that no z-index anywhere was wrong: the
       number that stopped working was the one that had never needed to exist.

     - the drawer panel and its scrim, both `position: fixed` below 64rem. A fixed
       descendant of a named element resolves against THAT element's box, not the
       viewport — so a full-height panel and a full-page scrim would have been laid
       out inside a 64px row. Not reported only because it needs a phone width.

   THE FIX IS THAT THE HEADER IS NOT THE ELEMENT THAT NEEDED A NAME. What it bought
   was "the band does not cross-fade", and the band is very nearly the same pixels in
   both documents — so at --dur-1 the cross-fade it was written to avoid is a fade
   between two near-identical images, which is invisible. The indicator is the part
   that genuinely has to be matched across the two pages, it is named (see
   `.site-nav a.is-current::after`), and a pseudo-element with no descendants can
   carry a name without any of the above being true of it.

   GENERAL FORM, worth more than this instance: a property that changes what an
   element IS — a stacking context, a containing block, a formatting context — is
   never scoped to the feature it was added for. `will-change`, `contain`,
   `filter`, `transform`, `backdrop-filter` and `view-transition-name` all do this,
   and all of them fail the same way: nothing errors, and something unrelated three
   hundred lines away quietly changes layer. */
.site-header {
    background: var(--c-pink);
}

/* THE BRAND ROW IS ALWAYS ON SCREEN — ON A NARROW WINDOW ONLY (#3149, asked for
   2026-08-20, and scoped to mobile in the same conversation). Two facts make it
   affordable there and not worth it here.

   `position: sticky` pins an ELEMENT, so what <header> holds decides what gets
   pinned. #3149 made the search band a SIBLING of the header for exactly this
   reason: a header carrying both bands would pin ~190px of a phone's 844.

   And what is pinned is a different thing at each width. Below 64rem the row is one
   compact line — mark, cart, glyph, 64px — and it carries the ONLY way to the media
   groups and the account, so keeping it on screen is keeping navigation reachable.
   Above 64rem the row is the navigation itself, already ~51px of tabs, account and
   basket, on a window tall enough that scrolling to the top costs nothing; pinning it
   would spend that height on every page for an affordance nobody was missing.

   So the markup change stays and the pin does not. That asymmetry is the point: the
   STRUCTURE has to serve the narrowest case, because there is one markup at every
   width (the rule this whole task turns on), while the BEHAVIOUR can be scoped.

   BOTH CHROMES SINCE #3189. This said "not the DH chrome at any width", on the
   reading that DH's header was two bands of its own and would cost twice as much to
   pin. That stopped being true twice over: the service links moved into the brand row
   on 2026-08-24, and #3189 gave DH the same drawer — so down here its row is the same
   64px line carrying the same only-way-in, and the argument above applies to it word
   for word. A pinned row is not a look, it is what makes a drawer's button reachable
   without scrolling back up.

   z-index 50 puts the row over the page. It deliberately TIES with the body-level
   popups (.cart-picker-menu, .sok-cart-menu), which are appended to <body> and so
   come later in the document: at equal z-index the later one wins, which is the
   right precedence — a menu the caller just opened should not slide under the
   header. It is also what makes the header a stacking context, which is what keeps
   the drawer and its scrim above the page; both live inside it. */
@media (max-width: 63.99rem) {
    .site-header {
        position: sticky;
        top: 0;
        z-index: 50;
    }

    /* A pinned row eats the top of anything an anchor scrolls to — the skip link's
       #main, and /artikel's #merinfo stamp. One number, slightly over the row's 64px,
       so a fragment lands under the chrome rather than behind it. In here with the
       pin, because with nothing pinned it would push every anchor down for no
       reason. */
    html { scroll-padding-top: 4.5rem; }
}

.site-header-inner {
    max-width: var(--page-max);
    margin: 0 auto;
    /* NO BOTTOM PADDING, AND THE ROW HANGS FROM ITS FLOOR. The tabs sit on the
       band's bottom edge, and everything else in the row is aligned to that same
       line — which is where the eye reads this row, the wordmark and the basket
       having sat there since the band was built.

       IT WAS CENTRED FOR ONE ITERATION on 2026-09-01 and put back the same day.
       The reasoning for centring was sound and the outcome was not what was asked
       for: with the tabs flat they no longer NEED the floor, so centring was the
       tidier rule — but it aligned the row by lifting the wordmark, the tabs and
       the basket UP to the account trigger, when what was wanted was the trigger
       coming DOWN to them. Three things moving is the wrong answer to one thing
       being out of place, whichever line they end up on. The trigger is sized to
       this row now instead (see .site-account-trigger), so the alignment is a
       property of that control and the row is untouched.

       --sp-2 above, not --sp-3 (asked for, 2026-08-19: make the row a bit
       smaller). The row's height is whatever its tallest child needs plus this,
       so the three numbers that decide it are this padding, that margin, and the
       wordmark's box — all three came down together, from ~66px to ~51px. */
    padding: var(--sp-2) var(--sp-4) 0;
    display: flex;
    flex-wrap: wrap;
    /* Bottom, not centre — same reason. */
    align-items: flex-end;
    gap: var(--sp-3) var(--sp-4);
}

/* The non-tab items take back the padding the row gave up.

   CHILD, LIKE THE REST, for `.site-user` since 2026-09-15. It was a descendant
   selector while the caller block sat inside `.site-drawer` — `display: contents`
   made it a flex ITEM of this row without being a child ELEMENT of it. The shop's
   block came back out into the row on 2026-09-01 and Digitalt Häfte's followed it
   (Chrome/_Account.cshtml), so in both chromes it is the row's own child now.
   `.site-cart` joins the list because it left `.site-user` in #3189.

   `.site-cart-wrap` is the DH chrome's, and it is on this list since #3189 for
   exactly the same reason the shop's cart is: it left `.site-user` too, so the row's
   own child is now the wrapper the cart menu hangs off rather than the cart. The `>`
   on both is what stops a chrome that ever nests one inside the other taking the
   margin twice. */
.site-header-inner > .site-logo,
.site-header-inner > .site-cart,
.site-header-inner > .site-cart-wrap,
.site-header-inner > .site-user { margin-bottom: var(--sp-2); }

/* THE DRAWER IS NOTHING AT ALL ABOVE THE BREAKPOINT. `display: contents` drops the
   wrapper's own box and promotes its children to flex items of the row, so the
   desktop header is byte-for-byte the layout it was before the wrapper existed.
   Below 64rem it becomes a real box — see the drawer section further down. */
.site-drawer { display: contents; }

/* THE SHOP'S WORDMARK, AND IT IS THE ONE ITEM THAT SETS THE ROW'S HEIGHT.
   Everything else in here is shorter than a tab: the strip is 42px, the basket and
   the account trigger are 30px plus their --sp-2 margin, and the mark is 896x960 —
   TALLER than it is wide — so its margin box is the only one that can exceed the
   tabs'. Whatever this number is, the header is 8px of top padding plus it.

   THE HISTORY IS THE ARITHMETIC. It was 2.375rem (--logo-w, still DH's) until
   2026-08-19, when the row was asked to be "a bit smaller" and this was what was
   actually making it tall — ~2.55rem of mark against 2.5rem of tab. 2rem was the
   value at which the mark's margin box (34.3 + 8 = 42.3px) and a tab's (42px) came
   out within a pixel, i.e. the point where the row is as short as the tabs allow
   and shrinking the mark further buys nothing.

   2.25rem SINCE 2026-09-01 (asked for: increase it a bit). 36px wide is 38.6px
   tall, so the margin box is 46.6px and the row is ~55px against ~50px — the mark
   is over the tabs again by ~4px, and that IS the cost, paid in header height on
   every page. It is deliberately one notch and not two: 2.5rem would put the row
   near 59px, which is where it was before the 2026-08-19 request, so the next step
   up is not a bigger mark but a reversal of that one.

   Scoped past .dh-logo rather than changing --logo-w, which both chromes read:
   the DH header is a different chrome with its own density and was not part of
   this, so its mark keeps the token's size. That is also why the token itself is
   untouched — it is still "the wordmark's box", just no longer the shop's. */
.site-header-inner > .site-logo:not(.dh-logo) { width: 2.25rem; }

/* The tab strip inside the brand row — BOTH chromes, since 2026-08-24. It grows into
   whatever the wordmark and the caller leave, and WRAPS onto its own line rather than
   being clipped when that is not enough: the ul's one-row clip would otherwise eat
   tabs from the right on a narrow window, which is a navigation item silently
   disappearing. */
.site-header-inner .site-nav {
    flex: 1 1 20rem;
    min-width: 0;
    /* The band is the header's now, so the nav brings no ground of its own. */
    background: transparent;
    padding-top: 0;
}

/* Inside the row it is no longer the full-width band it used to be: the header
   inner is already the --page-max column and already gutters, so the list simply
   fills its flex item. */
.site-header-inner .site-nav ul {
    max-width: none;
    margin: 0;
    padding: 0;
}

/* NOTHING LEFT TO UNDO. Standing alone the strip used to draw a hairline shelf
   for the folder tabs to sit on, and these two declarations took it away inside
   the brand row. The tabs went flat on 2026-09-01 and the shelf went with them
   (see the flat-tabs block), so the base rule no longer draws one — this is kept
   as a statement rather than deleted, because the reason it existed is still the
   design: the band's end is marked by the pink stopping and the page's grey
   starting, and a rule drawn here would span only the nav's flex item and stop
   short of both gutters. */
.site-header-inner .site-nav ul {
    box-shadow: none;
    padding-bottom: 0;
}

/* NO GROUND OF ITS OWN IN THE ROW. A flat tab is the band with a word on it, so
   there is nothing to fill: the rule that used to paint each tab --c-pink was
   there to make a bordered box disappear into the band it stood on, and with no
   box there is no box to hide. Hover and current are the base rules' — both are
   written to work on the pink and on the white the strip stands on when it is
   not in a header. */

/* TWO HEADER LAYOUTS SINCE 2026-09-01, WHERE THERE WERE THREE (asked for: the
   desktop header must handle 1024–1366px "without awkward wrapping or overflow",
   and below 1024px collapse into the drawer).

   WHAT WAS HERE. A middle band — from where the drawer stopped (60rem) to 72rem —
   in which the tab strip took a line of ITS OWN inside the pink band, below the
   mark and the caller. That was not a nicety: the ul clips to one row
   (overflow: hidden + max-height), so a strip that has to share the row with the
   wordmark and a ~310px caller block loses tabs off the end silently — measured
   at 520px and again at 960px, where *Spel* simply vanished. A dropped tab is a
   media group the customer can no longer reach, and nothing says so.

   WHY IT CAN GO NOW, AND THIS IS THE WHOLE OF IT. The 310px is gone. The caller's
   three loose items became one trigger (.site-account below), so the row's own
   arithmetic changed and the band that existed to survive that width no longer
   has anything to survive. This deletion and that consolidation are one change:
   put the three items back and this rule has to come back with them.

   THE NEW SUM, in the same terms the old one was written in — WHAT FITS IS A
   FUNCTION OF THE LABELS, NOT THE VIEWPORT. Seven labels plus 14 x --sp-4 of
   padding is ~700px of strip; the mark is 32px; the basket is ~120px with a full
   total; three flex gaps are 48px. That is ~900px before the account. The
   trigger is ~56px with its label hidden and ~150px with it shown, so:

     - at 1024px the content box is 992px (1024 less two 16px gutters) and the
       row needs ~956px. It fits, with the label hidden.
     - the label appears at 76rem, where the content box is 1184px against
       ~1050px needed. ~130px of slack, which is the margin the old rule kept and
       for the same reason: the clip is SILENT, so the margin holds for the
       widest caller and not the average one.

   Past --page-max (72rem) the row stops getting wider — .site-header-inner is
   capped — so above it the sum is against a fixed 1120px content box whatever the
   window does. That is why 76rem can be a threshold for the LABEL but never for
   the strip: at 76rem the row is already as wide as it will ever be.

   An eighth entry, or a longer word in any of the seven, spends that slack. It
   spends it invisibly, which is the one thing about this strip that has not
   changed in three tasks. */
/* THE LABEL IS THE LAST THING TO ARRIVE, NOT THE FIRST THING TO GO. Written as a
   min-width rule rather than as a band, because the trigger is label-less at
   BOTH ends — on a phone the row is the mark, the account and the basket and
   there is no room for a word, and between 64rem and 76rem the strip needs every
   pixel the sum above accounts for. A band would have said the same thing twice
   and left the two halves free to drift apart.

   The avatar and the chevron never go: the account is in the same place, at the
   same size, at every width, and what changes is only whether it also says the
   name. The trigger's aria-label carries the name at the widths where the eye
   cannot read it, which is why that attribute is written on the element rather
   than left to the visible text (Chrome/Default.cshtml). */
@media (min-width: 76rem) {
    .site-account .site-username {
        display: block;
    }
}

/* The wordmark is the brand's own artwork — orange, lowercase, and not
   something a font stack can approximate. Width is pinned to --logo-w; the
   height follows from the file's aspect ratio.

   It no longer sets the nav's indent. That rule existed to put the tabs under the
   search field's left edge while the two sat in different bands; #3140 put them in
   the same one, where both simply take the page gutter. */
/* THE MARK CARRIES THE SHOP -> HÄFTE NAVIGATION (asked for, 2026-09-01: the two
   chromes need "a smooth transfer somehow").

   THE ROWS ALREADY AGREE, and that is what makes this the only piece left. Both
   headers are .site-header-inner, --sp-2 above and below the tallest child, and in
   BOTH the tallest child is a tab: 14px of --fs-sm at line-height 1.715 plus 2 x
   --sp-2 plus the 2px indicator spacer = 42px, against a shop mark of 38.6px, a DH
   mark of 40.7px and DH's "BTJ-Häftet" word at 27.9px. So both bands are 58px and
   nothing under them moves when you cross between the two applications. That is
   not luck: the DH chrome is the shop's header with a different payload (see
   haftet.css's own note), so the number is set in one place for both.

   WHAT DOES MOVE IS THIS. The shop draws a 36px mark alone; DH draws a 38px mark
   with a word beside it, which puts the same wordmark at a different size in a
   different place. Sharing a name across the two documents makes the browser tween
   between those two boxes instead of cross-fading one out and the other in — so
   the thing a reader is looking at when they click "BTJ Häftet" is the thing that
   travels, and the band it sits on never appears to change at all.

   WHY IT IS SAFE HERE AND WAS NOT ON .site-header. A name makes the element a
   stacking context and a containing block for positioned descendants, permanently
   (the whole story is on .site-header). This is an <a> holding an <img> and a
   <span> and nothing else: there is nothing inside it that is positioned, so there
   is nothing for either effect to reach. That is the test to apply before naming
   anything else in this chrome — and it rules out .site-account and DH's
   .site-cart-wrap, which hold the two panels.

   ONE PER DOCUMENT, which is the other requirement a name has: exactly one of the
   two chromes renders, and neither draws a second .site-logo. */
.site-logo {
    display: inline-flex;
    width: var(--logo-w);
    view-transition-name: site-logo;
}

.site-logo img { display: block; width: 100%; height: auto; }

/* The mark does not restate itself on hover; the whole header row is the
   affordance and the cursor already says so. */
.site-logo:hover { opacity: 0.85; }

/* --- the shop's search, under the brand row ------------------------------ */

/* THE SEARCH SITS ON THE PAGE, NOT ON A BAND OF ITS OWN (asked for, 2026-08-19).
   --c-bg is the body's own ground, the same grey every page below is drawn on, so
   this block has no edge of its own at all: it is simply where the page starts.
   Two things follow and both are the point. The chrome becomes one coloured stripe
   instead of two, so it stops competing with the page for weight; and the current
   mediegrupp tab, which is filled in this very colour, breaks the pink above and
   merges into this — a tab with its sheet behind it. */
.site-subheader {
    background: var(--c-bg);
    padding-bottom: var(--sp-3);
}

/* The search is the first thing ON the page, in the same --page-max column as
   everything under it. It still reads after the tabs — they are in the band above
   now — so the layout still says "choose the shop, then search it".

   ONE LINE, NOT THREE (asked for, 2026-08-19): the label, then the field and the
   filters side by side. A flex row rather than a grid because the wrapping is the
   whole behaviour — the filters drop under the field when the line runs out, and a
   grid would need a media query to say the same thing at a width that depends on
   how many filters this media group even draws (two of the three are conditional).

   The label takes a whole line of its own; the field takes what is left after the
   filters, which are as wide as their own words. */
.site-search {
    max-width: var(--page-max);
    margin: 0 auto;
    padding: var(--sp-3) var(--sp-4) 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
}

/* THE SCOPE, IN WORDS. The old header box had no room for a label, so which media
   group a search was filtered to was said only by the lit tab and by an
   aria-label. On a media landing this line is now the loudest statement of which
   shop you are in — which matters more than it looks, because the landing's own
   <h1> is visually-hidden and this is half of what justified hiding it
   (docs/StartPageAnatomy.md §8). */
.site-search-label {
    /* Its own line: it names the field, so it must not read as a heading for the
       filters sitting beside it. */
    flex: 0 0 100%;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--c-text-muted);
}

/* Field + button, growing into whatever the filters leave. The 24rem basis is the
   width below which they stop sharing a line and the filters wrap underneath —
   narrower than that and the field is too short to read a title in. */
.site-search-row {
    flex: 1 1 24rem;
    min-width: 0;
    display: flex;
    gap: var(--sp-1);
    align-items: stretch;
    padding: var(--sp-1);
    border: 1px solid color-mix(in srgb, var(--c-outline) 45%, var(--c-surface));
    border-radius: 999px;
    background: var(--c-surface);
    box-shadow: 0 2px 6px color-mix(in srgb, var(--c-accent) 6%, transparent);
}

.site-search-row:focus-within {
    border-color: var(--c-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 18%, transparent);
}

.site-search-field {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
}

/* The magnifier sits inside the field rather than on the button: the field is
   what the customer aims at, and the icon is what tells them so. */
.site-search-icon {
    position: absolute;
    left: var(--sp-3);
    top: 50%;
    transform: translateY(-50%);
    color: var(--c-outline);
    pointer-events: none;
}

.site-search-input {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 2.5rem;
    padding: var(--sp-2) var(--sp-3) var(--sp-2) 2.5rem;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--c-text);
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    /* --fs-control-min exactly: 16px is the floor, not a preference — under it
       iOS zooms the page when the field is focused on a phone, so this is as
       small as the text in here is allowed to get. */
    font-size: var(--fs-control-min);
}

.site-search-tips {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    z-index: 30;
    margin: 0;
    padding: var(--sp-1) 0;
    list-style: none;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    max-height: 18rem;
    overflow-y: auto;
    font-size: var(--fs-sm);
}

.site-search-tip {
    padding: var(--sp-2) var(--sp-3);
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A hovered suggestion is tonal, not inverted — the teal-on-white fill was
   loud enough to read as "selected and submitted". */
.site-search-tip:hover,
.site-search-tip.active {
    background: var(--c-accent-soft);
    color: var(--c-accent-soft-text);
}

/* Focus belongs to the whole pill; suggestions remain outside its outline. */
.site-search-input:focus {
    outline: none;
    box-shadow: none;
}

.site-search-input::placeholder {
    color: var(--c-text-muted);
    opacity: 1;
}

.site-search-btn {
    flex: 0 0 auto;
    align-self: stretch;
    min-height: 2.5rem;
    padding: var(--sp-2) var(--sp-5);
    border: 0;
    border-radius: 999px;
    background: var(--c-accent);
    color: #fff;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 600;
    line-height: 1.25;
    cursor: pointer;
    white-space: nowrap;
}

.site-search-btn:hover { background: var(--c-accent-dark); }

/* THE FILTERS ARE SHOWN, NOT FOLDED, AND THEY SIT BESIDE THE FIELD. In the header
   they were behind a <details> with an empty summary, because three checkboxes
   would not fit that row. /sok — whose re-search form this now is — has always
   drawn them openly, and a fold would have made one control answer differently
   depending on which page drew it.

   Beside rather than below (asked for, 2026-08-19), which also stops them reading
   as a second row of the form. They are as wide as their own words and no wider,
   so the field keeps everything else; when the line will not hold both, this whole
   group wraps under the field as one block rather than breaking up.

   The language box is conditional — SearchFormLogic hides it outside
   Allt/Böcker/Ljudböcker — so how wide this group is depends on the media group,
   which is exactly why the wrapping is flex behaviour and not a breakpoint. */
.site-search-options {
    flex: 0 1 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-4);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

/* A CHECKBOX LABEL IS THE CLICK TARGET, SO IT MUST NOT BE THE QUIETEST THING IN ITS
   OWN ROW (2026-09-01). The group is set in --c-text-muted, which is right for a
   caption over a field (.ms-field > span) and wrong here: these three labels ARE
   the controls -- the box is 13px of it and the words are the rest -- and they sat
   greyed beside two full-strength teal items, so the row read loudest where it was
   least actionable. The group keeps --fs-sm; only the ink of the labels changes,
   the surrounding group colour still carrying the mark and the trigger. */
.site-search-options label { display: inline-flex; align-items: center; gap: var(--sp-1); color: var(--c-text); }

/* The filters' tap reach (#3341; the mechanism is the coarse-pointer block near the
   top). Half of .site-search's row gap up towards the field, half of this group's
   own row gap down towards the "Avancerad sökning" pill that wraps under them on a
   phone: 26px of label becomes ~32px of target and the band does not grow. 44px
   here would cost the band ~18px of visible height on every phone page. */
@media (pointer: coarse) {
    .site-search-options label {
        --reach-start: calc(var(--sp-2) / 2);
        --reach-end: calc(var(--sp-1) / 2);
    }
}

/* THE WAY IN IS A CONTROL, NOT A FILTER (2026-09-01). It shared the row's column
   gap and the link colour with three checkbox labels, so the one item that OPENS
   something was told apart from the things that qualify a search by nothing but
   an 11px chevron. It takes .tab's pill (see that block) -- separated by SHAPE
   rather than by a wider gap, which is what the group's own comment reached for
   and could not have, being as wide as its contents with no free space to push
   into.

   The open state is the payoff: `aria-expanded="true"` now fills the pill the way
   a selected tab is filled, so what the panel's state is is legible at the size of
   the control rather than at the size of a rotated chevron. It is the same pair
   the form inside the panel draws -- the way in and the tabs within it are one
   shape, which they were not when one was a link and the others were underlines.

   It stays an <a> to /sok/avancerad: the element is the no-JS path (#3140) and
   this changes only what it looks like. */
.site-search-adv { white-space: nowrap; }
/* The chevron says the link opens something here rather than taking you away. It is
   the same glyph the dropdown fields wear, and it turns over when the panel is open. */
.site-search-adv-chevron { margin-left: 0.15em; vertical-align: -0.1em; transition: transform var(--dur-2) var(--ease-out); }
[aria-expanded="true"] > .site-search-adv-chevron { transform: rotate(180deg); }


/* --- the advanced search panel (#3140) ------------------------------------

   The band's "Avancerad sökning" link drops the advanced form under the band
   instead of navigating to it, so a twelve-field search is reachable from wherever
   you are standing. Same popup vocabulary as .site-cart-menu and .site-search-tips:
   the surface, one hairline, the system shadow, z-index 30.

   Full band width rather than a dropdown under the field, because the form is a
   grid: at 72rem it is four columns and about four rows, which fits without a
   scroll. A 22rem menu would have made twelve stacked fields into a scrolling
   column, which is the shape the page already had and the reason for this change.

   Anchored to .site-subheader, the band's own full-bleed block, so the panel spans
   the viewport the way the band does while its contents keep the page's --page-max.
   Nothing needed for print: .site-header is display:none there already. */
.site-subheader { position: relative; }

/* IT OPENS UNDER THE BAND, AND ONLY THE QUERY ROW STANDS DOWN (asked for, 2026-08-20).

   Two passes to get here. Under the whole band, the panel put a twelve-field search
   directly below a one-line one — the two-controls-arguing shape /sok/avancerad avoids
   by suppressing the band, reappearing inside the band. Covering the band fixed that and
   took the three filters and the trigger with it, which is worse: those are not a second
   search control, they QUALIFY this one, and they have to stay reachable.

   So the line that duplicates the panel is the query row alone — the field and its Sok
   button — and that is what stands down. It is dimmed and marked inert by chrome.js, so
   it cannot be typed in, clicked or tabbed into while a richer form is open, and nothing
   moves or resizes: the filters, the ? and the trigger stay exactly where they were and
   stay live. The trigger is the way out again, so the panel needs no close of its own. */
.site-search.is-adv-open .site-search-label,
.site-search.is-adv-open .site-search-row {
    opacity: .38;
}

/* IT KEEPS THE NAV'S WIDTH (asked for, 2026-08-20). Edge to edge it was a third
   full-width stripe under two others, and on a wide screen most of that white was
   margin: the form inside stops at --page-max like everything else does, so the
   sheet was paying for width it did not use. left/right 0 with an auto inline
   margin is what centres an absolutely positioned box, and the panel is now the
   same column the brand row and the search band draw in — it reads as belonging
   to them rather than as a band of its own.

   Borders and a bottom radius come with that: a box with sides needs them, where
   an edge-to-edge sheet only needed a rule above and below. No top border, since
   the band it hangs from is directly above it. */
.site-adv-panel {
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    z-index: 30;
    max-width: var(--page-max);
    margin-inline: auto;
    max-height: min(34rem, calc(100vh - 10rem));
    overflow-y: auto;
    padding: var(--sp-4) 0 var(--sp-5);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-top: 0;
    border-radius: 0 0 var(--radius) var(--radius);
    box-shadow: var(--shadow);
}
/* Required: an absolutely positioned box does not honour [hidden] on its own
   (the same rule .site-cart-menu needs). */
.site-adv-panel[hidden] { display: none; }
/* First open fetches the form, which is seven database reads deep; this is what stands
   in the box until it lands, so a slow one is a wait rather than a blank panel. */
.site-adv-panel-loading {
    max-width: var(--page-max);
    margin: 0 auto;
    padding-inline: var(--sp-4);
    color: var(--c-text-muted);
}


/* --- the advanced search form (#3031) -------------------------------------

   Lives here rather than in sok.css because it has two homes: /sok/avancerad and
   the panel above, which is rendered on every shop page. One partial
   (_AdvancedForm.cshtml) draws both, so these rules have to reach both.

   A GRID, not a column. Twelve fields one per row at 48rem was a page you scrolled
   to reach the button of; auto-fit gives three columns on the page and four in the
   panel, and the whole form is about four rows either way. Fritext and the actions
   span the full width: the first is the free-text search the rest qualifies, and the
   second is where the form ends. */
/* A GROUND, BECAUSE THIRTEEN BORDERED BOXES ARE NOT A SURFACE (2026-09-01).
   The panel has always stood on --c-surface; the PAGE stood on nothing, so
   /sok/avancerad drew a dozen 1px fields straight onto the page's grey with no
   figure behind them. That is the fault DesignSystem.md names on the häfte's
   annotation panel -- not a wrong tone, a surface that was never given one --
   and it is what "ton, inte linjer" asks for one level up from the fields. The
   step is the system's own: --c-surface over --c-bg, --radius, and the ghost
   border rather than a drawn one. Scoped to the page by :not(), because inside
   the panel the surface is already there and a second one would be a card on a
   card.

   THE TINT IS ON THE FORM, AND THE FORM IS NOT THE REGION (asked for, 2026-09-01,
   over three turns: a full region in --c-indigo-soft, then in --c-sage-soft, then
   the fields themselves, then this). What the sequence settled is worth more than
   the value it landed on. A REGION tint says "everything inside this rectangle is
   one topic" -- true of the start page's editorial ad grid, which is where the
   .start-bands precedent came from, and not the useful fact here: it swept up the
   heading and the mediegrupp strip, which are about the form rather than part of
   it. Tinting the FIELDS said the opposite thing, that each box is its own object,
   which is how a filled input reads and is also how a DISABLED one reads.

   The form element is the one box that is neither: it is exactly the twelve fields
   plus the button that submits them, and the heading and the pills stay on the card
   above it. So the block is tinted and the fields inside it are white -- the
   figure/ground the region tint kept losing, at the level where the grouping is
   actually true. */
.sok-adv {
    max-width: 60rem;
    margin: 0 auto;
}
:not(.site-adv-panel) > .sok-adv {
    padding: var(--sp-5);
    background: var(--c-surface);
    border-radius: var(--radius);
    box-shadow: var(--ghost-border);
}
.sok-adv-title { margin: 0 0 var(--sp-2); font-size: var(--fs-lg); }
/* The panel IS the page column now, so the form fills it; the gutter is the same one
   .site-search uses, which is what puts the first field under the search box. */
.site-adv-panel .sok-adv { max-width: none; padding-inline: var(--sp-4); }

/* Layout only. What a tab LOOKS like is .tab's, shared -- see the block by
   .tablist. The row-gap is there because this strip wraps by design (seven
   labels, and four columns' worth of room in the panel); the underline it used
   to carry could not survive that, which is why it no longer has one.

   The BAND'S trigger wears the same pill (.site-search-adv), so the way into this
   form and the tabs inside it are one shape. */
.sok-adv-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-bottom: var(--sp-4);
}
/* THE SELECTED PILL IS TEAL, AND IT IS NOT DRAWN HERE (asked for, 2026-09-01:
   "something that fits the theme"). It takes .tab-active's --c-accent-soft off the
   shared block by .tablist -- there is no override in this file, and the absence is
   the decision. The resting fill is .tab's own --c-surface-2.

   It was four colours in a day, and what the swaps eliminated is worth more than
   the value they landed on. TEAL was the inherited default and came off on the
   argument that the block under it was teal too, so the one pill that had to stand
   out was saying so in the page's own voice -- true while the form's ground was
   --c-sage-soft, and not true of the ground it has now: the form is --c-pink, and
   teal against warm is the brand's own pair rather than a colour arguing with a
   near-copy of itself. ORANGE (--c-warm-soft) went because its tonal value is
   byte-identical to --c-pink, the header band forty pixels above, so the pill
   rhymed with the chrome instead of marking a choice inside the form -- and that
   is now doubly true, the form's own ground being that same value: an orange pill
   here would be a marker in the colour of two of the three things behind it. GOLD
   was a fifth hue added for one control, which is a hue with no owner; see the
   note where its tokens were.

   (The strip itself is NOT on the tinted block -- it sits on the card's white,
   above the form element, which is what the .sok-adv comment means by the heading
   and the pills staying on the card. So the pill's ground is white and the
   question the ground decides is only which hue is left free.)

   The general form: a control that says the same thing as a control elsewhere in
   the product should say it in the same colour, and when that reads badly the thing
   to change is usually what is BEHIND it. */

.sok-adv-form {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--sp-3) var(--sp-4);
    align-items: end;
    /* The tinted block -- see the .sok-adv comment for why this element and not the
       region around it.

       --c-pink, THE HEADER BAND'S OWN COLOUR (asked for, 2026-09-01: "light orange
       like nav?"). Named as --c-pink rather than as --c-warm-soft, which is the same
       six digits today: the intent is *that band*, so if the band ever moves this
       block should move with it, and only one of the two names carries that.

       It went sage -> --c-surface-cool -> here, and the sequence is the argument.
       Sage was a HUE WITH AN OWNER (the start page's shortcuts rail, where it says
       "these belong together"), so wearing it here made the form a second thing
       making that claim. --c-surface-cool was the opposite attempt -- a surface with
       no hue at all -- and it worked, but it was a rung invented for one block.
       --c-pink is the third answer and the only one already in the product: the
       form is not claiming a meaning of its own, it is wearing the chrome's.

       WHAT IT COSTS, and it is the reason this is a judgement rather than a fix.
       #fdece8 is a far smaller step off white than #e9ede9 was, so the tint no
       longer does much of the work of separating a field from its block -- the
       fields' --ghost-border carries that on its own now (which is what it is for,
       and what it does everywhere else in the shop). And the form is drawn ~100px
       under a band of exactly this colour when the panel is open, which makes it
       read as more chrome rather than as the page's own content. Both are visible
       in a browser and neither is measurable here. */
    padding: var(--sp-4);
    background: var(--c-pink);
    border-radius: var(--radius);
}
/* The free-text box and the actions are about the whole form, not about one column. */
.sok-adv-wide, .sok-adv-actions { grid-column: 1 / -1; }

.sok-adv-field { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
/* --fs-sm, not --fs-xs: this is the same kind of label Mina sidors form draws
   (.ms-field > span), and thirteen of them are the only thing telling a reader
   which field is which.

   FULL INK AND 600, not a colour. The labels were asked to pop, and the palette's
   answer to that is NOT to ink them: teal and orange are both spoken for by
   controls (a teal word over a field reads as clickable), and the region's own
   colour is doing the popping one level up. Thirteen labels tinted in anything is
   thirteen things claiming to be special, which is none. What they were short of
   was presence, so they get the label face's own weight and the page's ink. */
.sok-adv-field label { font-size: var(--fs-sm); font-weight: 600; color: var(--c-text); }
.sok-adv-field input[type="text"],
.sok-adv-field input[type="search"],
.sok-adv-field input[type="date"],
.sok-adv-field select {
    padding: var(--sp-1) var(--sp-2);
    /* WHITE, AND STATED. These four set no background at all until now, which was
       invisible while the form stood on white and is load-bearing the moment it
       stands on a tint: a transparent field takes its container's colour and there
       is no figure left. It is what .kv-field's input has always done.

       NO DRAWN BORDER. It carried 1px --c-border for its whole life, which was the
       only thing defining the box against a white page. The tint behind it does that
       now, so the line would be a second boundary device on the same edge -- "ton,
       inte linjer" is this file's first rule. The ghost border stays, the rim you
       feel rather than see. (No reflow: --ghost-border is an inset box-shadow.) */
    background: var(--c-surface);
    border: 0;
    box-shadow: var(--ghost-border);
    border-radius: var(--radius-sm);
    font: inherit;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    width: 100%;
    min-width: 0;
}

/* The ring the rest of the shop's fields wear (.kv-field's, .ms-combo's). Twelve
   fields with only the browser's default outline was the one place a keyboard
   caller had to guess where they were, and in the panel that default is drawn
   against a raised surface it was never picked for. */
.sok-adv-field input:focus-visible,
.sok-adv-field select:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}
/* Dropdown fields — native selects and the searchable language combo (input[list]) —
   share one chevron so the affordance is consistent; plain text fields stay bare.
   The glyph itself is --chevron, so the häfte's own dropdowns draw the same arrow. */
.sok-adv-field select,
.sok-adv-field input[list] {
    appearance: none;
    -webkit-appearance: none;
    background-image: var(--chevron);
    background-repeat: no-repeat;
    background-position: right var(--sp-2) center;
    background-size: 0.75rem;
    padding-right: calc(var(--sp-2) * 2 + 0.75rem);
}
/* Hide the browser's own datalist arrow; keep it clickable (opacity, not display)
   so the painted chevron opens the list. */
.sok-adv-field input[list]::-webkit-calendar-picker-indicator { opacity: 0; }

/* UTGIVNINGSDATUM — THE SAME DATE CONTROL MINA SIDOR DRAWS (asked for, 2026-08-24).
   Its order filter had already settled what this control is: .ms-date, 8.5rem wide at the control
   font size (minasidor.css). Everything else about the box — padding, border, radius, label font —
   the two forms already share, so matching it is a width and a font size and nothing more.

   Why it had to be said at all: `flex: 1 1 0; min-width: 0` gave each box half a track no matter
   what the control needs, and a date input does not scale when narrowed — the browser draws fixed
   segments plus a picker button INSIDE it, so it clips: the segments crowd and scroll as you type
   and the calendar button is cut off.

   Shrink is kept, with min-content as the floor: two 8.5rem boxes and the dash are wider than one
   track in the four-column panel, and shrinking to what the control needs is right where clipping
   below it is not. So the pair stays on one row, in its own cell — spanning two tracks moved the
   field to a row of its own and left a hole where it was.

   AND THE FLOOR HAS TO FIT THE TRACK, or the field does not clip, it OVERFLOWS: flex cannot shrink
   a box below min-content, so a pair that needs more than the cell holds simply draws outside it,
   over the next field. At Mina sidors --fs-base a date input's min-content is ~122px, so the pair
   needs ~262px against a 16rem (256px) track — six pixels, and the second box sat on Kategori. The
   two sizes it is made of therefore come from different places: the WIDTH is Mina sidors 8.5rem,
   which is what a track with room shows, while the font stays the form's --fs-sm and the padding
   is trimmed to --sp-1, because those are what set the floor the narrowest track has to hold.

   AND ON A PHONE NO FONT SIZE MAKES IT FIT, so the pair wraps (#3011, measured 2026-09-16). Mobile
   Chrome draws a date input with a 144px min-content whatever the font, so the pair needs ~304px,
   and at a 360px viewport the one-column track is ~300px: the second box ended 4px past the screen
   and the whole page scrolled sideways. `wrap` changes nothing where the pair fits — flex wraps
   only a line that overflows — and where it does not, the "till" box drops under the dash. */
.sok-adv-daterange { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); }
.sok-adv-field .sok-adv-daterange input[type="date"] {
    flex: 0 1 8.5rem;
    width: 8.5rem;
    min-width: min-content;
    padding-inline: var(--sp-1);
}

/* The refusal, said where the button is. Not red-on-white shouting: it is a correction to a
   press, not a failure of the form, so it is the rose the shop uses for "needs attention" —
   and not the "selected" fill, which is Smultron and would vanish into the form's own
   Smultron ground. hidden until there is something to say — see chrome.js. */
.sok-adv-empty {
    grid-column: 1 / -1;
    margin: 0;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    background: var(--c-warm-soft);
    color: var(--c-warm-soft-text);
    font-size: var(--fs-sm);
}
.sok-adv-empty[hidden] { display: none; }

/* A field refused on its own — Språk holding a value its list does not offer (avancerad.js,
   AvanceradModel.InvalidLanguage). The sentence under the field is ink, not red: --c-danger on
   the form's Smultron ground sits under 4.5:1 at this size, and the words are what carry it. The
   red is the ring, which is where the eye is sent. */
.sok-adv-field-error { margin: 0; font-size: var(--fs-sm); color: var(--c-text); }
.sok-adv-field-error[hidden] { display: none; }
.sok-adv-field input[aria-invalid="true"] { box-shadow: inset 0 0 0 2px var(--c-danger); }

.sok-adv-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-4); margin-top: var(--sp-1); }
.sok-adv-clear { color: var(--c-text-muted); text-decoration: none; }
.sok-adv-clear:hover { text-decoration: underline; }

.sok-btn {
    padding: var(--sp-2) var(--sp-5);
    font-size: var(--fs-base);
    font-weight: 600;
    color: #fff;
    background: var(--c-accent);
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
}
.sok-btn:hover { background: var(--c-accent-dark); }

/* Below 16px iOS zooms the page on focus, and a form of twelve fields would do it
   twelve times. The compact size is a desktop economy, so it is kept where a
   mouse is pointing.

   POINTER, NOT WIDTH (2026-09-01). This was `max-width: 40rem`, on the reasoning
   that below it the form is one column anyway -- which answers a question about
   ROOM, where the 16px floor is a question about the DEVICE. #3198 settled that
   one for the antal box: a phone in landscape is still a thumb and a touch laptop
   is not a phone. A tablet at 1024px, and this panel on any touch laptop, got
   14px fields and zoomed the page on focus -- the same defect the header search
   and the cart row's select had already been fixed for. */
@media (pointer: coarse) {
    .sok-adv-field input[type="text"],
    .sok-adv-field input[type="search"],
    .sok-adv-field input[type="date"],
    .sok-adv-field select { font-size: var(--fs-control-min); }
}

/* --- the caller: one control, and a menu behind it ----------------------- */
/* WHAT THIS REPLACED, AND WHY THE OLD RULES ARE STILL HERE (asked for,
   2026-09-01: consolidate the raw name, "Mina sidor" and "Logga ut" into a
   profile menu).

   The block was three loose items in the row. Signed in that is ~310px, and that
   number is quoted in four other comments in this file: it is why the narrow
   header could not be one row, why the whole block moved into the drawer, and
   why the one-row arrangement started at 72rem. One trigger is ~150px with its
   label and ~56px without, which is what pays for the 64rem breakpoint and the
   deletion of the middle band — see the .site-account-ready rule near the top.

   THE OLD DECLARATIONS ARE NOT DEAD. Both chromes draw the items as they were
   with no script (the panel stands open as a row, which is the state it degrades
   to — Digitalt Häfte's chrome also drew them that way with script until
   2026-09-15, when it took this trigger). So
   .site-username, .site-mypages, .site-logout and .site-login keep the treatment
   they had, and everything below `.site-account` is the menu drawn ON TOP of
   that rather than instead of it. Deleting them would have quietly restyled a
   chrome this change was not about. */

.site-user {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin-left: auto;
    font-size: var(--fs-sm);
    /* The menu hangs off this box. Harmless where there is no menu — DH's block
       and the no-script one position nothing. */
    position: relative;
}

/* WHO you are is not something to click, so it must not look like the two
   things beside it that are. It was bold near-black next to a teal underlined
   link and a teal un-underlined one — three treatments for three items, and the
   loudest of them was the one with no action behind it.

   Inside the trigger it is a different matter and the rule below re-states it:
   there the whole control is clickable and the name is its label, so muting it
   would mute the one word the control is identified by. */
.site-username {
    font-weight: 600;
    color: var(--c-text-muted);
}

/* "Logga ut" is a POST form (anti-forgery), but reads as a text link next to
   "Mina sidor". Reset the button chrome and match the header link styling. */
.site-logout-form { margin: 0; display: inline-flex; }

/* ONE TREATMENT FOR BOTH HEADER ACTIONS. .site-mypages had no rule at all, so
   it took the browser's underline while .site-logout — a <button> — did not;
   two links doing the same kind of thing were drawn differently for no reason
   other than which element each happened to be. The underline moves to hover,
   where it marks the one the pointer is on rather than all of them at once. */
.site-mypages,
.site-logout,
.site-login {
    padding: 0;
    border: 0;
    background: none;
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    font-weight: 600;
    color: var(--c-link);
    text-decoration: none;
    cursor: pointer;
}

.site-mypages:hover,
.site-logout:hover,
.site-login:hover {
    color: var(--c-accent-dark);
    text-decoration: underline;
}

/* --- "Mina sidor", beside the trigger --------------------------------------- */

/* IT IS A ROW ITEM AT THE WIDTH THE LABEL ARRIVES AT, AND A MENU ITEM BELOW IT
   (asked for, 2026-09-02: next to the account control rather than inside it).

   ONE ELEMENT, TWO PLACES. The server draws this link in the row and chrome.js
   moves that same node into the panel below 76rem — the DH left column's rule
   (#3192), not a second copy: two renderings of one link are two nodes carrying
   the class every fixture selects by. So there is no rule here that hides one
   copy and shows another; there is one copy, and this file only says whether the
   row is where it is allowed to be.

   THE THRESHOLD IS THE LABEL'S, DELIBERATELY. 76rem is where .site-username
   appears, and both are the same question asked once — is there room for a word.
   The sum is on the .site-account-ready comment near the top of this file: at
   1024px the row already needs ~956px of a 992px content box, and what a second
   ~90px label pushes off the end is a media group, silently. Above 76rem the row
   is at its cap (--page-max) and the slack is the margin that arithmetic keeps.

   `scripting: enabled` IS THE SAME PARSE-TIME QUESTION THE PANEL ASKS. chrome.js
   is deferred, so on a narrow window this link is in the row for the frame before
   the move; hiding it for exactly the browsers that will move it is what keeps
   that frame from being ~90px taller than the next one — §7.22.1's flash, which
   is why the element is rendered in the ROW rather than in the panel in the first
   place. With no script the query never matches, the link stays visible in the
   row, and that is the correct degraded state: the panel below it stands open as
   the rest of the block, which is what a scriptless caller has always been given.

   nowrap because "Mina sidor" is two words in a flex row that is allowed to get
   tight; a header link that wraps to two lines grows the band. */
.site-account > .site-mypages {
    white-space: nowrap;
}

@media (max-width: 75.99rem) and (scripting: enabled) {
    .site-account > .site-mypages {
        display: none;
    }
}

/* --- the trigger --------------------------------------------------------- */

/* THE APPEARANCE IS UNSCOPED; ONLY THE PANEL WAITS FOR THE CLASS. Everything
   here was scoped to `.site-account-ready` — chrome.js's — on the reasoning that
   the trigger and the panel are one control and one class should decide both.
   That was the bug reported the same day as "it jumps when you click through the
   nav", and it is #3064's, arriving by a second route.

   WHAT IT LOOKED LIKE. chrome.js is deferred and last in the body, so the
   document PAINTS before it runs. For that frame the block had no class: the
   trigger was `hidden`, and the panel — which deliberately carries no `hidden`
   attribute, because with no script it IS the account block — was a static div in
   the brand row holding a <p> of the full login name with three items stacked
   under it. About 90px of header, collapsing to 64px the moment the class landed.
   Every navigation, at every width.

   WHY THE FIX IS A SCOPE AND NOT A TIMER. The trigger's box is the same box in
   both states, so nothing about how it LOOKS needs to wait for anything; what has
   to wait is whether the panel is a popover or a row of links, and that is the
   only thing still keyed off the class. Rules that describe a control cost
   nothing while it is `hidden`.

   `:not([hidden])` ON THE DISPLAY, which is the one declaration that would
   otherwise defeat the attribute: an author `display: inline-flex` beats the UA
   sheet's `[hidden] { display: none }`, so a rule written without the guard would
   put a dead button in front of a scriptless caller — exactly what rendering it
   `hidden` exists to prevent. `.site-nav-toggle` carries the same guard for the
   same reason (#3149). */
.site-account-trigger:not([hidden]) {
    display: inline-flex;
}

/* THE TRIGGER IS THE CART'S BOX, AND THAT IS WHAT ALIGNS IT (asked for,
   2026-09-01: move the avatar down to the others).

   The row hangs from its floor — see .site-header-inner — so items are aligned by
   their bottom edge and their CONTENTS only line up if the boxes are the same
   height. This one was `min-height: --control-min` with a 28px avatar, against a
   basket that is 22px of glyph in 2 x --sp-1 of padding: 44px against 30px, both
   bottom-aligned, so the avatar's centre sat 7px above the basket's. Nothing was
   wrong with either control on its own, which is why it reads as the newer one
   being in the wrong place.

   So it takes the basket's box exactly — same glyph size, same padding, same
   margin, therefore the same content line — and the two controls at the trailing
   end of the row are now one shape stated twice rather than two shapes that
   happen to sit near each other.

   THE TOUCH FLOOR IS NOT LOST WITH THE min-height. This is a <button>, and the
   `pointer: coarse` block near the top of this file already gives every button
   --control-min on a touch device. So the compact box is a MOUSE economy — the
   same trade that block was written to make, and the same one the basket beside
   it has always had — rather than a target quietly dropped to 30px everywhere.
   Stating min-height here would have overridden that block on desktop and left it
   redundant on touch. */
.site-account-trigger {
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-1) var(--sp-2);
    border: 0;
    border-radius: var(--radius-pill);
    background: none;
    font: inherit;
    font-family: var(--font-label);
    color: var(--c-text);
    cursor: pointer;
}

/* The cart's own hover, and deliberately the same one: these are the two
   controls at the trailing end of the band and a reader should not have to work
   out that they are the same kind of thing. */
/* The ground fades in rather than snapping. Same declaration on the basket
   below, because the two are one shape stated twice and a hover that arrives at
   two different speeds is the fastest way to make that stop being true. */
.site-account-trigger {
    transition: background-color var(--dur-1) var(--ease-out),
                color var(--dur-1) var(--ease-out);
}

.site-account-trigger:hover {
    background: rgba(255, 255, 255, 0.55);
}

.site-account-trigger:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* THE AVATAR IS A SHAPE, NOT A PICTURE. There is no avatar image anywhere in the
   schema and inventing a source for one would be a request per header render; two
   letters in a filled circle is what the data can actually support. Teal rather
   than orange because orange in this row is the cart counter's — one badge that
   has to be found first, and a second orange disc four inches away is two.

   aria-hidden in the markup: two letters cannot name an account, and read out
   beside the label they would say the same name twice, badly. */
.site-account-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    /* 22px, THE BASKET'S GLYPH SIZE — the number _IconCart is rendered at in both
       chromes (`model="22"`). It was 1.75rem/28px, which is what made the trigger
       taller than the basket and put the avatar off its line. The two are a pair
       at the end of the row and a pair is easier to read at one size; matching the
       glyph rather than picking a new number is also what keeps them aligned if
       either is ever resized, because there is one number to change. */
    width: 1.375rem;
    height: 1.375rem;
    border-radius: var(--radius-pill);
    background: var(--c-accent);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 700;
    /* The two letters are set solid and centred; a line box taller than the disc
       would push them off its middle. */
    line-height: 1;
    letter-spacing: 0.02em;
    /* IT ARRIVES ON EVERY NAVIGATION (asked for, 2026-09-01), which is a thing
       worth being explicit about rather than a side effect: the shop is
       server-rendered, so every tab click is a fresh document and this plays
       again. That is the request -- the disc is the one mark in the row that is
       about the CALLER rather than about the shop, and a small arrival is what
       makes a reader notice it is still them after a page changes under it.

       IT MAY NOT CARRY ANYTHING, and that is the constraint the house rule near
       --dur-1 states: the global prefers-reduced-motion block zeroes every
       animation on the page, so anything said only by this movement is said to
       nobody. Nothing is -- the disc, its letters and its colour are all fully
       drawn at the end and identical to what they were before, and with motion
       off the element simply appears.

       --dur-2, the token for a shape changing size (--dur-1 is for a colour,
       which has no distance to travel). 0.72 rather than 0 as the starting
       scale, and no travel: a 22px disc growing from nothing reads as a
       notification badge landing, which is the cart counter's job four inches
       away. `backwards` so the first frame is the from-state even if the browser
       schedules the paint before the animation starts -- without it the disc can
       show at full size for one frame and then jump back to start. */
    animation: site-avatar-in var(--dur-2) var(--ease-out) backwards;
}

@keyframes site-avatar-in {
    from {
        opacity: 0;
        transform: scale(0.72);
    }
}

/* THE LABEL IS CLIPPED, NOT WRAPPED, AND THE CEILING IS TWO GUARDS DEEP.
   AccountDisplay already shortens the value for the row; this is the other half,
   and both are wanted: the C# one keeps the accessible name and the title from
   running to a paragraph, this one keeps a long unbroken login name from growing
   the row. Neither is the other's fallback.

   Scoped past .site-account so it reaches the trigger's label only. (It used to
   say "and not DH's caller block"; that block is this trigger now too.) */
.site-account .site-username {
    /* Hidden by default and shown at 76rem — see the min-width rule near the top
       of this file for why the threshold is stated once, in that direction. */
    display: none;
    max-width: 9rem;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    /* Full ink inside the trigger: see the base rule above for why it is muted
       everywhere else. */
    color: var(--c-text);
}

/* Turns over when the panel is open, which is the same thing the search band's
   own chevron does — one glyph, one behaviour, and the state is read off
   aria-expanded so the mark and the fact cannot drift apart. */
.site-account-chevron {
    flex: 0 0 auto;
    color: var(--c-text-muted);
    transition: transform var(--dur-2) var(--ease-out);
}

.site-account-trigger[aria-expanded="true"] .site-account-chevron {
    transform: rotate(180deg);
}

/* --- the panel ------------------------------------------------------------ */

/* NO FLASH OF AN OPEN PANEL (reported 2026-09-01: "it jumps when you click
   through the nav"). This is #3064's rule arriving at a second control, and the
   symptom was the same shape and much larger.

   chrome.js is deferred and last in the body, so the document PAINTS before it
   runs. The panel deliberately carries no `hidden` attribute — with no script it
   IS the account block, and hiding it would hide it from the one caller who
   cannot open it — so for that frame it was a static div in the brand row: a <p>
   of the full login name with three items stacked under it, about 90px of header
   collapsing to 64px the moment the class landed. On every navigation.

   `scripting: enabled` asks the same question chrome.js answers — is there script
   — at PARSE time, so the panel is shut and the trigger is up BEFORE the first
   paint instead of being corrected after it. The class stays the truth about the
   control; this pair only covers the gap before it lands, and stops applying the
   moment it does.

   BOTH DECLARATIONS OR NEITHER. Shutting the panel alone leaves a 0px hole where
   the trigger will be, so the basket slides ~56px right and back — a smaller jump
   in the same place. Revealing the trigger early is a one-frame disagreement
   between `hidden` and what is painted, in a browser that has just said script is
   enabled and where the attribute is removed milliseconds later; the alternative
   is a min-width kept in step with the trigger's own box by hand.

   Fallbacks are both the old behaviour. With script off the query never matches,
   so the panel stands open under no button exactly as it is meant to; a browser
   that does not know the feature keeps today's flash. */
@media (scripting: enabled) {
    .site-account:not(.site-account-ready) .site-account-trigger[hidden] {
        display: inline-flex;
    }

    .site-account:not(.site-account-ready) .site-account-menu {
        display: none;
    }
}

/* SHUT BY DEFAULT, AND display: none IS THE POINT. It is what keeps the menu's
   items out of the tab order and out of the accessibility tree while the menu is
   closed — the job the `hidden` attribute does for the cart menu, which this one
   cannot use because with no script the panel has to stand open. */
.site-account-ready .site-account-menu {
    display: none;
    position: absolute;
    top: calc(100% + var(--sp-1));
    /* Anchored to the trailing edge, because that is the edge it has: the
       trigger is the last thing before the basket and a panel dropped from its
       leading edge would hang across the tab strip. */
    right: 0;
    z-index: 40;
    min-width: 13rem;
    padding: var(--sp-2) 0;
    border-radius: var(--radius);
    background: var(--c-surface);
    box-shadow: var(--ghost-border), 0 8px 24px rgba(45, 52, 53, 0.16);

    /* IT ARRIVES RATHER THAN APPEARING (asked for, 2026-09-01). A panel toggled
       with `display` is the one thing CSS could not animate for most of this
       file's life — there are no frames between none and block — so the choice
       used to be between a transition and keeping the items out of the tab order,
       and the tab order wins every time.

       `transition-behavior: allow-discrete` is what removes that choice: display
       is held at its OLD value for the length of the transition on the way out,
       and flipped first on the way in, so opacity and transform get their frames
       at both ends while the closed panel is still genuinely `display: none`. The
       @starting-style block below is the other half — without it the opening
       frame has no "before" to interpolate from, because the element was not
       being rendered a moment ago.

       FOUR PIXELS OF TRAVEL, AND NOT MORE. The panel is anchored under its trigger
       and the eye already knows where it will be; a longer slide makes a menu feel
       like a page. What the movement is for is direction — it comes DOWN from the
       control that opened it, which is the one fact a fade alone cannot state.

       Degrades in exactly one step: a browser without allow-discrete or
       @starting-style shows and hides the panel instantly, which is what every
       browser did until now. Nothing is conditional on the animation — the panel
       is open or shut, and `hidden`-equivalent behaviour is `display`'s. */
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity var(--dur-2) var(--ease-out),
                transform var(--dur-2) var(--ease-out),
                display var(--dur-2) allow-discrete;
}

.site-account-ready .site-account-menu.is-open {
    display: block;
    opacity: 1;
    transform: none;
}

/* The state the opening frame interpolates FROM. It has to restate the closed
   values rather than inherit them: at the moment `display` flips, the element has
   no previous rendered state to take them from. */
@starting-style {
    .site-account-ready .site-account-menu.is-open {
        opacity: 0;
        transform: translateY(-4px);
    }
}

/* The name in full, and this is where it is in full: the trigger shows a short
   label by AccountDisplay and clips even that, so the one place a caller can
   always read which account they are in is the panel that opens off it. A
   heading for the menu (aria-labelledby), never a menuitem — it is not a thing
   to press. */
.site-account-ready .site-account-name {
    margin: 0;
    padding: var(--sp-2) var(--sp-4) var(--sp-3);
    border-bottom: 1px solid var(--c-border);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    font-weight: 400;
    letter-spacing: 0.06em;
    text-transform: none;
    color: var(--c-text-muted);
    /* A login name can be an e-mail address with no break opportunity in it, and
       a panel that widens to fit one would run off a phone's screen. */
    overflow-wrap: break-word;
}

/* THE NAME IS THE EMPHASIS, NOT THE LINE (asked for, 2026-09-11). The whole line
   used to be 700 in muted grey, which made the prefix and the name one weight and
   one ink — so "bold" was already true and read as nothing. Now "Inloggad som:"
   is the quiet label and the name steps up in weight, size and ink at once. */
.site-account-ready .site-account-name strong {
    font-size: var(--fs-sm);
    font-weight: 700;
    letter-spacing: normal;
    color: var(--c-text);
}

/* THE ITEMS ARE ROWS, WHICH IS THE DRAWER'S OWN VOCABULARY. A menu is a list of
   places to go, and the panel already draws them one under another
   — so full-width rows at --control-min, ink at full strength, and a ground on
   hover. Not the link treatment the same elements take in the row and in
   DH's chrome: teal underlined text stacked three deep reads as a paragraph of
   links rather than as a menu, which is the argument .dh-links makes one level
   down and .site-nav's drawer rows make one level up. */
.site-account-ready .site-account-menu .site-mypages,
.site-account-ready .site-account-menu .site-logout {
    transition: background-color var(--dur-1) var(--ease-out);
    display: flex;
    align-items: center;
    width: 100%;
    min-height: var(--control-min);
    padding: var(--sp-2) var(--sp-4);
    font-size: var(--fs-sm);
    color: var(--c-text);
    text-align: left;
    text-decoration: none;
}

.site-account-ready .site-account-menu .site-mypages:hover,
.site-account-ready .site-account-menu .site-logout:hover {
    background: var(--c-surface-2);
    color: var(--c-text);
    text-decoration: none;
}

/* Focus is not hover here either: a row lit only by a ground change is the same
   statement as the pointer being over it, and a keyboard caller stepping through
   the rows needs to know which one Enter will take. Inset so the ring stays
   inside the panel's rounded corners. */
.site-account-ready .site-account-menu .site-mypages:focus-visible,
.site-account-ready .site-account-menu .site-logout:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: -2px;
}

/* The form is a wrapper around one row and must not add a box of its own: it is
   inline-flex in the row above, which inside a panel would leave the button
   short of the panel's width and its hover ground stopping mid-row. */
.site-account-ready .site-account-menu .site-logout-form {
    display: block;
    width: 100%;
}
/* The cart is the header's one heavy control and it sat as three loose pieces —
   glyph, orange count, total — flush against "Logga ut" with only the flex gap
   between them. Boxed and given a ground on hover, it reads as the single
   button it behaves as, and the padding is what separates it from the links.
   The negative right margin keeps the glyph on the header's own gutter line, so
   the cluster gains a target without moving. */
.site-cart {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin-right: calc(-1 * var(--sp-2));
    padding: var(--sp-1) var(--sp-2);
    border-radius: var(--radius-sm);
    color: var(--c-text);
    text-decoration: none;
}

.site-cart {
    transition: background-color var(--dur-1) var(--ease-out),
                color var(--dur-1) var(--ease-out);
}

.site-cart:hover {
    color: var(--c-accent-dark);
    background: rgba(255, 255, 255, 0.55);
}

/* THE COUNT IS CARRIED BY SHAPE, NOT BY HUE — and that is a change of reason,
   not of code. This used to be "the one place the orange earns its keep": on a
   page whose every action was teal, a second brand colour was what made the
   count the thing the eye found first. The 2026-09 reskin folded that second
   colour into the accent, so --c-warm resolves to the same burgundy as the tab
   indicator two rows away.

   It still reads, because it is the only FILLED PILL in the header row and
   nothing else up here is a solid shape. Left as --c-warm deliberately: if a
   second voice is ever restored this is one of the two places that wants it.
   Nobody should "fix" the sameness by inventing a hue for this badge alone —
   that is the retired-token mistake the :root block records twice. */
.site-cart-count {
    background: var(--c-warm);
    color: #fff;
    border-radius: var(--radius-pill);
    min-width: 1.35em;
    text-align: center;
    padding: 0 0.4em;
    font-family: var(--font-label);
    font-size: 0.75rem;
    font-weight: 700;
}

.site-cart-total { font-weight: 600; }

.site-cart-count.hidden,
.site-cart-total.hidden { display: none; }

/* The cart menu. ONE chrome draws it since #3112 — Digitalt Häfte's, which lists
   every arbetsyta (the shop's was removed; see docs/CartAnatomy.md §8d). Legacy's
   DH dropdown hangs off the header button on hover; here it is a popover anchored
   to the icon, so the header keeps its height whether or not the caller has carts.
   The rules stay chrome-agnostic — they are keyed off the markup, not off DH. */
.site-cart-wrap { position: relative; }

.site-cart-menu {
    position: absolute;
    right: 0;
    top: calc(100% + var(--sp-2));
    z-index: 30;
    /* Wide enough for an arbetsyta's alias plus its customer number on one line,
       and capped so a caller with a dozen of them scrolls the panel instead of
       the page. */
    width: min(22rem, calc(100vw - 2 * var(--sp-4)));
    /* 32rem, not 28: at the old cap a caller with four arbetsytor — an ordinary
       number, not a dozen — had the last one below the fold of a panel whose
       whole job is to show them all at once. The viewport term is still the one
       that decides on a short window. */
    max-height: min(32rem, calc(100vh - 8rem));
    overflow-y: auto;
    padding: var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    text-align: left;
}

.site-cart-menu[hidden] { display: none; }

/* WHICH HÄFTE THE TOTALS ARE FOR — legacy's cart BUTTON draws the booklet's
   description above its "Kundvagnar: {total}" (DigitalBookletAjaxHTML.GetCartMenuHtml),
   and natively the button is an icon, so its first line is the menu's first line
   (asked for 2026-08-26; it had been a <p> in the DH brand row). Here rather than in
   haftet.css because it is one more line of THIS head, and a head split across two
   files is a head whose two halves can disagree — the menu has exactly one renderer
   and one chrome that draws it, which is what the block above records.

   An eyebrow over the heading, not a heading of its own: it is context for the list,
   and the list itself is not booklet-scoped. It may wrap — a title of unbounded length
   in a 22rem panel has the room the 64px row never had, so nothing is ellipsised. */
.cart-menu-booklet {
    margin: 0 0 var(--sp-1);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.cart-menu-title {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-base);
    font-weight: 700;
}

.cart-menu-totals {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--sp-1) var(--sp-3);
    margin: 0 0 var(--sp-3);
}

.cart-menu-totals > div { display: contents; }
.cart-menu-totals dt { color: var(--c-text-muted); }
.cart-menu-totals dd { margin: 0; text-align: right; font-weight: 600; }

.cart-menu-list {
    margin: 0 0 var(--sp-3);
    padding: 0;
    list-style: none;
}

/* One rule between carts, not a box around each: the panel is already a card. */
.cart-menu-item + .cart-menu-item {
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--c-border);
}

/* TWO LINES PER CART, NOT THREE. Name, then the price under it — the third
   was a full-height "Till kassan" button, removed 2026-08-27 because legacy's
   menu has no checkout link at all (it lists carts; the kassa is reached from
   the cart page each row links). Four carts at three lines ran past the panel's
   max-height with the last one below the fold, which is exactly the question the
   menu exists to answer.

   One column, so nothing has to be pinned: the head is a row of its own and the
   total is the only other cell, and a cart missing either — an empty arbetsyta
   has no total — leaves the survivor where it was. */
.cart-menu-item {
    display: grid;
    gap: var(--sp-1);
}

.cart-menu-item-head {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-2);
}

/* Same rule as the header's own links: the underline marks the one under the
   pointer, rather than drawing four of them down the panel at once. */
.cart-menu-item-name { color: var(--c-link); font-weight: 600; text-decoration: none; }
a.cart-menu-item-name:hover { text-decoration: underline; }

/* An empty arbetsyta is named but not linked (legacy does the same), so the
   name is a span there — it must not read as a dead link. */
span.cart-menu-item-name { color: var(--c-text-muted); }

/* Which arbetsyta the caller is standing in. Tonal fill, no border: the panel
   already has the only line worth drawing. */
.cart-menu-item.is-selected {
    margin-left: calc(-1 * var(--sp-2));
    padding-left: var(--sp-2);
    border-left: 3px solid var(--c-accent);
}

.cart-menu-badge {
    margin-left: var(--sp-2);
    padding: 0 var(--sp-2);
    border-radius: var(--radius-pill);
    background: var(--c-accent-soft);
    color: var(--c-accent-soft-text);
    font-size: var(--fs-xs);
    white-space: nowrap;
}
.cart-menu-item-count { color: var(--c-text-muted); white-space: nowrap; }
/* The grid owns the spacing now; the paragraph's own margins fought it. */
.cart-menu-item-total { margin: 0; color: var(--c-text-muted); }

/* White against the pink band above and the near-white page below: two tone
   changes, no rules.

   The padding is on the nav, not on the ul: box-sizing is border-box globally, so
   padding on the ul would come out of its max-height and clip the tabs it is
   there to fit. It gives the tab tops air under the pink band — a tab that starts
   flush against the band reads as hanging off it. */
.site-nav {
    background: var(--c-surface);
    padding-top: var(--sp-2);
}

.site-nav ul {
    max-width: var(--page-max);
    margin: 0 auto;
    /* The plain page gutter — the same one .site-search below it takes, which is
       what lines the two up now that they share a band (#3140). */
    padding: 0 var(--sp-4);
    display: flex;
    flex-wrap: wrap;
    /* Air between the tabs, not inside them: the 2px indicator spans each tab's
       padded box, so widening the padding would lengthen the rule under the word
       rather than separate the words. The column gap is the one to grow. Row gap
       stays --sp-2 — it only ever applies to a second row, which the max-height
       below clips away. */
    gap: var(--sp-2) var(--sp-4);
    list-style: none;
    /* Show only the tabs that fit on one line: any that don't wrap to a second
       row, which is clipped. No horizontal scrollbar, and never a half tab —
       the fixed 2.5rem tab height plus its 2px indicator (see .site-nav a) makes
       the cut exact. Above 64rem the row is wide enough that nothing is clipped
       and below it there is no strip at all, so this is a guard rather than a
       layout: what it protects against is a longer label or an eighth entry, and
       it protects SILENTLY, which is why the breakpoint arithmetic is written
       down where it is. */
    max-height: calc(2.5rem + 2px);
    overflow: hidden;
}

/* THE HEADLINE VOICE, NOT THE MICRO-LABEL ONE. The face was always right —
   --font-label and --font-headline are the same Jost — but the treatment was
   the system's quietest: --fs-xs, uppercase, 0.1em tracking, which is the
   .label recipe for annotating a number or titling a block. These are the
   shop's primary navigation, and at 12px all-caps they read as furniture
   printed above the page rather than as the six places a customer can go.
   Sentence case at --fs-sm gives them the brand face at a size worth reading;
   Böcker/Ljudböcker keep their descenders and their capital-B instead of being
   flattened into BÖCKER.

   LINE-HEIGHT IS LOAD-BEARING and had to be recomputed with the size. The ul
   clips to one row at calc(2.5rem + 2px), so the tab's own height decides
   whether the cut lands between rows or through one: 0.875rem x 1.715 = 1.5rem
   of line box, plus 2 x --sp-2 of padding, is exactly 2.5rem — and the 2px
   indicator below is the +2px. Recompute both together or the clip cuts a row
   in half. */
.site-nav a {
    display: block;
    /* The containing block for the indicator below. */
    position: relative;
    transition: background-color var(--dur-1) var(--ease-out),
                color var(--dur-1) var(--ease-out);
    /* GENEROUS, AND THE NUMBER IS NOT FREE (asked for, 2026-09-01: "clean
       typography with generous horizontal padding"). --sp-4 either side is what
       the folder tabs already spent and is the most the row can spend: the seven
       labels plus 14 x --sp-4 come to ~700px, which is the term the one-row
       breakpoint below is derived from. --sp-5 would be ~112px more and would
       push that breakpoint past 1366px, which is the width the request names. */
    padding: var(--sp-2) var(--sp-4);
    color: var(--c-text-muted);
    text-decoration: none;
    font-family: var(--font-headline);
    font-weight: 600;
    font-size: var(--fs-sm);
    line-height: 1.715;
    /* A SPACER, AND ONLY A SPACER since the indicator became a pseudo-element
       (see .site-nav a::after). It reserves 2px on every tab so they are all the
       same height whatever is current — the invariant carried over unchanged from
       the folder tabs, and the one the one-row clip depends on: the mark is never
       a reflow, so the row height stays one number. The bar is drawn ON this 2px
       rather than instead of it, which is what keeps that true while the mark
       itself animates. */
    border-bottom: 2px solid transparent;
    /* Only the top corners, because the bottom edge is the indicator: rounding it
       would round the line off at both ends and turn a rule into a lozenge. */
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: transparent;
    white-space: nowrap;
}

/* --- the tabs are flat, and were folder tabs until 2026-09-01 ------------- */
/* WHAT CHANGED AND WHAT IT COSTS (asked for: "remove individual rectangular
   border boxes around links ... distinct hover state and an elegant active
   state").

   Until now each of the seven drew a full 1px --c-border box with a 2px top
   edge, and the current one was filled --c-bg and broke a shelf line under the
   strip — a folder tab, on the argument that a tab says "the page is the sheet
   behind me" where an underline only says "this link is current". That argument
   is not withdrawn and this comment is the record of it: what is given up is the
   sheet, and with it the one shape that tied the current tab to the page below.
   What is bought is seven fewer boxes in the band — the strip was the heaviest
   thing in a chrome whose first rule is "ton, inte linjer", and seven rectangles
   drawn in the system's quietest grey is the one place the file broke it.

   THE INDICATOR IS --c-warm, WHICH IS WHERE IT ALREADY WAS. The folder tab put
   2px of the brand orange on the current tab's TOP edge and the drawer's current
   row puts it down the leading edge; this moves that same 2px to the bottom. So
   no colour is introduced and no meaning moves — "you are here" was orange
   before this change and is orange after it, in all three of the shapes this
   strip takes.

   NOT A FILLED PILL, though that was the alternative offered and is the shape
   the advanced form's mediegrupp strip took the same week. Two reasons it is
   wrong HERE and right there: this strip sits on the pink band, where a filled
   --c-warm-soft pill is invisible (that token IS --c-pink), and a fill would put
   a second solid warm shape a few inches from the cart's orange counter, which
   is the one thing in the chrome that has to be found first.

   THE SHELF WENT WITH THE BOXES. It was an inset shadow on the ul, there so the
   current tab could break it; with no tab reaching down to break anything it
   would be a rule under the strip and nothing else — and this design marks the
   band's end with the pink stopping, not with a line. */

/* Hover lifts the tab off the band rather than colouring it: the ink is already
   near-black on the current one and moving the resting ink instead would make
   hover and current the same statement. Translucent white, so it works on the
   pink band and on the white one the strip stands on when it is not in a header
   (the no-JS drawer case, where the list simply stands open). */
.site-nav a:hover {
    color: var(--c-text);
    background: rgba(255, 255, 255, 0.55);
}

/* THE INDICATOR IS A BAR THAT GROWS, NOT A BORDER THAT FLICKS ON (asked for,
   2026-09-01). It was `border-bottom-color`, which is a colour and therefore has
   only two states and no distance to cover — a fade is the most it can do, and a
   fade is what a tab strip does NOT want, since the thing that reads as movement
   between two tabs is one of them getting wider.

   WHY A PSEUDO-ELEMENT AND NOT THE BORDER. The border stays exactly where it was
   and is now a SPACER and nothing else: it reserves the 2px that keeps every tab
   the same height, which is the invariant the one-row clip depends on
   (calc(2.5rem + 2px), see .site-nav ul) and the reason becoming current can never
   be a reflow. A transform on a pseudo-element costs no layout at all — it is
   composited — so the growing bar cannot disturb that arithmetic even in
   principle.

   INSET FROM THE PADDING BOX. The bar is --sp-3 in from each end, so it sits under
   the WORD rather than under the tab's whole padded box. That is the difference
   between an indicator and a bottom border, and it is what lets the padding stay
   generous (the request that flattened these tabs) without the mark growing with
   it.

   scaleX FROM THE CENTRE, which is the one choice here with a real alternative:
   growing from the leading edge reads as "filling in", growing from the centre
   reads as "arriving". A nav strip is a set of peers and none of them is the
   origin of the others, so centre. transform-origin is stated rather than left to
   the 50% default, because the default is only right by coincidence.

   IT IS NOT DECORATION, so it must survive the reduced-motion rule: with
   transitions zeroed the bar still appears at scaleX(1), instantly. Which tab you
   are on is never carried by the animation. */
.site-nav a::after {
    content: "";
    position: absolute;
    left: var(--sp-3);
    right: var(--sp-3);
    /* The border strip itself: the padding box ends here and the reserved 2px is
       below it, so the bar lands exactly on the space the border was holding. */
    bottom: -2px;
    height: 2px;
    border-radius: 1px;
    background: var(--c-warm);
    transform: scaleX(0);
    transform-origin: center;
    transition: transform var(--dur-2) var(--ease-out),
                opacity var(--dur-2) var(--ease-out);
}

/* Hover and keyboard focus get the same bar at less than full strength — enough
   to say "this is where you would land", not so much that it can be mistaken for
   the tab you are on. Focus takes it too, so a keyboard walks the strip with the
   same feedback a pointer gets; the focus ring is still what says which tab has
   focus, and this is only the tab's own answer to being pointed at. */
.site-nav a:hover::after,
.site-nav a:focus-visible::after {
    transform: scaleX(1);
    opacity: 0.4;
}

/* The current tab. Two signals, neither of them the only one: the indicator and
   the ink at full strength. aria-current says it a third way, which is the one
   that survives a caller who cannot see either.

   LAST, AND THAT IS LOAD-BEARING. It ties with the hover rule above on
   specificity (0,2,1 both), so it wins only by coming after it — which is what
   makes hovering the tab you are already on keep the full-strength bar instead of
   dimming it to 0.4.

   THE WEIGHT IS NOT ONE OF THE SIGNALS (reported 2026-09-01: "it jumps when you
   click through the nav"). It was 700 here against 600 on the other six —
   carried over unchanged from the folder tabs, so older than the flattening — and
   a heavier face is a WIDER one: the current tab grew by a few pixels and every
   tab after it slid along, so the strip re-laid itself on every navigation. Small,
   constant, and in the one control a customer clicks through fastest. */
.site-nav a.is-current::after {
    transform: scaleX(1);
    opacity: 1;
    /* THE BAR SLIDES BETWEEN PAGES because this name is the same in both of them
       and only one tab can be current in either -- see the view-transition block
       near the top of this file. It is on the `.is-current` rule and not on the
       base `::after` deliberately: naming all seven would be a duplicate name and
       the browser would decline to transition any of them. */
    view-transition-name: site-nav-indicator;
}

.site-nav a.is-current {
    color: var(--c-text);
}

/* Focus is the keyboard's version of hover and must not be the hover rule: the
   ring is what says WHICH tab a key press will follow, and a background lift
   alone is too quiet to be that on a coloured band. Inset, because a tab sits
   flush on the band's bottom edge and an outward ring would be clipped by the
   ul's one-row overflow. */
.site-nav a:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: -2px;
}

/* THE BTJ HÄFTET ENTRY IS DRAWN AS A TAB — NO RULE OF ITS OWN (#3169, asked for
   2026-08-24: "let's use same ui as spel, musik etc").

   It began as a filled --c-accent-soft chip, on the argument the rules above
   used to make: a tab was a piece of the page reaching up into the band, the
   current one broke the shelf line in the page's own grey, and Digitalt Häfte
   replaces the whole chrome — so this link can never be `is-current`, and a tab
   that can never be current is arguably not a tab. That reading was rejected in
   favour of one strip that looks like one strip, and this comment is the record
   of the trade rather than an argument against it: the seven items are one
   control, and the cost is that the seventh promises a lit state it can never
   take. Flattening the tabs did not change that cost — it is the indicator that
   is now promised rather than the sheet.

   `.site-haftet` therefore carries NO styling. The class stays because the markup
   and the fixtures need to tell the entry from the six media groups — `MediaTabs`
   is the list of groups and the häfte is not one — but nothing keys a declaration
   off it, and nothing should: the moment it has a rule, the strip has two shapes
   again. */

/* NO INDENT ANY MORE (#3140). The tabs used to be pushed right by
   gutter + logo + the header's flex gap so that they lined up with the search
   field's left edge, one band above them. The search is now in the SAME band,
   directly under the tabs, and both take the plain page gutter — so the two line
   up by sharing a container instead of by a calculation that had to be kept in
   step with the header row's layout. */

/* ==========================================================================
   The narrow header: one row, and a drawer (#3149)

   THE ONE-ROW CLIP WAS A DROPPED MEDIA GROUP. `.site-nav ul` shows only the tabs
   that fit on one line and clips the rest (overflow: hidden + max-height), which is
   the right call at desktop width. On a phone it has no room at all: measured at
   360px, Filmer, Musik and Spel were simply not on the page, and nothing said so.

   Widening the clip cannot fix that, and this is the part worth keeping. What fits
   is a function of the LABELS — six of these words need ~570px of tab, so there is
   no phone width where the strip fits and no number to tune. So below 64rem the
   strip stops being a strip: it becomes a list of rows in a drawer, where the count
   of groups costs height instead of visibility.

   64rem SINCE 2026-09-01, AND IT WAS 60rem, AND 48rem BEFORE THAT (asked for:
   collapse below 1024px). Each move was made for the same reason and the reasons
   are worth keeping in one place. 48 → 60 because between them a tablet kept the
   second-line arrangement — a pinned header 112px tall on a portrait iPad,
   carrying a strip that fits there only because the row above it gave up its
   width. 60 → 64 because the second-line arrangement is gone entirely: the caller
   block was consolidated into one trigger, the row fits on one line from 1024px
   up (the arithmetic is on the .site-account-ready rule near the top of this
   file), and the drawer now starts exactly where that stops being true. So the
   number is still not picked — it is where the one-row sum runs out.

   TWO HEADER LAYOUTS NOW, WHERE THERE WERE THREE: the drawer below 64rem and one
   row above it. The middle band — the strip on a line of its own, 60rem to 72rem
   — was deleted with the 310px it existed to absorb.

   THE WHOLE ROW, NOT JUST THE TABS (asked for, 2026-08-20) — AND THEN NOT QUITE
   THE WHOLE ROW (asked for, 2026-09-01). The drawer took the six groups and the
   caller block together, because the block was ~310px and that is most of a 390px
   screen. The block is ~44px now, so it went back out into the row beside the
   mark and the basket, and the drawer's payload is the media groups alone.
   Digitalt Häfte kept its caller block in the drawer until 2026-09-15, when it took
   the same trigger (Chrome/_Account.cshtml), so the rules that drew a caller block
   as drawer rows went with it: no chrome puts one in there any more.

   THE DRAWER IS NOT A CARD. It is a surface the page slides under, so it has no
   radius and no card shadow of its own — only the edge shadow that says it is above
   the page. The tab list inside it keeps `--c-surface` so the no-JS case (where the
   wrapper stays `display: contents` and the list simply stands in the pink band)
   stays legible; inside the drawer that is white on white and reads as no box at all.

   THE TABS STOP BEING TABS DOWN HERE, deliberately. A tab is only a tab if there is
   a sheet behind it (see the folder-tab section above); stacked vertically there is
   no band edge to sit on, so the folder shape is dropped rather than drawn sideways.
   The current one is marked the way a list marks one of its rows — a rule down the
   leading edge, in --c-warm, which is the same colour the desktop tab puts on its
   top edge for the same job.
   ========================================================================== */

/* Not rendered where the drawer is not a drawer: there is nothing to open. A GLYPH
   AND NOTHING ELSE (asked for, 2026-08-20) — no word, no border, no ground. The
   padding is the touch target, not decoration: 24px of icon plus 2 x --sp-3 is
   exactly --control-min, and the negative margin keeps the bars on the page gutter
   line the way .site-cart keeps its glyph there.

   ON THE LEADING EDGE since #3163 (asked for 2026-08-21: "flytta hamburgermenyn från höger
   till vänster"), so that margin is --sp-3 to the LEFT — it is the same rule, on the
   gutter the button now stands against. The button also moved in the MARKUP rather
   than being ordered here: it is first in the row, first in the tab order, and a
   `order: -1` would have left a control that reads last to a screen reader announcing
   itself first to everyone else. */
.site-nav-toggle {
    display: none;
    align-items: center;
    justify-content: center;
    margin-left: calc(-1 * var(--sp-3));
    padding: var(--sp-3);
    border: 0;
    background: none;
    color: var(--c-text);
    cursor: pointer;
    /* ABOVE THE PANEL IT OPENS. The drawer covers the left-hand end of the row, this
       button included — so either the drawer carries a close button of its own or this
       one floats over it, and two controls for one state is how they end up
       disagreeing. That is the invariant the #3163 move had to preserve, and the
       reason the panel changed sides with the button: on opposite ends the glyph would
       have stopped standing on the surface it opens, and the drawer would have needed
       a second control. `position: relative` is only here to make the z-index apply. */
    position: relative;
    z-index: 3;
}

.site-nav-toggle:hover { color: var(--c-accent); }

/* One glyph per state. `hidden` on the button itself would hide both, so the swap
   is keyed off the header's open class and not off the button's own attributes. */
.site-nav-toggle-close { display: none; }
.site-header.site-nav-open .site-nav-toggle-open { display: none; }
.site-header.site-nav-open .site-nav-toggle-close { display: block; }

@media (max-width: 63.99rem) {
    /* EVERY RULE IN HERE IS BOTH CHROMES' (#3189). Until 2026-08-26 every selector
       below carried `:not(.dh-header)`, and the paragraph here said why: DH reuses
       .site-header, .site-header-inner, .site-user and .site-cart wholesale, so an
       unscoped `.site-header-inner .site-user` would have rebuilt DH's caller block as
       a column of drawer rows on a phone — for a chrome that rendered no toggle and had
       no drawer to put them in. That was a scope statement, not a design one, and the
       scope moved: "we want a hamburger same as [the shop] for mobile" on the häfte.

       So the opt-out came off rather than a second copy of these rules going into
       haftet.css. Every rule in here is about the SHAPE of a collapsed header — one row,
       a panel, a scrim, and links drawn as full-width rows — and none of it is about
       which links those are. Two chromes, one drawer; what differs is the payload, which
       is the same trade .dh-servicenav made when the service links became tabs.

       WHAT IS STILL DH'S OWN lives in haftet.css's own narrow block, and it is three
       rules: the häfte's title comes out of the row, the mark is allowed to ellipsis at
       last resort, and the strip's `margin-right: auto` — which puts the caller on the
       trailing edge of a ROW — is undone inside a column. Anything longer than that
       would be the second design this chrome has spent three tasks not becoming. */

    /* The button exists only where script has un-hidden it. */
    .site-nav-toggle:not([hidden]) { display: inline-flex; }

    /* NO FLASH OF AN OPEN DRAWER (#3064, reported paging a häfte on a phone).
       `site-nav-collapsible` is chrome.js's, and chrome.js is the LAST thing in the
       body — so on a phone the document paints before it runs, and for that frame the
       drawer is still `display: contents` and its whole payload is drawn INLINE in the
       header: both navs, the caller block, and on a häfte the rail haftet.js has not
       moved yet. What that looks like is a hamburger opening and shutting itself on
       every navigation, which on a paged listing is once per press.

       `scripting: enabled` asks the same question chrome.js answers — is there script
       — at PARSE time, so the payload can be hidden BEFORE the first paint instead of
       collapsed after it. The class stays the truth about the drawer: this rule only
       covers the gap before it lands, and stops applying the moment it does.

       Both fallbacks are the old behaviour and neither is a regression: with script
       off the query never matches, so the stack stands open under no button exactly as
       the paragraph above promises; a browser that does not know the feature keeps
       today's flash. */
    @media (scripting: enabled) {
        .site-header:not(.site-nav-collapsible) .site-drawer { display: none; }
    }

    /* --- the row, and the panel it hides ---------------------------------- */

    /* ONE ROW — but only where the drawer is real. `site-nav-collapsible` is put on
       the header by chrome.js in the same breath as un-hiding the toggle, so the two
       facts cannot disagree: no script means no button AND no drawer AND no one-row
       rule. What the no-JS header does instead is at the end of this block. */
    .site-header.site-nav-collapsible .site-header-inner {
        flex-wrap: nowrap;
        /* Centred, and the row gets its bottom padding back: the reason it had none
           is that the tabs had to reach the band's bottom edge to be tabs, and down
           here there are no tabs in the row. */
        align-items: center;
        padding-bottom: var(--sp-2);
        gap: var(--sp-2);
    }

    .site-header.site-nav-collapsible .site-header-inner > .site-logo,
    .site-header.site-nav-collapsible .site-header-inner > .site-cart,
    .site-header.site-nav-collapsible .site-header-inner > .site-cart-wrap,
    .site-header.site-nav-collapsible .site-header-inner > .site-account {
        margin-bottom: 0;
    }

    /* THE ACCOUNT IS IN THE ROW DOWN HERE TOO (asked for, 2026-09-01: keep the
       mark, the account and the basket in the top bar). It is a 44px trigger with
       no label at this width, so the row is the glyph, the mark, the account and
       the basket — about 210px of content at 320px, which is what makes a one-row
       phone header possible with three controls in it rather than two.

       It may not shrink and it may not wrap, for the reason the basket beside it
       was given the same pair in #3189: a flex item that wraps inside itself grows
       the line, and this row's whole point is that it is 64px. The mark is what
       gives — it takes the free space and has none of its own to lose. */
    .site-header.site-nav-collapsible .site-header-inner > .site-account {
        flex: 0 0 auto;
        white-space: nowrap;
    }

    /* AND THE BASKET IS ONE LINE (#3189). `.site-cart-total` is a price, and a price
       that wraps — "12 345" over "kr" — takes the 64px row to 74px and stretches the
       count pill into a lozenge beside it. Measured with a full basket: the DH header
       at 390px, and the shop's own at 320px, so this is the row's rule rather than
       either chrome's. The row is `flex-wrap: nowrap` already; what it was missing is
       that a flex item may still wrap INSIDE itself and grow the line that way.

       Refusing to wrap only works because the row has something else that gives — the
       shop's mark is a 32px glyph with the rest of the line spare, and DH's mark is
       elastic by the rule that pairs with this one in haftet.css. So the row narrows
       where a row is supposed to narrow, instead of growing a second line nobody asked
       for. Both selectors, because which of the two is the row's own child is the one
       thing that differs between the chromes. */
    .site-header.site-nav-collapsible .site-header-inner > .site-cart,
    .site-header.site-nav-collapsible .site-header-inner > .site-cart-wrap {
        flex: 0 0 auto;
        white-space: nowrap;
    }

    /* A THUMB'S TARGET, like the glyph and the account beside it (#3011, measured
       2026-09-16). The basket is a 22px icon in a padding box, which drew a 38x30
       target on a phone — the one control in the row under --control-min, and the
       one a shopper reaches for most. The row is 64px, so the height costs nothing,
       and nothing about the drawing moves: the box has no ground until hovered. */
    .site-header.site-nav-collapsible .site-cart {
        min-height: var(--control-min);
        min-width: var(--control-min);
        justify-content: center;
    }

    /* The signed-out account slot is a text link, 21px of line — the signed-in trigger it
       stands in for is 44px, so the same slot was a thumb's target for one caller and not
       the other. Same measurement, same fix. */
    .site-header.site-nav-collapsible .site-login {
        display: inline-flex;
        align-items: center;
        min-height: var(--control-min);
    }

    /* The mark takes the free space so the cart sits at the far end. It follows the
       glyph now rather than leading the row (#3163), so what is pushed apart is
       the same pair — everything that opens something on the left, the basket on the
       right — and the rule itself did not have to change. */
    .site-header.site-nav-collapsible .site-header-inner > .site-logo {
        margin-right: auto;
    }

    /* THE DRAWER. Full height, in from the LEFT since #3163, over the page — not
       pushing it down, which is what it did in the first cut of #3149 and what "show
       above content" replaced. Fixed rather than absolute so it is anchored to the
       viewport and not to a header that is only 64px tall; sticky does not create a
       containing block for fixed, so being inside the pinned header is fine.

       THE SIDE IS NOT A LOOK, IT IS WHICH BUTTON OPENED IT. The request was to move
       the hamburger from right to left, and the three declarations that answer it are
       here rather than on the button: the edge it is pinned to, the direction it is
       translated out of view, and which way its one shadow falls. A panel that came in
       from the right under a button on the left would leave the glyph floating over
       the page instead of over the surface it summoned. */
    .site-header.site-nav-collapsible .site-drawer {
        display: flex;
        flex-direction: column;
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        z-index: 2;
        /* Wide enough for the longest label with room to spare, and narrow enough
           that the dimmed page is still visibly there behind it — a panel that takes
           four fifths of the screen has stopped being a drawer and become a page. */
        width: min(17rem, 78vw);
        /* Clears the brand row, so the glyph floating above the panel floats above
           empty surface rather than over the first row of it. 4rem is that row's
           measured height (64px: --sp-2 above, the 2rem mark, --sp-2 below) — it is
           a constant because the row's contents are now fixed at this width, the
           caller's block having moved into the panel.

           A BORDER, NOT PADDING, BECAUSE THE PANEL SCROLLS. Padding on a scroll
           container is inside the scroll box: it scrolls away with the first screenful,
           and from then on the rows run up under the glyph — which on a long payload
           (the häfte's whole rail) is most of the panel's life, with the close glyph
           sitting on top of a category label. A border is outside the scroll box, so
           the strip stays put and the content is clipped by it. Same 4rem, same
           surface, and nothing else moves: the background paints under the border, the
           edge shadow is cast off the border box, and the scrollbar now starts below
           the glyph instead of running past it. */
        border-top: 4rem solid var(--c-surface);
        overflow-y: auto;
        background: var(--c-surface);
        /* The one shadow: an edge, not a card. It falls off the panel's OPEN edge, so
           it changed sign with the panel's side — a shadow cast off the screen edge is
           no shadow at all. */
        box-shadow: 8px 0 24px rgba(45, 52, 53, 0.18);
        transform: translateX(-100%);
        /* Not merely off-screen: a closed drawer must be out of the tab order and
           out of the accessibility tree, and `visibility` is what does both. It is
           switched at the END of the slide out and at the START of the slide in, so
           neither transition is skipped. */
        visibility: hidden;
        transition: transform var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3);
    }

    .site-header.site-nav-collapsible.site-nav-open .site-drawer {
        transform: none;
        visibility: visible;
        transition: transform var(--dur-3) var(--ease-out), visibility 0s;
    }

    /* Inside the drawer the nav is no longer a flex item competing for a row's width.
       `order: 0` is belt-and-braces since the deleted 60rem rule was what used to set
       `order: 3` on it — but the order the drawer wants is worth stating rather than
       inheriting from whatever the row happens to say: a menu answers "where can I
       go" first and "who am I" second, and that survived the rule that broke it. */
    .site-header.site-nav-collapsible .site-drawer .site-nav {
        flex: 0 0 auto;
        order: 0;
        margin-top: 0;
    }

    /* The scrim, as a pseudo-element because it is not content — nothing in the DOM
       has to exist for it. It dims the page AND swallows the click that dismisses the
       drawer: chrome.js closes on any click outside the panel, and a click here lands
       on .site-header, which is outside it.

       BLURRED, NOT JUST DIMMED (asked for, 2026-08-20). Blur is what actually stops
       the page being READ behind the panel: a tint alone leaves every line legible,
       just greyer, so the eye keeps working on it. It also lets the dim come down —
       0.28 with the blur against 0.42 without — so the page stays visibly present
       rather than being switched off, which is the difference between a drawer and a
       new screen. The -webkit- pair is for Safari, which still needs it; a browser
       with neither degrades to the tint, which is the design this replaced. */
    .site-header.site-nav-collapsible::after {
        content: "";
        position: fixed;
        inset: 0;
        z-index: 1;
        background: rgba(45, 52, 53, 0.28);
        -webkit-backdrop-filter: blur(4px);
        backdrop-filter: blur(4px);
        opacity: 0;
        visibility: hidden;
        transition: opacity var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3);
    }

    .site-header.site-nav-collapsible.site-nav-open::after {
        opacity: 1;
        visibility: visible;
        transition: opacity var(--dur-3) var(--ease-out), visibility 0s;
    }

    /* Nothing scrolls behind an open drawer. */
    body.has-open-drawer { overflow: hidden; }

    /* --- rows, not tabs: how the contents are drawn either way -------------- */

    .site-header .site-header-inner .site-nav ul {
        /* NO CLIP. This is the rule the whole section exists for — every group is on
           the page, and the list is as tall as the six of them need. */
        max-height: none;
        flex-direction: column;
        gap: 0;
        padding: 0;
        /* Kept white so the no-JS case, where this list stands open in the pink band,
           is still legible. Inside the drawer it is white on white and reads as no
           box at all — which is the point: a drawer is a surface, not a card. */
        background: var(--c-surface);
        box-shadow: none;
    }

    .site-header .site-header-inner .site-nav li { display: block; }

    /* A ROW, NOT A TAB: full width, one hairline between rows, and a leading edge
       reserved in every one so marking the current group is a colour change and never
       a reflow — the same reason the desktop tab always draws its 2px top.
       --control-min because this is the one navigation on the page a thumb has to
       hit; the padding alone would leave it under 44px.

       AND A ROW, NOT A KNAPP — which is why these keep `border-radius: 0` and their
       surface ground against a manual that asks every button to be rounded and
       burgundy. Some of them WERE <button> elements (the drawer's logout posted, until
       the caller block left the drawer), so a reviewer reading the element name
       rather than the control found a violation here; the verdict is written down so
       it is found as a decision instead. What
       the manual describes is a control you press to do a thing. This is a list of
       destinations in a slide-out panel, drawn as full-width rows separated by
       hairlines, and rounding them would put a 6px corner on every row of a
       vertical list — the drawer is a surface, not a tray of cards (#3193). If the
       customer reads it the other way this is the one place to change. */
    .site-header .site-header-inner .site-nav a {
        display: flex;
        align-items: center;
        width: 100%;
        min-height: var(--control-min);
        margin: 0;
        padding: var(--sp-2) var(--sp-4);
        border: 0;
        border-bottom: 1px solid var(--c-border);
        border-left: 3px solid transparent;
        border-radius: 0;
        background: var(--c-surface);
        font-family: var(--font-label);
        font-size: var(--fs-sm);
        font-weight: 600;
        color: var(--c-text);
        text-align: left;
        /* The tab's nowrap protected the one-row clip; a row can take two lines. */
        white-space: normal;
    }

    /* A row separates itself from the NEXT row, so the last one in each list draws no
       line — otherwise the group ends on a rule with nothing under it. */
    .site-header .site-header-inner .site-nav li:last-child a {
        border-bottom: 0;
    }

    /* The rows fade their ground too — --dur-1, the ink duration, because that is
       all that changes: a drawer row has no shape to move. */
    .site-header .site-header-inner .site-nav a {
        transition: background-color var(--dur-1) var(--ease-out),
                    border-left-color var(--dur-1) var(--ease-out),
                    color var(--dur-1) var(--ease-out);
    }

    .site-header .site-header-inner .site-nav a:hover {
        background: var(--c-surface-2);
        color: var(--c-accent);
    }

    /* The current group. Tinted ground so it reads as filled at a glance, the warm
       rule on the leading edge because that is where a list keeps its marker, and the
       accent + weight because a 3px line is not enough on its own for anyone who
       cannot see it — aria-current says it a fourth way. */
    .site-header .site-header-inner .site-nav a.is-current {
        background: var(--c-surface-2);
        border-left-color: var(--c-warm);
        color: var(--c-accent);
        font-weight: 700;
    }

    /* NO GROWING BAR DOWN HERE. `.site-nav a::after` is the strip's indicator and
       it follows the anchor into the drawer, where it would draw a second mark on
       a row that already has two — the tinted ground and the warm leading edge —
       and would sit on the hairline that separates one row from the next. A list
       marks its current row on the leading edge; that is the whole reason these
       stop being tabs at this width. */
    .site-header .site-header-inner .site-nav a::after { display: none; }

    /* Nothing here for BTJ Häftet (#3169): it is an ordinary row, exactly as it is an
       ordinary tab above the breakpoint. It is the last <li>, so the `li:last-child`
       rule above has already taken its bottom hairline. */

    /* --- and with no script at all ----------------------------------------- */

    /* There is no button and no drawer, so the nav simply stands open on a full-width
       line under the brand row. Written as its own rule rather than inherited from the
       drawer's, because in a COLUMN flex container `flex-basis` means height — `0 0
       100%` there would make the nav as tall as the panel. */
    .site-header:not(.site-nav-collapsible) .site-header-inner .site-nav {
        flex: 0 0 100%;
        margin: var(--sp-2) 0 0;
    }

    .site-header:not(.site-nav-collapsible) .site-header-inner .site-nav { order: 3; }
}

/* THE FOOTER IS BTJ.SE'S FOOTER, AND IT IS DARK (asked for, 2026-08-19). It had taken the header's
   --c-pink band (asked for, 2026-08-18) so that the page opened and closed on the same colour. That
   reads well when the footer is one line; it stops being the right answer once the footer is a
   five-column directory, because a pale warm band that size no longer closes the page — it becomes
   a second region of it, the same weight as the content above and competing with it.

   www.btj.se paints #282828 and sets the whole block in muted grey with white headings, which is
   what makes it read as the page ENDING rather than continuing. Measured off the live site
   2026-08-19: band rgb(40,40,40), links rgb(133,133,133) at 16px/400, headings white 14px/700
   uppercase, 60px of vertical padding.

   The tokens are scoped to the footer rather than promoted into the surface ladder. That ladder is
   light-on-warm-neutral and its steps mean "nested" (--c-surface-2 is a well inside a card); a dark
   value in it would claim a step that has no other members and no rule for when to use it. The
   footer is the only dark region in the shop, so it owns its own three colours. */
.site-footer {
    --c-footer-bg: #282828;
    /* NOT btj.se's #858585, which is 4.0:1 on that band and fails AA for 16px text. This is the
       nearest value that clears 4.5 (it measures 5.2:1) and it is indistinguishable at reading
       size — matching the impression is the goal, inheriting the contrast failure is not. */
    --c-footer-link: #9a9a9a;
    --c-footer-strong: #ffffff;
    /* The hairline. White at low alpha rather than --c-border, which is a light-surface value and
       would be a bright line across a dark band. A token because #3165 gave the narrow footer a rule
       between every folded row, and a separator drawn at two strengths is two separators. */
    --c-footer-rule: rgb(255 255 255 / 0.12);

    margin-top: var(--sp-8);
    background: var(--c-footer-bg);
    font-family: var(--font-headline);
    color: var(--c-footer-link);
}

/* TWO BLOCKS AT THE TOP LEVEL, NOT FIVE. The four link columns are one landmark and therefore one
   element; they divide the left-hand cell among themselves. 4fr/1fr rather than a flat five-track
   grid so that the four inner columns and the company block all come out at roughly one fifth,
   which is btj.se's own proportion.

   Nesting rather than one auto-fit grid over all five: a `span 4` child would pin the outer grid to
   at least four tracks at every width, which is exactly what the narrow layout must not do. */
.site-footer-inner {
    max-width: var(--page-max);
    margin: 0 auto;
    padding: var(--sp-8) var(--sp-4);
    display: grid;
    grid-template-columns: minmax(0, 4fr) minmax(13rem, 1fr);
    gap: var(--sp-5) var(--sp-6);
    align-items: start;
}

/* auto-fit, so the four columns become two and then one as the cell narrows, instead of each being
   squeezed to a word's width. 11rem is the widest single entry in them ("Personuppgiftspolicy",
   "För leverantörer/förlag"): below it every column wraps on every second row. */
.site-footer-links {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--sp-5) var(--sp-4);
    align-items: start;
}

/* btj.se's heading treatment exactly — white, bold, uppercase, at the size the links are NOT.
   Restated here because these are real h2 elements (they label the groups a screen reader reads),
   and the point is that they must not inherit the page's h2 scale. No letter-spacing: the site sets
   none, and Jost at 700 uppercase is already open enough that adding some reads as a different
   brand's small-caps label. */
.site-footer-heading {
    margin: 0 0 var(--sp-4);
    font-family: var(--font-headline);
    font-size: var(--fs-sm);
    font-weight: 700;
    text-transform: uppercase;
    color: var(--c-footer-strong);
}

/* A <summary> that is NOT a disclosure — this is the wide footer, where the groups stand open and
   the element is only the heading's wrapper (#3165). Three things have to be undone for that:

   - the marker. list-style: none is what standards-track browsers read; the -webkit- pseudo is
     what Safari reads, and it has to be a separate rule because an unknown selector invalidates
     the whole block it is in.
   - the CLICK. pointer-events: none, and it is the default rather than the exception: nothing may
     fold a group until chrome.js says the footer folds, so a browser without JS cannot end up with
     a row that shuts and no way to reopen it at a width where the chevron is not even drawn.
   - the CURSOR, which would otherwise promise the click that has just been taken away.

   Keyboard activation is the fourth, and it is not here: a summary is focusable by being a summary,
   and only a tabindex can change that. chrome.js owns it, per width. */
.site-footer-summary {
    display: block;
    list-style: none;
    pointer-events: none;
    cursor: default;
}

.site-footer-summary::-webkit-details-marker { display: none; }

/* Drawn only in the narrow footer. An affordance is a promise, and up here there is nothing to
   open: every group is already open and the summary does not take a click. */
.site-footer-chevron { display: none; }

.site-footer-links ul,
.site-footer-contact {
    list-style: none;
    margin: 0;
    padding: 0;
}

.site-footer-links li,
.site-footer-contact li { margin-bottom: var(--sp-2); }

/* Full size and NORMAL weight — the opposite of what the old one-line footer did, where two links
   among furniture had to hold their own and were set at --fs-sm/600. In a directory the weight is
   what the headings use to stand above the rows, so the rows have to give it up. */
.site-footer a {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    color: var(--c-footer-link);
    font-size: var(--fs-base);
    font-weight: 400;
    line-height: 1.6;
    text-decoration: none;
}

/* Hover goes to white rather than to --c-accent-dark: the teal is the shop's action colour and it
   is nearly invisible on #282828 — an affordance that disappears when you point at it. Brightening
   is the move the dark band affords. */
.site-footer a:hover { color: var(--c-footer-strong); text-decoration: underline; }

/* "(öppnas i nytt fönster)" on every outgoing link — in the accessible name, out of the picture.
   A visible marker on each would be the loudest thing on the page (twenty-two of the footer's
   twenty-seven links open a tab), and a reader who can see the link also sees the tab appear.

   Not .site-footer-newtab any more: the DH menu and the campaign links say the same sentence for
   the same reason, and one sentence rendered by three stylesheets is three chances to drift.

   position: absolute is doing two jobs. It hides the span, and it takes it out of the anchor's
   flex flow — .site-footer a is an inline-flex row with a gap, so an in-flow hidden child would
   still be a flex item and still claim its gap, spacing every link's words away from nothing. */
.newtab-hint {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* The glyph must not shrink when the words beside it wrap onto two lines, which is what an
   inline-flex child does by default. align-self keeps it on the first line rather than centred
   against a two-line block. */
.footer-icon { flex: none; align-self: flex-start; margin-top: 0.28em; }

/* An <address> is italic by UA default, which would set BTJ's own contact details apart from every
   other block in the footer for no reason a reader could name. */
.site-footer-org { font-style: normal; }

.site-footer-orgnr { font-size: var(--fs-base); line-height: 1.6; }

/* btj.se's address shape: the label is its own line, bold and white; the value sits under it in the
   link ink. Told apart by their labels, not by reading the sentence. */
.site-footer-address {
    margin: var(--sp-4) 0 0;
    font-size: var(--fs-base);
    line-height: 1.6;
}

.site-footer-address strong {
    display: block;
    font-weight: 700;
    color: var(--c-footer-strong);
}

/* The copyright is its own row under everything, separated by the same hairline that divides the
   folded rows in the narrow footer. */
.site-footer-legal {
    max-width: var(--page-max);
    margin: 0 auto;
    padding: var(--sp-4) var(--sp-4) var(--sp-6);
    border-top: 1px solid var(--c-footer-rule);
    font-size: var(--fs-xs);
}

/* ==========================================================================
   The narrow footer (#3165) — bokus.se's shape: the four groups become one column of folded rows
   and the identity block centres under them.

   WHY IT HAD TO CHANGE. Only the OUTER grid folded here before, so the four link groups kept their
   auto-fit and landed as a cramped 2×2 of six- and seven-link lists — about 1400px of footer under
   every page on a 390px phone, nearly all of it links nobody scrolled that far to find. Folding
   them makes the count cost a TAP rather than a screen, which is the trade #3149 made for the
   media-group tabs from the other direction: there a list made the count cost height instead of
   visibility, here a fold makes height cost a tap.

   WHAT IS TAKEN FROM BOKUS AND WHAT IS NOT. The shape — full-width rows, heading left, chevron
   right, a hairline between them, the identity centred below. NOT the palette: theirs is
   dark-on-white and this band is btj.se's own, and a footer that changed colour scheme at a
   breakpoint would be two footers rather than one seen twice. NOT the social row either: theirs is
   an icon-only centred strip, ours is a group of seven whose last two entries (BTJ-Bloggen, Policy
   för kommentarer) are not accounts and have no mark to be reduced to.

   THE BREAKPOINT is the one this file already folded the footer at, and deliberately not #3149's
   60rem: that is a different control laying out different content, and the rule those slices
   settled is that such a number is read off what is being laid out. 55rem is where four 11rem
   columns stop fitting; the fold starts exactly there, so the narrow footer has one state and not
   two.

   The company block still leaves the row first — it is the block with a hard minimum, since an
   address line does not wrap gracefully.
   ========================================================================== */
@media (max-width: 55rem) {
    .site-footer-inner {
        grid-template-columns: 1fr;
        gap: var(--sp-5);
        padding: var(--sp-6) var(--sp-4) var(--sp-4);
    }

    /* One column, and the gap goes to ZERO: the rows are told apart by the hairline between them,
       which is the separator doing the job the gap was doing. Keeping both would leave each line
       floating in a band of empty instead of sitting under the row it closes. */
    .site-footer-links {
        grid-template-columns: 1fr;
        gap: 0;
    }

    .site-footer-col { border-bottom: 1px solid var(--c-footer-rule); }
    .site-footer-col:first-of-type { border-top: 1px solid var(--c-footer-rule); }

    /* The row. Full width so the whole line is the target rather than just the words: with the
       chevron pushed to the far edge, the space between them is the largest part of the row and
       would otherwise be dead. --sp-4 top and bottom puts it at ~52px, clear of the 44px minimum. */
    .site-footer-summary {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--sp-3);
        padding: var(--sp-4) 0;
    }

    /* The heading is a row now rather than a label standing above a list, so it gives up its bottom
       margin and takes the links' size. It keeps btj.se's case and weight: Bokus set theirs large
       and sentence-case, but the casing is the brand's, and a footer whose headings shout on a
       desktop and murmur on a phone is one control saying two things. */
    .site-footer-summary .site-footer-heading {
        margin: 0;
        font-size: var(--fs-base);
    }

    /* Breathing room under an open group, so its last link does not sit on the next row's rule. */
    .site-footer-col > ul { padding-bottom: var(--sp-4); }

    /* The identity block is the end of the footer, not a fifth column — so it centres, and the
       contact rows centre their icon+value pair with it (they are inline-flex, so this is
       justify-content and not text-align). */
    .site-footer-org { text-align: center; }
    .site-footer-org .site-footer-heading { margin-bottom: var(--sp-3); }
    .site-footer-contact a { justify-content: center; }
    .site-footer-address { margin-top: var(--sp-3); }
    .site-footer-legal { text-align: center; }

    /* ---- The fold itself ----------------------------------------------------------------
       Scoped to the class chrome.js adds, so without JS none of it applies: the groups stay open,
       the chevron stays undrawn, and the narrow footer is the stack of labelled lists above. That
       is still better than the 2×2 it replaces, and it is honest — no mark promising a control that
       is not there. */
    .site-footer-collapsible .site-footer-summary {
        pointer-events: auto;
        cursor: pointer;
    }

    .site-footer-collapsible .site-footer-chevron {
        display: block;
        flex: none;
        transition: transform 150ms ease;   /* killed by the global reduced-motion rule above */
    }

    /* Down at a shut row, up at an open one. The selector is the element's OWN [open] state, so the
       mark and the fold cannot drift apart — nothing in the script touches the chevron. */
    .site-footer-collapsible .site-footer-col[open] > .site-footer-summary .site-footer-chevron {
        transform: rotate(180deg);
    }

    /* A summary is focusable, and down here it is a real control, so it needs a visible ring. The
       page's own focus colour is the teal, which is nearly invisible on #282828 — the same reason
       the link hover brightens instead of tinting. */
    .site-footer-collapsible .site-footer-summary:focus-visible {
        outline: 2px solid var(--c-footer-strong);
        outline-offset: -2px;
    }
}

/* ==========================================================================
   Title card — facts line + buy rows (shared by /sok and /artikel)

   Both pages render the same article shape, so the facts line and the buy rows are one
   vocabulary here rather than two divergent ones in btj.css and sok.css. The markup lives
   in Features/Shared/_TitleFacts, _ConfigIdent and _ConfigPrice; only the action cell and
   the track list differ per host.
   ========================================================================== */

.title-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2) var(--sp-3);
    margin: 0 0 var(--sp-2);
}

.title-fact-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.title-fact-list li:not(:last-child)::after {
    content: "·";
    margin-left: var(--sp-2);
    color: var(--c-border);
}

.fact-unreleased { color: var(--c-danger); font-weight: 600; }

/* The media type is the one categorical value in the line — a chip, not another muted fact.
   Sized down from the default chip: it labels a card, it does not head a section. */
.title-media { align-self: center; }

/* --- Buy rows -------------------------------------------------------------
   ONE grid per list, not one per row: `display: contents` drops each .buy-row box so every
   row's cells join the same grid, which is what makes column 2 size to the widest price
   across all rows and the prices line up. The cost is that a MISSING cell would pull the
   next row's ident up into it — so the pages always emit all three wrappers, every cell is
   pinned with an explicit grid-column, and empty ones are hidden rather than omitted.
   Row separators are gaps, not rules (docs/DesignSystem.md: ton, inte linjer). */

.buy-rows {
    display: grid;
    align-items: baseline;
    column-gap: var(--sp-4);
    row-gap: var(--sp-3);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
}

.buy-row { display: contents; }

.buy-ident { grid-column: 1; }
.buy-price {
    grid-column: 2;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.buy-action { grid-column: 3; white-space: nowrap; }
.buy-action:empty { display: none; }

.buy-name { font-weight: 600; }

.buy-delivery {
    margin-left: var(--sp-2);
    font-weight: 400;
    color: var(--c-text-muted);
    /* "(3-6 dgr)" is one token to read; without this it breaks after the hyphen
       in the hit card's narrow ident column. */
    white-space: nowrap;
}

.price-list { display: block; color: var(--c-text-muted); white-space: nowrap; }
.price-cust { display: block; font-weight: 700; white-space: nowrap; }

/* "(plus ev. utrustning)" is a note about the amount, not part of it — so it gets a line of
   its own under the price and is the one thing in this cell allowed to wrap. Inline it made
   the cell's max-content the whole sentence (~270px at fs-sm), and the hit card's price track
   is a max-content one, which cannot shrink: the button in the next track was pushed clear
   outside the tonal well and past the card's own edge — 77px at 1200px and wider, on the
   single-configuration rows PriceDisplay.ShouldAppendEquipmentSuffix fires on (cd, dvd, spel).
   Below 56rem the button has its own line, so only the wide layout broke. The article page's
   panel is page-wide and merely overhung its column by ~4px at 320px. Same reasoning as
   .sok-card-buy .buy-price:has(.price-prompt): a sentence in a price cell must not set the
   price track's width. */
.price-equip { display: block; white-space: normal; }
.price-prompt { color: var(--c-text-muted); font-style: italic; }
/* The equipment mark, and the note it opens. Both are _PriceInfo's; the reasoning for a drawn
   icon in a button rather than the character ⓘ in a span is written there.

   The note is a plain sibling revealed by :hover and :focus — no script, so it cannot be half
   initialised, and a tap counts because tapping a button focuses it. Same popup vocabulary as
   .site-search-tips: surface, one hairline, the system shadow. */
.price-info-wrap { position: relative; display: inline-block; }

.price-info {
    appearance: none;
    border: 0;
    padding: 0;
    background: none;
    /* The icon is the button's whole content, so the text strut would only push it off the
       baseline of the sentence it follows. */
    line-height: 0;
    vertical-align: -0.15em;
    color: var(--c-link);
    cursor: help;
}

/* 1em, so the mark tracks the text it annotates at every zoom and every font size. */
.price-info svg { width: 1em; height: 1em; }

.price-info:hover { color: var(--c-accent-dark); }

/* A FINGER'S WIDTH WITHOUT A LAYOUT'S (#3011, measured 2026-09-16). On a coarse pointer the global
   rule already makes the button 44px tall, but its width is the 1em glyph — 13 to 15px, on every buy
   row of /sok, /artikel and the DH card, under even WCAG's 24px floor. Widening the box would move
   the price sentence it sits in, whose line box the rules above and in sok.css were tuned to leave
   alone, so the TARGET grows and the box does not: an absolutely placed pseudo-element is hit-tested
   as part of the button and takes no room in the line. */
@media (pointer: coarse) {
    .price-info { position: relative; }
    .price-info::after {
        content: "";
        position: absolute;
        inset: 0 -0.875rem;
    }
}

.price-info-note {
    position: absolute;
    right: 0;
    bottom: calc(100% + var(--sp-1));
    z-index: 30;
    display: none;
    /* max-content capped, rather than a width: the sentence sets its own box up to a
       comfortable measure and the note never inherits the price cell's narrow column. */
    width: max-content;
    max-width: 16rem;
    padding: var(--sp-2) var(--sp-3);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    /* The cell it hangs off may be italic, bold, nowrap or right-aligned; the note is none of
       those — it is a sentence, and it says the same thing wherever the mark is rendered. */
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-style: normal;
    font-weight: 400;
    line-height: 1.4;
    text-align: left;
    white-space: normal;
    color: var(--c-text);
}

.price-info:hover + .price-info-note,
.price-info:focus + .price-info-note { display: block; }

/* Track lists per host — the article panel is page-wide, the hit card's buy column is not. */
.buy-panel .buy-rows {
    grid-template-columns: minmax(0, 1fr) max-content max-content;
    padding: var(--sp-4);
}

/* Narrow: price under the name, quantity under that. */
@media (max-width: 30rem) {
    .buy-panel .buy-rows { grid-template-columns: minmax(0, 1fr) max-content; }
    .buy-panel .buy-action { grid-column: 1 / -1; }
    .buy-panel .buy-price { text-align: left; }

    /* A SENTENCE IN THE PRICE CELL GOES UNDER THE NAME (#3011, measured 2026-09-16). The price
       track is max-content, so "Lägg till i kundvagn för att se ditt pris ⓘ" claimed its whole
       width and left the name column 45px at 360px: "Utrustad" broke mid-word as "Utrusta / d".
       An amount is short and stays beside its name; the prompt takes the full row under it. */
    .buy-panel .buy-row:has(.price-prompt) .buy-price { grid-column: 1 / -1; }
}

/* ==========================================================================
   Article detail

   DRAWN THE WAY DIGITALT HÄFTE DRAWS, and for the reason DesignSystem.md gives
   rather than for consistency's own sake: a boundary is a change of ground, not
   a line. The three blocks on this page (hero, buy panel, tabs) had arrived as
   bordered-and-shadowed boxes, which is the one treatment .dh-card deliberately
   does not use — a card there is --c-surface on --c-bg and nothing else, a well
   inside it is --c-surface-2, and a notice is that same tint plus a 3px stripe.
   Only the values below change; every grid, gap and padding is untouched, so
   nothing on the page moves.
   ========================================================================== */

.article-detail {
    display: grid;
    gap: var(--sp-5);
}

/* --- Hero: cover + headline + info list (mobile: stacked) ----------------- */

/* A .dh-card, with this page's own padding: surface on background, radius, and
   no line or shadow to hold it. */
.article-hero {
    display: grid;
    gap: var(--sp-5);
    background: var(--c-surface);
    border-radius: var(--radius);
    padding: var(--sp-5);
}

.article-cover {
    margin: 0;
    justify-self: center;
}

/* The middle column. min-width: 0 because a grid item's minimum is its min-content, so
   anything wide in here — a scroll container, a table with a min-width, a long unbroken
   string — does not overflow and scroll: it widens the whole column past its 1fr track
   and pushes the hero out of shape. Measured while this column still held such a table,
   before that work was retired: 536px became 640px. Kept as insurance for the next wide
   thing, which is why it is stated generally rather than against one pane. */
.article-headline { min-width: 0; }

/* The tinted ground rather than a shadow — .dh-card-cover img's reasoning holds
   here too: what a cover needs is something to sit on when it is transparent or
   short, not to be lifted off the card. */
.article-cover img {
    max-height: 320px;
    width: auto;
    background: var(--c-surface-2);
    border-radius: var(--radius-sm);
}

.article-title {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-2xl);
    line-height: 1.2;
}

.article-author {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-lg);
    color: var(--c-text-muted);
}

/* The facts line is .title-facts, above — shared with the hit card. */

.article-shelfsign {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.article-badges { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* Same shape and voice as .chip, and never a control: a badge states a fact about the article
   (unreleased, in the häfte, has a lektörsomdöme, already in a cart). Hence no hover state —
   nothing here is clickable. */
.badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    background: var(--c-badge-bg);
    font-family: var(--font-label);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--c-text-muted);
    text-decoration: none;
}

.badge-unreleased { background: var(--c-warm-soft); color: var(--c-warm-soft-text); }
/* Hit cards only, but defined here with the rest of the vocabulary. The server renders one on
   every card and hides it until article.InCart; sok.js reveals that one on a successful
   quick-add rather than building a second (#3212). */
.badge-incart { background: var(--c-ok-soft); color: var(--c-ok-text); }
.badge-incart.hidden { display: none; }

/* --- Right info list ------------------------------------------------------ */

/* The bibliographic column is a WELL inside the card, which is .dh-card-side:
   --c-surface-2 on the card's --c-surface. It was separated by a rule instead —
   a hairline above it on mobile, one down its left edge on wide screens — and a
   rule is the treatment this system does not use. The tint says the same thing
   at both widths, so the two breakpoint rules below stop having to swap which
   edge carries it. */
.article-info {
    background: var(--c-surface-2);
    border-radius: var(--radius);
    padding: var(--sp-4);
    font-size: var(--fs-sm);
    position: relative;
}

/* Pinned to the well's padded corner, not to its tinted edge.

   A GLYPH RATHER THAN A WORD, so it is sized as a target instead of as text:
   the icon is 18px and the padding around it is what a finger and a focus ring
   need. Negative margins of the same size cancel that padding, so the printer
   still sits on the well's own padded corner rather than 6px in from it — the
   link is bigger than it was without having moved.

   No underline and no separate hover colour rule: it takes --c-link from the
   `a` default and currentColor carries it into the stroke. */
.article-print {
    position: absolute;
    top: calc(var(--sp-4) - var(--sp-1));
    right: calc(var(--sp-4) - var(--sp-1));
    display: inline-flex;
    padding: var(--sp-1);
    border-radius: var(--radius-sm);
    line-height: 0;
    text-decoration: none;
}

.article-info-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--sp-2);
}

.article-info-list .info-title { font-weight: 600; }
.article-info-list .red-mark { color: var(--c-danger); }
.article-info-list .special-rights { cursor: help; text-decoration: underline dotted; }

/* --- Copy-to-clipboard, beside an identifier ------------------------------ */

/* SHARED, not article-detail's own. This started as Digitalt Häfte's port of
   legacy's CopyValueToClipboard (haftet.css) and the shop's bibliographic list
   grew the same control in #3126 — one control, one set of rules, the same
   reasoning that put its markup in one renderer (Model/CopyButton.cs). It lives
   in this section because the shop's is here; the DH card keeps only where it
   sits on the card (.dh-facts dd .copy-btn).

   A button, not a span with a click handler, so it is reachable from the
   keyboard; the glyph is legacy's own feather "copy". Muted at rest: the value
   is the content and this is an affordance beside it. */
.copy-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    margin-left: var(--sp-1);
    padding: 0 var(--sp-1);
    border: 0;
    background: none;
    color: var(--c-text-muted);
    font-family: var(--font-label);
    font-size: var(--fs-xs);
    cursor: pointer;
}

.copy-btn:hover { color: var(--c-accent-dark); }

/* Rendered hidden and revealed by /js/copy-button.js — the [hidden] attribute
   loses to the display above without this. */
.copy-btn[hidden] { display: none; }

/* The wrapper only exists so the announcement can be the button's SIBLING rather
   than its child; it must not change the inline flow the button had. */
.copy-control { display: inline; }

/* Exposed to a screen reader, invisible on screen. The clip pattern, not
   display:none or hidden — either of those takes it out of the accessibility
   tree too, which is the one thing it is for. */
.copy-status {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* NO white-space rule here, though the shape invites one. The glyph and the word
   cannot come apart — .copy-btn is a single-line flex container — and a rule on
   the wrapper could not stop the one break that does happen: the value is the
   <li>'s own text and sits OUTSIDE the wrapper, so it is the <li> that would have
   to be nowrap, which in the narrow rail would push a long identifier out of it.
   A button that wraps under its value is the better failure. */

/* --- Buy area ------------------------------------------------------------- */

.article-buy:empty { display: none; }

/* THE BANNER VOCABULARY, as haftet.css states it: a tinted ground plus one
   stripe on the inline start, no box around it. Both of these are the *info*
   role — a state the caller cannot act on (the title cannot be ordered here) —
   so the stripe is --c-border and not the accent, which in that vocabulary is
   reserved for something that happened. */
.buy-not-available,
.buy-request-offer {
    margin: 0;
    padding: var(--sp-4) var(--sp-5);
    background: var(--c-surface-2);
    border-inline-start: 3px solid var(--c-border);
    border-radius: var(--radius);
    color: var(--c-text-muted);
}

/* The rows themselves are .buy-rows / .buy-row, defined once with the title card above. */

/* A card, therefore no line and no shadow. NO `overflow` clip: the panel holds
   controls that answer OUTSIDE it — the equipment note hangs above its first row
   and the cart picker's menu below its last — and a clip is not something a
   z-index can climb out of, so the clipped note read as a box of text sliced off
   by the card's edge. What actually needed clipping is the ONE tinted band at the
   foot, which carries the corner itself (.buy-bar below): clip the thing that
   overflows, not every descendant that might. */
.buy-panel {
    background: var(--c-surface);
    border-radius: var(--radius);
}

/* --- THE ANTAL BOX (#3198) ------------------------------------------------
   One control for the six places that ask "how many?" — the result card, the
   article panel, the cart row (the shop's and the häfte's), the DH card, its
   add-all bar and the beställningslista. Markup: Features/Shared/_Antal.cshtml.
   A caller's own sheet may place .antal-field; nothing else about the control
   is theirs, which is the whole point of it being here.

   Width is 4rem: "99" plus the padding at every font size the shop uses. The
   three it replaces were 3em, 3.25rem and 4.5rem, and the smallest of them
   clipped a two-digit value on the DH card. */
.antal-field {
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

.antal {
    width: 4rem;
    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` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    text-align: center;
    /* The browser's own spinners go: they are hover-only, differently drawn in
       every browser, and absent altogether on iOS and Android — so they cannot
       be the stepper, and two steppers on one box is one too many. Ours is
       .antal-step below. */
    appearance: textfield;
    -moz-appearance: textfield;
}

.antal::-webkit-outer-spin-button,
.antal::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* The "1" an empty box means, on the surfaces that may default (AntalBox
   .OwnButton). Centred in a two-character field, the browser's default grey is
   the only thing separating a hint from a typed value — muted at full opacity,
   because this one is meant to be read. */
.antal::placeholder { color: var(--c-text-muted); opacity: 1; }

.antal-unit { margin-left: var(--sp-1); color: var(--c-text-muted); font-size: var(--fs-sm); }

/* --- the stepper, inserted by antal.js -----------------------------------
   Two arrangements, chosen by POINTER and not by width: a phone in landscape
   is still a thumb and a touch laptop is not a phone. On a mouse the keys sit
   on the box's trailing edge — a spinner's shape, except always visible and
   the same in every browser. */
.antal-stepper {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.antal-stepper .antal { padding-right: 1.4rem; }

.antal-step {
    position: absolute;
    right: 1px;
    width: 1.15rem;
    height: calc(50% - 1px);
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--c-text-muted);
    font-family: var(--font-label);
    font-size: 0.55rem;
    line-height: 1;
    cursor: pointer;
}

.antal-step-up { top: 1px; }
.antal-step-down { bottom: 1px; }
.antal-step:hover { color: var(--c-accent); }
.antal-step:disabled { opacity: 0.25; cursor: default; }
.antal-glyph-coarse { display: none; }

/* On a finger: − box +, each at the 44px target the rest of the site already
   takes on a coarse pointer. The box keeps the same 4rem it has on a mouse
   minus the room the edge keys needed. */
@media (pointer: coarse) {
    .antal-stepper { gap: var(--sp-1); }

    .antal-stepper .antal {
        width: 3.5rem;
        min-height: var(--control-min);
        padding-right: var(--sp-2);
        /* The iOS no-zoom floor: under 16px, focusing the field zooms the page
           and leaves the caller scrolled somewhere else. */
        font-size: var(--fs-control-min);
    }

    .antal-step {
        position: static;
        width: var(--control-min);
        height: var(--control-min);
        border: 1px solid var(--c-border);
        border-radius: var(--radius-sm);
        background: var(--c-surface);
        color: var(--c-text);
        font-size: 1.15rem;
    }

    /* − before the box, + after it, whatever the source order is. */
    .antal-step-down { order: -1; }
    .antal-glyph-fine { display: none; }
    .antal-glyph-coarse { display: block; }
}

.buy-dailydel {
    display: block;
    margin-top: var(--sp-1);
    font-weight: 400;
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.buy-eginfo {
    border-top: 1px solid var(--c-border);
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--fs-sm);
}

.buy-eginfo summary { cursor: pointer; font-weight: 600; color: var(--c-text-muted); }

/* ON A PHONE THE PADDING MOVES ONTO THE SUMMARY (#3011, measured 2026-09-16). A text summary is
   one line tall, so "Egen info" was a 21px target inside 45px of block that did nothing when
   pressed. Moving the block's vertical padding into the summary makes the whole strip the target
   and draws the same geometry: 12 + line + 12 closed, and the fields 12px under the label open. */
@media (pointer: coarse) {
    .buy-eginfo { padding-block: 0; }
    .buy-eginfo summary { padding-block: var(--sp-3); }
    .buy-eginfo[open] { padding-bottom: var(--sp-3); }
    .buy-eginfo-fields { padding-top: 0; }
}

.buy-eginfo-fields {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: var(--sp-3);
    padding-top: var(--sp-3);
}

.eginfo-field { display: grid; gap: var(--sp-1); }
.eginfo-field input, .eginfo-field select {
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
}

/* The action band at the foot of the panel. It already changes ground, so the
   rule above it was the boundary drawn twice — and the ground it changed to was
   --c-bg, the page's own colour, which reads as a hole in the card rather than
   as a band on it. --c-surface-2 is the next step DOWN the ladder from the
   card, which is the well tint the rest of the page now uses.

   .buy-eginfo above keeps its hairline: that block does not change ground, and
   a rule inside a panel is in-vocabulary where a rule around one is not
   (haftet.css .dh-buy draws exactly that line). */
.buy-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface-2);
    /* Always the panel's last child, and the only child that changes ground, so it
       is the only one whose square corners would show past the card's radius. */
    border-end-start-radius: var(--radius);
    border-end-end-radius: var(--radius);
}

/* "Ligger i kundvagn" and its detail (`#3113`) — its own row above .buy-bar, on the
   same tonal step and carrying no rule of its own, so the two read as one block. It
   is a <details> when there are lines to disclose and a <p> when there are not, and
   both wear this class: the row is one thing whichever shape it took. The
   font-family here is a no-op since the shop went to one face (2026-09) and is kept
   as a statement of WHAT THIS BLOCK IS — what the caller's own carts hold is UI
   furniture, not content — so that it is already right if prose is ever given a
   face of its own again. */
.buy-incart-row {
    margin: 0;
    padding: var(--sp-3) var(--sp-4) 0;
    background: var(--c-surface-2);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
}

.buy-incart-row.hidden { display: none; }

/* Deliberately no `display` on the summary: any value other than list-item drops the
   platform's disclosure triangle, and .buy-eginfo — the sibling disclosure a few
   pixels above — has one. The two spans are inline, so they wrap without needing
   flex, and the triangle turns as the row opens. */
.buy-incart-row > summary { cursor: pointer; }

.buy-incart { color: var(--c-accent-dark); font-weight: 600; }
.buy-incart.hidden { display: none; }

/* Not a <button>: a summary is already a button to the platform, keyboard and
   announced state included. It is drawn as a link because it reveals text in place
   rather than performing anything. */
.buy-incart-more { margin-left: var(--sp-3); color: var(--c-link); text-decoration: underline; }
.buy-incart-row:not([open]) .buy-incart-more-hide { display: none; }
.buy-incart-row[open] .buy-incart-more-show { display: none; }

/* After an add the lines are gone — they were rendered before the write (see
   article.js) — so the disclosure has nothing left to disclose and is drawn as the
   plain marker it has become. */
.buy-incart-row.is-stale > summary { cursor: default; list-style: none; }
.buy-incart-row.is-stale > summary::-webkit-details-marker { display: none; }
.buy-incart-row.is-stale .buy-incart-more { display: none; }

/* One <li> per (cart, who, when), so the lines are a list and not a paragraph. */
.buy-incart-detail {
    margin: 0;
    padding: var(--sp-2) 0 0;
    list-style: none;
    color: var(--c-text-muted);
}

.buy-incart-detail li {
    /* Wraps as a whole rather than mid-clause on a narrow viewport: each span is a
       flex item, so a line breaks between "av Anna" and the date, not inside them. */
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--sp-2);
}

.buy-incart-detail li + li { margin-top: var(--sp-1); }

.buy-incart-count { color: var(--c-text); }

/* The cart is the answer a caller with several of them came for, so it is the one
   emphasised part of the line. Quoted by the stylesheet rather than in the markup:
   an alias is data, and the quotes are presentation. */
.buy-incart-cart { font-weight: 600; color: var(--c-text); }
.buy-incart-cart::before { content: "\201d"; }
.buy-incart-cart::after { content: "\201d"; }

.buy-biblpost { font-size: var(--fs-sm); }

/* The bibliographic-post source selector on the article page's buy bar — the one surface in the
   shop that draws the control (Features/Shared/_BiblPost.cshtml).

   AND IT IS THE HOUSE DROPDOWN, NOT THE PLATFORM'S (asked for 2026-09-01). It had the box —
   padding, border, radius, label face — and not the arrow, so beside the antal box this codebase
   has drawn since #3198 it still read as something the browser put there. haftet.css's own comment
   already claimed this control as one of the custom set ("which is .sok-adv-field select's and
   .buy-bar select's"); that was true of the box and not of the chevron, and this is what makes the
   sentence true.

   NOT A NEW TREATMENT: --chevron, the same token .sok-adv-field, .dh-band-picker and the häfte's
   buy panel paint, with .dh-band-picker's own right-padding arithmetic — the gutter twice over plus
   the glyph's 0.75rem. A fourth chevron would be a fourth chevron free to drift.

   THE SIZE IS THE ONE THING THAT DOES NOT JOIN THEM. The DH blocks set --fs-xs so the control
   matches its own fine print; this one keeps --fs-control-min, because it is 16px and below that
   iOS zooms the page on focus — and unlike the häfte's panel there is no coarse-pointer rule below
   raising it back. Stating a smaller size here would silently zoom the page on every phone that
   opens it. */
.buy-bar select {
    appearance: none;
    -webkit-appearance: none;
    padding: var(--sp-1) calc(var(--sp-2) * 2 + 0.75rem) var(--sp-1) var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background-color: var(--c-surface);
    background-image: var(--chevron);
    background-repeat: no-repeat;
    background-position: right var(--sp-2) center;
    background-size: 0.75rem;
    color: var(--c-text);
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    /* 16px, not --fs-sm: below 16px iOS zooms the page on focus. */
    font-size: var(--fs-control-min);
    cursor: pointer;
}

.buy-bar select:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* Cart picker — dropdown + checkbox popover (progressive enhancement of the
   #cartSelector multi-select; see article.js enhanceCartPicker). */
.js-cart-select { display: none; }

.cart-picker { position: relative; }

.cart-picker-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-width: 200px;
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    background: var(--c-surface);
    color: var(--c-text);
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    text-align: left;
    cursor: pointer;
}
.cart-picker-toggle:hover { border-color: var(--c-accent); }
.cart-picker-caret { margin-left: auto; font-size: 0.8em; color: var(--c-text-muted); }

.cart-picker-menu {
    /* Absolute in DOCUMENT space, not in the picker's: article.js moves the menu to
       <body> and positions it from the toggle's page coordinates, so it scrolls with
       the page (no bounce) and no ancestor's stacking or scrolling can trap it. */
    position: absolute;
    z-index: 50;
    min-width: 240px;
    max-width: 320px;
    padding: var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-surface);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}

/* Searching the caller's carts (#3273) — the box cart-search.js builds for BOTH pickers, so it
   lives here rather than in sok.css: the article page's popover and the result card's add menu
   are different controls with one search in them. */
.cart-search {
    width: 100%;
    margin-bottom: var(--sp-2);
    /* Tighter than a form field: this one sits inside a popover, directly under the dropdown
       that opened it, and a box taller than its own trigger reads as the panel's subject
       rather than as a way to narrow the list below it. */
    padding: 0.25rem var(--sp-2);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    /* --fs-sm, matching .cart-picker-toggle above it and .cart-picker-item below it: at a desk
       this box is one control among the panel's own, and anything larger reads as the panel's
       subject rather than as a way to narrow the list under it. The floor comes back on a
       finger — see the (pointer: coarse) block, which MUST stay after this rule: the two tie at
       (0,1,0), so the later one wins. */
    font-size: var(--fs-sm);
}
.cart-search:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 1px;
}

/* The iOS no-zoom floor, on the pointer rather than the width — a phone in landscape is still a
   thumb. The shop waives this floor in one place (the DH buy panel's bibl-post select) and may
   not waive it here: this box is FOCUSED THE MOMENT THE PICKER OPENS, so under 16px opening the
   picker on a phone zooms and leaves the caller looking somewhere else entirely. A floor waived
   on the control with the strongest claim to it is not a floor. */
@media (pointer: coarse) {
    .cart-search {
        min-height: var(--control-min);
        font-size: var(--fs-control-min);
    }
}

/* What the typing did. Hidden while nothing is typed — an unfiltered list states its own
   length by being visible — so it costs no height on an ordinary open. */
.cart-search-status {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
}
.cart-search-status[hidden] { display: none; }

.cart-picker-actions {
    display: flex;
    gap: var(--sp-3);
    padding-bottom: var(--sp-2);
    margin-bottom: var(--sp-2);
    border-bottom: 1px solid var(--c-border);
}
.cart-picker-action {
    padding: 0;
    border: 0;
    background: none;
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    color: var(--c-accent);
    cursor: pointer;
}
.cart-picker-action:hover { text-decoration: underline; }

.cart-picker-list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 220px;
    overflow-y: auto;
}
.cart-picker-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-1);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    cursor: pointer;
}
.cart-picker-item:hover { background: var(--c-bg); }
.cart-picker-item input { margin: 0; }

.buy-button {
    margin-left: auto;
    padding: var(--sp-2) var(--sp-5);
    border: 0;
    border-radius: var(--radius);
    background: var(--c-accent);
    color: #fff;
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    font-weight: 700;
    cursor: pointer;
}

.buy-button:hover:not(:disabled) { background: var(--c-accent-dark); }
.buy-button:disabled { opacity: 0.45; cursor: not-allowed; }

/* Why the add button is dark (#3200), written by article.js. A line of its own — flex-basis
   100% in a wrap container — rather than a member of the row: the bar already holds the
   bibliografisk-post controls, the cart picker and the button, and a sentence squeezed between
   them would be the first thing to be pushed onto its own line anyway, sideways and unlabelled.
   Right-aligned so it sits under the button it is about.

   :empty rather than a hidden attribute, so the element is one thing the markup renders and the
   script only ever writes TEXT into — a hidden attribute would be a second piece of state, free
   to disagree with the sentence. It also keeps the live region in the DOM at all times, which is
   what lets a screen reader announce into it (a region that appears WITH its text may not be
   read at all). Muted, and not an error colour: nothing has gone wrong, something is missing. */
.buy-hint {
    flex-basis: 100%;
    margin: 0;
    text-align: right;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.buy-hint:empty { display: none; }

/* --- Tabs ----------------------------------------------------------------- */

/* The strip lives inside the hero card's middle column, where legacy's DetailBody
   draws it — so the margin is all that separates it from the headline above.
   It keeps its own --c-surface anyway: the tonal step below is stated against a
   surface, and that has to hold wherever the strip is drawn. What lets a wide pane
   scroll rather than stretch the column is .article-headline's min-width: 0, above. */
.article-tabs {
    margin-top: var(--sp-4);
    min-width: 0;
    background: var(--c-surface);
    border-radius: var(--radius);
    overflow: hidden;
    /* Four rows or so of a panel, then the "Läs mer" toggle. Applied by article.js
       (.is-clamped) and only where the content actually overflows it, so a short
       panel is never cut and a JS-less reader gets the whole thing. */
    --tabpanel-clamp: 7rem;
}

/* THE STRIP IS A LIST, NOT A TAB BAR -- every article, every width, both the
   article page and a /sok card (#3153, asked for 2026-08-20).

   It was underlined tabs with `flex-wrap: wrap` and no media query, and the
   measurement is what settled it: five panes need 656px of strip (Utgivnings-
   information alone is 171), /artikel's middle column is at most 536px (the
   hero at --page-max), and a /sok card's is 324px at 390px, 226px at 768px
   where the 20rem buy column arrives, and 562px at 1280px. So the strip is
   regularly wider than the column it is in -- on a phone and on a desktop
   alike -- and the accent underline then says something false: it marks "the
   sheet begins here" while another row or two of tabs sits between it and the
   panel. What fits is a function of the labels and of the column, not of the
   viewport, which is the same thing #3149 found about the mediegrupp tabs.

   The first build of this DID measure the wrap per strip and dressed only the
   strips that wrapped. That is defensible for /artikel, which draws one strip
   and has nothing to be inconsistent with, and wrong for /sok, which draws
   twenty-five down one page: at 768px a two-pane card kept its tabs while the
   three-pane card under it became a list, and the difference encoded nothing
   a reader can use -- which panes an article has is already stated by the
   labels. Deciding per SURFACE rather than per strip does not help either,
   because it leaves the same control drawn two ways in one product. So the
   verdict is one treatment everywhere, which is also the only one that cannot
   be inconsistent with itself, needs no JavaScript, and cannot flip while a
   /sok page lazy-loads its cards.

   The chip's SHAPE, the tab's own TYPE: pill, --c-surface-2, --c-accent-soft
   for the selected one, but sentence case at --fs-sm rather than the .chip
   vocabulary's tracked uppercase -- a chip is a marker and these are controls.
   The strip keeps the card's own --c-surface rather than sitting in a tonal
   well: it was --c-surface-2 once, with the active tab rising out of it, but
   the strip is regularly one tab wide (an article with only Mer info) and then
   the step is not a well with a tab in it, it is a beige bar with a white
   notch bitten out of it. The selected chip marks the selection on its own,
   which is what the underline did in the well too. */
.tablist {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    /* The underline is gone, so the row provides the air before the panel that
       the 3px border used to. It matters in a card, where the panel's own top
       padding is --sp-2 and not the article page's --sp-5. */
    padding-bottom: var(--sp-1);
    background: var(--c-surface);
}

/* THE ADVANCED FORM'S MEDIEGRUPP STRIP TAKES THE SAME TREATMENT (2026-09-01).
   It is the same control -- a wrapping row of mutually exclusive choices over
   the sheet below it -- and it had the underline shape this block was written
   to replace: a 1px rule under the strip plus a 2px accent border under the
   current one. Seven media-group labels in a 60rem column (four in the panel)
   wrap as reliably as a five-pane article strip does, and then the rule that
   says "the sheet begins here" sits with a row of tabs between it and the
   form. Sharing the selector rather than restating it is the point: a second
   copy of this treatment is free to drift from this one, and the ONLY thing
   that differs is the element -- these are <a> navigations to a media group's
   own URL, where /artikel's are <button>s in a JS pane engine. `text-decoration`
   is stated for that reason and for that reason alone; a <button> has none to
   begin with. The strip's own layout stays in the .sok-adv block below, which
   is where a reader of that form looks.

   THE THIRD CALLER IS THE BAND'S TRIGGER (.site-search-adv), and it is the one
   that uses the selected fill for something other than a selection: the panel it
   opens is either open or it is not, `aria-expanded` says which, and a filled pill
   says it at the size of the control instead of at the size of a chevron.

   THE FOURTH CALLER IS /kundservice's SECTION STRIP (.ks-tab, #3340). A different
   control from /artikel's -- legacy MD3TabNavigation, its own engine in
   kundservice.js -- with the same fault this block was written for: five section
   labels do not fit one line on a phone, and the 1px rule under the strip, with the
   current tab cutting a gap in it, sat under the SECOND row while the tab it belonged
   to was in the first. So it takes the shape, not the engine. `.is-active` is its
   spelling of the selected state, set by that script and nowhere else. */
.tab,
.sok-adv-tab,
.site-search-adv,
.ks-tab {
    appearance: none;
    border: 0;
    border-radius: var(--radius-pill);
    background: var(--c-surface-2);
    padding: var(--sp-2);
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--c-text-muted);
    text-decoration: none;
    cursor: pointer;
}

.tab:hover,
.sok-adv-tab:hover,
.site-search-adv:hover,
.ks-tab:hover {
    background: var(--c-surface-3);
    color: var(--c-text);
}

/* `.active` rather than `.tab-active` on the search strip: the markup spells it
   that way and a render fixture asserts `.sok-adv-tab.active`, so the class name
   is a contract. What it draws is the same. */
.tab-active,
.tab-active:hover,
.sok-adv-tab.active,
.sok-adv-tab.active:hover,
.site-search-adv[aria-expanded="true"],
.site-search-adv[aria-expanded="true"]:hover,
.ks-tab.is-active,
.ks-tab.is-active:hover {
    background: var(--c-accent-soft);
    color: var(--c-accent-soft-text);
}

.tabpanel { display: none; padding: var(--sp-5); }
.tabpanel-active { display: block; }

/* One face across every pane. The strip mixes prose (lektörsomdöme, förlagstext,
   innehållsförteckning) with reference data (Mer info's key/value table, Detaljer's
   bibliographic list), and the type rule at the top of this file pulls `table, th, td`
   into --font-label — so while the shop had two faces, which one a pane got was decided
   by whether its content happened to be built as a table, and Mer info sat in Jost
   beside a Detaljer list of the same kind of data in Source Serif 4. The strip is
   furniture, so it takes the label face throughout rather than half of each. THE BUG IS
   UNREACHABLE SINCE 2026-09, when the shop went to one face, and the rule stays because
   what it says about the strip is true independently of how many faces there are.

   .article-info-list is named as well because it is drawn twice: as the Detaljer pane on a
   search card and as the rail beside the panes on /artikel. Leaving the rail out would move
   the disagreement rather than settle it — it is the same list either way. */
.tabpanel-body,
.article-info-list {
    font-family: var(--font-label);
}

/* A single pane draws no strip, so there is no well for the panel to sit inside — it
   is just the column's content, and it lines up with the title above it rather than
   being indented by a frame that is not there. */
.article-tabs-solo .tabpanel { padding: 0; }

/* What the strip would have said, said once. Same voice as a tab label rather than a
   page heading: it names a pane, it does not open a section. The rule above it closes
   off the headline — title, facts, badges — so the pane reads as its own block rather
   than as more of the same column. */
.article-pane-heading {
    margin: 0 0 var(--sp-3);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--c-border);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--c-text-muted);
}

/* The mask, not a border, marks the cut: the last visible row fades out, so a row
   sliced in half reads as "there is more" rather than as a rendering fault. It is
   also why the clamp needn't land on a row boundary — rows differ in height across
   the five panels (prose, an info table, the schedule table). */
.tabpanel-body.is-clamped {
    max-height: var(--tabpanel-clamp);
    overflow-y: hidden;
    -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
}

/* A pane that scrolls is never clamped: a horizontal scrollbar takes 15px out of the
   112px clamp, and it sits exactly where the mask fades to nothing — so the one control
   saying "this table continues sideways" would be the part faded out. Kept although the
   only .table-scroll pane today is dead by data, because the rule is about the mechanism
   rather than that pane, and the next scrolling pane inherits it. Costs nothing: syncClamp
   then measures no vertical overflow, hides the toggle and takes .is-clamped off again. */
.tabpanel-body.table-scroll.is-clamped {
    max-height: none;
    -webkit-mask-image: none;
    mask-image: none;
}

.tabpanel-more {
    appearance: none;
    margin-top: var(--sp-2);
    padding: 0;
    border: 0;
    background: transparent;
    /* `font: inherit` resets font-family as part of the shorthand,
       so the label face is re-stated after it. */
    font: inherit;
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--c-accent);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.tabpanel-more:hover { color: var(--c-accent-dark); }

.review { margin: 0 0 var(--sp-4); }
.review-teaser { color: var(--c-text-muted); }
/* Inside the tab panel, so it is a well on --c-surface, not the page ground. */
.review-no-access {
    padding: var(--sp-4);
    background: var(--c-surface-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
}

.info-table { border-collapse: collapse; }
.info-table td { padding: var(--sp-1) var(--sp-4) var(--sp-1) 0; vertical-align: top; }
.info-table .info-key { font-weight: 600; white-space: nowrap; }
.info-table .info-subkey { padding-left: var(--sp-3); font-weight: 400; }

.table-scroll { overflow-x: auto; }

/* The Utgivningsinformation pane, which is DEAD BY DATA — MD3.ARTICLE_PUBLISHING_SCHEDULE
   has zero rows in prd and in tst, so this table cannot render anywhere. See
   ArticleDetailAnatomy.md §3.2 before spending anything on it.

   Left in its original shape on purpose. `width: 100%` does make the .table-scroll above
   dead weight — a table that can always shrink to its container never overflows, so it
   wraps every cell instead — but fixing that is presentation work for markup nobody can
   see, and the measurements behind the fix came from a table this file invented. */
.schedule-table { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); }
.schedule-table th, .schedule-table td {
    text-align: left;
    padding: var(--sp-2) var(--sp-3);
    border-bottom: 1px solid var(--c-border);
}

/* --- "Är du säker?" (confirm-submit.js) -----------------------------------
   The confirmation in front of a destructive submit — legacy's jQuery UI remove-all dialog, which
   both carts raise from the one shared control. Here rather than in kundvagn.css because the two
   pages that ask it are in different slices and under different chromes (the shop's and DH's), and a
   question about losing data should not be two dialogs that drifted apart.

   Shaped on the shop's other two dialogs (.ms-dialog on Mina sidor, .dh-dialog on a DH card):
   same width, same surface, same backdrop, ghost-then-affirmative actions. What differs is the one
   thing the dialog is about — the affirmative carries --c-danger rather than the accent, because it
   is the button that throws work away. */
.btj-confirm {
    width: min(26rem, calc(100vw - 2rem));
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    padding: var(--sp-4);
    color: var(--c-text);
    background: var(--c-surface);
    box-shadow: var(--shadow);
}

.btj-confirm::backdrop { background: rgba(20, 22, 26, .45); }

.btj-confirm h2 {
    font-size: var(--fs-lg);
    font-family: var(--font-label);
    margin: 0 0 var(--sp-2);
}

.btj-confirm p { margin: 0; }

/* What the write is aimed at, stated under the question (confirm-submit.js's data-confirm-field).
   A fact rather than a second sentence, so it takes the label face and the muted ink the rest of
   the shop gives to furniture — the question is what is being asked, and this is the value the
   answer will act on.

   Written as a descendant so it out-specifies `.btj-confirm p { margin: 0 }` — the same
   (0,1,1) trap the affirmative's rules below record, one element deeper. */
.btj-confirm .btj-confirm-detail {
    margin-top: var(--sp-2);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

/* Where a borrowed control sits while the dialog holds it (confirm-submit.js's
   data-confirm-control). Empty for every dialog that does not use one, so it must contribute
   nothing at all when it has no children — hence :not(:empty) rather than a margin this rule pays
   unconditionally.

   The label inside it stacks above its control and takes the question's own ink rather than the
   muted furniture tone .btj-confirm-detail uses: this is not a fact stated beside the question, it
   is the answer being chosen. */
.btj-confirm-control:not(:empty) { margin-top: var(--sp-3); }

.btj-confirm-control label {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    font-family: var(--font-label);
    font-size: var(--fs-sm);
    color: var(--c-text);
}

/* The control fills the dialog's width — it is the one thing being decided here, where on the row
   it was one item in a line of six. */
.btj-confirm-control select {
    width: 100%;
    padding: 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);
}

.btj-confirm-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
}

.btj-confirm-actions button {
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-border);
    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;
}

/* Both of these are written as descendants of .btj-confirm-actions, and that is load-bearing rather
   than tidy: the shared rule above is (0,1,1) — a class plus an element — so a bare .btj-confirm-ok
   at (0,1,0) LOSES to it and the affirmative renders as one more ghost button. Measured on tst, and
   it is the same specificity trap haftet.css records for .dh-dialog. */
.btj-confirm-actions .btj-confirm-cancel:hover { background: var(--c-bg); }

.btj-confirm-actions .btj-confirm-ok {
    background: var(--c-danger);
    border-color: var(--c-danger);
    color: #fff;
}

/* No colour token one step darker than --c-danger exists, and inventing one for a single hover
   would put a second red in the palette. color-mix over black is the same move .kv-remove:hover
   makes over transparent. */
.btj-confirm-actions .btj-confirm-ok:hover {
    background: color-mix(in srgb, var(--c-danger) 85%, #000);
    border-color: color-mix(in srgb, var(--c-danger) 85%, #000);
}

/* THE RED MEANS WORK IS LOST, and it only keeps meaning that if the dialogs that lose nothing do
   not wear it. Every caller until now was a delete — Ta bort, Töm kundvagn, avbeställ — so danger
   could be the shape's one look. The cart row's Flytta and Kopiera ask the same question about a
   write the caller cannot see the far side of, and neither destroys anything: a move carries the
   row's antal, etiketter and egen info to the other cart rather than dropping them, and a copy only
   adds. So they opt out (data-confirm-tone="neutral"). Same specificity trap as the rule above —
   .btj-confirm-actions has to be in the selector or the (0,1,1) base rule wins.

   THE DIFFERENCE IS WEIGHT, NOT HUE, and after the 2026-09 reskin that is not a refinement but the
   whole mechanism. This opt-out used to take a solid --c-accent, which against --c-danger was teal
   against bright red — two obviously different buttons. With a burgundy accent it would have been
   dark red against bright red: the same shape, the same position, in the same dialog, separated by
   twenty-five points of lightness and nothing else. Flytta and Kopiera sit two clicks from Töm
   kundvagn, so that is a colour change reachable from a destructive action.

   The tinted container is the design system's own answer and it is already written down for DH's
   panel — "Spara kommentar takes the TONED fill: visibly a button, visibly not the buy button
   beside it". Solid slab = this destroys work; tinted = an ordinary yes. It survives protanopia,
   deuteranopia and a greyscale printer, none of which a hue pair does.

   Moving --c-danger instead was considered and rejected: it buys ~10° of hue, breaks the
   industry-wide reading of red, and does nothing for any of the three cases above. */
.btj-confirm.is-neutral .btj-confirm-actions .btj-confirm-ok {
    background: var(--c-accent-soft);
    border-color: var(--c-accent);
    color: var(--c-accent-soft-text);
}

.btj-confirm.is-neutral .btj-confirm-actions .btj-confirm-ok:hover {
    background: color-mix(in srgb, var(--c-accent) 22%, var(--c-accent-soft));
    border-color: var(--c-accent-dark);
    color: var(--c-accent-soft-text);
}

.btj-confirm-actions button:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* The write is running (confirm-submit.js). Both buttons stop being choices — the question has been
   answered — and the affirmative says what is happening. The spinner is the decoration and the WORD
   is the information: the global prefers-reduced-motion rule above stops every animation on the
   page, so a busy state that was only a spinner would say nothing to a caller who asked for less
   motion. Kept at full opacity rather than the browser's greyed-out default, because this is not a
   button that has become unavailable — it is the one thing on screen reporting progress. */
.btj-confirm-actions button:disabled { cursor: default; }

.btj-confirm.is-busy .btj-confirm-cancel { opacity: .5; }

.btj-confirm.is-busy .btj-confirm-ok {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
}

.btj-confirm.is-busy .btj-confirm-ok::before {
    content: "";
    width: .85em;
    height: .85em;
    border: 2px solid rgba(255, 255, 255, .45);
    border-top-color: #fff;
    border-radius: var(--radius-pill);
    animation: btj-confirm-spin 700ms linear infinite;
}

@keyframes btj-confirm-spin {
    to { transform: rotate(360deg); }
}

/* --- Wider screens -------------------------------------------------------- */

@media (min-width: 720px) {
    .article-hero {
        grid-template-columns: minmax(140px, 200px) 1fr;
        align-items: start;
    }
    .article-cover { justify-self: start; }
}

@media (min-width: 1024px) {
    .article-hero {
        grid-template-columns: minmax(140px, 200px) 1fr 18rem;
    }
    /* No .article-info override here any more: the well carries its own tint at
       every width, so there is no rule to move from the top edge to the left. */
}

/* ==========================================================================
   Paper (#3025 M5) — moved to btj-print.css (#3060)

   The paper rules are no longer an @media block here. _Layout links them
   `media="print"` and _PrintLayout links the same file `media="all"`, so the
   chrome-less print VIEW shows the sheet on screen instead of showing the page
   and printing something else — which is what legacy's print popup does. That
   needs one file asked of two media, and an @media block can only ever be
   asked of one.

   Load order is unchanged and matters: btj.css, btj-print.css, the slice's
   sheet, the slice's print sheet.
   ========================================================================== */

/* ==========================================================================
   The cookie consent banner (#3329, docs/CookieConsent.md) — the shop's own,
   on every page. Features/Shared/_CookieConsent.cshtml, revealed by
   cookie-consent.js.

   A REGION AT THE FOOT OF THE VIEWPORT, NOT A MODAL: a consent banner may not
   be a cookie wall, so the page behind it stays readable and usable. Fixed, on
   the surface ladder's white with the house shadow, capped at the page width.

   REJECT AND ACCEPT CARRY THE SAME WEIGHT, which is the rule a consent banner
   is judged by: both are the solid accent, side by side. "Anpassa" / "Spara
   val" take the ghost weight because they lead to a choice rather than being
   one. Every button keeps --control-min so a thumb can hit it.
   ========================================================================== */
.cc-banner {
    position: fixed;
    inset-inline: var(--sp-4);
    bottom: var(--sp-4);
    z-index: 950;
    display: grid;
    gap: var(--sp-3);
    max-width: 44rem;
    margin-inline: auto;
    padding: var(--sp-5);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    background: var(--c-surface);
    color: var(--c-text);
    box-shadow: var(--shadow);
}

.cc-banner[hidden],
.cc-banner-choices[hidden] {
    display: none;
}

.cc-banner-title {
    margin: 0;
    font-size: var(--fs-lg);
    font-family: var(--font-label);
}

.cc-banner-title:focus { outline: none; }
.cc-banner-title:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

.cc-banner-text { margin: 0; }

.cc-banner-choices {
    display: grid;
    gap: var(--sp-2);
    margin: 0;
    padding: var(--sp-3);
    border: 0;
    border-radius: var(--radius);
    background: var(--c-surface-2);
}

.cc-banner-choices legend {
    padding: 0;
    margin-bottom: var(--sp-2);
    font-family: var(--font-label);
    font-weight: 600;
}

/* Baseline, not centre: a description that wraps onto a second line keeps the
   box beside the category name rather than floating between the two lines. */
.cc-banner-choices label {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
    min-height: var(--control-min);
    padding-block: var(--sp-1);
}

.cc-banner-choice-desc { color: var(--c-text-muted); }

.cc-banner-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--sp-2);
}

.cc-banner-actions button {
    min-height: var(--control-min);
    padding: var(--sp-2) var(--sp-4);
    border: 1px solid var(--c-accent);
    border-radius: var(--radius);
    font: inherit;
    font-family: var(--font-label);
    font-weight: 600;
    cursor: pointer;
}

/* Descendant selectors on purpose: `.cc-banner-actions button` is (0,1,1), so a
   bare class would lose to it — the trap .btj-confirm-actions records. */
.cc-banner-actions .cc-banner-choice {
    background: var(--c-accent);
    color: #fff;
}

.cc-banner-actions .cc-banner-choice:hover {
    background: var(--c-accent-dark);
    border-color: var(--c-accent-dark);
}

.cc-banner-actions .cc-banner-secondary {
    background: var(--c-surface);
    color: var(--c-accent);
}

.cc-banner-actions .cc-banner-secondary:hover { background: var(--c-accent-soft); }

.cc-banner-actions button:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* On a phone every answer is a full-width row in markup order, so the screen
   order is the reading order: Tillåt alla, Anpassa, Neka alla. Every row keeps
   the same size, which is what keeps "Neka alla" as easy as accepting. */
@media (max-width: 40rem) {
    .cc-banner { padding: var(--sp-4); }
    .cc-banner-actions { flex-direction: column; }
    .cc-banner-actions button { width: 100%; }
}
