/*! SLASHED v0.7.31 — slashed.optimal.flat.css (flat) | MIT License | github.com/codeslash-dev/SLASHED */

/* ─── core/layers.css ─── */
/* SLASHED — core/layers.css
   Global cascade order for every SLASHED layer.
   Import before all framework modules. */

/* ─── core/tokens.css ─── */
/* SLASHED — core/tokens.css
   @layer slashed.tokens
   Core design tokens: color sources, semantic colors, type, spacing, radii, shadows, motion, z-index.
   Prefix: --sf-*; brand theming starts with six -source-light tokens. */


/* ============================================================
   @property — BRAND COLORS (-source-light source tokens, animatable)
   Override these 6 tokens to rebrand — dark mode auto-derives.
   Optionally set --sf-color-X-source-dark for full control.
   ============================================================ */

@property --sf-color-primary-source-light   { syntax: "<color>"; inherits: true; initial-value: oklch(0.47 0.27 264); }
@property --sf-color-secondary-source-light { syntax: "<color>"; inherits: true; initial-value: oklch(0.22 0.04 264); }
@property --sf-color-tertiary-source-light  { syntax: "<color>"; inherits: true; initial-value: oklch(0.42 0.22 295); }
@property --sf-color-action-source-light    { syntax: "<color>"; inherits: true; initial-value: oklch(0.50 0.22 235); }
@property --sf-color-neutral-source-light   { syntax: "<color>"; inherits: true; initial-value: oklch(0.52 0.025 260); }
/* initial-value is 0.96 (not 0.99) so the +0.02/+0.04 surface offsets produce
   visually distinct levels in light mode. At 0.99 all lighter surfaces clamp
   to pure white and become indistinguishable. 0.96 gives: bg=0.98, inset=0.94,
   raised=1.0→white — all distinct. Consumers using near-1.0 values should rely
   on shadows rather than lightness for elevation. */
@property --sf-color-base-source-light  { syntax: "<color>"; inherits: true; initial-value: oklch(0.96 0.006 250); }

/* @property — STATUS COLORS (-source-light source tokens) */

@property --sf-color-success-source-light { syntax: "<color>"; inherits: true; initial-value: oklch(0.50 0.16 145); }
@property --sf-color-warning-source-light { syntax: "<color>"; inherits: true; initial-value: oklch(0.75 0.17 80);  }
@property --sf-color-info-source-light    { syntax: "<color>"; inherits: true; initial-value: oklch(0.48 0.18 235); }
@property --sf-color-danger-source-light  { syntax: "<color>"; inherits: true; initial-value: oklch(0.48 0.22 12);  }

/* Plain :root mirrors — character-for-character identical to each @property
   initial-value above. Engines lacking @property support read the token value
   from here. Do NOT edit these independently from the initial-value strings. */
:root {
  --sf-color-primary-source-light:   oklch(0.47 0.27 264);
  --sf-color-secondary-source-light: oklch(0.22 0.04 264);
  --sf-color-tertiary-source-light:  oklch(0.42 0.22 295);
  --sf-color-action-source-light:    oklch(0.50 0.22 235);
  --sf-color-neutral-source-light:   oklch(0.52 0.025 260);
  --sf-color-base-source-light:      oklch(0.96 0.006 250);
  --sf-color-success-source-light:   oklch(0.50 0.16 145);
  --sf-color-warning-source-light:   oklch(0.75 0.17 80);
  --sf-color-info-source-light:      oklch(0.48 0.18 235);
  --sf-color-danger-source-light:    oklch(0.48 0.22 12);
}

/* @property — DARK SOURCE TOKENS (-dark, animatable) */
/* Registers the auto-computed dark counterparts as typed <color> so their
   resolved absolute value is inherited by child elements (e.g. a section
   with [data-theme="dark"]). Without @property these tokens inherit as raw
   text, and the nested var()-in-oklch-from chain fails type-checking on
   registered child properties.
   initial-value = dark-mode value derived from the formula in themes.css
   (clamp(0.65, 0.95 - l*0.5, 0.88) for brand/status; inverted for base).
   Consumers who set --sf-color-X-dark explicitly do not need to register it
   — their override is a plain OKLCH literal and passes type-checking directly. */
@property --sf-color-primary-source-dark   { syntax: "<color>"; inherits: true; initial-value: oklch(0.715 0.243 264);  }
@property --sf-color-secondary-source-dark { syntax: "<color>"; inherits: true; initial-value: oklch(0.84 0.036 264);   }
@property --sf-color-tertiary-source-dark  { syntax: "<color>"; inherits: true; initial-value: oklch(0.74 0.198 295);   }
@property --sf-color-action-source-dark    { syntax: "<color>"; inherits: true; initial-value: oklch(0.70 0.198 235);   }
@property --sf-color-neutral-source-dark   { syntax: "<color>"; inherits: true; initial-value: oklch(0.69 0.0225 260);  }
@property --sf-color-base-source-dark      { syntax: "<color>"; inherits: true; initial-value: oklch(0.22 0.003 250);   }
@property --sf-color-success-source-dark   { syntax: "<color>"; inherits: true; initial-value: oklch(0.70 0.144 145);   }
@property --sf-color-warning-source-dark   { syntax: "<color>"; inherits: true; initial-value: oklch(0.65 0.153 80);    }
@property --sf-color-info-source-dark      { syntax: "<color>"; inherits: true; initial-value: oklch(0.71 0.162 235);   }
@property --sf-color-danger-source-dark    { syntax: "<color>"; inherits: true; initial-value: oklch(0.71 0.198 12);    }

/* @property — COMPUTED COLOR TOKENS (brand + status)
   Registers the computed tokens as typed <color> so CSS transitions can
   interpolate between light and dark values when [data-theme] changes.
   initial-value = light-mode value (safe fallback if nothing sets the token).
   Child-element theming works because [data-theme="dark"] in themes.css
   assigns var(--sf-color-X-dark) — now a registered typed <color> whose
   absolute value is inherited, so no multi-level var() resolution is needed. */
@property --sf-color-primary   { syntax: "<color>"; inherits: true; initial-value: oklch(0.47 0.27 264); }
@property --sf-color-secondary { syntax: "<color>"; inherits: true; initial-value: oklch(0.22 0.04 264); }
@property --sf-color-tertiary  { syntax: "<color>"; inherits: true; initial-value: oklch(0.42 0.22 295); }
@property --sf-color-action    { syntax: "<color>"; inherits: true; initial-value: oklch(0.50 0.22 235); }
@property --sf-color-neutral   { syntax: "<color>"; inherits: true; initial-value: oklch(0.52 0.025 260); }
@property --sf-color-base      { syntax: "<color>"; inherits: true; initial-value: oklch(0.96 0.006 250); }
@property --sf-color-success   { syntax: "<color>"; inherits: true; initial-value: oklch(0.50 0.16 145); }
@property --sf-color-warning   { syntax: "<color>"; inherits: true; initial-value: oklch(0.75 0.17 80);  }
@property --sf-color-info      { syntax: "<color>"; inherits: true; initial-value: oklch(0.48 0.18 235); }
@property --sf-color-danger    { syntax: "<color>"; inherits: true; initial-value: oklch(0.48 0.22 12);  }

/* Plain :root mirrors for computed tokens — resolve to the -source-light source token
   so user overrides to --sf-color-X-source-light are respected in all browsers.
   Overridden by the @supports block in themes.css whenever dark mode is active. */
:root {
  --sf-color-primary:   var(--sf-color-primary-source-light);
  --sf-color-secondary: var(--sf-color-secondary-source-light);
  --sf-color-tertiary:  var(--sf-color-tertiary-source-light);
  --sf-color-action:    var(--sf-color-action-source-light);
  --sf-color-neutral:   var(--sf-color-neutral-source-light);
  --sf-color-base:      var(--sf-color-base-source-light);
  --sf-color-success:   var(--sf-color-success-source-light);
  --sf-color-warning:   var(--sf-color-warning-source-light);
  --sf-color-info:      var(--sf-color-info-source-light);
  --sf-color-danger:    var(--sf-color-danger-source-light);
}

/* Mode flag — INTERNAL, not a public hook (SL-003: same "--sf-is-*" naming
   pattern as the public flags below, opposite contract — read here, don't
   set). Drives formula direction for non-color dark overrides. Set only by
   themes.css via [data-theme="dark"] and the prefers-color-scheme media
   query; every other read site (core/tokens.css, the configurator's power
   knobs) only reads it inside calc() expressions. Setting it directly
   desyncs it from color-scheme/[data-theme] and produces an inconsistent
   theme. */
@property --sf-is-dark { syntax: "<integer>"; inherits: true; initial-value: 0; }

/* No --sf-is-active/-current/-pressed/-open here: they registered an
   INTERACTION STATE FLAGS mechanism (inheritable custom properties set by
   .sf-is-active/.sf-is-open/etc for a consumer's own calc() to branch on)
   with zero consumers anywhere in this codebase, its demo, or the
   configurator beyond its own preview toggle — speculative surface,
   removed. Style off [aria-current]/[aria-expanded]/[aria-pressed]
   instead; see docs/states.md § "Prefer native state". */

/* ============================================================
   FLUID SCALE ENGINE — generative inputs
   ------------------------------------------------------------
   The fluid type / space / display scales are computed at runtime
   from these scalars. Override any of them on :root and every
   clamp() recalibrates — no build step, no token regeneration.

     --sf-fluid-min-vw / --sf-fluid-max-vw   viewport range (rem, unitless)
                                             over which values interpolate.
     --sf-text-ratio-min / -max              modular ratio of the TYPE scale
                                             at min / max viewport (dual-ratio:
                                             subtler on mobile, larger on desktop).
     --sf-text-base-min / -max               --sf-text-m endpoints (rem, unitless).
     --sf-text-display-base-min / -max       --sf-text-display-s endpoints.
     --sf-space-ratio-min / -max             modular ratio of the SPACE scale.
     --sf-space-base-min / -max              --sf-space-m endpoints (rem, unitless).
     --sf-fluid-width                        the length every fluid clamp()
                                             interpolates against. Defaults to
                                             100vw (viewport-relative). Override
                                             to 100cqi on a container scope to make
                                             the type + space + display scales
                                             container-relative — see .sf-fluid-cq
                                             in core/layout.css (issue #497).

   Registered as <number> so they can be used as pow() exponents/operands
   and multiplied by 1rem in calc(). Requires CSS pow() (Chrome 125+,
   Safari 15.4+, Firefox 118+); the effective SLASHED floor is higher
   (Chrome 125 / Safari 18.0 / Firefox 129), set by the colour system —
   see browser floor. Plain :root mirrors
   below keep values readable; do NOT edit them independently. */
