/* =============================================================
   DRYL Design System — Core Tokens & Primitives
   Glassmorphism · Dark · Indirect Light · Motion
   ============================================================= */

/* Registered so theme seeds interpolate — the derived color-mix chain glides
   on a theme change. Initial values mirror the default Nebula accent. */
@property --accent-a { syntax: "<color>"; inherits: true; initial-value: #7c5cff; }
@property --accent-b { syntax: "<color>"; inherits: true; initial-value: #22d3ee; }
@property --ai-a     { syntax: "<color>"; inherits: true; initial-value: #7c5cff; }
@property --ai-b     { syntax: "<color>"; inherits: true; initial-value: #22d3ee; }

/* Chart series tokens are registered too: explicit theme overrides
   (DrylTheme.Charts) glide like the seeds, and the initial-value doubles as
   the fallback palette in engines without relative color syntax. */
@property --chart-1 { syntax: "<color>"; inherits: true; initial-value: #8b7cf8; }
@property --chart-2 { syntax: "<color>"; inherits: true; initial-value: #0aa2b5; }
@property --chart-3 { syntax: "<color>"; inherits: true; initial-value: #bd7a12; }
@property --chart-4 { syntax: "<color>"; inherits: true; initial-value: #26a058; }
@property --chart-5 { syntax: "<color>"; inherits: true; initial-value: #d6428e; }
@property --chart-6 { syntax: "<color>"; inherits: true; initial-value: #5583e3; }

/* Neutral tokens are registered so a color-mode switch glides exactly like
   an accent-theme switch. Initial values mirror the built-in dark set. */
@property --ground   { syntax: "<color>"; inherits: true; initial-value: #000000; }
@property --bg-0     { syntax: "<color>"; inherits: true; initial-value: #000000; }
@property --bg-1     { syntax: "<color>"; inherits: true; initial-value: #07070a; }
@property --bg-2     { syntax: "<color>"; inherits: true; initial-value: #0c0c12; }
@property --bg-3     { syntax: "<color>"; inherits: true; initial-value: #14141c; }
@property --line        { syntax: "<color>"; inherits: true; initial-value: rgba(255,255,255,0.06); }
@property --line-strong { syntax: "<color>"; inherits: true; initial-value: rgba(255,255,255,0.12); }
@property --line-soft   { syntax: "<color>"; inherits: true; initial-value: rgba(255,255,255,0.04); }
@property --glass-1  { syntax: "<color>"; inherits: true; initial-value: rgba(255,255,255,0.03); }
@property --glass-2  { syntax: "<color>"; inherits: true; initial-value: rgba(255,255,255,0.05); }
@property --glass-3  { syntax: "<color>"; inherits: true; initial-value: rgba(255,255,255,0.08); }
@property --fg       { syntax: "<color>"; inherits: true; initial-value: #f4f4f7; }
@property --fg-muted { syntax: "<color>"; inherits: true; initial-value: rgba(244,244,247,0.62); }
@property --fg-dim   { syntax: "<color>"; inherits: true; initial-value: rgba(244,244,247,0.38); }
@property --fg-faint { syntax: "<color>"; inherits: true; initial-value: rgba(244,244,247,0.22); }

:root {
  /* Tell the browser this surface is dark — applies to native UA controls (<select> dropdowns, scrollbars, form autofill) so they don't ship light-mode defaults onto our glass. */
  color-scheme: dark;

  /* Palette — neutrals (pure black ground, layered glass) */
  --ground:        #000000;
  --bg-0:          #000000;
  --bg-1:          #07070a;
  --bg-2:          #0c0c12;
  --bg-3:          #14141c;
  --line:          rgba(255, 255, 255, 0.06);
  --line-strong:   rgba(255, 255, 255, 0.12);
  --line-soft:     rgba(255, 255, 255, 0.04);

  /* Glass surfaces */
  --glass-1:       rgba(255, 255, 255, 0.03);
  --glass-2:       rgba(255, 255, 255, 0.05);
  --glass-3:       rgba(255, 255, 255, 0.08);
  --glass-blur:    18px;

  /* Frosting comes in two tiers, because the browser charges for it per
     surface: a backdrop-filter re-samples and re-blurs whatever sits behind
     it, every frame the backdrop changes.

     A surface that FLOATS over scrolling content — topbar, sidebar, sticky
     header, popover, menu, tooltip, toast, dialog — earns that: the content
     sliding underneath is exactly what the frost is for.

     A surface that sits IN THE FLOW — a card, an expansion panel, a secondary
     button — has the page's own smooth background behind it. Blurring
     something already smooth changes almost nothing you can see (measured on
     the component overview: 0.84 of 255 average pixel difference), while 95
     of them on one page cost 3.4x the GPU draw of the whole page. So the
     in-flow tier keeps the translucent fill and skips the blur.

     A third case needs no token at all: a surface whose own background is
     opaque (menus, popovers and the bulk bar sit on --bg-2; the sticky table
     header on --panel-sticky at 0.92) can never show what is behind it, so a
     backdrop-filter there is invisible by definition and was pure cost — a
     wide table charged one per header cell. Frost belongs on translucent
     floating surfaces only.

     An app that wants frost everywhere can have it back in one line:
     --glass-fx-flow: blur(var(--glass-blur)) saturate(140%); */
  --glass-fx-float: blur(24px) saturate(160%);
  --glass-fx-flow:  none;

  /* Text */
  --fg:            #f4f4f7;
  --fg-muted:      rgba(244, 244, 247, 0.62);
  --fg-dim:        rgba(244, 244, 247, 0.38);
  --fg-faint:      rgba(244, 244, 247, 0.22);

  /* Accent — gradient violet → cyan (themeable seeds) */
  --accent-a:      #7c5cff;
  --accent-b:      #22d3ee;
  --accent:        var(--accent-a);
  --accent-soft:   color-mix(in srgb, var(--accent-a) 18%, transparent);
  --accent-line:   color-mix(in srgb, var(--accent-a) 45%, transparent);
  --accent-grad:   linear-gradient(135deg, var(--accent-a) 0%, var(--accent-b) 100%);
  --accent-grad-r: linear-gradient(135deg, var(--accent-b) 0%, var(--accent-a) 100%);

  /* AI accent — defaults to the brand accent (opt-in divergence via DrylTheme.AiAccent) */
  --ai-a:          var(--accent-a);
  --ai-b:          var(--accent-b);

  /* Semantic */
  --success:       #34d399;
  --warning:       #fbbf24;
  --danger:        #f87171;
  --info:          var(--accent-b);

  /* Charts — categorical series palette (dark-validated: lightness band,
     chroma floor, CVD adjacent ΔE ≥ 12, contrast ≥ 3:1 on black).
     Fixed order, assigned in sequence, never cycled; series 7+ → --fg-dim.
     Series 1/2 follow the theme seeds: hue from the accent, lightness snapped
     to the validated band (L 0.65) and chroma clamped, so any DrylTheme stays
     chart-legible without per-theme tuning. Slots 3–6 are fixed anchors;
     themes whose hues collide with them override via DrylTheme.Charts. */
  --chart-1:       oklch(from var(--accent-a) 0.65 clamp(0.1, c, 0.19) h);
  --chart-2:       oklch(from var(--accent-b) 0.65 clamp(0.1, c, 0.19) h);
  --chart-3:       #bd7a12;
  --chart-4:       #26a058;
  --chart-5:       #d6428e;
  --chart-6:       #5583e3;

  /* Type */
  --font-sans:     'Inter', 'Inter Placeholder', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono:     'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Scale */
  --r-xs: 6px;
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-xl: 28px;
  --r-pill: 999px;

  /* Spacing */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;

  /* Shadows / indirect light */
  --shadow-sm:  0 1px 2px rgba(0,0,0,0.4);
  --shadow-md:  0 8px 24px rgba(0,0,0,0.45), 0 2px 6px rgba(0,0,0,0.35);
  --shadow-lg:  0 24px 64px rgba(0,0,0,0.55), 0 8px 16px rgba(0,0,0,0.35);
  --glow-accent: 0 0 0 1px var(--accent-line),
                 0 8px 32px color-mix(in srgb, var(--accent-a) 35%, transparent),
                 0 0 64px color-mix(in srgb, var(--accent-b) 18%, transparent);
  --glow-soft:   0 0 60px color-mix(in srgb, var(--accent-a) 18%, transparent),
                 0 0 120px color-mix(in srgb, var(--accent-b) 8%, transparent);

  /* Background texture opacity (tokenized so light mode can dial it down) */
  --grain-opacity: 0.4;
  --aurora-opacity: 0.85;

  /* Motion */
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-viscous: cubic-bezier(0.45, 0.05, 0.15, 1); /* view-transition pseudo-elements only */
  --dur-fast:    140ms;
  --dur-med:     240ms;
  --dur-slow:    420ms;
  --dur-choreo:  900ms;  /* multi-step one-shot choreography; not for transitions */
  --delay-short: 200ms;  /* a beat's offset, so two things do not land at once */
  --delay-long:  800ms;  /* a hold before something retires itself */
  --reveal-step: 60ms;   /* stagger step between successive DrylReveal children */

  /* Layering */
  --z-sticky:    5;
  --z-drawer:   50;
  --z-modal:   100;
  --z-popover: 150;
  --z-toast:   200;
  --z-reconnect: 1000; /* DrylReconnectModal — above every other layer; the circuit is down, this must win */

  /* App chrome dimensions — consumed by .topbar / .sidebar / .app-shell.
     Overridable per layout via inline custom properties (DrylLayout.SidebarWidth). */
  --appbar-h:            60px;
  --sidebar-w:          260px;
  --sidebar-collapsed-w: 56px;

  /* Mode-dependent effect tokens — white highlights, sheens, shimmer sweeps
     and hover washes used across glass surfaces. Light-mode overrides live in
     both LIGHT-TOKEN-SET copies below; --on-accent* and --knob stay fixed
     because the accent-gradient fill they sit on doesn't change with mode. */
  --edge-hi:         rgba(255, 255, 255, 0.06);
  --edge-hi-strong:  rgba(255, 255, 255, 0.18);
  --ai-core:         #eef3ff;  /* hottest point of the AI aura (comet specular head) — near-white on dark ground; light copies use a saturated accent core */
  --ai-strength:     1;        /* aura presence multiplier — light copies raise it because translucent accents wash out on a bright ground */
  --sheen-grad:      linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015));
  --sheen-grad-soft: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0));
  --shimmer:         rgba(255, 255, 255, 0.22);
  --shimmer-strong:  rgba(255, 255, 255, 0.4);
  --hover-wash:      rgba(255, 255, 255, 0.02);
  --line-hover:      rgba(255, 255, 255, 0.18);
  --on-accent:       #ffffff;
  --on-accent-line:  rgba(255, 255, 255, 0.12);
  --on-accent-hi:    rgba(255, 255, 255, 0.2);
  --knob:            #ffffff;
  --backdrop:             rgba(0, 0, 0, 0.6);
  --backdrop-soft:        rgba(0, 0, 0, 0.4);
  --bar-bg:               rgba(0, 0, 0, 0.4);       /* DrylAppBar (flat) surface — themeable independently of the modal scrim */
  --press-wash:           rgba(0, 0, 0, 0.25);
  --scrollbar-thumb:       rgba(255, 255, 255, 0.08);
  --scrollbar-thumb-hover: rgba(255, 255, 255, 0.16);
  --code-bg:         rgba(0, 0, 0, 0.55);
  --code-fg:         #f4f4f7;

  /* Semantic-derived text/edge tokens (Task 7 literal sweep).
     --accent-fg / --danger-fg are the accent/danger-tinted TEXT colors used on
     glass (badges, code, AI indicator, danger menu items) — lighter than the
     solid --accent/--danger fills so they read on translucent surfaces.
     --*-hi are the bright gradient-endpoint tints used on toast progress bars.
     Their dark values are exact literals (the four originals don't sit on the
     seed-to-white mix line, so a color-mix here would shift dark rendering);
     the LIGHT-TOKEN-SET copies override them with seed color-mixes so light
     mode adapts to the semantic seeds. Do NOT remove those light overrides. */
  --accent-fg:  #d6cbff;
  --danger-fg:  #fca5a5;
  --success-hi: #6ee7b7;
  --warning-hi: #fcd34d;
  --danger-hi:  #fca5a5;
  --info-hi:    #67e8f9;

  /* DepthGlass edge/shadow tokens — the white inset highlights and black drop
     shadow used by .dg-* (DrylDepthGlass / DrylCard Depth). --depth-edge is
     the ~0.18 reference edge; --depth-edge-strong is the brightest inset
     (task-added, same pattern as the pair above — see task-7 report). */
  --depth-edge:        rgba(255, 255, 255, 0.18);
  --depth-edge-strong: rgba(255, 255, 255, 0.55);
  --depth-shadow:      rgba(0, 0, 0, 0.42);
  --accent-ico:        #c4b5fd;

  /* Semi-opaque panel surfaces — the near-solid dark planes cards, toasts,
     dialogs, tooltips and sticky headers sit on. Mode-dependent: the light
     set swaps them for near-solid light planes. */
  --panel-tint:        rgba(10, 10, 14, 0.4);
  --panel-soft:        rgba(10, 10, 14, 0.78);
  --panel-solid:       rgba(20, 20, 28, 0.95);
  --panel-sticky:      rgba(7, 7, 10, 0.92);
  /* Floating panels are glass, and glass has to let something through: at the
     0.95/0.97 these carried, the frost behind them had 3-5% to work with and
     a dialog read as a solid block. Now they are actually translucent, and
     they are the surfaces that earn the backdrop-filter (see --glass-fx-float). */
  --panel-grad:        linear-gradient(180deg, rgba(20, 20, 28, 0.72), rgba(10, 10, 14, 0.76));
  --panel-grad-strong: linear-gradient(180deg, rgba(20, 20, 28, 0.80), rgba(10, 10, 14, 0.84));
  --panel-float:       rgba(16, 16, 22, 0.76);
}

/* Theme seeds glide on change; instant for reduced-motion users. */
@media (prefers-reduced-motion: no-preference) {
  :root {
    transition: --accent-a var(--dur-slow) var(--ease-in-out),
                --accent-b var(--dur-slow) var(--ease-in-out),
                --ai-a var(--dur-slow) var(--ease-in-out),
                --ai-b var(--dur-slow) var(--ease-in-out),
                --chart-1 var(--dur-slow) var(--ease-in-out),
                --chart-2 var(--dur-slow) var(--ease-in-out),
                --chart-3 var(--dur-slow) var(--ease-in-out),
                --chart-4 var(--dur-slow) var(--ease-in-out),
                --chart-5 var(--dur-slow) var(--ease-in-out),
                --chart-6 var(--dur-slow) var(--ease-in-out),
                --ground var(--dur-slow) var(--ease-in-out),
                --bg-0 var(--dur-slow) var(--ease-in-out),
                --bg-1 var(--dur-slow) var(--ease-in-out),
                --bg-2 var(--dur-slow) var(--ease-in-out),
                --bg-3 var(--dur-slow) var(--ease-in-out),
                --line var(--dur-slow) var(--ease-in-out),
                --line-strong var(--dur-slow) var(--ease-in-out),
                --line-soft var(--dur-slow) var(--ease-in-out),
                --glass-1 var(--dur-slow) var(--ease-in-out),
                --glass-2 var(--dur-slow) var(--ease-in-out),
                --glass-3 var(--dur-slow) var(--ease-in-out),
                --fg var(--dur-slow) var(--ease-in-out),
                --fg-muted var(--dur-slow) var(--ease-in-out),
                --fg-dim var(--dur-slow) var(--ease-in-out),
                --fg-faint var(--dur-slow) var(--ease-in-out);
  }
}

/* =============================================================
   LIGHT MODE — token overrides ("Aurora Light").
   ⚠ THE BLOCK BELOW EXISTS TWICE (system media query + explicit
   attribute). Keep both copies IDENTICAL — checked by
   scripts/check-light-sync.mjs.
   ============================================================= */
@media (prefers-color-scheme: light) {
  :root:not([data-dryl-mode="dark"]) {
    /* LIGHT-TOKEN-SET — copy 1/2 */
    color-scheme: light;
    --ground:        #f2f2f9;
    --bg-0:          #f2f2f9;
    --bg-1:          #f5f5fa;
    --bg-2:          #f8f8fc;
    --bg-3:          #fbfbfe;
    --line:          rgba(18, 22, 40, 0.08);
    --line-strong:   rgba(18, 22, 40, 0.14);
    --line-soft:     rgba(18, 22, 40, 0.05);
    --glass-1:       rgba(255, 255, 255, 0.55);
    --glass-2:       rgba(255, 255, 255, 0.62);
    --glass-3:       rgba(255, 255, 255, 0.72);
    --fg:            #15151c;
    --fg-muted:      rgba(21, 21, 28, 0.62);
    --fg-dim:        rgba(21, 21, 28, 0.38);
    --fg-faint:      rgba(21, 21, 28, 0.22);
    --success:       #0e8a4d;
    --warning:       #b45309;
    --danger:        #dc2626;
    --info:          #0e7490;
    --chart-1:       oklch(from var(--accent-a) 0.52 clamp(0.1, c, 0.17) h);
    --chart-2:       oklch(from var(--accent-b) 0.52 clamp(0.1, c, 0.17) h);
    --chart-3:       #96610e;
    --chart-4:       #1d7f46;
    --chart-5:       #b0316f;
    --chart-6:       #3a63c4;
    --shadow-sm:  0 1px 2px rgba(28, 24, 70, 0.08);
    --shadow-md:  0 8px 24px rgba(28, 24, 70, 0.10), 0 2px 6px rgba(28, 24, 70, 0.06);
    --shadow-lg:  0 24px 64px rgba(28, 24, 70, 0.15), 0 8px 16px rgba(28, 24, 70, 0.08);
    --glow-accent: 0 0 0 1px var(--accent-line),
                   0 8px 32px color-mix(in srgb, var(--accent-a) 22%, transparent),
                   0 0 64px color-mix(in srgb, var(--accent-b) 12%, transparent);
    --glow-soft:   0 0 60px color-mix(in srgb, var(--accent-a) 12%, transparent),
                   0 0 120px color-mix(in srgb, var(--accent-b) 6%, transparent);
    --grain-opacity: 0.25;
    --aurora-opacity: 0.5;
    --edge-hi:         rgba(255, 255, 255, 0.85);
    --edge-hi-strong:  rgba(255, 255, 255, 0.95);
    --ai-core:         color-mix(in srgb, var(--ai-a) 82%, #000 18%);
    --ai-strength:     1.7;
    --sheen-grad:      linear-gradient(180deg, rgba(255,255,255,0.55), rgba(255,255,255,0.15));
    --sheen-grad-soft: linear-gradient(180deg, rgba(255,255,255,0.35), rgba(255,255,255,0));
    --shimmer:         rgba(255, 255, 255, 0.8);
    --shimmer-strong:  rgba(255, 255, 255, 0.95);
    --hover-wash:      rgba(18, 22, 40, 0.03);
    --line-hover:      rgba(18, 22, 40, 0.24);
    --backdrop:             rgba(26, 28, 48, 0.35);
    --backdrop-soft:        rgba(26, 28, 48, 0.22);
    --bar-bg:               rgba(252, 252, 255, 0.72);
    --press-wash:           rgba(18, 22, 40, 0.08);
    --scrollbar-thumb:       rgba(18, 22, 40, 0.18);
    --scrollbar-thumb-hover: rgba(18, 22, 40, 0.3);
    --code-bg:         #14141d;
    --accent-fg:  color-mix(in srgb, var(--accent-a) 72%, #10102a);
    --danger-fg:  #b91c1c;
    --depth-edge:        rgba(255, 255, 255, 0.9);
    --depth-edge-strong: rgba(255, 255, 255, 0.95);
    --depth-shadow:      rgba(28, 24, 70, 0.16);
    --success-hi: color-mix(in srgb, var(--success) 69%, white);
    --warning-hi: color-mix(in srgb, var(--warning) 78%, white);
    --danger-hi:  color-mix(in srgb, var(--danger) 63%, white);
    --info-hi:    color-mix(in srgb, var(--info) 66%, white);
    --accent-ico: color-mix(in srgb, var(--accent-a) 78%, #10102a);
    --panel-tint:        rgba(255, 255, 255, 0.4);
    --panel-soft:        rgba(255, 255, 255, 0.78);
    --panel-solid:       rgba(252, 252, 255, 0.95);
    --panel-sticky:      rgba(245, 245, 250, 0.92);
    --panel-grad:        linear-gradient(180deg, rgba(252, 252, 255, 0.72), rgba(243, 243, 250, 0.76));
    --panel-grad-strong: linear-gradient(180deg, rgba(252, 252, 255, 0.80), rgba(243, 243, 250, 0.84));
    --panel-float:       rgba(252, 252, 255, 0.76);
  }
}
:root[data-dryl-mode="light"] {
  /* LIGHT-TOKEN-SET — copy 2/2 */
  color-scheme: light;
  --ground:        #f2f2f9;
  --bg-0:          #f2f2f9;
  --bg-1:          #f5f5fa;
  --bg-2:          #f8f8fc;
  --bg-3:          #fbfbfe;
  --line:          rgba(18, 22, 40, 0.08);
  --line-strong:   rgba(18, 22, 40, 0.14);
  --line-soft:     rgba(18, 22, 40, 0.05);
  --glass-1:       rgba(255, 255, 255, 0.55);
  --glass-2:       rgba(255, 255, 255, 0.62);
  --glass-3:       rgba(255, 255, 255, 0.72);
  --fg:            #15151c;
  --fg-muted:      rgba(21, 21, 28, 0.62);
  --fg-dim:        rgba(21, 21, 28, 0.38);
  --fg-faint:      rgba(21, 21, 28, 0.22);
  --success:       #0e8a4d;
  --warning:       #b45309;
  --danger:        #dc2626;
  --info:          #0e7490;
  --chart-1:       oklch(from var(--accent-a) 0.52 clamp(0.1, c, 0.17) h);
  --chart-2:       oklch(from var(--accent-b) 0.52 clamp(0.1, c, 0.17) h);
  --chart-3:       #96610e;
  --chart-4:       #1d7f46;
  --chart-5:       #b0316f;
  --chart-6:       #3a63c4;
  --shadow-sm:  0 1px 2px rgba(28, 24, 70, 0.08);
  --shadow-md:  0 8px 24px rgba(28, 24, 70, 0.10), 0 2px 6px rgba(28, 24, 70, 0.06);
  --shadow-lg:  0 24px 64px rgba(28, 24, 70, 0.15), 0 8px 16px rgba(28, 24, 70, 0.08);
  --glow-accent: 0 0 0 1px var(--accent-line),
                 0 8px 32px color-mix(in srgb, var(--accent-a) 22%, transparent),
                 0 0 64px color-mix(in srgb, var(--accent-b) 12%, transparent);
  --glow-soft:   0 0 60px color-mix(in srgb, var(--accent-a) 12%, transparent),
                 0 0 120px color-mix(in srgb, var(--accent-b) 6%, transparent);
  --grain-opacity: 0.25;
  --aurora-opacity: 0.5;
  --edge-hi:         rgba(255, 255, 255, 0.85);
  --edge-hi-strong:  rgba(255, 255, 255, 0.95);
  --ai-core:         color-mix(in srgb, var(--ai-a) 82%, #000 18%);
  --ai-strength:     1.7;
  --sheen-grad:      linear-gradient(180deg, rgba(255,255,255,0.55), rgba(255,255,255,0.15));
  --sheen-grad-soft: linear-gradient(180deg, rgba(255,255,255,0.35), rgba(255,255,255,0));
  --shimmer:         rgba(255, 255, 255, 0.8);
  --shimmer-strong:  rgba(255, 255, 255, 0.95);
  --hover-wash:      rgba(18, 22, 40, 0.03);
  --line-hover:      rgba(18, 22, 40, 0.24);
  --backdrop:             rgba(26, 28, 48, 0.35);
  --backdrop-soft:        rgba(26, 28, 48, 0.22);
  --bar-bg:               rgba(252, 252, 255, 0.72);
  --press-wash:           rgba(18, 22, 40, 0.08);
  --scrollbar-thumb:       rgba(18, 22, 40, 0.18);
  --scrollbar-thumb-hover: rgba(18, 22, 40, 0.3);
  --code-bg:         #14141d;
  --accent-fg:  color-mix(in srgb, var(--accent-a) 72%, #10102a);
  --danger-fg:  #b91c1c;
  --depth-edge:        rgba(255, 255, 255, 0.9);
  --depth-edge-strong: rgba(255, 255, 255, 0.95);
  --depth-shadow:      rgba(28, 24, 70, 0.16);
  --success-hi: color-mix(in srgb, var(--success) 69%, white);
  --warning-hi: color-mix(in srgb, var(--warning) 78%, white);
  --danger-hi:  color-mix(in srgb, var(--danger) 63%, white);
  --info-hi:    color-mix(in srgb, var(--info) 66%, white);
  --accent-ico: color-mix(in srgb, var(--accent-a) 78%, #10102a);
  --panel-tint:        rgba(255, 255, 255, 0.4);
  --panel-soft:        rgba(255, 255, 255, 0.78);
  --panel-solid:       rgba(252, 252, 255, 0.95);
  --panel-sticky:      rgba(245, 245, 250, 0.92);
  --panel-grad:        linear-gradient(180deg, rgba(252, 252, 255, 0.72), rgba(243, 243, 250, 0.76));
  --panel-grad-strong: linear-gradient(180deg, rgba(252, 252, 255, 0.80), rgba(243, 243, 250, 0.84));
  --panel-float:       rgba(252, 252, 255, 0.76);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ground);
  color: var(--fg);
  font-family: var(--font-sans);
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 14px;
  line-height: 1.5;
  min-height: 100vh;
  overflow-x: hidden;
}

body {
  background:
    radial-gradient(1200px 800px at 15% -10%, color-mix(in srgb, var(--accent-a) 12%, transparent), transparent 60%),
    radial-gradient(1000px 700px at 100% 0%, color-mix(in srgb, var(--accent-b) 8%, transparent), transparent 60%),
    radial-gradient(900px 700px at 50% 110%, color-mix(in srgb, var(--accent-a) 6%, transparent), transparent 60%),
    var(--ground);
  background-attachment: fixed;
}

/* Subtle film grain over everything */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.04 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}

/* Aurora — slow drifting orbs in the deep background.
   The blur lives on each orb (not the container): the orbs animate
   transform-only, so each one is a self-contained composited layer whose
   blurred texture the GPU just moves — a container-level filter would force
   re-filtering the whole oversized surface every frame of the drift. */
.aurora {
  position: fixed;
  inset: -20vh -20vw;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: var(--aurora-opacity);
}
.aurora::before,
.aurora::after,
.aurora .orb {
  content: '';
  position: absolute;
  border-radius: 50%;
  mix-blend-mode: screen;
  filter: blur(70px) saturate(140%);
}
.aurora::before {
  width: 50vw;
  height: 50vw;
  left: -10vw;
  top: 10vh;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent-a) 55%, transparent), transparent 60%);
  animation: drift-a 22s var(--ease-in-out) infinite alternate;
}
.aurora::after {
  width: 45vw;
  height: 45vw;
  right: -5vw;
  top: -10vh;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent-b) 38%, transparent), transparent 60%);
  animation: drift-b 28s var(--ease-in-out) infinite alternate;
}
.aurora .orb {
  width: 40vw;
  height: 40vw;
  left: 40%;
  bottom: -20vh;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent-a) 30%, transparent), transparent 60%);
  animation: drift-c 34s var(--ease-in-out) infinite alternate;
}

@keyframes drift-a {
  0%   { transform: translate3d(0,0,0) scale(1); }
  100% { transform: translate3d(15vw, 10vh, 0) scale(1.15); }
}
@keyframes drift-b {
  0%   { transform: translate3d(0,0,0) scale(1); }
  100% { transform: translate3d(-12vw, 8vh, 0) scale(1.1); }
}
@keyframes drift-c {
  0%   { transform: translate3d(0,0,0) scale(1); }
  100% { transform: translate3d(-8vw, -12vh, 0) scale(1.2); }
}

/* App scaffold */
#root { position: relative; z-index: 2; min-height: 100vh; }

/* ============== TYPE ============== */
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -0.02em; }
h1 { font-size: 56px; line-height: 1.05; letter-spacing: -0.035em; font-weight: 700; }
h2 { font-size: 32px; line-height: 1.15; letter-spacing: -0.025em; }
h3 { font-size: 20px; line-height: 1.3; }
h4 { font-size: 15px; line-height: 1.4; }

.eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-dim);
  font-weight: 500;
}

.mono { font-family: var(--font-mono); }

.gradient-text {
  background: var(--accent-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

p { margin: 0; color: var(--fg-muted); }
.lead { font-size: 17px; line-height: 1.55; color: var(--fg-muted); max-width: 64ch; }

a { color: var(--accent-b); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 4px; }

/* ============== GLASS PRIMITIVES ============== */
.glass {
  background: var(--glass-1);
  border: 1px solid var(--line);
  backdrop-filter: var(--glass-fx-flow);
  -webkit-backdrop-filter: var(--glass-fx-flow);
  border-radius: var(--r-lg);
}

.glass-card {
  position: relative;
  background:
    var(--sheen-grad),
    var(--panel-tint);
  border: 1px solid var(--line);
  backdrop-filter: var(--glass-fx-flow);
  -webkit-backdrop-filter: var(--glass-fx-flow);
  border-radius: var(--r-lg);
  box-shadow:
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-md);
  transition: transform var(--dur-med) var(--ease-out),
              border-color var(--dur-med) var(--ease-out),
              box-shadow var(--dur-med) var(--ease-out);
}
.glass-card::after {
  /* radial glow behind the card */
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  background: radial-gradient(80% 60% at 50% 0%, transparent, transparent 70%);
  pointer-events: none;
  transition: background var(--dur-med) var(--ease-out);
  z-index: -1;
}
.glass-card:hover::after {
  background: radial-gradient(80% 60% at 50% 0%, color-mix(in srgb, var(--accent-a) 18%, transparent), transparent 70%);
}
.glass-card:hover {
  border-color: var(--line-strong);
}

/* Glass-card padding presets (consumed by DrylCard.CardPadding) */
.glass-card.p-0       { padding: 0; }
.glass-card.p-tight   { padding: var(--sp-4); }
.glass-card.p-default { padding: var(--sp-5); }
.glass-card.p-loose   { padding: var(--sp-6); }

/* ============== BUTTONS ============== */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 38px;
  padding: 0 16px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  background: transparent;
  color: var(--fg);
  font: 500 13px/1 var(--font-sans);
  letter-spacing: -0.005em;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: transform var(--dur-med) var(--ease-spring),
              background var(--dur-med) var(--ease-out),
              border-color var(--dur-med) var(--ease-out),
              box-shadow var(--dur-med) var(--ease-out),
              color var(--dur-med) var(--ease-out);
  isolation: isolate;
}
/* Press: drop + shrink, then spring back on release (transform easing above). */
.btn:not(:disabled):active { transform: translateY(0) scale(0.96); }

/* Glass sheen — a soft reflection sweeps across the surface on hover. Clipped by
   border-radius (no overflow:hidden, so the AI-aura ring is untouched). Not on the
   chromeless Ghost variant, and never while disabled. */
.btn:not(.btn-ghost)::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(
    105deg,
    transparent 38%,
    var(--shimmer) 50%,
    transparent 62%
  );
  background-size: 250% 100%;
  background-position: 150% 0;
  opacity: 0;
  pointer-events: none;
  z-index: 2;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.btn:not(.btn-ghost):not(:disabled):hover::after {
  opacity: 1;
  background-position: -50% 0;
  transition: background-position var(--dur-slow) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}

/* Icons spring on hover: trailing slides forward (forward affordance), leading pops,
   icon-only scales. Marker classes come from DrylButton's leading/trailing slots. */
.btn .btn-ico-lead,
.btn .btn-ico-trail { transition: transform var(--dur-med) var(--ease-spring); }
.btn:not(:disabled):hover .btn-ico-trail { transform: translateX(3px); }
.btn:not(:disabled):hover .btn-ico-lead  { transform: translateX(-1px) scale(1.06); }
.btn-icon:not(:disabled):hover .btn-ico-lead,
.btn-icon:not(:disabled):hover .btn-ico-trail { transform: scale(1.12); }

/* Primary — quiet at rest, expressive on engagement. It stands on the same glass
   as Secondary and is told apart from it by one thing: a hairline drawn from
   --accent-grad. At rest it carries no shadow at all, so the whole motion budget
   is spent on hover, where the accent washes INTO the surface as a tint and the
   glow ignites from nothing. The tint stays a tint: the fill is Bold's, and
   DESIGN-08 allows the saturated fill on that one variant only.

   The resting tint is declared as the same gradient at zero accent rather than
   omitted, so the hover interpolates two images of one shape instead of swapping
   a background-image in — the latter would step. */
.btn-primary {
  background-color: var(--glass-2);
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--accent-a) 0%, transparent),
    color-mix(in srgb, var(--accent-b) 0%, transparent));
  backdrop-filter: var(--glass-fx-flow);
  color: var(--fg);
}
/* The hairline is a masked pseudo-element rather than a border-color: a border
   cannot carry a gradient and follow --r-md at the same time, and border-image
   ignores the radius outright. Inherits the radius, so --r-sm at ButtonSize.Small
   is followed without restating it. ::after is the sheen's. */
.btn-primary::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--accent-grad);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 1;
}
.btn-primary:hover {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--accent-a) 26%, transparent),
    color-mix(in srgb, var(--accent-b) 22%, transparent));
  box-shadow: 0 8px 30px color-mix(in srgb, var(--accent-a) 28%, transparent);
  transform: translateY(-1px);
}

