/* ============================================================
   Vetyr Design System — CORE
   The shared, brand- and domain-neutral design foundation.
   Surfaces, foreground, a themeable accent, type, spacing,
   geometry, generic UI states. Build designs on top of this;
   keep anything brand- or domain-specific out of the core.

   Source of truth: this file (vetyr/specs/design/core/core.css).
   Modules vendor this verbatim; they never edit it.

   Updated: 2026-07-17 — Typography → Merkevarehåndbok v1.2 /
   Fargesystem v1.1 (TYPOGRAPHY-ONLY; colours, spacing, radius,
   leading UNCHANGED). Three faces, three roles: Familjen Grotesk =
   DISPLAY (titles ≥20px) + uppercase MICRO-LABELS (Familjen 500);
   Inter = the body/UI workhorse (all reading text, nav, buttons,
   badges, pills, sentence-case labels); IBM Plex Mono = DATA ONLY
   (numbers, %, time, IDs — tabular). This REVERSES v1.0's
   mono-for-all-machine-text rule (mono is no longer a label face)
   and retires Space Mono. Colour/geometry values still derive from
   the 2026-07-05 refresh (Navy surfaces, Stein accent never a fill,
   Papir primary buttons) — see Fargesystem v1.1.
   ============================================================ */

/* ============================================================
   FONTS — self-hosted (constitution forbids CDNs / npm build).
   Three faces (Merkevarehåndbok v1.2):
   • Familjen Grotesk — DISPLAY + micro-labels; variable (400–700),
     one file per subset.
   • Inter — the body/UI workhorse; variable (100–900), one file per
     subset. Chosen for small text (large x-height, open apertures).
   • IBM Plex Mono — DATA ONLY (tabular figures); static 400 + 500 +
     600, one file per subset per weight.
   Subset unicode-ranges are lifted verbatim from the Fargesystem
   @font-face blocks (identical across all three faces); the `latin`
   subset already covers Norwegian æ / ø / å (U+0000-00FF). Files live
   at ./assets/fonts/. Space Mono is retired (mono is no longer a
   label face — v1.2 reverses v1.0's mono-for-all-machine-text rule).

   Typography swappability — GUARANTEE #1 (metric-locked fallbacks):
   each face ships a paired `… Fallback` @font-face with
   size-adjust / ascent-override / descent-override / line-gap-override
   so a future primary-face swap is a 2-token edit with no layout drift.
   fontTools is unavailable here, so the override numbers below are
   DOCUMENTED PLACEHOLDERS tuned by eye to Arial / Consolas — they
   MUST be re-measured (capsize / fontTools) against the chosen
   fallback before shipping. See README §Typography.
   ============================================================ */

