/* ============================================================
 * Axismundi — components.css
 * v0.2.0 — v2 rebuild · Chunk A — Foundation primitives
 *
 * Build order (per BRIEF priority):
 *   Chunk A (this file, current pass):
 *     §0 State-layer foundation
 *     §1 Avatar
 *     §2 Button (S size, 5 variants)
 *     §3 Icon button (4 variants, default size)
 *     §4 Divider
 *   Chunk B: Card, App bar, Nav rail, Tabs
 *   Chunk C: Dialog, Sheet, Snackbar
 *   Chunk D: Text field, Search, Chip, feed-specific compositions
 *
 * No literal color/size values in components — every value
 * resolves to a token from tokens.css (sys / comp / alias / layout).
 * State layer impl uses Pattern A (currentColor + opacity token)
 * per prompt-v2 §4.2.
 * ============================================================ */

/* ============================================================
 * §0 State-layer foundation
 *
 * Pattern A — currentColor overlay scaled by opacity tokens.
 * The host element must establish a positioning context;
 * ::before is the state layer surface and inherits the host's
 * border-radius via inherit, so it always matches container
 * geometry (M3 §0.7).
 *
 * Usage:
 *   .my-component { position: relative; ... }
 *   .my-component::before { ...applied automatically via the
 *      .has-state-layer class or [data-state-layer] attr. }
 *
 * Color/opacity transitions use *effects* spring (no overshoot).
 * Shape morphs (corner-radius) on individual components use
 * *spatial* spring — see §2 Button.
 * ============================================================ */
.has-state-layer,
[data-state-layer] {
  position: relative;
  isolation: isolate; /* keep ::before from leaking through z-index */
}

.has-state-layer::before,
[data-state-layer]::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

.has-state-layer:hover::before,
[data-state-layer]:hover::before {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}
.has-state-layer:focus-visible::before,
[data-state-layer]:focus-visible::before {
  opacity: var(--md-sys-state-focus-state-layer-opacity);
}
.has-state-layer:active::before,
[data-state-layer]:active::before {
  opacity: var(--md-sys-state-pressed-state-layer-opacity);
}
/* Dragged is JS-driven — set [data-dragging="true"] on host */
.has-state-layer[data-dragging="true"]::before,
[data-state-layer][data-dragging="true"]::before {
  opacity: var(--md-sys-state-dragged-state-layer-opacity);
}

/* ============================================================
 * §1 Avatar (M3 §19.7 leading-slot, extracted)
 *
 * Default — 40px, primary-container fill, on-primary-container
 * label/icon. Initials variant uses title-medium weight-medium.
 * Image variant fits via object-fit: cover.
 * ============================================================ */
.ax-avatar {
  --_ax-avatar-size: var(--comp-avatar-size); /* 40px default */

  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;

  width:  var(--_ax-avatar-size);
  height: var(--_ax-avatar-size);
  border-radius: var(--comp-avatar-radius); /* corner-full */

  background-color: var(--md-sys-color-primary-container);
  color:            var(--md-sys-color-on-primary-container);

  font-family:    var(--md-sys-typescale-title-medium-font);
  font-size:      var(--md-sys-typescale-title-medium-size);
  line-height:    1; /* override for centered glyph */
  font-weight:    var(--md-sys-typescale-title-medium-weight);
  letter-spacing: var(--md-sys-typescale-title-medium-tracking);

  overflow: hidden;
  user-select: none;
}
.ax-avatar.is-size-sm { --_ax-avatar-size: 32px; }
.ax-avatar.is-size-lg { --_ax-avatar-size: 56px; }
.ax-avatar.is-size-xs { --_ax-avatar-size: 24px; } /* M3 §14.5 — chip leading slot */

.ax-avatar > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ============================================================
 * §2 Button — M3-COMPONENT-SPECS §4
 *
 * Scope: S size (40px) only this chunk. XS/M/L/XL deferred.
 * Variants: filled, tonal, elevated, outlined, text.
 * Default shape uses a morphing-safe pill radius (height / 2).
 * On press, square morph via spatial spring.
 * ============================================================ */
.ax-button {
  --_button-height: var(--comp-button-height);
  --_button-padding-inline: var(--space-md);
  --_button-icon-size: var(--comp-icon-size-sm);
  --_button-pressed-radius: var(--md-sys-shape-corner-small);
  --_button-outline-width: 1px;

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

  height: var(--_button-height);
  padding-inline: var(--_button-padding-inline);
  border: 0;
  border-radius: calc(var(--_button-height) / 2); /* morphing-safe pill */

  font-family:    var(--md-sys-typescale-label-large-font);
  font-size:      var(--md-sys-typescale-label-large-size);
  line-height:    var(--md-sys-typescale-label-large-line-height);
  font-weight:    var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);

  cursor: pointer;
  user-select: none;
  white-space: nowrap;

  /* shape morph (spatial) + color/opacity (effects) */
  transition:
    border-radius
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}
.ax-button:active {
  /* §4.3 pressed morph: corner-full → corner-small (S size) */
  border-radius: var(--_button-pressed-radius);
}

.ax-button > .ax-button-icon {
  width: var(--_button-icon-size);
  height: var(--_button-icon-size);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.ax-button.is-size-xs {
  --_button-height: var(--comp-button-height-xs);
  --_button-padding-inline: var(--comp-button-padding-inline-xs);
  --_button-icon-size: var(--comp-button-icon-size-xs);
  --_button-pressed-radius: var(--md-sys-shape-corner-small);
  --_button-outline-width: var(--comp-button-outline-width-xs);
}
.ax-button.is-size-s {
  --_button-height: var(--comp-button-height-s);
  --_button-padding-inline: var(--comp-button-padding-inline-s);
  --_button-icon-size: var(--comp-button-icon-size-s);
  --_button-pressed-radius: var(--md-sys-shape-corner-small);
  --_button-outline-width: var(--comp-button-outline-width-s);
}
.ax-button.is-size-m {
  --_button-height: var(--comp-button-height-m);
  --_button-padding-inline: var(--comp-button-padding-inline-m);
  --_button-icon-size: var(--comp-button-icon-size-m);
  --_button-pressed-radius: var(--md-sys-shape-corner-medium);
  --_button-outline-width: var(--comp-button-outline-width-m);
}
.ax-button.is-size-l {
  --_button-height: var(--comp-button-height-l);
  --_button-padding-inline: var(--comp-button-padding-inline-l);
  --_button-icon-size: var(--comp-button-icon-size-l);
  --_button-pressed-radius: var(--md-sys-shape-corner-large);
  --_button-outline-width: var(--comp-button-outline-width-l);
}
.ax-button.is-size-xl {
  --_button-height: var(--comp-button-height-xl);
  --_button-padding-inline: var(--comp-button-padding-inline-xl);
  --_button-icon-size: var(--comp-button-icon-size-xl);
  --_button-pressed-radius: var(--md-sys-shape-corner-large);
  --_button-outline-width: var(--comp-button-outline-width-xl);
}

/* --- Variants (color tokens per §4.4) --- */
.ax-button.is-filled {
  background-color: var(--md-sys-color-primary);
  color:            var(--md-sys-color-on-primary);
  box-shadow: var(--md-sys-elevation-shadow-level0);
}
.ax-button.is-tonal {
  background-color: var(--md-sys-color-secondary-container);
  color:            var(--md-sys-color-on-secondary-container);
  box-shadow: var(--md-sys-elevation-shadow-level0);
}
.ax-button.is-elevated {
  background-color: var(--md-sys-color-surface-container-low);
  color:            var(--md-sys-color-primary);
  box-shadow: var(--md-sys-elevation-shadow-level1);
}
.ax-button.is-elevated:hover {
  box-shadow: var(--md-sys-elevation-shadow-level2);
}
.ax-button.is-outlined {
  background-color: transparent;
  color:            var(--md-sys-color-on-surface-variant);
  outline: var(--_button-outline-width) solid var(--md-sys-color-outline-variant);
  outline-offset: -1px; /* sits inside the radius */
}
.ax-button.is-text {
  background-color: transparent;
  color:            var(--md-sys-color-primary);
  padding-inline: max(var(--space-sm), calc(var(--_button-padding-inline) / 2));
}

/* --- Disabled — Pattern A (§0.8) --- */
.ax-button:disabled,
.ax-button[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  /* container = on-surface @ 0.10; label/icon = on-surface @ 0.38 */
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 10%,
    transparent
  );
  color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 38%,
    transparent
  );
  box-shadow: var(--md-sys-elevation-shadow-level0);
  outline: 0;
}
/* Text variant disabled keeps no container fill */
.ax-button.is-text:disabled,
.ax-button.is-text[aria-disabled="true"] {
  background-color: transparent;
}

/* ============================================================
 * §3 Icon button — M3-COMPONENT-SPECS §6
 *
 * Default size: 40×40 container with 24px glyph. 48×48 touch
 * target enforced via min-width/min-height. Toggle behavior
 * is JS-driven via [aria-pressed]; the glyph swap (outlined
 * unselected → filled selected) is the prototype's job.
 * ============================================================ */
.ax-icon-button {
  --_icon-button-size: var(--comp-button-height);
  --_icon-button-radius: calc(max(var(--_icon-button-size), var(--comp-touch-target)) / 2);
  --_icon-button-icon-size: var(--comp-icon-size-md);
  --_icon-button-pressed-radius: var(--md-sys-shape-corner-small);
  --_icon-button-outline-width: 1px;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;

  width:  var(--_icon-button-size);
  height: var(--_icon-button-size);
  min-width:  var(--comp-touch-target); /* a11y target */
  min-height: var(--comp-touch-target);
  padding: 0;
  border: 0;
  border-radius: var(--_icon-button-radius);

  cursor: pointer;
  user-select: none;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    border-radius
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial);
}
.ax-icon-button > svg,
.ax-icon-button > .ax-icon {
  width: var(--_icon-button-icon-size);
  height: var(--_icon-button-icon-size);
  display: block;
}

.ax-icon-button.is-size-xs {
  --_icon-button-size: var(--comp-button-height-xs);
  --_icon-button-radius: calc(var(--_icon-button-size) / 2);
  --_icon-button-icon-size: var(--comp-button-icon-size-xs);
  --_icon-button-pressed-radius: var(--md-sys-shape-corner-small);
  --_icon-button-outline-width: var(--comp-button-outline-width-xs);
  min-inline-size: var(--_icon-button-size);
  min-block-size: var(--_icon-button-size);
}
.ax-icon-button.is-size-s {
  --_icon-button-size: var(--comp-button-height-s);
  --_icon-button-radius: calc(var(--_icon-button-size) / 2);
  --_icon-button-icon-size: var(--comp-button-icon-size-m);
  --_icon-button-pressed-radius: var(--md-sys-shape-corner-small);
  --_icon-button-outline-width: var(--comp-button-outline-width-s);
  min-inline-size: var(--_icon-button-size);
  min-block-size: var(--_icon-button-size);
}
.ax-icon-button.is-size-m {
  --_icon-button-size: var(--comp-button-height-m);
  --_icon-button-radius: calc(var(--_icon-button-size) / 2);
  --_icon-button-icon-size: var(--comp-button-icon-size-m);
  --_icon-button-pressed-radius: var(--md-sys-shape-corner-medium);
  --_icon-button-outline-width: var(--comp-button-outline-width-m);
  min-inline-size: var(--_icon-button-size);
  min-block-size: var(--_icon-button-size);
}
.ax-icon-button.is-size-l {
  --_icon-button-size: var(--comp-button-height-l);
  --_icon-button-radius: calc(var(--_icon-button-size) / 2);
  --_icon-button-icon-size: var(--comp-button-icon-size-l);
  --_icon-button-pressed-radius: var(--md-sys-shape-corner-large);
  --_icon-button-outline-width: var(--comp-button-outline-width-l);
  min-inline-size: var(--_icon-button-size);
  min-block-size: var(--_icon-button-size);
}
.ax-icon-button.is-size-xl {
  --_icon-button-size: var(--comp-button-height-xl);
  --_icon-button-radius: calc(var(--_icon-button-size) / 2);
  --_icon-button-icon-size: var(--comp-button-icon-size-xl);
  --_icon-button-pressed-radius: var(--md-sys-shape-corner-large);
  --_icon-button-outline-width: var(--comp-button-outline-width-xl);
  min-inline-size: var(--_icon-button-size);
  min-block-size: var(--_icon-button-size);
}
.ax-icon-button:active {
  border-radius: var(--_icon-button-pressed-radius);
}

/* --- Variants per §6.3 (default mode) --- */
.ax-icon-button.is-filled {
  background-color: var(--md-sys-color-primary);
  color:            var(--md-sys-color-on-primary);
}
.ax-icon-button.is-tonal {
  background-color: var(--md-sys-color-secondary-container);
  color:            var(--md-sys-color-on-secondary-container);
}
.ax-icon-button.is-outlined {
  background-color: transparent;
  color:            var(--md-sys-color-on-surface-variant);
  outline: var(--_icon-button-outline-width) solid var(--md-sys-color-outline-variant);
  outline-offset: -1px;
}
.ax-icon-button.is-standard {
  background-color: transparent;
  color:            var(--md-sys-color-on-surface-variant);
}

/* --- Toggle: selected per §6.4 --- */
.ax-icon-button.is-filled[aria-pressed="true"]    { /* unchanged: primary/on-primary */ }
.ax-icon-button.is-tonal[aria-pressed="false"] {
  background-color: var(--md-sys-color-secondary-container);
  color:            var(--md-sys-color-on-secondary-container);
}
.ax-icon-button.is-tonal[aria-pressed="true"] {
  background-color: var(--md-sys-color-secondary);
  color:            var(--md-sys-color-on-secondary);
}
.ax-icon-button.is-outlined[aria-pressed="true"] {
  background-color: var(--md-sys-color-inverse-surface);
  color:            var(--md-sys-color-inverse-on-surface);
  outline-color: transparent;
}
.ax-icon-button.is-standard[aria-pressed="true"] {
  color: var(--md-sys-color-primary);
}

/* --- Disabled — Pattern A --- */
.ax-icon-button:disabled,
.ax-icon-button[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 10%,
    transparent
  );
  color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 38%,
    transparent
  );
  outline: 0;
}
.ax-icon-button.is-standard:disabled,
.ax-icon-button.is-standard[aria-disabled="true"] {
  background-color: transparent;
}

/* ============================================================
 * §4 Divider — M3-COMPONENT-SPECS §18
 *
 * Use as <hr class="ax-divider"> or <div class="ax-divider"
 * role="separator" />. Default full-width; .is-style-inset
 * shifts left edge by 16px; .is-style-middle-inset insets both.
 * ============================================================ */
.ax-divider {
  display: block;
  height: 1px;
  width: 100%;
  border: 0;
  margin: 0;
  background-color: var(--md-sys-color-outline-variant);
}
.ax-divider.is-style-inset {
  margin-inline-start: var(--space-md);
  width: calc(100% - var(--space-md));
}
.ax-divider.is-style-middle-inset {
  margin-inline: var(--space-md);
  width: calc(100% - (var(--space-md) * 2));
}

/* ============================================================
 * Chunk B — Container & Navigation
 *
 *   §5 Card       — M3 §11
 *   §6 App bar    — M3 §1
 *   §7 Nav rail   — M3 §24 (collapsed only)
 *   §8 Tabs       — M3 §31 (primary + secondary, CSS-only indicator)
 *
 * BEM-style naming: .card--filled, .app-bar--small, etc.
 * Interactive containers compose .has-state-layer mixin (§0).
 * No literal color/size values — all references resolve to
 * tokens defined in tokens.css.
 * ============================================================ */

/* ============================================================
 * §5 Card — M3-COMPONENT-SPECS §11
 *
 * 3 variants: filled / elevated / outlined.
 * Default shape corner-medium, padding md, body-large content.
 * Interactive cards: add .card--interactive (or button element)
 *   + .has-state-layer to enable hover/focus/pressed surfaces.
 * Disabled per §0.8 Pattern B (whole-container 0.38).
 * ============================================================ */
.card {
  display: block;
  position: relative;
  padding: var(--comp-card-padding);
  border-radius: var(--comp-card-radius);
  color: var(--md-sys-color-on-surface);

  font-family:    var(--md-sys-typescale-body-large-font);
  font-size:      var(--md-sys-typescale-body-large-size);
  line-height:    var(--md-sys-typescale-body-large-line-height);
  font-weight:    var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* --- §11.3 Variants — container --- */
.card--filled {
  background-color: var(--md-sys-color-surface-container-highest);
  box-shadow: var(--md-sys-elevation-shadow-level0);
}
.card--elevated {
  background-color: var(--md-sys-color-surface-container-low);
  box-shadow: var(--md-sys-elevation-shadow-level1);
}
.card--outlined {
  background-color: var(--md-sys-color-surface);
  box-shadow: var(--md-sys-elevation-shadow-level0);
  outline: 1px solid var(--md-sys-color-outline-variant);
  outline-offset: -1px;
}

/* --- §11.4 Hover — only for interactive cards --- */
.card--interactive {
  cursor: pointer;
}
.card--interactive.card--elevated:hover {
  box-shadow: var(--md-sys-elevation-shadow-level2);
}

/* --- §0.11 Focus — outer +2px --- */
.card--interactive:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}
/* outlined cards already have a 1px outline → keep secondary on focus */
.card--outlined.card--interactive:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* --- §0.8 Disabled — Pattern B (whole-container 0.38) --- */
.card[aria-disabled="true"],
.card:disabled {
  opacity: 0.38;
  pointer-events: none;
  cursor: not-allowed;
}

/* --- Card slots (optional helpers) --- */
.card__media {
  margin: calc(var(--comp-card-padding) * -1);
  margin-bottom: var(--comp-card-padding);
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
  overflow: hidden;
}
.card__media > img,
.card__media > video {
  display: block;
  width: 100%;
  height: auto;
}
.card__title {
  font-family:    var(--md-sys-typescale-title-large-font);
  font-size:      var(--md-sys-typescale-title-large-size);
  line-height:    var(--md-sys-typescale-title-large-line-height);
  font-weight:    var(--md-sys-typescale-title-large-weight);
  letter-spacing: var(--md-sys-typescale-title-large-tracking);
  margin-bottom: var(--space-xs);
}
.card__subtitle {
  font-family:    var(--md-sys-typescale-body-medium-font);
  font-size:      var(--md-sys-typescale-body-medium-size);
  line-height:    var(--md-sys-typescale-body-medium-line-height);
  font-weight:    var(--md-sys-typescale-body-medium-weight);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
  color: var(--md-sys-color-on-surface-variant);
}
.card__actions {
  display: flex;
  gap: var(--space-sm);
  margin-top: var(--comp-card-padding);
}

/* ============================================================
 * §6 App bar — M3-COMPONENT-SPECS §1
 *
 * 3 variants:
 *   .app-bar--small             — 64px,  title-large
 *   .app-bar--medium-flexible   — 112px (default) / 136px (max)
 *                                  headline-medium, subtitle slot
 *   .app-bar--large-flexible    — 120px (default) / 152px (max)
 *                                  display-small, subtitle slot
 *
 * Resting: surface bg, level0. On scroll: surface-container,
 * level2. Toggled via [data-scrolled="true"] on the host (JS).
 * Padding 4px L/R per spec; icon spacing 0.
 * ============================================================ */
.app-bar {
  display: flex;
  align-items: center;
  width: 100%;
  padding-inline: var(--space-xs); /* 4px L/R */
  background-color: var(--md-sys-color-surface);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-shadow-level0);

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Scrolled state — applies to all variants */
.app-bar[data-scrolled="true"] {
  background-color: var(--md-sys-color-surface-container);
  box-shadow: var(--md-sys-elevation-shadow-level2);
}

/* --- Variant: small --- */
.app-bar--small {
  height: 64px;
}
.app-bar--small .app-bar__title {
  font-family:    var(--md-sys-typescale-title-large-font);
  font-size:      var(--md-sys-typescale-title-large-size);
  line-height:    var(--md-sys-typescale-title-large-line-height);
  font-weight:    var(--md-sys-typescale-title-large-weight);
  letter-spacing: var(--md-sys-typescale-title-large-tracking);
}

/* --- Variant: medium-flexible --- */
.app-bar--medium-flexible {
  min-height: 112px;
  max-height: 136px;
  flex-direction: column;
  align-items: stretch;
  padding-block: var(--space-sm);
}
.app-bar--medium-flexible .app-bar__title {
  font-family:    var(--md-sys-typescale-headline-medium-font);
  font-size:      var(--md-sys-typescale-headline-medium-size);
  line-height:    var(--md-sys-typescale-headline-medium-line-height);
  font-weight:    var(--md-sys-typescale-headline-medium-weight);
  letter-spacing: var(--md-sys-typescale-headline-medium-tracking);
}

/* --- Variant: large-flexible --- */
.app-bar--large-flexible {
  min-height: 120px;
  max-height: 152px;
  flex-direction: column;
  align-items: stretch;
  padding-block: var(--space-sm);
}
.app-bar--large-flexible .app-bar__title {
  font-family:    var(--md-sys-typescale-display-small-font);
  font-size:      var(--md-sys-typescale-display-small-size);
  line-height:    var(--md-sys-typescale-display-small-line-height);
  font-weight:    var(--md-sys-typescale-display-small-weight);
  letter-spacing: var(--md-sys-typescale-display-small-tracking);
}

/* --- Slots --- */
.app-bar__leading,
.app-bar__trailing {
  display: inline-flex;
  align-items: center;
  gap: 0; /* per spec — icon spacing 0 */
  flex-shrink: 0;
}
.app-bar__title {
  flex: 1 1 auto;
  min-width: 0;
  /* Truncate by default; flexible variants can override via .app-bar__title--wrap */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-bar__title--wrap {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}
.app-bar__subtitle {
  font-family:    var(--md-sys-typescale-title-medium-font);
  font-size:      var(--md-sys-typescale-title-medium-size);
  line-height:    var(--md-sys-typescale-title-medium-line-height);
  font-weight:    var(--md-sys-typescale-title-medium-weight);
  letter-spacing: var(--md-sys-typescale-title-medium-tracking);
  color: var(--md-sys-color-on-surface-variant);
}
/* Subtitle is only valid on flexible variants */
.app-bar--small .app-bar__subtitle { display: none; }

/* When flexible variants stack, the title row keeps icon controls
 * inline with leading/trailing while the title/subtitle stack
 * underneath. Author wraps icons in .app-bar__row. */
.app-bar__row {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 56px; /* leading icon row baseline */
}

/* ============================================================
 * §7 Nav rail (collapsed) — M3-COMPONENT-SPECS §24
 *
 * Default 96px width (--comp-rail-width); narrow variant 80px.
 * Container: surface bg, level0, corner-none.
 * Item: 64px tall total. Active indicator 56×32 pill on icon
 * row, secondary-container fill, on-secondary-container glyph.
 * Per §0.13: indicator only — no state-layer ring on selected
 * indicator; the state layer applies to the full item.
 *
 * Vertical rhythm: top 44px, item gap 4px (§24.2).
 * Item is a button-like element with .has-state-layer.
 * ============================================================ */
.nav-rail {
  display: flex;
  flex-direction: column;
  width: var(--comp-rail-width);
  min-height: 100%;
  background-color: var(--nav-rail-bg);
  color: var(--md-sys-color-on-surface-variant);
  border-radius: var(--md-sys-shape-corner-none);
  padding-top: 44px;
  gap: var(--space-xs); /* 4px between items */
}
.nav-rail.is-narrow {
  width: var(--comp-rail-narrow); /* 80px */
}

.nav-rail__item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);

  width: 100%;
  height: 64px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  user-select: none;

  font-family:    var(--md-sys-typescale-label-medium-font);
  font-size:      var(--md-sys-typescale-label-medium-size);
  line-height:    var(--md-sys-typescale-label-medium-line-height);
  font-weight:    var(--md-sys-typescale-label-medium-weight);
  letter-spacing: var(--md-sys-typescale-label-medium-tracking);

  transition:
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Active indicator (56×32 pill behind icon) — §0.13.
 * Uses ::before so .has-state-layer's ::before is reserved for
 * state surfaces. The indicator wraps the icon glyph only.
 * Author markup: <button class="nav-rail__item">
 *                  <span class="nav-rail__icon">...</span>
 *                  <span class="nav-rail__label">...</span>
 *                </button>
 * The indicator is drawn around .nav-rail__icon. */
