/* ═══════════════════════════════════════════════════════════════════════════════════════════
   PULSN — TYPOGRAPHIC SCALE  (audit finding D3)

   Belongs with `assets/pulsn-theme.css`, but lives in its OWN file, because the theme file is
   just now getting its colour and token vocabulary collected (D2/D8). Typography is nevertheless an axis the
   theme engine does not have at all: `pulsn-theme.css` contains 0 `font-size` declarations.

   ── THE MEASURED PROBLEM (2026-07-29, not the audit's figure — re-measured) ──────────────────
   Union over personale/index.html, index.html, tilmelding/index.html, kvittering.html,
   fx-header.js and mails/*.html, comments stripped first:

       36 distinct plain px values  +  15 ad hoc clamp() expressions  +  `0`   =  52
       (the audit wrote 50; the difference is the normalisation of `!important` and clamp)

   Below 17px lie EIGHTEEN steps — 8, 8.5, 9, 9.5 … 16.5 — exactly as the audit wrote.
   Nine of them are HALF pixels: 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5,
   206 declarations in all.

   ── WHY THE SCALE IS NOT "7 STEPS" ──────────────────────────────────────────────────────────
   It is tempting to squeeze 36 values down to 7. But two sizes that serve two purposes are
   not debt, and a scale that forces them together is worse than the 36. Measured frequency shows that
   10-16px each carry real weight in a dense admin surface:

       12px ×120   13px ×119   11px ×92   15px ×87   14px ×55   10px ×42   16px ×24

   What is NOT a purpose, on the other hand, is the half pixels. At 1× DPR 12px and 12.5px cannot be
   told apart; they are drift from five years of polishing, not five decisions. The scale here
   therefore collapses the nine half steps and LETS the whole ones stand.

   Hence two ramps on one foundation — and it is not an exception, it is typography:
     · 9-16px: LINEAR ramp (±1px). A geometric ratio is useless down here — 1.2 from 13px
       jumps 13 → 15.6 → 18.7 and so skips 14px, which carries 55 declarations.
     · 18px and up: GEOMETRIC on `--fs-ratio`. Up here it is the reverse: 1px steps are invisible,
       and the ratio is what makes a heading a heading.

   ── TWO-THREE KNOBS, NOT 36 MAGIC NUMBERS ────────────────────────────────────────────────────
   Everything below is derived from `--fs-base`, `--fs-step` and `--fs-ratio`. The owner is reluctant to lock anything
   down, so a surface must be able to turn the base without anyone touching fourteen numbers:

       measured dominant base:  personale 12px  ·  index.html 13px  ·  fx-header 13px

   ── THE TRAP: A DERIVED TOKEN IS **NOT** RE-DERIVED BY ITSELF ──────────────────────────────────
   A custom property gets its var() substitution on the element where it IS DECLARED, and
   is then inherited as a finished value. If you set `--fs-base: 12px` on an element further down,
   `--fs-md` therefore does NOT change — it was calculated on :root. (The same trap as the `--bg`
   alias on nested themes, measured earlier.)
   Therefore the whole ladder stands on `:root, [data-fs-scope]`: a surface that wants another base
   sets `data-fs-scope` AND `--fs-base` on the same element, and then the ladder is calculated afresh.

       <main data-fs-scope style="--fs-base: 12px">   ← the whole portal one step down

   ── ⛔⛔ THIS FILE IS NOT ALONE WITH ITS NAMES — AND IT MUST BE LOADED LAST ────────────────────
   `assets/pulsn-theme.css` declares ELEVEN of the same `--fs-*`. That is an EMERGENCY ROUTE for the five
   surfaces that do not fetch this file (fx-header.js · fx-header1.js · flexybox-header1.html ·
   flexybox-header-test.html · personale/fremmoede-test.html) — not an alternative.

   **THIS FILE WINS, AND ONLY BECAUSE IT IS LOADED AFTER.** Both blocks stand on `:root` with the same
   specificity (0,1,0), so the order in <head> is the whole mechanism. If someone swaps the two
   <link> lines, the house's font sizes change silently — there is no error to see.
   `tests/design_system_contract_test.php` §B measures the order on every surface that fetches both.

   MEASURED 28-08-2026 (`docs/design/kontrol.html`, section ②): after the cleanup the two agree
   on eight of eleven steps. THREE are deliberately held at the emergency route's numbers, because
   surfaces that do not fetch this file write exactly those numbers in as fallback:
       --fs-3xs  emergency 10px / true 9px      --fs-lg  emergency 15px / true 14px
       --fs-4xl  emergency 30px / true 21,6px
   The list stands in the theme file with its closing condition, and §C in the test holds it — **it may only
   get shorter.**
   ⚠ `--fs-5xl` … `--fs-8xl` exist ONLY here. A surface without this file that uses them gets an
   invalid declaration, and the text inherits silently (measured: 13px instead of 31,1px).

   ── COLOUR ────────────────────────────────────────────────────────────────────────────────
   There are no colours in this file, and none are coming. The brand colour has not been chosen yet.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

:root,
[data-fs-scope] {

  /* ─── THE BUTTONS ─────────────────────────────────────────────────────────────────────── */
  /* ⭐ rem, NOT px (27-08-2026). The whole ladder is derived from these two, so they are the only two
     lines that have to change unit — the rest follows by itself. It is the only reason
     the change is feasible at all.
     MEASURED BEFORE: 848 px declarations against 16 in rem. A human who has turned up the font in their
     browser — because their sight is poor, or because the phone is held at arm's length — got NO
     difference on PULSN. That is WCAG 1.4.4.
     ⚠ 0.8125rem IS 13px at the default setting. Nobody sees a difference today; the difference
     arises only for whoever has asked for it themselves. */
  --fs-base:  0.8125rem;   /* 13px — body text; measured as the most frequent across the surfaces */
  --fs-step:  0.0625rem;   /*  1px — the UI ramp's increment, linear; see the note above           */
  --fs-ratio: 1.2;    /* the display ramp's ratio (major second); 1.25 makes the jumps coarser */

  /* ─── UI RAMP: linear, 9-16px at the default base ─────────────────────────────────────── */
  --fs-3xs: calc(var(--fs-base) - 2 * var(--fs-step));   /* 11px — FLOOR (was 9px): micro labels, table notes — readable on a screen at reception (UI review 07-09-2026) */
  --fs-2xs: calc(var(--fs-base) - 2 * var(--fs-step));   /* 11px — FLOOR (was 10px): badges, chips — nothing that has to be read stands under 11px */
  --fs-xs:  calc(var(--fs-base) - 2 * var(--fs-step));   /* 11px — secondary meta text         */
  --fs-sm:  calc(var(--fs-base) - 1 * var(--fs-step));   /* 12px — dense table text             */
  --fs-md:  var(--fs-base);                              /* 13px — BODY TEXT                    */
  --fs-lg:  calc(var(--fs-base) + 1 * var(--fs-step));   /* 14px — emphasised body text         */
  --fs-xl:  calc(var(--fs-base) + 2 * var(--fs-step));   /* 15px — lead-in, large buttons  */

  /* ⛔ THE FIELD'S OWN STEP — and the whole point is that THE FLOOR LIVES IN THE VALUE, not in a rule.
     The first attempt (27-08) was a competing rule:
         @media (pointer: coarse) { input, select, textarea { font-size: max(var(--fs-xl),16px) } }
     It is (0,0,1) in specificity. The eight rules it was written against are `.field input`
     (0,1,1), `.bf select` (0,1,1) and `.ov-w-tinput` (0,1,0). **The more specific wins,
     regardless of order and regardless of `@media`.** The value was right; the rule never got through.
     I reported it as solved. It was inert. A fix that does not fix is worse than none,
     because it gets ticked off.

     ⭐ THE ANSWER IS NOT `!important` — that would start a specificity war in a file where there
     are already 32 of them. The answer is to move the floor INTO THE TOKEN: a rule that writes
     `font-size: var(--fs-field)` brings the floor with it, however specific it is.
     **Every `input`, `select` and `textarea` must use `--fs-field`.** */
  --fs-field: var(--fs-xl);
  --fs-2xl: calc(var(--fs-base) + 3 * var(--fs-step));   /* 16px — smallest heading         */

  /* ─── DISPLAY RAMP: geometric. The chain is deliberate — CSS' calc() has no pow(). ───────── */
  --fs-display-base: calc(var(--fs-base) + 5 * var(--fs-step));  /* 18px — where the ramp changes */
  --fs-3xl: var(--fs-display-base);                        /* 18.0px */
  --fs-4xl: calc(var(--fs-3xl) * var(--fs-ratio));         /* 21.6px */
  --fs-5xl: calc(var(--fs-4xl) * var(--fs-ratio));         /* 25.9px */
  --fs-6xl: calc(var(--fs-5xl) * var(--fs-ratio));         /* 31.1px */
  --fs-7xl: calc(var(--fs-6xl) * var(--fs-ratio));         /* 37.3px */
  --fs-8xl: calc(var(--fs-7xl) * var(--fs-ratio));         /* 44.8px */

  /* ─── FLUID DISPLAY: replaces the 15 handwritten clamp() expressions ─────────────────────
     Each clamp fetches its floor and ceiling from the ladder above, so a changed base or ratio
     moves them along. Only the vw figure is its own — it IS the design decision: how fast
     the font grows with the screen. */
  --fs-fluid-1:    clamp(var(--fs-4xl),  5vw, var(--fs-6xl));                        /* 21.6 →  31.1 */
  --fs-fluid-2:    clamp(var(--fs-5xl),  7vw, var(--fs-7xl));                        /* 25.9 →  37.3 */
  --fs-fluid-3:    clamp(var(--fs-6xl),  9vw, var(--fs-8xl));                        /* 31.1 →  44.8 */
  --fs-fluid-hero: clamp(var(--fs-7xl), 14vw, calc(var(--fs-8xl) * var(--fs-ratio)
                                                                * var(--fs-ratio)
                                                                * var(--fs-ratio)));  /* 37.3 →  77.4 */

  /* ─── LINE HEIGHT ──────────────────────────────────────────────────────────────────────
     Measured: 23 distinct values over 230 declarations. 1.5/1.55/1.6/1.65/1.7 are 162 of them —
     five numbers for one purpose. Here are the five steps that actually differ. Unitless, so they
     scale with the font size; a line height in px breaks as soon as the base changes. */
  --lh-flat:    1;      /* icons, number badges — text that must NOT wrap */
  --lh-tight:   1.15;   /* large headings */
  --lh-snug:    1.3;    /* small headings, buttons */
  --lh-normal:  1.5;    /* UI text, tables */
  --lh-relaxed: 1.65;   /* running body text */

  /* ─── TRACKING ────────────────────────────────────────────────────────────────────────
     Measured: 37 distinct values — and a good part of them are the same number spelled twice
     (`.1em` ×15 and `0.1em` ×7 are the same tracking). One unit × an integer is enough. */
  --ls-unit:  .04em;
  --ls-tight: calc(var(--ls-unit) * -0.5);   /* -.02em — large display lines are tightened    */
  --ls-none:  0;
  --ls-1:     var(--ls-unit);                /*  .04em                                    */
  --ls-2:     calc(var(--ls-unit) * 2);      /*  .08em                                    */
  --ls-3:     calc(var(--ls-unit) * 3);      /*  .12em — CAPITALS, the most frequent           */
  --ls-5:     calc(var(--ls-unit) * 5);      /*  .20em — letter-spaced label                 */
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   FIELDS DO NOT ZOOM ON A PHONE
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⛔ iOS Safari ZOOMS the whole page in when you touch a field with text under 16px. Not as
   an error — as a helpfulness. The page jumps, and you have to zoom out yourself. In the middle of
   a sign-up.

   MEASURED 27-08-2026: eight such rules in the house — the portal's `.field input` (15px), `.bf select`
   (14px), `.ov-w-tinput` (13px), the member surface's `.po-field input` (15px), `select, textarea`
   (14px) and the department pickers (13px). That is both in the portal and in the middle of the sign-up.

   ⚠ ONLY ON TOUCH SCREENS. `pointer: coarse` is a finger, `fine` is a mouse. A PC has no
   zoom behaviour to avoid, so there 16px would just make the forms looser without solving anything.
   That is what the question «mobile, tablet OR PC» is about: the same rule need not
   apply to all three.

   ⚠ `max()` and not `16px`: the floor may lift, but never lower. If the user has turned up, so
   `--fs-xl` is already 19px, the field must STAY 19px. A fixed number would take the setting
   from exactly the person who asked for it.

   ⛔⛔ **THE FLOOR DOES NOT COVER THE EIGHT RULES THAT CALLED IT FORTH** (written down 28-08-2026).
   The selector `input, select, textarea` weighs (0,0,1). The eight rules the note above counts are
   class-scoped — `.field input` weighs (0,1,1) and WINS. The rule here is thus a floor for the
   fields NOBODY has set a size on; it does not override those that ARE set.
   ⚠ `!important` would close the hole and open a worse one: it would also pull a deliberate 18px field
   DOWN to 16px, because `max()` can only lift relative to `--fs-xl` — not relative to what the
   author wrote. Therefore it does not stand here.
   ⇒ The fix belongs with the eight rules (use `--fs-2xl` or larger there), not in this block.
   The guidance in `pulsn-theme.css`, point ⑧, says the same to whoever writes a new one.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* On a touch screen the TOKEN itself is lifted — so every single field rule follows along,
   without anyone having to win a cascade. `max()` and not `16px`: the floor may lift, never
   lower. If the user has turned up, so `--fs-xl` is already 19px, the field must STAY 19px. */
