/* =============================================================================
 * Frihat Legal Platform — Design System · 01 · BASE
 * =============================================================================
 *
 * PURPOSE
 *   The reset and element layer. Its job is that ANY new page inherits the
 *   design system with zero opt in: no class, no wrapper, no import. A Razor
 *   page that emits nothing but bare h1, p, table and button markup must
 *   already look like Frihat, in Arabic, in RTL, in light and in dark.
 *
 *   Everything here styles BARE ELEMENTS, with four documented class exceptions:
 *   fx-skip-link and fx-sr-only (accessibility primitives the layout needs
 *   before any component exists), fx-scroll-x (the structural escape hatch for
 *   the no horizontal page scroll guarantee), and the section 14 motion
 *   foundation — fx-stagger and fx-page-enter — which every component layer
 *   consumes and which therefore has exactly one author, this file.
 *
 * SPECIFICITY BUDGET, EXCEPTIONS
 *   Three sets of rules exceed the 0,1,0 budget, all deliberately:
 *     · section 8.1 and 11.1 reach 0,2,1 — an input has to be selected by what
 *       it is NOT (checkbox, radio, range, colour, file, and the input-as-button
 *       types), and a :not() taking a selector list counts only once.
 *     · section 14's stagger index rules are 0,2,0 — an index cannot be carried
 *       without nth-child.
 *     · section 10's body row hover is 0,1,2 — with the row rule removed, hover
 *       is what finds a row, and it must not also light the header row.
 *   In every case the escape hatch for a component is to override the PROPERTY
 *   (background-color, box-shadow, animation-name), never to out-specify the
 *   selector.
 *
 * LAYER POSITION
 *   Second file in the bundle, immediately after 00-tokens.css. Every component
 *   layer (02 and up) assumes this layer has already run.
 *
 * DEPENDENCIES
 *   00-tokens.css only. Every value in this file comes from a token; the sole
 *   exceptions are the print block and the forced colours block, which are
 *   documented in place.
 *
 * SPECIFICITY BUDGET
 *   Element selectors and single classes only. Nothing in this file may be
 *   harder to override than 0,1,0, because component layers must be able to
 *   restyle any element without escalating.
 *
 * COMMENT CONVENTION
 *   Token names appear without their leading double hyphen. Em dashes for
 *   punctuation.
 *
 * THE 2026-08-02 LINEAR RETUNE — WHAT MOVED IN THIS FILE
 *   · body is now the SECONDARY text tier, not the primary one. Read the note
 *     over section 2; it is the single most consequential line in the file.
 *   · headings dropped from 700 to 600 and their tracking follows the SIZE.
 *   · focus is a 2px outline at 2px offset, everywhere, with no exceptions.
 *     The bloom, the lift and the 6px spread ring are gone.
 *   · every bare-element hover is behind (any-hover: hover) so a touch device
 *     never gets a stuck hover state.
 *   · the bare table lost its header fill and gained a 32px header row at the
 *     tertiary tier; rows separate by HEIGHT and hover fill, nothing else.
 *   · scrollbars are fully tokenised — 6px at rest, 10px engaged.
 *
 * THE 2026-08-02 SECOND PASS — WHAT MOVED IN THIS FILE
 *   · the fx-focus-bloom keyframe is DELETED. It had no consumer left; the
 *     comments in 03 to 06 that record its retirement are the design record and
 *     stay. Section 14.1 carries the note explaining the deletion.
 *   · the brand caret broadened from input[type="search"] to the SEARCHBOX and
 *     COMBOBOX roles, so the Cases and Courts dialogs stop needing 05-forms to
 *     restate it. Section 11.
 *   · ::selection is declared HERE and should be declared nowhere else;
 *     03-leptonx section 2 holds a duplicate that wants removing. Section 11.
 *   · the reduced motion damper gained fx-dur-quick and — closing a real hole —
 *     fx-dur-enter and fx-dur-exit, which were never damped. Section 15.
 *   · the stagger child now resolves to 180ms rather than 250ms, because
 *     fx-dur-slow left the banned motion band. Nothing here changed to make
 *     that happen; the ruling is in 00-tokens section 7. Section 14.3.
 *
 * GEOMETRY — SHARP CORNERS, NO STROKES
 *   Crisp, not soft, and not outlined. Corners come from the radius scale in
 *   00-tokens (4/6/8/10/12/14, floor 4px). Separation comes from FILL and
 *   ELEVATION: a control is findable because it sits in fx-color-field-bg with
 *   fx-shadow-well under it, a floating surface is findable because
 *   fx-shadow-float-* holds it above the page, and focus is an OUTLINE.
 *   This layer draws NO stroke anywhere. Every element that used to carry
 *   one now separates by LEVEL (blockquote), by a hairline from the opaque
 *   line family (the hr) or by SPACING alone (table rows).
 *
 *   The only border declarations left in this file are of three kinds, and each
 *   one is annotated where it appears:
 *     · border: 0 — a suppression, not a stroke, and only on the elements that
 *       genuinely arrive from the user agent WITH a border: hr, fieldset, the
 *       text controls in 8.1, and the fx-sr-only recipe.
 *     · the transparent scrollbar-thumb inset (section 11) — the only way to
 *       inset a WebKit thumb; it paints nothing.
 *     · the forced-colours block (section 16), where the user has asked the
 *       operating system for strokes and the borderless direction is
 *       deliberately suspended.
 *       CORRECTED 2026-08-03: this used to read "the forced-colours and
 *       prefers-contrast blocks". THERE IS NO prefers-contrast BLOCK IN THIS
 *       FILE, and there should not be one — the omission is the design, not a
 *       gap. Every value this layer paints reads a token, and 00-tokens promotes
 *       all of them inside that query: the four text tiers collapse onto full
 *       strength body ink, every divider and border grade collapses onto the
 *       strong line, fx-border-width goes to 2px and fx-focus-width to 3px, and
 *       the brand marks step to green-900 or mint-300. A rule here would only be
 *       able to restate that, and would risk holding a value DOWN against the
 *       promotion. The one thing the token layer does NOT promote is
 *       fx-shadow-well, and it does not need to: the well already measures
 *       4.13:1 light and 4.34:1 dark against the field fill, comfortably past
 *       the 3:1 that SC 1.4.11 asks of the borderless control in section 8.1.
 *   Anything else that draws a visible line is a defect, not a decision.
 *
 *   THE ACCESSIBILITY CATCH, because it will bite anyone who "cleans this up":
 *   a borderless control still owes SC 1.4.11 a 3:1 identification cue. It is
 *   carried by the strongest band of fx-shadow-well, measured in 00-tokens at
 *   4.13:1 light and 4.34:1 dark. Delete the well and the platform fails.
 *   Note the SPLIT that makes this coherent: structural separation (a card
 *   edge, the hr, a section rule) is allowed to sit far below 3:1, because it
 *   identifies nothing. Only a control boundary owes the 3:1.
 *
 * RTL
 *   Logical properties throughout. A physical property appears only where the
 *   thing being described is genuinely physical: a box-shadow offset, a
 *   transform, a scrollbar gutter.
 * ========================================================================== */


/* ── 1) Box model and colour scheme ────────────────────────────────────────
 * color-scheme drives the native widgets the platform cannot style: form
 * controls, the scrollbar chrome, and the built in date and colour pickers.
 * It is bound to the theme attribute, not to the OS preference, because
 * frihat-theme.js is the authority on which theme is active.
 * ────────────────────────────────────────────────────────────────────────── */
*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    box-sizing: border-box;
    color-scheme: light;
}

html[data-bs-theme="dark"] {
    color-scheme: dark;
}


/* ── 2) Document root and body ─────────────────────────────────────────────
 * The horizontal overflow guarantee lives here. A Frihat page never produces a
 * horizontal scrollbar on the document: wide content belongs to a scroll
 * container (see fx-scroll-x in section 12), never to the page. overflow-x is
 * clipped rather than hidden because clip does not create a scroll container
 * and therefore does not break position: sticky inside the layout.
 *
 * Viewport height uses dvh, with svh declared first as the fallback for engines
 * that ship svh but not dvh. Bare vh is never used: on mobile it measures the
 * largest viewport and pushes content under the browser chrome.
 * ────────────────────────────────────────────────────────────────────────── */
html {
    max-inline-size: 100%;
    overflow-x: clip;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    tab-size: 4;

    /* THE SHELL AGAIN, AND IT IS NOT DUPLICATION OF THE body RULE BELOW.
     * LeptonXLite paints `html { background-color: #161616 }` in BOTH modes.
     * While body was pale that never showed, because body's own fill covered
     * the viewport. It stops being invisible the moment body carries a colour
     * that does NOT reach the bottom of a short page — overscroll, a page whose
     * content is shorter than the viewport, and rubber-band scrolling on macOS
     * and iOS all expose the root's fill, and a BLACK strip under a green shell
     * is the result. Painting the root with the same knob makes the two agree by
     * construction rather than by body always happening to be tall enough. */
    background-color: var(--fx-color-shell);
}

/* ══ THE DOCUMENT DEFAULT IS THE SECONDARY TEXT TIER ═══════════════════════
 *
 * body reads fx-color-text-MUTED, not fx-color-text. This is deliberate and it
 * is the change every downstream layer has to understand.
 *
 * In the four tier model (00-tokens 2.2) the secondary tier IS the resting
 * colour of content: body copy, table cell values, default menu labels. The
 * primary tier is a PROMOTION — it is what a heading gets, what a selected row
 * gets, and what a hovered item's label gets. If body sat at the primary tier
 * there would be nothing left to promote TO, and hierarchy would have to be
 * expressed by bolding instead, which is exactly the habit the retune removes.
 *
 * Despite the token name, muted is not a dimmed grade: it measures 10.42:1 on
 * the page. Anything genuinely secondary in IMPORTANCE — metadata, icons,
 * placeholders, timestamps — belongs to fx-color-text-subtle, one tier further
 * down and the far bigger jump.
 *
 * LETTER SPACING. body carries fx-tracking-ui, which is -0.011em in Latin and
 * exactly 0 in Arabic via the token layer guard. Never write a negative
 * letter-spacing at a call site; route it through a fx-tracking-* token so the
 * guard keeps protecting the cursive script automatically.
 * ═══════════════════════════════════════════════════════════════════════════ */
body {
    margin: 0;
    max-inline-size: 100%;
    min-block-size: 100svh;
    min-block-size: 100dvh;
    overflow-x: clip;

    font-family: var(--fx-font-body);
    font-size: var(--fx-text-base);
    font-weight: var(--fx-weight-regular);
    line-height: var(--fx-leading-normal);
    letter-spacing: var(--fx-tracking-ui);

    color: var(--fx-color-text-muted);
    /* ══ THE DOCUMENT CANVAS IS RECESSED — 2026-08-03 ══════════════════════
     * Was fx-color-bg, which is level 0. It moved with 03-leptonx section 3.0
     * and the two are ONE decision, not two.
     *
     * WHY IT COULD NOT BE DONE ALONE. Inside the app shell this declaration is
     * invisible: .lpx-content-container covers the document completely and
     * paints its own canvas over it. Changing body by itself would therefore
     * have fixed nothing and would have looked like a no-op change.
     *
     * WHY IT HAD TO BE DONE AT ALL. Once the shell's canvas is recessed, the
     * screens the shell does NOT reach — the login page (Layout is null) and the
     * error pages — would have been the only surfaces in the platform still
     * sitting on level 0. Two canvases, one product. The token layer's own
     * definition settles which one is right: 00-tokens 2.1 calls
     * surface-sunken the plane BELOW the ladder, and a page canvas with a card
     * floating on it is exactly that.
     *
     * WHAT IT DOES TO THE SHELL-LESS PAGES, checked before committing to it
     * rather than after: .frihat-login-card is a level 3 surface with
     * fx-shadow-float-lg on this canvas, so the step it was already making
     * gets LARGER, not smaller. The login page was in fact the platform's one
     * correct example of a raised card on a recessed canvas — this change makes
     * the shell agree with it, which is the direction the evidence pointed. The
     * login brand band paints its own fill and is unaffected. */
    /* THE SHELL, since 2026-08-04. This was fx-color-surface-sunken; see
     * 00-tokens 2.4.1 for the client instruction and for why the canvas token
     * could not simply be repointed — four of its twelve consumers are
     * form-control fills. In dark the shell token resolves to that same canvas,
     * so this line is a no-op there and only light moves. */
    background-color: var(--fx-color-shell);

    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;

    /* A long unbroken token — a case reference, a URL, an email — must wrap
     * rather than widen the page. This is half of the overflow guarantee. */
    overflow-wrap: break-word;
}

/* Smooth in-page navigation, but only for users who did not ask for less
 * motion. The reduce branch in section 13 turns this back off. */
@media (prefers-reduced-motion: no-preference) {
    html {
        scroll-behavior: smooth;
    }
}