@property --sf-fluid-min-vw          { syntax: "<number>"; inherits: true; initial-value: 22.5; }
@property --sf-fluid-max-vw          { syntax: "<number>"; inherits: true; initial-value: 90; }
@property --sf-text-ratio-min        { syntax: "<number>"; inherits: true; initial-value: 1.25; }
@property --sf-text-ratio-max        { syntax: "<number>"; inherits: true; initial-value: 1.333; }
@property --sf-text-base-min         { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --sf-text-base-max         { syntax: "<number>"; inherits: true; initial-value: 1.25; }
@property --sf-text-display-base-min { syntax: "<number>"; inherits: true; initial-value: 2.4; }
@property --sf-text-display-base-max { syntax: "<number>"; inherits: true; initial-value: 3; }
@property --sf-space-ratio-min       { syntax: "<number>"; inherits: true; initial-value: 1.25; }
@property --sf-space-ratio-max       { syntax: "<number>"; inherits: true; initial-value: 1.333; }
@property --sf-space-base-min        { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --sf-space-base-max        { syntax: "<number>"; inherits: true; initial-value: 2; }

/* Fluid interpolation driver — the length term every fluid clamp() below
   measures against. Universal syntax ("*") not "<length>": a registered
   <length> requires a computationally-independent initial-value, and 100vw
   (viewport-dependent) is not one. The real default lives on :root; an
   opt-in scope (e.g. .sf-fluid-cq) overrides it to 100cqi so the whole
   generative scale becomes container-relative without duplicating a single
   formula. */
@property --sf-fluid-width           { syntax: "*"; inherits: true; }

/* ── Output token @property registrations ──────────────────────────────────
   Radius + space: <length> syntax enables CSS transitions on layout values —
   border-radius/padding/gap etc. now interpolate instead of snapping.
   Shadow: syntax:"*" (no <shadow> type in spec); inherits:true so :root values
   cascade normally; registers the properties formally for DevTools inspection.

   initial-value ≠ :root default (by spec): a registered <length> requires a
   computationally-independent initial-value, so these carry `initial-value: 0`
   while their real defaults are the calc()-derived values set on :root below
   (see --sf-fluid-width above for the same constraint stated at length). One
   consequence worth flagging: once a property is registered, the initial
   value IS a value — so a fallback like `var(--sf-radius-m, 12px)` will NEVER
   fire; an unset --sf-radius-m resolves to the registered `0`, not to "no
   value". Author fallbacks against the token's :root default, not this 0. */

@property --sf-radius-none  { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-2xs   { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-xs    { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-s     { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-m     { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-l     { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-xl    { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-2xl   { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-3xl   { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-4xl   { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-radius-full  { syntax: "<length>"; inherits: true; initial-value: 9999px; }
@property --sf-radius-pill  { syntax: "<length>"; inherits: true; initial-value: 9999px; }
@property --sf-radius-outer { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-media-radius { syntax: "<length>"; inherits: true; initial-value: 0; }

@property --sf-space-none       { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-px         { syntax: "<length>"; inherits: true; initial-value: 1px; }
@property --sf-space-2xs        { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-xs         { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-s          { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-m          { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-l          { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-xl         { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-2xl        { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-3xl        { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-space-4xl        { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-gap              { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-content-gap      { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-gutter           { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-component-pad    { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-field-block      { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-section-pad--xs  { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-section-pad--s   { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-section-pad--m   { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-section-pad--l   { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-section-pad--xl  { syntax: "<length>"; inherits: true; initial-value: 0; }
@property --sf-section-pad--2xl { syntax: "<length>"; inherits: true; initial-value: 0; }

@property --sf-shadow-none      { syntax: "*"; inherits: true; }
@property --sf-shadow-xs        { syntax: "*"; inherits: true; }
@property --sf-shadow-s         { syntax: "*"; inherits: true; }
@property --sf-shadow-m         { syntax: "*"; inherits: true; }
@property --sf-shadow-l         { syntax: "*"; inherits: true; }
@property --sf-shadow-xl        { syntax: "*"; inherits: true; }
@property --sf-shadow-2xl       { syntax: "*"; inherits: true; }
@property --sf-shadow-inner     { syntax: "*"; inherits: true; }
@property --sf-text-shadow-none { syntax: "*"; inherits: true; }
@property --sf-text-shadow-xs   { syntax: "*"; inherits: true; }
@property --sf-text-shadow-s    { syntax: "*"; inherits: true; }
@property --sf-text-shadow-m    { syntax: "*"; inherits: true; }
@property --sf-text-shadow-l    { syntax: "*"; inherits: true; }
@property --sf-text-shadow-xl   { syntax: "*"; inherits: true; }
@property --sf-drop-shadow-xs   { syntax: "*"; inherits: true; }
@property --sf-drop-shadow-s    { syntax: "*"; inherits: true; }
@property --sf-drop-shadow-m    { syntax: "*"; inherits: true; }
@property --sf-drop-shadow-l    { syntax: "*"; inherits: true; }
@property --sf-drop-shadow-xl   { syntax: "*"; inherits: true; }
@property --sf-shadow-glow      { syntax: "*"; inherits: true; }

:root {
  --sf-fluid-min-vw:          22.5;
  --sf-fluid-max-vw:          90;
  --sf-fluid-width:           100vw;  /* interpolation driver; 100cqi on a container scope → container-relative fluid (#497) */
  --sf-text-ratio-min:        1.25;
  --sf-text-ratio-max:        1.333;
  --sf-text-base-min:         1;
  --sf-text-base-max:         1.25;
  --sf-text-display-base-min: 2.4;
  --sf-text-display-base-max: 3;
  --sf-space-ratio-min:       1.25;
  --sf-space-ratio-max:       1.333;
  --sf-space-base-min:        1;
  --sf-space-base-max:        2;
}

/* ============================================================
   DERIVED SEMANTIC TOKENS
   ============================================================ */

:root {

  /* ----------------------------------------------------------
     Color scheme
     ---------------------------------------------------------- */
  --sf-color-scheme: light dark;

  /* ----------------------------------------------------------
     LumLocker — OKLCH L value used by the
     :root[data-lumlocker] override in core/themes.css.
     Locks the 4 brand colors (primary, secondary, tertiary, action)
     to one shared lightness while keeping their individual hue and
     chroma. Neutral and base are excluded — neutral is a desaturated
     grey (locking it to a brand L would make it chromatic) and base
     must remain near-white in light mode for the page background.

     --sf-lumlocker is the LIGHT-mode target. Dark mode uses
     --sf-lumlocker-dark, which defaults to the mirror of the light L
     around 0.59 — the midpoint between the light (~0.96) and dark
     (~0.22) page backgrounds — i.e. calc(1.18 - L). Mirroring around
     the background midpoint (not 0.5) is what keeps the lock's
     contrast against the surface equal in both themes; a naive 1 - L
     would leave the dark lock too dark to read. Clamped to a visible
     band. A single knob that works in both themes. Override either:
       :root { --sf-lumlocker: 0.55 }          (light)
       :root { --sf-lumlocker-dark: 0.82 }     (dark, breaks the mirror)
     ---------------------------------------------------------- */
  --sf-lumlocker: 0.65;
  --sf-lumlocker-dark: clamp(0.5, calc(1.18 - var(--sf-lumlocker)), 0.92);

  /* ----------------------------------------------------------
     Resolved color tokens — auto-switch via light-dark().
     Components reference these. Override the -source-light / -source-dark
     source tokens above to customise; or override these directly
     with your own light-dark() expression.
     ---------------------------------------------------------- */
  /* Surface alias (plain var — no IACVT, stays ungated) */
  --sf-color-surface: var(--sf-color-base);

  /* ----------------------------------------------------------
     Text — plain var aliases (ungated)
     ---------------------------------------------------------- */

  /* ----------------------------------------------------------
     Text-on-color — plain var aliases (ungated)
     ---------------------------------------------------------- */
  --sf-color-text--on-base:    var(--sf-color-text);

  /* Static fallbacks for engines that support oklch(from …) but not sign()
     (Chrome 125–137, Firefox 128–130). The sign() gate below overrides these
     with the exact auto-contrast formula when the engine supports it.
     Derived from the default palette at threshold 0.6: surfaces with L < 0.6
     get near-white text (0.95); warning (L 0.75) and code (on light inset)
     get near-black (0.1). Custom palettes must override --sf-color-text--on-*
     manually if their surface colours differ from the defaults. */
  --sf-color-text--on-primary:   oklch(0.95 0 0);
  --sf-color-text--on-secondary: oklch(0.95 0 0);
  --sf-color-text--on-tertiary:  oklch(0.95 0 0);
  --sf-color-text--on-action:    oklch(0.95 0 0);
  --sf-color-text--on-neutral:   oklch(0.95 0 0);
  --sf-color-text--on-success:   oklch(0.95 0 0);
  --sf-color-text--on-warning:   oklch(0.1  0 0);
  --sf-color-text--on-info:      oklch(0.95 0 0);
  --sf-color-text--on-danger:    oklch(0.95 0 0);
  --sf-color-text--on-inverse:   oklch(0.95 0 0);
  --sf-color-code-text:          oklch(0.1  0 0);

  /* ----------------------------------------------------------
     Borders — plain var aliases (ungated)
     ---------------------------------------------------------- */
  --sf-color-border--focus: var(--sf-color-action);

  /* ----------------------------------------------------------
     Links — plain var aliases and geometry (ungated)
     ---------------------------------------------------------- */
  --sf-color-link--disabled:  var(--sf-color-text--disabled);
  /* Underline geometry — consumed by a:link in core/base.css and by
     the .sf-link--* macros. `auto` defers to the font's own metrics;
     set an explicit length (e.g. 0.08em) for a uniform rule. */
  --sf-link-underline-offset:    0.15em;
  --sf-link-underline-thickness: auto;

  /* ----------------------------------------------------------
     Interactive states — plain var aliases (ungated)
     ---------------------------------------------------------- */
  --sf-color-bg--disabled: var(--sf-color-inset);
  --sf-color-code-bg:      var(--sf-color-inset);

  /* ----------------------------------------------------------
     Selection & backdrop — static values (ungated)
     ---------------------------------------------------------- */
  --sf-color-selection-text: inherit;
  --sf-color-mark-text:      inherit;
  /* Dim: classic semi-transparent black for modal backdrops, distinct from --sf-color-overlay */
  --sf-color-dim:            oklch(0 0 0 / 0.5);
  --sf-color-white:          oklch(100% 0 0);
  --sf-color-black:          oklch(0% 0 0);

  /* ----------------------------------------------------------
     Scrollbar
     ---------------------------------------------------------- */
  --sf-scrollbar-thumb: var(--sf-color-neutral);
  --sf-scrollbar-track: transparent;

  /* ----------------------------------------------------------
     Shadow strength — base opacity for light mode; auto-boosted by
     +0.17 in dark mode via --sf-is-dark. Override with a calc() to
     preserve the adaptation: calc(0.1 + var(--sf-is-dark) * 0.17).
     ---------------------------------------------------------- */
  --sf-shadow-strength:   calc(0.08 + var(--sf-is-dark) * 0.17);
  /* Shadow lightness: 0.15 is the default density. Independent token allows
     tuning shadow depth globally across light/dark and surfaces. */
  --sf-shadow-lightness:  0.15;
  /* ----------------------------------------------------------
     Shadow glow color — tint used by --sf-shadow-glow. Override to
     retint glows; opacity/dark-mode adaptation is controlled by
     --sf-shadow-strength.
     ---------------------------------------------------------- */
  --sf-shadow-glow-color: var(--sf-color-primary);

  /* ----------------------------------------------------------
     Opacity — disabled state effects
     ---------------------------------------------------------- */
  --sf-opacity-disabled:  0.45;

  /* ----------------------------------------------------------
     Focus / form colors
     ---------------------------------------------------------- */
  --sf-focus-ring-color:  var(--sf-color-action);
  --sf-focus-ring-shadow: 0 0 0 var(--sf-focus-ring-offset) var(--sf-color-bg, var(--sf-color-base-source-light, #fff)),
                          0 0 0 calc(var(--sf-focus-ring-offset) + var(--sf-focus-ring-width)) var(--sf-focus-ring-color);
  --sf-caret-color:       var(--sf-color-action);

}
/* ── @supports gate: light-dark() + oklch(from …) ──
   Safari 15.4 supports light-dark() but only adds oklch(from) in 16.4.
   Gating on both prevents Safari 15.x/16.x from parsing the derived
   color tokens and silently failing all base colors. */
@supports (color: light-dark(white, black)) and (color: oklch(from red l c h)) {
  :root {

    /* ----------------------------------------------------------
       Resolved color tokens — auto-switch via light-dark().
       Dark auto-derivation formula (brand + status):
       clamp(0.65, 0.95 - l*0.5, 0.88) lightens dark-mode value relative to the light source.
       Surface inverts: clamp(0.16, 1.18 - l, 0.24) — near-white flips to near-dark.
       Override any --sf-color-X-dark to take full per-mode control.
       SL-001: this same clamp() formula is re-declared flat in themes.css
       (SECTION-LEVEL THEMING) so [data-theme] overrides on non-:root elements
       still resolve correctly — light-dark() only bakes at :root. Keep both
       copies in sync if the formula ever changes. */
    --sf-color-primary:   light-dark(var(--sf-color-primary-source-light),   var(--sf-color-primary-source-dark,   oklch(from var(--sf-color-primary-source-light)   clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));
    --sf-color-secondary: light-dark(var(--sf-color-secondary-source-light), var(--sf-color-secondary-source-dark, oklch(from var(--sf-color-secondary-source-light) clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));
    --sf-color-tertiary:  light-dark(var(--sf-color-tertiary-source-light),  var(--sf-color-tertiary-source-dark,  oklch(from var(--sf-color-tertiary-source-light)  clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));
    --sf-color-action:    light-dark(var(--sf-color-action-source-light),    var(--sf-color-action-source-dark,    oklch(from var(--sf-color-action-source-light)    clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));
    --sf-color-neutral:   light-dark(var(--sf-color-neutral-source-light),   var(--sf-color-neutral-source-dark,   oklch(from var(--sf-color-neutral-source-light)   clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));
    --sf-color-base:      light-dark(var(--sf-color-base-source-light),      var(--sf-color-base-source-dark,      oklch(from var(--sf-color-base-source-light)      clamp(0.16, calc(1.18 - l),        0.24) calc(c * 0.5) h)));

    --sf-color-success: light-dark(var(--sf-color-success-source-light), var(--sf-color-success-source-dark, oklch(from var(--sf-color-success-source-light) clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));
    --sf-color-warning: light-dark(var(--sf-color-warning-source-light), var(--sf-color-warning-source-dark, oklch(from var(--sf-color-warning-source-light) clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));

    --sf-color-info:    light-dark(var(--sf-color-info-source-light),    var(--sf-color-info-source-dark,    oklch(from var(--sf-color-info-source-light)    clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));
    --sf-color-danger:  light-dark(var(--sf-color-danger-source-light),  var(--sf-color-danger-source-dark,  oklch(from var(--sf-color-danger-source-light)  clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h)));

    /* ----------------------------------------------------------
       Text — light and dark require opposite formula directions,
       so each side of light-dark() references its source token.
       ---------------------------------------------------------- */
    --sf-color-text: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.05, calc(l - 0.4 - var(--sf-contrast-bias)), 0.35) c h),
      oklch(from var(--sf-color-neutral)       clamp(0.70, calc(l + 0.25 + var(--sf-contrast-bias)), 1)    c h)
    );
    --sf-color-text--muted: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.35, calc(l - var(--sf-contrast-bias)), 0.62) c h),
      oklch(from var(--sf-color-neutral)       clamp(0.48, calc(l + var(--sf-contrast-bias)), 0.74) c h)
    );
    --sf-color-text--subtle: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.15, calc(l - 0.25 - var(--sf-contrast-bias)), 0.45) c h),
      oklch(from var(--sf-color-neutral)       clamp(0.55, calc(l + 0.1 + var(--sf-contrast-bias)), 0.90) c h)
    );
    --sf-color-text--placeholder: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.45, calc(l + 0.15), 0.75) c h),
      oklch(from var(--sf-color-neutral)       clamp(0.35, calc(l - 0.1),  0.65) c h)
    );
    --sf-color-text--disabled: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.55, calc(l + 0.25), 0.82) c h),
      oklch(from var(--sf-color-neutral)       clamp(0.25, calc(l - 0.2),  0.55) c h)
    );
    --sf-color-text--inverse: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.85, calc(l + 0.4), 0.98) c h),
      oklch(from var(--sf-color-neutral)       clamp(0.05, calc(l - 0.4), 0.35) c h)
    );
    --sf-color-heading: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.05, calc(l - 0.4 - var(--sf-contrast-bias)), 0.35) c h),
      oklch(from var(--sf-color-neutral)       clamp(0.70, calc(l + 0.25 + var(--sf-contrast-bias)), 1)    c h)
    );

    /* ----------------------------------------------------------
       Borders — direction-dependent, reference source tokens.
       Light: borders are lighter than neutral (l + offset).
       Dark:  borders are darker  than neutral (l - offset).
       ---------------------------------------------------------- */
    --sf-color-border: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.70, calc(l + 0.35), 0.95) 0.005 h),
      oklch(from var(--sf-color-neutral)       clamp(0.25, calc(l - 0.3),  0.55) 0.005 h)
    );
    --sf-color-border--subtle: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.75, calc(l + 0.4),  0.97) 0.005 h),
      oklch(from var(--sf-color-neutral)       clamp(0.20, calc(l - 0.38), 0.45) 0.005 h)
    );
    --sf-color-border--strong: light-dark(
      oklch(from var(--sf-color-neutral-source-light) clamp(0.55, calc(l + 0.1),  0.85) 0.02  h),
      oklch(from var(--sf-color-neutral)       clamp(0.38, calc(l - 0.1),  0.65) 0.02  h)
    );

    /* ----------------------------------------------------------
       Links — derived from --sf-color-action (auto-adapts)
       ---------------------------------------------------------- */
    /* Links keep the action hue but CLAMP lightness toward a contrast-safe
       band — a ceiling in light mode, a floor in dark mode — so link text
       clears WCAG AA (4.5:1) on the page background for the default palette
       AND the large majority of brand overrides (a fixed offset alone only
       fixed the default). Lightness dominates OKLCH contrast but doesn't
       fully determine it: very high-chroma hues (saturated yellow/green) can
       still fall short even at a safe lightness — those remain the consumer's
       responsibility (see docs/theming.md → "Link contrast").

       NOTE: the consumer-facing :link rule in core/base.css uses
       `a:link` (not bare `a`) so its specificity matches WebKit's UA
       stylesheet `a:link { color: -webkit-link }`. Without that
       pseudo-class the framework's `--sf-color-link` value is
       silently ignored on unvisited anchors in WebKit and
       `-webkit-link` (≈ #00728f in dark mode) paints instead —
       regardless of how this token is computed. PR #73 and several
       follow-up attempts mistakenly chased the formula. */
    --sf-color-link:            light-dark(
      oklch(from var(--sf-color-action) clamp(0, min(l - 0.07, 0.48), 1) c h),
      oklch(from var(--sf-color-action) clamp(0.68, l, 1) c h)
    );
    --sf-color-link--hover:     light-dark(
      oklch(from var(--sf-color-action) clamp(0, min(l - 0.15, 0.40), 1) c h),
      oklch(from var(--sf-color-action) clamp(0, max(l + 0.10, 0.68), 1) c h)
    );
    --sf-color-link--active:    light-dark(
      oklch(from var(--sf-color-action) clamp(0, min(l - 0.21, 0.34), 1) c h),
      oklch(from var(--sf-color-action) clamp(0, max(l + 0.15, 0.74), 1) c h)
    );
    /* Visited link — 60° hue shift from action gives clearly distinct
       chroma without colliding with default tertiary (h ≈ 310). Same
       lightness clamp as the base link. If your brand puts action and
       tertiary close in hue, override directly:
         :root { --sf-color-link--visited: #6b46c1; } */
    --sf-color-link--visited:   light-dark(
      oklch(from var(--sf-color-action) clamp(0, min(l - 0.07, 0.48), 1) c calc(h + 60)),
      oklch(from var(--sf-color-action) clamp(0.68, l, 1) c calc(h + 60))
    );

    /* ----------------------------------------------------------
       Selection
       ---------------------------------------------------------- */
    --sf-color-selection-bg:   light-dark(
      oklch(from var(--sf-color-action-source-light) l c h / 0.28),
      oklch(from var(--sf-color-action-source-dark) clamp(0.62, calc(0.93 - l * 0.4), 0.78) c h / 0.55)
    );

    /* Alt selection — the opposite scheme's treatment, for a surface
       that deliberately inverts colours relative to the page (e.g. a
       dark card on a light page) where the default pairing above
       would land with poor contrast. Consumed by .sf-selection--alt
       (optional/utilities.css). */
    --sf-color-selection-bg--alt: light-dark(
      oklch(from var(--sf-color-action-source-dark) clamp(0.62, calc(0.93 - l * 0.4), 0.78) c h / 0.55),
      oklch(from var(--sf-color-action-source-light) l c h / 0.28)
    );
    --sf-color-selection-text--alt: inherit;

    /* ----------------------------------------------------------
       Status triplets — strong (direction-dependent, uses light-dark)
       danger = destructive actions, form validation errors, negative states
       ---------------------------------------------------------- */
    --sf-color-success-strong: light-dark(
      oklch(from var(--sf-color-success-source-light) calc(l - 0.15) c h),
      oklch(from var(--sf-color-success)       clamp(0.70, calc(l + 0.15), 1) c h)
    );
    --sf-color-warning-strong: light-dark(
      oklch(from var(--sf-color-warning-source-light) calc(l - 0.25) c h),
      oklch(from var(--sf-color-warning)       clamp(0.70, calc(l + 0.05), 1) c h)
    );
    --sf-color-info-strong: light-dark(
      oklch(from var(--sf-color-info-source-light) calc(l - 0.1) c h),
      oklch(from var(--sf-color-info)       clamp(0.70, calc(l + 0.15), 1) c h)
    );
    --sf-color-danger-strong: light-dark(
      oklch(from var(--sf-color-danger-source-light) calc(l - 0.1) c h),
      oklch(from var(--sf-color-danger)       clamp(0.70, calc(l + 0.15), 1) c h)
    );

  }
}
/* ── @supports gate: oklch(from …) relative color ── */
@supports (color: oklch(from red l c h)) {
  :root {

    /* ----------------------------------------------------------
       Surfaces — derived from --sf-color-base (auto-adapts)
       ---------------------------------------------------------- */
    --sf-color-bg:      oklch(from var(--sf-color-base) calc(l + 0.02) c h);
    --sf-color-inset:   oklch(from var(--sf-color-base) calc(l - 0.02) c h);
    --sf-color-raised:  oklch(from var(--sf-color-base) calc(l + 0.04) c h);
    /* Overlay: high-opacity wash of the base color for light/dark mode safe layers */
    --sf-color-overlay: oklch(from var(--sf-color-base) l c h / 0.9);
    --sf-color-inverse: oklch(from var(--sf-color-base) calc(1 - l) c h);

    /* ----------------------------------------------------------
       Border alpha tokens
       ---------------------------------------------------------- */
    --sf-color-border--disabled: oklch(from var(--sf-color-border--subtle) l 0 h / 0.5);
    --sf-color-border--translucent: oklch(from var(--sf-color-neutral) l c h / 0.15);

    /* ----------------------------------------------------------
       Link alpha token
       ---------------------------------------------------------- */
    --sf-color-link--underline: oklch(from var(--sf-color-action) l c h / 0.3);

    /* ----------------------------------------------------------
       Interactive states — derived from --sf-color-neutral (auto-adapts)
       ---------------------------------------------------------- */
    --sf-color-bg--hover:    oklch(from var(--sf-color-neutral) l c h / 0.08);
    --sf-color-bg--active:   oklch(from var(--sf-color-neutral) l c h / 0.12);
    --sf-color-bg--selected: oklch(from var(--sf-color-action) l c h / 0.1);
    --sf-color-bg--focus:    oklch(from var(--sf-color-action) l c h / 0.06);

    /* ----------------------------------------------------------
       Selection & mark
       ---------------------------------------------------------- */
    --sf-color-mark-bg: oklch(from var(--sf-color-warning) l c h / 0.25);

    /* ----------------------------------------------------------
       Status triplets — subtle/muted/tint (alpha variants)
       bg/border use same formula both modes. -tint is the 5%
       near-transparent rung, mirroring the brand families'
       --sf-color-{family}-tint aliases (full 10-family coverage).
       ---------------------------------------------------------- */
    --sf-color-success-subtle: oklch(from var(--sf-color-success) l c h / 0.12);
    --sf-color-success-muted:  oklch(from var(--sf-color-success) l c h / 0.3);
    --sf-color-success-tint:   oklch(from var(--sf-color-success) l c h / 0.05);

    --sf-color-warning-subtle: oklch(from var(--sf-color-warning) l c h / 0.12);
    --sf-color-warning-muted:  oklch(from var(--sf-color-warning) l c h / 0.3);
    --sf-color-warning-tint:   oklch(from var(--sf-color-warning) l c h / 0.05);

    --sf-color-info-subtle:    oklch(from var(--sf-color-info) l c h / 0.12);
    --sf-color-info-muted:     oklch(from var(--sf-color-info) l c h / 0.3);
    --sf-color-info-tint:      oklch(from var(--sf-color-info) l c h / 0.05);

    --sf-color-danger-subtle:  oklch(from var(--sf-color-danger) l c h / 0.12);
    --sf-color-danger-muted:   oklch(from var(--sf-color-danger) l c h / 0.3);
    --sf-color-danger-tint:    oklch(from var(--sf-color-danger) l c h / 0.05);

    /* ----------------------------------------------------------
       Gradients — derived from brand colors (auto-adapt)
       All 4 core brand families (primary/secondary/tertiary/action)
       share the same darkening formula; --sf-gradient-brand is a
       separate decorative hue-shift option, independent of the
       colour axis.
       ---------------------------------------------------------- */
    --sf-gradient-primary:   linear-gradient(in oklch 135deg, var(--sf-color-primary), oklch(from var(--sf-color-primary) calc(l - 0.08) c h));
    --sf-gradient-secondary: linear-gradient(in oklch 135deg, var(--sf-color-secondary), oklch(from var(--sf-color-secondary) calc(l - 0.08) c h));
    --sf-gradient-tertiary:  linear-gradient(in oklch 135deg, var(--sf-color-tertiary), oklch(from var(--sf-color-tertiary) calc(l - 0.08) c h));
    --sf-gradient-action:    linear-gradient(in oklch 135deg, var(--sf-color-action), oklch(from var(--sf-color-action) calc(l - 0.08) c h));
    --sf-gradient-brand:     linear-gradient(in oklch 135deg, var(--sf-color-primary), oklch(from var(--sf-color-primary) l c calc(h + 30)));
    --sf-gradient-surface:   linear-gradient(in oklab 180deg, var(--sf-color-surface), var(--sf-color-bg));

    --sf-gradient-fade--r: linear-gradient(in oklch to right,  transparent, var(--sf-color-bg));
    --sf-gradient-fade--l: linear-gradient(in oklch to left,   transparent, var(--sf-color-bg));
    --sf-gradient-fade--t: linear-gradient(in oklch to top,    transparent, var(--sf-color-bg));
    --sf-gradient-fade--b: linear-gradient(in oklch to bottom, transparent, var(--sf-color-bg));

    /* ----------------------------------------------------------
       Shadow glow
       ---------------------------------------------------------- */
    --sf-shadow-glow: 0 0 15px 2px oklch(from var(--sf-shadow-glow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2), 0.7));

  }
}

/* ── @supports gate: oklch(from …) + sign() ──────────────────
   Chrome added oklch(from) in v119 but sign() only in v138
   (Mar 2026). Without this gate, Chrome 119–137 evaluates the
   outer block to true but IACVT-crashes on sign() inside oklch(),
   making all text-on-color and auto-contrast code text invisible. */
@supports (color: oklch(from red l c h)) and (width: calc(1px * sign(1))) {
  :root {

    /* ----------------------------------------------------------
       Code text — auto-contrasts with --sf-color-code-bg.
       ---------------------------------------------------------- */
    /* Inline-code text colour. Auto-contrasts with --sf-color-code-bg using
       the same luminance formula as --sf-color-text--on-*. This prevents
       invisible text when code appears inside inverted-colour containers
       (e.g. sf-imposter with a dark background where inherited colour is
       near-white). Override with a tint value if desired. */
    --sf-color-code-text: oklch(from var(--sf-color-code-bg) clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);

    /* ----------------------------------------------------------
       Text-on-color — sign(var(--sf-contrast-threshold) - l) * 999 is mode-agnostic:
       auto-selects light or dark text based on background luminance.
       Uses resolved tokens — works for any user-supplied color.

       CONTRAST NOTE: Guarantees ≥ 3:1 (WCAG AA Large Text / UI).
       Colors in the mid-luminance range (L ≈ 0.52–0.67) may not
       reach 4.5:1 (AA Normal Text) regardless of threshold chosen —
       this is a mathematical limitation of any binary black/white
       decision. The SLASHED configurator will warn users when their
       brand colors fall in this range. For body text on colored
       backgrounds, override --sf-color-text--on-* manually.
       ---------------------------------------------------------- */
    --sf-color-text--on-primary:   oklch(from var(--sf-color-primary)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-secondary: oklch(from var(--sf-color-secondary) clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-tertiary:  oklch(from var(--sf-color-tertiary)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-action:    oklch(from var(--sf-color-action)     clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-neutral:   oklch(from var(--sf-color-neutral)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-success:   oklch(from var(--sf-color-success)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-warning:   oklch(from var(--sf-color-warning)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-info:      oklch(from var(--sf-color-info)       clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-danger:    oklch(from var(--sf-color-danger)     clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-inverse:   oklch(from var(--sf-color-inverse)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);

  }
}

/* ============================================================
   COLOR PALETTE — numeric ramps, alpha variants + aliases

   Numeric palette steps (-50…-950), alpha variants (-a5…-a80),
   and semantic convenience aliases live in core as the single source
   of truth for the 6 brand colour families. Shade steps mix toward
   --sf-color-surface (tints) or --sf-color-text (shades); the base
   family inverts tint direction so the ramp reads light→dark regardless
   of the base surface value.
   ============================================================ */

/* ── @supports gate: palette ramps + alpha aliases ── */
@supports (color: oklch(from red l c h)) {
:root {

  /* ----------------------------------------------------------
     Palette ramp lightness anchors — PUBLIC-ADVANCED
     Each numeric step is generated in OKLCH by pulling the family
     colour's own lightness a fixed fraction toward an ABSOLUTE target:
     tints (50-400) toward --sf-palette-tint-l, shades (600-950) toward
     --sf-palette-shade-l. 500 = the family colour itself. Because the
     targets are absolute (not the theme's surface/text tokens) and the
     pull is clamped (max()/min()) so a tint can never end darker than
     the base nor a shade lighter, the ramp is MONOTONIC light→dark for
     ANY source colour and any anchor configuration — it cannot fold.
     Per-step pull fractions and chroma taper are baked into each family
     below; these two knobs set how light the lightest / how dark the
     darkest step may reach. The `base` family keeps its own absolute
     ladder (near-white→near-black) and does not read these.
     ---------------------------------------------------------- */
  --sf-palette-tint-l:  0.97;
  --sf-palette-shade-l: 0.1;

  /* ----------------------------------------------------------
     primary
     ---------------------------------------------------------- */
  --sf-color-primary-50: oklch(from var(--sf-color-primary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.95)) calc(c * 0.2) h);
  --sf-color-primary-100: oklch(from var(--sf-color-primary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.88)) calc(c * 0.3) h);
  --sf-color-primary-200: oklch(from var(--sf-color-primary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.72)) calc(c * 0.5) h);
  --sf-color-primary-300: oklch(from var(--sf-color-primary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.52)) calc(c * 0.72) h);
  --sf-color-primary-400: oklch(from var(--sf-color-primary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.3)) calc(c * 0.88) h);
  --sf-color-primary-500: var(--sf-color-primary);
  --sf-color-primary-600: oklch(from var(--sf-color-primary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.16)) calc(c * 0.96) h);
  --sf-color-primary-700: oklch(from var(--sf-color-primary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.36)) calc(c * 0.88) h);
  --sf-color-primary-800: oklch(from var(--sf-color-primary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.58)) calc(c * 0.72) h);
  --sf-color-primary-900: oklch(from var(--sf-color-primary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.78)) calc(c * 0.52) h);
  --sf-color-primary-950: oklch(from var(--sf-color-primary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.9)) calc(c * 0.38) h);

  --sf-color-primary-a5:  oklch(from var(--sf-color-primary) l c h / 0.05);
  --sf-color-primary-a10: oklch(from var(--sf-color-primary) l c h / 0.10);
  --sf-color-primary-a30: oklch(from var(--sf-color-primary) l c h / 0.30);
  --sf-color-primary-a50: oklch(from var(--sf-color-primary) l c h / 0.50);
  --sf-color-primary-a80: oklch(from var(--sf-color-primary) l c h / 0.80);

  --sf-color-primary-superlight: var(--sf-color-primary-50);
  --sf-color-primary-xlight:     var(--sf-color-primary-200);
  --sf-color-primary-lighter:    var(--sf-color-primary-400);
  --sf-color-primary-darker:     var(--sf-color-primary-600);
  --sf-color-primary-xdark:      var(--sf-color-primary-800);
  --sf-color-primary-superdark:  var(--sf-color-primary-950);
  --sf-color-primary--hover:     var(--sf-color-primary-darker);
  --sf-color-primary--active:    var(--sf-color-primary-xdark);
  --sf-color-primary-subtle:     var(--sf-color-primary-a10);
  --sf-color-primary-muted:      var(--sf-color-primary-a30);
  --sf-color-primary-tint:      var(--sf-color-primary-a5);



  /* ----------------------------------------------------------
     secondary
     ---------------------------------------------------------- */
  --sf-color-secondary-50: oklch(from var(--sf-color-secondary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.95)) calc(c * 0.2) h);
  --sf-color-secondary-100: oklch(from var(--sf-color-secondary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.88)) calc(c * 0.3) h);
  --sf-color-secondary-200: oklch(from var(--sf-color-secondary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.72)) calc(c * 0.5) h);
  --sf-color-secondary-300: oklch(from var(--sf-color-secondary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.52)) calc(c * 0.72) h);
  --sf-color-secondary-400: oklch(from var(--sf-color-secondary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.3)) calc(c * 0.88) h);
  --sf-color-secondary-500: var(--sf-color-secondary);
  --sf-color-secondary-600: oklch(from var(--sf-color-secondary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.16)) calc(c * 0.96) h);
  --sf-color-secondary-700: oklch(from var(--sf-color-secondary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.36)) calc(c * 0.88) h);
  --sf-color-secondary-800: oklch(from var(--sf-color-secondary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.58)) calc(c * 0.72) h);
  --sf-color-secondary-900: oklch(from var(--sf-color-secondary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.78)) calc(c * 0.52) h);
  --sf-color-secondary-950: oklch(from var(--sf-color-secondary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.9)) calc(c * 0.38) h);

  --sf-color-secondary-a5:  oklch(from var(--sf-color-secondary) l c h / 0.05);
  --sf-color-secondary-a10: oklch(from var(--sf-color-secondary) l c h / 0.10);
  --sf-color-secondary-a30: oklch(from var(--sf-color-secondary) l c h / 0.30);
  --sf-color-secondary-a50: oklch(from var(--sf-color-secondary) l c h / 0.50);
  --sf-color-secondary-a80: oklch(from var(--sf-color-secondary) l c h / 0.80);

  --sf-color-secondary-superlight: var(--sf-color-secondary-50);
  --sf-color-secondary-xlight:     var(--sf-color-secondary-200);
  --sf-color-secondary-lighter:    var(--sf-color-secondary-400);
  --sf-color-secondary-darker:     var(--sf-color-secondary-600);
  --sf-color-secondary-xdark:      var(--sf-color-secondary-800);
  --sf-color-secondary-superdark:  var(--sf-color-secondary-950);
  --sf-color-secondary--hover:     var(--sf-color-secondary-darker);
  --sf-color-secondary--active:    var(--sf-color-secondary-xdark);
  --sf-color-secondary-subtle:     var(--sf-color-secondary-a10);
  --sf-color-secondary-muted:      var(--sf-color-secondary-a30);
  --sf-color-secondary-tint:      var(--sf-color-secondary-a5);



  /* ----------------------------------------------------------
     tertiary
     ---------------------------------------------------------- */
  --sf-color-tertiary-50: oklch(from var(--sf-color-tertiary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.95)) calc(c * 0.2) h);
  --sf-color-tertiary-100: oklch(from var(--sf-color-tertiary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.88)) calc(c * 0.3) h);
  --sf-color-tertiary-200: oklch(from var(--sf-color-tertiary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.72)) calc(c * 0.5) h);
  --sf-color-tertiary-300: oklch(from var(--sf-color-tertiary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.52)) calc(c * 0.72) h);
  --sf-color-tertiary-400: oklch(from var(--sf-color-tertiary) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.3)) calc(c * 0.88) h);
  --sf-color-tertiary-500: var(--sf-color-tertiary);
  --sf-color-tertiary-600: oklch(from var(--sf-color-tertiary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.16)) calc(c * 0.96) h);
  --sf-color-tertiary-700: oklch(from var(--sf-color-tertiary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.36)) calc(c * 0.88) h);
  --sf-color-tertiary-800: oklch(from var(--sf-color-tertiary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.58)) calc(c * 0.72) h);
  --sf-color-tertiary-900: oklch(from var(--sf-color-tertiary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.78)) calc(c * 0.52) h);
  --sf-color-tertiary-950: oklch(from var(--sf-color-tertiary) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.9)) calc(c * 0.38) h);

  --sf-color-tertiary-a5:  oklch(from var(--sf-color-tertiary) l c h / 0.05);
  --sf-color-tertiary-a10: oklch(from var(--sf-color-tertiary) l c h / 0.10);
  --sf-color-tertiary-a30: oklch(from var(--sf-color-tertiary) l c h / 0.30);
  --sf-color-tertiary-a50: oklch(from var(--sf-color-tertiary) l c h / 0.50);
  --sf-color-tertiary-a80: oklch(from var(--sf-color-tertiary) l c h / 0.80);

  --sf-color-tertiary-superlight: var(--sf-color-tertiary-50);
  --sf-color-tertiary-xlight:     var(--sf-color-tertiary-200);
  --sf-color-tertiary-lighter:    var(--sf-color-tertiary-400);
  --sf-color-tertiary-darker:     var(--sf-color-tertiary-600);
  --sf-color-tertiary-xdark:      var(--sf-color-tertiary-800);
  --sf-color-tertiary-superdark:  var(--sf-color-tertiary-950);
  --sf-color-tertiary--hover:     var(--sf-color-tertiary-darker);
  --sf-color-tertiary--active:    var(--sf-color-tertiary-xdark);
  --sf-color-tertiary-subtle:     var(--sf-color-tertiary-a10);
  --sf-color-tertiary-muted:      var(--sf-color-tertiary-a30);
  --sf-color-tertiary-tint:      var(--sf-color-tertiary-a5);



  /* ----------------------------------------------------------
     action
     ---------------------------------------------------------- */
  --sf-color-action-50: oklch(from var(--sf-color-action) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.95)) calc(c * 0.2) h);
  --sf-color-action-100: oklch(from var(--sf-color-action) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.88)) calc(c * 0.3) h);
  --sf-color-action-200: oklch(from var(--sf-color-action) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.72)) calc(c * 0.5) h);
  --sf-color-action-300: oklch(from var(--sf-color-action) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.52)) calc(c * 0.72) h);
  --sf-color-action-400: oklch(from var(--sf-color-action) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.3)) calc(c * 0.88) h);
  --sf-color-action-500: var(--sf-color-action);
  --sf-color-action-600: oklch(from var(--sf-color-action) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.16)) calc(c * 0.96) h);
  --sf-color-action-700: oklch(from var(--sf-color-action) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.36)) calc(c * 0.88) h);
  --sf-color-action-800: oklch(from var(--sf-color-action) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.58)) calc(c * 0.72) h);
  --sf-color-action-900: oklch(from var(--sf-color-action) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.78)) calc(c * 0.52) h);
  --sf-color-action-950: oklch(from var(--sf-color-action) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.9)) calc(c * 0.38) h);

  --sf-color-action-a5:  oklch(from var(--sf-color-action) l c h / 0.05);
  --sf-color-action-a10: oklch(from var(--sf-color-action) l c h / 0.10);
  --sf-color-action-a30: oklch(from var(--sf-color-action) l c h / 0.30);
  --sf-color-action-a50: oklch(from var(--sf-color-action) l c h / 0.50);
  --sf-color-action-a80: oklch(from var(--sf-color-action) l c h / 0.80);

  --sf-color-action-superlight: var(--sf-color-action-50);
  --sf-color-action-xlight:     var(--sf-color-action-200);
  --sf-color-action-lighter:    var(--sf-color-action-400);
  --sf-color-action-darker:     var(--sf-color-action-600);
  --sf-color-action-xdark:      var(--sf-color-action-800);
  --sf-color-action-superdark:  var(--sf-color-action-950);
  --sf-color-action--hover:     var(--sf-color-action-darker);
  --sf-color-action--active:    var(--sf-color-action-xdark);
  --sf-color-action-subtle:     var(--sf-color-action-a10);
  --sf-color-action-muted:      var(--sf-color-action-a30);
  --sf-color-action-tint:      var(--sf-color-action-a5);



  /* ----------------------------------------------------------
     neutral
     ---------------------------------------------------------- */
  --sf-color-neutral-50: oklch(from var(--sf-color-neutral) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.95)) calc(c * 0.2) h);
  --sf-color-neutral-100: oklch(from var(--sf-color-neutral) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.88)) calc(c * 0.3) h);
  --sf-color-neutral-200: oklch(from var(--sf-color-neutral) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.72)) calc(c * 0.5) h);
  --sf-color-neutral-300: oklch(from var(--sf-color-neutral) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.52)) calc(c * 0.72) h);
  --sf-color-neutral-400: oklch(from var(--sf-color-neutral) max(l, calc(l + (var(--sf-palette-tint-l) - l) * 0.3)) calc(c * 0.88) h);
  --sf-color-neutral-500: var(--sf-color-neutral);
  --sf-color-neutral-600: oklch(from var(--sf-color-neutral) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.16)) calc(c * 0.96) h);
  --sf-color-neutral-700: oklch(from var(--sf-color-neutral) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.36)) calc(c * 0.88) h);
  --sf-color-neutral-800: oklch(from var(--sf-color-neutral) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.58)) calc(c * 0.72) h);
  --sf-color-neutral-900: oklch(from var(--sf-color-neutral) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.78)) calc(c * 0.52) h);
  --sf-color-neutral-950: oklch(from var(--sf-color-neutral) min(l, calc(l + (var(--sf-palette-shade-l) - l) * 0.9)) calc(c * 0.38) h);

  --sf-color-neutral-a5:  oklch(from var(--sf-color-neutral) l c h / 0.05);
  --sf-color-neutral-a10: oklch(from var(--sf-color-neutral) l c h / 0.10);
  --sf-color-neutral-a30: oklch(from var(--sf-color-neutral) l c h / 0.30);
  --sf-color-neutral-a50: oklch(from var(--sf-color-neutral) l c h / 0.50);
  --sf-color-neutral-a80: oklch(from var(--sf-color-neutral) l c h / 0.80);

  --sf-color-neutral-superlight: var(--sf-color-neutral-50);
  --sf-color-neutral-xlight:     var(--sf-color-neutral-200);
  --sf-color-neutral-lighter:    var(--sf-color-neutral-400);
  --sf-color-neutral-darker:     var(--sf-color-neutral-600);
  --sf-color-neutral-xdark:      var(--sf-color-neutral-800);
  --sf-color-neutral-superdark:  var(--sf-color-neutral-950);
  --sf-color-neutral--hover:     var(--sf-color-neutral-darker);
  --sf-color-neutral--active:    var(--sf-color-neutral-xdark);
  --sf-color-neutral-subtle:     var(--sf-color-neutral-a10);
  --sf-color-neutral-muted:      var(--sf-color-neutral-a30);
  --sf-color-neutral-tint:      var(--sf-color-neutral-a5);



  /* ----------------------------------------------------------
     base — fixed-lightness ramp (mode-independent, monotonic)
     ────────────────────────────────────────────────────────
     base is a surface-elevation family, not a mid-lightness brand
     colour. The ramp uses oklch() relative syntax with fixed L steps
     spanning 0.97 → 0.06, preserving the base source's hue and chroma.
     50 is always near-white; 950 is always near-black regardless of
     light/dark mode or the source colour's position.

     OKLCH L values (fixed, mode-independent):
       base-50  = 0.97   near-white
       base-100 = 0.94   very light gray
       base-200 = 0.88   light gray
       base-300 = 0.78   medium-light gray
       base-400 = 0.65   medium gray
       base-500 = 0.50   mid gray
       base-600 = 0.37   medium-dark gray
       base-700 = 0.27   dark gray
       base-800 = 0.17   very dark gray
       base-900 = 0.11   near-black
       base-950 = 0.06   near-black

     For mode-adaptive surface differentiation use the surface tokens
     (--sf-color-inset / --sf-color-raised / --sf-color-bg) which derive
     from --sf-color-base via relative oklch and adapt to light/dark mode.
     For overlays use alpha variants (-a10…-a80).
     ---------------------------------------------------------- */
  --sf-color-base-50:  oklch(from var(--sf-color-base) 0.97 c h);
  --sf-color-base-100: oklch(from var(--sf-color-base) 0.94 c h);
  --sf-color-base-200: oklch(from var(--sf-color-base) 0.88 c h);
  --sf-color-base-300: oklch(from var(--sf-color-base) 0.78 c h);
  --sf-color-base-400: oklch(from var(--sf-color-base) 0.65 c h);
  --sf-color-base-500: oklch(from var(--sf-color-base) 0.50 c h);
  --sf-color-base-600: oklch(from var(--sf-color-base) 0.37 c h);
  --sf-color-base-700: oklch(from var(--sf-color-base) 0.27 c h);
  --sf-color-base-800: oklch(from var(--sf-color-base) 0.17 c h);
  --sf-color-base-900: oklch(from var(--sf-color-base) 0.11 c h);
  --sf-color-base-950: oklch(from var(--sf-color-base) 0.06 c h);

  --sf-color-base-a5:  oklch(from var(--sf-color-base) l c h / 0.05);
  --sf-color-base-a10: oklch(from var(--sf-color-base) l c h / 0.10);
  --sf-color-base-a30: oklch(from var(--sf-color-base) l c h / 0.30);
  --sf-color-base-a50: oklch(from var(--sf-color-base) l c h / 0.50);
  --sf-color-base-a80: oklch(from var(--sf-color-base) l c h / 0.80);

  /* No relative aliases (lighter/darker/xlight/…) on base: it is an
     absolute scale, so a step's name would lie depending on where the
     source sits (e.g. "lighter" would be darker than a near-white base
     in light mode). For relative, mode-adaptive surface elevation use
     --sf-color-bg / --sf-color-inset / --sf-color-raised instead. */
  --sf-color-base--hover:     oklch(from var(--sf-color-base) clamp(0.01, calc(l - 0.04 + var(--sf-is-dark) * 0.08), 0.99) c h);
  --sf-color-base--active:    oklch(from var(--sf-color-base) clamp(0.01, calc(l - 0.08 + var(--sf-is-dark) * 0.16), 0.99) c h);
  --sf-color-base-subtle:     var(--sf-color-base-a10);
  --sf-color-base-muted:      var(--sf-color-base-a30);
  --sf-color-base-tint:      var(--sf-color-base-a5);


}
}