@media (pointer: coarse) {
  :root, [data-fs-scope] { --fs-field: max(var(--fs-xl), 16px); }
}

/* Emergency route for fields nobody has touched yet: the rule here still loses to a class selector,
   but it costs nothing and helps the fields that do NOT have a rule of their own. It is not
   the fix — the fix is `--fs-field` on every field rule. */
@media (pointer: coarse) {
  input, select, textarea { font-size: var(--fs-field); }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   UTILITY CLASSES — so a surface can move one element at a time instead of in one big jolt.
   The ratchet (`tests/design_literal_ratchet_test.php`) measures exactly that movement.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

.t-3xs { font-size: var(--fs-3xs); }
.t-2xs { font-size: var(--fs-2xs); }
.t-xs  { font-size: var(--fs-xs);  }
.t-sm  { font-size: var(--fs-sm);  }
.t-md  { font-size: var(--fs-md);  }
.t-lg  { font-size: var(--fs-lg);  }
.t-xl  { font-size: var(--fs-xl);  }
.t-2xl { font-size: var(--fs-2xl); }
.t-3xl { font-size: var(--fs-3xl); }
.t-4xl { font-size: var(--fs-4xl); }
.t-5xl { font-size: var(--fs-5xl); }
.t-6xl { font-size: var(--fs-6xl); }
.t-7xl { font-size: var(--fs-7xl); }
.t-8xl { font-size: var(--fs-8xl); }

/* Title steps. D11 found 149 <h3> and zero <h2>, because the heading level was chosen by
   SIZE. With these you can choose the level by structure and the expression afterwards. */
.title-1 { font-size: var(--fs-5xl); line-height: var(--lh-tight);  letter-spacing: var(--ls-tight); }
.title-2 { font-size: var(--fs-4xl); line-height: var(--lh-tight);  letter-spacing: var(--ls-tight); }
.title-3 { font-size: var(--fs-3xl); line-height: var(--lh-snug);   }
.title-4 { font-size: var(--fs-2xl); line-height: var(--lh-snug);   }

/* The label above a field / a column: capitals, letter-spaced, small. 32 different chip, badge
   and tab classes (D5) each wrote their own variant of exactly this combination. */
.t-label { font-size: var(--fs-3xs); line-height: var(--lh-snug); letter-spacing: var(--ls-3); text-transform: uppercase; }
