/*
 * SeaTwirl IR wireframes — shared stylesheet.
 *
 * Mid-fidelity: the SeaTwirl palette, the two-voice type system (Mona Sans plus
 * Red Hat Mono Light) and the design system's geometry are applied for real,
 * over photography. Dimensionally honest: every dimension, type size and colour
 * still reaches the page through a CSS variable carrying Ollie's exact token
 * name, so the production theme inherits the decisions, not the declarations.
 *
 * Layout conventions mirror the Claude Design mockups (resources/mockups/):
 * full-width section bands laying content on a shared satsyta grid (flexible
 * margins around a narrow main measure plus a widget column; main + widget
 * merge into the wide measure, everything into the full bleed), sections
 * separated by background shifts rather than rules, mono/sans role split (mono for
 * eyebrows, metadata, numerals and source notes; sans for headings, body, tags
 * and buttons per docs/typografi.md), light-weight display headings, rounded
 * panels and pill chips, hairline row lists, bottom-anchored KPI panels, and a
 * dark subscribe panel. The signature motifs — Purple dawn CTAs and numerals,
 * the marker highlight, the notched feature image — come from the design system
 * (resources/design-system/). Placeholders remain only where no photo exists.
 */

/* ---------- Vendored fonts (self-hosted so the wireframes work offline) ----------
   Mona Sans is the editorial (sans) voice; Red Hat Mono Light is the numeral and
   meta voice. Both are pinned to a named upstream release and never fetched from
   Google Fonts — whose copies are a generation behind, arrive from a third-party
   host, and leave no file in hand to pin. docs/adr/0008 carries the decision, the
   measurements and why the two voices are treated differently.

   Mona Sans is release 2.0.27, shipped exactly as GitHub publishes it: axes
   untouched, no subset. It uses both of them for real — weights 300–700 across
   the design, and the optical-size axis that sets 12 px metadata and a 64 px
   heading in different cuts. Pinning opsz would take the file from 135 kB to 78,
   and would switch off the very thing this release has that the theme's bundled
   2.000 does not; it also moves every line break, since the axis changes advance
   widths by up to 6,3 % end to end. Vertical metrics are opsz-independent.

   Red Hat Mono is release 5.0.0, instanced at Light 300 and cut to latin +
   latin-ext: 33,3 → 11,4 kB. It may be cut precisely because the design uses one
   weight and only one — see docs/typografi.md — so its 300–700 axis was ballast,
   and it is the axis rather than the character set that holds the weight (29,2 →
   13,4 kB pinned, then 11,4 subset). The swap from the previous 1.030 build is
   metrically neutral: identical vertical metrics, zero differing advance widths
   over all 231 shared codepoints, nothing dropped, coverage up from 380 to 404.

   Every page preloads Mona Sans, and the reason it is worth a link in 26 heads is
   that this declaration is the only thing that mentions the file: without a
   preload the font is discovered when the stylesheet has been fetched AND parsed,
   which measured at 900–2000 ms into the load on a throttled 4G.

   The preload is a trade, not a free win, and the measurement says so. It costs
   roughly 300 ms of LCP on a slow connection, because the font's 135 kB shares
   the pipe with this render-blocking stylesheet. It buys the page being set in
   its own typography 60–670 ms sooner, and — wherever the font lands before the
   first paint, which on cable it does — it removes the reflow the swap causes:
   the site header measures its own wrap from rendered label widths, so a font
   swap moves the whole page. The reports page's CLS went 0,0230 → 0,0000 that
   way. The trade was taken knowing the LCP cost, because the baseline's LCP is
   credited to a heading painted in the FALLBACK face; it is a flattering number
   measuring the wrong thing, and every page stays inside 2 500 ms either way.

   The preload does not always win that race, and a measurement that says it does
   is a measurement served without compression. Gzip the stylesheet the way any
   real host does and it arrives first, the page paints in the fallback face, and
   the swap lands afterwards — home on a phone then measured CLS 0,1444 on two
   runs in three. Which is what the metric-matched fallback below is for. */

/* ---------- Metric-matched fallback for the header ----------
   The site header decides whether its row has wrapped by measuring the rendered
   width of its own labels, and the answer sets --wf-header-h, which every page's
   top padding follows. So one binary flip, in the chrome, moves the whole
   document — and a fallback face of a different width can answer it differently
   from Mona Sans. That flip was the entire defect: home at 390 px was bimodal,
   0,0000 or 0,1444 depending on which face was measured, while every other page
   and width stayed under 0,004.

   These faces make the fallback a faithful proxy for that measurement.
   size-adjust comes from rendering, not from the font's own xAvgCharWidth — which
   claims Mona Sans is WIDER than Arial and is wrong: measured, a Swedish
   paragraph at weight 350 is 91,57 % of Arial's width and 90,90 % of Helvetica
   Neue's. The vertical overrides are Mona Sans's own metrics (ascent 1090,
   descent 320 per 1000 upem, no line gap) divided by that ratio, which is the
   only one of three arrangements that measured well.

   They apply to the header and to nothing else, and that boundary is the whole
   result. Put on the page's own font stack they fix the header and break the
   body: matching AVERAGE width still breaks individual lines somewhere else, and
   the press list went 0,0000 → 0,1274 while every 1440 px page went ~0,003 →
   ~0,070 in footer and band reflow. For running text system-ui is already the
   better match — San Francisco sets a Swedish paragraph at 98,7 % of Mona Sans
   and breaks it in the same places, which a nominally exact 90,90 % Helvetica
   does not. Scoped to the chrome, all six combinations land at 0,0005–0,0050 and
   repeat to the fourth decimal over five runs. */
@font-face {
    font-family: "Mona Sans Fallback";
    src: local("Helvetica Neue"), local("HelveticaNeue");
    size-adjust: 90.90%;
    ascent-override: 119.91%;
    descent-override: 35.20%;
    line-gap-override: 0%;
}

@font-face {
    font-family: "Mona Sans Fallback";
    src: local("Arial"), local("Liberation Sans"), local("Roboto");
    size-adjust: 91.57%;
    ascent-override: 119.04%;
    descent-override: 34.95%;
    line-gap-override: 0%;
}

@font-face {
    font-family: "Mona Sans";
    font-style: normal;
    font-weight: 200 900;
    font-display: swap;
    src: url("fonts/Mona-Sans.woff2") format("woff2");
}

/* One file, one weight, no unicode-range split. The design uses Light and only
   Light, so the variable axis was ballast; the two ranges were Google's split,
   which cost a second request for the accented characters the Swedish tree needs
   on nearly every page. Coverage is the union of what the two files carried and
   what latin + latin-ext declare, so nothing was lost in the cut — 373 codepoints
   against the previous 368. */
@font-face {
    font-family: "Red Hat Mono";
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: url("fonts/RedHatMono-Light.woff2") format("woff2");
}

:root {

    /* Ollie layout widths (theme.json settings.layout). */
    --wp--style--global--content-size: 740px;
    --wp--style--global--wide-size: 1260px;

    /* Ollie spacing scale (theme.json settings.spacing.spacingSizes). */
    --wp--preset--spacing--small: clamp(.5rem, 2.5vw, 1rem);
    --wp--preset--spacing--medium: clamp(1.5rem, 4vw, 2rem);
    --wp--preset--spacing--large: clamp(2rem, 5vw, 3rem);
    --wp--preset--spacing--x-large: clamp(3rem, 7vw, 5rem);
    --wp--preset--spacing--xx-large: clamp(4rem, 9vw, 7rem);
    --wp--preset--spacing--xxx-large: clamp(5rem, 12vw, 9rem);
    --wp--preset--spacing--xxxx-large: clamp(6rem, 14vw, 13rem);

    /* Pure hybrid fluid font-size scale — the decided scale for the IR site
       (docs/adr/0004-typskala-ren-hybrid.md; fluid range 320–1260 px in the
       clamp() form WordPress emits). At the desktop end the steps follow
       Perfect Fifth (1.5) up to large, Minor Sixth (1.6) into x-large and
       the Golden Ratio (1.618) into xx-large; the mobile minima flatten the
       hierarchy so display sizes fit small screens. */
    --wp--preset--font-size--x-small: clamp(.75rem, 0.75rem + ((1vw - 0.2rem) * 0.213), .875rem);
    --wp--preset--font-size--small: clamp(.875rem, 0.875rem + ((1vw - 0.2rem) * 0.213), 1rem);
    --wp--preset--font-size--base: clamp(1rem, 1rem + ((1vw - 0.2rem) * 0.213), 1.125rem);
    --wp--preset--font-size--medium: clamp(1.25rem, 1.25rem + ((1vw - 0.2rem) * 0.745), 1.6875rem);
    --wp--preset--font-size--large: clamp(1.875rem, 1.875rem + ((1vw - 0.2rem) * 1.117), 2.53125rem);
    --wp--preset--font-size--x-large: clamp(2.8125rem, 2.8125rem + ((1vw - 0.2rem) * 2.106), 4.05rem);
    --wp--preset--font-size--xx-large: clamp(4.25rem, 4.25rem + ((1vw - 0.2rem) * 3.915), 6.55rem);

    /* Ollie font families — neutral system stacks until the design pass. The
       sans/mono ROLE split follows the design system (Red Hat Mono roles →
       monospace; Mona Sans roles → primary); the faces themselves come later. */
    --wp--preset--font-family--primary: "Mona Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --wp--preset--font-family--monospace: "Red Hat Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

    /* Ollie font weights and line heights (theme.json settings.custom). */
    --wp--custom--font-weight--light: 300;
    --wp--custom--font-weight--regular: 400;
    --wp--custom--font-weight--medium: 500;
    --wp--custom--font-weight--semi-bold: 600;
    --wp--custom--font-weight--bold: 700;

    /* Named for the role they serve rather than for a step on the scale above:
       running text sits at Light and the ingress answers it at Bold. They kept
       their own names when the values became 300 and 700 (Thomas 2026-08-10,
       down from 350 and 750) because the role is what the rest of the stylesheet
       states — a heading that wants Light says so, and the two roles can move
       again without every rule that serves them being rewritten. */
    --wp--custom--font-weight--body: 300;
    --wp--custom--font-weight--lead: 700;
    --wp--custom--line-height--none: 1;
    --wp--custom--line-height--tight: 1.1;
    --wp--custom--line-height--snug: 1.2;
    --wp--custom--line-height--body: 1.5;
    --wp--custom--line-height--relaxed: 1.625;

    /* Ollie 11-slot palette, carrying SeaTwirl's values (verbatim from
       resources/design-system/tokens/colors.css). The slot names are a closed
       WordPress set, so the brand profile's five colours take the slots whose
       role they can actually fill at WCAG AA: Deep sea is the ink and the
       structural navy, Purple dawn the attention fill, and Frost smoke and Sea
       shell the two light grounds. Purple dawn cannot take `primary`, which
       Ollie's patterns use as ink on white — it reaches only 4.06:1 there.
       border-dark is near-black navy and is therefore reserved for heavy,
       active or structural borders — never a quiet hairline. */
    --wp--preset--color--primary: #181838;            /* Deep sea */
    --wp--preset--color--primary-accent: #EAEAF9;     /* Frost smoke — Purple dawn's light partner */
    --wp--preset--color--primary-alt: #8263FF;        /* Purple dawn */
    --wp--preset--color--primary-alt-accent: #ffffff; /* Text on Purple dawn */
    --wp--preset--color--main: #101018;               /* Neutral ink */
    --wp--preset--color--main-accent: #D9D9E0;        /* Neutral light */
    --wp--preset--color--base: #ffffff;
    --wp--preset--color--secondary: #5f5f6b;          /* Neutral mid */
    --wp--preset--color--tertiary: #EFECE5;           /* Sea shell — Golden sand's light partner */
    --wp--preset--color--border-light: #D9D9E0;
    --wp--preset--color--border-dark: #181838;

    /* Brand colours the closed slot set has no room for, plus the one tint the
       contrast requirement forces. Golden sand is the profile's fifth colour,
       mirrored from the design system's --ds-golden-sand; it is a ground only,
       and takes ink text (7.31:1), never the profile's white (2.59:1, which
       fails at every size). The deep Purple dawn is Purple dawn taken down in
       lightness alone — oklch L .615 → .55, chroma and hue untouched — because
       Purple dawn itself only ever clears the large-text 3:1 threshold: 4.06:1
       on white and 3.41:1 on Frost smoke as ink, 4.06:1 under white as a fill.
       The deep tone clears normal-text AA in both directions (5.36 on white,
       4.50 on Frost smoke, 4.55 on Sea shell, and 5.36 under white). Purple
       dawn therefore keeps the display numerals and the 20 px/700 CTA, where
       large text applies; every smaller purple word and every smaller purple
       fill takes the deep tone. */
    --ds-golden-sand: #C49A58;
    --ds-purple-dawn-deep: #704DE8;

    /* Ollie border-radius presets (theme.json settings.border.radiusSizes). */
    --wp--preset--border-radius--xs: .25rem;
    --wp--preset--border-radius--sm: .5rem;
    --wp--preset--border-radius--lg: 1.25rem;
    --wp--preset--border-radius--full: 100rem;

    /* Raw radii the design system applies directly rather than as presets; the
       --ds- namespace marks them as design-system values with no Ollie token. */
    --ds-radius-button: 10px;
    --ds-radius-card: 20px;

    /* Ollie shadow preset (theme.json settings.shadow.presets) — verbatim from
       resources/design-system/tokens/shadows.css. */
    --wp--preset--shadow--large-dark: 0px 219px 61px 0px rgba(20, 17, 31, 0.00), 0px 140px 56px 0px rgba(20, 17, 31, 0.01), 0px 79px 47px 0px rgba(20, 17, 31, 0.05), 0px 35px 35px 0px rgba(20, 17, 31, 0.09), 0px 9px 19px 0px rgba(20, 17, 31, 0.10);

    /* Card-lift shadow: Ollie's shadow ink at a fraction of its throw, so a
       hovered card reads as one step off the band rather than as the floating
       megamenu panel. No Ollie preset covers a hover state. */
    --wf-lift-shadow: 0 6px 18px -6px rgba(20, 17, 31, .22), 0 2px 5px -2px rgba(20, 17, 31, .12);

    /* Keyboard focus ring — see the section of the same name for the rule it
       serves. One width does double duty as the offset, so the ring and the gap
       it stands off in can never drift apart. The colour is a variable because
       the ground decides it, exactly as --wf-numeral-color's does. */
    --wf-focus-w: calc(var(--wf-border-w) * 2);
    --wf-focus-ring: var(--wp--preset--color--primary-alt);

    /* Wireframe-only helpers — no Ollie token exists for these; the --wf- */
    /* namespace keeps them clearly separate from the design system. */
    --wf-border-w: 1px;
    /* Two numbers, because the header band and the space it steals from the
       page stopped being the same thing. The band is what the header draws;
       --wf-header-h is what the page must hold clear of it, which is the band
       only for as long as the header is fixed. When the reader's text size
       makes the actions row too wide for one line, the header wraps and
       rejoins the flow — it then occupies its own space, and the page owes it
       nothing (see the site-header section). */
    --wf-header-band: 4.25rem;
    --wf-header-h: var(--wf-header-band);
    /* The hero band's height, written once because two elements answer to it.
       The band is the hero's own floor, and the open megamenu takes the same
       number as its floor minus the header it hangs from — so the panel's
       bottom edge lands on the hero's, and no strip of photograph shows below
       an open menu. The two used to be unrelated: the hero was capped at 46rem
       while the panel was however tall its content made it (642 px), which left
       26 px of blue on any viewport tall enough for the cap to bite.

       45rem is half of 90rem, the reference width the image ladder's `sizes`
       expressions count from, so the band is exactly 2:1 at 1440 px — where the
       scrollbar is an overlay; a classic scrollbar reserves the gutter and
       makes it 1.98:1. It is a height, not a ratio: above 1440 px the band
       letterboxes rather than growing, which is what keeps the panel able to
       cover it at all. See the megamenu section for the floor and
       docs/bilder.md for what the proportion means for the photograph. */
    --wf-hero-h: min(82vh, 45rem);
    /* Pointer target floor — see the section of the same name for how it is
       reached. 24 px is WCAG 2.2 AA (2.5.8); the padding is half the distance
       from there to the smallest inline text box the system sets (Red Hat Mono
       at x-small occupies a 16 px content area), so that worst case lands
       exactly on the floor and every larger box clears it. */
    --wf-target-min: 24px;
    --wf-target-pad: calc((var(--wf-target-min) - 16px) / 2);
    /* The size the filled CTA's label may not go below. White on Purple dawn is
       4.06:1, which 1.4.3 passes for large text only, and large text begins at
       18.66 px once the weight is bold — so this is a contrast threshold that
       happens to be spelled as a font-size, not a step on the type scale.

       It is a fixed rem rather than one of the fluid presets on purpose. The
       preset that reaches this size, --wp--preset--font-size--medium, starts
       here and then grows by a third across the viewport range, so adopting it
       would hold the contrast but resize the button; and any smaller preset
       spends part of its range under the threshold, which would drop the label
       out of AA at exactly the narrow widths where it is read most. The number
       is therefore stated once, here, where what it protects can be read off
       its name. The site header's CTA sits below it and is navy instead — see
       .wf-btn. */
    --wf-cta-large-text: 1.25rem;
    --wf-gutter: clamp(1.25rem, 6vw, 5.625rem);
    --wf-card-min: 16rem;
    /* A KPI panel may not wrap its figure (see .wf-kpi-value), so the track it
       stands in has to hold the widest figure the system sets. Measured against
       the rendered type at every width, both binding cases land on the same
       number: "48,7 MSEK" with the panel's padding needs 14rem, and so does the
       longest one-line label, "Soliditet · Q1 2026". Below this the nowrap
       figure simply leaves the panel. */
    --wf-stat-min: 14rem;
    /* The filter row's search field borrows nothing from the stat floor — it is
       an input that must survive a 320px screen, and it kept 11rem only because
       that was what the token happened to hold. */
    --wf-search-min: 11rem;
    /* Floor for the neighbour-sized image, which is the only frame a text can
       size: three lines of prose beside it must not press it into a strip. */
    --wf-img-min-h: 18rem;
    --wf-portrait: 5.5rem;
    /* Hero text is white over a photograph nobody has vetted, so the scrim has
       to carry the contrast on its own. Deep sea at .60 alpha over the brightest
       possible photo — pure white — still leaves white text at 4.5:1, so .60 is
       the floor the gradient may not dip below at any point; the eyebrow and the
       lead are normal-size text and get no large-text concession. The middle
       stop used to sit at .38, which opened a window onto the image right where
       the hero content lands (2.5–3.5:1 in the worst case). If step 3 lands hero
       images that are dark by construction, this can be reopened per page. */
    --wf-hero-scrim: linear-gradient(180deg, rgba(24, 24, 56, .72) 0%, rgba(24, 24, 56, .60) 45%, rgba(24, 24, 56, .80) 100%);
    /* The proportion ladder (docs/bilder.md): one step wider than neutral, one
       step narrower, and nothing in between to blur them. A frame that has
       neither a neighbour to measure against nor a given physical size takes its
       ratio from here, and 3:2 is what applies when nothing else is said. Covers
       keep the document's own proportion because that proportion is the
       information, which is the one documented departure from the ladder — and
       it is measured rather than assumed: SeaTwirl's reports are landscape A4
       (841,89 × 595,28 pt), so 297:210 and not the 210:297 this stood at until
       2026-08-10, which cropped away half of every cover. */
    --wf-ratio-wide: 2 / 1;
    --wf-ratio-photo: 3 / 2;
    --wf-ratio-portrait: 4 / 5;
    --wf-ratio-cover: 297 / 210;
    --wf-tracking-wide: .14em;
    --wf-tracking-label: .08em;
    --wf-tracking-display: -.015em;

    /* The floor under the whole sheet. The scale (ADR 0004) bottoms out at 12 px,
       but two roles are defined as a fraction of a step above them — the metadata
       tag at 0.8 × x-small and a display numeral's unit at half its figure — and
       a fraction of a fluid size is fluid too, so both used to keep falling: 9.6
       and 10 px at 320 px. That is under what PRODUCT.md's audience — older
       private investors reading on a phone — can be asked to decode.

       11 px is where an uppercase label stops being the smallest letterform as
       well as the smallest size: measured in Mona Sans, caps at 11 px stand
       8.0 px against the 6.3 px x-height of body text at the scale's own 12 px
       minimum. Every derived size is clamped to it from below, so the proportion
       holds wherever there is room for it and gives way where there is not. The
       desktop ends are untouched. */
    --wf-font-size-floor: .6875rem;

    /* The one step the site sets below the scale. The tag role (DESIGN.md) puts
       metadata pills at 0.8 × x-small so a chip can never be mistaken for a
       button — decorative labels only. Anything a reader has to read or press
       takes a step on the scale itself; the filter toggles and the subscribe
       panel's consent line used to reach for this multiplier and no longer do. */
    --wf-font-size-tag: max(var(--wf-font-size-floor), calc(var(--wp--preset--font-size--x-small) * .8));

    /* Display numerals are Purple dawn on light grounds and white on dark (purple
       on navy is only 4.2:1), so the ground flips this one variable rather than
       every numeral rule restating the pairing. */
    --wf-numeral-color: var(--wp--preset--color--primary-alt);

    /* North-east arrow whose head meets the top-right corner of its bounding
       box with equal clearance above and to the right; used as a mask so it
       renders in the current text colour. */
    --wf-arrow-ne: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 12.5 12.5 3.5M4.5 3.5h8v8' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3C/svg%3E");

    /* Magnifying glass rendered as a mask (current text colour), used inside
       search fields; the design pass swaps it for the icon library's glyph
       (Phosphor's ph-magnifying-glass). */
    --wf-icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='6.75' cy='6.75' r='4.75' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='m10.5 10.5 4 4' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3C/svg%3E");

    /* The accordion's own marker, drawn as a mask so it takes the summary's
       colour and can be turned. It points down, and the closed state turns it a
       quarter to the left — which is what makes "the arrow swings down" a single
       transition rather than two glyphs swapped. The design pass swaps it for
       Phosphor's ph-caret-down. */
    --wf-icon-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m3.5 6 4.5 4.5L12.5 6' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

    /* Download-action glyphs (masks, current text colour) for the .wf-dllink
       icon-above-label control: a document with a down arrow (report PDF), a
       screen on a stand (presentation/slides) and a play frame (video). The
       design pass swaps them for Phosphor's ph-file-arrow-down /
       ph-presentation / ph-video. */
    --wf-icon-file-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='3.5' y='1.5' width='9' height='13' rx='1.5' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M8 5v5.5M5.75 8.25 8 10.5l2.25-2.25' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --wf-icon-slides: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='2.5' width='12' height='8' rx='1' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M8 10.5V13M5.5 14 8 13l2.5 1' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --wf-icon-video: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='3.5' width='12' height='9' rx='1.5' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M6.75 6 10.25 8 6.75 10Z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E");

    /* Calendar with a plus sign (mask, current text colour) for the
       add-to-calendar chip. The design pass swaps it for Phosphor's
       ph-calendar-plus. */
    --wf-icon-cal-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2' y='3' width='12' height='11.5' rx='1.5' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M2 6.5h12M5.25 1.25V4M10.75 1.25V4M8 8.25v3.5M6.25 10h3.5' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");

    /* Circled check (mask, current text colour) for the board cards' oberoende
       rows. The design pass swaps it for Phosphor's ph-check-circle. */
    --wf-icon-check-circle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.25' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='m5.25 8 2 2 3.5-4' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

    /* Head-and-shoulders glyph (mask, so it renders in whatever colour the rule
       paints) standing in for a portrait where no photograph exists. The design
       pass swaps it for Phosphor's ph-user. */
    --wf-icon-user: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='5.25' r='3' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M2.75 14.5a5.25 5.25 0 0 1 10.5 0' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");

    /* A decorative rising polyline standing in for a real chart. Unlike the icon
       masks above this is an ordinary background, so it carries its own Purple
       dawn stroke; the viewBox matches its host's aspect ratio so `contain`
       scales the line uniformly.

       WARNING — the %238263FF below is the only colour in this sheet that does
       not follow its token. A data URI is an opaque string to CSS, so
       var(--wp--preset--color--primary-alt) cannot be read from inside one and
       the hex has to be written out. Repaint Purple dawn in the palette above
       and this declaration stays behind at the old value until someone edits it
       by hand. One occurrence to change: the chart's stroke.

       The sparkline used to stand here as a second such line, with two more
       occurrences. It is drawn by the build out of docs/data/aktiekurs.json as
       of 2026-08-10 — see .wf-spark — and takes currentColor like everything
       else, so it is no longer an exception to anything. */
    --wf-chart-line: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 90'%3E%3Cpolyline points='8,74 30,66 52,70 74,52 96,58 118,36 140,20' fill='none' stroke='%238263FF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ---------- Base ---------- */

* { box-sizing: border-box; }

/* Nothing sets a floor it did not ask for.

   A flex or grid item's `min-width` defaults to `auto`, which resolves to its
   min-content width: the item refuses to go below its widest unbreakable run,
   whatever the room around it. Nobody writes that floor and nothing inherits
   past it — it is simply there, under every row and every track, and while the
   band is the larger of the two it is invisible. It stops being invisible the
   moment a reader enlarges the browser's text: min-content is set in the same
   rem as the type and doubles with it, the viewport does not, and the floor
   becomes the width. That is the whole of what pushed fourteen of these
   twenty-six pages sideways at a doubled text size — a search field's 11rem, a
   nested panel's padding, one Swedish compound in a filter chip — and each was
   the same bug wearing a different component.

   Every floor in this system that is real is written down: the search field's
   11rem, the KPI panel's 14rem, the card grid's 16rem, the widget's own
   min-content that decides when it folds. The automatic one carries nothing
   but the accidents, so it goes — once, here, rather than at each of the
   fifteen places it happened to surface. This sits in :where() at zero
   specificity, so every floor that was written down still outranks it. */
:where(body *) { min-width: 0; }

/* In-page jumps glide rather than cut, and stop clear of the fixed header so an
   anchored section never lands underneath it. Users who ask their system for
   less motion get the instant jump back. */
html {
    scroll-behavior: smooth;
    scroll-padding-top: calc(var(--wf-header-h) + var(--wp--preset--spacing--small));
    /* The gutter is reserved whether or not the page is currently scrollable,
       which is what lets the megamenu hold the page still behind it without
       anything moving: site.js takes the scrollbar away for as long as the
       panel is open, and on a platform that draws a classic scrollbar that
       would otherwise hand the viewport 15 px back and shift every fixed edge
       in the chrome. Reserving it costs nothing here — all 26 pages are longer
       than the viewport — and it removes the same jump between any two pages
       that are not. */
    scrollbar-gutter: stable;
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
}

/* Running text carries the page, so it is set from its own token rather than
   borrowing Regular: everything below that declares no weight of its own
   inherits this, and everything that pins a weight of its own is untouched
   by design. */
body {
    margin: 0;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--base);
    font-weight: var(--wp--custom--font-weight--body);
    line-height: var(--wp--custom--line-height--body);
    color: var(--wp--preset--color--main);
    background: var(--wp--preset--color--base);
    /* A word longer than the column it stands in is the one thing a layout
       cannot solve for itself: no track, floor or measure can go below it, so
       it sets the width of everything above it and the page leaves the screen.
       Swedish compounds reach that length on their own — "kandidatexamen" is
       350 px at a doubled text size — and an address like an e-mail is one
       word however long it gets. This is a last resort and behaves like one:
       it does nothing at all until a word has no line it fits on, which is why
       it can be stated once for the whole document rather than argued
       component by component. */
    overflow-wrap: break-word;
}

h1, h2, h3, h4 {
    line-height: var(--wp--custom--line-height--tight);
    margin-block: 0 var(--wp--preset--spacing--small);

    /* Swedish compounds are long — "Bolagsstyrning", "Pressmeddelanden" — and at
       display size on a phone a single word is wider than the measure, so a
       heading needs somewhere to break. It is not `hyphens: auto`: that is not a
       last resort but an improvement, taken whenever it makes a better line, and
       it cut "Riskerna med en in-vestering" on a 360 px screen where the space
       before the word was free. A hyphen in a heading is reserved for the ones
       written into the text.

       `anywhere` rather than `break-word` because a box's minimum width is the
       longest run it cannot break, and `break-word` is deliberately not counted
       towards that minimum while `anywhere` is. So `break-word` would rescue the
       ink but leave a card's floor standing at the whole unbroken compound,
       which is the width that pushes the grid past the screen at a doubled text
       size. This lowers the floor as well as breaking the line. */
    overflow-wrap: anywhere;
}