.nav-rail__icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 32px;
  border-radius: var(--md-sys-shape-corner-full);
  z-index: 0;

  transition:
    background-color
      var(--md-sys-motion-curve-default-effects-duration)
      var(--md-sys-motion-curve-default-effects),
    color
      var(--md-sys-motion-curve-default-effects-duration)
      var(--md-sys-motion-curve-default-effects);
}
.nav-rail__icon > svg,
.nav-rail__icon > .ax-icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
  position: relative;
  z-index: 1;
}

.nav-rail__item.is-active .nav-rail__icon {
  background-color: var(--nav-item-active);
  color: var(--nav-on-item-active);
}
.nav-rail__item.is-active {
  color: var(--md-sys-color-on-surface);
}

/* State layer scoped to the icon pill — composing .has-state-layer
 * on .nav-rail__icon directly is the author's choice. The class is
 * left out of this stylesheet so authors can opt in:
 *   <span class="nav-rail__icon has-state-layer">...</span>  */

/* Disabled */
.nav-rail__item:disabled,
.nav-rail__item[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.nav-rail__item:disabled .nav-rail__icon,
.nav-rail__item[aria-disabled="true"] .nav-rail__icon {
  background-color: transparent;
  color: inherit;
}

/* ----------------------------------------------------------
 * §7.2 Expanded variant — M3-COMPONENT-SPECS §24
 *
 * Standalone Expanded rail: 220px min-width, can grow to 360px max.
 * Items lay out as horizontal rows: icon + label-large on a single line,
 * indicator becomes a full-width pill (vs. the 56×32 collapsed pill).
 *
 * Modal Expanded variant: NOT a separate component. Compose by placing
 * .nav-rail.is-expanded inside .sheet--side-modal — the sheet handles
 * scrim + slide-in, the rail provides the layout. This avoids
 * duplicating sheet chrome inside the rail itself.
 * ---------------------------------------------------------- */
.nav-rail.is-expanded {
  width: var(--layout-rail-expanded);       /* 220px standalone */
  max-width: var(--layout-rail-expanded-max); /* 360px ceiling */
  padding-top: var(--space-md);
  padding-inline: var(--space-sm);
  gap: var(--space-xs);
}
.nav-rail.is-expanded .nav-rail__item {
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
  gap: var(--space-md);
  height: 56px;
  padding-inline: var(--space-md);
  border-radius: var(--md-sys-shape-corner-full);
  /* Label-large typescale per §24.3 */
  font-family:    var(--md-sys-typescale-label-large-font);
  font-size:      var(--md-sys-typescale-label-large-size);
  line-height:    var(--md-sys-typescale-label-large-line-height);
  font-weight:    var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
}
/* Active indicator — full-width 56px pill on the item itself,
 * NOT the icon-scoped pill. Reset the collapsed indicator first. */
.nav-rail.is-expanded .nav-rail__item.is-active {
  background-color: var(--nav-item-active);
  color: var(--nav-on-item-active);
}
.nav-rail.is-expanded .nav-rail__item.is-active .nav-rail__icon {
  background-color: transparent;            /* indicator moves to item */
  color: inherit;
}
/* Icon container shrinks to natural size — no 56×32 pill. */
.nav-rail.is-expanded .nav-rail__icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
  border-radius: 0;
  flex-shrink: 0;
}
/* Label visible (in collapsed it's already shown; this just affirms). */
.nav-rail.is-expanded .nav-rail__label {
  display: inline;
  text-align: start;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============================================================
 * §8 Tabs — M3-COMPONENT-SPECS §31
 *
 * 2 variants:
 *   .tabs--primary   — 3px indicator, rounded top, color primary
 *   .tabs--secondary — 2px indicator, flat, on-surface label
 *
 * Container: 48px (label only) / 64px (icon+label) — toggled by
 * .tabs--with-icon on the host. Surface bg.
 *
 * Active indicator: implemented as ::after on .tabs__tab. Shown
 * only when .is-active. Slide animation between tabs is JS.
 *
 * Focus indicator on tab uses inner offset -3px per §0.11.
 * ============================================================ */
.tabs {
  display: flex;
  align-items: stretch;
  width: 100%;
  background-color: var(--md-sys-color-surface);
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }

.tabs__tab {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);

  flex: 1 1 0;
  min-width: max-content;
  height: 48px;
  padding-inline: var(--space-md);
  border: 0;
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;

  font-family:    var(--md-sys-typescale-title-small-font);
  font-size:      var(--md-sys-typescale-title-small-size);
  line-height:    var(--md-sys-typescale-title-small-line-height);
  font-weight:    var(--md-sys-typescale-title-small-weight);
  letter-spacing: var(--md-sys-typescale-title-small-tracking);

  transition:
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

.tabs--with-icon .tabs__tab {
  height: 64px;
}
.tabs__tab > svg,
.tabs__tab > .ax-icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
}

/* Active indicator — drawn as ::after; CSS-only.
 * Slide-on-switch is a prototype-layer JS concern. */
.tabs__tab::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: 0;
  background-color: var(--md-sys-color-primary);
  border-radius: 0;
  pointer-events: none;
  opacity: 0;

  transition:
    height
      var(--md-sys-motion-curve-default-spatial-duration)
      var(--md-sys-motion-curve-default-spatial),
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* --- Variant: primary --- */
.tabs--primary .tabs__tab.is-active {
  color: var(--md-sys-color-primary);
}
.tabs--primary .tabs__tab.is-active::after {
  height: 3px;
  border-start-start-radius:  var(--md-sys-shape-corner-extra-small);
  border-start-end-radius: var(--md-sys-shape-corner-extra-small);
  opacity: 1;
}

/* --- Variant: secondary --- */
.tabs--secondary .tabs__tab.is-active {
  color: var(--md-sys-color-on-surface);
}
.tabs--secondary .tabs__tab.is-active::after {
  height: 2px;
  border-radius: 0;
  opacity: 1;
}

/* Inner-offset focus per §0.11 — tabs are tightly packed, so the
 * outer +2px would clip against neighbours. Override locally. */
.tabs__tab:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: -3px;
}

/* Disabled */
.tabs__tab:disabled,
.tabs__tab[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}

/* ============================================================
 * Chunk C-1 — Input primitives
 *
 *   §9  Text field — M3 §32 (filled + outlined)
 *   §10 Search bar — M3 §29 (rest state only)
 *
 * Out of scope (BLOCK-COMPONENT-MAP §1.3 — bucket E, form
 * plugin territory): switch, checkbox, radio, slider.
 *
 * Text-field uses its own active indicator instead of the
 * global :focus-visible ring per §0.11. Search bar uses the
 * standard outer +2px focus.
 * ============================================================ */

/* ============================================================
 * §9 Text field — M3-COMPONENT-SPECS §32
 *
 * Refactored markup (v0.3.0+):
 *   <div class="text-field text-field--filled">
 *     <div class="text-field__container">                 ← div, NOT label
 *       <input class="text-field__input" id="tf-id"
 *              placeholder=" "
 *              required pattern="..." maxlength="..." />
 *       <label class="text-field__label" for="tf-id">     ← real <label>
 *           Email
 *       </label>
 *       <span class="text-field__leading-icon">…</span>     (optional)
 *       <span class="text-field__prefix">₩</span>           (optional)
 *       <span class="text-field__suffix">KRW</span>         (optional)
 *       <button class="ax-icon-button is-standard has-state-layer
 *                      text-field__trailing-icon">…</button>(optional, interactive)
 *       <span class="text-field__error-icon">…</span>       (auto-shown on error)
 *     </div>
 *     <div class="text-field__bottom">                       (optional)
 *       <span class="text-field__supporting">Helper</span>
 *       <span class="text-field__counter">0 / 280</span>
 *     </div>
 *   </div>
 *
 * IMPORTANT — A11y + HTML standards (v0.3.5 fix):
 *   The container is a <div>, not a <label>. HTML5 forbids labelable
 *   elements (<button>, <a>) from being descendants of a <label> unless
 *   the label is for that element — putting <button class="ax-icon-button">
 *   inside <label> causes label-click → input-focus to fight the button's
 *   own click handler, breaking clear/voice/dropdown actions.
 *   The label is now a real <label class="text-field__label" for="tf-id">
 *   with explicit for/id pairing.
 *
 * IMPORTANT — DOM order:
 *   input MUST come first in DOM. All slot spans/labels are general
 *   siblings of the input — selectors like `:placeholder-shown ~ .__prefix`
 *   rely on this. Visual column placement is set via grid-column on each
 *   slot, AND every slot includes `grid-row: 1` so they all stay on the
 *   single row regardless of DOM order. (Without grid-row: 1, an item with
 *   grid-column: 1 placed AFTER an item in column 3 would auto-place to
 *   row 2 because grid auto-flow can't move backward in a row.)
 *
 * Why the wrapper split (vs the v0.2.x single-label markup):
 *   - Container background no longer leaks onto supporting text / counter
 *     (those sit in __bottom, OUTSIDE the visual container).
 *   - Prefix/suffix slots fit cleanly via 5-column grid.
 *   - Native validation (:user-invalid) can drive .is-error without JS.
 *
 * Floating label (§0.9 pattern 5):
 *   - rest (input empty + not focused) → 16px, vertically centered
 *   - populated or focused             → 12px, top-aligned
 *   The trick: pair `placeholder=" "` with `:placeholder-shown`
 *   to detect "empty" without JS. Same as v0.2.x.
 *
 * Prefix / suffix visibility (M3 spec):
 *   Hidden at rest, shown only when label floats up. Otherwise the
 *   label and the prefix would both occupy the same baseline (visual
 *   collision). See `:not(:focus-within) :placeholder-shown ~` rules.
 *
 * Validation:
 *   - Manual: add `.is-error` to the wrapper.
 *   - Native: HTML5 attributes (required, pattern, maxlength, type=email…)
 *     trigger :user-invalid AFTER user interaction. Both paths share styles.
 *
 * Active indicator (per §0.11):
 *   filled    → bottom border 1px (rest) → 2px primary (focus)
 *   outlined  → 1px outline (rest) → 3px primary (focus)
 *   These REPLACE the global :focus-visible ring (lives on __container).
 *
 * Outlined notch backdrop:
 *   Floated label gets a `surface` background to break the outline visually.
 *   Assumes the field sits on a `surface` parent — see caveat 9.2 in
 *   OVERVIEW.md for nested-surface workaround (v1.5+ tokenization planned).
 *
 * Icons & images (M3 §32 — leading + trailing slots):
 *   - Leading icon  : input affordance signifier (search, calendar…)
 *   - Trailing icon : action (clear, voice, dropdown), or error icon
 *   - Error icon    : auto-shown via .is-error / :user-invalid; trailing
 *                     icon hidden in same grid column (mutually exclusive)
 *   - INTERACTIVE icons (clear, voice, dropdown) — compose ax-icon-button:
 *
 *       <button class="ax-icon-button is-standard has-state-layer
 *                      text-field__trailing-icon"
 *               aria-label="…"> <svg/> </button>
 *
 *     The system's icon-button gives 40×40 size, state layer, focus ring;
 *     the slot class only positions it via grid-column. CSS adds negative
 *     margin so the 40×40 button doesn't expand the input column visually.
 *
 *   - Clear button : add .is-clear modifier — auto-hides when input is
 *                    empty (M3 §32: "appear only when input text is
 *                    present"). Other interactive trailing icons (voice,
 *                    dropdown, calendar) stay visible always.
 * ============================================================ */
.text-field {
  --_tf-h: 56px;                         /* container height (single-line) */
  --_tf-px: var(--space-md);             /* container horizontal padding */
  --_tf-label-rest-size: var(--md-sys-typescale-body-large-size);
  --_tf-label-rest-lh:   var(--md-sys-typescale-body-large-line-height);
  --_tf-label-float-size: var(--md-sys-typescale-body-small-size);
  --_tf-label-float-lh:   var(--md-sys-typescale-body-small-line-height);

  display: flex;
  flex-direction: column;
  width: 100%;
}

/* --- Visual container (label) — holds icons, input, label, prefix/suffix --- */
.text-field__container {
  position: relative;
  display: grid;
  /* leading | prefix | input/label | suffix | trailing+error */
  grid-template-columns: auto auto 1fr auto auto;
  align-items: center;
  column-gap: var(--space-sm);

  width: 100%;
  min-height: var(--_tf-h);
  padding-inline: var(--_tf-px);
  color: var(--md-sys-color-on-surface);

  font-family:    var(--md-sys-typescale-body-large-font);
  font-size:      var(--md-sys-typescale-body-large-size);
  line-height:    var(--md-sys-typescale-body-large-line-height);
  font-weight:    var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);

  cursor: text;
  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* --- Slots --- */
.text-field__leading-icon,
.text-field__trailing-icon,
.text-field__error-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--md-sys-color-on-surface-variant);
}
.text-field__leading-icon  { grid-column: 1; grid-row: 1; }
.text-field__trailing-icon { grid-column: 5; grid-row: 1; }
.text-field__error-icon    { grid-column: 5; grid-row: 1; color: var(--md-sys-color-error); }

.text-field__leading-icon  > svg,
.text-field__leading-icon  > .ax-icon,
.text-field__trailing-icon > svg,
.text-field__trailing-icon > .ax-icon,
.text-field__error-icon    > svg,
.text-field__error-icon    > .ax-icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
}

/* Error icon hidden by default — shown via .is-error / :user-invalid */
.text-field__error-icon { display: none; }

/* Clear button — auto-hide when input is empty (M3 §32: "appear only when
   input text is present"). Author opt-in via .is-clear modifier so other
   trailing icons (voice, dropdown, calendar) aren't accidentally hidden. */
.text-field__container .text-field__input:placeholder-shown
  ~ .text-field__trailing-icon.is-clear {
  display: none;
}

/* Interactive trailing/leading icons (clear, voice, dropdown) should be
   built by composing with .ax-icon-button — author writes the markup as
   <button class="ax-icon-button is-standard has-state-layer text-field__trailing-icon">.
   The system's icon-button handles 40×40 size + state layer + focus, and
   the slot class only adds grid placement.

   Negative inline margin pulls the 40×40 button visually inward so the
   visible icon centers like a static 24×24 span (40-24=16, ÷2=8). Without
   this, grid columns 1/5 grow wider than expected. */
.text-field__container > button.text-field__trailing-icon.ax-icon-button,
.text-field__container > button.text-field__leading-icon.ax-icon-button {
  margin-block: -8px;
  margin-inline: -8px;
}

/* Prefix / suffix — affixed text alongside input */
.text-field__prefix,
.text-field__suffix {
  color: var(--md-sys-color-on-surface-variant);
  font: inherit;
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
}
.text-field__prefix { grid-column: 2; grid-row: 1; }
.text-field__suffix { grid-column: 4; grid-row: 1; }

/* When a suffix exists, right-align the input value. Use case:
 * amounts ("1,200 KRW"), units ("64 GB"), email-local before
 * "@example.com", percentages — all read better with the value
 * sitting flush with the suffix.
 *
 * `:has(~ .text-field__suffix)` on the input matches any input
 * whose forward-sibling stack contains a suffix. logical `end`
 * keeps RTL behavior correct (suffix flips, alignment flips). */
.text-field__input:has(~ .text-field__suffix) {
  text-align: end;
}

/* Hide prefix/suffix at rest (input empty + not focused).
   The label occupies the same horizontal space at rest, so showing both
   creates visual clutter ("₩ Price" double cue). When user focuses or types,
   label floats up and prefix/suffix become visible affordances.

   Requires: prefix/suffix MUST come AFTER the input in DOM order
   (general sibling combinator `~` only matches forward siblings).
   Their visual columns are set by grid-column above. */
.text-field__container:not(:focus-within)
  .text-field__input:placeholder-shown ~ .text-field__prefix,
.text-field__container:not(:focus-within)
  .text-field__input:placeholder-shown ~ .text-field__suffix {
  visibility: hidden;
}

.text-field__input {
  grid-column: 3;
  grid-row: 1;
  width: 100%;
  min-width: 0;
  background: transparent;
  border: 0;
  outline: 0;
  color: inherit;
  /* Caret defaults to primary so the focus-state cursor color matches
   * the focus indicator (active indicator, label color). The input
   * value text itself uses `color: inherit` and stays on-surface. */
  caret-color: var(--md-sys-color-primary);
  font: inherit;

  /* Reserve space for floated label above input baseline */
  padding-block: var(--space-md) 0;
}
.text-field__input::placeholder {
  /* Hide the placeholder character — the floating label IS the cue */
  color: transparent;
}

/* Label sits over input column, anchored vertically centered at rest. */
.text-field__label {
  position: absolute;
  inset-inline-start: var(--_tf-px);
  inset-block-start: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--md-sys-color-on-surface-variant);

  font-size:   var(--_tf-label-rest-size);
  line-height: var(--_tf-label-rest-lh);

  transition:
    top
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    transform
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    font-size
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Float when populated OR focused */
.text-field__container:focus-within .text-field__label,
.text-field__input:not(:placeholder-shown) ~ .text-field__label {
  top: 8px;
  transform: translateY(0);
  font-size:   var(--_tf-label-float-size);
  line-height: var(--_tf-label-float-lh);
}

/* When leading-icon present, label shifts to clear it.
   For outlined variant, this offset only applies at REST — when the label
   floats up, it sits over the outline's top edge (the "notch") at the
   original padding position, not above the leading icon. Material Web
   ref does the same: notch always near the outline's leading corner. */
.text-field--with-leading .text-field__label {
  inset-inline-start: calc(var(--_tf-px) + var(--comp-icon-size-md) + var(--space-sm));
}
.text-field--outlined.text-field--with-leading
  .text-field__container:focus-within .text-field__label,
.text-field--outlined.text-field--with-leading
  .text-field__input:not(:placeholder-shown) ~ .text-field__label {
  /* Cancel leading-icon offset → notch sits at outline's leading corner,
     same as a no-leading-icon outlined field. Subtract space-xs to account
     for the notch's padding-inline (added below for backdrop effect). */
  inset-inline-start: calc(var(--_tf-px) - var(--space-xs));
}

/* --- Bottom row — supporting text + counter, OUTSIDE the container --- */
.text-field__bottom {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-md);
  padding-inline: var(--_tf-px);
  margin-top: var(--space-xs);

  font-family:    var(--md-sys-typescale-body-small-font);
  font-size:      var(--md-sys-typescale-body-small-size);
  line-height:    var(--md-sys-typescale-body-small-line-height);
  font-weight:    var(--md-sys-typescale-body-small-weight);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
}
.text-field__supporting,
.text-field__counter {
  color: var(--md-sys-color-on-surface-variant);
}
.text-field__counter {
  white-space: nowrap;
  margin-inline-start: auto; /* push counter to trailing edge if alone */
}

/* ----- Variant: filled ----- */
.text-field--filled .text-field__container {
  background-color: var(--md-sys-color-surface-container-highest);
  border-radius: var(--md-sys-shape-corner-extra-small-top);
  /* Active indicator = bottom border. 1px rest → 2px focus. */
  box-shadow: inset 0 -1px 0 0 var(--md-sys-color-on-surface-variant);
}
.text-field--filled .text-field__container:hover {
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 8%,
    var(--md-sys-color-surface-container-highest)
  );
  box-shadow: inset 0 -1px 0 0 var(--md-sys-color-on-surface);
}
.text-field--filled .text-field__container:focus-within {
  box-shadow: inset 0 -2px 0 0 var(--md-sys-color-primary);
}
.text-field--filled .text-field__container:focus-within .text-field__label {
  color: var(--md-sys-color-primary);
}

/* ----- Variant: outlined ----- */
.text-field--outlined .text-field__container {
  background-color: transparent;
  border-radius: var(--md-sys-shape-corner-extra-small);
  /* Active indicator = full outline. 1px rest → 2px focus (M3 spec). */
  box-shadow: inset 0 0 0 1px var(--md-sys-color-outline);
}

/* In outlined, the floated label notches the OUTLINE (sits at top:-50%),
   so the input doesn't need to reserve top padding for it. Resetting to 0
   lets the input center vertically inside the container — matches M3 spec. */
.text-field--outlined .text-field__input {
  padding-block: 0;
}
.text-field--outlined .text-field__container:hover {
  box-shadow: inset 0 0 0 1px var(--md-sys-color-on-surface);
}
.text-field--outlined .text-field__container:focus-within {
  box-shadow: inset 0 0 0 2px var(--md-sys-color-primary);
}
.text-field--outlined .text-field__container:focus-within .text-field__label {
  color: var(--md-sys-color-primary);
}
/* Outlined float-label "notch" — surface backdrop so label breaks the outline.
 * Assumes parent surface = `surface`. See caveat 9.2 (v1.5+ tokenization). */
.text-field--outlined .text-field__container:focus-within .text-field__label,
.text-field--outlined .text-field__input:not(:placeholder-shown)
  ~ .text-field__label {
  background-color: var(--md-sys-color-surface);
  padding-inline: var(--space-xs);
  /* shift up so the notch sits over the outline, not inside */
  top: calc(var(--_tf-label-float-lh) * -0.5);
}

/* ----- Error state — manual (.is-error) OR native (:user-invalid) -----
   :user-invalid fires only AFTER user interaction → matches form UX
   convention better than :invalid (which is true on initial load for
   any required-but-empty field). */
.text-field.is-error.text-field--filled .text-field__container,
.text-field--filled:has(.text-field__input:user-invalid) .text-field__container {
  box-shadow: inset 0 -2px 0 0 var(--md-sys-color-error);
}
.text-field.is-error.text-field--outlined .text-field__container,
.text-field--outlined:has(.text-field__input:user-invalid) .text-field__container {
  box-shadow: inset 0 0 0 1px var(--md-sys-color-error);
}
.text-field.is-error.text-field--outlined .text-field__container:focus-within,
.text-field--outlined:has(.text-field__input:user-invalid) .text-field__container:focus-within {
  box-shadow: inset 0 0 0 2px var(--md-sys-color-error);
}

/* Error: label, supporting, counter all turn error-colored.
   IMPORTANT: includes :focus-within selector so error wins over the
   variant-level focus rules (.text-field--filled :focus-within .__label →
   primary) which otherwise have higher specificity. */
.text-field.is-error .text-field__label,
.text-field.is-error .text-field__supporting,
.text-field.is-error .text-field__counter,
.text-field.is-error .text-field__container:focus-within .text-field__label,
.text-field:has(.text-field__input:user-invalid) .text-field__label,
.text-field:has(.text-field__input:user-invalid) .text-field__supporting,
.text-field:has(.text-field__input:user-invalid) .text-field__counter,
.text-field:has(.text-field__input:user-invalid)
  .text-field__container:focus-within .text-field__label {
  color: var(--md-sys-color-error);
}

