/*!
 * LoamUI — @loamui/core
 * The complete, static stylesheet. Import once at your app root:
 *   import "@loamui/core/styles.css";
 * Nothing runs at runtime — no CSS-in-JS.
 */

@layer loamui.tokens, loamui.elements, loamui.components;

/**
 * LoamUI — design tokens (the `loamui.tokens` layer).
 * Components reference tokens, never raw values, so theming is token
 * remapping at any scope. Bands: inputs → neutrals → derived → scales.
 * Every colour is prefixed `--loam-color-`; there are no raw colour
 * primitives on this surface — the inputs ARE the palette, and everything
 * else is derived from them.
 */

@layer loamui.tokens {
  :root {
    /* ── 1. Inputs — the decisions a theme makes ───────────────────── */

    /* Two font families, both system by default — a theme swaps either.
       Body carries copy; display carries headings (designs typically pair
       a body and a display face). */
    --loam-font: system-ui, sans-serif;
    --loam-font-display: var(--loam-font);
    --loam-font-mono:
      ui-monospace, "SF Mono", "JetBrains Mono", menlo, consolas, monospace;

    /* Primary is the neutral action colour — near-black in light, near-white
       in dark. That is the white-label default: a theme remaps this one line
       to brand. Success owns green; accent is the small chromatic flourish
       (violet by default, for indicators and highlights), kept distinct in hue
       from the status colours so the two roles never read as the same. */
    --loam-color-primary: light-dark(
      oklch(25% 0.006 160deg),
      oklch(92% 0.004 160deg)
    );
    --loam-color-accent: light-dark(
      oklch(48% 0.25 305deg),
      oklch(70% 0.17 305deg)
    );

    /* Status hues. Each derives the same family as primary (soft, strong,
       ring), so a context region can remap the family wholesale. */
    --loam-color-success: light-dark(
      oklch(56% 0.13 150deg),
      oklch(68% 0.13 150deg)
    );
    --loam-color-danger: light-dark(
      oklch(58% 0.2 25deg),
      oklch(66% 0.19 25deg)
    );
    --loam-color-warning: light-dark(
      oklch(72% 0.16 75deg),
      oklch(79% 0.15 80deg)
    );
    --loam-color-info: light-dark(
      oklch(58% 0.16 245deg),
      oklch(68% 0.15 245deg)
    );

    /* Link: accessible blue — the most legible default for `a` tags. A
       project can override with the primary colour where the brand calls
       for it. */
    --loam-color-link: light-dark(
      oklch(50% 0.12 250deg),
      oklch(70% 0.12 250deg)
    );

    /* Highlight: bright yellow for `<mark>` and `::selection`, matching the
       web platform's own default. */
    --loam-color-highlight: light-dark(
      oklch(92% 0.15 100deg),
      oklch(86% 0.16 100deg)
    );

    /* ── 2. Neutrals ───────────────────────────────────────────────── */

    --loam-color-bg: light-dark(oklch(100% 0 0deg), oklch(17% 0.008 160deg));
    --loam-color-bg-subtle: light-dark(
      oklch(98% 0.004 160deg),
      oklch(21% 0.008 160deg)
    );
    --loam-color-surface: light-dark(
      oklch(100% 0 0deg),
      oklch(23% 0.008 160deg)
    );

    /* Foreground — text, but also icons, illustrations and chart fills;
       strong and dim/muted variants. Backgrounds are never dimmed. */
    --loam-color-fg: light-dark(
      oklch(24% 0.02 160deg),
      oklch(96% 0.006 160deg)
    );
    --loam-color-fg-strong: light-dark(
      oklch(15% 0.02 160deg),
      oklch(99% 0 0deg)
    );
    --loam-color-fg-muted: light-dark(
      oklch(52% 0.02 160deg),
      oklch(72% 0.015 160deg)
    );
    --loam-color-fg-dim: light-dark(
      oklch(55% 0.015 160deg),
      oklch(66% 0.012 160deg)
    );

    /* Line — borders, and separators too. */
    --loam-color-line: light-dark(
      oklch(91% 0.006 160deg),
      oklch(32% 0.01 160deg)
    );
    --loam-color-line-strong: light-dark(
      oklch(62% 0.012 160deg),
      oklch(60% 0.014 160deg)
    );

    /* The one foreground that reads on every solid status fill: near-white
       in light (where the fills darken), near-black in dark (where the
       fills stay raw) — the only pairing all statuses hold AA with. */
    --loam-color-on-strong: light-dark(
      oklch(99% 0 0deg),
      oklch(17% 0.01 160deg)
    );

    /* ── 3. Derived — recipes, never hand-tuned per status ─────────── */

    /* Soft tints: pinned lightness and chroma, the colour's own hue —
       relative colour keeps every soft on one recipe, so a rebrand or a
       new status derives its tint for free. */
    --loam-color-primary-soft: light-dark(
      oklch(from var(--loam-color-primary) 96% 0.02 h),
      oklch(from var(--loam-color-primary) 28% 0.03 h)
    );
    --loam-color-success-soft: light-dark(
      oklch(from var(--loam-color-success) 96% 0.035 h),
      oklch(from var(--loam-color-success) 31% 0.075 h)
    );
    --loam-color-danger-soft: light-dark(
      oklch(from var(--loam-color-danger) 96% 0.035 h),
      oklch(from var(--loam-color-danger) 31% 0.075 h)
    );
    --loam-color-warning-soft: light-dark(
      oklch(from var(--loam-color-warning) 96% 0.035 h),
      oklch(from var(--loam-color-warning) 31% 0.075 h)
    );
    --loam-color-info-soft: light-dark(
      oklch(from var(--loam-color-info) 96% 0.035 h),
      oklch(from var(--loam-color-info) 31% 0.075 h)
    );

    /* Solid-fill variants: AA for --loam-color-on-strong. In light the colour
       darkens 22%; in dark the raw colour already carries dark text.
       Audited pairs — see scripts/contrast-audit.mjs. */
    --loam-color-primary-strong: light-dark(
      color-mix(in oklab, var(--loam-color-primary), oklch(0% 0 0deg) 22%),
      var(--loam-color-primary)
    );
    --loam-color-success-strong: light-dark(
      color-mix(in oklab, var(--loam-color-success), oklch(0% 0 0deg) 22%),
      var(--loam-color-success)
    );
    --loam-color-danger-strong: light-dark(
      color-mix(in oklab, var(--loam-color-danger), oklch(0% 0 0deg) 22%),
      var(--loam-color-danger)
    );
    --loam-color-warning-strong: light-dark(
      color-mix(in oklab, var(--loam-color-warning), oklch(0% 0 0deg) 22%),
      var(--loam-color-warning)
    );
    --loam-color-info-strong: light-dark(
      color-mix(in oklab, var(--loam-color-info), oklch(0% 0 0deg) 22%),
      var(--loam-color-info)
    );

    /* Status focus rings alias the solid fills — one audited family covers
       both jobs, and an opaque ring holds its contrast on any surface. */
    --loam-color-primary-ring: var(--loam-color-primary-strong);
    --loam-color-success-ring: var(--loam-color-success-strong);
    --loam-color-danger-ring: var(--loam-color-danger-strong);
    --loam-color-warning-ring: var(--loam-color-warning-strong);
    --loam-color-info-ring: var(--loam-color-info-strong);

    /* Hover lift for surfaces: derived, so a re-themed surface keeps its
       own hover for free. */
    --loam-color-surface-hover: light-dark(
      oklch(from var(--loam-color-surface) 97% c h),
      oklch(from var(--loam-color-surface) 27% c h)
    );

    /* Active focus-ring colour, remapped contextually (e.g. fields with a
       detected error switch it to the danger ring). Pairs with the fixed
       --loam-ring-width below. */
    --loam-color-ring: var(--loam-color-primary-ring);

    /* ── 4. Scales & fixed constants ───────────────────────────────── */

    /* Fluid type scale — cqi falls back to the small viewport where no
       container is declared. Regenerate, don't hand-edit:
       https://utopia.fyi/type/calculator/?c=320,16,1.125,1240,18,1.25,4,2 */
    --loam-text-xs: clamp(0.72rem, 0.8145rem - 0.122cqi, 0.7901rem);
    --loam-text-sm: clamp(0.8889rem, 0.885rem + 0.0193cqi, 0.9rem);
    --loam-text-md: clamp(1rem, 0.9565rem + 0.2174cqi, 1.125rem);
    --loam-text-lg: clamp(1.125rem, 1.0272rem + 0.4891cqi, 1.4063rem);
    --loam-text-xl: clamp(1.2656rem, 1.0944rem + 0.856cqi, 1.7578rem);
    --loam-text-2xl: clamp(1.4238rem, 1.1548rem + 1.3451cqi, 2.1973rem);
    --loam-text-3xl: clamp(1.6018rem, 1.2036rem + 1.9909cqi, 2.7466rem);

    /* Fluid spacing scale — minimums are the fixed values, so nothing
       shrinks below them. Regenerate, don't hand-edit:
       https://utopia.fyi/space/calculator/?c=320,16,1.125,1240,18,1.25&s=0.25|0.5|0.75,1.5 */
    --loam-space-xs: clamp(0.25rem, 0.2391rem + 0.0543cqi, 0.2813rem);
    --loam-space-sm: clamp(0.5rem, 0.4783rem + 0.1087cqi, 0.5625rem);
    --loam-space-md: clamp(0.75rem, 0.7174rem + 0.163cqi, 0.8438rem);
    --loam-space-lg: clamp(1rem, 0.9565rem + 0.2174cqi, 1.125rem);
    --loam-space-xl: clamp(1.5rem, 1.4348rem + 0.3261cqi, 1.6875rem);

    /* Radius — deliberately fixed: corner rounding shouldn't breathe with
       the viewport. */
    --loam-radius-sm: 0.375rem;
    --loam-radius-md: 0.5rem;
    --loam-radius-lg: 0.75rem;
    --loam-radius-xl: 1rem;
    --loam-radius-full: 9999px;

    /* Elevation — a real drop in light; in dark the drop goes transparent
       and an inset top highlight carries the bevel, so the light source
       stays consistent (a shadow below a surface makes no sense on a dark
       canvas). */
    --loam-shadow-sm:
      inset 0 1px 0 0 light-dark(transparent, oklch(100% 0 0deg / 6%)),
      0 1px 2px light-dark(oklch(20% 0.02 160deg / 8%), transparent);
    --loam-shadow-raised:
      inset 0 1px 0 0
        light-dark(oklch(100% 0 0deg / 60%), oklch(100% 0 0deg / 10%)),
      0 1px 2px light-dark(oklch(20% 0.02 160deg / 10%), transparent),
      0 2px 4px light-dark(oklch(20% 0.02 160deg / 7%), transparent),
      0 4px 8px light-dark(oklch(20% 0.02 160deg / 4%), transparent);
    --loam-shadow-md:
      inset 0 1px 0 0 light-dark(transparent, oklch(100% 0 0deg / 6%)),
      0 4px 6px -1px light-dark(oklch(20% 0.02 160deg / 10%), transparent),
      0 2px 4px -2px light-dark(oklch(20% 0.02 160deg / 8%), transparent);
    --loam-shadow-lg:
      inset 0 1px 0 0 light-dark(transparent, oklch(100% 0 0deg / 8%)),
      0 12px 20px -6px light-dark(oklch(20% 0.02 160deg / 14%), transparent),
      0 4px 8px -4px light-dark(oklch(20% 0.02 160deg / 10%), transparent);

    /* Motion — sm: micro feedback; md: default transitions; lg: overlay
       enter/exit and large movement; elastic: sparingly. */
    --loam-duration-sm: 100ms;
    --loam-duration-md: 175ms;
    --loam-duration-lg: 300ms;
    --loam-ease: cubic-bezier(0.4, 0, 0.2, 1);
    --loam-ease-elastic: cubic-bezier(0.34, 1.56, 0.64, 1);

    /* Fixed interaction constants: chosen once, not derived. Ring width pairs
       with the derived --loam-color-ring; disabled opacity may be remapped by a
       component locally. */
    --loam-ring-width: 3px;
    --loam-disabled-opacity: 0.55;

    color-scheme: light dark;

    /* No control-sizing tokens: inputs and buttons align by a shared derived
       anatomy (padding-block --loam-space-sm + --loam-text-sm × 1.2 + 1px
       borders). Do not add height tokens. */
  }

  /* Manual theme override at any scope — the attribute just sets
     color-scheme, so light-dark() re-resolves for that subtree. */
  [data-theme="light"] {
    color-scheme: light;
  }

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

  /*
   * Contexts — `--loam-context: success|warning|info|danger` on any region
   * re-answers the semantic tokens via container style queries. Colour
   * tokens ONLY — never spacing, sizing, or layout.
   */
  @container (style(--loam-context: danger)) {
    :where([class*="loam-"], [class*="loam-"] *) {
      --loam-color-primary: var(--loam-color-danger);
      --loam-color-accent: var(--loam-color-danger);
      --loam-color-primary-soft: var(--loam-color-danger-soft);
      --loam-color-primary-ring: var(--loam-color-danger-ring);

      /* --loam-color-ring and --loam-color-primary-strong bake their var()s AT
         :root — custom properties resolve var() where declared, so both
         must be re-answered directly here or focus rings and solid fills
         would stay brand-coloured. */
      --loam-color-ring: var(--loam-color-danger-ring);
      --loam-color-primary-strong: var(--loam-color-danger-strong);
    }
  }

  @container (style(--loam-context: success)) {
    :where([class*="loam-"], [class*="loam-"] *) {
      --loam-color-primary: var(--loam-color-success);
      --loam-color-accent: var(--loam-color-success);
      --loam-color-primary-soft: var(--loam-color-success-soft);
      --loam-color-primary-ring: var(--loam-color-success-ring);
      --loam-color-ring: var(--loam-color-success-ring);
      --loam-color-primary-strong: var(--loam-color-success-strong);
    }
  }

  @container (style(--loam-context: warning)) {
    :where([class*="loam-"], [class*="loam-"] *) {
      --loam-color-primary: var(--loam-color-warning);
      --loam-color-accent: var(--loam-color-warning);
      --loam-color-primary-soft: var(--loam-color-warning-soft);
      --loam-color-primary-ring: var(--loam-color-warning-ring);
      --loam-color-ring: var(--loam-color-warning-ring);
      --loam-color-primary-strong: var(--loam-color-warning-strong);
    }
  }

  @container (style(--loam-context: info)) {
    :where([class*="loam-"], [class*="loam-"] *) {
      --loam-color-primary: var(--loam-color-info);
      --loam-color-accent: var(--loam-color-info);
      --loam-color-primary-soft: var(--loam-color-info-soft);
      --loam-color-primary-ring: var(--loam-color-info-ring);
      --loam-color-ring: var(--loam-color-info-ring);
      --loam-color-primary-strong: var(--loam-color-info-strong);
    }
  }
}