/* Bold — the filled treatment Primary used to carry, unchanged, for the rare hero
   call to action. This is the single surface DESIGN-08 permits the accent to fill. */
.btn-bold {
  background: var(--accent-grad);
  color: var(--on-accent);
  border-color: var(--on-accent-line);
  box-shadow:
    0 1px 0 var(--on-accent-hi) inset,
    0 0 0 1px color-mix(in srgb, var(--accent-a) 25%, transparent),
    0 6px 24px color-mix(in srgb, var(--accent-a) 35%, transparent),
    0 0 48px color-mix(in srgb, var(--accent-b) 18%, transparent);
}
.btn-bold:hover {
  box-shadow:
    0 1px 0 var(--on-accent-hi) inset,
    0 0 0 1px color-mix(in srgb, var(--accent-a) 40%, transparent),
    0 10px 36px color-mix(in srgb, var(--accent-a) 50%, transparent),
    0 0 64px color-mix(in srgb, var(--accent-b) 28%, transparent);
  transform: translateY(-1px);
}

.btn-secondary {
  background: var(--glass-2);
  border-color: var(--line-strong);
  backdrop-filter: var(--glass-fx-flow);
}
.btn-secondary:hover {
  background: var(--glass-3);
  border-color: var(--accent-line);
  box-shadow: 0 0 0 1px var(--accent-line), 0 0 24px color-mix(in srgb, var(--accent-a) 18%, transparent);
}

.btn-ghost {
  background: transparent;
  color: var(--fg-muted);
}
.btn-ghost:hover {
  background: var(--glass-2);
  color: var(--fg);
}

.btn-danger {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
  color: var(--danger-fg);
}
.btn-danger:hover {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
  box-shadow: 0 0 24px color-mix(in srgb, var(--danger) 25%, transparent);
}

.btn-icon {
  width: 38px;
  padding: 0;
}

/* Pressed / toggled state — driven by DrylButton's Pressed parameter
   (aria-pressed="true"). The on-state is relative to the variant it sits on: one
   step above that variant's rest, never above its own hover. The rule below is
   that step for the variants that carry no accent at rest — Secondary and Ghost —
   and reads like a secondary switched on. The three variants that do carry
   something at rest override it, because this rule replaces box-shadow wholesale
   and would otherwise invert their loudness.

   The overrides are gated behind :not(:disabled) rather than ordered around
   .btn:disabled, so the disabled rule keeps stripping the glow from its own,
   lower specificity. Each carries a :hover companion: a bare .btn-x.btn--active
   ties with .btn-x:hover and, sitting later, would freeze the hover it ties. */
.btn--active {
  border-color: var(--accent-line);
  box-shadow: 0 0 0 1px var(--accent-line), 0 0 20px color-mix(in srgb, var(--accent-a) 18%, transparent);
  color: var(--fg);
}

/* Primary already draws a gradient hairline and carries no resting glow on
   purpose. The rule above would add a flat ring inside that hairline and a glow
   the variant spent its whole budget avoiding, leaving the switched-on state
   louder than the action beside it. On means instead that the hover tint has
   washed in and stayed — below hover's strength, so engagement still has
   somewhere to go. */
.btn-primary.btn--active:not(:disabled) {
  border-color: transparent;
  box-shadow: none;
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--accent-a) 16%, transparent),
    color-mix(in srgb, var(--accent-b) 13%, transparent));
}
.btn-primary.btn--active:not(:disabled):hover {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--accent-a) 26%, transparent),
    color-mix(in srgb, var(--accent-b) 22%, transparent));
  box-shadow: 0 8px 30px color-mix(in srgb, var(--accent-a) 28%, transparent);
}

/* Bold is already the loudest surface the library has. The rule above would trade
   its four-layer glow for a thin accent ring and leave a toggled Bold quieter
   than an untoggled one. It keeps its own glow and marks on with an inset ring,
   so the state reads as pressed in rather than lit up. The resting inset
   highlight goes: a 2px ring covers the 1px line it would draw. */
.btn-bold.btn--active:not(:disabled) {
  border-color: var(--on-accent-line);
  box-shadow:
    inset 0 0 0 2px var(--on-accent-line),
    0 6px 24px color-mix(in srgb, var(--accent-a) 35%, transparent),
    0 0 48px color-mix(in srgb, var(--accent-b) 18%, transparent);
}
.btn-bold.btn--active:not(:disabled):hover {
  box-shadow:
    inset 0 0 0 2px var(--on-accent-line),
    0 10px 36px color-mix(in srgb, var(--accent-a) 50%, transparent),
    0 0 64px color-mix(in srgb, var(--accent-b) 28%, transparent);
}

/* Danger's colour is the one thing a toggle may not overwrite: the rule above
   rings it in --accent-line and reads as an accent switch on a destructive
   action. Same shape, drawn from --danger. */
.btn-danger.btn--active:not(:disabled) {
  border-color: color-mix(in srgb, var(--danger) 55%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--danger) 55%, transparent),
              0 0 20px color-mix(in srgb, var(--danger) 22%, transparent);
  color: var(--danger-fg);
}
.btn-danger.btn--active:not(:disabled):hover {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--danger) 55%, transparent),
              0 0 28px color-mix(in srgb, var(--danger) 32%, transparent);
}

/* Disabled — must read clearly as inert across every variant: dimmed, flat,
   desaturated, and stripped of the accent glow that otherwise makes a primary
   look active. Hover sheen, press and icon springs are already gated behind
   :not(:disabled). */
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
  filter: grayscale(0.5);
  transform: none;
}

.btn-sm { height: 30px; padding: 0 12px; font-size: 12px; border-radius: var(--r-sm); }
.btn-sm.btn-icon { width: 30px; padding: 0; }
.btn-lg { height: 46px; padding: 0 22px; font-size: 14px; }
.btn-lg.btn-icon { width: 46px; padding: 0; }

/* ============== BUTTON GROUP ============== */
/* Joins adjacent DrylButtons into a segmented control: inner corners are
   flattened and borders overlap by 1px so the cluster reads as one outline.
   Outer corners keep each button's own radius (so --sm / --lg are honoured). */
.btn-group { display: inline-flex; align-items: stretch; }
.btn-group--block { display: flex; width: 100%; }
.btn-group--block > .btn { flex: 1 1 0; }

.btn-group > .btn:not(:first-child) {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  margin-left: -1px;
}
.btn-group > .btn:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
/* Raise the interacted segment so its border / glow isn't clipped by the
   neighbour overlapping it. */
.btn-group > .btn:hover,
.btn-group > .btn:focus-visible,
.btn-group > .btn.btn--active { z-index: 1; }

/* ============== SPLIT BUTTON ============== */
/* A main action joined to a caret (the caret lives inside a DrylMenu's
   popover-anchor, hence the descendant selectors for the second segment). */
.split-btn { display: inline-flex; align-items: stretch; }
.split-btn--block { display: flex; width: 100%; }
.split-btn--block > .btn { flex: 1 1 auto; }

.split-btn > .btn {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.split-btn > .popover-anchor { margin-left: -1px; }
.split-btn > .popover-anchor .btn {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.split-btn > .btn:hover,
.split-btn > .btn:focus-visible,
.split-btn > .popover-anchor .btn:hover,
.split-btn > .popover-anchor .btn:focus-visible { z-index: 1; }

/* ---- Color-mode toggle ---------------------------------------- */
.color-mode-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--glass-1);
  color: var(--fg-muted);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.color-mode-toggle:hover { border-color: var(--line-hover); color: var(--fg); box-shadow: var(--shadow-sm); }
.color-mode-toggle .cmt-core,
.color-mode-toggle .cmt-rays,
.color-mode-toggle .cmt-moon {
  transform-origin: 12px 12px;
  transition: opacity var(--dur-med) var(--ease-out),
              transform var(--dur-med) var(--ease-spring);
}
/* chosen: light → sun */
.color-mode-toggle.is-light .cmt-moon { opacity: 0; transform: rotate(-40deg) scale(0.6); }
.color-mode-toggle.is-light .cmt-core,
.color-mode-toggle.is-light .cmt-rays { opacity: 1; transform: none; }
/* chosen: dark → moon */
.color-mode-toggle.is-dark .cmt-moon { opacity: 1; transform: none; }
.color-mode-toggle.is-dark .cmt-core,
.color-mode-toggle.is-dark .cmt-rays { opacity: 0; transform: rotate(40deg) scale(0.5); }
/* chosen: system → moon base + auto badge */
.color-mode-toggle.is-system .cmt-moon { opacity: 1; transform: none; }
.color-mode-toggle.is-system .cmt-core,
.color-mode-toggle.is-system .cmt-rays { opacity: 0; transform: rotate(40deg) scale(0.5); }
.color-mode-toggle .cmt-auto {
  position: absolute; right: -3px; bottom: -3px;
  font-size: 8px; font-weight: 700; line-height: 1;
  padding: 2px 3px; border-radius: var(--r-xs);
  background: var(--accent-grad); color: var(--on-accent);
  opacity: 0; transform: scale(0.5);
  transition: opacity var(--dur-med) var(--ease-out),
              transform var(--dur-med) var(--ease-spring);
}
.color-mode-toggle.is-system .cmt-auto { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .color-mode-toggle .cmt-core, .color-mode-toggle .cmt-rays,
  .color-mode-toggle .cmt-moon, .color-mode-toggle .cmt-auto { transition: none; }
}

/* ---- Screen-reader-only utility --------------------------------
   Visually hidden but still reachable by assistive tech; layout-only,
   no color tokens needed. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ============== INPUTS ============== */
.input,
.textarea,
.select {
  width: 100%;
  height: 38px;
  padding: 0 14px;
  border-radius: var(--r-md);
  background: var(--glass-1);
  border: 1px solid var(--line-strong);
  color: var(--fg);
  font: 400 13px/1 var(--font-sans);
  transition: border-color var(--dur-med) var(--ease-out),
              background var(--dur-med) var(--ease-out),
              box-shadow var(--dur-med) var(--ease-out);
  outline: none;
}
.input::placeholder, .textarea::placeholder { color: var(--fg-dim); }
.input:hover, .textarea:hover, .select:hover { border-color: var(--line-hover); }
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--accent-line);
  background: var(--glass-2);
  /* 1px flush ring fuses with the border into one crisp accent edge, then a soft
     halo, then ambient glow — reads as a single confident corner, not a thin line
     with a detached block around it. */
  box-shadow: 0 0 0 1px var(--accent-line),
              0 0 0 4px color-mix(in srgb, var(--accent-a) 12%, transparent),
              0 0 24px color-mix(in srgb, var(--accent-a) 18%, transparent);
}
/* The global :focus-visible rule adds a bright cyan outline that clashes with the
   violet glow above. Suppress it here — the glow already satisfies WCAG focus visibility. */
.input:focus-visible,
.textarea:focus-visible,
.select:focus-visible { outline: none; }
.textarea {
  height: auto;
  padding: 12px 14px;
  resize: vertical;
  line-height: 1.5;
  min-height: 96px;
}

/* Select — strip native arrow, draw our own, give <option> a real dark surface */
.select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a0a0b0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 36px;
  cursor: pointer;
}
.select option {
  background: var(--bg-2);
  color: var(--fg);
}

.field-label {
  display: block;
  font-size: 12px;
  color: var(--fg-muted);
  margin-bottom: 6px;
  font-weight: 500;
}