/* Error: hide trailing-icon, show error-icon (mutually exclusive on grid col 5) */
.text-field.is-error .text-field__trailing-icon,
.text-field:has(.text-field__input:user-invalid) .text-field__trailing-icon {
  display: none;
}
.text-field.is-error .text-field__error-icon,
.text-field:has(.text-field__input:user-invalid) .text-field__error-icon {
  display: inline-flex;
}

/* Error: input value color, caret, and password-mask dots all turn error.
 * Why all three:
 *   color                   — password type uses currentColor for the
 *                              masking glyph (•••), so the dots only turn
 *                              red if the input's `color` is error.
 *   caret-color             — overrides the default primary caret so the
 *                              cursor is also red while typing.
 *   -webkit-text-fill-color — Safari/iOS user-agent style for input value
 *                              wins over plain `color` in some states; the
 *                              fill-color override matches the WebKit-only
 *                              precedence so the value text actually
 *                              renders red on iOS as well as Chromium. */
.text-field.is-error .text-field__input,
.text-field:has(.text-field__input:user-invalid) .text-field__input {
  color: var(--md-sys-color-error);
  caret-color: var(--md-sys-color-error);
  -webkit-text-fill-color: var(--md-sys-color-error);
}

/* ----- Disabled — Pattern D (§0.8) -----
 *   filled container fill   → on-surface @ 0.04
 *   filled active indicator → on-surface @ 0.38
 *   outlined outline        → on-surface @ 0.12
 *   label / icons           → on-surface @ 0.38
 *   input value             → on-surface @ 0.38
 *   bottom row text         → on-surface @ 0.38                          */
.text-field:has(.text-field__input:disabled),
.text-field[aria-disabled="true"] {
  cursor: not-allowed;
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.text-field--filled:has(.text-field__input:disabled) .text-field__container,
.text-field--filled[aria-disabled="true"] .text-field__container {
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 4%,
    transparent
  );
  box-shadow: inset 0 -1px 0 0
    color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.text-field--outlined:has(.text-field__input:disabled) .text-field__container,
.text-field--outlined[aria-disabled="true"] .text-field__container {
  box-shadow: inset 0 0 0 1px
    color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
}
.text-field:has(.text-field__input:disabled) .text-field__label,
.text-field[aria-disabled="true"] .text-field__label,
.text-field:has(.text-field__input:disabled) .text-field__leading-icon,
.text-field:has(.text-field__input:disabled) .text-field__trailing-icon,
.text-field:has(.text-field__input:disabled) .text-field__prefix,
.text-field:has(.text-field__input:disabled) .text-field__suffix,
.text-field:has(.text-field__input:disabled) .text-field__supporting,
.text-field:has(.text-field__input:disabled) .text-field__counter,
.text-field[aria-disabled="true"] .text-field__leading-icon,
.text-field[aria-disabled="true"] .text-field__trailing-icon,
.text-field[aria-disabled="true"] .text-field__prefix,
.text-field[aria-disabled="true"] .text-field__suffix,
.text-field[aria-disabled="true"] .text-field__supporting,
.text-field[aria-disabled="true"] .text-field__counter {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}

/* Disabled input value color — WebKit/Blink user-agent stylesheets force
 * `color: GrayText` on `input:disabled`, which overrides our `color: inherit`
 * cascade. iOS Safari is especially aggressive. The `-webkit-text-fill-color`
 * property has higher precedence than UA `color` and is the documented
 * mechanism to opt out of the native disabled-gray. */
.text-field__input:disabled,
.text-field[aria-disabled="true"] .text-field__input {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  -webkit-text-fill-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}

/* ----- Textarea support -----
 * Textareas don't need the 5-column grid (no prefix/suffix in textarea).
 * Use a simple block layout with a generous top padding that reserves
 * space for both the floating label (when up) and the textarea content
 * (so first line of text doesn't collide with the rest-position label).
 *
 * Layout shifts:
 *   - container: grid → block
 *   - container padding-top: enough to clear label at rest (top: var(--space-md))
 *   - container padding-bottom: var(--space-md) for symmetry
 *   - textarea padding-block: 0 (container handles it)
 *   - label rest position: top: var(--space-md) (top-anchored, not centered)
 *   - label float position: top: 8px (same as single-line)
 */
.text-field__container:has(textarea.text-field__input) {
  display: block;            /* drop the 5-column grid for textareas */
  align-items: initial;
  /* Top padding clears rest-position label (16px line-height + breathing) */
  padding-block: calc(var(--space-md) + var(--_tf-label-rest-lh)) var(--space-md);
}

/* Reset textarea padding — container handles spacing now */
.text-field__container:has(textarea.text-field__input) textarea.text-field__input {
  padding-block: 0;
  display: block;
  width: 100%;
}

/* Label sits at the top instead of vertically centered */
.text-field__container:has(textarea.text-field__input) .text-field__label {
  top: var(--space-md);
  transform: none;
}

/* Float when populated or focused — same 8px as single-line */
.text-field__container:has(textarea.text-field__input):focus-within .text-field__label,
.text-field__container:has(textarea.text-field__input)
  textarea.text-field__input:not(:placeholder-shown)
  ~ .text-field__label {
  top: 8px;
}

textarea.text-field__input {
  resize: vertical;
  min-height: 96px;
}


/* ============================================================
 * §10 Search bar — M3-COMPONENT-SPECS §29
 *
 * Rest state only. Active/expanded "search view" is a dialog
 * surface and lives in Chunk D.
 *
 * Container: surface-container-high, level3, corner-full, 56px.
 * Hover surface = state layer Pattern A on the host.
 * Focus indicator = standard outer +2px (§0.11) — search bar
 * stands alone, no clipping concern.
 * ============================================================ */
.search-bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-md);

  width: 100%;
  height: 56px;
  padding-inline: var(--space-md);
  border-radius: var(--md-sys-shape-corner-full);

  background-color: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-shadow-level3);

  font-family:    var(--md-sys-typescale-body-large-font);
  font-size:      var(--md-sys-typescale-body-large-size);
  line-height:    var(--md-sys-typescale-body-large-line-height);
  font-weight:    var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);

  cursor: text;
  isolation: isolate;
  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Hover state layer — Pattern A on the host */
.search-bar::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}
.search-bar:hover::before {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}

.search-bar__leading-icon,
.search-bar__trailing {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.search-bar__leading-icon {
  color: var(--md-sys-color-on-surface);
}
.search-bar__leading-icon > svg,
.search-bar__leading-icon > .ax-icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
}
/* v3.4.4: Material Symbols glyph variant — sized by font-size, not box.
   Matches the icon-font runtime introduced in v3.4.3. */
.search-bar__leading-icon > .material-symbols-rounded {
  font-size: var(--comp-icon-size-md);
}

.search-bar__trailing {
  color: var(--md-sys-color-on-surface-variant);
  gap: var(--space-sm);
}
.search-bar__trailing > svg,
.search-bar__trailing > .ax-icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
}

.search-bar__input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: 0;
  outline: 0;
  font: inherit;
  color: inherit;
}
.search-bar__input::placeholder {
  color: var(--md-sys-color-on-surface-variant);
}

/* Focus — standard outer +2px (§0.11) */
.search-bar:focus-within {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* Disabled */
.search-bar[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 10%,
    transparent
  );
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  box-shadow: var(--md-sys-elevation-shadow-level0);
}

/* ============================================================
 * Chunk C-2 — Chip + Dialog
 *
 *   §11 Chip   — M3 §14 (assist / filter / input / suggestion)
 *   §12 Dialog — M3 §17 (basic + full-screen)
 * ============================================================ */

/* ============================================================
 * §11 Chip — M3-COMPONENT-SPECS §14
 *
 * 4 variants:
 *   .chip--assist      — outlined by default, action-y
 *   .chip--filter      — selected/unselected, toggle behavior
 *   .chip--input       — represents user input, removable
 *   .chip--suggestion  — flat, suggestive
 *
 * Common:
 *   height 32px, corner-small (8px), label-large, leading + trailing
 *   icon slots 18px. State layer: .has-state-layer (author opt-in).
 *
 * Selected state (filter only) per §0.13:
 *   secondary-container fill + on-secondary-container glyph.
 * Disabled per §0.8 Pattern A.
 * ============================================================ */
.chip {
  --_chip-h: 32px;
  /* Chip icon = 18px, all variants. Per M3-COMPONENT-SPECS §14.2.
   * Component-specific literal — NOT tokenized. Only chip uses 18px;
   * adding a global token would tempt reuse where 20px/24px is correct.
   * Avatar leading slot (chip-input §14.5) uses 24px via .is-size-xs. */
  --_chip-icon: 18px;

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

  height: var(--_chip-h);
  padding-inline: var(--space-md);
  border: 0;
  border-radius: var(--md-sys-shape-corner-small);

  background-color: transparent;
  color: var(--md-sys-color-on-surface);

  font-family:    var(--md-sys-typescale-label-large-font);
  font-size:      var(--md-sys-typescale-label-large-size);
  line-height:    var(--md-sys-typescale-label-large-line-height);
  font-weight:    var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);

  cursor: pointer;
  user-select: none;
  white-space: nowrap;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

.chip__leading-icon,
.chip__trailing-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
/* Icon sizing — covers both wrapping spans AND SVGs that ARE the slot.
 * Avatar leading (.chip__leading-icon.ax-avatar) doesn't match these
 * selectors, so its 24px size from .ax-avatar is preserved. */
svg.chip__leading-icon,
svg.chip__trailing-icon,
.chip__leading-icon  > svg,
.chip__leading-icon  > .ax-icon,
.chip__trailing-icon > svg,
.chip__trailing-icon > .ax-icon {
  width: var(--_chip-icon);
  height: var(--_chip-icon);
}

/* --- Variants — rest container --- */
.chip--assist,
.chip--filter,
.chip--input {
  /* Outlined surface */
  outline: 1px solid var(--md-sys-color-outline-variant);
  outline-offset: -1px;
}
.chip--suggestion {
  /* Flat */
  outline: 0;
}

/* --- Selected state (filter only) per §0.13 ---
 * Three valid markers:
 *   [aria-pressed="true"] — multi-select chip (M3 default toolbar pattern)
 *   [aria-checked="true"] — single-select chip (radiogroup pattern, M3 spec
 *                           "use button group/radio when one-of-N")
 *   .is-selected           — CSS-only state for prototypes without JS
 * All three resolve to the same selected-fill styling. */
.chip--filter[aria-pressed="true"],
.chip--filter[aria-checked="true"],
.chip--filter.is-selected {
  outline: 0;
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
}

/* --- Trailing-icon click target (input chip remove) --- */
.chip--input .chip__trailing-icon {
  cursor: pointer;
}

/* --- Disabled — Pattern A (§0.8) --- */
.chip:disabled,
.chip[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 10%,
    transparent
  );
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  outline-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 12%,
    transparent
  );
}
.chip--suggestion:disabled,
.chip--suggestion[aria-disabled="true"] {
  background-color: transparent;
}

/* ============================================================
 * §12 Dialog — M3-COMPONENT-SPECS §17
 *
 * Two variants: basic dialog (.dialog--basic) and full-screen
 * dialog (.dialog--full-screen). Both are siblings of a
 * scrim layer .modal-scrim. Open state toggled via [open]
 * attribute (HTML <dialog>) or .is-open on a custom host.
 *
 * Markup contract:
 *   <div class="modal-scrim" data-open="true"></div>
 *   <dialog class="dialog dialog--basic" open>
 *     <h2 class="dialog__headline">…</h2>
 *     <div class="dialog__supporting">…</div>
 *     <div class="dialog__actions">…</div>
 *   </dialog>
 *
 * Basic: surface-container-high, level3, corner-extra-large
 *        (28px), max-width 560px (--layout-modal-max), padding 24.
 * Full-screen: surface, no shadow, fills viewport, app-bar
 *        slot at top.
 * ============================================================ */

/* Modal scrim — shared by dialog + sheet (M3 §0.12).
 * Sits below the surface; toggle via [data-open="true"] or .is-open. */
.modal-scrim {
  position: fixed;
  inset: 0;
  background-color: var(--md-sys-color-scrim);
  opacity: 0;
  pointer-events: none;
  z-index: 1000;
  transition:
    opacity
      var(--md-sys-motion-curve-default-effects-duration)
      var(--md-sys-motion-curve-default-effects);
}
.modal-scrim[data-open="true"],
.modal-scrim.is-open {
  opacity: 0.32;
  pointer-events: auto;
}

/* Common dialog reset (when using <dialog> element) */
.dialog {
  position: fixed;
  border: 0;
  margin: 0;
  padding: 0;
  background: transparent;
  color: var(--md-sys-color-on-surface);
  z-index: 1001;
}
.dialog::backdrop {
  /* Defer to .modal-scrim — using both stacks two scrims (one
   * native, one ours) which double-darkens. Native ::backdrop
   * also can't be opacity-transitioned in step with our scrim,
   * so we make it transparent and let .modal-scrim do the work. */
  background-color: transparent;
  opacity: 0;
}

/* --- Variant: basic --- */
.dialog--basic {
  inset: 50% auto auto 50%;
  transform: translate(-50%, -50%);

  width: min(100% - (var(--space-lg) * 2), var(--layout-modal-max));
  max-height: calc(100vh - (var(--space-xl) * 2));
  overflow: auto;

  background-color: var(--md-sys-color-surface-container-high);
  border-radius: var(--md-sys-shape-corner-extra-large);
  box-shadow: var(--md-sys-elevation-shadow-level3);
  padding: var(--space-lg);

  display: flex;
  flex-direction: column;
  gap: var(--space-md);

  /* Enter motion — default-spatial spring */
  transition:
    opacity
      var(--md-sys-motion-curve-default-effects-duration)
      var(--md-sys-motion-curve-default-effects),
    transform
      var(--md-sys-motion-curve-default-spatial-duration)
      var(--md-sys-motion-curve-default-spatial);
}

/* Closed state for non-<dialog> hosts */
.dialog--basic:not([open]):not(.is-open) {
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -48%) scale(0.98);
}

.dialog__icon {
  align-self: center;
  color: var(--md-sys-color-secondary);
}
.dialog__icon > svg,
.dialog__icon > .ax-icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
}

.dialog__headline {
  font-family:    var(--md-sys-typescale-headline-small-font);
  font-size:      var(--md-sys-typescale-headline-small-size);
  line-height:    var(--md-sys-typescale-headline-small-line-height);
  font-weight:    var(--md-sys-typescale-headline-small-weight);
  letter-spacing: var(--md-sys-typescale-headline-small-tracking);
  color: var(--md-sys-color-on-surface);
  margin: 0;
}
/* When .dialog__icon is present, headline center-aligns per §17.3 */
.dialog:has(.dialog__icon) .dialog__headline {
  text-align: center;
}

.dialog__supporting {
  font-family:    var(--md-sys-typescale-body-medium-font);
  font-size:      var(--md-sys-typescale-body-medium-size);
  line-height:    var(--md-sys-typescale-body-medium-line-height);
  font-weight:    var(--md-sys-typescale-body-medium-weight);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
  color: var(--md-sys-color-on-surface-variant);
}

.dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-top: var(--space-md);
}

/* --- Variant: full-screen --- */
.dialog--full-screen {
  /* position: fixed + inset: 0 fills the viewport without invoking
   * 100vw / 100vh, which include scrollbar gutter and cause horizontal
   * overflow on Windows / GTK. inset: 0 sizes the box to the visible
   * viewport edges directly. */
  inset: 0;
  max-width: none;
  max-height: none;
  border-radius: 0;
  background-color: var(--md-sys-color-surface);
  box-shadow: var(--md-sys-elevation-shadow-level0);

  display: flex;
  flex-direction: column;
  padding: 0;
}
.dialog--full-screen .dialog__app-bar {
  flex: 0 0 auto;
}
.dialog--full-screen .dialog__body {
  flex: 1 1 auto;
  overflow: auto;
  padding: var(--space-lg);
}
.dialog--full-screen:not([open]):not(.is-open) {
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
}

/* ============================================================
 * Chunk D — Sheet + Snackbar (final chunk)
 *
 *   §13 Sheet    — M3 §26 (bottom-modal + side-modal)
 *   §14 Snackbar — M3 §28
 *
 * Sheets share .modal-scrim with dialog (§0.12). Out of scope:
 * standard / detached side sheet variants (deferred to v1.5).
 * ============================================================ */

/* ============================================================
 * §13 Sheet — M3-COMPONENT-SPECS §26
 *
 * 2 variants: bottom-modal, side-modal.
 *
 * Markup contract:
 *   <div class="modal-scrim" data-open="true"></div>
 *   <aside class="sheet sheet--bottom-modal is-open">
 *     <div class="sheet__handle" aria-hidden="true"></div>
 *     <header class="sheet__header">…</header>
 *     <div class="sheet__body">…</div>
 *   </aside>
 *
 * Both variants:
 *   surface-container-low, level1, slide-in via default-spatial
 * ============================================================ */
.sheet {
  position: fixed;
  background-color: var(--md-sys-color-surface-container-low);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-shadow-level1);
  z-index: 1001;
  display: flex;
  flex-direction: column;

  transition:
    transform
      var(--md-sys-motion-curve-default-spatial-duration)
      var(--md-sys-motion-curve-default-spatial),
    opacity
      var(--md-sys-motion-curve-default-effects-duration)
      var(--md-sys-motion-curve-default-effects);
}

/* --- Variant: bottom-modal ---
 * Top margin 72px (mobile), 56px (window > 640px).
 * Width: full / max 640px.
 * Corner: extra-large on top edges only. */
.sheet--bottom-modal {
  inset-inline-start: 50%;
  bottom: 0;
  width: min(100%, 640px);
  max-height: calc(100vh - 72px);
  transform: translateX(-50%);

  border-start-start-radius:  var(--md-sys-shape-corner-extra-large);
  border-start-end-radius: var(--md-sys-shape-corner-extra-large);
  padding-top: var(--space-md);
}
@media (min-width: 640px) {
  .sheet--bottom-modal {
    max-height: calc(100vh - 56px);
  }
}

/* Drag handle — 32×4px, on-surface-variant */
.sheet__handle {
  align-self: center;
  width: 32px;
  height: 4px;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-on-surface-variant);
  opacity: 0.4;
  margin-bottom: var(--space-md);
  flex-shrink: 0;
}

.sheet__header {
  padding: 0 var(--space-lg) var(--space-md);
  flex-shrink: 0;
}
.sheet__title {
  font-family:    var(--md-sys-typescale-headline-small-font);
  font-size:      var(--md-sys-typescale-headline-small-size);
  line-height:    var(--md-sys-typescale-headline-small-line-height);
  font-weight:    var(--md-sys-typescale-headline-small-weight);
  letter-spacing: var(--md-sys-typescale-headline-small-tracking);
  margin: 0;
}
.sheet__body {
  flex: 1 1 auto;
  overflow: auto;
  padding: 0 var(--space-lg) var(--space-lg);
}

/* Closed state for bottom-modal */
.sheet--bottom-modal:not(.is-open):not([open]) {
  transform: translate(-50%, 100%);
  opacity: 0;
  pointer-events: none;
}

/* --- Variant: side-modal ---
 * Width 256px, full height, corner-large on the leading edge only. */
.sheet--side-modal {
  top: 0;
  inset-inline-start: 0;
  width: 256px;
  height: 100vh;

  border-start-end-radius: var(--md-sys-shape-corner-large);
  border-end-end-radius:   var(--md-sys-shape-corner-large);
  padding: var(--space-lg) 0;
}
.sheet--side-modal .sheet__header,
.sheet--side-modal .sheet__body {
  padding-inline: var(--space-md);
}

.sheet--side-modal:not(.is-open):not([open]) {
  transform: translateX(-100%);
  opacity: 0;
  pointer-events: none;
}

/* RTL: side-modal slides in from the right */
[dir="rtl"] .sheet--side-modal:not(.is-open):not([open]) {
  transform: translateX(100%);
}

/* ============================================================
 * §14 Snackbar — M3-COMPONENT-SPECS §28
 *
 * Container: inverse-surface bg, inverse-on-surface text,
 *   level3, corner-extra-small, body-medium.
 * Single-line: 48px. Two-line: 68px (.snackbar--two-line).
 * Action: inverse-primary, label-large, hover state layer.
 * Close icon (optional): inverse-on-surface, 24px.
 *
 * Note: positioning + queue management live in prototype JS.
 * This stylesheet defines visual chrome only.
 * ============================================================ */
.snackbar {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);

  min-height: 48px;
  max-width: 600px;
  padding: var(--space-sm) var(--space-md);

  background-color: var(--md-sys-color-inverse-surface);
  color: var(--md-sys-color-inverse-on-surface);
  border-radius: var(--md-sys-shape-corner-extra-small);
  box-shadow: var(--md-sys-elevation-shadow-level3);

  font-family:    var(--md-sys-typescale-body-medium-font);
  font-size:      var(--md-sys-typescale-body-medium-size);
  line-height:    var(--md-sys-typescale-body-medium-line-height);
  font-weight:    var(--md-sys-typescale-body-medium-weight);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}

.snackbar--two-line {
  min-height: 68px;
  align-items: flex-start;
  padding-block: var(--space-md);
}

.snackbar__label {
  flex: 1 1 auto;
  min-width: 0;
}

/* Action button — flat, on inverse surface */
.snackbar__action {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;

  height: 36px;
  padding-inline: var(--space-sm);
  border: 0;
  background: transparent;
  color: var(--md-sys-color-inverse-primary);
  border-radius: var(--md-sys-shape-corner-extra-small);

  font-family:    var(--md-sys-typescale-label-large-font);
  font-size:      var(--md-sys-typescale-label-large-size);
  line-height:    var(--md-sys-typescale-label-large-line-height);
  font-weight:    var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);

  cursor: pointer;
  isolation: isolate;
  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}
/* State layer Pattern A — uses currentColor (inverse-primary) */
.snackbar__action::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}
.snackbar__action:hover::before {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}
.snackbar__action:focus-visible::before {
  opacity: var(--md-sys-state-focus-state-layer-opacity);
}
.snackbar__action:active::before {
  opacity: var(--md-sys-state-pressed-state-layer-opacity);
}
.snackbar__action:focus-visible {
  outline: 2px solid var(--md-sys-color-inverse-primary);
  outline-offset: 2px;
}

/* Close icon button — flat, inverse-on-surface */
.snackbar__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border: 0;
  background: transparent;
  color: var(--md-sys-color-inverse-on-surface);
  cursor: pointer;
}
.snackbar__close > svg,
.snackbar__close > .ax-icon {
  width: var(--comp-icon-size-md);
  height: var(--comp-icon-size-md);
}


/* ============================================================
 * Chunk E1 — FAB + Extended FAB
 *
 *   §15 FAB           — M3-COMPONENT-SPECS §7
 *   §16 Extended FAB  — M3-COMPONENT-SPECS §8
 *
 * Both are bucket D (template-level placement, but the component
 * itself is just an inline-block element — page-level positioning
 * is the author's job).
 *
 * Color styles (6 visually equivalent, M3 §7.3):
 *   Tonal primary (default) — primary-container / on-primary-container
 *   Tonal secondary, tertiary
 *   Primary, secondary, tertiary (high-emphasis)
 *
 * State layer pattern: scoped ::before, currentColor + opacity tokens
 * (Pattern A — see §0 Foundation).
 * ============================================================ */

/* ============================================================
 * §15 FAB — M3-COMPONENT-SPECS §7
 *
 *   Variants: default (.ax-fab), .is-medium, .is-large
 *   Color styles: 6 (.is-tonal-* default + .is-primary, .is-secondary, .is-tertiary)
 *   Icon-only (no label) — for label use Extended FAB §16
 * ============================================================ */