/**
 * LoamUI — element styles (the `loamui.elements` layer).
 *
 * One of the library's three primitives: enhanced default styles for the
 * native elements themselves, page-wide. We modernise the browser's
 * sensible defaults to improve UX and accessibility — building on them,
 * not resetting them. Importing the stylesheet opts the app in; the
 * cascade keeps every rule overridable — anything the app writes outside a
 * layer wins, and the components layer wins for a component's own parts. No
 * `:where()` armour is needed for that, so selectors here are plain.
 *
 * Margins are additive: blocks carry only a block-end margin, and extra
 * space before a heading comes from an adjacent-sibling rule — nothing
 * needs "unsetting" at the top of a container.
 */

/* Registered channels (must live outside a layer). The radius channel is
   set inline by several components; without inherits: false it would
   cascade into everything nested inside them. The contextualism channel
   inherits by design — nearest ancestor that sets it wins. */
@property --_radius {
  inherits: false;
  syntax: "*";
}

@property --loam-context {
  inherits: true;
  syntax: "*";
}

@property --loam-button-color {
  inherits: true;
  syntax: "*";
}

@layer loamui.elements {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  /* Computed leading, declared broadly on purpose: line-height inherits as a
     COMPUTED value, so a body-level calc would freeze at the body's font size.
     Per-element, every font size derives its own leading, and 2ex tracks the
     font's actual x-height. Form controls and sub/sup set their own leading
     directly, so they are excluded here rather than overridden, keeping the
     rule additive. */
  :where(:not(button, input, select, textarea, sub, sup)) {
    line-height: calc(0.5rem + 2ex);
  }

  html {
    scrollbar-gutter: stable;

    @media (prefers-reduced-motion: no-preference) {
      scroll-behavior: smooth;
    }
  }

  /* Body carries the reading text. OpenType and hyphenation defaults come
     from the platform's own typographic wisdom: old-style proportional
     figures in copy, common ligatures, hanging punctuation, and hyphenated
     justification where the language allows. */
  body {
    background-color: var(--loam-color-bg);
    color: var(--loam-color-fg);
    font-family: var(--loam-font);
    font-kerning: normal;
    font-optical-sizing: auto;
    font-size: var(--loam-text-md);
    font-variant-ligatures: common-ligatures contextual;
    font-variant-numeric: oldstyle-nums proportional-nums;
    hanging-punctuation: first allow-end last;
    hyphenate-limit-chars: 7 4 3;
    hyphens: auto;
    margin: 0;
    text-size-adjust: none;
    text-wrap: pretty;
  }

  /* ── Typography ─────────────────────────────────────────────────── */

  /* Headings use the display family and tighten slightly with size — the
     tightening is in `em`, so it scales with the font automatically (larger
     headings sit tighter). Lining figures suit display type. */
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    color: var(--loam-color-fg-strong);
    font-family: var(--loam-font-display);
    font-variant-numeric: lining-nums proportional-nums;
    letter-spacing: -0.02em;
    margin-block: 0 var(--loam-space-sm);
    text-wrap: balance;
  }

  h1 {
    font-size: var(--loam-text-3xl);
    font-weight: 700;
  }

  h2 {
    font-size: var(--loam-text-2xl);
    font-weight: 700;
  }

  h3 {
    font-size: var(--loam-text-xl);
    font-weight: 700;
  }

  h4 {
    font-size: var(--loam-text-lg);
    font-weight: 600;
  }

  h5 {
    font-size: var(--loam-text-md);
    font-weight: 600;
  }

  h6 {
    font-size: var(--loam-text-sm);
    font-weight: 600;
  }

  /* Breathing room before a heading that follows content — additive, so
     a heading that opens its container needs no reset. */
  :is(p, ul, ol, dl, pre, blockquote, figure, table)
    + :is(h1, h2, h3, h4, h5, h6) {
    margin-block-start: var(--loam-space-xl);
  }

  p {
    margin-block: 0 var(--loam-space-md);
    overflow-wrap: break-word;
  }

  ul,
  ol {
    margin-block: 0 var(--loam-space-md);
    padding-inline-start: var(--loam-space-xl);
  }

  li {
    margin-block-end: var(--loam-space-xs);
  }

  li::marker {
    color: var(--loam-color-fg-muted);
  }

  blockquote {
    border-inline-start: 3px solid var(--loam-color-line-strong);
    color: var(--loam-color-fg-muted);
    margin-block: 0 var(--loam-space-md);
    margin-inline: 0;
    padding-inline-start: var(--loam-space-lg);
  }

  hr {
    border: none;
    border-block-start: 1px solid var(--loam-color-line);
    margin-block: var(--loam-space-lg);
  }

  small {
    font-size: var(--loam-text-sm);
  }

  figcaption {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-sm);
    margin-block-start: var(--loam-space-xs);
  }

  mark {
    background: var(--loam-color-highlight);
    border-radius: var(--loam-radius-sm);
    color: oklch(20% 0 0deg);
    padding-inline: 0.15em;
  }

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

  /* Keep super/subscripts from disturbing line rhythm. */
  sub,
  sup {
    font-size: 0.75em;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }

  sub {
    inset-block-start: 0.25em;
  }

  sup {
    inset-block-start: -0.5em;
  }

  table {
    border-collapse: collapse;
  }

  th {
    text-align: start;
  }

  /* ── Links ──────────────────────────────────────────────────────── */

  /* Links default to the accessible blue, with the classic visited (toward
     purple) and active (toward red) hue shifts derived by relative colour —
     the most legible, most familiar link affordance. A project can override
     --loam-color-link with the brand where it wants to. */
  a {
    &:any-link {
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      text-decoration-color: oklch(from currentcolor l c h / 60%);
      text-decoration-skip-ink: auto;
      text-decoration-thickness: max(0.08em, 1px);
      text-underline-offset: 0.15em;

      @media (prefers-reduced-motion: no-preference) {
        transition: text-decoration-color var(--loam-duration-md)
          var(--loam-ease);
      }
    }

    &:link {
      color: var(--loam-color-link);
    }

    &:visited {
      color: oklch(from var(--loam-color-link) l c calc(h + 35));
    }

    &:active {
      color: oklch(from var(--loam-color-link) l c calc(h + 100));
    }

    @media (hover: hover) {
      &:hover {
        text-decoration: none;
      }
    }
  }

  /* ── Code ───────────────────────────────────────────────────────── */

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

  :is(code, kbd, samp):not(pre *) {
    background: var(--loam-color-bg-subtle);
    border-radius: var(--loam-radius-sm);
    padding: 0.1em 0.35em;
  }

  pre {
    background: var(--loam-color-bg-subtle);
    border-radius: var(--loam-radius-md);
    margin-block: 0 var(--loam-space-md);

    /* overflow (shorthand), not the logical overflow-inline: the latter is not
       yet Baseline, so code blocks would not scroll in some browsers. */
    overflow: auto;
    padding: var(--loam-space-md);
  }

  /* ── Media ──────────────────────────────────────────────────────── */

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

  /* ── Forms & interaction ────────────────────────────────────────── */

  button,
  input,
  select,
  textarea,
  optgroup {
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
  }

  input,
  select,
  textarea,
  progress {
    accent-color: var(--loam-color-primary);
    caret-color: var(--loam-color-primary);
  }

  button,
  input,
  select,
  textarea,
  [role="button"] {
    -webkit-tap-highlight-color: transparent;
  }

  /* Native buttons wear the Button component's own base look (the same
     derivation recipe, on the neutral text channel — the contrast audit
     reads these weights and asserts they match Button.css). Real actions
     should still use <Button/>, which adds context adaptation. */
  button,
  input:is([type="button"], [type="reset"], [type="submit"]),
  ::file-selector-button {
    background: light-dark(
      color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 90%),
      color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 75%)
    );
    border: 1px solid
      color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 80%);
    border-radius: var(--loam-radius-md);
    box-shadow: var(--loam-shadow-raised);
    color: light-dark(
      color-mix(in oklab, var(--loam-color-fg) 70%, oklch(0% 0 0deg)),
      color-mix(in oklab, var(--loam-color-fg) 55%, oklch(100% 0 0deg))
    );
    cursor: pointer;
    font-size: var(--loam-text-sm);
    font-weight: 600;
    line-height: 1.2;
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-lg);
    text-align: center;
    user-select: none;
    white-space: nowrap;

    &:active:not(:disabled) {
      box-shadow: var(--loam-shadow-sm);
    }

    @media (prefers-reduced-motion: no-preference) {
      transition: background var(--loam-duration-sm) var(--loam-ease);
    }

    @media (hover: hover) {
      &:hover:not(:disabled) {
        background: light-dark(
          color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 85%),
          color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 70%)
        );
      }
    }
  }

  /* Textual fields wear the Input component's field look. */
  input:not(
    [type="button"],
    [type="checkbox"],
    [type="color"],
    [type="file"],
    [type="hidden"],
    [type="image"],
    [type="radio"],
    [type="range"],
    [type="reset"],
    [type="submit"]
  ),
  select,
  textarea {
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line-strong);
    border-radius: var(--loam-radius-md);
    font-size: var(--loam-text-sm);
    line-height: 1.2;
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-md);
  }

  ::file-selector-button {
    margin-inline-end: var(--loam-space-md);
  }

  fieldset {
    border: 1px solid var(--loam-color-line);
    border-radius: var(--loam-radius-md);
    padding: var(--loam-space-lg);
  }

  legend {
    font-weight: 500;
    padding-inline: var(--loam-space-xs);
  }

  summary {
    cursor: pointer;
    font-weight: 500;
  }

  /* Focus ring: outline offset from the element so the true surface shows
     through the gap — on any background, the ring reads as separate. The
     outline survives forced-colors, follows radius, and isn't clipped by
     overflow. */
  :focus-visible {
    outline: var(--loam-ring-width) solid var(--loam-color-ring);
    outline-offset: 2px;
  }

  :is(:disabled, [data-disabled], [aria-disabled="true"]),
  :is(:disabled, [data-disabled], [aria-disabled="true"]) * {
    cursor: not-allowed;
  }

  /* ── Page behaviours ────────────────────────────────────────────── */

  ::selection {
    background: var(--loam-color-highlight);
    color: oklch(20% 0 0deg);
  }

  /* Anchored targets never hide beneath sticky chrome. */
  [id] {
    scroll-margin-block-start: var(--loam-space-xl);
  }

  /* ── Print ──────────────────────────────────────────────────────── */

  @media print {
    :is(code, kbd, samp):not(pre *),
    pre,
    mark {
      background: none;
      border: 1px solid var(--loam-color-line);
    }

    button,
    input:is([type="button"], [type="reset"], [type="submit"]),
    ::file-selector-button {
      box-shadow: none;
    }

    a[href^="http"]::after {
      content: " (" attr(href) ")";
      font-size: var(--loam-text-xs);
    }
  }
}