/* The same reason, in the roles the heading rule does not reach. A compound is
   one word wherever it lands, and the places that hold a single word as their
   whole content — a field's label, a filter's toggle, a document chip, a
   button — stand in boxes far narrower than any heading's measure. At the
   scale's own sizes they fit and the question never arises; at a doubled text
   size "Organisationsnummer" is 250 px inside a 240 px band, and it took the
   panel, the widget around it and the page's right edge with it.

   Lowering the floor is what does the work here, not merely wrapping the ink. A
   box states a minimum width, and that minimum is the longest run it cannot
   break — `break-word` is deliberately not counted towards it, so it would
   rescue the ink while leaving the floor exactly where the unbroken word put it.
   `anywhere` is counted, which is why it is the value used both here and on the
   headings: it moves the floor rather than only the text standing on it. */
:is(.wf-eyebrow, .wf-mega-kicker, .wf-subscribe-kicker, .wf-kpi-label,
    .wf-factlabel, .wf-person-label, .wf-chip, .wf-pill, .wf-tag, .wf-btn,
    button, label, legend) {
    overflow-wrap: anywhere;
}

/* Display headings are light and tightly tracked, as in the mockups. */
h1, h2 {
    font-weight: var(--wp--custom--font-weight--light);
    letter-spacing: var(--wf-tracking-display);
}

h3, h4 { font-weight: var(--wp--custom--font-weight--medium); }

h1 { font-size: var(--wp--preset--font-size--x-large); max-width: 20ch; text-wrap: balance; }
h2 { font-size: var(--wp--preset--font-size--large); max-width: 24ch; text-wrap: balance; }
h3 { font-size: var(--wp--preset--font-size--medium); }
h4 { font-size: var(--wp--preset--font-size--base); }

p, ul, ol, dl, table, form, blockquote, figure {
    margin-block: 0 var(--wp--preset--spacing--small);
}

/* Bulleted and numbered content lists get air *between* their items, so each
   item reads as its own point rather than as a run of wrapped lines. The gap
   goes on the item's leading edge only — never above the first or below the
   last — so the list keeps a paragraph's outer rhythm. Chrome lists (crumbs,
   megamenu, doc and press lists, timeline, fact rows) carry their own classes
   and spacing and are left alone. */
ul:not([class]) > li + li,
ol:not([class]) > li + li,
ul.wf-measure > li + li,
ol.wf-measure > li + li {
    margin-block-start: calc(var(--wp--preset--spacing--small) / 2);
}

/* Running text now sits below Regular, so the UA's `bolder` keyword on
   strong/b would resolve straight to Bold (700) — a heavier jump than any
   emphasis inside running text should make. Pinning the step keeps it a
   deliberate choice rather than an artefact of the base weight. */
strong, b { font-weight: var(--wp--custom--font-weight--semi-bold); }

a { color: var(--wp--preset--color--main); }
a:hover { color: var(--wp--preset--color--primary); }

/* Text links in running copy and standalone action links carry Purple dawn.
   An underline at rest is optional; on hover it is not. Navigation, card and
   list-title links keep the ink colour their own rules already give them. */
.wf-measure a,
.wf-lead a,
.wf-prose a,
.wf-bento-desc a,
details a,
.wf-arrowlink,
.wf-morelink a {
    color: var(--ds-purple-dawn-deep);
}

.wf-measure a:hover,
.wf-lead a:hover,
.wf-prose a:hover,
.wf-bento-desc a:hover,
details a:hover,
.wf-arrowlink:hover,
.wf-morelink a:hover {
    color: var(--ds-purple-dawn-deep);
    text-decoration: underline;
}

/* Body copy never runs wider than a comfortable measure. */
.wf-measure { max-width: 64ch; }

/* The system's one visually hidden utility: text that is spoken but not drawn,
   for the places where the page has already said something in a form a screen
   reader cannot reach — a table whose name is the heading standing above it
   rather than anything inside the table itself.

   It is not `display: none` and not `visibility: hidden`, since both take the
   text out of the accessibility tree along with the picture, which is the
   opposite of the point. The clip-and-shrink form is what leaves the text in
   the tree while giving it no area on screen: a 1 px box, its own overflow
   hidden, and `inset(50%)` clipping even that away. `white-space: nowrap`
   stops a long string from being laid out as a tall column of single
   characters, which is the one way this technique still costs layout, and the
   absolute position keeps the leftover pixel from taking part in any row it
   would otherwise sit in. */
.wf-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ---------- Layout primitives ---------- */

/* Chrome containers (header, megamenu, footer): centered padded block. The
   caps include the gutter padding so the content box equals the band grid's
   measure — chrome content aligns exactly with wide section content below. */
.wf-inner {
    margin-inline: auto;
    padding-inline: var(--wf-gutter);
}

.wf-wide { max-width: calc(var(--wp--style--global--wide-size) + 2 * var(--wf-gutter)); }

/* Satsyta model (page bands): equal flexible margins around a wide measure.
   Children sit left-aligned in the wide measure, capped to the main (narrow)
   measure by default; the widget space is the wide measure's remainder,
   engaged through the .wf-withwidget pairing below:
   | margin | main | widget | margin |   (smalt innehåll + widgets)
   | margin |     wide      | margin |   (brett innehåll)
   |            full                 |   (fullbrett innehåll)          */
.wf-band > .wf-inner {
    display: grid;
    grid-template-columns:
        [full-start] minmax(var(--wf-gutter), 1fr)
        [wide-start] minmax(0, var(--wp--style--global--wide-size))
        [wide-end] minmax(var(--wf-gutter), 1fr)
        [full-end];
    margin-inline: 0;
    padding-inline: 0;
    max-width: none;
}

/* Default to the main measure unless the band opts into the wide measure
   (.wf-wide). The width caps carry zero specificity so any component's own
   measure (h1's 20ch, .wf-lead's 64ch, …) wins over the satsyta default. */
.wf-band > .wf-inner > * { grid-column: wide; }
:where(.wf-band > .wf-inner) > * { max-width: var(--wp--style--global--content-size); }
:where(.wf-band > .wf-inner.wf-wide) > * { max-width: none; }

/* Narrow content paired with a supporting widget (image, fact box, single
   card, small table) on the same rows. The widget belongs to the content on
   its left; the main measure keeps priority when space tightens.

   The pairing wraps rather than squeezes: the widget's floor is its own
   min-content width — the width at which its longest unbreakable run exactly
   fills the line — and the moment the remainder falls below that, the widget
   folds under the content instead of letting the text run past the padding.
   The floor is measured, never guessed, so it follows the content if a value
   changes. The basis is the exact remainder, so as long as both fit on one
   line the geometry equals the two-track grid this replaces: content measure
   left, the rest right.

   Because the floor is min-content, it is only ever as safe as the content is
   breakable: a value held to one line by nowrap put the whole widget past the
   band at a doubled text size, since a floor taller than the page is still a
   floor. The fact rows therefore keep their figures whole with non-breaking
   spaces in the copy rather than with nowrap on the slot, which leaves every
   run in the widget breakable at some point and the floor genuinely below the
   band. Anything dropped in here later owes the same. */
.wf-band > .wf-inner > .wf-withwidget {
    max-width: none;
    display: flex;
    flex-wrap: wrap;
    gap: var(--wp--preset--spacing--medium) var(--wp--preset--spacing--large);
    align-items: start;
}

.wf-withwidget > :not(.wf-widget) {
    flex: 1 1 var(--wp--style--global--content-size);
    min-width: 0;
}

/* Alone on its line — folded, or on a viewport too narrow for the pairing —
   the widget grows to the full measure, exactly as the folded grid row did. */
.wf-withwidget > .wf-widget {
    flex: 1 1 calc(100% - var(--wp--style--global--content-size) - var(--wp--preset--spacing--large));
}

/* The floor is a fold threshold, so it is only stated where a fold is still in
   question. Once the row is too narrow to hold the main measure at all there
   is nothing left to fold away from, and a floor that no longer decides
   anything can still overrule: at a doubled text size the widget's min-content
   is 298 px — an ISIN, a label and two paddings — against a 240 px band, and
   the panel took the page sideways to keep a promise nobody was waiting on.

   The bound is in rem because a rem in a media query is the browser's own text
   size, so one number reads the same question at both: 416 px at the default
   root, 832 px at a doubled one, and at each of them the main measure alone
   already fills the band. Below this the widget is folded and alone on its
   line whatever this property says; above it, the floor is back and decides
   the fold exactly as it did. */
@media (min-width: 26rem) {
    .wf-withwidget > .wf-widget { min-width: min-content; }
}

/* Follow-on content in the same band (e.g. the risk disclosure panel under
   the share chart) keeps its distance from the widget row above. */
.wf-band > .wf-inner > .wf-withwidget + * { margin-block-start: var(--wp--preset--spacing--medium); }

/* Panels paired in a widget row stretch to equal height. */
.wf-withwidget > .wf-panel { align-self: stretch; }

/* Chart panel: graph, period toggles and source note framed together. The
   graph absorbs surplus height so the panel can match the widget beside it;
   the source note anchors at the bottom behind a hairline, as in the mockups. */
.wf-chartpanel {
    display: flex;
    flex-direction: column;
    gap: var(--wp--preset--spacing--small);
}

.wf-chartpanel .wf-chart { flex: 1; margin: 0; }
.wf-chartpanel .wf-toggles { margin: 0; }

.wf-chartpanel .wf-sourcenote {
    margin: 0;
    padding-top: var(--wp--preset--spacing--small);
    border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
}

/* Every section is its own full-width band with a visible label tag. */
.wf-band {
    position: relative;
    padding-block: var(--wp--preset--spacing--x-large);
}

/* Sections are separated by their backgrounds, never by rules. When two
   neighbouring bands share a background the seam is invisible, so the lower
   band drops its top padding — otherwise the visible gap would double. The
   seam only exists when the band above is actually rendered: variant
   sections (wireframes/inspector.js) stay in the DOM as hidden siblings rather
   than being removed, so the leading compound must exclude [hidden] too, or a
   hidden variant would still count as the band "above" and steal the padding
   from the one variant that is actually shown. */
.wf-band:not(.wf-band-alt, .wf-band-dark, .wf-crumbbar, [hidden]) + .wf-band:not(.wf-band-alt, .wf-band-dark),
.wf-band-alt:not([hidden]) + .wf-band-alt,
.wf-band-dark:not(.wf-herofull, [hidden]) + .wf-band-dark {
    padding-block-start: 0;
}

.wf-band-alt { background: var(--wp--preset--color--tertiary); }

/* Dark bands carry light text, mirroring the mockups' navy sections. Every dark
   ground keys off primary (navy) and grades its text by transparency of white:
   headings and primary text solid, body 75 %, muted 55 %. */
.wf-band-dark {
    background: var(--wp--preset--color--primary);
    color: var(--wp--preset--color--base);
}

.wf-band-dark a { color: var(--wp--preset--color--base); }
.wf-band-dark .wf-lead { color: color-mix(in srgb, var(--wp--preset--color--base) 75%, transparent); }
.wf-band-dark .wf-meta, .wf-band-dark .wf-footnote, .wf-band-dark .wf-eyebrow, .wf-band-dark .wf-sourcenote, .wf-band-dark blockquote cite { color: color-mix(in srgb, var(--wp--preset--color--base) 55%, transparent); }
.wf-band-dark .wf-eyebrow-num { color: var(--wp--preset--color--base); }

/* Insurance, not a fix for a live bug: a colour set directly on <time> always
   beats one inherited from .wf-meta's dimming just above, so a dark-band meta
   line would otherwise render its date at full ink against a near-black
   ground. No page combines the two today, but the markup that would is
   ordinary. */
.wf-band-dark .wf-meta time { color: inherit; }

/* The quote rule is Deep sea, which is the dark ground's own colour, so on dark
   it swaps to the light neutral rather than disappearing into the band. */
.wf-band-dark blockquote { border-inline-start-color: var(--wp--preset--color--main-accent); }

/* Dark grounds flip the display numerals and the accent links to white; the KPI
   panel resets the numeral colour again below, because it keeps its own light
   surface even when it sits inside a dark band. */
.wf-band-dark, .wf-panel-dark, .wf-herofull { --wf-numeral-color: var(--wp--preset--color--base); }

.wf-band-dark .wf-measure a,
.wf-band-dark .wf-lead a,
.wf-band-dark .wf-prose a,
.wf-band-dark .wf-arrowlink,
.wf-panel-dark .wf-measure a,
.wf-panel-dark .wf-arrowlink {
    color: var(--wp--preset--color--base);
}

/* ---------- Eyebrows (numbered mono section labels) ---------- */

.wf-eyebrow {
    display: flex;
    align-items: baseline;
    gap: .5em;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    letter-spacing: normal;
    text-transform: uppercase;
    color: var(--wp--preset--color--main);
    margin-block: 0 var(--wp--preset--spacing--medium);
}

/* Inside a panel the eyebrow is a card label, not a section label: the section
   gap would leave the label floating above its own content. */
.wf-panel > .wf-eyebrow { margin-block-end: var(--wp--preset--spacing--small); }

/* When a band's only label is its eyebrow, the eyebrow *is* the section
   heading and carries the tag, so the outline never skips from the page h1 to
   a card h3. The tag brings the display-heading rules with it and they have to
   be sent back: a mono label at x-small wants the body line box, the band's
   full width and no balancing. The eyebrow's own declarations already outrank
   the element selectors for size, weight and tracking. */
h2.wf-eyebrow {
    line-height: var(--wp--custom--line-height--body);
    max-width: none;
    text-wrap: wrap;
}

/* The numeral keeps the mono voice even inside sans hosts (the megamenu hub
   links became sans), so it carries its own family and Light weight; the index
   is the accent of the eyebrow, so it takes the purple ink while the label does
   not — ink rather than Purple dawn because the eyebrow sets x-small. */
.wf-eyebrow-num { font-family: var(--wp--preset--font-family--monospace); font-weight: var(--wp--custom--font-weight--light); color: var(--ds-purple-dawn-deep); }

/* ---------- Site header (fixed; transparent over the hero, else solid) ---------- */

/* The header is fixed, so pages without a full-bleed hero clear its height. */
body { padding-top: var(--wf-header-h); }
body:has(.wf-herofull) { padding-top: 0; }

.wf-siteheader {
    /* The one place in the system where rendered text width decides layout, so
       the one place that carries the metric-matched fallback. See the faces at
       the top of this sheet for why it is not on the page's own stack. */
    font-family: "Mona Sans", "Mona Sans Fallback", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    position: fixed;
    inset: 0 0 auto;
    z-index: 60;
    background: color-mix(in srgb, var(--wp--preset--color--base) 92%, transparent);
    backdrop-filter: saturate(180%) blur(8px);
    border-bottom: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    transition: background .28s ease, border-color .28s ease;
}

/* The band holds one line for as long as one line holds the chrome, and wraps
   when it does not. A height would have made that impossible: the row's width
   is set in rem and the viewport is not, so a reader who doubles the browser's
   text size doubles the wordmark and the three actions against a band that
   cannot grow, and the CTA — the header's whole purpose — left the screen by
   228 px on every page. Nothing is dropped and nothing is capped to buy the
   line back; the row is simply allowed to become two. */
.wf-siteheader .wf-inner {
    /* The band's height now lives on the row, not on the header, so the header
       can grow when the row wraps. The hairline comes off it because the
       header's own figure was always a border-box one: the row plus the border
       it draws underneath is the 4.25rem the design names. */
    min-height: calc(var(--wf-header-band) - var(--wf-border-w));
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--small);
    row-gap: calc(var(--wp--preset--spacing--small) / 2);
    padding-block: calc(var(--wp--preset--spacing--small) / 2);
}

.wf-logo {
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--bold);
    text-decoration: none;
    letter-spacing: .04em;
    white-space: nowrap;
}

.wf-logo span { font-weight: var(--wp--custom--font-weight--regular); color: var(--wp--preset--color--secondary); }

/* The wordmark is the sender's mark, not a link with a look: nothing at rest and
   nothing on hover (docs/lankar-och-knappar.md, fourth exception). Stated
   explicitly because the generic `a:hover` outweighs .wf-logo, which sets no
   colour of its own, and pulled the mark from Neutral ink to Deep sea on every
   page without a hero. The overlay rule is more specific and still wins. */
.wf-logo:hover { color: var(--wp--preset--color--main); }

/* Right-hand actions slot: language link · menu toggle · subscribe CTA. The
   slot wraps for the same reason the band does, and keeps its right edge when
   it does, so a wrapped row still reads as the thing that hangs off the end of
   the header rather than as a second row of navigation. */
.wf-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--wp--preset--spacing--medium);
    row-gap: calc(var(--wp--preset--spacing--small) / 2);
}

/* The three actions read as one row, so they share one size. The x-small they
   used to carry sat two steps under the CTA's 20 px and made the row look like
   fine print beside a billboard. */
.wf-lang, .wf-menubtn {
    font-family: var(--wp--preset--font-family--primary);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    font-size: var(--wp--preset--font-size--small);
    letter-spacing: .04em;
    color: var(--wp--preset--color--main);
    text-decoration: none;
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: .25em 0;
    white-space: nowrap;
    cursor: pointer;
}

/* Both are bare text — no shape of their own — so both take the bare-text
   hover: an underline, nothing else. The menu toggle only looks like a button
   because it sits beside one in the actions row; it owns no box, so it is the
   same role as the language switch.

   The toggle underlines its word and not its caret. The rule is about text and
   the caret is an icon, and the flex gap between them cuts the stroke into two
   marks — a line under the label and a loose dash under the arrow — which
   reads as a rendering fault rather than as a hover. It has to be done by
   underlining the label rather than by exempting the caret: a decoration
   propagates to descendants and none of them can decline it, and the usual
   escape of making the descendant an atomic inline is closed here because a
   flex child is blockified and takes the stroke anyway. */
.wf-lang:hover { text-decoration: underline; }
.wf-menubtn:hover .wf-menubtn-label { text-decoration: underline; }

.wf-menubtn { display: inline-flex; align-items: center; gap: .5em; }

/* The header CTA drops to the actions row's shared size and trades the
   Purple-dawn fill for the navy one. White on Purple dawn is 4.06:1 — AA only
   as large text — so the brand fill needs the 20 px/700 label the in-page CTAs
   carry; at nav size it would fail. Navy clears AA at any size, which is what
   lets the three actions match. It also sits lower and tighter than the
   standard button so the header's 4.25rem band shows air above and below. */
.wf-siteheader .wf-btn {
    font-size: var(--wp--preset--font-size--small);
    padding: .55em 1.2em;
    line-height: var(--wp--custom--line-height--snug);
    background: var(--wp--preset--color--primary);
    border-color: var(--wp--preset--color--primary);
}

/* Four items on one 4.25rem line do not fit a phone at the shared size, so the
   whole row steps down together — the balance the sizes were unified for is
   between them, not with any absolute value. */
@media (max-width: 40rem) {
    .wf-logo { font-size: var(--wp--preset--font-size--base); }
    .wf-actions { gap: var(--wp--preset--spacing--small); }
    .wf-lang, .wf-menubtn { font-size: var(--wp--preset--font-size--x-small); }
    .wf-siteheader .wf-btn { font-size: var(--wp--preset--font-size--x-small); padding: .5em .9em; }
}

/* On the narrowest phones the four items still exceed the line, and none of
   them may be dropped: the language switch is the site's only route to the
   other language and the CTA is the header's whole purpose. The wordmark's
   " · IR" suffix is the one part that is redundant — the page it links to
   says so — so it is what gives way. */
@media (max-width: 23rem) {
    .wf-logo span { display: none; }
    .wf-actions { gap: calc(var(--wp--preset--spacing--small) / 2); }
    .wf-siteheader .wf-btn { padding: .5em .7em; }
}

/* A wrapped header is no longer a band; it is a block. Nailing three lines of
   chrome to the top of a phone would spend a quarter of the viewport on
   navigation for the whole of every page — and the reader who enlarged the
   text is the one who can least afford it. So the header gives the top of the
   screen back: it rejoins the flow, scrolls away with everything else, and is
   reached the way the footer is. `--wf-header-h` goes to zero in the same
   move, which is the whole of the adjustment — the body's top padding, the
   hero's clearance under the fixed band, the megamenu's offset and the
   scroll-padding are all written in terms of it and follow without being told.

   Whether the row wrapped is measured in site.js rather than declared at a
   breakpoint here. The width where it breaks is a property of the labels, and
   the labels are translated: a number tuned to "Prenumerera" would be wrong
   for "Subscribe" the day the English tree is built. Without the script the
   header still wraps and still loses nothing; it simply stays fixed. */
:root[data-header-stacked] { --wf-header-h: 0px; }

:root[data-header-stacked] .wf-siteheader {
    position: static;
    transition: none;
}

/* Opened from a header that stands in the flow, the panel belongs in the flow
   directly beneath it: it pushes the page down instead of covering it, and it
   is free to run past one viewport because the page now scrolls it. The hero's
   hem stops being its business in the same move — a panel that pushes the page
   ahead of it covers nothing, so the floor that makes it reach the hem on a
   hero page is dropped rather than padding a phone's menu out to no purpose. */
:root[data-header-stacked] .wf-mega {
    position: static;
    max-height: none;
    min-height: 0;
}

/* Transparent over the hero until scroll or an open menu forces it solid. */
.wf-siteheader[data-overlay] {
    background: transparent;
    backdrop-filter: none;
    border-color: transparent;
}

.wf-siteheader[data-overlay] .wf-logo,
.wf-siteheader[data-overlay] .wf-lang,
.wf-siteheader[data-overlay] .wf-menubtn {
    color: var(--wp--preset--color--base);
    text-shadow: 0 1px 4px color-mix(in srgb, var(--wp--preset--color--main) 40%, transparent);
}

.wf-siteheader[data-overlay] .wf-logo span { color: var(--wp--preset--color--border-light); }

/* The CTA inverts along with the logo and links above: a navy fill sinks into
   the dark imagery the heroes use, and Purple dawn cannot carry a nav-sized
   label (see above). Navy on white is the solid state's pairing mirrored, so
   it clears AA at any size too. */
.wf-siteheader[data-overlay] .wf-btn {
    background: var(--wp--preset--color--base);
    border-color: var(--wp--preset--color--base);
    color: var(--wp--preset--color--primary);
}

.wf-siteheader[data-overlay] .wf-btn:hover { color: var(--wp--preset--color--primary); }

/* ---------- Megamenu (full-width panel below the header) ---------- */

.wf-mega {
    position: fixed;
    top: var(--wf-header-h);
    left: 0;
    right: 0;
    z-index: 55;
    max-height: calc(100vh - var(--wf-header-h));
    overflow-y: auto;
    background: var(--wp--preset--color--base);
    border-bottom: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    box-shadow: var(--wp--preset--shadow--large-dark);
}

/* On a page that opens with a hero, the panel reaches at least to the hero's
   hem. A panel that stops short leaves a band of photograph under it, and a
   sliver of blue below a white panel reads as a mistake rather than as a
   glimpse of the page. The floor is the hero's own height less the header the
   panel hangs from, so the two edges meet by construction rather than by two
   numbers happening to agree.

   Self-correcting in both directions: grow the panel's content and it simply
   runs past the hero, which is what an open menu is entitled to do; shrink it
   and this floor holds it against the hem. Today it does nothing visible — it
   stretches the panel by 10 px — and that is the point of writing it as a
   floor rather than trimming the hero to fit.

   Never taller than the viewport, so it cannot fight the max-height above it:
   --wf-hero-h is capped at 82vh, and this subtracts the header from that. */
body:has(.wf-herofull) .wf-mega {
    min-height: calc(var(--wf-hero-h) - var(--wf-header-h));
}

.wf-mega .wf-inner {
    display: grid;
    gap: var(--wp--preset--spacing--large);
    padding-block: var(--wp--preset--spacing--large);
}

/* Tier 1 — four live-data shortcut cards. */
.wf-mega-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
    gap: var(--wp--preset--spacing--small);
}

.wf-mega-card {
    display: flex;
    flex-direction: column;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    min-height: 8.5rem;
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    padding: var(--wp--preset--spacing--medium);
    text-decoration: none;
}

.wf-mega-kicker {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: normal;
    color: var(--wp--preset--color--secondary);
    margin: 0;
}

.wf-mega-card h3 { font-size: var(--wp--preset--font-size--base); font-weight: var(--wp--custom--font-weight--semi-bold); line-height: var(--wp--custom--line-height--body); margin: 0; }

.wf-mega-price {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--large);
    line-height: var(--wp--custom--line-height--none);
    color: var(--wf-numeral-color);
    margin: 0;
}

.wf-mega-price small { font-size: .4em; color: var(--wp--preset--color--secondary); }

.wf-mega-card .wf-meta { margin-block: auto 0; }
.wf-mega-card .wf-meta-tags { margin-block: 0; } /* tag and price-change rows stay with the content above; only the card's last meta line is pushed to the bottom */
.wf-mega-card .wf-sourcenote { margin-block: auto 0; }

/* Tier 2 — link columns headed by numbered eyebrow hub links. */
.wf-mega-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
    gap: var(--wp--preset--spacing--medium) var(--wp--preset--spacing--large);
}

.wf-mega-hub {
    display: flex;
    align-items: baseline;
    gap: .5em;
    font-family: var(--wp--preset--font-family--primary);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    font-size: var(--wp--preset--font-size--x-small);
    letter-spacing: var(--wf-tracking-wide);
    text-transform: uppercase;
    text-decoration: none;
    color: var(--wp--preset--color--main);
    margin-bottom: var(--wp--preset--spacing--small);
}

.wf-mega-hub:hover { text-decoration: underline; }

.wf-mega-col ul { list-style: none; padding: 0; margin: 0; }

.wf-mega-col li a {
    display: block;
    padding: .3em 0;
    font-size: var(--wp--preset--font-size--small);
    text-decoration: none;
}

.wf-mega-col li a:hover { text-decoration: underline; }
.wf-mega-col li a[aria-current="page"], .wf-mega-hub[aria-current="page"] { font-weight: var(--wp--custom--font-weight--semi-bold); }
.wf-mega-col li.wf-mega-divider { border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light); margin-top: .5em; padding-top: .5em; }

/* Tier 3 — quiet single-line bottom row. */
.wf-mega-bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wp--preset--spacing--small);
    border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    padding-top: var(--wp--preset--spacing--small);
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
}

.wf-mega-bottom a { color: var(--wp--preset--color--secondary); text-decoration: none; }
.wf-mega-bottom a:hover { color: var(--wp--preset--color--main); text-decoration: underline; }

/* The Certified Adviser note anchors the bottom row's right edge. */
.wf-mega-adviser { margin-inline-start: auto; }

/* ---------- Breadcrumbs bar ---------- */

.wf-crumbbar { padding-block: var(--wp--preset--spacing--small); }

.wf-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6em;
    list-style: none;
    padding: 0;
    margin: 0;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
}

.wf-crumbs li + li::before { content: "›"; margin-right: .6em; }
.wf-crumbs a { color: var(--wp--preset--color--secondary); text-decoration: none; }
.wf-crumbs a:hover { color: var(--wp--preset--color--main); text-decoration: underline; }
.wf-crumbs [aria-current="page"] { color: var(--wp--preset--color--main); }

/* ---------- Site footer (deep dark: wordmark + mono links + subscribe) ---------- */

/* Reveal effect: the opaque main layer scrolls over the footer, which sits
   pinned at the viewport bottom behind it and is uncovered at the page end. */
main {
    position: relative;
    z-index: 1;
    background: var(--wp--preset--color--base);
}

.wf-sitefooter {
    background: var(--wp--preset--color--primary);
    color: var(--wp--preset--color--base);
    padding-block: var(--wp--preset--spacing--xx-large) var(--wp--preset--spacing--large);
}

/* Pin only on viewports tall enough to show the whole footer — a stuck footer
   taller than the viewport would leave its top rows unreachable. */
@media (min-width: 64rem) and (min-height: 48rem) {
    .wf-sitefooter {
        position: sticky;
        bottom: 0;
    }
}

.wf-sitefooter a { color: var(--wp--preset--color--base); }

.wf-footgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
    gap: var(--wp--preset--spacing--large);
    align-items: start;
}

/* Same exception as the header's mark: no rest state and no hover. It needs no
   hover rule of its own — the footer's own `a` colour already outranks the
   generic `a:hover` — but nothing may be added here that gives it one. */
.wf-footer-logo {
    display: inline-block;
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--bold);
    letter-spacing: .04em;
    text-decoration: none;
    margin-bottom: var(--wp--preset--spacing--medium);
}

.wf-footer-logo span { font-weight: var(--wp--custom--font-weight--regular); color: var(--wp--preset--color--border-light); }

.wf-footlinks {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium);
    list-style: none;
    padding: 0;
    margin: 0;
    max-width: 30rem;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
}

.wf-footlinks a { color: var(--wp--preset--color--border-light); text-decoration: none; }
.wf-footlinks a:hover { color: var(--wp--preset--color--base); text-decoration: underline; }

.wf-footsub h2 { font-size: var(--wp--preset--font-size--medium); margin-bottom: var(--wp--preset--spacing--medium); }