/* ── 3) Automatic font selection by language ───────────────────────────────
 * ABP request localization sets the lang attribute on html. Reassigning
 * fx-font-body and fx-font-heading here means every downstream file, component
 * and page inherits the right face without ever testing the language itself.
 *
 * Since 2026-08-02 both stacks resolve into the SAME superfamily — IBM Plex
 * Sans Arabic and IBM Plex Sans — and each lists the other as its first
 * fallback, so a mixed Arabic and Latin string never falls through to a system
 * default mid sentence and never changes design mid sentence either.
 *
 * The optical size multiplier (fx-font-scale) lives in 00-tokens.css because
 * it is a property of the scale rather than the reset. It is now 1 in both
 * languages: Plex Arabic and Plex Sans are metrically harmonised, so there is
 * nothing left to compensate for.
 * ────────────────────────────────────────────────────────────────────────── */
html[lang^="ar"]:root {
    --fx-font-body: var(--fx-font-ar);
    --fx-font-heading: var(--fx-font-ar);
}

html[lang^="en"]:root {
    --fx-font-body: var(--fx-font-lat);
    --fx-font-heading: var(--fx-font-lat);
}


/* ── 4) Typographic rhythm ─────────────────────────────────────────────────
 * Headings carry no block-start margin — the preceding element owns the gap.
 * This is the single most reliable way to keep vertical rhythm predictable
 * when Razor partials are composed in an order nobody planned for.
 * text-wrap: balance evens out heading line lengths; pretty prevents orphans
 * in body copy. Both degrade silently where unsupported.
 *
 * WEIGHT. Headings are 600 — SEMIBOLD, NOT BOLD. h1 and h2 used to be 700 and
 * are not any more. In the retuned system hierarchy is carried by SIZE and by
 * COLOUR TIER (a heading is the primary tier, body is the secondary one), and
 * a third simultaneous signal makes a page shout. 700 survives in the token
 * ladder for print and for genuinely editorial pages, but it does not appear
 * in application chrome.
 *
 * LEADING COLLAPSES AS SIZE GROWS. The base rule sets snug (1.35) for the
 * title tier; h1 and h2 override to tight (1.15) because at display size the
 * lines must knit into a block rather than read as a stack of sentences.
 * ────────────────────────────────────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
    margin-block: 0 var(--fx-space-3);
    font-family: var(--fx-font-heading);
    font-weight: var(--fx-weight-semibold);
    line-height: var(--fx-leading-snug);
    color: var(--fx-color-text);
    text-wrap: balance;
}

/* Tracking follows the SIZE, not the tag: the ramp is steepest at display size
 * and tapers to the body value by h5. Every value resolves to 0 in Arabic via
 * the token layer guard, so nothing here has to know about the script. */
h1 { font-size: var(--fx-text-4xl); line-height: var(--fx-leading-tight); letter-spacing: var(--fx-tracking-display); }
h2 { font-size: var(--fx-text-3xl); line-height: var(--fx-leading-tight); letter-spacing: var(--fx-tracking-heading); }
h3 { font-size: var(--fx-text-2xl); letter-spacing: var(--fx-tracking-heading); }
h4 { font-size: var(--fx-text-xl); letter-spacing: var(--fx-tracking-title); }
h5 { font-size: var(--fx-text-lg); letter-spacing: var(--fx-tracking-title); }
h6 { font-size: var(--fx-text-md); font-weight: var(--fx-weight-medium); letter-spacing: var(--fx-tracking-ui); color: var(--fx-color-text-subtle); }

/* Arabic headings need more leading than Latin at the same size — IBM Plex
 * Sans Arabic sets its diacritics well above the baseline and its descenders
 * well below it, so the tight display leading collides where Latin does not.
 * The tracking reset here is belt and braces: the token layer guard has
 * already zeroed the ramp for html[lang^="ar"], and this restates it at the
 * element so a component that hard codes a heading size cannot reintroduce it. */
html[lang^="ar"] h1,
html[lang^="ar"] h2,
html[lang^="ar"] h3 {
    line-height: var(--fx-leading-snug);
    letter-spacing: var(--fx-tracking-normal);
}

p {
    margin-block: 0 var(--fx-space-4);
    text-wrap: pretty;
}

p:last-child {
    margin-block-end: 0;
}

/* small is metadata, and metadata is the TERTIARY tier. */
small {
    font-size: var(--fx-text-sm);
    color: var(--fx-color-text-subtle);
}

/* Emphasis is a PROMOTION, not a weight change alone: strong steps up to the
 * primary text tier and takes 600, which is the last place in the element layer
 * where weight and colour move together. In running prose that is correct —
 * strong is semantic emphasis, not chrome. */
strong, b {
    font-weight: var(--fx-weight-semibold);
    color: var(--fx-color-text);
}

em, i {
    font-style: italic;
}

mark {
    padding-inline: var(--fx-space-1);
    border-radius: var(--fx-radius-xs);
    color: var(--fx-color-text);
    background-color: var(--fx-color-accent-subtle);
}

abbr[title] {
    text-decoration: underline dotted;
    text-underline-offset: 0.2em;
    cursor: help;
}


/* ── 5) Lists, quotes, rules, definitions ──────────────────────────────────
 * Logical padding so the marker sits on the correct side in RTL without any
 * direction specific rule.
 * ────────────────────────────────────────────────────────────────────────── */
ul, ol {
    margin-block: 0 var(--fx-space-4);
    padding-inline-start: var(--fx-space-5);
}

ul ul, ul ol, ol ul, ol ol {
    margin-block-end: 0;
}

li {
    margin-block-end: var(--fx-space-1);
}

/* A LIST MARKER IS A TEXT TIER, NOT A BRAND SITE. THIS WAS A RULING, NOT A
 * PREFERENCE, AND THAT DISTINCTION IS WHY THE NOTE IS THIS LONG.
 *
 * This painted fx-color-accent until 2026-08-02 (fifth pass), on the authority of
 * 00-tokens section 2.5, which listed "a list marker" among the decorative accent
 * sites. Section 3.9 — the accent contract, and the section that actually governs
 * WHETHER a thing may be brand at all — never listed one and forbade everything it
 * did not list. The two sections contradicted each other outright, and 3.9 was
 * rewritten that day to resolve it AGAINST 2.5: the marker entry is now in 3.9's
 * FORBIDDEN list and the clause in 2.5 is recorded there as withdrawn.
 *
 * The reasoning on record, so nobody restores this from the superseded 2.5:
 *   · A bullet answers NEITHER question in 3.9. It does not say who this product
 *     is (Q1) and it does not respond to a pointer or a key (Q2). Q3 sends it to
 *     weight, size, space and a text tier.
 *   · It is the most GENERIC GLYPH in the system, and 3.9 already rules that a
 *     generic icon takes a text tier one below its label. A marker is the purest
 *     case of that rule, not an exception to it.
 *   · FREQUENCY. Every sanctioned site in 3.9 is BOUNDED — once per page, one per
 *     region, one per sorted column, one per strip. A marker recurs once per list
 *     item without any bound at all, so a single long list can put more coloured
 *     glyphs on screen than the entire rest of the accent budget.
 *
 * subtle, not muted, and not faint. It is ONE TIER BELOW the body text the bullet
 * introduces, which is exactly the relationship the generic-icon rule describes —
 * the marker is furniture belonging to the line, so it must not read at the same
 * strength as the line's words. muted IS the body tier under the four tier model
 * (00-tokens 2.2) and would leave the marker indistinguishable from its content;
 * faint is quaternary and deliberately sub-AA, which is wrong for a mark that
 * carries the list's structure. */
li::marker {
    color: var(--fx-color-text-subtle);
}

dl {
    margin-block: 0 var(--fx-space-4);
}

/* A definition term is a LABEL, and a label is the workhorse 500 weight at the
 * primary tier — not 600. Its definition is body, so it inherits and needs no
 * colour of its own; the rule is kept only to zero the user agent margin. */
dt {
    font-weight: var(--fx-weight-medium);
    color: var(--fx-color-text);
}

dd {
    margin-block-end: var(--fx-space-2);
    margin-inline-start: 0;
}

/* A quote is a PLANE, not a bar. The 2px olive stroke that used to sit at the
 * inline start is gone: the block now steps to level 2 — the engaged plane — and
 * pays for the loss of the stroke with real interior padding and the control
 * radius. Fill plus air reads as a pulled-out passage; a coloured rule reads as
 * a border, which is the one thing the system does not draw. */
blockquote {
    margin-block: 0 var(--fx-space-4);
    margin-inline: 0;
    padding-block: var(--fx-space-3);
    padding-inline: var(--fx-space-4);
    border-radius: var(--fx-radius-md);
    background-color: var(--fx-color-level-2);
}

blockquote > :last-child {
    margin-block-end: 0;
}

/* A HAIRLINE FROM THE OPAQUE LINE FAMILY, not a band. This reverses the
 * 2026-08-01 decision to paint separations as a 3px block of very low contrast
 * fill. That decision was a reaction to a hairline that was too STRONG, and the
 * retune fixes the strength instead: fx-color-divider measures 1.13:1 against
 * a level 1 surface, which is Linear's own near-invisible tier and reads as a
 * change of plane rather than as a drawn border. A 3px band, by contrast, always
 * reads as a thin empty strip of something else.
 *
 * The block-size rather than a border keeps the line at exactly one token width
 * — including the 0.5px it becomes at 2dppx — and the border: 0 is load bearing
 * and stays: the user agent ships hr as an inset stroke, so this is the
 * SUPPRESSION of a line, not the drawing of one. Deleting it brings the user
 * agent's own hairline back on top of ours. */
hr {
    margin-block: var(--fx-space-5);
    border: 0;
    block-size: var(--fx-border-width);
    background-color: var(--fx-color-divider);
    opacity: 1;
}


/* ── 6) Code ───────────────────────────────────────────────────────────────
 * Code is always LTR even inside an Arabic RTL page, otherwise identifiers and
 * punctuation reorder and become unreadable. direction and unicode-bidi are
 * the correct tools here, not a physical text-align.
 * ────────────────────────────────────────────────────────────────────────── */
/* IBM Plex Mono is vendored at 400 and 500 only, so every code element sits at
 * 400 and none of them may be bolded — a 600 here would be a synthesised smear.
 * See the weight note in 00-tokens section 5. */
code, kbd, samp, pre {
    font-family: var(--fx-font-mono);
    font-size: var(--fx-text-sm);
    font-weight: var(--fx-weight-regular);
    direction: ltr;
    unicode-bidi: isolate;
}

/* Fill, not outline — the sunken surface is what marks a code run. No border: 0
 * reset is needed on any of the four code elements, because none of them arrives
 * from the user agent with a border in the first place; a suppression that
 * suppresses nothing is noise in a file whose whole point is that no line is
 * drawn here. */
code {
    padding-block: 0.1em;
    padding-inline: var(--fx-space-1);
    border-radius: var(--fx-radius-xs);
    color: var(--fx-color-text);
    background-color: var(--fx-color-surface-sunken);
    box-shadow: var(--fx-shadow-inset);
}

pre {
    margin-block: 0 var(--fx-space-4);
    padding: var(--fx-space-4);
    overflow-x: auto;
    max-inline-size: 100%;
    line-height: var(--fx-leading-snug);
    color: var(--fx-color-text);
    background-color: var(--fx-color-surface-sunken);
    border-radius: var(--fx-radius-md);
    box-shadow: var(--fx-shadow-inset);
}

pre code {
    padding: 0;
    background: none;
    box-shadow: none;
    font-size: inherit;
}

/* A key cap is one of the very few things that is genuinely PHYSICAL, so it
 * keeps a raised treatment — but it gets it from the five layer contact stack,
 * not from a stroke and not from a drop shadow. fx-shadow-stack is exactly the
 * treatment: it is what makes a small object look manufactured. The label is
 * the medium weight, which is where Linear puts kbd chips, and the mono face
 * has no heavier instance anyway. */
kbd {
    padding-block: 0.15em;
    padding-inline: var(--fx-space-2);
    font-size: var(--fx-text-xs);
    font-weight: var(--fx-weight-medium);
    color: var(--fx-color-text);
    background-color: var(--fx-color-surface-raised);
    border-radius: var(--fx-radius-xs);
    box-shadow: var(--fx-shadow-stack);
}


/* ── 7) Links ──────────────────────────────────────────────────────────────
 * Underlined by default, because colour alone must never be the only carrier
 * of meaning (WCAG 1.4.1). The underline is offset and thinned so it clears
 * Arabic descenders instead of cutting through them.
 *
 * Link text is one of the SIX sanctioned brand sites (00-tokens 3.9). It is the
 * only place in this whole file where a brand colour is painted.
 *
 * WHY HOVER IS BEHIND any-hover. A touch device reports a hover on tap and then
 * KEEPS it until the user taps something else, so an ungated hover rule leaves
 * a stuck state behind every tap. Gating it means the resting appearance is the
 * only appearance a touch user ever sees, which is correct. Every hover rule in
 * this file is gated the same way; component layers must do the same.
 * ────────────────────────────────────────────────────────────────────────── */
a {
    color: var(--fx-color-link);
    text-decoration: underline;
    text-decoration-thickness: var(--fx-border-width);
    text-underline-offset: 0.22em;
    text-decoration-color: var(--fx-color-accent-subtle);
    /* State changes are FAST — 100ms, which is Linear's hover timing. Entrances
     * get enter or base, presses get instant, exits get exit. transition-property
     * is ENUMERATED here and everywhere: `all` transitions properties nobody
     * intended and is the usual cause of a mysterious layout animation. */
    transition:
        color var(--fx-dur-fast) var(--fx-ease-standard),
        text-decoration-color var(--fx-dur-fast) var(--fx-ease-standard);
}