/* Alert */
@layer loamui.components {
/*
 * Everything here derives from a single --_accent channel (the same
 * derivation scheme as Button's --_color).
 * There is no color prop; the surrounding --loam-context region picks the
 * accent. The message stays
 * --loam-text-based so long copy remains readable on the tint.
 */
@scope (.loam-Alert) to ([class*="loam-"]) {
  :scope {
    --_accent: var(--loam-color-fg);

    align-items: flex-start;

    /* The dark tint mixes less background in — the dark page background is
       far from the channel, so an 88% mix would read near-black (same
       compensation as Button's 90%/75%). */
    background: light-dark(
      color-mix(in oklab, var(--_accent), var(--loam-color-bg) 88%),
      color-mix(in oklab, var(--_accent), var(--loam-color-bg) 70%)
    );

    /* Tints mix in oklab (cartesian), not oklch: hue interpolation against
       the near-achromatic page background rotates the channel's hue (the
       declared hue of an achromatic colour still interpolates); cartesian
       mixing only desaturates, so the channel's hue survives. */
    border: 1px solid
      color-mix(in oklab, var(--_accent), var(--loam-color-bg) 55%);
    border-radius: var(--loam-radius-md);
    display: block flex;
    font-size: var(--loam-text-sm);
    gap: var(--loam-space-md);
    padding-block: var(--loam-space-md);
    padding-inline: var(--loam-space-lg);
  }

  span.icon {
    align-items: center;
    block-size: 1.25em;
    color: var(--_accent);
    display: inline flex;
    flex: none;
    font-size: var(--loam-text-md);
    inline-size: 1.25em;
    justify-content: center;
    margin-block-start: 0.05em;
  }

  div.body {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-xs);
    min-inline-size: 0;
  }

  div.title {
    /* The title is TEXT in the channel's hue, not a swatch: mixed toward
       black (light) / white (dark) so it keeps AA contrast on the tint even
       for light channels like warning. Only the decorative icon carries the
       raw channel — the title must read. */
    color: light-dark(
      color-mix(in oklab, var(--_accent) 70%, oklch(0% 0 0deg)),
      color-mix(in oklab, var(--_accent) 55%, oklch(100% 0 0deg))
    );
    font-size: var(--loam-text-sm);
    font-weight: 600;
    line-height: 1.4;
  }

  /* The message carries full-strength text; next to a title it recedes to
     muted so the accent-coloured heading leads. */
  div.message {
    color: var(--loam-color-fg);

    :scope:has(> div.body > div.title) > div.body > & {
      color: var(--loam-color-fg-muted);
    }
  }

  /* Context: within a status region — the channel takes the status colour.
     (Even though the region also remaps --loam-color-primary, the alert keys its
     own channel because its rest state is neutral text, not primary.) */
  @container (style(--loam-context: primary)) {
    :scope {
      --_accent: var(--loam-color-primary);
    }
  }

  @container (style(--loam-context: success)) {
    :scope {
      --_accent: var(--loam-color-success);
    }
  }

  @container (style(--loam-context: warning)) {
    :scope {
      --_accent: var(--loam-color-warning);
    }
  }

  @container (style(--loam-context: info)) {
    :scope {
      --_accent: var(--loam-color-info);
    }
  }

  @container (style(--loam-context: danger)) {
    :scope {
      --_accent: var(--loam-color-danger);
    }
  }
}
}

/* Avatar */
@layer loamui.components {
@scope (.loam-Avatar) to ([class*="loam-"]) {
  :scope {
    --_bg: var(--loam-color-primary-strong);
    --_fg: var(--loam-color-on-strong);

    align-items: center;
    background: var(--_bg);
    block-size: var(--_size, 2.5rem);
    border-radius: var(--_radius, var(--loam-radius-full));
    color: var(--_fg);
    display: inline flex;
    flex: none;
    font-size: var(--loam-text-sm);
    font-weight: 600;
    inline-size: var(--_size, 2.5rem);
    justify-content: center;
    line-height: 1;
    overflow: clip;
    user-select: none;
  }

  img.image {
    block-size: 100%;
    display: block flow;
    inline-size: 100%;
    object-fit: cover;
  }

  span.initials {
    font-size: 0.85em;
    letter-spacing: 0.02em;
  }

  svg.glyph {
    block-size: 65%;
    inline-size: 65%;
    opacity: 0.9;
  }

  /* The initials background answers the surrounding context region, the
     same contract as every status-coloured component. */
  @container (style(--loam-context: danger)) {
    :scope {
      --_bg: var(--loam-color-danger-strong);
    }
  }

  @container (style(--loam-context: warning)) {
    :scope {
      --_bg: var(--loam-color-warning-strong);
    }
  }

  @container (style(--loam-context: info)) {
    :scope {
      --_bg: var(--loam-color-info-strong);
    }
  }

  @container (style(--loam-context: success)) {
    :scope {
      --_bg: var(--loam-color-success-strong);
    }
  }
}

/* Group — overlap children with a surface ring */
@scope (.loam-Avatar-group) {
  :scope {
    align-items: center;
    display: inline flex;

    > * {
      border: 2px solid var(--loam-color-surface);
      box-sizing: content-box;
      margin-inline-start: calc(-1 * var(--_overlap, 0.5rem));

      /* Identity at rest — the hover lift transitions both ways. */
      translate: 0 0;

      @media (prefers-reduced-motion: no-preference) {
        transition: translate var(--loam-duration-md) var(--loam-ease);
      }
    }

    > *:first-child {
      margin-inline-start: 0;
    }

    @media (hover: hover) {
      > *:hover {
        translate: 0 -2px;
        z-index: 1;
      }
    }
  }
}
}

/* Badge */
@layer loamui.components {
/*
 * The pill is mixed down from one --_color channel: a soft tint toward
 * the page background, with the text pushed toward black or white so the
 * label always reads on its own tint. The accent is the region's.
 */
@scope (.loam-Badge) to ([class*="loam-"]) {
  :scope {
    --_color: var(--loam-color-fg);

    align-items: center;

    /* Tints mix in oklab (cartesian), not oklch: hue interpolation against
       the near-achromatic page background rotates the channel's hue (the
       declared hue of an achromatic colour still interpolates); cartesian
       mixing only desaturates, so the channel's hue survives. */
    background: color-mix(in oklab, var(--_color), var(--loam-color-bg) 85%);
    block-size: var(--_h, 1.375rem);
    border-radius: var(--_radius, var(--loam-radius-full));
    color: light-dark(
      color-mix(in oklab, var(--_color) 70%, oklch(0% 0 0deg)),
      color-mix(in oklab, var(--_color) 55%, oklch(100% 0 0deg))
    );
    display: inline flex;
    font-size: var(--_fz, var(--loam-text-xs));
    font-weight: 600;
    gap: var(--_gap, var(--loam-space-xs));
    inline-size: fit-content;
    letter-spacing: 0.01em;
    line-height: 1;
    padding-inline: var(--_px, 0.625rem);
    user-select: none;
    white-space: nowrap;

    &[data-size="sm"] {
      --_h: 1.125rem;
      --_px: 0.5rem;
      --_fz: 0.6875rem;
    }

    &[data-size="lg"] {
      --_h: 1.625rem;
      --_px: 0.8125rem;
      --_fz: var(--loam-text-sm);
      --_gap: var(--loam-space-sm);
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        background var(--loam-duration-md) var(--loam-ease),
        color var(--loam-duration-md) var(--loam-ease);
    }
  }

  /* The status dot carries the raw channel — a swatch, not text. */
  span.dot {
    background: var(--_color);
    block-size: 0.5em;
    border-radius: var(--loam-radius-full);
    flex: none;
    inline-size: 0.5em;
  }

  :scope:has(svg) {
    gap: var(--_gap, var(--loam-space-xs));

    svg {
      block-size: auto;
      flex: none;
      inline-size: 1em;
    }
  }

  /* Context: within a status region — the channel takes the status colour.
     (Even though the region also remaps --loam-color-primary, the badge keys its
     own channel because its rest state is neutral text, not primary.) */
  @container (style(--loam-context: primary)) {
    :scope {
      --_color: var(--loam-color-primary);
    }
  }

  @container (style(--loam-context: success)) {
    :scope {
      --_color: var(--loam-color-success);
    }
  }

  @container (style(--loam-context: warning)) {
    :scope {
      --_color: var(--loam-color-warning);
    }
  }

  @container (style(--loam-context: info)) {
    :scope {
      --_color: var(--loam-color-info);
    }
  }

  @container (style(--loam-context: danger)) {
    :scope {
      --_color: var(--loam-color-danger);
    }
  }

  /* Optical trim: cap-to-alphabetic, so the label centres on the glyphs
     rather than the line box — the pill case where half-leading shows. */
  @supports (text-box: trim-both cap alphabetic) {
    :scope {
      text-box: trim-both cap alphabetic;
    }
  }
}
}

/* Breadcrumbs */
@layer loamui.components {
@scope (.loam-Breadcrumbs) {
  :scope {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-sm);

    ol {
      align-items: center;
      display: block flex;
      flex-wrap: wrap;
      gap: var(--loam-space-sm);
      list-style: none;
      margin: 0;
      padding: 0;
    }

    li {
      margin-block: 0;
    }
  }
}

/*
 * Items are scoped to their own root: an <li class="loam-Breadcrumbs-item"> is
 * rendered by the standalone Item component, without the root class on it.
 */
@scope (.loam-Breadcrumbs-item) to ([class*="loam-"]) {
  :scope {
    align-items: center;
    display: inline flex;
    min-inline-size: 0;

    &:not(:first-child)::before {
      color: var(--loam-color-fg-dim);

      /* The empty alt keeps the glyph out of the accessibility tree. */
      content: var(--_separator, "/") / "";
      margin-inline-end: var(--loam-space-sm);
      user-select: none;
    }

    :where(a) {
      border-radius: var(--loam-radius-sm);
      color: var(--loam-color-fg-muted);
      padding-inline: 2px;
      text-decoration: none;

      @media (prefers-reduced-motion: no-preference) {
        transition: color var(--loam-duration-md) var(--loam-ease);
      }

      @media (hover: hover) {
        &:hover {
          color: var(--loam-color-primary);
          text-decoration: underline;
        }
      }
    }

    &[data-current] {
      color: var(--loam-color-fg);
      font-weight: 600;
    }
  }
}
}

/* Button */
@layer loamui.components {
/*
 * Button — one colour channel (--_color); every look derives from it via
 * color-mix and relative colour. Appearance is decided by context, not
 * props. The one instance-level escape hatch is the public
 * --loam-button-color property (registered in elements.css).
 */
@scope (.loam-Button) {
  :scope {
    --_color: var(--loam-button-color, var(--loam-color-fg));

    background: light-dark(
      color-mix(in oklab, var(--_color), var(--loam-color-bg) 90%),
      color-mix(in oklab, var(--_color), var(--loam-color-bg) 75%)
    );

    /* Tints mix in oklab (cartesian), not oklch: hue interpolation against
       the near-achromatic page background rotates the channel's hue;
       cartesian mixing only desaturates, so the hue survives. */
    border: 1px solid
      color-mix(in oklab, var(--_color), var(--loam-color-bg) 80%);
    border-radius: var(--loam-button-radius, var(--loam-radius-md));
    box-shadow: var(--loam-shadow-raised);

    /* Text is the channel's hue mixed for AA on the tint, the same
       recipe as Badge and Alert; raw --_color sat below 4.5:1 on its
       own background in six of eight scheme/status combinations. */
    color: light-dark(
      color-mix(in oklab, var(--_color) 70%, oklch(0% 0 0deg)),
      color-mix(in oklab, var(--_color) 55%, oklch(100% 0 0deg))
    );
    display: inline flow-root;
    font-size: var(--loam-text-sm);
    font-weight: 600;
    line-height: 1.2;
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-lg);
    text-align: center;
    text-decoration: none;

    /* Identity at rest — the pressed shift transitions both ways. */
    translate: 0 0;
    user-select: none;
    white-space: nowrap;

    &:active:not(:disabled) {
      background: light-dark(
        color-mix(in oklab, var(--_color), var(--loam-color-bg) 85%),
        color-mix(in oklab, var(--_color), var(--loam-color-bg) 70%)
      );
      box-shadow: var(--loam-shadow-sm);
      color: light-dark(
        oklch(from var(--_color) calc(l - 0.2) c h),
        oklch(from var(--_color) calc(l + 0.2) c h)
      );
      translate: 0 1px;
    }

    &:disabled {
      opacity: var(--loam-disabled-opacity);
    }

    @media (hover: hover) {
      &:hover:not(:disabled) {
        background: light-dark(
          color-mix(in oklab, var(--_color), var(--loam-color-bg) 85%),
          color-mix(in oklab, var(--_color), var(--loam-color-bg) 70%)
        );
        color: light-dark(
          oklch(from var(--_color) calc(l - 0.2) c h),
          oklch(from var(--_color) calc(l + 0.2) c h)
        );
      }
    }

    /* The pressed shift and shadow drop are motion — opt-in only. */
    @media (prefers-reduced-motion: no-preference) {
      transition:
        background var(--loam-duration-sm) var(--loam-ease),
        border-color var(--loam-duration-sm) var(--loam-ease),
        color var(--loam-duration-sm) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease),
        translate var(--loam-duration-md) var(--loam-ease);
    }
  }

  /* A composed Loader is an icon here: the same detection, layout and 1em
     sizing (answered through the loader's inherited size hook). */
  :scope:has(svg, .loam-Loader) {
    --loam-loader-size: 1em;

    align-items: center;
    display: inline flex;
    gap: var(--loam-space-sm);
    justify-content: center;

    svg {
      block-size: auto;
      inline-size: 1em;
    }
  }

  /* Icon-only is detected from the accessible name, not DOM shape: an
     icon-only button MUST carry an aria-label, and `svg:only-child` can't
     tell "icon + bare text" from "icon alone" (text nodes aren't elements). */
  :scope[aria-label]:has(svg, .loam-Loader) {
    padding-inline: var(--loam-space-sm);
  }

  /* Context: within a primary region — THE action of the area. */
  @container (style(--loam-context: primary)) {
    :scope {
      --_color: var(--loam-color-primary);
    }
  }

  /* Context: within a status region. (The region also re-answers the semantic
     tokens for every other component — see the Contexts section of tokens.css;
     these rules key the button's own channel.) */
  @container (style(--loam-context: danger)) {
    :scope {
      --_color: var(--loam-color-danger);
    }
  }

  @container (style(--loam-context: success)) {
    :scope {
      --_color: var(--loam-color-success);
    }
  }

  @container (style(--loam-context: warning)) {
    :scope {
      --_color: var(--loam-color-warning);
    }
  }

  @container (style(--loam-context: info)) {
    :scope {
      --_color: var(--loam-color-info);
    }
  }

  @container (inline-size <= 16rem) {
    :scope {
      inline-size: 100%;
    }
  }
}
}