.ax-fab {
  /* Default = tonal primary (M3 §7.3 default style) */
  --_fab-bg: var(--md-sys-color-primary-container);
  --_fab-fg: var(--md-sys-color-on-primary-container);

  /* Default = small/standard FAB (M3 §7.2) */
  --_fab-size: 56px;
  --_fab-icon: var(--comp-icon-size-md); /* 24px */
  --_fab-radius: var(--md-sys-shape-corner-large); /* 16px */

  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: var(--_fab-size);
  height: var(--_fab-size);
  padding: 0;

  background-color: var(--_fab-bg);
  color: var(--_fab-fg);
  border: 0;
  border-radius: var(--_fab-radius);

  box-shadow: var(--md-sys-elevation-shadow-level3);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* State layer (Pattern A — currentColor + opacity tokens) */
.ax-fab::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}

.ax-fab:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.ax-fab:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.ax-fab:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }

/* Elevation states (M3 §7.4) */
.ax-fab:hover  { box-shadow: var(--md-sys-elevation-shadow-level4); }
.ax-fab:active,
.ax-fab:focus-visible { box-shadow: var(--md-sys-elevation-shadow-level3); }

/* Focus indicator (§0.11 outer +2px) */
.ax-fab:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* Icon (24px default, scales with variant) */
.ax-fab > svg,
.ax-fab > .ax-icon {
  width: var(--_fab-icon);
  height: var(--_fab-icon);
  fill: currentColor;
  pointer-events: none;
}

/* --- Sizes (M3 §7.2) --- */
.ax-fab.is-medium {
  --_fab-size: 80px;
  --_fab-icon: 28px; /* M3 §7.2 medium FAB icon */
  --_fab-radius: var(--md-sys-shape-corner-large-increased); /* 20px */
}
.ax-fab.is-large {
  --_fab-size: 96px;
  --_fab-icon: 36px; /* M3 §7.2 large FAB icon */
  --_fab-radius: var(--md-sys-shape-corner-extra-large); /* 28px */
}

/* --- Color styles (M3 §7.3, 6 styles) --- */
.ax-fab.is-tonal-secondary {
  --_fab-bg: var(--md-sys-color-secondary-container);
  --_fab-fg: var(--md-sys-color-on-secondary-container);
}
.ax-fab.is-tonal-tertiary {
  --_fab-bg: var(--md-sys-color-tertiary-container);
  --_fab-fg: var(--md-sys-color-on-tertiary-container);
}
.ax-fab.is-primary {
  --_fab-bg: var(--md-sys-color-primary);
  --_fab-fg: var(--md-sys-color-on-primary);
}
.ax-fab.is-secondary {
  --_fab-bg: var(--md-sys-color-secondary);
  --_fab-fg: var(--md-sys-color-on-secondary);
}
.ax-fab.is-tertiary {
  --_fab-bg: var(--md-sys-color-tertiary);
  --_fab-fg: var(--md-sys-color-on-tertiary);
}

/* --- Disabled (Pattern A: container fade) --- */
.ax-fab:disabled,
.ax-fab[aria-disabled="true"] {
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 10%,
    transparent
  );
  color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 38%,
    transparent
  );
  box-shadow: var(--md-sys-elevation-shadow-level0);
  cursor: not-allowed;
  pointer-events: none;
}
.ax-fab:disabled::before,
.ax-fab[aria-disabled="true"]::before {
  display: none;
}


/* ============================================================
 * §16 Extended FAB — M3-COMPONENT-SPECS §8
 *
 *   FAB with text label + leading icon. Stretches to content.
 *   Only "small" size in v0.2.5 (M3 §8.2 — small is default).
 *   Color styles: same 6 as FAB.
 * ============================================================ */
.ax-fab-extended {
  /* Default = tonal primary */
  --_fab-bg: var(--md-sys-color-primary-container);
  --_fab-fg: var(--md-sys-color-on-primary-container);

  --_fab-icon: var(--comp-icon-size-md); /* 24px */
  --_fab-radius: var(--md-sys-shape-corner-large); /* 16px */

  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm); /* 8px between icon and label */

  height: 56px;          /* M3 §8.2 container height */
  min-width: 80px;       /* M3 §8.2 min width */
  padding: 0 var(--space-md); /* 16px L/R per M3 §8.2 */

  background-color: var(--_fab-bg);
  color: var(--_fab-fg);
  border: 0;
  border-radius: var(--_fab-radius);

  /* Typography (M3 §8.2 — title-medium) */
  font-family: var(--md-sys-typescale-title-medium-font);
  font-size: var(--md-sys-typescale-title-medium-size);
  line-height: var(--md-sys-typescale-title-medium-line-height);
  font-weight: var(--md-sys-typescale-title-medium-weight);
  letter-spacing: var(--md-sys-typescale-title-medium-tracking);

  box-shadow: var(--md-sys-elevation-shadow-level3);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* State layer */
.ax-fab-extended::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
.ax-fab-extended:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.ax-fab-extended:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.ax-fab-extended:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }

/* Elevation (same as FAB) */
.ax-fab-extended:hover  { box-shadow: var(--md-sys-elevation-shadow-level4); }
.ax-fab-extended:active,
.ax-fab-extended:focus-visible { box-shadow: var(--md-sys-elevation-shadow-level3); }

/* Focus indicator */
.ax-fab-extended:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* Icon */
.ax-fab-extended__icon,
.ax-fab-extended > svg:first-child,
.ax-fab-extended > .ax-icon:first-child {
  width: var(--_fab-icon);
  height: var(--_fab-icon);
  fill: currentColor;
  flex-shrink: 0;
  pointer-events: none;
}

/* Label slot — keeps text from shrinking awkwardly */
.ax-fab-extended__label {
  display: inline-block;
  white-space: nowrap;
}

/* --- Color styles (same 6 as FAB) --- */
.ax-fab-extended.is-tonal-secondary {
  --_fab-bg: var(--md-sys-color-secondary-container);
  --_fab-fg: var(--md-sys-color-on-secondary-container);
}
.ax-fab-extended.is-tonal-tertiary {
  --_fab-bg: var(--md-sys-color-tertiary-container);
  --_fab-fg: var(--md-sys-color-on-tertiary-container);
}
.ax-fab-extended.is-primary {
  --_fab-bg: var(--md-sys-color-primary);
  --_fab-fg: var(--md-sys-color-on-primary);
}
.ax-fab-extended.is-secondary {
  --_fab-bg: var(--md-sys-color-secondary);
  --_fab-fg: var(--md-sys-color-on-secondary);
}
.ax-fab-extended.is-tertiary {
  --_fab-bg: var(--md-sys-color-tertiary);
  --_fab-fg: var(--md-sys-color-on-tertiary);
}

/* Disabled (Pattern A) */
.ax-fab-extended:disabled,
.ax-fab-extended[aria-disabled="true"] {
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 10%,
    transparent
  );
  color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface) 38%,
    transparent
  );
  box-shadow: var(--md-sys-elevation-shadow-level0);
  cursor: not-allowed;
  pointer-events: none;
}
.ax-fab-extended:disabled::before,
.ax-fab-extended[aria-disabled="true"]::before {
  display: none;
}


/* ============================================================
 * Chunk E2 — Navigation bar + Badge
 *
 *   §17 Nav bar      — M3-COMPONENT-SPECS §23 (mobile bottom nav)
 *   §18 Badge        — M3-COMPONENT-SPECS §2
 *
 * Both paired because nav bar items typically host badges.
 * Active indicator pattern shared with nav rail (§0.13 secondary-container).
 * ============================================================ */

/* ============================================================
 * §17 Nav bar — M3-COMPONENT-SPECS §23
 *
 *   Mobile bottom nav (768px and below typically). 64px height.
 *   Container: surface-container + level2.
 *   Item active indicator: 56×32 pill (vertical layout, default).
 *   Per §0.13 — secondary-container fill + on-secondary-container glyph.
 * ============================================================ */
.nav-bar {
  display: flex;
  align-items: stretch;
  justify-content: space-around;

  width: 100%;
  height: 64px; /* M3 §23.2 */
  padding: 0;

  background-color: var(--md-sys-color-surface-container);
  box-shadow: var(--md-sys-elevation-shadow-level2);

  /* Mobile-friendly: respect safe area on iOS */
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.nav-bar__item {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;

  flex: 1 1 0;
  min-width: 0;

  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);

  font-family: var(--md-sys-typescale-label-medium-font);
  font-size: var(--md-sys-typescale-label-medium-size);
  line-height: var(--md-sys-typescale-label-medium-line-height);
  font-weight: var(--md-sys-typescale-label-medium-weight);
  letter-spacing: var(--md-sys-typescale-label-medium-tracking);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  /* Decoration reset for <a> usage */
  text-decoration: none;
}

/* Icon wrapper — hosts the active indicator (pill behind icon) */
.nav-bar__icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: 56px;
  height: 32px; /* M3 §23.4 active indicator size */
  margin-bottom: 4px; /* M3 §23.4 indicator ↔ label space */

  border-radius: var(--md-sys-shape-corner-full);
  transition: background-color
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}

.nav-bar__icon > svg,
.nav-bar__icon > .ax-icon {
  width: var(--comp-icon-size-md); /* 24px M3 §23.2 */
  height: var(--comp-icon-size-md);
  fill: currentColor;
  pointer-events: none;
  position: relative;
  z-index: 1;
}
/* v3.4.4: Material Symbols glyph variant. The base .material-symbols-rounded
   class (icons.css §1) already provides font-size: 24px and
   pointer-events: none. This rule re-establishes the z-index stacking that
   lifts the glyph above the active-state pill indicator. */
.nav-bar__icon > .material-symbols-rounded {
  font-size: var(--comp-icon-size-md);
  position: relative;
  z-index: 1;
}

/* Label */
.nav-bar__label {
  display: block;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* State layer — applied on the icon (the active indicator area) */
.nav-bar__item::before {
  content: "";
  position: absolute;
  /* Sized to match the icon pill, not the whole item — closer to M3 reality */
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, calc(-50% - 10px)); /* offset to icon center, label below */
  width: 56px;
  height: 32px;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: 0;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}

.nav-bar__item:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.nav-bar__item:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.nav-bar__item:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }

/* Focus indicator (§0.11 — outer +2px, but for tightly packed nav use inner -3px) */
.nav-bar__item:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: -3px;
}

/* --- Active state (§0.13) --- */
.nav-bar__item.is-active,
.nav-bar__item[aria-current="page"],
.nav-bar__item[aria-selected="true"] {
  color: var(--md-sys-color-on-secondary-container);
}
.nav-bar__item.is-active .nav-bar__icon,
.nav-bar__item[aria-current="page"] .nav-bar__icon,
.nav-bar__item[aria-selected="true"] .nav-bar__icon {
  background-color: var(--md-sys-color-secondary-container);
}
.nav-bar__item.is-active .nav-bar__label,
.nav-bar__item[aria-current="page"] .nav-bar__label,
.nav-bar__item[aria-selected="true"] .nav-bar__label {
  color: var(--md-sys-color-on-surface);
  font-weight: 600; /* slightly bumped for active emphasis */
}

/* --- Disabled (Pattern A — fade) --- */
.nav-bar__item:disabled,
.nav-bar__item[aria-disabled="true"] {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  cursor: not-allowed;
  pointer-events: none;
}


/* ============================================================
 * §18 Badge — M3-COMPONENT-SPECS §2
 *
 *   2 variants: small (dot, 6×6) / large (numeric, 16×16+ to 16×34).
 *   Color: error container + on-error glyph.
 *   Position: anchor top-trailing of icon corner (badge center on corner).
 *   Hosts: nav bar items, nav rail items, icon buttons, menu items, tabs.
 *
 *   Implementation note: badge sits in absolutely-positioned slot.
 *   For label-bearing hosts (nav-bar item, nav-rail item) the badge MUST be
 *   placed inside the icon wrapper (e.g. .nav-bar__icon.has-badge), not on
 *   the outer button — otherwise 50% reference falls between icon and label.
 *
 *   Width-aware positioning:
 *     - top/inset-inline-end pin badge's box edge to (icon-corner - badge-half).
 *     - transform: translate(50%, -50%) then pushes the badge half its size
 *       outward, so its CENTER lands exactly on the icon corner.
 *     - This is width-agnostic — "3", "12", "99+" all anchor cleanly because
 *       browser computes badge's actual width at layout time.
 * ============================================================ */
.ax-badge {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  background-color: var(--md-sys-color-error);
  color: var(--md-sys-color-on-error);

  pointer-events: none;
  z-index: 2;

  /* Default = small dot (M3 §2.3, 6×6) */
  width: 6px;
  height: 6px;
  border-radius: 3px; /* M3 spec: small badge = 3dp corner radius */

  /* Anchor = icon top-right corner. Translate then snaps badge CENTER there. */
  --_badge-icon-half: calc(var(--comp-icon-size-md, 24px) / 2);

  top: calc(50% - var(--_badge-icon-half));
  inset-inline-end: calc(50% - var(--_badge-icon-half));
  transform: translate(50%, -50%);
}

/* Dot variant — kill any default font-driven sizing artifacts */
.ax-badge:empty {
  font-size: 0;
  line-height: 0;
}

/* Large variant — numeric label.
   Position calc inherited from .ax-badge above; no override needed —
   transform: translate(50%, -50%) handles any label width automatically. */
.ax-badge.is-large {
  width: auto;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px; /* M3 spec: large badge = 8dp corner radius */

  font-family: var(--md-sys-typescale-label-small-font);
  font-size: var(--md-sys-typescale-label-small-size);
  line-height: var(--md-sys-typescale-label-small-line-height);
  font-weight: var(--md-sys-typescale-label-small-weight);
  letter-spacing: var(--md-sys-typescale-label-small-tracking);

  white-space: nowrap;
}

/* Inline (non-positioned) badge — for manual placement (e.g. inside text).
   vertical-align: middle keeps the badge centered with surrounding x-height
   text instead of sitting on the baseline (which leaves the badge sitting
   slightly low next to the cap). */
.ax-badge.is-inline {
  position: relative;
  top: auto;
  inset-inline-end: auto;
  transform: none;
  vertical-align: middle;
  /* Tiny optical adjustment so the visual center matches text x-height,
     not the bounding-box geometric center. */
  margin-block-start: -0.1em;
}

/* Host helper — apply to whichever element should be the badge's positioning
   parent. For label-bearing components, this is the icon wrapper, not the
   outer button. See nav-bar example: span.nav-bar__icon.has-badge wraps the
   svg and the badge together. */
.has-badge {
  position: relative;
  overflow: visible;
}


/* ============================================================
 * Chunk E3 — Menu
 *
 *   §19 Menu — M3-COMPONENT-SPECS §22
 *
 * Scope (v0.2.5):
 *   - Vertical orientation (Standard color) only
 *   - Item slots: leading icon, label, supporting text, trailing supporting,
 *     trailing icon
 *   - Section divider + section label
 *   - Selected state (tertiary-container fill)
 *
 * Deferred to v1.5+:
 *   - Horizontal orientation (M3 §22.6)
 *   - Vibrant color mode (M3 §22.8)
 *   - Position-aware item corners (first/last child outer corners — §22.5
 *     patterns 2+3). Current build uses uniform corner-extra-small for items.
 *   - Submenu (active item with open submenu)
 *
 * Positioning: this stylesheet defines visual only. JS-driven anchor
 * positioning to a trigger (icon button etc.) is the author's job — typical
 * approach: position: absolute on .ax-menu, parent has position: relative,
 * top/inset-inline-start computed from trigger bounds.
 * ============================================================ */
.ax-menu {
  display: flex;
  flex-direction: column;
  gap: 2px; /* M3 §22.2 between items */

  min-width: 112px; /* sensible floor — M3 doesn't pin a value, but typical menus need this */
  max-width: 280px; /* sensible ceiling for one-line items */
  padding: 8px 0; /* M3 §22.3 top/bottom space */

  background-color: var(--md-sys-color-surface-container-low);
  border-radius: var(--md-sys-shape-corner-large); /* 16px M3 §22.2 vertical */
  box-shadow: var(--md-sys-elevation-shadow-level2);

  /* Hide overflow so corners clip cleanly when item shapes flush to edges */
  overflow: hidden;

  /* Hidden by default — author shows via JS toggling .is-open */
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    visibility 0s linear var(--md-sys-motion-curve-fast-effects-duration);
}

.ax-menu.is-open {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    visibility 0s linear 0s;
}

/* Section label (small heading inside menu) */
.ax-menu__section-label {
  display: block;
  padding: 8px 16px 4px;

  color: var(--md-sys-color-on-surface-variant);

  font-family: var(--md-sys-typescale-label-medium-font);
  font-size: var(--md-sys-typescale-label-medium-size);
  line-height: var(--md-sys-typescale-label-medium-line-height);
  font-weight: var(--md-sys-typescale-label-medium-weight);
  letter-spacing: var(--md-sys-typescale-label-medium-tracking);

  /* Cosmetic — no state, no interaction */
  user-select: none;
}

/* Section divider — between groups */
.ax-menu__divider {
  height: 1px;
  margin: 4px 0;
  background-color: var(--md-sys-color-outline-variant);
  border: 0;
}

/* Item — base */
.ax-menu__item {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  gap: 12px; /* M3 §22.3 between space (icon ↔ label) */

  width: 100%;
  min-height: 44px; /* M3 §22.3 */
  padding: 0 16px; /* M3 §22.3 leading/trailing space */
  border: 0;
  border-radius: var(--md-sys-shape-corner-extra-small); /* M3 §22.5 base */
  background: transparent;
  color: var(--md-sys-color-on-surface);

  font-family: var(--md-sys-typescale-label-large-font);
  font-size: var(--md-sys-typescale-label-large-size);
  line-height: var(--md-sys-typescale-label-large-line-height);
  font-weight: var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);

  text-align: start;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    border-radius
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial);
}

/* Reset for <a> usage */
.ax-menu__item:where(a) {
  text-decoration: none;
}

/* State layer (Pattern A — currentColor + opacity) */
.ax-menu__item::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
.ax-menu__item:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.ax-menu__item:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.ax-menu__item:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }

/* Focus indicator — inner -3px (§0.11 dense layout) */
.ax-menu__item:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: -3px;
}

/* Item slots */
.ax-menu__item-leading,
.ax-menu__item-trailing {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 20px;  /* M3 §22.3 leading/trailing icon size */
  height: 20px;
  color: var(--md-sys-color-on-surface-variant);
}
.ax-menu__item-leading > svg,
.ax-menu__item-leading > .ax-icon,
.ax-menu__item-trailing > svg,
.ax-menu__item-trailing > .ax-icon {
  width: 20px;
  height: 20px;
  fill: currentColor;
  pointer-events: none;
}

/* Label fills available space; supporting text wraps below if needed */
.ax-menu__item-label {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}
.ax-menu__item-label > .ax-menu__item-supporting {
  color: var(--md-sys-color-on-surface-variant);
  font-family: var(--md-sys-typescale-body-small-font);
  font-size: var(--md-sys-typescale-body-small-size);
  line-height: var(--md-sys-typescale-body-small-line-height);
  font-weight: var(--md-sys-typescale-body-small-weight);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
}

/* Trailing supporting text (e.g. keyboard shortcut "⌘K") */
.ax-menu__item-trailing-text {
  flex-shrink: 0;
  color: var(--md-sys-color-on-surface-variant);
  font-family: var(--md-sys-typescale-label-large-font);
  font-size: var(--md-sys-typescale-label-large-size);
  line-height: var(--md-sys-typescale-label-large-line-height);
  font-weight: var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
}

/* Selected state (M3 §22.7) */
.ax-menu__item.is-selected,
.ax-menu__item[aria-selected="true"] {
  background-color: var(--md-sys-color-tertiary-container);
  color: var(--md-sys-color-on-tertiary-container);
  border-radius: var(--md-sys-shape-corner-medium); /* §22.5 selected */
}
.ax-menu__item.is-selected .ax-menu__item-leading,
.ax-menu__item.is-selected .ax-menu__item-trailing,
.ax-menu__item[aria-selected="true"] .ax-menu__item-leading,
.ax-menu__item[aria-selected="true"] .ax-menu__item-trailing {
  color: var(--md-sys-color-on-tertiary-container);
}

/* Disabled (Pattern A — fade) */
.ax-menu__item:disabled,
.ax-menu__item[aria-disabled="true"] {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  cursor: not-allowed;
  pointer-events: none;
}
.ax-menu__item:disabled .ax-menu__item-leading,
.ax-menu__item:disabled .ax-menu__item-trailing,
.ax-menu__item[aria-disabled="true"] .ax-menu__item-leading,
.ax-menu__item[aria-disabled="true"] .ax-menu__item-trailing {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}


/* ============================================================
 * Chunk E4 — Loading indicator + Tooltip
 *
 *   §20 Loading indicator — M3-COMPONENT-SPECS §20
 *   §21 Tooltip           — M3-COMPONENT-SPECS §34
 *
 * Both bucket F (theme JS only — no WP block analog). Static visual
 * defined here; show/hide and positioning is the author's job.
 * ============================================================ */

/* ============================================================
 * §20 Loading indicator — M3-COMPONENT-SPECS §20
 *
 *   Replaces indeterminate circular progress for short waits / pull-to-refresh.
 *   2 variants: default (uncontained) / contained (filled container).
 *
 *   Implementation: SVG circle with stroke-dashoffset animation.
 *   M3 Expressive uses a "morph" animation where the active arc bounces
 *   between varying lengths — current build uses a simpler rotating arc
 *   with growing/shrinking stroke-dasharray (closer to legacy spinner).
 *   True morph animation deferred to v1.5+ (would require requestAnimationFrame
 *   or more complex SVG keyframes).
 * ============================================================ */
.ax-loading {
  display: inline-block;
  flex-shrink: 0;

  /* Size: M3 §20.2 — active indicator 48×48 */
  width: 48px;
  height: 48px;

  color: var(--md-sys-color-primary);
}

.ax-loading__svg {
  display: block;
  width: 100%;
  height: 100%;
  animation: ax-loading-rotate 1.6s linear infinite;
}

.ax-loading__circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 4; /* M3 §21.4 active indicator thickness */
  stroke-linecap: round;
  stroke-dasharray: 100, 200;
  stroke-dashoffset: 0;
  transform-origin: center;
  animation: ax-loading-dash 1.4s ease-in-out infinite;
}

@keyframes ax-loading-rotate {
  to { transform: rotate(360deg); }
}

@keyframes ax-loading-dash {
  0% {
    stroke-dasharray: 1, 200;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 90, 200;
    stroke-dashoffset: -35;
  }
  100% {
    stroke-dasharray: 90, 200;
    stroke-dashoffset: -125;
  }
}

/* Reduce-motion fallback — replace animation with low-motion pulse */
@media (prefers-reduced-motion: reduce) {
  .ax-loading__svg {
    animation: none;
  }
  .ax-loading__circle {
    stroke-dasharray: 60, 200;
    animation: ax-loading-pulse 2s ease-in-out infinite;
  }
  @keyframes ax-loading-pulse {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 1; }
  }
}

/* Contained variant (M3 §20.1) */
.ax-loading.is-contained {
  /* Container 38×48 per M3 §20.2 — wider than tall */
  width: 38px;
  height: 48px;

  display: inline-flex;
  align-items: center;
  justify-content: center;

  background-color: var(--md-sys-color-primary-container);
  border-radius: var(--md-sys-shape-corner-full);

  color: var(--md-sys-color-on-primary-container);
}
.ax-loading.is-contained .ax-loading__svg {
  width: 24px;
  height: 24px;
}