@media (any-hover: hover) {
    a:hover {
        color: var(--fx-color-link-hover);
        text-decoration-color: currentColor;
    }
}

a:active {
    color: var(--fx-color-link-hover);
}

/* A link that is really a button, a tab or a menu item is styled by its own
 * component layer, so the base underline must not fight it. */
a[role="button"],
a[role="tab"],
a[role="menuitem"] {
    text-decoration: none;
}


/* ── 8) Form controls ──────────────────────────────────────────────────────
 * Bare controls inherit the font. This is the browser default that silently
 * breaks Arabic pages: an unstyled input falls back to the UA font, so a form
 * renders in a different face from the label beside it.
 * ────────────────────────────────────────────────────────────────────────── */
button,
input,
optgroup,
select,
textarea {
    font: inherit;
    color: inherit;
    margin: 0;
}

button,
select {
    text-transform: none;
}

/* THE BUTTON SHAPE IS ROUTED THROUGH ONE TOKEN, AND THE PILL IS GONE.
 *
 * fx-radius-control is the single value that decides the button shape for the
 * whole platform. It is applied here, on the BARE element, so an unstyled button
 * on a page no component layer has touched already has the right shape, and at
 * 0,0,1 it loses to any .btn class, so a component layer that wants a different
 * shape simply says so. None of that changed.
 *
 * WHAT CHANGED, 2026-08-03: THE TOKEN NO LONGER RESOLVES TO A STADIUM. The
 * client retired the pill, and fx-radius-control now reads fx-radius-md — the
 * same 8px corner section 8.1 gives a text control. fx-radius-pill still exists
 * and still means fully round; it is simply no longer what a button takes. The
 * ruling is in 00-tokens section 6 and this file only consumes it.
 *
 * THE PARAGRAPH THAT USED TO CLOSE THIS NOTE IS DELETED RATHER THAN AMENDED,
 * because it had turned into an argument for a distinction that no longer
 * exists. It read: "inputs are NOT routed through it: a pill shaped text field
 * is a different decision and the reference does not make it." That was true and
 * useful while the token was a stadium. It is now actively misleading — a button
 * and a text field resolve to the SAME 8px — and the specific harm is that a
 * reader who found it would conclude the two shapes were being held apart on
 * purpose, and could restore the pill to make the sentence true again. The two
 * are deliberately identical now.
 *
 * The declaration below is still the one line reversible knob it always was:
 * repoint fx-radius-control in 00-tokens and every button on the platform
 * follows, with nothing to change here. */
button,
[type="button"],
[type="reset"],
[type="submit"] {
    cursor: pointer;
    border-radius: var(--fx-radius-control);
    /* WCAG 2.2 SC 2.5.8: no interactive target below 24 by 24 CSS px.
     * fx-control-h-xs IS 24px and is the absolute floor; sm (28px) is the floor
     * for anything with a text label. */
    min-block-size: var(--fx-control-h-sm);
    min-inline-size: var(--fx-control-h-sm);
}

textarea {
    resize: vertical;
    min-block-size: calc(var(--fx-control-h-lg) * 2);
}

/* A form label is chrome, and chrome is the workhorse 500 weight at the
 * primary tier. Tracking follows the UI tier and is zeroed for Arabic. */
label {
    display: inline-block;
    font-weight: var(--fx-weight-medium);
    letter-spacing: var(--fx-tracking-ui);
    color: var(--fx-color-text);
}

/* Placeholders are the TERTIARY tier, which is where the reference puts them
 * and where they belong: a placeholder is a hint about the field, not content
 * in it. This reverses the previous rule, which sent them to muted because on
 * the old wide ladder subtle fell to 3.9:1 against the dark well. The
 * compressed ladder removed that constraint — subtle now measures 5.71:1 on
 * the light well and 5.14:1 on the dark one, so SC 1.4.3 has real headroom. */
::placeholder {
    color: var(--fx-color-text-subtle);
    opacity: 1;
}

/* ── 8.1 The filled, borderless control ───────────────────────────────────
 * Bare inputs arrive from the user agent with a 1px stroke, square-ish corners
 * and no fill — precisely the look the client rejected, and it would be the
 * default on every page that has not yet been through a component layer. So
 * the borderless treatment lives here, at the element layer: fill plus well,
 * no stroke, tight radius.
 *
 * IDENTIFICATION comes from fx-color-field-bg (a different fill from whatever
 * the control sits on) and from fx-shadow-well, whose strongest band clears
 * 3:1 against that fill in both themes. Read the rule over fx-shadow-well in
 * 00-tokens before changing anything here — removing the well makes every bare
 * control on the platform fail SC 1.4.11.
 *
 * The :not() list carries the controls that are native widgets rather than
 * text wells (checkbox, radio, range, colour, file) and the input-as-button
 * types, which belong to the button treatment instead. A :not() taking a
 * selector LIST counts once toward specificity, so this stays at 0,1,1.
 *
 * ══ THE FILL IS READ FROM fx-ctl-bg, NOT NAMED DIRECTLY — 2026-08-03 ═══════
 *
 * This rule, its hover twin below, and the focus rule in 11.1 all used to name
 * a fill TOKEN outright: field-bg here, field-bg-hover on hover, field-bg-focus
 * on focus. All three now read `var(fx-ctl-bg, <that same token>)`, and the
 * change is a LAYERING correction rather than a visual one. Nothing moves for a
 * control whose local already resolves to the token this rule used to name,
 * which is every ordinary field on the platform.
 *
 * WHY IT WAS WRONG. The system's rule is that a consumer moves a LOCAL and
 * never out specifies a PROPERTY — which holds AT REST, and the last block of
 * this header is the narrowing that says where it stops. These three rules
 * inverted even the rest state case: they painted a
 * property at 0,1,1, 0,3,1 and 0,2,1, so a component that had correctly moved
 * fx-ctl-bg still lost the paint to this file unless it ALSO out specified the
 * declaration. 05-forms sets that local for readonly, for the plaintext variant
 * and for disabled at 0,1,1 and 0,2,0, and 09-tabulator hands it `transparent`
 * on a cell editor. Every one of those was overpainted here on hover.
 *
 * MEASURED BEFORE THE CHANGE, dark theme, pointer resting on the field:
 *     readonly, local says surface-sunken  ......  painted field-bg-hover
 *     plaintext, local says transparent  ........  painted field-bg-hover
 * on a level 1 plane, on a level 2 card, on a page and inside an ABP modal
 * alike. A readonly field on a legal form therefore became indistinguishable
 * from an editable one for as long as the pointer was over it. After the change
 * all three paint what the local says, in every one of those places.
 *
 * WHY THE WEIGHTS DO NOT DROP, WHICH IS THE PART A TIDY UP WOULD BREAK. The
 * obvious alternative is to demote these rules to setting fx-ctl-bg and let the
 * rest rule do the painting. That would hand the platform straight back to
 * Bootstrap: `.form-control:focus` is 0,2,0 and repaints the fill from
 * bs-body-bg, and outside `.lpx-content` — which is where EVERY ABP modal is
 * appended, and where most of this platform's data entry happens — nothing else
 * is heavy enough to answer it. The weights stay exactly as they were; only the
 * VALUE moved into the local.
 *
 * AND IT REMOVES A STATE COLLISION FOR FREE. Hover is 0,3,1 and focus is 0,2,1,
 * so hover used to outrank focus and a field focused with the pointer still on
 * it kept the HOVER fill. That is the same defect 05-forms section 2 records
 * having fixed at its own tier with a pair of guards. Now that both rules read
 * ONE variable, whichever of them wins the property is irrelevant: the local
 * itself resolves to the focus value, and both paint it.
 *
 * NO `:not([readonly])` GUARD IS ADDED, AND THE OMISSION IS THE DESIGN. The
 * hover rule below needs no readonly arm because a readonly control's local
 * already says surface-sunken, so the rule paints the correct fill by reading
 * it. Adding the guard would state the same fact twice and would then have to be
 * kept in step with 05-forms section 2 forever. The one case the fallback still
 * reaches is a readonly input whose type is outside 05-forms section 1's list
 * and which therefore carries no locals at all; there is no such type in the
 * product.
 *
 * ══ AND THE CONTRACT THIS OFFERS A COMPONENT IS A REST STATE CONTRACT ══════
 * NARROWED 2026-08-03, after the broader promise was tested live and failed.
 *
 * The paragraph above says a consumer leaves the page field system by MOVING
 * THE LOCAL rather than out specifying a painted property. AT REST that is true
 * and it is the right contract. IN THE HOVER AND FOCUS STATES IT IS NOT, and no
 * amount of care at the consumer's end makes it true: 05-forms section 2
 * redeclares fx-ctl-bg on hover AND on focus at (0,4,1), and 05-forms loads
 * after 03-leptonx, so a component that sets the local at (0,2,0) in its own
 * layer is simply overwritten the moment a pointer arrives. Proven live, not
 * reasoned: fx-ctl-bg set to transparent at (0,2,0) still computed #fbfdf9 on
 * hover.
 *
 * THOSE TWO WEIGHTS ARE NOT THE THING TO CHANGE, which is the whole reason the
 * promise is narrowed instead of honoured. Both are load bearing and both are
 * recent fixes rather than accidents: the guards on the hover rule are what
 * make it CORRECT, since a disabled or readonly control must never take a hover
 * fill, and the matching armour on the focus rule is what stopped focus losing
 * to hover on /Account/Manage. Lowering either arm reopens a defect that was
 * closed the same day; lowering both keeps them level with each other but drops
 * them under the guards they need. There is no weight for that pair that is
 * simultaneously correct and beatable from a component layer.
 *
 * SO THE CONTRACT IS: THE LOCAL IS A REST STATE CONTRACT. A component that must
 * hold its own fill through hover and focus states it ITSELF, on its own
 * selector, in the same states — which is what `.form-control-plaintext` and
 * the tabulator cell editor already do, and it costs them two extra rules
 * rather than a specificity war. 05-forms section 2's header carries the same
 * ruling and the measurements behind it; the two files agree, and if they ever
 * drift the measured one wins.
 * ────────────────────────────────────────────────────────────────────────── */
input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="image"], [type="submit"], [type="button"], [type="reset"]),
select,
textarea {
    padding-block: 0;
    padding-inline: var(--fx-control-pad-x-md);
    /* lg, not md: a TEXT FIELD is 40px, not 32px. 32px is the menu and select
     * ITEM height, which is a row inside a popup, not a control you type into. */
    min-block-size: var(--fx-control-h-lg);

    color: var(--fx-color-text);
    /* The local first, the resting fill as its fallback. See the fx-ctl-bg note
     * in the section header for why the value moved and the weight did not. */
    background-color: var(--fx-ctl-bg, var(--fx-color-field-bg));
    /* Load bearing. Every one of these controls ships with a user agent stroke,
     * so this is the line being SUPPRESSED — it is the whole reason the platform
     * is borderless by default rather than only on styled pages. */
    border: 0;
    border-radius: var(--fx-radius-md);
    box-shadow: var(--fx-shadow-well);

    transition:
        background-color var(--fx-dur-fast) var(--fx-ease-standard),
        box-shadow var(--fx-dur-fast) var(--fx-ease-standard);
}

/* A textarea is a block of text, not a single line control, so it needs real
 * block padding; the single line controls centre their text on the min height. */
textarea {
    padding-block: var(--fx-space-2);
}

/* Gated on any-hover so a touch device never keeps a field lit after a tap.
 *
 * The hover FILL is the fallback, not the declaration, so this rule paints
 * whatever fx-ctl-bg resolves to when the pointer arrives, and a control with no
 * local at all still lightens.
 *
 * WHICH IS NOT THE SAME AS SAYING A COMPONENT'S OWN LOCAL SURVIVES INTO THIS
 * STATE. 05-forms section 2 sets fx-ctl-bg on hover at (0,4,1) from a later
 * sheet, so what this rule reads here is the FORM LAYER's hover value, not a
 * component's. A component that must hold its own fill on hover states it in
 * the hover state itself. Read the rest state contract in the 8.1 header. */
@media (any-hover: hover) {
    input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="image"], [type="submit"], [type="button"], [type="reset"]):hover:not(:disabled),
    select:hover:not(:disabled),
    textarea:hover:not(:disabled) {
        background-color: var(--fx-ctl-bg, var(--fx-color-field-bg-hover));
    }
}

/* The user agent gives a fieldset a 2px groove. Suppressing it is load bearing;
 * a form section groups by its legend and by spacing, never by a box. */
fieldset {
    margin: 0;
    padding: 0;
    border: 0;
    min-inline-size: 0;
}

/* A legend is a SECTION HEADING, and a section heading is 500 — not 600. 600 is
 * reserved for page and dialog titles. */
legend {
    padding: 0;
    margin-block-end: var(--fx-space-2);
    font-size: var(--fx-text-lg);
    font-weight: var(--fx-weight-medium);
    letter-spacing: var(--fx-tracking-title);
    color: var(--fx-color-text);
}