/* Card */
@layer loamui.components {
@scope (.loam-Card) to ([class*="loam-"]) {
  :scope {
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line);
    border-radius: var(--_radius, var(--loam-radius-lg));
    display: block flow;
    padding: var(--loam-space-lg);
  }
}
}

/* Checkbox */
@layer loamui.components {
/* The control is a native checkbox — the elements layer paints it with
   accent-color (the neutral primary). The component adds only a comfortable
   size, the invalid affordance, and the label row. */
@scope (.loam-Checkbox) {
  :scope {
    accent-color: var(--loam-color-primary);
    block-size: 1.15em;
    flex-shrink: 0;
    inline-size: 1.15em;

    /* Optically align with the first line of label text. */
    margin: 0.15em 0 0;

    /* Invalid is a box-shadow, not an outline, so it stacks with rather than
       replaces the elements-layer :focus-visible ring; the focus ring itself
       turns danger-coloured. Mirrors Switch. */
    &[aria-invalid="true"] {
      --loam-color-ring: var(--loam-color-danger-ring);

      box-shadow: 0 0 0 2px var(--loam-color-danger);
    }
  }
}

/* The labelled form: an inline row with the control beside its text.
   Donut: the label body hosts consumer content. */
@scope (.loam-Checkbox-wrapper) to ([class*="loam-"]) {
  :scope {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-xs);

    &[data-disabled] {
      opacity: var(--loam-disabled-opacity);
    }
  }

  label {
    align-items: flex-start;
    cursor: pointer;
    display: block flex;
    gap: var(--loam-space-sm);

    &:has(input:disabled) {
      cursor: not-allowed;
    }
  }

  span.body {
    display: block flex;
    flex-direction: column;
    gap: 0.1rem;
  }

  span.label {
    font-size: var(--loam-text-sm);
    font-weight: 500;
    line-height: 1.35;
  }

  span.description {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-xs);
  }
}
}

/* DateInput */
@layer loamui.components {
/* The legend comes from the Fieldset primitive and each part reuses
   Input, so borders, focus rings and the aria-invalid danger state
   are Input's own. Only the description/error text and the part layout
   live here. The donut stops this scope at the composed Inputs. */
@scope (.loam-DateInput) to ([class*="loam-"]) {
  p.description {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-sm);
    margin: 0;
  }

  p.error {
    color: var(--loam-color-danger);
    font-size: var(--loam-text-sm);
    margin: 0;
  }

  div.parts {
    display: block flex;
    flex-wrap: wrap;
    gap: var(--loam-space-md);
  }

  [data-part] {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-xs);
  }

  label {
    font-size: var(--loam-text-sm);
    font-weight: 500;
  }
}

/* Deliberate reaches into the composed Input — scoped without the donut on
   purpose, because the subject IS the nested component. Width is
   information: the input is sized to its answer (two digits, four for the
   year, plus a character of editing slack) and the box shrink-wraps it —
   the control's padding and borders are never restated here. */
@scope (.loam-DateInput) {
  [data-part] > .loam-Input-field {
    inline-size: fit-content;
  }

  [data-part="year"] .loam-Input-field input {
    flex: none;
    inline-size: 5ch;
  }

  [data-part="day"] .loam-Input-field input {
    flex: none;
    inline-size: 3ch;
  }

  /* The month takes names ("Mar") as well as numbers — give the answer
     room to be typed. */
  [data-part="month"] .loam-Input-field input {
    flex: none;
    inline-size: 4ch;
  }
}
}

/* Details */
@layer loamui.components {
/* The native disclosure, standalone: each Details is its own boxed
   surface. A set sharing a `name` needs no wrapper — the browser owns
   the exclusivity. */
@scope (.loam-Details) to ([class*="loam-"]) {
  :scope {
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line);
    border-radius: var(--loam-radius-md);
    inline-size: 100%;
    overflow: clip;

    > summary {
      align-items: center;
      cursor: pointer;
      display: block flex;
      font-size: var(--loam-text-md);
      font-weight: 600;
      gap: var(--loam-space-md);
      justify-content: space-between;
      line-height: 1.4;
      list-style: none;
      padding-block: var(--loam-space-md);
      padding-inline: var(--loam-space-lg);
      user-select: none;

      @media (prefers-reduced-motion: no-preference) {
        transition: background var(--loam-duration-md) var(--loam-ease);
      }
    }
  }

  :scope > summary::-webkit-details-marker {
    display: none;
  }

  :scope[open] > summary {
    color: var(--loam-color-primary);
  }

  span.label {
    min-inline-size: 0;
  }

  svg.chevron {
    color: var(--loam-color-fg-muted);
    flex: none;
    rotate: 0deg;

    :scope[open] > summary & {
      color: var(--loam-color-primary);
      rotate: 180deg;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition: rotate var(--loam-duration-md) var(--loam-ease);
    }
  }

  div.content {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-sm);
    line-height: 1.55;
    padding-block: 0 var(--loam-space-lg);
    padding-inline: var(--loam-space-lg);
  }

  @media (hover: hover) {
    :scope > summary:hover {
      background: var(--loam-color-surface-hover);
    }
  }

  /* Open/close height animation on the platform's own content slot.
     interpolate-size is required for the 0 → auto interpolation and is not
     yet Baseline, so the whole recipe gates on it (elsewhere: today's
     instant toggle). Declared on the item, never :root — a library must not
     leak keyword interpolation into the host page. */
  @supports (interpolate-size: allow-keywords) {
    :scope {
      interpolate-size: allow-keywords;
    }

    @media (prefers-reduced-motion: no-preference) {
      :scope::details-content {
        block-size: 0;
        overflow-block: clip;
        transition:
          block-size var(--loam-duration-md) var(--loam-ease),
          content-visibility var(--loam-duration-md) var(--loam-ease)
            allow-discrete;
      }

      :scope[open]::details-content {
        block-size: auto;
      }
    }
  }
}
}

/* Drawer */
@layer loamui.components {
@scope (.loam-Drawer-panel) to ([class*="loam-"]) {
  :scope {
    --_size: 24rem;
    --_hidden: 0 0;

    background: var(--loam-color-surface);
    border: 0;
    box-shadow: var(--loam-shadow-lg);
    font-size: var(--loam-text-sm);
    margin: 0;
    overflow: auto;
    overscroll-behavior: contain;
    padding: var(--loam-space-lg);

    &[data-size="sm"] {
      --_size: 18rem;
    }

    &[data-size="lg"] {
      --_size: 30rem;
    }

    &[data-side="start"],
    &[data-side="end"] {
      block-size: auto;
      inline-size: min(var(--_size), 100dvi - var(--loam-space-xl));
      inset-block: 0;
      max-block-size: 100dvb;
    }

    &[data-side="start"] {
      --_hidden: -100% 0;

      border-end-end-radius: var(--loam-radius-lg);
      border-start-end-radius: var(--loam-radius-lg);
      inset-inline: 0 auto;
    }

    &[data-side="end"] {
      --_hidden: 100% 0;

      border-end-start-radius: var(--loam-radius-lg);
      border-start-start-radius: var(--loam-radius-lg);
      inset-inline: auto 0;
    }

    &[data-side="top"],
    &[data-side="bottom"] {
      block-size: min(var(--_size), 100dvb - var(--loam-space-xl));
      inline-size: auto;
      inset-inline: 0;
      max-inline-size: 100dvi;
    }

    &[data-side="top"] {
      --_hidden: 0 -100%;

      border-end-end-radius: var(--loam-radius-lg);
      border-end-start-radius: var(--loam-radius-lg);
      inset-block: 0 auto;
    }

    &[data-side="bottom"] {
      --_hidden: 0 100%;

      border-start-end-radius: var(--loam-radius-lg);
      border-start-start-radius: var(--loam-radius-lg);
      inset-block: auto 0;
    }

    &::backdrop {
      background: color-mix(in oklch, var(--loam-color-fg) 45%, transparent);
    }

    @media (forced-colors: active) {
      border: 1px solid;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        translate var(--loam-duration-lg) var(--loam-ease),
        display var(--loam-duration-lg) var(--loam-ease) allow-discrete,
        overlay var(--loam-duration-lg) var(--loam-ease) allow-discrete;
      translate: 0 0;

      &:not([open]) {
        translate: var(--_hidden);
      }

      &::backdrop {
        opacity: 1;
        transition:
          opacity var(--loam-duration-lg) var(--loam-ease),
          display var(--loam-duration-lg) var(--loam-ease) allow-discrete,
          overlay var(--loam-duration-lg) var(--loam-ease) allow-discrete;

        @starting-style {
          opacity: 0;
        }
      }

      &:not([open])::backdrop {
        opacity: 0;
      }

      @starting-style {
        translate: var(--_hidden);
      }
    }
  }

  h2.title {
    font-size: var(--loam-text-lg);
    font-weight: 600;
    margin-block: 0 var(--loam-space-sm);
  }

  p.description {
    color: var(--loam-color-fg-muted);
    margin-block: 0 var(--loam-space-md);
  }
}
}

/* ErrorSummary */
@layer loamui.components {
@scope (.loam-ErrorSummary) {
  :scope {
    background: var(--loam-color-danger-soft);
    border: 2px solid var(--loam-color-danger);
    border-radius: var(--loam-radius-md);
    padding: var(--loam-space-lg);

    &:focus-visible {
      outline: var(--loam-ring-width) solid var(--loam-color-danger-ring);
      outline-offset: 2px;
    }
  }

  h2 {
    font-size: var(--loam-text-md);
    font-weight: 700;
    margin: 0;
    text-wrap: balance;
  }

  li {
    margin-block: 0;
  }

  ul {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-xs);
    margin-block: var(--loam-space-sm) 0;
    margin-inline: 0;
    padding-inline-start: var(--loam-space-lg);
  }

  a {
    color: light-dark(
      color-mix(in oklab, var(--loam-color-danger) 70%, oklch(0% 0 0deg)),
      color-mix(in oklab, var(--loam-color-danger) 55%, oklch(100% 0 0deg))
    );
    font-size: var(--loam-text-sm);
    font-weight: 600;
    text-decoration: underline;
    text-decoration-skip-ink: auto;
  }
}
}

/* Field */
@layer loamui.components {
/*
 * Field — parts are targeted from within the @scope; the root keeps a
 * `loam-` class so the scope root is unambiguous and app styles can find it.
 * The scope stops at any nested loam-classed element: the field hosts
 * controls it doesn't own (a Checkbox has its own label), and the donut
 * keeps these type selectors on the field's own parts only.
 */
@scope (.loam-Field) to ([class*="loam-"]) {
  :scope {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-xs);
    inline-size: 100%;
  }

  label {
    align-items: center;
    display: block flex;
    font-size: var(--loam-text-sm);
    font-weight: 500;
    gap: var(--loam-space-sm);
  }

  span.optional {
    color: var(--loam-color-fg-muted);
    font-weight: 400;
  }

  p.description {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-sm);
    margin: 0;
  }

  p.error {
    color: var(--loam-color-danger);
    font-size: var(--loam-text-sm);
    margin: 0;
  }
}

/* The screen-reader-only "Error: " prefix inside error messages:
   role="alert" announces the text, and the prefix makes it unmistakable
   out of context. Self-scoped; shared by every error renderer. */
@scope (.loam-Error-prefix) {
  :scope {
    block-size: 1px;
    border: 0;
    clip-path: inset(50%);
    inline-size: 1px;
    margin: -1px;
    overflow: clip;
    padding: 0;
    position: absolute;
    white-space: nowrap;
  }
}
}

/* Fieldset */
@layer loamui.components {
/*
 * Fieldset — native <fieldset>/<legend>, styled with @scope. The browser's
 * default fieldset border/margin/padding are reset so it composes cleanly.
 */
@scope (.loam-Fieldset) to ([class*="loam-"]) {
  :scope {
    border: 0;
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-sm);
    margin: 0;
    min-inline-size: 0;
    padding: 0;
  }

  legend {
    font-size: var(--loam-text-sm);
    font-weight: 500;
    padding: 0;
  }

  span.optional {
    color: var(--loam-color-fg-muted);
    font-weight: 400;
  }
}
}