/* Small variant — for inline use (e.g. inside a button) */
.ax-loading.is-small {
  width: 24px;
  height: 24px;
}
.ax-loading.is-small .ax-loading__circle {
  stroke-width: 3;
}


/* ============================================================
 * §21 Tooltip — M3-COMPONENT-SPECS §34
 *
 *   2 variants: plain (short label, no interaction) / rich (subhead + body
 *   + buttons, dismissible).
 *
 *   Positioning: author's job. Typical pattern is data attribute
 *   `data-tooltip="..."` + JS that creates/positions a `.ax-tooltip` near
 *   the trigger. This stylesheet defines visual only — show via `.is-open`.
 *
 *   Plain — inverse-surface ground (high contrast, brief). Rich — surface
 *   ground (more readable for longer content with actions).
 * ============================================================ */
.ax-tooltip {
  position: absolute;
  z-index: 1100; /* above modals, snackbar level */
  pointer-events: none;

  /* Visibility (author toggles .is-open) */
  visibility: hidden;
  opacity: 0;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    visibility 0s linear var(--md-sys-motion-curve-fast-effects-duration);
}
.ax-tooltip.is-open {
  visibility: visible;
  opacity: 1;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    visibility 0s linear 0s;
}

/* --- Plain variant (M3 §34.2) --- */
.ax-tooltip.is-plain {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 4px 8px; /* tighter than rich; M3 §34.2 padding 8px (used for L/R) */

  background-color: var(--md-sys-color-inverse-surface);
  color: var(--md-sys-color-inverse-on-surface);
  border-radius: var(--md-sys-shape-corner-extra-small);

  font-family: var(--md-sys-typescale-body-small-font);
  font-size: var(--md-sys-typescale-body-small-size);
  line-height: var(--md-sys-typescale-body-small-line-height);
  font-weight: var(--md-sys-typescale-body-small-weight);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);

  white-space: nowrap;
  max-width: 240px;
}

/* --- Rich variant (M3 §34.3) --- */
.ax-tooltip.is-rich {
  display: flex;
  flex-direction: column;
  gap: 4px;

  min-width: 200px;
  max-width: 320px;
  padding: 12px 16px 8px; /* M3 §34.3 — top 12, sides 16, bottom 8 */

  background-color: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  box-shadow: var(--md-sys-elevation-shadow-level2);

  /* Rich tooltips are dismissible — re-enable pointer events */
  pointer-events: auto;
}

/* Rich subhead */
.ax-tooltip__subhead {
  color: var(--md-sys-color-on-surface-variant);

  font-family: var(--md-sys-typescale-title-small-font);
  font-size: var(--md-sys-typescale-title-small-size);
  line-height: var(--md-sys-typescale-title-small-line-height);
  font-weight: var(--md-sys-typescale-title-small-weight);
  letter-spacing: var(--md-sys-typescale-title-small-tracking);
}

/* Rich supporting text */
.ax-tooltip__supporting {
  color: var(--md-sys-color-on-surface-variant);

  font-family: var(--md-sys-typescale-body-medium-font);
  font-size: var(--md-sys-typescale-body-medium-size);
  line-height: var(--md-sys-typescale-body-medium-line-height);
  font-weight: var(--md-sys-typescale-body-medium-weight);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}

/* Rich actions row */
.ax-tooltip__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}

/* Rich action button — text-button-like, primary color */
.ax-tooltip__action {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--md-sys-shape-corner-full);
  background: transparent;
  color: var(--md-sys-color-primary);

  font-family: var(--md-sys-typescale-label-large-font);
  font-size: var(--md-sys-typescale-label-large-size);
  line-height: var(--md-sys-typescale-label-large-line-height);
  font-weight: var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* State layer (Pattern A) on rich action buttons */
.ax-tooltip__action::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
.ax-tooltip__action:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.ax-tooltip__action:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.ax-tooltip__action:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }

.ax-tooltip__action:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}


/* ============================================================
 * Chunk F1 — Checkbox + Radio (Tier 2 / form externals)
 *
 *   §22 Checkbox      — M3-COMPONENT-SPECS §13
 *   §23 Radio button  — M3-COMPONENT-SPECS §25
 *
 * Bucket E (form plugin styling target). System defines APPEARANCE only
 * here. In Phase 2, form plugins (CF7, WPForms, Gravity, etc.) emit their
 * own native <input type="checkbox/radio"> markup; theme JS adds the
 * Axismundi classes to those outputs so styling matches.
 *
 * Approach: hide the native input visually, render a custom visual via
 * a sibling/child .ax-{check,radio}__visual span. Native input remains
 * for accessibility (focus ring, screen readers, form submission).
 * ============================================================ */

/* ============================================================
 * §22 Checkbox — M3-COMPONENT-SPECS §13
 *
 *   Visual: 18×18 box, 2px corner, 2px outline.
 *   Selected: primary fill + on-primary check icon.
 *   State layer: 40×40 circle around visual (visible on hover/focus/pressed).
 *
 *   Note on state colors (M3 §13.3 — pressed quirk):
 *     Unselected hover/focus = on-surface
 *     Unselected PRESSED     = primary  (preview of selected)
 *     Selected hover/focus   = primary
 *     Selected PRESSED       = on-surface (preview of unselected)
 *
 *   Disabled (Pattern C — outline ↓ + container ↓, icon stays):
 *     Unselected outline = on-surface 38%
 *     Selected container = on-surface 38%
 *     Selected icon      = surface (constant)
 * ============================================================ */
.ax-checkbox {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* Hide native input visually but keep it accessible */
.ax-checkbox__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1; /* above visual so clicks register on the bigger area */
}

/* Visual container — also hosts the state layer + check icon */
.ax-checkbox__visual {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: 18px; /* M3 §13.1 container size */
  height: 18px;
  border-radius: 2px; /* M3 §13.1 sub-xs literal */
  border: 2px solid var(--md-sys-color-on-surface-variant); /* M3 §13.2 unselected outline */
  background-color: transparent;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    border-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* State layer — 40×40 circle, behind visual, currentColor-driven */
.ax-checkbox__visual::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  width: 40px; /* M3 §13.1 state layer size */
  height: 40px;
  border-radius: 50%;
  background-color: var(--md-sys-color-on-surface);
  opacity: 0;
  pointer-events: none;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Check icon — hidden by default, shown when checked */
.ax-checkbox__check {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--md-sys-color-on-primary);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}

/* Hover / focus / pressed — state layer becomes visible.
   :hover and :focus-visible drive on-surface color (unselected).
   :active uses primary (M3 pressed quirk). */
.ax-checkbox__input:hover ~ .ax-checkbox__visual::before {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}
.ax-checkbox__input:focus-visible ~ .ax-checkbox__visual::before {
  opacity: var(--md-sys-state-focus-state-layer-opacity);
}
.ax-checkbox__input:active ~ .ax-checkbox__visual::before {
  opacity: var(--md-sys-state-pressed-state-layer-opacity);
  background-color: var(--md-sys-color-primary); /* unselected pressed = primary */
}

/* Focus indicator — on the visual itself (not the hidden input) */
.ax-checkbox__input:focus-visible ~ .ax-checkbox__visual {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* --- Selected state (input:checked) --- */
.ax-checkbox__input:checked ~ .ax-checkbox__visual {
  background-color: var(--md-sys-color-primary);
  border-color: var(--md-sys-color-primary);
}
.ax-checkbox__input:checked ~ .ax-checkbox__visual .ax-checkbox__check {
  opacity: 1;
}
/* Selected hover/focus → primary state layer; pressed → on-surface */
.ax-checkbox__input:checked:hover ~ .ax-checkbox__visual::before,
.ax-checkbox__input:checked:focus-visible ~ .ax-checkbox__visual::before {
  background-color: var(--md-sys-color-primary);
}
.ax-checkbox__input:checked:active ~ .ax-checkbox__visual::before {
  background-color: var(--md-sys-color-on-surface);
}

/* --- Indeterminate state — horizontal bar instead of check --- */
.ax-checkbox__input:indeterminate ~ .ax-checkbox__visual {
  background-color: var(--md-sys-color-primary);
  border-color: var(--md-sys-color-primary);
}
.ax-checkbox__input:indeterminate ~ .ax-checkbox__visual .ax-checkbox__check {
  opacity: 0; /* hide check */
}
.ax-checkbox__input:indeterminate ~ .ax-checkbox__visual::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  width: 10px;
  height: 2px;
  background-color: var(--md-sys-color-on-primary);
  border-radius: 1px;
}

/* --- Error state --- */
.ax-checkbox.is-error .ax-checkbox__visual {
  border-color: var(--md-sys-color-error);
}
.ax-checkbox.is-error .ax-checkbox__input:checked ~ .ax-checkbox__visual {
  background-color: var(--md-sys-color-error);
  border-color: var(--md-sys-color-error);
}
.ax-checkbox.is-error .ax-checkbox__input:checked ~ .ax-checkbox__visual .ax-checkbox__check {
  stroke: var(--md-sys-color-on-error);
}
.ax-checkbox.is-error .ax-checkbox__visual::before {
  background-color: var(--md-sys-color-error);
}

/* --- Disabled (Pattern C) --- */
.ax-checkbox__input:disabled {
  cursor: not-allowed;
}
.ax-checkbox__input:disabled ~ .ax-checkbox__visual {
  border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.ax-checkbox__input:disabled:checked ~ .ax-checkbox__visual,
.ax-checkbox__input:disabled:indeterminate ~ .ax-checkbox__visual {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  border-color: transparent;
}
.ax-checkbox__input:disabled:checked ~ .ax-checkbox__visual .ax-checkbox__check {
  /* Selected disabled icon stays full opacity, color = surface (M3 §13.4) */
  stroke: var(--md-sys-color-surface);
  opacity: 1;
}
.ax-checkbox__input:disabled ~ .ax-checkbox__visual::before {
  display: none; /* no state layer when disabled */
}

/* Adjacent label */
.ax-checkbox__label {
  font-family: var(--md-sys-typescale-body-large-font);
  font-size: var(--md-sys-typescale-body-large-size);
  line-height: var(--md-sys-typescale-body-large-line-height);
  font-weight: var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);
  color: var(--md-sys-color-on-surface);
}
.ax-checkbox__input:disabled ~ .ax-checkbox__label {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}


/* ============================================================
 * §23 Radio button — M3-COMPONENT-SPECS §25
 *
 *   Visual: 20×20 outer ring (2px stroke), 10×10 inner dot (selected).
 *   State layer: 40×40 circle around visual.
 *   Same cross-color pressed quirk as checkbox.
 *   Disabled (Pattern C): icon 38%, label preserved.
 * ============================================================ */
.ax-radio {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.ax-radio__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.ax-radio__visual {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: 20px; /* M3 §25.1 icon size */
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--md-sys-color-on-surface-variant);
  background-color: transparent;

  transition: border-color
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}

/* State layer — 40×40 circle */
.ax-radio__visual::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: var(--md-sys-color-on-surface);
  opacity: 0;
  pointer-events: none;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Inner dot — hidden by default, scales in on selected */
.ax-radio__visual::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  width: 10px; /* selected dot — 10/20 ratio per M3 baseline */
  height: 10px;
  border-radius: 50%;
  background-color: var(--md-sys-color-primary);
  transform: translate(-50%, -50%) scale(0);
  transition: transform
    var(--md-sys-motion-curve-fast-spatial-duration)
    var(--md-sys-motion-curve-fast-spatial);
}

/* Hover/focus/pressed (M3 §25.3) */
.ax-radio__input:hover ~ .ax-radio__visual::before {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}
.ax-radio__input:focus-visible ~ .ax-radio__visual::before {
  opacity: var(--md-sys-state-focus-state-layer-opacity);
}
.ax-radio__input:active ~ .ax-radio__visual::before {
  opacity: var(--md-sys-state-pressed-state-layer-opacity);
  background-color: var(--md-sys-color-primary); /* unselected pressed = primary */
}

.ax-radio__input:focus-visible ~ .ax-radio__visual {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* --- Selected state --- */
.ax-radio__input:checked ~ .ax-radio__visual {
  border-color: var(--md-sys-color-primary);
}
.ax-radio__input:checked ~ .ax-radio__visual::after {
  transform: translate(-50%, -50%) scale(1);
}
.ax-radio__input:checked:hover ~ .ax-radio__visual::before,
.ax-radio__input:checked:focus-visible ~ .ax-radio__visual::before {
  background-color: var(--md-sys-color-primary);
}
.ax-radio__input:checked:active ~ .ax-radio__visual::before {
  background-color: var(--md-sys-color-on-surface);
}

/* --- Disabled (Pattern C — icon 38%, label kept) --- */
.ax-radio__input:disabled {
  cursor: not-allowed;
}
.ax-radio__input:disabled ~ .ax-radio__visual {
  border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.ax-radio__input:disabled:checked ~ .ax-radio__visual::after {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.ax-radio__input:disabled ~ .ax-radio__visual::before {
  display: none;
}

/* Label */
.ax-radio__label {
  font-family: var(--md-sys-typescale-body-large-font);
  font-size: var(--md-sys-typescale-body-large-size);
  line-height: var(--md-sys-typescale-body-large-line-height);
  font-weight: var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);
  color: var(--md-sys-color-on-surface);
}
.ax-radio__input:disabled ~ .ax-radio__label {
  /* Per M3 §25.4 — disabled radio's adjacent label keeps on-surface (full).
     But for accessibility cue we still soften slightly. Author can override. */
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}


/* ============================================================
 * Chunk F2 — Switch + Slider (Tier 2 / form externals)
 *
 *   §24 Switch — M3-COMPONENT-SPECS §30
 *   §25 Slider — M3-COMPONENT-SPECS §27
 *
 * Both bucket E. Switch follows checkbox/radio pattern (hide native input,
 * render custom visual, native input handles a11y + form). Slider is harder
 * because <input type="range"> has vendor-specific pseudo-elements; we
 * style ::-webkit-slider-thumb / ::-moz-range-thumb / etc. directly.
 * ============================================================ */

/* ============================================================
 * §24 Switch — M3-COMPONENT-SPECS §30
 *
 *   Track: 52×32, corner-full, surface-container-highest (unselected) /
 *          primary (selected). 2px outline outline-color when unselected.
 *   Handle: morphs size by state — 16 unselected, 24 selected, 28 pressed.
 *           Selected color = on-primary, unselected = outline.
 *   Icon (optional): 16px inside handle. When icon present, unselected
 *           handle is 24×24 (not 16×16).
 *
 *   v0.2.5 scope: track + handle + state layer. Optional icon variant
 *   deferred — 80% of use cases don't need it, and the icon-on-handle
 *   markup adds complexity.
 *
 *   Disabled (Pattern C):
 *     Track: on-surface 12%
 *     Handle: on-surface 38% (unselected) / surface (selected, full opacity)
 * ============================================================ */
.ax-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.ax-switch__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

/* Track — pill container.
   box-sizing: border-box → outer width = 52, inner = 48.
   Handle positions use inset-inline-start measured from outer leading edge
   (CSS abspos default). Border is transparent (visual via border-color) so
   2px region is part of the visible track surface; handle positions
   factor it in via "leading-pad + border-width" for symmetric breathing. */
.ax-switch__track {
  position: relative;
  flex-shrink: 0;
  display: inline-block;
  box-sizing: border-box;

  width: 52px;  /* M3 §30.1 track width */
  height: 32px; /* M3 §30.1 track height */
  border-radius: var(--md-sys-shape-corner-full);

  background-color: var(--md-sys-color-surface-container-highest);
  border: 2px solid var(--md-sys-color-outline);

  /* Handle is allowed to extend outside on pressed (28×28 > 28 inner-h).
     Visible cue per M3 §0.9 pattern 4 size morph. */
  overflow: visible;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    border-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Handle — slides between left and right via inset-inline-start animation.

   Geometry note (CSS spec):
     box-sizing: border-box on the track means abspos children's containing
     block = padding edge (inside the 2px border), not outer edge.
     So inset-inline-start: 0 puts handle right at the border's inner edge.

     Track padding-box = 48 wide (52 outer - 2*2 border).
     Visual leading pad of 8px from outer = 8 - 2 (border) = 6px from
     padding edge → inset-inline-start: 6px.

   Rest positions:
     Unselected (16×16): leading = 6, rest center x (from padding edge) = 6 + 8 = 14.
     Selected   (24×24): inner trailing pad 2 from padding-box end →
                          leading = 48 - 24 - 2 = 22.
                          rest center x = 22 + 12 = 34.
     Pressed (28×28): grows around rest center → leading = center - 14.
       Unselected pressed: 14 - 14 = 0.
       Selected pressed:   34 - 14 = 20.

   State layer (40×40) center on handle rest center:
     Unselected: 14 - 20 = -6.
     Selected:   34 - 20 = 14.
   */
.ax-switch__track::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;

  width: 16px;
  height: 16px;
  border-radius: 50%;
  background-color: var(--md-sys-color-outline);

  inset-inline-start: 6px;
  transform: translateY(-50%);

  transition:
    inset-inline-start
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    width
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    height
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* State layer — 40×40 circle around handle position */
.ax-switch__track::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: var(--md-sys-color-on-surface);
  opacity: 0;
  pointer-events: none;

  /* Unselected center 14 - state-layer-half 20 = -6 */
  inset-inline-start: -6px;
  transform: translateY(-50%);

  transition:
    inset-inline-start
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* Hover/focus/pressed states */
.ax-switch__input:hover ~ .ax-switch__track::before {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}
.ax-switch__input:focus-visible ~ .ax-switch__track::before {
  opacity: var(--md-sys-state-focus-state-layer-opacity);
}
.ax-switch__input:active ~ .ax-switch__track::before {
  opacity: var(--md-sys-state-pressed-state-layer-opacity);
}

/* Pressed handle morph (size up to 28×28, M3 §30.2).
   Grows around rest center 14 → leading = 0 (sits at padding edge). */
.ax-switch__input:active ~ .ax-switch__track::after {
  width: 28px;
  height: 28px;
  inset-inline-start: 0;
}

/* Focus indicator on the track */
.ax-switch__input:focus-visible ~ .ax-switch__track {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* --- Selected state (input:checked) --- */
.ax-switch__input:checked ~ .ax-switch__track {
  background-color: var(--md-sys-color-primary);
  border-color: var(--md-sys-color-primary);
}
.ax-switch__input:checked ~ .ax-switch__track::after {
  /* Selected handle: 24×24, on-primary, slides to right.
     Padding-box width 48, inner trailing pad 2 → leading = 48 - 24 - 2 = 22. */
  width: 24px;
  height: 24px;
  background-color: var(--md-sys-color-on-primary);
  inset-inline-start: 22px;
}
.ax-switch__input:checked ~ .ax-switch__track::before {
  /* State layer follows. Selected handle rest center = 22 + 12 = 34.
     Leading = 34 - 20 (state-layer-half) = 14. */
  background-color: var(--md-sys-color-primary);
  inset-inline-start: 14px;
}
.ax-switch__input:checked:active ~ .ax-switch__track::after {
  /* Selected pressed: 28×28 grows around rest center 34 → leading = 20. */
  width: 28px;
  height: 28px;
  inset-inline-start: 20px;
}

/* --- Disabled (Pattern C) --- */
.ax-switch__input:disabled {
  cursor: not-allowed;
}
.ax-switch__input:disabled ~ .ax-switch__track {
  background-color: color-mix(in srgb, var(--md-sys-color-surface-container-highest) 88%, transparent);
  border-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
}
.ax-switch__input:disabled ~ .ax-switch__track::after {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.ax-switch__input:disabled:checked ~ .ax-switch__track {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
  border-color: transparent;
}
.ax-switch__input:disabled:checked ~ .ax-switch__track::after {
  /* Selected disabled handle = surface, full opacity (M3 §30.6) */
  background-color: var(--md-sys-color-surface);
}
.ax-switch__input:disabled ~ .ax-switch__track::before {
  display: none;
}

/* Label */
.ax-switch__label {
  font-family: var(--md-sys-typescale-body-large-font);
  font-size: var(--md-sys-typescale-body-large-size);
  line-height: var(--md-sys-typescale-body-large-line-height);
  font-weight: var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);
  color: var(--md-sys-color-on-surface);
}
.ax-switch__input:disabled ~ .ax-switch__label {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}


/* ============================================================
 * §25 Slider — M3-COMPONENT-SPECS §27
 *
 *   v0.2.5 scope: XS (default) horizontal, standard variant only.
 *   Track 16px, handle morph (4px → 2px on press per §0.9 pattern 4).
 *
 *   Deferred to v1.5+:
 *     - S/M/L/XL sizes (§27.3)
 *     - Vertical orientation
 *     - Centered + range variants
 *     - Stop indicators (discrete steps)
 *     - Inset icon, value indicator (tooltip-like)
 *
 *   Implementation: native <input type="range"> + vendor pseudo-elements.
 *   Cross-browser: ::-webkit-slider-{runnable-track,thumb} (Chrome/Safari/Edge)
 *                  ::-moz-range-{track,thumb} (Firefox).
 *
 *   Active fill: requires JS to compute (set background-image: linear-gradient
 *   from 0 to value%). For pure CSS demo, we use accent-color as a fallback.
 * ============================================================ */
.ax-slider {
  position: relative;
  display: inline-flex;
  align-items: center;

  width: 100%;
  min-width: 200px;
  height: 44px; /* M3 §27.3 XS handle height (touch target) */
}

/* Native input — full visual control */
.ax-slider__input {
  /* Used by JS to compute active-fill percentage:
     style.setProperty('--_value', `${(val - min) / (max - min) * 100}%`)
     Default 0% so authors who skip JS still see a clean inactive track. */
  --_value: 0%;

  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background: transparent;

  /* Reset default appearance */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;

  /* accent-color used as native fallback when JS doesn't set --_value
     (modern Chrome/Safari/Edge will fill active track automatically when
     the runnable track has a transparent background). */
  accent-color: var(--md-sys-color-primary);

  cursor: pointer;
}

/* Track — Chrome/Safari/Edge.
   linear-gradient driven by --_value gives precise M3 active-fill split.

   COLOR TOKENS (M3 Slider spec — Ji-woon spec read 2026-05-08):
     Active track:   var(--md-sys-color-primary)
     Inactive track: var(--md-sys-color-secondary-container)

   The inactive track is a container surface, not foreground text. Using
   secondary-container keeps light/dark contrast consistent with progress
   indicators and avoids inverted theme reads.

   Gradient direction: `to right` (physical) instead of `to inline-end`
   (logical) — older Safari (≤16) parses logical gradient directions as
   invalid, dropping the entire background and making the track look
   "missing." The physical fallback is RTL-correct because the gradient
   value (`--_value`) is set by JS using min/max math, not by direction;
   in RTL the visible fill direction is inverted by the user-agent
   slider orientation, not by gradient direction. */
.ax-slider__input::-webkit-slider-runnable-track {
  width: 100%;
  height: 16px; /* M3 §27.3 XS track height */
  border-radius: 8px; /* M3 §27.3 XS track shape */
  background:
    linear-gradient(
      to right,
      var(--md-sys-color-primary) 0% var(--_value),
      var(--md-sys-color-secondary-container) var(--_value) 100%
    );
}

/* Track — Firefox.
   Firefox has a native ::-moz-range-progress pseudo-element for active fill,
   but supporting both methods means the gradient fallback also works here. */
.ax-slider__input::-moz-range-track {
  width: 100%;
  height: 16px;
  border-radius: 8px;
  background: var(--md-sys-color-secondary-container);
}
.ax-slider__input::-moz-range-progress {
  height: 16px;
  border-radius: 8px 0 0 8px;
  background: var(--md-sys-color-primary);
}

/* Thumb — Chrome/Safari/Edge */
.ax-slider__input::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 4px; /* M3 §27.3 handle width — rest/hover */
  height: 44px; /* M3 §27.3 XS handle height */
  border: 0;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-primary);

  /* Re-center vertically (track 16, thumb 44, offset = -(44-16)/2 = -14) */
  margin-top: -14px;

  cursor: pointer;
  transition:
    width
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial);
}

/* Thumb — Firefox */
.ax-slider__input::-moz-range-thumb {
  width: 4px;
  height: 44px;
  border: 0;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-primary);
  cursor: pointer;
  transition:
    width
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial);
}