/* TABULAR FIGURES, AND NO FONT FAMILY. The family declaration that used to sit
 * in this rule is DELETED, 2026-08-03, and its absence is the fix.
 *
 * It read `font-family: var(fx-font-numeric)`, which is a LATIN FIRST stack —
 * "IBM Plex Sans", then Plex Mono. That token is right for a numeric COLUMN, a
 * money cell or a count badge, where nothing Arabic is set beside the digits. It
 * is wrong for every element in this rule, and it had already been overridden
 * for two of the three:
 *
 *   input[type=number]  05-forms section 1 and section 10.1 both set
 *                       `font-family: inherit` at (0,1,1) in a LATER file, so
 *                       this declaration lost. Dead, not merely redundant.
 *   input[type=tel]     the same 05-forms section 1 control rule names it. Also
 *                       dead.
 *   output              NOTHING overrode it, so this was the one element still
 *                       resolving the Latin chain — and it is the element where
 *                       that matters most. An output is the COMPUTED VALUE of
 *                       the field beside it. Leaving it on a different stack
 *                       from the input that produced it is the same "from
 *                       another planet" defect 05-forms fixed on the input, one
 *                       element further along the same form.
 *
 * AND IT COSTS NOTHING TO DROP. Measured on the running build at 40px, Plex Sans
 * and Plex Sans Arabic give IDENTICAL digit advances — 0123456789 at 200.00px
 * and 1111111111 at 186.64px in both faces. They are metrically harmonised for
 * Latin digits, so moving off the Latin stack changes no digit width anywhere.
 * There is no trade being made here, which is why this is a deletion rather than
 * a balancing act.
 *
 * WHAT STAYS, AND MUST. tabular-nums stays on all three. It is presently INERT
 * platform wide — the vendored Plex subsets carry no `tnum` table, which is a
 * FONT PIPELINE defect owned elsewhere — but the declaration is correct and
 * starts working the day the subset ships the feature. Do not remove it because
 * it currently does nothing.
 *
 * These elements now inherit the document's own body stack through the `font:
 * inherit` in section 8, which resolves to Plex Sans Arabic on an Arabic page
 * and Plex Sans on an English one, exactly like every sibling control. A
 * component that genuinely wants the Latin numeric stack — a grid money column,
 * a table numeric cell, a count badge — names fx-font-numeric itself, and 07, 09
 * and 10 all do. */
input[type="number"],
input[type="tel"],
output {
    font-variant-numeric: tabular-nums;
}


/* ── 9) Replaced elements ──────────────────────────────────────────────────
 * The other half of the overflow guarantee: media can never be wider than its
 * container, so it can never widen the page.
 * ────────────────────────────────────────────────────────────────────────── */
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
    display: block;
    max-inline-size: 100%;
    block-size: auto;
}

/* An icon inside a run of text must stay on the baseline, not break the line.
 * Scoped to the places icons actually appear, so a full width inline SVG
 * illustration is not shrunk to 1em by accident. */
button svg,
a svg,
label svg,
th svg,
td svg,
summary svg {
    display: inline-block;
    vertical-align: -0.125em;
}

button svg:not([width]):not([height]),
a svg:not([width]):not([height]),
label svg:not([width]):not([height]) {
    inline-size: 1em;
    block-size: 1em;
}

figure {
    margin: 0 0 var(--fx-space-4);
}

figcaption {
    margin-block-start: var(--fx-space-2);
    font-size: var(--fx-text-sm);
    color: var(--fx-color-text-muted);
}


/* ── 10) Tables ────────────────────────────────────────────────────────────
 * text-align: start rather than left, so an Arabic table aligns to the right
 * without a direction specific override. A table wider than its container must
 * be wrapped in fx-scroll-x (section 12) — the page itself never scrolls.
 * ────────────────────────────────────────────────────────────────────────── */
table {
    border-collapse: collapse;
    border-spacing: 0;
    max-inline-size: 100%;
    text-indent: 0;
    caption-side: bottom;
}

/* ══ THE LINEAR TABLE — NO SEPARATORS, NO ZEBRA, NO HEADER FILL ════════════
 *
 * Three things a bare table does NOT get, and every one of them is a habit
 * worth naming so nobody puts it back:
 *
 *   NO ROW SEPARATOR    A table that needs a visible line between every row is
 *                       a table whose rows are too tight. Row HEIGHT is the
 *                       separator. This was already true here and stays true.
 *   NO ZEBRA STRIPING   Alternating fills halve the effective contrast of every
 *                       other row and make a hover state impossible to see
 *                       against half the table. If a user cannot track a row
 *                       across the screen, the answer is the hover fill, which
 *                       follows the cursor and therefore actually helps.
 *   NO HEADER FILL      thead used to step to level 2. It no longer paints at
 *                       all. The header is identified by being SMALLER, LIGHTER
 *                       and SHORTER than the body — 12px, weight 500, the
 *                       tertiary tier, in a 32px row — which is more legible as
 *                       "this is a label row" than a band of colour is.
 *
 * DENSITY. The header row is fx-row-h-head (32px) and body rows land on
 * fx-row-h (36px) through snug leading plus one space step of block padding.
 * The previous 16px block padding produced a roughly 57px row, which read as a
 * list of cards rather than as a table.
 * ═══════════════════════════════════════════════════════════════════════════ */
th,
td {
    text-align: start;
    vertical-align: middle;
    padding-block: var(--fx-space-2);
    padding-inline: var(--fx-space-3);
    line-height: var(--fx-leading-snug);
}

/* A table header is a LABEL: small, light, medium weight, tertiary tier. It is
 * never bold and never coloured. */
th {
    font-size: var(--fx-text-xs);
    font-weight: var(--fx-weight-medium);
    letter-spacing: var(--fx-tracking-ui);
    color: var(--fx-color-text-subtle);
}

/* Applied to the cells rather than to thead because under border-collapse a
 * height or background set on thead is not honoured in every engine. */
thead th {
    block-size: var(--fx-row-h-head);
    padding-block: 0;
}

/* Numeric cells align only if the figures are tabular. Cheap here, and it stops
 * every grid component having to remember it. */
td {
    font-variant-numeric: tabular-nums;
}

/* SPECIFICITY EXCEPTION, the fourth in this file and the smallest: 0,1,2. A row
 * hover cannot be expressed at 0,1,0 without also lighting the header row, which
 * is not hoverable content. A component that needs to defeat this overrides
 * background-color, never the selector. Body rows only.
 *
 * The hover fill is fx-color-hover — a NEUTRAL wash, not a brand tint. A green
 * row hover is the single most common way a Linear-style table gets undone.
 * See the accent rule in 00-tokens section 3.9. */
tbody tr {
    min-block-size: var(--fx-row-h);
    transition: background-color var(--fx-dur-fast) var(--fx-ease-standard);
}

@media (any-hover: hover) {
    tbody tr:hover {
        background-color: var(--fx-color-hover);
    }
}

caption {
    padding-block: var(--fx-space-2);
    font-size: var(--fx-text-sm);
    color: var(--fx-color-text-subtle);
    text-align: start;
}


/* ── 11) Focus, selection, scrollbars ──────────────────────────────────────
 *
 * ══ FOCUS IS ONE THING: A 2px OUTLINE AT 2px OFFSET ═══════════════════════
 *
 * RETUNED 2026-08-02. There used to be two focus treatments — a fallback
 * outline for non controls and a "bloom" for anything operable, made of a solid
 * ring, a 6px spread halo and a blurred brand glow, with the control lifting
 * 1px as it took focus. All of that is GONE. There is now exactly one focus
 * indicator in the entire platform:
 *
 *     outline: 2px solid var(fx-color-focus-ring);
 *     outline-offset: 2px;
 *
 * NOT a box-shadow. NOT a glow. No blur, no spread, no halo, no rgba brand
 * bloom, no lift. Reasons, in order of weight:
 *   1. An outline follows the element's own border-radius, is drawn outside the
 *      box so it cannot be clipped by the element's own overflow, and SURVIVES
 *      FORCED COLOURS, where every box-shadow is dropped. A shadow-based
 *      indicator disappears for exactly the users who most need it.
 *   2. One indicator means one thing to verify. Two meant the bloom's inner
 *      step had to be audited separately every time anything moved.
 *   3. A halo plus a lift is decoration attached to an accessibility affordance.
 *      It made focus read as a state change rather than as a cursor position.
 *
 * focus-visible only, never bare focus: a mouse click on a button must not
 * leave a ring behind.
 *
 * THE RING IS BRAND IN BOTH THEMES. This paragraph used to say the opposite and
 * the correction is recorded rather than swept, because the superseded reasoning
 * is exactly what a reader would otherwise re-derive:
 *
 *   SUPERSEDED, and do not restore it. "The ring is NOT brand in light. It is
 *   translucent green-black ink, measured at 3.47:1 against the page and 3.63:1
 *   against white — comfortably over the SC 1.4.11 floor of 3:1, and above
 *   Linear's own #0006, which measures 2.85:1 and fails. In DARK the ring IS
 *   brand green, because on a near black page a translucent white ring cannot be
 *   told apart from the hover wash beside it." That was defensible while focus
 *   counted as INTERACTION, which under the accent contract is neutral.
 *
 *   WHAT REVERSED IT: 00-tokens 2.8 reclassified focus as a POSITION signal —
 *   it is the keyboard user's answer to "where am I" — and every position signal
 *   in this system is brand. So LIGHT JOINED DARK rather than dark staying the
 *   exception, and the light ring is now green-800, the same step already in
 *   force for fx-color-primary and fx-color-link.
 *
 * THE FIGURES NOW IN FORCE, measured against every plane a ring can land on,
 * SC 1.4.11 floor 3:1:
 *   light  green-800   level 0 7.24   level 1 7.57   level 2 7.91   level 3 8.27
 *   dark   green-500   level 0 5.52   level 1 5.26   level 2 5.01   level 3 4.75
 * The light ring is therefore more than twice as strong on every plane as the
 * translucent ink it replaced, which carried under 0.6 of headroom on the page
 * and no brand at all. Both themes are re-verified over the interaction washes
 * as well, since a ring routinely lands on an already hovered row — worst case
 * 3.74:1 in dark. See 00-tokens 2.8 and 3.8; NOTHING IN THIS FILE PICKS THE
 * COLOUR, it only reads fx-color-focus-ring, so the token layer is the authority
 * and this comment must follow it rather than lead it.
 *
 * ROVING FOCUS IS THE ONE EXCEPTION, AND DOWNSTREAM LAYERS OWN IT.
 * Inside a widget with roving tabindex — a menu, a listbox, a select popup, a
 * grid — the focused item must NOT draw a ring. A ring inside a popup traces a
 * second rounded rectangle 2px outside the item, inside a container that is
 * only 4px wider than the item, and the result reads as a rendering fault.
 * Those items get:
 *
 *     outline: none;
 *     background-color: var(--fx-color-active);
 *
 * The background change is the focus indicator, and because fx-color-active is
 * a neutral 8 percent wash over a known surface it clears the non-text contrast
 * requirement as a state change of the item itself rather than as an added
 * boundary. The RING stays on the container, which is what the user tabbed to.
 * This is Linear's own pattern and it is the ONLY sanctioned way to suppress a
 * focus ring anywhere in this system.
 *
 * KNOWN LIMIT: on a deep green chrome surface such as the sidebar rail, the
 * light theme ink ring falls below 3:1. Any layer that paints a dark brand
 * surface must re-declare fx-color-focus-ring locally to a light value on that
 * surface. Because the ring reads a token, that is a one line fix at the point
 * of the problem and needs no change here.
 *
 * SCROLLBARS
 *   THE STANDARD PROPERTIES ARE THE PLATFORM MECHANISM. `scrollbar-width` and
 *   `scrollbar-color`, stated on `*` and on the handful of elements a vendor
 *   claims by name, are what every scrollbar in this product is drawn with. The
 *   WebKit pseudo elements are kept only for engines that do not implement the
 *   standard ones, behind an at-supports test on the PROPERTY.
 *
 *   THIS WAS INVERTED UNTIL 2026-09-04 AND THE INVERSION SHIPPED A WHITE
 *   SCROLLBAR INSIDE EVERY DARK PANEL. The reasoning is in section 11 below and
 *   it is worth reading before touching either syntax; the short version is that
 *   the two syntaxes are still mutually EXCLUSIVE, that Chrome resolves the
 *   conflict in favour of the standard properties, and that an INHERITED
 *   `scrollbar-color` counts — so a single vendor rule on `body` was enough to
 *   delete the entire WebKit skin from the whole document.
 *
 *   ONE CARVE-OUT, RECORDED 2026-08-03 AND STILL IN FORCE: HIDING a scrollbar is
 *   a local operation; STYLING one is not. `scrollbar-width: none`, paired at the
 *   same call site with `::-webkit-scrollbar { display: none }`, is a TOTAL
 *   SUPPRESSION and is the one thing a component layer may declare for itself.
 *   It is REQUIRED rather than redundant: section 11 declares `scrollbar-width:
 *   thin` on `*`, so a scroll rail that does not restate `none` locally grows
 *   exactly the scrollbar it was built to hide, in every engine. The four
 *   sanctioned sites are all in 06-navigation — the tab and pill scroll rail,
 *   the folded vertical rail, the segmented track and the stepper — plus the
 *   breadcrumb and the sidebar rail in 03-leptonx, and each carries a note
 *   pointing back here. Anything else that declares a WIDTH, a COLOUR or a THUMB
 *   outside section 11 is still a defect.
 * ────────────────────────────────────────────────────────────────────────── */