/* ============================================================
   NON-COLOR DESIGN TOKENS
   ============================================================ */

:root {

  /* ----------------------------------------------------------
     Scale multipliers
     ---------------------------------------------------------- */
  --sf-space-scale:        1;
  --sf-text-scale:         1;
  --sf-text-display-scale: 1;
  --sf-radius-scale:       1;
  --sf-motion-scale:       1;
  --sf-section-scale:      1;

  /* ----------------------------------------------------------
     Font families
     ---------------------------------------------------------- */
  --sf-font-body:    system-ui, -apple-system, sans-serif;
  --sf-font-heading: var(--sf-font-body);
  --sf-font-display: var(--sf-font-heading);
  --sf-font-mono:    ui-monospace, monospace;

  /* Optional system-font stacks (zero-cost, no @import) */
  --sf-font-humanist:  "Seravek", "Gill Sans Nova", "Ubuntu", "Calibri", "DejaVu Sans", source-sans-pro, sans-serif;
  --sf-font-geometric: "Avenir", "Montserrat", "Corbel", "URW Gothic", source-sans-pro, sans-serif;
  --sf-font-slab:      "Rockwell", "Rockwell Nova", "Roboto Slab", "DejaVu Serif", "Sitka Small", serif;

  /* Opt-in OpenType / variable-font controls (token-only, not applied by the framework) */
  --sf-font-features:  normal;  /* e.g. "cv11", "ss01" */
  --sf-font-variation: normal;  /* e.g. "wght" 450, "opsz" 32 */
  --sf-optical-sizing: auto;

  /* ----------------------------------------------------------
     Font weights
     ---------------------------------------------------------- */
  /* Base weights — trimmed, recognizable names. For an off-scale weight
     (e.g. 100/900 display work) write font-weight: 100 directly, or re-tune
     a role token below globally — every weight here is overridable. */
  --sf-font-weight-light:    300;
  --sf-font-weight-normal:   400;
  --sf-font-weight-medium:   500;
  --sf-font-weight-semibold: 600;
  --sf-font-weight-bold:     700;

  /* Semantic role aliases — set intent, not a number. Override these to
     reweight the whole site (e.g. --sf-font-weight-body: 300). */
  --sf-font-weight-body:        var(--sf-font-weight-normal);    /* body copy            */
  --sf-font-weight-heading:     var(--sf-font-weight-semibold);  /* h1–h6                */
  --sf-font-weight-display:     var(--sf-font-weight-bold);      /* hero / display       */
  --sf-font-weight-interactive: var(--sf-font-weight-semibold);  /* buttons, badges, nav.
     Note: "interactive" here names a TYPOGRAPHY WEIGHT TIER; .sf-card--interactive
     names a card BEHAVIOUR modifier. Deliberate coexistence (different property
     spaces) — do not introduce a third meaning of "interactive". */
  --sf-font-weight-strong:      var(--sf-font-weight-bold);      /* <strong> / emphasis  */

  /* ----------------------------------------------------------
     Font sizes — fluid
     ---------------------------------------------------------- */
  /* ----------------------------------------------------------
     Font sizes — fluid (generative)
     Computed from --sf-text-ratio-{min,max} + --sf-text-base-{min,max}
     over the --sf-fluid-{min,max}-vw range. Step exponent N is relative
     to --sf-text-m (N=0): 2xs=-3 … 4xl=5. Override the inputs above to
     reshape the whole scale; override an individual --sf-text-* to pin one.
     ---------------------------------------------------------- */
  --sf-text-2xs: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -3) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -3) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -3) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -3) * 1rem)) * var(--sf-text-scale));
  --sf-text-xs:  calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -2) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -2) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -2) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -2) * 1rem)) * var(--sf-text-scale));
  --sf-text-s:   calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -1) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -1) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -1) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -1) * 1rem)) * var(--sf-text-scale));
  --sf-text-m:   calc(clamp(calc(var(--sf-text-base-min) * 1rem), calc((var(--sf-text-base-max) - var(--sf-text-base-min)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * 1rem), calc(var(--sf-text-base-max) * 1rem)) * var(--sf-text-scale));
  --sf-text-l:   calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 1) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 1) * 1rem)) * var(--sf-text-scale));
  --sf-text-xl:  calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 2) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 2) * 1rem)) * var(--sf-text-scale));
  --sf-text-2xl: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 3) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 3) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 3) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 3) * 1rem)) * var(--sf-text-scale));
  --sf-text-3xl: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 4) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 4) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 4)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 4) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 4) * 1rem)) * var(--sf-text-scale));
  --sf-text-4xl: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 5) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 5) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 5)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 5) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 5) * 1rem)) * var(--sf-text-scale));

  /* Display — reuses the type ratios with its own larger base (s=0, m=1, l=2). */
  --sf-text-display-s: calc(clamp(calc(var(--sf-text-display-base-min) * 1rem), calc((var(--sf-text-display-base-max) - var(--sf-text-display-base-min)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-display-base-min) * 1rem), calc(var(--sf-text-display-base-max) * 1rem)) * var(--sf-text-display-scale));
  --sf-text-display-m: calc(clamp(calc(var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc((var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 1) - var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc(var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 1) * 1rem)) * var(--sf-text-display-scale));
  --sf-text-display-l: calc(clamp(calc(var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc((var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 2) - var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc(var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 2) * 1rem)) * var(--sf-text-display-scale));

  /* ----------------------------------------------------------
     Line heights & letter spacing
     ---------------------------------------------------------- */
  --sf-leading-tight:   1.1;
  --sf-leading-snug:    1.3;
  --sf-leading-normal:  1.5;
  --sf-leading-relaxed: 1.625;

  /* Leading taper — progressive tightener for the per-size line-height
     tokens below. Each step up the type scale subtracts
     `step-index × taper` from that step's default leading.
     0 (default) keeps the curated stepped defaults; try 0.02 for a smooth
     metric taper across the scale. */
  --sf-leading-taper: 0;

  /* Display line-heights — display sizes need tighter leading than the
     --sf-leading-* scale provides. Not auto-applied (display sizes have no
     dedicated element); wire alongside font-size:
       h1.hero { font-size: var(--sf-text-display-l);
                 line-height: var(--sf-display-l-line-height); } */
  --sf-display-s-line-height: var(--sf-leading-tight);
  --sf-display-m-line-height: 1.05;
  --sf-display-l-line-height: 1;

  --sf-tracking-tight:   -0.025em;
  --sf-tracking-normal:   0;
  --sf-tracking-wide:     0.025em;
  --sf-tracking-wider:    0.05em;
  --sf-tracking-widest:   0.1em;

  /* Numeric figure style — tabular-nums gives every digit a fixed
     advance width so numbers line up in vertical columns (price
     lists, totals, invoices, dashboards). Consumed by number inputs
     in optional/forms.css and available to BEM components, e.g.
     `.price { font-variant-numeric: var(--sf-font-numeric); }`.
     Universal browser support. */
  --sf-font-numeric: tabular-nums;

  /* ----------------------------------------------------------
     Per-size typography sub-properties
     Not auto-applied — opt-in composable overrides. Wire alongside
     font-size in your own rules:
       .element { line-height: var(--sf-text-xl-line-height); }
     Small text: relaxed leading. Body: normal. Large: progressively
     tighter. --sf-leading-taper (default 0) tightens the whole
     scale at once.
     ---------------------------------------------------------- */

  --sf-text-2xs-line-height:            var(--sf-leading-relaxed);
  --sf-text-2xs-font-weight:            var(--sf-font-weight-body);
  --sf-text-2xs-letter-spacing:         var(--sf-tracking-normal);
  --sf-text-2xs-max-width:              55ch;

  --sf-text-xs-line-height:             calc(var(--sf-leading-relaxed) - 1 * var(--sf-leading-taper));
  --sf-text-xs-font-weight:             var(--sf-font-weight-body);
  --sf-text-xs-letter-spacing:          var(--sf-tracking-normal);
  --sf-text-xs-max-width:               60ch;

  --sf-text-s-line-height:              calc(var(--sf-leading-relaxed) - 2 * var(--sf-leading-taper));
  --sf-text-s-font-weight:              var(--sf-font-weight-body);
  --sf-text-s-letter-spacing:           var(--sf-tracking-normal);
  --sf-text-s-max-width:                65ch;

  --sf-text-m-line-height:              calc(var(--sf-leading-normal) - 3 * var(--sf-leading-taper));
  --sf-text-m-font-weight:              var(--sf-font-weight-body);
  --sf-text-m-letter-spacing:           var(--sf-tracking-normal);
  --sf-text-m-max-width:                65ch;

  --sf-text-l-line-height:              calc(var(--sf-leading-normal) - 4 * var(--sf-leading-taper));
  --sf-text-l-font-weight:              var(--sf-font-weight-body);
  --sf-text-l-letter-spacing:           var(--sf-tracking-normal);
  --sf-text-l-max-width:                none;

  --sf-text-xl-line-height:             calc(var(--sf-leading-snug) - 5 * var(--sf-leading-taper));
  --sf-text-xl-font-weight:             var(--sf-font-weight-body);
  --sf-text-xl-letter-spacing:          var(--sf-tracking-normal);
  --sf-text-xl-max-width:               none;

  --sf-text-2xl-line-height:            calc(var(--sf-leading-snug) - 6 * var(--sf-leading-taper));
  --sf-text-2xl-font-weight:            var(--sf-font-weight-heading);
  --sf-text-2xl-letter-spacing:         var(--sf-tracking-normal);
  --sf-text-2xl-max-width:              none;

  --sf-text-3xl-line-height:            calc(var(--sf-leading-tight) - 7 * var(--sf-leading-taper));
  --sf-text-3xl-font-weight:            var(--sf-font-weight-heading);
  --sf-text-3xl-letter-spacing:         var(--sf-tracking-tight);
  --sf-text-3xl-max-width:              none;

  --sf-text-4xl-line-height:            calc(var(--sf-leading-tight) - 8 * var(--sf-leading-taper));
  --sf-text-4xl-font-weight:            var(--sf-font-weight-heading);
  --sf-text-4xl-letter-spacing:         var(--sf-tracking-tight);
  --sf-text-4xl-max-width:              none;

  /* ----------------------------------------------------------
     Icon sizes — em-based by design.
     Icons are sized in em so they scale with the surrounding
     text: an icon in a button at --sf-text-m renders at 1rem;
     moved into a heading at --sf-text-2xl, the same icon scales
     up automatically.
     Global scaling flows transitively through --sf-text-scale
     (parent text grows → em-icons grow) — there is no separate
     --sf-icon-scale knob by design. clamp() is intentionally
     absent here: making icons fluid INDEPENDENTLY of context
     would break the relationship to surrounding text.
     Capped at --2xl (4em ≈ 64px on default text); above that the
     element ceases to be an icon and becomes an illustration.
     ---------------------------------------------------------- */
  --sf-icon-xs:  0.875em;
  --sf-icon-s:   1em;
  --sf-icon-m:   1.5em;
  --sf-icon-l:   2em;
  --sf-icon-xl:  3em;
  --sf-icon-2xl: 4em;

  /* ----------------------------------------------------------
     Spacing — fixed
     ---------------------------------------------------------- */
  --sf-space-none:   0;
  --sf-space-px:     1px;

  /* Spacing — fluid (generative). Same engine as type; space uses base_max=2
     (more dramatic growth than type's 1.25). Step exponent N relative to
     --sf-space-m (N=0): 2xs=-3 … 4xl=5. */
  --sf-space-2xs: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -3) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -3) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -3) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -3) * 1rem)) * var(--sf-space-scale));
  --sf-space-xs:  calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) * 1rem)) * var(--sf-space-scale));
  --sf-space-s:   calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -1) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -1) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -1) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -1) * 1rem)) * var(--sf-space-scale));
  --sf-space-m:   calc(clamp(calc(var(--sf-space-base-min) * 1rem), calc((var(--sf-space-base-max) - var(--sf-space-base-min)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * 1rem), calc(var(--sf-space-base-max) * 1rem)) * var(--sf-space-scale));
  --sf-space-l:   calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 1) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 1) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 1) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 1) * 1rem)) * var(--sf-space-scale));
  --sf-space-xl:  calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) * 1rem)) * var(--sf-space-scale));
  --sf-space-2xl: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 3) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 3) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 3) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 3) * 1rem)) * var(--sf-space-scale));
  --sf-space-3xl: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 4) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 4) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 4)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 4) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 4) * 1rem)) * var(--sf-space-scale));
  --sf-space-4xl: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 5) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 5) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 5)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 5) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 5) * 1rem)) * var(--sf-space-scale));

  /* ----------------------------------------------------------
     Density — the compact ↔ comfortable dial for interactive
     control GEOMETRY. 1 = default; <1 packs controls tighter
     (dashboards, data tables, power-user tools), >1 loosens them.
     It is a discrete design/product choice (or a user preference),
     NOT a viewport response — control height must never shrink on
     small screens where touch targets need to be larger, so unlike
     the fluid text and space scales the size ladder stays non-fluid
     and is scaled only by this deliberate knob. Orthogonal to
     --sf-space-scale (overall whitespace) and --sf-section-scale
     (macro section rhythm); combine them for a fully compact UI.
     Accessibility is unaffected: the coarse-pointer floor lives on
     --sf-touch-target (core/accessibility.css), never on a rung, so
     a small --sf-density can't shrink native controls below the
     WCAG target. Like the other global multipliers it is a :root
     dial — the size ladder is computed at :root and inherits, so a
     nested override does not retroactively rescale it (same
     computed-value rule as --sf-space-scale et al.). */
  --sf-density: 1;

  /* ----------------------------------------------------------
     UI sizes — a purely geometric rung ladder (+0.5rem / 8px per
     step: 24 · 32 · 40 · 48 · 56 px at density 1). These are free
     design knobs, scaled by --sf-density; overriding a rung just
     rescales that component height. Accessibility floors live on
     their own tokens (--sf-touch-target), never on a rung.
     ---------------------------------------------------------- */
  --sf-size-xs: calc(1.5rem * var(--sf-density));
  --sf-size-s:  calc(2rem   * var(--sf-density));
  --sf-size-m:  calc(2.5rem * var(--sf-density));
  --sf-size-l:  calc(3rem   * var(--sf-density));
  --sf-size-xl: calc(3.5rem * var(--sf-density));

  /* ----------------------------------------------------------
     Containers
     ---------------------------------------------------------- */
  --sf-container-narrow:  38rem;
  --sf-container-prose:   65ch;
  --sf-container-default: 75rem;
  --sf-container-wide:    90rem;
  --sf-container-full:    100%;

  /* ----------------------------------------------------------
     Aspect ratios
     ---------------------------------------------------------- */
  --sf-ratio-square:   1;
  --sf-ratio-video:    16 / 9;
  --sf-ratio-cinema:   21 / 9;
  --sf-ratio-4-3:      4 / 3;
  --sf-ratio-3-2:      3 / 2;
  --sf-ratio-portrait: 3 / 4;
  --sf-ratio-golden:   1.618 / 1;

  /* ----------------------------------------------------------
     Border widths
     --sf-border-width-hairline: device-pixel-dependent. On 1x DPR
     displays (Windows non-Retina) the browser will round 0.5px up
     to 1px, so it visually matches --sf-border-width-1. On 2x+
     DPR (macOS/iOS Retina, most modern phones) it renders as a
     true half-pixel hairline. Use it for subtle separators where
     1px on low-DPR is acceptable.
     ---------------------------------------------------------- */
  /* --sf-border-scale — global multiplier for border widths. Set to 0 for
     borderless, 2 for heavy borders. Hairline is intentionally excluded
     (it's a device-pixel constant, not a design variable). */
  --sf-border-scale:           1;
  --sf-border-width-hairline: 0.5px;
  --sf-border-width-1:        calc(1px * var(--sf-border-scale, 1));
  --sf-border-width-2:        calc(2px * var(--sf-border-scale, 1));
  --sf-border-width-3:        calc(3px * var(--sf-border-scale, 1));
  --sf-border-width-4:        calc(4px * var(--sf-border-scale, 1));

  /* ----------------------------------------------------------
     Border style — global knob paired with --sf-border-width-* and
     --sf-color-border*. Consumer BEM:
       .my-card { border: var(--sf-border-width-1) var(--sf-border-style) var(--sf-color-border); }
     Set once on :root to restyle every framework border, or override
     per element. For one-off decorative styles use the CSS keyword
     directly (e.g. `border-style: dashed`).
     ---------------------------------------------------------- */
  --sf-border-style: solid;     /* default */

  /* ----------------------------------------------------------
     Border shorthands — convenience tokens composing width, style,
     and color into a single value for the common cases.
     subtle / strong vary COLOR CONTRAST only — all three use
     --sf-border-width-1 (1px). This matches the IBM Carbon and
     GitHub Primer convention where subtle/strong name a color axis,
     not a weight axis. For a thicker border compose manually:
       border: var(--sf-border-width-2) var(--sf-border-style) var(--sf-color-border--strong)
     Usage: border: var(--sf-border);
            border-bottom: var(--sf-border-subtle);
     ---------------------------------------------------------- */
  --sf-border:        var(--sf-border-width-1) var(--sf-border-style) var(--sf-color-border);
  --sf-border-subtle: var(--sf-border-width-1) var(--sf-border-style) var(--sf-color-border--subtle);
  --sf-border-strong: var(--sf-border-width-1) var(--sf-border-style) var(--sf-color-border--strong);

  /* ----------------------------------------------------------
     Divider — global separator system. Drives `hr` (base) and
     the `.sf-divider` layout primitive.
     ---------------------------------------------------------- */
  --sf-divider-width: var(--sf-border-width-1);
  --sf-divider-style: solid;
  --sf-divider-color: var(--sf-color-border);
  --sf-divider-gap:   var(--sf-space-m);

  /* ----------------------------------------------------------
     Border radius — scale ramp, all multiplied by --sf-radius-scale.
     ---------------------------------------------------------- */
  --sf-radius-none: 0;
  --sf-radius-2xs:  calc(1px  * var(--sf-radius-scale));
  --sf-radius-xs:   calc(2px  * var(--sf-radius-scale));
  --sf-radius-s:    calc(4px  * var(--sf-radius-scale));
  --sf-radius-m:    calc(8px  * var(--sf-radius-scale));
  --sf-radius-l:    calc(12px * var(--sf-radius-scale));
  --sf-radius-xl:   calc(16px * var(--sf-radius-scale));
  --sf-radius-2xl:  calc(24px * var(--sf-radius-scale));
  --sf-radius-3xl:  calc(32px * var(--sf-radius-scale));
  --sf-radius-4xl:  calc(48px * var(--sf-radius-scale));
  /* ----------------------------------------------------------
     Full/pill — topological constants, not scaled by --sf-radius-scale.
     A pill/circle is a topological constant, not a relative size:
     any --sf-radius-scale: 0 ("sharp" design system) should still
     render pill buttons as pills. 9999px is a magic-number convention
     large enough to clip to half the shorter edge for any realistic UI.
     ---------------------------------------------------------- */
  --sf-radius-full: 9999px;
  /* Pill — semantic alias of --sf-radius-full. Reach for this name
     on rectangles you want capsule-shaped (badges, tags, chips); use
     --sf-radius-full when you mean "as round as topologically possible". */
  --sf-radius-pill: var(--sf-radius-full);
  /* Concentric helper — outer radius for a container that wraps
     content padded by --sf-component-pad with inner radius m.
     Keeps inner elements visually proportional inside a padded box.
     Override per use:
       .my-card { border-radius: var(--sf-radius-outer); }
     Maths: outer = inner + padding ⇒ aligned curves at corners. */
  --sf-radius-outer: calc(var(--sf-radius-m) + var(--sf-component-pad));

  /* Opt-in global media radius — 0 by default (off). Set to a
     --sf-radius-* value to round <img>/<figure> globally:
       :root { --sf-media-radius: var(--sf-radius-m); }
     Applied via :where() in core/base.css so it never fights
     .sf-bg-layer or a component's own radius. */
  --sf-media-radius: 0;

  /* ----------------------------------------------------------
     Shadows — BEM consumer API
     Use in your own classes: .card { box-shadow: var(--sf-shadow-m); }
     ---------------------------------------------------------- */


  /* ----------------------------------------------------------
     Blur & opacity
     One frosted-surface default (nav/overlay glass) and one general
     de-emphasis opacity. For other amounts write the value directly:
     backdrop-filter: blur(24px); opacity: .3;
     (--sf-opacity-disabled is defined with the interaction tokens.)
     ---------------------------------------------------------- */
  --sf-blur:          12px;
  --sf-opacity-muted: 0.5;

  /* ----------------------------------------------------------
     Motion & easing
     ---------------------------------------------------------- */
  --sf-duration-none:    0ms;
  --sf-duration-instant: calc(100ms * var(--sf-motion-scale));
  --sf-duration-fast:    calc(150ms * var(--sf-motion-scale));
  --sf-duration-normal:  calc(250ms * var(--sf-motion-scale));
  --sf-duration-slow:    calc(400ms * var(--sf-motion-scale));
  --sf-duration-slower:  calc(600ms * var(--sf-motion-scale));

  /* Opt-in light/dark cross-fade duration — the knob for .sf-theme-transition
     (core/themes.css). Registered <color> tokens make the fade possible;
     this controls how long it takes. */
  --sf-theme-transition-duration: calc(300ms * var(--sf-motion-scale));

  --sf-ease-linear:  linear;
  --sf-ease-out:     cubic-bezier(0.25, 0, 0.15, 1);
  --sf-ease-in:      cubic-bezier(0.5, 0, 0.75, 0.25);
  --sf-ease-in-out:  cubic-bezier(0.4, 0, 0.2, 1);
  --sf-ease-spring:    linear(0, 0.5, 1.1, 0.95, 1.02, 1);
  --sf-ease-elastic:   linear(0, 0.3, 1.2, 0.9, 1.05, 1);
  --sf-ease-bounce:    linear(0, 0.35 18%, 1 32%, 0.86 42%, 1.02 56%, 0.98 72%, 1);
  --sf-ease-overshoot: linear(0, 0.6 30%, 1.08 55%, 0.98 75%, 1);

  /* ----------------------------------------------------------
     Animation presets — pair keyframes (core/motion.css) with
     duration + easing. Use directly: animation: var(--sf-animation-fade-in);
     ---------------------------------------------------------- */
  --sf-animation-fade-in:        sf-fade-in        var(--sf-duration-normal) var(--sf-ease-out)       both;
  --sf-animation-fade-out:       sf-fade-out       var(--sf-duration-normal) var(--sf-ease-in)        both;
  --sf-animation-slide-in-up:    sf-slide-in-up    var(--sf-duration-normal) var(--sf-ease-out)       both;
  --sf-animation-slide-in-down:  sf-slide-in-down  var(--sf-duration-normal) var(--sf-ease-out)       both;
  --sf-animation-slide-in-left:  sf-slide-in-left  var(--sf-duration-normal) var(--sf-ease-out)       both;
  --sf-animation-slide-in-right: sf-slide-in-right var(--sf-duration-normal) var(--sf-ease-out)       both;
  --sf-animation-scale-up:       sf-scale-up       var(--sf-duration-normal) var(--sf-ease-overshoot) both;
  --sf-animation-scale-down:     sf-scale-down     var(--sf-duration-normal) var(--sf-ease-in)        both;
  --sf-animation-slide-out-up:    sf-slide-out-up    var(--sf-duration-normal) var(--sf-ease-in) both;
  --sf-animation-slide-out-down:  sf-slide-out-down  var(--sf-duration-normal) var(--sf-ease-in) both;
  --sf-animation-slide-out-left:  sf-slide-out-left  var(--sf-duration-normal) var(--sf-ease-in) both;
  --sf-animation-slide-out-right: sf-slide-out-right var(--sf-duration-normal) var(--sf-ease-in) both;
  --sf-animation-color-pulse:    sf-color-pulse    var(--sf-duration-slow)   var(--sf-ease-in-out)    infinite;
  --sf-animation-ping:           sf-ping           var(--sf-duration-slow)   var(--sf-ease-out)       infinite;
  --sf-animation-blink:          sf-blink          calc(1s * var(--sf-motion-scale)) steps(1, end) infinite;
  --sf-animation-float:          sf-float          calc(3s * var(--sf-motion-scale)) var(--sf-ease-in-out) infinite;
  --sf-animation-spin:    sf-spin var(--sf-duration-slower) linear infinite;
  --sf-animation-shimmer: sf-shimmer calc(1.5s * var(--sf-motion-scale)) var(--sf-ease-in-out) infinite;

  /* Stagger step — the per-item delay increment consumed by the .sf-stagger
     utility (core/motion.css). One knob retunes the whole choreography; the
     class multiplies it by the child's index and by --sf-motion-scale, so the
     rhythm follows the motion knob without baking the scale in here. */
  --sf-stagger-step: 75ms;

  /* Hover-transform magnitudes — the .sf-hover-* utilities (optional/
     utilities.css) read these so the effect strength is one global knob per
     axis rather than hard-coded per class. --grow/--shrink are scale factors;
     --lift is the float/sink translate distance; --slide is the slide
     distance. */
  --sf-hover-grow-scale:   1.05;
  --sf-hover-shrink-scale: 0.95;
  --sf-hover-lift:         0.25em;
  --sf-hover-slide:        0.5em;

  /* ----------------------------------------------------------
     Scroll-driven animation range — consumer API. Attach these
     to your own scroll-timeline via animation-range. Not consumed
     by the framework itself; intended for BEM component authors.
     ---------------------------------------------------------- */
  --sf-scroll-timeline-range-start: entry 0%;
  --sf-scroll-timeline-range-end:   cover 30%;

  /* Exit counterpart — element is well into view before starting to
     leave, finishing exactly as it scrolls fully out. */
  --sf-scroll-timeline-range-exit-start: cover 70%;
  --sf-scroll-timeline-range-exit-end:   exit 100%;

  /* ----------------------------------------------------------
     Mask scrim — edge-fade stops for scroll reels / overflow.
     Use in a mask-image gradient to fade content near an edge:
       mask-image: linear-gradient(to right,
         transparent 0, #000 var(--sf-mask-scrim-start),
         #000 calc(100% - var(--sf-mask-scrim-end)), transparent 100%);
     ---------------------------------------------------------- */
  --sf-mask-scrim-start: var(--sf-space-l);
  --sf-mask-scrim-end:   var(--sf-space-l);

  /* ----------------------------------------------------------
     Z-index
     ---------------------------------------------------------- */
  /* Semantic z-index ladder — UI roles, not magic integers. Follows the
     Bootstrap/Chakra convention (1000-base, ordered by stacking role) so
     the names are familiar and the gaps leave room to insert custom layers.
     Low-level rungs (below/base/raised) handle local stacking; the named
     roles handle page chrome and overlays.

     Modal / popover / tooltip components render in the browser TOP LAYER
     (native <dialog> / [popover]), ordered above the page independently of
     z-index. The --sf-z-modal / --sf-z-overlay / --sf-z-tooltip rungs are
     the fallback for non-top-layer implementations that must beat the page. */
  --sf-z-below:  -1;
  --sf-z-base:    0;
  --sf-z-raised:  1;
  --sf-z-sticky:   1000;  /* sticky headers / sidebars            */
  --sf-z-fixed:    1010;  /* fixed app chrome (navbar, FAB)       */
  --sf-z-dropdown: 1020;  /* menus — clear sticky + fixed         */
  --sf-z-overlay:  1030;  /* scrims / non-top-layer backdrops     */
  --sf-z-modal:    1040;  /* dialogs (non-top-layer fallback)     */
  --sf-z-toast:    1050;  /* transient notifications              */
  --sf-z-tooltip:  1060;  /* tooltips — above everything          */

  /* ----------------------------------------------------------
     Layout & a11y
     ---------------------------------------------------------- */
  /* Header height fluidly interpolates between the mobile and desktop
     tokens across the framework viewport range (--sf-fluid-{min,max}-vw),
     the same range used by the fluid type/space scales. Override either
     endpoint to retune; set both equal for a fixed height. clamp() keeps the
     result bounded to the endpoints even when they are overridden. */
  --sf-header-height-mobile:  3.5rem;
  --sf-header-height-desktop: 5rem;
  --sf-header-height: clamp(
    var(--sf-header-height-mobile),
    calc((var(--sf-header-height-desktop) - var(--sf-header-height-mobile)) / ((var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * 1rem) * (100vw - var(--sf-fluid-min-vw) * 1rem) + var(--sf-header-height-mobile)),
    var(--sf-header-height-desktop));
  --sf-sticky-offset-mobile:  var(--sf-header-height-mobile);
  --sf-sticky-offset-desktop: var(--sf-header-height-desktop);
  --sf-sticky-offset: clamp(
    var(--sf-sticky-offset-mobile),
    calc((var(--sf-sticky-offset-desktop) - var(--sf-sticky-offset-mobile)) / ((var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * 1rem) * (100vw - var(--sf-fluid-min-vw) * 1rem) + var(--sf-sticky-offset-mobile)),
    var(--sf-sticky-offset-desktop));
  /* Focus ring dimensions and minimum touch-target size. Governs keyboard
     navigation affordance and WCAG 2.5.5 pointer-target requirements.
     --sf-touch-target owns the 44px WCAG floor as a literal — it is NOT an
     alias of a --sf-size-* rung, so retuning the size scale can never drag
     the accessibility minimum below spec. Override it deliberately if you
     have your own target policy. */
  --sf-focus-ring-width:  2px;
  --sf-focus-ring-offset: 2px;
  --sf-focus-ring-style:  solid;
  --sf-touch-target:      2.75rem;  /* 44px — WCAG 2.5.5 minimum, independent of the size scale */
  /* Global text-contrast knob. Nudges derived reading-text colours
     toward the extremes (darker in light mode, lighter in dark mode).
     Positive = more contrast; 0 = no change. Consumed by
     --sf-color-text, --sf-color-text--subtle, --sf-color-heading. */
  --sf-contrast-bias:     0;
  /* Lightness crossover for text-on-color auto-contrast. Colours with
     L above this value get dark text; below get light text.
     Default 0.6 guarantees ≥ 3:1 for most colours.
     The binary choice (near-black or near-white) cannot guarantee 4.5:1 when
     the background sits in the L ≈ 0.52–0.67 band — both extremes land near
     the crossover. Two escape hatches:
       1. Shift the threshold per surface — lower (e.g. 0.55) if your brand
          lands near 0.6 and you prefer white text on it:
            @layer slashed.overrides {
              .sf-surface--primary { --sf-contrast-threshold: 0.55; }
            }
       2. Override the resolved token directly when an exact colour is needed:
            @layer slashed.overrides {
              .sf-surface--primary { --sf-color-text--on-primary: oklch(0.15 0 0); }
            }
     CSS `contrast-color()` (currently only in the spec, no browser support)
     will replace this formula once it ships. */
  --sf-contrast-threshold: 0.6;

  /* Device safe-area insets (notches, rounded corners, home indicators).
     0px on devices without intrusions. Read these in your own padding/
     margin; the framework consumes them in layout edge spacing. */
  --sf-safe-top:    env(safe-area-inset-top,    0px);
  --sf-safe-bottom: env(safe-area-inset-bottom, 0px);
  --sf-safe-left:   env(safe-area-inset-left,   0px);
  --sf-safe-right:  env(safe-area-inset-right,  0px);

  /* ----------------------------------------------------------
     Print
     ---------------------------------------------------------- */
  --sf-print-page-margin: 2cm;
  --sf-print-page-size:   a4;
  --sf-print-base-size:   11pt;

  /* ----------------------------------------------------------
     Object fit / position — global defaults for replaced elements.
     Applied in core/base.css on img and video. Override per element
     via inline style="--sf-object-position: top" or in BEM CSS.
     ---------------------------------------------------------- */
  --sf-object-fit:      cover;
  --sf-object-position: 50% 50%;

}

/* @supports — Shadow ramp (box-shadow, text-shadow, drop-shadow) */
@supports (color: oklch(from red l c h)) {
  :root {

    /* Shadow tint — near-black derived from the neutral; the tint simply
       inherits the neutral's own chroma/hue, so a colourless neutral yields
       colourless shadows. Forced dark in both modes; --sf-shadow-strength
       handles dark-mode intensity. Override to a brand colour for explicitly
       tinted shadows — keep it dark or you get bright slabs:
       oklch(from var(--sf-color-primary) 0.15 c h). */
    --sf-shadow-color: oklch(from var(--sf-color-neutral) var(--sf-shadow-lightness) c h);

    /* Elevation shadow ramp — box-shadow presets from xs to 2xl.
       Opacity scales with --sf-shadow-strength so dark-mode boost is automatic. */
    --sf-shadow-none:  none;
    --sf-shadow-xs:    0 1px 2px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 0.5), 0.7));
    --sf-shadow-s:     0 1px 2px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 0.5), 0.7)),
                       0 2px 6px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, var(--sf-shadow-strength), 0.7));
    --sf-shadow-m:     0 1px 3px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 0.5), 0.7)),
                       0 4px 12px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2), 0.7));
    --sf-shadow-l:     0 2px 4px 0   oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 0.5), 0.7)),
                       0 8px 24px 0  oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 3), 0.7)),
                       0 16px 48px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2), 0.7));
    --sf-shadow-xl:    0 2px 8px 0   oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 0.5), 0.7)),
                       0 12px 36px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 3.5), 0.7)),
                       0 24px 72px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2.5), 0.7));
    --sf-shadow-2xl:   0 4px 12px 0       oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 0.6), 0.7)),
                       0 20px 60px 0       oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 4), 0.7)),
                       0 40px 100px -8px   oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 5), 0.7));
    --sf-shadow-inner: inset 0 2px 4px 0 oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2), 0.7));

    /* ----------------------------------------------------------
       Text shadows — share --sf-shadow-color / --sf-shadow-strength
       ---------------------------------------------------------- */
    --sf-text-shadow-none: none;
    --sf-text-shadow-xs: 0 0.5px 1px oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 1),   0.7));
    --sf-text-shadow-s:  0 1px 2px   oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 1.5), 0.7));
    --sf-text-shadow-m:  0 2px 4px   oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2),   0.7));
    --sf-text-shadow-l:  0 4px 8px   oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2.5), 0.7));
    --sf-text-shadow-xl: 0 8px 16px  oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 3),   0.7));

    /* Drop shadows — for filter: drop-shadow() (respects alpha edges) */
    --sf-drop-shadow-xs: drop-shadow(0 0.5px 1px  oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 1),   0.7)));
    --sf-drop-shadow-s:  drop-shadow(0 1px 2px    oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 1.5), 0.7)));
    --sf-drop-shadow-m:  drop-shadow(0 4px 6px    oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2),   0.7)));
    --sf-drop-shadow-l:  drop-shadow(0 8px 16px   oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 2.5), 0.7)));
    --sf-drop-shadow-xl: drop-shadow(0 16px 32px  oklch(from var(--sf-shadow-color) l c h / clamp(0, calc(var(--sf-shadow-strength) * 3),   0.7)));

  }
}