/* Input */
@layer loamui.components {
@scope (.loam-Input-field) to ([class*="loam-"]) {
  :scope {
    align-items: center;
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line-strong);
    border-radius: var(--_radius, var(--loam-radius-md));
    display: block flex;
    font-size: var(--loam-text-sm);
    gap: var(--loam-space-sm);
    line-height: 1.2;

    /* Height is derived, not declared: same stack as Button (padding-block
       space-sm + text-sm × 1.2 + 1px border), so inputs and buttons align
       by construction at every container width. */
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-md);

    &:focus-within {
      border-color: var(--loam-color-primary);
      outline: var(--loam-ring-width) solid var(--loam-color-ring);
      outline-offset: 2px;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        border-color var(--loam-duration-sm) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease);
    }
  }

  /* Autofilled: the wrapper border carries the signal (the input itself
     paints the tint — see below). :autofill and :-webkit-autofill must stay
     in separate blocks — one unsupported selector invalidates a shared
     list. Declared before the error state so danger wins at equal
     specificity. */
  :scope:has(input:autofill) {
    border-color: color-mix(
      in oklab,
      var(--loam-color-primary) 50%,
      var(--loam-color-line-strong)
    );
  }

  :scope:has(input:-webkit-autofill) {
    border-color: color-mix(
      in oklab,
      var(--loam-color-primary) 50%,
      var(--loam-color-line-strong)
    );
  }

  /* Invalid after a composed Field.Error or a native submit attempt. */
  :scope:has(input[aria-invalid="true"]) {
    --loam-color-ring: var(--loam-color-danger-ring);

    border-color: var(--loam-color-danger);
  }

  :scope[data-disabled] {
    background: var(--loam-color-bg-subtle);
    opacity: var(--loam-disabled-opacity);
  }

  input {
    background: transparent;
    border: none;
    color: inherit;
    flex: 1;
    font: inherit;
    min-inline-size: 0;
    outline: none;

    /* UA inputs carry 1-2px default padding; with a derived (auto) height
       that would break the Button alignment, so zero it explicitly. */
    padding: 0;
  }

  input::placeholder {
    color: var(--loam-color-fg-dim);
  }

  /* background-color cannot be overridden on an autofilled field; an inset
     shadow wider than the control paints the tint instead. Separate blocks —
     see the wrapper rules above. */
  input:autofill {
    box-shadow: inset 0 0 0 100px
      color-mix(
        in oklab,
        var(--loam-color-primary) 8%,
        var(--loam-color-surface)
      );
  }

  input:-webkit-autofill {
    box-shadow: inset 0 0 0 100px
      color-mix(
        in oklab,
        var(--loam-color-primary) 8%,
        var(--loam-color-surface)
      );
  }

  span.section {
    align-items: center;
    color: var(--loam-color-fg-muted);
    display: inline flex;
    flex-shrink: 0;

    /* Sections must not stretch the derived height. */
    svg {
      block-size: auto;
      inline-size: 1.125em;
    }
  }
}
}

/* Loader */
@layer loamui.components {
@scope (.loam-Loader) {
  :scope {
    /* The hook is inherited, so a composing component can size a loader
       from its own context (Button answers it with the same 1em its
       icons get) without fighting this default's specificity. */
    --_size: var(--loam-loader-size, 1.5rem);

    align-items: center;
    block-size: var(--_size);

    /* The accent — a loader is a pure indicator, and indicators carry the
       chromatic flourish while everything else stays neutral. A context
       region still recolours it (context remaps the accent too). */
    color: var(--loam-color-accent);
    display: inline flex;
    inline-size: var(--_size);
    justify-content: center;
    vertical-align: middle;
  }

  span.sr-only {
    block-size: 1px;
    border: 0;
    clip-path: inset(50%);
    inline-size: 1px;
    margin: -1px;
    overflow: clip;
    padding: 0;
    position: absolute;
    white-space: nowrap;
  }

  /* Spinner — rotating ring */
  span.spinner {
    block-size: 100%;
    border: max(2px, calc(var(--_size) * 0.12)) solid
      color-mix(in oklch, currentcolor 25%, transparent);
    border-block-start-color: currentcolor;
    border-radius: var(--loam-radius-full);
    inline-size: 100%;

    @media (prefers-reduced-motion: no-preference) {
      animation: loam-loader-spin 0.65s linear infinite;
    }
  }
}

@keyframes loam-loader-spin {
  to {
    transform: rotate(1turn);
  }
}
}

/* Menu */
@layer loamui.components {
@scope (.loam-Menu) {
  :scope {
    display: inline flow-root;
    position: relative;
  }
}

/* The popup escapes the wrapper into the top layer when enhanced, so it
   carries its own scope (same engine as Popover). */
@scope (.loam-Menu-popup) to ([class*="loam-"]) {
  :scope {
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line);
    border-radius: var(--loam-radius-md);
    box-shadow: var(--loam-shadow-lg);
    inline-size: max-content;
    margin: 0;
    max-inline-size: min(18rem, 90vi);
    min-inline-size: 11rem;
    padding: var(--loam-space-xs);

    /* Enhanced: top layer + anchor positioning, tethered to the trigger. */
    @supports (anchor-name: --loam-probe) {
      /* The UA gives [popover] `inset: 0`; clear it so only the insets each
         branch sets take part, otherwise the box is over-constrained. */
      inset: auto;
      inset-inline-start: anchor(start);
      position: fixed;
      position-try: flip-block;

      &[data-position="bottom"] {
        inset-block-start: calc(anchor(end) + var(--loam-space-xs));
      }

      &[data-position="top"] {
        inset-block-end: calc(anchor(start) + var(--loam-space-xs));
      }

      @media (prefers-reduced-motion: no-preference) {
        /* -md where Popover uses -lg: intentional — menus should feel
           snappier than panels. */
        opacity: 1;
        transition:
          opacity var(--loam-duration-md) var(--loam-ease),
          translate var(--loam-duration-md) var(--loam-ease),
          display var(--loam-duration-md) var(--loam-ease) allow-discrete,
          overlay var(--loam-duration-md) var(--loam-ease) allow-discrete;
        translate: 0 0;

        /* Exit: transition back to the closed state (see Modal). */
        &:not(:popover-open) {
          opacity: 0;
          translate: 0 -0.25rem;
        }

        &[data-position="top"] {
          &:not(:popover-open) {
            translate: 0 0.25rem;
          }

          @starting-style {
            translate: 0 0.25rem;
          }
        }

        @starting-style {
          opacity: 0;
          translate: 0 -0.25rem;
        }
      }
    }

    /* Fallback: no anchor positioning (and no top layer) — anchor to the
       wrapper as a normal absolutely-positioned panel. */
    @supports not (anchor-name: --loam-probe) {
      inset-inline-start: 0;
      position: absolute;
      z-index: 50;

      &[data-position="bottom"] {
        inset-block-start: calc(100% + var(--loam-space-xs));
      }

      &[data-position="top"] {
        inset-block-end: calc(100% + var(--loam-space-xs));
      }
    }
  }

  .item {
    align-items: center;
    background: none;
    border: 0;
    border-radius: var(--loam-radius-sm);
    box-shadow: none;
    color: var(--loam-color-fg);
    cursor: default;
    display: block flex;
    font: inherit;
    font-size: var(--loam-text-sm);
    gap: var(--loam-space-sm);
    inline-size: 100%;

    /* Focus IS the highlight in a menu (roving focus, APG pattern); the
       elements-layer ring is for tabbable controls, not menu items. */
    outline: none;
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-sm);
    text-align: start;
    text-decoration: none;
    user-select: none;
    white-space: nowrap;

    &:focus:not([aria-disabled="true"]) {
      background: var(--loam-color-primary-soft);
    }

    &[aria-disabled="true"] {
      color: var(--loam-color-fg-dim);
    }

    svg {
      block-size: auto;
      flex-shrink: 0;
      inline-size: 1em;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition: background var(--loam-duration-sm) var(--loam-ease);
    }

    @media (hover: hover) {
      &:hover:not([aria-disabled="true"]) {
        background: var(--loam-color-primary-soft);
      }
    }
  }

  div.group-label {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    padding-block: var(--loam-space-xs);
    padding-inline: var(--loam-space-sm);
    text-transform: uppercase;
  }

  hr {
    border: 0;
    border-block-start: 1px solid var(--loam-color-line);
    margin-block: var(--loam-space-xs);
    margin-inline: calc(-1 * var(--loam-space-xs));
  }

  /* Forced colors: the roving highlight is background-only, and
     aria-disabled (unlike :disabled) gets no automatic GrayText. */
  @media (forced-colors: active) {
    .item:focus:not([aria-disabled="true"]),
    .item:hover:not([aria-disabled="true"]) {
      background: Highlight;
      color: HighlightText;
      forced-color-adjust: none;
    }

    .item[aria-disabled="true"] {
      color: GrayText;
    }
  }
}
}

/* Modal */
@layer loamui.components {
@scope (.loam-Modal-popup) to ([class*="loam-"]) {
  :scope {
    --_max: 32rem;

    background: var(--loam-color-surface);
    border: 0;
    border-radius: var(--loam-radius-lg);
    box-shadow: var(--loam-shadow-lg);
    font-size: var(--loam-text-sm);

    /* UA centering (fixed + auto margins) is kept; we only size and skin. */
    inline-size: min(100% - 2 * var(--loam-space-lg), var(--_max));
    max-block-size: calc(100dvb - 2 * var(--loam-space-xl));
    overflow-block: auto;
    padding: var(--loam-space-lg);

    &[data-size="sm"] {
      --_max: 24rem;
    }

    &[data-size="lg"] {
      --_max: 44rem;
    }

    &::backdrop {
      background: color-mix(in oklch, var(--loam-color-fg) 45%, transparent);
    }

    @media (prefers-reduced-motion: no-preference) {
      opacity: 1;
      scale: 1;
      transition:
        opacity var(--loam-duration-lg) var(--loam-ease),
        translate var(--loam-duration-lg) var(--loam-ease),
        scale var(--loam-duration-lg) var(--loam-ease),
        display var(--loam-duration-lg) var(--loam-ease) allow-discrete,
        overlay var(--loam-duration-lg) var(--loam-ease) allow-discrete;
      translate: 0 0;

      /* Exit is the transition BACK to the closed state — without these the
         dialog would sit fully opaque for the whole duration, then vanish
         (display/overlay flip discretely at the end). */
      &:not([open]) {
        opacity: 0;
        scale: 0.98;
        translate: 0 0.5rem;
      }

      &::backdrop {
        opacity: 1;
        transition:
          opacity var(--loam-duration-lg) var(--loam-ease),
          display var(--loam-duration-lg) var(--loam-ease) allow-discrete,
          overlay var(--loam-duration-lg) var(--loam-ease) allow-discrete;

        @starting-style {
          opacity: 0;
        }
      }

      &:not([open])::backdrop {
        opacity: 0;
      }

      @starting-style {
        opacity: 0;
        scale: 0.98;
        translate: 0 0.5rem;
      }
    }
  }

  h2.title {
    font-size: var(--loam-text-lg);
    font-weight: 600;
    margin-block: 0 var(--loam-space-sm);
  }

  p.description {
    color: var(--loam-color-fg-muted);
    margin-block: 0 var(--loam-space-md);
  }
}
}

/* Pagination */
@layer loamui.components {
@scope (.loam-Pagination) {
  :scope {
    /* Derived control height (the Button anatomy, incl. 1px borders) —
       keeps square min-width and the dots cell in step with the pages. */
    --_control-h: calc(
      1.2 * var(--loam-text-sm) + 2 * var(--loam-space-sm) + 2px
    );
  }

  li {
    margin-block: 0;
  }

  ul {
    align-items: center;
    display: block flex;
    flex-wrap: wrap;
    gap: var(--loam-space-xs);
    list-style: none;
    margin: 0;
    padding: 0;
  }

  /* The Button base recipe on the neutral text channel — the contrast
     audit reads these weights and asserts they match Button.css. */
  .control {
    align-items: center;
    background: light-dark(
      color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 90%),
      color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 75%)
    );
    border: 1px solid
      color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 80%);
    border-radius: var(--loam-radius-md);
    box-shadow: var(--loam-shadow-raised);
    color: light-dark(
      color-mix(in oklab, var(--loam-color-fg) 70%, oklch(0% 0 0deg)),
      color-mix(in oklab, var(--loam-color-fg) 55%, oklch(100% 0 0deg))
    );
    display: inline flex;
    font-family: inherit;
    font-size: var(--loam-text-sm);
    font-weight: 600;
    justify-content: center;
    line-height: 1.2;
    min-inline-size: var(--_control-h);
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-sm);
    text-decoration: none;

    &:active:not([data-active], [data-disabled]) {
      box-shadow: var(--loam-shadow-sm);
    }

    &[data-active] {
      background: var(--loam-color-primary-strong);
      border-color: var(--loam-color-primary);
      color: var(--loam-color-on-strong);
      cursor: default;
    }

    &[data-disabled] {
      color: var(--loam-color-fg-dim);
      opacity: 0.45;
    }

    :where(svg) {
      block-size: 1.125em;
      inline-size: 1.125em;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        background var(--loam-duration-sm) var(--loam-ease),
        border-color var(--loam-duration-sm) var(--loam-ease),
        color var(--loam-duration-sm) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease);
    }

    @media (hover: hover) {
      &:hover:not([data-active], [data-disabled]) {
        background: light-dark(
          color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 85%),
          color-mix(in oklab, var(--loam-color-fg), var(--loam-color-bg) 70%)
        );
      }
    }
  }

  span.dots {
    align-items: center;
    block-size: var(--_control-h);
    color: var(--loam-color-fg-dim);
    display: inline flex;
    justify-content: center;
    min-inline-size: var(--_control-h);
    user-select: none;
  }

  /* Forced colors: the active page's brand fill is stripped, leaving it
     identical to its neighbours. */
  @media (forced-colors: active) {
    .control[data-active] {
      background: Highlight;
      border-color: Highlight;
      color: HighlightText;
      forced-color-adjust: none;
    }
  }
}
}