:focus-visible {
    outline: var(--fx-focus-width) solid var(--fx-color-focus-ring);
    outline-offset: var(--fx-focus-offset);
}

:focus:not(:focus-visible) {
    outline: none;
}

/* 11.1 THE FOCUSED FIELD BRIGHTENS.
 *
 * The only thing left of the old bloom. A focused text control lights its well
 * to the top of the elevation ladder, which is a fill change and nothing else —
 * no ring of its own, no lift, no glow. The RING is the outline declared above
 * and it is identical on every focusable thing in the document, which is the
 * whole point of the retune.
 *
 * The two rules that used to live here are gone entirely: the one that set
 * outline-color: transparent and painted fx-shadow-focus-glow instead, and the
 * one that lifted the control by fx-lift-focus. Nothing in this file suppresses
 * the outline any more, which also means section 16's forced-colours block no
 * longer has to fight to bring it back.
 *
 * Buttons are deliberately NOT in this list: they carry their own fill from a
 * component layer and must not be repainted to the field colour. A button's
 * focus indicator is the outline, and only the outline.
 *
 * THE FILL IS READ FROM fx-ctl-bg AND THE FOCUS TOKEN IS ITS FALLBACK, exactly
 * as in 8.1 and for the same reason. This rule keeps its 0,2,1 weight because
 * that is what answers Bootstrap's `.form-control:focus` at 0,2,0 outside
 * `.lpx-content`, which is where every ABP modal lives; only the value moved.
 * The full argument, and the measurements that forced it, are in the 8.1 header.
 * ────────────────────────────────────────────────────────────────────────── */
select:focus-visible,
textarea:focus-visible,
input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="image"], [type="submit"], [type="button"], [type="reset"]):focus-visible {
    background-color: var(--fx-ctl-bg, var(--fx-color-field-bg-focus));
}

/* The text caret in a search or command input is one of the SIX sanctioned
 * brand sites (00-tokens 3.9). It is the smallest possible use of brand colour
 * and it is only visible while the user is typing, which is exactly why the
 * reference spends it there.
 *
 * BROADENED 2026-08-02 (second pass), AND BROADENED BY ROLE RATHER THAN BY
 * TYPE. The selector used to be input[type="search"] alone, which missed most
 * of the platform: the Cases and Courts dialog scripts emit their search boxes
 * as type="text", so 05-forms had to restate the caret for .fx-search-input and
 * .fx-command-input, and every future search box would have had to remember to
 * do the same.
 *
 * WHY NOT SIMPLY EVERY TEXT INPUT. Because the brand caret is site 4 of six and
 * the accent rule is a budget, not a decoration. A case-title field and a
 * client-name field get the ordinary caret; a SEARCH or COMMAND field gets the
 * brand one, because that is the one input in the reference that is a tool
 * rather than a form control.
 *
 * WHY ROLE AND NOT A CLASS. This file styles bare elements and has exactly four
 * documented class exceptions; .fx-search-input and .fx-command-input belong to
 * 05-forms and adding them here would be a fifth. role="searchbox" and
 * role="combobox" are the ARIA roles those two widgets already owe assistive
 * technology, so the markup earns the caret by correctly declaring what it is,
 * and nothing has to be styled twice. An input that wants the brand caret and
 * does not carry one of these roles has an accessibility defect first and a
 * caret problem second.
 *
 * Specificity is 0,1,1 — the same as input[type="search"] was, and inside the
 * band section 8.1 and 11.1 already document. */
input[type="search"],
input[role="searchbox"],
input[role="combobox"] {
    caret-color: var(--fx-color-primary);
}

/* ::selection AND ::target-text ARE OWNED HERE, AND ONLY HERE.
 *
 * They are bare pseudo-elements on the document with no component to belong to,
 * which is the exact definition of this layer's job. 03-leptonx.css section 2
 * declares an identical ::selection block; that copy is redundant and should be
 * removed, because a vendor-override layer restating a global means two files
 * have to agree forever about what selected text looks like, and the one that
 * loads later silently wins. Reported upward rather than edited, since this
 * file does not own 03. */
::selection {
    color: var(--fx-color-selection-fg);
    background-color: var(--fx-color-selection-bg);
}

::target-text {
    color: var(--fx-color-selection-fg);
    background-color: var(--fx-color-selection-bg);
}

/* ══ 11.2) THE SCROLLBAR CONTRACT. REWRITTEN 2026-09-04, AND THE OLD ONE WAS
 *         HALF A CONTRACT THAT NEVER RAN IN THE BROWSER THE OFFICE USES. ═════
 *
 * ── THE DEFECT, AS THE OWNER REPORTED IT ────────────────────────────────────
 *
 * «في سلايد بار قديم ومش مدموج داخل البوباب» — the scrollbar inside a popup is
 * the raw operating system one rather than part of the panel. Measured on
 * /ExpenseRequests in Chrome, dark theme, on the open select2 dropdown's
 * `.select2-results__options`:
 *
 *     getComputedStyle(el).scrollbarColor  →  rgb(231,233,236) rgb(255,255,255)
 *     getComputedStyle(el).scrollbarWidth  →  auto
 *     CSS.supports('selector(::-webkit-scrollbar)')  →  true
 *
 * A near white thumb on a pure white track, inside a dark panel, at the browser
 * default width. Not one declaration of ours was in force.
 *
 * ── THE CHAIN, AND EVERY LINK OF IT IS CHECKABLE ────────────────────────────
 *
 *   1. LeptonXLite ships, with NO theme condition of any kind:
 *        body, pre code, .lpx-demo-scrollspy, .card-body, .modal-body,
 *        .form-select { scrollbar-color: rgb(231,233,236) rgb(255,255,255) }
 *   2. `scrollbar-color` INHERITS. So every scrollable descendant of `body`
 *      computes that near white pair — dropdown panels, dialog bodies, card
 *      bodies, grids, code blocks, everything.
 *   3. AND AN INHERITED VALUE IS STILL A SPECIFIED VALUE FOR THE PURPOSES OF
 *      THE CHROME 121 RULE. This is the link the previous contract missed. The
 *      note above it correctly recorded that declaring `scrollbar-color` makes
 *      Chrome discard every `::-webkit-scrollbar` rule for that element — and
 *      then assumed the only way to declare it was to write it ourselves. A
 *      vendor writing it once on `body` was enough to delete the tokenised
 *      WebKit skin from the entire document.
 *   4. So the counter measure never ran either. The standard properties sat
 *      inside `@supports not selector(::-webkit-scrollbar)`, which is FALSE in
 *      Chrome, so the whole block was skipped. It was only ever a Firefox
 *      branch. Chrome had no rule of ours at all, in either syntax, and fell
 *      through to LeptonX's inherited light pair.
 *
 * The comments in this file were right about the mechanism and wrong about who
 * would trigger it. That is the failure mode this section now guards.
 *
 * ── THE CHOICE, MADE DELIBERATELY: ONE MECHANISM, NOT TWO ───────────────────
 *
 * THE STANDARD PROPERTIES WIN AND THE PSEUDO ELEMENTS RETREAT TO A FALLBACK.
 * Chrome, Edge and Firefox all implement `scrollbar-width` / `scrollbar-color`,
 * and Chrome resolves the conflict in their favour, so building on them is
 * building on the branch the engine has already chosen. It also removes the
 * whole hazard class rather than working around one instance of it: there is no
 * longer any element in the product that could receive both syntaxes, so no
 * vendor rule anywhere can silently disable our skin again.
 *
 * THE PSEUDO ELEMENTS ARE KEPT, GATED ON THE PROPERTY RATHER THAN ON THEM. The
 * block below sits inside `@supports not (scrollbar-color: auto)`, which is true
 * only on engines that do not implement the standard property at all — Safari
 * before 18.2. Gating on the PROPERTY is what makes the two blocks mutually
 * exclusive by construction; the old gate tested for the pseudo elements, which
 * every WebKit engine has, and therefore separated nothing.
 *
 * WHAT IS LOST, STATED PLAINLY RATHER THAN GLOSSED. The constant 10px gutter
 * with a thumb inset that ANIMATES from 6px to 10px cannot be expressed in two
 * properties, so on Chrome and Firefox the reveal is now a discrete swap on
 * hover instead of a transition. That is exactly the approximation the Firefox
 * branch has shipped since 2026-08-04 and nobody has reported it; the overlay
 * IDEA — invisible at rest, present when the pointer is over the thing that
 * scrolls — survives intact, and it is the idea the owner asked for. The
 * animation was worth less than a white scrollbar in a dark dialog cost.
 *
 * ── WHY THE SELECTOR IS `*` AND WHY THERE IS ALSO A NAMED LIST ──────────────
 *
 * `*` rather than `html`, because a directly matched declaration always beats an
 * INHERITED one whatever the specificity, so `*` is what cancels LeptonX's
 * inheritance on every scroll container in the product at once — the Tabulator
 * table holder, a dialog body, a horizontally scrolling button group, a code
 * block. It is also why the whole fix belongs here and not in the component that
 * revealed it.
 *
 * BUT `*` IS (0,0,0) AND LOSES TO A VENDOR RULE THAT NAMES THE SAME ELEMENT.
 * LeptonX's selector list is not decoration: `body` at (0,0,1), `pre code` at
 * (0,0,2) and `.card-body` / `.modal-body` / `.form-select` at (0,1,0) all
 * out-specify `*`, and those are the dialog and card scrollers, which is most of
 * the scrolling surface this product has. So the same values are restated for
 * exactly that list, each arm prefixed with `html` to clear the vendor's weight
 * by one step without a flag and without a tie. Ties were avoided on purpose:
 * source order would decide them, and the bundle order is not something this
 * file should have to depend on. */
/* ══ OVERLAY BEHAVIOUR — THE THUMB IS INVISIBLE AT REST, 2026-08-04 ═══════
 * Owner instruction: «make scroll bar hidden make it modern scrollbar of the
 * side menu and all system». Modern here means the macOS/Linear behaviour — the
 * scrollbar is not a permanent piece of furniture, it appears when the pointer
 * is over the thing that scrolls and fades out again.
 *
 * THE GUTTER STAYS RESERVED. Only the THUMB's colour changes; the track keeps
 * its width. This is deliberate and it is the difference between an overlay
 * scrollbar and a broken one: if the gutter collapsed, every scroll container in
 * the product would reflow its content the moment the pointer entered it, and a
 * horizontal jump under the cursor is worse than a visible scrollbar. Reserving
 * the gutter is also what keeps the rail's own width arithmetic true.
 *
 * ⚠ AMENDED 2026-09-04. Two sentences that stood here described only the WebKit
 * branch and are now true only on the fallback engine. The GUTTER figure was
 * "10px", which was the pseudo element track size; on Chrome and Firefox the
 * width is now whatever the engine draws for `scrollbar-width: thin`, and the
 * rail's arithmetic in 03-leptonx still reads the size token, which is
 * unchanged. The reveal was described as a fade driven by a transition on the
 * thumb; on the standard properties it is a discrete swap, because neither
 * property is animatable. Nothing about the INTENT moved — invisible at rest,
 * present while the pointer is over the thing that scrolls.
 *
 * REVEALED BY HOVERING THE CONTAINER, NOT THE THUMB. `*:hover` styles the
 * scrollbar OF the hovered element, so moving into a scrolling panel brings its
 * thumb up. On the fallback engine `::-webkit-scrollbar-thumb:hover` adds a
 * second, stronger step for the thumb itself; the standard properties expose no
 * hook for that, so there it is one step rather than two.
 *
 * TOUCH IS EXCLUDED, AND THAT IS AN ACCESSIBILITY DECISION RATHER THAN AN
 * OVERSIGHT. There is no hover on a touch screen, so a rest state of
 * `transparent` gated on hover would leave those users with no scroll position
 * indicator at all — no way to see how long a case list is or where they are in
 * it. The reveal therefore lives inside `any-hover: hover`, and coarse pointers
 * keep the previous always-visible thumb. */
/* THE UNIVERSAL TIER. Not gated, on purpose: every engine the office runs takes
 * this, and it is the block whose absence in Chrome was the whole defect. */
*,
*::before,
*::after {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}

/* THE VENDOR'S OWN LIST, RESTATED. See the note above for why `*` is not enough
 * for these six. `html` is a prefix for weight only and changes nothing about
 * which elements match. */
html,
html body,
html pre code,
html .lpx-demo-scrollspy,
html .card-body,
html .modal-body,
html .form-select {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}

@media (any-hover: hover) {
    /* `*:hover` is (0,1,0) — a pseudo class carries class weight — so this arm
     * already clears every selector in the vendor list on its own and needs no
     * `html` twin. */
    *:hover {
        scrollbar-color: var(--fx-scrollbar-thumb-hover) transparent;
    }
}