/* ============================================================
   TYPOGRAPHY ALIASES — part of the per-style typography bundle
   for a named text role (body, code, heading, h1–h6, display).
   The full bundle spans font-family, size, weight, line-height,
   and letter-spacing across related tokens.
   ============================================================ */

:root {
  --sf-body-font-size:     var(--sf-text-m);
  --sf-body-line-height:   var(--sf-leading-normal);
  --sf-body-font-weight:   var(--sf-font-weight-body);
  --sf-body-font-family:   var(--sf-font-body);
  --sf-body-color:         var(--sf-color-text);
  --sf-body-text-wrap:     pretty;
  --sf-body-strong-weight: var(--sf-font-weight-strong);
  --sf-body-em-style:      italic;

  --sf-code-font-size:     0.875em;

  --sf-heading-font-family: var(--sf-font-heading);
  --sf-heading-color:       var(--sf-color-heading);
  --sf-heading-text-wrap:   balance;

  --sf-h1-size:           var(--sf-text-4xl);
  --sf-h1-line-height:    var(--sf-leading-tight);
  --sf-h1-font-weight:    var(--sf-font-weight-heading);
  --sf-h1-letter-spacing: var(--sf-tracking-tight);

  --sf-h2-size:           var(--sf-text-3xl);
  --sf-h2-line-height:    var(--sf-leading-tight);
  --sf-h2-font-weight:    var(--sf-font-weight-heading);
  --sf-h2-letter-spacing: var(--sf-tracking-tight);

  --sf-h3-size:           var(--sf-text-2xl);
  --sf-h3-line-height:    var(--sf-leading-snug);
  --sf-h3-font-weight:    var(--sf-font-weight-heading);
  --sf-h3-letter-spacing: var(--sf-tracking-normal);

  --sf-h4-size:           var(--sf-text-xl);
  --sf-h4-line-height:    var(--sf-leading-snug);
  --sf-h4-font-weight:    var(--sf-font-weight-heading);
  --sf-h4-letter-spacing: var(--sf-tracking-normal);

  --sf-h5-size:           var(--sf-text-l);
  --sf-h5-line-height:    var(--sf-leading-normal);
  --sf-h5-font-weight:    var(--sf-font-weight-heading);
  --sf-h5-letter-spacing: var(--sf-tracking-normal);

  --sf-h6-size:           var(--sf-text-m);
  --sf-h6-line-height:    var(--sf-leading-normal);
  --sf-h6-font-weight:    var(--sf-font-weight-heading);
  --sf-h6-letter-spacing: var(--sf-tracking-wide);

  /* ----------------------------------------------------------
     Heading line-length constraints — public control hooks.
     Default `none` = no constraint. Set to a ch value to cap
     heading width independently of the prose container:
       :root { --sf-h1-max-width: 20ch; --sf-h2-max-width: 28ch; }
     Consumed by h1–h6 in core/base.css.
     ---------------------------------------------------------- */
  --sf-h1-max-width: none;
  --sf-h2-max-width: none;
  --sf-h3-max-width: none;
  --sf-h4-max-width: none;
  --sf-h5-max-width: none;
  --sf-h6-max-width: none;

  /* ----------------------------------------------------------
     Semantic gap tokens — the three rhythms every layout primitive
     defaults to. Also the BEM consumer API: .nav { gap: var(--sf-gap); }
     Override globally to retune all primitives of that rhythm at once.
     ---------------------------------------------------------- */
  --sf-gap:           var(--sf-space-m);  /* loose: between components   */
  --sf-content-gap:   var(--sf-space-s);  /* tight: within content       */
  --sf-gutter:        var(--sf-space-l);  /* wide: page/section gutters  */
  --sf-component-pad: var(--sf-space-m);  /* standard component padding  */
  --sf-field-block:   var(--sf-space-xs); /* form-field block padding — matches the shipped field default (see optional/forms.css) */
  --sf-field-required-marker: " *";
  /* Leading space is baked into the default so consumers can fully
     disable the indicator with `--sf-link-external-marker: ""` —
     without the space a bare "" still left a stray space after the
     link text. Override with your own glyph (include leading space
     if you want one): `:root { --sf-link-external-marker: " →" }`. */
  --sf-link-external-marker: " \2197";  /* " ↗" — consumed by .sf-link-external */
  /* Accessible name for the marker glyph, via the CSS alt-text syntax
     (`content: <value> / <string>`) — read by screen readers in place
     of the glyph, appended after the link's own text. Localise or
     disable (together with the marker) per-site. */
  --sf-link-external-label: "opens in a new window or external site";

  /* ----------------------------------------------------------
     Section padding
     ---------------------------------------------------------- */
  /* All sizes multiply by --sf-section-scale — one dial re-rhythms
     every section on the page (default 1 = no change). */
  --sf-section-pad:     var(--sf-section-pad--m);
  --sf-section-pad--xs: calc(var(--sf-space-xl)  * var(--sf-section-scale));
  --sf-section-pad--s:  calc(var(--sf-space-2xl) * var(--sf-section-scale));
  --sf-section-pad--m:  calc(var(--sf-space-3xl) * var(--sf-section-scale));
  --sf-section-pad--l:  calc(var(--sf-space-4xl) * var(--sf-section-scale));
  --sf-section-pad--xl: calc(var(--sf-space-4xl) * 1.5 * var(--sf-section-scale));
  --sf-section-pad--2xl: calc(var(--sf-space-4xl) * 2 * var(--sf-section-scale));

  /* ----------------------------------------------------------
     Transition shorthands

     All tokens name an explicit property set (no `transition: all`).
     The tempo presets (fast / slow / enter / exit) cover the curated
     "common interactive + entrance" set — color, background-color,
     border-color, box-shadow, opacity, transform, filter.
     ---------------------------------------------------------- */
  --sf-transition-colors:
    color var(--sf-duration-normal) var(--sf-ease-out),
    background-color var(--sf-duration-normal) var(--sf-ease-out),
    border-color var(--sf-duration-normal) var(--sf-ease-out),
    text-decoration-color var(--sf-duration-normal) var(--sf-ease-out),
    fill var(--sf-duration-normal) var(--sf-ease-out),
    stroke var(--sf-duration-normal) var(--sf-ease-out);
  /* Form fields: color/bg at normal pace, border/shadow/opacity at fast
     so focus rings and validation states react immediately. */
  --sf-transition-form-field:
    color              var(--sf-duration-normal) var(--sf-ease-out),
    background-color   var(--sf-duration-normal) var(--sf-ease-out),
    text-decoration-color var(--sf-duration-normal) var(--sf-ease-out),
    fill               var(--sf-duration-normal) var(--sf-ease-out),
    stroke             var(--sf-duration-normal) var(--sf-ease-out),
    border-color       var(--sf-duration-fast)   var(--sf-ease-out),
    box-shadow         var(--sf-duration-fast)   var(--sf-ease-out),
    opacity            var(--sf-duration-fast)   var(--sf-ease-out);
  /* Single-property transition: transform only. */
  --sf-transition-transform: transform var(--sf-duration-normal) var(--sf-ease-out);
  /* Single-property transition: opacity only. */
  --sf-transition-opacity:   opacity var(--sf-duration-normal) var(--sf-ease-out);
  /* Single-property transition: box-shadow only. */
  --sf-transition-shadow:    box-shadow var(--sf-duration-normal) var(--sf-ease-out);

  /* All interactive properties at fast duration — for hover/active states that need to feel snappy. */
  --sf-transition-fast:
    color var(--sf-duration-fast) var(--sf-ease-out),
    background-color var(--sf-duration-fast) var(--sf-ease-out),
    border-color var(--sf-duration-fast) var(--sf-ease-out),
    box-shadow var(--sf-duration-fast) var(--sf-ease-out),
    opacity var(--sf-duration-fast) var(--sf-ease-out),
    transform var(--sf-duration-fast) var(--sf-ease-out),
    filter var(--sf-duration-fast) var(--sf-ease-out);
  /* All interactive properties at slow duration — for deliberate, attention-drawing animations. */
  --sf-transition-slow:
    color var(--sf-duration-slow) var(--sf-ease-in-out),
    background-color var(--sf-duration-slow) var(--sf-ease-in-out),
    border-color var(--sf-duration-slow) var(--sf-ease-in-out),
    box-shadow var(--sf-duration-slow) var(--sf-ease-in-out),
    opacity var(--sf-duration-slow) var(--sf-ease-in-out),
    transform var(--sf-duration-slow) var(--sf-ease-in-out),
    filter var(--sf-duration-slow) var(--sf-ease-in-out);
  /* Entrance transition: normal duration + ease-out for elements entering the view. */
  --sf-transition-enter:
    color var(--sf-duration-normal) var(--sf-ease-out),
    background-color var(--sf-duration-normal) var(--sf-ease-out),
    border-color var(--sf-duration-normal) var(--sf-ease-out),
    box-shadow var(--sf-duration-normal) var(--sf-ease-out),
    opacity var(--sf-duration-normal) var(--sf-ease-out),
    transform var(--sf-duration-normal) var(--sf-ease-out),
    filter var(--sf-duration-normal) var(--sf-ease-out);
  /* Exit transition: fast duration + ease-in so exits feel crisp and don't linger. */
  --sf-transition-exit:
    color var(--sf-duration-fast) var(--sf-ease-in),
    background-color var(--sf-duration-fast) var(--sf-ease-in),
    border-color var(--sf-duration-fast) var(--sf-ease-in),
    box-shadow var(--sf-duration-fast) var(--sf-ease-in),
    opacity var(--sf-duration-fast) var(--sf-ease-in),
    transform var(--sf-duration-fast) var(--sf-ease-in),
    filter var(--sf-duration-fast) var(--sf-ease-in);

  /* Top-layer transition building block (dialog, [popover]).
     transition-behavior: allow-discrete keeps the discrete `display`
     and `overlay` properties in the transition so entry animations can
     fire and exit animations finish before the element leaves the top
     layer. Compose it into a component transition alongside the visual
     properties.
     NOTE: Requires @starting-style to be defined on the element for
     the entrance transition to trigger.
     Progressive enhancement — unsupporting engines simply show/hide instantly.
     Chrome/Edge/Safari and Firefox (2025+) support allow-discrete. */
  --sf-transition-overlay:
    overlay var(--sf-duration-normal) allow-discrete,
    display var(--sf-duration-normal) allow-discrete;
}

/* ─── core/tokens.layout.css ─── */
/* SLASHED — core/tokens.layout.css
   @layer slashed.tokens
   Layout primitive override tokens for gaps, padding, min sizes, tracks, and containers.
   Prefix: --sf-*; loaded before core/layout.css. */

:root {

  /* ----------------------------------------------------------
     Stack
     ---------------------------------------------------------- */
  --sf-stack-gap: var(--sf-content-gap);

  /* ----------------------------------------------------------
     Box
     ---------------------------------------------------------- */
  --sf-box-padding:      var(--sf-space-m);
  --sf-box-border-width: 0;
  --sf-box-border-color: var(--sf-color-border);

  /* ----------------------------------------------------------
     Center
     ---------------------------------------------------------- */
  --sf-center-max:    var(--sf-container-default);
  --sf-center-gutter: var(--sf-gutter);

  /* ----------------------------------------------------------
     Cluster
     ---------------------------------------------------------- */
  --sf-cluster-gap:     var(--sf-gap);
  --sf-cluster-align:   center;
  --sf-cluster-justify: flex-start;

  /* ----------------------------------------------------------
     Sidebar
     ---------------------------------------------------------- */
  --sf-sidebar-gap:           var(--sf-gap);
  --sf-sidebar-min-width:     50%;           /* content column minimum */
  --sf-sidebar-width:         18rem;         /* sidebar panel default size */

  /* ----------------------------------------------------------
     Switcher
     ---------------------------------------------------------- */
  --sf-switcher-threshold: 30rem;
  --sf-switcher-gap:       var(--sf-gap);

  /* ----------------------------------------------------------
     Grid (auto-fill, breakpoint-free)
     ---------------------------------------------------------- */
  --sf-grid-min:         16rem;
  --sf-grid-gap:         var(--sf-gap);

  --sf-grid-min-xs:      10rem;
  --sf-grid-min-s:       13rem;
  --sf-grid-min-m:       16rem;
  --sf-grid-min-l:       20rem;
  --sf-grid-min-xl:      24rem;
  --sf-grid-min-2xl:     28rem;

  /* ----------------------------------------------------------
     Equal columns — CSS multi-column flowing layout (not grid).
     Content flows between columns like a newspaper: fills one
     column top-to-bottom, continues at the top of the next.
     Use for FAQ lists, prose, anything that should distribute
     evenly without being pinned to discrete grid cells — reach
     for .sf-grid/.sf-grid--fit instead when items need their own
     fixed cell.
     ---------------------------------------------------------- */
  --sf-equal-min-col:    16rem;
  --sf-equal-gap:        var(--sf-gap);
  --sf-equal-rule-width: 0;
  --sf-equal-rule-style: solid;
  --sf-equal-rule-color: var(--sf-color-border);

  /* ----------------------------------------------------------
     Cover
     ---------------------------------------------------------- */
  --sf-cover-min-height: 100dvh;
  --sf-cover-padding:    var(--sf-section-pad);

  /* ----------------------------------------------------------
     Frame
     ---------------------------------------------------------- */
  --sf-frame-ratio: 16 / 9;    /* literal <ratio>, valid in aspect-ratio */

  /* ----------------------------------------------------------
     Background layer (.sf-bg-layer) — cover media behind sibling content.
     inset is a single length applied to every edge; the layer size is
     derived from it, so a non-zero inset still fills correctly.
     ---------------------------------------------------------- */
  /* 0px, not bare 0 — .sf-bg-layer's inline-size/block-size multiply this by 2
     inside a calc() alongside a percentage; a unitless 0 makes that
     calc() a <number>/<percentage> type mismatch (invalid at
     computed-value time), silently collapsing .sf-bg-layer to its intrinsic
     size. */
  --sf-bg-layer-inset:    0px;
  --sf-bg-layer-fit:      cover;
  --sf-bg-layer-position: 50% 50%;
  --sf-bg-layer-radius:   0;
  --sf-bg-layer-z:        -2;        /* behind content; above the parent background */

  /* ----------------------------------------------------------
     Icon (boxed variant) — extras for .sf-icon--boxed.
     The naked .sf-icon sizing tokens (--sf-icon-xs..xl) live in
     core/tokens.css alongside the typography scale.
     ---------------------------------------------------------- */
  --sf-icon-box-pad:    0.5em;
  --sf-icon-box-radius: var(--sf-radius-s);
  --sf-icon-box-bg:     var(--sf-color-inset);
  --sf-icon-box-border: var(--sf-border-width-1) solid var(--sf-color-border);

  /* ----------------------------------------------------------
     Reel
     ---------------------------------------------------------- */
  --sf-reel-item-width: max-content;
  --sf-reel-gap:        var(--sf-gap);
  --sf-reel-height:     auto;

  /* ----------------------------------------------------------
     Imposter (centered absolute/fixed overlay)
     ---------------------------------------------------------- */
  --sf-imposter-margin: var(--sf-space-m);

  /* ----------------------------------------------------------
     Bento grid
     ---------------------------------------------------------- */
  --sf-bento-cols-default: 4;
  --sf-bento-row-default:  10rem;
  --sf-bento-row-compact:  6rem;
  --sf-bento-row-tall:     16rem;
  --sf-bento-gap:          var(--sf-gap);

  /* ----------------------------------------------------------
     Content grid (breakout pattern)
     ---------------------------------------------------------- */
  --sf-breakout-width: var(--sf-container-wide);
  --sf-content-width:  var(--sf-container-default);

  /* ----------------------------------------------------------
     Prose
     ---------------------------------------------------------- */
  --sf-prose-paragraph: var(--sf-content-gap);

  /* ----------------------------------------------------------
     Alternate (zigzag)
     ---------------------------------------------------------- */
  --sf-alternate-gap:       var(--sf-content-gap);
  --sf-alternate-inner-gap: var(--sf-gap);

}

/* ─── core/tokens.macros.css ─── */
/* SLASHED — core/tokens.macros.css
   @layer slashed.tokens
   Macro recipe override tokens for prose, surfaces, flow, media, hero, scrims, and clamps.
   Prefix: --sf-*; loaded before core/macros.css. */