/* Input field wrapper (label + input + icons + helper) */
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.input-wrapper {
  position: relative;
  display: flex;
  align-items: center;
}

/* Textarea wrapper — gives <textarea> a positionable host so AI primitives
   (and future extensions) have something to layer on. Visually transparent. */
.textarea-wrapper {
  position: relative;
  display: flex;
  width: 100%;
}
.textarea-wrapper > .textarea { flex: 1; }

/* AI mode on inputs needs an explicit radius on the wrapper so .ai-aura-ring
   (which uses border-radius: inherit) traces the input's rounded corners. */
.input-wrapper.ai-aura,
.textarea-wrapper.ai-aura {
  border-radius: var(--r-md);
}

/* AI mode on the file-upload drop zone — echo the .file-drop radius (--r-lg)
   on the wrapper so the rotating ring and breathing glow trace its rounded
   corners instead of a square box. */
.file-upload-wrapper.ai-aura {
  border-radius: var(--r-lg);
}

/* AI mode on radio groups — give the wrapper a surface + radius so the
   rotating ring has rounded corners and the group reads as a contained panel. */
.radio-group-wrapper.ai-aura {
  border-radius: var(--r-lg);
  padding: var(--sp-3);
  background: var(--glass-1);
}

/* AI mode on the rating stars — a radius + inline-flex box so the ring/glow
   trace the stars as a contained pill instead of a square that overflows them. */
.rating-wrapper.ai-aura {
  display: inline-flex;
  border-radius: var(--r-md);
  padding: var(--sp-2);
}

/* AI mode on buttons uses the shared aura vocabulary (.ai-aura-ring / -glow / -wash
   via <DrylAuraElements>), same as every other host — no button-specific ring. The
   button carries no overflow:hidden and is position:relative + isolate, so the
   default primitive sizing traces its rounded box and glows outside it untouched. */

@media (prefers-reduced-motion: reduce) {
  .btn:not(.btn-ghost)::after { display: none; }
  .btn .btn-ico-lead,
  .btn .btn-ico-trail { transition: none; }
  .btn:hover .btn-ico-lead,
  .btn:hover .btn-ico-trail { transform: none; }
}

.input-wrapper .input-icon,
.tbl-search .input-icon {
  position: absolute;
  color: var(--fg-dim);
  pointer-events: none;
  transition: color var(--dur-med) var(--ease-out);
}
/* Vertically center any absolutely-positioned input icon — the leading glyph and,
   crucially, the trailing spinner. Without an explicit top the trailing element
   resolves its static position at the wrapper's baseline and sinks to the bottom.
   line-height:0 collapses wrapper spans (e.g. the spinner's positioning span) to
   their content height so the centering isn't skewed by inline baseline slack. */
.input-wrapper .input-icon { top: 50%; transform: translateY(-50%); line-height: 0; }
.input-wrapper:focus-within .input-icon,
.tbl-search:focus-within .input-icon { color: var(--fg-muted); }
.input-wrapper .input-icon-leading  { left: 12px; }
.input-wrapper .input-icon-trailing { right: 12px; }

.input.has-leading-icon  { padding-left: 38px; }
.input.has-trailing-icon { padding-right: 38px; }

.input.input-error,
.textarea.input-error,
.select.input-error { border-color: color-mix(in srgb, var(--danger) 50%, transparent); }
.input.input-error:focus,
.textarea.input-error:focus,
.select.input-error:focus {
  border-color: var(--danger);
  background: var(--glass-2);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 12%, transparent), 0 0 24px color-mix(in srgb, var(--danger) 18%, transparent);
}
.input.input-success,
.textarea.input-success,
.select.input-success { border-color: color-mix(in srgb, var(--success) 50%, transparent); }
.input.input-success:focus,
.textarea.input-success:focus,
.select.input-success:focus {
  border-color: var(--success);
  background: var(--glass-2);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--success) 12%, transparent), 0 0 24px color-mix(in srgb, var(--success) 18%, transparent);
}

.input:disabled, .textarea:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.input-helper {
  font-size: 12px;
  color: var(--fg-dim);
  line-height: 1.4;
}
.input-helper-error { color: var(--danger); }

/* Checkbox */
.checkbox {
  appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  background: var(--glass-1);
  border: 1px solid var(--line-strong);
  display: inline-grid;
  place-content: center;
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
  position: relative;
}
.checkbox:hover { border-color: var(--accent-line); }
.checkbox:checked {
  background: var(--accent-grad);
  border-color: transparent;
  box-shadow: 0 0 16px color-mix(in srgb, var(--accent-a) 50%, transparent);
}
.checkbox::after {
  content: '';
  width: 10px;
  height: 6px;
  border: 2px solid var(--knob);
  border-top: 0;
  border-right: 0;
  transform: rotate(-45deg) translate(1px, -1px) scale(0);
  opacity: 0;
  transition: transform var(--dur-med) var(--ease-spring),
              opacity var(--dur-fast) var(--ease-out);
}
.checkbox:checked::after {
  transform: rotate(-45deg) translate(1px, -1px) scale(1);
  opacity: 1;
}

/* Toggle */
.toggle {
  appearance: none;
  width: 36px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--glass-2);
  border: 1px solid var(--line-strong);
  position: relative;
  cursor: pointer;
  transition: background var(--dur-med) var(--ease-out), box-shadow var(--dur-med) var(--ease-out);
}
.toggle::after {
  content: '';
  position: absolute;
  top: 1px;
  left: 1px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--fg-muted);
  transition: transform var(--dur-med) var(--ease-spring), background var(--dur-med) var(--ease-out);
}
.toggle:checked {
  background: var(--accent-grad);
  border-color: transparent;
  box-shadow: 0 0 18px color-mix(in srgb, var(--accent-a) 45%, transparent);
}
.toggle:checked::after {
  background: var(--knob);
  transform: translateX(16px);
}

/* ============== BADGES ============== */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  border: 1px solid var(--line-strong);
  background: var(--glass-2);
  color: var(--fg-muted);
}
.badge-accent {
  color: var(--accent-fg);
  border-color: var(--accent-line);
  background: var(--accent-soft);
}
.badge-success { color: var(--fg); border-color: color-mix(in srgb, var(--success) 30%, transparent); background: color-mix(in srgb, var(--success) 10%, transparent); }
.badge-warning { color: var(--fg); border-color: color-mix(in srgb, var(--warning) 30%, transparent); background: color-mix(in srgb, var(--warning) 10%, transparent); }
.badge-danger  { color: var(--fg); border-color: color-mix(in srgb, var(--danger) 30%, transparent); background: color-mix(in srgb, var(--danger) 10%, transparent); }
.badge-success.badge-dot::before { color: var(--success); }
.badge-warning.badge-dot::before { color: var(--warning); }
.badge-danger.badge-dot::before { color: var(--danger); }
.badge-dot::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 8px currentColor;
}

/* ============== UTILITIES ============== */
.row { display: flex; align-items: center; gap: var(--sp-3); }
.col { display: flex; flex-direction: column; gap: var(--sp-3); }
.between { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.divider { height: 1px; background: var(--line); width: 100%; }
.divider-v { width: 1px; background: var(--line); align-self: stretch; }

.fade-in { animation: fadeIn var(--dur-slow) var(--ease-out) both; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.stagger > * { animation: rise var(--dur-slow) var(--ease-out) both; }
.stagger > *:nth-child(1) { animation-delay: 0ms; }
.stagger > *:nth-child(2) { animation-delay: 60ms; }
.stagger > *:nth-child(3) { animation-delay: 120ms; }
.stagger > *:nth-child(4) { animation-delay: 180ms; }
.stagger > *:nth-child(5) { animation-delay: 240ms; }
.stagger > *:nth-child(6) { animation-delay: 300ms; }
.stagger > *:nth-child(7) { animation-delay: 360ms; }
.stagger > *:nth-child(8) { animation-delay: 420ms; }
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }

/* ──────────────────────────────────────────────────────────
 * Motion Foundation — shared primitives for DrylPresence and
 * DrylReveal. All token-driven; all neutralised under
 * prefers-reduced-motion (see the @media block right below).
 * ────────────────────────────────────────────────────────── */

/* Presence — enter/exit for a single child whose unmount is deferred
 * by DrylPresence until the exit animation finishes. The *-out
 * keyframes are matched by name in dryl.motion.onExit, so they must
 * stay prefixed `presence-out`. DrylPresence renders a real wrapping
 * element — animations do not apply to display:contents. */
.presence { min-width: 0; }

.presence-enter { animation: presence-in-fade var(--dur-med) var(--ease-out) both; }
.presence-exit  { animation: presence-out-fade var(--dur-med) var(--ease-out) both; }

.presence--scale.presence-enter      { animation-name: presence-in-scale; }
.presence--scale.presence-exit       { animation-name: presence-out-scale; }
.presence--slide-up.presence-enter   { animation-name: presence-in-up; }
.presence--slide-up.presence-exit    { animation-name: presence-out-up; }
.presence--slide-down.presence-enter { animation-name: presence-in-down; }
.presence--slide-down.presence-exit  { animation-name: presence-out-down; }
.presence--slide-left.presence-enter { animation-name: presence-in-left; }
.presence--slide-left.presence-exit  { animation-name: presence-out-left; }
.presence--slide-right.presence-enter{ animation-name: presence-in-right; }
.presence--slide-right.presence-exit { animation-name: presence-out-right; }

/* Speed modifiers — remap the animation onto the other fixed duration tokens. */
.presence--fast.presence-enter, .presence--fast.presence-exit { animation-duration: var(--dur-fast); }
.presence--slow.presence-enter, .presence--slow.presence-exit { animation-duration: var(--dur-slow); }

@keyframes presence-in-fade   { from { opacity: 0; } to { opacity: 1; } }
@keyframes presence-out-fade  { from { opacity: 1; } to { opacity: 0; } }
@keyframes presence-in-scale  { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: scale(1); } }
@keyframes presence-out-scale { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.94); } }
@keyframes presence-in-up     { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
@keyframes presence-out-up    { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(12px); } }
@keyframes presence-in-down   { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
@keyframes presence-out-down  { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-12px); } }
@keyframes presence-in-left   { from { opacity: 0; transform: translateX(12px); } to { opacity: 1; transform: translateX(0); } }
@keyframes presence-out-left  { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(12px); } }
@keyframes presence-in-right  { from { opacity: 0; transform: translateX(-12px); } to { opacity: 1; transform: translateX(0); } }
@keyframes presence-out-right { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(-12px); } }

/* Reveal — scroll-triggered entrance. dryl.motion.observe adds
 * .is-revealed when the wrapper scrolls into view; direct children
 * tween in, optionally staggered via per-child --reveal-i. */
.reveal > * {
  opacity: 0;
  transition:
    opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
  transition-delay: calc(var(--reveal-i, 0) * var(--reveal-step));
}
.reveal--rise > *    { transform: translateY(16px); }
.reveal--scale-in > *{ transform: scale(0.96); }
.reveal.is-revealed > * { opacity: 1; transform: none; }

/* Reduced motion — show end states instantly, no movement. dryl.motion also
   short-circuits in JS, but this covers the CSS-only paths too. */
@media (prefers-reduced-motion: reduce) {
  .presence-enter,
  .presence-exit { animation: none; }
  .reveal > * {
    transition: none;
    transition-delay: 0ms;
  }
  .reveal--rise > *,
  .reveal--scale-in > * { transform: none; }
  .tabs.is-ink-ready .tab-ink { transition: none; }
  .dialog-backdrop { animation: none; transition: none; }
  .dialog { animation: none; }
  .dialog-layer.is-exiting .dialog { animation: none; }
  .dialog-layer.is-handoff:not(.is-exiting) .dialog { animation-delay: 0ms; }
}

/* ──────────────────────────────────────────────────────────
 * Depth Glass — DrylDepthGlass and the optional Depth warp on
 * DrylCard. A glass pane that warps in 3D toward the pointer: the
 * surface tilts in perspective while the content and gloss parallax in
 * separate layers, so it reads like a sheet of glass floating above
 * the page. Pure CSS transforms driven by --tx/--ty/--mx/--my (written
 * by dryl.depthglass) — composited, reliable, no SVG filters. Stilled
 * under reduced motion.
 *   .dg-warp    — the motion (shared by DrylDepthGlass and DrylCard)
 *   .dg-surface — the standalone glass look (DrylDepthGlass only;
 *                 DrylCard brings its own via .glass-card)
 *   --dg-tilt — max tilt angle   --dg-par — max parallax shift
 * ────────────────────────────────────────────────────────── */
.dg-warp {
  --dg-tilt: 8deg;
  --dg-par: 10px;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transform:
    perspective(900px)
    rotateX(calc(var(--ty, 0) * var(--dg-tilt) * -1))
    rotateY(calc(var(--tx, 0) * var(--dg-tilt)))
    translateY(var(--dg-lift, 0px));
  transition:
    transform var(--dur-med) var(--ease-spring),
    box-shadow var(--dur-med) var(--ease-out);
  will-change: transform;
}
.dg-warp:hover {
  --dg-lift: -4px;
  box-shadow:
    0 22px 50px var(--depth-shadow),
    inset 1px 1px 0 var(--depth-edge-strong),
    inset -1px -1px 0 color-mix(in srgb, var(--depth-edge) 78%, transparent),
    inset 0 0 26px color-mix(in srgb, var(--depth-edge) 39%, transparent);
}

.dg--subtle { --dg-tilt: 4deg;  --dg-par: 6px;  }
.dg--medium { --dg-tilt: 8deg;  --dg-par: 10px; }
.dg--strong { --dg-tilt: 13deg; --dg-par: 16px; }

/* Standalone surface look (DrylDepthGlass). */
.dg-surface {
  border-radius: var(--r-lg);
  background: var(--glass-1);
  backdrop-filter: blur(8px) saturate(1.5);
  -webkit-backdrop-filter: blur(8px) saturate(1.5);
  border: 1px solid var(--depth-edge);
  box-shadow:
    0 12px 36px color-mix(in srgb, var(--depth-shadow) 76%, transparent),
    inset 1px 1px 0 color-mix(in srgb, var(--depth-edge-strong) 91%, transparent),
    inset -1px -1px 0 color-mix(in srgb, var(--depth-edge) 67%, transparent),
    inset 0 0 20px color-mix(in srgb, var(--depth-edge) 28%, transparent);
}
.dg-surface > .dg-content { padding: var(--sp-5); color: var(--fg); }

.dg-sheen,
.dg-specular {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}

/* Top gloss — parallaxes opposite the tilt so the sheen slides across the
   glass as it warps (depth cue). */
.dg-sheen {
  z-index: 1;
  /* Overscan the gloss well past its max parallax travel so its own rectangular
     edge never slides into view as a hard seam near a corner. The parent clips
     the overflow back to the card's rounded shape (overflow:hidden + radius). */
  inset: -32px;
  background: linear-gradient(155deg, color-mix(in srgb, var(--depth-edge-strong) 44%, transparent), transparent 45%);
  mix-blend-mode: screen;
  opacity: 0.7;
  transform: translate(
    calc(var(--tx, 0) * var(--dg-par) * -1.4),
    calc(var(--ty, 0) * var(--dg-par) * -1.4));
  transition: transform var(--dur-med) var(--ease-out);
}

/* Travelling glossy highlight — follows the pointer (falls back to top-centre). */
.dg-specular {
  z-index: 2;
  background: radial-gradient(
    220px 220px at var(--mx, 50%) var(--my, 0%),
    color-mix(in srgb, var(--depth-edge-strong) 73%, transparent), transparent 60%);
  mix-blend-mode: screen;
  opacity: 0.6;
}

/* Content floats above the glass plane — parallaxes WITH the tilt and lifts
   on hover so it reads as raised. */
.dg-content {
  position: relative;
  z-index: 3;
  transform: translate(
    calc(var(--tx, 0) * var(--dg-par)),
    calc(var(--ty, 0) * var(--dg-par)));
  transition: transform var(--dur-med) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .dg-warp,
  .dg-sheen,
  .dg-content { transform: none; transition: none; }
  .dg-warp:hover { --dg-lift: 0px; }
}

/* Scrollbar */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: 10px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); background-clip: padding-box; border: 2px solid transparent; }
::-webkit-scrollbar-track { background: transparent; }

/* Focus ring for keyboard users.
   Do NOT force a border-radius here — modern browsers draw the outline along the
   element's own radius, and clobbering it (every component shares this rule's
   specificity) makes rounded surfaces like .input snap to square corners on focus. */
:focus-visible { outline: 2px solid var(--accent-b); outline-offset: 2px; }

/* ============== APP CHROME ============== */
.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "drawer topbar"
    "drawer main";
  height: 100vh;
  /* The shell fills the viewport and never scrolls itself, so the sidebar and
     top bar stay fixed in place; only .main scrolls (see its rule below). */
  overflow: hidden;
  transition: grid-template-columns var(--dur-med) var(--ease-in-out);
}
/* Collapsed sidebar: the grid column shrinks to icon width. DrylLayout sets
   this class from its SidebarCollapsed state so the body reflows in step with
   the sidebar's own .is-collapsed width animation. Desktop only — on mobile the
   grid is a single column and the sidebar is an overlay, so collapse is moot. */
@media (min-width: 1024px) {
  .app-shell.is-sidebar-collapsed {
    grid-template-columns: var(--sidebar-collapsed-w) 1fr;
  }
}
.app-shell > .sidebar { grid-area: drawer; }
.app-shell > .topbar  { grid-area: topbar; }
.app-shell > .main    { grid-area: main; }

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  padding: 24px 18px;
  border-right: 1px solid var(--line);
  background: var(--sheen-grad-soft);
  backdrop-filter: blur(20px);
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 10;
  overflow: hidden;
  transition: width var(--dur-med) var(--ease-in-out),
              padding var(--dur-med) var(--ease-in-out);
}

/* Structured slots (DrylDrawer Header / Content / Footer). The content area
   scrolls; header and footer stay pinned. Purely additive — drawers that pass
   plain ChildContent never render these wrappers. */
.sidebar-header { flex: 0 0 auto; }
.sidebar-content { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--sp-1); }
.sidebar-footer {
  flex: 0 0 auto;
  margin-top: var(--sp-2);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}

/* Collapsed: icon-only rail. Hide text labels but keep leading icons centred.
   No JS needed — the nav primitives already wrap labels in <span>.
   Confined to desktop so the mobile overlay (below) always shows full labels —
   the collapsed state is a desktop affordance only. */
@media (min-width: 1024px) {
  .sidebar.is-collapsed {
    width: var(--sidebar-collapsed-w);
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }
  .sidebar.is-collapsed .nav-item > span,
  .sidebar.is-collapsed .nav-title,
  .sidebar.is-collapsed .nav-section-toggle-label,
  .sidebar.is-collapsed .nav-section-link span,
  .sidebar.is-collapsed .nav-section-chevron,
  .sidebar.is-collapsed .sidebar-collapse-hide {
    display: none;
  }
  /* Centring alone is not enough: the row keeps its expanded horizontal padding,
     which on a rail this narrow leaves less room than the icon needs and squashes
     it — 16px of icon in 5px of space. The rail is the padding now, so the rows
     inside it give theirs up and each icon gets its full width back. */
  .sidebar.is-collapsed .nav-item,
  .sidebar.is-collapsed .nav-section-toggle {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
  }
  /* The icon is the whole row here — it may never be shrunk to fit. */
  .sidebar.is-collapsed .nav-item > svg,
  .sidebar.is-collapsed .nav-item .ico,
  .sidebar.is-collapsed .nav-section-toggle > svg {
    flex: 0 0 auto;
    min-width: 16px;
  }
  /* A classic scrollbar claims a quarter of a rail this narrow and pushes every
     icon off-centre with the remainder. On a rail it is also unusable as a
     control, so it stops taking space here — the rail still scrolls by wheel,
     trackpad and keyboard. */
  .sidebar.is-collapsed .sidebar-content { scrollbar-width: none; }
  .sidebar.is-collapsed .sidebar-content::-webkit-scrollbar { width: 0; height: 0; }
}

/* Static mode: never becomes an overlay, stays an in-flow column at any width. */
.sidebar--static { transform: none !important; }

/* Flyout mode: always an overlay (fixed, off-canvas) regardless of viewport,
   sliding in when .is-open. Pairs with the always-rendered .sidebar-backdrop. */
.sidebar--flyout {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--sidebar-w);
  height: 100vh;
  transform: translateX(-100%);
  transition: transform var(--dur-med) var(--ease-out);
  z-index: var(--z-drawer);
  border-right: 1px solid var(--line-strong);
}
.sidebar--flyout.is-open { transform: translateX(0); }

/* Desktop-visible sidebar collapse toggle (DrylAppBar ShowSidebarToggle / a
   header button). Reuses the hamburger visual but is shown at all widths. */
.sidebar-toggle {
  display: inline-flex;
  width: 36px;
  height: 36px;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid var(--line);
  color: var(--fg-muted);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.sidebar-toggle:hover {
  background: var(--glass-2);
  border-color: var(--line-strong);
  color: var(--fg);
}
.sidebar-toggle:focus-visible {
  outline: none;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--backdrop);
  backdrop-filter: blur(4px);
  z-index: 40;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-med) var(--ease-out);
}
/* Flyout drawers overlay at every viewport width, so their backdrop must show
   regardless of the mobile media query below. */
.sidebar-backdrop--flyout { display: block; }
.sidebar-backdrop--flyout.is-open { opacity: 1; pointer-events: auto; }