/* Pressed thumb morph — 4px → 2px (M3 §27.3 pressed) */
.ax-slider__input:active::-webkit-slider-thumb {
  width: 2px;
}
.ax-slider__input:active::-moz-range-thumb {
  width: 2px;
}

/* Focus — apply outline to the thumb (vendor pseudo), not the full 44px input
   container. Latter would render a giant 44px-tall outline ring. */
.ax-slider__input:focus,
.ax-slider__input:focus-visible {
  outline: none;
}
.ax-slider__input:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}
.ax-slider__input:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* --- Disabled (Pattern C) --- */
.ax-slider__input:disabled {
  cursor: not-allowed;
}

/* IMPORTANT: use `background` (shorthand) — `background-color` alone leaves
   the active-state linear-gradient on background-image, causing fill bleed
   in disabled state (Webkit). */
.ax-slider__input:disabled::-webkit-slider-runnable-track {
  background: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
}
.ax-slider__input:disabled::-moz-range-track {
  background: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
}

/* Firefox: hide native progress bar in disabled state (otherwise primary
   color leaks through the disabled track). */
.ax-slider__input:disabled::-moz-range-progress {
  background: transparent;
}

.ax-slider__input:disabled::-webkit-slider-thumb {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.ax-slider__input:disabled::-moz-range-thumb {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}

/* --- Optional: value indicator (label that shows current value).
   Author renders this above the slider; value computed in JS. --- */
.ax-slider__value {
  position: absolute;
  bottom: calc(100% + 12px); /* M3 §27.4 value indicator bottom space */
  padding: 2px 8px;
  background-color: var(--md-sys-color-inverse-surface);
  color: var(--md-sys-color-inverse-on-surface);
  border-radius: var(--md-sys-shape-corner-extra-small);

  font-family: var(--md-sys-typescale-label-large-font);
  font-size: var(--md-sys-typescale-label-large-size);
  line-height: var(--md-sys-typescale-label-large-line-height);
  font-weight: var(--md-sys-typescale-label-large-weight);

  pointer-events: none;
  white-space: nowrap;
}


/* ============================================================
 * Chunk G1 — List + Progress indicator (Tier 3)
 *
 *   §26 List               — M3-COMPONENT-SPECS §19
 *   §27 Progress indicator — M3-COMPONENT-SPECS §21
 *
 * Tier 3 = system completeness. Not directly used by microblog prototype
 * but required for design system catalog completeness.
 * ============================================================ */

/* ============================================================
 * §26 List — M3-COMPONENT-SPECS §19
 *
 *   Container: surface, level0, corner-large.
 *   Item: 56 (1-line) / 72 (2-line) / 88 (3-line) tall, 16 L/R padding.
 *   Slots: leading (icon 24 / avatar 40 / image 56), content
 *          (overline + label + supporting), trailing (icon 24 / text).
 *   Selection: .is-selected → secondary-container fill + on-secondary-container
 *              text/icon, with corner-large shape per §19.6 expressive state machine.
 *
 *   v0.3.5 scope:
 *     - Standard + segmented (.ax-list--segmented adds 2px gap, rounds items)
 *     - Single-select selection state
 *     - Expressive shape state machine (hover/focus/pressed/selected morph)
 *     - All slots
 *
 *   Deferred to v1.5+:
 *     - Video slot (100×56 / 114×64)
 *     - Multi-select differentiated visuals
 *     - Drag handle slot
 * ============================================================ */
.ax-list {
  display: flex;
  flex-direction: column;

  background-color: var(--md-sys-color-surface);
  border-radius: var(--md-sys-shape-corner-large); /* M3 §19.2 — 16px */
  /* Level0 — surfaces sit flat by default */
}

/* Segmented — 2px gap between items, each item gets full corner-large shape */
.ax-list.ax-list--segmented {
  gap: 2px; /* M3 §19.2 segmented gap */
  background-color: transparent; /* wrapper only; item containers own surface */
  border-radius: 0;
}
.ax-list.ax-list--segmented .ax-list__item {
  background-color: var(--md-sys-color-surface);
  border-radius: var(--md-sys-shape-corner-large);
}

/* --- Item — base --- */
.ax-list__item {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  gap: 12px; /* M3 §19.3 between content */

  width: 100%;
  min-height: 56px; /* M3 §19.3 single-line */
  padding: 10px 16px; /* M3 §19.3 top/bottom 10, leading/trailing 16 */
  border: 0;
  border-radius: var(--md-sys-shape-corner-extra-small); /* §19.6 rest */
  background: transparent;
  color: var(--md-sys-color-on-surface);

  text-align: start;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  /* Expressive shape state machine (§19.6) — morph radius across states */
  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    border-radius
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial);
}

/* Reset for <a> usage */
.ax-list__item:where(a) {
  text-decoration: none;
}

/* Two-line item — slightly taller, content stacks */
.ax-list__item.ax-list__item--two-line {
  min-height: 72px;
}
/* Three-line item */
.ax-list__item.ax-list__item--three-line {
  min-height: 88px;
}

/* State layer (Pattern A) */
.ax-list__item::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
.ax-list__item:hover::before  {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}
.ax-list__item:focus-visible::before {
  opacity: var(--md-sys-state-focus-state-layer-opacity);
}
.ax-list__item:active::before {
  opacity: var(--md-sys-state-pressed-state-layer-opacity);
}

/* Expressive shape morph (§19.6) — only when item is interactive */
.ax-list__item:hover {
  border-radius: var(--md-sys-shape-corner-medium);
}
.ax-list__item:focus-visible,
.ax-list__item:active {
  border-radius: var(--md-sys-shape-corner-large);
}

/* Focus indicator (§0.11) — inner -3px (dense layout) */
.ax-list__item:focus-visible {
  outline: 3px solid var(--md-sys-color-secondary);
  outline-offset: -3px;
}

/* --- Slots --- */

/* Leading slot — icon (24px) / avatar (40px, separate component) / image (56×56) */
.ax-list__leading {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--md-sys-color-on-surface-variant);
}
.ax-list__leading > svg,
.ax-list__leading > .ax-icon {
  width: 24px; /* M3 §19.7 baseline icon */
  height: 24px;
  fill: currentColor;
}
.ax-list__leading > img,
.ax-list__leading > .ax-list__leading-image {
  width: 56px; /* M3 §19.7 image */
  height: 56px;
  object-fit: cover;
  border-radius: var(--md-sys-shape-corner-small); /* expressive variant — small rounding */
}

/* Content — main column, fills available space */
.ax-list__content {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ax-list__overline {
  color: var(--md-sys-color-on-surface-variant);
  font-family: var(--md-sys-typescale-label-small-font);
  font-size: var(--md-sys-typescale-label-small-size);
  line-height: var(--md-sys-typescale-label-small-line-height);
  font-weight: var(--md-sys-typescale-label-small-weight);
  letter-spacing: var(--md-sys-typescale-label-small-tracking);
}
.ax-list__label {
  color: inherit; /* on-surface (default) or on-secondary-container (selected) */
  font-family: var(--md-sys-typescale-body-large-font);
  font-size: var(--md-sys-typescale-body-large-size);
  line-height: var(--md-sys-typescale-body-large-line-height);
  font-weight: var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);

  /* Single-line truncate by default; multi-line variants override */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ax-list__supporting {
  color: var(--md-sys-color-on-surface-variant);
  font-family: var(--md-sys-typescale-body-medium-font);
  font-size: var(--md-sys-typescale-body-medium-size);
  line-height: var(--md-sys-typescale-body-medium-line-height);
  font-weight: var(--md-sys-typescale-body-medium-weight);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);

  /* Two-line: truncate at 1 line. Three-line: 2 lines. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ax-list__item--three-line .ax-list__supporting {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* Trailing slot — icon (24) or label-small text */
.ax-list__trailing {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--md-sys-color-on-surface-variant);
}
.ax-list__trailing > svg,
.ax-list__trailing > .ax-icon {
  width: 24px;
  height: 24px;
  color: var(--md-sys-color-on-surface); /* M3 unselected trailing icon */
  fill: currentColor;
}
.ax-list__trailing-text {
  color: var(--md-sys-color-on-surface-variant);
  white-space: nowrap;
  font-family: var(--md-sys-typescale-label-small-font);
  font-size: var(--md-sys-typescale-label-small-size);
  line-height: var(--md-sys-typescale-label-small-line-height);
  font-weight: var(--md-sys-typescale-label-small-weight);
  letter-spacing: var(--md-sys-typescale-label-small-tracking);
}

/* --- Selected state (M3 §19.5 + §19.6) --- */
.ax-list__item.is-selected,
.ax-list__item[aria-selected="true"] {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  border-radius: var(--md-sys-shape-corner-large); /* §19.6 selected */
}
.ax-list__item.is-selected .ax-list__leading,
.ax-list__item.is-selected .ax-list__trailing,
.ax-list__item.is-selected .ax-list__overline,
.ax-list__item.is-selected .ax-list__supporting,
.ax-list__item.is-selected .ax-list__trailing-text,
.ax-list__item[aria-selected="true"] .ax-list__leading,
.ax-list__item[aria-selected="true"] .ax-list__trailing,
.ax-list__item[aria-selected="true"] .ax-list__overline,
.ax-list__item[aria-selected="true"] .ax-list__supporting,
.ax-list__item[aria-selected="true"] .ax-list__trailing-text {
  color: var(--md-sys-color-on-secondary-container);
}
.ax-list__item.is-selected .ax-list__trailing > svg,
.ax-list__item.is-selected .ax-list__trailing > .ax-icon,
.ax-list__item[aria-selected="true"] .ax-list__trailing > svg,
.ax-list__item[aria-selected="true"] .ax-list__trailing > .ax-icon {
  color: var(--md-sys-color-on-secondary-container);
}

/* Expanded parent row — M3 List Expand trailing icon container */
.ax-list__item[aria-expanded="true"] .ax-list__trailing {
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-surface-container);
}

/* Disabled — Pattern A (§19.9) */
.ax-list__item:disabled,
.ax-list__item[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  border-radius: var(--md-sys-shape-corner-extra-small); /* §19.6 disabled */
}
.ax-list__item.is-selected:disabled,
.ax-list__item.is-selected[aria-disabled="true"],
.ax-list__item[aria-selected="true"]:disabled,
.ax-list__item[aria-selected="true"][aria-disabled="true"] {
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.ax-list__item:disabled .ax-list__leading,
.ax-list__item:disabled .ax-list__trailing,
.ax-list__item:disabled .ax-list__supporting,
.ax-list__item:disabled .ax-list__overline,
.ax-list__item:disabled .ax-list__trailing-text,
.ax-list__item[aria-disabled="true"] .ax-list__leading,
.ax-list__item[aria-disabled="true"] .ax-list__trailing,
.ax-list__item[aria-disabled="true"] .ax-list__supporting,
.ax-list__item[aria-disabled="true"] .ax-list__overline,
.ax-list__item[aria-disabled="true"] .ax-list__trailing-text {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}
.ax-list__item:disabled .ax-list__trailing > svg,
.ax-list__item:disabled .ax-list__trailing > .ax-icon,
.ax-list__item[aria-disabled="true"] .ax-list__trailing > svg,
.ax-list__item[aria-disabled="true"] .ax-list__trailing > .ax-icon {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}

/* Divider between items (use existing ax-divider, or include shorthand) */
.ax-list__divider {
  height: 1px;
  margin: 0 16px; /* M3 §19.3 divider leading/trailing 16 */
  background-color: var(--md-sys-color-outline-variant);
  border: 0;
}


/* ============================================================
 * §27 Progress indicator — M3-COMPONENT-SPECS §21
 *
 *   2 variants × 2 behaviors:
 *     linear / circular  ×  determinate / indeterminate
 *
 *   v0.3.5 scope: flat shape only (4px linear height, 40px circular size).
 *   Deferred to v1.5+: wavy shape (M3 expressive — complex SVG path animation).
 *
 *   Linear: track + active fill via background gradient (determinate uses
 *   --_value custom property like slider; indeterminate uses keyframe animation).
 *
 *   Circular: SVG circle with stroke-dasharray (determinate sets percent
 *   via JS; indeterminate uses rotation + dasharray morph, similar to
 *   ax-loading but progress-spec colors).
 *
 *   Reduced motion: indeterminate animations replaced with low-motion alternatives.
 * ============================================================ */

/* --- Linear --- */
.ax-progress-linear {
  --_value: 0%; /* author/JS sets for determinate */

  position: relative;
  display: block;
  width: 100%;
  height: 4px; /* M3 §21.3 flat */
  background-color: var(--md-sys-color-secondary-container);
  border-radius: var(--md-sys-shape-corner-full);
  overflow: hidden;
}

/* Determinate fill — gradient driven by --_value */
.ax-progress-linear.is-determinate {
  background:
    linear-gradient(
      to inline-end,
      var(--md-sys-color-primary) 0% var(--_value),
      var(--md-sys-color-secondary-container) var(--_value) 100%
    );
}

/* Indeterminate — animated bar */
.ax-progress-linear.is-indeterminate::before,
.ax-progress-linear.is-indeterminate::after {
  content: "";
  position: absolute;
  inset-block: 0;
  background-color: var(--md-sys-color-primary);
  border-radius: inherit;
  will-change: transform;
}
.ax-progress-linear.is-indeterminate::before {
  animation: ax-progress-linear-1 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
.ax-progress-linear.is-indeterminate::after {
  animation: ax-progress-linear-2 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
  animation-delay: 1.15s;
}

@keyframes ax-progress-linear-1 {
  0%   { inset-inline-start: -35%; inset-inline-end: 100%; }
  60%  { inset-inline-start: 100%; inset-inline-end: -90%; }
  100% { inset-inline-start: 100%; inset-inline-end: -90%; }
}
@keyframes ax-progress-linear-2 {
  0%   { inset-inline-start: -200%; inset-inline-end: 100%; }
  60%  { inset-inline-start: 107%;  inset-inline-end: -8%; }
  100% { inset-inline-start: 107%;  inset-inline-end: -8%; }
}

/* Reduced motion fallback — replace animation with low-amplitude pulse */
@media (prefers-reduced-motion: reduce) {
  .ax-progress-linear.is-indeterminate::before,
  .ax-progress-linear.is-indeterminate::after {
    animation: ax-progress-linear-pulse 2s ease-in-out infinite;
    inset-inline: 0;
  }
  .ax-progress-linear.is-indeterminate::after {
    display: none;
  }
  @keyframes ax-progress-linear-pulse {
    0%, 100% { opacity: 0.4; }
    50%      { opacity: 1; }
  }
}

/* --- Circular --- */
.ax-progress-circular {
  --_value: 0; /* 0 to 100 (no % — used in calc for stroke-dashoffset) */

  display: inline-block;
  width: 40px; /* M3 §21.4 flat */
  height: 40px;
  color: var(--md-sys-color-primary);
}

.ax-progress-circular__svg {
  display: block;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg); /* start at 12 o'clock */
}
.ax-progress-circular__track {
  fill: none;
  stroke: var(--md-sys-color-secondary-container);
  stroke-width: 4; /* M3 §21.4 active indicator thickness */
}
.ax-progress-circular__active {
  fill: none;
  stroke: currentColor;
  stroke-width: 4;
  stroke-linecap: round;
}

/* Determinate — author/JS sets stroke-dashoffset.
   Circumference of r=18 circle = 2π × 18 ≈ 113.097.
   stroke-dasharray: 113.097
   stroke-dashoffset: 113.097 × (1 - value/100)
   Author can set --_value (0–100) and use the calc below. */
.ax-progress-circular.is-determinate .ax-progress-circular__active {
  stroke-dasharray: 113.097;
  stroke-dashoffset: calc(113.097 * (1 - var(--_value) / 100));
  transition: stroke-dashoffset
    var(--md-sys-motion-curve-default-spatial-duration)
    var(--md-sys-motion-curve-default-spatial);
}

/* Indeterminate — rotation + dasharray morph */
.ax-progress-circular.is-indeterminate .ax-progress-circular__svg {
  animation: ax-progress-circular-rotate 1.6s linear infinite;
}
.ax-progress-circular.is-indeterminate .ax-progress-circular__active {
  stroke-dasharray: 100, 200;
  stroke-dashoffset: 0;
  animation: ax-progress-circular-dash 1.4s ease-in-out infinite;
}

@keyframes ax-progress-circular-rotate {
  to { transform: rotate(270deg); }
}
@keyframes ax-progress-circular-dash {
  0%   { stroke-dasharray: 1, 200;  stroke-dashoffset: 0; }
  50%  { stroke-dasharray: 90, 200; stroke-dashoffset: -35; }
  100% { stroke-dasharray: 90, 200; stroke-dashoffset: -125; }
}

/* Reduced motion — opacity pulse */
@media (prefers-reduced-motion: reduce) {
  .ax-progress-circular.is-indeterminate .ax-progress-circular__svg {
    animation: none;
  }
  .ax-progress-circular.is-indeterminate .ax-progress-circular__active {
    stroke-dasharray: 60, 200;
    animation: ax-progress-circular-pulse 2s ease-in-out infinite;
  }
  @keyframes ax-progress-circular-pulse {
    0%, 100% { opacity: 0.4; }
    50%      { opacity: 1; }
  }
}


/* ============================================================
 * Chunk G2 — Button group + Toolbar (Tier 3)
 *
 *   §28 Button group — M3-COMPONENT-SPECS §5
 *   §29 Toolbar      — M3-COMPONENT-SPECS §33
 * ============================================================ */

/* ============================================================
 * §28 Button group — M3-COMPONENT-SPECS §5
 *
 *   2 variants:
 *     - Standard (.ax-button-group): per-button rounded, individually
 *       spaced. Selected button widens ~15%, neighbors compress
 *       (§5.6 spring motion).
 *     - Connected (.ax-button-group--connected): 2px gap, full-pill outer
 *       corners (first + last), small inner corners.
 *       INNER corners morph by state (M3 §5.4 expressive):
 *         rest:     XS 4 / S·M 8 / L 16 / XL 20 dp
 *         pressed:  XS 4 / S·M 4 / L 12 / XL 16 dp
 *         selected: 50% (full pill — middle buttons become capsules)
 *
 *   ── Markup patterns (two are supported; pick by use case) ──
 *
 *   (A) RADIO + LABEL — single-select, CSS-only, no JS.
 *       PRIMARY pattern. Use for any group where exactly one option is
 *       selected at a time and clicking is purely a *value choice*
 *       (view mode, sort order, alignment, density, etc.). ARIA spec
 *       compliant — `<input type="radio">` is the canonical mutually-
 *       exclusive selector. Native keyboard arrow-key navigation +
 *       form submit + reset all free.
 *
 *         <fieldset class="ax-button-group">
 *           <legend class="u-vh">View mode</legend>
 *           <input type="radio" name="view" id="v-day"
 *                  class="ax-button-group__input">
 *           <label for="v-day"
 *                  class="ax-button is-outlined has-state-layer">Day</label>
 *           <input type="radio" name="view" id="v-week"
 *                  class="ax-button-group__input" checked>
 *           <label for="v-week"
 *                  class="ax-button is-outlined has-state-layer">Week</label>
 *           <input type="radio" name="view" id="v-month"
 *                  class="ax-button-group__input">
 *           <label for="v-month"
 *                  class="ax-button is-outlined has-state-layer">Month</label>
 *         </fieldset>
 *
 *       Notes:
 *       - <fieldset> is the semantically correct container; native
 *         keyboard radio-group behavior comes from name=. role="radiogroup"
 *         is implicit on <fieldset> + radios; ARIA explicit not needed.
 *       - <legend> labels the group for AT; .u-vh = visually hidden
 *         (provided in components.css §0 utilities) when no visible
 *         heading is wanted.
 *       - The <input> is hidden via `position: absolute; opacity: 0`
 *         (NOT `display: none` — that strips it from the tab order).
 *
 *   (B) <button aria-pressed> — multi-select toggles, JS-driven.
 *       Use for toolbars where each button is an *independent toggle*
 *       (B / I / U / S text formatting). NOT mutually exclusive — radio
 *       semantics wrong. Requires theme JS to flip aria-pressed on click.
 *
 *         <div class="ax-button-group ax-button-group--connected"
 *              role="toolbar" aria-label="Text formatting">
 *           <button type="button" class="ax-button is-tonal has-state-layer"
 *                   aria-pressed="true">B</button>
 *           <button type="button" class="ax-button is-tonal has-state-layer"
 *                   aria-pressed="false">I</button>
 *           ...
 *         </div>
 *
 *       Theme JS pattern (Phase 2B — scripts/theme.js):
 *         document.querySelectorAll('[role="toolbar"] [aria-pressed]')
 *           .forEach(btn => btn.addEventListener('click', () => {
 *             const v = btn.getAttribute('aria-pressed') === 'true';
 *             btn.setAttribute('aria-pressed', String(!v));
 *           }));
 *
 *   ── DO NOT use button group for tabs (panel switchers).
 *      Tabs have their own ARIA pattern: role="tablist" / "tab" /
 *      "tabpanel". Will be a separate component. ──
 *
 *   Sizes inherit from Button §4 — default size = M (40px).
 *
 *   Deferred to v1.5+:
 *     - Toggle button shape morph (square ↔ round) per §5.6
 *     - Square-shape variant (vs default round) for connected group
 * ============================================================ */

/* --- §28.1 Container ------------------------------------------- */
.ax-button-group {
  --_button-group-button-height: var(--comp-button-height);
  --_button-group-button-padding-inline: var(--space-md);
  --_button-group-button-icon-size: var(--comp-icon-size-sm);
  --_button-group-pressed-radius: var(--md-sys-shape-corner-small);
  --_button-group-outline-width: 1px;
  --_button-group-pill-radius: calc(var(--_button-group-button-height) / 2);

  /* <fieldset> default styling reset (border, padding, margin) — needed
     because pattern (A) uses <fieldset> as the container. */
  margin: 0;
  padding: 0;
  border: 0;
  min-inline-size: 0; /* fieldset's intrinsic min-content quirk */

  display: inline-flex;
  align-items: center;
  gap: var(--space-sm); /* M3 §5.3 — M/L/XL gap = 8px */
}
.ax-button-group > legend {
  /* If a visible legend is used, give it room. The .u-vh utility on
     legend hides it visually but keeps it for AT. */
  padding: 0;
  margin-block-end: var(--space-xs);
}

/* XS / S — slightly tighter or wider gap per M3 §5.3 */
.ax-button-group.is-size-xs {
  --_button-group-button-height: var(--comp-button-height-xs);
  --_button-group-button-padding-inline: var(--comp-button-padding-inline-xs);
  --_button-group-button-icon-size: var(--comp-button-icon-size-xs);
  --_button-group-pressed-radius: var(--md-sys-shape-corner-small);
  --_button-group-outline-width: var(--comp-button-outline-width-xs);
  gap: 18px; /* §5.3 XS */
}
.ax-button-group.is-size-s {
  --_button-group-button-height: var(--comp-button-height-s);
  --_button-group-button-padding-inline: var(--comp-button-padding-inline-s);
  --_button-group-button-icon-size: var(--comp-button-icon-size-s);
  --_button-group-pressed-radius: var(--md-sys-shape-corner-small);
  --_button-group-outline-width: var(--comp-button-outline-width-s);
  gap: 12px; /* §5.3 S */
}
.ax-button-group.is-size-m {
  --_button-group-button-height: var(--comp-button-height-m);
  --_button-group-button-padding-inline: var(--comp-button-padding-inline-m);
  --_button-group-button-icon-size: var(--comp-button-icon-size-m);
  --_button-group-pressed-radius: var(--md-sys-shape-corner-medium);
  --_button-group-outline-width: var(--comp-button-outline-width-m);
}
.ax-button-group.is-size-l {
  --_button-group-button-height: var(--comp-button-height-l);
  --_button-group-button-padding-inline: var(--comp-button-padding-inline-l);
  --_button-group-button-icon-size: var(--comp-button-icon-size-l);
  --_button-group-pressed-radius: var(--md-sys-shape-corner-large);
  --_button-group-outline-width: var(--comp-button-outline-width-l);
}
.ax-button-group.is-size-xl {
  --_button-group-button-height: var(--comp-button-height-xl);
  --_button-group-button-padding-inline: var(--comp-button-padding-inline-xl);
  --_button-group-button-icon-size: var(--comp-button-icon-size-xl);
  --_button-group-pressed-radius: var(--md-sys-shape-corner-large);
  --_button-group-outline-width: var(--comp-button-outline-width-xl);
}

/* --- §28.2 Hidden radio input (pattern A) ----------------------
 * Visually hide the <input> but keep it in the tab order + AT tree.
 * `position: absolute` + `opacity: 0` is the WCAG-safe hide.
 * Each label becomes the visual button; clicking the label toggles
 * its associated input automatically (native HTML behavior, no JS).
 * The next sibling combinator (`:checked + .ax-button`) drives the
 * selected styling. */
.ax-button-group__input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  pointer-events: none;
  /* opacity: 0 not needed — clip-path inset(50%) hides it; keeps a11y
     announcements working better than display: none. */
}