/* Coarse pointers keep a visible thumb — see the note above. There is no hover
 * on a touch screen, so a transparent rest state would leave those users with no
 * scroll position indicator at all. */
@media (any-hover: none) {
    *,
    *::before,
    *::after,
    html,
    html body,
    html pre code,
    html .lpx-demo-scrollspy,
    html .card-body,
    html .modal-body,
    html .form-select {
        scrollbar-color: var(--fx-scrollbar-thumb-hover) transparent;
    }
}

/* ══ THE FALLBACK ENGINE ONLY. Safari before 18.2 has the pseudo elements and
 * NOT the property, so it is the only engine that reaches this block, and no
 * element anywhere can therefore receive both syntaxes. Everything below is the
 * previous WebKit skin, unchanged in value: a constant gutter with the thumb
 * inset by a token and clipped to its content box.
 *
 * DO NOT REMOVE THE GATE AND DO NOT CHANGE IT BACK TO A `selector()` TEST. A
 * `selector(::-webkit-scrollbar)` test is TRUE in Chrome, which is what made the
 * previous arrangement a Firefox-only branch. See 11.2. ══════════════════════ */
@supports not (scrollbar-color: auto) {

    ::-webkit-scrollbar {
        inline-size: var(--fx-scrollbar-size);
        block-size: var(--fx-scrollbar-size);
    }

    ::-webkit-scrollbar-track {
        background-color: transparent;
    }

    /* The transparent border here is the standard WebKit thumb inset and is NOT a
     * stroke: paired with background-clip: content-box it is the only way to give a
     * scrollbar thumb padding, and it paints nothing at any colour or theme.
     *
     * The inset is what produces the 6px resting thumb inside a constant 10px
     * gutter, and dropping it to 0 on hover is what grows the thumb to 10px without
     * reflowing the scroll container. See the scrollbar note in 00-tokens 9. */
    ::-webkit-scrollbar-thumb {
        /* The RESTING colour is the token, and it is overridden to transparent by
         * the overlay rule below on hover-capable pointers only. Stating the token
         * here rather than `transparent` is what leaves touch devices with a visible
         * thumb without needing a second declaration. */
        background-color: var(--fx-scrollbar-thumb);
        background-clip: content-box;
        border: var(--fx-scrollbar-thumb-inset) solid transparent;
        border-radius: var(--fx-radius-pill);
        transition:
            background-color var(--fx-dur-fast) var(--fx-ease-standard),
            border-width var(--fx-dur-fast) var(--fx-ease-standard);
    }

    @media (any-hover: hover) {
        /* Gone until the pointer is over something that scrolls. The transition
         * above carries both directions, so it fades rather than blinking. */
        ::-webkit-scrollbar-thumb {
            background-color: transparent;
        }

        /* Pointer inside the scrolling element — the thumb comes up at its resting
         * weight. */
        :hover::-webkit-scrollbar-thumb {
            background-color: var(--fx-scrollbar-thumb);
        }

        /* Pointer on the thumb itself — the second, stronger step. */
        ::-webkit-scrollbar-thumb:hover {
            background-color: var(--fx-scrollbar-thumb-hover);
            border-width: 0;
        }
    }

    ::-webkit-scrollbar-thumb:active {
        background-color: var(--fx-scrollbar-thumb-active);
        border-width: 0;
    }

    ::-webkit-scrollbar-corner {
        background-color: transparent;
    }

} /* end @supports not (scrollbar-color: auto) — the pre-18.2 Safari fallback */


/* ── 12) Accessibility primitives ──────────────────────────────────────────
 *
 * fx-skip-link — the first focusable element in the layout. Off screen until it
 *   takes focus, then it slides into the inline start corner. Both focus and
 *   focus-visible are matched: revealing a skip link is not a focus RING, and
 *   it must appear for any focus route the browser chooses.
 *
 * fx-sr-only — visible to assistive technology, invisible on screen. clip-path
 *   rather than the legacy clip property, with the 1px box kept so the text
 *   still has a layout position for screen reader cursor routing.
 *
 * fx-scroll-x — the escape hatch behind the no horizontal page scroll rule.
 *   Wrap any genuinely wide content (a data table, a diagram, a code block, a
 *   Tabulator shell) so it scrolls inside itself. Declared here rather than in
 *   the utilities layer because it is structural: without it, the overflow-x
 *   clip on the document would silently hide data.
 * ────────────────────────────────────────────────────────────────────────── */
.fx-skip-link {
    position: absolute;
    z-index: var(--fx-z-toast);
    inset-inline-start: var(--fx-space-2);
    inset-block-start: calc(-1 * var(--fx-space-8) - var(--fx-space-4));

    display: inline-flex;
    align-items: center;
    min-block-size: var(--fx-control-h-lg);
    padding-block: var(--fx-space-2);
    padding-inline: var(--fx-space-4);

    font-weight: var(--fx-weight-medium);
    text-decoration: none;

    color: var(--fx-color-link);
    background-color: var(--fx-color-surface-raised);
    border-radius: var(--fx-radius-md);
    /* A float shadow only. The skip link used to carry the focus bloom as well
     * so it "arrived already focused"; with focus now an outline, the real
     * focus-visible outline lands on it the moment it appears, which is both
     * correct and unmissable without stacking two indicators. */
    box-shadow: var(--fx-shadow-float-lg);

    transition: inset-block-start var(--fx-dur-fast) var(--fx-ease-standard);
}

.fx-skip-link:focus,
.fx-skip-link:focus-visible {
    inset-block-start: var(--fx-space-2);
}

.fx-sr-only {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    /* Part of the canonical visually-hidden recipe and kept for that reason: the
     * class is routinely put on a fieldset, a legend or a button, every one of
     * which arrives with a user agent border that would inflate the 1px box. A
     * suppression, not a stroke. */
    border: 0;
}

.fx-scroll-x {
    max-inline-size: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    /* A scrollable region must be keyboard reachable (WCAG 2.1.1); pages that
     * use this wrapper should also give it tabindex="0" and an accessible name. */

    /* STILL NO scrollbar-width HERE, AND THE REASON HAS CHANGED — AMENDED
     * 2026-09-04. It used to be avoided because the property made Chrome discard
     * the WebKit skin. Under the contract in 11.2 the standard properties ARE the
     * skin, and section 11 declares them on `*`, so this wrapper already has them
     * and restating anything here would only create a second place to keep in
     * step. A component never states a scrollbar width, a colour or a thumb; the
     * one operation it may state for itself is total suppression. */
}


/* ── 13) State: disabled, busy, hidden ─────────────────────────────────────
 * aria-disabled is styled alongside the real disabled attribute because a
 * disabled native control is removed from the tab order, which is wrong for a
 * control that must stay discoverable and announce why it is unavailable.
 *
 * NOTE on hidden: Bootstrap declares this with an importance flag. That flag is
 * reserved for the utilities layer in this system, so a component that sets a
 * display value on an element it also hides must clear the display itself.
 * ────────────────────────────────────────────────────────────────────────── */
/* Disabled text is the QUATERNARY tier — fx-color-disabled-fg points straight
 * at fx-color-text-faint in both themes. It is deliberately sub-AA: a disabled
 * control is not something the user is expected to read and act on, and the
 * standard exempts it. If a disabled label is carrying information the user
 * genuinely needs, the information is in the wrong place, not the wrong tier. */
:disabled,
[disabled],
[aria-disabled="true"] {
    cursor: not-allowed;
    color: var(--fx-color-disabled-fg);
}

/* A disabled control is FLAT. It loses the well, so it stops reading as
 * something you could type into, and it never lifts. The old border-color line
 * is gone: the border it recoloured is already 0, so it drew nothing and only
 * kept the retired stroke channel alive at a call site. Losing the well IS the
 * disabled signal, and it is the correct one — an unavailable control should
 * stop looking like a place you can put something. */
button:disabled,
input:disabled,
select:disabled,
textarea:disabled {
    background-color: var(--fx-color-disabled-bg);
    box-shadow: none;
    transform: none;
}

[aria-busy="true"] {
    cursor: progress;
}

[hidden] {
    display: none;
}


/* ── 14) Motion foundation ─────────────────────────────────────────────────
 *
 * THE SHARED MOTION VOCABULARY. Every component layer (02 and up) animates by
 * referencing what is defined here; nothing above this file declares its own
 * keyframes for these six behaviours, so a card, a grid row, a menu item and a
 * toast all enter the same way and the UI reads as one system rather than as
 * five people's ideas of a fade.
 *
 * The keyframes themselves are declared UNGATED. A keyframe does not animate
 * anything on its own, and gating the declaration behind no-preference would
 * leave a component referencing a name that does not exist in the reduce
 * branch, which is worse than a zero duration animation. Everything that
 * actually APPLIES an animation is gated, and section 15 additionally kills the
 * stagger and entrance classes outright — a zero duration animation still
 * paints its fill-mode start frame for a tick, which is a visible flash.
 *
 * DIRECTIONALITY. transform is a physical property; there is no logical
 * translate. fx-slide-in-inline therefore reads its start offset from
 * fx-slide-from, which this file flips for RTL, so a toast or an offcanvas
 * slides in from the INLINE END in both Arabic and English without a component
 * ever testing the direction.
 *
 * ══ THE FILL MODE RULE, ADDED 2026-08-03 ══════════════════════════════════
 *
 * AN ENTRANCE FILLS `backwards`. AN EXIT FILLS `forwards` (or `both`). Never
 * the other way round, and an entrance must not say `both` out of habit.
 *
 * THE REASON IS NOT TIDINESS, IT IS A CONTAINING BLOCK. Every entrance in this
 * system ends on the element's own resting style — opacity 1 and either
 * translateY(0), scale(1) or transform: none. `both` and `forwards` keep that
 * end frame APPLIED FOREVER, and a filled transform is still a transform: the
 * element goes on creating a stacking context and, worse, a containing block
 * for every `position: fixed` descendant it has. lpx-content is the whole main
 * region of the application. With a forwards filled fx-rise-in on it, a fixed
 * toast stack, a fixed loading overlay or a Popper popup using the fixed
 * strategy anywhere inside the page anchors to lpx-content instead of to the
 * viewport, and lands in the wrong place for the entire life of the document.
 * The same applies to every staggered card, every toast and every dialog.
 *
 * `backwards` gives up nothing. It applies the FROM frame during the delay and
 * before the animation starts, which is the whole reason `both` was reached for
 * — it is what stops a staggered child painting at full opacity for the frames
 * before its turn. What it drops is the forward fill, and there is nothing to
 * drop: the to-frame IS the resting style, so the element lands on the same
 * pixels either way and is then handed back to the ordinary cascade.
 *
 * IT ALSO FIXES A REAL VISIBILITY BUG. Bootstrap hides a `.alert.fade` and a
 * `.toast.fade` by taking `.show` off and letting the base rule fall to
 * opacity 0. An entrance animation filling forwards OUTRANKS that base rule,
 * so a dismissed alert stayed on screen at opacity 1. Under `backwards` the
 * cascade gets the element back and the vendor's hide works again.
 *
 * AN EXIT IS THE OPPOSITE CASE and keeps its forward fill: its to-frame is
 * opacity 0, which is NOT the resting style, and it has to hold until the
 * script unmounts the node. Same for a drain bar, which must stay at empty.
 * ────────────────────────────────────────────────────────────────────────── */

/* 14.1 Keyframes. Names are contractual — other layers reference them. */

