/* Welkin — canonical cascade-layer order (docs/04-css-architecture.md).
   This declaration is public API: reordering is a breaking change.
   Every source file repeats this exact line first, so any subset of files
   concatenates correctly in any order (docs/decisions/ADR-0003). */
@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin reset (ADR-0004): own minimal reset, zero specificity via :where(),
   lowest layer — nothing a user writes can lose to it. Scope is fixed by the
   ADR; opinions beyond normalisation belong in `base`. Documented line by line
   in docs/04-css-architecture.md. */
@layer reset {
  /* Predictable sizing everywhere, including generated content. */
  :where(*, *::before, *::after) {
    box-sizing: border-box;
  }

  /* Margin trimming: default margins fight the .stack primitive's owned
     rhythm (docs/06); spacing is a layout decision, not an element default. */
  :where(*) {
    margin: 0;
  }

  /* Top-layer elements are centred by the UA via margin: auto against
     inset: 0 — that is layout, not flow rhythm, so the trim above must not
     strip it (a zero-margin dialog/popover pins to the top-left corner). */
  :where(dialog, [popover]) {
    margin: auto;
  }

  /* Root defaults. Unitless line-height inherits proportionally.
     -webkit-text-size-adjust: iOS Safari inflates text in landscape without
     it (vendor/physical form is the interoperable one — justified exception
     per ADR-0009). scroll-padding keeps in-page navigation targets clear of
     sticky chrome (WCAG 2.4.11 Focus Not Obscured); the navbar's block-size
     token feeds it, with a small fallback gap for pages without a bar. */
  :where(html) {
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
    scroll-padding-block-start: var(--wel-navbar-block-size, 1rem);
  }

  /* Smooth in-page scrolling as motion: opt-in under the user's preference
     only — the additive rule from docs/03. The --wel-motion multiplier
     cannot zero a discrete keyword, so the preference gates it directly. */
  @media (prefers-reduced-motion: no-preference) {
    :where(html) {
      scroll-behavior: smooth;
    }
  }

  /* Media defaults: block display kills phantom baseline gaps; max-inline-size
     keeps replaced elements inside their container; block-size auto preserves
     aspect ratio when a width attribute is present. */
  :where(img, picture, video, canvas, svg) {
    display: block;
    max-inline-size: 100%;
    block-size: auto;
  }

  /* Form controls inherit the surrounding voice instead of UA chrome fonts. */
  :where(input, button, textarea, select) {
    font: inherit;
  }

  /* Headings bind as visual units; paragraphs avoid orphans. Core-tier candy
     (docs/03) — engines without these text-wrap values keep normal wrapping. */
  :where(h1, h2, h3, h4, h5, h6) {
    text-wrap: balance;
  }

  :where(p) {
    text-wrap: pretty;
  }

  /* Long words/URLs must never force container overflow. */
  :where(p, h1, h2, h3, h4, h5, h6) {
    overflow-wrap: break-word;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin colour tokens (docs/05). Three tiers:
   primitives (oklch ramps) -> semantic roles (light-dark(), typed) -> component
   tokens (defined in each component's file). Components never read primitives.
   Token names are semver-governed API. */

/* ---- @property registrations (semantic colour tokens are typed: docs/05).
   initial-value is the concrete light-scheme value — registrations cannot
   carry var()/light-dark(), so these back-stop the :root declarations. */
@layer tokens {
  /* ---- Scheme wiring (ADR-0007, amended 1.0.1). [data-theme] on any subtree
     pins the scheme — page-level toggle and scoped mixed-mode regions with one
     mechanism. A JS toggle only ever sets data-theme.
     Because the semantic tokens are typed (@property "<color>"), light-dark()
     and the accent derivations resolve at computed-value time ON THE ELEMENT
     THAT DECLARES THEM — descendants inherit the already-resolved colour. A
     pin that only set color-scheme was therefore inert for tokens (T-84): the
     token block below re-declares on every [data-theme] subtree root, so the
     whole palette re-resolves against the pinned scheme and the locally
     cascaded accent. Consequence for themes: declare on `:root, [data-theme]`
     (doc 10) or a :root-only theme stops at the first data-theme boundary.
     The pin rules live AFTER the token block: equal specificity, so source
     order is what lets them beat its `color-scheme: light dark`.
     Fallback stance: engines without light-dark() resolve each typed token
     to its @property initial-value — the static light palette (ADR-0007). */
  @property --wel-color-surface        { syntax: "<color>"; inherits: true; initial-value: oklch(97% 0.002 250); }
  @property --wel-color-surface-raised { syntax: "<color>"; inherits: true; initial-value: oklch(99.5% 0.001 250); }
  @property --wel-color-surface-sunken { syntax: "<color>"; inherits: true; initial-value: oklch(94% 0.003 250); }
  @property --wel-color-ink            { syntax: "<color>"; inherits: true; initial-value: oklch(23% 0.004 250); }
  @property --wel-color-ink-muted      { syntax: "<color>"; inherits: true; initial-value: oklch(46% 0.009 250); }
  @property --wel-color-ink-faint      { syntax: "<color>"; inherits: true; initial-value: oklch(62% 0.01 250); }
  @property --wel-color-accent          { syntax: "<color>"; inherits: true; initial-value: oklch(54% 0.16 255); }
  @property --wel-color-accent-hover    { syntax: "<color>"; inherits: true; initial-value: oklch(48% 0.16 255); }
  @property --wel-color-accent-active   { syntax: "<color>"; inherits: true; initial-value: oklch(44% 0.16 255); }
  @property --wel-color-accent-tint     { syntax: "<color>"; inherits: true; initial-value: oklch(92% 0.021 255); }
  @property --wel-color-accent-contrast { syntax: "<color>"; inherits: true; initial-value: oklch(100% 0 0); }
  @property --wel-color-border        { syntax: "<color>"; inherits: true; initial-value: oklch(88% 0.005 250); }
  @property --wel-color-border-strong { syntax: "<color>"; inherits: true; initial-value: oklch(62% 0.01 250); }
  @property --wel-color-info    { syntax: "<color>"; inherits: true; initial-value: oklch(46% 0.144 255); }
  @property --wel-color-success { syntax: "<color>"; inherits: true; initial-value: oklch(46% 0.135 150); }
  @property --wel-color-warning { syntax: "<color>"; inherits: true; initial-value: oklch(46% 0.117 80); }
  @property --wel-color-danger  { syntax: "<color>"; inherits: true; initial-value: oklch(46% 0.153 25); }
  @property --wel-color-info-tint    { syntax: "<color>"; inherits: true; initial-value: oklch(91% 0.019 255); }
  @property --wel-color-success-tint { syntax: "<color>"; inherits: true; initial-value: oklch(91% 0.018 150); }
  @property --wel-color-warning-tint { syntax: "<color>"; inherits: true; initial-value: oklch(91% 0.016 80); }
  @property --wel-color-danger-tint  { syntax: "<color>"; inherits: true; initial-value: oklch(91% 0.02 25); }

  /* ---- Primitives: oklch ramps, written out in full (ADR-0002 — no
     generation at build time; a docs-phase script may rewrite this block).
     Fixed lightness targets per step, per-hue chroma curve peaking mid-ramp,
     so every ramp reads evenly spaced. Never consumed by components. */
  :root, [data-theme] {
    color-scheme: light dark;

    /* grey (hue 250, near-neutral) */
    --wel-grey-50:  oklch(97% 0.002 250);
    --wel-grey-100: oklch(94% 0.003 250);
    --wel-grey-200: oklch(88% 0.005 250);
    --wel-grey-300: oklch(80% 0.007 250);
    --wel-grey-400: oklch(71% 0.009 250);
    --wel-grey-500: oklch(62% 0.01 250);
    --wel-grey-600: oklch(54% 0.01 250);
    --wel-grey-700: oklch(46% 0.009 250);
    --wel-grey-800: oklch(38% 0.008 250);
    --wel-grey-900: oklch(30% 0.006 250);
    --wel-grey-950: oklch(23% 0.004 250);
    --wel-grey-975: oklch(17% 0.003 250);

    /* blue (hue 255) — default accent + info */
    --wel-blue-50:  oklch(97% 0.024 255);
    --wel-blue-100: oklch(94% 0.04 255);
    --wel-blue-200: oklch(88% 0.072 255);
    --wel-blue-300: oklch(80% 0.104 255);
    --wel-blue-400: oklch(71% 0.136 255);
    --wel-blue-500: oklch(62% 0.16 255);
    --wel-blue-600: oklch(54% 0.16 255);
    --wel-blue-700: oklch(46% 0.144 255);
    --wel-blue-800: oklch(38% 0.12 255);
    --wel-blue-900: oklch(30% 0.088 255);
    --wel-blue-950: oklch(23% 0.064 255);
    --wel-blue-975: oklch(17% 0.048 255);

    /* green (hue 150) — success */
    --wel-green-50:  oklch(97% 0.023 150);
    --wel-green-100: oklch(94% 0.038 150);
    --wel-green-200: oklch(88% 0.068 150);
    --wel-green-300: oklch(80% 0.098 150);
    --wel-green-400: oklch(71% 0.128 150);
    --wel-green-500: oklch(62% 0.15 150);
    --wel-green-600: oklch(54% 0.15 150);
    --wel-green-700: oklch(46% 0.135 150);
    --wel-green-800: oklch(38% 0.113 150);
    --wel-green-900: oklch(30% 0.083 150);
    --wel-green-950: oklch(23% 0.06 150);
    --wel-green-975: oklch(17% 0.045 150);

    /* amber (hue 80) — warning */
    --wel-amber-50:  oklch(97% 0.02 80);
    --wel-amber-100: oklch(94% 0.033 80);
    --wel-amber-200: oklch(88% 0.059 80);
    --wel-amber-300: oklch(80% 0.085 80);
    --wel-amber-400: oklch(71% 0.111 80);
    --wel-amber-500: oklch(62% 0.13 80);
    --wel-amber-600: oklch(54% 0.13 80);
    --wel-amber-700: oklch(46% 0.117 80);
    --wel-amber-800: oklch(38% 0.098 80);
    --wel-amber-900: oklch(30% 0.072 80);
    --wel-amber-950: oklch(23% 0.052 80);
    --wel-amber-975: oklch(17% 0.039 80);

    /* red (hue 25) — danger */
    --wel-red-50:  oklch(97% 0.026 25);
    --wel-red-100: oklch(94% 0.043 25);
    --wel-red-200: oklch(88% 0.077 25);
    --wel-red-300: oklch(80% 0.111 25);
    --wel-red-400: oklch(71% 0.145 25);
    --wel-red-500: oklch(62% 0.17 25);
    --wel-red-600: oklch(54% 0.17 25);
    --wel-red-700: oklch(46% 0.153 25);
    --wel-red-800: oklch(38% 0.128 25);
    --wel-red-900: oklch(30% 0.094 25);
    --wel-red-950: oklch(23% 0.068 25);
    --wel-red-975: oklch(17% 0.051 25);

    /* ---- Semantic roles. Each defined ONCE with light-dark() (ADR-0007);
       [data-theme] pinning + the graduation-audit fallback are T-17's concern.
       Interaction shades are computed, not enumerated (docs/05 derivation):
       one --wel-color-accent override retints hover/active/tint/contrast. */
    --wel-color-surface:        light-dark(var(--wel-grey-50), var(--wel-grey-975));
    --wel-color-surface-raised: light-dark(oklch(99.5% 0.001 250), var(--wel-grey-950));
    --wel-color-surface-sunken: light-dark(var(--wel-grey-100), oklch(13% 0.003 250));

    --wel-color-ink:       light-dark(var(--wel-grey-950), var(--wel-grey-100));
    --wel-color-ink-muted: light-dark(var(--wel-grey-700), var(--wel-grey-400));
    --wel-color-ink-faint: light-dark(var(--wel-grey-500), var(--wel-grey-600));

    --wel-color-border:        light-dark(var(--wel-grey-200), var(--wel-grey-800));
    /* border-strong must hold 3:1 vs surface in both schemes (WCAG 1.4.11) */
    --wel-color-border-strong: light-dark(var(--wel-grey-500), var(--wel-grey-500));

    --wel-color-accent:          light-dark(var(--wel-blue-600), var(--wel-blue-400));
    /* Interaction shades: fixed lightness steps. Below l 0.12 the step
       flips to LIGHTEN, so a near-black accent still shows hover/active
       feedback — clamp(0, (l - 0.12) * infinity, 1) is 1 above the cut,
       0 below (the same flip idiom as -contrast under it). */
    --wel-color-accent-hover:    oklch(from var(--wel-color-accent) calc(l + 0.06 - 0.12 * clamp(0, (l - 0.12) * infinity, 1)) c h);
    --wel-color-accent-active:   oklch(from var(--wel-color-accent) calc(l + 0.10 - 0.20 * clamp(0, (l - 0.12) * infinity, 1)) c h);
    --wel-color-accent-tint:     color-mix(in oklch, var(--wel-color-accent) 12%, var(--wel-color-surface));
    /* Text-on-accent, DERIVED (T-130, docs/05): white when the accent's
       linear luminance y < 0.179, black above. That cut is the only band
       where both flip sides clear 4.5:1 (white text needs y <= 0.183,
       black needs y >= 0.175), so ANY in-gamut accent keeps the
       primary-button pairing — including pure black and pure white.
       xyz-d65 y is the specified colour's luminance (pre-gamut-mapping);
       0.9505/1.089 are the D65 white point, so the flip lands on exact
       white or black. check-contrast.mjs sweeps this guarantee in CI.
       Engines without relative colour drop the declaration and fall back
       to the @property initial-value (white), matching the static light
       palette stance (ADR-0007). */
    --wel-color-accent-contrast: color(from var(--wel-color-accent) xyz-d65
                                   calc(clamp(0, (0.179 - y) * infinity, 1) * 0.9505)
                                   clamp(0, (0.179 - y) * infinity, 1)
                                   calc(clamp(0, (0.179 - y) * infinity, 1) * 1.089));

    /* Tones (data-tone axes): text-safe base in both schemes; tints follow
       the surface automatically via color-mix, like accent-tint. */
    --wel-color-info:    light-dark(var(--wel-blue-700), var(--wel-blue-300));
    --wel-color-success: light-dark(var(--wel-green-700), var(--wel-green-300));
    --wel-color-warning: light-dark(var(--wel-amber-700), var(--wel-amber-300));
    --wel-color-danger:  light-dark(var(--wel-red-700), var(--wel-red-300));

    --wel-color-info-tint:    color-mix(in oklch, var(--wel-color-info) 12%, var(--wel-color-surface));
    --wel-color-success-tint: color-mix(in oklch, var(--wel-color-success) 12%, var(--wel-color-surface));
    --wel-color-warning-tint: color-mix(in oklch, var(--wel-color-warning) 12%, var(--wel-color-surface));
    --wel-color-danger-tint:  color-mix(in oklch, var(--wel-color-danger) 12%, var(--wel-color-surface));
  }

  /* ---- Image dimming (T-102): photos glare on dark surfaces. Filter
     values cannot ride light-dark(), so the resolved scheme is
     reconstructed the pin-compatible way: OS preference here, then the
     pin rules below re-declare per [data-theme] subtree root exactly like
     the palette (T-84) — they sit after this block so source order lets a
     pin beat the OS preference on the same element. The light value is
     the IDENTITY filter, never `none`: `none` inside a longer filter list
     invalidates the whole declaration, and consumers compose this token
     (.dim, .duotone combo — utilities.css). Nothing applies the token by
     itself; the .dim utility (or the user's own img rule) opts media in. */
  :root {
    /* the dark-scheme amount, themable in one place */
    --wel-img-dim-amount: brightness(0.85) contrast(1.05);
    --wel-img-dim: brightness(1);
  }

  @media (prefers-color-scheme: dark) {
    :root {
      --wel-img-dim: var(--wel-img-dim-amount);
    }
  }

  /* Scheme pins — after the token block on purpose (see the wiring note):
     same specificity as its [data-theme] arm, so source order is what makes
     the pin win over `color-scheme: light dark` (and the dim token's OS
     arm above). */
  [data-theme="light"] {
    color-scheme: light;
    --wel-img-dim: brightness(1);
  }

  [data-theme="dark"] {
    color-scheme: dark;
    --wel-img-dim: var(--wel-img-dim-amount);
  }

  /* Forced colors: the user demanded the system palette — a scoped scheme
     pin is meaningless there, and Chromium resolves forced CanvasText per
     the pinned scheme while forcing backgrounds to the page Canvas, which
     turns a dark pin into white-on-white. Un-pin so text and background
     force consistently. (After the pins: same specificity, source order.) */
  @media (forced-colors: active) {
    [data-theme] {
      color-scheme: light dark;
    }

    /* Dimming off too — never alter the user's demanded rendering. */
    :root,
    [data-theme] {
      --wel-img-dim: brightness(1);
    }
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin focus-ring tokens (docs/05, docs/09). Consumed by the single
   global :focus-visible rule in `base` and nothing else. Widths/offsets are
   px hairline-class values (ADR-0008): the ring must stay crisp, and its
   visibility must not depend on user font size. */
@layer tokens {
  :root {
    --wel-focus-ring-color: var(--wel-color-accent);
    --wel-focus-ring-width: 2px;
    --wel-focus-ring-offset: 2px;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin motion tokens (docs/05). The multiplier rule: every duration in
   the codebase is written calc(duration-token * var(--wel-motion)) — no raw
   durations anywhere (lint-checked). Zero-duration transitions still fire
   transitionend, so JS never needs a reduced-motion branch. */
@layer tokens {
  @property --wel-motion { syntax: "<number>"; inherits: true; initial-value: 1; }

  :root {
    --wel-motion: 1;

    --wel-motion-duration-1: 100ms;
    --wel-motion-duration-2: 200ms;
    --wel-motion-duration-3: 300ms;
    --wel-motion-duration-4: 500ms;

    --wel-motion-ease: cubic-bezier(0.2, 0, 0.2, 1);
    --wel-motion-ease-in: cubic-bezier(0.4, 0, 1, 1);
    --wel-motion-ease-out: cubic-bezier(0, 0, 0.2, 1);
    --wel-motion-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  @media (prefers-reduced-motion: reduce) {
    :root {
      --wel-motion: 0;
    }
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin radius, border, and shadow tokens (docs/05). Radii are typed
   lengths; borders are px hairlines by decision (ADR-0008 — physical
   crispness, deliberately non-scaling); shadows are composite values built
   from one light-dark() shadow colour so dark mode deepens every elevation
   step through a single token (ADR-0007). */
@layer tokens {
  @property --wel-radius-sm   { syntax: "<length>"; inherits: true; initial-value: 0.25rem; }
  @property --wel-radius-md   { syntax: "<length>"; inherits: true; initial-value: 0.5rem; }
  @property --wel-radius-lg   { syntax: "<length>"; inherits: true; initial-value: 1rem; }
  @property --wel-radius-full { syntax: "<length>"; inherits: true; initial-value: 999rem; }
  @property --wel-radius-control { syntax: "<length>"; inherits: true; initial-value: 0.375rem; }
  @property --wel-radius-surface { syntax: "<length>"; inherits: true; initial-value: 0.75rem; }
  @property --wel-shadow-color { syntax: "<color>"; inherits: true; initial-value: oklch(25% 0.01 250 / 0.15); }

  /* `:root, [data-theme]` — --wel-shadow-color is typed, so its light-dark()
     resolves where declared; re-declaring on pinned subtrees keeps dark-mode
     elevation working inside them (T-84, same mechanism as tokens/color.css). */
  :root, [data-theme] {
    --wel-radius-sm: 0.25rem;
    --wel-radius-md: 0.5rem;
    --wel-radius-lg: 1rem;
    --wel-radius-full: 999rem;
    /* Semantic radii — themes change product personality here. */
    --wel-radius-control: 0.375rem;
    --wel-radius-surface: 0.75rem;

    --wel-border-width: 1px;
    --wel-border-width-strong: 2px;

    /* Elevation. Shadows barely read on dark surfaces, so the dark value is
       deeper and more opaque — one token, both schemes. */
    --wel-shadow-color: light-dark(oklch(25% 0.01 250 / 0.15), oklch(5% 0.01 250 / 0.55));
    --wel-shadow-1: 0 1px 2px var(--wel-shadow-color);
    --wel-shadow-2: 0 2px 6px var(--wel-shadow-color);
    --wel-shadow-3: 0 6px 16px var(--wel-shadow-color);
    --wel-shadow-4: 0 12px 32px var(--wel-shadow-color);
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin space tokens (docs/05, ADR-0008). Same fluid mechanism as type:
   step n = anchor x multiplier (1, 2, 3, 4, 6, 8, 12, 16, 24), each a
   clamp() between compact (anchor-min 0.25rem) and generous
   (anchor-max 0.3125rem) across a 20rem -> 80rem inline viewport, so
   layouts breathe proportionally at every size. Endpoints derive from the
   typed anchors — re-declaring the two anchors re-densifies the system. */
@layer tokens {
  @property --wel-space-anchor-min { syntax: "<length>"; inherits: true; initial-value: 0.25rem; }
  @property --wel-space-anchor-max { syntax: "<length>"; inherits: true; initial-value: 0.3125rem; }

  :root {
    --wel-space-anchor-min: 0.25rem;
    --wel-space-anchor-max: 0.3125rem;

    /* x1 */
    --wel-space-1: clamp(var(--wel-space-anchor-min), 0.2292rem + 0.1042vi, var(--wel-space-anchor-max));
    /* x2 */
    --wel-space-2: clamp(calc(var(--wel-space-anchor-min) * 2), 0.4583rem + 0.2083vi, calc(var(--wel-space-anchor-max) * 2));
    /* x3 */
    --wel-space-3: clamp(calc(var(--wel-space-anchor-min) * 3), 0.6875rem + 0.3125vi, calc(var(--wel-space-anchor-max) * 3));
    /* x4 */
    --wel-space-4: clamp(calc(var(--wel-space-anchor-min) * 4), 0.9167rem + 0.4167vi, calc(var(--wel-space-anchor-max) * 4));
    /* x6 */
    --wel-space-5: clamp(calc(var(--wel-space-anchor-min) * 6), 1.375rem + 0.625vi, calc(var(--wel-space-anchor-max) * 6));
    /* x8 */
    --wel-space-6: clamp(calc(var(--wel-space-anchor-min) * 8), 1.8333rem + 0.8333vi, calc(var(--wel-space-anchor-max) * 8));
    /* x12 */
    --wel-space-7: clamp(calc(var(--wel-space-anchor-min) * 12), 2.75rem + 1.25vi, calc(var(--wel-space-anchor-max) * 12));
    /* x16 */
    --wel-space-8: clamp(calc(var(--wel-space-anchor-min) * 16), 3.6667rem + 1.6667vi, calc(var(--wel-space-anchor-max) * 16));
    /* x24 */
    --wel-space-9: clamp(calc(var(--wel-space-anchor-min) * 24), 5.5rem + 2.5vi, calc(var(--wel-space-anchor-max) * 24));

    /* Semantic alias: default container padding. */
    --wel-space-gutter: var(--wel-space-5);
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin typography tokens (docs/05, ADR-0008). Fluid modular scale:
   each step is clamp(min, preferred, max) interpolating between a
   small-context size (anchor-min x ratio-min^step, ratio 1.2 minor third)
   and a large-context size (anchor-max x ratio-max^step, ratio 1.25 major
   third) across a 20rem -> 80rem inline viewport. rem + vi inside clamp()
   keeps zoom working (WCAG 1.4.4).

   clamp endpoints are calc()-derived from the anchors, so re-declaring the
   two anchor tokens re-densifies the whole scale (ADR-0008); the preferred
   term's slope is written out numerically for the default anchors — a
   docs-phase generator may rewrite this file (ADR-0002), never the build. */
@layer tokens {
  /* Anchors are typed lengths; ratio tokens document the scale and feed the
     regeneration script. */
  @property --wel-text-anchor-min { syntax: "<length>"; inherits: true; initial-value: 1rem; }
  @property --wel-text-anchor-max { syntax: "<length>"; inherits: true; initial-value: 1.125rem; }

  :root {
    --wel-text-anchor-min: 1rem;
    --wel-text-anchor-max: 1.125rem;
    --wel-text-ratio-min: 1.2;
    --wel-text-ratio-max: 1.25;

    /* step -1: min = anchor-min / 1.2, max = anchor-max / 1.25 */
    --wel-text-size--1: clamp(calc(var(--wel-text-anchor-min) * 0.8333), 0.8111rem + 0.1111vi, calc(var(--wel-text-anchor-max) * 0.8));
    /* step 0: the anchors themselves (body size) */
    --wel-text-size-0: clamp(var(--wel-text-anchor-min), 0.9583rem + 0.2083vi, var(--wel-text-anchor-max));
    /* step 1: ratio^1 -> 1.2 / 1.25 */
    --wel-text-size-1: clamp(calc(var(--wel-text-anchor-min) * 1.2), 1.1313rem + 0.3438vi, calc(var(--wel-text-anchor-max) * 1.25));
    /* step 2: ratio^2 -> 1.44 / 1.5625 */
    --wel-text-size-2: clamp(calc(var(--wel-text-anchor-min) * 1.44), 1.3341rem + 0.5297vi, calc(var(--wel-text-anchor-max) * 1.5625));
    /* step 3: ratio^3 -> 1.728 / 1.9531 */
    --wel-text-size-3: clamp(calc(var(--wel-text-anchor-min) * 1.728), 1.5716rem + 0.7822vi, calc(var(--wel-text-anchor-max) * 1.9531));
    /* step 4: ratio^4 -> 2.0736 / 2.4414 */
    --wel-text-size-4: clamp(calc(var(--wel-text-anchor-min) * 2.0736), 1.8493rem + 1.1217vi, calc(var(--wel-text-anchor-max) * 2.4414));
    /* step 5: ratio^5 -> 2.4883 / 3.0518 */
    --wel-text-size-5: clamp(calc(var(--wel-text-anchor-min) * 2.4883), 2.1733rem + 1.5748vi, calc(var(--wel-text-anchor-max) * 3.0518));
    /* step 6: ratio^6 -> 2.986 / 3.8147 */
    --wel-text-size-6: clamp(calc(var(--wel-text-anchor-min) * 2.986), 2.5508rem + 2.1758vi, calc(var(--wel-text-anchor-max) * 3.8147));

    /* Font stacks — system stacks, no bundled webfonts (docs/05). */
    --wel-text-font-body: system-ui, sans-serif;
    --wel-text-font-display: var(--wel-text-font-body);
    --wel-text-font-mono: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace;

    /* Leading and tracking companions. */
    --wel-text-leading-tight: 1.25;
    --wel-text-leading-normal: 1.5;
    --wel-text-leading-loose: 1.75;
    --wel-text-tracking-tight: -0.01em;
    --wel-text-tracking-normal: 0;
    --wel-text-tracking-wide: 0.04em;

    /* Max line length; consumed by .prose and .center (they agree by
       construction — docs/components/prose.md). */
    --wel-text-measure: 65ch;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin base form defaults (docs/04): light element normalisation only.
   The full field/control treatment (borders, sizes, validation states) is
   the form-controls component (docs/components/form-controls.md); base makes
   bare controls presentable and scheme-correct with zero classes. */
@layer base {
  /* Retint native widgets (checks, radios, progress, range) from the accent
     token — the docs/components quick path; dark mode and forced colors stay
     native via color-scheme. */
  input, textarea, select, progress, meter {
    accent-color: var(--wel-color-accent);
  }

  ::placeholder {
    color: var(--wel-color-ink-faint);
    opacity: 1; /* Firefox default dims via opacity; the token carries the value */
  }

  fieldset {
    border: var(--wel-border-width) solid var(--wel-color-border);
    border-radius: var(--wel-radius-md);
    padding-block: var(--wel-space-3);
    padding-inline: var(--wel-space-4);
  }

  legend {
    padding-inline: var(--wel-space-1);
  }

  button:not(:disabled),
  select:not(:disabled),
  input:where([type="checkbox"], [type="radio"], [type="range"]):not(:disabled) {
    cursor: pointer;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin base media defaults (docs/04). Sizing/display normalisation lives
   in the reset; base adds the light presentational voice. */
@layer base {
  figcaption {
    color: var(--wel-color-ink-muted);
    font-size: var(--wel-text-size--1);
  }

  iframe {
    border: none;
    max-inline-size: 100%;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin base table defaults (docs/04): a bare <table> reads as a table —
   hairline row rules, start-aligned headers. The full component (scroller,
   variants, sticky, sorting) is docs/components/table.md. */
@layer base {
  table {
    border-collapse: collapse;
  }

  caption {
    color: var(--wel-color-ink-muted);
    font-size: var(--wel-text-size--1);
    text-align: start;
  }

  th {
    text-align: start;
    font-weight: 600;
  }

  th, td {
    border-block-end: var(--wel-border-width) solid var(--wel-color-border);
    padding-block: var(--wel-space-1);
    padding-inline: var(--wel-space-2);
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* Welkin base typography (docs/04): opinionated element defaults — the
   "classless-ish" experience. Elements are made presentable everywhere;
   editorial rhythm (inter-block spacing, measure) is .prose's job, not
   base's. No class selectors in this layer. */
@layer base {
  body {
    background: var(--wel-color-surface);
    color: var(--wel-color-ink);
    font-family: var(--wel-text-font-body);
    font-size: var(--wel-text-size-0);
    line-height: var(--wel-text-leading-normal);
  }

  /* A data-theme scope pins its colour scheme (ADR-0007, tokens/color.css);
     painting surface + ink here is what makes the pinned region visibly its
     own scheme rather than only retinting native widgets. */
  [data-theme] {
    background: var(--wel-color-surface);
    color: var(--wel-color-ink);
  }

  /* The single global focus ring (docs/09): token-driven, zero-specificity,
     :focus-visible only — never rings on mouse click, never removed.
     Components restyle it via the three tokens, nothing else. */
  :where(:focus-visible) {
    outline: var(--wel-focus-ring-width) solid var(--wel-focus-ring-color);
    outline-offset: var(--wel-focus-ring-offset);
  }

  /* Heading scale: h1..h6 -> size-5..size-0 on the fluid scale (docs/05);
     .prose adopts the same mapping so documents agree with app chrome. */
  h1, h2, h3, h4, h5, h6 {
    font-family: var(--wel-text-font-display);
    line-height: var(--wel-text-leading-tight);
  }

  h1 { font-size: var(--wel-text-size-5); letter-spacing: var(--wel-text-tracking-tight); }
  h2 { font-size: var(--wel-text-size-4); letter-spacing: var(--wel-text-tracking-tight); }
  h3 { font-size: var(--wel-text-size-3); }
  h4 { font-size: var(--wel-text-size-2); }
  h5 { font-size: var(--wel-text-size-1); }
  h6 { font-size: var(--wel-text-size-0); }

  small { font-size: var(--wel-text-size--1); }

  a {
    color: var(--wel-color-accent);
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.15em;
  }

  a:hover {
    color: var(--wel-color-accent-hover);
    text-decoration-thickness: 0.14em;
  }

  code, kbd, samp, pre {
    font-family: var(--wel-text-font-mono);
    font-size: 0.925em;
  }

  code, kbd {
    background: var(--wel-color-surface-sunken);
    border-radius: var(--wel-radius-sm);
    padding-block: 0.1em;
    padding-inline: 0.3em;
  }

  pre {
    background: var(--wel-color-surface-sunken);
    border-radius: var(--wel-radius-md);
    padding: var(--wel-space-3);
    overflow-x: auto; /* physical form permitted until overflow-inline graduates (ADR-0009) */
  }

  /* Inline code inside a pre block must not double up the treatment. */
  pre code {
    background: none;
    border-radius: 0;
    padding: 0;
    font-size: 1em;
  }

  hr {
    border: none;
    border-block-start: var(--wel-border-width) solid var(--wel-color-border);
  }

  mark {
    background: var(--wel-color-warning-tint);
    color: var(--wel-color-ink);
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .center — horizontally centred, measure-capped content column (docs/06,
   T-26). A three-track grid with named lines rather than auto margins, so
   a data-breakout child (full-bleed figure, edge-to-edge section) can
   escape the measure to the full container width while normal flow stays
   capped. Default cap agrees with .prose by construction: both read
   --wel-text-measure. Knobs: --wel-center-max, --wel-center-gutter. */
@layer layout {
  .center {
    display: grid;
    grid-template-columns:
      [full-start] minmax(var(--wel-center-gutter, var(--wel-space-gutter)), 1fr)
      [content-start] min(var(--wel-center-max, var(--wel-text-measure)), 100% - 2 * var(--wel-center-gutter, var(--wel-space-gutter)))
      [content-end] minmax(var(--wel-center-gutter, var(--wel-space-gutter)), 1fr)
      [full-end];
    container-type: inline-size;
    container-name: layout;
  }

  .center > :where(*) {
    grid-column: content;
  }

  .center > :where([data-breakout]) {
    grid-column: full;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .cluster — horizontal grouping that wraps (docs/06, T-22): tag lists,
   button rows, nav items. Knobs: --wel-cluster-gap, --wel-cluster-align,
   --wel-cluster-justify. Establishes a queryable container per the doctrine
   (docs/06 §doctrine #3/#4). */
@layer layout {
  .cluster {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wel-cluster-gap, var(--wel-space-3));
    align-items: var(--wel-cluster-align, center);
    justify-content: var(--wel-cluster-justify, flex-start);
    container-type: inline-size;
    container-name: layout;
  }

  /* Guardrail (ADR-0013): a nested primitive's inline-size containment
     makes its content contribute nothing to flex-basis: auto, so as a
     cluster item it would collapse to zero width. Growing is the only
     content-independent size a wrapping row can give it; zero width is
     never intent, so this only ever replaces a broken render. flex-basis
     stays auto: an explicit inline-size on the item is still honoured as
     the basis, and :where() keeps any author rule winning. */
  .cluster > :where(.stack, .cluster, .grid, .switcher, .sidebar-layout, .center, .cover, .frame) {
    flex-grow: 1;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .cover — full-height cover with a vertically centred principal element
   (docs/06, T-27): heroes, splash sections. Mark the principal child with
   data-principal; its auto block margins centre it while header/footer
   children keep to the edges. Knobs: --wel-cover-min-height,
   --wel-cover-padding. */
@layer layout {
  .cover {
    display: flex;
    flex-direction: column;
    gap: var(--wel-space-4);
    min-block-size: var(--wel-cover-min-height, 100dvb);
    padding: var(--wel-cover-padding, var(--wel-space-5));
    container-type: inline-size;
    container-name: layout;
  }

  .cover > :where([data-principal]) {
    margin-block: auto;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .frame — aspect-ratio media frame with object-fit cropping (docs/06,
   T-28): thumbnails, video embeds, art-directed images that must hold a
   shape regardless of intrinsic media size. Knob: --wel-frame-ratio. */
@layer layout {
  .frame {
    aspect-ratio: var(--wel-frame-ratio, 16 / 9);
    overflow: hidden;
    container-type: inline-size;
    container-name: layout;
  }

  .frame > :where(img, video, picture) {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .grid — auto-fit card grid: as many min-wide columns as fit (docs/06,
   T-25). The min(100%, …) guard keeps a track from forcing overflow in
   containers narrower than the minimum. No column classes, no breakpoints —
   the track list is the whole layout. Knobs: --wel-grid-min, --wel-grid-gap.
   Subgrid row alignment (data-align="rows") is T-29. */
@layer layout {
  .grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--wel-grid-min, 20rem)), 1fr));
    gap: var(--wel-grid-gap, var(--wel-space-4));
    container-type: inline-size;
    container-name: layout;
  }
}

/* data-align="rows" (docs/06, T-29): every child spans its row's implicit
   tracks with subgrid, so headers, bodies, and footers align across cards
   regardless of content length. The named exception to "components never
   depend on parent layout". Gated: without subgrid, span 3 would interleave
   cards across three outer rows — inside @supports the whole rule drops and
   cards stay self-sized. */
@layer variants {
  @supports (grid-template-rows: subgrid) {
    .grid[data-align="rows"] > :where(*) {
      display: grid;
      grid-row: span 3;
      grid-template-rows: subgrid;
    }
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .sidebar-layout — sidebar + content pair that wraps to stacked when the
   content's share would drop below its minimum (docs/06, T-23). Intrinsic:
   no breakpoint — the flex-basis/grow arithmetic decides from available
   space. First child is the sidebar; data-side="end" flips the roles so
   the sidebar sits after the content. Knobs: --wel-sidebar-width,
   --wel-sidebar-content-min (percentage of the pair), --wel-sidebar-gap. */
@layer layout {
  .sidebar-layout {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wel-sidebar-gap, var(--wel-space-5));
    container-type: inline-size;
    container-name: layout;
  }

  /* Sidebar: fixed preferred width, may grow when wrapped to full row. */
  .sidebar-layout > :where(:first-child) {
    flex-grow: 1;
    flex-basis: var(--wel-sidebar-width, 20rem);
  }

  /* Content: grows greedily; wrapping triggers when it would be squeezed
     below its minimum share. */
  .sidebar-layout > :where(:last-child) {
    flex-grow: 999;
    flex-basis: 0;
    min-inline-size: var(--wel-sidebar-content-min, 50%);
  }
}

/* data-side="end": the sidebar is the last child (after the content in
   source order). Attribute rules live in `variants` per ADR-0010's layout
   budget (0-1-0, primitive class only). */
@layer variants {
  .sidebar-layout[data-side="end"] > :where(:first-child) {
    flex-grow: 999;
    flex-basis: 0;
    min-inline-size: var(--wel-sidebar-content-min, 50%);
  }

  .sidebar-layout[data-side="end"] > :where(:last-child) {
    flex-grow: 1;
    flex-basis: var(--wel-sidebar-width, 20rem);
    min-inline-size: auto;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .stack — vertical flow with consistent rhythm (docs/06, T-21). The reset
   strips element margins; the stack owns inter-sibling space instead —
   spacing is a layout decision, not an element default. Rhythm varies by
   nesting stacks, not per-pair margin exceptions. Establishes a queryable
   container per the doctrine (docs/06 §doctrine #3/#4). */
@layer layout {
  .stack {
    display: flex;
    flex-direction: column;
    gap: var(--wel-stack-gap, var(--wel-space-4));
    container-type: inline-size;
    container-name: layout;
  }
}

@layer reset, tokens, base, layout, components, variants, states, utilities, overrides;

/* .switcher — row of equals that switches to a column below a width
   threshold (docs/06, T-24). The (threshold - 100%) * 999 flex-basis trick:
   positive (huge) when the container is narrower than the threshold, so
   every item jumps to full width; negative (clamped to content) when wider.
   Knobs: --wel-switcher-threshold, --wel-switcher-gap; data-limit="2|3|4"
   caps how many items may share a row (a selector cannot read a custom
   property, so the limit is markup — docs/06). */
@layer layout {
  .switcher {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wel-switcher-gap, var(--wel-space-4));
    container-type: inline-size;
    container-name: layout;
  }

  .switcher > :where(*) {
    flex-grow: 1;
    flex-basis: calc((var(--wel-switcher-threshold, 30rem) - 100%) * 999);
  }
}

/* data-limit: with more items than the limit, every item goes full width —
   a crowded row degrades to a clean column rather than a cramped squeeze. */
@layer variants {
  .switcher[data-limit="2"] > :where(:nth-last-child(n + 3), :nth-last-child(n + 3) ~ *),
  .switcher[data-limit="3"] > :where(:nth-last-child(n + 4), :nth-last-child(n + 4) ~ *),
  .switcher[data-limit="4"] > :where(:nth-last-child(n + 5), :nth-last-child(n + 5) ~ *) {
    flex-basis: 100%;
  }
}