.wf-fineprint {
    display: grid;
    gap: var(--wp--preset--spacing--medium);
    margin-top: var(--wp--preset--spacing--x-large);
    padding-top: var(--wp--preset--spacing--medium);
    border-top: var(--wf-border-w) solid var(--wp--preset--color--main-accent);
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--main-accent);
}

.wf-fineprint p { margin: 0; }

/* The label above a fine-print block — *IR-kontakt*, *Certified Adviser*. It
   read as the first of three equal lines, so nothing but the word order said it
   was a heading (Thomas 2026-08-10). Capitals and the footer's full white are
   what mark it, and the damped ink below it is what it is marked against: a
   label is separated here by colour and case, never by weight, because the mono
   ships in Light alone (docs/typografi.md). Untracked, as every mono line on the
   site is. The half-step below it is the same gap the subscribe block above uses
   between its own kicker and what follows. */
.wf-fineprint .wf-finelabel {
    text-transform: uppercase;
    color: var(--wp--preset--color--base);
    margin-bottom: calc(var(--wp--preset--spacing--small) / 2);
}

/* Bare text in the footer, same as .wf-footlinks: no mark at rest, and the hover
   lifts the damped ink to full white along with the underline
   (docs/lankar-och-knappar.md, role 4). */
.wf-fineprint a { color: inherit; text-decoration: none; }
.wf-fineprint a:hover { color: var(--wp--preset--color--base); text-decoration: underline; }

/* Left/right pairing inside a fineprint row (contacts, policy links). */
.wf-fineprint-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--small) var(--wp--preset--spacing--large);
}

/* Both halves may shrink. Left to themselves each row is as wide as the widest
   run it holds — a policy link, a registration number — and the footer's own
   track was as wide as the widest row, so at a doubled text size the fine
   print set the width of the page it stands at the bottom of. */
.wf-fineprint { grid-template-columns: minmax(0, 1fr); }
.wf-fineprint-row > * { min-width: 0; }

/* ---------- Images and placeholders ---------- */

/* The div is always the ratio frame. Where a photograph exists it carries an
   <img>; where none does it keeps a caption on a flat Frost smoke ground. The
   hatch is gone — at mid-fidelity an empty slot must read as a deliberate
   surface, not as unfinished work. Mono captions are never tracked
   (docs/typografi.md). */
.wf-img {
    background: var(--wp--preset--color--primary-accent);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--wp--preset--color--secondary);
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: normal;
    aspect-ratio: var(--wf-ratio-photo);
    margin-block: 0 var(--wp--preset--spacing--small);
}

/* A photograph fills its frame edge to edge and takes the frame's own corners,
   so the placeholder ground and hairline behind it would only ever show as a
   stray outline. */
.wf-img img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
}

.wf-img:has(img) { background: none; border-color: transparent; }

/* A frame's step on the ladder, declared on the frame itself. On a frame that is
   also neighbour-sized it states which step to return to once the row folds, and
   .wf-fill-img below deliberately outranks it for as long as a neighbour is
   there to measure against — same specificity, later in the cascade. */
.wf-portrait-img { aspect-ratio: var(--wf-ratio-portrait); }

/* The lead image at the top of an article runs the full content measure, so it
   takes the ladder's widest step; on anything narrower 2:1 would read as a
   strip. It is a frame in the flow rather than a header, hence a plain rhythmic
   gap below it instead of the tighter one .wf-img carries inside a card. */
/* The proportion is the picture's and is written on the element out of
   `docs/data/ledbilder.json`, exactly as a report cover's is: eleven of the 71
   photographs are taller than they are wide, and a 2:1 slice of a standing
   picture cuts the face off whichever edge it is taken from. Those eleven stand
   in 3:2. The value here is therefore what an article with no such property
   falls back to, and not the rule. */
.wf-leadimg {
    aspect-ratio: var(--wf-ratio-lead, var(--wf-ratio-wide));
    margin-block-end: var(--wp--preset--spacing--medium);
}

/* Image that takes its height from the text beside it rather than from a ratio,
   so the pair reads as one block however long the prose runs. The floor keeps a
   short text from collapsing it; no ceiling, because the text is the ceiling.
   This is the only correct frame for an image standing beside a text in a
   .wf-cols row: the row stretches its items, so a frame that kept a ratio there
   would resolve its width from the stretched height and burst the track. */
.wf-fill-img {
    position: relative;
    aspect-ratio: auto;
    min-height: var(--wf-img-min-h);
    margin-block-end: 0;
}

/* This is the one frame with no ratio to give it a height, so an <img> left in
   the normal flow would hand it the file's own proportion and become the tallest
   thing in the row — the file setting the frame, which is exactly what the frame
   rule forbids. Out of the flow it contributes no height, and the neighbour is
   the only thing left that can size the frame. */
.wf-fill-img > img {
    position: absolute;
    inset: 0;
}

/* Once the row has folded there is no neighbour left to measure against, and an
   unconstrained frame would stretch to whatever the grid gave it. The image
   rejoins the ladder at its default step for the rest of the way down, and gives
   up its floor with it — the floor guards against a short text, and a ratio held
   against a floor would resolve the width from the height and burst the column.
   The threshold is the width at which the auto-fit row itself folds. */
@container (max-width: 33.5rem) {
    .wf-fill-img {
        aspect-ratio: var(--wf-ratio-photo);
        min-height: 0;
    }

    /* Default is the ladder's standard step, but a frame that declared a step of
       its own returns to that one — otherwise a portrait motif would be cropped
       to a landscape slot on exactly the screens with the least room to spare. */
    .wf-fill-img.wf-portrait-img { aspect-ratio: var(--wf-ratio-portrait); }
}

/* The design system's notched corner: three card radii and one deep cut at the
   bottom-left. Reserved for the two large feature images — applying it to cards,
   article thumbnails or heroes would turn a signature into a mannerism. */
.wf-portrait-img, .wf-fill-img {
    border-radius: var(--ds-radius-card) var(--ds-radius-card) var(--ds-radius-card) 100px;
    overflow: hidden;
}

/* Report and presentation covers stand on Sea shell instead, so a wall of them
   reads as paper rather than as a grid of empty frames. */
.wf-cover { aspect-ratio: var(--wf-ratio-cover); background: var(--wp--preset--color--tertiary); }

/* Charts, KPI diagrams and maps keep their caption and gain a decorative rising
   line, so the slot states what belongs there without pretending to hold data. */
.wf-chart {
    aspect-ratio: var(--wf-ratio-photo);
    border-radius: var(--wp--preset--border-radius--xs);
    background: var(--wf-chart-line) no-repeat center / contain, var(--wp--preset--color--primary-accent);
}

/* The decorative line and its ground belong to the placeholder, so a frame that
   has been given a real picture drops them — the same shape as the initials
   plate's :has(img) rule. It says in the stylesheet what the markup says: a
   frame carrying a picture has stopped standing in for one. */
.wf-chart:has(img) { background: none; }

/* Portrait slots with no photograph: the caption gives way to a centred user
   glyph, tinted back from Purple dawn so it reads as a missing portrait rather
   than as a picture of nobody. The glyph is a square in a 4:5 frame, so its
   share of the width has to exceed its share of the height for the two to
   average out to the weight a square frame gave it at 40 %; at 48 % of the width
   it covers 38 % of the height, and the mark still reads as deliberate instead
   of adrift. Sized from the width and not the height because the same rule also
   serves the neighbour-sized frame, whose height the text beside it decides. */
.wf-person-head .wf-img > span,
.wf-contact .wf-img > span,
.wf-contact-split > .wf-img > span {
    display: none;
}

.wf-person-head .wf-img:not(:has(img))::after,
.wf-contact .wf-img:not(:has(img))::after,
.wf-contact-split > .wf-img:not(:has(img))::after {
    content: "";
    width: 48%;
    aspect-ratio: 1;
    background-color: color-mix(in srgb, var(--wp--preset--color--primary-alt) 45%, transparent);
    -webkit-mask: var(--wf-icon-user) no-repeat center / contain;
    mask: var(--wf-icon-user) no-repeat center / contain;
}

/* The typographic plate: a portrait slot for someone the company has no
   photograph of and has been asked for one (F15 in docs/innehall/oppna-fragor.md).
   The anonymous glyph says "no picture"; the initials say "no picture of this
   person", which is the difference between a slot that is empty and one that is
   waiting. Set from the card with --wf-initials so one rule serves any name; a
   slot without the property keeps the glyph. Decorative throughout — the name
   stands in the heading beside it, and reading two letters aloud adds nothing. */
.wf-person-head .wf-img-initials:not(:has(img))::after,
.wf-contact .wf-img-initials:not(:has(img))::after {
    content: var(--wf-initials);
    width: auto;
    aspect-ratio: auto;
    background: none;
    -webkit-mask: none;
    mask: none;
    color: color-mix(in srgb, var(--wp--preset--color--primary-alt) 45%, transparent);

    /* Sans, not the numeral voice. docs/typografi.md keeps mono to a closed list
       of eight roles — numbering, dates, measurements, eyebrows, source notes,
       machine identifiers, breadcrumbs, code — and a person's initials are none
       of them. They stand in for a photograph, not for a figure. */
    font-family: var(--wp--preset--font-family--primary);

    /* Sized off the frame rather than the type scale, for the reason the glyph
       above is: the two share a slot and have to carry the same weight in it. */
    font-size: calc(var(--wf-portrait) * .3);
    font-weight: var(--wp--custom--font-weight--body);
}

/* ---------- Buttons, chips and pills ---------- */

/* The in-page CTA is Purple dawn everywhere, on light grounds and dark alike —
   the design system never inverts the brand fill. White on #8263FF is 4.06:1,
   so the label must qualify as large text: 20 px at weight 700, exactly as the
   design system's own button sets it. The site header is the one exception, and
   for that reason: its CTA sits at nav size, where the fill cannot hold. */
.wf-btn {
    display: inline-flex;
    align-items: center;
    gap: .6em;
    padding: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium);
    background: var(--wp--preset--color--primary-alt);
    color: var(--wp--preset--color--primary-alt-accent);
    text-decoration: none;
    font-size: var(--wf-cta-large-text);
    font-weight: var(--wp--custom--font-weight--bold);
    text-transform: uppercase;
    letter-spacing: .02em;
    border: var(--wf-border-w) solid var(--wp--preset--color--primary-alt);
    border-radius: var(--ds-radius-button);
    cursor: pointer;
}

/* Hover keeps the fill and leaves the label alone. An underline is a text
   link's mark, and drawing one inside a button reads as a link that happens to
   have a box around it; the button's hover is the lift below. The colour is
   restated at its rest value only so the generic `a:hover` cannot repaint a
   label the fill has already been contrast-checked against — a real colour
   shift would need a hover slot the 11-slot palette does not have. */
.wf-btn:hover { color: var(--wp--preset--color--primary-alt-accent); }

.wf-btn-secondary {
    background: transparent;
    color: var(--wp--preset--color--main);
    border-color: var(--wp--preset--color--border-dark);
}

/* The outlined button used to fill on hover. That is the fill swap the rules
   rejected: a hovered secondary and a primary at rest would read as the same
   object, and fill already carries a meaning of its own — `aria-pressed` on a
   toggle. Only the colour hold is left, for the same reason as above. */
.wf-btn-secondary:hover { color: var(--wp--preset--color--main); }

/* On a dark band or a photo hero the outlined CTA cannot use the navy border —
   it would vanish — so it outlines itself in translucent white instead. */
.wf-band-dark .wf-btn-secondary,
.wf-herofull .wf-btn-secondary {
    background: transparent;
    border-color: color-mix(in srgb, var(--wp--preset--color--base) 55%, transparent);
    color: var(--wp--preset--color--base);
}

.wf-band-dark .wf-btn-secondary:hover,
.wf-herofull .wf-btn-secondary:hover {
    color: var(--wp--preset--color--base);
}

.wf-btnrow { display: flex; flex-wrap: wrap; gap: var(--wp--preset--spacing--small); }

/* Metadata pills: sans SemiBold uppercase (docs/typografi.md keeps tags out of
   the mono voice), set on the floored tag step below the smallest Ollie size so
   they read as labels rather than buttons — the site's one decorative type, and
   the only consumer of --wf-font-size-tag. The default is a quiet Sea-shell
   chip; every variant below keeps the same geometry and differs only in fill.
   The border is carried transparently on the base so an outlined variant does
   not shift the box.

   A pill never breaks across two lines. It is one label — "Lösen minskning",
   "Icke-regulatorisk" — and a label split down the middle of a rounded box
   reads as two damaged chips rather than one long one. The longest in the site
   is 17 characters at the floored tag step, so holding it on one line costs
   nothing at 320 px; where a row of them will not fit, the row wraps between
   pills, which is what the gap is for. */
.wf-pill {
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
    border: var(--wf-border-w) solid transparent;
    border-radius: var(--wp--preset--border-radius--full);
    padding: .15em .75em;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wf-font-size-tag);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    text-transform: uppercase;
    letter-spacing: var(--wf-tracking-label);
    color: var(--wp--preset--color--secondary);
    background: var(--wp--preset--color--tertiary);
}

/* Regulatory emphasis ("Regulatorisk"): the brand navy, at full strength. */
.wf-pill-solid {
    background: var(--wp--preset--color--primary);
    color: var(--wp--preset--color--base);
}

/* Insider and other emphasis that must outrank the regulatory tag beside it.
   The pill sets its label well under 10 px, where white on Purple dawn itself
   (4.06:1) is short of AA, so the fill is the deep purple — 5.36:1, and close
   enough in hue that the pill still reads as the accent. */
.wf-pill-accent {
    background: var(--ds-purple-dawn-deep);
    color: var(--wp--preset--color--primary-alt-accent);
}

/* Press releases: outlined rather than filled, so a list of them stays calm.
   This is the pill family's one heavy border. */
.wf-pill-outline {
    background: transparent;
    border-color: var(--wp--preset--color--border-dark);
    color: var(--wp--preset--color--main);
}

/* Sea shell disappears on navy, so the default pill becomes a translucent white
   wash on any dark ground. */
.wf-band-dark .wf-pill:not(.wf-pill-solid, .wf-pill-accent, .wf-pill-outline),
.wf-panel-dark .wf-pill:not(.wf-pill-solid, .wf-pill-accent, .wf-pill-outline) {
    background: color-mix(in srgb, var(--wp--preset--color--base) 12%, transparent);
    color: var(--wp--preset--color--base);
}

/* Document/format chips: mono file links shaped like small buttons. */
.wf-chip {
    display: inline-flex;
    align-items: center;
    gap: .5em;
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--sm);
    padding: .5em .9em;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
    font-weight: var(--wp--custom--font-weight--medium);
    text-decoration: none;
    background: var(--wp--preset--color--base);
}

/* Chips no longer refuse to wrap. The nowrap was there to keep a two-word
   label like "Lägg till i kalender" from arriving as a stack, and at every
   size the scale actually sets it still does — a chip at x-small is far
   shorter than the rows it sits in, so nothing wraps that would not have
   wrapped anyway. What the rule could not survive was one word longer than
   the whole row: "Poströstningsformulär" at a doubled text size is wider than
   a 320 px screen, and nowrap made that the width of the chip, the row and
   the page. .wf-docgroup had already had to hand the rule back for the same
   reason; this generalises that exception rather than waiting for the next
   one. */

/* Chip with a leading calendar glyph, used for add-to-calendar actions. */
.wf-chip-cal::before {
    content: "";
    width: 1.1em;
    height: 1.1em;
    background-color: currentColor;
    -webkit-mask: var(--wf-icon-cal-plus) no-repeat center / contain;
    mask: var(--wf-icon-cal-plus) no-repeat center / contain;
}

/* Download action: a format icon stacked above its short label, replacing the
   text file chips in the report archives. One reusable button per format; the
   glyph is selected with a modifier that sets --wf-dl-icon.

   Role 4, naked text (docs/lankar-och-knappar.md): the surrounding doc list
   already says these are clickable, so rest carries no colour of its own —
   and never Purple dawn, which role 1 owns as its one identity mark. Leaving
   colour unset here hands the ink to the generic `a` rule, the same full-ink
   rest state as .wf-mega-hub and the card-title links: nothing dims this
   label the way the footer or breadcrumb chrome deliberately does, so there
   is no muting to lift on hover. The icon rides currentColor, so it follows
   whatever ink that leaves the label with. */
.wf-dllink {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    min-width: 3.5rem;
    font-family: var(--wp--preset--font-family--primary);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: var(--wf-tracking-label);
    text-align: center;
    text-decoration: none;
}

.wf-dllink::before {
    content: "";
    width: 1.9rem;
    height: 1.9rem;
    background-color: currentColor;
    -webkit-mask: var(--wf-dl-icon) no-repeat center / contain;
    mask: var(--wf-dl-icon) no-repeat center / contain;
}

/* One hover response: the underline. Colour is left unset here too, as on
   .wf-lang and .wf-mega-hub, so the generic `a:hover` rule is allowed through
   rather than fought off — Neutral ink to Deep sea is two near-blacks, not a
   second signal alongside the underline. */
.wf-dllink:hover { text-decoration: underline; }

.wf-dllink-pdf { --wf-dl-icon: var(--wf-icon-file-down); }
.wf-dllink-presentation { --wf-dl-icon: var(--wf-icon-slides); }
.wf-dllink-video { --wf-dl-icon: var(--wf-icon-video); }

/* Column-aligned download links: give every link the same fixed width (wide
   enough for the longest label, "Tilläggsprospekt") so the centred glyphs line
   up in straight columns across rows of varying label length. Scoped to the
   opt-in doclist modifier so the default ragged-but-compact layout used
   elsewhere (report and presentation archives) is untouched. */
.wf-doclist-dlcols .wf-dllink { width: 10.5rem; min-width: 0; }

/* Column-aligned download actions, the other case: an archive where every row
   draws from the same fixed set of actions, but some rows are missing one. Each
   action type gets a column of its own, so a row without a video leaves that
   column empty instead of sliding its presentation glyph into the place video
   keeps in every other row.

   The track width has to come from something the row cannot influence: every
   .wf-doc-links is a grid of its own, so content-sized tracks would be as wide
   as that row's own labels and no wider — which is the misalignment this is
   meant to cure. Hence a fixed outer measure cut into three equal parts. The
   measure is 26rem because the widest label, "Presentation", needs its third of
   it, and it yields to the row on narrow screens so the actions shrink with the
   page instead of pushing it sideways.

   The order is fixed sitewide — Video, Presentation, PDF — and the markup is
   written in that order too. The column assignment below only guarantees that a
   row missing one action still leaves its column empty; it must never be what
   puts the actions in order, because a grid that reorders on screen without
   reordering the DOM hands a keyboard or screen-reader user a different sequence
   from the one everybody else sees.

   Opt-in like .wf-doclist-dlcols above, and only for lists where no row repeats
   an action type: a type owns exactly one column, and two links of the same
   type would land on top of each other. */
.wf-doclist-actioncols .wf-doc-links {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    width: min(100%, 26rem);
}

.wf-doclist-actioncols .wf-dllink { min-width: 0; }
.wf-doclist-actioncols .wf-dllink-video { grid-column: 1; }
.wf-doclist-actioncols .wf-dllink-presentation { grid-column: 2; }
.wf-doclist-actioncols .wf-dllink-pdf { grid-column: 3; }

/* ---------- Meta, notes, prose ---------- */

.wf-meta {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: calc(var(--wp--preset--spacing--small) / 2) var(--wp--preset--spacing--small);
}

/* The date is the scanning anchor in every meta line, not only the press
   list it first served — press list, article cards, article and press
   detail headers, and the investment-case timeline all lead with it — so it
   is lifted out of the mono metaline's x-small grey wherever that line
   appears. In the press list it must also hold its own against the tag
   pills beside it, which win on weight and fill mass even though their type
   is smaller; docs/typografi.md ties mono to Light 300 and lets only size
   and colour carry weight, never bolding, so size is the one lever left and
   the date takes two steps up from x-small to answer the pills. Family and
   Light weight are still inherited from .wf-meta. */
.wf-meta time {
    font-size: var(--wp--preset--font-size--base);
    color: var(--wp--preset--color--main);
}

.wf-footnote, .wf-note, .wf-sourcenote {
    font-family: var(--wp--preset--font-family--monospace);
    font-size: var(--wp--preset--font-size--x-small);
    font-weight: var(--wp--custom--font-weight--light);
    color: var(--wp--preset--color--secondary);
    max-width: 80ch;
}

.wf-note {
    border: var(--wf-border-w) dashed var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--sm);
    padding: var(--wp--preset--spacing--small);
}

/* The document note (ADR 0018) is apparatus for the paper and for nothing else,
   so it is not drawn on screen at all: the reader who can follow the link gains
   nothing from its address written out, and the marker beside every such link
   would be sixteen numbers in a paragraph nobody is reading on paper. The block
   and its markers are given back in the print section at the end of this sheet,
   which is where their whole design lives.

   Hidden and not removed, because the block has to exist in the document for
   the print stylesheet to have something to show — this is one element in two
   media, not two elements.

   The address keeps its floor here rather than in that section, because a floor
   is a property of the string and not of the medium.
   `cdn.bequoted.com/media/1/88c94cc5` is 33 characters with no break
   opportunity in it, and an element's min-content floor is its longest
   unbreakable run: on a narrow page the address would run straight out through
   the margin. `anywhere` and never `break-word`, because only `anywhere` counts
   toward the min-content the floor is made of. */
.wf-endnotes, .wf-endnotemark { display: none; }
.wf-endnote-url { overflow-wrap: anywhere; }

/* The notice on a disclosure shown in a language version it was not published
   in (ADR 0009). Four fifths of the archive exists only in Swedish, and the
   fifteen that exist only in English get the same treatment the other way; the
   post is shown rather than hidden, and this is what tells the reader why the
   document that follows is not in the language they chose.

   It is product and not annotation, so it takes the running size and a solid
   rule rather than the quiet mono and dashed frame of a wireframe note: a
   reader who has just been handed a document in another language has to be
   told plainly, not in the smallest type on the page. It reads as one block
   with the headline above it, which is why the fill is the pills' Sea shell
   rather than a band of its own. */
.wf-langnotice {
    margin: var(--wp--preset--spacing--medium) 0;
    border-left: 3px solid var(--wp--preset--color--primary);
    border-radius: 0 var(--wp--preset--border-radius--sm) var(--wp--preset--border-radius--sm) 0;
    background: var(--wp--preset--color--tertiary);
    padding: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium);
    font-size: var(--wp--preset--font-size--small);
}

.wf-langnotice p { margin: 0; max-width: 64ch; }
.wf-langnotice p + p { margin-top: calc(var(--wp--preset--spacing--small) / 2); }

/* A source note directly after a framed block (panel, card/KPI grid,
   accordion) keeps clear of the frame above — the same gap in every such
   pairing, matching the chart panel's internal note spacing. */
.wf-panel + .wf-sourcenote,
.wf-cardgrid + .wf-sourcenote,
.wf-statgrid + .wf-sourcenote,
details + .wf-sourcenote {
    margin-block-start: var(--wp--preset--spacing--small);
}

/* Prose resuming after a KPI grid (the investment case's market section)
   needs more clearance than the default paragraph gap to read as a new
   thought rather than a caption to the grid. */
.wf-statgrid + .wf-measure { margin-block-start: var(--wp--preset--spacing--large); }

/* Mirror of the rule above on the leading edge: the intro paragraph gets the
   same large trailing clearance the following paragraph gets, so the KPI grid
   reads as evenly framed. Placed on the paragraph (not the grid) because the
   band's .wf-inner is a grid — its item margins do not collapse, so a margin on
   the grid would stack on the paragraph's own margin and overshoot. */
.wf-measure:has(+ .wf-statgrid) { margin-block-end: var(--wp--preset--spacing--large); }

/* Mirror on the other side: prose that introduces a card grid keeps a real gap
   from it, so the cards read as a set the paragraph leads into rather than as
   the paragraph's own next line. */
.wf-measure + .wf-cardgrid { margin-block-start: var(--wp--preset--spacing--medium); }

/* And the same gap on the trailing edge, for prose that closes a card grid
   rather than introduces one (the investment case's referral to the full risk
   disclosure, under the risk cards). Without it the paragraph reads as the
   last card's overflow instead of as the section's closing line. */
.wf-cardgrid + .wf-measure { margin-block-start: var(--wp--preset--spacing--medium); }

/* Extra clearance above a paragraph that introduces a fresh step after a fact
   panel (prospekt's "Så tecknar du" line), larger than the default paragraph
   gap so it reads as a new thought rather than a caption to the panel. */
.wf-measure-gap-lg { margin-block-start: var(--wp--preset--spacing--large); }

/* Local override: fine-print panels that read as running prose rather than
   tabular/numeric data (aktien/om's riskupplysning, prenumerera's person-
   uppgifter) stay sans-serif unlike the shared .wf-footnote default — and take
   the next size up, since these are paragraphs to be read rather than captions
   to be glanced at. x-small bottoms out at 12 px, which is too small for a
   risk disclosure someone is expected to actually read.

   The subscribe panel's personal-data line is the same sentence class as
   prenumerera's — consent copy standing beside the field it governs — and joins
   the rule here so one statement is set one way wherever it appears. It arrives
   from the opposite direction: it was the site's smallest text at 9.7 px in mono
   Light on navy, an x-height of 4.7 px carrying the only legally meaningful
   words on that panel. */
.wf-footnote.wf-footnote-sans,
.wf-subscribe-form .wf-footnote {
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--small);
}

/* The ingress is the one place the sans is pushed past Bold — it has to read
   as a different register from the running text beneath it, and since it
   shares that text's size and its muted ink, weight is the only axis left to
   separate them on. */
.wf-lead {
    font-size: var(--wp--preset--font-size--base);
    line-height: var(--wp--custom--line-height--body);
    color: var(--wp--preset--color--secondary);
    max-width: 64ch;
    font-weight: var(--wp--custom--font-weight--lead);
}

/* The brand's marker highlight: a Purple dawn stroke swept under the word. The
   text keeps its ink colour — the stroke sits behind the baseline, not behind
   the whole word — so the motif belongs on light grounds only, never in a dark
   band or hero.

   The design system draws it as the lower two-fifths of the line box, which is
   a marker stroke at the body and small-heading sizes its card demonstrates. On
   a display heading that same fraction is a 28 px slab of full-saturation
   accent, which stops reading as a mark and starts reading as a colour field.
   The stroke is therefore capped at a fixed fraction of the type size instead
   of the line box, so the motif keeps its weight as the heading grows.
   box-decoration-break keeps a mark that wraps drawing one stroke per line. */
.wf-mark {
    background: linear-gradient(var(--wp--preset--color--primary-alt), var(--wp--preset--color--primary-alt)) no-repeat 0 90% / 100% .26em;
    box-decoration-break: clone;
}

/* Quotes carry a vertical bar flush left of the text, as in the mockups. */
blockquote {
    margin-block: var(--wp--preset--spacing--medium);
    border-inline-start: calc(var(--wf-border-w) * 3) solid var(--wp--preset--color--border-dark);
    padding-inline-start: var(--wp--preset--spacing--medium);
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--light);
    font-style: italic;
    line-height: var(--wp--custom--line-height--snug);
    max-width: 44ch;
}

blockquote cite {
    display: block;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-style: normal;
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: normal;
    color: var(--wp--preset--color--secondary);
    margin-top: var(--wp--preset--spacing--small);
}

/* Arrow links ("Se alla … →") — sans per the typography rule; the arrow
   glyph travels in the link text. */
.wf-arrowlink {
    display: inline-flex;
    align-items: center;
    gap: .5em;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
    font-weight: var(--wp--custom--font-weight--medium);
    text-decoration: none;
}

/* An element inside the link is a flex item; the text around it is not.

   281 of the 294 arrow links hold nothing but text, which a flex container
   gathers into one anonymous item — a block box that breaks like any other
   paragraph, whatever it is given. The thirteen "Bakgrund till: <rubrik> →"
   links on article pages are the exception: the build marks the quoted
   disclosure's own language on it (tools/list-surfaces.mjs), and that span is
   an item of its own. Three items on a line that cannot wrap are laid end to
   end and the link is as wide as their sum — 403 px inside a 240 px paragraph
   at a doubled text size, on eight of the 71 article pages. `min-width: 0` is
   already on the span and helps nothing: what is too wide is the line, not the
   item standing on it. At the browser's own text size the same three items
   stood as three columns of a table nobody drew — the label stacked word over
   word on the left, the headline in a column beside it, the arrow off in a
   third — which is the older and quieter half of the same mistake.

   `display: contents` takes the box away and keeps the marking: the span stays
   in the tree with its `lang` where the screen reader reads it, and the text
   inside it rejoins the run on either side, so these thirteen become the one
   anonymous item the other 281 always were. Letting the line wrap instead also
   fits inside the viewport, but it wraps between the items rather than between
   the words — the label alone on one line, the arrow alone on the next — and it
   does that at every text size. */