/* ---- Familjen Grotesk (variable 400–700) ---- */
@font-face {
  font-family: 'Familjen Grotesk'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("assets/fonts/familjen-grotesk-latin.7eb7ec31a3a1.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Familjen Grotesk'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("assets/fonts/familjen-grotesk-latin-ext.f2c82a825501.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Familjen Grotesk'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("assets/fonts/familjen-grotesk-vietnamese.090fd2cbf2ff.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* ---- Inter (variable 100–900) — body / UI workhorse ---- */
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("assets/fonts/inter-latin.260c81a4759b.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("assets/fonts/inter-latin-ext.1ad231aac0a8.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("assets/fonts/inter-vietnamese.9df17551da76.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* ---- IBM Plex Mono (static 400) — DATA ONLY (tabular) ---- */
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-400-latin.79936b18df9f.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-400-latin-ext.8450d5944de2.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-400-vietnamese.4674a6178258.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* ---- IBM Plex Mono (static 500) ---- */
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-500-latin.b1c8a895f5fd.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-500-latin-ext.fa8d8838b94a.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-500-vietnamese.bec18f63784c.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* ---- IBM Plex Mono (static 600) ---- */
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 600; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-600-latin.5a2ce7798732.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 600; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-600-latin-ext.f2fe87d55f72.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 600; font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-600-vietnamese.ebd7c5239559.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* ---- Metric-locked fallbacks (GUARANTEE #1). PLACEHOLDER overrides —
   re-measure (capsize / fontTools) against the real fallback before
   shipping (README). Tuned by eye to Arial (sans) / Consolas (mono). ---- */
@font-face {
  font-family: 'Familjen Grotesk Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('sans-serif');
  size-adjust: 104%;          /* PLACEHOLDER — tune to match FG advance widths */
  ascent-override: 92%;       /* PLACEHOLDER */
  descent-override: 24%;      /* PLACEHOLDER */
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('sans-serif');
  size-adjust: 107%;          /* PLACEHOLDER — Inter runs wider than Arial */
  ascent-override: 90%;       /* PLACEHOLDER */
  descent-override: 22%;      /* PLACEHOLDER */
  line-gap-override: 0%;
}
@font-face {
  font-family: 'IBM Plex Mono Fallback';
  src: local('Consolas'), local('Menlo'), local('monospace');
  size-adjust: 100%;          /* PLACEHOLDER — IBM Plex Mono ≈ Consolas advance */
  ascent-override: 92%;       /* PLACEHOLDER */
  descent-override: 24%;      /* PLACEHOLDER */
  line-gap-override: 0%;
}

:root {
  color-scheme: light dark;

  /* ---- Type — three faces, three roles (Merkevarehåndbok v1.2) ----
     Swap a primary face in ONE place (these three tokens); the metric-
     locked `… Fallback` faces keep layout stable during the swap.
     A raw font-family literal anywhere else is a DRIFT violation.
       --font-display  Familjen Grotesk — titles ≥20px + uppercase micro-labels.
       --font-sans     Inter — the body/UI workhorse: reading text, nav,
                       buttons, badges, pills, sentence-case labels.
       --font-mono     IBM Plex Mono — DATA ONLY (numbers, %, time, IDs).
                       MUST carry tabular figures (monospaced → tabular by
                       construction; data components add font-variant-numeric:
                       tabular-nums explicitly). NEVER on labels/nav/badges. */
  --font-display: 'Familjen Grotesk', 'Familjen Grotesk Fallback', system-ui,
               -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-sans: 'Inter', 'Inter Fallback', system-ui,
               -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-mono: 'IBM Plex Mono', 'IBM Plex Mono Fallback', ui-monospace,
               SFMono-Regular, "Berkeley Mono", Menlo, Monaco, Consolas, "Courier New", monospace;

  /* Root reference is 16px so 1rem = 16px (honest rem math, respects the
     user's browser font-size). --text-console is the dense data-table
     base; ≥16px inputs on mobile avoid iOS auto-zoom (see components.css).
     Density choice: brand reading body = 16px (--text-base); data-dense
     console rows opt into 15px (--text-console). */
  --font-size: 16px;
  --font-weight-normal:   400;
  --font-weight-medium:   500;
  --font-weight-semibold: 600;
  --font-weight-bold:     700;
  --font-weight-black:    800;

  --text-xs:      0.6875rem; /* 11 — FLOOR: micro-labels / data / badges (never below) */
  --text-sm:      0.8125rem; /* 13 — body-UI / data; weight ≥500 at or under this */
  --text-console: 0.9375rem; /* 15 — dense data-table base (density choice) */
  --text-base:    1rem;      /* 16 — body / reading (brand body); form inputs ≥16 on mobile */
  --text-lg:      1.0625rem; /* 17 — h3 (brand display H3) */
  --text-xl:      1.25rem;   /* 20 — section / page titles */
  --text-2xl:     1.75rem;   /* 28 — h2 (brand display H2) */
  --text-3xl:     2rem;      /* 32 — stat headlines */
  --text-5xl:     2.875rem;  /* 46 — h1 (brand display H1) */
  --text-8xl:     5.5rem;    /* 88 — hero numerals, temperature gauge */

  --leading-tight:  1.2;
  --leading-normal: 1.5;

  /* Heading tracking + leading are TOKENS (swappability GUARANTEE #2):
     a font re-tune is a few edits here, not a per-element sweep.
     v1.2 tracking rule: POSITIVE on small/uppercase text, NEGATIVE only on
     the display/H1 role. Leading tokens are UNCHANGED from the prior refresh. */
  --leading-h1: 1.04;  --tracking-h1: -0.02em;   /* Display — the ONLY negative tracking */
  --leading-h2: 1.12;  --tracking-h2:  0;         /* Heading (was -0.010em) */
  --leading-h3: 1.30;  --tracking-h3:  0;         /* Heading (was -0.005em) */
  --leading-h4: 1.40;  --tracking-h4:  0;         /* Heading */

  /* v1.2 small-text role tracking — positive only (kept off the negative side). */
  --tracking-micro: 0.04em;   /* uppercase micro-labels (Familjen 500, ≥11px) */
  --tracking-data:  0.01em;   /* mono tabular data — numbers / time / IDs */

  /* ---- Surface scale — Vetyr Navy (dark, layered by panel value, not shadow) ---- */
  --void:    #0E1B26;     /* Navy 950 — page background */
  --panel:   #14283A;     /* Navy 900 — primary surface (cards, rows) */
  --panel-2: #1E3548;     /* Navy 800 — raised (header, detail) */
  --panel-3: #35536A;     /* Navy 600 — hover state */
  --rule:    #5085AB;     /* Navy 500 — visible borders. WCAG 2.2 SC 1.4.11 (≥3:1 non-text)
                             against EVERY surface it is drawn on: --void 4.39,
                             --panel 3.79, --panel-2 3.19. */
  --rule-2:  #213A4C;     /* subtle dividers — DECORATIVE ONLY; never a control boundary */

  /* ---- Foreground ------------------------------------------------------------
     RATIOS ARE STATED AGAINST THE WORST-CASE SURFACE THE TOKEN MAY SIT ON
     (--panel-2 #1E3548), NOT against --void. A token's stated guarantee must hold
     everywhere it is legal to use it; anchoring the ramp to the darkest surface
     made "4.5:1 AA" true only on the page background and false on the primary
     surface, which is what invited the misuse this ramp was corrected for.
     --------------------------------------------------------------------------- */
  --text:    #F4F2ED;     /* 11.3:1 AAA (void 15.6) */
  --text-2:  #9FB6C4;     /* 6.0:1  AA+ (void 8.3) */
  --dim:     #8FA0AD;     /* 4.7:1  AA — smallest step legal for BODY TEXT anywhere */
  --faint:   #688297;     /* 3.2:1  — LARGE-TEXT / DECORATIVE only; NEVER body text */

  /* ---- Accent — Stein — THEMEABLE ------------------------------
     Stein ("hugd stein") is the interactive accent. HARD RULE:
     Stein is an ACCENT, NEVER a fill — no glow, no neon. Use it as
     TEXT / RULE / OUTLINE (links, focus ring, live-dot, active nav).
     Primary buttons are NOT Stein-filled — they are Papir (below).
     A module may retheme by overriding --accent / --accent-core /
     --accent-soft, staying within brand rules (no neon).
     ------------------------------------------------------------ */
  --accent:      #ABA292;              /* Stein Bright — dark-UI interactive (text/rule/outline) */
  --accent-core: #847A6B;              /* brand mark + thin rule/border accent (darker Stein) */
  --accent-deep: #5F584C;              /* accent TEXT on LIGHT surfaces */
  --accent-soft: rgba(171, 162, 146, 0.12); /* subtle tint / hover */

  /* Legacy alias — older markup used --accent-dim for the darker accent.
     Maps to --accent-core (the darker Stein). */
  --accent-dim: var(--accent-core);

  /* Deprecated legacy aliases — the accent is Stein now, NOT cyan.
     Kept only so pre-refresh markup referencing --cyan* still resolves.
     Do not use in new markup. */
  --cyan:      var(--accent);
  --cyan-dim:  var(--accent-core);
  --cyan-soft: var(--accent-soft);

  /* ---- Brand / light-surface tokens ----
     Papir = warm neutral light surface + the PRIMARY BUTTON fill.
     Navy  = brand ink (dark heading text on light, primary-button fg). */
  --papir:          #F4F2ED;  /* Papir — light surface / primary-button fill */
  --papir-2:        #ECE9E2;  /* Stein-tinted light surface (raised) */
  --papir-border:   #DAD5C9;  /* border on Papir surfaces */
  --papir-border-2: #E4E0D7;  /* subtle divider on Papir surfaces */
  --navy:           #1A2E3E;  /* Brand Ink — primary-button text, headings on light */
  --logo-ink:       #F1EEE6;  /* logo wordmark on dark ONLY — not the Papir token */

  /* ---- Button tokens (approved decision) ----
     Primary  = Papir fill + Navy text (ONE primary action per view).
     Secondary= Stein OUTLINE (border --accent-core, text --accent).
     Ghost    = Stein TEXT only.
     Stein can never FILL, so the primary *surface* is Papir even though
     --primary (the brand interactive colour) is Stein — see aliases. */
  --btn-primary-bg:            var(--papir);
  --btn-primary-fg:            var(--navy);
  --btn-primary-bg-hover:      #FFFFFF;
  --btn-primary-bg-active:     var(--papir-2);
  --btn-secondary-fg:          var(--accent);
  --btn-secondary-border:      var(--accent-core);
  --btn-secondary-bg-hover:    var(--accent-soft);
  --btn-secondary-border-hover:var(--accent);
  --btn-ghost-fg:              var(--accent);
  --btn-ghost-fg-hover:        var(--text);
  --btn-ghost-bg-hover:        var(--panel-2);

  /* ---- Generic UI states (universal feedback, not domain ramps) ----
     Re-tuned to the brand Drift hues (mirror patterns.css OK/Watch/
     Critical/Info). Status colour ALWAYS pairs with a shape + label —
     never colour alone (WCAG; ≈8% of men can't split red/green). */
  --success:      #45C97D;                    /* brand OK   (circle ●) */
  --success-soft: rgba(69, 201, 125, 0.14);
  --warning:      #F0BE45;                    /* brand Watch (triangle ▲) */
  --warning-soft: rgba(240, 190, 69, 0.14);
  --danger:       #F26553;                    /* brand Critical (diamond ◆) — 5.4:1 AA on --void */
  --danger-soft:  rgba(242, 101, 83, 0.14);
  --info:         #66A2DC;                    /* brand Info  (rounded square ■) */
  --info-soft:    rgba(102, 162, 220, 0.14);

  /* ---- Compatibility aliases (shadcn-style names) ---- */
  --background:            var(--void);
  --foreground:            var(--text);
  --card:                  var(--panel);
  --card-foreground:       var(--text);
  --popover:               var(--panel-2);
  --popover-foreground:    var(--text);
  --primary:               var(--accent);   /* brand interactive colour = Stein.
                                                NOTE: the primary BUTTON surface is
                                                Papir (--btn-primary-bg) — Stein
                                                can't fill. Stein stays text/rule/outline. */
  --primary-foreground:    var(--navy);      /* text on the Papir primary button */
  --secondary:             var(--panel-2);
  --secondary-foreground:  var(--text);
  --muted:                 var(--panel-2);
  --muted-foreground:      var(--dim);
  --destructive:           var(--danger);    /* = critical */
  --destructive-foreground:var(--navy);
  --border:                var(--rule);
  --input:                 var(--rule);
  --input-background:      var(--panel-2);
  --ring:                  var(--accent);     /* 2px Stein focus ring, offset 2px */

  /* ---- Geometry: hard edges. Almost no radius. ---- */
  --radius:    2px;
  --radius-sm: 2px;
  --radius-md: 2px;
  --radius-lg: 2px;
  --radius-xl: 2px;

  /* ---- Depth: never shadow on inline surfaces; only overlays ---- */
  --shadow-sm: none;
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 24px 64px rgba(0, 0, 0, 0.6);

  /* ---- Spacing (4px base) ---- */
  --space-0_5: 0.125rem;
  --space-1:   0.25rem;
  --space-1_5: 0.375rem;
  --space-2:   0.5rem;
  --space-2_5: 0.625rem;
  --space-3:   0.75rem;
  --space-4:   1rem;
  --space-6:   1.5rem;
  /* WCAG 2.2 SC 2.5.8 minimum interactive target. Named so a component references
     the FLOOR rather than re-typing a magic number, and so raising it is one edit.

     DECOUPLED FROM THE SPACE SCALE ON PURPOSE (2026-07-26). This was
     `var(--space-6)`, on the reasoning "it is 24px because --space-6 is" — which
     had the causality backwards and wired an accessibility floor to a rhythm
     decision. It is 24px because THE SUCCESS CRITERION SAYS 24, and it must not
     move when the spacing scale is retuned. Two failure modes the alias carried:
     a designer adjusting `--space-6` would silently move a conformance floor, and
     `1.5rem` is 24px only while `--font-size` is 16px — SC 2.5.8 specifies 24 CSS
     PIXELS, so a reduced root font size would drop the floor below the criterion
     while every rule still "referenced the token".

     AND A BARE `24px` WAS ONLY HALF RIGHT (corrected 2026-07-26, on measurement).
     It cannot fall below the criterion, but it also cannot GROW for a reader who
     has enlarged their root font — the accessibility gain a rem length was reaching
     for. Measured in Chromium on a native checkbox at three root sizes:
     `1.5rem` gives 18 / 24 / 30 (NON-CONFORMANT at a 12px root); `24px` gives
     24 / 24 / 24 (conformant, never grows); `max(24px, 1.5rem)` gives 24 / 24 / 30.
     Only the last satisfies both — it CANNOT fall below the SC and still tracks the
     reader upward.

     The `px` term is the CRITERION: SC 2.5.8 specifies 24 CSS pixels, and a CSS px
     does not scale with root font-size. The `rem` term is READER-TRACKING and is
     written as a literal `1.5rem` ON PURPOSE — its meaning is "1.5x the reader's
     root size" (the same visual proportion 24px represents at the 16px default),
     NOT "the 6th spacing step". It must never be re-spelled `var(--space-6)`: that
     would re-couple a conformance floor to a rhythm decision, which is the defect
     this definition already corrected once. */
  --target-min: max(24px, 1.5rem);
  --space-8:   2rem;

  /* ---- Layout measure: the bound on a single-column reading/entry column ----

     THESE ARE TOKENS, NOT "STRUCTURAL CONSTANTS", AND THE DISTINCTION IS THE
     QUESTION EACH ANSWERS. design.md § "Control-room decisions" sanctions a few raw
     lengths in a MODULE DIALECT — "the two fixed rail widths (structural, like a
     breakpoint)". A rail width answers "how wide is THIS ONE layout's fixed rail":
     local to one composition, and correct as a dialect literal. The two values below
     answer "how wide may a single-column form get before it stops reading well" and
     "how close to the screen edge may a panel come" — CROSS-SURFACE guarantees that
     recur on every auth surface, every narrow-viewport panel, and any future
     single-column form. That is a token question, so they are authored here, once.
     Spelling either of them as a dialect literal on the rail-width precedent would be
     classifying by shape (both are lengths in a layout sheet) rather than by question.

     BOTH ARE `rem`, AND THIS IS DELIBERATELY THE OPPOSITE CHOICE FROM `--target-min`
     ABOVE — do not "correct" one into the other by pattern-matching its neighbour.
     `--target-min` carries a `px` term because SC 2.5.8 specifies 24 CSS PIXELS: the
     criterion is stated in px, so the token must not fall below it when the root
     shrinks. Nothing external specifies a measure or a gutter. What they must do is
     TRACK THE READER: a reader who enlarges their root font wants a wider column and
     a wider gutter, in the same proportion, and a px measure would hold the column
     still while the text inside it grew — the exact overflow a measure exists to
     prevent. A bare `rem` is therefore right here and wrong there, for the same
     reason in both directions.

     `--measure-form` is ARGUED, NOT MEASURED, and must stay re-derivable.
     26rem = 416px at the 16px default root; inside the `.card` padding
     (`--space-4` each side) that leaves ~384px of content — a comfortable single
     field, and enough that the longer `nb` strings ("Send innloggingslenke") set on
     one line, which § "Bilingual EN / NO" requires layouts to tolerate. It sits at
     the generous end of the range clean sign-in surfaces use (~340-420px).
     WHAT WOULD CHANGE IT, sharpest first: (1) a surface inside this bound that must
     carry TWO columns or a data table — that is a different pattern and owes its own
     measure, not a widening of this one; (2) a locale materially longer than `nb`
     wrapping a primary button label onto two lines — re-derive from the longest
     rendered control label, not from taste; (3) a root font size far from 16px is
     NOT a trigger, because the rem spelling already handles it.

     `--gutter-viewport` is the FLOOR at the narrow end, and it is a separate token
     rather than `var(--space-4)` for the same reason `--target-min` is not
     `var(--space-6)`: it states a layout GUARANTEE ("a panel is never flush to the
     screen edge") that must be re-tunable without moving the spacing rhythm, and that
     a gate asserts directly. It is a taste value, not a conformance floor — but a
     guarantee wired to a rhythm step moves silently when the rhythm is retuned, which
     is the defect this file has already corrected once. */
  --measure-form:     26rem;    /* max inline size of a single-column form/panel */
  --gutter-viewport:  1.25rem;  /* min inline distance from a panel to the screen edge */

  /* ---- Optical seating: WHERE a short group sits in a full-height field ----
     Added 2026-07-29 (owner ruling: the sign-in surface is OPTICALLY centred, not
     geometrically). It is a token for the same reason the two values above are: the
     discriminating question is what the value ANSWERS. A rail width answers "how wide
     is THIS ONE layout's rail" (dialect literal, correct). This answers "where does a
     short content group sit in a field taller than it" — a cross-surface guarantee that
     already has two consumers (every auth surface; the management area's centred
     empty/short states), so spelling it as a dialect fraction would be classifying by
     shape (both are bare numbers in a layout sheet) rather than by question.

     IT IS A UNITLESS FRACTION, NOT A LENGTH, AND NOT A `--leading-*`. It is the share
     of the FREE block space (viewport block size MINUS the group's rendered block size)
     that sits ABOVE the group; the remaining 1 - x sits below. Do not read it as a
     position in the viewport and do not alias it to a leading step: a leading answers
     "how tall is a line", this answers "where does a block sit", and the two are only
     neighbours in spelling.

     THE VALUE IS DERIVED, NOT PICKED. x = 1/(1+phi) = (3 - sqrt 5)/2 = 0.381966..., the
     golden section of the free space. Two independent supports, and it is the AGREEMENT
     that makes it a derivation rather than a taste:
       (1) the classical construction — the optical centre of a page is its golden
           section, 0.382 of the height from the top;
       (2) the classical BAND — "one eighth to one tenth above the geometric centre",
           i.e. 0.375 to 0.400 of the height. 0.381966 lands inside it.
     Applied to the FREE SPACE rather than to the viewport, it reduces to (1) exactly as
     the group's height approaches zero, and its offset above the geometric midpoint is
     x' = (1/2 - x) = 0.118034 of the free space.

     APPLYING IT TO FREE SPACE IS LOAD-BEARING, NOT A REFINEMENT. Spelled as a fixed
     fraction OF THE VIEWPORT it would seat a group taller than 1 - 2*0.118034 = 76.4%
     of the viewport with its top ABOVE the viewport top: clipped, and unreachable by
     scrolling. Against the free space the offset vanishes as the group grows, so an
     optically seated surface and a non-overflowing one are satisfiable at the same time,
     right up to the branch boundary where the group meets the viewport height and this
     construction becomes the geometric midpoint (its correct limit).

     WHAT WOULD CHANGE IT, sharpest first: (1) a content group whose visual weight is not
     roughly even — a heavy footer/legal block at its foot reads lower than its box, and
     owes its OWN derivation, not a re-tune of this one; (2) a surface the owner rules
     GEOMETRICALLY centred — that surface does not use this token, and this token is NOT
     set to 0.5 to express it: 0.5 is a different property wearing this one's name;
     (3) a change of display face, leading, or locale is NOT a trigger — the construction
     is taken against the group's RENDERED block size, which already carries them. */
  --optical-centre:   0.381966; /* share of free block space ABOVE an optically seated group */
}

/* ============================================================
   Base type cascade
   ============================================================ */
html  { font-size: var(--font-size); background: var(--void); }
body  {
  margin: 0; padding: 0;
  font-family: var(--font-sans);
  font-size:   var(--text-base);
  line-height: var(--leading-normal);
  background:  var(--void);
  color:       var(--text);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings use the DISPLAY face (Familjen Grotesk). v1.2: --font-sans is now
   Inter, so headings set font-family explicitly. H1 = Display (700, negative
   tracking); H2–H4 = Heading (600, tracking 0). Tracking + leading are tokens
   (swappability GUARANTEE #2) so a font re-tune stays here, not per element. */
h1 { font-family: var(--font-display); font-size: var(--text-5xl); font-weight: var(--font-weight-bold); line-height: var(--leading-h1); letter-spacing: var(--tracking-h1); margin: 0; }
h2 { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--font-weight-semibold); line-height: var(--leading-h2); letter-spacing: var(--tracking-h2); margin: 0; }
h3 { font-family: var(--font-display); font-size: var(--text-lg);  font-weight: var(--font-weight-semibold); line-height: var(--leading-h3); letter-spacing: var(--tracking-h3); margin: 0; }
h4 { font-family: var(--font-display); font-size: var(--text-base);font-weight: var(--font-weight-semibold); line-height: var(--leading-h4); letter-spacing: var(--tracking-h4); margin: 0; }

p, label, button, input, select, textarea {
  font-family: var(--font-sans);
  font-size:   var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text);
}
label, button { font-weight: var(--font-weight-semibold); }

/* Mono is DATA ONLY (v1.2). The utility bakes in the data role: tabular
   figures + the +.01em data tracking. IBM Plex Mono is monospaced, so its
   figures are tabular by construction; tnum is declared for intent. */
.mono         { font-family: var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data); }
.tabular-nums { font-variant-numeric: tabular-nums; }

/* Stein is never a solid fill — the selection highlight is a Stein TINT,
   not a Stein surface. */
::selection { background: color-mix(in oklab, var(--accent) 30%, transparent); color: var(--text); }

/* ---- Target size — WCAG 2.2 SC 2.5.8 (constitution § Accessibility, ≥24×24) ----
   A PLATFORM FLOOR, not a per-component courtesy: a control inherits the minimum
   here instead of each component re-deriving it. Use `--target-min` — never a
   re-typed `24px`. Overriding it DOWNWARD is a drift violation.

   SCOPE IS DELIBERATE, and the exclusions are SC 2.5.8's own exceptions, not
   convenience:
   * `a[href]` is NOT blanket-floored — the **inline** exception covers a link in a
     sentence, and a min-height on prose anchors would break text flow. A
     BLOCK-LEVEL or nav anchor is not inline and MUST carry the floor through its
     own component class (`.btn`, nav/menu items, list-row links).
   * A native checkbox/radio renders ~13×13 whatever its label measures, and **a
     large label is not the hit area unless the control is stretched to it** — so
     the box itself is floored, and a label that WRAPS one is floored with it.
   * Where a design visually hides the native control and draws its own affordance,
     the floor belongs on the VISIBLE affordance; the hidden input keeps it
     harmlessly. */
button, [role="button"], summary,
input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
select, textarea {
  min-block-size: var(--target-min);
}
/* The floor is expressed with `min-*` ON PURPOSE, and that is what makes it a
   FLOOR rather than a suggestion. A `min-inline-size` clamps a module's `width`
   at USED-VALUE time — they are different properties, so the clamp applies
   REGARDLESS of selector specificity or source order. Written as `inline-size`
   this rule loses to any equal-specificity module rule that sets `width`, which
   is exactly how a module silently reduced this floor to 16px. Never re-spell
   these as `inline-size` / `block-size` / `width` / `height`. */
input[type="checkbox"], input[type="radio"] {
  min-inline-size: var(--target-min);
  min-block-size:  var(--target-min);
}
label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--target-min);
}

* { scrollbar-width: thin; scrollbar-color: var(--rule) transparent; }
*::-webkit-scrollbar         { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb   { background: var(--rule); border-radius: 0; }
*::-webkit-scrollbar-track   { background: transparent; }

.kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; padding: 1px 5px;
  font-family: var(--font-mono);
  font-size: 11px; font-weight: 600; line-height: 1.4; /* v1.2: 11px floor (was 10px) */
  letter-spacing: var(--tracking-data);
  background: var(--panel-2);
  color: var(--text-2);
  border: 1px solid var(--rule);
  border-radius: 2px;
}