/* --- §28.3 Button-element layout (both patterns) ---------------
 * Pattern A: each <label.ax-button> is a flex item.
 * Pattern B: each <button.ax-button> is a flex item.
 * Same selector covers both via the .ax-button class.
 *
 * BUG FIX: was `flex: 1 1 auto` — `auto` basis = each button starts
 * at content width, so `flex-grow: 1.15` only redistributed *leftover
 * space* by 1.15:1:1 ratio. With "Day" (3 chars) vs "Month" (5 chars),
 * the content-width difference dominated and the 15% widen was barely
 * visible. Changed to `flex: 1 1 0` so grow ratios determine absolute
 * widths. min-inline-size: max-content prevents text wrapping in the
 * compressed neighbors. */
.ax-button-group .ax-button {
  --_button-height: var(--_button-group-button-height);
  --_button-padding-inline: var(--_button-group-button-padding-inline);
  --_button-icon-size: var(--_button-group-button-icon-size);
  --_button-pressed-radius: var(--_button-group-pressed-radius);
  --_button-outline-width: var(--_button-group-outline-width);

  flex: 1 1 0;
  min-inline-size: max-content;
  transition:
    flex-grow
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    border-radius
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    outline-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* --- §28.4 Selected — three triggers unified -------------------
 *   Pattern A: <input>:checked + .ax-button       (radio CSS-only)
 *   Pattern B: .ax-button[aria-pressed="true"]    (toolbar via JS)
 *   Legacy:    .ax-button.is-selected             (manual class — kept
 *               for backward compat with style guide demos that don't
 *               wire JS, e.g. static screenshots)
 *
 * Per M3 §5.6 — selected uses secondary-container regardless of base
 * variant (filled / outlined / tonal). Single unambiguous "active" cue
 * across the whole group.
 * For outlined: clear outline so secondary-container fill reads cleanly. */
.ax-button-group .ax-button-group__input:checked + .ax-button,
.ax-button-group .ax-button[aria-pressed="true"],
.ax-button-group .ax-button.is-selected {
  flex-grow: 1.15; /* §5.6 ~15% widen */
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
}
.ax-button-group .ax-button-group__input:checked + .ax-button.is-outlined,
.ax-button-group .ax-button.is-outlined[aria-pressed="true"],
.ax-button-group .ax-button.is-outlined.is-selected {
  outline-color: transparent;
}

/* --- §28.5 Focus — pattern A delegates focus to <input>,
 * which is visually hidden, so we surface focus on its sibling label.
 * Re-uses the global :focus-visible style (§5 in base.css) by
 * applying the same outline pattern. */
.ax-button-group .ax-button-group__input:focus-visible + .ax-button {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* --- §28.6 Disabled — when the radio input is disabled, dim the
 * associated label. Pattern B uses .ax-button:disabled directly,
 * already styled in §3 (Button). */
.ax-button-group .ax-button-group__input:disabled + .ax-button {
  opacity: 0.38; /* M3 disabled state alpha */
  pointer-events: none;
}

/* --- §28.7 Connected variant — base ----------------------------
 *   2px gap. First/last outer corners full-pill. Inner corners
 *   size-dependent (set below). Pressed/selected morphs inner corners. */
.ax-button-group.ax-button-group--connected {
  gap: 2px; /* M3 §5.4 */
}

.ax-button-group--connected .ax-button {
  border-radius: var(--md-sys-shape-corner-small); /* M default = 8dp */
}
.ax-button-group--connected.is-size-xs .ax-button {
  border-radius: var(--md-sys-shape-corner-extra-small); /* XS = 4dp */
}
.ax-button-group--connected.is-size-s .ax-button,
.ax-button-group--connected.is-size-m .ax-button {
  border-radius: var(--md-sys-shape-corner-small); /* S/M = 8dp */
}
.ax-button-group--connected.is-size-l .ax-button {
  border-radius: var(--md-sys-shape-corner-large); /* L = 16dp */
}
.ax-button-group--connected.is-size-xl .ax-button {
  border-radius: var(--md-sys-shape-corner-large-increased); /* XL = 20dp */
}

/* First + last — outer corners full-pill at rest.
 *
 * Pattern A complication: in the radio markup, the first + last
 * .ax-button labels are NOT first-child / last-child of the fieldset
 * (the <input> + <legend> precede them). Use logical positional
 * selectors that work for both patterns:
 *
 *   :first-of-type / :last-of-type — selects first/last <label> AND
 *   first/last <button> independently. Works because in pattern A
 *   the elements are <label>, in pattern B they're <button>. The
 *   :*-of-type selector is keyed to element type, not class. */
.ax-button-group--connected label.ax-button:first-of-type,
.ax-button-group--connected button.ax-button:first-of-type {
  border-start-start-radius: var(--_button-group-pill-radius);
  border-end-start-radius:   var(--_button-group-pill-radius);
}
.ax-button-group--connected label.ax-button:last-of-type,
.ax-button-group--connected button.ax-button:last-of-type {
  border-start-end-radius: var(--_button-group-pill-radius);
  border-end-end-radius:   var(--_button-group-pill-radius);
}

/* Min-width 48px for XS / S (touch target a11y — §5.4) */
.ax-button-group--connected.is-size-xs .ax-button,
.ax-button-group--connected.is-size-s  .ax-button {
  min-inline-size: 48px;
}

/* --- §28.8 Connected: pressed state ----------------------------
 *   Inner corners shrink. First/last must restore outer pill so only
 *   inner corner morphs. Pressed values: M → 4 / L → 12 / XL → 16 dp.
 *   :active works on <button> (pattern B). Pattern A uses <label> which
 *   also gets :active when clicked — both covered. */
.ax-button-group--connected .ax-button:active {
  border-radius: var(--_button-group-pressed-radius);
}
.ax-button-group--connected label.ax-button:first-of-type:active,
.ax-button-group--connected button.ax-button:first-of-type:active {
  border-start-start-radius: var(--_button-group-pill-radius);
  border-end-start-radius:   var(--_button-group-pill-radius);
}
.ax-button-group--connected label.ax-button:last-of-type:active,
.ax-button-group--connected button.ax-button:last-of-type:active {
  border-start-end-radius: var(--_button-group-pill-radius);
  border-end-end-radius:   var(--_button-group-pill-radius);
}

/* --- §28.9 Connected: selected — all corners full pill ---------
 *   §5.4 inner = 50%. Middle buttons morph rectangle → capsule;
 *   first/last already had outer pill, only inner corners change.
 *
 * BUG FIX: previously :active and .is-selected had matching specificity,
 * relying on source order (latter-wins). When a user actively presses
 * an already-selected button, M3 spec says pressed state takes
 * precedence. Explicit .is-selected:active rules below resolve this. */
.ax-button-group--connected .ax-button-group__input:checked + .ax-button,
.ax-button-group--connected .ax-button[aria-pressed="true"],
.ax-button-group--connected .ax-button.is-selected {
  border-radius: var(--_button-group-pill-radius);
}

/* Selected + pressed — inner pressed corner wins, outer pill stays. */
.ax-button-group--connected .ax-button-group__input:checked + .ax-button:active,
.ax-button-group--connected .ax-button[aria-pressed="true"]:active,
.ax-button-group--connected .ax-button.is-selected:active {
  border-radius: var(--_button-group-pressed-radius);
}
/* Restore first/last outer pill in selected+pressed */
.ax-button-group--connected label.ax-button:first-of-type.is-selected:active,
.ax-button-group--connected button.ax-button:first-of-type[aria-pressed="true"]:active,
.ax-button-group--connected .ax-button-group__input:checked + label.ax-button:first-of-type:active {
  border-start-start-radius: var(--_button-group-pill-radius);
  border-end-start-radius:   var(--_button-group-pill-radius);
}
.ax-button-group--connected label.ax-button:last-of-type.is-selected:active,
.ax-button-group--connected button.ax-button:last-of-type[aria-pressed="true"]:active,
.ax-button-group--connected .ax-button-group__input:checked + label.ax-button:last-of-type:active {
  border-start-end-radius: var(--_button-group-pill-radius);
  border-end-end-radius:   var(--_button-group-pill-radius);
}


/* ============================================================
 * §29 Toolbar — M3-COMPONENT-SPECS §33
 *
 *   2 variants × 2 colors:
 *     docked / floating  ×  standard / vibrant
 *
 *   v0.3.5 scope:
 *     - docked + floating
 *     - Standard + Vibrant color schemes
 *     - Slot-based: any combination of .ax-icon-button, .ax-button,
 *       text-field, ax-button-group can sit inside.
 *
 *   Deferred to v1.5+:
 *     - Floating-with-FAB variant (toolbar-bound FAB sizes diverge from
 *       standalone FAB — §33.6). Adds significant per-instance markup.
 *     - Vertical floating orientation.
 * ============================================================ */
.ax-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;

  background-color: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface-variant);
}

/* --- Variant: docked (default) --- */
.ax-toolbar.ax-toolbar--docked {
  width: 100%;
  height: 64px; /* M3 §33.2 */
  padding-inline: 16px; /* M3 §33.2 leading/trailing */
  gap: 4px; /* M3 §33.2 min space — author may widen via inline style */
  border-radius: 0; /* §33.2 corner-none */
}

/* --- Variant: floating --- */
.ax-toolbar.ax-toolbar--floating {
  height: 64px; /* M3 §33.3 */
  padding-inline: 8px; /* M3 §33.3 leading/trailing inside */
  gap: 4px; /* M3 §33.3 between actions */
  border-radius: var(--md-sys-shape-corner-full);
  box-shadow: var(--md-sys-elevation-shadow-level3);
  /* Author handles outer margin from screen edge (16/24 per §33.3) */
}

/* --- Color: Vibrant (M3 §33.5) --- */
.ax-toolbar.ax-toolbar--vibrant {
  background-color: var(--md-sys-color-primary-container);
  color: var(--md-sys-color-on-primary-container);
}

/* Slot button styling — toolbar inherits its color into nested buttons.
   Using composition with .ax-icon-button.is-standard: standard variant has
   no fill, picks up currentColor → toolbar's color cascade applies. */
.ax-toolbar .ax-icon-button.is-standard {
  color: inherit; /* on-surface-variant (standard) or on-primary-container (vibrant) */
}

/* Selected icon button (toggle pressed state) — secondary-container fill.
   This is a TOOLBAR-LOCAL selected style, not the icon button's own toggle. */
.ax-toolbar .ax-icon-button.is-selected,
.ax-toolbar .ax-icon-button[aria-pressed="true"] {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
}
.ax-toolbar.ax-toolbar--vibrant .ax-icon-button.is-selected,
.ax-toolbar.ax-toolbar--vibrant .ax-icon-button[aria-pressed="true"] {
  background-color: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface);
}

/* Disabled — Pattern A on the slot buttons (handled by the buttons themselves) */

/* Trailing slot helper — push remaining buttons to the right */
.ax-toolbar__spacer {
  flex: 1 1 auto;
}


/* ============================================================
 * Chunk G3 — Carousel (Tier 3 끝)
 *
 *   §30 Carousel — M3-COMPONENT-SPECS §12
 *
 *   v0.3.5 scope:
 *     - Multi-browse (default — mixed sizes large/medium/small)
 *     - Hero (large featured + small queue)
 *     - Uncontained (items bleed past trailing edge)
 *
 *   Deferred to v1.5+:
 *     - Center-aligned hero (symmetric featured)
 *     - Uncontained multi-aspect-ratio
 *     - Full-screen (stories — separate markup pattern)
 *     - Item morph animation (M3 expressive — items resize as they enter/exit
 *       viewport during scroll)
 *
 *   Implementation: native horizontal scroll + scroll-snap. Modern browsers
 *   only (2025 standard). No JS required for basic interaction.
 *   Authors can add prev/next buttons + scroll indicators on top.
 *
 *   Item corner: corner-extra-large (28px) per M3 §12.2.
 * ============================================================ */
.ax-carousel {
  /* Defaults: multi-browse layout */
  --_carousel-leading: 16px;  /* M3 §12.3 multi-browse */
  --_carousel-trailing: 16px;
  --_carousel-block: 8px;
  --_carousel-gap: 8px;

  position: relative;
  background-color: var(--md-sys-color-surface);
  overflow: hidden; /* contain bleed; track handles scroll */
}

/* --- Track — scroll container --- */
.ax-carousel__track {
  display: flex;
  align-items: stretch;
  gap: var(--_carousel-gap);

  padding-block: var(--_carousel-block);
  padding-inline: var(--_carousel-leading) var(--_carousel-trailing);

  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;

  /* Hide scrollbar visually (still scrollable). Author can re-show via
     `style="scrollbar-width: thin"`. */
  scrollbar-width: none;
  -ms-overflow-style: none;

  /* iOS smooth scroll */
  -webkit-overflow-scrolling: touch;
}
.ax-carousel__track::-webkit-scrollbar {
  display: none;
}

/* Reduced motion — disable smooth scroll snap */
@media (prefers-reduced-motion: reduce) {
  .ax-carousel__track {
    scroll-behavior: auto;
  }
}

/* --- Item — base --- */
.ax-carousel__item {
  position: relative;
  flex-shrink: 0;
  scroll-snap-align: start;

  border-radius: var(--md-sys-shape-corner-extra-large); /* M3 §12.2 — 28px */
  background-color: var(--md-sys-color-surface-container);
  overflow: hidden;
  isolation: isolate; /* contain ::before/::after state layers if added later */
}

/* Default item dimensions — tweak per layout below */
.ax-carousel__item {
  width: 200px;          /* sensible default — multi-browse "medium" */
  aspect-ratio: 1 / 1;   /* square fallback when no inner content drives height */
}

/* Item size variants (multi-browse + hero use these) */
.ax-carousel__item--large {
  width: 280px;
}
.ax-carousel__item--medium {
  width: 200px;
}
.ax-carousel__item--small {
  /* M3 §12.4 — small = 40–56px wide. Pick midpoint for visibility. */
  width: 56px;
}

/* Item content slots (author can put media + label) */
.ax-carousel__item-media,
.ax-carousel__item-media > img,
.ax-carousel__item-media > video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ax-carousel__item-label {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  padding: 12px 16px;

  /* Theme-aware contrast pair (inverse-surface box):
     - Light mode: inverse-surface=dark, inverse-on-surface=light → readable
     - Dark mode:  inverse-surface=light, inverse-on-surface=dark → readable
     Scrim gradient was theme-invariant (always dark) which broke text
     legibility in dark mode (inverse-on-surface = dark in dark theme,
     making dark text on dark scrim). */
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-inverse-surface) 80%,
    transparent
  );
  color: var(--md-sys-color-inverse-on-surface);

  font-family: var(--md-sys-typescale-title-medium-font);
  font-size: var(--md-sys-typescale-title-medium-size);
  line-height: var(--md-sys-typescale-title-medium-line-height);
  font-weight: var(--md-sys-typescale-title-medium-weight);
  letter-spacing: var(--md-sys-typescale-title-medium-tracking);
}

/* --- Layout: Multi-browse (default) --- */
/* Inherits base padding/gap. Just use size variants on items. */

/* --- Layout: Hero --- */
.ax-carousel.ax-carousel--hero {
  --_carousel-leading: 16px;
  --_carousel-trailing: 16px;
  --_carousel-block: 8px;
  --_carousel-gap: 8px;
}
.ax-carousel--hero .ax-carousel__item--large {
  width: clamp(280px, 80%, 480px); /* hero featured — wide */
  aspect-ratio: 16 / 9;
}
.ax-carousel--hero .ax-carousel__item--small {
  width: 80px;
  aspect-ratio: 1 / 1;
}

/* --- Layout: Uncontained — items bleed past trailing edge --- */
.ax-carousel.ax-carousel--uncontained {
  --_carousel-leading: 16px;
  --_carousel-trailing: 0px;  /* bleed */
  --_carousel-block: 8px;
  --_carousel-gap: 8px;
}

/* --- Optional prev/next buttons (author opts in via .has-controls)
   Composes with .ax-icon-button. Author renders inside .ax-carousel
   alongside the track. --- */
.ax-carousel__nav {
  position: absolute;
  inset-block-start: 50%;
  transform: translateY(-50%);
  z-index: 1;
  pointer-events: auto;
}
.ax-carousel__nav--prev { inset-inline-start: 8px; }
.ax-carousel__nav--next { inset-inline-end: 8px; }


/* ============================================================
 * Chunk H1 — FAB menu (Tier 4 / deferred from v0.2.5)
 *
 *   §31 FAB menu — M3-COMPONENT-SPECS §9
 *
 *   Pattern: close-button anchor + 2-6 list items. Toggle via .is-open.
 *   List items stack vertically above the close button.
 *
 *   3 color sets: Primary / Secondary / Tertiary (via .is-color-* modifier).
 *
 *   v0.4.0 scope: structural styling. Item enter/exit stagger animation
 *   (M3 expressive — 50ms cascade, scale 0.7 → 1) deferred to v1.5+.
 *   Current build = simultaneous opacity + transform fade.
 *
 *   Markup pattern:
 *     <div class="ax-fab-menu is-color-primary">
 *       <button class="ax-fab-menu__close" aria-expanded="true|false">
 *         <svg class="ax-fab-menu__close-icon-rest">  ← shown when closed
 *         <svg class="ax-fab-menu__close-icon-open">  ← shown when open (X)
 *       </button>
 *       <ul class="ax-fab-menu__list">
 *         <li class="ax-fab-menu__item">
 *           <button class="ax-fab-menu__item-button">
 *             <span class="ax-fab-menu__item-icon"><svg/></span>
 *             <span class="ax-fab-menu__item-label">Action</span>
 *           </button>
 *         </li>
 *       </ul>
 *     </div>
 * ============================================================ */
.ax-fab-menu {
  position: relative;
  display: inline-flex;
  flex-direction: column-reverse;
  align-items: flex-end;
  gap: 8px; /* M3 §9.2 close-to-first-item space */

  /* Default color set = Primary */
  --_fab-menu-close-bg: var(--md-sys-color-primary);
  --_fab-menu-close-fg: var(--md-sys-color-on-primary);
  --_fab-menu-item-bg: var(--md-sys-color-primary-container);
  --_fab-menu-item-fg: var(--md-sys-color-on-primary-container);
}

/* Color sets (M3 §9.4) */
.ax-fab-menu.is-color-secondary {
  --_fab-menu-close-bg: var(--md-sys-color-secondary);
  --_fab-menu-close-fg: var(--md-sys-color-on-secondary);
  --_fab-menu-item-bg: var(--md-sys-color-secondary-container);
  --_fab-menu-item-fg: var(--md-sys-color-on-secondary-container);
}
.ax-fab-menu.is-color-tertiary {
  --_fab-menu-close-bg: var(--md-sys-color-tertiary);
  --_fab-menu-close-fg: var(--md-sys-color-on-tertiary);
  --_fab-menu-item-bg: var(--md-sys-color-tertiary-container);
  --_fab-menu-item-fg: var(--md-sys-color-on-tertiary-container);
}

/* --- Close button (anchor) ---
   56×56 corner-full, level3, primary fill (default).
   Same dimensions as default FAB but with menu-toggle semantics. */
.ax-fab-menu__close {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: 56px;  /* M3 §9.2 */
  height: 56px;
  padding: 0;
  border: 0;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--_fab-menu-close-bg);
  color: var(--_fab-menu-close-fg);
  box-shadow: var(--md-sys-elevation-shadow-level3);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    box-shadow
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}
.ax-fab-menu__close:hover {
  box-shadow: var(--md-sys-elevation-shadow-level4);
}

/* State layer (Pattern A) */
.ax-fab-menu__close::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: var(--_fab-menu-close-fg);
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
.ax-fab-menu__close:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.ax-fab-menu__close:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.ax-fab-menu__close:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }

.ax-fab-menu__close:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* Close-button icons — author renders both, CSS swaps via .is-open */
.ax-fab-menu__close-icon-rest,
.ax-fab-menu__close-icon-open {
  width: 20px; /* M3 §9.2 */
  height: 20px;
  fill: currentColor;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects),
    transform
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial);
}
.ax-fab-menu__close-icon-open {
  position: absolute;
  opacity: 0;
  transform: rotate(-90deg);
}
.ax-fab-menu.is-open .ax-fab-menu__close-icon-rest {
  opacity: 0;
  transform: rotate(90deg);
}
.ax-fab-menu.is-open .ax-fab-menu__close-icon-open {
  opacity: 1;
  transform: rotate(0deg);
}

/* --- List ---
   Stacks vertically above close button (column-reverse on parent).
   Hidden by default, shown via .is-open. */
.ax-fab-menu__list {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px; /* M3 §9.3 between items */

  margin: 0;
  padding: 0;
  list-style: none;

  /* Hidden by default — author toggles .is-open on the wrapper */
  visibility: hidden;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    transform
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    visibility 0s linear var(--md-sys-motion-curve-fast-effects-duration);
}
.ax-fab-menu.is-open .ax-fab-menu__list {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
  transition:
    opacity
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    transform
      var(--md-sys-motion-curve-fast-spatial-duration)
      var(--md-sys-motion-curve-fast-spatial),
    visibility 0s linear 0s;
}

.ax-fab-menu__item {
  display: block;
}

/* --- Item button ---
   56px height, corner-full, primary-container (default), level0, label-large.
   Composition: this is its own component (not .ax-button) because spec
   defines specific dimensions + slot layout. */