.wf-arrowlink span { display: contents; }

/* ---------- Panels ---------- */

.wf-panel {
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    padding: var(--wp--preset--spacing--medium);
}

.wf-panel-tint { background: var(--wp--preset--color--tertiary); }

/* ---------- Video overlay ---------- */

/* A recording plays over the archive rather than sending the reader to YouTube
   (decided 2026-08-09). Built by wireframes/video.js on first use, so the
   markup below exists only once a reader has asked for it.

   A native `dialog`: the focus trap, Escape, the inert background and the top
   layer come with `showModal()`, and each is something a hand-rolled overlay
   gets subtly wrong. `max-width` is read off the wide measure so the player
   lands on the same grid as the page under it. */
.wf-videomodal {
    width: min(100% - var(--wp--preset--spacing--medium) * 2, 60rem);
    max-height: calc(100dvh - var(--wp--preset--spacing--large) * 2);
    padding: var(--wp--preset--spacing--medium);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    box-shadow: var(--wf-lift-shadow);
    color: var(--wp--preset--color--main);
    overflow: auto;
}

.wf-videomodal::backdrop { background: rgba(20, 17, 31, .62); }

.wf-videomodal-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--medium);
    margin-bottom: var(--wp--preset--spacing--small);
}

.wf-videomodal-title {
    margin: 0;
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--light);
    letter-spacing: normal;
    text-wrap: wrap;
}

/* Square, so the glyph has the 24 px pointer target the rest of the site keeps
   (docs/lankar-och-knappar.md) without a box that reads as a primary action. */
.wf-videomodal-close {
    flex: none;
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--full);
    background: transparent;
    color: inherit;
    font-size: var(--wp--preset--font-size--small);
    line-height: 1;
    cursor: pointer;
}

.wf-videomodal-close:hover { background: var(--wp--preset--color--tertiary); }

/* The frame keeps 16:9 whatever the film is, so opening one does not resize the
   overlay around it while the player loads. */
.wf-videomodal-frame {
    aspect-ratio: 16 / 9;
    background: var(--wp--preset--color--primary);
    border-radius: var(--wp--preset--border-radius--sm);
    overflow: hidden;
}

.wf-videomodal-frame iframe { display: block; width: 100%; height: 100%; border: 0; }

.wf-videomodal-meta {
    margin: var(--wp--preset--spacing--small) 0 0;
    font-family: var(--wp--preset--font-family--monospace);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
}

.wf-videomodal-meta:empty { display: none; }

/* A framed panel following a card grid (kontakt's Certified Adviser box under
   the contact cards) keeps a medium gap from the grid above. */
.wf-cardgrid + .wf-panel { margin-block-start: var(--wp--preset--spacing--medium); }

/* Two panels stacked in the same band with nothing else to separate them (the
   utveckling section's chart, then its table, directly underneath) would
   otherwise touch borders with no gap at all. Kept narrow to this one
   pairing rather than the generic `.wf-panel + .wf-panel`: that broader
   selector also catches panels that sit side by side — the widget row's
   chart-plus-fact-box pairing, the risk section's three-up card grid — where
   a top margin would misalign them instead of adding a seam. */
.wf-chartpanel + .wf-tablepanel { margin-block-start: var(--wp--preset--spacing--medium); }

.wf-panel-dark {
    background: var(--wp--preset--color--primary);
    border-color: var(--wp--preset--color--primary);
    color: var(--wp--preset--color--base);
}

.wf-panel-dark a { color: var(--wp--preset--color--base); }

/* ---------- Grids ---------- */

.wf-cardgrid, .wf-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--wf-card-min), 100%), 1fr));
    gap: var(--wp--preset--spacing--medium);
    align-items: stretch;
}

/* A neighbour-sized image has to know whether it still has a neighbour, and only
   the row itself knows: auto-fit folds on the row's own width, not the
   viewport's. Making the row a query container lets the image ask. */
.wf-cols { container-type: inline-size; }

/* A lone card has no sibling to size it, so the auto-fit track hands it the
   whole row and it reads as a banner rather than as a member of the card
   family. 24rem is roughly the width a card takes in a three-up grid at the
   wide measure, which keeps the family's silhouette. Scoped to :only-child,
   so a grid with more than one card is untouched, and the card still
   collapses below the cap on narrow screens. */
.wf-cardgrid > :only-child { max-width: 24rem; }

/* The same cap, made a rule of the cross-link card rather than of how many of
   them happen to stand in a row (Thomas 2026-08-10). A link card is a fixed
   promise — a heading, a line, an arrow — and it does not become a better
   promise for being 614 px wide, which is what two of them in the wide measure
   were.

   The cap is on the *track* and not on the card, which is the difference
   between a rule that survives a grid and one that does not: a `max-width` on
   the card leaves the track its full share and the gap opens inside it, so the
   cards drift apart while each looks right. Below the cap nothing changes —
   the track floor is untouched, so a narrow screen still gets one card filling
   the column. `justify-content` is what keeps the capped row from being spread
   across the measure once the tracks stop stretching.

   **26rem is measured, not chosen for its roundness.** Counted over the tree:
   18 rows of three, 16 of two, one of six and one lone card. At the 1260 px
   wide measure a three-up card is 398 px and a six-up 291 px, and 26rem = 416
   sits just above the widest of them — so the eighteen rows and the six-up row
   are pixel for pixel what they were, and only the sixteen two-up rows and the
   lone card, the ones that were reading as banners, move. A cap of 24rem would
   have taken 14 px off all eighteen and left every three-up row with a ragged
   44 px of slack at the measure's right edge. */
.wf-cardgrid:has(> .wf-cross) {
    grid-template-columns: repeat(auto-fit, minmax(min(var(--wf-card-min), 100%), 26rem));
    justify-content: start;
}

/* Fixed-count card grid: 3 columns on desktop, 2 on tablet, 1 on mobile.

   Every track is written minmax(0, 1fr) rather than 1fr, which is the same
   thing at any width where the cards fit and a different thing at every width
   where they do not. A bare 1fr carries an automatic minimum of min-content,
   so the track never falls below the widest word, figure or padded box inside
   it — a floor nobody declared and nothing overrides. It is invisible while
   the band is the larger of the two, and at a doubled text size it stops being
   invisible: min-content doubles with the type and the band does not, so the
   floor becomes the width and the card walks out of the page. The explicit 0
   says what a fraction was always meant to say — this track takes what is
   left, and what is left is never negative. */
.wf-cardgrid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

@media (max-width: 60rem) {
    .wf-cardgrid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 40rem) {
    .wf-cardgrid-3 { grid-template-columns: minmax(0, 1fr); }
}

.wf-statgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--wf-stat-min), 100%), 1fr));
    gap: var(--wp--preset--spacing--small);
}

/* Two-column document body with sidebar (document pages, FAQ). */
.wf-docbody {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(min(16rem, 100%), 19rem);
    gap: var(--wp--preset--spacing--large);
    align-items: start;
}

/* Stacked, the fact box goes above the document and not below it (Thomas
   2026-08-10). Beside the text it is a companion the eye reaches whenever it
   likes; under fourteen paragraphs of articles of association it is a footnote
   nobody scrolls to, and what it holds — company name, registration number,
   share capital, financial year — is what most readers came for. The source
   order stays document-then-sidebar, because that is the reading order a screen
   reader and a print stylesheet want; only the visual order turns. That normally
   costs a divergence between the eye's order and the tab order, and here it
   costs nothing: the box holds labels and values and not one focusable element.
   Put a link back into it and this rule has to be weighed again. */
@media (max-width: 60rem) {
    .wf-docbody { grid-template-columns: minmax(0, 1fr); }
    .wf-docbody > .wf-sidebar { order: -1; }
}

.wf-sidebar { display: flex; flex-direction: column; gap: var(--wp--preset--spacing--medium); }

/* A document header panel sitting directly above the two-column body (the
   bolagsordning fulltext) needs a deliberate gap; the grid provides none. */
.wf-panel + .wf-docbody { margin-block-start: var(--wp--preset--spacing--large); }

/* The prose column's first heading drops its top margin so it aligns with the
   sidebar's first panel — the gap above the body comes from the rule above,
   applied equally to both columns rather than only pushing the prose down. */
.wf-docbody .wf-prose > :first-child { margin-top: 0; }

/* ---------- Cross-link cards (länkkort) ---------- */

.wf-cross {
    display: flex;
    flex-direction: column;
    gap: var(--wp--preset--spacing--medium);
    min-height: 11rem;
    padding: var(--wp--preset--spacing--medium);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    text-decoration: none;
}

/* Only the link form gets a hover; the numbered variant below is static. The
   hover itself is the shared card lift further down. */

/* Named variant: a non-link cross card carrying the bento's mono index atom
   instead of the corner arrow (the investment case's value-driver cards). */
.wf-cross-numbered { cursor: default; }

/* The arrow is drawn as a mask so its head sits exactly in the box's
   top-right corner with equal clearance above and to the right. The design
   pass must pick the icon-library glyph with the same corner-touching
   geometry (Phosphor's ph-arrow-up-right); a text glyph like "↗" sits low in
   its em box and makes the card's top spacing read asymmetric. */
.wf-cross-arrow {
    align-self: flex-end;
    flex: none;
    display: inline-block;
    width: 1em;
    height: 1em;
    overflow: hidden;
    text-indent: 200%;
    white-space: nowrap;
    background-color: currentColor;
    -webkit-mask: var(--wf-arrow-ne) no-repeat center / contain;
    mask: var(--wf-arrow-ne) no-repeat center / contain;
    color: var(--wp--preset--color--secondary);
}

.wf-cross-body { margin-top: auto; }
.wf-cross h3 { font-size: var(--wp--preset--font-size--medium); font-weight: var(--wp--custom--font-weight--regular); margin-bottom: calc(var(--wp--preset--spacing--small) / 2); }
.wf-cross p { font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--secondary); margin: 0; max-width: 44ch; }

/* Equalise the grid rows so wrapped bento cards share one height. */
.wf-cardgrid:has(.wf-bento) { grid-auto-rows: 1fr; }

/* Bento quick links: mono index top-left, arrow mark top-right, title +
   description anchored at bottom. The card is the positioning context so the
   arrow can be pulled out of flow into that corner. */
.wf-bento {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--medium);
    min-height: 9rem;
    padding: var(--wp--preset--spacing--medium);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    text-decoration: none;
}

.wf-bento-num {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
}

/* Lifted out of the title's flow and pinned to the card's own corner, inset
   by the card's own padding so it sits flush with the border on both edges —
   the link-card mark belongs at the top-right of the card, not trailing the
   title down at the bottom. */
.wf-bento .wf-cross-arrow {
    position: absolute;
    top: var(--wp--preset--spacing--medium);
    right: var(--wp--preset--spacing--medium);
}

.wf-bento-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--small);
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--regular);

    /* A bento title is a span, so the heading rule that breaks long Swedish
       compounds never reaches it — and its text is an anonymous flex item beside
       the arrow, which has no selector to give a min-width to. `anywhere` is the
       one wrap value that lowers a box's min-content width as well as allowing
       the break, so it settles both at once. */
    overflow-wrap: anywhere;
}

/* Nav pages extend the bento with a short description under the title. */
.wf-bento-body {
    display: grid;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    margin-top: auto;
}

.wf-bento-desc {
    font-size: var(--wp--preset--font-size--small);
    color: var(--wp--preset--color--secondary);
    max-width: 44ch;
    margin: 0;
    /* Same reason as the title: a grid item will not shrink below the longest
       word it holds, and "offentliggöranden" is longer than a three-up card. */
    min-width: 0;
    overflow-wrap: anywhere;
}

/* ---------- Report cards ---------- */

/* Compact horizontal report teaser (thumbnail beside text) for pages that only surface a couple of reports. */
.wf-reportrow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wp--preset--spacing--medium);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    padding: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium);
    text-decoration: none;
}

/* The row is a cover, a title and a mark on one line, and it stays that way
   until the line cannot hold all three. Every part of it is sized in rem — the
   64 px cover is 128 px at a doubled text size, against a content box that is
   still 214 px — so a row held to one line had nowhere to put the title but
   past the card's own edge. Wrapping gives the title and its mark the second
   line and keeps the cover where it was: the same three parts, in the same
   reading order, on a card that has grown a row instead of losing its end. The
   title's basis is what decides when that happens, and it is deliberately
   short — 5rem is under the width the title takes at the scale's own root on
   the narrowest screen the site serves, so the row still holds all three at
   rest and breaks only once the type has outgrown it. */
.wf-reportrow > div { flex: 1 1 5rem; min-width: 0; }

.wf-reportrow .wf-img { flex: none; width: 4rem; margin: 0; border-radius: var(--wp--preset--border-radius--sm); }
.wf-reportrow h3 { font-size: var(--wp--preset--font-size--base); font-weight: var(--wp--custom--font-weight--medium); margin: 0; }
.wf-reportrow .wf-meta { margin: 0; }

/* Download mark: the row links straight to the PDF, as the mockups' PDF chips
   do. Same file-with-down-arrow glyph as the archives' PDF download action;
   the design pass swaps it for Phosphor's `ph-file-arrow-down` (the format
   stays in the meta line's "· PDF"). */
.wf-dlmark {
    flex: none;
    margin-left: auto;
    width: 1.9rem;
    height: 1.9rem;
    background-color: currentColor;
    -webkit-mask: var(--wf-icon-file-down) no-repeat center / contain;
    mask: var(--wf-icon-file-down) no-repeat center / contain;
    color: var(--wp--preset--color--secondary);
}

/* The mark says what the card leads to, and a recording is not a file. It has to
   stand after the block above, which sets its image through the shorthand. */
.wf-dlmark-video {
    -webkit-mask-image: var(--wf-icon-video);
    mask-image: var(--wf-icon-video);
}

/* ---------- Hover lift (cards · buttons) ---------- */

/* One hover affordance, shared by every link card and button in the
   wireframes: the element lifts a hair off the band on a soft shadow, and
   nothing about it is repainted.

   Repainting the ground failed on two counts. On a tinted band a card hovered
   into the band's own colour and dissolved into it (Sea shell card on a Sea
   shell band — the start page's quick links, the FAQ's cross cards). And on
   the inverted card the generic `a:hover` reached past the card's own class
   and pulled the label to Deep sea over near-black. A lift touches no colour,
   so it works on every ground and cannot break a contrast ratio. It is also
   the only response left standing: it costs no palette slot, and a fill shift
   is spoken for — `aria-pressed` on a toggle already means filled.

   Everything below owns a surface — a filled or outlined shape, a bordered
   box — so the shadow reads as that surface stepping off the band. Bare text
   (the menu toggle, "visa mer", download links, breadcrumbs and the like)
   owns no surface to lift; it underlines on hover instead, alongside the
   prose link it shares that response with. */
.wf-bento,
a.wf-cross,
.wf-reportrow,
.wf-mega-card,
.wf-chip,
.wf-btn,
.wf-pagination :is(a, button),
.wf-toggles button,
form button,
.wf-form-stacked button {
    transition: transform .18s ease, box-shadow .18s ease;
}

.wf-bento:hover,
a.wf-cross:hover,
.wf-reportrow:hover,
.wf-mega-card:hover,
.wf-chip:hover,
.wf-btn:hover,
.wf-pagination :is(a, button):hover,
.wf-toggles button:hover,
form button:hover,
.wf-form-stacked button:hover {
    transform: translateY(-2px);
    box-shadow: var(--wf-lift-shadow);
}

/* Each of the three original cards holds its text colour through the hover,
   so the generic `a:hover` cannot recolour a label the card has already set.
   The mega-card is an `<a>` too and needs the same hold. The rest of the
   shared list keep the hover colour they already had — the lift adds no
   colour of its own, so none of those declarations needed to move. */
.wf-bento:hover,
a.wf-cross:hover,
.wf-reportrow:hover,
.wf-mega-card:hover { color: var(--wp--preset--color--main); }

/* Readers who ask for less motion keep the shadow and lose the travel. */
@media (prefers-reduced-motion: reduce) {

    .wf-bento,
    a.wf-cross,
    .wf-reportrow,
    .wf-mega-card,
    .wf-chip,
    .wf-btn,
    .wf-pagination :is(a, button),
    .wf-toggles button,
    form button,
    .wf-form-stacked button { transition: none; }

    .wf-bento:hover,
    a.wf-cross:hover,
    .wf-reportrow:hover,
    .wf-mega-card:hover,
    .wf-chip:hover,
    .wf-btn:hover,
    .wf-pagination :is(a, button):hover,
    .wf-toggles button:hover,
    form button:hover,
    .wf-form-stacked button:hover { transform: none; }

}

/* ---------- Focus ring (WCAG 2.2 AA · 2.4.7 and 1.4.11) ---------- */

/* The rest state answers "can I click here?" and the hover answers "am I on
   it?" (docs/lankar-och-knappar.md). The keyboard asks a third question that
   those rules explicitly neither answer nor excuse — where am I now? — and
   until this section existed three elements answered it on purpose: the two
   chart plots and the table wrapper, each a plain box made focusable by hand
   and so given a ring by hand. Everything else fell through to the user
   agent's default. Nothing suppressed a ring on anything clickable — 2.4.7 was
   met the whole time, and the single `outline: none` this sheet now carries is
   on `<main>`, which is not a control (see the end of this section). What was
   missing is that the ring was drawn to the browser's taste rather than the
   system's, and it landed on the Purple dawn CTA, the navy footer and the
   megamenu's cards with none of them having a say in it.

   One ring now, for everything the keyboard can land on. It is the plots' ring
   generalised rather than a new one: Purple dawn, two hairlines wide, standing
   two hairlines off the element so the gap shows the ground on both sides of
   the stroke. That is what makes the ring measurable — its neighbour is the
   band it sits on, not whichever border, fill or photograph the element itself
   happens to carry.

   Outline and nothing else. An outline takes no space, so no ring shifts a row
   or reflows a table; it costs no box-shadow, which the lift has already spent
   on every element that owns a surface; and it is the one property a
   forced-colours theme keeps after it has thrown away every colour in this
   sheet.

   Focus does not borrow the hover's vocabulary. Nothing here lifts, underlines
   or repaints: a keyboard that lands on a card must not be told the pointer is
   on it, and an element that both lifted and ringed would be the two-signal
   hedge the one-response rule exists to prevent. The two channels can be lit
   at once — tab to a button the mouse happens to be resting on — and they stay
   legible together precisely because they share no property. */
:focus-visible {
    outline: var(--wf-focus-w) solid var(--wf-focus-ring);
    outline-offset: var(--wf-focus-w);
}

/* Purple dawn holds against every light ground the system lays: 4.06:1 on
   white, 3.44 on Sea shell, 3.41 on Frost smoke — all clear of the 3:1 that
   1.4.11 asks of a non-text indicator. Over the hero photograph it holds
   against nothing. The scrim guarantees a floor, not a ceiling: Deep sea at
   .60 alpha over the brightest image a page could carry leaves a ground of
   #747488, and Purple dawn on that is 1.12:1 — the ring is simply not there.
   No purple can be made to work there, so the ground flips the ring to white
   instead, which is the move --wf-numeral-color already makes a few hundred
   lines up and for the same reason.

   The dark bands and the navy footer come along, though purple would pass on
   them: 4.22:1 on Deep sea is a ring you have to go looking for, and white on
   the same ground is 17.6:1. A pass on paper is not the point of a focus ring.
   The header takes the flip with its overlay state, because that state is
   exactly when it lies over the photograph — when the menu opens or the page
   scrolls, the attribute goes and the ring returns to purple along with the
   header's own ink. */
.wf-band-dark,
.wf-panel-dark,
.wf-herofull,
.wf-kpi-dark,
.wf-sitefooter,
.wf-siteheader[data-overlay] {
    --wf-focus-ring: var(--wp--preset--color--base);
}

/* The one element that is focusable without being a control: `<main>`, made so
   by the skip link below purely as a place to land. It is the ring's own logic
   that excuses it rather than an exception to it — the ring answers *where am
   I now*, and a rectangle drawn around the entire page answers nothing. All
   the reader sees of it is a line under the header and two at the window's
   edges, which reads as a frame around the site rather than as a position in
   it. The ring stays on the link that did the jumping, which is the element
   the keyboard was actually on; 2.4.7 asks after interface components, and a
   page's content region is not one. This is the sheet's only `outline: none`,
   and it is spent here so that the skip link can move focus at all — the
   alternative is not to move it, which costs a screen reader the whole point
   of the link. */
main[tabindex="-1"]:focus { outline: none; }

/* ---------- Skip link (WCAG 2.2 A · 2.4.1) ---------- */

/* The bypass the keyboard needs and the pointer never sees. It stands off the
   top of the screen until it takes focus, then drops into the header band at
   the gutter — the same line the wordmark starts on, so it arrives where the
   eye already is rather than in a corner of its own.

   Hidden by travel and not by `.wf-visually-hidden`: a clipped box cannot slide
   back into view, and a skip link that is only in the accessibility tree is a
   link the sighted keyboard user cannot follow. It is moved with `translate`
   rather than `transform`, which leaves `transform` free for the hover lift
   .wf-btn already brings — the two would otherwise overwrite each other, and
   the lift is the response docs/lankar-och-knappar.md gives role 3.

   It is dressed as the header's CTA, and for that button's reason: this one
   also sits at nav size, where white on Purple dawn (4.06:1) is AA for large
   text only. Navy clears AA at any size. */
.wf-skiplink {
    position: fixed;
    inset-block-start: 0;
    inset-inline-start: var(--wf-gutter);
    z-index: 70;
    translate: 0 -150%;
    font-size: var(--wp--preset--font-size--small);
    padding: .55em 1.2em;
    line-height: var(--wp--custom--line-height--snug);
    background: var(--wp--preset--color--primary);
    border-color: var(--wp--preset--color--primary);
}

/* `:focus` and not `:focus-visible`: nothing but a keyboard ever reaches this
   link, so the heuristic has nothing to decide, and a browser that guessed
   wrong would hide the element under the reader's own cursor. */
.wf-skiplink:focus { translate: 0 var(--wp--preset--spacing--small); }

/* ---------- Pointer target floor (WCAG 2.2 AA · 2.5.8) ---------- */

/* Every control below stood under 24 CSS px on its short side at phone widths:
   the footer's link row at 17 px, the breadcrumbs at 16, the press archive's
   genre pills at 19.5. None of them is a link inside a sentence, so the
   success criterion's inline exception does not cover them — they are
   standalone rows and controls, and the archive's pills are the primary way to
   filter press releases on a phone.

   The floor is 24 px, not the 44 px of 2.5.5 (AAA), and that is a measured
   decision rather than a modest one. PRODUCT.md's audience — older private
   investors reading dense material on a phone — argues for the larger figure,
   but every failing control sits in a row whose neighbour is 8–10 px away: the
   footer's link rows, the breadcrumb trail, the wrapped pill rows in the filter
   frame. A 44 px box would swallow that gap and overlap the box beside it, so a
   finger landing between two pills would activate whichever one paints last.
   That trades a small target for an ambiguous one, which is the worse failure.
   Inside this design's row rhythm the gaps cap the reachable figure at about
   29 px; going beyond it means opening up the rows themselves, which is a
   change to the vertical rhythm and belongs to the production build's own
   decision, not to a fix that must leave every page's layout where it stands.

   Nothing here grows the type. Which mechanism a control takes depends on what
   its box is, and the first two leave the drawn form exactly where it stood —
   the hit area grows and the design does not move:

   1. A box that draws nothing — a bare text link, an arrow link, "visa mer" —
      simply takes the padding. On an inline box vertical padding never enters
      the line box, so those rules need nothing else; on the flex and
      inline-flex boxes an equal negative margin hands back the height the
      padding took, leaving the text on the same baseline it sat on before.
   2. A box that is drawn — the filter and period pills, the switch track —
      cannot take padding without growing the fill, and the pill's geometry is
      the metadata pill's geometry (docs/lankar-och-knappar.md dresses the
      filter as what it filters). Those take a transparent extension centred on
      the control instead: it is part of the control for hit-testing, so the
      target is 24 px while the pill is still exactly the pill.
   3. A box that is drawn but sized by its own text — the pagination's numbers
      — is the one case where the ink itself is what falls short, and there the
      box takes a floor of its own. There is no geometry to protect: the row's
      boxes are already every width their numbers make them, so the narrow one
      growing into the floor is the row becoming more even rather than less.

   The list titles are here for the same reason as the rows: a card title is
   bare text (docs/lankar-och-knappar.md, role 4), so its box is only as tall as
   its own type, and a single-line article title measured 23 px.

   Links inside a sentence keep their size on purpose — the prose links, the FAQ
   answers, the subscribe page's consent line. That is the criterion's own
   inline exception, and it is the right call on the merits too: their height is
   set by the line-height of the text around them, so padding would reach into
   the line above and take the clicks meant for whatever sits there. */

.wf-footlinks a,
.wf-fineprint a,
.wf-contactlines a,
.wf-langnotice a,
.wf-lonelink a,
.wf-sourcenote a,
.wf-articlecard :is(h2, h3) a,
.wf-pressitem :is(h2, h3) a {
    padding-block: var(--wf-target-pad);
}

/* The language notice's link is in the list above because the inline exception
   does not reach it. 2.5.8 exempts a target whose size is constrained by the
   line-height of the *non-target* text around it, and there is none: the link
   is the whole of its paragraph (ADR 0009, and tools/list-surfaces.mjs writes
   the two paragraphs), which makes it a standalone navigational control that
   happens to be set in prose rather than a link inside a sentence. It drew
   19 px at 320 and 22 px at 1440 — the spread is the band's fluid small size,
   not a difference in kind.

   `.wf-lonelink` is that same finding in the one place it is not ours to
   write: the old site's own articles, where a bare address, a video's URL or a
   line like "Läs artikeln här" stands as the whole of its paragraph. 26 blocks
   on 18 article pages do — the disclosure corpus writes the shape once and
   with a full stop after it, which is the case the mark is withheld from,
   below. They measured 22–23 px —
   the height varies with the fluid type scale, so the same link failed at some
   widths and passed at others. **No selector reaches the shape**:
   `p > a:only-child` matches a paragraph that also holds words, and CSS has no
   selector for a text node. The alternative was padding every prose link, and
   that was measured rather than argued: over the 288 detail pages it puts 17
   pairs of adjacent-line links into an overlap of 1,9–7,0 px, where the point
   between them answers to whichever paints last — the ambiguous target this
   section rejects at 44 px, reached from a third direction. So the shape is
   named where it is known, by tools/brodtext.mjs, which argues the departure
   in its own header. Where the source put words on the link's own line —
   "E-post: " before an address, or the `[embed]` its editor left around a
   video's URL — the mark is not written and the link keeps its size: that is
   the second half of the inline exception, and the identical contact block
   written as one paragraph with breaks is exempt already. Nor is it written
   where the sentence's own full stop stands outside the link, which is where
   the mark would move the text rather than only its box: the grown link fills
   its line and the stop drops to a line of its own beneath it. Three
   paragraphs in the corpus are that shape and keep their size. */

/* An inline box's vertical padding is drawn but not necessarily hit: it hangs
   outside the line box, into the space the neighbouring line stands in, and
   whichever of the two paints last takes the point. So mechanism 1 only pays
   out where the row above and below leave that space free. Measured, five of
   the seven selectors above do — the footer's and the fine print's rows carry
   their own spacing, and a card's title has a whole card under it. Two do not.

   The contact lines are a bare `ul` at the body's line-height, so consecutive
   rows stand 21 px apart while each link's box is 29 px tall. The boxes
   overlapped by 7 px, the padding bought nothing, and the phone numbers
   measured 21 px — the ambiguous target this section rejects at 44 px, arrived
   at from the other direction.

   The notice's link is the same finding in a milder form: measured on its own,
   the padding does reach the floor — 27 px at 320 and 30 px at 1440 — but it
   reaches it by hanging in the 4 px its two paragraphs stand apart, which is
   `spacing--small / 2` at 320 px and not a decision about targets at all. A
   floor that holds because two unrelated tokens happen to line up is a floor
   that is lost silently the day one of them moves, and the focus ring, drawn
   two hairlines outside the box, is out there with it — over the line above.

   Becoming an inline-block is what makes the padding real: the box enters the
   flow, so it pushes the next row down by exactly the padding rather than
   reaching into it, and the row pitch grows to hold it. Nothing else changes —
   the link still shrinks to its own text, still carries no mark at rest and
   still answers hover with an underline (docs/lankar-och-knappar.md, role 4),
   and the notice stays the quiet band its own rule describes, eight pixels
   taller.

   It settles a second failure in the same move. An inline box hands back one
   rectangle per line, so a link that wraps is a target per line: an address is
   one long token and breaks mid-word (see .wf-contactlines below), leaving a
   20 px fragment on its second line at 768 px, and the notice's link wraps at
   320 px, leaving 95,7 × 19 px on its own — neither of which the sweep sees,
   since it measures the first line box. An inline-block wraps inside a single
   box, so the target is the whole link at the column's width however many lines
   it takes. */