/* Popover */
@layer loamui.components {
@scope (.loam-Popover) {
  :scope {
    display: inline flow-root;
    position: relative;
  }
}

/* The popup escapes the wrapper into the top layer when enhanced, so it
   carries its own scope. */
@scope (.loam-Popover-popup) to ([class*="loam-"]) {
  :scope {
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line);
    border-radius: var(--loam-radius-md);
    box-shadow: var(--loam-shadow-lg);
    inline-size: max-content;
    margin: 0;
    max-inline-size: min(20rem, 90vi);
    min-inline-size: 12rem;
    padding: var(--loam-space-md);

    /* Enhanced: top layer + anchor positioning, tethered to the trigger.
       position-anchor / anchor-name are set from inline styles with a
       per-instance name shared by Trigger and Popup. */
    @supports (anchor-name: --loam-probe) {
      /* The UA gives [popover] `inset: 0`; clear it so only the insets each
         branch sets take part, otherwise the box is over-constrained. */
      inset: auto;
      inset-inline-start: anchor(start);
      position: fixed;
      position-try: flip-block;

      &[data-position="bottom"] {
        inset-block-start: calc(anchor(end) + var(--loam-space-xs));
      }

      &[data-position="top"] {
        inset-block-end: calc(anchor(start) + var(--loam-space-xs));
      }

      @media (prefers-reduced-motion: no-preference) {
        opacity: 1;
        transition:
          opacity var(--loam-duration-lg) var(--loam-ease),
          translate var(--loam-duration-lg) var(--loam-ease),
          display var(--loam-duration-lg) var(--loam-ease) allow-discrete,
          overlay var(--loam-duration-lg) var(--loam-ease) allow-discrete;
        translate: 0 0;

        /* Exit: transition back to the closed state (see Modal). */
        &:not(:popover-open) {
          opacity: 0;
          translate: 0 -0.25rem;
        }

        &[data-position="top"] {
          &:not(:popover-open) {
            translate: 0 0.25rem;
          }

          @starting-style {
            translate: 0 0.25rem;
          }
        }

        @starting-style {
          opacity: 0;
          translate: 0 -0.25rem;
        }
      }
    }

    /* Fallback: no anchor positioning (and no top layer) — anchor to the
       wrapper as a normal absolutely-positioned panel. */
    @supports not (anchor-name: --loam-probe) {
      inset-inline-start: 0;
      position: absolute;
      z-index: 50;

      &[data-position="bottom"] {
        inset-block-start: calc(100% + var(--loam-space-xs));
      }

      &[data-position="top"] {
        inset-block-end: calc(100% + var(--loam-space-xs));
      }
    }
  }

  h2.title {
    font-size: var(--loam-text-md);
    font-weight: 600;
    margin: 0;
  }

  p.description {
    color: var(--loam-color-fg-muted);
    margin-block-end: 0;
    margin-inline: 0;

    &:not(h2.title + *) {
      margin-block-start: 0;
    }

    h2.title + & {
      margin-block-start: var(--loam-space-xs);
    }
  }
}
}

/* Progress */
@layer loamui.components {
@scope (.loam-Progress) {
  :scope {
    --_h: 0.625rem;

    background: var(--loam-color-bg-subtle);
    block-size: var(--_h);
    border-radius: var(--_radius, var(--loam-radius-full));
    display: block flow;
    inline-size: 100%;
    overflow: clip;

    &[data-size="sm"] {
      --_h: 0.375rem;
    }

    &[data-size="lg"] {
      --_h: 1rem;
    }
  }

  div.bar {
    align-items: center;

    /* The fill IS the primary token — a `--loam-context` region re-answers
       it (see the Contexts section of tokens.css), so status needs no
       component code here. */
    background: var(--loam-color-primary-strong);
    block-size: 100%;
    border-radius: inherit;
    color: var(--loam-color-on-strong);
    display: block flex;
    inline-size: 0;
    justify-content: flex-end;

    @media (prefers-reduced-motion: no-preference) {
      transition: inline-size var(--loam-duration-lg) var(--loam-ease);
    }
  }

  span.label {
    font-size: var(--loam-text-xs);
    font-weight: 600;
    line-height: 1;
    padding-inline: var(--loam-space-sm);
    white-space: nowrap;
  }

  div.bar[data-striped] {
    background-image: repeating-linear-gradient(
      45deg,
      color-mix(in oklch, var(--loam-color-on-strong) 22%, transparent) 0,
      color-mix(in oklch, var(--loam-color-on-strong) 22%, transparent) 0.5rem,
      transparent 0.5rem,
      transparent 1rem
    );
    background-size: 1.414rem 1.414rem;
  }

  div.bar[data-animated] {
    @media (prefers-reduced-motion: no-preference) {
      animation: loam-progress-stripes 1s linear infinite;
    }
  }

  /* Forced colors: track and fill are background-painted and would vanish. */
  @media (forced-colors: active) {
    :scope {
      border: 1px solid CanvasText;
    }

    div.bar {
      background: Highlight;
      forced-color-adjust: none;
    }
  }
}

@keyframes loam-progress-stripes {
  from {
    background-position: 0 0;
  }

  to {
    background-position: 1.414rem 0;
  }
}
}

/* Radio */
@layer loamui.components {
/* The control is a native radio — the elements layer paints it with
   accent-color (the neutral primary). The component adds size, the invalid
   affordance, the label row and the group layout. */
@scope (.loam-Radio) {
  :scope {
    accent-color: var(--loam-color-primary);
    block-size: 1.15em;
    flex-shrink: 0;
    inline-size: 1.15em;
    margin: 0.05em 0 0;
  }
}

/* A group with a detected error marks every radio inside (ARIA carries the
   invalid state on the group, not the radios, so there is no per-radio
   aria-invalid rule). The danger ring is a box-shadow so it stacks with rather
   than replaces the elements-layer :focus-visible ring; the focus ring itself
   turns danger-coloured. Mirrors Switch. */
@scope (.loam-Radio-group[aria-invalid="true"]) {
  .loam-Radio {
    --loam-color-ring: var(--loam-color-danger-ring);

    box-shadow: 0 0 0 2px var(--loam-color-danger);
  }
}

/* ---- Single radio (labelled row) ---- */
@scope (.loam-Radio-wrapper) to ([class*="loam-"]) {
  :scope {
    align-items: flex-start;
    cursor: pointer;
    display: inline flex;
    gap: var(--loam-space-sm);

    &:has(input:disabled) {
      cursor: not-allowed;
    }

    &[data-disabled] {
      opacity: var(--loam-disabled-opacity);
    }
  }

  span.body {
    display: block flex;
    flex-direction: column;
    gap: 0.1rem;
    line-height: 1.35;
  }

  span.label {
    font-size: var(--loam-text-sm);
    font-weight: 500;
  }

  span.description {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-xs);
  }
}

/* ---- Group ---- (a Fieldset — reset/legend come from the Fieldset layer) */
@scope (.loam-Radio-group) to ([class*="loam-"]) {
  :scope {
    gap: var(--loam-space-xs);
  }

  span.description {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-xs);
  }

  [data-orientation] {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-sm);
    margin-block-start: var(--loam-space-xs);
  }

  [data-orientation="horizontal"] {
    flex-flow: row wrap;
    gap: var(--loam-space-lg);
  }

  span.error {
    color: var(--loam-color-danger);
    font-size: var(--loam-text-xs);
    margin-block-start: var(--loam-space-xs);
  }
}
}

/* Range */
@layer loamui.components {
/* The bare control is the `<input type="range">` itself — no wrapper element
   in that form — so the input is its own scope root. */
@scope (.loam-Range) {
  :scope {
    --_track-h: 0.4167em;
    --_thumb: 1.25em;

    /* accent-color paints the filled portion + thumb robustly cross-browser. */
    accent-color: var(--loam-color-primary);
    appearance: none;
    background: transparent;
    cursor: pointer;
    font-size: var(--loam-text-sm);
    inline-size: 100%;
    margin: 0;

    &[aria-invalid="true"] {
      accent-color: var(--loam-color-danger);
    }

    &[data-disabled] {
      opacity: var(--loam-disabled-opacity);
    }

    &:disabled {
      cursor: not-allowed;
    }
  }

  /* ---- Track ---- */
  :scope::-webkit-slider-runnable-track {
    background: var(--loam-color-line-strong);
    block-size: var(--_track-h);
    border-radius: var(--loam-radius-full);
  }

  :scope::-moz-range-track {
    background: var(--loam-color-line-strong);
    block-size: var(--_track-h);
    border-radius: var(--loam-radius-full);
  }

  /* Firefox paints the filled portion explicitly. */
  :scope::-moz-range-progress {
    background: var(--loam-color-primary);
    block-size: var(--_track-h);
    border-radius: var(--loam-radius-full);
  }

  /* ---- Thumb ---- */
  :scope::-webkit-slider-thumb {
    appearance: none;
    background: var(--loam-color-primary);
    block-size: var(--_thumb);
    border: 2px solid var(--loam-color-on-strong);
    border-radius: var(--loam-radius-full);
    box-shadow: var(--loam-shadow-sm);
    inline-size: var(--_thumb);
    margin-block-start: calc((var(--_track-h) - var(--_thumb)) / 2);

    /* Identity at rest — the hover grow transitions both ways. */
    scale: 1;

    @media (prefers-reduced-motion: no-preference) {
      transition:
        scale var(--loam-duration-md) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease);
    }
  }

  :scope::-moz-range-thumb {
    background: var(--loam-color-primary);
    block-size: var(--_thumb);
    border: 2px solid var(--loam-color-on-strong);
    border-radius: var(--loam-radius-full);
    box-shadow: var(--loam-shadow-sm);
    inline-size: var(--_thumb);

    /* Identity at rest — the hover grow transitions both ways. */
    scale: 1;

    @media (prefers-reduced-motion: no-preference) {
      transition:
        scale var(--loam-duration-md) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease);
    }
  }

  /* The ring belongs on the thumb (pseudo-element, box-shadow — outline is
     unreliable on ::-webkit-slider-thumb); suppress the elements ring. */
  :scope:focus-visible {
    outline: none;
  }

  :scope:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 0 0 var(--loam-ring-width) var(--loam-color-ring);
  }

  :scope:focus-visible::-moz-range-thumb {
    box-shadow: 0 0 0 var(--loam-ring-width) var(--loam-color-ring);
  }

  @media (hover: hover) {
    :scope:hover:not(:disabled)::-webkit-slider-thumb {
      scale: 1.08;
    }
  }

  @media (hover: hover) {
    :scope:hover:not(:disabled)::-moz-range-thumb {
      scale: 1.08;
    }
  }

  /* Forced colors: the custom track and thumb are background-painted and
     would vanish (appearance: none opts out of native painting). */
  @media (forced-colors: active) {
    :scope::-webkit-slider-runnable-track {
      background: ButtonText;
      forced-color-adjust: none;
    }

    :scope::-moz-range-track {
      background: ButtonText;
      forced-color-adjust: none;
    }

    :scope::-moz-range-progress {
      background: Highlight;
      forced-color-adjust: none;
    }

    :scope::-webkit-slider-thumb {
      background: Highlight;
      border-color: Canvas;
      forced-color-adjust: none;
    }

    :scope::-moz-range-thumb {
      background: Highlight;
      border-color: Canvas;
      forced-color-adjust: none;
    }
  }
}
}

/* Select */
@layer loamui.components {
@scope (.loam-Select-field) {
  :scope {
    align-items: center;
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line-strong);
    border-radius: var(--_radius, var(--loam-radius-md));
    display: block flex;

    /* Height is derived from the select's own padding + font (the Button
       anatomy); the field contributes only the 1px border. */
    font-size: var(--loam-text-sm);
    line-height: 1.2;
    position: relative;

    &:focus-within {
      border-color: var(--loam-color-primary);
      outline: var(--loam-ring-width) solid var(--loam-color-ring);
      outline-offset: 2px;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        border-color var(--loam-duration-sm) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease);
    }
  }

  /* Autofilled: the wrapper border carries the signal (the select itself
     paints the tint — see below). :autofill and :-webkit-autofill must stay
     in separate blocks — one unsupported selector invalidates a shared
     list. Declared before the error state so danger wins at equal
     specificity. */
  :scope:has(select:autofill) {
    border-color: color-mix(
      in oklab,
      var(--loam-color-primary) 50%,
      var(--loam-color-line-strong)
    );
  }

  :scope:has(select:-webkit-autofill) {
    border-color: color-mix(
      in oklab,
      var(--loam-color-primary) 50%,
      var(--loam-color-line-strong)
    );
  }

  :scope:has(select[aria-invalid="true"]) {
    --loam-color-ring: var(--loam-color-danger-ring);

    border-color: var(--loam-color-danger);
  }

  :scope[data-disabled] {
    background: var(--loam-color-bg-subtle);
    opacity: var(--loam-disabled-opacity);
  }

  select {
    appearance: none;
    background: transparent;
    border: none;
    color: inherit;
    cursor: pointer;
    flex: 1;
    font: inherit;
    min-inline-size: 0;
    outline: none;

    /* Padding lives on the <select> so its whole face stays clickable. */
    padding-block: var(--loam-space-sm);

    /* Leave room for the chevron on the trailing side. */
    padding-inline: var(--loam-space-md) 2.25rem;

    &:disabled {
      cursor: not-allowed;
    }
  }

  /* background-color cannot be overridden on an autofilled field; an inset
     shadow wider than the control paints the tint instead. Separate blocks —
     see the wrapper rules above. */
  select:autofill {
    box-shadow: inset 0 0 0 100px
      color-mix(
        in oklab,
        var(--loam-color-primary) 8%,
        var(--loam-color-surface)
      );
  }

  select:-webkit-autofill {
    box-shadow: inset 0 0 0 100px
      color-mix(
        in oklab,
        var(--loam-color-primary) 8%,
        var(--loam-color-surface)
      );
  }

  /* The unanswered prompt stays muted but AA-readable: it is the
     control's visible value until answered, i.e. instruction text. */
  select:has(option[value=""]:checked) {
    color: var(--loam-color-fg-muted);
  }

  svg.chevron {
    block-size: 1.125em;
    color: var(--loam-color-fg-muted);
    flex-shrink: 0;

    /* Fluid: overrides the SVG's 16px width/height attributes. */
    inline-size: 1.125em;
    inset-block-start: 50%;
    inset-inline-end: var(--loam-space-md);
    pointer-events: none;
    position: absolute;
    translate: 0 -50%;
  }
}
}