/* The default entrance: fade up into place. Lists, cards, rows, page regions. */
@keyframes fx-rise-in {
    from {
        opacity: 0;
        transform: translateY(8px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Opacity only, for anything whose position must not shift — a table cell
 * repaint, a lazily filled avatar, an inline validation message. */
@keyframes fx-fade-in {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

/* THE POPUP ENTRANCE — opacity plus scale, never a slide and never a bare fade.
 *
 * The start scale reads fx-enter-scale (0.96), and the CALL SITE must set a
 * transform-origin pointing at the trigger — the edge or corner the panel
 * belongs to — so the panel reads as growing out of the control that opened it
 * rather than as materialising in place:
 *
 *     .fx-menu { transform-origin: top center; }
 *     .fx-menu[data-placement="bottom-end"] { transform-origin: top right; }
 *
 * Pair with fx-dur-enter and fx-ease-standard on the way in, and fx-dur-exit
 * with fx-ease-in-quad on the way out — the exit is deliberately faster.
 * fx-ease-spring is available but is NOT the default any more: an overshoot on
 * a menu reads as imprecision. Save it for something physical. */
@keyframes fx-scale-in {
    from {
        opacity: 0;
        transform: scale(var(--fx-enter-scale));
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

/* fx-shine AND fx-pulse-ring — BOTH DELETED 2026-08-03. THIS NOTE IS THE RECORD,
 * and it follows the precedent fx-focus-bloom set immediately below: a keyframe
 * with no consumer is removed, and its reasoning stays where a reader looking
 * for the name will find it.
 *
 * NEITHER HAD A CALL SITE ANYWHERE IN THE PRODUCT. Swept across all twelve
 * stylesheets, every .cshtml and every authored .js: the only occurrences were
 * their own declarations here and one line in 03-leptonx section 8 that lists
 * the keyframe names this file owns. Nothing applied either one, in any state,
 * on any element.
 *
 * ── fx-shine WAS the gloss sweep across a primary button: a linear-gradient
 * highlight on a ::after, travelling the button's own width over fx-shine-dur.
 *
 * WHY IT GOES, beyond having no consumer. It is the last survivor of the
 * decorative state channel the 2026-08-02 retune removed everywhere else. That
 * pass deleted the focus bloom, the 6px spread halo, the focus lift and the
 * hover glow on one consistent argument: a control expresses state through
 * FILL, TIER and POSITION, not through applied shine. A gloss sweep is that
 * habit in its purest form. Its duration made it worse rather than better — 700ms
 * is seven times the 100ms platform hover rate, long enough that a user who
 * clicks twice watches two sweeps overlap on the same button.
 *
 * The cost of leaving it declared was not bytes. It was that the next author
 * building a call to action would find a ready made, system blessed way to put
 * back the exact thing the retune spent a day taking out, and would reasonably
 * assume its presence in this file was an endorsement.
 *
 * ── fx-pulse-ring WAS an attention emphasis: a box-shadow ring expanding from 0
 * to 3px and dissolving, tinted through fx-color-primary-rgb. Three reasons, any
 * one of which would have been enough on its own:
 *
 *   1. THE COLOUR WAS A FORBIDDEN BRAND USE. The accent contract in 00-tokens
 *      3.9 rules that identity and current position are BRAND and that
 *      INTERACTION IS NEUTRAL. A pulse marking "this row just arrived" or "this
 *      field failed" is neither identity nor position — it is a response to a
 *      change, which 3.9 sends to a text tier or, where the change carries
 *      meaning, to a STATUS family. Painting a failed field in the brand green
 *      would have been wrong twice over: not brand's job, and the wrong signal
 *      for the state it was marking.
 *   2. IT ANIMATED A box-shadow. Section 11 chose an outline over a shadow for
 *      the focus indicator precisely because forced colours drops every
 *      box-shadow outright. An attention marker built on a shadow is invisible
 *      in the one mode where a user most needs to be told where to look.
 *   3. ITS OWN DOCUMENTATION HAD TO OPEN WITH A PROHIBITION — "this is NOT a
 *      focus indicator and must never be used as one". A primitive whose first
 *      line is a warning about how it will be misread, sitting in the file that
 *      spends sixty lines separating focus from everything else, is a trap
 *      rather than a primitive.
 *
 * NOTHING REPLACES EITHER, and nothing should. A button that needs emphasis uses
 * fill and tier. Something that needs to be pointed at uses the status families
 * in 08-feedback, or fx-fade-in on a real element.
 *
 * TWO TOKENS SURVIVE THIS AND ARE NOT THIS FILE'S TO RETIRE:
 *   · fx-color-primary-rgb keeps other consumers (03-leptonx aliases it twice),
 *     so it is untouched and stays live.
 *   · fx-shine-dur now has NO consumer at all. It is declared in 00-tokens
 *     section 7, so retiring it is a token layer change and is reported upward
 *     rather than made here. Section 15 goes on damping it, and MUST, for as
 *     long as it is declared — see the note there. */

/* fx-focus-bloom — DELETED 2026-08-02 (second pass). THIS NOTE IS THE RECORD.
 *
 * The keyframe declared here animated opacity 0 to 1 plus scale 0.94 to 1, and
 * it was the second half of the old focus treatment: a ring plus a bloom that
 * grew under the control as it took focus. The retune replaced all of that with
 * a 2px outline at 2px offset (see section 11), and the keyframe was left
 * declared on the argument that its NAME was contractual and that it was still a
 * serviceable generic "an overlay arrives" curve.
 *
 * Neither half of that argument survived the audit. The name is referenced by
 * nothing: every mention of fx-focus-bloom across 03-leptonx, 04-buttons,
 * 05-forms and 06-navigation is a COMMENT recording that the bloom was removed
 * from that layer, and those comments are the design record and stay exactly
 * where they are. And a component that wants an overlay to appear already has
 * fx-scale-in, which is the contractual popup entrance, differs only in its
 * start scale (0.96 against 0.94), and does not carry the word "focus" in its
 * name where a future reader will find it and reattach it to an accessibility
 * affordance. Keeping a second, nearly identical curve alive purely so it could
 * be misused was the whole cost.
 *
 * Nothing replaces it. If an overlay needs to appear, it uses fx-scale-in or
 * fx-fade-in. Focus does not animate at all.
 */

/* Toasts and offcanvas ONLY — the two things that genuinely have an edge to
 * come from. Everything else enters with fx-scale-in. Enters from the inline
 * end in both directions — see DIRECTIONALITY above. */
@keyframes fx-slide-in-inline {
    from {
        opacity: 0;
        transform: translateX(var(--fx-slide-from, 0.5rem));
    }

    to {
        opacity: 1;
        transform: translateX(0);
    }
}

/* 14.2 Inline direction for the slide. LTR enters from the physical right,
 * RTL from the physical left; both are the INLINE END. Declared on the token
 * layer's own specificity ladder so the Arabic override outranks the default.
 *
 * The distance was cut from 20px to 8px in the 2026-08-02 retune. A 20px travel
 * at 500ms is a panel making an entrance; 8px is a panel confirming it arrived.
 * The reference's entrances are almost entirely opacity and scale, and the
 * residual translate is there to give the motion a direction, not a journey. */
html:root {
    --fx-slide-from: 0.5rem;
}

html[dir="rtl"]:root,
html[lang^="ar"]:root {
    --fx-slide-from: -0.5rem;
}

/* 14.3 THE STAGGER MECHANISM
 *
 * CSS cannot count, so the index is materialised by nth-child rules and read
 * back through calc(). Twenty children are enumerated individually; everything
 * from the twenty first onward is pinned to index 20, so a 500 row grid never
 * schedules a twenty second delay.
 *
 * HOW A COMPONENT OPTS IN — the whole API is one class on the PARENT:
 *
 *     <div class="fx-card-grid fx-stagger"> … children … </div>
 *     <tbody class="fx-stagger"> … rows … </tbody>
 *
 * Nothing goes on the children. The parent's direct children each animate with
 * fx-rise-in, offset by fx-stagger-step (40ms) times their position.
 *
 * TO CHANGE THE ENTRANCE, override the animation-name on the children and leave
 * the delay machinery alone:
 *
 *     .fx-toast-stack.fx-stagger > * { animation-name: fx-slide-in-inline; }
 *
 * TO CHANGE THE RATE, override the step on the parent — a dense grid wants a
 * faster tick than four dashboard cards:
 *
 *     .fx-grid-rows.fx-stagger { --fx-stagger-step: 24ms; }
 *
 * FOR CONTENT INSERTED BY JAVASCRIPT after first paint, set the index directly
 * on the new element (style="--fx-stagger-index: 3") rather than re-running the
 * nth-child match, and add the fx-stagger class to the element itself.
 *
 * SPECIFICITY NOTE. These rules are 0,2,0, above this file's stated 0,1,0
 * budget. That is deliberate and is the one exception: a bare .fx-stagger > *
 * cannot carry an index, and a component that needs to defeat these overrides
 * animation-name and animation-delay, not the selector.
 * ────────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
    /* THE CHILD DURATION IS fx-dur-slow, WHICH IS NOW 180ms AND NO LONGER A
     * CONTRADICTION. This rule was the ONLY consumer of fx-dur-slow in the
     * whole platform while that token sat at 250ms — inside the 200 to 400ms
     * band 00-tokens section 7 bans — which is why two component layers wrote
     * headers explaining that they refuse to touch it. The token was retuned to
     * 180ms in the same pass; read the ruling over section 7 for why. Nothing
     * changed here except that the value this line resolves to is now on scale.
     *
     * DO NOT REACH FOR A LONGER DURATION TO MAKE AN ENTRANCE FEEL CONSIDERED.
     * The perceived length of a stagger is the child duration PLUS the
     * accumulated delay — twenty children at 180ms with a 40ms step already
     * runs for 940ms end to end. The dial is fx-stagger-step, overridden on the
     * PARENT as shown above.
     *
     * `backwards`, NOT `both` — see THE FILL MODE RULE in the section header.
     * The backward fill is the half that matters here: it is what holds a child
     * at opacity 0 and 8px low through the delay before its turn comes. The
     * forward half was pinning translateY(0) on every staggered card for the
     * life of the page, and an identity transform is still a transform, so each
     * card became a containing block for any fixed positioned thing inside it. */
    .fx-stagger > * {
        animation: fx-rise-in var(--fx-dur-slow) var(--fx-ease-decel) backwards;
        animation-delay: calc(var(--fx-stagger-index, 0) * var(--fx-stagger-step));
    }

    .fx-stagger > *:nth-child(1)  { --fx-stagger-index: 0; }
    .fx-stagger > *:nth-child(2)  { --fx-stagger-index: 1; }
    .fx-stagger > *:nth-child(3)  { --fx-stagger-index: 2; }
    .fx-stagger > *:nth-child(4)  { --fx-stagger-index: 3; }
    .fx-stagger > *:nth-child(5)  { --fx-stagger-index: 4; }
    .fx-stagger > *:nth-child(6)  { --fx-stagger-index: 5; }
    .fx-stagger > *:nth-child(7)  { --fx-stagger-index: 6; }
    .fx-stagger > *:nth-child(8)  { --fx-stagger-index: 7; }
    .fx-stagger > *:nth-child(9)  { --fx-stagger-index: 8; }
    .fx-stagger > *:nth-child(10) { --fx-stagger-index: 9; }
    .fx-stagger > *:nth-child(11) { --fx-stagger-index: 10; }
    .fx-stagger > *:nth-child(12) { --fx-stagger-index: 11; }
    .fx-stagger > *:nth-child(13) { --fx-stagger-index: 12; }
    .fx-stagger > *:nth-child(14) { --fx-stagger-index: 13; }
    .fx-stagger > *:nth-child(15) { --fx-stagger-index: 14; }
    .fx-stagger > *:nth-child(16) { --fx-stagger-index: 15; }
    .fx-stagger > *:nth-child(17) { --fx-stagger-index: 16; }
    .fx-stagger > *:nth-child(18) { --fx-stagger-index: 17; }
    .fx-stagger > *:nth-child(19) { --fx-stagger-index: 18; }
    .fx-stagger > *:nth-child(20) { --fx-stagger-index: 19; }

    /* Everything past the twentieth child lands together — the stagger has done
     * its job by then and a longer ramp only delays the data. */
    .fx-stagger > *:nth-child(n+21) { --fx-stagger-index: 20; }

    /* 14.4 PAGE ENTRANCE
     *
     * Razor Pages does a full document load on every navigation, so a plain CSS
     * animation on the content region fires exactly once per page with no
     * JavaScript, no flash of unstyled motion and no scroll restoration fight.
     * lpx-content is LeptonX's main region; fx-page-enter is the opt in for any
     * region outside the theme shell (the login card, a print preview frame).
     *
     * Deliberately fast — fx-dur-base, not slow. A page entrance that outlasts
     * the user's first click is a page that feels slow, not premium.
     *
     * `backwards`, AND ON THIS RULE IT IS THE MOST LOAD BEARING INSTANCE OF THE
     * FILL MODE RULE IN THE SYSTEM. lpx-content is the main region: everything
     * the user ever sees is inside it. A forward filled translateY(0) here made
     * it a containing block for every `position: fixed` descendant in the whole
     * application — the toast stack in 08, the fixed loading overlay, and any
     * Popper popup configured with the fixed strategy — each of which would
     * anchor to the content region rather than to the viewport and sit visibly
     * wrong, permanently, with nothing in the markup to explain why. */
    .lpx-content,
    .fx-page-enter {
        animation: fx-rise-in var(--fx-dur-base) var(--fx-ease-decel) backwards;
    }
}

/* 14.5 GLOBAL PRESS AFFORDANCE
 *
 * Every button in the document — including ones no component layer has ever
 * styled, and including LeptonX's and Bootstrap's — reacts to being pressed.
 * This is what makes an unstyled corner of the app still feel built.
 *
 * Ungated: the transition runs through fx-dur-instant, which section 15 damps
 * to 0.01ms, and section 15 also removes the :active transform outright so a
 * reduced motion user gets no scale jump at all.
 *
 * transform only — no box-shadow here — so this never fights a component that
 * owns its own pressed elevation through fx-shadow-press.
 *
 * BORDER-COLOUR IS IN THE LIST, ADDED 2026-08-03, AND IT IS A BEHAVIOUR FIX
 * RATHER THAN AN ADDITION. An outline button — LeptonX's, Bootstrap's, or any
 * corner of the app no component layer has reached — changes its FILL and its
 * EDGE on the same hover. The fill was easing over 160ms and the edge was
 * snapping on frame one, which is the failure mode where one state channel of a
 * component moves and its neighbour does not: nobody can name it and everybody
 * reads it as the control being half broken. It rides the same duration as the
 * fill so the two land together.
 *
 * OUTLINE IS DELIBERATELY NOT IN THE LIST AND MUST NOT BE ADDED. Focus is an
 * outline (section 11) and focus does not animate: an indicator that eases in
 * is an indicator that is not there yet for the first frames after the keyboard
 * moved, which is exactly when it is needed. */
button,
[role="button"],
a.btn,
[type="button"],
[type="reset"],
[type="submit"] {
    transition:
        transform var(--fx-dur-instant) var(--fx-ease-standard),
        box-shadow var(--fx-dur-base) var(--fx-ease-standard),
        color var(--fx-dur-base) var(--fx-ease-standard),
        border-color var(--fx-dur-base) var(--fx-ease-standard),
        background-color var(--fx-dur-base) var(--fx-ease-standard);
}

button:active,
[role="button"]:active,
a.btn:active,
[type="button"]:active,
[type="reset"]:active,
[type="submit"]:active {
    transform: scale(var(--fx-press-scale));
}

button:disabled:active,
[type="button"]:disabled:active,
[type="reset"]:disabled:active,
[type="submit"]:disabled:active,
[aria-disabled="true"]:active {
    transform: none;
}


/* ── 15) Reduced motion ────────────────────────────────────────────────────
 *
 * Two layers of damping.
 *
 *   1. The token layer. Every duration token collapses to a hair above zero.
 *      Because the design system animates exclusively through fx-dur-*, this
 *      alone neutralises all Frihat motion, and it does so WITHOUT an
 *      importance flag and without touching specificity.
 *
 *   2. A best effort universal damper for third party CSS (Bootstrap, LeptonX,
 *      Tabulator) that hard codes its own durations. This one is specificity
 *      0,0,0 and will lose to any component rule; it is a safety net, not a
 *      guarantee. The guarantee is layer 1, which is why hard coding a duration
 *      in a Frihat file is a defect.
 *
 *   3. An explicit kill on the entrance and stagger classes from section 14.
 *      A zero duration animation is NOT a no-op: animation-fill-mode both still
 *      applies the from-frame, so the element paints at opacity 0 and offset
 *      for a tick before snapping. animation: none removes the frame entirely.
 *      The press transform is removed for the same reason — damping its
 *      duration would leave an instantaneous scale jump, which is exactly the
 *      kind of movement the preference asks us not to make.
 *
 * Opacity is deliberately NOT suppressed. Durations go to zero, so a fade
 * becomes an instant swap rather than disappearing — state stays perceivable.
 * ────────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    /* html:root (0,1,1), not a bare html (0,0,1): this block has to outrank the
     * duration declarations in 00-tokens.css, which are themselves on html:root.
     * Equal specificity plus later source order carries it. At 0,0,1 it would
     * lose outright and the damper would silently do nothing. */
    html:root {
        --fx-dur-instant: 0.01ms;
        --fx-dur-fast:    0.01ms;
        /* Added with the token 2026-08-02 (second pass). EVERY duration on the
         * ladder must be listed here or layer 1 stops being a guarantee and
         * becomes a coincidence — a component animating through an undamped
         * token would keep moving for a user who asked it not to. If a duration
         * token is ever added to 00-tokens section 7, it is added here in the
         * same edit.
         *
         * fx-dur-enter and fx-dur-exit were MISSING from this list and are
         * added in the same pass. That was a real hole rather than a judgement
         * call: the header above states that layer 1 neutralises all Frihat
         * motion "because the design system animates exclusively through
         * fx-dur-*", and two of the eight fx-dur-* tokens were not damped, so
         * every popup entrance and exit in 05, 06 and 07 was falling through to
         * the 0,0,0 universal damper below — a safety net that loses to any
         * component rule, which is precisely what a popup rule is. They are
         * short (80ms and 60ms) so the visible change is small, but a
         * guarantee with two exceptions in it is not a guarantee. */
        --fx-dur-quick:   0.01ms;
        --fx-dur-base:    0.01ms;
        --fx-dur-slow:    0.01ms;
        --fx-dur-slower:  0.01ms;
        --fx-dur-enter:   0.01ms;
        --fx-dur-exit:    0.01ms;

        /* THE LAST HOLE IN THE LADDER, CLOSED 2026-08-03. fx-shine-dur is
         * declared in 00-tokens section 7 alongside the eight above and was the
         * one duration token in that section this block never damped. The
         * paragraph above states the rule without an exception in it: a duration
         * token in section 7 is damped here in the same edit, or layer 1 is a
         * coincidence rather than a guarantee.
         *
         * IT IS KEPT DELIBERATELY NOW THAT ITS KEYFRAME IS GONE, and that is the
         * part worth stating, because the obvious next edit is to delete this
         * line too. Later the same day fx-shine was removed from section 14 as a
         * zero consumer animation, so the token is now orphaned outright — but
         * IT IS STILL DECLARED, in a file this one does not own. The damper is
         * keyed to what 00-tokens DECLARES, not to what currently consumes it:
         * for as long as a duration token exists, any component in any layer can
         * reach for it, and a token that is reachable but undamped is exactly the
         * hole this block was written to close. The line goes when the token
         * goes, in 00-tokens, in one edit — not before, and not from here.
         *
         * It is a one shot sweep, not a loop, so collapsing it to zero is the
         * correct damping and not a strobe risk. */
        --fx-shine-dur:   0.01ms;

        scroll-behavior: auto;
    }

    /* animation-iteration-count: 1 IS THE LINE THAT STOPS A LOOP, and knowing
     * its exact reach matters more than the line itself.
     *
     * A looping indicator must STOP under this preference, not run at a 0.02ms
     * period — a spinner damped by duration alone does not become still, it
     * becomes a strobe, which is worse for a vestibular user than the motion it
     * replaced. This declaration is what prevents that, and it works for every
     * third party loop that sets its period through `animation-duration` or
     * leaves the count at the initial value.
     *
     * WHAT IT CANNOT REACH, STATED SO NOBODY ASSUMES OTHERWISE: this rule is
     * (0,0,0). A component that writes the `animation` SHORTHAND with the
     * `infinite` keyword sets duration and count together at its own
     * specificity and wins both, so the loop survives at a damped period. That
     * is a real strobe and the fix belongs in the file that owns the loop —
     * either stop it there, or restate a HUMAN period the way 08-feedback does
     * for its spinners. It cannot be fixed from here without an importance flag,
     * which this system does not permit. Every Frihat loop is accounted for:
     * 08 stops the skeleton sweep and the indeterminate progress sliver and
     * deliberately slows the three spinners, and 09 owns its own. */
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms;
        animation-delay: 0.01ms;
        animation-iteration-count: 1;
        transition-duration: 0.01ms;
        transition-delay: 0.01ms;
        scroll-behavior: auto;
    }

    /* Layer 3 — see the header. The entrance machinery is removed rather than
     * damped, and the press affordance stops moving. */
    .fx-stagger > *,
    .lpx-content,
    .fx-page-enter {
        animation: none;
    }

    button:active,
    [role="button"]:active,
    a.btn:active,
    [type="button"]:active,
    [type="reset"]:active,
    [type="submit"]:active {
        transform: none;
    }

    /* NOTHING IS DONE TO FOCUS HERE, AND THAT IS THE CORRECT OUTCOME. The focus
     * indicator is an outline; an outline does not move, does not scale and does
     * not fade, so there is no motion in it to damp. The rule that used to sit
     * here existed only to cancel the focus LIFT, and the lift is gone. This
     * note stays so nobody reintroduces a moving focus treatment and then has to
     * rediscover why it needed an exception. */

    /* The scrollbar thumb grows on hover through a border-width transition;
     * damping the duration is enough, but the thumb must still reach its
     * engaged size instantly rather than not at all. Covered by the universal
     * damper above — listed here only so the behaviour is on the record. */
}


/* ── 16) Forced colours (Windows High Contrast) ────────────────────────────
 * In forced colours mode the user agent replaces every colour, drops every
 * box-shadow and ignores background images. The only correct response is to
 * restate the focus indicator with system colour keywords so it survives, and
 * to make sure component boundaries still have a real border to be repainted.
 * System keywords, not tokens, are used here by design.
 * ────────────────────────────────────────────────────────────────────────── */
@media (forced-colors: active) {
    /* THIS BLOCK GOT SIMPLER, AND THAT IS THE STRONGEST ARGUMENT FOR THE NEW
     * FOCUS MODEL. Section 11.1 used to make the outline transparent on every
     * interactive element and let a box-shadow bloom carry focus, which meant
     * this block had to restate the outline at 0,1,1 and 0,2,1 just to undo the
     * suppression — an accessibility indicator that had to be rescued in the
     * one mode where accessibility is the entire point. Nothing suppresses the
     * outline any more, so the single bare rule below is sufficient and the
     * per-element rescue is deleted. Only the thickness is promoted here.
     *
     * The borderless direction is still deliberately suspended in this mode: a
     * user in forced colours has asked the operating system for strokes, and
     * shadows are dropped entirely, so component boundaries need a real border
     * to be repainted with a system colour. */
    :focus-visible {
        outline: 3px solid Highlight;
        outline-offset: 2px;
    }

    a {
        color: LinkText;
    }

    .fx-skip-link {
        color: LinkText;
        background-color: Canvas;
        border: 2px solid CanvasText;
    }

    button,
    input,
    select,
    textarea {
        border: 1px solid ButtonText;
    }

    /* The bare table separates by row height and hover fill, both of which are
     * unavailable here — forced colours flattens the hover wash to the system
     * palette. A header rule is the only separation left that survives. */
    thead th {
        border-block-end: 1px solid CanvasText;
    }
}


/* ── 17) Print ─────────────────────────────────────────────────────────────
 * A printed case file, hearing schedule or invoice must be black on white
 * regardless of the theme the user happened to have on screen. The two brand
 * primitives fx-p-white and fx-p-black are read directly here — the one place
 * in the system where a primitive is consumed outside the token layer, because
 * "always white" and "always black" are by definition not theme semantics.
 *
 * Application chrome is suppressed by structure, not by class list: anything
 * inside a nav, an aside, a footer or a dialog is chrome. Component layers may
 * add their own print rules, but must not undo these.
 * ────────────────────────────────────────────────────────────────────────── */
@media print {
    html,
    body {
        background-color: var(--fx-p-white);
        color: var(--fx-p-black);
        overflow: visible;
    }

    /* Paper does not animate. An entrance whose fill-mode start frame is
     * opacity 0 must never be what the printer captures, so the section 14
     * machinery is removed here exactly as it is under reduced motion. */
    .fx-stagger > *,
    .lpx-content,
    .fx-page-enter,
    *,
    *::before,
    *::after {
        animation: none;
        transition: none;
    }

    nav,
    aside,
    footer,
    dialog,
    [role="navigation"],
    [role="toolbar"],
    [role="dialog"],
    .fx-skip-link {
        display: none;
    }

    /* THE STATE SELECTORS ARE NAMED, AND THEY HAVE TO BE. A bare `a` is (0,0,1)
     * and section 7 sets the link ink on `a:hover` and `a:active` at (0,1,1), so
     * until 2026-08-03 this rule could not reach either: a link the pointer
     * happened to be resting on printed in fx-color-link-hover — a mid green
     * that a monochrome laser renders as grey — instead of black. That is not
     * hypothetical at print time. The pointer sits wherever the user left it when
     * the print stylesheet is applied, engines differ on whether :hover still
     * matches during printing, and `a:hover` lives inside `any-hover: hover`,
     * which stays true on a desktop while printing. :active is named for
     * symmetry and costs nothing. Same reasoning, and the same fix, as the state
     * selectors on the print rule in 11-home section 7. */
    a,
    a:hover,
    a:active {
        color: var(--fx-p-black);
        text-decoration: underline;
    }

    /* The four text tiers all collapse to black on paper. The tertiary and
     * quaternary tiers are light greys chosen against a screen surface; printed,
     * they come out as a faint smudge or drop out of a monochrome laser
     * entirely. Table headers, captions, small print and disabled labels are
     * exactly the content a printed case file must retain. */
    h1, h2, h3, h4, h5, h6,
    th, td, caption, small, dt, dd, figcaption, legend,
    :disabled,
    [disabled],
    [aria-disabled="true"] {
        color: var(--fx-p-black);
    }

    /* On screen the table separates by row height and by a hover fill. Neither
     * exists on paper, so the header rule comes back — the one place in the
     * system where a printed stroke is the right answer. */
    thead th {
        border-block-end: 1px solid var(--fx-p-black);
    }

    /* Shadows do not print, and the elevation ladder is invisible on white
     * paper, so the two surfaces that relied on a fill lose their identity.
     * A printed code block or pull quote keeps a rule instead. */
    pre,
    blockquote {
        background: none;
        box-shadow: none;
        border-inline-start: 2px solid var(--fx-p-black);
    }

    /* Print the destination of an external link; skip in-page and script links,
     * whose href carries no information on paper. */
    a[href^="http"]::after {
        content: " (" attr(href) ")";
        font-size: var(--fx-text-xs);
        word-break: break-all;
    }

    h1, h2, h3, h4, h5, h6 {
        break-after: avoid;
        page-break-after: avoid;
    }

    p,
    blockquote,
    li,
    tr,
    figure,
    pre,
    table {
        break-inside: avoid;
        page-break-inside: avoid;
    }

    thead {
        display: table-header-group;
    }

    img,
    svg {
        max-inline-size: 100%;
        break-inside: avoid;
    }
}