.ax-fab-menu__item-button {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  gap: 8px; /* M3 §9.3 between icon and label */

  height: 56px; /* M3 §9.3 */
  padding: 0 24px; /* M3 §9.3 leading/trailing */
  border: 0;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--_fab-menu-item-bg);
  color: var(--_fab-menu-item-fg);

  font-family: var(--md-sys-typescale-title-medium-font);
  font-size: var(--md-sys-typescale-title-medium-size);
  line-height: var(--md-sys-typescale-title-medium-line-height);
  font-weight: var(--md-sys-typescale-title-medium-weight);
  letter-spacing: var(--md-sys-typescale-title-medium-tracking);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  transition: background-color
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}

/* State layer */
.ax-fab-menu__item-button::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
.ax-fab-menu__item-button:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.ax-fab-menu__item-button:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.ax-fab-menu__item-button:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }

.ax-fab-menu__item-button:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* Item icon */
.ax-fab-menu__item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.ax-fab-menu__item-icon > svg,
.ax-fab-menu__item-icon > .ax-icon {
  width: 24px; /* M3 §9.3 */
  height: 24px;
  fill: currentColor;
}

.ax-fab-menu__item-label {
  white-space: nowrap;
}

/* Disabled item — Pattern A */
.ax-fab-menu__item-button:disabled,
.ax-fab-menu__item-button[aria-disabled="true"] {
  cursor: not-allowed;
  pointer-events: none;
  background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}


/* ============================================================
 * Chunk H2 — Split button
 *
 *   §32 Split button — M3-COMPONENT-SPECS §10
 *
 *   Anatomy: leading button (primary action) + trailing button
 *   (dropdown trigger). 2px gap, container shape = corner-full,
 *   inner corners = 4-12dp by size, morph to 8dp on hover/pressed.
 *   When trailing button selected (menu open): inner corner = 50%.
 *
 *   Composition: two .ax-button instances inside .ax-split-button.
 *   Group only adds inner-corner shape rules — buttons reuse all base
 *   variant styling (filled/outlined/tonal, sizes, state layer).
 *
 *   v0.4.0 scope: M (default) + S sizes. XS/L/XL extrapolated from spec
 *   but only M shown in style guide demos.
 * ============================================================ */
.ax-split-button {
  display: inline-flex;
  align-items: stretch;
  gap: 2px; /* M3 §10.2 */
}

/* Both child buttons get inner small corners at rest, outer pill via
   first/last-child overrides. Same logical-property pattern as button
   group connected. */
.ax-split-button .ax-button {
  border-radius: var(--md-sys-shape-corner-extra-small); /* §10.2 M = 4dp */
}
.ax-split-button.is-size-l .ax-button {
  border-radius: var(--md-sys-shape-corner-small); /* §10.2 L = 8dp */
}
.ax-split-button.is-size-xl .ax-button {
  border-radius: var(--md-sys-shape-corner-medium); /* §10.2 XL = 12dp */
}

/* Outer corners — full pill */
.ax-split-button .ax-button:first-child {
  border-start-start-radius: var(--md-sys-shape-corner-full);
  border-end-start-radius:   var(--md-sys-shape-corner-full);
}
.ax-split-button .ax-button:last-child {
  border-start-end-radius: var(--md-sys-shape-corner-full);
  border-end-end-radius:   var(--md-sys-shape-corner-full);
}

/* Inner corner morphs to 8dp on hover (M3 §10.2 spec) */
.ax-split-button .ax-button:hover {
  border-radius: var(--md-sys-shape-corner-small);
}
.ax-split-button .ax-button:first-child:hover {
  border-start-start-radius: var(--md-sys-shape-corner-full);
  border-end-start-radius:   var(--md-sys-shape-corner-full);
}
.ax-split-button .ax-button:last-child:hover {
  border-start-end-radius: var(--md-sys-shape-corner-full);
  border-end-end-radius:   var(--md-sys-shape-corner-full);
}

/* Selected button (menu open) → 50% inner corner */
.ax-split-button .ax-button.is-selected,
.ax-split-button .ax-button[aria-expanded="true"] {
  border-radius: var(--md-sys-shape-corner-full);
}

/* Trailing icon button — narrower than label button. Author renders
   the dropdown chevron icon inside. The optical centering offset
   (M3 §10.3) is applied via padding tweak — author may add inline
   style="padding-inline-start: calc(var(--space-md) - 1px)" for XS/S
   if needed. */
.ax-split-button__trailing-icon {
  width: 20px;
  height: 20px;
  fill: currentColor;
}
/* v3.4.4: Material Symbols glyph variant of the split-button chevron.
   Material Symbols glyphs size by font-size, not by box width/height. */
.material-symbols-rounded.ax-split-button__trailing-icon {
  font-size: 20px;
}


/* ============================================================
 * Chunk H3 — Date picker (visual structure only)
 *
 *   §33 Date picker — M3-COMPONENT-SPECS §15
 *
 *   This component provides M3-styled markup for a calendar grid.
 *   Actual date logic (current month, day labels, selection state,
 *   navigation) is the author's responsibility — typically driven
 *   by a JS library (e.g. flatpickr, vanilla-calendar) or a server-
 *   rendered template.
 *
 *   v0.4.0 scope: docked variant only. Modal + modal-input variants
 *   reuse same internals + ax-dialog wrapper (deferred markup pattern
 *   to author).
 *
 *   Markup pattern:
 *     <div class="ax-date-picker">
 *       <header class="ax-date-picker__header">
 *         <span class="ax-date-picker__supporting">Select date</span>
 *         <span class="ax-date-picker__headline">2026년 5월 3일</span>
 *       </header>
 *       <nav class="ax-date-picker__nav">
 *         <button class="ax-date-picker__nav-prev">‹</button>
 *         <span class="ax-date-picker__nav-label">2026년 5월</span>
 *         <button class="ax-date-picker__nav-next">›</button>
 *       </nav>
 *       <div class="ax-date-picker__weekdays">
 *         <span>일</span> <span>월</span> ... <span>토</span>
 *       </div>
 *       <div class="ax-date-picker__grid">
 *         <button class="ax-date-picker__cell is-outside">28</button>
 *         <button class="ax-date-picker__cell">1</button>
 *         <button class="ax-date-picker__cell is-today">3</button>
 *         <button class="ax-date-picker__cell is-selected">15</button>
 *         <button class="ax-date-picker__cell is-in-range">10</button>
 *         ...
 *       </div>
 *     </div>
 * ============================================================ */
.ax-date-picker {
  display: inline-flex;
  flex-direction: column;
  gap: 8px;
  width: 360px;
  padding: 16px;
  background-color: var(--md-sys-color-surface-container-high);
  border-radius: var(--md-sys-shape-corner-large); /* docked variant */
  box-shadow: var(--md-sys-elevation-shadow-level3);
}

.ax-date-picker__header {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 4px 12px;
}
.ax-date-picker__supporting {
  color: var(--md-sys-color-on-surface-variant);
  font-family: var(--md-sys-typescale-label-large-font);
  font-size: var(--md-sys-typescale-label-large-size);
  line-height: var(--md-sys-typescale-label-large-line-height);
  font-weight: var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
}
.ax-date-picker__headline {
  color: var(--md-sys-color-on-surface);
  font-family: var(--md-sys-typescale-headline-large-font);
  font-size: var(--md-sys-typescale-headline-large-size);
  line-height: var(--md-sys-typescale-headline-large-line-height);
  font-weight: var(--md-sys-typescale-headline-large-weight);
  letter-spacing: var(--md-sys-typescale-headline-large-tracking);
}

/* Month navigation row */
.ax-date-picker__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: 4px;
}
.ax-date-picker__nav-label {
  color: var(--md-sys-color-on-surface-variant);
  font-family: var(--md-sys-typescale-label-large-font);
  font-size: var(--md-sys-typescale-label-large-size);
  line-height: var(--md-sys-typescale-label-large-line-height);
  font-weight: var(--md-sys-typescale-label-large-weight);
  letter-spacing: var(--md-sys-typescale-label-large-tracking);
}

/* Weekday labels row */
.ax-date-picker__weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  text-align: center;
  color: var(--md-sys-color-on-surface);
  padding-block: 8px 0;
  font-family: var(--md-sys-typescale-body-small-font);
  font-size: var(--md-sys-typescale-body-small-size);
  line-height: var(--md-sys-typescale-body-small-line-height);
  font-weight: var(--md-sys-typescale-body-small-weight);
  letter-spacing: var(--md-sys-typescale-body-small-tracking);
}

/* Date grid */
.ax-date-picker__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

/* Date cell — square button, corner-full, 40dp (M3 §15.3) */
.ax-date-picker__cell {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: 40px;
  height: 40px;
  padding: 0;
  margin: 0 auto;
  border: 0;
  border-radius: var(--md-sys-shape-corner-full); /* §15.3 */
  background: transparent;
  color: var(--md-sys-color-on-surface);

  font-family: var(--md-sys-typescale-body-large-font);
  font-size: var(--md-sys-typescale-body-large-size);
  line-height: var(--md-sys-typescale-body-large-line-height);
  font-weight: var(--md-sys-typescale-body-large-weight);
  letter-spacing: var(--md-sys-typescale-body-large-tracking);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* State layer Pattern A */
.ax-date-picker__cell::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
.ax-date-picker__cell:hover::before  { opacity: var(--md-sys-state-hover-state-layer-opacity); }
.ax-date-picker__cell:focus-visible::before { opacity: var(--md-sys-state-focus-state-layer-opacity); }
.ax-date-picker__cell:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); }
.ax-date-picker__cell:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* Today — primary outline (M3 §15.3) */
.ax-date-picker__cell.is-today {
  outline: 1px solid var(--md-sys-color-primary);
  outline-offset: -1px;
  color: var(--md-sys-color-primary);
}

/* Selected — primary fill */
.ax-date-picker__cell.is-selected,
.ax-date-picker__cell[aria-selected="true"] {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  outline: 0;
}

/* Outside-month — 38% opacity label (M3 §15.3) */
.ax-date-picker__cell.is-outside {
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
}

/* In-range (date range) — secondary-container active indicator */
.ax-date-picker__cell.is-in-range {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  border-radius: 0; /* range cells form a continuous band */
}
/* Range endpoints — start + end are themselves selected (primary fill).
   They sit on top of the in-range secondary-container band, so the cell
   visually anchors the band's start/end with full primary emphasis,
   matching M3/Compose DateRangePicker behavior. */
.ax-date-picker__cell.is-in-range.is-range-start,
.ax-date-picker__cell.is-in-range.is-range-end {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
}
.ax-date-picker__cell.is-in-range.is-range-start {
  border-start-start-radius: var(--md-sys-shape-corner-full);
  border-end-start-radius:   var(--md-sys-shape-corner-full);
}
.ax-date-picker__cell.is-in-range.is-range-end {
  border-start-end-radius: var(--md-sys-shape-corner-full);
  border-end-end-radius:   var(--md-sys-shape-corner-full);
}


/* ============================================================
 * Chunk H4 — Time picker (visual structure only)
 *
 *   §34 Time picker — M3-COMPONENT-SPECS §16
 *
 *   M3-styled markup. Clock dial drag interaction stays in author
 *   code (§16.5 dial container is just visual scaffolding).
 *
 *   v0.4.0 scope: input variant (two text inputs + AM/PM radio
 *   selector) + dial container structure. Full clock-dial drag
 *   interaction deferred to v1.5+ (or external library).
 *
 *   ── Markup contract (input variant — primary) ──
 *
 *   Time fields are <input type="text" inputmode="numeric"> — they
 *   accept direct typing (M3 §16.3 input variant). NOT <button>.
 *   The selected field gets `.is-selected` (dial focuses on it).
 *
 *   Period selector (AM/PM) is a radio + label pair — semantically
 *   correct (mutually-exclusive single-select), CSS-only, native
 *   keyboard arrow navigation. Same pattern as button group §28.
 *
 *     <div class="ax-time-picker">
 *       <span class="ax-time-picker__supporting">Enter time</span>
 *       <div class="ax-time-picker__inputs">
 *         <input class="ax-time-picker__field is-selected"
 *                type="text" inputmode="numeric" maxlength="2"
 *                aria-label="Hour" value="10">
 *         <span class="ax-time-picker__separator">:</span>
 *         <input class="ax-time-picker__field"
 *                type="text" inputmode="numeric" maxlength="2"
 *                aria-label="Minute" value="30">
 *
 *         <fieldset class="ax-time-picker__period">
 *           <legend class="u-vh">AM or PM</legend>
 *           <input type="radio" name="period" id="p-am"
 *                  class="ax-time-picker__period-input" checked>
 *           <label for="p-am" class="ax-time-picker__period-btn">AM</label>
 *           <input type="radio" name="period" id="p-pm"
 *                  class="ax-time-picker__period-input">
 *           <label for="p-pm" class="ax-time-picker__period-btn">PM</label>
 *         </fieldset>
 *       </div>
 *     </div>
 *
 *   ── 24-hour mode ──
 *   Add `.is-24h` to the time picker. Hides the period <fieldset>
 *   (no AM/PM in 24h). Same pattern as the button-controlled markup.
 * ============================================================ */
.ax-time-picker {
  display: inline-flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-lg); /* M3 §16.2 */
  background-color: var(--md-sys-color-surface-container-high);
  border-radius: var(--md-sys-shape-corner-extra-large); /* §16.2 */
  box-shadow: var(--md-sys-elevation-shadow-level3);
}

.ax-time-picker__supporting {
  color: var(--md-sys-color-on-surface-variant);
  font-family: var(--md-sys-typescale-label-medium-font);
  font-size: var(--md-sys-typescale-label-medium-size);
  line-height: var(--md-sys-typescale-label-medium-line-height);
  font-weight: var(--md-sys-typescale-label-medium-weight);
  letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}

.ax-time-picker__inputs {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  /* Zero out the inline line-box for inline children. flex children
   * with their own explicit `line-height` (the input fields, the
   * separator below) retain theirs — only inline text nodes and
   * default-line-height inline-level content are affected.
   *
   * Without this, the colon span's line-box (display-large
   * line-height ≈ 64px) creates a tall inline box around the
   * glyph; align-items: center centers that BOX, not the glyph,
   * so the colon visually drifts ~4px below the input numbers'
   * optical center. */
  line-height: 0;
}

/* Time field (HH or MM) — display-large in surface-container-highest box.
 * Accepts <input type="text" inputmode="numeric"> as the canonical
 * markup; <button> markup also supported for backward-compat. */
.ax-time-picker__field {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  inline-size: 96px;  /* M3 §16.3 input variant */
  block-size: 80px;
  padding: 0;
  border: 0;
  outline: 0;
  border-radius: var(--md-sys-shape-corner-small); /* §16.3 */
  background-color: var(--md-sys-color-surface-container-highest);
  color: var(--md-sys-color-on-surface);
  /* When the markup is <input>, text-align centers the typed value.
   * For <button> markup, justify-content already centers the label. */
  text-align: center;

  font-family:    var(--md-sys-typescale-display-large-font);
  font-size:      var(--md-sys-typescale-display-large-size);
  line-height:    var(--md-sys-typescale-display-large-line-height);
  font-weight:    var(--md-sys-typescale-display-large-weight);
  letter-spacing: var(--md-sys-typescale-display-large-tracking);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* When rendered as a real text input, allow typing — switch cursor */
input.ax-time-picker__field {
  cursor: text;
  /* Prevent iOS user-agent inner shadow + appearance default */
  appearance: none;
  -webkit-appearance: none;
}

.ax-time-picker__field.is-selected,
.ax-time-picker__field[aria-selected="true"] {
  background-color: var(--md-sys-color-primary-container);
  color: var(--md-sys-color-on-primary-container);
}

/* State layer — only on button-style fields (input has its own focus
 * via :focus-visible outline below). */
button.ax-time-picker__field::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity
    var(--md-sys-motion-curve-fast-effects-duration)
    var(--md-sys-motion-curve-fast-effects);
}
button.ax-time-picker__field:hover::before {
  opacity: var(--md-sys-state-hover-state-layer-opacity);
}
button.ax-time-picker__field:focus-visible::before {
  opacity: var(--md-sys-state-focus-state-layer-opacity);
}

/* Input variant focus — field gains 2px primary outline.
 * (Replaces global :focus-visible from base.css §5 with a tighter
 * outline that lives inside the box, matching display-large content.) */
input.ax-time-picker__field:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: -2px;
}

.ax-time-picker__separator {
  color: var(--md-sys-color-on-surface);
  font-family:    var(--md-sys-typescale-display-large-font);
  font-size:      var(--md-sys-typescale-display-large-size);
  /* line-height: 1 — tight box around the colon glyph for visual
   * alignment with input fields' centered digits. Typescale's
   * default line-height would inflate the box and push the glyph
   * off-center within the flex line. */
  line-height: 1;
  font-weight:    var(--md-sys-typescale-display-large-weight);
  user-select: none;
}

/* ── Period selector (AM/PM) — vertical 52×80 stacked ──────────
 * Markup is <fieldset> wrapping radio + label pairs. Same hidden-
 * input pattern as button group §28: visually-hide the input,
 * paint the label as the visible button, drive selected state via
 * `:checked + label`.
 *
 * Backward-compat: keeps `.is-selected` and `[aria-selected="true"]`
 * selectors so existing <button>-based markup keeps working
 * (those need JS toggle to flip state). */
.ax-time-picker__period {
  /* Reset <fieldset> defaults (margin, padding, min-inline-size).
   * Border KEPT (not reset to 0) — the period selector needs a
   * 1px outline-color border, see comment below. */
  margin: 0;
  padding: 0;
  min-inline-size: 0;

  display: inline-flex;
  flex-direction: column;
  inline-size: 52px; /* M3 §16.4 vertical (border-box: includes 1px borders) */

  /* Use BORDER, not outline. Outline at offset:-1px would draw the
   * line on TOP of children at paint time, but visually 1px next
   * to a saturated tertiary-container fill on the selected button
   * gets lost — the selected half reads as flush with the parent
   * edge.
   *
   * Border is part of the box geometry: it draws BEFORE children
   * paint, and children's content boxes (and their backgrounds)
   * sit INSIDE the border-box's content area. Selected button's
   * fill physically cannot extend over the border. The frame
   * stays visible regardless of selection state. */
  border: 1px solid var(--md-sys-color-outline);
  border-radius: var(--md-sys-shape-corner-small);
  overflow: hidden; /* clip child bg corners to inner border-radius */
}

/* Visually hidden radio inputs — same clip-path pattern as
 * §28 (button group). Keeps tab order + AT announcements. */
.ax-time-picker__period-input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  pointer-events: none;
}

.ax-time-picker__period-btn {
  position: relative;
  isolation: isolate;
  flex: 1 1 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  block-size: 40px; /* 80 / 2 = 40 each */
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);

  font-family:    var(--md-sys-typescale-title-medium-font);
  font-size:      var(--md-sys-typescale-title-medium-size);
  line-height:    var(--md-sys-typescale-title-medium-line-height);
  font-weight:    var(--md-sys-typescale-title-medium-weight);
  letter-spacing: var(--md-sys-typescale-title-medium-tracking);

  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    background-color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects),
    color
      var(--md-sys-motion-curve-fast-effects-duration)
      var(--md-sys-motion-curve-fast-effects);
}

/* AM/PM divider — first label gets a bottom border. The border
 * draws on TOP of any sibling background-color (per CSS painting
 * order: bg → border → content), so the line stays visible even
 * when AM is selected and its tertiary-container fill extends
 * to the bottom edge. Resolves "selected covers outline / no
 * divider between AM and PM" issue. */
.ax-time-picker__period-btn:first-of-type {
  border-block-end: 1px solid var(--md-sys-color-outline);
}

/* Selected — three triggers (radio :checked, .is-selected manual,
 * aria-selected for backward-compat) */
.ax-time-picker__period-input:checked + .ax-time-picker__period-btn,
.ax-time-picker__period-btn.is-selected,
.ax-time-picker__period-btn[aria-selected="true"] {
  background-color: var(--md-sys-color-tertiary-container);
  color: var(--md-sys-color-on-tertiary-container);
}

/* Focus — when the radio receives keyboard focus, surface a
 * focus ring on its sibling label (radio itself is hidden). */
.ax-time-picker__period-input:focus-visible + .ax-time-picker__period-btn {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: -2px;
  /* z-index lift so the outline isn't clipped by parent overflow:hidden */
  z-index: 1;
}

/* State layer — hover/focus tint on the label. Period-btn has no
 * isolation hook in the parent (parent has overflow:hidden which
 * clips ::before), so use a self-contained tint via background. */
.ax-time-picker__period-btn:hover {
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-surface-variant)
    calc(var(--md-sys-state-hover-state-layer-opacity) * 100%),
    transparent
  );
}
/* When selected + hovering, blend tint over tertiary-container instead */
.ax-time-picker__period-input:checked + .ax-time-picker__period-btn:hover,
.ax-time-picker__period-btn.is-selected:hover,
.ax-time-picker__period-btn[aria-selected="true"]:hover {
  background-color: color-mix(
    in srgb,
    var(--md-sys-color-on-tertiary-container)
    calc(var(--md-sys-state-hover-state-layer-opacity) * 100%),
    var(--md-sys-color-tertiary-container)
  );
}

/* 24-hour mode — hide period selector */
.ax-time-picker.is-24h .ax-time-picker__period {
  display: none;
}

/* --- Dial variant container (visual scaffolding only) ---
 * Identical to before. Author renders 12 hour labels positioned
 * on the circle + selector handle/track via SVG or absolute spans. */
.ax-time-picker__dial {
  position: relative;
  inline-size: 256px; /* M3 §16.5 */
  block-size: 256px;
  margin: 0 auto;
  border-radius: 50%;
  background-color: var(--md-sys-color-surface-container-highest);
}
/* Author renders 12 hour labels positioned on the circle + selector
   handle/track via SVG or absolute-positioned spans. CSS provides the
   container styling; the geometry is JS-driven. */

/* --- 24-hour variant ---
   Hides the AM/PM period selector. Hour field accepts 0-23 (author
   handles validation). Per Jetpack Compose `is24Hour=true` pattern. */
.ax-time-picker.is-24h .ax-time-picker__period {
  display: none;
}

/* ============================================================
 * Date / Time picker — usage patterns (reference, not enforced)
 *
 * The picker components above are visual primitives. Real-world use
 * follows three patterns from Jetpack Compose's M3 reference:
 *
 *   1. DOCKED — small inline form pattern
 *      <div class="text-field text-field--outlined">
 *        <div class="text-field__container">
 *          <input id="dob" readonly value="2026-05-15" />
 *          <label for="dob">생년월일</label>
 *          <button class="ax-icon-button ... text-field__trailing-icon"
 *                  aria-label="달력 열기"
 *                  data-popup-toggle="dob-popup">
 *            <svg/* calendar icon *\/></svg>
 *          </button>
 *        </div>
 *      </div>
 *      <div id="dob-popup" hidden class="ax-popup">
 *        <div class="ax-date-picker">...</div>
 *      </div>
 *      → Author wires popup show/hide to trailing icon click.
 *
 *   2. MODAL — full picker inside dialog
 *      <dialog class="dialog">
 *        <div class="dialog__content">
 *          <div class="ax-date-picker">...</div>
 *        </div>
 *        <div class="dialog__actions">
 *          <button class="ax-button is-text">취소</button>
 *          <button class="ax-button is-text">확인</button>
 *        </div>
 *      </dialog>
 *
 *   3. MODAL-INPUT — text-field-only inside dialog (no calendar grid)
 *      <dialog class="dialog">
 *        <div class="dialog__content">
 *          <div class="text-field text-field--outlined">...</div>
 *        </div>
 *        <div class="dialog__actions">...</div>
 *      </dialog>
 *
 * Time picker analogues: dial = MODAL, input = MODAL-INPUT.
 * ============================================================ */