/* Separator */
@layer loamui.components {
@scope (.loam-Separator) {
  :scope {
    align-self: stretch;
    border: 0;
    border-block-start: 1px solid var(--loam-color-line);
    margin: 0;

    &[data-orientation="vertical"] {
      block-size: auto;
      border-block-start: 0;
      border-inline-start: 1px solid var(--loam-color-line);
      inline-size: 0;
      min-block-size: 1em;
    }
  }
}
}

/* SignpostLink */
@layer loamui.components {
@scope (.loam-SignpostLink) to ([class*="loam-"]) {
  :scope {
    align-items: center;
    border-radius: var(--loam-radius-sm);
    color: var(--loam-color-fg-strong);
    display: inline flex;
    font-size: var(--loam-text-md);
    font-weight: 600;
    gap: var(--loam-space-sm);
    max-inline-size: fit-content;
    text-decoration: none;
  }

  /* The underline is always drawn with a transparent colour, so colour,
     thickness and offset can each transition. */
  span.label {
    text-decoration: underline;
    text-decoration-color: transparent;
    text-decoration-thickness: max(0.08em, 1px);
    text-underline-offset: 0.15em;

    :scope:active & {
      text-decoration-color: var(--loam-color-primary-strong);
      text-decoration-thickness: max(0.16em, 2px);
      text-underline-offset: 0.15em;
      transition: none;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        text-decoration-color var(--loam-duration-md) var(--loam-ease),
        text-decoration-thickness var(--loam-duration-md) var(--loam-ease),
        text-underline-offset var(--loam-duration-md) var(--loam-ease-elastic);
    }
  }

  /* The circled arrow rides the font size, so the signpost rescales as one
     piece wherever a consumer changes it. Solid fill on the -strong family
     with on-strong — the audited pairing — and a context region recolours
     it by re-answering the token. */
  span.icon {
    background: var(--loam-color-primary-strong);
    block-size: 1.5em;
    border-radius: var(--loam-radius-full);
    color: var(--loam-color-on-strong);
    display: inline grid;
    flex-shrink: 0;
    inline-size: 1.5em;
    place-items: center;

    svg {
      block-size: 65%;
      inline-size: 65%;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition: translate var(--loam-duration-md) var(--loam-ease-elastic);
    }
  }

  @media (hover: hover) {
    :scope:hover span.label {
      text-decoration-color: var(--loam-color-primary);
      text-decoration-thickness: max(0.16em, 2px);
      text-underline-offset: 0.3em;
    }
  }

  @media (hover: hover) {
    :scope:hover span.icon {
      translate: 0.15em 0;
    }
  }
}
}

/* Skeleton */
@layer loamui.components {
@scope (.loam-Skeleton) {
  :scope {
    block-size: var(--_h, 1lh);

    /* Public shape hook — consumers may set it per instance or region. */
    border-radius: var(--loam-skeleton-radius, var(--loam-radius-md));

    /* Bare text children have no box for the child rule below to hide. */
    color: transparent;
    display: block flow;
    inline-size: var(--_w, 100%);
    overflow: clip;
    pointer-events: none;
    position: relative;
    user-select: none;
  }

  /* Wrapped children size the box: measured, not declared. */
  :scope:has(> *) {
    block-size: var(--_h, auto);
    inline-size: var(--_w, fit-content);
  }

  /* Element children keep their layout but paint nothing — visibility
     also hides backgrounds, which a transparent color cannot. */
  :scope > * {
    visibility: hidden;
  }

  /* After the :has() rule so the circle wins at equal specificity.
     2.5rem matches Avatar's md, the shape this exists to stand in for. */
  :scope[data-circle] {
    aspect-ratio: 1;
    block-size: auto;
    border-radius: var(--loam-radius-full);
    inline-size: var(--_size, 2.5rem);
  }

  /* The tint is a weak mix of the text colour: text is guaranteed to
     contrast with any surface, so the skeleton stays visible on cards,
     stages and the page in both schemes. The gradient is static by
     default; the moving shimmer is opt-in motion (below), so
     reduced-motion users need no override. */
  :scope::after {
    background-color: color-mix(in oklab, var(--loam-color-fg) 8%, transparent);
    background-image: linear-gradient(
      90deg,
      transparent 0%,
      color-mix(in oklab, var(--loam-color-fg) 7%, transparent) 50%,
      transparent 100%
    );
    content: "";
    inset: 0;
    position: absolute;

    @media (prefers-reduced-motion: no-preference) {
      animation: loam-skeleton-shimmer 1.4s ease-in-out infinite;
      background-size: 200% 100%;
    }
  }
}

/* Keyframes are not scope-able; they stay at the top level. */
@keyframes loam-skeleton-shimmer {
  from {
    background-position: 200% 0;
  }

  to {
    background-position: -200% 0;
  }
}
}

/* SkipLink */
@layer loamui.components {
@scope (.loam-SkipLink) {
  /* Fixed placement keeps the link out of flow in both states, so
     appearing on focus never shifts the page; z-index above every
     overlay. */
  :scope {
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line-strong);
    border-radius: var(--loam-radius-md);
    box-shadow: var(--loam-shadow-lg);
    color: var(--loam-color-fg);
    font-size: var(--loam-text-sm);
    font-weight: 600;
    inset-block-start: var(--loam-space-sm);
    inset-inline-start: var(--loam-space-sm);
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-lg);
    position: fixed;
    text-decoration: none;
    z-index: 110;

    /* Hidden is the unfocused state — :focus, not :focus-visible, because
       assistive tech can focus without the keyboard heuristics and the
       link must appear for every focus. */
    &:not(:focus) {
      block-size: 1px;
      border: 0;
      clip-path: inset(50%);
      inline-size: 1px;
      overflow: clip;
      padding: 0;
      white-space: nowrap;
    }
  }
}
}

/* Switch */
@layer loamui.components {
@scope (.loam-Switch-wrapper) to ([class*="loam-"]) {
  :scope {
    align-items: center;
    cursor: pointer;
    display: inline flex;
    gap: var(--loam-space-sm);

    &:has(input:disabled) {
      cursor: not-allowed;
    }

    &[data-label-position="start"] {
      flex-direction: row-reverse;
    }
  }

  /* Inside the labelled wrapper the wrapper does the disabled fade (the bare
     control's :not guard, in the control scope, stops the two compounding). */
  :scope[data-disabled] {
    opacity: var(--loam-disabled-opacity);
  }
}

/* Size variables live on the control so the bare SwitchControl (composed
   inside a Field) sizes itself without the labelled wrapper. */
@scope (.loam-Switch-control) {
  :scope {
    --_track-w: 2.7778em;
    --_track-h: 1.5278em;
    --_gap: 0.2083em;

    align-items: center;
    cursor: pointer;
    display: inline flex;
    font-size: var(--loam-text-sm);
    position: relative;
  }

  /* Bare control fades itself; inside the labelled wrapper the wrapper does
     (the :not guard stops the two from compounding). */
  :scope[data-disabled]:where(:not(.loam-Switch-wrapper *)) {
    opacity: var(--loam-disabled-opacity);
  }

  /* Visually hide the native control but keep it in the a11y tree &
     focusable. The hit area is at least 24px (WCAG 2.5.8) even when the
     visible track is smaller — centred, invisible, no layout impact. */
  input {
    block-size: max(100%, 24px);
    cursor: inherit;
    inline-size: max(100%, 24px);
    inset-block-start: 50%;
    inset-inline-start: 50%;
    margin: 0;
    opacity: 0;
    position: absolute;
    translate: -50% -50%;
  }

  span.track {
    background: var(--loam-color-line-strong);
    block-size: var(--_track-h);
    border-radius: var(--loam-radius-full);
    flex-shrink: 0;
    inline-size: var(--_track-w);
    padding: var(--_gap);
    position: relative;

    @media (prefers-reduced-motion: no-preference) {
      transition:
        background var(--loam-duration-sm) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease);
    }
  }

  span.thumb {
    background: var(--loam-color-on-strong);
    block-size: calc(var(--_track-h) - 2 * var(--_gap));
    border-radius: var(--loam-radius-full);
    box-shadow: var(--loam-shadow-sm);
    display: block flow;
    inline-size: calc(var(--_track-h) - 2 * var(--_gap));

    @media (prefers-reduced-motion: no-preference) {
      transition: translate var(--loam-duration-md) var(--loam-ease-elastic);
    }
  }

  input:checked + span.track {
    background: var(--loam-color-primary);

    span.thumb {
      translate: calc(var(--_track-w) - var(--_track-h)) 0;
    }
  }

  /* The ring belongs on the track, not on the invisible hit-area input. */
  input:focus-visible {
    outline: none;

    + span.track {
      outline: var(--loam-ring-width) solid var(--loam-color-ring);
      outline-offset: 2px;
    }
  }

  /* Invalid after a composed Field error or a native submit attempt. */
  input[aria-invalid="true"] + span.track {
    --loam-color-ring: var(--loam-color-danger-ring);

    box-shadow: inset 0 0 0 1px var(--loam-color-danger);
  }

  /* The wrapper ancestor sits outside the scope root; the subject (the
     track) stays in scope. */
  @media (hover: hover) {
    :is(.loam-Switch-wrapper, :scope):hover input:not(:disabled) + span.track {
      background: color-mix(
        in oklch,
        var(--loam-color-line-strong) 80%,
        var(--loam-color-primary)
      );
    }
  }

  /* Forced colors strips backgrounds — without this the switch is invisible.
     System colours keep the user's palette: ButtonText chrome, Highlight for
     the on state (thumb position still moves as the second signal). */
  @media (forced-colors: active) {
    span.track {
      border: 1px solid ButtonText;
    }

    span.thumb {
      background: ButtonText;
      forced-color-adjust: none;
    }

    input:checked + span.track {
      background: Highlight;
      border-color: Highlight;
      forced-color-adjust: none;

      span.thumb {
        background: HighlightText;
      }
    }
  }
}

@scope (.loam-Switch-wrapper) to ([class*="loam-"]) {
  span.label {
    font-size: var(--loam-text-sm);
    font-weight: 500;
    line-height: 1.35;
  }
}

@scope (.loam-Switch-field) to ([class*="loam-"]) {
  :scope {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-xs);
  }

  span.description {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-xs);
  }
}
}

/* Table */
@layer loamui.components {
@scope (.loam-Table-scroll) {
  :scope {
    border-radius: var(--loam-radius-md);
    inline-size: 100%;
    overflow-inline: auto;
  }
}

@scope (.loam-Table) to ([class*="loam-"]) {
  :scope {
    border-collapse: collapse;
    font-size: var(--loam-text-sm);
    inline-size: 100%;

    &[data-caption-side="top"] {
      /* stylelint-disable-next-line value-keyword-layout-mappings --
         caption-side has no logical equivalent; the physical keyword is
         the only form. */
      caption-side: top;
    }

    &[data-caption-side="bottom"] {
      /* stylelint-disable-next-line value-keyword-layout-mappings --
         see above: no logical equivalent exists. */
      caption-side: bottom;
    }

    caption {
      color: var(--loam-color-fg-muted);
      font-size: var(--loam-text-xs);
      padding-block: var(--loam-space-sm);
      text-align: start;
    }

    :where(th, td) {
      border-block-end: 1px solid var(--loam-color-line);
      padding-block: var(--loam-space-md);
      padding-inline: var(--loam-space-lg);
      text-align: start;
      vertical-align: middle;
    }

    :where(thead th) {
      border-block-end: 1px solid var(--loam-color-line-strong);
      color: var(--loam-color-fg-muted);
      font-size: var(--loam-text-xs);
      font-weight: 600;
      letter-spacing: 0.03em;
      text-transform: uppercase;
      white-space: nowrap;
    }

    :where(tbody tr:last-child) :where(td) {
      border-block-end: none;
    }

    &[data-col-borders] :where(th, td):not(:last-child) {
      border-inline-end: 1px solid var(--loam-color-line);
    }

    &[data-striped] :where(tbody tr:nth-child(even)) {
      background: var(--loam-color-bg-subtle);
    }

    @media (prefers-reduced-motion: no-preference) {
      &[data-hover] :where(tbody tr) {
        transition: background var(--loam-duration-md) var(--loam-ease);
      }
    }

    @media (hover: hover) {
      &[data-hover] :where(tbody tr:hover) {
        background: var(--loam-color-surface-hover);
      }
    }
  }
}
}

/* Tabs */
@layer loamui.components {
@scope (.loam-Tabs) {
  :scope {
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-lg);
    inline-size: 100%;
  }
}

/*
 * The list is scoped to its own root: Tabs.List renders without the
 * .loam-Tabs class on it (and, unlike Tab/Panel, is not context-guarded).
 */
@scope (.loam-Tabs-list) {
  :scope {
    border-block-end: 1px solid var(--loam-color-line);
    display: block flex;
    gap: var(--loam-space-xs);
  }
}