.wf-contactlines a,
.wf-langnotice a,
.wf-lonelink a { display: inline-block; }

/* The box the flow now holds states a minimum width, and that minimum is the
   longest run it cannot break — so the link stops being able to size itself
   below its longest word. At a doubled text size on a 320 px screen that word
   is "språkversionen" at 191 px inside a 141 px paragraph, and the sentence ran
   out through the band's own fill. The contact lines met this the moment they
   became inline-blocks too and answer it from their container (.wf-contactlines
   below); the notice answers it here, with the same value and for the reason
   stated there and on the headings — `break-word` rescues the ink and leaves
   the floor where the unbroken word put it, and only `anywhere` counts toward
   the minimum the floor is made of.

   The lone links need it most of all: the paragraph that is nothing but a link
   is very often a bare address, and the longest of them is 130 characters with
   no space in it. */
.wf-langnotice a,
.wf-lonelink a { overflow-wrap: anywhere; }

.wf-arrowlink,
.wf-morebtn,
.wf-mega-bottom a {
    padding-block: var(--wf-target-pad);
    margin-block: calc(-1 * var(--wf-target-pad));
}

/* The hub sets the gap down to its own link column, so only its top margin is
   the padding's counterweight. */
.wf-mega-hub {
    padding-block: var(--wf-target-pad);
    margin-block: calc(-1 * var(--wf-target-pad)) calc(var(--wp--preset--spacing--small) - var(--wf-target-pad));
}

/* The breadcrumb links are the one bare-text case that also falls short across:
   "Hem" is 22 px wide. Horizontal padding would push the separators along and
   move the trail, so the crumbs take the same transparent extension the drawn
   controls take, which reaches the floor on both axes and moves nothing.

   The extension is positioned against the control itself; the switch already
   stands relative for its own knob and is not repeated here. */
.wf-crumbs a,
.wf-toggles button {
    position: relative;
}

.wf-crumbs a::after,
.wf-toggles button::after,
.wf-switch input[type="checkbox"]::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    translate: -50% -50%;
    width: max(100%, var(--wf-target-min));
    height: max(100%, var(--wf-target-min));
}

/* A control sized by its own text is sized by its narrowest possible content,
   and the pagination's step to page one carries the narrowest glyph the
   typeface has. Text plus padding drew 23,0 px at 320 px where "2" drew 25,5
   and "22" drew 33,3 — the box was authored by the digit, and the digit was a
   "1". Mechanism 2 would answer the hit test and leave the ink a pixel short,
   which is the wrong trade for a box whose width is content-derived to begin
   with: no geometry here is being protected, so the floor goes on the box and
   the one number that needs it grows into the width its neighbours already
   have.

   Both selectors take it, for the reason the pagination section gives: the link
   and the spent end arrow must draw the same rectangle, and a floor on one of
   them alone would be exactly the step in the row's top edge that rule exists
   to prevent. Naming the floor as a length is the move --wf-cta-large-text
   already makes — 24 px is a success criterion's constant, not a step on any
   scale in this sheet — and it binds only where a number is narrower than it,
   which above the browser's default text size is nowhere.

   The ink is centred because the box is no longer purely its content's: a digit
   that has grown into the floor would otherwise stand against its left padding,
   and a box that has not grown cannot tell the difference. */
.wf-pagination :is(a, button),
.wf-pagination-end {
    min-width: var(--wf-target-min);
    text-align: center;
}

/* ---------- KPI panels (bottom-anchored mono numerals) ---------- */

.wf-kpi {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    min-height: 9rem;
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--tertiary);
    /* The panel owns its text colour to match its own light background, so the
       numerals stay legible when the card sits on a dark band (the case page's
       market section) where the band otherwise cascades a light text colour —
       and for the same reason it restores the light ground's numeral colour. */
    color: var(--wp--preset--color--main);
    --wf-numeral-color: var(--wp--preset--color--primary-alt);
    padding: var(--wp--preset--spacing--medium);
}

/* A display figure and its unit are a single typographic unit and must never
   be split across lines. Shared with the share price below. */
.wf-kpi-value {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    /* One step down from the H1 grade: at x-large a four-character unit like
       "MSEK" cannot fit a four-up KPI row, and a card figure shouldn't carry
       the page's H1 weight anyway. */
    font-size: var(--wp--preset--font-size--large);
    line-height: var(--wp--custom--line-height--none);
    color: var(--wf-numeral-color);
    margin: 0;
    /* The nowrap that used to sit here held the figure and its unit on one
       line. It was protecting the wrong seam. What must never break is the
       figure — "48,7" arriving as "48," over "7" is a different number — and
       the figure is one token, so nothing can break it whatever this property
       says. The seam the rule actually governed was the space before the unit,
       and at a doubled text size on a 320 px screen the pair is wider than the
       panel however narrow the panel's padding gets. Held to one line it left
       the page; allowed to break, "MSEK" drops under its figure and the panel
       still reads as one value, which is what the rule was for. */
}

/* Display-metric unit/suffix (%, MSEK, SEK …): half the numeral's grade on the
   same baseline, staying mono, per the brand's KPI pattern. Shared by the KPI
   values and the share price. */
.wf-unit {
    font-size: .5em;
    color: var(--wp--preset--color--secondary);
}

/* Stat captions read as sans SemiBold small labels in the brand's template. */
.wf-kpi-label {
    font-family: var(--wp--preset--font-family--primary);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: var(--wf-tracking-label);
    color: var(--wp--preset--color--secondary);
    margin: 0;
}

/* The first KPI panel inverts, echoing the mockups' KPI band. Purple dawn on
   navy is only 4.2:1, so the inverted panel's numerals go white instead. */
.wf-kpi-dark { background: var(--wp--preset--color--primary); border-color: var(--wp--preset--color--primary); color: var(--wp--preset--color--base); --wf-numeral-color: var(--wp--preset--color--base); }
.wf-kpi-dark .wf-kpi-value .wf-unit, .wf-kpi-dark .wf-kpi-label { color: color-mix(in srgb, var(--wp--preset--color--base) 55%, transparent); }

/* Variant with single-line labels, so the bottom-anchored figures align across
   the cards. It used to raise the track floor to 13rem to buy the room; the
   floor is now 14rem for every stat grid and already covers the longest label
   this variant carries, so only the nowrap is left to state.

   And the nowrap is stated as a preference rather than as a law, because the
   room it depends on is bought in rem and spent against the viewport. 14rem
   holds "Soliditet · Q1 2026" at the scale's own root; at a doubled one the
   label needs 284 px and the whole band on a small phone is 240. The alignment
   this variant exists for survives the label wrapping — the figures are
   bottom-anchored, so a second line pushes the label up rather than the figure
   down — and it does not survive the label leaving the card.

   The threshold is in rem, which is what makes one number serve both cases: a
   rem in a media query is the browser's own text size, so 15rem asks "is there
   room for this label", not "is this a phone". At the default root it is
   240 px and every supported viewport clears it, so nothing changes; at a
   doubled root it is 480 px, which is the width at which the band, less the
   panel's padding, first holds the 284 px the label then needs. */
@media (min-width: 15rem) {
    .wf-statgrid-oneline .wf-kpi-label { white-space: nowrap; }
}

/* ---------- Share overview row ---------- */

/* Three tracks — closing price, sparkline, share count — so the sparkline owns
   the space between the two figures instead of hanging off one of them, and all
   three stretch to a common height. Below the breakpoint the three would not fit
   side by side, so they stack. */
.wf-quoterow {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: stretch;
    gap: var(--wp--preset--spacing--medium);
    padding-bottom: var(--wp--preset--spacing--medium);
    border-bottom: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    margin-bottom: var(--wp--preset--spacing--medium);
}

/* The stacking itself. What stacking does to the three blocks' alignment is a
   separate matter and stands at the end of this section — after the rules it
   overrides, since a media query buys no specificity. */
@media (max-width: 56rem) {
    .wf-quoterow { grid-template-columns: 1fr; }
}

.wf-ticker {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    letter-spacing: normal;
    text-transform: uppercase;
    color: var(--wp--preset--color--secondary);
}

/* Same reasoning as .wf-kpi-value above: the figure is one token and needs no
   help staying whole, so the pair may fall to two lines rather than off the
   page. */
.wf-price {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-large);
    line-height: var(--wp--custom--line-height--none);
    color: var(--wf-numeral-color);
}

.wf-change {
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--small);
}

/* Two mirrored figures: closing price with spark to the left, share count to
   the right in the same style, both captions sharing one bottom line. */
.wf-quote-close,
.wf-quote-shares {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    align-self: stretch;
}

.wf-quote-shares {
    align-items: flex-end;
    text-align: right;
}

.wf-quote-figures {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wp--preset--spacing--medium);
}

/* Sparkline slot: a Purple dawn line drawn straight on the band. Unlike the
   chart placeholder it takes neither ground nor frame — a sparkline is a bare
   line in the flow of the text it belongs to, and a framed panel would promote
   it to a chart it is not. It sits between the two figures rather than beside
   one of them — belonging to neither — and stretches over the row's full height,
   from the top of the numerals to the baseline of the captions under them. The
   min-height is the floor for the narrow screens where the row wraps and the
   stretch has no taller sibling to measure against. **This holds only while the
   row is a row.** Stacked, the graph is anchored to the start edge and sized by
   the drawing's own ratio instead — see the row's own media query above, which
   is where all three of these declarations are overridden.

   The line itself is written into the page by the build out of
   docs/data/aktiekurs.json, never by this sheet: the series is refetched daily
   and a line living in a custom property would stand at whatever the day it was
   typed looked like. That is also why the colour is a token again — an inline
   SVG takes currentColor, where the data URI it replaced had the hex written
   out because a data URI is opaque to CSS. It carries its own aria-label naming
   the window and its extremes, so no visible caption is needed beside the
   figure that already gives the level. */
/* The graph and the line that says what it is. Both neighbours in the row are
   a figure over a mono caption anchored to the bottom of the band, and the
   sparkline was the one column with no caption at all — a line with no scale
   and no label, which a reader had to guess at (Thomas 2026-08-10). Same
   `flex-end` and the same half-step gap as the two columns beside it, so the
   three captions sit on one line across the row. */
.wf-spark {
    justify-self: center;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-items: center;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    width: 100%;
    max-width: 22rem;
    min-height: 4.5rem;
    color: var(--wp--preset--color--primary-alt);
}

.wf-spark-line {
    display: block;
    width: 100%;
    flex: 1 1 4.5rem;
    min-height: 4.5rem;
}

/* The caption is the row's meta voice and not the graph's colour: the line is
   drawn in Purple dawn through `currentColor` on the block above, and a caption
   in that same fill would read as part of the drawing. */
.wf-spark .wf-ticker { color: var(--wp--preset--color--secondary); }

/* Stacked, the row unmirrors. The three alignments — price at the left edge,
   graph centred, share count at the right — exist to make a horizontal row read
   as a spread, and one column turns that same spread into a staircase stepping
   down to the right (Thomas 2026-08-12). So all three take the start edge, and
   the graph takes the drawing's own 3:1 rather than whatever height the row
   happens to have: the sparkline's viewBox is 120 × 40, its box was 4,6:1, and
   `meet` centres a drawing in a box wider than its ratio — so the line floated
   inside its own slot with some 60 px of nothing on either side, and could not
   have lined up with the figures even once its box did. With the ratio pinned
   there is no letterbox left to float in, and graph, numerals and all three
   captions sit on one left edge. The 22rem cap stays: at full width the same
   ratio would put 235 px of height on a tablet.

   **This block must stay last in the section.** Every declaration in it
   overrides one written above at the same specificity, and a media query adds
   none — placed earlier it silently does nothing, which is how the first
   attempt at this fix passed every check and changed no pixel.

   It is layout and not print, but it is what the sheet prints: the sheet is
   laid out at 765 px, below this breakpoint, and ADR 0021 makes that identity
   the rule rather than a coincidence. */
@media (max-width: 56rem) {
    .wf-quote-shares { align-items: flex-start; text-align: left; }
    .wf-spark { justify-self: start; align-items: flex-start; }
    .wf-spark-line { flex: none; aspect-ratio: 3 / 1; min-height: 0; }
}

/* ---------- Hero (full-bleed image band at the very top of the page) ---------- */

/* The fixed header lies transparent on top; content anchors bottom-left as in
   the mockups' hero (image + toning overlay + white text). Each page supplies
   its own photograph through --wf-hero-image on the section element; the navy
   scrim above it carries the white text, and navy alone is the fallback where
   no photograph is set. The band owns its text colour rather than depending on
   .wf-band-dark being spelled out in the markup.

   Mind the path: a relative url() inside a custom property is resolved against
   THIS stylesheet, not against the page that sets the property. Every page
   therefore writes the same `url('images/…')`, regardless of how deep it sits —
   a page-relative `../images/…` silently 404s and leaves a flat navy band.

   The same path lives in a third place: the page's <link rel="preload">, which
   is what stops the band's photograph from waiting for the stylesheet to parse.
   That href is page-relative, so it reads `../images/…` where this property
   reads `images/…`. The two spellings must resolve to the same absolute URL or
   the browser fetches the file twice; verify in the network log, not by eye. */
.wf-herofull {
    display: flex;
    align-items: flex-end;
    min-height: var(--wf-hero-h);
    background-color: var(--wp--preset--color--primary);
    background-image: var(--wf-hero-scrim), var(--wf-hero-image, none);
    background-size: cover;
    /* Which point of the photograph the band holds on to as it widens. The band
       is a fixed height, so a wider screen does not show more of the picture —
       it shows a shorter slice of it, `720 × (file width) / (screen width)` of
       the file's rows. Where that slice is taken is the only say the page has,
       and it belongs to the photograph rather than to the band: a hero framed
       around its horizon wants the middle, one framed around something standing
       up in it wants the top. Hence a property with the ordinary answer as its
       default, set beside --wf-hero-image on the section for the pages that
       need another. Hem sets `center 10%` so the turbine's blades survive a
       1920 px screen; see docs/bilder.md for the measured rows. */
    background-position: var(--wf-hero-pos, center);
    color: var(--wp--preset--color--base);
    /* The fixed header lies on top of this band, so the top padding is the only
       thing keeping the content out from under it. xx-large bottoms out at 4rem,
       which is less than the 4.25rem header, so below roughly 780 px — where the
       clamp is still at its floor — the eyebrow's line box slid under the header
       as soon as a wrapping h1 pushed the band past its min-height. Taking the
       header's own height plus a gap as the floor fixes that at the source: both
       terms are rem-based, so the clearance holds under a text-only zoom exactly
       as the header's height does, and the desktop end is unchanged because
       xx-large is the larger term there. */
    padding-block:
        max(var(--wp--preset--spacing--xx-large), calc(var(--wf-header-h) + var(--wp--preset--spacing--medium)))
        var(--wp--preset--spacing--x-large);
}

.wf-herofull .wf-inner { width: 100%; }
.wf-herofull h1 { font-size: var(--wp--preset--font-size--xx-large); }

/* The hero undims the dark ground's text ladder. .wf-band-dark grades its text
   by transparency of white — 75 % for the lead, 55 % for the muted lines — and
   those figures are correct against flat Deep sea, where 55 % still reaches
   5.94:1. Over a photograph they have nothing to stand on: the scrim above is
   specified to carry solid white at 4.5:1 and nothing dimmer, so the dimmed
   runs spend a budget that was never theirs. Measured against the rendered
   pixels of the hero photograph, the 55 % eyebrow label reached only 3.02:1 at
   1440 px and 3.85:1 at 390 px, and the 75 % lead 4.46:1 at 320 px — all three
   normal-size text needing 4.5:1 under WCAG 1.4.3, which is the concession the
   scrim's own comment already says the eyebrow and the lead do not get. Solid
   white clears the floor, and the three runs still read as three because size,
   weight and family separate them; opacity was never carrying that job here.
   The flat bands keep their ladder, which is sound on flat navy.

   How much headroom solid white has is a property of the photograph, not of
   this rule, so it has to be re-measured whenever the hero image changes — and
   changing --wf-hero-pos is changing the image, because it changes which rows
   of it end up behind the text. The figure is the lightest pixel any glyph
   actually covers, not an average and not the lightest pixel in the text's box:
   the box spans line gaps and ragged right edges where nothing is written, and
   reading those turns a passing hero into a failing number.

   The current hero-hem.webp, cut so the whole rotor reads and anchored at
   `center 10%`, measures on its worst line — the h1 — 4.57:1 at 390 px, 4.61:1
   at 1440 px, 4.70:1 at 320 and 1920 px and 4.83:1 at 2560 px. The eyebrow
   ranges 7.2:1 to 8.4:1 and the lead 6.4:1 to 13.1:1. Passing throughout, but
   the h1 has under a tenth of a point in hand at 390 px, and far less margin
   than the portrait crop two heroes ago (5.24:1 to 5.41:1) — the text sits
   nearer the waterline than it used to, which is the price of showing the
   blades. A future hero brighter still would fail, and the scrim, not the text
   colour, is the thing to reach for then.

   The numeral is in the list so the hero owns its text colour outright, without
   .wf-band-dark being spelled out in the markup — the promise the block comment
   above makes and --wf-numeral-color already keeps. Left to the base rule it
   would fall back to Purple dawn Deep and measure 1.11:1 on the scrim. The
   ladder's remaining two members, .wf-sourcenote and a blockquote's cite, are
   out of scope on purpose: neither belongs in a full-bleed hero, and a
   .wf-herofull descendant rule could not outrank .wf-band-dark blockquote cite
   anyway. Ordering matters — this has to stay after the ladder it undoes. */
.wf-herofull .wf-eyebrow,
.wf-herofull .wf-eyebrow-num,
.wf-herofull .wf-lead,
.wf-herofull .wf-meta,
.wf-herofull .wf-footnote {
    color: var(--wp--preset--color--base);
}

/* ---------- Lists (press, documents, events) ---------- */

.wf-presslist, .wf-doclist, .wf-eventlist { list-style: none; padding: 0; margin: 0; }

/* The seam the browser filter writes into, on the archives whose rows are list
   items: the year rule stands outside the list it rules off, so the year groups
   are alternating paragraphs and `ul`s and something has to hold them. The
   press archive needs none because its rows are articles and its own
   `.wf-presslist` is already that element. It draws nothing of its own. */
.wf-archive { display: block; }

.wf-pressitem, .wf-doc, .wf-event {
    border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    padding-block: var(--wp--preset--spacing--medium);
}

.wf-pressitem .wf-meta { margin-bottom: calc(var(--wp--preset--spacing--small) / 2); }

/* The row's title takes its level from the document, not from the component:
   h2 on the archive where the release is the page's own content, h3 where the
   list sits under a band heading. The look is the component's either way, so
   the type is stated for both levels — including the display-heading tracking
   and balancing that the h2 tag would otherwise bring in and that a list of
   forty-word release titles has no use for. */
.wf-pressitem :is(h2, h3) { font-size: var(--wp--preset--font-size--medium); font-weight: var(--wp--custom--font-weight--light); letter-spacing: normal; text-wrap: wrap; margin: 0; max-width: 52ch; }
.wf-pressitem :is(h2, h3) a { text-decoration: none; }
.wf-pressitem :is(h2, h3) a:hover { text-decoration: underline; }
.wf-pressitem p:last-child { margin: calc(var(--wp--preset--spacing--small) / 2) 0 0; font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--secondary); max-width: 64ch; }

/* Year divider inside chronological lists. Its rule line replaces the top
   hairline of the item that follows, sitting exactly where that hairline
   would be — one divider, never two. */
.wf-yearrule {
    display: flex;
    align-items: center;
    gap: var(--wp--preset--spacing--small);
    padding-top: var(--wp--preset--spacing--large);
    margin-bottom: 0;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    letter-spacing: normal;
    color: var(--wp--preset--color--secondary);
}

.wf-yearrule::after {
    content: "";
    flex: 1;
    border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
}

.wf-yearrule + .wf-pressitem,
.wf-yearrule + .wf-doclist > .wf-doc:first-child,
.wf-yearrule + .wf-eventlist > .wf-event:first-child {
    border-top: 0;
}

.wf-doc {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium);
}

.wf-doc-title { margin: 0; font-weight: var(--wp--custom--font-weight--medium); }
.wf-doc p { margin: 0; }
.wf-doc-links { display: flex; flex-wrap: wrap; gap: var(--wp--preset--spacing--small); }

/* Events carry a boxed mono date chip, as in the mockups' calendar. */
.wf-event {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wp--preset--spacing--medium);
    align-items: flex-start;
}

.wf-datechip {
    flex: none;
    width: 3.6rem;
    text-align: center;
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--sm);
    background: var(--wp--preset--color--tertiary);
    padding-block: calc(var(--wp--preset--spacing--small) / 2);
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    line-height: var(--wp--custom--line-height--none);
}

/* The day numeral carries its emphasis through size, not weight — mono is
   never bold. Weight is pinned explicitly, rather than left to inherit,
   because the sitewide `strong, b` rule earlier in this file would otherwise
   reach in here too and push it from Light to SemiBold. */
.wf-datechip b {
    display: block;
    font-size: var(--wp--preset--font-size--medium);
    font-weight: inherit;
}

.wf-datechip span {
    display: block;
    margin-top: .3em;
    font-size: var(--wp--preset--font-size--x-small);
    letter-spacing: normal;
    text-transform: uppercase;
    color: var(--wp--preset--color--secondary);
}

.wf-event h3 { font-size: var(--wp--preset--font-size--base); margin: 0 0 .2em; }
.wf-event p { margin: 0; font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--secondary); max-width: 56ch; }
.wf-event > div:not(.wf-datechip):not(.wf-doc-links) { flex: 1; min-width: min(20rem, 100%); }
.wf-event .wf-meta { margin-top: .4em; }

/* The shared list rule (.wf-pressitem, .wf-doc, .wf-event) draws a top hairline
   and block padding meant for stacked rows in a list. When such a block is
   instead placed inside a .wf-panel, the panel frame is the boundary, so those
   turn into a stray light hairline over the panel's darker top edge and padding
   on top of the panel's own. Neutralise them for that case (bolagsstämmor's
   upcoming-meeting card and its doc-header panel); list contexts are untouched. */
.wf-panel > .wf-event { border-top: 0; padding-block-start: 0; }
.wf-panel.wf-doc { border-top-color: var(--wp--preset--color--border-light); }

/* Stacked meeting row (bolagsstämmor archive): a .wf-doc that lays its parts out
   vertically — title, meta, an optional note, then one or two labelled document
   groups — rather than the default space-between row. It keeps the shared
   .wf-doc hairline/padding rhythm and the yearrule first-child reset. */
.wf-meeting { display: block; }
.wf-meeting .wf-meta { margin-block: calc(var(--wp--preset--spacing--small) / 2) 0; }

/* A short sans note between a meeting's meta line and its document groups. The
   selector outweighs the shared `.wf-doc p { margin: 0 }` reset. */
.wf-meeting .wf-meeting-note {
    margin-block: calc(var(--wp--preset--spacing--small) / 2) 0;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--small);
    color: var(--wp--preset--color--secondary);
    max-width: 56ch;
}

/* A labelled group of document chips: a small mono caption over a chip row,
   used for the "Inför/Efter stämman" groupings in a meeting row and for the
   "Handlingar inför stämman" block in the upcoming-meeting panel. */
.wf-docgroup { margin-top: var(--wp--preset--spacing--small); }

.wf-docgroup-label {
    margin: 0 0 calc(var(--wp--preset--spacing--small) / 2);
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: normal;
    color: var(--wp--preset--color--secondary);
}

/* Inside a meeting row the shared `.wf-doc p { margin: 0 }` reset outranks the
   label's own margin; restore its bottom gap with a higher-specificity rule. */
.wf-meeting .wf-docgroup-label { margin-bottom: calc(var(--wp--preset--spacing--small) / 2); }

/* A dashed note following a document group (upcoming-meeting panel, variant
   with published documents) keeps clear of the chips above it. */
.wf-docgroup + .wf-note { margin-block-start: var(--wp--preset--spacing--medium); }


/* ---------- Articles (news list cards) ---------- */

.wf-articlecard {
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    padding: var(--wp--preset--spacing--medium);
}

.wf-articlecard .wf-img { border-radius: var(--wp--preset--border-radius--sm); }
/* Same as the press row: the card's title level follows the document, its look
   follows the card — so the h2 tag brings neither the display tracking, nor the
   24ch heading cap the card is already narrower than, nor the balancing that
   would push a three-line title into a fourth line. */
.wf-articlecard :is(h2, h3) { font-size: var(--wp--preset--font-size--base); font-weight: var(--wp--custom--font-weight--medium); letter-spacing: normal; text-wrap: wrap; max-width: none; }
.wf-articlecard :is(h2, h3) a { text-decoration: none; }
.wf-articlecard :is(h2, h3) a:hover { text-decoration: underline; }
.wf-articlecard p:last-child { margin: 0; font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--secondary); }

/* ---------- Filter bar ---------- */

.wf-filter {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wp--preset--spacing--small);
    align-items: center;
    margin: 0;
}

input, select, button, textarea {
    font: inherit;
    font-size: var(--wp--preset--font-size--small);
    padding: calc(var(--wp--preset--spacing--small) / 2) var(--wp--preset--spacing--small);
    border: var(--wf-border-w) solid var(--wp--preset--color--secondary);
    border-radius: var(--wp--preset--border-radius--sm);
    background: var(--wp--preset--color--base);
    color: var(--wp--preset--color--main);
}

/* The search floor is a promise about the field, not about the page: it says a
   search box narrower than this is not worth offering. Written bare it also
   said the box may be wider than the frame around it, and at a doubled text
   size it was — 11rem reads as 352 px against a 310 px band, which is what
   pushed the whole filter frame off six of the archive pages. Capping it at
   the container is the same idiom the card and stat grids already use for
   their own floors: the floor holds until the floor is the larger of the two,
   and then the container wins, because nothing is served by a field that is
   the right size in a place the reader cannot see. */
input[type="search"] { flex: 1; min-width: min(var(--wf-search-min), 100%); }

/* Search field with a leading magnifying glass. The icon is drawn on a
   wrapper so it can render as a mask in the secondary text colour. */
.wf-searchfield { position: relative; }
.wf-filter .wf-searchfield { flex: 1; min-width: min(var(--wf-search-min), 100%); }

.wf-searchfield input[type="search"] {
    width: 100%;
    min-width: 0;
    padding-inline-start: 2.6em;
}

.wf-searchfield::before {
    content: "";
    position: absolute;
    left: 1em;
    top: 50%;
    translate: 0 -50%;
    width: 1em;
    height: 1em;
    background-color: var(--wp--preset--color--secondary);
    -webkit-mask: var(--wf-icon-search) no-repeat center / contain;
    mask: var(--wf-icon-search) no-repeat center / contain;
    pointer-events: none;
}

/* Several dropdowns sharing one filter row (person + period, år + märkning). */
.wf-filter-selects {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wp--preset--spacing--small);
    align-items: end;

    /* A select is the one control whose width is written by its data: the box is
       as wide as its widest option, and the insider register's widest is a real
       name, "Gabriel Ingemar Strängberg". Nothing above it says no, so at a
       doubled text size that option made the control 410 px inside a 142 px card
       and gave all sixteen pages of the archive a sideways scrollbar.

       The cap has to start here and be handed down, which is the whole reason
       one declaration will not do it. Capping the select alone resolves its
       percentage against a parent that is itself already too wide, so it changes
       nothing: the row overflows the form, and the field's automatic minimum is
       its own content. Each step is let go of in turn — the row is bounded, the
       field is allowed to shrink below its content, and only then does the
       select's cap mean anything. */
    max-width: 100%;
}

/* Every dropdown carries a visible label above it. The selected value replaces
   the control's own wording, so without one a chosen "2024" no longer says what
   it filters — and two dropdowns side by side become indistinguishable. The
   label's styling is the stacked form's, declared once further down. */
.wf-filter-field { display: flex; flex-direction: column; min-width: 0; }
.wf-filter-field select { max-width: 100%; }

/* Stacked filter: period first, then genre toggles, then search — each on
   its own row. The period dropdown keeps its natural width; no selection
   means all. Sits in its own frame — hairline border, card radius, inner
   padding — like the mockups' filter card. */