:root {

  /* ----------------------------------------------------------
     Flow — distance between flow children (lobotomized owl).
     Consumed by .sf-flow > * + *.
     ---------------------------------------------------------- */
  --sf-flow-space: var(--sf-content-gap);

  /* ----------------------------------------------------------
     Line clamp — default line count for .sf-line-clamp-N.
     Override per element: style="--sf-line-clamp: 5".
     The fixed-N variants .sf-line-clamp-2 / -3 don't read this
     token (they hardcode the value), so the default only
     applies to .sf-line-clamp-N callers.
     ---------------------------------------------------------- */
  --sf-line-clamp: 3;

  /* ----------------------------------------------------------
     Aspect ratio — default for .sf-aspect (generic ratio container,
     distinct from layout's .sf-frame which has its own --sf-frame-ratio).
     ---------------------------------------------------------- */
  --sf-aspect: 16 / 9;

  /* ----------------------------------------------------------
     Prose — per-instance override knobs for .sf-prose.
     All values default to system spacing/color tokens so prose
     stays in sync with the global scale unless overridden.

     Override per-element:
       style="--sf-prose-marker-color: var(--sf-color-secondary)"
     ---------------------------------------------------------- */
  --sf-prose-heading-gap:      var(--sf-space-s);
  --sf-prose-list-gap:         var(--sf-space-xs);
  --sf-prose-block-margin:     var(--sf-space-m);
  --sf-prose-media-margin:     var(--sf-space-m);
  --sf-prose-media-radius:     var(--sf-radius-m);
  --sf-prose-figure-margin:    var(--sf-space-l);
  --sf-prose-marker-color:     var(--sf-color-primary);
  --sf-prose-figcaption-size:  var(--sf-text-s);
  --sf-prose-blockquote-padding: var(--sf-space-m);
  --sf-prose-blockquote-border:  var(--sf-border-width-2) solid var(--sf-color-border--subtle);
  --sf-prose-hr-margin:          var(--sf-space-l);
  --sf-prose-nested-list-gap:    var(--sf-space-2xs);
  --sf-prose-table-pad:          var(--sf-space-xs);

  /* ----------------------------------------------------------
     Surface color — input for the generic .sf-surface macro.
     Set it on any element carrying the class and the macro derives
     background, auto-contrast foreground, and the full contextual
     token set from it:
       <section class="sf-surface"
                style="--sf-surface-color: var(--sf-color-primary-100)">
     Inherits: a nested .sf-surface picks up the outer surface's
     color unless it sets its own. Any resolvable CSS color works.
     ---------------------------------------------------------- */
  --sf-surface-color: var(--sf-color-base);

  /* ----------------------------------------------------------
     Scroll shadow / overflow fade — mask gradient size.
     Used by .sf-scroll-shadow (top+bottom) and .sf-overflow-fade
     (end-edge horizontal fade).
     ---------------------------------------------------------- */
  --sf-scroll-shadow-size: 2rem;

  /* ----------------------------------------------------------
     Content visibility — placeholder size for .sf-content-auto.
     Feeds contain-intrinsic-size so offscreen sections reserve
     space before their first render, keeping the scrollbar and
     scroll position stable. Override per element:
     style="--sf-content-intrinsic-size: 800px".
     ---------------------------------------------------------- */
  --sf-content-intrinsic-size: 500px;

  /* ----------------------------------------------------------
     Scrim — darkening overlay for text-over-image legibility,
     consumed by the .sf-scrim macro. The gradient is composed
     from a direction + a color stop so consumers can retune
     either independently:
       style="--sf-scrim-color: oklch(0 0 0 / 0.75)"
       style="--sf-scrim-direction: to top right"
     --sf-scrim-gradient is the final composed value; override it
     directly for a multi-stop or radial scrim.
     ---------------------------------------------------------- */
  --sf-scrim-color:     oklch(0 0 0 / 0.55);
  --sf-scrim-direction: to top;
  --sf-scrim-gradient:  linear-gradient(var(--sf-scrim-direction), var(--sf-scrim-color), transparent);
  /* Text-on-image legibility WITHOUT darkening the picture — a soft
     shadow halo behind glyphs, consumed by .sf-text-protect. */
  --sf-scrim-text-shadow: 0 1px 3px oklch(0 0 0 / 0.6);

  /* ----------------------------------------------------------
     Named background surface preset — input set for the .sf-surface-bg
     macro (issue #523). One reusable, named background bundling a base
     colour fallback + image/gradient/pattern + sizing + an optional
     overlay + an optional animation. Define a preset by setting these
     on a scope, then apply .sf-surface-bg to compose them:
       .hero-surface {
         --sf-surface-bg-image:   url("/hero.avif");
         --sf-surface-bg-overlay: var(--sf-scrim-gradient);  // reuse the scrim
         --sf-surface-bg-animation: sf-pan 40s linear infinite;
       }
       <section class="hero-surface sf-surface-bg">…</section>
     -overlay layers ABOVE -image (first background-image layer). Builds on
     the existing scrim + gradient tokens rather than new infra; for a
     blurred backdrop compose .sf-scrim / a filter on top. Inert by default
     (image/overlay/animation = none) so the bare class is a no-op until a
     preset feeds it. ---------------------------------------------------- */
  --sf-surface-bg-color:      transparent;
  --sf-surface-bg-image:      none;
  --sf-surface-bg-overlay:    none;
  --sf-surface-bg-size:       cover;
  --sf-surface-bg-position:   center;
  --sf-surface-bg-repeat:     no-repeat;
  --sf-surface-bg-attachment: scroll;
  --sf-surface-bg-animation:  none;

  /* ----------------------------------------------------------
     Overlap recipe — how far .sf-overlap pulls into the adjacent
     element. .sf-overlap-host's padding compensation defaults to this
     same value via a var() fallback in the rule itself (not aliased
     here — see core/macros.css), so one override on the host tunes
     both unless --sf-overlap-host-pad is set independently.
     ---------------------------------------------------------- */
  --sf-overlap-pull: var(--sf-space-xl);

}

/* ─── core/reset.css ─── */
/* SLASHED — core/reset.css
   @layer slashed.reset
   Browser normalization, sizing defaults, viewport behavior, media safety, and form inheritance.
   No component styling. */


*, *::before, *::after {
  box-sizing: border-box;
}

html {
  /* stylelint-disable-next-line property-no-vendor-prefix -- Safari <17: unprefixed text-size-adjust only landed in Safari 17 */
  -webkit-text-size-adjust: 100%;
  text-size-adjust:          100%;
  scroll-padding-block-start: calc(var(--sf-header-height, 5rem) + var(--sf-space-m, 1rem));
  scrollbar-gutter:    stable;
  color-scheme:        var(--sf-color-scheme, light dark);
  hanging-punctuation: first last;
}

@supports (interpolate-size: allow-keywords) {
  html { interpolate-size: allow-keywords; }
}

body {
  min-height:      100dvh;
  min-inline-size: 320px;
}

button {
  text-transform: none;
}

search {
  display: block;
}

summary {
  cursor: pointer;
}

table {
  border-collapse: collapse;
}

[hidden] {
  display: none !important;
}

dialog, [popover] {
  padding:      0;
  border:       none;
  background:   Canvas;
  color:        inherit;
  color-scheme: inherit;
}

fieldset {
  min-width: 0;
}

[inert] {
  cursor: default;
}

/* ─── core/base.css ─── */
/* SLASHED — core/base.css
   @layer slashed.base
   Element defaults for typography, media, links, tables, and forms.
   Token-first, classless foundation. */


html {
  scrollbar-color: var(--sf-scrollbar-thumb) var(--sf-scrollbar-track);
}

body {
  margin:           0;
  font-family:      var(--sf-body-font-family, var(--sf-font-body));
  font-size:        var(--sf-body-font-size,   var(--sf-text-m));
  line-height:      var(--sf-body-line-height, var(--sf-leading-normal));
  font-weight:      var(--sf-body-font-weight, var(--sf-font-weight-body));
  color:            var(--sf-body-color,       var(--sf-color-text));
  background-color: var(--sf-color-bg, #fff);
}

h1, h2, h3, h4, h5, h6 {
  margin:            0;
  font-family:       var(--sf-heading-font-family, var(--sf-font-heading));
  line-height:       var(--sf-leading-tight);
  color:             var(--sf-heading-color, var(--sf-color-heading));
  text-wrap:         var(--sf-heading-text-wrap);
  overflow-wrap:     break-word;
  text-rendering:    optimizelegibility;
}

h1 { font-size: var(--sf-h1-size); line-height: var(--sf-h1-line-height); font-weight: var(--sf-h1-font-weight); letter-spacing: var(--sf-h1-letter-spacing); max-inline-size: var(--sf-h1-max-width); }
h2 { font-size: var(--sf-h2-size); line-height: var(--sf-h2-line-height); font-weight: var(--sf-h2-font-weight); letter-spacing: var(--sf-h2-letter-spacing); max-inline-size: var(--sf-h2-max-width); }
h3 { font-size: var(--sf-h3-size); line-height: var(--sf-h3-line-height); font-weight: var(--sf-h3-font-weight); letter-spacing: var(--sf-h3-letter-spacing); max-inline-size: var(--sf-h3-max-width); }
h4 { font-size: var(--sf-h4-size); line-height: var(--sf-h4-line-height); font-weight: var(--sf-h4-font-weight); letter-spacing: var(--sf-h4-letter-spacing); max-inline-size: var(--sf-h4-max-width); }
h5 { font-size: var(--sf-h5-size); line-height: var(--sf-h5-line-height); font-weight: var(--sf-h5-font-weight); letter-spacing: var(--sf-h5-letter-spacing); max-inline-size: var(--sf-h5-max-width); }
h6 { font-size: var(--sf-h6-size); line-height: var(--sf-h6-line-height); font-weight: var(--sf-h6-font-weight); letter-spacing: var(--sf-h6-letter-spacing); max-inline-size: var(--sf-h6-max-width); }

p {
  margin:        0;
  text-wrap:     var(--sf-body-text-wrap);
  overflow-wrap: break-word;
}

b, strong {
  font-weight: var(--sf-body-strong-weight);
}

em {
  font-style: var(--sf-body-em-style);
}

small {
  font-size: var(--sf-text-s, 0.875em);
}

mark {
  background-color: var(--sf-color-mark-bg, Mark);
  color:            var(--sf-color-mark-text, MarkText);
  border-radius:    var(--sf-radius-xs);
  padding-inline:   0.2em;
}

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

kbd, samp {
  font-family: var(--sf-font-mono);
  font-size:   inherit;
}

sub, sup {
  font-size:      75%;
  line-height:    0;
  position:       relative;
  vertical-align: baseline;
}
sup { top:    -0.5em; }
sub { bottom: -0.25em; }

/* :link (0,1,1) not bare a: beats WebKit UA rule a:link { color: -webkit-link }
   that silently overrides --sf-color-link in dark mode. */
a:link {
  color:                   var(--sf-color-link);
  text-decoration-color:   var(--sf-color-link--underline);
  text-underline-offset:   var(--sf-link-underline-offset);
  text-decoration-thickness: var(--sf-link-underline-thickness);
  text-underline-position: from-font;
  transition:
    color                 var(--sf-duration-fast) var(--sf-ease-out),
    text-decoration-color var(--sf-duration-fast) var(--sf-ease-out);
}
a:hover   { color: var(--sf-color-link--hover);   text-decoration-color: currentcolor; }
a:visited { color: var(--sf-color-link--visited); }
a:active  { color: var(--sf-color-link--active);  }
a:not([class]) { text-decoration-skip-ink: auto; }

a, code {
  overflow-wrap: break-word;
}

code {
  font-family:      var(--sf-font-mono);
  font-size:        var(--sf-code-font-size);
  padding:          0.125em 0.3em;
  border-radius:    var(--sf-radius-xs);
  background-color: var(--sf-color-code-bg);
  color:            var(--sf-color-code-text, inherit);
}

pre {
  font-family:   var(--sf-font-mono);
  font-size:     var(--sf-text-s);
  padding:       var(--sf-space-m);
  border-radius: var(--sf-radius-m);
  background:    var(--sf-color-code-block-bg, var(--sf-color-code-bg));
  color:         var(--sf-color-code-block-text, inherit);
  overflow-x:      auto;
  overflow-inline: auto;
  tab-size:        2;
  hyphens:       none;
}
pre code {
  background:    none;
  color:         inherit;
  padding:       0;
  font-size:     inherit;
  border-radius: 0;
}

img, picture, video, canvas {
  display:         block;
  max-inline-size: 100%;
  block-size:      auto;
}

/* Zero-specificity so .sf-bg-layer, .sf-frame > img, and any component's own
   radius (later layer or higher specificity) always win. */
:where(img, figure) {
  border-radius: var(--sf-media-radius);
}

img, video {
  object-fit:      var(--sf-object-fit);
  object-position: var(--sf-object-position);
}

iframe, embed, object {
  display:         block;
  max-inline-size: 100%;
  block-size:      auto;
  border:          none;
}

hr {
  border:             none;
  border-block-start: var(--sf-divider-width) var(--sf-divider-style) var(--sf-divider-color);
}

::selection {
  background:  var(--sf-color-selection-bg);
  color:       var(--sf-color-selection-text);
  text-shadow: none;
}

th, caption {
  text-align: start;
}

input, textarea {
  caret-color: var(--sf-caret-color);
}

input, progress {
  accent-color: var(--sf-color-action);
}

::backdrop {
  background-color: var(--sf-color-dim);
}

button, input, select, textarea {
  font:           inherit;
  color:          inherit;
  letter-spacing: inherit;
  color-scheme:   inherit;
  line-height:    inherit;
}
button {
  cursor: pointer;
}
a, button, input, select, textarea, summary {
  touch-action: manipulation;
}
textarea {
  resize: vertical;
}

optgroup { font: inherit; }
::placeholder { opacity: 1; color: var(--sf-color-text--placeholder, currentcolor); }

blockquote {
  border-inline-start: var(--sf-border-width-3) var(--sf-border-style) var(--sf-color-border);
  padding-inline-start: var(--sf-space-m);
}

dt {
  font-weight: var(--sf-font-weight-heading);
}

dd {
  margin-inline-start: var(--sf-space-m);
}

table {
  text-align: start;
}

th {
  font-weight: var(--sf-font-weight-heading);
}

td, th {
  padding: var(--sf-space-2xs) var(--sf-space-xs);
  border-block-end: var(--sf-border-width-hairline) var(--sf-border-style) var(--sf-color-border--subtle);
}

/* ─── optional/forms.css ─── */
/* SLASHED — optional/forms.css
   @layer slashed.forms
   Classless form controls: text inputs, selects, textareas, checks/radios, fieldsets, labels.
   Values resolve through core and optional component tokens. */


/* Shared base for text-like inputs, select, textarea */

input:where(
  [type="text"], [type="email"], [type="password"], [type="url"],
  [type="tel"], [type="number"], [type="search"], [type="date"],
  [type="datetime-local"], [type="month"], [type="week"],
  [type="time"], :not([type])
),
select,
textarea {

  appearance: none;

  display: block;
  inline-size: 100%;
  /* Field metrics resolve through the component-tier field knobs first, then
     the core rhythm knob, then the literal — so overriding any of them retunes
     fields without touching global spacing/radius. Every fallback equals the
     shipped default, so the default render is unchanged. --sf-field-padding-*
     and --sf-field-radius live in optional/tokens.components.css (present in
     the -components/full bundles); --sf-field-block is the core-tier fallback
     so the knob still works in the forms-only bundles. */
  padding-block: var(--sf-field-padding-block, var(--sf-field-block, var(--sf-space-xs)));
  padding-inline: var(--sf-field-padding-inline, var(--sf-space-s));
  font-size: var(--sf-text-m);
  line-height: var(--sf-leading-normal);
  color: var(--sf-color-text);
  background-color: var(--sf-color-surface);
  border: var(--sf-border-width-1) solid var(--sf-field-border-color, var(--sf-color-border));
  border-radius: var(--sf-field-radius, var(--sf-radius-m));
  transition: var(--sf-transition-form-field);
}

/* Focus */
input:where(
  [type="text"], [type="email"], [type="password"], [type="url"],
  [type="tel"], [type="number"], [type="search"], [type="date"],
  [type="datetime-local"], [type="month"], [type="week"],
  [type="time"], :not([type])
):focus-visible,
select:focus-visible,
textarea:focus-visible {
  border-color: var(--sf-color-border--focus);
  outline: var(--sf-focus-ring-width) var(--sf-focus-ring-style, solid) var(--sf-color-border--focus);
  outline-offset: var(--sf-focus-ring-offset);
}

/* Disabled */
input:disabled,
select:disabled,
textarea:disabled {
  background-color: var(--sf-color-bg--disabled);
  color: var(--sf-color-text--disabled);
  opacity: var(--sf-opacity-disabled);
  cursor: not-allowed;
}

/* Placeholder */
input::placeholder,
textarea::placeholder {
  color: var(--sf-color-text--placeholder);
  opacity: 1;
}

/* Autofill: strips Safari's own dark-mode autofill background; Chromium/Firefox honour
   the CSS background without this, but WebKit doesn't. */
/* stylelint-disable selector-pseudo-class-no-unknown */
input:-webkit-autofill {
  box-shadow: inset 0 0 0 1000px var(--sf-color-surface);
  -webkit-text-fill-color: var(--sf-color-text);
  caret-color: var(--sf-color-text);
  border-color: var(--sf-field-border-color, var(--sf-color-border));
}
input:autofill {
  box-shadow: inset 0 0 0 1000px var(--sf-color-surface);
  -webkit-text-fill-color: var(--sf-color-text);
  caret-color: var(--sf-color-text);
  border-color: var(--sf-field-border-color, var(--sf-color-border));
}
/* stylelint-enable selector-pseudo-class-no-unknown */

/* Opt-in native validation feedback. Unconditional :user-invalid/:user-valid
   styling was removed in 0.8.0 (see docs/migration.md): it fires on simple
   focus+blur, so a still-empty required field could be marked invalid mid
   multi-field form-fill, before any submit was attempted — the pattern
   Bootstrap's own .was-validated gate exists to avoid. .sf-live-validate is
   that same gate: put it on a <form> or <fieldset> to scope the pivot to
   that subtree, so it only lights up on a real submit attempt (or explicit
   interaction) instead of applying everywhere by default.

   Safe to combine with manual validation: .sf-is-invalid/.sf-is-valid
   (core/states.css, @layer slashed.states) always win over the rules below
   regardless of selector specificity, because slashed.states is declared
   AFTER slashed.forms in the layer order (core/layers.css) — cascade layer
   order beats specificity. A field you've manually marked .sf-is-invalid
   (e.g. a server-side error) can't be silently un-coloured by these rules
   even if the browser considers it natively valid. */
.sf-live-validate input:user-invalid,
.sf-live-validate select:user-invalid,
.sf-live-validate textarea:user-invalid {
  --sf-field-border-color: var(--sf-color-danger);
}

.sf-live-validate input:user-valid,
.sf-live-validate select:user-valid,
.sf-live-validate textarea:user-valid {
  --sf-field-border-color: var(--sf-color-success);
}

/* Numeric inputs */
input[type="number"] {
  font-variant-numeric: var(--sf-font-numeric, tabular-nums);
}

/* Textarea specifics */
textarea {
  min-block-size: 6rem;
  resize:         vertical;
}

@supports (field-sizing: content) {
  textarea {
    field-sizing: content;
  }
}

/* Select: chevron uses FieldText system color — custom properties don't propagate
   into data-URI SVG backgrounds. */
select {
  padding-inline-end: calc(var(--sf-space-s) + 1.5em);
}

select:not([multiple], [size]) {
  background-repeat: no-repeat;
  background-position: right var(--sf-space-s) center;
  background-size: 1em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='FieldText' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
}

:dir(rtl) select:not([multiple], [size]) {
  background-position: left var(--sf-space-s) center;
}

/* Button / submit / reset. Scoped to :where(form, fieldset) so a bare
   <button> or <input type="submit"/"reset"/"button"> used elsewhere on
   the page (a page-builder's hamburger toggle, a modal close control, a
   third-party widget) is left alone — only controls living inside an
   actual form get the classless treatment. */
:where(form, fieldset) button:not([class*="sf-"]),
:where(form, fieldset) input[type="submit"]:not([class*="sf-"]),
:where(form, fieldset) input[type="reset"]:not([class*="sf-"]),
:where(form, fieldset) input[type="button"]:not([class*="sf-"]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sf-space-xs);
  padding-block: var(--sf-space-xs);
  padding-inline: var(--sf-space-m);
  font-size: var(--sf-text-m);
  font-weight: var(--sf-font-weight-heading);
  line-height: var(--sf-leading-normal);
  color: var(--sf-color-text--on-action);
  background-color: var(--sf-color-action);
  border: var(--sf-border-width-1) solid transparent;
  border-radius: var(--sf-radius-m);
  cursor: pointer;
  transition: var(--sf-transition-colors);
  text-decoration: none;
}

:where(form, fieldset) button:not([class*="sf-"]):hover,
:where(form, fieldset) input[type="submit"]:not([class*="sf-"]):hover,
:where(form, fieldset) input[type="reset"]:not([class*="sf-"]):hover,
:where(form, fieldset) input[type="button"]:not([class*="sf-"]):hover {

  background-color: var(--sf-color-action--hover, var(--sf-color-action));
}

:where(form, fieldset) button:not([class*="sf-"]):focus-visible,
:where(form, fieldset) input[type="submit"]:not([class*="sf-"]):focus-visible,
:where(form, fieldset) input[type="reset"]:not([class*="sf-"]):focus-visible,
:where(form, fieldset) input[type="button"]:not([class*="sf-"]):focus-visible {
  outline: var(--sf-focus-ring-width) var(--sf-focus-ring-style, solid) var(--sf-color-border--focus);
  outline-offset: var(--sf-focus-ring-offset);
}

:where(form, fieldset) button:not([class*="sf-"]):disabled,
:where(form, fieldset) input[type="submit"]:not([class*="sf-"]):disabled,
:where(form, fieldset) input[type="reset"]:not([class*="sf-"]):disabled,
:where(form, fieldset) input[type="button"]:not([class*="sf-"]):disabled {
  opacity: var(--sf-opacity-disabled);
  cursor:  not-allowed;

}

/* Checkbox & radio */
input[type="checkbox"],
input[type="radio"] {
  inline-size:    1.125em;
  block-size:     1.125em;
  accent-color:   var(--sf-color-action);
  cursor:         pointer;
  vertical-align: middle;
}

/* File input */
input[type="file"] {
  cursor: pointer;
}
input[type="file"]::file-selector-button {
  appearance: none;
  margin-inline-end: var(--sf-space-s);
  padding-block: var(--sf-space-xs);
  padding-inline: var(--sf-space-m);
  font-weight: var(--sf-font-weight-heading);
  color: var(--sf-color-text--on-action);
  background-color: var(--sf-color-action);
  border: var(--sf-border-width-1) solid transparent;
  border-radius: var(--sf-radius-m);
  cursor: pointer;
  transition: var(--sf-transition-colors);
}
input[type="file"]::file-selector-button:hover {
  background-color: var(--sf-color-action--hover, var(--sf-color-action));
}

/* Range */
input[type="range"] {
  appearance: none;
  inline-size: 100%;
  background: transparent;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  block-size: var(--sf-border-width-3, 0.25rem);
  background-color: var(--sf-color-border);
  border-radius: var(--sf-radius-full);
}
input[type="range"]::-moz-range-track {
  block-size: var(--sf-border-width-3, 0.25rem);
  background-color: var(--sf-color-border);
  border-radius: var(--sf-radius-full);
}
input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  inline-size: 1em;
  block-size: 1em;
  margin-block-start: calc((var(--sf-border-width-3, 0.25rem) - 1em) / 2);
  background-color: var(--sf-color-action);
  border-radius: var(--sf-radius-full);
}
input[type="range"]::-moz-range-thumb {
  inline-size: 1em;
  block-size: 1em;
  background-color: var(--sf-color-action);
  border: none;
  border-radius: var(--sf-radius-full);
}
input[type="range"]:focus-visible {
  outline: var(--sf-focus-ring-width) var(--sf-focus-ring-style, solid) var(--sf-color-border--focus);
  outline-offset: var(--sf-focus-ring-offset);
}

/* Fieldset & legend */
fieldset {
  border: var(--sf-border-width-1) solid var(--sf-color-border--subtle);
  border-radius: var(--sf-radius-m);
  padding: var(--sf-space-m);
}

legend {
  font-weight: var(--sf-font-weight-heading);
  padding-inline: var(--sf-space-xs);
}

/* Label */
label {
  display: block;
  font-weight: var(--sf-font-weight-heading);
  margin-block-end: var(--sf-space-xs);
  color: var(--sf-field-text-color, inherit);
}

label:has(:required)::after,
label:has(+ :required)::after,
label:has(+ * :required)::after {
  content: var(--sf-field-required-marker, " *");
  color: var(--sf-color-danger);
}

/* ─── core/layout.css ─── */
/* SLASHED — core/layout.css
   @layer slashed.layout
   Layout primitives: section, container, stack, cluster, grid, sidebar, switcher, frame.
   Prefix: .sf-*; local rhythm via --sf-*-gap / sizing tokens.
   SL-005: every @container query in this file hardcodes its breakpoint
   instead of referencing a --sf-* token — var() is not allowed inside an
   @container condition per the CSS spec. This applies framework-wide to
   every @container site below; not re-explained at each one.
   SL-005a: those hardcoded breakpoints are expressed in REM, never em.
   Inside an @container condition a font-relative em resolves against the
   QUERY CONTAINER's own computed font-size — and under SLASHED's default
   fluid type that font-size is inherited from body (a clamp() over 100vw,
   ~16–20px), so an em breakpoint would silently slide with the VIEWPORT and
   break the "responds to its own container width, not the viewport" contract
   (docs/architecture.md). rem resolves against the ROOT (:root/html stays a
   fixed 16px; fluid type is applied to body, not html), so the breakpoint is
   viewport-independent while still honouring user zoom / root-font-size
   preferences. Note this is the OPPOSITE of the @media convention, where em
   and rem both resolve against the root — do not copy "use em for a11y
   breakpoints" from @media into @container. (Matches Tailwind, whose
   container-query breakpoints are rem; cf. issue #497 fluid-cq work.)
   SL-034: a query container cannot be the subject of its own @container
   query — conditions only ever match descendants of the nearest ancestor
   container, never the element that establishes it (self-querying is a
   silent no-op, not an error). Primitives that need to resize THEMSELVES
   (.sf-grid-cols-2/3/4/6, .sf-bento) must therefore rely on an ancestor
   container (.sf-container / .sf-cq / .sf-fluid-cq) rather than declaring
   `container` on themselves. .sf-grid-cols-1-2 etc. already follow this —
   they've never declared their own `container` — so they were unaffected.
   .sf-alternate is a different, unrelated-but-similar-looking pattern: it
   DOES declare `container` on itself, but only to restyle its CHILDREN
   (`.sf-alternate > *`), which is not self-querying and is fine. Not
   re-explained at each site below. */


/* Section */
.sf-section     { padding-block: var(--sf-section-pad); }
.sf-section--xs { --sf-section-pad: var(--sf-section-pad--xs); }
.sf-section--s  { --sf-section-pad: var(--sf-section-pad--s); }
.sf-section--m  { --sf-section-pad: var(--sf-section-pad--m); }
.sf-section--l  { --sf-section-pad: var(--sf-section-pad--l); }
.sf-section--xl { --sf-section-pad: var(--sf-section-pad--xl); }
.sf-section--2xl { --sf-section-pad: var(--sf-section-pad--2xl); }

.sf-section--guttered { padding-inline: var(--sf-gutter); }
.sf-section--guttered .sf-container { padding-inline: 0; }
/* .sf-content-grid carries the gutter in its own edge tracks
   (minmax(gutter,1fr)), not in padding — so unlike .sf-container it can't be
   de-duplicated by zeroing its padding (it has none). Left alone, the
   section's padding boxes the grid in: content ends up double-guttered and
   .sf-full-bleed stops at the padding edge instead of the section edge.
   Drop the section's own padding so the grid spans edge-to-edge and its
   tracks provide the single gutter; full-bleed then reaches the true edge.
   Only when the content grid is the SOLE direct child: dropping the section
   padding is right for the grid but would strip the gutter from any non-grid
   sibling (a heading, a plain block) that legitimately relies on it — and
   those two wants are physically irreconcilable (the gutter *is* the section
   padding that full-bleed must escape). So we restrict to the unambiguous
   case; a mixed section keeps its padding and behaves exactly as before this
   rule existed. Direct-child scope also stops a nested grid deeper in the
   section from stripping the gutter. */
.sf-section--guttered:has(> .sf-content-grid):not(:has(> :not(.sf-content-grid))) {
  padding-inline: 0;
}

.sf-section-group > .sf-section + .sf-section { padding-block-start: 0; }

/* Header — block padding on the standard spacing scale. Headers usually
   want lighter vertical rhythm than .sf-section, so these map to
   --sf-space-* directly. Only block padding is set; any inline gutter
   (from .sf-section--guttered, a container, or the element itself) is
   left intact. */
.sf-header--xs { padding-block: var(--sf-space-xs); }
.sf-header--s  { padding-block: var(--sf-space-s); }
.sf-header--m  { padding-block: var(--sf-space-m); }
.sf-header--l  { padding-block: var(--sf-space-l); }
.sf-header--xl { padding-block: var(--sf-space-xl); }

/* Collapse modifier */
.sf-section--collapse:has(+ .sf-section--collapse) {
  padding-block-end: calc(var(--sf-section-pad) / 2);
}
.sf-section--collapse + .sf-section--collapse {
  padding-block-start: calc(var(--sf-section-pad) / 2);
}

.sf-divider {
  inline-size:        100%;
  border:             none;
  border-block-start: var(--sf-divider-width) var(--sf-divider-style) var(--sf-divider-color);
  margin-block:       var(--sf-divider-gap);
}
.sf-divider--vertical {
  inline-size:          auto;
  border-block-start:   none;
  border-inline-start:  var(--sf-divider-width) var(--sf-divider-style) var(--sf-divider-color);
  margin-block:         0;
  margin-inline:        var(--sf-divider-gap);
  align-self:           stretch;
}

.sf-divider--soft   { --sf-divider-color: var(--sf-color-border--subtle); }
.sf-divider--strong { --sf-divider-color: var(--sf-color-border--strong); }
.sf-divider--dashed { --sf-divider-style: dashed; }
.sf-divider--dotted { --sf-divider-style: dotted; }

.sf-divider--gradient {
  border:        none;
  block-size:    var(--sf-divider-width);
  background:    linear-gradient(to right, transparent, var(--sf-divider-color), transparent);
}