.drawer-toggle {
  display: none;
  width: 36px;
  height: 36px;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid var(--line);
  color: var(--fg-muted);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.drawer-toggle:hover {
  background: var(--glass-2);
  border-color: var(--line-strong);
  color: var(--fg);
}
.drawer-toggle:focus-visible {
  outline: none;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

@media (max-width: 1023px) {
  .app-shell {
    grid-template-columns: 1fr;
    grid-template-areas:
      "topbar"
      "main";
  }
  .app-shell > .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    width: 280px;
    height: 100vh;
    transform: translateX(-100%);
    transition: transform var(--dur-med) var(--ease-out);
    z-index: 50;
    border-right: 1px solid var(--line-strong);
  }
  .app-shell > .sidebar.is-open {
    transform: translateX(0);
  }
  .sidebar-backdrop {
    display: block;
  }
  .sidebar-backdrop.is-open {
    opacity: 1;
    pointer-events: auto;
  }
  .drawer-toggle {
    display: inline-flex;
  }
  /* The collapse toggle is a desktop affordance; on mobile the hamburger
     (drawer-toggle) opens the overlay instead, so hide the collapse button. */
  .sidebar-toggle {
    display: none;
  }
  .topbar { padding: 0 16px; }
  .main-inner { padding: 24px 16px 64px; }
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 8px;
}
.brand-mark {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--accent-grad);
  display: grid;
  place-content: center;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 14px;
  color: var(--on-accent);
  letter-spacing: -0.02em;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--on-accent) 10%, transparent) inset, 0 8px 24px color-mix(in srgb, var(--accent-a) 40%, transparent), 0 0 32px color-mix(in srgb, var(--accent-b) 20%, transparent);
  position: relative;
  overflow: hidden;
}
.brand-mark::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 30%, var(--shimmer-strong) 50%, transparent 70%);
  animation: shimmer 4s var(--ease-in-out) infinite;
  z-index: 1;
  pointer-events: none;
}
.brand-mark-logo {
  width: 22px;
  height: 22px;
  object-fit: contain;
  display: block;
  position: relative;
  z-index: 2;
  mix-blend-mode: screen;
}
@keyframes shimmer {
  0%, 100% { transform: translateX(-100%); }
  50% { transform: translateX(100%); }
}
.brand-name { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; }
.brand-sub  { font-size: 11px; color: var(--fg-dim); font-family: var(--font-mono); letter-spacing: 0.05em; }

.nav-section {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nav-title {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-faint);
  padding: 16px 12px 4px;
}
.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 34px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  color: var(--fg-muted);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  user-select: none;
}
.nav-item:hover { background: var(--glass-2); color: var(--fg); }
.nav-item.active {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent-a) 18%, transparent), color-mix(in srgb, var(--accent-b) 4%, transparent));
  color: var(--fg);
  box-shadow: inset 0 0 0 1px var(--accent-line), inset 0 0 18px color-mix(in srgb, var(--accent-a) 15%, transparent);
}
.nav-item.active::before {
  content: '';
  position: absolute;
  left: -18px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 18px;
  border-radius: 0 3px 3px 0;
  background: var(--accent-grad);
  box-shadow: 0 0 12px var(--accent-a);
}
.nav-item .ico { width: 16px; height: 16px; flex: 0 0 16px; opacity: 0.8; }
.nav-item .kbd {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-faint);
  padding: 2px 6px;
  border-radius: 4px;
  border: 1px solid var(--line);
}

/* ── Scrollable nav area ──────────────────────────────── */
.nav-scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: var(--sp-2);
}
.nav-scroll::-webkit-scrollbar { width: 4px; }
.nav-scroll::-webkit-scrollbar-track { background: transparent; }
.nav-scroll::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 2px;
}

/* ── Collapsible nav-section ──────────────────────────── */

/* Header used when no Href — entire row is the toggle button */
.nav-section-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-faint);
  padding: 16px 12px 4px;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out);
  line-height: 1;
  text-align: left;
}
.nav-section-toggle:hover { color: var(--fg-dim); }
.nav-section-toggle .ico { width: 14px; height: 14px; flex: 0 0 14px; opacity: 0.6; }
.nav-section-toggle-label { flex: 1; }

/* Header row used when Href is set — NavLink + separate chevron button */
.nav-section-header {
  display: flex;
  align-items: center;
  padding: 10px 4px 2px;
  gap: 2px;
}
.nav-section-link {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 34px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  color: var(--fg-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
  user-select: none;
  min-width: 0;
  position: relative;
}
.nav-section-link:hover { background: var(--glass-2); color: var(--fg); }
.nav-section-link.active {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent-a) 18%, transparent), color-mix(in srgb, var(--accent-b) 4%, transparent));
  color: var(--fg);
  box-shadow: inset 0 0 0 1px var(--accent-line), inset 0 0 18px color-mix(in srgb, var(--accent-a) 15%, transparent);
}
.nav-section-link.active::before {
  content: '';
  position: absolute;
  left: -22px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 18px;
  border-radius: 0 3px 3px 0;
  background: var(--accent-grad);
  box-shadow: 0 0 12px var(--accent-a);
}
.nav-section-link .ico { width: 16px; height: 16px; flex: 0 0 16px; opacity: 0.8; }

.nav-section-chevron-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  background: none;
  border: none;
  border-radius: var(--r-sm);
  color: var(--fg-faint);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.nav-section-chevron-btn:hover { background: var(--glass-2); color: var(--fg-muted); }

/* Rotating chevron (shared by both header variants) */
.nav-section-chevron {
  display: block;
  transition: transform var(--dur-med) var(--ease-out);
  transform: rotate(0deg);
  flex: 0 0 auto;
}
.nav-section-chevron.is-open { transform: rotate(90deg); }

/* Animated collapse container — CSS grid trick (no fixed max-height) */
.nav-children {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-med) var(--ease-out);
}
.nav-children.is-open { grid-template-rows: 1fr; }
.nav-children-inner {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: 2px;
}

/* Indented sub nav-items */
.nav-item--sub {
  padding-left: 30px;
  height: 30px;
  font-size: 12.5px;
}

.topbar {
  height: var(--appbar-h);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 32px;
  border-bottom: 1px solid var(--line);
  background: var(--bar-bg);
  backdrop-filter: blur(20px);
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}
/* Raised elevation: lifts the bar off the content with a shadow and a denser
   glass tint. Flat (default) keeps the original flush look. */
.topbar.is-raised {
  background: var(--glass-2);
  box-shadow: var(--shadow-md);
}
/* Slotted layout (DrylAppBar Start / Center / End). Only rendered when at least
   one of those slots is used; plain ChildContent keeps the flat flex row above.
   Start and End flex equally so Center stays optically centred between them. */
.topbar-start  { display: flex; align-items: center; gap: var(--sp-3); flex: 1 1 0; min-width: 0; }
.topbar-center { display: flex; align-items: center; gap: var(--sp-3); flex: 0 1 auto; justify-content: center; min-width: 0; }
.topbar-end    { display: flex; align-items: center; gap: var(--sp-3); flex: 1 1 0; min-width: 0; justify-content: flex-end; }
.crumb {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--fg-muted);
}
.crumb .sep { color: var(--fg-faint); }
.crumb b { color: var(--fg); font-weight: 500; }

.search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 12px;
  border-radius: var(--r-md);
  background: var(--glass-1);
  border: 1px solid var(--line);
  color: var(--fg-muted);
  width: 320px;
  font-size: 13px;
  transition: all var(--dur-med) var(--ease-out);
  cursor: text;
}
.search:hover { border-color: var(--line-strong); }
.search:focus-within { border-color: var(--accent-line); box-shadow: 0 0 0 4px var(--accent-soft); }
.search input { background: transparent; border: 0; outline: 0; color: var(--fg); width: 100%; font: 400 13px var(--font-sans); }
.search input::placeholder { color: var(--fg-dim); }

/* Topbar hardening — page-level chrome, so a viewport media query (consistent
   with the existing app-shell breakpoints). On small screens the bar tightens
   its padding and the fixed-width search becomes flexible so nothing clips off
   the right edge. */
@media (max-width: 768px) {
  .topbar { padding: 0 var(--sp-4); gap: var(--sp-3); }
  .search { width: auto; min-width: 0; flex: 1 1 auto; }
}

.main {
  /* The only scroll region inside the app shell: the sidebar and top bar are
     fixed, page content scrolls here. It spans the full grid track so its
     scrollbar sits flush at the viewport edge — the reading-width cap lives on
     .main-inner, never on the scroll container itself. (A max-width here would
     make .main narrower than its track on wide viewports and centre it, so the
     scrollbar would float inward "in the middle of the page".) min-height:0
     lets this grid row shrink below its content so overflow-y can take effect. */
  width: 100%;
  min-height: 0;
  overflow-y: auto;
}
/* Inner column — caps the reading width and centres it inside the full-width
   scroll region. Padding lives here (not on .main) so the scrollbar stays flush
   against the viewport edge instead of hugging the padded content box. */
.main-inner {
  max-width: 1400px;
  margin-inline: auto;
  padding: 40px 48px 80px;
}

/* ============== KBD / CODE ============== */
.kbd {
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 4px;
  border: 1px solid var(--line-strong);
  background: var(--glass-2);
  color: var(--fg-muted);
}
code, .code-inline {
  font-family: var(--font-mono);
  font-size: 12.5px;
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--glass-2);
  border: 1px solid var(--line);
  color: var(--accent-fg);
}

.code-block {
  position: relative;
  border-radius: var(--r-md);
  background: var(--code-bg);
  border: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.6;
  padding: 16px 18px;
  color: var(--code-fg);
  overflow: auto;
  box-shadow: inset 0 0 60px color-mix(in srgb, var(--accent-a) 5%, transparent);
}
.code-block .tok-keyword { color: #c4b5fd; }
.code-block .tok-tag     { color: #67e8f9; }
.code-block .tok-attr    { color: #fbcfe8; }
.code-block .tok-string  { color: #86efac; }
.code-block .tok-comment { color: var(--fg-faint); font-style: italic; }
.code-block .tok-prop    { color: #fde68a; }

/* ============== SECTIONS ============== */
.section { margin-bottom: 56px; }
.section-head { margin-bottom: 22px; display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; }
.section-head h2 { letter-spacing: -0.025em; }
.section-head p { max-width: 56ch; }

.swatch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}
.swatch {
  position: relative;
  height: 96px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  overflow: hidden;
  padding: 12px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  transition: transform var(--dur-med) var(--ease-out);
  cursor: pointer;
}
.swatch:hover { transform: translateY(-2px); }
.swatch .name { font-size: 12px; font-weight: 500; }
.swatch .val { font-family: var(--font-mono); font-size: 10.5px; color: var(--fg-dim); margin-top: 2px; }

.type-row {
  display: grid;
  grid-template-columns: 140px 1fr 160px;
  gap: 24px;
  align-items: baseline;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
.type-row:last-child { border-bottom: 0; }
.type-row .label { font-family: var(--font-mono); font-size: 11px; color: var(--fg-dim); letter-spacing: 0.08em; text-transform: uppercase; }
.type-row .spec  { font-family: var(--font-mono); font-size: 11px; color: var(--fg-dim); text-align: right; }

/* Stats / metric cards */
.metric {
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 130px;
  position: relative;
  overflow: hidden;
}
.metric .label { font-size: 12px; color: var(--fg-muted); display: flex; align-items: center; gap: 8px; }
.metric .value { font-size: 32px; font-weight: 600; letter-spacing: -0.025em; line-height: 1; }
.metric .delta { font-family: var(--font-mono); font-size: 11px; }
.metric .delta.up { color: var(--success); }
.metric .delta.down { color: var(--danger); }
.metric .spark {
  position: absolute;
  right: 16px;
  bottom: 16px;
  width: 96px;
  height: 32px;
  opacity: 0.7;
}

/* Tables — base (used by prototype and inline usage) */
.tbl {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13px;
}
.tbl th {
  text-align: left;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-dim);
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
}
.tbl td {
  padding: 14px;
  border-bottom: 1px solid var(--line);
  color: var(--fg-muted);
  vertical-align: middle;
}
.tbl tr:hover td { background: var(--hover-wash); color: var(--fg); }
.tbl tr:last-child td { border-bottom: 0; }
.tbl .name { color: var(--fg); font-weight: 500; }

/* ============== TABLE COMPONENT (DrylTable) ============== */

/* Outer wrapper — clips overflow for rounded card corners */
.tbl-root { overflow: hidden; }

/* Sticky header — needs a semi-opaque background so content scrolls under */
.tbl-root .tbl th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--panel-sticky);
}

/* Horizontal scroll container */
.tbl-wrap { overflow-x: auto; }

/* Row interaction */
.tbl-root .tbl tbody tr {
  transition: background var(--dur-fast) var(--ease-out);
}
.tbl-root .tbl tbody tr:hover td { background: var(--glass-2); color: var(--fg); }

/* Selected row */
.tbl-root .tbl tbody tr.tbl-row--selected td {
  background: color-mix(in srgb, var(--accent-a) 7%, transparent);
}
.tbl-root .tbl tbody tr.tbl-row--selected:hover td {
  background: color-mix(in srgb, var(--accent-a) 12%, transparent);
}

/* Checkbox column */
.tbl-col-check {
  width: 44px;
  padding-left: var(--sp-4);
  padding-right: 0;
}

/* Primary cell — full fg color + medium weight */
.tbl td.tbl-td-primary { color: var(--fg); font-weight: 500; }

/* Empty state row */
.tbl-root .tbl tbody tr.tbl-empty-row td {
  text-align: center;
  padding: var(--sp-8) var(--sp-5);
  color: var(--fg-dim);
  border-bottom: none;
}

/* Loading state row */
.tbl-root .tbl tbody tr.tbl-loading-row td {
  border-bottom: none;
  padding: 0;
}

/* AI-generated row — fades in with a slight rise and a brief accent flash
   that lingers for ~1.6s before settling. Composes with the table's wrapper
   .ai-aura ring/glow when those are active. */
.tbl-root .tbl tbody tr.tbl-row--ai-enter td {
  animation:
    tbl-row-ai-rise var(--dur-slow) var(--ease-out) both,
    tbl-row-ai-flash var(--dur-choreo) var(--ease-out) both;
}

/* AI mode on the table wrapper — the .tbl-root has overflow:hidden so it can
   clip content to its rounded corners, which would otherwise eat the outer
   ring and glow. These overrides pull the AI signals inside the bounds:
   the ring traces the very-outermost pixel row, and the glow uses inset
   box-shadows so it lives within the clipped region too. */
.tbl-root .ai-aura-ring { inset: 0; }
.tbl-root .ai-aura-glow {
  box-shadow:
    inset 0 0 0 1px var(--accent-line),
    inset 0 0 48px color-mix(in srgb, var(--ai-a) 18%, transparent),
    inset 0 0 96px color-mix(in srgb, var(--ai-b) 8%, transparent);
}
/* Suppress the Generated lift on the table — visually distracting on a tall
   surface. The wash + row entry animations carry the reveal moment. */
.tbl-root.ai-aura.ai-generated { animation: none; }

@keyframes tbl-row-ai-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes tbl-row-ai-flash {
  0%   { background-color: color-mix(in srgb, var(--ai-a) 16%, transparent); }
  60%  { background-color: color-mix(in srgb, var(--ai-a) 6%, transparent); }
  100% { background-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .tbl-root .tbl tbody tr.tbl-row--ai-enter td { animation: none; }
}

/* ============== TABLE KPI SUMMARY BAR ============== */

/* Grid of KPI tiles above the table header */
.tbl-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  border-bottom: 1px solid var(--line);
}

/* Individual KPI tile */
.tbl-kpi {
  padding: var(--sp-4) var(--sp-5);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
  overflow: hidden;
}
.tbl-kpi:last-child { border-right: none; }

.tbl-kpi-label {
  font-size: 12px;
  color: var(--fg-muted);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tbl-kpi-value {
  font-size: 32px;
  font-weight: 700;
  color: var(--fg);
  line-height: 1.05;
  letter-spacing: -0.025em;
  font-feature-settings: "tnum";
}

.tbl-kpi-footer {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-2);
  min-height: 34px;
}

.tbl-kpi-delta {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 3px;
  align-self: flex-end;
}
.tbl-kpi-delta--positive { color: var(--success); }
.tbl-kpi-delta--negative { color: var(--danger); }
.tbl-kpi-delta--neutral  { color: var(--fg-muted); }

/* Sparkline */
.tbl-kpi-sparkline {
  width: 96px;
  height: 32px;
  flex-shrink: 0;
  margin-left: auto;
  opacity: 0.7;
  transition: opacity var(--dur-med) var(--ease-out);
}
.tbl-kpi:hover .tbl-kpi-sparkline { opacity: 1; }
.tbl-kpi-sparkline polyline {
  stroke: var(--accent-b);
  fill: none;
}

/* ============== TABLE TOOLBAR + SORT (Phase 1) ============== */

/* Toolbar above the table — search input on the left, extras on the right.
   Sits between the optional KPI summary bar and the table header. */
.tbl-toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line);
}

/* Search wrapper — pins icon + input together, takes natural width on the left. */
.tbl-search {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 240px;
  max-width: 360px;
  flex: 0 1 auto;
}
.tbl-search .input { width: 100%; }
.tbl-search .input-icon-leading { left: 10px; pointer-events: none; }

/* Extras slot — pushed to the far right of the toolbar. */
.tbl-toolbar-extra {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Sortable header — click target, keyboard-focusable. */
.tbl th.tbl-th-sort {
  cursor: pointer;
  user-select: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.tbl th.tbl-th-sort:hover { color: var(--fg-muted); }
.tbl th.tbl-th-sort:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: -1px;
}
.tbl th.tbl-th-sort[aria-sort="ascending"],
.tbl th.tbl-th-sort[aria-sort="descending"] {
  color: var(--fg);
}

/* Header inner layout — label + sort icon side-by-side. */
.tbl th.tbl-th-sort .tbl-th-label { display: inline; }
.tbl-th-sort-icon {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  vertical-align: -2px;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.tbl th.tbl-th-sort:hover .tbl-th-sort-icon { opacity: 0.5; }
.tbl-th-sort-icon.is-active { opacity: 1; }

/* Column alignment helpers */
.tbl-align-center { text-align: center; }
.tbl-align-end    { text-align: right; }

/* Header inner layout — splits the sort-click area from the filter trigger so
   clicking the filter icon does NOT trigger a sort. */
.tbl-th-inner {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.tbl-th-clickable {
  display: inline-flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
}
.tbl th.tbl-th-sort .tbl-th-clickable {
  cursor: pointer;
  user-select: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.tbl th.tbl-th-sort .tbl-th-clickable:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* ============== TABLE FILTERS (Phase 2) ============== */

/* Tiny filter trigger button rendered in the header next to the title. */
.tbl-filter-trigger {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  color: var(--fg-dim);
  cursor: pointer;
  padding: 2px 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.tbl-filter-trigger:hover { color: var(--fg-muted); background: var(--glass-2); }
.tbl-filter-trigger:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: 1px;
}
.tbl-filter-trigger.is-active {
  color: var(--accent-a);
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

/* Popover anchored to the right edge of the column header. Sits above the
   table body via z-index and the .tbl-th-inner positioning context. */
.tbl-filter-popover {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: var(--z-drawer);
  min-width: 240px;
  max-width: 320px;
  background: var(--panel-float);
  backdrop-filter: var(--glass-fx-float);
  -webkit-backdrop-filter: var(--glass-fx-float);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  padding: 0;
  /* The header has uppercase mono-styling — reset to normal body text inside. */
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg);
  animation: tbl-filter-popover-in var(--dur-med) var(--ease-out);
}
@keyframes tbl-filter-popover-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.tbl-filter-popover-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.tbl-filter-popover-title {
  font-weight: 500;
  color: var(--fg);
}
.tbl-filter-popover-close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 2px;
  border-radius: var(--r-xs);
  display: inline-flex;
}
.tbl-filter-popover-close:hover { color: var(--fg); }

.tbl-filter-popover-body {
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-height: 280px;
  overflow-y: auto;
}
.tbl-filter-popover-body .input { width: 100%; }

.tbl-filter-option {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 0;
  cursor: pointer;
  color: var(--fg-muted);
}
.tbl-filter-option:hover { color: var(--fg); }
.tbl-filter-option .checkbox { flex: 0 0 auto; }

.tbl-filter-empty {
  color: var(--fg-dim);
  font-style: italic;
}

.tbl-filter-popover-footer {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--line);
}

/* Active-filter chips shown in the toolbar. */
.tbl-filter-chips {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.tbl-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px 3px 8px;
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-pill);
  font-size: 12px;
  color: var(--fg);
}
.tbl-filter-chip-label { color: var(--fg-muted); }
.tbl-filter-chip-value { font-weight: 500; }
.tbl-filter-chip-close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 2px;
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.tbl-filter-chip-close:hover { color: var(--fg); background: var(--press-wash); }

/* ============== TABLE FOOTER + PAGINATION (Phase 2) ============== */

.tbl-footer {
  border-top: 1px solid var(--line);
}

.tbl-pagination {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  flex-wrap: wrap;
}
.tbl-pagination-info {
  color: var(--fg-muted);
  font-size: 12px;
  font-family: var(--font-mono);
}
.tbl-pagination-size {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--fg-muted);
  font-size: 12px;
}
.tbl-pagination-size .select { padding-top: 4px; padding-bottom: 4px; min-width: 64px; }

.tbl-pagination-pages {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}
.tbl-pagination-numbers {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
/* On a narrow slot the numbered page buttons collapse, leaving the
   first/prev/next/last controls and the "Showing x–y of z" summary. */
@container (max-width: 480px) {
  .tbl-pagination-numbers { display: none; }
}
.tbl-pagination-gap {
  color: var(--fg-dim);
  font-family: var(--font-mono);
  padding: 0 6px;
  user-select: none;
}

/* ============== TABLE GROUPING / DETAIL / ACTIONS / BULK (Phase 3) ============== */

/* Group header row — sits between groups, mono uppercase label + count badge.
   Sticky under the table header when grouping + sticky-header are both active. */
.tbl-root .tbl tbody tr.tbl-group-header td {
  background: var(--glass-2);
  border-bottom: 1px solid var(--line);
  padding: 0;
}
.tbl-root .tbl tbody tr.tbl-group-header:hover td { background: var(--glass-2); }

.tbl-group-toggle {
  appearance: none;
  background: transparent;
  border: 0;
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  text-align: left;
  cursor: pointer;
  color: var(--fg-muted);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: color var(--dur-fast) var(--ease-out);
}
.tbl-group-toggle:hover { color: var(--fg); }
.tbl-group-toggle:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: -1px;
}

.tbl-group-chevron {
  display: inline-flex;
  align-items: center;
  color: var(--fg-dim);
  transition: transform var(--dur-fast) var(--ease-out);
}
.tbl-group-chevron.is-collapsed { transform: rotate(-90deg); }

.tbl-group-label {
  color: var(--fg);
  flex: 1 1 auto;
}

.tbl-group-count {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
  padding: 1px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--glass-1);
}

/* Expand column — chevron toggle on the row for DetailTemplate. */
.tbl-col-expand {
  width: 36px;
  padding-left: var(--sp-3);
  padding-right: 0;
}

.tbl-expand-btn {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  color: var(--fg-dim);
  cursor: pointer;
  padding: 2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    color var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.tbl-expand-btn:hover { color: var(--fg); background: var(--glass-2); }
.tbl-expand-btn:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: 1px;
}
.tbl-expand-btn.is-expanded {
  color: var(--accent-a);
  transform: rotate(90deg);
}