.wf-filter-stacked {
    flex-direction: column;
    align-items: stretch;
    gap: var(--wp--preset--spacing--medium);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);

    /* The spacing token is `clamp(1.5rem, 4vw, 2rem)`, and its floor is written
       in rem — so it doubles with the reader's text size while the screen does
       not. That is right for a band, which is given the page's whole width, and
       wrong for a card that has to hold a control as well: at 320 px with the
       text doubled the floor alone took 96 of the card's 240 px and left 142 for
       a dropdown. The floor is therefore capped against the screen itself, which
       is the one measure that does not grow with the type. It bites only below
       about 400 px — at 390 px it is 23 px against the token's 24 — and above
       that the token is unchanged. */
    padding: min(var(--wp--preset--spacing--medium), 6vw);
}
.wf-filter-stacked input[type="search"], .wf-filter-stacked .wf-searchfield { flex: none; }
.wf-filter-stacked .wf-toggles { margin: 0; gap: var(--wp--preset--spacing--small); }
.wf-filter-stacked select { align-self: flex-start; }

/* Filtered results sit directly below the filter frame, with no hairlines
   of their own — the frame above is the only boundary. The press list's first
   year divider drops its inter-group top padding so the list hugs the frame,
   reading as one search-and-results unit (as on the artiklar card list).

   A general sibling and not an adjacent one, because the status line stands
   between the two whenever the filter has narrowed something. It is `:empty`
   and undrawn the rest of the time, so the unfiltered page is unchanged. */
.wf-filter-stacked ~ .wf-cardgrid,
.wf-filter-stacked ~ .wf-panel,
.wf-filter-stacked ~ .wf-archive,
.wf-filter-stacked ~ .wf-presslist { margin-block-start: var(--wp--preset--spacing--medium); }
.wf-filter-stacked ~ .wf-archive > .wf-yearrule:first-child,
.wf-filter-stacked ~ .wf-presslist > .wf-yearrule:first-child { padding-top: 0; }

/* Genre toggles dressed as the cards' metadata pills, so the filter and the
   cards it filters read as one system. Background/color/border are set
   explicitly to override the generic form button's primary fill.

   The dressing stops at the type: the fill, the geometry and the uppercase are
   the pill's, the size is not. This rule used to take the pill's tag step too,
   which left the primary way to filter press releases on a phone at 9.7 px —
   smaller than the same component's own period switches on aktien/om, which sit
   at x-small, and the one place in the system where a control was set below
   every size a reader is offered. It takes the toggle role's x-small from
   .wf-toggles button instead; the pill stays one step below it, which is the
   right way round between a label you read and a control you press.

   The label tracking leaves with the small size that earned it: .08em is there
   to open up cramped capitals, and capitals at 12–14 px are not cramped. It was
   also buying nothing but width across ten chips, and giving it back is what
   pays for most of the growth — the untracked genre row on nyhetsrum carries
   three chips where the tracked row carried two, and at 1440 px both rows fit on
   one line as before. Where the labels are long sentences of their own
   (rapporter's "Bokslutskommuniké") the frame does grow a row or two on a
   phone; a filter you can read is worth the rows. The pills keep the tracking,
   because they keep the size that needs it. */
.wf-filter-stacked .wf-toggles button {
    background: var(--wp--preset--color--base);
    color: var(--wp--preset--color--main);
    border-color: var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--full);
    padding: .15em .75em;
    font-weight: var(--wp--custom--font-weight--medium);
    text-transform: uppercase;
    letter-spacing: normal;
}

/* Pressed genre toggle matches the cards' solid pill exactly (primary fill),
   rather than the generic toggle's main-color press used elsewhere (e.g.
   chart period switches). Scoped so those other .wf-toggles keep their
   current look. */
.wf-filter-stacked .wf-toggles button[aria-pressed="true"] {
    background: var(--wp--preset--color--primary);
    border-color: var(--wp--preset--color--primary);
    color: var(--wp--preset--color--base);
}

/* ---------- Pagination ---------- */

.wf-pagination {
    border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    padding-top: var(--wp--preset--spacing--medium);
    margin-top: var(--wp--preset--spacing--medium);
}

/* Card-grid results (e.g. the article list) carry no hairlines of their
   own — the pagination's top border would draw one where none should be.
   The margin-top above is kept for vertical rhythm. */
.wf-cardgrid + .wf-pagination { border-top: 0; padding-top: 0; }

/* What the sheet holds, for paper only (ADR 0019). On screen the pagination
   says it better and says it as a control; this exists for the sheet the
   pagination is not printed on, and is given its ink in the print section. It
   stands after the navigation rather than before it so that the combinator
   above still sees the two as neighbours. */
.wf-listnote { display: none; }

/* The pages are a list, and saying so is the whole reason the `ul` is here: a
   screen reader then opens the control with how many stops it has, which is
   the one thing a run of loose links cannot say and the reader most wants to
   know. The row itself is the list — the nav keeps the rule above it and
   nothing else, so the flex box sits where the items actually are.

   It carries a class for the reason every other chrome list does: the base
   rules dress an *unclassed* list, giving it a bottom margin and air between
   its items, and both of those are for a list of points to be read rather than
   a row of controls. Naming the list is how this sheet says "this one brings
   its own spacing" — the alternative, a counter-rule, would have to outrank
   `ul:not([class]) > li + li` and would leave the base rule still firing and
   still being undone. The gap here is the flex `gap`, which works on both axes
   at once and so also spaces the second row of a pagination that wrapped. */
.wf-pagination-list {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Both boxes are laid out the same way, so the two draw the same rectangle.
   Left inline, the link's height comes from its font metrics and the span's
   from its line-height, and the row ends up a pixel out of true — visible as a
   step in the border along the row's top edge. Before the list existed the
   links were flex items and were blockified for free; inside a list item they
   have to be told. */
.wf-pagination :is(a, button),
.wf-pagination-end {
    display: block;
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--sm);
    padding: calc(var(--wp--preset--spacing--small) / 2) var(--wp--preset--spacing--small);
    text-decoration: none;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--small);
}

/* The arrow at either end of the sequence points at no page, so it is not a
   link — the markup gives it a `span`, and this is what that looks like.

   What goes is the frame, not the space. docs/lankar-och-knappar.md gives a
   button no ornament at rest because the drawn form *is* its mark, and the
   converse has to hold too: something with no destination may not wear the
   mark. Dimming the ink alone was tried first and says nothing — mid grey
   against ink inside an identical box reads as the same control at a glance,
   which is worse than not marking the state at all. Losing the border says it
   in one look.

   The border turns transparent rather than being dropped, so the box keeps its
   width to the pixel and the numbers stand in the same place on the first and
   last page as on every page between them. The glyph stays, in the meta grey
   the system already uses for what is present but quiet — 6.4:1 on white, past
   what 1.4.3 asks even of the inactive controls it would excuse — because the
   row still has to show that there is a direction here, and that it is spent.
   No hover and no pointer: there is nothing to answer *am I on it?* about. */
.wf-pagination-end {
    border-color: transparent;
    color: var(--wp--preset--color--secondary);
}

/* Where the sequence skips. Twenty-two pages of press releases do not fit on a
   phone, so the numbers run first, last and the reader's own neighbourhood, and
   this stands in for the rest.

   It borrows the numbers' box metrics so the row keeps one baseline, but not
   their frame: it is not a control and has nowhere to go. Like the spent arrow
   it leaves the accessibility tree — `aria-current` has already said where the
   reader stands, and an ellipsis read aloud adds nothing to that. */
.wf-pagination-gap {
    display: block;
    padding: calc(var(--wp--preset--spacing--small) / 2) 0;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--small);
    color: var(--wp--preset--color--secondary);
}

/* Filtered, the steps are buttons and not links: there is no page on disk for
   "page three of the disclosures marked Emissioner", and the control redraws
   the list where it stands (wireframes/list.js). The box is shared with the
   links above, so the row keeps its shape when a filter is applied; what is
   left is the browser's own button dressing, which has to go. The ink is
   named rather than inherited so that it is the same ink as the links' — a
   button inherits from the list item, a link does not. */
.wf-pagination button {
    background: none;
    color: var(--wp--preset--color--main);
    cursor: pointer;
}

.wf-pagination [aria-current="page"] { background: var(--wp--preset--color--main); color: var(--wp--preset--color--base); }

/* What the filter found, for a reader who cannot see the list change under it.
   Quiet meta text under the panel, and gone entirely while nothing is filtered
   — an empty status line still takes its margin, and the space would read as a
   missing element rather than as an answer not yet asked for. */
.wf-filter-status {
    margin: calc(var(--wp--preset--spacing--small) / 2) 0 0;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--small);
    color: var(--wp--preset--color--secondary);
}

.wf-filter-status:empty { display: none; }

/* ---------- Tables ---------- */

table { width: 100%; border-collapse: collapse; font-size: var(--wp--preset--font-size--small); }

th, td {
    text-align: left;
    padding: var(--wp--preset--spacing--small);
    border-bottom: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    vertical-align: top;
}

/* Header cells stay in the sans voice — docs/typografi.md keeps table content
   out of mono — as SemiBold tracked capitals over the design system's
   deliberately heavy header rule, the one structural line in a table. */
th {
    font-family: var(--wp--preset--font-family--primary);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--wp--preset--color--secondary);
    border-bottom-color: var(--wp--preset--color--border-dark);
}

/* A row header is structure, not emphasis. Where the first column holds the
   labels the row is about — a shareholder, a programme, a key figure — that
   cell is a `th` so a screen reader can name the row in every cell of it, and
   it is then dressed back down to exactly what a `td` looks like. The header
   rule above answers the column head, which is a different thing: the column
   head is drawn *as* a head, above the one heavy line the table carries. Two
   `th`s doing different jobs may not look alike, and the one that reads as
   content is the one that must not change. */
tbody th {
    font-weight: inherit;
    font-size: inherit;
    text-transform: none;
    letter-spacing: normal;
    color: inherit;
    border-bottom-color: var(--wp--preset--color--border-light);
}

/* Row hover picks up the surface one step from the ground it sits on. */
tbody tr:hover { background: var(--wp--preset--color--tertiary); }
.wf-band-dark tbody tr:hover, .wf-panel-dark tbody tr:hover { background: color-mix(in srgb, var(--wp--preset--color--base) 5%, transparent); }

td.wf-num, th.wf-num { text-align: right; }

/* Numeric cells stay sans but adopt tabular figures so columns line up without
   the readability cost of mono — "mono highlights, sans reports". */
td.wf-num { font-family: var(--wp--preset--font-family--primary); font-variant-numeric: tabular-nums; }
/* The sum row weights every cell in it, row header included — `> *` rather
   than `td`, since the row's own label is now a `th`. */
tr.wf-sumrow > * { font-weight: var(--wp--custom--font-weight--bold); }

/* A hairline down the first column of a second block of columns, where one
   table carries two kinds of period side by side (quarters, then full years).
   The rule reads as a group boundary; the header's own heavy bottom rule stays
   the only structural line across the table. */
th.wf-colsplit, td.wf-colsplit { border-left: var(--wf-border-w) solid var(--wp--preset--color--border-light); }

/* Wide tables already scroll (overflow-x below), but nothing on screen says
   so — the right-hand columns simply stop at the panel edge and read as the
   end of the data. The classic covers-and-shadows technique fixes that with
   no script and no markup change: two gradients matching the panel's ground
   are painted `local`, so they travel with the content and sit flush at its
   two ends; two more are painted at the default `scroll`, pinned to the box
   instead. A shadow only shows through where its cover has moved off past
   the box edge, so it appears exactly while there is more to scroll to and
   vanishes on its own once the table fits or is scrolled to that end. The
   ground is a custom property so a dark table panel can override it in one
   line. Shadow ink reuses --wf-lift-shadow's base at a lighter .16 alpha —
   this is a hint, not a lift.

   A cover has to be fully opaque for as far as the shadow it hides reaches, or
   the two blend instead of cancelling: a cover that starts fading at x = 0 is
   already well short of alpha 1 where the shadow still has substance, and the
   sum is a faint smudge on both edges of a table that is not scrolled and hides
   nothing at all. So the cover holds solid ground across the shadow's whole
   width before it begins to fade, and only its tail is a gradient — that tail
   is what keeps the cue from snapping into view as a hard edge. The plateau is
   the shadow's own width plus a hair, so the far edge is clean the instant a
   table stops overflowing and the near edge fades in over the first two dozen
   pixels of scroll rather than at the first one. The shadow's width lives in a
   property of its own because the plateau has to follow it: let the two drift
   apart and the smudge comes straight back. */
.wf-tablewrap {
    --wf-scrollcue-ground: var(--wp--preset--color--base);
    --wf-scrollcue-w: .75rem;
    /* Ollie's shadow ink, at the one alpha neither shadow preset carries: the
       cue is an affordance smudge rather than an elevation, so it takes the
       system's ink but not the Lift or Overlay throw. Named alongside its two
       siblings so the pair of gradients below cannot drift apart. */
    --wf-scrollcue-ink: rgba(20, 17, 31, .16);
    overflow-x: auto;
    background:
        linear-gradient(to right, var(--wf-scrollcue-ground) 0 calc(var(--wf-scrollcue-w) + 1px), transparent) 0 0 / 2rem 100% no-repeat local,
        linear-gradient(to left, var(--wf-scrollcue-ground) 0 calc(var(--wf-scrollcue-w) + 1px), transparent) 100% 0 / 2rem 100% no-repeat local,
        radial-gradient(farthest-side at 0 50%, var(--wf-scrollcue-ink), transparent) 0 0 / var(--wf-scrollcue-w) 100% no-repeat,
        radial-gradient(farthest-side at 100% 50%, var(--wf-scrollcue-ink), transparent) 100% 0 / var(--wf-scrollcue-w) 100% no-repeat;
}

/* A wide table only scrolls if something can move it, so the wrapper is a
   focusable control in its own right (role="region" + tabindex) and takes the
   shared focus ring like any other. It needs nothing of its own: the ring's
   own offset clears .wf-tablepanel's padding, so it reads against the white
   panel ground rather than colliding with the panel's 1px border. */

/* Local override: the Utveckling table's eight data columns squeeze its label
   column to a few characters before the wrap element's own scroll takes over,
   which invited the row names to wrap across several lines instead. Keeping
   the label on one line pushes the table wider and lets .wf-tablewrap do the
   scrolling it already exists for, without touching the other .wf-tablepanel
   tables that never grew this many columns. The selector names the position
   rather than the element: the label column is a `th` in the body and a `th`
   in the head, and it is the column that has to hold its line. */
.wf-kpitable :is(th, td):first-child { white-space: nowrap; }

/* The share-count row's space-grouped thousands (e.g. "5 983 459") are a single
   figure, not separate words, and must not break across lines either — the
   same rule as .wf-kpi-value and .wf-price above, applied here to a data cell
   rather than a display figure. */
.wf-kpitable td.wf-num { white-space: nowrap; }

/* Below the tablet threshold the label column is allowed to wrap to two lines
   and the horizontal cell padding halves — that buys far more width than the
   type size does. The size itself drops exactly one token step, to the
   scale's floor, and stops there: 12 px is as small as tabular data may go.
   Below that the wrapper's horizontal scroll (.wf-tablewrap) takes over.
   Scoped to this one table — it is the only nine-column table on the site —
   so the shared table/th/td rules keep serving the other four tables
   unchanged. The .wf-num cells keep their white-space: nowrap so grouped
   thousands never break; the first column is the only one released back to
   wrapping. */
@media (max-width: 56rem) {
    .wf-kpitable { font-size: var(--wp--preset--font-size--x-small); }
    .wf-kpitable th, .wf-kpitable td { padding-inline: calc(var(--wp--preset--spacing--small) / 2); }
    .wf-kpitable :is(th, td):first-child { white-space: normal; }
}

/* Tables sit inside rounded panels, as in the mockups. */
.wf-tablepanel { padding: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium); }

/* Same reasoning as the fact rows': inside a frame the last row's rule doubles
   the frame below it. A table standing on its own band keeps its closing rule. */
.wf-tablepanel tbody tr:last-child > * { border-bottom: 0; }

/* Local override: adds extra clearance above one specific table panel (aktien/om,
   teckningsoptioner) so it doesn't crowd the intro text above it. Does not change
   the shared .wf-tablepanel spacing used on other pages. */
.wf-tablepanel-gap-lg { margin-top: var(--wp--preset--spacing--large); }

/* Local override: balances a table panel that closes its band (styrning/ledning,
   incitamentsprogram) — the space above the panel equals the band's bottom
   padding, minus the preceding paragraph's own bottom margin. */
.wf-tablepanel-symmetric { margin-top: calc(var(--wp--preset--spacing--x-large) - var(--wp--preset--spacing--small)); }

/* ---------- Fact rows (aktiefakta panel) ---------- */

/* Fact rows with right-aligned values. */
.wf-factrows { list-style: none; padding: 0; margin: 0; }

.wf-factrows li {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .2em var(--wp--preset--spacing--medium);
    padding-block: var(--wp--preset--spacing--small);
    border-bottom: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    font-size: var(--wp--preset--font-size--small);
}

/* The rules divide rows, so the last row has nothing left to divide: the
   panel's own frame closes the list a few pixels below, and a rule there reads
   as a doubled border. */
.wf-factrows li:last-child { border-bottom: 0; }

.wf-factrows .wf-factlabel { color: var(--wp--preset--color--secondary); }
.wf-factrows .wf-factvalue { font-family: var(--wp--preset--font-family--primary); font-weight: var(--wp--custom--font-weight--medium); font-variant-numeric: tabular-nums; text-align: right; }

/* Every row is two lines — label left, value right-aligned below — even when
   the pair would fit on one line, so long pairs (which must wrap in a 19rem
   sidebar) and short ones read the same. */
.wf-factrows li { flex-wrap: wrap; }
.wf-factrows .wf-factlabel { flex-basis: 100%; }

/* The value used to be nowrap, which was the right rule stated in the wrong
   place. What may not break is a figure — 2,10 SEK, 5 983 459 — and the value
   slot holds more than figures: a subscription period is two dates with a dash
   between them, and a postal address is four lines of street and city. Held to
   one line, the longest of them ran 322 px through a 254 px panel and took the
   contact page's whole widget with it, because a widget folds on its own
   min-content and nowrap is what min-content was measuring.

   So the guarantee moves to where the thing it protects actually is. Grouped
   thousands already keep their integrity from non-breaking spaces in the copy;
   a figure and its unit now do the same, which is one rule instead of two and
   is true at any width rather than at the widths that happened to fit. A range
   breaks at its dash, an address breaks at its lines, and nothing in this
   panel can be pushed past its own frame again. */
.wf-factrows .wf-factvalue { margin-inline-start: auto; }

/* ---------- Person cards (board and management) ---------- */

/* Column card: head (portrait + name + role) over a hairline, a stacked body
   of label-over-value fields, and a tinted holdings footer anchored to the
   bottom so footers align across a row of cards of unequal body length. */
.wf-person {
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.wf-person-head {
    display: flex;
    align-items: center;
    gap: var(--wp--preset--spacing--medium);
    padding: var(--wp--preset--spacing--medium);
}

/* The portrait chip sits on the ladder (docs/bilder.md): the card type gives it
   its width, the ladder's 4:5 step gives it its height. A fixed width is what
   keeps every chip in a row identical however much text its card carries, and
   letting the ratio resolve the height is what stops the chip from being an
   exception to the rule the rest of the images follow. */
.wf-person-head .wf-img {
    width: var(--wf-portrait);
    aspect-ratio: var(--wf-ratio-portrait);
    flex: none;
    border-radius: var(--wp--preset--border-radius--sm);
    margin: 0;
}

/* The name column is a flex item, and a flex item's automatic minimum is its own
   min-content width — the longest unbreakable word in it. That is why a
   three-up row of cards used to cut "Ulrika Lenhammar" off inside the card: the
   column would not shrink, and the heading rule's break-word lowers where a line
   may break without lowering that minimum. The column is given a floor it can
   actually work in and the row is allowed to wrap, so when a name cannot sit
   beside the portrait the portrait moves above it instead. */
.wf-person-head { flex-wrap: wrap; }

.wf-person-head > div:not(.wf-img) {
    flex: 1;
    min-width: min(100%, 9rem);
}

.wf-person-head h3 {
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    margin: 0;
}

.wf-role {
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    text-transform: uppercase;
    letter-spacing: var(--wf-tracking-label);
    color: var(--wp--preset--color--secondary);
    margin: .4em 0 0;
    /* "Kraftöverföring", "Rotorutveckling": single words longer than the column
       a three-up card leaves, and uppercase makes them longer still. */
    overflow-wrap: break-word;
}

/* Stacked label-over-value fields, separated from the head by a full-width
   hairline. Fields breathe with a medium gap; label and value are tight. */
.wf-person-body {
    display: flex;
    flex-direction: column;
    gap: var(--wp--preset--spacing--medium);
    border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    padding: var(--wp--preset--spacing--medium);
}

.wf-person-field { display: grid; grid-template-columns: minmax(0, 1fr); gap: calc(var(--wp--preset--spacing--small) / 2); }

/* The two-column Född | Invald row (resp. Anställd sedan / VD sedan). */
.wf-person-duo {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--wp--preset--spacing--medium);
}

.wf-person-label {
    font-family: var(--wp--preset--font-family--primary);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: var(--wf-tracking-label);
    color: var(--wp--preset--color--secondary);
    margin: 0;
}

.wf-person-value { margin: 0; font-size: var(--wp--preset--font-size--small); }
.wf-person-duo .wf-person-value { font-size: var(--wp--preset--font-size--base); }

/* Bullet list of assignments with hollow ring markers, matching the mockup. */
.wf-person-uppdrag {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    font-size: var(--wp--preset--font-size--small);
}

.wf-person-uppdrag li { position: relative; padding-left: 1.4em; }

.wf-person-uppdrag li::before {
    content: "";
    position: absolute;
    left: .15em;
    top: .5em;
    width: .5em;
    height: .5em;
    border: calc(var(--wf-border-w) * 1.5) solid var(--wp--preset--color--secondary);
    border-radius: 50%;
}

/* Oberoende: a labelled statement on the left, a circled check plus "Ja" on
   the right. Board cards only. */
.wf-person-indep {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: calc(var(--wp--preset--spacing--small) / 2);
    font-size: var(--wp--preset--font-size--small);
}

.wf-person-indep li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--small);
}

.wf-person-yes { display: inline-flex; align-items: center; gap: .45em; white-space: nowrap; }

.wf-person-yes::before {
    content: "";
    width: 1.1em;
    height: 1.1em;
    flex: none;
    background-color: var(--wp--preset--color--secondary);
    -webkit-mask: var(--wf-icon-check-circle) no-repeat center / contain;
    mask: var(--wf-icon-check-circle) no-repeat center / contain;
}

/* Clamp a prose value to three lines until expanded by its "Visa mer" button. */
.wf-clamp {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

.wf-clamp.is-expanded {
    display: block;
    -webkit-line-clamp: none;
    overflow: visible;
}

/* Clamp a long assignment list to its first two items until expanded. */
.wf-clamp-list:not(.is-expanded) > li:nth-child(n+3) { display: none; }

/* The clamp is worked by `line-clamp` and `display`, and neither can be
   interpolated, so persons.js animates the height instead and marks the journey
   with .is-animating. For its duration the clamp is lifted — the whole text has
   to be there for a height to travel over — and the overflow does the trimming
   the clamp was doing. Every rule here therefore has to reach past a clamped
   state, which is why it sits after them and matches their specificity. */
.wf-clamp.is-animating {
    display: block;
    -webkit-line-clamp: none;
}

.wf-clamp-list.is-animating > li:nth-child(n+3) { display: list-item; }

/* 200 ms: long enough that the text is seen unfolding rather than replaced, and
   short enough that the card still answers the click immediately. */
.wf-clamp.is-animating,
.wf-clamp-list.is-animating {
    overflow: hidden;
    transition: max-height .2s ease;
}

/* Inline "Visa mer" / "Visa mindre" toggle in the accent (secondary) role.
   Flex with a generous gap: the rotated chevron's glyph box overhangs its
   layout box, so part of the gap is consumed visually. */
.wf-morebtn {
    display: inline-flex;
    align-items: center;
    gap: .7em;
    justify-self: start;
    /* Only the horizontal half of the button reset: the block padding is the
       pointer target floor's, and a `padding: 0` here would silently undo it. */
    padding-inline: 0;
    border: 0;
    background: transparent;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
    cursor: pointer;
}

/* Bare text: the toggle is dimmed to the secondary ink at rest, so hover both
   undims it to full ink and underlines it — one response (the bare-text
   underline), plus the removal of the dimming that made it bare in the first
   place. */
.wf-morebtn:hover { color: var(--wp--preset--color--main); text-decoration: underline; }

/* Disclosure convention: the chevron points down while more content can be
   revealed and up once everything is shown. The glyph in the markup is a
   right-pointing chevron, so rest state is a +90° turn. */
.wf-morebtn-icon {
    display: inline-block;
    transform: rotate(90deg);
    transition: transform .2s ease;
}

.wf-morebtn[aria-expanded="true"] .wf-morebtn-icon { transform: rotate(-90deg); }

/* Readers who ask for less motion get the disclosure as a plain switch, chevron
   included. Nothing is lost with the travel gone: the label, the ARIA state and
   the chevron's direction all still say which way the toggle stands. persons.js
   reads the computed duration, finds it zero and settles on the spot. */
@media (prefers-reduced-motion: reduce) {

    .wf-clamp.is-animating,
    .wf-clamp-list.is-animating,
    .wf-morebtn-icon { transition: none; }

}

/* Tinted footer band anchored to the card bottom: holdings figure over an
   optional options line, with the reporting date to the right. */
.wf-person-holdings {
    margin-top: auto;
    background: var(--wp--preset--color--tertiary);
    border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    padding: var(--wp--preset--spacing--medium);
}

.wf-person-holdings .wf-person-label { margin-bottom: calc(var(--wp--preset--spacing--small) / 2); }

.wf-person-holdings-main {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .1em .4em;
    margin: 0;
}

/* docs/typografi.md reserves the accent ink for mono display numerals ("Mono
   framhäver — sans redovisar"); this is a reported figure in a card body, not
   a highlighted KPI, and the tinted footer band plus the "Innehav" label
   already do the emphasising, so weight and tabular alignment are the only
   marking it needs. A lone enlarged, coloured figure inside a card body would
   be the only one of its kind on the site and read as an exception with no
   rule behind it. */
.wf-person-shares {
    font-family: var(--wp--preset--font-family--primary);
    font-weight: var(--wp--custom--font-weight--semi-bold);
    font-size: var(--wp--preset--font-size--base);
    font-variant-numeric: tabular-nums;
    line-height: var(--wp--custom--line-height--none);
    color: var(--wp--preset--color--main);
}

/* "Uppgift saknas" is prose, not a figure; stepped down one size so it shares
   one line with the reporting date at three-column card width, and kept in
   the muted secondary ink since a null state has no value to state. */
.wf-person-shares-na { font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--secondary); }

.wf-person-unit { font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--secondary); }

.wf-person-asof {
    margin-left: auto;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
}

/* The longest variant ("+ NN NNN kvalificerade personaloptioner") does not fit
   one line at three-column card width above the x-small step, and the line used
   to be held together by shrinking the type instead — which paid for a desktop
   layout constraint at every width, including the phone, where the card is a
   single column with room to spare and the shrink cost the most.

   What actually may not break is the figure, not the sentence: "37 500" split
   across two lines would read as two numbers. So the nowrap moves onto the
   figure alone, the phrase after it is free to wrap when the column is narrow,
   and the size returns to the card's own metadata step. text-wrap: pretty keeps
   the wrap from leaving "personaloptioner" alone on the second line. */
.wf-person-options {
    margin: calc(var(--wp--preset--spacing--small) / 2) 0 0;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
    text-wrap: pretty;
}

.wf-person-options b {
    font-weight: var(--wp--custom--font-weight--medium);
    color: var(--wp--preset--color--main);
    white-space: nowrap;
}

/* ---------- Contact cards ---------- */

.wf-contact {
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    padding: var(--wp--preset--spacing--medium);
}

.wf-contact h3 { font-size: var(--wp--preset--font-size--medium); font-weight: var(--wp--custom--font-weight--medium); margin-bottom: 0; }

/* Same chip as in the person card, and for the same reason: width from the card
   type, height from the ladder's 4:5 step. */
.wf-contact .wf-img {
    width: var(--wf-portrait);
    aspect-ratio: var(--wf-ratio-portrait);
    border-radius: var(--wp--preset--border-radius--sm);
}

.wf-contactlines {
    list-style: none;
    padding: 0;
    margin: var(--wp--preset--spacing--small) 0 0;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--small);
}

/* Addresses and numbers carry no spaces to wrap at, so a long one ran straight
   out of the details column and under the portrait beside it. `anywhere` is
   needed rather than break-word: the line has to be allowed to break mid-token
   and the column has to be allowed to size itself below that token's width. */
.wf-contactlines { overflow-wrap: anywhere; }

.wf-contactlines a { text-decoration: none; }
.wf-contactlines a:hover { text-decoration: underline; }

/* Two-column contact card (kontakt page): details left, a portrait/logo
   placeholder filling the card's right column at full height. */