/* Divide — rules between direct children, reusing .sf-divider's tokens.
   Unlike .sf-divider (a standalone separator element), .sf-divide is
   applied to the container and needs no extra markup per gap. */
.sf-divide > * + * {
  border-block-start: var(--sf-divider-width) var(--sf-divider-style) var(--sf-divider-color);
}
.sf-divide--vertical > * + * {
  border-block-start:  none;
  border-inline-start: var(--sf-divider-width) var(--sf-divider-style) var(--sf-divider-color);
}


/* Container */
.sf-container {
  container:      sf-layout / inline-size;
  width:          100%;
  max-width:      var(--sf-container-default);
  margin-inline:  auto;
  padding-inline: var(--sf-gutter);
}
.sf-container--narrow { max-width: var(--sf-container-narrow); }
.sf-container--prose  { max-width: var(--sf-container-prose); }
.sf-container--wide   { max-width: var(--sf-container-wide); }
.sf-container--full   { max-width: var(--sf-container-full); }

/* .sf-cq: CQ scope without .sf-container's max-width/centering.
   Use when a CQ-dependent primitive needs a container ancestor but no layout chrome. */
.sf-cq {
  container-type: inline-size;
}

/* Overlay — absolute-fill layout helper for overlays/backdrops. */
.sf-overlay {
  position: absolute;
  inset:    0;
}


/* .sf-fluid-cq: opt a subtree into a CONTAINER-relative fluid scale (issue
   #497). By default --sf-text-* / --sf-space-* interpolate against 100vw (the
   viewport); inside .sf-fluid-cq they interpolate against 100cqi (this box's
   inline size), so type + spacing track the container the way the layout
   primitives (.sf-grid-cols-*, .sf-content-grid, .sf-bento) already do. Use
   on nested / narrow contexts (sidebars, cards, 1/3 columns) where a
   viewport-driven scale would size text as if it spanned the full page.

   Why the tokens are RE-DECLARED (not just --sf-fluid-width flipped): the
   --sf-text-* / --sf-space-* tokens are computed at :root, so their
   var(--sf-fluid-width) is substituted there (100vw) and the *resolved*
   length inherits down — overriding --sf-fluid-width lower in the tree can't
   retroactively rewrite an already-computed inherited value. The scale is
   therefore re-declared in-scope so var(--sf-fluid-width) re-resolves to
   100cqi. It is re-declared on the direct children, not the element itself:
   a query container's own cq units resolve against its ANCESTOR container, so
   the re-computation must happen one level in — children compute against
   .sf-fluid-cq and deeper descendants inherit that resolved value.

   The formulas below are character-identical to their :root twins in
   core/tokens.css (both read var(--sf-fluid-width)); keep the two copies in
   sync if the generative formula ever changes (cf. themes.css SL-001). */
.sf-fluid-cq { container-type: inline-size; }
.sf-fluid-cq > * {
    --sf-fluid-width: 100cqi;
    --sf-text-2xs: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -3) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -3) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -3) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -3) * 1rem)) * var(--sf-text-scale));
    --sf-text-xs: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -2) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -2) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -2) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -2) * 1rem)) * var(--sf-text-scale));
    --sf-text-s: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -1) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -1) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), -1) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), -1) * 1rem)) * var(--sf-text-scale));
    --sf-text-m: calc(clamp(calc(var(--sf-text-base-min) * 1rem), calc((var(--sf-text-base-max) - var(--sf-text-base-min)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * 1rem), calc(var(--sf-text-base-max) * 1rem)) * var(--sf-text-scale));
    --sf-text-l: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 1) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 1) * 1rem)) * var(--sf-text-scale));
    --sf-text-xl: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 2) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 2) * 1rem)) * var(--sf-text-scale));
    --sf-text-2xl: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 3) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 3) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 3) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 3) * 1rem)) * var(--sf-text-scale));
    --sf-text-3xl: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 4) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 4) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 4)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 4) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 4) * 1rem)) * var(--sf-text-scale));
    --sf-text-4xl: calc(clamp(calc(var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 5) * 1rem), calc((var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 5) - var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 5)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-base-min) * pow(var(--sf-text-ratio-min), 5) * 1rem), calc(var(--sf-text-base-max) * pow(var(--sf-text-ratio-max), 5) * 1rem)) * var(--sf-text-scale));
    --sf-text-display-s: calc(clamp(calc(var(--sf-text-display-base-min) * 1rem), calc((var(--sf-text-display-base-max) - var(--sf-text-display-base-min)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-display-base-min) * 1rem), calc(var(--sf-text-display-base-max) * 1rem)) * var(--sf-text-display-scale));
    --sf-text-display-m: calc(clamp(calc(var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc((var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 1) - var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 1) * 1rem), calc(var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 1) * 1rem)) * var(--sf-text-display-scale));
    --sf-text-display-l: calc(clamp(calc(var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc((var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 2) - var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-text-display-base-min) * pow(var(--sf-text-ratio-min), 2) * 1rem), calc(var(--sf-text-display-base-max) * pow(var(--sf-text-ratio-max), 2) * 1rem)) * var(--sf-text-display-scale));
    --sf-space-2xs: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -3) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -3) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -3) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -3) * 1rem)) * var(--sf-space-scale));
    --sf-space-xs: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) * 1rem)) * var(--sf-space-scale));
    --sf-space-s: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -1) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -1) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -1) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -1) * 1rem)) * var(--sf-space-scale));
    --sf-space-m: calc(clamp(calc(var(--sf-space-base-min) * 1rem), calc((var(--sf-space-base-max) - var(--sf-space-base-min)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * 1rem), calc(var(--sf-space-base-max) * 1rem)) * var(--sf-space-scale));
    --sf-space-l: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 1) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 1) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 1)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 1) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 1) * 1rem)) * var(--sf-space-scale));
    --sf-space-xl: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) * 1rem)) * var(--sf-space-scale));
    --sf-space-2xl: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 3) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 3) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 3)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 3) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 3) * 1rem)) * var(--sf-space-scale));
    --sf-space-3xl: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 4) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 4) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 4)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 4) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 4) * 1rem)) * var(--sf-space-scale));
    --sf-space-4xl: calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 5) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 5) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 5)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 5) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 5) * 1rem)) * var(--sf-space-scale));
}


/* Stack & gap */
.sf-stack {
  display:        flex;
  flex-direction: column;
  gap:            var(--sf-stack-gap);
}
.sf-stack--xs  { gap: var(--sf-space-xs); }
.sf-stack--s   { gap: var(--sf-space-s); }
.sf-stack--m   { gap: var(--sf-space-m); }
.sf-stack--l   { gap: var(--sf-space-l); }
.sf-stack--xl  { gap: var(--sf-space-xl); }
.sf-stack--2xl { gap: var(--sf-space-2xl); }

.sf-stack--center  { align-items: center; }
.sf-stack--end     { align-items: flex-end; }
.sf-stack--stretch { align-items: stretch; }

.sf-gap { gap: var(--sf-gap); }
.sf-gap--xs  { gap: var(--sf-space-xs); }
.sf-gap--s   { gap: var(--sf-space-s); }
.sf-gap--m   { gap: var(--sf-space-m); }
.sf-gap--l   { gap: var(--sf-space-l); }
.sf-gap--xl  { gap: var(--sf-space-xl); }
.sf-gap--2xl { gap: var(--sf-space-2xl); }


/* Box */
.sf-box {
  padding: var(--sf-box-padding);
  outline: var(--sf-box-border-width) solid var(--sf-box-border-color);
}


/* Center */
.sf-center {
  box-sizing:      content-box;
  width:           auto;
  margin-inline:   auto;
  max-inline-size: var(--sf-center-max);
  padding-inline:  var(--sf-center-gutter);
}

.sf-center--intrinsic {
  display:        flex;
  flex-direction: column;
  align-items:    center;
}

/* Place-center — the box-neutral centring primitive. Unlike .sf-center (which
   centres ITSELF as a max-width wrapper) this centres an element's CONTENT on
   both axes, and unlike the flow primitives' --center modifiers it imposes no
   row/column/wrap semantics: a plain element becomes a centring box. It is
   the framework's named form of the community `display:grid; place-items:
   center` idiom (cf. Tailwind's place-items-center). Give the box a size
   (min-block-size / height) for the block-axis centring to have room. */
.sf-place-center {
  display:     grid;
  place-items: center;
}


/* Cluster */
.sf-cluster {
  display:         flex;
  flex-wrap:       wrap;
  gap:             var(--sf-cluster-gap);
  align-items:     var(--sf-cluster-align);
  justify-content: var(--sf-cluster-justify);
}
.sf-cluster--xs      { gap: var(--sf-space-xs); }
.sf-cluster--s       { gap: var(--sf-space-s); }
.sf-cluster--m       { gap: var(--sf-space-m); }
.sf-cluster--l       { gap: var(--sf-space-l); }
.sf-cluster--xl      { gap: var(--sf-space-xl); }
.sf-cluster--2xl     { gap: var(--sf-space-2xl); }

.sf-cluster--no-wrap  { flex-wrap: nowrap; }
.sf-cluster--center   { justify-content: center; }
.sf-cluster--end      { justify-content: flex-end; }
.sf-cluster--between  { justify-content: space-between; }


/* Sidebar */
.sf-sidebar {
  display:   flex;
  flex-wrap: wrap;
  gap:       var(--sf-sidebar-gap);
}

.sf-sidebar > :first-child {
  flex-basis: var(--sf-sidebar-width);
  flex-grow:  1;
}

.sf-sidebar > :last-child {
  flex-basis:      0;
  flex-grow:       999;
  min-inline-size: var(--sf-sidebar-min-width);
}

.sf-sidebar--right > :first-child {
  flex-basis:      0;
  flex-grow:       999;
  min-inline-size: var(--sf-sidebar-min-width);
}

.sf-sidebar--right > :last-child {
  flex-basis: var(--sf-sidebar-width);
  flex-grow:  1;
}

.sf-sidebar--narrow { --sf-sidebar-width: 12rem; }
.sf-sidebar--wide   { --sf-sidebar-width: 26rem; }


/* Switcher */
.sf-switcher {
  display:   flex;
  flex-wrap: wrap;
  gap:       var(--sf-switcher-gap);
}

.sf-switcher > * {
  flex-grow:  1;
  flex-basis: calc((var(--sf-switcher-threshold) - 100%) * 999);
}

.sf-switcher--no-wrap  { flex-wrap: nowrap; overflow-x: auto; overflow-inline: auto; }
.sf-switcher--vertical { flex-direction: column; }


/* Grid & icon */
.sf-grid {
  display:               grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--sf-grid-min), 100%), 1fr));
  gap:                   var(--sf-grid-gap);
}

.sf-grid--fit {
  grid-template-columns: repeat(auto-fit, minmax(min(var(--sf-grid-min), 100%), 1fr));
}

.sf-grid--xs { --sf-grid-min: var(--sf-grid-min-xs); }
.sf-grid--s  { --sf-grid-min: var(--sf-grid-min-s); }
.sf-grid--m  { --sf-grid-min: var(--sf-grid-min-m); }
.sf-grid--l  { --sf-grid-min: var(--sf-grid-min-l); }
.sf-grid--xl { --sf-grid-min: var(--sf-grid-min-xl); }
.sf-grid--2xl { --sf-grid-min: var(--sf-grid-min-2xl); }

.sf-grid--dense { grid-auto-flow: dense; }

/* Grid-flex — flexbox alternative to .sf-grid for item counts that
   don't divide evenly: leftover last-row items stretch to fill the
   row by default, or stay fixed-width and centered with --center.
   CSS Grid keeps orphans pinned to their track; flex does not. */
.sf-grid-flex {
  display:   flex;
  flex-wrap: wrap;
  gap:       var(--sf-grid-gap);
}
.sf-grid-flex > * {
  flex: 1 1 min(var(--sf-grid-min), 100%);
}

.sf-grid-flex--center { justify-content: center; }
.sf-grid-flex--center > * {
  flex-grow:  0;
  flex-basis: min(var(--sf-grid-min), 100%);
}

.sf-grid-flex--xs  { --sf-grid-min: var(--sf-grid-min-xs); }
.sf-grid-flex--s   { --sf-grid-min: var(--sf-grid-min-s); }
.sf-grid-flex--m   { --sf-grid-min: var(--sf-grid-min-m); }
.sf-grid-flex--l   { --sf-grid-min: var(--sf-grid-min-l); }
.sf-grid-flex--xl  { --sf-grid-min: var(--sf-grid-min-xl); }
.sf-grid-flex--2xl { --sf-grid-min: var(--sf-grid-min-2xl); }

.sf-icon {
  display:        inline-block;
  inline-size:    var(--sf-icon-size, var(--sf-icon-m));
  block-size:     var(--sf-icon-size, var(--sf-icon-m));
  flex-shrink:    0;
  vertical-align: text-bottom;
  fill:           currentcolor;
}
.sf-icon--xs  { --sf-icon-size: var(--sf-icon-xs); }
.sf-icon--s   { --sf-icon-size: var(--sf-icon-s); }
.sf-icon--m   { --sf-icon-size: var(--sf-icon-m); }
.sf-icon--l   { --sf-icon-size: var(--sf-icon-l); }
.sf-icon--xl  { --sf-icon-size: var(--sf-icon-xl); }
.sf-icon--2xl { --sf-icon-size: var(--sf-icon-2xl); }

.sf-icon--boxed {
  inline-size:    calc(var(--sf-icon-size, var(--sf-icon-m)) + var(--sf-icon-box-pad) * 2);
  block-size:     calc(var(--sf-icon-size, var(--sf-icon-m)) + var(--sf-icon-box-pad) * 2);
  padding:        var(--sf-icon-box-pad);
  border:         var(--sf-icon-box-border);
  border-radius:  var(--sf-icon-box-radius);
  background:     var(--sf-icon-box-bg);
  box-sizing:     border-box;
}


/* Cover */
.sf-cover {
  display:        flex;
  flex-direction: column;
  min-height:     var(--sf-cover-min-height);
  padding-block:  var(--sf-cover-padding);
}

.sf-cover > .sf-cover__center {
  margin-block: auto;
}

.sf-cover--min { min-height: 50dvh; }

.sf-cover--max { min-height: 0; max-block-size: 100dvh; }
.sf-cover--padding-s { padding-block: var(--sf-space-xl); }
.sf-cover--padding-l { padding-block: var(--sf-space-4xl); }


/* Frame */
.sf-frame {
  aspect-ratio:    var(--sf-frame-ratio);
  overflow:        hidden;
  display:         flex;
  justify-content: center;
  align-items:     center;
}

.sf-frame > img,
.sf-frame > video {
  inline-size: 100%;
  block-size:  100%;
  object-fit:  cover;
}

.sf-frame--square   { aspect-ratio: var(--sf-ratio-square); }
.sf-frame--video    { aspect-ratio: var(--sf-ratio-video); }
.sf-frame--cinema   { aspect-ratio: var(--sf-ratio-cinema); }
.sf-frame--portrait { aspect-ratio: var(--sf-ratio-portrait); }
.sf-frame--4-3      { aspect-ratio: var(--sf-ratio-4-3); }
.sf-frame--3-2      { aspect-ratio: var(--sf-ratio-3-2); }
.sf-frame--golden   { aspect-ratio: var(--sf-ratio-golden); }


/* Background layer — absolutely-positioned cover media that sits behind a
   parent's content. The parent is auto-promoted to a positioned, isolated
   stacking context via :has(), so .sf-bg-layer composes natively under .sf-scrim
   (background → scrim gradient → content). Works as a bare <img class="sf-bg-layer">
   or as a wrapper with a nested > img / > video. */
:where(:has(> .sf-bg-layer)) {
  position:  relative;
  isolation: isolate;
}

.sf-bg-layer {
  position:        absolute;
  inset:           var(--sf-bg-layer-inset);
  inline-size:     calc(100% - 2 * var(--sf-bg-layer-inset));
  block-size:      calc(100% - 2 * var(--sf-bg-layer-inset));
  object-fit:      var(--sf-bg-layer-fit);
  object-position: var(--sf-bg-layer-position);
  border-radius:   var(--sf-bg-layer-radius);
  z-index:         var(--sf-bg-layer-z);
  pointer-events:  none;
}

.sf-bg-layer > :is(img, video, picture) {
  inline-size:     100%;
  block-size:      100%;
  object-fit:      var(--sf-bg-layer-fit);
  object-position: var(--sf-bg-layer-position);
  border-radius:   var(--sf-bg-layer-radius);
  pointer-events:  none;
}

.sf-bg-layer > picture > img {
  inline-size:     100%;
  block-size:      100%;
  object-fit:      var(--sf-bg-layer-fit);
  object-position: var(--sf-bg-layer-position);
  border-radius:   var(--sf-bg-layer-radius);
  pointer-events:  none;
}


.sf-reel {
  display:               flex;
  block-size:            var(--sf-reel-height);
  gap:                   var(--sf-reel-gap);
  overflow-x:                 auto;
  overflow-inline:            auto;
  scroll-snap-type:           inline mandatory;
  scroll-padding-inline:      var(--sf-reel-gap);
  overscroll-behavior-inline: contain;
  scrollbar-width:       thin;
  scrollbar-color:       var(--sf-scrollbar-thumb) var(--sf-scrollbar-track);
}

.sf-reel::-webkit-scrollbar        { block-size: 6px; }
.sf-reel::-webkit-scrollbar-track  { background: var(--sf-scrollbar-track); border-radius: var(--sf-radius-full); }
.sf-reel::-webkit-scrollbar-thumb  { background: var(--sf-scrollbar-thumb); border-radius: var(--sf-radius-full); }

.sf-reel > * {
  flex:              0 0 var(--sf-reel-item-width);
  scroll-snap-align: start;
}


/* Physical left (not inset-inline-start): translate() uses the physical axis,
   so logical + translate breaks centering in RTL layouts. */
.sf-imposter {
  position:          absolute;
  inset-block-start: 50%;
  left:              50%;
  translate:         -50% -50%;
}

.sf-imposter--fixed {
  position: fixed;
}

.sf-imposter--contain {
  max-inline-size: calc(100% - var(--sf-imposter-margin) * 2);
  max-block-size:  calc(100% - var(--sf-imposter-margin) * 2);
  overflow:        auto;
}


.sf-alternate {
  display:   grid;
  gap:       var(--sf-alternate-gap);
  container: sf-alternate / inline-size;
}

.sf-alternate > * {
  display:     grid;
  gap:         var(--sf-alternate-inner-gap);
  align-items: center;
}

@container sf-alternate (min-width: 48rem) {
  .sf-alternate > * {
    grid-template-columns: 1fr 1fr;
  }
  .sf-alternate > :nth-child(even) > :first-child { order: 2; }
  .sf-alternate > :nth-child(even) > :last-child  { order: 1; }
}


.sf-pancake {
  display:             grid;
  grid-template-rows:  auto 1fr auto;
  min-height:          100dvh;
}


.sf-grid-cols-1 {
  display: grid;
  gap:     var(--sf-grid-gap);
}


.sf-grid-cols-2, .sf-grid-cols-3, .sf-grid-cols-4, .sf-grid-cols-6 {
  display: grid;
  gap:     var(--sf-grid-gap);
}

@container (min-width: 30rem) {
  .sf-grid-cols-4 { grid-template-columns: repeat(2, 1fr); }
  .sf-grid-cols-6 { grid-template-columns: repeat(3, 1fr); }
}

@container (min-width: 48rem) {
  .sf-grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
  .sf-grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
  .sf-grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
  .sf-grid-cols-6 { grid-template-columns: repeat(6, 1fr); }
}


.sf-grid-cols-1-2, .sf-grid-cols-2-1,
.sf-grid-cols-1-3, .sf-grid-cols-3-1 {
  display: grid;
  gap:     var(--sf-grid-gap);
}

@container (min-width: 48rem) {
  .sf-grid-cols-1-2 { grid-template-columns: 1fr 2fr; }
  .sf-grid-cols-2-1 { grid-template-columns: 2fr 1fr; }
  .sf-grid-cols-1-3 { grid-template-columns: 1fr 3fr; }
  .sf-grid-cols-3-1 { grid-template-columns: 3fr 1fr; }
}

.sf-equal {
  column-width: var(--sf-equal-min-col);
  column-gap:   var(--sf-equal-gap);
  column-rule:  var(--sf-equal-rule-width) var(--sf-equal-rule-style) var(--sf-equal-rule-color);
}
.sf-equal > * { break-inside: avoid; }

.sf-equal--2 { column-count: 2; }
.sf-equal--3 { column-count: 3; }
.sf-equal--4 { column-count: 4; }
.sf-equal--6 { column-count: 6; }


.sf-content-grid {
  display: grid;
  /* Establish the same NAMED inline-size query container as .sf-container
     (`sf-layout`), not just an anonymous one. .sf-content-grid is a
     section-level wrapper that REPLACES .sf-container when you need
     breakout/full-bleed — but .sf-container carries the CQ scope that
     .sf-grid-cols-* / .sf-bento rely on, and dropping it silently killed
     those primitives inside a content grid. Matching the name too means a
     user's `@container sf-layout (…)` rule keeps matching across the swap, so
     the two wrappers are genuinely interchangeable (anonymous
     `@container (…)` queries already matched either — the framework's own
     primitives use those — but a name-targeted query would not have).
     Safe re SL-034: .sf-content-grid's own tracks are fixed (they never
     @container-query themselves), so becoming a query container can't create
     a self-query. Safe re full-bleed: inline-size containment doesn't change
     track resolution — the `full` track still reaches the wrapper's edges. */
  container: sf-layout / inline-size;
  grid-template-columns:
    [full-start]
    minmax(var(--sf-gutter), 1fr)
    [breakout-start]
    minmax(0, calc((var(--sf-breakout-width) - var(--sf-content-width)) / 2))
    [content-start]
    min(var(--sf-content-width), 100% - var(--sf-gutter) * 2)
    [content-end]
    minmax(0, calc((var(--sf-breakout-width) - var(--sf-content-width)) / 2))
    [breakout-end]
    minmax(var(--sf-gutter), 1fr)
    [full-end];
}

.sf-content-grid > *              { grid-column: content; }
.sf-content-grid > .sf-breakout   { grid-column: breakout; }
.sf-content-grid > .sf-full-bleed { grid-column: full; }

.sf-bento {
  display:               grid;
  grid-template-columns: repeat(var(--sf-bento-cols, var(--sf-bento-cols-default)), 1fr);
  grid-auto-rows:        minmax(var(--sf-bento-row, var(--sf-bento-row-default)), auto);
  grid-auto-flow:        dense;
  gap:                   var(--sf-bento-gap);
}

@container (max-width: 29.99rem) {
  .sf-bento { grid-template-columns: 1fr; }
}

@container (min-width: 30rem) and (max-width: 47.99rem) {
  .sf-bento { grid-template-columns: repeat(2, 1fr); }
}

/* Container column modifiers */
.sf-bento--2       { --sf-bento-cols: 2; }
.sf-bento--3       { --sf-bento-cols: 3; }
.sf-bento--6       { --sf-bento-cols: 6; }

/* Container row height modifiers — named after the --sf-bento-row-* token
   they set, not just "tall"/"compact", so they can't be mistaken for the
   child span modifier below. */
.sf-bento--row-compact { --sf-bento-row: var(--sf-bento-row-compact); }
.sf-bento--row-tall    { --sf-bento-row: var(--sf-bento-row-tall); }

/* Child span modifiers — go on an item inside .sf-bento, never on
   .sf-bento itself. .sf-bento-tall (this item spans 2 rows) is not an
   alias of .sf-bento--row-tall above (every row in the grid is taller). */
.sf-bento-wide     { grid-column: span 2; }
.sf-bento-full     { grid-column: 1 / -1; }
.sf-bento-tall     { grid-row:    span 2; }
.sf-bento-featured { grid-column: span 2; grid-row: span 2; }

/* A spanning child can't request more columns than the 1-column mobile
   grid has, or CSS Grid adds an implicit column to satisfy the span,
   breaking the single-column layout the query above exists to guarantee.
   Placed after the modifiers above (equal specificity) so it wins on
   source order regardless of query-match state. */
@container (max-width: 29.99rem) {
  .sf-bento-wide, .sf-bento-featured { grid-column: span 1; }
}


.sf-subgrid      { display: grid; grid-template-columns: subgrid; }
.sf-subgrid-rows { display: grid; grid-template-rows:    subgrid; }

/* ─── core/macros.css ─── */
/* SLASHED — core/macros.css
   @layer slashed.macros
   Reusable recipes: prose, surface, flow, cards, media, hero, scrims, clamps.
   Prefix: .sf-*; values resolve through macro tokens. */



.sf-prose {
  max-width:     var(--sf-container-prose);
  overflow-wrap: break-word;
}

.sf-prose > * + *              { margin-block-start: var(--sf-prose-paragraph); }
.sf-prose :is(h2, h3, h4, h5, h6) + * { margin-block-start: var(--sf-prose-heading-gap); }
.sf-prose li + li              { margin-block-start: var(--sf-prose-list-gap); }
.sf-prose :is(blockquote, pre) { margin-block: var(--sf-prose-block-margin); }
.sf-prose blockquote           { font-style: italic; padding-inline-start: var(--sf-prose-blockquote-padding); border-inline-start: var(--sf-prose-blockquote-border); }
.sf-prose img                  { margin-block: var(--sf-prose-media-margin); border-radius: var(--sf-prose-media-radius); }
.sf-prose :is(ul, ol)          { padding-inline-start: 1.25em; }
.sf-prose > :is(ul, ol)        { margin-block-end: 0; }
.sf-prose :is(ul, ol) :is(ul, ol) { margin-block-start: var(--sf-prose-nested-list-gap); }
.sf-prose ul                   { list-style: disc; }
.sf-prose ol                   { list-style: decimal; }
.sf-prose ::marker             { color: var(--sf-prose-marker-color); }
.sf-prose hr                   { margin-block: var(--sf-prose-hr-margin); }
.sf-prose figure               { margin-block: var(--sf-prose-figure-margin); }
.sf-prose figcaption           { font-size: var(--sf-prose-figcaption-size); color: var(--sf-color-text--muted); margin-block-start: var(--sf-prose-list-gap); }
.sf-prose table                { margin-block: var(--sf-prose-block-margin); display: block; overflow-x: auto; overflow-inline: auto; inline-size: 100%; max-inline-size: 100%; }
.sf-prose :is(th, td)          { padding: var(--sf-prose-table-pad); }
.sf-prose video                { margin-block: var(--sf-prose-media-margin); border-radius: var(--sf-prose-media-radius); }

/* Specificity 0-2-1 intentionally beats .sf-prose's 0-2-0 child selectors —
   documented exception. */
.sf-prose .sf-not-prose > * + *    { margin-block-start: 0; }
.sf-prose .sf-not-prose li + li    { margin-block-start: 0; }

.sf-prose .sf-not-prose :is(ul,ol) { list-style: revert; padding-inline-start: revert; }
.sf-prose .sf-not-prose ::marker   { color: inherit; }
.sf-prose .sf-not-prose blockquote {
  margin-block: 0;
  padding-inline-start: 0;
  border-inline-start: 0;
  font-style: normal;
}
.sf-prose .sf-not-prose hr         { margin-block: 0; }
.sf-prose .sf-not-prose img        { margin-block: 0; border-radius: 0; }
.sf-prose .sf-not-prose figure     { margin-block: 0; }
.sf-prose .sf-not-prose figcaption { font-size: inherit; color: inherit; margin-block-start: 0; }
.sf-prose .sf-not-prose table      { margin-block: 0; display: revert; overflow-x: revert; overflow-inline: revert; inline-size: revert; max-inline-size: revert; }
.sf-prose .sf-not-prose :is(th,td) { padding: revert; }
.sf-prose .sf-not-prose video      { margin-block: 0; border-radius: 0; }

.sf-flow > * + * {
  margin-block-start: var(--sf-flow-space);
}

.sf-truncate {
  overflow:      hidden;
  text-overflow: ellipsis;
  white-space:   nowrap;
}