/* Reorder grip column — leading drag handle when Reorderable is set. */
.tbl-col-grip {
  width: 32px;
  padding-left: var(--sp-3);
  padding-right: 0;
}

.tbl-grip {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  color: var(--fg-dim);
  cursor: grab;
  padding: 2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.tbl-grip:hover { color: var(--fg); background: var(--glass-2); }
.tbl-grip:active { cursor: grabbing; }
.tbl-grip:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: 1px;
  color: var(--fg);
}
.tbl-grip:disabled {
  cursor: default;
  opacity: 0.4;
  background: transparent;
  color: var(--fg-dim);
}

/* Row being dragged — dims so the drop target reads clearly. */
.tbl-root .tbl tbody tr.tbl-row--dragging td {
  opacity: 0.45;
}

/* Drop target — an accent line traces the row the drag is hovering over. */
.tbl-root .tbl tbody tr.tbl-row--drop-target td {
  box-shadow: inset 0 2px 0 0 var(--accent-a);
}

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

/* Detail row — full-width panel that slides open under the expanded row. */
.tbl-root .tbl tbody tr.tbl-row-detail td {
  background: var(--bg-2);
  padding: 0;
  border-bottom: 1px solid var(--line);
}
.tbl-root .tbl tbody tr.tbl-row-detail:hover td {
  background: var(--bg-2);
  color: var(--fg-muted);
}
.tbl-row-detail-inner {
  padding: var(--sp-5);
  color: var(--fg-muted);
  animation: tbl-detail-in var(--dur-med) var(--ease-out);
}
@keyframes tbl-detail-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Trailing per-row actions column. */
th.tbl-col-actions,
td.tbl-col-actions {
  width: 1%;
  white-space: nowrap;
  text-align: right;
  padding-right: var(--sp-4);
}
.tbl-row-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  justify-content: flex-end;
}

/* ============== TABLE INLINE EDITING ============== */

/* The row currently being edited gets a soft accent wash so it stands apart from
   the rest of the body, including under the hover rule. */
.tbl-root .tbl tbody tr.tbl-row--editing td,
.tbl-root .tbl tbody tr.tbl-row--editing:hover td {
  background: var(--accent-soft);
}

/* A cell hosting an inline editor sheds the text padding so the control fills the
   cell, and lets DRYL inputs stretch to the column width. */
td.tbl-td-editing {
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
}
td.tbl-td-editing .input,
td.tbl-td-editing .select,
td.tbl-td-editing .textarea,
td.tbl-td-editing input,
td.tbl-td-editing select {
  width: 100%;
}

/* Edit affordances in the actions column: pencil (enter), commit, cancel. */
.tbl-edit-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--fg-muted);
  cursor: pointer;
  transition:
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.tbl-edit-btn:hover {
  border-color: var(--line-strong);
  color: var(--fg);
  background: var(--glass-1);
}
.tbl-edit-btn:focus-visible {
  outline: none;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.tbl-edit-btn--commit {
  border-color: var(--accent-line);
  color: var(--accent-a);
}
.tbl-edit-btn--commit:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-a);
}
.tbl-edit-btn--cancel:hover {
  border-color: var(--danger);
  color: var(--danger);
  background: transparent;
}

/* ============== TABLE COLUMN PIN / RESIZE / REORDER ============== */

/* Pinned (frozen) columns — the layout helper sets the per-column left/right offset; CSS owns
   the sticky behaviour, opaque backing and layering. */
.tbl .tbl-pin {
  position: sticky;
  z-index: 1;
  background: var(--bg-2);
}
/* Keep pinned body cells opaque through hover so scrolling content can't bleed under them. */
.tbl-root .tbl tbody tr td.tbl-pin,
.tbl-root .tbl tbody tr:hover td.tbl-pin {
  background: var(--bg-2);
}
/* Pinned header cells sit above the sticky header row and the pinned body cells. */
.tbl-root .tbl thead th.tbl-pin {
  z-index: 4;
}
/* Edge rule that separates a frozen column from the scrolling area. */
.tbl .tbl-pin-start { box-shadow: inset -1px 0 0 var(--line); }
.tbl .tbl-pin-end   { box-shadow: inset 1px 0 0 var(--line); }

/* Column resize grip — sits at the right edge of a resizable header. */
.tbl-col-resize {
  position: absolute;
  top: 0;
  right: 0;
  width: 9px;
  height: 100%;
  cursor: col-resize;
  touch-action: none;
  z-index: 2;
}
.tbl-col-resize::after {
  content: "";
  position: absolute;
  top: 20%;
  right: 3px;
  width: 2px;
  height: 60%;
  border-radius: var(--r-pill);
  background: var(--line);
  opacity: 0;
  transition:
    opacity var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.tbl th:hover .tbl-col-resize::after { opacity: 1; }
.tbl-col-resize:hover::after { background: var(--accent-line); opacity: 1; }

/* While a resize drag is in progress, lock text selection for a clean drag. */
.tbl-resizing { user-select: none; cursor: col-resize; }

/* Reorderable headers — grab affordance + drag/drop states. */
.tbl th[draggable="true"] .tbl-th-clickable { cursor: grab; }
.tbl th.tbl-th--col-dragging { opacity: 0.5; }
.tbl th.tbl-th--col-drop-target {
  box-shadow: inset 2px 0 0 var(--accent-a);
}

/* Floating bulk-action bar — slides in from the top when a row is selected. */
.tbl-bulk-bar {
  position: sticky;
  top: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background:
    var(--sheen-grad),
    var(--bg-2);
  border-bottom: 1px solid var(--accent-line);
  box-shadow: inset 0 -1px 0 var(--accent-line);
  animation: tbl-bulk-in var(--dur-med) var(--ease-spring);
}
@keyframes tbl-bulk-in {
  from { transform: translateY(-100%); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}

.tbl-bulk-bar-info {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
}
.tbl-bulk-bar-count {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
  color: var(--accent-a);
  padding: 2px 8px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r-pill);
  background: var(--accent-soft);
}
.tbl-bulk-bar-label {
  color: var(--fg-muted);
  font-size: 12px;
  letter-spacing: 0.02em;
}
.tbl-bulk-bar-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: var(--sp-2);
}
.tbl-bulk-bar-close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 4px;
  border-radius: var(--r-xs);
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.tbl-bulk-bar-close:hover { color: var(--fg); background: var(--glass-2); }

@media (prefers-reduced-motion: reduce) {
  .tbl-bulk-bar { animation: none; }
  .tbl-row-detail-inner { animation: none; }
}

/* ============== TABLE VIRTUALIZE / STICKY / COLUMN MENU (Phase 4) ============== */

/* Scrollable body — used when Height is set or Virtualize is on. The sticky
   header (default) anchors to the top of this scroll area. */
.tbl-wrap--scroll {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* Opt-out for the sticky header — applied on the .tbl-root wrapper. */
.tbl-root.tbl-no-sticky .tbl th {
  position: static;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Column-visibility menu trigger sits in the toolbar next to extras. */
.tbl-col-menu-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.tbl-col-menu-wrap--auto { margin-left: auto; }

/* Toolbar action cluster (e.g. CSV export). When it is the first right-aligned
   element (no custom ToolbarContent), it anchors the right group. */
.tbl-toolbar-action {
  display: inline-flex;
  align-items: center;
}
.tbl-toolbar-action--auto { margin-left: auto; }

.tbl-col-menu-trigger {
  appearance: none;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--fg-dim);
  cursor: pointer;
  padding: 6px 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.tbl-col-menu-trigger:hover { color: var(--fg); border-color: var(--line-strong); background: var(--glass-2); }
.tbl-col-menu-trigger:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: 1px;
}
.tbl-col-menu-trigger.is-active {
  color: var(--accent-a);
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

/* Column-visibility popover — same surface vocabulary as the filter popover. */
.tbl-col-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: var(--z-drawer);
  min-width: 200px;
  max-width: 280px;
  background: var(--panel-float);
  backdrop-filter: var(--glass-fx-float);
  -webkit-backdrop-filter: var(--glass-fx-float);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--fg);
  animation: tbl-filter-popover-in var(--dur-med) var(--ease-out);
}
.tbl-col-menu-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.tbl-col-menu-title { font-weight: 500; }
.tbl-col-menu-close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 2px;
  border-radius: var(--r-xs);
  display: inline-flex;
}
.tbl-col-menu-close:hover { color: var(--fg); }

.tbl-col-menu-body {
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-height: 280px;
  overflow-y: auto;
}
.tbl-col-menu-option {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 0;
  cursor: pointer;
  color: var(--fg-muted);
}
.tbl-col-menu-option:hover { color: var(--fg); }

/* Avatar */
.avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--accent-grad);
  display: inline-grid;
  place-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--on-accent);
  flex: 0 0 28px;
  box-shadow: 0 0 0 2px var(--bg-0), 0 0 12px color-mix(in srgb, var(--accent-a) 40%, transparent);
  overflow: hidden;
  user-select: none;
}
.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Sizes (base .avatar is Medium / 28px) */
.avatar--sm { width: 24px; height: 24px; flex-basis: 24px; font-size: 10px; }
.avatar--lg { width: 40px; height: 40px; flex-basis: 40px; font-size: 15px; }
/* Square variant */
.avatar--square { border-radius: var(--r-sm); }
/* Counter ("+N") tile in a group */
.avatar--counter {
  background: var(--glass-3);
  color: var(--fg-muted);
  box-shadow: 0 0 0 2px var(--bg-0);
}

/* Wrapper that hosts the presence dot */
.avatar-wrap {
  position: relative;
  display: inline-flex;
  flex: none;
}
.avatar-status {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--bg-0);
}
.avatar-wrap--sm .avatar-status { width: 7px; height: 7px; }
.avatar-wrap--lg .avatar-status { width: 12px; height: 12px; }
.avatar-status--online  { background: var(--success); box-shadow: 0 0 0 2px var(--bg-0), 0 0 8px color-mix(in srgb, var(--success) 60%, transparent); }
.avatar-status--busy    { background: var(--danger);  box-shadow: 0 0 0 2px var(--bg-0), 0 0 8px color-mix(in srgb, var(--danger) 60%, transparent); }
.avatar-status--away    { background: var(--warning); box-shadow: 0 0 0 2px var(--bg-0), 0 0 8px color-mix(in srgb, var(--warning) 55%, transparent); }
.avatar-status--offline { background: var(--fg-faint); }

/* Stacked group */
.avatar-group {
  display: inline-flex;
  align-items: center;
}
.avatar-group > * + * {
  margin-left: -8px;
}
.avatar-group--sm > * + * { margin-left: -6px; }
.avatar-group--lg > * + * { margin-left: -12px; }

/* ============== TOASTS (DrylToast + DrylToastProvider) ============== */

/* Fixed-position host that holds the toast stack. pointer-events:none lets
   clicks pass through the empty space; each .toast-slot re-enables them. */
.toast-host {
  position: fixed;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-5);
  pointer-events: none;
  max-width: min(440px, calc(100vw - var(--sp-7)));
  width: max-content;
}

.toast-host.is-top-right     { top: 0;    right: 0; }
.toast-host.is-top-left      { top: 0;    left: 0; }
.toast-host.is-top-center    { top: 0;    left: 50%; transform: translateX(-50%); }
.toast-host.is-bottom-right  { bottom: 0; right: 0; flex-direction: column-reverse; }
.toast-host.is-bottom-left   { bottom: 0; left: 0; flex-direction: column-reverse; }
.toast-host.is-bottom-center { bottom: 0; left: 50%; transform: translateX(-50%); flex-direction: column-reverse; }

.toast-slot { pointer-events: auto; }

.toast {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  padding-right: 38px;          /* room for the close button */
  padding-bottom: calc(var(--sp-4) + 2px); /* room for the progress bar */
  border-radius: var(--r-md);
  background:
    var(--sheen-grad),
    var(--panel-soft);
  border: 1px solid var(--line-strong);
  backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  box-shadow:
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-md);
  min-width: 280px;
  max-width: 100%;
  isolation: isolate;
  overflow: hidden;
  color: var(--fg);
  font-size: 13px;
  line-height: 1.45;
  /* Direction-aware entrance is set further down per host position. */
  animation: toast-in-right var(--dur-slow) var(--ease-spring) both;
  transition:
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}

/* Subtle lift on hover — combined with the pause on progress this signals
   "I'm holding this open so you can read me". */
.toast:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-lg);
}

/* One-shot shine sweep across the toast shortly after entry. Pure delight. */
.toast::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(120deg, transparent 30%, color-mix(in srgb, var(--shimmer) 41%, transparent) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: toast-shine var(--dur-choreo) var(--ease-out) var(--delay-short) both;
  pointer-events: none;
  z-index: 3;
  mix-blend-mode: overlay;
}
@keyframes toast-shine {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

/* Direction-aware entry — slide in from the edge nearest the anchor. */
.toast-host.is-top-right .toast,
.toast-host.is-bottom-right .toast { animation-name: toast-in-right; }

.toast-host.is-top-left .toast,
.toast-host.is-bottom-left .toast  { animation-name: toast-in-left; }

.toast-host.is-top-center .toast    { animation-name: toast-in-top; }
.toast-host.is-bottom-center .toast { animation-name: toast-in-bottom; }

@keyframes toast-in-right {
  0%   { opacity: 0; transform: translateX(120%) scale(0.85); filter: blur(6px); }
  60%  { opacity: 1; transform: translateX(-3%)  scale(1.02); filter: blur(0); }
  100% { opacity: 1; transform: translateX(0)    scale(1);    filter: blur(0); }
}
@keyframes toast-in-left {
  0%   { opacity: 0; transform: translateX(-120%) scale(0.85); filter: blur(6px); }
  60%  { opacity: 1; transform: translateX(3%)    scale(1.02); filter: blur(0); }
  100% { opacity: 1; transform: translateX(0)     scale(1);    filter: blur(0); }
}
@keyframes toast-in-top {
  0%   { opacity: 0; transform: translateY(-110%) scale(0.9); filter: blur(6px); }
  60%  { opacity: 1; transform: translateY(5%)    scale(1.02); filter: blur(0); }
  100% { opacity: 1; transform: translateY(0)     scale(1);    filter: blur(0); }
}
@keyframes toast-in-bottom {
  0%   { opacity: 0; transform: translateY(110%) scale(0.9); filter: blur(6px); }
  60%  { opacity: 1; transform: translateY(-5%)  scale(1.02); filter: blur(0); }
  100% { opacity: 1; transform: translateY(0)    scale(1);    filter: blur(0); }
}

/* Exit keyframes — single shared animation. JS listens for `toast-out`
   animationend and tells .NET to drop the entry from the stack. The
   actual exit *rule* is defined further down so it wins over the
   direction-aware and ai-generated animation overrides. */
@keyframes toast-out {
  to { opacity: 0; transform: scale(0.92) translateY(-6px); filter: blur(3px); }
}

/* Icon chip — pops with spring after the toast itself lands. */
.toast-icon-chip {
  width: 30px;
  height: 30px;
  border-radius: var(--r-sm);
  display: grid;
  place-content: center;
  background: var(--glass-2);
  color: var(--fg-muted);
  border: 1px solid var(--line-strong);
  animation: toast-icon-pop var(--dur-slow) var(--ease-spring) var(--delay-short) both;
  position: relative;
  z-index: 2;
  flex-shrink: 0;
}
@keyframes toast-icon-pop {
  0%   { opacity: 0; transform: rotate(-18deg) scale(0.3); }
  60%  { opacity: 1; transform: rotate(8deg)   scale(1.15); }
  100% { opacity: 1; transform: rotate(0)      scale(1); }
}

/* Body content column */
.toast-content {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  position: relative;
  z-index: 2;
}
.toast-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.005em;
}
.toast-body {
  font-size: 12.5px;
  color: var(--fg-muted);
  line-height: 1.5;
}
.toast-title + .toast-body { margin-top: 2px; }

/* Close button */
.toast-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 22px;
  height: 22px;
  display: inline-grid;
  place-content: center;
  border-radius: var(--r-xs);
  background: transparent;
  border: 1px solid transparent;
  color: var(--fg-dim);
  cursor: pointer;
  transition:
    color        var(--dur-fast) var(--ease-out),
    background   var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
  z-index: 5;
}
.toast-close:hover {
  color: var(--fg);
  background: var(--glass-2);
  border-color: var(--line);
}
.toast-close:focus-visible {
  outline: none;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* Progress bar — single source of truth for auto-dismiss. The CSS animation
   drives both the visual countdown and (via animationend in dryl.toast.js)
   the actual close. `animation-play-state: paused` on hover keeps the
   timer and the bar perfectly in sync. */
.toast-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  width: 100%;
  background: linear-gradient(90deg, var(--accent-a), var(--accent-b));
  transform-origin: left center;
  animation-name: toast-progress;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
  pointer-events: none;
  z-index: 4;
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}
.toast-progress.is-hidden { opacity: 0; }
.toast:hover .toast-progress { animation-play-state: paused; }
@keyframes toast-progress {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}

/* ── Variants — semantic accent on icon chip, colored left rail, soft glow.
   The left rail is drawn with an inset box-shadow so the radius still clips
   it cleanly. */
.toast.toast-success {
  border-color: color-mix(in srgb, var(--success) 28%, transparent);
  box-shadow:
    inset 3px 0 0 var(--success),
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-md),
    0 0 32px color-mix(in srgb, var(--success) 18%, transparent);
}
.toast.toast-success .toast-icon-chip {
  background: color-mix(in srgb, var(--success) 15%, transparent);
  color: var(--success);
  border-color: color-mix(in srgb, var(--success) 35%, transparent);
}
.toast.toast-success .toast-progress {
  background: linear-gradient(90deg, var(--success), var(--success-hi));
}

.toast.toast-warning {
  border-color: color-mix(in srgb, var(--warning) 28%, transparent);
  box-shadow:
    inset 3px 0 0 var(--warning),
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-md),
    0 0 32px color-mix(in srgb, var(--warning) 18%, transparent);
}
.toast.toast-warning .toast-icon-chip {
  background: color-mix(in srgb, var(--warning) 15%, transparent);
  color: var(--warning);
  border-color: color-mix(in srgb, var(--warning) 35%, transparent);
}
.toast.toast-warning .toast-progress {
  background: linear-gradient(90deg, var(--warning), var(--warning-hi));
}

.toast.toast-error {
  border-color: color-mix(in srgb, var(--danger) 32%, transparent);
  box-shadow:
    inset 3px 0 0 var(--danger),
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-md),
    0 0 36px color-mix(in srgb, var(--danger) 22%, transparent);
}
.toast.toast-error .toast-icon-chip {
  background: color-mix(in srgb, var(--danger) 15%, transparent);
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
}
.toast.toast-error .toast-progress {
  background: linear-gradient(90deg, var(--danger), var(--danger-hi));
}

.toast.toast-info {
  border-color: color-mix(in srgb, var(--info) 28%, transparent);
  box-shadow:
    inset 3px 0 0 var(--info),
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-md),
    0 0 32px color-mix(in srgb, var(--info) 18%, transparent);
}
.toast.toast-info .toast-icon-chip {
  background: color-mix(in srgb, var(--info) 15%, transparent);
  color: var(--info);
  border-color: color-mix(in srgb, var(--info) 35%, transparent);
}
.toast.toast-info .toast-progress {
  background: linear-gradient(90deg, var(--info), var(--info-hi));
}

/* ── AI dominance (CLAUDE.md §2.10) ── When .ai-aura is set, the rotating
   gradient ring and breathing glow take over the visual; the variant left
   rail and tinted border step back so the AI signal reads cleanly. The
   variant accent survives only on the icon chip so a user can still tell
   "AI delivered a success / warning / error". */
.toast.ai-aura {
  border-color: var(--accent-line);
}
.toast.ai-aura.toast-success,
.toast.ai-aura.toast-warning,
.toast.ai-aura.toast-error,
.toast.ai-aura.toast-info {
  box-shadow:
    inset 0 1px 0 var(--edge-hi),
    var(--shadow-md);
}
/* The host has overflow:hidden so the outer ai-aura ring/glow would be
   clipped. Pull them inside the bounds — same trick as DrylTable. */
.toast.ai-aura .ai-aura-ring { inset: 0; }
.toast.ai-aura .ai-aura-glow {
  box-shadow:
    inset 0 0 0 1px var(--accent-line),
    inset 0 0 32px color-mix(in srgb, var(--ai-a) 18%, transparent),
    inset 0 0 64px color-mix(in srgb, var(--ai-b) 8%, transparent);
}
.toast.ai-aura .toast-progress {
  background: var(--accent-grad);
}
/* The standard ai-generated translate would conflict with the entrance lift. */
.toast.ai-aura.ai-generated { animation: none; }

/* Exit animation — placed last with .toast-host scoping so the specificity
   (0,3,0) matches the direction rules and the ai-generated override, and
   source order makes this rule win. Without this, .toast.is-leaving (0,2,0)
   would lose to those, the exit animation would never play, animationend
   would never fire for `toast-out`, and the toast would sit on screen
   forever — close button included. */
.toast-host .toast.is-leaving {
  animation: toast-out var(--dur-med) var(--ease-out) forwards;
  pointer-events: none;
}
.toast-host .toast.is-leaving::after          { animation: none; opacity: 0; }
.toast-host .toast.is-leaving .toast-progress { animation: none; opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .toast,
  .toast::after,
  .toast-icon-chip,
  .toast-progress { animation: none; }
  .toast:hover { transform: none; }
}

/* Skeleton — the shimmer is an oversized gradient strip sliding via transform
   (compositor) instead of a background-position sweep, which repaints every
   frame — noticeable when a loading view shows a whole stack of skeletons.
   Geometry matches the old 200%-tile, two-periods-per-cycle sweep. */
.skel {
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--glass-1);
  border-radius: 6px;
  height: 14px;
}
.skel::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 500%;
  background: linear-gradient(90deg, var(--glass-1) 0%, var(--glass-3) 50%, var(--glass-1) 100%);
  background-size: 40% 100%;
  animation: skel 1.4s var(--ease-in-out) infinite;
}
@keyframes skel { 0% { translate: -80% 0; } 100% { translate: 0 0; } }
/* A placeholder is usually most of the screen while a page loads, so the one
   thing on it that never stops moving is the one that matters most here. With
   motion reduced the sliding strip is not painted at all and the block rests as
   a flat token surface; it still reads as "content is coming", without the
   sweep (UX-06). Components that recolor the strip restore their signal as a
   static tint instead — see DrylSkeleton's streaming state. */