/* The portrait track is a share of the card with a floor and a ceiling, and
   the floor is the term that had to learn about text size: 6rem is 96 px at
   the scale's own root and 192 px at a doubled one, which on a 320 px screen
   is most of the card and more than the card can give. Capped at 40 % it stays
   the floor wherever the card is wide enough to honour it — every width and
   text size but that one — and gives way where honouring it would cost the
   details beside it their column. */
.wf-contact-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) min(clamp(6rem, 35%, 11rem), 40%);
    gap: var(--wp--preset--spacing--medium);
}

/* The neighbour-sized case (docs/bilder.md): the frame takes its width from the
   column and its height from the details beside it, and the floor keeps a card
   with few contact lines from pressing it into a strip. The portrait chip's own
   5.5rem is the right floor here — the 18rem meant for section images would
   overrun the card it sits in. */
.wf-contact-split > .wf-img {
    width: auto;
    height: auto;
    min-height: var(--wf-portrait);
    aspect-ratio: auto;
    margin: 0;
}

/* ---------- Accordion ---------- */

details {
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--sm);
    background: var(--wp--preset--color--base);
    padding: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium);
    margin-bottom: var(--wp--preset--spacing--small);
}

/* The row is a flex line so the marker can sit at the far edge and stay there
   however many lines the question wraps to. The UA marker is switched off in
   both spellings — `list-style` covers the standard one, the WebKit pseudo the
   one Safari still draws — and replaced by ::after below.

   Making the row a flex line is also what put the automatic minimum back on the
   question. The text sits in no element of its own, so the browser wraps it in
   an *anonymous* flex item — and `:where(body *) { min-width: 0 }` at the top
   of this sheet selects elements, which an anonymous box is not and no selector
   can name. The one floor the site-wide rule cannot reach is therefore the one
   this rule makes: at a doubled text size it was "demonstrationsverket", 401 px
   of unbreakable word in a 142 px summary and 490 px of document in a 320 px
   viewport. `overflow-wrap` is inherited, which is how it gets in where a
   selector cannot, and it is `anywhere` rather than `break-word` because only
   `anywhere` counts toward the min-content the floor is made of — the same
   distinction docs/typografi.md draws, and the reason the rest of the sheet
   spells it this way. */
summary {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--medium);
    overflow-wrap: anywhere;
    cursor: pointer;
    font-weight: var(--wp--custom--font-weight--medium);
    list-style: none;
}

summary::-webkit-details-marker { display: none; }

/* Points down when open, a quarter turn to the left when closed. The turn and
   the panel below travel together and over the same time, so the two read as
   one movement. Baseline alignment would put the glyph's own baseline on the
   text's, which for a box-shaped mask means low; the translate lifts it back
   onto the first line's optical centre. */
summary::after {
    content: "";
    flex: none;
    width: .8em;
    height: .8em;
    translate: 0 .04em;
    rotate: -90deg;
    transition: rotate .22s ease;
    background-color: var(--wp--preset--color--secondary);
    -webkit-mask: var(--wf-icon-chevron-down) no-repeat center / contain;
    mask: var(--wf-icon-chevron-down) no-repeat center / contain;
}

details[open] > summary::after { rotate: 0deg; }

/* The panel grows and shrinks instead of appearing and disappearing.
   `::details-content` is the box the browser wraps the answer in, and making it
   a single-row grid is what lets the height be animated at all: `0fr → 1fr` is
   two lengths, where `height: auto` is a keyword and interpolates only where
   `interpolate-size` is supported — which Chrome 151 does and **Firefox 153
   does not**, and Firefox is the browser the review is done in. The grid form
   needs no `interpolate-size`, no measured pixel height and no script.

   `content-visibility` has to travel with it. The browser flips it to `hidden`
   the instant the element closes, which would cut the answer away before it had
   finished collapsing; `allow-discrete` holds the old value until the transition
   is done. Both properties are given the same duration for that reason. */
details::details-content {
    display: grid;
    grid-template-rows: 0fr;
    overflow: hidden;
    transition: grid-template-rows .22s ease, content-visibility .22s allow-discrete;
}

details[open]::details-content { grid-template-rows: 1fr; }

/* A grid item's automatic minimum is its content, which would hold the row open
   at full height and leave nothing to animate. */
details > :not(summary) { min-height: 0; }

details p { margin-block: var(--wp--preset--spacing--small) 0; font-size: var(--wp--preset--font-size--small); }

/* Reduced motion gets the accordion as a plain switch: the panel is there or it
   is not, and the marker stands in the direction that says which. Nothing is
   lost, because neither state was ever carried by the movement. */
@media (prefers-reduced-motion: reduce) {

    summary::after,
    details::details-content { transition: none; }

}
.wf-faqgroup { margin-bottom: var(--wp--preset--spacing--large); }

.wf-faqgroup h2 {
    display: flex;
    gap: .5em;
    align-items: baseline;
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--medium);
    letter-spacing: -.01em;
}

/* Mono section numbers in front of grouped headings (§, 01 …) — an index, so
   they take the purple ink like the eyebrow's numeral. Their hosts set medium,
   which starts at 20 px on mobile and so falls short of the large-text
   threshold that lets Purple dawn itself carry a numeral. */
.wf-secnum {
    /* The index sits beside its heading in a flex row and is the one item
       there that must not give: it is already the shortest thing on the line,
       and a heading that borrows two pixels from its own number has taken them
       from the wrong place. */
    flex: none;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    color: var(--ds-purple-dawn-deep);
    white-space: nowrap;
}

/* ---------- Timeline ---------- */

.wf-timeline { list-style: none; padding: 0; margin: 0; border-left: calc(var(--wf-border-w) * 2) solid var(--wp--preset--color--border-dark); }

.wf-timeline li { position: relative; padding-left: var(--wp--preset--spacing--large); padding-bottom: var(--wp--preset--spacing--medium); }

/* The marker dot centres on the first line's box (the dateline, now enlarged
   to --font-size--base by .wf-meta time), not on a fixed offset — its box
   height is that font size at the inherited body line-height, so the dot's
   top is half of that box minus half the dot's own size. A hard-coded nudge
   would drift out of true the next time either scale changes. */
.wf-timeline li::before {
    content: "";
    position: absolute;
    left: calc(var(--wp--preset--spacing--small) / -2);
    top: calc((var(--wp--preset--font-size--base) * var(--wp--custom--line-height--body) - var(--wp--preset--spacing--small)) / 2);
    width: var(--wp--preset--spacing--small);
    height: var(--wp--preset--spacing--small);
    background: var(--wp--preset--color--main);
    border-radius: 50%;
}

.wf-timeline h3 { font-size: var(--wp--preset--font-size--base); margin: 0; }
.wf-timeline p { margin: 0; font-size: var(--wp--preset--font-size--small); color: var(--wp--preset--color--secondary); max-width: 56ch; }
.wf-timeline .wf-meta { margin: 0; }

/* ---------- Subscribe panel ---------- */

.wf-subscribepanel {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--large);
    padding: var(--wp--preset--spacing--large);
}

.wf-subscribepanel h2 { max-width: 20ch; margin: 0; }

.wf-subscribe-kicker {
    display: block;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    font-size: var(--wp--preset--font-size--x-small);
    text-transform: uppercase;
    letter-spacing: normal;
    color: var(--wp--preset--color--border-light);
    margin-bottom: var(--wp--preset--spacing--small);
}

.wf-subscribe-form { flex: 1; min-width: min(18rem, 100%); max-width: 28rem; margin: 0; }

/* The whole row shares one rule: field and submit sit on the same underline,
   which is the form's entire chrome. */
.wf-underlinefield {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wp--preset--spacing--small);
    border-bottom: var(--wf-border-w) solid color-mix(in srgb, var(--wp--preset--color--base) 45%, transparent);
    padding-bottom: calc(var(--wp--preset--spacing--small) / 2);
}

/* min-width: 0 because a flex item otherwise refuses to shrink past its own
   intrinsic size, and an input's is its default character count — enough to push
   the submit off the panel on the narrowest phones. The submit keeps its width;
   the field gives way, which is the right way round on one shared underline. */
.wf-underlinefield input {
    flex: 1;
    min-width: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: inherit;
    padding-inline: 0;
}

/* The submit is deliberately kept as text on the shared underline rather than
   dressed as a button — the panel around it is already the call to action, and
   a box inside it competes with its own framing. It is therefore bare text by
   role (docs/lankar-och-knappar.md): the generic form button above hands it a
   fill and a lift, and it gives all three back for the underline. */
.wf-underlinefield button {
    border: 0;
    background: transparent;
    color: inherit;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--small);
    /* The submit used to hold one line at any cost, which was affordable while
       it was the shorter half of the row. At a doubled text size "Prenumerera"
       alone is wider than the panel it stands in, and one line at any cost
       became one line off the screen — so nothing here keeps it on one line, and
       it breaks by the shared control rule above like every other button. */
    cursor: pointer;
    padding-inline: 0;
}

.wf-underlinefield button:hover {
    transform: none;
    box-shadow: none;
    text-decoration: underline;
}

/* Family and size come from the read-me fine-print rule the panel's consent
   line shares with prenumerera's; only the panel's own spacing and its lighter
   ink on the dark ground belong here. */
.wf-subscribe-form .wf-footnote {
    margin: var(--wp--preset--spacing--small) 0 0;
    color: var(--wp--preset--color--border-light);
}

/* ---------- Stacked form (subscribe page) ---------- */

.wf-form-stacked { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--wp--preset--spacing--medium); max-width: var(--wp--style--global--content-size); margin-block-end: 0; }

/* A note box following the stacked form (prenumerera's personuppgifter box)
   sits centred between the submit button and the section's bottom edge: its
   top margin equals the band's bottom padding, so the gaps read equal. */
.wf-form-stacked + .wf-panel { margin-block-start: var(--wp--preset--spacing--x-large); }
/* The system's one field-label expression, shared by the stacked form and the
   filter frame's dropdowns, so a labelled control looks the same everywhere. */
.wf-form-stacked label,
.wf-filter-field > label { display: block; font-family: var(--wp--preset--font-family--primary); font-weight: var(--wp--custom--font-weight--semi-bold); font-size: var(--wp--preset--font-size--x-small); text-transform: uppercase; letter-spacing: .06em; margin-bottom: calc(var(--wp--preset--spacing--small) / 2); }
.wf-form-stacked input[type="text"], .wf-form-stacked input[type="email"] { width: 100%; }
/* min-width: 0 because a fieldset is the one element whose minimum width the
   user agent sets and no cascade reaches: it defaults to min-content, which is
   not a value any author stylesheet wrote and not one any grid track can
   override. Harmless while the legend and the checkbox labels are shorter than
   the band; at a doubled text size "Pressmeddelanden" made the subscribe form
   165 px wider than the page. */
.wf-form-stacked fieldset { min-width: 0; border: var(--wf-border-w) solid var(--wp--preset--color--border-light); border-radius: var(--wp--preset--border-radius--lg); padding: var(--wp--preset--spacing--medium); }
.wf-form-stacked legend { font-family: var(--wp--preset--font-family--primary); font-weight: var(--wp--custom--font-weight--semi-bold); font-size: var(--wp--preset--font-size--x-small); text-transform: uppercase; letter-spacing: .06em; }
/* Checkbox labels are ordinary sans body text, so they reset the field label's
   uppercase SemiBold treatment back to a regular running weight. */
.wf-form-stacked fieldset label { display: flex; align-items: center; gap: var(--wp--preset--spacing--small); font-weight: var(--wp--custom--font-weight--regular); font-size: var(--wp--preset--font-size--small); text-transform: none; letter-spacing: 0; margin: 0 0 var(--wp--preset--spacing--small); }
.wf-form-stacked input[type="checkbox"] { padding: 0; }

/* On/off switches for the content choices: the checkbox itself is restyled
   as a pill track whose knob slides right when checked. */
.wf-switch input[type="checkbox"] {
    appearance: none;
    position: relative;
    flex: none;
    width: 2.6em;
    height: 1.4em;
    padding: 0;
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--full);
    background: var(--wp--preset--color--tertiary);
    cursor: pointer;
}

.wf-switch input[type="checkbox"]::before {
    content: "";
    position: absolute;
    top: 50%;
    left: .15em;
    translate: 0 -50%;
    width: 1em;
    height: 1em;
    border-radius: 50%;
    background: var(--wp--preset--color--base);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    transition: left .15s ease;
}

.wf-switch input[type="checkbox"]:checked {
    background: var(--wp--preset--color--primary);
    border-color: var(--wp--preset--color--primary);
}

.wf-switch input[type="checkbox"]:checked::before {
    left: calc(100% - 1.25em);
    border-color: var(--wp--preset--color--base);
}

/* Each choice carries a line naming when that content is sent, so the row is a
   switch beside a two-line block and no longer beside a single word. Grid and
   not flex, because the switch has to centre against the name alone: centred
   against the whole block it would float down into the gap between the two
   lines, and aligned to the top it would sit high against a name that wraps. */
.wf-form-stacked fieldset .wf-switch {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 0 var(--wp--preset--spacing--small);
    margin: 0 0 var(--wp--preset--spacing--medium);
}

.wf-form-stacked fieldset .wf-switch:last-child { margin-bottom: 0; }

/* The label is a grid cell now, so it drops the row layout and the trailing
   margin it carried while it was the flex container. */
.wf-form-stacked fieldset .wf-switch label { display: block; margin: 0; cursor: pointer; }

/* The cadence line is the consent panel's fine print set beside the field it
   describes: same sans, same muted ink, one step down in size. It is tied to
   its switch with aria-describedby rather than folded into the label, so the
   accessible name stays the choice itself and the cadence is read after it. */
.wf-switch-takt {
    grid-column: 2;
    font-family: var(--wp--preset--font-family--primary);
    font-size: var(--wp--preset--font-size--x-small);
    color: var(--wp--preset--color--secondary);
    max-width: 60ch;
    margin: calc(var(--wp--preset--spacing--small) / 4) 0 0;
}

form button, .wf-form-stacked button {
    background: var(--wp--preset--color--primary);
    color: var(--wp--preset--color--base);
    border-color: var(--wp--preset--color--primary);
    font-weight: var(--wp--custom--font-weight--bold);
    text-transform: uppercase;
    letter-spacing: .02em;
    font-size: var(--wp--preset--font-size--small);
    cursor: pointer;
}

/* ---------- Period toggles (charts) ---------- */

.wf-toggles { display: flex; flex-wrap: wrap; gap: calc(var(--wp--preset--spacing--small) / 2); margin-bottom: var(--wp--preset--spacing--small); }
.wf-toggles button { cursor: pointer; font-family: var(--wp--preset--font-family--primary); font-size: var(--wp--preset--font-size--x-small); }
.wf-toggles button[aria-pressed="true"] { background: var(--wp--preset--color--main); color: var(--wp--preset--color--base); border-color: var(--wp--preset--color--main); }

/* ---------- Stock chart (kursgraf) ---------- */

/* The real, interactive share-price graph (chart.js). It stands where a photo
   slot would, so it takes the same ratio and, inside .wf-chartpanel, absorbs
   surplus height exactly as .wf-chart does — that is what keeps the panel level
   with the Aktiefakta widget beside it. */
.wf-stockchart {
    display: flex;
    flex-direction: column;
    gap: var(--wp--preset--spacing--small);
    aspect-ratio: var(--wf-ratio-photo);
    min-height: 16rem;
}

.wf-chartpanel .wf-stockchart { flex: 1; }

/* The readout stands in for a floating tooltip: it is the current-price display
   until the pointer enters the plot, which is why it takes the full framhävt
   mätvärde treatment rather than tooltip styling. It wraps on narrow screens. */
.wf-stockchart-readout {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: calc(var(--wp--preset--spacing--small) / 2) var(--wp--preset--spacing--small);
    margin: 0;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    line-height: var(--wp--custom--line-height--none);
}

/* Figure and unit are one measurement, so they keep a word space between them
   while the readout's own gap separates the three fields. */
.wf-stockchart-value { display: inline-flex; align-items: baseline; gap: .35em; color: var(--ds-purple-dawn-deep); }
.wf-stockchart-price { font-size: var(--wp--preset--font-size--medium); }

/* Half the figure's grade, per the KPI pattern — but the share price is set from
   medium rather than the KPI panels' large, so half of it reached 10 px on a
   phone, the smallest type on the site. It takes the floor; the proportion is
   "ungefär halva graden" (docs/typografi.md) and gives way at the bottom end. */
.wf-stockchart-unit { font-size: max(var(--wf-font-size-floor), calc(var(--wp--preset--font-size--medium) / 2)); }

/* The palette has no red and no green, so direction is carried by the deep
   Purple dawn against the neutral mid — the same pairing the rest of the sheet
   uses for emphasised versus quiet metadata. */
.wf-stockchart-change { font-size: var(--wp--preset--font-size--small); }
.wf-stockchart-change-up { color: var(--ds-purple-dawn-deep); }
.wf-stockchart-change-down { color: var(--wp--preset--color--secondary); }
.wf-stockchart-date { font-size: var(--wp--preset--font-size--x-small); color: var(--wp--preset--color--secondary); }

/* The plot is the labelled, focusable object; the SVG inside is pure paint,
   placed out of flow so measuring the box can never feed back into its own
   size. The axis type size is set here because chart.js derives its insets and
   dot radius from it. Pointer gestures drive the crosshair, so the browser must
   not claim them for scrolling. */
.wf-stockchart-plot {
    position: relative;
    flex: 1;
    min-height: 0;
    touch-action: none;
    font-size: var(--wp--preset--font-size--x-small);
}

.wf-stockchart-canvas { position: absolute; inset: 0; }

/* The plot is a bare box with no corner of its own, so the shared focus ring
   has nothing to take a radius from and would square off inside the chart
   frame's own xs corners. The radius is set on the focus state alone because
   that is the only moment anything is drawn to round. */
.wf-stockchart-plot:focus-visible { border-radius: var(--wp--preset--border-radius--xs); }

/* Axis labels are liten bruksmono per docs/typografi.md. The x-axis anchors its
   outer two labels inwards so neither is clipped at an edge. */
.wf-stockchart-tick,
.wf-stockchart-xlabel {
    font-family: var(--wp--preset--font-family--monospace);
    font-size: var(--wp--preset--font-size--x-small);
    font-weight: var(--wp--custom--font-weight--light);
    fill: var(--wp--preset--color--secondary);
}

.wf-stockchart-tick { text-anchor: end; dominant-baseline: middle; }
.wf-stockchart-xlabel { text-anchor: middle; dominant-baseline: hanging; }
.wf-stockchart-xlabel-first { text-anchor: start; }
.wf-stockchart-xlabel-last { text-anchor: end; }

/* Gridlines are hairlines and must stay hairlines: crispEdges plus the
   half-pixel offsets chart.js applies keeps them on one device row instead of
   smearing across two. The axis rule is firmer because it is a boundary, not a
   reading aid. */
.wf-stockchart-grid,
.wf-stockchart-axis,
.wf-stockchart-crosshair {
    stroke-width: var(--wf-border-w);
    shape-rendering: crispEdges;
}

.wf-stockchart-grid { stroke: var(--wp--preset--color--border-light); }
.wf-stockchart-axis { stroke: color-mix(in srgb, var(--wp--preset--color--secondary) 40%, transparent); }
.wf-stockchart-crosshair { stroke: color-mix(in srgb, var(--wp--preset--color--secondary) 55%, transparent); }

/* The curve is a graphical object, so Purple dawn itself is fine here at
   4.06:1; the fill fades it into the white panel instead of boxing the plot in
   a tint, which would cost the line its contrast. */
.wf-stockchart-line {
    fill: none;
    stroke: var(--wp--preset--color--primary-alt);
    stroke-width: calc(var(--wf-border-w) * 2);
    stroke-linejoin: round;
    stroke-linecap: round;
}

.wf-stockchart-fill-top { stop-color: var(--wp--preset--color--primary-alt); stop-opacity: .22; }
.wf-stockchart-fill-bottom { stop-color: var(--wp--preset--color--primary-alt); stop-opacity: 0; }

/* The marker exists in the DOM at all times and is only made visible, so
   hovering never rebuilds the SVG. Its dot carries a white halo so it stays
   legible where the curve passes under it. */
.wf-stockchart-marker { visibility: hidden; }
.wf-stockchart-marker.is-visible { visibility: visible; }

.wf-stockchart-dot {
    fill: var(--wp--preset--color--primary-alt);
    stroke: var(--wp--preset--color--base);
    stroke-width: calc(var(--wf-border-w) * 2);
}

/* ---------- KPI development chart ---------- */

/* Cash at each quarter end against the quarter's own result (chart.js). Unlike
   the share-price graph it stands alone across a wide band, where a photo ratio
   would make it absurdly tall, so it is constrained by height alone: a floor
   that keeps twelve bars and their labels readable, a ceiling that keeps the
   table below it within reach, and growth with the band in between. */
.wf-kpichart {
    display: flex;
    flex-direction: column;
    gap: var(--wp--preset--spacing--small);
    height: 34vw;
    min-height: 14rem;
    max-height: 21rem;
}

/* Same reasoning as the share-price readout: it is the latest-quarter display
   until the pointer enters the plot, so it takes the framhävt mätvärde
   treatment rather than tooltip styling, and wraps on narrow screens. */
.wf-kpichart-readout {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: calc(var(--wp--preset--spacing--small) / 2) var(--wp--preset--spacing--small);
    margin: 0;
    font-family: var(--wp--preset--font-family--monospace);
    font-weight: var(--wp--custom--font-weight--light);
    line-height: var(--wp--custom--line-height--none);
}

/* Two figures on one line need naming, so each carries a mono capital tag —
   the eyebrow role docs/typografi.md allows. The emphasis matches the plot:
   the cash figure in the deep Purple dawn its line takes, the result in the
   neutral mid its bars take. */
.wf-kpichart-cash,
.wf-kpichart-result { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: .35em; }
.wf-kpichart-cash { color: var(--ds-purple-dawn-deep); font-size: var(--wp--preset--font-size--medium); }
.wf-kpichart-result { color: var(--wp--preset--color--secondary); font-size: var(--wp--preset--font-size--small); }

.wf-kpichart-tag { font-size: var(--wp--preset--font-size--x-small); text-transform: uppercase; color: var(--wp--preset--color--secondary); }
.wf-kpichart-unit { font-size: var(--wp--preset--font-size--x-small); }
.wf-kpichart-quarter { font-size: var(--wp--preset--font-size--x-small); color: var(--wp--preset--color--secondary); }

/* The legend, sat between the readout and the plot. It is decoration, not a
   control — aria-hidden in the markup, so it needs no focus style and no
   hover response of its own — and takes the liten bruksmono treatment the
   axis labels already carry, wrapping inline on narrow widths rather than
   forcing the panel wider. */
.wf-kpichart-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: calc(var(--wp--preset--spacing--small) / 2) var(--wp--preset--spacing--medium);
    margin: 0;
    font-family: var(--wp--preset--font-family--monospace);
    font-size: var(--wp--preset--font-size--x-small);
    font-weight: var(--wp--custom--font-weight--light);
    color: var(--wp--preset--color--secondary);
}

.wf-kpichart-legend-item { display: inline-flex; align-items: center; gap: .4em; }

/* The line swatch echoes the cash line's own colour and stroke weight; the
   bar swatch echoes the bars' quiet fill. Both are fixed-size decoration
   rather than scaled SVG, so a plain div stands in for each. */
.wf-kpichart-legend-line {
    width: 1.1em;
    height: calc(var(--wf-border-w) * 2);
    background: var(--ds-purple-dawn-deep);
    border-radius: var(--wf-border-w);
}

.wf-kpichart-legend-bar {
    width: .7em;
    height: .7em;
    background: color-mix(in srgb, var(--wp--preset--color--secondary) 45%, transparent);
    border-radius: var(--wp--preset--border-radius--xs);
}

/* The plot is the labelled, focusable object; the SVG inside is pure paint,
   placed out of flow so measuring the box can never feed back into its own
   size. The axis type size is set here because chart.js derives its insets,
   label stride and dot radii from it. Pointer gestures drive the crosshair, so
   the browser must not claim them for scrolling. */
.wf-kpichart-plot {
    position: relative;
    flex: 1;
    min-height: 0;
    touch-action: none;
    font-size: var(--wp--preset--font-size--x-small);
}

.wf-kpichart-canvas { position: absolute; inset: 0; }

/* Same as the share chart's plot: a bare box, rounded only while the ring is
   drawn, so the ring echoes the chart frame's own xs corners. */
.wf-kpichart-plot:focus-visible { border-radius: var(--wp--preset--border-radius--xs); }

/* Axis and annotation labels are liten bruksmono per docs/typografi.md. */
.wf-kpichart-tick,
.wf-kpichart-xlabel,
.wf-kpichart-eventlabel {
    font-family: var(--wp--preset--font-family--monospace);
    font-size: var(--wp--preset--font-size--x-small);
    font-weight: var(--wp--custom--font-weight--light);
    fill: var(--wp--preset--color--secondary);
}

.wf-kpichart-tick { text-anchor: end; dominant-baseline: middle; }
.wf-kpichart-xlabel { text-anchor: middle; dominant-baseline: hanging; }
.wf-kpichart-eventlabel { text-anchor: middle; }

/* Hairlines must stay hairlines: crispEdges plus the half-pixel offsets
   chart.js applies keeps them on one device row instead of smearing across
   two. The zero rule is firmer than the gridlines because the bars hang from
   it; the emission markers are dashed because they annotate the plot rather
   than measure it. */
.wf-kpichart-grid,
.wf-kpichart-axis,
.wf-kpichart-event,
.wf-kpichart-crosshair {
    stroke-width: var(--wf-border-w);
    shape-rendering: crispEdges;
}

.wf-kpichart-grid { stroke: var(--wp--preset--color--border-light); }
.wf-kpichart-axis { stroke: color-mix(in srgb, var(--wp--preset--color--secondary) 55%, transparent); }
.wf-kpichart-crosshair { stroke: color-mix(in srgb, var(--wp--preset--color--secondary) 55%, transparent); }
.wf-kpichart-event { stroke: color-mix(in srgb, var(--wp--preset--color--secondary) 45%, transparent); stroke-dasharray: 3 3; }

/* The palette has no red and no green, so the two series are told apart by the
   same pairing the rest of the sheet uses for emphasised versus quiet: the cash
   line in the deep Purple dawn, the loss bars in a tint of the neutral mid.
   The line takes the deep tone rather than Purple dawn itself because it runs
   under the emission hairlines and over the bars, where 4.06:1 is thin. */
.wf-kpichart-line {
    fill: none;
    stroke: var(--ds-purple-dawn-deep);
    stroke-width: calc(var(--wf-border-w) * 2);
    stroke-linejoin: round;
    stroke-linecap: round;
}

.wf-kpichart-point { fill: var(--ds-purple-dawn-deep); }
.wf-kpichart-bar { fill: color-mix(in srgb, var(--wp--preset--color--secondary) 45%, transparent); }

/* The marker exists in the DOM at all times and is only made visible, so
   hovering never rebuilds the SVG. Its dot carries a white halo so it stays
   legible where the cash line passes under it. */
.wf-kpichart-marker { visibility: hidden; }
.wf-kpichart-marker.is-visible { visibility: visible; }

.wf-kpichart-dot {
    fill: var(--ds-purple-dawn-deep);
    stroke: var(--wp--preset--color--base);
    stroke-width: calc(var(--wf-border-w) * 2);
}

/* ---------- Prose (full-text documents, press body) ---------- */

.wf-prose { max-width: var(--wp--style--global--content-size); }
.wf-prose h2 { font-size: var(--wp--preset--font-size--medium); font-weight: var(--wp--custom--font-weight--medium); letter-spacing: -.01em; margin-top: var(--wp--preset--spacing--large); display: flex; gap: .5em; align-items: baseline; }
.wf-prose h3 { font-size: var(--wp--preset--font-size--base); margin-top: var(--wp--preset--spacing--medium); }
.wf-prose p { max-width: 64ch; }

/* ---------- Columns section (Hem offentliggöranden) ---------- */

/* A column's heading is its eyebrow, which brings its own type — a size step
   keyed to the bare `h2` would fight it, so there is none. */
.wf-col .wf-pressitem { padding-block: var(--wp--preset--spacing--small); }
.wf-col .wf-pressitem :is(h2, h3) { font-size: var(--wp--preset--font-size--base); font-weight: var(--wp--custom--font-weight--regular); }
.wf-col .wf-event { padding-block: var(--wp--preset--spacing--small); }

/* Let the text block shrink in narrow columns so the date chip stays on the
   same row, in front of the title and metadata lines. */
.wf-col .wf-event > div:not(.wf-datechip) { min-width: 0; }
.wf-morelink { margin-top: var(--wp--preset--spacing--small); }

/* ---------- Jurisdiktionsgate ---------- */