/* Tab and Panel can only render inside <Tabs> (their context throws outside). */
@scope (.loam-Tabs) to ([class*="loam-"]) {
  button.tab {
    align-items: center;
    background: transparent;
    border: none;
    border-block-end: 2px solid transparent;
    border-radius: var(--loam-radius-sm) var(--loam-radius-sm) 0 0;
    box-shadow: none;
    color: var(--loam-color-fg-muted);
    display: inline flex;
    font-family: inherit;
    font-size: var(--loam-text-sm);
    font-weight: 600;
    gap: var(--loam-space-sm);
    line-height: 1;
    margin-block-end: -1px;
    padding-block: var(--loam-space-md);
    padding-inline: var(--loam-space-lg);
    position: relative;

    &:focus-visible {
      border-radius: var(--loam-radius-sm) var(--loam-radius-sm) 0 0;
    }

    &:disabled {
      opacity: 0.5;
    }

    &[data-active] {
      border-block-end-color: var(--loam-color-primary);
      color: var(--loam-color-primary);
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        color var(--loam-duration-sm) var(--loam-ease),
        border-color var(--loam-duration-sm) var(--loam-ease),
        background var(--loam-duration-sm) var(--loam-ease);
    }

    @media (hover: hover) {
      &:hover:not(:disabled) {
        background: color-mix(
          in oklch,
          var(--loam-color-primary-soft) 50%,
          transparent
        );
        color: var(--loam-color-fg);
      }
    }
  }

  /* Icons compose as children and are detected, exactly as in Button. */
  button.tab:has(svg) {
    align-items: center;
    display: inline flex;
    gap: 0.45em;

    svg {
      block-size: 1.1em;
      inline-size: 1.1em;
    }
  }

  div.panel {
    font-size: var(--loam-text-md);
    line-height: 1.55;

    &:focus-visible {
      border-radius: var(--loam-radius-sm);
    }
  }

  /* Boolean hidden only — [hidden="until-found"] must keep the UA's
     content-visibility treatment or find-in-page reveal breaks. */
  div.panel[hidden]:not([hidden="until-found"]) {
    display: none;
  }

  /* Forced colors: guarantee the active underline with a system colour.
     Must come after the base rule — same specificity, order decides. */
  @media (forced-colors: active) {
    button.tab[data-active] {
      border-block-end-color: Highlight;
    }
  }
}
}

/* Textarea */
@layer loamui.components {
@scope (.loam-Textarea-field) {
  :scope {
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line-strong);
    border-radius: var(--_radius, var(--loam-radius-md));
    display: block flex;
    font-size: var(--loam-text-sm);
    line-height: 1.2;
    padding-block: var(--loam-space-sm);
    padding-inline: var(--loam-space-md);

    &:focus-within {
      border-color: var(--loam-color-primary);
      outline: var(--loam-ring-width) solid var(--loam-color-ring);
      outline-offset: 2px;
    }

    @media (prefers-reduced-motion: no-preference) {
      transition:
        border-color var(--loam-duration-sm) var(--loam-ease),
        box-shadow var(--loam-duration-md) var(--loam-ease);
    }
  }

  /* Autofilled: the wrapper border carries the signal (the textarea itself
     paints the tint — see below). :autofill and :-webkit-autofill must stay
     in separate blocks — one unsupported selector invalidates a shared
     list. Declared before the error state so danger wins at equal
     specificity. */
  :scope:has(textarea:autofill) {
    border-color: color-mix(
      in oklab,
      var(--loam-color-primary) 50%,
      var(--loam-color-line-strong)
    );
  }

  :scope:has(textarea:-webkit-autofill) {
    border-color: color-mix(
      in oklab,
      var(--loam-color-primary) 50%,
      var(--loam-color-line-strong)
    );
  }

  :scope:has(textarea[aria-invalid="true"]) {
    --loam-color-ring: var(--loam-color-danger-ring);

    border-color: var(--loam-color-danger);
  }

  :scope[data-disabled] {
    background: var(--loam-color-bg-subtle);
    opacity: var(--loam-disabled-opacity);
  }

  textarea {
    background: transparent;
    border: none;
    color: inherit;
    flex: 1;
    font: inherit;

    /* Floor in lh so it rides the fluid font; `rows` in the markup is the
       semantic fallback where lh is unsupported. */
    min-block-size: 3.5lh;
    min-inline-size: 0;
    outline: none;
    padding: 0;
    resize: block;
  }

  textarea::placeholder {
    color: var(--loam-color-fg-dim);
  }

  /* background-color cannot be overridden on an autofilled field; an inset
     shadow wider than the control paints the tint instead. Separate blocks —
     see the wrapper rules above. */
  textarea:autofill {
    box-shadow: inset 0 0 0 100px
      color-mix(
        in oklab,
        var(--loam-color-primary) 8%,
        var(--loam-color-surface)
      );
  }

  textarea:-webkit-autofill {
    box-shadow: inset 0 0 0 100px
      color-mix(
        in oklab,
        var(--loam-color-primary) 8%,
        var(--loam-color-surface)
      );
  }

  textarea:disabled {
    resize: none;
  }

  /* Native auto-grow where the platform provides it; the floor above still
     applies. */
  @supports (field-sizing: content) {
    textarea {
      field-sizing: content;
      max-block-size: 10lh;
    }
  }
}
}

/* Toast */
@layer loamui.components {
/* The viewport lives in the top layer (popover="manual"), so it carries its
   own scope; toasts stack from the block-end corner. */
@scope (.loam-Toast-viewport) to ([class*="loam-"]) {
  :scope {
    background: none;
    border: 0;
    display: block flex;
    flex-direction: column;
    gap: var(--loam-space-sm);
    inline-size: min(22rem, calc(100vi - 2 * var(--loam-space-lg)));

    /* Clear the UA's [popover] inset/size defaults. */
    inset: auto;
    inset-block-end: var(--loam-space-lg);
    inset-inline-end: var(--loam-space-lg);
    margin: 0;
    outline: none;
    overflow: visible;
    padding: 0;
    position: fixed;
  }

  /* No toasts: don't intercept clicks in the corner. */
  :scope[data-empty] {
    pointer-events: none;
  }

  div.toast {
    align-items: flex-start;
    background: var(--loam-color-surface);
    border: 1px solid var(--loam-color-line);
    border-radius: var(--loam-radius-md);
    box-shadow: var(--loam-shadow-lg);
    display: block flex;
    gap: var(--loam-space-sm);
    padding: var(--loam-space-md);

    @media (prefers-reduced-motion: no-preference) {
      transition:
        opacity var(--loam-duration-lg) var(--loam-ease),
        translate var(--loam-duration-lg) var(--loam-ease);

      @starting-style {
        opacity: 0;
        translate: 0 0.5rem;
      }
    }
  }

  /* Exit: the provider marks the toast, plays this, then removes it. */
  div.toast[data-exiting] {
    opacity: 0;
    translate: 0 0.5rem;
  }

  div.content {
    flex: 1;
    min-inline-size: 0;
  }

  div.title {
    font-size: var(--loam-text-sm);
    font-weight: 600;
  }

  div.description {
    color: var(--loam-color-fg-muted);
    font-size: var(--loam-text-sm);
    overflow-wrap: break-word;

    div.title + & {
      margin-block-start: var(--loam-space-xs);
    }
  }

  button.close {
    background: none;
    block-size: 1.5rem;
    border: 0;
    border-radius: var(--loam-radius-sm);
    box-shadow: none;
    color: var(--loam-color-fg-muted);
    cursor: pointer;
    display: inline grid;
    flex-shrink: 0;
    inline-size: 1.5rem;
    margin: 0;
    padding: 0;
    place-items: center;

    svg {
      block-size: 1em;
      inline-size: 1em;
    }

    @media (hover: hover) {
      &:hover {
        background: var(--loam-color-bg-subtle);
        color: var(--loam-color-fg);
      }
    }
  }

  @supports not selector(:popover-open) {
    :scope {
      z-index: 100;
    }
  }
}
}

/* Tooltip */
@layer loamui.components {
@scope (.loam-Tooltip) {
  :scope {
    display: inline flow-root;
    position: relative;
  }
}

/* The bubble escapes into the top layer when enhanced, so it carries its
   own scope. */
@scope (.loam-Tooltip-popup) to ([class*="loam-"]) {
  :scope {
    --_gap: var(--loam-space-xs);
    --_arrow: 0.3125rem;

    background: var(--loam-color-fg);
    border: 0;
    border-radius: var(--loam-radius-sm);
    box-shadow: var(--loam-shadow-md);
    color: var(--loam-color-bg);
    font-size: var(--loam-text-xs);
    inline-size: max-content;
    margin: 0;
    max-inline-size: 16rem;

    /* The UA gives [popover] `overflow: auto`, which would clip the arrow
       poking outside the bubble. Tooltips never scroll. */
    overflow: visible;
    padding-block: var(--loam-space-xs);
    padding-inline: var(--loam-space-sm);
    text-align: center;
    white-space: normal;

    /* Enhanced: top layer + anchor positioning. position-anchor /
       anchor-name are set from inline styles with a per-instance name
       shared by Trigger and Popup. */
    @supports (anchor-name: --loam-probe) {
      /* stylelint-disable-next-line declaration-property-value-no-unknown --
         lets the arrow query which position-try fallback applied (Chrome
         143+; inert elsewhere); stylelint's CSS data doesn't know the
         value yet. */
      container-type: anchored;

      /* The UA gives [popover] `inset: 0`; clear it so only the insets each
         branch sets take part, otherwise the box is over-constrained. */
      inset: auto;
      position: fixed;

      &[data-position="top"] {
        inset-block-end: calc(anchor(start) + var(--_gap));
        justify-self: anchor-center;
        position-try: flip-block;
      }

      &[data-position="bottom"] {
        inset-block-start: calc(anchor(end) + var(--_gap));
        justify-self: anchor-center;
        position-try: flip-block;
      }

      &[data-position="left"] {
        align-self: anchor-center;
        inset-inline-end: calc(anchor(start) + var(--_gap));
        position-try: flip-inline;
      }

      &[data-position="right"] {
        align-self: anchor-center;
        inset-inline-start: calc(anchor(end) + var(--_gap));
        position-try: flip-inline;
      }

      @media (prefers-reduced-motion: no-preference) {
        opacity: 1;
        scale: 1;
        transition:
          opacity var(--loam-duration-lg) var(--loam-ease),
          scale var(--loam-duration-lg) var(--loam-ease),
          display var(--loam-duration-lg) var(--loam-ease) allow-discrete,
          overlay var(--loam-duration-lg) var(--loam-ease) allow-discrete;

        /* Exit: transition back to the closed state (see Modal). */
        &:not(:popover-open) {
          opacity: 0;
          scale: 0.96;
        }

        @starting-style {
          opacity: 0;
          scale: 0.96;
        }
      }
    }

    /* Fallback: anchored to the wrapper. */
    @supports not (anchor-name: --loam-probe) {
      position: absolute;
      z-index: 40;

      /* translate is physical, inset-inline-* is flow-relative — the
         centering shift must follow the text direction. */
      &[data-position="top"] {
        inset-block-end: calc(100% + var(--_gap));
        inset-inline-start: 50%;

        &:dir(ltr) {
          translate: -50% 0;
        }

        &:dir(rtl) {
          translate: 50% 0;
        }
      }

      &[data-position="bottom"] {
        inset-block-start: calc(100% + var(--_gap));
        inset-inline-start: 50%;

        &:dir(ltr) {
          translate: -50% 0;
        }

        &:dir(rtl) {
          translate: 50% 0;
        }
      }

      &[data-position="left"] {
        inset-block-start: 50%;
        inset-inline-end: calc(100% + var(--_gap));
        translate: 0 -50%;
      }

      &[data-position="right"] {
        inset-block-start: 50%;
        inset-inline-start: calc(100% + var(--_gap));
        translate: 0 -50%;
      }
    }
  }

  /* Arrow — points back toward the trigger for the requested side. */
  span.arrow {
    block-size: 0;
    border: var(--_arrow) solid transparent;
    inline-size: 0;
    position: absolute;

    :scope[data-position="top"] & {
      border-block-start-color: var(--loam-color-fg);
      inset-block-start: 100%;
      inset-inline-start: calc(50% - var(--_arrow));
    }

    :scope[data-position="bottom"] & {
      border-block-end-color: var(--loam-color-fg);
      inset-block-end: 100%;
      inset-inline-start: calc(50% - var(--_arrow));
    }

    :scope[data-position="left"] & {
      border-inline-start-color: var(--loam-color-fg);
      inset-block-start: calc(50% - var(--_arrow));
      inset-inline-start: 100%;
    }

    :scope[data-position="right"] & {
      border-inline-end-color: var(--loam-color-fg);
      inset-block-start: calc(50% - var(--_arrow));
      inset-inline-end: 100%;
    }

    /* When position-try flipped the bubble, the arrow follows: it moves to
       the opposite edge and points back at the trigger. Anchored container
       queries are the only CSS signal for the applied fallback, so these
       are inherently overrides of the requested side; browsers without
       them keep the requested-side arrow. */
    @container anchored(fallback: flip-block) {
      :scope[data-position="top"] & {
        border-block-end-color: var(--loam-color-fg);
        border-block-start-color: transparent;
        inset-block: auto 100%;
      }

      :scope[data-position="bottom"] & {
        border-block-end-color: transparent;
        border-block-start-color: var(--loam-color-fg);
        inset-block: 100% auto;
      }
    }

    @container anchored(fallback: flip-inline) {
      :scope[data-position="left"] & {
        border-inline-end-color: var(--loam-color-fg);
        border-inline-start-color: transparent;
        inset-inline: auto 100%;
      }

      :scope[data-position="right"] & {
        border-inline-end-color: transparent;
        border-inline-start-color: var(--loam-color-fg);
        inset-inline: 100% auto;
      }
    }
  }
}
}