@media (prefers-reduced-motion: reduce) {
  .skel::before { display: none; }
  /* Resting on --glass-3 rather than --glass-1: that is the bright midpoint the
     sweep passes through, so a still block is as legible as a moving one was at
     its clearest, instead of sitting at the dim end of its own range. It is the
     difference between reading as a placeholder and reading as nothing in light
     mode, where --glass-1 is near-white on a near-white page. */
  .skel { background: var(--glass-3); }
}

/* Spinner */
.spinner {
  display: block;
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--edge-hi-strong) 56%, transparent);
  border-top-color: var(--accent-b);
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Progress */
.progress {
  height: 6px;
  width: 100%;
  border-radius: var(--r-pill);
  background: var(--glass-2);
  overflow: hidden;
  position: relative;
}
.progress-bar {
  height: 100%;
  background: var(--accent-grad);
  border-radius: var(--r-pill);
  box-shadow: 0 0 12px color-mix(in srgb, var(--accent-a) 50%, transparent);
  transition: width var(--dur-slow) var(--ease-out);
}

/* DrylProgress — wrapper, label row, sizes, variants, indeterminate */
.progress-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: 100%;
}
.progress-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  font-size: 12px;
  color: var(--fg-muted);
}
.progress-pct {
  font-family: var(--font-mono);
  color: var(--fg);
}
/* Aura host sits outside the track so the ring is not clipped by overflow:hidden */
.progress-wrap {
  position: relative;
  width: 100%;
  border-radius: var(--r-pill);
}
.progress--sm { height: 4px; }
.progress--lg { height: 10px; }

.progress-bar--success { background: var(--success); box-shadow: 0 0 12px color-mix(in srgb, var(--success) 50%, transparent); }
.progress-bar--warning { background: var(--warning); box-shadow: 0 0 12px color-mix(in srgb, var(--warning) 50%, transparent); }
.progress-bar--danger  { background: var(--danger);  box-shadow: 0 0 12px color-mix(in srgb, var(--danger) 50%, transparent); }

.progress--indeterminate .progress-bar {
  width: 40%;
  transition: none;
  animation: progress-indeterminate 1.4s var(--ease-in-out) infinite;
}
@keyframes progress-indeterminate {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(320%); }
}
@media (prefers-reduced-motion: reduce) {
  .progress--indeterminate .progress-bar { animation: none; width: 100%; }
}

/* ============== TABS (DrylTabs / DrylTab) ============== */

.tabs-root {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.tabs {
  position: relative;
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  padding: 0 var(--sp-1);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }

.tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 14px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--fg-muted);
  white-space: nowrap;
  flex-shrink: 0;
  cursor: pointer;
  user-select: none;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  isolation: isolate;
  transition:
    color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.tab:hover { color: var(--fg); background: var(--glass-1); }
.tab:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--accent-soft);
}

.tab.is-active { color: var(--fg); }
.tab.is-active::after {
  content: '';
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: -1px;
  height: 2px;
  background: var(--accent-grad);
  border-radius: 2px;
  box-shadow: 0 0 12px var(--accent-a);
  transform-origin: center;
  animation: tab-underline-in var(--dur-med) var(--ease-spring) both;
  z-index: 2;
}
@keyframes tab-underline-in {
  from { transform: scaleX(0); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}

/* Gliding underline (DrylTabs AnimateIndicator, the default). A single shared
   ink element is measured + positioned by dryl.motion.moveIndicator; CSS
   glides it on a spring. When present it replaces the per-tab ::after, which
   stays as the fallback when AnimateIndicator=false or JS is unavailable. */
.tabs--glide .tab.is-active::after { display: none; }
.tab-ink {
  position: absolute;
  bottom: -1px;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--accent-grad);
  border-radius: 2px;
  box-shadow: 0 0 12px var(--accent-a);
  opacity: 0;
  pointer-events: none;
  z-index: 2;
}
.tabs.is-ink-ready .tab-ink {
  transition:
    transform var(--dur-med) var(--ease-spring),
    width var(--dur-med) var(--ease-spring),
    opacity var(--dur-fast) var(--ease-out);
}

.tab.is-disabled,
.tab[disabled] {
  color: var(--fg-faint);
  cursor: not-allowed;
  pointer-events: none;
}

.tab .tab-icon {
  flex: 0 0 14px;
  opacity: 0.85;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.tab:hover .tab-icon,
.tab.is-active .tab-icon { opacity: 1; }

/* Trailing slot for badges, counters, AI indicators etc. */
.tab-trailing {
  display: inline-flex;
  align-items: center;
  margin-left: 2px;
}

/* AI mode on a single tab — additive ring drawn via a dedicated .tab-comet
   child (kept inside the tab's rounded bounds so the rotating gradient traces
   just this tab, not the whole tab bar). The .is-active ::after underline
   still wins on top, so an active+AI tab reads as both selected and AI-busy.
   The wrapper carries the static border mask; only its oversized conic
   square ::before rotates (transform → compositor, no per-frame paint). */
.tab.ai-aura {
  color: var(--accent-fg);
  background: var(--accent-soft);
}
.tab-comet {
  position: absolute;
  inset: 2px 2px 0 2px;
  border-radius: var(--r-sm);
  padding: 1px;
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  overflow: hidden; /* keep the rotating square's layer bounds inside the tab */
  container-type: size;
  pointer-events: none;
  z-index: 1;
  opacity: 0.85;
}
.tab-comet::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150cqmax;
  height: 150cqmax;
  translate: -50% -50%;
  background: conic-gradient(
    from 0deg,
    var(--accent-a),
    var(--accent-b),
    var(--accent-a),
    var(--accent-b),
    var(--accent-a)
  );
  animation: ai-comet-spin 6s linear infinite;
}
.tab.ai-thinking .tab-comet  { opacity: 1; }
.tab.ai-generated .tab-comet { opacity: 1; }
.tab.ai-thinking .tab-comet::before  { animation-duration: 1.8s; }
.tab.ai-streaming .tab-comet::before { animation-duration: 3s; }
.tab.ai-generated .tab-comet::before { animation-duration: 4s; }

/* Tab panel — fades in on switch via @key on the panel element */
.tab-panel {
  padding: var(--sp-5) 0 0;
  animation: tab-panel-in var(--dur-med) var(--ease-out) both;
  min-width: 0;
}
@keyframes tab-panel-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Direction-aware content entry: switching forward slides the new panel in
   from the right, switching back from the left (reuses the presence
   keyframes — 12px travel reads clearly without feeling like a carousel). */
.tab-panel--fwd  { animation-name: presence-in-left; }
.tab-panel--back { animation-name: presence-in-right; }

@media (prefers-reduced-motion: reduce) {
  .tab.is-active::after,
  .tab-comet::before,
  .tab-panel,
  .tab-panel--fwd,
  .tab-panel--back { animation: none; }
  .tab-comet { opacity: 0.6; }
}

/* Dialog */
body.dryl-scroll-locked { overflow: hidden; }

.dialog-backdrop {
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--backdrop) 92%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: grid;
  place-items: center;
  padding: var(--sp-5);
  z-index: var(--z-modal);
  animation: fadeIn var(--dur-med) var(--ease-out);
  /* Exit via transition (not animation) so an interrupted fade — a new dialog
     arriving while the last one leaves — reverses smoothly from the current
     opacity instead of restarting. */
  transition: opacity var(--dur-med) var(--ease-out);
}

/* Fullscreen dialogs fill the viewport — kill the backdrop padding so they
   actually reach the edges. */
.dialog-backdrop:has(.dialog-fullscreen) {
  padding: 0;
}

/* Exit — DrylDialogProvider adds .is-exiting once every remaining dialog is
   exiting; entries are removed on dialogOut's animationend (dryl.motion.onExit
   on the layer) with a C#-side watchdog as fallback. pointer-events: none is
   the safety net: a fading (or somehow stuck) overlay must never eat clicks. */
.dialog-backdrop.is-exiting {
  opacity: 0;
  pointer-events: none;
}

/* Each service dialog lives in its own full-viewport layer above the shared
   backdrop, so stacked or hand-off dialogs never double the dark/blur wash. */
.dialog-layer {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: var(--sp-5);
}
.dialog-layer:has(.dialog-fullscreen) { padding: 0; }
.dialog-layer.is-exiting { pointer-events: none; }
.dialog-layer.is-exiting .dialog { animation: dialogOut var(--dur-med) var(--ease-in-out) both; }

/* Handoff — this dialog was opened while its predecessor is still animating
   out: hold the enter for a beat so the swap reads as a sequenced cross-fade
   instead of a collision. */
.dialog-layer.is-handoff:not(.is-exiting) .dialog {
  animation-delay: var(--dur-fast);
  animation-fill-mode: backwards;
}

@keyframes dialogOut {
  from { opacity: 1; transform: scale(1) translateY(0); }
  to   { opacity: 0; transform: scale(0.96) translateY(8px); }
}

.dialog {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 640px;
  max-width: 100%;
  max-height: 90vh; /* fallback for browsers without dvh */
  max-height: 90dvh;
  border-radius: var(--r-lg);
  background: var(--panel-grad);
  backdrop-filter: var(--glass-fx-float);
  -webkit-backdrop-filter: var(--glass-fx-float);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-lg), 0 0 80px color-mix(in srgb, var(--accent-a) 25%, transparent);
  animation: dialogIn var(--dur-med) var(--ease-spring);
  color: var(--fg);
}

.dialog-sm         { width: 420px; }
.dialog-md         { width: 640px; }
.dialog-lg         { width: 960px; }
.dialog-fullscreen {
  width: 100vw;
  height: 100vh; /* fallback for browsers without dvh */
  height: 100dvh;
  max-height: 100vh;
  max-height: 100dvh;
  border-radius: 0;
  border: none;
}

.dialog-header {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}

.dialog-header-icon {
  display: inline-flex;
  width: 28px;
  height: 28px;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  background: var(--glass-2);
  border: 1px solid var(--line);
  color: var(--fg-muted);
  flex: 0 0 auto;
}

.dialog-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg);
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dialog-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid transparent;
  color: var(--fg-muted);
  cursor: pointer;
  flex: 0 0 auto;
  transition:
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}
.dialog-close:hover {
  background: var(--glass-2);
  border-color: var(--line);
  color: var(--fg);
}
.dialog-close:focus-visible {
  outline: none;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

.dialog-body {
  padding: var(--sp-5);
  overflow-y: auto;
  flex: 1 1 auto;
  color: var(--fg-muted);
  line-height: 1.55;
}
.dialog-body > :first-child { margin-top: 0; }
.dialog-body > :last-child  { margin-bottom: 0; }

.dialog-footer {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--line);
  flex: 0 0 auto;
}

.dialog-fullscreen .dialog-body { max-height: none; }

/* Fullscreen dialogs respect the safe area (iOS home indicator). */
.dialog-fullscreen .dialog-footer {
  padding-bottom: calc(var(--sp-4) + env(safe-area-inset-bottom));
}

/* Mobile: non-fullscreen dialogs dock to the bottom edge as a sheet —
   full width, rounded top, dvh-bounded height. Fullscreen dialogs are
   unaffected (they fill everything anyway). */
@media (max-width: 640px) {
  .dialog-backdrop:not(:has(.dialog-fullscreen)),
  .dialog-layer:not(:has(.dialog-fullscreen)) {
    place-items: end center;
    padding: var(--sp-6) 0 0;
  }

  .dialog:not(.dialog-fullscreen) {
    width: 100%;
    max-height: calc(100dvh - var(--sp-8));
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-left: none;
    border-right: none;
    border-bottom: none;
  }

  .dialog:not(.dialog-fullscreen) .dialog-footer {
    padding-bottom: calc(var(--sp-4) + env(safe-area-inset-bottom));
  }
}

@keyframes dialogIn {
  from { opacity: 0; transform: scale(0.96) translateY(8px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

/* Tooltip */
.tt-wrap { display: inline-flex; }
/* The bubble lives as a body-level portal (see dryl.tooltip in dryl.js) so it
   escapes ancestor overflow/backdrop-filter clipping; the JS flips the
   preferred placement when the viewport has no room on that side. */
.tt-portal {
  position: fixed;
  z-index: 9000;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 11px;
  white-space: nowrap;
  background: var(--panel-solid);
  border: 1px solid var(--line-strong);
  color: var(--fg);
  pointer-events: none;
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.tt-portal.from-below { transform: translateY(-4px); }
.tt-portal.is-open { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .tt-portal { transition: none; }
}

/* Alert */
.alert {
  display: flex;
  gap: 12px;
  padding: 14px 16px;
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  background: var(--glass-1);
  backdrop-filter: var(--glass-fx-flow);
}
.alert .ico {
  width: 28px; height: 28px;
  border-radius: 8px;
  display: grid; place-content: center;
  flex: 0 0 28px;
}
.alert.info .ico    { background: var(--accent-soft); color: var(--accent-ico); }
.alert.success .ico { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.alert.warning .ico { background: color-mix(in srgb, var(--warning) 15%, transparent); color: var(--warning); }
.alert.danger .ico  { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
/* KI-Variante — Akzentgradient als Icon-Chip, violett-cyanfarbener Text */
.alert.ai .ico      { background: var(--accent-soft); color: var(--accent-ico); }
.alert .title { font-weight: 500; font-size: 13px; margin-bottom: 2px; }
.alert .body { font-size: 12.5px; color: var(--fg-muted); }
/* Flexible text area — takes the remaining space */
.alert-content { flex: 1; min-width: 0; }
/* Dismiss button */
.alert.is-dismissible { padding-right: var(--sp-3); }
.alert-dismiss {
  margin-left: auto;
  flex-shrink: 0;
  align-self: flex-start;
  width: 24px; height: 24px;
  display: grid; place-content: center;
  background: transparent;
  border: none;
  border-radius: var(--r-xs);
  color: var(--fg-dim);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color     var(--dur-fast) var(--ease-out);
}
.alert-dismiss:hover { background: var(--glass-3); color: var(--fg); }
.alert-dismiss:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 1px; }
/* AI aura on the alert — the border recedes so the gradient ring dominates.
   The alert has no overflow:hidden, so the default inset of -1px works. */
.alert.ai-aura { border-color: var(--accent-line); }

/* Spotlight card */
.spotlight-card {
  position: relative;
  overflow: hidden;
}
.spotlight-card .spotlight-fx {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(360px circle at var(--mx, 50%) var(--my, 50%),
              color-mix(in srgb, var(--accent-a) 18%, transparent),
              color-mix(in srgb, var(--accent-b) 6%, transparent) 30%,
              transparent 60%);
  transition: opacity var(--dur-med) var(--ease-out);
  z-index: 1;
}
.spotlight-card:hover .spotlight-fx { opacity: 1; }
[data-spotlights="off"] .spotlight-card:hover .spotlight-fx { opacity: 0; }
[data-spotlights="off"] .spotlight-card:hover::before { opacity: 0; }
.spotlight-card .spotlight-content { position: relative; z-index: 2; }
.spotlight-card::before {
  /* Edge light that activates on hover */
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: radial-gradient(360px circle at var(--mx, 50%) var(--my, 50%),
              color-mix(in srgb, var(--accent-a) 60%, transparent), transparent 50%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0;
  transition: opacity var(--dur-med) var(--ease-out);
  pointer-events: none;
  z-index: 3;
}
.spotlight-card:hover::before { opacity: 1; }

/* Chart helpers */
.chart-card { padding: 22px 24px; }
.chart-legend { display: flex; gap: 18px; font-size: 12px; color: var(--fg-muted); }
.legend-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }

/* ============================================================
   AI MODE — ambient signal for AI-driven surfaces
   ============================================================
   Apply alongside any existing surface class (.glass-card, .btn, .input, …).
   Implemented as child elements (not pseudos) so it composes with surfaces
   that already use ::before/::after (spotlight-card, glass-card).

     <div class="glass-card ai-aura">
       <div class="ai-aura-ring"></div>
       <div class="ai-aura-comet"></div>
       <div class="ai-aura-glow"></div>
       …content…
     </div>

   Modifiers on the same host:
     .ai-thinking   — accelerates the ring + glow for active tool calls
     .ai-streaming  — long-running token stream (slightly slower than thinking)
     .ai-generated  — one-shot reveal after generation completes
*/

/* Legacy registered angle property — the comet no longer animates it (it spins
   via transform on the compositor, see .ai-aura-comet); kept registered so any
   consumer CSS reading var(--ai-aura-angle) keeps resolving to a valid angle. */
@property --ai-aura-angle { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

/* ── Host + per-state design tokens ──────────────────────────────────────
   Every AI-aware surface sets .ai-aura (+ an optional state class). The state
   classes only reset a handful of CSS vars; the ring / glow / wash read them,
   so both aura variants (Comet default, .ai-aura--aurora) share ONE colour +
   motion language and differ only in how the ring paints. Base .ai-aura (no
   state class) == Active. --ai-hot / --ai-cool bias only between the themed
   --ai-a / --ai-b (no new hues); --ai-core is the specular head (theme-aware). */
.ai-aura {
  position: relative;
  isolation: isolate;
  --ai-hot: var(--ai-a);
  --ai-cool: var(--ai-b);
  --aura-base: 26%;          /* even base-saum accent strength   */
  --aura-halo: 26px;         /* halo blur radius                 */
  --aura-halo-a: 18%;        /* halo inner-colour strength       */
  --aura-comet-dur: 9s;      /* comet orbit period               */
  --aura-halo-dur: 6s;       /* halo breathing period            */
  --aura-drift-dur: 12s;     /* aurora field drift period        */
  --aura-sheen: none;        /* directional sheen (Streaming)    */
}
.ai-aura.ai-thinking {       /* concentrating — violet-dominant, fast & tight */
  --ai-hot: var(--ai-a); --ai-cool: var(--ai-a);
  --aura-base: 34%; --aura-halo: 20px; --aura-halo-a: 30%;
  --aura-comet-dur: 3.2s; --aura-halo-dur: 1.6s; --aura-drift-dur: 4s;
}
.ai-aura.ai-streaming {      /* output flowing — cyan-dominant, directional */
  --ai-hot: var(--ai-b); --ai-cool: var(--ai-b);
  --aura-base: 28%; --aura-halo: 34px; --aura-halo-a: 22%;
  --aura-comet-dur: 5s; --aura-halo-dur: 2.6s; --aura-drift-dur: 6s;
  --aura-sheen: block;
}
.ai-aura.ai-generated {      /* done — calm, carries the one-shot bloom */
  --aura-base: 40%; --aura-halo: 30px; --aura-halo-a: 26%;
  --aura-comet-dur: 6s; --aura-halo-dur: 5s; --aura-drift-dur: 5s;
}

/* ── COMET (default) ── even base saum + a travelling specular comet ────── */

/* Even, aspect-independent base saum: a themed hairline via a linear (not
   conic) gradient, so wide surfaces never go patchy on the long edges. */
.ai-aura-ring {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--ai-hot) calc(var(--aura-base) * var(--ai-strength)), transparent),
    color-mix(in srgb, var(--ai-cool) calc(var(--aura-base) * var(--ai-strength)), transparent));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 2;
}
/* The comet: a masked-border conic with one bright, localized arc that orbits —
   a long glowing tail building to a specular white head. The chained drop-shadows
   let the head bloom (white core + wider cool halo) past the 1px border.

   Performance: the wrapper (.ai-aura-comet, a sibling of the ring) is static —
   it carries the border mask and the bloom filter. Only its ::before rotates:
   an oversized conic square spun via transform, which runs on the compositor.
   Animating the conic's angle instead (the old @property approach) repaints
   the gradient + re-runs both drop-shadows on the main thread every frame,
   which multiplied across auras is enough to tank mid-range machines. */
.ai-aura-comet {
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1.5px;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  /* Clip the rotating square to the wrapper: without this its composited
     layer bounds span far past the surface and the compositor pays for the
     overlap every frame (measured ~2× frame cost on aura-dense pages). */
  overflow: hidden;
  container-type: size;
  pointer-events: none;
  z-index: 2;
  filter:
    drop-shadow(0 0 6px color-mix(in srgb, var(--ai-core) 78%, transparent))
    drop-shadow(0 0 13px color-mix(in srgb, var(--ai-cool) 45%, transparent));
}
/* 150cqmax ≥ the wrapper's diagonal (√2 · max side), so the rotating square
   covers the border band at every angle regardless of aspect ratio. */
.ai-aura-comet::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150cqmax;
  height: 150cqmax;
  translate: -50% -50%;
  background: conic-gradient(from 0deg,
    transparent 0deg 284deg,
    color-mix(in srgb, var(--ai-hot) 60%, transparent) 318deg,
    color-mix(in srgb, var(--ai-cool) 95%, transparent) 343deg,
    var(--ai-core) 351deg,
    color-mix(in srgb, var(--ai-cool) 55%, transparent) 356deg,
    transparent 360deg);
  animation: ai-comet-spin var(--aura-comet-dur) linear infinite;
}

/* Breathing halo behind the host. */
.ai-aura-glow {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--ai-hot) calc(34% * var(--ai-strength)), transparent),
    0 0 var(--aura-halo) color-mix(in srgb, var(--ai-hot) calc(var(--aura-halo-a) * var(--ai-strength)), transparent),
    0 0 calc(var(--aura-halo) * 2.2) color-mix(in srgb, var(--ai-cool) calc(12% * var(--ai-strength)), transparent);
  animation: ai-aura-breathe var(--aura-halo-dur) var(--ease-in-out) infinite;
}
/* Streaming: a directional sheen sweeping in reading direction, clipped to the
   host's rounded box by the glow's overflow. The gradient strip is oversized
   and slides via transform (compositor) instead of a background-position move,
   which would repaint every frame. Travel matches the old -30%→130% position
   sweep on a 220%-wide tile (offsets expressed in the strip's own width). */
.ai-aura-glow::before {
  content: '';
  display: var(--aura-sheen, none);
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 220%;
  background: linear-gradient(100deg,
    transparent 38%,
    color-mix(in srgb, var(--ai-b) 26%, transparent) 50%,
    transparent 62%);
  animation: ai-aura-sheen 2.4s var(--ease-in-out) infinite;
}

@keyframes ai-comet-spin   { to { rotate: 1turn; } }
@keyframes ai-aura-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
@keyframes ai-aura-sheen      { 0% { translate: 16.4% 0; } 55%, 100% { translate: -70.9% 0; } }

/* ── AURORA (variant) ── soft, blurred, flowing edge field; no travelling head.
   Calm enough to show many at once. Same states + colour weighting; only the
   ring paint changes (halo, sheen and bloom are shared with Comet).
   The flow is two oversized gradient strips sliding via transform (compositor)
   under the ring's static mask — the counter-drifting layer simply runs the
   same keyframes half a period out of phase. The old multi-layer
   background-position drift repainted three gradients per element per frame. */