/* Phase 4's surface 18: a jurisdiction warning laid over a page that carries
   offer material during a subscription period. It is *not* an access control —
   the content stays in the document, the blur is aesthetic, and nothing here
   tries to make the gate hard to pass. What it must do is stand without
   JavaScript, because it is that the warning was shown which has legal weight.

   So it is not a `dialog`. `showModal()` needs a script, and `<dialog open>` in
   static HTML is not modal at all: no backdrop, no inerting, no top layer. The
   role comes from the attributes in the markup, the modality from `inert` on
   everything beside it, and the scroll lock from the rule below. Escape does
   nothing, which is the wanted behaviour and follows from not being a `dialog`.

   Nothing animates, so there is no `prefers-reduced-motion` branch to get
   wrong: a warning that fades in is a warning that was not there yet. */
.wf-gate {
    position: fixed;
    z-index: 9000;
    inset: 0;
    display: grid;
    place-items: center;
    padding: var(--wp--preset--spacing--medium);
    overflow: auto;
    overscroll-behavior: contain;
    background: rgba(20, 17, 31, .55);
    backdrop-filter: blur(10px);
}

/* The panel is opaque, which is what makes contrast decidable at all: the blur
   behind it is an unpredictable photograph or band, and text over that could
   not be checked once and stay checked. Everything readable sits on `base`. */
.wf-gate-panel {
    display: flex;
    flex-direction: column;
    width: min(100%, 46rem);
    max-height: calc(100dvh - var(--wp--preset--spacing--medium) * 2);
    border: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    border-radius: var(--wp--preset--border-radius--lg);
    background: var(--wp--preset--color--base);
    box-shadow: var(--wf-lift-shadow);
    color: var(--wp--preset--color--main);
    overflow: hidden;
}

/* The scroll sits one level in so the panel can clip it. Firefox paints a
   scrollbar on the scrolling element's own box, which squares off whichever
   rounded corner it runs into; inside `overflow: hidden` it is clipped by the
   radius like any other child. `min-height: 0` is what lets a flex item shrink
   below its content and actually scroll. */
.wf-gate-body {
    min-height: 0;
    padding: var(--wp--preset--spacing--large);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The panel takes focus on open so the reader starts at the heading, and the
   ring is suppressed for that one reason: it is a container, not a control.
   Nothing can tab to it — `tabindex="-1"` sees to that — so no keyboard user
   ever lands here by navigating, and a ring drawn around the whole dialog
   would claim it is focusable when the two answers below are. */
.wf-gate-panel:focus, .wf-gate-panel:focus-visible { outline: none; }

.wf-gate-body > * + * { margin-top: var(--wp--preset--spacing--small); }
.wf-gate-body p { max-width: 64ch; }

.wf-gate-title {
    font-size: var(--wp--preset--font-size--medium);
    font-weight: var(--wp--custom--font-weight--light);
    letter-spacing: -.01em;
}

.wf-gate-lead { font-weight: var(--wp--custom--font-weight--medium); }

.wf-gate-terms {
    max-width: 64ch;
    padding-left: 1.25em;
    list-style: disc;
}

/* The two answers step down from the CTA's 1.25rem, exactly as the header's
   button does and for the same reason: that size is calibrated for a CTA that
   carries a full-width band, and inside a panel it shouts. The fill is left
   alone — this is the site's primary action, not chrome. */
.wf-gate-actions .wf-btn {
    font-size: var(--wp--preset--font-size--small);
    padding: .55em 1.2em;
    line-height: var(--wp--custom--line-height--snug);
}

.wf-gate-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wp--preset--spacing--small);
    margin-top: var(--wp--preset--spacing--medium);
}

/* Scroll lock without a script, and without knowing which markup the gate sits
   in. The condition is "this page has a gate, and the gate is not inside a
   hidden ancestor" — true in the product, where the gate stands in the static
   HTML, and false in the wireframe until the review panel selects the mode. */
html:has([data-wf-gate]):not(:has([hidden] [data-wf-gate])) { overflow: hidden; }

/* ---------- Print ---------- */

/* The site's one print section, and the whole of it. Everything above is
   written for a screen; what follows is the same pages read on paper — a
   quarterly report before a meeting, the articles of association, the
   shareholder list, the financial calendar. PRODUCT.md's audience prints, and
   `ir/docs/kriterier-ir-webbplats.md` judges how reachable that material is.

   **It is a common base and not a print design per page.** The rules below are
   the ones that hold for every page in the tree; where a single page still
   comes out ugly and the base is not what makes it so, that is logged rather
   than answered with a rule of its own.

   The one thing this section may not lean on is `print-color-adjust: exact`.
   Whether a background is printed at all is the reader's setting, not ours —
   Chrome's *Background graphics* is off by default — so anything whose meaning
   is carried by a fill has to be given a form that survives the fill being
   dropped. Hence outline pills below, and hence every inverted ground being
   neutralised here explicitly rather than left to the browser's stripping: with
   backgrounds turned on, a rule that only *expects* the navy to go would print
   white text on navy after all. `exact` is spent once, on the two graphs, where
   the colour is the data.

   No source line is added to the page. The browser prints the address and the
   date in its own page footer, and the retrieval date is already in the source
   notes — a second one would be this sheet stating something it cannot know. */
@media print {

    /* The page box. Generous rather than tight: the browser's own header and
       footer are drawn inside this margin, and a narrow one puts the site's
       first line of text under the URL. */
    @page {
        margin: 18mm 16mm;
    }

    /* Four variables carry most of what has to change, so most of what has to
       change is a matter of setting them. The body's top padding, the hero's
       clearance under a fixed band, the megamenu's offset, every band's inner
       grid and every grown hit area read from these and follow without being
       told.

       `--wf-header-h` is what the page holds clear of a fixed header, and
       nothing is fixed on paper. `--wf-gutter` is the band grid's side margin,
       and `@page` has drawn that margin already.

       The pointer-target floor is 24 px because a finger is, and paper has no
       finger — but it does not merely stop earning its keep here, it costs. The
       padding on a bare-text link hangs *outside* the line box, so on four hub
       pages the footer's last row of policy links reached three pixels past the
       end of the sheet and bought a second one, carrying nothing but the
       transparent bottom of a padding box. Three pixels of hit area, one sheet
       of paper, and the sheet was blank. */
    :root {
        --wf-header-h: 0px;
        --wf-gutter: 0px;
        --wf-target-min: 0px;
        --wf-target-pad: 0px;
    }

    /* -- Chrome ------------------------------------------------------------ */

    /* The header is reduced to the wordmark and rejoins the flow, which is what
       puts it on the first sheet and only there. A sheet from a shareholder
       list that does not name the company is an anonymous paper, and the
       footer's fine print — the other place the sender is named — lands on page
       nine. The language switch, the menu toggle and the CTA are controls and
       go with the rest of them. */
    .wf-siteheader {
        position: static;
        background: none;
        backdrop-filter: none;
        border-bottom: 0;
    }

    .wf-siteheader .wf-inner { min-height: 0; padding-block: 0; }
    .wf-actions { display: none; }

    /* Over a hero the header draws itself in white, which on paper is nothing
       at all. The overlay state is answered in its own shape so the cascade
       decides this by order rather than by specificity. */
    .wf-siteheader[data-overlay] .wf-logo {
        color: var(--wp--preset--color--main);
        text-shadow: none;
    }

    .wf-siteheader[data-overlay] .wf-logo span { color: var(--wp--preset--color--secondary); }

    /* Controls, and nothing but controls. Each is a thing to press, and a
       pressed thing on paper is ink spent on an action the reader cannot take:
       the megamenu, the keyboard's bypass, the archive filters and their status
       line, the pagination, the two subscription panels, the in-page calls to
       action, and the video overlay itself if a reader prints with one open.

       The review layer is here for a different reason — it is not the product.
       **The selector is quoted from `agents.d/granskningsverktyg.md` and may be
       widened, never renamed**: every measurement in the repository quotes it,
       and a rename would silently stop excluding the tool from its own results.
       The Inspector Panel also hides itself at print from its own injected
       stylesheet, with a narrower list that does not reach the variant
       switcher. Both stand: the panel's own rule is what keeps it correct when
       phase 7's step 7.8 lifts it out of this context. */
    .wf-mega,
    .wf-skiplink,
    .wf-filter,
    .wf-filter-stacked,
    .wf-filter-status,
    .wf-pagination,
    .wf-subscribepanel,
    .wf-videomodal,
    .wf-btnrow,
    main .wf-btn,
    [class*="wfv-"],
    [class*="wfc-"],
    [class*="wfi-"] {
        display: none;
    }

    /* A band whose whole content was the subscription panel would otherwise
       print as its own padding. */
    .wf-band:has(.wf-subscribepanel) { display: none; }

    /* What the pagination leaves behind (ADR 0019). With the control gone, a
       sheet carrying ten of 145 rows and a sheet carrying all 145 are the same
       sheet to look at, and the reader who files the first one has filed
       something they cannot tell is a fragment. The line says which it is —
       *145 transaktioner*, or *12 av 217 pressmeddelanden*, or the page of how
       many where the sheet holds one of several.

       It takes the rule the pagination had above it: the list has ended, and a
       count set straight against the last row would read as another row. Quiet
       text, because it is a statement about the sheet and not part of it. */
    .wf-listnote {
        display: block;
        border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
        padding-top: var(--wp--preset--spacing--small);
        margin-top: var(--wp--preset--spacing--small);
        font-family: var(--wp--preset--font-family--primary);
        font-size: var(--wp--preset--font-size--small);
        color: var(--wp--preset--color--secondary);
    }

    /* Breadcrumbs stay, as text. On paper they are not navigation but context —
       *where in this site does the sheet in my hand come from* — which is worth
       more here than on a screen, where the reader can look at the address bar.
       They keep the ink they had; their transparent hit extension collapses onto
       their own box with the target floor above, so nothing is stated here. */

    /* The site footer keeps its fine print and drops everything else. What is
       left is the IR contact, the Certified Adviser, the company name and its
       registration number — which is exactly what a printed document from a
       listed company should carry — inverted onto the paper's own white. */
    .wf-sitefooter {
        position: static;
        padding-block: var(--wp--preset--spacing--medium) 0;
    }

    .wf-footgrid { display: none; }

    .wf-fineprint {
        margin-top: var(--wp--preset--spacing--medium);
        color: var(--wp--preset--color--secondary);
        border-top-color: var(--wp--preset--color--border-light);
    }

    /* -- Dark grounds ------------------------------------------------------ */

    /* Every inverted ground goes to the paper's own white, and every rule that
       painted text against it is answered in the shape it was written in, so
       that the cascade settles this by order and not by a war of specificity.
       Restating a selector is deliberate here: the alternative is `!important`,
       and this sheet carries none.

       The hero is treated as the same case. It keeps its eyebrow and its `h1`
       and loses the photograph and the dark ground under them; the ingress is
       not dropped with them, because it is the page's own text and the paper is
       poorer without it. What goes from the hero is the pair of buttons, with
       every other call to action above. */
    .wf-band-dark,
    .wf-panel-dark,
    .wf-kpi-dark,
    .wf-herofull,
    .wf-sitefooter {
        background: none;
        color: var(--wp--preset--color--main);
        --wf-numeral-color: var(--ds-purple-dawn-deep);
    }

    .wf-herofull {
        min-height: 0;
        background-image: none;
        padding-block: 0 var(--wp--preset--spacing--medium);
    }

    .wf-band-dark a,
    .wf-panel-dark a,
    .wf-sitefooter a,
    .wf-band-dark .wf-measure a,
    .wf-band-dark .wf-lead a,
    .wf-band-dark .wf-prose a,
    .wf-band-dark .wf-arrowlink,
    .wf-panel-dark .wf-measure a,
    .wf-panel-dark .wf-arrowlink,
    .wf-fineprint .wf-finelabel {
        color: var(--wp--preset--color--main);
    }

    .wf-band-dark .wf-lead,
    .wf-band-dark .wf-meta,
    .wf-band-dark .wf-footnote,
    .wf-band-dark .wf-eyebrow,
    .wf-band-dark .wf-sourcenote,
    .wf-band-dark blockquote cite,
    .wf-kpi-dark .wf-kpi-value .wf-unit,
    .wf-kpi-dark .wf-kpi-label,
    .wf-herofull .wf-eyebrow,
    .wf-herofull .wf-lead,
    .wf-herofull .wf-meta,
    .wf-herofull .wf-footnote {
        color: var(--wp--preset--color--secondary);
    }

    .wf-band-dark .wf-eyebrow-num,
    .wf-herofull .wf-eyebrow-num { color: var(--ds-purple-dawn-deep); }

    /* The quote rule swapped to the light neutral for the dark ground; on paper
       it is back on white and takes the ordinary one. */
    .wf-band-dark blockquote { border-inline-start-color: var(--wp--preset--color--border-dark); }

    /* -- Markings ---------------------------------------------------------- */

    /* **The gravest single defect this section answers.** *Regulatorisk* is a
       filled navy pill with white text, and with the reader's background
       graphics off it prints white on white: the one fact that separates a
       regulatory disclosure from a commercial one leaves the paper without a
       trace. Every pill therefore takes outline form — a border and black text
       — which holds in black and white and depends on no setting of anybody's.

       The three variants and the two dark-ground rules are restated in their
       own shapes for the reason given above; `.wf-band-dark .wf-pill:not(…)`
       outranks a plain `.wf-pill` and would otherwise keep its wash. */
    .wf-pill,
    .wf-pill-solid,
    .wf-pill-accent,
    .wf-pill-outline,
    .wf-band-dark .wf-pill:not(.wf-pill-solid, .wf-pill-accent, .wf-pill-outline),
    .wf-panel-dark .wf-pill:not(.wf-pill-solid, .wf-pill-accent, .wf-pill-outline) {
        background: none;
        border-color: var(--wp--preset--color--main);
        color: var(--wp--preset--color--main);
    }

    /* Affordances drawn as a mask over `background-color`: they are exactly the
       ink a reader's background setting takes away, so on paper they are either
       absent or an empty box holding a space open. Their labels stay and say
       what they said. The assignment list's ring markers and the year rule are
       borders rather than backgrounds and print either way, so they are not
       here. */
    .wf-cross-arrow,
    .wf-dlmark,
    .wf-dllink::before,
    .wf-chip-cal::before,
    .wf-person-yes::before,
    summary::after {
        display: none;
    }

    /* -- What the removals leave behind ------------------------------------ */

    /* ADR 0021 points 3 and 4, which are one rule: **what is left when the
       print has taken something away may not keep the size that only worked
       together with the thing that has gone.** Two cases and three rules, and
       they are cases of that one rule rather than separate ugliness reports.

       **A height that exists to be pointed at.** A link card is nine or eleven
       rem tall because a finger has to land on it and because a row of them has
       to read as a row; on paper it is an empty box with a number and a heading
       in the corner of it, and six of them cost the front page a sheet. This is
       the same family as `--wf-target-min` above, which is already zeroed here
       for exactly this reason, and it follows it. The cards keep their frame,
       their padding and their order — only the floor goes, so each one is as
       tall as what it says. */
    .wf-bento,
    .wf-cross {
        min-height: 0;
    }

    /* And the promise that a wrapped row of them shares one height goes with
       the floor, one step further on the same reason: **it is a promise about
       a row the reader takes in at once, and a sheet cannot keep it.** A `1fr`
       auto row sizes itself from the grid's own height, which a page break
       makes indefinite — so Chrome stops fragmenting the grid and moves all
       six cards whole to the next sheet, leaving half of this one white and
       the heading stranded above nothing. That single declaration was the
       front page's sixth sheet: with it six, without it five, and no line of
       content moved to another column. Height is the dimension ADR 0021
       leaves free. */
    .wf-cardgrid:has(.wf-bento) { grid-auto-rows: auto; }

    /* **A display grade that was sized against a photograph.** The hero's
       heading stands in the largest step of the scale because it lies over a
       picture filling the view; the picture is gone in the dark grounds above,
       and the grade left alone took a third of the first sheet. It takes the
       step every other page's `h1` takes, which is the size it was always in
       scale with once the ground beneath it went. */
    .wf-herofull h1 { font-size: var(--wp--preset--font-size--x-large); }

    /* **ADR 0021's third case is not in the tree, and no rule is written for
       it.** An image frame with no photograph prints as an empty outlined box,
       because its placeholder mark is a mask over a ground the reader's
       browser may drop — but **385 of the tree's 386 frames hold an `<img>`**,
       and the one that does not is the initials plate, whose mark is two
       letters set in type. Type prints. */

    /* -- The two graphs ---------------------------------------------------- */

    /* The one place colour is data rather than decoration, so the one place
       `print-color-adjust: exact` is spent: the share-price curve and the cash
       line are told apart from the loss bars by nothing else, and a reader's
       background setting must not be what decides whether the two series can be
       told apart at all.

       The period switches go with the other controls. What prints is the period
       the reader had chosen, which is the right answer. */
    .wf-stockchart,
    .wf-kpichart {
        print-color-adjust: exact;
        -webkit-print-color-adjust: exact;
    }

    .wf-toggles { display: none; }

    /* And the drawing is made to fit the box it is given, which the `viewBox`
       makes a matter of scale rather than of redrawing.

       `chart.js` sizes the SVG in CSS pixels — `width` and `height` attributes
       and a `viewBox` to match — so that its text and strokes are set rather
       than scaled, and a `ResizeObserver` keeps the three in step as the band
       changes width. On paper there is no such observer: **Firefox prints from a
       static clone of the document and runs no script in it**, so the graph
       arrives at the width of the window it was last drawn in. Printed from a
       1 280 px window it was 800 px of curve laid across a 490 px panel and over
       the source note beneath it.

       An absolutely positioned SVG does not take its size from `inset: 0`: it is
       a replaced element, its `width: auto` resolves to the intrinsic width — the
       attribute — and the over-constrained inset is dropped. Stating the two
       percentages is what hands the box back to the layout, and the `viewBox`
       then scales the drawing into it. No script changes; this is the whole of
       what the graphs needed. */
    .wf-stockchart-canvas,
    .wf-kpichart-canvas {
        width: 100%;
        height: 100%;
    }

    /* -- Collapsed states -------------------------------------------------- */

    /* Everything folded away is opened, because paper has no way to unfold it.
       Frågor och svar is otherwise a sheet of questions with no answers, and a
       person card's assignments stop after two.

       `::details-content` is the box the browser wraps an answer in, and a
       closed `details` gets `content-visibility: hidden` on it from the user
       agent — which is why forcing the answer open is a matter of that property
       and the grid row, and not of `display` on anything in the markup. The
       transitions go with them: nothing here is animating, and a transition on
       a property this section has just set is a way of setting it late. */
    details::details-content {
        content-visibility: visible;
        grid-template-rows: 1fr;
        overflow: visible;
        transition: none;
    }

    .wf-clamp,
    .wf-clamp.is-animating {
        display: block;
        -webkit-line-clamp: none;
        overflow: visible;
        max-height: none;
    }

    .wf-clamp-list:not(.is-expanded) > li:nth-child(n+3) { display: list-item; }

    .wf-morebtn { display: none; }

    /* -- Overflow ---------------------------------------------------------- */

    /* A box that scrolls on screen clips on paper: there is no second gesture,
       so whatever hangs outside the box is simply not printed. Both of these
       are opened, and a wide table is then allowed to be wide — whether any of
       them is *too* wide for the sheet is what `tools/print-check.mjs` measures,
       and the remedy if one is would be a smaller figure rather than a landscape
       page (a named `@page` rule holds in Chrome and not in Firefox).

       The scroll cue's gradients go with the scrolling they announced. */
    .wf-tablewrap {
        overflow: visible;
        background: none;
    }

    .wf-person { overflow: visible; }

    /* The frame around a table goes, and it is the first of the four things a
       wide table needed. On screen the rounded box is what tells a table apart
       from the band around it; on paper the sheet's own margin does that, and
       the frame's side padding is 54 px of a 673 px sheet spent saying something
       the paper has already said. The closing rule the frame used to stand in
       for comes back with it, or the last row would end in mid-air. */
    .wf-tablepanel { padding: 0; border: 0; }
    .wf-tablepanel tbody tr:last-child > * { border-bottom: var(--wf-border-w) solid var(--wp--preset--color--border-light); }

    /* A sheet is 673 px where a desktop band is 1 260, so the two widest tables
       — the insider register at 953 px and the financial overview at 720 —
       reached past the margin and Chrome answered by scaling the whole document
       down to fit: the insider register printed at 71 % of its type, every line
       on the page, to save one table.

       **The remedy is a smaller figure and not a landscape sheet.** A named
       `@page` rule with `size: landscape` holds in Chrome and not in Firefox,
       and Firefox is where this is reviewed; half a target is not a target. So
       the tables take the scale's own floor and a quarter of the cell padding —
       12 px is as small as tabular data may go here, and it is reached rather
       than passed. The padding is stated twice because the narrow-screen rule
       names `.wf-kpitable`'s cells by class and would otherwise keep the wider
       value on the one table with the most columns to spend it on.

       That is still not enough, and the rest is a matter of *which* boxes are
       allowed to give. An auto table layout hands each column its min-content
       width first and shares out what is left, so a column's floor is its
       longest unbreakable run and the table's floor is the sum of them.
       `overflow-wrap: anywhere` takes a floor away — which is the whole reason
       it is the data cells' and nobody else's:

       - **A `th` keeps its word.** Let the header break and the layout squeezes
         the column to its widest header letter, then breaks the word to match:
         *TRANSAKTIO / N*. A column head is two or three words long and is the
         one text in a table that has to be read at a glance.
       - **A figure keeps its integrity**, and it is stated here for every table
         rather than only for the one that already said it: *5 983 459* wrapped
         after the first group is two numbers, and *2025.09.11* broken after the
         second full stop is a date nobody can scan a column of. Both are their
         own kind of cell — `.wf-num` and `<time>` — so both can be named.
       - **What is left is prose in a data cell**, a job title or a shareholder's
         name, and prose is exactly the thing that may take an extra line rather
         than take the page. */
    table { font-size: var(--wp--preset--font-size--x-small); }

    th, td,
    .wf-kpitable :is(th, td) {
        padding: calc(var(--wp--preset--spacing--small) / 2) calc(var(--wp--preset--spacing--small) / 4);
    }

    td { overflow-wrap: anywhere; }

    td time,
    td.wf-num,
    th.wf-num {
        overflow-wrap: normal;
        white-space: nowrap;
    }

    /* -- Page breaks ------------------------------------------------------- */

    /* A table's head repeats over every sheet it runs onto, which is the whole
       difference between a shareholder list and a column of unlabelled figures
       on page two. */
    thead { display: table-header-group; }

    /* Nothing is cut through the middle that reads as one object: a row, a card,
       a list row, one note. Panels and accordions are deliberately not in this
       list — they are containers of arbitrary height, and a container taller
       than the sheet that refuses to break is how a page comes out blank.

       **The person card is the promise stated one level down, because measured
       it does not fit.** Dan Friberg's and Mike Anderson's cards are 1 312 px
       tall on a 986 px sheet once the clamps are lifted, so a rule that held the
       card whole would push each of them onto a sheet of its own and still have
       to break it — a mostly empty sheet bought for a promise that cannot be
       kept anyway. What the promise is actually about survives at the parts: the
       portrait never parts from the name, a field never parts from its label,
       and the holdings band stays whole. Between two fields is where a long card
       may break, and there it reads as a card continued rather than as a card
       damaged. */
    tr,
    figure,
    .wf-cross,
    .wf-bento,
    .wf-contact,
    .wf-articlecard,
    .wf-reportrow,
    .wf-kpi,
    .wf-pressitem,
    .wf-doc,
    .wf-event,
    .wf-endnote,
    .wf-person-head,
    .wf-person-field,
    .wf-person-duo,
    .wf-person-holdings {
        break-inside: avoid;
    }

    /* A heading at the foot of a sheet is a heading for nothing. */
    h1, h2, h3, h4, .wf-eyebrow, .wf-yearrule, .wf-docgroup-label {
        break-after: avoid;
    }

    /* No line stranded alone at either end of a sheet. */
    p, li, blockquote { orphans: 3; widows: 3; }

    /* -- Rhythm ------------------------------------------------------------ */

    /* The bands' screen rhythm is built for a scrolling page — x-large above and
       below every one of them, which on paper is most of a sheet spent on air
       between sections. One medium step per band, and none between two of them,
       since the seam on paper is the space itself: every ground is white here,
       so the alternating fills that used to mark the seam mark nothing. */
    .wf-band { padding-block: var(--wp--preset--spacing--medium); }
    .wf-band + .wf-band { padding-block-start: 0; }
    .wf-crumbbar { padding-block: 0 var(--wp--preset--spacing--small); }

    /* The satsyta grid steps aside, and this is the one rule here that is about
       a browser rather than about paper. Every band's inner box is a three-track
       grid — gutter, wide measure, gutter — and with `--wf-gutter` at zero above
       the two outer tracks are nothing and the middle one is the whole box, so
       on paper the grid computes exactly what a plain block does.

       **Firefox does not fragment a grid container.** It moves the whole box to
       the next sheet instead, and where the box is a whole section that is a
       sheet given away: every press release printed its wordmark and its
       breadcrumbs on sheet one and began the release itself on sheet two,
       measured in Firefox 153, which is the browser this is reviewed in. Chrome
       fragments the same grid without complaint and shows nothing of it — one of
       the two reasons the sample is read in Firefox and not only measured in
       Chrome. `> *` keeps `grid-column: wide`, which a block container ignores.

       This is stated for the *band's* container only, and not for the grids that
       are doing work — card grids, stat grids, the two-column document body.
       Those mean their columns, and a rule that flattened them would answer a
       fragmentation defect by deleting the layout. */
    .wf-band > .wf-inner { display: block; }

    /* -- The document note ------------------------------------------------- */

    /* ADR 0018's block, drawn here and nowhere else. It is hidden in the screen
       cascade (see *Meta, notes, prose*), so this is what gives it back, and the
       markup it dresses is minted by `tools/build.mjs` — a numbered entry per
       address the page names, and a marker beside the link that names it.

       The heading is already a mono label: the build writes it as an
       `h2.wf-eyebrow`, which is this sheet's own expression for exactly that.

       **The marker generates no box of its own, and that is the whole of ADR
       0021 point 2.** It stands inside its link, at the end of it, and it is
       hidden on screen and drawn here — so a box would be an element only paper
       sees, and an element only paper sees becomes a *cell* wherever it stands
       in a grid or a flex row. Inside a `.wf-dllink`, which is a column of icon
       over label, that cell is a third line: `[1]` under UTFALL rather than
       beside it. `display: contents` hands the numeral to the label's own text
       run, where the two are one line of text and no cell at all. The price is
       the raise — `vertical-align` needs a box to move — and a bracketed
       numeral at 70 % is legible on the baseline, which a third line was not
       worth. */
    .wf-endnotes {
        display: block;
        border-top: var(--wf-border-w) solid var(--wp--preset--color--border-light);
    }

    .wf-endnote-list {
        list-style: none;
        margin: 0;
        padding: 0;
        font-size: var(--wp--preset--font-size--x-small);
    }

    .wf-endnote + .wf-endnote { margin-block-start: calc(var(--wp--preset--spacing--small) / 2); }

    .wf-endnote-num,
    .wf-endnote-url {
        font-family: var(--wp--preset--font-family--monospace);
        font-weight: var(--wp--custom--font-weight--light);
        color: var(--wp--preset--color--secondary);
    }

    .wf-endnotemark {
        display: contents;
        font-family: var(--wp--preset--font-family--monospace);
        font-size: .7em;
        color: var(--wp--preset--color--secondary);
    }

    /* -- The jurisdiction gate --------------------------------------------- */

    /* Printing is where the gate can leak, and the reason is technical rather
       than editorial: `filter` and `backdrop-filter` are commonly dropped at
       print, so a page printed with the gate up would carry an unblurred, fully
       legible copy of exactly what the gate holds back — clearer on paper than
       on screen. The rule prints the warning and suppresses everything else;
       after confirmation the gate is gone from the DOM and the page prints
       normally.

       It stands here unchanged from the day it was written, folded into this
       section rather than rewritten by it. Its first rule outranks everything
       above — the header, the crumbs, the footer's fine print — which is what
       makes a gated page print as the warning alone. */
    body:has([data-wf-gate]):not(:has([hidden] [data-wf-gate])) > *:not(:has([data-wf-gate])) { display: none; }

    .wf-gate {
        position: static;
        display: block;
        padding: 0;
        overflow: visible;
        background: none;
        backdrop-filter: none;
    }

    .wf-gate-panel {
        display: block;
        width: auto;
        max-height: none;
        border: 0;
        box-shadow: none;
        overflow: visible;
    }

    .wf-gate-body { padding: 0; overflow: visible; }

    .wf-gate-actions { display: none; }

}