.sf-line-clamp-2,
.sf-line-clamp-3,
.sf-line-clamp-N {
  display:            -webkit-box;
  /* stylelint-disable-next-line property-no-vendor-prefix -- de-facto standard; unprefixed CSS Overflow 4 line-clamp not yet shipped */
  -webkit-box-orient: vertical;
  overflow:           hidden;
}

/* stylelint-disable-next-line property-no-vendor-prefix -- see above */
.sf-line-clamp-2 { -webkit-line-clamp: 2; }
/* stylelint-disable-next-line property-no-vendor-prefix -- see above */
.sf-line-clamp-3 { -webkit-line-clamp: 3; }
.sf-line-clamp-N {
  /* stylelint-disable-next-line property-no-vendor-prefix -- see above */
  -webkit-line-clamp: var(--sf-line-clamp);
}

.sf-equal-height {
  display:     flex;
  align-items: stretch;
}
.sf-equal-height > * {
  block-size: auto;
  flex:       1 1 auto;
}

.sf-aspect {
  aspect-ratio: var(--sf-aspect, 16 / 9);
}

.sf-scroll-shadow {
  overflow-y:     auto;
  overflow-block: auto;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image:
    linear-gradient(to bottom,
      transparent 0,
      black var(--sf-scroll-shadow-size),
      black calc(100% - var(--sf-scroll-shadow-size)),
      transparent 100%);
  mask-image:
    linear-gradient(to bottom,
      transparent 0,
      black var(--sf-scroll-shadow-size),
      black calc(100% - var(--sf-scroll-shadow-size)),
      transparent 100%);
}

.sf-scroll-snap {
  overflow-y:                auto;
  overflow-block:            auto;
  scroll-snap-type:          block mandatory;
  overscroll-behavior-block: contain;
}
.sf-scroll-snap > * {
  scroll-snap-align: start;
}

.sf-overflow-fade {
  overflow: hidden;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image: linear-gradient(
    to right,
    black 0,
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    black 0,
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
}

.sf-overflow-fade--right {
  overflow: hidden;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image: linear-gradient(
    to right,
    black 0,
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    black 0,
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
}

.sf-overflow-fade--left {
  overflow: hidden;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black 100%
  );
}

.sf-overflow-fade--top {
  overflow: hidden;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black 100%
  );
}

.sf-overflow-fade--bottom {
  overflow: hidden;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image: linear-gradient(
    to bottom,
    black 0,
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    black 0,
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
}

.sf-overflow-fade--block {
  overflow: hidden;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
}

.sf-overflow-fade--inline {
  overflow: hidden;
  /* stylelint-disable-next-line property-no-vendor-prefix -- required for Safari < 15.4 */
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0,
    black var(--sf-mask-scrim-start),
    black calc(100% - var(--sf-mask-scrim-end)),
    transparent 100%
  );
}

.sf-no-tap-highlight {
  /* stylelint-disable-next-line property-no-vendor-prefix -- no unprefixed equivalent */
  -webkit-tap-highlight-color: transparent;
}

.sf-surface { background: var(--sf-surface-color); }

.sf-surface--primary   { --sf-surface-color: var(--sf-color-primary);   background: var(--sf-color-primary);   color: var(--sf-color-text--on-primary); }
.sf-surface--secondary { --sf-surface-color: var(--sf-color-secondary); background: var(--sf-color-secondary); color: var(--sf-color-text--on-secondary); }
.sf-surface--tertiary  { --sf-surface-color: var(--sf-color-tertiary);  background: var(--sf-color-tertiary);  color: var(--sf-color-text--on-tertiary); }
.sf-surface--action    { --sf-surface-color: var(--sf-color-action);    background: var(--sf-color-action);    color: var(--sf-color-text--on-action); }
.sf-surface--neutral   { --sf-surface-color: var(--sf-color-neutral);   background: var(--sf-color-neutral);   color: var(--sf-color-text--on-neutral); }
.sf-surface--inverse   { --sf-surface-color: var(--sf-color-inverse);   background: var(--sf-color-inverse);   color: var(--sf-color-text--on-inverse); }
.sf-surface--success   { --sf-surface-color: var(--sf-color-success);   background: var(--sf-color-success);   color: var(--sf-color-text--on-success); }
.sf-surface--warning   { --sf-surface-color: var(--sf-color-warning);   background: var(--sf-color-warning);   color: var(--sf-color-text--on-warning); }
.sf-surface--info      { --sf-surface-color: var(--sf-color-info);      background: var(--sf-color-info);      color: var(--sf-color-text--on-info); }
.sf-surface--danger    { --sf-surface-color: var(--sf-color-danger);    background: var(--sf-color-danger);    color: var(--sf-color-text--on-danger); }

@supports (color: oklch(from red l c h)) and (width: calc(1px * sign(1))) {
  :is(.sf-surface,
      .sf-surface--primary, .sf-surface--secondary, .sf-surface--tertiary,
      .sf-surface--action, .sf-surface--neutral, .sf-surface--inverse,
      .sf-surface--success, .sf-surface--warning,
      .sf-surface--info, .sf-surface--danger) {

    --sf-surface-contrast: oklch(from var(--sf-surface-color) clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0 / 1);
    background: var(--sf-surface-color);
    color: var(--sf-surface-contrast);
    --sf-color-text:              var(--sf-surface-contrast);
    --sf-color-heading:           var(--sf-surface-contrast);
    --sf-color-link:              oklch(from var(--sf-surface-contrast) l calc(c + 0.08) h);
    --sf-color-link--hover:       var(--sf-surface-contrast);
    --sf-color-link--underline:   oklch(from var(--sf-surface-contrast) l c h / 0.5);
    --sf-color-text--subtle:   oklch(from var(--sf-surface-contrast) l c h / 0.70);
    --sf-color-text--placeholder: oklch(from var(--sf-surface-contrast) l c h / 0.45);
    --sf-color-text--disabled:    oklch(from var(--sf-surface-contrast) l c h / 0.30);
    --sf-color-border:            oklch(from var(--sf-surface-contrast) l c h / 0.20);
    --sf-color-border--subtle:    oklch(from var(--sf-surface-contrast) l c h / 0.12);
    --sf-color-border--strong:    oklch(from var(--sf-surface-contrast) l c h / 0.35);
    --sf-focus-ring-color:        var(--sf-surface-contrast);
    --sf-caret-color:             var(--sf-surface-contrast);

    --sf-shadow-color:            oklch(from var(--sf-surface-color) var(--sf-shadow-lightness) c h);

    /* :root-only alias-overrides seal their value at the DECLARING element
       (var() resolves on :root), so a heading/body that reads the alias
       reads the frozen page value and ignores this surface's re-declared
       semantic token above — leaving headings dark on a dark surface.
       Re-declare the aliases here so they re-resolve against THIS surface's
       adapted --sf-color-heading / --sf-color-text. Same class of bug as the
       [data-theme] block in themes.css (#496), extended to heading/body. */
    --sf-heading-color:           var(--sf-color-heading);
    --sf-body-color:              var(--sf-color-text);

    /* INTERNAL flag (inherits): signals "inside an auto-contrast surface" so a
       nested .sf-card can restore surface-appropriate foreground for its own
       (non-adapting) background via a style query — see .sf-card in
       optional/components.css (#496). Not a public hook; read, don't set. */
    --sf-surface-active:          1;
  }
}
.sf-scrim {
  position:  relative;
  isolation: isolate;
}
.sf-scrim::before {
  content:        "";
  position:       absolute;
  inset:          0;
  z-index:        0;
  background:     var(--sf-scrim-gradient);
  pointer-events: none;
}

.sf-scrim > :not(img, picture, video, svg, canvas),
.sf-scrim__content {
  position: relative;
  z-index:  1;
}
.sf-scrim--top    { --sf-scrim-direction: to bottom; }
.sf-scrim--bottom { --sf-scrim-direction: to top; }
.sf-scrim--full   { --sf-scrim-gradient: var(--sf-scrim-color); }

/* Named background surface preset (issue #523). Composes the
   --sf-surface-bg-* token set (see tokens.macros.css) into one reusable,
   named background: base colour + image/gradient/pattern, sizing, an
   optional overlay layered above the image, and an optional animation.
   Inert until a preset feeds the tokens. The overlay is the first
   background-image layer so it paints ABOVE the image. */
.sf-surface-bg {
  background-color:      var(--sf-surface-bg-color);
  background-image:      var(--sf-surface-bg-overlay), var(--sf-surface-bg-image);
  background-size:       var(--sf-surface-bg-size);
  background-position:   var(--sf-surface-bg-position);
  background-repeat:     var(--sf-surface-bg-repeat);
  background-attachment: var(--sf-surface-bg-attachment);
  animation:             var(--sf-surface-bg-animation);
}

.sf-text-protect {
  text-shadow: var(--sf-scrim-text-shadow);
}

.sf-text-gradient {
  background-image: var(--sf-gradient-primary);
  background-clip: text;
  color: transparent;
}

.sf-link-external::after {
  content:         var(--sf-link-external-marker);
  display:         inline-block;
  font-size:       0.85em;
  text-decoration: none;
}
/* Alt-text form (accessible name for the glyph) — gated so a UA that
   rejects the "value / string" grammar keeps the plain marker above
   instead of dropping the whole declaration. */
@supports (content: "x" / "y") {
  .sf-link-external::after {
    content: var(--sf-link-external-marker) / var(--sf-link-external-label);
  }
}

.sf-content-auto {
  content-visibility:     auto;
  contain-intrinsic-size: auto var(--sf-content-intrinsic-size, 500px);
}

.sf-tabular-nums {
  font-variant-numeric: var(--sf-font-numeric, tabular-nums);
}

/* filter: drop-shadow() utilities — unlike box-shadow, follows the element's
   actual alpha shape (SVG icons, PNG cutouts, transparent logos). */
.sf-drop-shadow-xs { filter: var(--sf-drop-shadow-xs); }
.sf-drop-shadow-s  { filter: var(--sf-drop-shadow-s); }
.sf-drop-shadow-m  { filter: var(--sf-drop-shadow-m); }
.sf-drop-shadow-l  { filter: var(--sf-drop-shadow-l); }
.sf-drop-shadow-xl { filter: var(--sf-drop-shadow-xl); }

.sf-link--subtle:link,
.sf-link--subtle:visited {
  text-decoration-line: none;
}
.sf-link--subtle:is(:link, :visited):hover,
.sf-link--subtle:is(:link, :visited):focus-visible {
  text-decoration-line:  underline;
  text-decoration-color: currentcolor;
}

.sf-link--reverse:link,
.sf-link--reverse:visited {
  text-decoration-line: underline;
}
.sf-link--reverse:is(:link, :visited):hover,
.sf-link--reverse:is(:link, :visited):focus-visible {
  text-decoration-line: none;
}

/* Overlap recipe — one element intentionally overlapping the previous
   one (e.g. an image pulled up over the card below it), via a negative
   logical margin + a raised stacking context. Directional variants are
   standalone (margins are distinct properties, not a re-pointable knob). */
.sf-overlap,
.sf-overlap--down {
  position: relative;
  z-index:  var(--sf-z-raised);
}
.sf-overlap        { margin-block-start:  calc(-1 * var(--sf-overlap-pull)); }
.sf-overlap--down  { margin-block-end:    calc(-1 * var(--sf-overlap-pull)); }

/* Card-container recipe — the receiving side of an overlap: an isolated
   stacking context with block-start padding compensation so in-flow
   content clears an element intruding from above. The padding knob
   defaults to the pull knob so one override tunes both. */
.sf-overlap-host {
  position:  relative;
  isolation: isolate;
  /* var() fallback resolved on this element, not pre-baked at :root —
     so a local --sf-overlap-pull override (even without setting
     --sf-overlap-host-pad) is honored (#496-class pitfall). */
  padding-block-start: var(--sf-overlap-host-pad, var(--sf-overlap-pull));
}

/* ─── core/states.css ─── */
/* SLASHED — core/states.css
   @layer slashed.states
   Generic runtime states toggled by JS or ARIA: visibility, interactivity, selection, busy/current.
   Prefix: .sf-is-*; not utilities or component-specific variants.

   Reach for a native mechanism first — a pseudo-class (:disabled, :read-only,
   :empty, :user-invalid), a boolean/enumerated attribute ([hidden], [disabled],
   [readonly]), or an ARIA state ([aria-busy], [aria-current], [aria-expanded],
   [aria-selected], [aria-pressed]) — since the browser (and assistive tech)
   already understands it for free. Add a .sf-is-* class only for states with
   no native equivalent, or ones this file's own tokens/consumers need as a
   hook (see docs/states.md). See docs/states.md § "Prefer native state" for
   the full rationale and the [hidden] precedent that got .sf-is-hidden
   removed in favour of the attribute it duplicated. */


/* No visibility classes here: .sf-is-hidden duplicated [hidden] (removed —
   core/reset.css hardens that attribute identically). .sf-invisible /
   .sf-visible moved to optional/utilities.css as single-property helpers
   (same call as Bulma's is-hidden/is-invisible living under "helpers",
   not component/state docs) — still the hook docs/roadmap.md's
   reveal-on-scroll pattern targets, just renamed and relocated. */

/* -- INTERACTIVITY --
   No .sf-is-readonly here: it duplicated :read-only for real form controls,
   and its pointer-events:none blocked text selection that a genuinely
   read-only field should still allow — reach for :read-only, or
   [aria-readonly] on a non-native widget, instead. */

/* cursor: not-allowed inherits to children with pointer-events: auto —
   intentional, part of the .sf-is-disabled API surface. */
.sf-is-disabled {
  opacity:        var(--sf-opacity-disabled);
  pointer-events: none;

  cursor:         not-allowed;
  user-select:    none;
}

/* -- LOADING / ASYNC FEEDBACK -- */

.sf-is-loading {
  color:          transparent !important;
  pointer-events: none;
  position:       relative;
}

.sf-is-loading > * {
  opacity: 0 !important;
}

.sf-is-loading::after {
  content:             "";
  position:            absolute;
  inset-block-start:   50%;
  inset-inline-start:  50%;
  inline-size:         1em;
  block-size:          1em;
  margin-block-start:  -0.5em;
  margin-inline-start: -0.5em;
  border:        var(--sf-border-width-2) solid var(--sf-color-border--strong);
  border-block-start-color: var(--sf-color-action);
  border-radius: var(--sf-radius-full);

  animation: var(--sf-animation-spin);
}

/* No .sf-is-busy / .sf-is-pending here: both were a couple of
   property-value declarations (cursor:progress, plus opacity for
   pending's "optimistic UI" dimming) with no consumer and no native gap
   they filled — [aria-busy="true"] covers the semantic, and the visual
   is two lines to hand-roll in your own component CSS if you want it. */

/* Shimmer loading placeholder (was .sf-is-skeleton pre-Unreleased; renamed so
   "skeleton" names only the planned .sf-skeleton component, not a state —
   see docs/migration.md and roadmap.md #575). The class name now mirrors its
   driving token, --sf-animation-shimmer. */
img.sf-is-shimmer,
.sf-is-shimmer img {
  opacity: 0 !important;
}

.sf-is-shimmer {
  color:           transparent !important;
  background:      linear-gradient(
                     90deg,
                     var(--sf-color-inset) 25%,
                     var(--sf-color-raised) 50%,
                     var(--sf-color-inset) 75%
                   );
  background-size: 200% 100%;
  animation: var(--sf-animation-shimmer);
  border-radius:   var(--sf-radius-s);
  pointer-events:  none;
  user-select:     none;
}

/* -- SELECTED / HIGHLIGHTED --
   No .sf-is-active / .sf-is-open / .sf-is-collapsed / .sf-is-expanded /
   .sf-is-current / .sf-is-pressed here: these only ever set an
   inheritable --sf-is-* custom property for a hypothetical consumer
   calc() to branch on, with zero consumer anywhere in this codebase or
   its demo. Speculative "might be useful one day" surface, removed —
   see docs/states.md § "Prefer native state". Reach for [aria-current],
   [aria-expanded], [aria-pressed] as the styling hook instead; .sf-is-
   selected / .sf-is-highlighted below cover the two cases (selection,
   transient emphasis) that still need their own visual. */

.sf-is-selected {
  background-color: var(--sf-color-bg--selected);
}

.sf-is-highlighted {
  background-color: var(--sf-color-bg--focus);
}

/* -- VALIDATION / FEEDBACK --
   No .sf-is-danger here: identical implementation to .sf-is-invalid/
   .sf-is-error (both only ever wrote --sf-field-*, so it never worked as
   the "destructive-action context" its own docs described outside a form
   field) and, per docs/migration.md, visual variants belong in your own
   component CSS anyway. Use .sf-is-invalid/.sf-is-error, or your own
   `--danger` variant class, instead. */

.sf-is-valid,
.sf-is-success {
  --sf-field-border-color: var(--sf-color-success);
  --sf-field-text-color:   var(--sf-color-success-strong);
}

.sf-is-invalid,
.sf-is-error {
  --sf-field-border-color: var(--sf-color-danger);
  --sf-field-text-color:   var(--sf-color-danger-strong);
}

.sf-is-warning {
  --sf-field-border-color: var(--sf-color-warning);
  --sf-field-text-color:   var(--sf-color-warning-strong);
}

.sf-is-info {
  --sf-field-border-color: var(--sf-color-info);
  --sf-field-text-color:   var(--sf-color-info-strong);
}

/* -- DRAG & DROP -- */

.sf-is-dragging {
  opacity: var(--sf-opacity-muted);
  cursor:  grabbing;
}

.sf-is-drop-target {
  outline:        var(--sf-focus-ring-width) dashed var(--sf-color-action);
  outline-offset: var(--sf-focus-ring-offset);
}

.sf-is-draggable {
  cursor: grab;
}

/* -- EMPTY STATE -- */

.sf-is-empty:empty {
  display: none;
}

/* KEYFRAMES (used by states above) */

/* ─── core/themes.css ─── */
/* SLASHED — core/themes.css
   @layer slashed.themes
   Light/dark activation, scoped theme color inheritance, LumLocker, and scheme-aware color adjustments.
   Uses data-theme, .sf-theme-light/.sf-theme-dark aliases, and token overrides. */


@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    color-scheme: dark;
    --sf-is-dark: 1;
  }
}

:is([data-theme="light"], .sf-theme-light) { color-scheme: light; --sf-is-dark: 0; }
:is([data-theme="dark"], .sf-theme-dark)  { color-scheme: dark;  --sf-is-dark: 1; }

:where([data-theme]:not(:root, html), .sf-theme-light:not(:root, html), .sf-theme-dark:not(:root, html)) {
  background-color: var(--sf-color-bg);
  color:            var(--sf-color-text);

  /* Component-layer and :root-only alias tokens that merely alias a themed
     core token (no light/dark branching of their own, e.g. --sf-card-bg:
     var(--sf-color-surface)) are declared once in :root (tokens.css /
     optional/tokens.components.css). Without a redeclaration here, that
     var() substitutes using :root's OWN --sf-color-surface/-heading/-text
     at computed-value time and the result inherits down frozen — a nested
     data-theme section further in never gets a chance to re-substitute it,
     breaking the "data-theme works on any element" guarantee
     (docs/theming.md) for .sf-card and for heading/body colour (#496).
     Re-declaring the aliases here, at the same non-root scope, forces a
     fresh substitution against this section's own (possibly overridden)
     --sf-color-surface / -border / -heading / -text. */
  --sf-card-bg:           var(--sf-color-surface);
  --sf-card-border-color: var(--sf-color-border);
  --sf-heading-color:     var(--sf-color-heading);
  --sf-body-color:        var(--sf-color-text);
}

/* LumLocker: locks 4 brand colors to a fixed OKLCH L — --sf-lumlocker in
   light, --sf-lumlocker-dark (default calc(1.18 - L), the mirror around the
   light/dark background midpoint) in dark, so contrast against the surface
   holds in both themes. Neutral/base excluded — forcing them to a brand L
   breaks their semantic role. */
@supports (color: oklch(from red l c h)) {
:root[data-lumlocker] {
  --sf-color-primary: light-dark(
    oklch(from var(--sf-color-primary-source-light) var(--sf-lumlocker) c h),
    oklch(from var(--sf-color-primary-source-dark, var(--sf-color-primary-source-light)) var(--sf-lumlocker-dark) c h)
  );
  --sf-color-secondary: light-dark(
    oklch(from var(--sf-color-secondary-source-light) var(--sf-lumlocker) c h),
    oklch(from var(--sf-color-secondary-source-dark, var(--sf-color-secondary-source-light)) var(--sf-lumlocker-dark) c h)
  );
  --sf-color-tertiary: light-dark(
    oklch(from var(--sf-color-tertiary-source-light) var(--sf-lumlocker) c h),
    oklch(from var(--sf-color-tertiary-source-dark, var(--sf-color-tertiary-source-light)) var(--sf-lumlocker-dark) c h)
  );
  --sf-color-action: light-dark(
    oklch(from var(--sf-color-action-source-light) var(--sf-lumlocker) c h),
    oklch(from var(--sf-color-action-source-dark, var(--sf-color-action-source-light)) var(--sf-lumlocker-dark) c h)
  );
}

:root[data-lumlocker] :is([data-theme="dark"], .sf-theme-dark) {
  --sf-color-primary:   oklch(from var(--sf-color-primary-source-dark,   var(--sf-color-primary-source-light))   var(--sf-lumlocker-dark) c h);
  --sf-color-secondary: oklch(from var(--sf-color-secondary-source-dark, var(--sf-color-secondary-source-light)) var(--sf-lumlocker-dark) c h);
  --sf-color-tertiary:  oklch(from var(--sf-color-tertiary-source-dark,  var(--sf-color-tertiary-source-light))  var(--sf-lumlocker-dark) c h);
  --sf-color-action:    oklch(from var(--sf-color-action-source-dark,    var(--sf-color-action-source-light))    var(--sf-lumlocker-dark) c h);
}
:root[data-lumlocker] :is([data-theme="light"], .sf-theme-light) {
  --sf-color-primary:   oklch(from var(--sf-color-primary-source-light)   var(--sf-lumlocker) c h);
  --sf-color-secondary: oklch(from var(--sf-color-secondary-source-light) var(--sf-lumlocker) c h);
  --sf-color-tertiary:  oklch(from var(--sf-color-tertiary-source-light)  var(--sf-lumlocker) c h);
  --sf-color-action:    oklch(from var(--sf-color-action-source-light)    var(--sf-lumlocker) c h);
}
}

/* SECTION-LEVEL THEMING */
/* light-dark() bakes at :root declaration time; re-declarations on [data-theme]
   elements are required for section-level theming to work.
   SL-001: the clamp() derivation formula below is the same one used inside
   tokens.css's "Resolved color tokens — auto-switch via light-dark()"
   block (search core/tokens.css for that comment) — duplicated here, not
   shared, because this file needs flat values outside light-dark().
   Keep both copies in sync if the formula ever changes. */