.ai-aura--aurora .ai-aura-ring {
  inset: -2px;
  padding: 2.5px;
  background: none;
  overflow: hidden; /* clip the oversized drifting strips' layer bounds */
  filter: blur(1.5px);
}
.ai-aura--aurora .ai-aura-ring::before,
.ai-aura--aurora .ai-aura-ring::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 200%;
  animation: ai-aura-drift var(--aura-drift-dur) var(--ease-in-out) infinite;
}
.ai-aura--aurora .ai-aura-ring::before {
  background:
    radial-gradient(58% 130% at 18% 0%,  color-mix(in srgb, var(--ai-hot) 92%, transparent), transparent 62%),
    radial-gradient(90% 150% at 50% 50%, color-mix(in srgb, var(--ai-hot) calc(34% * var(--ai-strength)), transparent), transparent 72%);
}
.ai-aura--aurora .ai-aura-ring::after {
  background:
    radial-gradient(58% 130% at 82% 100%, color-mix(in srgb, var(--ai-cool) 92%, transparent), transparent 62%);
  animation-delay: calc(var(--aura-drift-dur) / -2);
}
.ai-aura--aurora .ai-aura-comet { display: none; }
@keyframes ai-aura-drift {
  0%, 100% { translate: 0 0; }
  50%      { translate: -50% 0; }
}

/* ── Generated one-shot ── gentle lift + a bright bloom flashing inward from the
   edge, then settling. Render <div class="ai-aura-wash"></div> only while Generated. */
.ai-aura.ai-generated { animation: ai-generated-lift var(--dur-choreo) var(--ease-out) both; }
@keyframes ai-generated-lift {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.ai-aura-wash {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 3;
}
.ai-aura.ai-generated .ai-aura-wash {
  opacity: 0;
  box-shadow:
    inset 0 0 0 1px var(--ai-core),
    inset 0 0 22px color-mix(in srgb, var(--ai-hot) 26%, transparent),
    0 0 30px color-mix(in srgb, var(--ai-hot) 34%, transparent);
  animation: ai-aura-bloom var(--dur-choreo) var(--ease-out) both;
}
@keyframes ai-aura-bloom {
  0%   { opacity: 0; transform: scale(1.012); }
  28%  { opacity: 0.95; }
  100% { opacity: 0; transform: scale(1); }
}

/* Afterglow — once Generated has bloomed, the travelling comet retires, leaving a
   calm static saum + gently breathing halo: the "AI was here" trace, held just long
   enough to be read. It is not a resting place — AuraLifecycle takes the whole aura
   off the surface once the one-shot has played (see its Generated branch), because a
   halo that breathes for the life of the page costs frames for something that has
   nothing left to say. The retire fade runs on the static wrapper (opacity →
   compositor); the spin keeps its base ai-comet-spin animation on the ::before. */
.ai-aura.ai-generated .ai-aura-comet {
  animation: ai-comet-retire var(--dur-choreo) var(--ease-out) var(--delay-long) forwards;
}
/* The halo retires with it — it keeps breathing through the bloom, then fades out
   on the same beat. A host that composes an AuraLifecycle unmounts the aura
   outright; the two that drive these classes from item data (DrylTabs,
   DrylNotifications) have no lifecycle to do it, and a halo breathing for the rest
   of the page's life is frames spent on a surface with nothing left to say. */
.ai-aura.ai-generated .ai-aura-glow {
  animation:
    ai-aura-breathe var(--aura-halo-dur) var(--ease-in-out) infinite,
    ai-comet-retire var(--dur-choreo) var(--ease-out) var(--delay-long) forwards;
}
/* visibility: hidden at the end tells the compositor to stop drawing the
   (still spinning) comet surface entirely — a retired comet must not keep
   costing a render pass per frame for the rest of the surface's life. */
@keyframes ai-comet-retire { to { opacity: 0; visibility: hidden; } }

/* Graceful exit — when a surface leaves AI mode the host keeps the aura mounted
   for one --dur-slow beat with .ai-aura--out, dissolving the border + halo instead
   of snapping the DOM away (rule 2.12: mounts AND unmounts animate). */
.ai-aura--out .ai-aura-ring,
.ai-aura--out .ai-aura-comet,
.ai-aura--out .ai-aura-glow {
  animation: none;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .ai-aura .ai-aura-ring,
  .ai-aura .ai-aura-ring::before,
  .ai-aura .ai-aura-ring::after,
  .ai-aura .ai-aura-comet,
  .ai-aura .ai-aura-comet::before,
  .ai-aura .ai-aura-glow,
  .ai-aura .ai-aura-glow::before,
  .ai-aura .ai-aura-wash,
  .ai-aura.ai-generated .ai-aura-comet,
  .ai-aura.ai-generated .ai-aura-glow,
  .ai-aura.ai-generated .ai-aura-wash,
  .ai-aura.ai-generated { animation: none; transition: none; }
  /* keep the calm, even static saum; hide the now-frozen travelling comet */
  .ai-aura .ai-aura-comet { opacity: 0; }
}

/* ============== EXPANSION PANELS ============== */

/*
  Stacking behaviour
  ──────────────────
  Adjacent closed panels share their border (margin-top: -1px).
  • First/last panels get rounded top/bottom corners.
  • An open panel lifts out: full border-radius + gap margin + stronger shadow.
  • The panel after an open one resets its negative margin so the gap is clean.

  Body animation
  ──────────────
  Uses the grid-template-rows: 0fr → 1fr trick.
  The inner overflow:hidden wrapper collapses to zero height while the content
  itself is never "squeezed" — it always renders at its natural size.
*/

.expansion {
  position: relative;
  background: var(--glass-1);
  border: 1px solid var(--line);
  backdrop-filter: var(--glass-fx-flow);
  -webkit-backdrop-filter: var(--glass-fx-flow);
  border-radius: 0;
  z-index: 0;
  transition:
    margin         var(--dur-med) var(--ease-out),
    border-color   var(--dur-med) var(--ease-out),
    border-radius  var(--dur-med) var(--ease-out),
    box-shadow     var(--dur-med) var(--ease-out),
    z-index        0s             var(--dur-med); /* delay z-index reset until animation is done */
}

/* Collapse shared border between adjacent panels */
.expansion + .expansion { margin-top: -1px; }

/* Round top of first panel in a stack */
.expansion:first-child { border-radius: var(--r-md) var(--r-md) 0 0; }

/* Round bottom of last panel in a stack */
.expansion:last-child { border-radius: 0 0 var(--r-md) var(--r-md); }

/* Single standalone panel */
.expansion:first-child:last-child { border-radius: var(--r-md); }

/* Open state — detach from siblings */
.expansion.is-open {
  margin: var(--sp-3) 0;
  border-radius: var(--r-md);
  border-color: var(--line-strong);
  box-shadow: var(--shadow-md);
  z-index: 1;
  transition:
    margin         var(--dur-med) var(--ease-out),
    border-color   var(--dur-med) var(--ease-out),
    border-radius  var(--dur-med) var(--ease-out),
    box-shadow     var(--dur-med) var(--ease-out);
}

/* Sibling right after an open panel: cancel the shared-border pull-up */
.expansion.is-open + .expansion { margin-top: 0; }

/* Disabled panel */
.expansion.is-disabled { pointer-events: none; }
.expansion.is-disabled .expansion-header { opacity: 0.4; cursor: not-allowed; }

/* ── Header ── */
.expansion-header {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-4) var(--sp-5);
  background: none;
  border: none;
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  text-align: left;
  transition: color var(--dur-fast) var(--ease-out);
}

.expansion-header:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: -2px;
  border-radius: inherit;
}

.expansion-header:hover .expansion-chevron { color: var(--fg-muted); }

.expansion-icon { color: var(--fg-muted); flex-shrink: 0; }

.expansion-title { flex: 1; }

.expansion-header-trailing {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Chevron flips 180° when open */
.expansion-chevron {
  color: var(--fg-dim);
  flex-shrink: 0;
  transition: transform var(--dur-med) var(--ease-out), color var(--dur-med) var(--ease-out);
}
.expansion.is-open .expansion-chevron {
  transform: rotate(180deg);
  color: var(--fg-muted);
}

/* ── Body (grid-rows animation) ── */
.expansion-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-med) var(--ease-out);
}

.expansion.is-open .expansion-body {
  grid-template-rows: 1fr;
}

/* overflow:hidden here is what allows 0fr to fully collapse */
.expansion-body-inner {
  overflow: hidden;
}

.expansion-body-content {
  padding: 0 var(--sp-5) var(--sp-5);
  color: var(--fg-muted);
}

@media (prefers-reduced-motion: reduce) {
  .expansion,
  .expansion-body,
  .expansion-chevron { transition: none; }
}

/* ============== AI INDICATOR — small status pill ============== */
.ai-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 10px 0 8px;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--accent-fg);
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  position: relative;
  overflow: hidden;
}
.ai-indicator .ai-indicator-ico {
  flex: 0 0 12px;
  color: var(--ai-b);
  animation: ai-indicator-pulse 2.4s var(--ease-in-out) infinite;
}
.ai-indicator.is-thinking .ai-indicator-ico  { animation-duration: 1s; }
.ai-indicator.is-streaming .ai-indicator-ico { animation-duration: 1.6s; }

/* Subtle sweep across the pill background */
.ai-indicator::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 30%, color-mix(in srgb, var(--shimmer) 82%, transparent) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: ai-indicator-shimmer 3.6s var(--ease-in-out) infinite;
  pointer-events: none;
}
.ai-indicator.is-thinking::after  { animation-duration: 1.4s; }
.ai-indicator.is-streaming::after { animation-duration: 2.2s; }

@keyframes ai-indicator-pulse {
  0%, 100% { opacity: 0.7; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.12); }
}
@keyframes ai-indicator-shimmer {
  0%   { transform: translateX(-100%); }
  60%  { transform: translateX(100%); }
  100% { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
  .ai-indicator .ai-indicator-ico,
  .ai-indicator::after { animation: none; }
}

/* ============== MENU (DrylMenu / DrylMenuItem) ============== */

/* Positioning anchor — wraps trigger + panel together */
.menu-anchor {
  position: relative;
  display: inline-block;
}

/* Block variant: stretches the anchor full-width */
.menu-anchor--block {
  display: block;
}

/* Transparent pass-through for the trigger slot */
.menu-trigger { display: contents; }

/* Dropdown panel */
.menu-panel {
  position: absolute;
  top: calc(100% + var(--sp-1));
  left: 0;
  z-index: var(--z-drawer);
  min-width: 180px;
  /* Cap to the viewport on a phone so a right-anchored menu can't push off-screen. */
  max-width: min(320px, calc(100vw - var(--sp-4)));
  display: flex;
  flex-direction: column;
  background: var(--panel-float);
  backdrop-filter: var(--glass-fx-float);
  -webkit-backdrop-filter: var(--glass-fx-float);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-1) 0;
  outline: none;
  animation: menu-in var(--dur-med) var(--ease-out) both;
}

/* Placement variants */
.menu-panel--end { left: auto; right: 0; }
.menu-panel--top { top: auto; bottom: calc(100% + var(--sp-1)); }

@keyframes menu-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

/* Individual item — rendered as <button> with role="menuitem" */
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-2) var(--sp-4);
  background: transparent;
  border: 0;
  color: var(--fg-muted);
  font: 400 13px/1.4 var(--font-sans);
  text-align: left;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition:
    background var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}
.menu-item:hover { background: var(--glass-2); color: var(--fg); }
.menu-item:focus-visible {
  outline: none;
  background: var(--glass-2);
  color: var(--fg);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.menu-item:disabled,
.menu-item[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* Danger variant */
.menu-item--danger { color: var(--danger); }
.menu-item--danger:hover,
.menu-item--danger:focus-visible {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  color: var(--danger-fg);
}

/* Keyboard shortcut hint — pushed to trailing edge */
.menu-item-shortcut {
  margin-left: auto;
  padding-left: var(--sp-5);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
}

/* Section separator */
.menu-separator {
  height: 1px;
  background: var(--line);
  border: 0;
  margin: var(--sp-1) 0;
}

/* Section header */
.menu-header {
  padding: var(--sp-2) var(--sp-4) calc(var(--sp-1) / 2);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-dim);
  user-select: none;
}

@media (prefers-reduced-motion: reduce) {
  .menu-panel { animation: none; }
}

/* ============== NUMBER INPUT STEPPER (DrylInputNumber) ============== */

/* Stretch so the stepper fills the full input height */
.input-wrapper.has-stepper { align-items: stretch; }

/* Input side: take remaining space, square off the right edge */
.input-wrapper.has-stepper .input {
  flex: 1;
  min-width: 0;
  border-radius: var(--r-md) 0 0 var(--r-md);
  border-right: none;
}

/* Sync the separator to the input's border states */
.input-wrapper.has-stepper:hover .num-stepper        { border-left-color: var(--line-hover); }
.input-wrapper.has-stepper:focus-within .num-stepper { border-left-color: var(--accent-line); }

.num-stepper {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  border-left: 1px solid var(--line-strong);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  overflow: hidden;
  transition: border-left-color var(--dur-med) var(--ease-out);
}
.num-step-btn {
  flex: 1;
  display: grid;
  place-content: center;
  width: 28px;
  border: none;
  background: transparent;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 0;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.num-step-btn:hover:not(:disabled)  { color: var(--fg); background: var(--glass-2); }
.num-step-btn:active:not(:disabled) { background: var(--glass-3); color: var(--accent-a); }
.num-step-btn:first-child           { border-bottom: 1px solid var(--line); }
.num-step-btn:disabled              { opacity: 0.4; cursor: not-allowed; }
.num-step-btn:focus-visible {
  position: relative;
  z-index: 1;
  outline: 2px solid color-mix(in srgb, var(--accent-a) 50%, transparent);
  outline-offset: -2px;
}

/* hide native number spinners — we render our own */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; }

/* ============== RADIO GROUP (DrylRadioGroup / DrylRadio) ============== */

.radio-group {
  display: flex;
  gap: var(--sp-3);
}
.radio-group--vertical   { flex-direction: column; }
.radio-group--horizontal { flex-direction: row; flex-wrap: wrap; align-items: center; }

.radio {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  user-select: none;
}
.radio-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.radio-control {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  border-radius: var(--r-pill);
  background: var(--glass-1);
  border: 1px solid var(--line-strong);
  display: grid;
  place-content: center;
  transition: border-color var(--dur-fast) var(--ease-out),
              background   var(--dur-fast) var(--ease-out),
              box-shadow   var(--dur-fast) var(--ease-out);
}
.radio-control::after {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--knob);
  transform: scale(0);
  transition: transform var(--dur-med) var(--ease-spring);
}
.radio:hover .radio-control {
  border-color: var(--accent-line);
}
.radio-input:checked + .radio-control {
  background: var(--accent-grad);
  border-color: transparent;
  box-shadow: 0 0 14px color-mix(in srgb, var(--accent-a) 45%, transparent);
}
.radio-input:checked + .radio-control::after {
  transform: scale(1);
}
.radio:focus-within .radio-control {
  outline: 2px solid color-mix(in srgb, var(--accent-a) 50%, transparent);
  outline-offset: 2px;
}
.radio-label {
  font-size: 14px;
  color: var(--fg);
  line-height: 1.4;
}
.radio--disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* ============== CHIPS (used by DrylMultiSelect) ============== */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  background: var(--glass-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: 12px;
  color: var(--fg-muted);
  white-space: nowrap;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chip-remove {
  flex-shrink: 0;
  display: grid;
  place-content: center;
  width: 14px;
  height: 14px;
  border: none;
  background: transparent;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 0;
  border-radius: var(--r-xs);
  transition: color var(--dur-fast) var(--ease-out);
}
.chip-remove:hover { color: var(--fg); }
.chip-overflow {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--glass-2);
  border: 1px solid var(--line);
  font-size: 11px;
  color: var(--fg-dim);
  white-space: nowrap;
  flex-shrink: 0;
}
.multiselect-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1);
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

/* ============== FILE UPLOAD (DrylFileUpload) ============== */

.file-drop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-6) var(--sp-5);
  background: var(--glass-1);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  transition: border-color var(--dur-med) var(--ease-out),
              background   var(--dur-med) var(--ease-out),
              box-shadow   var(--dur-med) var(--ease-out);
  text-align: center;
  cursor: pointer;
}
.file-drop:hover,
.file-drop--active {
  border-color: var(--accent-line);
  background: var(--glass-2);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent-a) 8%, transparent), 0 0 28px color-mix(in srgb, var(--accent-a) 14%, transparent);
}
.file-drop--disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}
.file-drop-icon {
  color: var(--fg-dim);
  transition: color var(--dur-med) var(--ease-out);
}
.file-drop:hover .file-drop-icon,
.file-drop--active .file-drop-icon { color: var(--fg-muted); }
.file-drop-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-muted);
}
.file-drop-sub {
  font-size: 12px;
  color: var(--fg-dim);
}
.file-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.file-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--glass-1);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  min-width: 0;
}
.file-item-icon { flex-shrink: 0; color: var(--fg-dim); }
.file-item-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: var(--fg);
}
.file-item-size {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--fg-dim);
  font-family: var(--font-mono);
}
.file-item-remove {
  flex-shrink: 0;
  display: grid;
  place-content: center;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 0;
  border-radius: var(--r-xs);
  margin-left: auto;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.file-item-remove:hover {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
}

/* ============== SLIDER (DrylSlider) ============== */

.slider-wrap {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.slider-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}
.slider-value {
  font-size: 12px;
  color: var(--fg-muted);
  font-family: var(--font-mono);
}

@media (prefers-reduced-motion: reduce) {
  .radio-control, .radio-control::after { transition: none; }
}

/* ─────────────────────────────────────────────────────────
   DrylMarkdown — rendered Markdown content. These rules are global
   (not component-scoped) because the HTML is injected via MarkupString
   and therefore carries no CSS-isolation attribute.
   ───────────────────────────────────────────────────────── */
.md {
  position: relative;
  /* AI mode adds padding + glass (below); transitioning them on the base class
     makes both edges glide — the panel inflates when the aura mounts and, after
     the .ai-aura--out beat unmounts it, deflates over one --dur-slow instead of
     snapping the text back to the bare layout (rule 2.12). */
  background: transparent;
  border-radius: var(--r-lg);
  padding: 0;
  transition: padding var(--dur-slow) var(--ease-out),
              background-color var(--dur-slow) var(--ease-out);
}

/* In AI mode the rotating ring uses border-radius:inherit and the glow draws a
   1px outline — with a bare .md (no radius, no surface) that resolves to a sharp
   rectangle hugging the text. Give the host a glass surface + rounded corners so
   the ring frames a proper panel, mirroring DrylRating / DrylEmptyState. */
.md.ai-aura {
  background: var(--glass-1);
  padding: var(--sp-4);
}

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

.md-content {
  color: var(--fg-muted);
  font-size: 14px;
  line-height: 1.65;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.md-content > * { margin: 0; }

/* ── Streaming materialize (AI vocabulary, CLAUDE.md §2.10) ──
   Freshly streamed AI text should not pop in at full opacity — it comes
   alive. Two Blazor-safe reveals, no JS DOM surgery (that corrupts Blazor
   Server's batch diff):

   Plain text (DrylMessage Text=…, no Markdown) — each freshly arrived word is
   rendered server-side as a .stream-token span inside .stream-text; it blur-
   fades to crisp, staggered per word via --stream-i so a burst reads as a
   wave. Already-shown words are never re-created, so they never re-animate.

   Markdown (DrylMarkdown / a Markdown DrylMessage bubble) — words live inside
   Markdig markup that can't be tokenized without touching Blazor-owned nodes,
   so instead the *newest* top-level block (the one currently being written)
   carries a vertical hot→cold temperature: its bottom (the newest line, at the
   writing head) glows hot in --ai-a and cools upward through --ai-b to settled
   --fg. As the block grows, older lines rise into the cold zone — the text
   "cools with the writing head", the block-level echo of the per-word token
   cooldown. As each block finishes and the next begins, :last-child moves on
   and the settled block relaxes to normal text. Pure CSS, no markup change.

   .stream-caret / *::after — a small glowing accent dot riding the tip of the
   stream, the inline "the AI is here" indicator. Both reveals share it. */
.stream-token {
  display: inline;
  animation: stream-token-in var(--dur-slow) var(--ease-out) both;
  animation-delay: calc(var(--stream-i, 0) * 30ms);
}

/* Cooldown glow: a freshly arrived word ignites in the theme's start accent
   (--ai-a, "hot") — the glyph itself is tinted and blooms — then visibly cools
   to the end accent (--ai-b) and settles to normal text (--fg) as the glow dies.
   Words mount one after another behind the moving caret, so it reads as a short
   glowing trail cooling in the cursor's wake. Colour comes only from the AI
   accent tokens, so it follows whatever the active theme derives — never a
   hardcoded hue. Same --dur-slow / --ease-out as the reveal (no new motion).

   The reveal (opacity 0→1, blur 5→0) finishes early, in the first ~30%, so the
   hot phase is seen on a fully-visible word — opacity/blur would otherwise dim
   the glow exactly when it should be brightest. Every keyframe keeps two shadow
   layers so text-shadow interpolates cleanly; the settled word ends glowless. */
@keyframes stream-token-in {
  0% {
    opacity: 0;
    filter: blur(5px);
    color: var(--ai-a);
    text-shadow:
      0 0 8px  color-mix(in srgb, var(--ai-a) 75%, transparent),
      0 0 18px color-mix(in srgb, var(--ai-a) 45%, transparent);
  }
  30% {
    opacity: 1;
    filter: blur(0);
    color: var(--ai-a);
    text-shadow:
      0 0 8px  color-mix(in srgb, var(--ai-a) 75%, transparent),
      0 0 18px color-mix(in srgb, var(--ai-a) 45%, transparent);
  }
  65% {
    color: var(--ai-b);
    text-shadow:
      0 0 6px  color-mix(in srgb, var(--ai-b) 42%, transparent),
      0 0 13px color-mix(in srgb, var(--ai-b) 20%, transparent);
  }
  100% {
    opacity: 1;
    filter: blur(0);
    color: var(--fg);
    text-shadow:
      0 0 0 transparent,
      0 0 0 transparent;
  }
}

/* Newest-block cooldown. Excludes DrylCodeBlock (.code-block) — clipping a code
   surface to its text would erase its chrome. A vertical hot→cold gradient is
   anchored to the block box (0deg = bottom→top): the bottom (newest line, at
   the writing head) is hot --ai-a, cooling through --ai-b to settled --fg for
   the upper (older) lines. A short one-line block reads warm throughout; a tall
   one has most of its body cold with only the fresh bottom glowing.

   The gradient covers the box 1:1 (background-size 100% 100%, no-repeat), so
   EVERY glyph is painted and nothing goes transparent — the invisibility /
   caret-drift trap of an oversized swept image (see the 2.4.1 sheen fix) can't
   arise, because background-position is never animated. Instead the hot zone
   just breathes in brightness (stream-cooldown) so the glow stays alive between
   token bursts, while the actual cooling is driven by the text growing: each
   line rises out of the hot bottom into the cold body as the writing head
   descends. Colour comes only from the AI accent tokens + --fg. */
.md.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block),
.chat-bubble.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block) {
  background-image: linear-gradient(
    0deg,
    var(--ai-a) 0%, var(--ai-b) 22%,
    var(--fg) 58%, var(--fg) 100%);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
          color: transparent;
  animation: stream-cooldown var(--dur-slow) var(--ease-in-out) infinite alternate;
}

@keyframes stream-cooldown {
  from { filter: brightness(1); }
  to   { filter: brightness(1.12); }
}

.stream-caret,
.stream-text::after,
.md.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block)::after,
.chat-bubble.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block)::after {
  content: '';
  display: inline-block;
  width: 0.5em;
  height: 0.5em;
  margin-left: 0.14em;
  border-radius: var(--r-pill);
  vertical-align: baseline;
  background: linear-gradient(135deg, var(--ai-a), var(--ai-b));
  -webkit-text-fill-color: initial;   /* immune to the parent's text-fill on the sheen block */
  box-shadow:
    0 0 10px color-mix(in srgb, var(--ai-a) 60%, transparent),
    0 0 22px color-mix(in srgb, var(--ai-b) 35%, transparent);
  animation: stream-caret-pulse 1.2s var(--ease-in-out) infinite;
}

@keyframes stream-caret-pulse {
  0%, 100% { opacity: 1;    transform: scale(1); }
  50%      { opacity: 0.4;  transform: scale(0.68); }
}

@media (prefers-reduced-motion: reduce) {
  .stream-token { animation: none; }
  .md.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block),
  .chat-bubble.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block) {
    animation: none;
    /* Drop the clip so text returns to its normal colour, no frozen hot zone. */
    -webkit-text-fill-color: currentColor;
            color: var(--fg-muted);
    background-image: none;
  }
  /* Keep the caret as a static position marker; just stop it pulsing. */
  .stream-caret,
  .stream-text::after,
  .md.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block)::after,
  .chat-bubble.ai-aura.ai-streaming .md-content > *:last-child:not(.code-block)::after {
    animation: none;
  }
}

.md-content h1,
.md-content h2,
.md-content h3,
.md-content h4 {
  color: var(--fg);
  line-height: 1.3;
  font-weight: 600;
}
.md-content h1 { font-size: 22px; }
.md-content h2 { font-size: 18px; }
.md-content h3 { font-size: 15px; }
.md-content h4 { font-size: 14px; }

.md-content p { color: var(--fg-muted); }

.md-content a {
  color: var(--accent-b);
  text-decoration: none;
  border-bottom: 1px solid var(--accent-line);
  transition: color var(--dur-fast) var(--ease-out);
}
.md-content a:hover { color: var(--fg); }

.md-content strong { color: var(--fg); font-weight: 600; }

.md-content ul,
.md-content ol {
  padding-left: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.md-content li { color: var(--fg-muted); }

.md-content blockquote {
  padding: var(--sp-1) var(--sp-3);
  border-left: 2px solid var(--accent-line);
  color: var(--fg-dim);
  background: var(--glass-1);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}

/* Inline code (fenced blocks are delegated to DrylCodeBlock). */
.md-content :not(pre) > code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  padding: 0.1em 0.4em;
  color: var(--fg);
  background: var(--glass-2);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}

.md-content hr { border: none; border-top: 1px solid var(--line); }

.md-content table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.md-content th,
.md-content td {
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--line);
}
.md-content th { color: var(--fg); font-weight: 600; }
.md-content td { color: var(--fg-muted); }

/* ─────────────────────────────────────────────────────────
   DrylCitation / DrylCitationList — source attribution for AI answers.
   ───────────────────────────────────────────────────────── */
.citation-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  margin: 0 1px;
  vertical-align: super;
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1;
  color: var(--accent-b);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-xs);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.citation-chip:hover,
.citation-chip--open {
  color: var(--fg);
  background: var(--accent-grad);
}

.citation-pop {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: 280px;
  padding: var(--sp-1);
}
.citation-pop-title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--fg);
  font-size: 13px;
  font-weight: 600;
}
.citation-pop-title svg { color: var(--accent-b); flex: none; }
.citation-pop-snippet {
  margin: 0;
  color: var(--fg-muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.citation-pop-link,
.citation-item-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  color: var(--accent-b);
  font-size: 12px;
  text-decoration: none;
  word-break: break-all;
}
.citation-pop-link:hover,
.citation-item-link:hover { color: var(--fg); }

.citation-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.citation-list-title {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.citation-list-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  counter-reset: none;
}
.citation-item {
  display: flex;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--glass-1);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.citation-item-index {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--accent-b);
  background: var(--accent-soft);
  border-radius: var(--r-xs);
}
.citation-item-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}
.citation-item-title { color: var(--fg); font-size: 13px; font-weight: 600; }
.citation-item-snippet { margin: 0; color: var(--fg-muted); font-size: 12.5px; line-height: 1.5; }

/* ── DrylList / DrylListItem ───────────────────────────────────
   Token-driven list. The container (.list) sets the row gap via a
   density custom property; each item (.list-item) is a flex row of
   [marker | content | end]. Markers, dividers, selected and
   interactive states are all token-driven. Counter scoping per
   .list keeps Decimal numbering correct across nested sub-lists. */
.list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  counter-reset: dryl-list;
  --list-pad-y: var(--sp-2);
  --list-gap: var(--sp-1);
}
.list--compact     { --list-pad-y: var(--sp-1); --list-gap: 0; }
.list--default     { --list-pad-y: var(--sp-2); --list-gap: var(--sp-1); }
.list--comfortable { --list-pad-y: var(--sp-3); --list-gap: var(--sp-2); }

.list-item {
  counter-increment: dryl-list;
  display: flex;
  align-items: stretch;
  gap: var(--sp-2);
  margin-top: var(--list-gap);
  border-radius: var(--r-sm);
}
.list-item:first-child { margin-top: 0; }

/* Dividers — a hairline rule between adjacent rows (no inter-row gap). */
.list--dividers { --list-gap: 0; }
.list--dividers > .list-item { margin-top: 0; border-radius: 0; }
.list--dividers > .list-item + .list-item { border-top: 1px solid var(--line); }

/* Nested sub-list — indent and add a connector rail. */
.list .list-item .list {
  margin-top: var(--sp-1);
  padding-left: var(--sp-4);
  border-left: 1px solid var(--line);
}

/* Row body (div for static, button for interactive) — shared layout. */
.list-item-row {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--list-pad-y) var(--sp-2);
  border-radius: inherit;
  color: var(--fg-muted);
  text-align: left;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.list-item-content { min-width: 0; flex: 1 1 auto; line-height: 1.5; }

/* Marker + leading slots — aligned to the first text line. */
.list-marker,
.list-item-icon,
.list-item-start {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.5em;
}
.list-marker { min-width: var(--sp-3); color: var(--fg-dim); }
.list-marker--default::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--fg-dim);
}
.list-marker--disc::before { content: '\2022'; font-size: 1.1em; line-height: 1; }
.list-marker--dash::before { content: '\2013'; }
.list-marker--decimal {
  color: var(--fg-muted);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.list-marker--decimal::before { content: counter(dryl-list) '.'; }
.list-item-icon { color: var(--fg-dim); }

/* Trailing slot — pinned right, vertically centred on the row. */
.list-item-end {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding-right: var(--sp-2);
  align-self: center;
}

/* Interactive rows — button reset + hover/active affordance. */
button.list-item-row {
  width: 100%;
  font: inherit;
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
}
.list-item.is-interactive > .list-item-row:hover {
  background: var(--glass-1);
  border-color: var(--line);
  color: var(--fg);
}
button.list-item-row:focus-visible {
  outline: none;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Selected — accent line + glass tint. */
.list-item.is-selected > .list-item-row {
  background: var(--glass-2);
  border-left: 2px solid var(--accent-line);
  color: var(--fg);
}
.list-item.is-selected .list-marker { color: var(--accent-b); }

/* Disabled — dim and inert. */
.list-item.is-disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* ─────────────────────────────────────────────────────────
   Responsive foundation
   Breakpoint scale (px values intentionally literal — var() is
   illegal inside @container / @media conditions):
     Sm 480  ·  Md 768  ·  Lg 1024  ·  Xl 1280
   ───────────────────────────────────────────────────────── */

/* Make an element a container-query context so descendants can
   adapt to this element's inline size instead of the viewport. */
.cq { container-type: inline-size; }

/* Global safety layer — defensive + additive. Kills the
   "nothing shrinks / clips" bug class without an opinionated reset. */
img, svg, video, canvas { max-width: 100%; height: auto; }
.stack, .row, .col, .between, .glass-card { min-width: 0; }
.glass-card { overflow-wrap: anywhere; }
pre, .code-block, code { overflow-wrap: anywhere; }
pre { overflow-x: auto; }

/* Rows inside a card wrap instead of clipping on a narrow slot. A row that
   fits on one line is unaffected; it only wraps when it would overflow. */
.glass-card .row { flex-wrap: wrap; }

/* DrylStack CollapseBelow — horizontal stack flips to vertical
   below the chosen container width. Default row; query overrides. */
.stack-collapse-sm, .stack-collapse-md,
.stack-collapse-lg, .stack-collapse-xl { flex-direction: row; }
@container (max-width: 480px)  { .stack-collapse-sm { flex-direction: column; } }
@container (max-width: 768px)  { .stack-collapse-md { flex-direction: column; } }
@container (max-width: 1024px) { .stack-collapse-lg { flex-direction: column; } }
@container (max-width: 1280px) { .stack-collapse-xl { flex-direction: column; } }

/* DrylGrid fixed-column modes that step down on narrow slots. */
.grid-cols-2, .grid-cols-3, .grid-cols-4 { display: grid; }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@container (max-width: 768px) {
  .grid-cols-3, .grid-cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container (max-width: 480px) {
  .grid-cols-2, .grid-cols-3, .grid-cols-4 { grid-template-columns: 1fr; }
}

/* ============== CHARTS (DrylLineChart / DrylBarChart / DrylAreaChart / DrylDonutChart) ==============
   Hybrid rendering: marks are SVG (stretched viewBox, non-scaling strokes) or
   percent-positioned HTML; all text (axes, legend, tooltip) is HTML so it never
   distorts. One shared vocabulary for all four chart components. */

.chart {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* AI mode on charts — like .radio-group-wrapper.ai-aura: give the bare chart
   a surface + radius so the rotating ring traces rounded corners and the
   chart reads as a contained AI panel. */
.chart.ai-aura {
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  background: var(--glass-1);
}

.chart-body {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: 1fr auto;
  height: var(--chart-h, 260px);
  padding-top: var(--sp-2);   /* headroom so the top tick label isn't clipped */
}

/* ── Y axis ──
   Width is established by the invisible .chart-ysizer (the longest tick label)
   so wide values ("12.000") never clip; the visible ticks are absolute. */
.chart-yaxis {
  position: relative;
  grid-row: 1;
  grid-column: 1;
  min-width: 2.6em;
  padding-right: var(--sp-2);
  font-size: 11px;
  color: var(--fg-dim);
}
.chart-ysizer {
  visibility: hidden;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.chart-yaxis > span:not(.chart-ysizer) {
  position: absolute;
  right: var(--sp-2);
  transform: translateY(-50%);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ── Plot area ── */
.chart-plot {
  position: relative;
  grid-row: 1;
  grid-column: 2;
  min-width: 0;
}
.chart-gridline {
  position: absolute;
  left: 0; right: 0;
  height: 1px;
  background: var(--line);
  pointer-events: none;
}
.chart-zeroline { background: var(--line-strong); }

.chart-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  /* Left→right wipe reveal — the line/area "draws in". clip-path is in the
     element's own box space, so it survives preserveAspectRatio=none and the
     non-scaling-stroke pathLength quirk (see .chart-line note). Vertical slack
     keeps stroke overflow at peaks from clipping; backwards fill reverts to
     no clip after, so nothing stays masked. */
  animation: chart-draw var(--dur-slow) var(--ease-out) backwards;
}

/* ── X axis ── */
.chart-xaxis {
  position: relative;
  grid-row: 2;
  grid-column: 2;
  height: 1.9em;
  font-size: 11px;
  color: var(--fg-dim);
}
.chart-xaxis span {
  position: absolute;
  top: var(--sp-2);
  transform: translateX(-50%);
  white-space: nowrap;
}

/* ── Legend ── */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  font-size: 12px;
  color: var(--fg-muted);
}
.chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.chart-swatch {
  width: 10px;
  height: 10px;
  border-radius: var(--r-xs);
  flex: none;
}

/* ── Hover columns, crosshair, tooltip (pure CSS — zero JS, zero roundtrips) ── */
.chart-col {
  position: absolute;
  top: 0; bottom: 0;
  border-radius: var(--r-xs);
}
.chart-col:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--accent-line);
}
.chart-crosshair {
  position: absolute;
  left: 50%; top: 0; bottom: 0;
  width: 1px;
  background: var(--line-strong);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
  pointer-events: none;
}
.chart-col:hover .chart-crosshair,
.chart-col:focus-visible .chart-crosshair { opacity: 1; }

.chart-tip {
  position: absolute;
  top: var(--sp-2);
  left: calc(50% + var(--sp-3));
  min-width: 120px;
  max-width: 240px;
  padding: var(--sp-3);
  background: var(--panel-solid);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-md);
  font-size: 12px;
  color: var(--fg);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
  pointer-events: none;
  z-index: 3;
}
.chart-tip-flip {
  left: auto;
  right: calc(50% + var(--sp-3));
}
.chart-col:hover .chart-tip,
.chart-col:focus-visible .chart-tip {
  opacity: 1;
  transform: translateY(0);
}
.chart-tip-title {
  color: var(--fg-dim);
  font-size: 11px;
  margin-bottom: var(--sp-2);
  white-space: nowrap;
}
.chart-tip-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  white-space: nowrap;
}
.chart-tip-row + .chart-tip-row { margin-top: var(--sp-1); }
.chart-tip-row .chart-swatch { width: 8px; height: 8px; }
.chart-tip-name { color: var(--fg-muted); margin-right: auto; }
.chart-tip-value {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-left: var(--sp-4);
}

/* ── Line / area marks ──
   Note: no stroke-dasharray draw-in here — Chromium ignores pathLength for
   dash computation under vector-effect:non-scaling-stroke, which would leave
   the line permanently dashed. The "draw-in" is a clip-path wipe on the whole
   .chart-svg instead (see there); the paths themselves stay fully opaque. */
.chart-line {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.chart-area {
  stroke: none;
}
.chart-marker {
  position: absolute;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: var(--r-pill);
  box-shadow: 0 0 0 2px var(--bg);   /* surface ring — legibility over lines */
  pointer-events: none;
  /* Pop in left→right, roughly tracking the line wipe above it. */
  animation: chart-fade var(--dur-med) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 40ms);
}

/* ── Bars ── */
.chart-bars { position: absolute; inset: 0; }
.chart-band {
  position: absolute;
  top: 0; bottom: 0;
  display: flex;
  justify-content: center;
  align-items: stretch;
  gap: 2px;                 /* surface gap between adjacent bars */
  padding: 0 var(--sp-1);
}
.chart-bar-slot {
  position: relative;
  flex: 1 1 0;
  max-width: 24px;          /* bars never fill the slot */
  min-width: 2px;
}
.chart-bar {
  position: absolute;
  left: 0; right: 0;
  border-radius: 4px 4px 0 0;   /* rounded data-end, square baseline */
  transform-origin: bottom;
  animation: chart-grow var(--dur-slow) var(--ease-spring) both;
  animation-delay: calc(var(--i, 0) * 30ms);
  transition: filter var(--dur-fast) var(--ease-out);
}
.chart-bar-neg {
  border-radius: 0 0 4px 4px;
  transform-origin: top;
  animation-name: chart-grow-neg;
}
.chart-band:hover .chart-bar { filter: brightness(1.15); }

/* Stacked segments — 2px surface gap between segments, cap segment rounded */
.chart-seg {
  position: absolute;
  left: 0; right: 0;
  transform-origin: bottom;
  animation: chart-grow var(--dur-slow) var(--ease-spring) both;
  animation-delay: calc(var(--i, 0) * 30ms);
}
.chart-seg-cap { border-radius: 4px 4px 0 0; }

/* ── Donut ── */
/* The wheel is square (see .donut-slice), so a fixed height is also a fixed WIDTH: in any
   slot narrower than --chart-h it used to spill out both sides. The chart root is a query
   container and the box takes min(wanted height, available width) — the wheel shrinks to
   its slot instead of overflowing it. Tooltip anchors and --donut-hole are percentages and
   ride along. A container never queries itself, hence the type on the root. */
.chart-kind-donut { container-type: inline-size; }
.donut-box {
  position: relative;
  height: min(var(--chart-h, 260px), 100cqw);
  display: flex;
  justify-content: center;
}
/* Each slice wrapper is a centred square matching the SVG's viewBox, so the
   tooltip's percentage anchors map 1:1 onto donut coordinates (a full-width
   wrapper would stretch the anchor space and fling tips sideways). */
.donut-slice {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  translate: -50%;
  aspect-ratio: 1;
  pointer-events: none;
}
.donut-slice svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.donut-slice path {
  pointer-events: auto;
  cursor: default;
  /* Uniform separator: a bg-colored stroke gives a constant-width gap at every
     radius (angular padding wedges out to a point at the centre — ugly on pies).
     Same --bg "surface" token the markers ring with. */
  stroke: var(--bg);
  stroke-width: 1.5;
  transition: transform var(--dur-fast) var(--ease-spring);
  animation: donut-in var(--dur-med) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 40ms);
  outline: none;
}
.donut-slice:hover path,
.donut-slice:focus-within path {
  transform: translate(var(--dx, 0), var(--dy, 0));
}
.donut-slice:focus-within path {
  stroke: var(--accent-line);
  stroke-width: 1.5;
}
/* Centred on the slice's mid-angle anchor; translate composes with the
   .chart-tip enter transform, so the fade/slide-in is untouched. */
.donut-tip {
  top: var(--tip-top, 50%);
  left: var(--tip-left, 50%);
  translate: -50% -50%;
}
.donut-slice:hover .donut-tip,
.donut-slice:focus-within .donut-tip {
  opacity: 1;
  transform: translateY(0);
}
.donut-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  height: var(--donut-hole, 60%);   /* sized to the hole so content never covers the ring */
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
}
.donut-center > * { pointer-events: auto; }

/* ── Motion ── */
@keyframes chart-grow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes chart-grow-neg { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes chart-fade { from { opacity: 0; } to { opacity: 1; } }
/* Horizontal wipe; -10% top/bottom + -2% right slack so non-scaling strokes at
   peaks/last point never clip during the reveal. */
@keyframes chart-draw {
  from { clip-path: inset(-10% 102% -10% 0); }
  to   { clip-path: inset(-10% -2% -10% 0); }
}
@keyframes donut-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .chart-svg,
  .chart-line, .chart-area, .chart-marker,
  .chart-bar, .chart-seg, .donut-slice path {
    animation: none;
  }
  .chart-tip, .chart-crosshair, .chart-bar, .donut-slice path { transition: none; }
}


/* ──────────────────────────────────────────────────────────
   Morphs — the "Depth Glass" movement vocabulary.
   Driven by dryl.morph (IDrylMorph), which animates real elements with
   FLIP: measure, re-render, invert, play. There is nothing to style here
   for the movement itself — the transform is created by the Web Animations
   API with --dur-slow and --ease-viscous, and the DepthGlass tier's blur
   travels with it.

   What does need rules is the hand-over. A target that changes size hands
   its old face to a clone taken before the change; the clone rides the same
   curve to the new box and fades out while the real element settles in, so
   the two read as one object growing rather than two views swapping. Those
   clones sit in a top-layer holder — what they stand in for may itself have
   been in the top layer (a dialog, the canvas), and nothing underneath it
   can be drawn over it. The holder is inert in every sense: it takes no
   pointer, paints nothing of its own and is aria-hidden.

   The View Transition API was removed in 3.0.0: it replaced the live page
   with snapshots for the duration of a transition and swapped back at the
   end, which was visible across the whole viewport — text antialiasing
   shifted, backdrop-filter surfaces recomposited — for a change to one
   element. FLIP leaves the page live.

   Reduced motion is honoured in the engine (dryl.morph checks
   dryl.reduced() before animating anything), so nothing below ever runs
   under it — no clone is created in the first place.
   ────────────────────────────────────────────────────────── */

.dryl-morph-ghosts {
  position: fixed;
  inset: 0;
  /* The UA stylesheet gives every [popover] width/height: fit-content, and an
     empty holder is then 0 x 0 — which quietly collapses any clone sized
     against it (a dialog's max-width: 100% resolved to 2px). The holder must
     be the viewport, so a clone measures itself the way the original did. */
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
  pointer-events: none;
}

.dryl-morph-ghosts::backdrop { background: none; }

.dryl-morph-ghost {
  position: absolute;
  top: 0;
  left: 0;
  right: auto;
  bottom: auto;
  margin: 0;
  box-sizing: border-box;
  transform-origin: top left;
  pointer-events: none;
  will-change: transform, opacity;
}

/* These shared decorators and dialog variants must win over their ordinary
   rules above, including exit/handoff selectors. Preference changes apply live. */
@media (prefers-reduced-motion: reduce) {
  .spinner,
  .fade-in,
  .stagger > *,
  .aurora::before,
  .aurora::after,
  .aurora .orb,
  .dialog-backdrop,
  .dialog,
  .dialog-layer.is-exiting .dialog,
  .dialog-layer.is-handoff:not(.is-exiting) .dialog {
    animation: none;
    transition: none;
  }
}

@media (forced-colors: active) {
  .input:focus-visible,
  .textarea:focus-visible,
  .select:focus-visible,
  .dialog-close:focus-visible {
    outline: auto var(--accent-b);
  }
}