@supports (color: oklch(from red l c h)) {

      :root {
    --sf-color-primary-source-dark:   oklch(from var(--sf-color-primary-source-light)   clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-secondary-source-dark: oklch(from var(--sf-color-secondary-source-light) clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-tertiary-source-dark:  oklch(from var(--sf-color-tertiary-source-light)  clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-action-source-dark:    oklch(from var(--sf-color-action-source-light)    clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-neutral-source-dark:   oklch(from var(--sf-color-neutral-source-light)   clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-base-source-dark:      oklch(from var(--sf-color-base-source-light)      clamp(0.16, calc(1.18 - l),        0.24) calc(c * 0.5) h);
    --sf-color-success-source-dark:   oklch(from var(--sf-color-success-source-light)   clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-warning-source-dark:   oklch(from var(--sf-color-warning-source-light)   clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-info-source-dark:      oklch(from var(--sf-color-info-source-light)      clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
    --sf-color-danger-source-dark:    oklch(from var(--sf-color-danger-source-light)    clamp(0.65, calc(0.95 - l * 0.5), 0.88) calc(c * 0.9) h);
  }

      :is([data-theme="dark"], .sf-theme-dark) {

    --sf-color-primary:   var(--sf-color-primary-source-dark);
    --sf-color-secondary: var(--sf-color-secondary-source-dark);
    --sf-color-tertiary:  var(--sf-color-tertiary-source-dark);
    --sf-color-action:    var(--sf-color-action-source-dark);
    --sf-color-neutral:   var(--sf-color-neutral-source-dark);
    --sf-color-base:      var(--sf-color-base-source-dark);
    --sf-color-surface:   var(--sf-color-base);

    --sf-color-bg:      oklch(from var(--sf-color-base) calc(l + 0.02) c h);
    --sf-color-inset:   oklch(from var(--sf-color-base) calc(l - 0.02) c h);
    --sf-color-raised:  oklch(from var(--sf-color-base) calc(l + 0.04) c h);
    --sf-color-overlay: oklch(from var(--sf-color-base) l c h / 0.9);
    --sf-color-inverse: oklch(from var(--sf-color-base) calc(1 - l) c h);

    --sf-color-success: var(--sf-color-success-source-dark);
    --sf-color-warning: var(--sf-color-warning-source-dark);
    --sf-color-info:    var(--sf-color-info-source-dark);
    --sf-color-danger:  var(--sf-color-danger-source-dark);

    --sf-color-text:              oklch(from var(--sf-color-neutral) clamp(0.70, calc(l + 0.25 + var(--sf-contrast-bias)), 1)    c h);
    --sf-color-text--subtle:   oklch(from var(--sf-color-neutral) clamp(0.55, calc(l + 0.1 + var(--sf-contrast-bias)), 0.90) c h);
    --sf-color-text--placeholder: oklch(from var(--sf-color-neutral) clamp(0.35, calc(l - 0.1),  0.65) c h);
    --sf-color-text--disabled:    oklch(from var(--sf-color-neutral) clamp(0.25, calc(l - 0.2),  0.55) c h);
    --sf-color-text--inverse:     oklch(from var(--sf-color-neutral) clamp(0.05, calc(l - 0.4),  0.35) c h);
    --sf-color-heading:           oklch(from var(--sf-color-neutral) clamp(0.70, calc(l + 0.25 + var(--sf-contrast-bias)), 1)    c h);

    --sf-color-border:         oklch(from var(--sf-color-neutral) clamp(0.25, calc(l - 0.3),  0.55) 0.005 h);
    --sf-color-border--subtle: oklch(from var(--sf-color-neutral) clamp(0.20, calc(l - 0.38), 0.45) 0.005 h);
    --sf-color-border--strong: oklch(from var(--sf-color-neutral) clamp(0.38, calc(l - 0.1),  0.65) 0.02  h);

    --sf-color-link:          oklch(from var(--sf-color-action) clamp(0.68, l, 1) c h);
    --sf-color-link--hover:   oklch(from var(--sf-color-action) clamp(0, max(l + 0.10, 0.68), 1) c h);
    --sf-color-link--active:  oklch(from var(--sf-color-action) clamp(0, max(l + 0.15, 0.74), 1) c h);
    --sf-color-link--visited: oklch(from var(--sf-color-action) clamp(0.68, l, 1) c calc(h + 60));

    --sf-color-selection-bg: oklch(from var(--sf-color-action-source-dark) clamp(0.62, calc(0.93 - l * 0.4), 0.78) c h / 0.55);

    --sf-color-success-strong: oklch(from var(--sf-color-success) clamp(0.70, calc(l + 0.15), 1) c h);
    --sf-color-warning-strong: oklch(from var(--sf-color-warning) clamp(0.70, calc(l + 0.05), 1) c h);
    --sf-color-info-strong:    oklch(from var(--sf-color-info)    clamp(0.70, calc(l + 0.15), 1) c h);
    --sf-color-danger-strong:  oklch(from var(--sf-color-danger)  clamp(0.70, calc(l + 0.15), 1) c h);

    /* :root-only aliases from tokens.css — var() resolves on the declaring
       element, so without these re-declarations a nested [data-theme]
       section inherits the :root-resolved (wrong-scheme) values (#496). */
    --sf-color-code-bg:        var(--sf-color-inset);
    --sf-color-bg--disabled:   var(--sf-color-inset);
    --sf-color-link--disabled: var(--sf-color-text--disabled);
    --sf-scrollbar-thumb:      var(--sf-color-neutral);
    /* Non-sign() fallback: keeps inline code readable in engines with
       oklch(from) but not sign() — the sign()-gated auto-contrast block
       below overrides this with a proper per-background contrast calc
       wherever sign() is supported. */
    --sf-color-code-text:      var(--sf-color-text);
  }

      :is([data-theme="light"], .sf-theme-light) {

    --sf-color-primary:   var(--sf-color-primary-source-light);
    --sf-color-secondary: var(--sf-color-secondary-source-light);
    --sf-color-tertiary:  var(--sf-color-tertiary-source-light);
    --sf-color-action:    var(--sf-color-action-source-light);
    --sf-color-neutral:   var(--sf-color-neutral-source-light);
    --sf-color-base:      var(--sf-color-base-source-light);
    --sf-color-surface:   var(--sf-color-base);

    --sf-color-bg:      oklch(from var(--sf-color-base) calc(l + 0.02) c h);
    --sf-color-inset:   oklch(from var(--sf-color-base) calc(l - 0.02) c h);
    --sf-color-raised:  oklch(from var(--sf-color-base) calc(l + 0.04) c h);
    --sf-color-overlay: oklch(from var(--sf-color-base) l c h / 0.9);
    --sf-color-inverse: oklch(from var(--sf-color-base) calc(1 - l) c h);

    --sf-color-success: var(--sf-color-success-source-light);
    --sf-color-warning: var(--sf-color-warning-source-light);
    --sf-color-info:    var(--sf-color-info-source-light);
    --sf-color-danger:  var(--sf-color-danger-source-light);

    --sf-color-text:              oklch(from var(--sf-color-neutral-source-light) clamp(0.05, calc(l - 0.4 - var(--sf-contrast-bias)), 0.35) c h);
    --sf-color-text--subtle:   oklch(from var(--sf-color-neutral-source-light) clamp(0.15, calc(l - 0.25 - var(--sf-contrast-bias)), 0.45) c h);
    --sf-color-text--placeholder: oklch(from var(--sf-color-neutral-source-light) clamp(0.45, calc(l + 0.15), 0.75) c h);
    --sf-color-text--disabled:    oklch(from var(--sf-color-neutral-source-light) clamp(0.55, calc(l + 0.25), 0.82) c h);
    --sf-color-text--inverse:     oklch(from var(--sf-color-neutral-source-light) clamp(0.85, calc(l + 0.4),  0.98) c h);
    --sf-color-heading:           oklch(from var(--sf-color-neutral-source-light) clamp(0.05, calc(l - 0.4 - var(--sf-contrast-bias)), 0.35) c h);

    --sf-color-border:         oklch(from var(--sf-color-neutral-source-light) clamp(0.70, calc(l + 0.35), 0.95) 0.005 h);
    --sf-color-border--subtle: oklch(from var(--sf-color-neutral-source-light) clamp(0.75, calc(l + 0.4),  0.97) 0.005 h);
    --sf-color-border--strong: oklch(from var(--sf-color-neutral-source-light) clamp(0.55, calc(l + 0.1),  0.85) 0.02  h);

    --sf-color-link:          oklch(from var(--sf-color-action) clamp(0, min(l - 0.07, 0.48), 1) c h);
    --sf-color-link--hover:   oklch(from var(--sf-color-action) clamp(0, min(l - 0.15, 0.40), 1) c h);
    --sf-color-link--active:  oklch(from var(--sf-color-action) clamp(0, min(l - 0.21, 0.34), 1) c h);
    --sf-color-link--visited: oklch(from var(--sf-color-action) clamp(0, min(l - 0.07, 0.48), 1) c calc(h + 60));

    --sf-color-selection-bg: oklch(from var(--sf-color-action-source-light) l c h / 0.28);

    --sf-color-success-strong: oklch(from var(--sf-color-success-source-light) calc(l - 0.15) c h);
    --sf-color-warning-strong: oklch(from var(--sf-color-warning-source-light) calc(l - 0.25) c h);
    --sf-color-info-strong:    oklch(from var(--sf-color-info-source-light)    calc(l - 0.1)  c h);
    --sf-color-danger-strong:  oklch(from var(--sf-color-danger-source-light)  calc(l - 0.1)  c h);

    /* :root-only aliases — see the matching note in the dark block (#496). */
    --sf-color-code-bg:        var(--sf-color-inset);
    --sf-color-bg--disabled:   var(--sf-color-inset);
    --sf-color-link--disabled: var(--sf-color-text--disabled);
    --sf-scrollbar-thumb:      var(--sf-color-neutral);
    /* Non-sign() fallback — see matching note in the dark block (#496). */
    --sf-color-code-text:      var(--sf-color-text);
  }

}

@supports (color: oklch(from red l c h)) and (width: calc(1px * sign(1))) {
  :is([data-theme="dark"], .sf-theme-dark) {
    --sf-color-text--on-primary:   oklch(from var(--sf-color-primary)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-secondary: oklch(from var(--sf-color-secondary) clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-tertiary:  oklch(from var(--sf-color-tertiary)  clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-action:    oklch(from var(--sf-color-action)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-neutral:   oklch(from var(--sf-color-neutral)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-success:   oklch(from var(--sf-color-success)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-warning:   oklch(from var(--sf-color-warning)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-info:      oklch(from var(--sf-color-info)      clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-danger:    oklch(from var(--sf-color-danger)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-inverse:   oklch(from var(--sf-color-inverse)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    /* Auto-contrast code text re-derived in-scope so it tracks the
       per-scope --sf-color-code-bg above (#496). Same formula as
       core/tokens.css — keep in sync. */
    --sf-color-code-text:          oklch(from var(--sf-color-code-bg)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
  }
  :is([data-theme="light"], .sf-theme-light) {
    --sf-color-text--on-primary:   oklch(from var(--sf-color-primary)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-secondary: oklch(from var(--sf-color-secondary) clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-tertiary:  oklch(from var(--sf-color-tertiary)  clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-action:    oklch(from var(--sf-color-action)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-neutral:   oklch(from var(--sf-color-neutral)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-success:   oklch(from var(--sf-color-success)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-warning:   oklch(from var(--sf-color-warning)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-info:      oklch(from var(--sf-color-info)      clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-danger:    oklch(from var(--sf-color-danger)    clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    --sf-color-text--on-inverse:   oklch(from var(--sf-color-inverse)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
    /* See matching note in the dark block (#496). */
    --sf-color-code-text:          oklch(from var(--sf-color-code-bg)   clamp(0.1, sign(var(--sf-contrast-threshold) - l) * 999, 0.95) 0 0);
  }
}

/* OPT-IN THEME CROSS-FADE */
.sf-theme-transition {
  transition:
    --sf-color-primary   var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-secondary var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-tertiary  var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-action    var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-neutral   var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-base      var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-success   var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-warning   var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-info      var(--sf-theme-transition-duration) var(--sf-ease-out),
    --sf-color-danger    var(--sf-theme-transition-duration) var(--sf-ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .sf-theme-transition { transition: none; }
}

/* ─── core/motion.css ─── */
/* SLASHED — core/motion.css
   @layer slashed.motion
   Global transitions, view transitions, keyframes, and motion helpers.
   Respects prefers-reduced-motion and --sf-motion-scale. */


@media (prefers-reduced-motion: no-preference) {

  html {
    scroll-behavior: smooth;
  }

  a, button, input, select, textarea, summary {
    transition-property:
      color, background-color, border-color, text-decoration-color,
      fill, stroke, opacity, box-shadow, transform, scale, translate,
      filter, backdrop-filter;
    transition-duration:        var(--sf-duration-fast);
    transition-timing-function: var(--sf-ease-out);
  }

  @supports (view-transition-name: none) {
    ::view-transition-old(root),
    ::view-transition-new(root) {
      animation-duration:        var(--sf-duration-normal);
      animation-timing-function: var(--sf-ease-out);
    }
  }

  
  .sf-fade-in        { animation: var(--sf-animation-fade-in); }
  .sf-fade-out       { animation: var(--sf-animation-fade-out); }
  .sf-slide-in-up    { animation: var(--sf-animation-slide-in-up); }
  .sf-slide-in-down  { animation: var(--sf-animation-slide-in-down); }
  .sf-slide-in-left  { animation: var(--sf-animation-slide-in-left); }
  .sf-slide-in-right { animation: var(--sf-animation-slide-in-right); }
  .sf-scale-up       { animation: var(--sf-animation-scale-up); }
  .sf-scale-down     { animation: var(--sf-animation-scale-down); }

  
  /* animation-duration set unconditionally: without it, engines without
     animation-timeline snap to end state at 0s. */
  .sf-entrance--fade,
  .sf-entrance--fade-up,
  .sf-entrance--fade-down,
  .sf-entrance--fade-left,
  .sf-entrance--fade-right,
  .sf-entrance--scale-up {
    animation-duration:        var(--sf-duration-slow);
    animation-timing-function: var(--sf-ease-out);
    animation-fill-mode:       both;
  }
  @supports (animation-timeline: view()) {
    .sf-entrance--fade,
    .sf-entrance--fade-up,
    .sf-entrance--fade-down,
    .sf-entrance--fade-left,
    .sf-entrance--fade-right,
    .sf-entrance--scale-up {
      animation-fill-mode: forwards;
      animation-timeline: view();
      animation-range:    var(--sf-scroll-timeline-range-start, entry 0%)
                          var(--sf-scroll-timeline-range-end,   cover 30%);
    }
  }
  .sf-entrance--fade        { animation-name: sf-fade-in; }
  .sf-entrance--fade-up     { animation-name: sf-slide-in-up; }
  .sf-entrance--fade-down   { animation-name: sf-slide-in-down; }
  .sf-entrance--fade-left   { animation-name: sf-slide-in-left; }
  .sf-entrance--fade-right  { animation-name: sf-slide-in-right; }
  .sf-entrance--scale-up    { animation-name: sf-scale-up; }


  /* Exit counterpart of .sf-entrance--*: fires as the element scrolls
     out of view. Every declaration below lives inside @supports —
     unlike entrance (where a non-scroll fallback still ends visible),
     an unconditional exit animation would leave content permanently
     hidden on engines without animation-timeline: view() support, so
     there is deliberately no fallback declaration outside @supports. */
  @supports (animation-timeline: view()) {
    .sf-exit--fade,
    .sf-exit--fade-up,
    .sf-exit--fade-down,
    .sf-exit--fade-left,
    .sf-exit--fade-right,
    .sf-exit--scale-down {
      animation-duration:        var(--sf-duration-slow);
      animation-timing-function: var(--sf-ease-in);
      animation-fill-mode:       both;
      animation-timeline: view();
      animation-range:    var(--sf-scroll-timeline-range-exit-start, cover 70%)
                          var(--sf-scroll-timeline-range-exit-end,   exit 100%);
    }
    .sf-exit--fade        { animation-name: sf-fade-out; }
    .sf-exit--fade-up     { animation-name: sf-slide-out-up; }
    .sf-exit--fade-down   { animation-name: sf-slide-out-down; }
    .sf-exit--fade-left   { animation-name: sf-slide-out-left; }
    .sf-exit--fade-right  { animation-name: sf-slide-out-right; }
    .sf-exit--scale-down  { animation-name: sf-scale-down; }
  }

  /* Stagger — choreography, not the animation itself. Put .sf-stagger on a
     PARENT; every direct child gets an incrementing animation-delay so a
     time-based entrance (.sf-fade-in / .sf-slide-in-*) plays in sequence.
     Children WITHOUT an animation just carry an inert delay (no-op), so you
     opt in per child with no need to exclude anything. Pairs with time-based
     animations only — the .sf-entrance / .sf-exit families are scroll-driven
     (their rhythm is animation-range, not animation-delay).

     One formula, one knob (--sf-stagger-step); the two paths below only
     differ in how --sf-stagger-i (the child's index) is set. */
  .sf-stagger > * {
    animation-delay: calc(
      var(--sf-stagger-i, 0) * var(--sf-stagger-step, 75ms) * var(--sf-motion-scale, 1)
    );
  }

  /* Modern path: real, unbounded index straight from the DOM position. */
  @supports (animation-delay: calc(sibling-index() * 1ms)) {
    .sf-stagger > * { --sf-stagger-i: calc(sibling-index() - 1); }
  }

  /* Fallback (no sibling-index()): a bounded :nth-child ramp reusing the same
     formula. The default is the plateau value, so the 9th child onward shares
     the last rung's delay instead of growing without limit — 8 rungs cover a
     4-column grid's first two visible rows. Split into @supports not(...) so
     the modern override above wins cleanly without a specificity fight
     (:nth-child would otherwise out-specify the sibling-index rule). */
  @supports not (animation-delay: calc(sibling-index() * 1ms)) {
    .sf-stagger > *              { --sf-stagger-i: 8; }
    .sf-stagger > *:nth-child(1) { --sf-stagger-i: 0; }
    .sf-stagger > *:nth-child(2) { --sf-stagger-i: 1; }
    .sf-stagger > *:nth-child(3) { --sf-stagger-i: 2; }
    .sf-stagger > *:nth-child(4) { --sf-stagger-i: 3; }
    .sf-stagger > *:nth-child(5) { --sf-stagger-i: 4; }
    .sf-stagger > *:nth-child(6) { --sf-stagger-i: 5; }
    .sf-stagger > *:nth-child(7) { --sf-stagger-i: 6; }
    .sf-stagger > *:nth-child(8) { --sf-stagger-i: 7; }
  }

}

@keyframes sf-fade-in   { from { opacity: 0; } to { opacity: 1; } }
@keyframes sf-fade-out  { from { opacity: 1; } to { opacity: 0; } }
@keyframes sf-slide-in-up    { from { opacity: 0; translate: 0 1rem;  } to { opacity: 1; translate: 0 0; } }
@keyframes sf-slide-in-down  { from { opacity: 0; translate: 0 -1rem; } to { opacity: 1; translate: 0 0; } }
@keyframes sf-slide-in-left  { from { opacity: 0; translate: 1rem 0;  } to { opacity: 1; translate: 0 0; } }
@keyframes sf-slide-in-right { from { opacity: 0; translate: -1rem 0; } to { opacity: 1; translate: 0 0; } }
@keyframes sf-scale-up   { from { opacity: 0; scale: 0.92; } to { opacity: 1; scale: 1; } }
@keyframes sf-scale-down { from { opacity: 1; scale: 1; } to { opacity: 0; scale: 0.92; } }

@keyframes sf-slide-out-up    { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -1rem; } }
@keyframes sf-slide-out-down  { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 1rem;  } }
@keyframes sf-slide-out-left  { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: -1rem 0; } }
@keyframes sf-slide-out-right { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 1rem 0;  } }

@keyframes sf-spin    { to { rotate: 360deg; } }
@keyframes sf-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }

@keyframes sf-ping  { 75%, 100% { scale: 2; opacity: 0; } }
@keyframes sf-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
@keyframes sf-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -0.5rem; } }


@supports (@property --x { syntax: "<color>"; inherits: false; initial-value: white; }) {
  @keyframes sf-color-pulse {
    0%   { --sf-color-primary-source-light: var(--sf-color-primary-source-light); }
    50%  { --sf-color-primary-source-light: oklch(from var(--sf-color-primary-source-light) clamp(0, calc(l + 0.25), 0.9) c h); }
    100% { --sf-color-primary-source-light: var(--sf-color-primary-source-light); }
  }

  @media (prefers-reduced-motion: no-preference) {
    .sf-color-pulse {
      animation: var(--sf-animation-color-pulse);
    }
  }
}

@supports (transition-behavior: allow-discrete) {
  dialog, [popover] {
    transition: var(--sf-transition-overlay);
  }
}

/* ─── core/accessibility.css ─── */
/* SLASHED — core/accessibility.css
   @layer slashed.accessibility
   Focus rings, screen-reader helpers, skip links, and reduced-friction a11y utilities.
   Prefix: .sf-* plus native focus selectors. */


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

:focus-visible {
  outline:        var(--sf-focus-ring-width) var(--sf-focus-ring-style, solid) var(--sf-focus-ring-color) !important;
  outline-offset: var(--sf-focus-ring-offset) !important;
}

/* .sf-focus-shadow: replaces outline ring with box-shadow ring; !important required
   to take over the hardened :focus-visible outline (WCAG 2.4.7 still met). */
.sf-focus-shadow:focus-visible {
  outline:    none                         !important;
  box-shadow: var(--sf-focus-ring-shadow)  !important;
}

/* forced-colors: box-shadow vanishes — restore outline ring for WCAG 2.4.7
   in Windows High Contrast. */
@media (forced-colors: active) {
  .sf-focus-shadow:focus-visible {
    outline:        var(--sf-focus-ring-width) solid !important;
    outline-offset: var(--sf-focus-ring-offset) !important;
    box-shadow:     none !important;
  }
}

/* Reduced motion. Token override helps components that read */
@media (prefers-reduced-motion: reduce) {
  :root {
    --sf-duration-instant: 0.01ms;
    --sf-duration-fast:    0.01ms;
    --sf-duration-normal:  0.01ms;
    --sf-duration-slow:    0.01ms;
    --sf-duration-slower:  0.01ms;
  }

  *,
  *::before,
  *::after {
    animation-duration:        0.01ms !important;
    animation-iteration-count: 1      !important;
    transition-duration:       0.01ms !important;
    scroll-behavior:           auto   !important;
  }
}

.no-motion,
.no-motion *,
.no-motion *::before,
.no-motion *::after {
  animation-duration:        0.01ms !important;
  animation-iteration-count: 1      !important;
  transition-duration:       0.01ms !important;
  scroll-behavior:           auto   !important;
}

/* High contrast */
@media (prefers-contrast: more) {
  :root {
    --sf-focus-ring-width: 3px;

    --sf-contrast-bias: 0.06;
  }

  hr {
    border-block-start-width: var(--sf-border-width-2);
  }
}

@media (prefers-reduced-transparency: reduce) {
  ::backdrop {
    background-color: var(--sf-color-neutral);
    opacity:          1;
  }
}

/* Reduced data */
@media (prefers-reduced-data: reduce) {
  :root {
    --sf-shadow-strength: 0;
    --sf-blur: 0px;
  }
}

/* Minimum touch targets — a backstop for genuinely bare, unstyled controls.
   The floor targets only controls that carry NO class at all. The moment a
   control has any class it is considered *owned* — by SLASHED itself (.sf-btn
   owns a deliberate XS–XL size ladder via --sf-btn-min-height / --sf-size-*
   rungs), by the author, or by a third-party widget (a page-builder hamburger
   toggle, a plugin control) — and its sizing belongs to whoever put that class
   there, not to this blanket rule.

   Why so conservative: a blanket floor on *every* button/control silently
   overrode its owners. It collapsed .sf-btn's whole size ladder to one height
   on touch, and it stretched narrow custom widgets (e.g. a hamburger icon
   button) to 44px on BOTH axes — at a specificity higher than a single class,
   so the owner couldn't even override it without !important. Element-level
   selectors that reach for someone else's markup are the "too aggressive base
   style" trap; scoping the floor to classless controls keeps it from ever
   touching markup the framework doesn't own. It extends the "un-styled
   element" idiom already used for `a:not([class])` in base.

   "Un-styled" here means *no effective class* — no class attribute at all, OR
   an empty one (`class=""`), which templating/component renderers routinely
   emit for a control the author never styled. Both keep the floor. The test
   is written `:not([class]:not([class=""]))` — read it as "not (has a
   non-empty class)": it excludes only controls carrying a real class token.
   (A whitespace-only `class=" "` is treated as owned; that pathological case
   isn't worth widening the selector for.) `:where()` keeps the element list
   at zero specificity so the whole rule stays a low, easily-reasoned weight.

   Bare `<button>text</button>` and un-classed native controls — almost always
   the author's own quick markup — keep the WCAG 2.5.5 44px floor on both axes.
   To opt a control OUT, give it any class. To put the floor back ON an owned
   control, add the .sf-touch-target opt-in class below, set its own
   min-block/inline-size, or on .sf-btn use
   `--sf-btn-min-height: var(--sf-touch-target)`. */
@media (pointer: coarse) {
  :where(
    button,
    input[type="button"], input[type="submit"], input[type="reset"],
    select, summary,
    input[type="checkbox"], input[type="radio"]
  ):not([class]:not([class=""])) {
    min-block-size:  var(--sf-touch-target);
    min-inline-size: var(--sf-touch-target);
  }
}

/* .sf-touch-target — the explicit opt-IN counterpart to the class-less floor
   above. Because the automatic floor deliberately never touches a control
   that carries a class, put this class on any control you own (a button with
   your own class, a third-party widget you can add a class to) to guarantee
   the WCAG 2.5.5 44px minimum hit area on both axes. Unlike the automatic
   floor it is NOT gated to a coarse pointer — an explicit opt-in is
   predictable everywhere. Size follows --sf-touch-target, so a wrapper (or
   the element itself) can retune it locally.

     <button class="my-menu-toggle sf-touch-target" aria-label="Menu">…</button>

   It sets ONLY the two min-size properties — deliberately no `display` — so it
   never strips a native affordance (e.g. a <summary>'s list-item marker, a
   control's baseline alignment). It therefore expects a target whose display
   already honours min-inline-size: buttons and native form controls
   (inline-block/flex) do. On a purely inline element (a bare <a>) add your own
   `display: inline-flex` (or inline-block) alongside it.

   No !important: it's a single class (specificity 0,1,0), so your own
   component rule can still override it when a specific control needs to opt
   back out. */
.sf-touch-target {
  min-block-size:  var(--sf-touch-target);
  min-inline-size: var(--sf-touch-target);
}

/* Disabled states */
[disabled],
[aria-disabled="true"] {
  cursor:  not-allowed;
  opacity: var(--sf-opacity-disabled);
}

/* Screen-reader-only. Atomic contract: partial override either exposes hidden content
   or hides intended content — all-or-nothing, whole set hardened. */
.sr-only,
.sr-only-focusable:not(:focus, :focus-within) {
  position:    absolute            !important;
  width:       1px                 !important;
  height:      1px                 !important;
  padding:     0                   !important;
  margin:      -1px                !important;
  overflow:    hidden              !important;
  overflow:    clip                !important;
  clip-path:   inset(50%)          !important;
  white-space: nowrap              !important;
  border:      0                   !important;
}

/* Skip link */
.skip-link {
  position:          absolute;
  inset-block-start: -100%;
  inset-inline-start: var(--sf-space-m);
  z-index:           var(--sf-z-tooltip);
  padding:           var(--sf-space-s) var(--sf-space-m);
  background:        var(--sf-color-bg);
  color:             var(--sf-color-text);
  border:            var(--sf-border-width-1) solid var(--sf-color-border);
  border-radius:     var(--sf-radius-m);
  transition:        inset-block-start var(--sf-duration-fast) var(--sf-ease-out);
}
.skip-link:focus {
  inset-block-start: var(--sf-space-m);
}

/* Focus-parent */

.sf-focus-parent:focus-within {
  outline:        var(--sf-focus-ring-width) var(--sf-focus-ring-style, solid) var(--sf-focus-ring-color);
  outline-offset: var(--sf-focus-ring-offset);
}

/* Clickable-parent */
/* Perf note (SL-002): the two selector groups below each combine multiple
   :has()-bearing branches, forcing subtree re-evaluation on DOM mutation
   inside .sf-clickable-parent. Fine for typical card/list-item usage; avoid
   applying this class to large (100+ node) grids or lists without profiling
   first. */
.sf-clickable-parent {
  --sf-clickable-overlay-z: 1;
  position: relative;
  cursor:   pointer;
}

.sf-clickable-parent:not(:has([data-overlay-link], .sf-clickable-parent__overlay)) a:where(:any-link)::after,
.sf-clickable-parent [data-overlay-link]::after,
.sf-clickable-parent .sf-clickable-parent__overlay::after {
  content:        "";
  position:       absolute;
  inset:          0;
  z-index:        var(--sf-clickable-overlay-z);
  pointer-events: auto;
}

.sf-clickable-parent :is(
  button, input, textarea, select,
  summary, video, audio,
  [role="button"], [data-no-overlay]
),
.sf-clickable-parent:has([data-overlay-link]) a:where(:any-link):not([data-overlay-link]),
.sf-clickable-parent:has(.sf-clickable-parent__overlay) a:where(:any-link):not(.sf-clickable-parent__overlay),
.sf-clickable-parent:not(:has([data-overlay-link], .sf-clickable-parent__overlay)) a:where(:any-link):not(:first-of-type) {
  position: relative;
  z-index:  calc(var(--sf-clickable-overlay-z) + 1);
}

.sf-clickable-parent:focus-within {
  outline:        var(--sf-focus-ring-width) var(--sf-focus-ring-style, solid) var(--sf-focus-ring-color);
  outline-offset: var(--sf-focus-ring-offset);
}

/* Forced colors (Windows High Contrast). Outline-based focus */
@media (forced-colors: active) {
  :root {
    --sf-focus-ring-color: Highlight;
  }

  :focus-visible {
    outline-color: Highlight !important;
  }

  :where(*) {
    box-shadow:  none;
    text-shadow: none;
  }

  input,
  select,
  textarea {
    border: var(--sf-border-width-1) solid;
  }

  button,
  input[type="submit"],
  input[type="reset"],
  input[type="button"] {
    border-color: ButtonBorder !important;
  }

  .sf-text-gradient {
    background-image:         none !important;
    /* stylelint-disable-next-line property-no-vendor-prefix */
    -webkit-background-clip:  unset !important;
    background-clip:          unset !important;
    color:                    CanvasText !important;
  }

  .sf-is-loading::after {
    border-color:             ButtonText !important;
    border-block-start-color: Highlight !important;
  }
}

/* ─── core/print.css ─── */
/* SLASHED — core/print.css
   @layer slashed.print
   Print-only visibility, page defaults, ink-saving resets, and URL disclosure.
   Prefix: .print-* for explicit print helpers. */



.print-only {
  display: none;
}

@media print {

  /* --sf-print-page-size / -margin are read here so a :root override retunes
     the printed page box. Each fallback equals the historical literal, so the
     default print render is unchanged. Verified in Chromium that var() is
     substituted inside @page descriptors; engines lacking general custom-
     property substitution in @page fall back to the literal. */
  @page {

    size:   var(--sf-print-page-size, a4) portrait;
    margin: var(--sf-print-page-margin, 2cm);
  }

  body {
    font-size: var(--sf-print-base-size, 11pt);
  }

  *,
  *::before,
  *::after {
    box-shadow:  none;
    text-shadow: none;
  }

  a[href]:not([href^="#"], [href^="javascript:" i], [href^="mailto:" i], [href^="tel:" i], .sf-link-external)::after {
    content:    " (" attr(href) ")";
    font-size:  90%;
    word-break: break-all;
  }

  abbr[title]::after {
    content: " (" attr(title) ")";
  }

  img, svg, canvas, figure, table, pre, blockquote {
    break-inside: avoid;

  }

  thead {
    display: table-header-group;
  }

  tr {
    break-inside: avoid;
  }

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

  }

  /* Fluid --sf-h*-size tokens resolve via 100vw clamp()s, which have no
     reliable meaning on a print page box. Pin print heading sizes to
     static pt values instead. */
  h1 { font-size: 24pt; }
  h2 { font-size: 20pt; }
  h3 { font-size: 16pt; }
  h4 { font-size: 14pt; }
  h5 { font-size: 12pt; }
  h6 { font-size: 11pt; }

  p {
    orphans: 3;
    widows:  3;
  }

  pre, blockquote {
    border:  var(--sf-border-width-1) solid var(--sf-color-border--strong);
    padding: var(--sf-space-s);
  }

  nav, aside, button, input, select, textarea,
  audio, video, dialog, [popover], .no-print {
    display: none !important;
  }

  .print-only {
    display: revert !important;
  }

  details { display: block; }
  details > summary { display: none !important; }
  details > :not(summary) { display: block; }

  
  .print-color-exact,
  .print-color-exact * {
    print-color-adjust:         exact !important;
    -webkit-print-color-adjust: exact !important;
  }

  .print-no-color,
  .print-no-color *,
  .print-no-color *::before,
  .print-no-color *::after {
    background:  transparent !important;
    color:       CanvasText  !important;
  }

}
