/* =============================================================================
   @leanwise/design v4.1.0 — token SUBSET, vendored inline.
   tokens.css sha256:6bbf8d4578be
   selection sha256:83f1e76d13b6  (176 seeds -> 237 tokens)

   GENERATED by `lw-subset`. Do not edit these values by hand: the digests above
   are the only thing that can tell a later reader whether this block still
   matches the package. Re-run the generator instead, and diff.

   Vendored because this consumer cannot import — a Worker serving hand-written
   HTML with no bundler, whose bytes ship again on every request. Every OTHER
   kind of consumer must depend on the package: see README §Install.

   Seeds (the design decision; everything else is the transitive var() closure):
     --lw-anchor-offset
     --lw-bg
     --lw-bg-c
     --lw-bg-inset
     --lw-bg-muted
     --lw-bg-subtle
     --lw-bottom-nav-h
     --lw-brand-300
     --lw-brand-400
     --lw-brand-500
     --lw-brand-500-c
     --lw-brand-600
     --lw-brand-700-c
     --lw-brand-glow
     --lw-brand-line
     --lw-brand-on
     --lw-brand-soft
     --lw-brand-text
     --lw-btn-pad-x
     --lw-btn-pad-x-lg
     --lw-btn-pad-x-sm
     --lw-btn-tracking
     --lw-card-pad
     --lw-chart-axis
     --lw-chart-grid
     --lw-chip-dot
     --lw-chip-gap
     --lw-chip-pad-x
     --lw-chip-pad-y
     --lw-chip-text
     --lw-col-max
     --lw-col-page
     --lw-control-h-lg
     --lw-control-h-md
     --lw-control-h-sm
     --lw-cta-400
     --lw-cta-500
     --lw-cta-600
     --lw-cta-600-c
     --lw-cta-soft
     --lw-cta-text
     --lw-danger
     --lw-danger-on
     --lw-danger-soft
     --lw-danger-text-c
     --lw-diff-add
     --lw-diff-add-line
     --lw-diff-del
     --lw-diff-del-line
     --lw-diff-mod
     --lw-diff-mod-line
     --lw-dur-lg
     --lw-dur-loop
     --lw-dur-loop-fast
     --lw-dur-loop-slow
     --lw-dur-md
     --lw-dur-sm
     --lw-dur-xl
     --lw-dur-xs
     --lw-ease-in-out
     --lw-ease-out
     --lw-fg
     --lw-fg-c
     --lw-fg-faint
     --lw-fg-faint-c
     --lw-fg-muted
     --lw-fg-subtle
     --lw-field-pad-x
     --lw-focus-ring
     --lw-focus-ring-danger
     --lw-font-display
     --lw-font-mono
     --lw-font-sans
     --lw-fw-bold
     --lw-fw-medium
     --lw-fw-regular
     --lw-fw-semibold
     --lw-lh-normal
     --lw-lh-relaxed
     --lw-lh-snug
     --lw-lh-tight
     --lw-line
     --lw-line-control
     --lw-line-strong
     --lw-measure
     --lw-measure-prose
     --lw-measure-sm
     --lw-navy-700
     --lw-navy-900
     --lw-navy-deep
     --lw-neutral-on
     --lw-neutral-soft
     --lw-on-brand
     --lw-on-cta
     --lw-on-danger
     --lw-on-dark
     --lw-on-dark-1
     --lw-on-dark-2
     --lw-on-dark-3
     --lw-on-dark-border
     --lw-on-dark-border-hover
     --lw-on-dark-fill
     --lw-on-dark-fill-strong
     --lw-on-dark-line
     --lw-on-dark-line-strong
     --lw-on-dark-wash
     --lw-pad-x
     --lw-radius-lg
     --lw-radius-md
     --lw-radius-pill
     --lw-radius-sm
     --lw-radius-xl
     --lw-radius-xs
     --lw-row-h
     --lw-safe-bottom
     --lw-safe-left
     --lw-safe-right
     --lw-safe-top
     --lw-scrim
     --lw-shadow-lg
     --lw-shadow-md
     --lw-shadow-sm
     --lw-shadow-xl
     --lw-shadow-xs
     --lw-space-12
     --lw-space-16
     --lw-space-20
     --lw-space-24
     --lw-space-32
     --lw-space-4
     --lw-space-40
     --lw-space-48
     --lw-space-64
     --lw-space-8
     --lw-success
     --lw-success-c
     --lw-success-on
     --lw-success-on-c
     --lw-success-soft
     --lw-switch-h
     --lw-switch-inset
     --lw-switch-thumb
     --lw-switch-w
     --lw-sys-mac-amber
     --lw-sys-mac-green
     --lw-sys-mac-red
     --lw-text-2xs
     --lw-text-body
     --lw-text-display
     --lw-text-eyebrow
     --lw-text-h1
     --lw-text-h2
     --lw-text-h3
     --lw-text-lead
     --lw-text-sm
     --lw-text-xs
     --lw-th-text
     --lw-th-tracking
     --lw-topbar-h
     --lw-tracking-eyebrow
     --lw-tracking-tight
     --lw-tracking-tighter
     --lw-tracking-wide
     --lw-warning
     --lw-warning-on
     --lw-warning-soft
     --lw-word-spacing-caps
     --lw-z-local-1
     --lw-z-local-2
     --lw-z-local-3
     --lw-z-local-4
     --lw-z-modal
     --lw-z-nav
     --lw-z-overlay
     --lw-z-raised
     --lw-z-toast
   ============================================================================= */

:root {
  --lw-font-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --lw-font-mono: "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --lw-font-display: var(--lw-font-sans);
  --lw-text-display: clamp(40px, 6vw, 84px);
  --lw-text-h1: clamp(38px, 5vw, 72px);
  --lw-text-h2: clamp(28px, 3.4vw, 48px);
  --lw-text-h3: clamp(20px, 2vw, 27px);
  --lw-text-lead: clamp(16px, 1.2vw, 19px);
  --lw-text-body: 16px;
  --lw-text-sm: 14px;
  --lw-text-xs: 12.5px;
  --lw-text-2xs: 12px;
  --lw-text-eyebrow: var(--lw-text-2xs);
  --lw-fw-regular: 400; /* @kind other */
  --lw-fw-medium: 500; /* @kind other */
  --lw-fw-semibold: 600; /* @kind other */
  --lw-fw-bold: 700; /* @kind other */
  --lw-lh-tight: 1.05; /* @kind other */
  --lw-lh-snug: 1.2; /* @kind other */
  --lw-lh-normal: 1.55; /* @kind other */
  --lw-lh-relaxed: 1.7; /* @kind other */
  --lw-tracking-tighter: -0.035em; /* @kind other */
  --lw-tracking-tight: -0.022em; /* @kind other */
  --lw-tracking-wide: 0.02em; /* @kind other */
  --lw-tracking-eyebrow: 0.14em; /* @kind other */
  --lw-word-spacing-caps: 0.10em; /* @kind other */
  --lw-brand-300-c: 184.5 60.0% 64.0%; /* @kind color */ /* #6CD2DA — on navy paper 10.58 */
  --lw-brand-400-c: 183.5 62.0% 47.5%; /* @kind color */ /* #2EBBC4 — teal as TEXT on dark (8.04); the deck's dark accent */
  --lw-brand-500-c: 185.0 82.0% 26.5%; /* @kind color */ /* #0C727B — THE brand color. WHITE ink, 5.66 */
  --lw-brand-600-c: 186.0 86.0% 21.0%; /* @kind color */ /* #075A64 — hover fill (white ink 7.91) */
  --lw-brand-700-c: 187.0 88.0% 16.5%; /* @kind color */ /* #05464F — deepest tier, on white 10.52 */
  --lw-brand-300: hsl(var(--lw-brand-300-c));
  --lw-brand-400: hsl(var(--lw-brand-400-c));
  --lw-brand-500: hsl(var(--lw-brand-500-c));
  --lw-brand-600: hsl(var(--lw-brand-600-c));
  --lw-navy-700-c: 205.2 97.0% 23.5%; /* @kind color */ /* #024576 — the mark's navy end, DO NOT MOVE */
  --lw-navy-900-c: 213.0 72.0% 15.0%; /* @kind color */ /* #0B2342 — white ink 15.75 */
  --lw-navy-deep-c: 217.0 66.0% 10.5%; /* @kind color */ /* #09172C — white ink 17.94 */
  --lw-navy-paper-c: 220   48.8% 8.4%; /* @kind color */ /* #0B1220 — --lw-bg on dark */
  --lw-navy-raised-c: 221.4 46.0% 12.4%; /* @kind color */ /* #111A2E — cards, popovers */
  --lw-navy-inset-c: 221.1 45.2% 16.5%; /* @kind color */ /* #17233D — muted / inset */
  --lw-on-navy-1-c: 215   33.3% 92.9%; /* @kind color */ /* #E7ECF3 — body ink, 15.76 on paper */
  --lw-on-navy-2-c: 218.7 21.7% 72.0%; /* @kind color */ /* #A8B3C7 — 8.88 on paper */
  --lw-on-navy-3-c: 218.5 17.5% 57.5%; /* @kind color */ /* #808DA6 — 5.62 on paper, 4.6 on a raised card */
  --lw-on-navy-4-c: 218.5 17.5% 46.0%; /* @kind color */ /* decorative only, fails AA as body text */
  --lw-navy-line-1-c: 220.5 38.7% 21.8%; /* @kind color */ /* #22304D — hairline */
  --lw-navy-line-2-c: 220.5 38.7% 28.0%; /* @kind color */ /* divider */
  --lw-navy-line-control-c: 220.5 38.7% 48.0%; /* @kind color */ /* #4B6AAA — 3.51 on paper, 3.24 on raised */
  --lw-sys-mac-red-c: 4.0 100% 67.5%; /* @kind color */ /* #FF5F57 */
  --lw-sys-mac-amber-c: 41.0 99.0% 61.0%; /* @kind color */ /* #FEBC2E */
  --lw-sys-mac-green-c: 135.0 66.0% 47.0%; /* @kind color */ /* #28C840 */
  --lw-sys-mac-red: hsl(var(--lw-sys-mac-red-c));
  --lw-sys-mac-amber: hsl(var(--lw-sys-mac-amber-c));
  --lw-sys-mac-green: hsl(var(--lw-sys-mac-green-c));
  --lw-navy-700: hsl(var(--lw-navy-700-c));
  --lw-navy-900: hsl(var(--lw-navy-900-c));
  --lw-navy-deep: hsl(var(--lw-navy-deep-c));
  --lw-cta-400-c: 44.0 96.0% 58.0%; /* @kind color */ /* #FBC42D — ANCHOR of the ramp; amber as TEXT on dark (11.62) */
  --lw-cta-500-c: 43.0 98.0% 50.0%; /* @kind color */ /* #FCB603 — the CTA fill (navy ink, 10.54) */
  --lw-cta-600-c: 38.0 95.0% 44.0%; /* @kind color */ /* #DB8D06 — hover (navy ink, 6.97) */
  --lw-cta-soft-c: 46.0 100%  90.0%; /* @kind color */ /* #FFF3CC */
  --lw-cta-700-c: 30.0 88.0% 27.0%; /* @kind color */ /* #814508 — amber as TEXT on white (7.53) */
  --lw-cta-400: hsl(var(--lw-cta-400-c));
  --lw-cta-500: hsl(var(--lw-cta-500-c));
  --lw-cta-600: hsl(var(--lw-cta-600-c));
  --lw-cta-soft: hsl(var(--lw-cta-soft-c));
  --lw-surface-0-c: 0     0%    100%; /* @kind color */ /* #FFFFFF */
  --lw-surface-1-c: 38    16.0% 98.4%; /* @kind color */ /* #FCFBFA */
  --lw-surface-2-c: 38    13.0% 96.5%; /* @kind color */ /* #F7F6F5 */
  --lw-surface-3-c: 38    12.0% 94.2%; /* @kind color */ /* #F2F1EE */
  --lw-text-1-c: 220   48.8% 8.4%; /* @kind color */ /* #0B1220 — also the ink on every brand fill */
  --lw-text-2-c: 215   13.8% 34.1%; /* @kind color */ /* #4B5563 */
  --lw-text-3-c: 220    8.9% 43.3%; /* @kind color */ /* #656B78 — the muted floor. 5.34 on white, and 4.75 on
                                                              surface-3 (4.61 at the v1.13.0 tiers, 4.51 before the paper warmed): through v1.1.2 this was 46.1% (#6B7280), AA on
                                                              the page and 4.07 on an inset panel — where `code`, `.lbl`
                                                              and most muted captions actually sit. A floor measured
                                                              against only the lightest surface is not a floor. */
  --lw-text-4-c: 217.9 10.6% 64.9%; /* @kind color */ /* #9CA3AF — decorative only, fails AA as body text */
  --lw-border-1-c: 38  10.0% 91.5%; /* @kind color */ /* #EBEAE7 — warm, with the paper (hue with the v3.1.5 lifts) */
  --lw-border-2-c: 38   8.0% 83.9%; /* @kind color */ /* #D9D7D3 */
  --lw-border-control-c: 216 12.2% 57.0%; /* @kind color */ /* #848F9F — 3.29 on surface-0, 3.13 on surface-1 */
  --lw-success-c: 142.1 76.2% 36.3%; /* @kind color */ /* #16A34A  fill */
  --lw-success-soft-c: 140.6 84.2% 92.5%; /* @kind color */ /* #DCFCE7  chip tint */
  --lw-warning-c: 37.0  98.0% 50.0%; /* @kind color */ /* #FC9D03  fill — 7.17 with navy ink */
  --lw-warning-soft-c: 48.0  96.5% 88.8%; /* @kind color */ /* #FEF3C7  chip tint */
  --lw-danger-c: 0     72.2% 50.6%; /* @kind color */ /* #DC2626  fill — the one fill that takes WHITE ink */
  --lw-danger-soft-c: 0     93.3% 94.1%; /* @kind color */ /* #FEE2E2  chip tint */
  --lw-neutral-soft-c: 213.0 30.0% 95.0%; /* @kind color */ /* #EEF2F6  chip tint */
  --lw-success-text-c: 142.8 64.2% 24.1%; /* @kind color */ /* #166534 — 7.13 on white, 6.49 on tint */
  --lw-warning-text-c: 25.0  92.0% 28.0%; /* @kind color */ /* #893C06 — 7.46 on white, 6.64 on tint */
  --lw-danger-text-c: 0     73.7% 41.8%; /* @kind color */ /* #B91C1C — 6.47 on white, 5.30 on tint */
  --lw-neutral-text-c: 215.0 30.0% 26.0%; /* @kind color */ /* #2E3F56 — 10.4 on white, 9.8 on tint */
  --lw-neutral-soft: hsl(var(--lw-neutral-soft-c));
  --lw-neutral-on: hsl(var(--lw-neutral-on-c));
  --lw-success: hsl(var(--lw-success-c));
  --lw-warning: hsl(var(--lw-warning-c));
  --lw-danger: hsl(var(--lw-danger-c));
  --lw-on-dark: #FFFFFF;
  --lw-on-dark-1: rgba(255, 255, 255, 0.92);
  --lw-on-dark-2: rgba(255, 255, 255, 0.70);
  --lw-on-dark-3: rgba(255, 255, 255, 0.48);
  --lw-on-dark-wash: rgba(255, 255, 255, 0.03);
  --lw-on-dark-fill: rgba(255, 255, 255, 0.06);
  --lw-on-dark-fill-strong: rgba(255, 255, 255, 0.16);
  --lw-on-dark-line: rgba(255, 255, 255, 0.14);
  --lw-on-dark-line-strong: rgba(255, 255, 255, 0.22);
  --lw-on-dark-border: rgba(255, 255, 255, 0.22);
  --lw-on-dark-border-hover: rgba(255, 255, 255, 0.45);
  --lw-space-4: 4px;
  --lw-space-8: 8px;
  --lw-space-12: 12px;
  --lw-space-16: 16px;
  --lw-space-20: 20px;
  --lw-space-24: 24px;
  --lw-space-32: 32px;
  --lw-space-40: 40px;
  --lw-space-48: 48px;
  --lw-space-64: 64px;
  --lw-control-h-sm: 32px;
  --lw-control-h-md: 40px; /* the default control face: input, select, group */
  --lw-control-h-lg: 48px;
  --lw-field-pad-x: var(--lw-space-12);
  --lw-row-h: 44px; /* table/grid row */
  --lw-card-pad: var(--lw-space-24);
  --lw-btn-pad-x-sm: 12px;
  --lw-btn-pad-x: 18px;
  --lw-btn-pad-x-lg: 24px;
  --lw-btn-tracking: -0.005em;
  --lw-chip-pad-y: 5px;
  --lw-chip-pad-x: 9px;
  --lw-chip-gap: 5px;
  --lw-chip-text: var(--lw-text-2xs);
  --lw-chip-dot: 5px;
  --lw-th-text: var(--lw-text-2xs);
  --lw-th-tracking: 0.12em;
  --lw-switch-w: 38px;
  --lw-switch-h: 22px;
  --lw-switch-thumb: 16px;
  --lw-switch-inset: 2px;
}
[data-density="compact"] {
  --lw-control-h-sm: 28px;
  --lw-control-h-md: 32px;
  --lw-control-h-lg: 40px;
  --lw-field-pad-x: var(--lw-space-8);
  --lw-row-h: 32px;
  --lw-card-pad: var(--lw-space-16);
}
[data-density="comfortable"] {
  --lw-control-h-sm: 32px;
  --lw-control-h-md: 40px;
  --lw-control-h-lg: 48px;
  --lw-field-pad-x: var(--lw-space-12);
  --lw-row-h: 44px;
  --lw-card-pad: var(--lw-space-24);
}
@media (pointer: coarse) {
  :root, [data-density="compact"], [data-density="comfortable"] {
    --lw-control-h-sm: 44px;
    --lw-control-h-md: 44px;
    --lw-control-h-lg: 48px;
    --lw-row-h: 48px;
  }
}
:root {
  --lw-radius-xs: 2px; /* hairline chips, tags, code marks */
  --lw-radius-sm: 4px; /* inputs, checkboxes, small controls */
  --lw-radius-md: 8px; /* the default: buttons, fields — shadcn --radius maps here */
  --lw-radius-lg: 12px; /* cards, popovers */
  --lw-radius-xl: 16px; /* panels, modals */
  --lw-radius-pill: 999px;
  --lw-shadow-ink-c: 30 10% 10%; /* @kind color */ /* #1C1A17 — the light theme's shadow ink */
  --lw-shadow-xs: 0 1px 0 hsl(var(--lw-shadow-ink-c) / 0.05);
  --lw-shadow-sm: 0 1px 2px hsl(var(--lw-shadow-ink-c) / 0.06);
  --lw-shadow-md: 0 6px 20px -8px hsl(var(--lw-shadow-ink-c) / 0.14), 0 1px 2px hsl(var(--lw-shadow-ink-c) / 0.05);
  --lw-shadow-lg: 0 24px 56px -22px hsl(var(--lw-shadow-ink-c) / 0.22), 0 2px 6px hsl(var(--lw-shadow-ink-c) / 0.06);
  --lw-shadow-xl: 0 32px 64px -24px hsl(var(--lw-shadow-ink-c) / 0.30), 0 4px 12px hsl(var(--lw-shadow-ink-c) / 0.08);
  --lw-focus-ring: 0 0 0 2px var(--lw-brand-500); /* @kind color */
  --lw-focus-ring-danger: 0 0 0 2px var(--lw-danger-on); /* @kind color */
  --lw-brand-glow: 0 8px 24px -6px hsl(var(--lw-brand-500-c) / 0.25);
  --lw-dur-xs: 100ms; /* @kind other */ /* micro: a hover color swap                */
  --lw-dur-sm: 180ms; /* @kind other */ /* the default interaction                   */
  --lw-dur-md: 300ms; /* @kind other */ /* a panel sliding in                        */
  --lw-dur-lg: 500ms; /* @kind other */ /* an editorial entrance / first-paint rise  */
  --lw-dur-xl: 800ms; /* @kind other */ /* a slow, deliberate reveal                 */
  --lw-ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* @kind other */ /* the house curve */
  --lw-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); /* @kind other */
  --lw-dur-loop-fast: 1.1s; /* @kind other */ /* caret blinks                      */
  --lw-dur-loop: 1.5s; /* @kind other */ /* pulse, shimmer, trace, tool dot   */
  --lw-dur-loop-slow: 2.4s; /* @kind other */ /* reduced-motion spinner            */
  --lw-topbar-h: 56px; /* @kind dimension */
  --lw-anchor-offset: calc(var(--lw-topbar-h) + var(--lw-space-8)); /* @kind dimension */
  --lw-col-max: 1200px;
  --lw-measure-prose: 68ch; /* @kind dimension */
  --lw-measure: 60ch; /* @kind dimension */
  --lw-measure-sm: 46ch; /* @kind dimension */
  --lw-col-page: 1400px;
  --lw-pad-x: clamp(20px, 4vw, 56px);
  --lw-z-local-1: 1; /* @kind other */
  --lw-z-local-2: 2; /* @kind other */
  --lw-z-local-3: 3; /* @kind other */
  --lw-z-local-4: 4; /* @kind other */
  --lw-z-raised: 10; /* @kind other */
  --lw-z-nav: 50; /* @kind other */
  --lw-z-overlay: 90; /* @kind other */
  --lw-z-toast: 100; /* @kind other */
  --lw-z-modal: 110; /* @kind other */
  --lw-bg-c: var(--lw-surface-0-c); /* @kind color */
  --lw-bg-subtle-c: var(--lw-surface-1-c); /* @kind color */
  --lw-bg-muted-c: var(--lw-surface-2-c); /* @kind color */
  --lw-bg-inset-c: var(--lw-surface-3-c); /* @kind color */
  --lw-fg-c: var(--lw-text-1-c); /* @kind color */
  --lw-fg-muted-c: var(--lw-text-2-c); /* @kind color */
  --lw-fg-subtle-c: var(--lw-text-3-c); /* @kind color */
  --lw-fg-faint-c: var(--lw-text-4-c); /* @kind color */
  --lw-line-c: var(--lw-border-1-c); /* @kind color */
  --lw-line-strong-c: var(--lw-border-2-c); /* @kind color */
  --lw-line-control-c: var(--lw-border-control-c); /* @kind color */
  --lw-brand-text-c: var(--lw-brand-500-c); /* @kind color */
  --lw-cta-text-c: var(--lw-cta-700-c); /* @kind color */
  --lw-brand-on-c: var(--lw-brand-700-c); /* @kind color */
  --lw-success-on-c: var(--lw-success-text-c); /* @kind color */
  --lw-warning-on-c: var(--lw-warning-text-c); /* @kind color */
  --lw-danger-on-c: var(--lw-danger-text-c); /* @kind color */
  --lw-neutral-on-c: var(--lw-neutral-text-c); /* @kind color */
  --lw-on-brand-c: 0 0% 100%; /* @kind color */ /* white — the brand fill is dark */
  --lw-on-cta-c: 220 48.8% 8.4%; /* @kind color */ /* navy — amber is a LIGHT fill */
  --lw-on-danger-c: 0 0% 100%; /* @kind color */ /* white */
  --lw-bg: hsl(var(--lw-bg-c));
  --lw-bg-subtle: hsl(var(--lw-bg-subtle-c));
  --lw-bg-muted: hsl(var(--lw-bg-muted-c));
  --lw-bg-inset: hsl(var(--lw-bg-inset-c));
  --lw-fg: hsl(var(--lw-fg-c));
  --lw-fg-muted: hsl(var(--lw-fg-muted-c));
  --lw-fg-subtle: hsl(var(--lw-fg-subtle-c));
  --lw-fg-faint: hsl(var(--lw-fg-faint-c));
  --lw-line: hsl(var(--lw-line-c));
  --lw-line-strong: hsl(var(--lw-line-strong-c));
  --lw-line-control: hsl(var(--lw-line-control-c));
  --lw-brand-text: hsl(var(--lw-brand-text-c));
  --lw-cta-text: hsl(var(--lw-cta-text-c));
  --lw-on-brand: hsl(var(--lw-on-brand-c));
  --lw-on-cta: hsl(var(--lw-on-cta-c));
  --lw-on-danger: hsl(var(--lw-on-danger-c));
  --lw-scrim: hsl(var(--lw-bg-c) / 0.55);
  --lw-brand-soft: hsl(var(--lw-brand-500-c) / 0.14);
  --lw-brand-line: hsl(var(--lw-brand-500-c) / 0.34);
  --lw-brand-on: hsl(var(--lw-brand-on-c));
  --lw-success-on: hsl(var(--lw-success-on-c));
  --lw-success-soft: hsl(var(--lw-success-soft-c));
  --lw-warning-on: hsl(var(--lw-warning-on-c));
  --lw-warning-soft: hsl(var(--lw-warning-soft-c));
  --lw-danger-on: hsl(var(--lw-danger-on-c));
  --lw-danger-soft: hsl(var(--lw-danger-soft-c));
}
.dark,
[data-theme="dark"] {
  --lw-bg-c: var(--lw-navy-paper-c); /* @kind color */ /* #0B1220 paper */
  --lw-bg-subtle-c: var(--lw-navy-raised-c); /* @kind color */ /* #111A2E surface — cards/popovers */
  --lw-bg-muted-c: var(--lw-navy-inset-c); /* @kind color */ /* #17233D */
  --lw-bg-inset-c: var(--lw-navy-inset-c); /* @kind color */
  --lw-fg-c: var(--lw-on-navy-1-c); /* @kind color */ /* #E7ECF3 */
  --lw-fg-muted-c: var(--lw-on-navy-2-c); /* @kind color */ /* #A8B3C7 — 8.86 on paper */
  --lw-fg-subtle-c: var(--lw-on-navy-3-c); /* @kind color */ /* #808EA6 — 5.62 on paper, 4.6 on a raised card. Was 56.3% (#7C8AA3),
                                                              AA on the page ground only — the same hole as --lw-text-3. */
  --lw-fg-faint-c: var(--lw-on-navy-4-c); /* @kind color */
  --lw-line-c: var(--lw-navy-line-1-c); /* @kind color */ /* #22304D */
  --lw-line-strong-c: var(--lw-navy-line-2-c); /* @kind color */
  --lw-line-control-c: var(--lw-navy-line-control-c); /* @kind color */ /* #4B6AAA — 3.51 on bg, 3.24 on bg-subtle */
  --lw-brand-text-c: var(--lw-brand-400-c); /* @kind color */
  --lw-cta-text-c: var(--lw-cta-400-c); /* @kind color */
  --lw-brand-on-c: var(--lw-brand-300-c); /* @kind color */
  --lw-success-on-c: 158.1 64.4% 51.6%; /* @kind color */ /* #34D399 */
  --lw-warning-on-c: var(--lw-cta-400-c); /* @kind color */ /* #FBC42D — the amber anchor */
  --lw-danger-on-c: 0     90.6% 70.8%; /* @kind color */ /* #F87171 — 6.77 on paper */
  --lw-neutral-on-c: 214.0 22.0% 72.0%; /* @kind color */ /* #A8B6C7 — 8.1 on paper */
  --lw-success-soft-c: 153.3 47.4% 11.2%; /* @kind color */ /* #0F2A1E — ink lands at 7.97 */
  --lw-warning-soft-c: 38.9  67.3% 10.8%; /* @kind color */ /* #2E2109 — 9.40 */
  --lw-danger-soft-c: 0     53.2% 15.1%; /* @kind color */ /* #3B1212 — 5.93 */
  --lw-neutral-soft-c: 215.0 30.0% 15.0%; /* @kind color */ /* #1B2432 — ink lands at 7.6 */
  --lw-cta-soft-c: 34.0  62.0% 13.0%; /* @kind color */ /* #36240D — cta-400 on it 9.22 */
  --lw-shadow-ink-c: 0 0% 0%; /* @kind color */
  --lw-shadow-xs: 0 1px 0 hsl(var(--lw-line-c) / 0.5);
  --lw-shadow-sm: 0 1px 0 hsl(var(--lw-line-c) / 0.6);
  --lw-shadow-md: 0 1px 0 hsl(var(--lw-line-c) / 0.6), 0 8px 24px rgba(0, 0, 0, 0.45);
  --lw-shadow-lg: 0 1px 0 hsl(var(--lw-line-c) / 0.6), 0 30px 60px -28px rgba(0, 0, 0, 0.85);
  --lw-shadow-xl: 0 1px 0 hsl(var(--lw-line-c) / 0.6), 0 40px 80px -32px rgba(0, 0, 0, 0.9);
  --lw-focus-ring: 0 0 0 2px var(--lw-brand-400);
  --lw-focus-ring-danger: 0 0 0 2px var(--lw-danger-on);
}
@media (prefers-color-scheme: dark) {
  :root {
    --lw-bg-c: var(--lw-navy-paper-c); /* @kind color */
    --lw-bg-subtle-c: var(--lw-navy-raised-c); /* @kind color */
    --lw-bg-muted-c: var(--lw-navy-inset-c); /* @kind color */
    --lw-bg-inset-c: var(--lw-navy-inset-c); /* @kind color */
    --lw-fg-c: var(--lw-on-navy-1-c); /* @kind color */
    --lw-fg-muted-c: var(--lw-on-navy-2-c); /* @kind color */
    --lw-fg-subtle-c: var(--lw-on-navy-3-c); /* @kind color */
    --lw-fg-faint-c: var(--lw-on-navy-4-c); /* @kind color */
    --lw-line-c: var(--lw-navy-line-1-c); /* @kind color */
    --lw-line-strong-c: var(--lw-navy-line-2-c); /* @kind color */
    --lw-line-control-c: var(--lw-navy-line-control-c); /* @kind color */ /* #4B6AAA — 3.51 on bg, 3.24 on bg-subtle */
    --lw-brand-text-c: var(--lw-brand-400-c); /* @kind color */
    --lw-cta-text-c: var(--lw-cta-400-c); /* @kind color */
    --lw-brand-on-c: var(--lw-brand-300-c); /* @kind color */
    --lw-success-on-c: 158.1 64.4% 51.6%; /* @kind color */
    --lw-warning-on-c: var(--lw-cta-400-c); /* @kind color */
    --lw-danger-on-c: 0     90.6% 70.8%; /* @kind color */
    --lw-neutral-on-c: 214.0 22.0% 72.0%; /* @kind color */
    --lw-success-soft-c: 153.3 47.4% 11.2%; /* @kind color */ /* #0F2A1E — ink lands at 7.97 */
    --lw-warning-soft-c: 38.9  67.3% 10.8%; /* @kind color */ /* #2E2109 — 9.40 */
    --lw-danger-soft-c: 0     53.2% 15.1%; /* @kind color */ /* #3B1212 — 5.93 */
    --lw-neutral-soft-c: 215.0 30.0% 15.0%; /* @kind color */ /* #1B2432 — ink lands at 7.6 */
    --lw-cta-soft-c: 34.0  62.0% 13.0%; /* @kind color */ /* #36240D — cta-400 on it 9.22 */
    --lw-shadow-ink-c: 0 0% 0%; /* @kind color */
    --lw-shadow-xs: 0 1px 0 hsl(var(--lw-line-c) / 0.5);
    --lw-shadow-sm: 0 1px 0 hsl(var(--lw-line-c) / 0.6);
    --lw-shadow-md: 0 1px 0 hsl(var(--lw-line-c) / 0.6), 0 8px 24px rgba(0, 0, 0, 0.45);
    --lw-shadow-lg: 0 1px 0 hsl(var(--lw-line-c) / 0.6), 0 30px 60px -28px rgba(0, 0, 0, 0.85);
    --lw-shadow-xl: 0 1px 0 hsl(var(--lw-line-c) / 0.6), 0 40px 80px -32px rgba(0, 0, 0, 0.9);
    --lw-focus-ring: 0 0 0 2px var(--lw-brand-400);
    --lw-focus-ring-danger: 0 0 0 2px var(--lw-danger-on);
  }
  :root.light, :root[data-theme="light"] {
    --lw-bg-c: var(--lw-surface-0-c);
    --lw-bg-subtle-c: var(--lw-surface-1-c);
    --lw-bg-muted-c: var(--lw-surface-2-c);
    --lw-bg-inset-c: var(--lw-surface-3-c);
    --lw-fg-c: var(--lw-text-1-c);
    --lw-fg-muted-c: var(--lw-text-2-c);
    --lw-fg-subtle-c: var(--lw-text-3-c);
    --lw-fg-faint-c: var(--lw-text-4-c);
    --lw-line-c: var(--lw-border-1-c);
    --lw-line-strong-c: var(--lw-border-2-c);
    --lw-line-control-c: var(--lw-border-control-c); /* @kind color */
    --lw-brand-text-c: var(--lw-brand-500-c);
    --lw-cta-text-c: var(--lw-cta-700-c);
    --lw-brand-on-c: var(--lw-brand-700-c); /* @kind color */
    --lw-success-on-c: var(--lw-success-text-c);
    --lw-warning-on-c: var(--lw-warning-text-c);
    --lw-danger-on-c: var(--lw-danger-text-c);
    --lw-neutral-on-c: var(--lw-neutral-text-c);
    --lw-success-soft-c: 140.6 84.2% 92.5%; /* @kind color */
    --lw-warning-soft-c: 48.0  96.5% 88.8%; /* @kind color */
    --lw-danger-soft-c: 0     93.3% 94.1%; /* @kind color */
    --lw-neutral-soft-c: 213.0 30.0% 95.0%; /* @kind color */
    --lw-cta-soft-c: 46.0 100% 90.0%; /* @kind color */
    --lw-shadow-ink-c: 30 10% 10%; /* @kind color */
    --lw-shadow-xs: 0 1px 0 hsl(var(--lw-shadow-ink-c) / 0.05);
    --lw-shadow-sm: 0 1px 2px hsl(var(--lw-shadow-ink-c) / 0.06);
    --lw-shadow-md: 0 6px 20px -8px hsl(var(--lw-shadow-ink-c) / 0.14), 0 1px 2px hsl(var(--lw-shadow-ink-c) / 0.05);
    --lw-shadow-lg: 0 24px 56px -22px hsl(var(--lw-shadow-ink-c) / 0.22), 0 2px 6px hsl(var(--lw-shadow-ink-c) / 0.06);
    --lw-shadow-xl: 0 32px 64px -24px hsl(var(--lw-shadow-ink-c) / 0.30), 0 4px 12px hsl(var(--lw-shadow-ink-c) / 0.08);
    --lw-focus-ring: 0 0 0 2px var(--lw-brand-500);
  }
}
:root {
  --lw-chart-grid: var(--lw-line);
  --lw-chart-axis: var(--lw-fg-subtle);
}
:root {
  --lw-diff-add: hsl(140.0 60.0% 94.0%); /* @kind color */
  --lw-diff-add-line: hsl(140.0 44.0% 74.0%); /* @kind color */
  --lw-diff-del: hsl(0 84.0% 95.5%); /* @kind color */
  --lw-diff-del-line: hsl(0 62.0% 82.0%); /* @kind color */
  --lw-diff-mod: hsl(44.0 92.0% 92.0%); /* @kind color */
  --lw-diff-mod-line: hsl(44.0 70.0% 72.0%); /* @kind color */
}
@media (prefers-color-scheme: dark) {
  :root.light, :root[data-theme="light"] {
    --lw-diff-add: hsl(140.0 60.0% 94.0%); /* @kind color */
    --lw-diff-add-line: hsl(140.0 44.0% 74.0%); /* @kind color */
    --lw-diff-del: hsl(0 84.0% 95.5%); /* @kind color */
    --lw-diff-del-line: hsl(0 62.0% 82.0%); /* @kind color */
    --lw-diff-mod: hsl(44.0 92.0% 92.0%); /* @kind color */
    --lw-diff-mod-line: hsl(44.0 70.0% 72.0%); /* @kind color */
  }
  :root {
    --lw-diff-add: hsl(150.0 46.0% 12.0%); /* @kind color */
    --lw-diff-add-line: hsl(150.0 38.0% 28.0%); /* @kind color */
    --lw-diff-del: hsl(0 48.0% 15.0%); /* @kind color */
    --lw-diff-del-line: hsl(0 40.0% 32.0%); /* @kind color */
    --lw-diff-mod: hsl(40.0 52.0% 14.0%); /* @kind color */
    --lw-diff-mod-line: hsl(40.0 44.0% 30.0%); /* @kind color */
  }
}
:root {
  --lw-safe-top: env(safe-area-inset-top, 0px); /* @kind other */
  --lw-safe-right: env(safe-area-inset-right, 0px); /* @kind other */
  --lw-safe-bottom: env(safe-area-inset-bottom, 0px); /* @kind other */
  --lw-safe-left: env(safe-area-inset-left, 0px); /* @kind other */
  --lw-bottom-nav-h: 56px; /* @kind other */
}

/* =============================================================================
   @leanwise/design — reset.css

   The nine bare-element rules that used to sit at the top of base.css.

   WHY THEY ARE THEIR OWN FILE NOW. Un-layered CSS beats every Tailwind utility,
   whatever the specificity — so `button { background: none; border: 0; padding: 0 }`
   silently strips the padding and background off every `<Button>` in a Tailwind
   app, and `body { font-size: var(--lw-text-body) }` overrides the type scale.
   That made base.css un-importable by exactly the consumers the README told to
   import it, and it is why tss-app refuses base.css AND product.css and therefore
   consumes zero `.lw-*` components.

   With the reset split out, base.css is `.lw-*`-only and safe for anyone:

     @import "@leanwise/design/base.css" layer(components);

   A vanilla (non-Tailwind) consumer wants this file, and loads it first,
   before base.css. A Tailwind consumer almost certainly does NOT: Tailwind's own
   preflight already covers the box-sizing, the margin zeroing and the image
   defaults, and the opinionated `button` and `body` rules here are the ones that
   do the damage. If you want the LeanWise typography defaults in a Tailwind app,
   set them on your own `body` rule inside `@layer base` instead — that keeps them
   beatable by a utility, which is the whole point.

   Nothing here is `.lw-*`-scoped, by definition. `check:presence` asserts the
   split holds in BOTH directions — base/marketing/product must contain no bare
   element selectors, and this file must contain nothing else — because asserting
   only one direction lets the split quietly un-split from the other side.
   ============================================================================= */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  font-family: var(--lw-font-sans);
  color: var(--lw-fg);
  background: var(--lw-bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  font-size: var(--lw-text-body);
  line-height: var(--lw-lh-normal);
}

img { display: block; max-width: 100%; }
button, input, textarea, select { font-family: inherit; }
a { color: inherit; text-decoration: none; }
button { cursor: pointer; background: none; border: 0; padding: 0; }

/* A ROLE, not a tier: --lw-brand-100 is theme-invariant, so on dark it put the
   near-white --lw-fg on a near-white #D5F3F6 ground — selected text vanished
   at ~1.1:1. The tint follows the theme and the ink is stated explicitly. */
::selection { background: var(--lw-brand-soft); color: var(--lw-fg); }

/* iOS Safari zooms the page when focusing an input under 16px. Keep the tighter
   desktop size, but never trip the zoom on a phone. --lw-bp-md (768px) covers
   every phone and tablet portrait; a media query cannot read a var(), so the
   number is written out and the token is named here.

   It lives in the reset rather than with the control styles because it applies to
   every input on the page, including ones this package knows nothing about. */
@media (max-width: 768px) {
  input, textarea, select { font-size: 16px; }
}


/* @leanwise/design — BASE layer
 *
 * The SHARED layer: everything both a product and a marketing page need. It used
 * to be lw.css, described as "the marketing layer", and that was wrong in a way
 * that cost real time — `.lw-btn` lives here, so an app that loaded only the
 * product layer got correct forms, tables and overlays and unstyled buttons. The
 * names now say what the files hold.
 *
 * Contents: the reset, the layout primitives, type utilities, buttons, pills,
 * cards and card anatomy, chips, the form field and control face, the console
 * surface, spacing and motion utilities, the code surface, the file tree, the
 * status chip, the top bar and its brand mark, and the consolidated
 * pointer-affordance list.
 *
 * v1.3.0 promoted three blocks out of product.css for the SAME reason `.lw-btn`
 * moved here: layout, the form controls and `.lw-topbar` are not app surfaces, so
 * a marketing site that correctly dropped the product layer got correct buttons,
 * cards and heroes on an unstyled page with an unstyled header. `.lw-topbar` was
 * the proof — its `.brand-mark` rules were already in this file while the rest of
 * the component was in the other one.
 *
 * Load order is tokens → base → marketing and/or product. The pointer list stays
 * at the FOOT of this file with the product layer loading after it — exactly the
 * arrangement it had inside lw.css, which is what README rule 7 is about.
 */

/* =============================================================================
   The `.lw-*` layer — marketing-site component classes.
   Import AFTER tokens.css. Only leanwise-ai needs this; apps use the Tailwind
   preset instead. Both read the same tokens, which is what keeps the site and the
   products looking like one company.

   Naming rule: a class names the ROLE, and the role names the color it resolves
   to. `.lw-btn-cta` is the amber, `.lw-btn-brand` the cyan, `--lw-space-24` is
   24px. No alias may resolve to a color other than the one it is named after.
   ============================================================================= */

/* The bare-element reset that used to open this file now lives in reset.css.
   It was moved because un-layered element rules beat every Tailwind utility, so
   `button { background: none; border: 0; padding: 0 }` stripped every <Button>
   in a Tailwind app — which made this file un-importable by the very consumers
   the README told to import it. This layer is now `.lw-*`-only and safe to load
   as `@import "@leanwise/design/base.css" layer(components);`.

   Vanilla consumers load reset.css themselves, first (the lw.css shim that used
   to do it was removed in v2.0.0). Nothing else changed: same rules, same order,
   same values. */

/* One screen-reader-only utility, under two names. `.visually-hidden` lived here
   and `.lw-sr-only` lived in product.css with a different clip technique — the
   same utility twice, in two layers, so a page loading only base.css had one and
   a page loading only product.css had the other. `.lw-sr-only` is the canonical
   name (it carries the package prefix); `.visually-hidden` is kept as an alias
   for one major because consumers use it. */
.lw-sr-only,
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
/* ⚠ A TABLE DOES NOT OBEY THE RULE ABOVE WITHOUT THIS LINE, and the failure is a
   sideways-scrolling page.

   `width` is a MINIMUM to a table box, and `overflow` does not clip one the way it
   clips a block — so `.lw-sr-only` on a `<table>` left it laid out at its full
   content width AND absolutely positioned: a 702px table inside a 375px viewport,
   measured. `table-layout: fixed` is what makes the 1px bind.

   This shipped in `DataTable` (every `BarChart` and `LineChart` carries one) from
   the day the sr-only table was introduced, and no gate could see it: the specimen
   cards render at 1280px where a 702px table fits, the text is invisible by
   definition, and axe has no opinion about geometry. It is the second time an
   accessibility affordance has broken the layout it was added to protect —
   `CompareTable`'s state words were the first, and they widened the flagship
   consumer's document on every phone.

   The `> table` half covers the wrapped form, which is what a caller reaches for
   after being bitten by the unwrapped one. */
table.lw-sr-only,
table.visually-hidden,
.lw-sr-only > table,
.visually-hidden > table {
  table-layout: fixed;
  width: 1px;
}

/* ...and the CAPTION is the second half of the same bug. `table-layout: fixed`
   alone got the measured table from 702px down to 183px, not to 1px, because a
   caption box is laid out in the table's anonymous WRAPPER and sets a floor under
   it — with the `white-space: nowrap` above, that floor is the caption's whole
   sentence on one line. Taking the caption out of flow is what actually reaches
   1px; `max-width` does not, because a table box treats it the same way it treats
   `width`. Measured on all three specimens.

   The accessible name survives — it is computed from the caption ELEMENT, and CSS
   position does not move a node in the a11y tree. Verified against Chrome's own
   tree, not assumed: all three tables still report their caption as their name. */
table.lw-sr-only > caption,
table.visually-hidden > caption,
.lw-sr-only > table > caption,
.visually-hidden > table > caption {
  position: absolute;
  width: 1px;
  overflow: hidden;
}

/* The skip link. Hidden until focused, then a real, visible control at the top
   of the page — the canonical fix for a shell that puts a long sidebar ahead of
   the content, which is nine of the twelve templates. */
.lw-skip {
  position: absolute;
  inset-inline-start: var(--lw-space-8);
  inset-block-start: var(--lw-space-8);
  z-index: var(--lw-z-toast);
  padding: 10px 16px;
  border-radius: var(--lw-radius-sm);
  background: var(--lw-brand-500);
  color: var(--lw-on-brand);
  font-size: var(--lw-text-sm);
  font-weight: var(--lw-fw-semibold);
  text-decoration: none;
  transform: translateY(-200%);
}
.lw-skip:focus-visible {
  transform: none;
  outline: none;
  box-shadow: var(--lw-focus-ring);
}

.lw-container {
  max-width: var(--lw-col-max);
  margin: 0 auto;
  padding: 0 var(--lw-pad-x);
}

/* PROMOTED from product.css in v1.3.0, verbatim. Arrangement is not an app
   surface: a marketing page that correctly drops the product layer still has to
   lay itself out, and it was getting nothing at all. */
/* ---- Layout primitives -----------------------------------------------------
   Four names cover almost every arrangement. They exist so "responsive" is a
   token decision, not a fresh media query in every consumer. */

/* PAGE — the app shell width (wider than the reading column, holds rails). */
.lw-page { width: 100%; max-width: var(--lw-col-page); margin-inline: auto; padding-inline: var(--lw-pad-x); }

/* STACK — vertical rhythm. The gap is the only thing it does. */
.lw-stack { display: flex; flex-direction: column; gap: var(--lw-space-16); }
.lw-stack-4 { gap: var(--lw-space-4); } .lw-stack-8 { gap: var(--lw-space-8); }
.lw-stack-12 { gap: var(--lw-space-12); } .lw-stack-20 { gap: var(--lw-space-20); }
.lw-stack-24 { gap: var(--lw-space-24); } .lw-stack-32 { gap: var(--lw-space-32); }
.lw-stack-48 { gap: var(--lw-space-48); } .lw-stack-64 { gap: var(--lw-space-64); }

/* CLUSTER — horizontal group that wraps. Toolbars, chip rows, action bars.
   Wrapping is the default because a row that cannot wrap will overflow a phone. */
.lw-cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--lw-space-8); }
.lw-cluster-12 { gap: var(--lw-space-12); } .lw-cluster-16 { gap: var(--lw-space-16); }
.lw-cluster-24 { gap: var(--lw-space-24); }
.lw-cluster-between { justify-content: space-between; }
.lw-cluster-end { justify-content: flex-end; }
.lw-cluster-baseline { align-items: baseline; }

/* NOWRAP — the icon-and-prose row, and the one shape `.lw-cluster` could not make.
   A flex item's base size is `auto` → max-content, and a WRAP container moves an
   item that does not fit onto the next line rather than shrinking it. So every
   "icon + a sentence" row dropped its sentence underneath the icon the moment the
   sentence ran past one line, leaving a column of orphaned glyphs: 52 rows across
   the flagship site's five marketing pages, both locales, at 375px AND at 1280px.

   Nothing caught it, and that is the part worth keeping. The row stays inside the
   viewport, so an overflow gate measures nothing; axe has no opinion about which
   line a glyph sits on; and the icon is still beside the text whenever the text is
   short, so every specimen card and every screenshot of a SHORT label looks right.

   `min-inline-size: 0` is the half that makes `nowrap` safe. Without it each child
   keeps its min-content floor and the row OVERFLOWS instead of wrapping its text —
   trading a visible bug for the invisible one this system has already fixed four
   times (`.lw-grid-2`, `.lw-story`, `.lw-console-log`, `Console`'s inline tracks).
   `.lw-icon` is `flex: none`, so a zero floor cannot shrink the glyph itself.

   ⚠ That floor is why this is NOT the modifier for a toolbar. "A row that does not
   wrap" also describes `.lw-topbar`, whose children must keep their intrinsic width
   — it is a row whose width is the SUM of its parts, and the 144px `ThemeToggle`
   that pushed a 375px bar to 469px is what that costs when it is got wrong. Reach
   for this one only when a child is PROSE that should wrap inside its own box.

   ⚠ The shape itself is already spelled twice in this package, bolted to one
   component each: `.lw-plan-feature` (flex, marketing.css) and `.lw-step`'s
   `40px minmax(0, 1fr)` grid. The real fix is to promote ONE media object into this
   file and re-point all three; until that happens a fourth spelling is a bug. */
.lw-cluster-nowrap { flex-wrap: nowrap; }
/* `*`, not `:where(*)` — the universal selector already carries zero specificity,
   so the wrapper bought nothing but the suggestion that it did. */
.lw-cluster-nowrap > * { min-inline-size: 0; }

/* GRID — auto-fit columns. --lw-grid-min is the only knob; the grid reflows on
   its own at every width, so no breakpoint is needed for the common case.

   `min(…, 100%)` around the track minimum, and it is the whole reason the claim
   above is true. A bare `minmax(320px, 1fr)` is a DEFINITE minimum: once the
   container is narrower than it, the track keeps its 320px and the row overflows
   — silently, because a grid does not clip and the page simply grows a
   horizontal scrollbar. `.lw-grid-2` is 320px, so every consumer using it broke
   at a 320px viewport (a ~280px column after the container's padding) — measured
   on the flagship site's customer stories, which sat 40px past the fold. Wrapping
   in `min()` caps the floor at the space that actually exists, so the last column
   narrows instead of escaping. */
.lw-grid-2 { --lw-grid-min: 320px; } .lw-grid-3 { --lw-grid-min: 240px; } .lw-grid-4 { --lw-grid-min: 180px; }
.lw-grid { display: grid; gap: var(--lw-space-16); grid-template-columns: repeat(auto-fit, minmax(min(var(--lw-grid-min, 240px), 100%), 1fr)); align-items: stretch; }
.lw-grid-24 { gap: var(--lw-space-24); }

/* SPLIT — a main column and a fixed-ish rail that collapses to one column.
   1024px is --lw-bp-lg; a two-pane app cannot hold two panes below it. */
.lw-split { display: grid; gap: var(--lw-space-24); grid-template-columns: minmax(0, 1fr) var(--lw-split-rail, 320px); align-items: start; }
.lw-split-start { grid-template-columns: var(--lw-split-rail, 280px) minmax(0, 1fr); }
/* --lw-bp-lg (1024px) — the rail has no room beside the main column below it. */
@media (max-width: 1023.98px) { .lw-split, .lw-split-start { grid-template-columns: minmax(0, 1fr); } }

/* Utility: a scroll area that does not leak its scroll to the page. */
.lw-scroll { overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--lw-line-strong) transparent; }
.lw-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.lw-scroll::-webkit-scrollbar-thumb { background: var(--lw-line-strong); border-radius: var(--lw-radius-pill); border: 3px solid transparent; background-clip: content-box; }
.lw-scroll::-webkit-scrollbar-track { background: transparent; }

/* ---- Type ---------------------------------------------------------------- */

.lw-mono {
  font-family: var(--lw-font-mono);
  letter-spacing: var(--lw-tracking-wide);
}

/* The signature motif: mono, uppercase, wide-tracked, with a cyan hexagon. */
.lw-eyebrow {
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-eyebrow);
  font-weight: var(--lw-fw-medium);
  letter-spacing: var(--lw-tracking-eyebrow);
  /* Tracking opens EVERY gap by one step. A letter gap starts near zero, so one
     step is a several-hundred-percent change; a word gap starts wide, so the
     same step barely moves it. The word boundary therefore loses its lead over
     the letter boundary and the label reads as loose letters rather than words.
     The answer is to open the word space as well — never to close it. */
  word-spacing: var(--lw-word-spacing-caps);
  text-transform: uppercase;
  color: var(--lw-fg-subtle);
  display: inline-flex;
  align-items: center;
  gap: var(--lw-space-8);
}
/* The node is a HEXAGON, not a dot (v0.4.0) — the mark's shape carried into the
   type system, at the one size where it still reads. The halo it replaced was
   --lw-focus-ring used as decoration, which is a token borrowed out of role. */
.lw-eyebrow::before {
  content: "";
  width: 8px; height: 9px;
  flex: none;
  background: var(--lw-brand-500);
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
/* `.muted` drops the brand node, nothing else: the base ink is already the subtle
   tier, so re-declaring it here was a variant that changed nothing anyone could
   see. What separates the two is whether the label is worth a brand mark. */
.lw-eyebrow.muted::before { background: var(--lw-fg-faint); }
/* PROMOTED from product.css in v1.3.0, with `.lw-icon-btn` and for the same
   reason. The node is the one part of the motif pinned to a TIER, so on a dark
   band the label re-points to the dark subtle ink while its node stays
   brand-500 — a cyan too dark for navy. marketing.css patches `.lw-section.dark`
   and `.lw-hero-dark`; a plain `.lw-band-dark` was the hole, and it lived one
   file over in a layer a marketing page has no reason to load. Position was not
   load-bearing (`:is(.dark, …)` outranks the base rule wherever it sits);
   PRESENCE was. */
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-eyebrow::before { background: var(--lw-brand-400); }
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-eyebrow.muted::before { background: var(--lw-fg-faint); }

.lw-h1 {
  font-family: var(--lw-font-display);
  font-size: var(--lw-text-h1);
  line-height: var(--lw-lh-tight);
  letter-spacing: var(--lw-tracking-tighter);
  font-weight: var(--lw-fw-semibold);
  margin: 0;
  text-wrap: balance;
}
/* DISPLAY — one notch above h1, weight 500: the lighter editorial voice of a
   landing/auth hero headline. Mirrors the preset's `text-display`. */
.lw-display {
  font-family: var(--lw-font-display);
  font-size: var(--lw-text-display);
  line-height: var(--lw-lh-tight);
  letter-spacing: var(--lw-tracking-tighter);
  font-weight: var(--lw-fw-medium);
  margin: 0;
  text-wrap: balance;
}
/* `.lw-prose h2` / `h3` are GROUPED onto the scale rules rather than restated
   under the prose section. An authored `<h2 class="lw-h2">` and a markdown `##`
   inside `.lw-prose` are the same heading at the same level, and a second copy
   of the size is a second copy that drifts — which is the whole argument of
   README rule 1, applied to a declaration instead of a component. */
.lw-h2,
.lw-prose h2 {
  font-family: var(--lw-font-display);
  font-size: var(--lw-text-h2);
  line-height: var(--lw-lh-snug);
  letter-spacing: var(--lw-tracking-tight);
  font-weight: var(--lw-fw-semibold);
  margin: 0;
  text-wrap: balance;
}
.lw-h3,
.lw-prose h3 {
  font-size: var(--lw-text-h3);
  line-height: var(--lw-lh-snug);
  letter-spacing: var(--lw-tracking-tight);
  font-weight: var(--lw-fw-semibold);
  margin: 0;
}
.lw-lead {
  font-size: var(--lw-text-lead);
  line-height: var(--lw-lh-normal);
  color: var(--lw-fg-muted);
  max-width: var(--lw-measure);
  margin: 0;
  text-wrap: pretty;
}

/* ---- Buttons ------------------------------------------------------------- */

.lw-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* Height is DECLARED, never accumulated from padding — padding-derived heights
     drift the moment a consumer changes the font. 32 / 40 / 48 on the 8px grid, in
     step with spacing and the input row height. Horizontal padding is ~0.45x the
     height: the wide, engineered stance rather than a cramped chip.

     The value comes from --lw-control-h-md so a button in a [data-density] scope
     matches the inputs beside it. A marketing band sets no density attribute, so it
     resolves to the 40px default and a hero is untouched. */
  height: var(--lw-control-h-md);
  padding: 0 var(--lw-btn-pad-x);
  /* STATED, not inherited from the reset. `reset.css`'s `button { background: none;
     border: 0 }` used to sit at the top of base.css; v1.2.0 split it into its own
     file so a Tailwind app could import base.css at all. Correct — but the button
     family had been leaning on it, and base.css alone therefore left the UA
     `buttonface` and the `2px outset` bevel in place on every `.lw-btn` for any
     consumer that does not load a reset. Only the LINK and no-variant buttons
     declare no fill of their own, so they are the ones where it shows as a COLOUR:
     `--lw-brand-400` on Chromium's dark-scheme buttonface #6B6B6B is **2.28:1**,
     four serious axe failures in the rest and hover cells of the state matrix.
     Nobody saw it because every React specimen card was blank from v1.2 to v1.3
     (see tools/lw-bundle.mjs). The rule: a property that carries a COLOUR is
     base.css's to state, because a colour base.css does not state is a colour the
     contrast gate cannot see. Geometry (box-sizing) stays the reset's job — that
     one fails visibly. `.lw-btn-ghost` re-declares its 1px border below. */
  background: none;
  border: 0;
  font-family: var(--lw-font-sans);
  font-size: var(--lw-text-sm);
  font-weight: var(--lw-fw-medium);
  letter-spacing: var(--lw-btn-tracking);
  border-radius: var(--lw-radius-md);
  transition: background var(--lw-dur-sm), color var(--lw-dur-sm),
              border-color var(--lw-dur-sm), transform var(--lw-dur-sm),
              box-shadow var(--lw-dur-sm);
  white-space: nowrap;
  line-height: 1.2;
}
/* Glyphs scale with the label, never with the box: 16px at default, 14 / 18 at the
   ends. An icon larger than its cap height reads as a sticker on the button. */
.lw-btn svg, .lw-btn .lw-icon { width: 16px; height: 16px; flex: none; }
.lw-btn-sm svg, .lw-btn-sm .lw-icon { width: 14px; height: 14px; }
.lw-btn-lg svg, .lw-btn-lg .lw-icon { width: 18px; height: 18px; }
/* A glyph icon (icon font, ✕, arrow) has no width/height to set — it is TYPE, so it
   needs a font-size or it silently renders at the label size and looks a size too small.
   1em on the box, centered, so glyph and SVG icons occupy the same square. */
.lw-btn .lw-icon { display: inline-flex; align-items: center; justify-content: center; font-size: 16px; line-height: 1; }
.lw-btn-sm .lw-icon { font-size: 14px; }
.lw-btn-lg .lw-icon { font-size: 18px; }
/* Icon-only: the glyph IS the content, so it steps up one notch from the inline icon —
   it has to hold the square on its own without a label beside it for scale. */
.lw-btn-icon { font-size: 17px; line-height: 1; }
.lw-btn-icon.lw-btn-sm { font-size: 14px; }
.lw-btn-icon.lw-btn-lg { font-size: 20px; }
.lw-btn .arrow { transition: transform var(--lw-dur-sm); }
.lw-btn:hover .arrow { transform: translateX(2px); }
.lw-btn:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
/* A brand ring on a brand FILL is 1:1 against it, so the filled variants take an
   offset outline instead — the transparent gap separates ring from fill and shows
   the real local ground, which a box-shadow gap cannot do. */
.lw-btn-brand:focus-visible, .lw-btn-cta:focus-visible, .lw-btn-danger:focus-visible {
  box-shadow: none; outline: 2px solid var(--lw-brand-500); outline-offset: 2px;
}
.lw-btn-cta:focus-visible { outline-color: var(--lw-cta-text); }
.lw-btn-danger:focus-visible { outline-color: var(--lw-danger-on); }
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-btn-brand:focus-visible { outline-color: var(--lw-brand-400); }

/* CTA = amber. One per view. This is the whole point of having an amber.
   Never named "primary": shadcn's --primary is the cyan, and one word cannot mean
   two colors. Note the navy ink — white on #FCB603 is 1.77 and fails WCAG AA. */
.lw-btn-cta {
  background: var(--lw-cta-500);
  color: var(--lw-on-cta);   /* NOT --lw-fg: that flips near-white on dark → 1.77 on amber */
  box-shadow: 0 1px 2px hsl(var(--lw-cta-600-c) / 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.lw-btn-cta:hover {
  background: var(--lw-cta-600);
  transform: translateY(-1px);
  box-shadow: 0 10px 22px -10px hsl(var(--lw-cta-600-c) / 0.55);
}

/* BRAND = cyan. Secondary emphasis, product accents.
   Ink is --lw-on-brand (white, 5.66), never --lw-fg: --lw-fg re-points per theme,
   and the fill does not, so its ink must not either. */
.lw-btn-brand {
  background: var(--lw-brand-500);
  color: var(--lw-on-brand);
  box-shadow: 0 1px 2px hsl(var(--lw-brand-700-c) / 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.20);
}
.lw-btn-brand:hover { background: var(--lw-brand-600); transform: translateY(-1px); } /* white 7.91 */

/* INK = navy. High-contrast neutral action. */
.lw-btn-ink { background: var(--lw-navy-900); color: var(--lw-on-dark); }
.lw-btn-ink:hover { background: var(--lw-navy-700); transform: translateY(-1px); }

.lw-btn-ghost {
  background: var(--lw-bg);
  color: var(--lw-fg);
  border: 1px solid var(--lw-line-control);
}
.lw-btn-ghost:hover { border-color: var(--lw-fg-subtle); background: var(--lw-bg-subtle); }

/* Ghost is the one variant with no fill of its own, so it is the only one that has
   to be told about the ground it sits on. Consumers must never patch this inline:
   an inline style outranks :hover and silently removes the hover state. */
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-btn-ghost {
  background: transparent;
  color: var(--lw-on-dark);
  border-color: var(--lw-on-dark-border);
}
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-btn-ghost:hover {
  background: var(--lw-on-dark-fill);
  border-color: var(--lw-on-dark-border-hover);
  color: var(--lw-on-dark);
}
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-btn-link { color: var(--lw-brand-400); }
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-btn-link:hover { border-bottom-color: var(--lw-brand-400); }

/* The focus ring is brand-500 at 30% — nearly invisible against navy. Dark grounds
   take the 400 tier so the ring reads in both themes. The tier swap lives in the
   token, so this rule only has to name the token. */
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-btn:focus-visible {
  box-shadow: var(--lw-focus-ring);
}

/* Pressed. Every variant lifts on hover, so every variant must land again on press —
   otherwise the button stays raised while the pointer is down and feels stuck. */
.lw-btn:active { transform: translateY(0); transition-duration: var(--lw-dur-xs); }

.lw-btn-link {
  padding: 0;
  gap: 6px;
  color: var(--lw-brand-text);
  border-radius: 0;
  border-bottom: 1px solid transparent;
  font-weight: var(--lw-fw-medium);
}
.lw-btn-link:hover { border-bottom-color: var(--lw-brand-500); }

/* DANGER = destructive confirms. The one fill that takes white ink. */
.lw-btn-danger { background: var(--lw-danger); color: var(--lw-on-danger); }
.lw-btn-danger:hover { background: hsl(var(--lw-danger-text-c)); transform: translateY(-1px); }

/* Sizes. Every one clears a 44px target except -sm, which is for dense toolbars
   and table rows where the row itself carries the hit area. */
.lw-btn-sm { height: var(--lw-control-h-sm); padding: 0 var(--lw-btn-pad-x-sm); font-size: var(--lw-text-xs, 12px); border-radius: var(--lw-radius-sm); gap: 6px; }
.lw-btn-lg { height: var(--lw-control-h-lg); padding: 0 var(--lw-btn-pad-x-lg); font-size: var(--lw-text-body); border-radius: var(--lw-radius-lg); gap: 10px; }
/* Icon-only is a SQUARE of the same height — never a separate size scale, or icon and
   text buttons stop lining up in a toolbar. aria-label is required; there is no text. */
.lw-btn-icon { width: var(--lw-control-h-md); padding: 0; }
.lw-btn-icon.lw-btn-sm { width: var(--lw-control-h-sm); }
.lw-btn-icon.lw-btn-lg { width: var(--lw-control-h-lg); }

/* Touch. 40px is correct for a pointer and short of the 44px touch target, so on a
   coarse pointer every size grows to 44 instead of the design shipping one size that
   is wrong for one of the two input methods. */
@media (pointer: coarse) {
  .lw-btn { min-height: 44px; }
  .lw-btn-sm { min-height: 44px; padding: 0 14px; }
  .lw-btn-icon, .lw-btn-icon.lw-btn-sm { min-width: 44px; }
}

/* Disabled is a STATE, not a variant: it must flatten every fill identically, or a
   disabled CTA still reads as the loudest thing on the page.

   ROLE tokens, not the surface-3/text-4 TIERS this used through v1.0. A tier is a
   fixed palette value that no dark scope re-points, so a disabled button inside
   .lw-band-dark kept a light-grey fill and light-grey ink — a pale block sitting on
   navy. bg-muted and fg-faint are re-pointed by every dark scope, so the flattening
   follows the ground. README rule 2, caught by the state matrix in Button.card. */
.lw-btn:disabled, .lw-btn[aria-disabled="true"] {
  background: var(--lw-bg-muted); color: var(--lw-fg-faint);
  box-shadow: none; transform: none; cursor: not-allowed; pointer-events: none;
}
/* …except the link variant, which has no fill in any other state. Flattening it
   to bg-muted grew a grey box around a word that was never a box — the disabled
   and loading link were the only two cells in the matrix with a shape of their
   own. A link goes quiet by losing its ink and its rule, not by gaining a
   surface. */
.lw-btn-link:disabled, .lw-btn-link[aria-disabled="true"] { background: none; border-bottom-color: transparent; }
.lw-btn[data-loading="true"] { position: relative; color: transparent; pointer-events: none; }
/* The spinner cannot use currentColor — the loading state sets color:transparent to
   hide the label, which would make the ring invisible too. Each dark fill re-points
   this ink to white; the amber CTA pins the navy that the fill demands, and only
   the ghost/link variants — which paint on the page ground — track --lw-fg. */
.lw-btn { --lw-btn-spin-ink: var(--lw-fg); }
.lw-btn-brand, .lw-btn-ink, .lw-btn-danger { --lw-btn-spin-ink: var(--lw-on-dark); }
/* The amber fill is theme-invariant, so its spinner ink must be too. */
.lw-btn-cta { --lw-btn-spin-ink: var(--lw-on-cta); }
.lw-btn:disabled, .lw-btn[aria-disabled="true"] { --lw-btn-spin-ink: var(--lw-fg-faint); }
.lw-btn[data-loading="true"]::after {
  content: ""; position: absolute; width: 15px; height: 15px; border-radius: 999px;
  border: 2px solid var(--lw-btn-spin-ink); border-top-color: transparent; opacity: .9;
  animation: lwBtnSpin var(--lw-dur-xl) linear infinite;
}
@keyframes lwBtnSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .lw-btn:hover { transform: none; }
  .lw-btn[data-loading="true"]::after { animation-duration: var(--lw-dur-loop-slow); }
}

/* ---- Icon ---------------------------------------------------------------- */

/* PROMOTED from product.css in v1.3.1, verbatim — one rule, and the entire
   layout contract for the icon set. `Icon` is a PRIMITIVE and half the
   marketing layer names a glyph through it (`SiteFooter`, `AnnounceBar`,
   `FeatureGrid`, `PlanCard`, `CompareTable`, `Steps`, `EmptyState`,
   `Disclosure`, `NavToggle`, `Button`), so it has no business in the app layer.
   Without it an `<svg>` takes the UA's `display: inline`, sits on the text
   baseline and grows its own line box: a footer link carrying an `external`
   marker measured 39px instead of 27px.

   It sits BELOW the `.lw-btn .lw-icon` sizing rules deliberately, but nothing
   rests on that — those are 0-2-0 against this 0-1-0, so they outrank it from
   anywhere. Kept in reading order, not cascade order.

   Nobody found this by reading. `check:visual` found it, the moment
   `marketing.card.html` and `site-chrome.card.html` stopped loading
   product.css — which is the whole argument for making a specimen load exactly
   what its documented recipe loads. */
.lw-icon { flex: none; display: inline-block; vertical-align: -0.14em; }

/* ---- Icon button --------------------------------------------------------- */

/* PROMOTED from product.css in v1.3.0 — fb2d3ad's correction, one file further.
   `.lw-icon-btn` is the shared borderless icon control: one place that owns the
   hit area and the brand focus ring, so a bare glyph is never a focus stop with
   no visible indicator. It is not an app-shell thing. `.lw-topbar-toggle`
   composes it, the announce bar's dismiss is one, and the site footer's social
   row is a line of them — three marketing surfaces on a page that has no reason
   to load product.css. Until this move the toggle got its display, position and
   ring from base.css and none of the shared face.

   `.lw-dialog-close` did NOT come with it: a dialog is an app surface, and the
   two were only ever one selector list because they share a face. `Dialog.jsx`
   now emits `lw-icon-btn lw-dialog-close`, so product.css keeps the close
   control's one real delta (its optical margin) and no copy of the face —
   which is the point of the split, not a side effect of it. */
.lw-icon-btn { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: var(--lw-radius-sm); background: none; color: var(--lw-fg-subtle); cursor: pointer; transition: color var(--lw-dur-xs) var(--lw-ease-out); }
.lw-icon-btn:hover { color: var(--lw-fg); }
.lw-icon-btn:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-icon-btn[disabled] { color: var(--lw-fg-faint); cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { .lw-icon-btn { transition: none; } }

/* ---- Pills / tags -------------------------------------------------------- */

.lw-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--lw-space-8);
  padding: 5px 10px 5px 8px;
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-2xs);
  font-weight: var(--lw-fw-medium);
  letter-spacing: 0.10em;
  word-spacing: var(--lw-word-spacing-caps);
  text-transform: uppercase;
  background: var(--lw-bg);
  border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-pill);
  color: var(--lw-fg-muted);
}
.lw-pill .dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--lw-success);
  box-shadow: 0 0 0 3px hsl(var(--lw-success-c) / 0.20);
}
.lw-pill.live { border-color: hsl(var(--lw-success-c) / 0.40); color: hsl(var(--lw-success-on-c)); }
.lw-pill.live .dot { animation: lwPulse var(--lw-dur-loop) ease-in-out infinite; }
.lw-pill.rd { color: var(--lw-fg-subtle); }
.lw-pill.rd .dot {
  background: var(--lw-fg-faint);
  /* --lw-fg-faint-c, not the raw --lw-text-4-c tier: the fill already reads the
     semantic role, and the raw tier is never re-pointed by a dark scope — so on navy
     the dot changed colour and its halo did not. */
  box-shadow: 0 0 0 3px hsl(var(--lw-fg-faint-c) / 0.20);
}
/* brand-50 and brand-200 are palette TIERS — the same near-white in both
   themes — so pairing them with brand-text (which re-points to the light
   brand-400 on dark) put light cyan on near-white at 2.18. The tint that
   follows the theme is brand-soft, and its ink is brand-on. */
.lw-pill.brand {
  border-color: var(--lw-brand-line);
  color: var(--lw-brand-on);
  background: var(--lw-brand-soft);
}
.lw-pill.brand .dot {
  background: var(--lw-brand-500);
  box-shadow: 0 0 0 3px hsl(var(--lw-brand-500-c) / 0.22);
}

@keyframes lwPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

/* ---- Cards --------------------------------------------------------------- */

.lw-card {
  background: var(--lw-bg);
  border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-lg);
  transition: border-color var(--lw-dur-sm), box-shadow var(--lw-dur-sm), transform var(--lw-dur-sm);
}
/* Interactive cards. A card that lifts on hover is a control: it needs a pointer
   cursor, a keyboard focus ring and a press state, or it looks clickable to a mouse
   and is invisible to everything else. */
.lw-card-interactive { cursor: pointer; }
.lw-card-interactive:hover {
  border-color: var(--lw-line-strong);
  box-shadow: var(--lw-shadow-md);
  transform: translateY(-2px);
}
.lw-card-interactive:active { transform: translateY(0); box-shadow: var(--lw-shadow-sm); transition-duration: var(--lw-dur-xs); }
.lw-card-interactive:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-color: var(--lw-brand-500); }
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-card-interactive:focus-visible { box-shadow: var(--lw-focus-ring); border-color: var(--lw-brand-400); }
@media (prefers-reduced-motion: reduce) {
  .lw-card-interactive:hover { transform: none; }
}

/* ---- Card anatomy ---------------------------------------------------------
   The card owns its interior — a 20px pad and a 12px vertical rhythm — so cards
   line up across consumers instead of each inventing its own padding. */
/* Flex column, not grid: `margin-top:auto` on the footer only pushes the footer to
   the bottom inside a flex container, which is what aligns footers across a row. */
.lw-card { padding: 20px; display: flex; flex-direction: column; gap: 12px; }
/* A Disclosure as the card's LAST child: its own bottom rule sat 20px above the
   card's edge, so a closed panel read as a card with an empty body (measured on
   an approval page). The card's edge is the boundary there; the rule steps aside. */
.lw-card > .lw-disclosure:last-child { border-block-end: 0; }
/* A Disclosure that IS a card (`lw-disclosure lw-card`): the card's 20px padding and
   its 12px flex gap both wrapped a 56px summary, measured at 110px closed with 36px
   above the title and 48px below — five of them stacked on an approval page. The
   summary carries its own vertical rhythm, so the card's collapses to the horizontal. */
.lw-disclosure.lw-card { gap: 0; padding-block: var(--lw-space-4); }
/* An interactive card IS a <button>, and a button centres its text: without this
   the body copy sat centred while the title read left. Any surface that can
   become a button inherits page text alignment, not the control default. */
button.lw-card, button.lw-feature, button.lw-story, button.lw-stat-tile,
button.lw-kpi, button.lw-nav-item, button.lw-source-item, button.lw-logo-item,
button.lw-empty { text-align: start; font: inherit; color: inherit; }
/* `flex-wrap: wrap`, and it is the same defect the top bar had in v1.3.4: a row
   that cannot wrap and whose children cannot shrink is as wide as the SUM of its
   parts. This head routinely holds a title on the left and a status chip on the
   right, and `.lw-chip` is `white-space: nowrap` by design — so on a ~280px card
   at a 320px viewport, "Delivery Specification" beside "Passed with Warning"
   needed 327px and the chip hung 27px past the card, taking the document with
   it. Wrapping drops the chip onto its own line instead, which is the right
   answer at that width and changes nothing wherever the row already fits. */
.lw-card-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px; }
.lw-card-eyebrow {
  font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); line-height: 1.4;
  letter-spacing: 0.12em; word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase; color: var(--lw-fg-subtle);
}
.lw-card-title { font-size: 16px; font-weight: var(--lw-fw-semibold, 600); letter-spacing: -0.012em; line-height: 1.3; margin: 0; }
.lw-card-body { font-size: 13.5px; line-height: 1.55; color: var(--lw-fg-muted); margin: 0; text-wrap: pretty; }
/* The footer pins to the bottom, so cards of different text lengths still align
   their actions on one line across a row. This is the whole reason to name it. */
.lw-card-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: auto; padding-top: 12px; border-top: 1px solid var(--lw-line);
}
/* Media bleeds to the edge by cancelling the pad; the concentric rule sets the
   inner radius to outer minus zero inset — the same tier, top corners only. */
.lw-card-media { margin: -20px -20px 4px; border-radius: var(--lw-radius-lg) var(--lw-radius-lg) 0 0; overflow: hidden; display: block; }
/* Selection is an inset ring, so it costs no layout and cannot shift a grid row. */
.lw-card.is-selected, .lw-card[data-selected="true"], .lw-card[aria-selected="true"] {
  border-color: transparent;
  box-shadow: inset 0 0 0 1.5px var(--lw-brand-500);
}
/* Equal-height cards on one row. auto-fit + stretch is what keeps footers aligned.
   `min(240px, 100%)` for the same reason as `.lw-grid` above: a definite track
   minimum wider than its container overflows the page rather than shrinking. */
.lw-card-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); align-items: stretch; }
.lw-card-grid > .lw-card { height: 100%; }

/* Chip — the card's status atom. Reads a status family's soft tint + text tier, so
   it re-points on dark with no conditional. */
.lw-chip {
  display: inline-flex; align-items: center; gap: var(--lw-chip-gap);
  font-size: var(--lw-chip-text); font-weight: var(--lw-fw-medium); line-height: 1;
  padding: var(--lw-chip-pad-y) var(--lw-chip-pad-x); border-radius: var(--lw-radius-pill);
  background: var(--lw-brand-soft); color: var(--lw-brand-on); white-space: nowrap;
}
.lw-chip-success { background: var(--lw-success-soft); color: var(--lw-success-on); }
.lw-chip-warning { background: var(--lw-warning-soft); color: var(--lw-warning-on); }
.lw-chip-danger  { background: var(--lw-danger-soft);  color: var(--lw-danger-on); }
.lw-chip-neutral { background: var(--lw-neutral-soft); color: var(--lw-neutral-on); }
.lw-chip::before { content: ""; width: var(--lw-chip-dot); height: var(--lw-chip-dot); border-radius: 999px; background: currentColor; flex: none; }

/* ---- Avatar -------------------------------------------------------------- */

/* PROMOTED from product.css in v1.3.1, verbatim. `Avatar` is not an app-shell
   thing and never was: `Byline` renders one, and `ArticleCard` renders a
   `Byline`. Both are MARKETING components — so the package's own documented
   article-index recipe (a `Grid` of `ArticleCard`s) drew an unstyled 0×0 circle
   unless the page also loaded the whole app-surface layer. The flagship
   consumer was doing exactly that: importing `product.css` through a `?url`
   side-channel on two public routes for these four rules and the three blocks
   below. Same defect as `.lw-btn` and as `fb2d3ad`'s topbar — a component's CSS
   stranded in a layer its own consumers do not load.

   No dark patch: every value here is a ROLE (`--lw-brand-soft` / `--lw-brand-on`),
   so a dark band re-points the whole atom with nothing to move. */
/* AUTH — the one screen every product has and this package only ever DREW.
   `templates/auth/Auth.dc.html` has shipped a five-screen sign-in design since
   v1.4 built entirely from inline `style=` attributes, so the one thing a
   consumer could not do was use it. These are those measurements, named.

   Not vertically centred, and that is the whole point of `.lw-auth`: a phone
   keyboard covers the lower half of the viewport, and a centred card puts its
   submit button underneath the keyboard the moment the first field is focused.
   Top-aligned with generous space above reads as centred on a desktop and stays
   reachable on a phone. */
.lw-auth { display: flex; flex-direction: column; align-items: center; min-height: 100dvh; padding: clamp(24px, 8vh, 96px) var(--lw-pad-x) var(--lw-space-48); }
.lw-auth-panel { width: 100%; max-width: 380px; display: flex; flex-direction: column; gap: var(--lw-space-20); }
.lw-auth-panel-wide { max-width: 460px; }
/* The head sits OUTSIDE the card: a product name inside the card competes with
   the form's own label hierarchy, and on a narrow screen it is the first thing
   to be mistaken for a field label. */
.lw-auth-head { display: flex; flex-direction: column; gap: var(--lw-space-4); }
/* The row under the form — a locale switcher, a support link. Muted and
   separated, because it is not part of the form and must not read as another
   control the user is expected to fill in. */
.lw-auth-foot { display: flex; align-items: center; justify-content: center; gap: var(--lw-space-12); flex-wrap: wrap; padding-block-start: var(--lw-space-4); }
/* "or" between a password form and an SSO button. Hand-written as three spans
   in five places before it was a rule. */
.lw-auth-or { display: flex; align-items: center; gap: var(--lw-space-12); color: var(--lw-fg-subtle); font-size: var(--lw-text-xs); }
.lw-auth-or::before, .lw-auth-or::after { content: ""; flex: 1; height: 1px; background: var(--lw-line); }
.lw-avatar { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; flex: none; border-radius: 50%; background: var(--lw-brand-soft); color: var(--lw-brand-on); font-size: 12px; font-weight: var(--lw-fw-medium); letter-spacing: 0.01em; overflow: hidden; user-select: none; }
.lw-avatar img { width: 100%; height: 100%; object-fit: cover; }
.lw-avatar-sm { width: 24px; height: 24px; font-size: var(--lw-text-2xs); }
.lw-avatar-lg { width: 44px; height: 44px; font-size: 15px; }

/* ---- Empty state --------------------------------------------------------- */

/* PROMOTED from product.css in v1.3.1, verbatim, WITH its `.lw-band-dark`
   patches — position is not load-bearing, presence is (v1.3.0's lesson, applied
   the first time rather than in a follow-up). A search or article index with no
   results is the same screen on a marketing site as in an app; the documented
   article-index recipe names `EmptyState` for exactly that, and it rendered as
   unstyled centred text.

   `button.lw-empty { text-align: start }` sits ABOVE, in the card-anatomy block,
   and stays there: at 0-1-1 it outranks `.lw-empty`'s `text-align: center`
   regardless of order, which is the same relationship it had across the two
   files. Nothing about the composited result moves. */
/* EMPTY STATE — the screen a product is judged by, and the one that gets left
   as "No data". Title, one line of why, one action. */
.lw-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; text-align: center; padding: var(--lw-space-48) var(--lw-space-24); border: 1px dashed var(--lw-line-strong); border-radius: var(--lw-radius-lg); background: var(--lw-bg-subtle); }
.lw-empty .t { font-size: var(--lw-text-body); font-weight: var(--lw-fw-semibold); letter-spacing: -0.012em; color: var(--lw-fg); }
.lw-empty .s { font-size: var(--lw-text-sm); color: var(--lw-fg-muted); max-width: var(--lw-measure-sm); text-wrap: pretty; }
.lw-empty .glyph { display: inline-flex; font-family: var(--lw-font-mono); font-size: 20px; color: var(--lw-fg-subtle); }
/* The dashed frame and its ink on a dark band. Of the generic `.lw-band-dark`
   ground rule these sat beside, `.lw-kpi` and `.lw-source-item` are app surface
   and stayed in product.css; `.lw-table-wrap` followed the table here in v1.7.0
   — see the promotion below. */
.lw-band-dark .lw-empty, [data-band="dark"] .lw-empty { background: var(--lw-on-dark-wash); border-color: var(--lw-on-dark-line-strong); }
.lw-band-dark .lw-empty .t, [data-band="dark"] .lw-empty .t { color: var(--lw-on-dark-1); }
.lw-band-dark .lw-empty .s, [data-band="dark"] .lw-empty .s { color: var(--lw-on-dark-2); }

/* PROMOTED from product.css in v1.7.0, verbatim and in source order, WITH its
   dark-band patches — the fb2d3ad lesson is that position is not load-bearing
   but PRESENCE is, and a base-only page never loads product.css at all.

   Why a table is not app surface: `leanwise-ai` publishes its measured
   field-extraction accuracy — seven rows, each percentage beside the sample it
   was measured on — on a MARKETING page, `/product#evidence`. That is the site's
   single most important evidence artifact and it rendered as a bare UA table for
   four releases: no frame, no radius, no header face, no tabular figures, and no
   `overflow-x: auto`, which is the one that turns a wide table into a sideways
   page on a phone. Nothing could see it — axe does not score aesthetics, and a
   narrow table never trips a width gate. A table is a DATA display, and data is
   exactly what a page arguing from evidence has to show.

   Placed here, before the v1.3.0 forms promotion, so the consolidated
   pointer-affordance list at the FOOT of this file still comes last. The two
   `.lw-table` cursor rules and the `.num` nowrap rule stay in that list where
   they already were: it is documented as last-in-file precisely so a later
   `cursor: default` cannot silently cancel it, and moving them up here would
   break the invariant to tidy an import. */

/* TABLE. Hairline rows, mono numerics, a sticky head. tabular-nums on every
   numeric cell is the whole reason a table reads as data rather than as text. */
.lw-table-wrap { width: 100%; overflow-x: auto; border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg); background: var(--lw-bg); }
.lw-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--lw-text-sm); }
.lw-table th {
  text-align: start; font-family: var(--lw-font-mono); font-size: var(--lw-th-text);
  letter-spacing: var(--lw-th-tracking); word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase; font-weight: var(--lw-fw-medium);
  color: var(--lw-fg-subtle); background: var(--lw-bg-subtle);
  padding: 10px var(--lw-space-16); white-space: nowrap;
  border-bottom: 1px solid var(--lw-line); position: sticky; top: 0; z-index: var(--lw-z-raised);
}
.lw-table td { padding: 12px var(--lw-space-16); border-bottom: 1px solid var(--lw-line); color: var(--lw-fg); vertical-align: middle; }
.lw-table tbody tr:last-child td { border-bottom: 0; }
.lw-table .num { font-family: var(--lw-font-mono); font-variant-numeric: tabular-nums; text-align: end; }
.lw-table th.num { text-align: end; }
.lw-table .muted { color: var(--lw-fg-muted); }
.lw-table tbody tr { transition: background var(--lw-dur-xs) var(--lw-ease-out); }
.lw-table-hover tbody tr:hover td { background: var(--lw-bg-subtle); }
.lw-table-compact td { padding: 8px var(--lw-space-12); }
.lw-table-compact th { padding: 8px var(--lw-space-12); }
/* A sortable header is a button, not a th with a click handler. */
/* The 16px-tall sort control was the console's smallest target by a wide
   margin: font-size --lw-th-text on a zero-padding inline-flex, i.e. the
   glyph box and nothing else. `--lw-control-h-sm` is 32px, and 44px under
   `pointer: coarse` (tokens.css) — so this one declaration is the whole
   touch story, and it is REAL height, not a `.lw-hit` overlay: the space
   below a header cell belongs to the first data row, and an overlaid 44px
   box would take that row's taps. The th grows with it; nothing else moves,
   because the header is already taller than 32px on a fine pointer. */
.lw-table th button { appearance: none; border: 0; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; padding: 0; min-height: var(--lw-control-h-sm); }
.lw-table th button:hover { color: var(--lw-fg); }
.lw-table th button:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs); }
/* Only the column that is ACTUALLY sorted takes brand ink. Every sortable
   header carries aria-sort — "none" when it is not the active one, which is
   correct for assistive tech — so the selector has to exclude that value or the
   whole header row reads as active. */
.lw-table th[aria-sort]:not([aria-sort="none"]) button { color: var(--lw-brand-text); }
/* The caret is an affordance on an inactive header and a state on the active
   one, so the two must not be a hair apart. */
.lw-table th button .lw-icon { opacity: 0.3; }
.lw-table th[aria-sort]:not([aria-sort="none"]) button .lw-icon { opacity: 1; }
/* On a right-aligned column the caret must sit to the LEFT of the label. Trailing
   it puts the caret's width between the header text and the cell's right edge,
   so the label stops one glyph short of the column of figures it names and the
   two read as different columns. Reversing keeps label and figures on one edge. */
.lw-table th.num button { flex-direction: row-reverse; }

/* The table's dark-band patches, moved in the same commit rather than left for a
   follow-up pass — that is the one thing fb2d3ad got wrong and v1.3.0 had to
   correct. The WASH tier on the header, not the fill: `--lw-on-dark-3` is the
   muted floor (4.91 on navy-deep) and has no headroom, so every point the header
   ground is lightened comes straight off the ink. At the 0.06 fill this measured
   4.33 and axe caught it; at 0.03 it is 4.67. */
.lw-band-dark .lw-table-wrap, [data-band="dark"] .lw-table-wrap { background: var(--lw-on-dark-wash); border-color: var(--lw-on-dark-line); }
.lw-band-dark .lw-table th, [data-band="dark"] .lw-table th { background: var(--lw-on-dark-wash); color: var(--lw-on-dark-3); border-bottom-color: var(--lw-on-dark-line); }
.lw-band-dark .lw-table td, [data-band="dark"] .lw-table td { color: var(--lw-on-dark-1); border-bottom-color: var(--lw-on-dark-line); }

/* ---- The overflow hint ----------------------------------------------------
   `.lw-table-wrap` has scrolled sideways since v1.7.0 and never SAID so. On a
   phone a 318px track holding an 832px table shows the first two columns and
   nothing that suggests there are five more: leanwise-inspect's console shipped
   six such tables, each 1.5-2.4x the viewport, and the only affordance was the
   scrollbar — which a touch device does not paint until the finger is already
   moving.

   `data-overflow` is set by `useOverflow()` (components/_overflow.js) and ONLY
   when the wrap actually overflows, so a table that fits is untouched and a
   vanilla consumer (no React, no effect) sees exactly what it saw before. The
   attribute is the whole contract: any consumer that measures its own scroller
   can set it by hand.

   MASK, not a pseudo-element: `.lw-table-wrap` is the scroll container, so an
   absolutely positioned `::after` resolves against its SCROLLABLE area and
   parks at the far end of the content rather than at the visible edge — the
   fade would sit 800px off-screen. A mask is painted with the element's own
   box and stays put. The cost is that the frame fades with the content; that
   reads as the surface running past the edge, which is the message.

   The direction is PHYSICAL and has to be: `linear-gradient` has no logical
   `to inline-end`, and in RTL the overflow is on the other side — so the flip
   is stated rather than inherited. Same call, and the same reason, as
   `.lw-dgrid`'s pinned-column shadow two files over. */
.lw-table-wrap[data-overflow="true"] {
  --lw-table-fade: var(--lw-space-32);
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--lw-table-fade)), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--lw-table-fade)), transparent);
}
[dir="rtl"] .lw-table-wrap[data-overflow="true"] {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - var(--lw-table-fade)), transparent);
  mask-image: linear-gradient(to left, #000 calc(100% - var(--lw-table-fade)), transparent);
}

/* ---- Collapse to cards ----------------------------------------------------
   `<Table collapse="cards">`. Below --lw-bp-md of CONTAINER width every row
   paints as a card: first cell the title, the next two a meta line, the rest
   behind a native <details>.

   A CONTAINER query, not a media query, because the thing that is too narrow is
   the table's track, not the screen — the same table is fine at 768px of page
   inside a 300px rail and unreadable at 1280px of page inside a 318px one. The
   containment is scoped to `[data-collapse="cards"]` so no existing table
   acquires a containment context it did not ask for.

   Where `@container` is not supported the whole block is dropped and the table
   scrolls as it always did, hint included. That is the fallback, and it is why
   `.lw-table-sort` and `.lw-row-more` are hidden by DEFAULT and revealed by the
   query rather than the other way round: an unsupporting browser must not be
   left with a sort control the visible header row already carries.

   THE DOM DOES NOT CHANGE. One `<table>` at every width, re-laid-out by CSS —
   no width measurement, no second render tree, and nothing that can disagree
   with itself between the server and the client. The price is that `display:
   block` on table elements drops the implicit table semantics, so `Table` emits
   explicit `role="table"/"rowgroup"/"row"/"columnheader"/"cell"` whenever
   `collapse` is set. See Table.d.ts for why the <details> holds the control and
   not the cells. */
.lw-table-wrap[data-collapse="cards"] { container: lw-table / inline-size; }
.lw-table-sort { display: none; }
.lw-table-collapse .lw-row-more { display: none; }

/* 767.98px is --lw-bp-md (768px) in the .98 form this file uses everywhere. A
   custom property cannot be read in a container or media condition, so the
   number is written out here as it is in every @media above. */
@container lw-table (max-width: 767.98px) {
  .lw-table-sort {
    display: flex; justify-content: flex-end;
    padding: var(--lw-space-8) var(--lw-space-12);
    border-bottom: 1px solid var(--lw-line);
    background: var(--lw-bg-subtle);
  }
  .lw-table-collapse { display: block; }
  /* The header row goes, and with it every `aria-sort` button — which is why
     the sort menu exists. `display: none` and not a visually-hidden thead: a
     hidden-but-present header would be read by assistive tech AND printed by
     the `data-label` prefixes below, so every detail cell would announce its
     column name twice. */
  .lw-table-collapse > thead { display: none; }
  .lw-table-collapse > tbody { display: block; }
  .lw-table-collapse > tbody > tr {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: var(--lw-space-4) var(--lw-space-12);
    padding: var(--lw-space-12) var(--lw-space-16);
    border-bottom: 1px solid var(--lw-line);
  }
  .lw-table-collapse > tbody > tr:last-child { border-bottom: 0; }
  .lw-table-collapse > tbody > tr > td { display: block; padding: 0; border: 0; }
  .lw-table-collapse td[data-part="title"] {
    flex: 1 0 100%;
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--lw-space-12);
    font-size: var(--lw-text-body); font-weight: var(--lw-fw-semibold); color: var(--lw-fg);
  }
  .lw-table-collapse td[data-part="meta"] { color: var(--lw-fg-muted); font-size: var(--lw-text-xs); }
  .lw-table-collapse td[data-part="detail"] { display: none; flex: 1 0 100%; }
  /* The column header, per cell, from `data-label`. It is generated content on
     purpose: the alternative is a real element in every detail cell of every
     row, which a WIDE table then has to hide — markup carried at all widths to
     be used at one. Chromium, Gecko and WebKit all expose `content` text to the
     accessibility tree, and the thead above is gone, so this is the ONLY
     spelling of the column name in the collapsed view. A non-string header
     leaves the attribute off and the cell prints its value alone. */
  .lw-table-collapse td[data-part="detail"]::before {
    content: attr(data-label);
    font-family: var(--lw-font-mono); font-size: var(--lw-th-text);
    letter-spacing: var(--lw-th-tracking); text-transform: uppercase;
    color: var(--lw-fg-subtle);
  }
  .lw-table-collapse tr:has(> td > .lw-row-more[open]) > td[data-part="detail"] {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--lw-space-16);
  }
  /* Right alignment is a COLUMN affordance: it exists so figures stack under
     one another. In a card there is no column to stack in, and a lone
     right-aligned meta value reads as detached from the label it answers. The
     mono tabular face stays — that is about the figures themselves. */
  .lw-table-collapse td.num { text-align: start; }
  .lw-table-collapse td[data-part="detail"].num { text-align: end; }
  /* The disclosure. Not `.lw-disclosure`: that class owns a full-width block
     with a 16px block padding and a bottom rule, which is a section boundary,
     not a control sitting on a card's title line. */
  .lw-table-collapse .lw-row-more { display: block; flex: none; }
  .lw-table-collapse .lw-row-more > summary {
    display: inline-flex; align-items: center; gap: var(--lw-space-4);
    /* --lw-control-h-sm is 44px under `pointer: coarse` (tokens.css), so the
       target is real height rather than an overlaid ::after — a row card has a
       neighbour directly below it, and an overlay would take its taps. */
    min-height: var(--lw-control-h-sm);
    font-family: var(--lw-font-mono); font-size: var(--lw-th-text);
    letter-spacing: var(--lw-th-tracking); text-transform: uppercase;
    font-weight: var(--lw-fw-medium); color: var(--lw-fg-subtle);
    list-style: none;
  }
  .lw-table-collapse .lw-row-more > summary::-webkit-details-marker { display: none; }
  .lw-table-collapse .lw-row-more > summary:hover { color: var(--lw-fg); }
  .lw-table-collapse .lw-row-more > summary:focus-visible {
    outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs);
  }
  .lw-table-collapse .lw-row-more > summary > svg,
  .lw-table-collapse .lw-row-more > summary > .lw-icon {
    transition: transform var(--lw-dur-sm) var(--lw-ease-out);
  }
  .lw-table-collapse .lw-row-more[open] > summary > svg,
  .lw-table-collapse .lw-row-more[open] > summary > .lw-icon { transform: rotate(180deg); }
  /* A card is the hover unit once the row is one. The cell-level rule would
     paint four disjoint blocks with the card's padding left cold between them. */
  .lw-table-hover.lw-table-collapse tbody tr:hover td { background: none; }
  .lw-table-hover.lw-table-collapse tbody tr:hover { background: var(--lw-bg-subtle); }
  /* The dark-band patch, in the same commit as the rules it patches — the one
     thing fb2d3ad got wrong and v1.3.0 had to correct. */
  .lw-band-dark .lw-table-collapse > tbody > tr,
  [data-band="dark"] .lw-table-collapse > tbody > tr { border-bottom-color: var(--lw-on-dark-line); }
  .lw-band-dark .lw-table-sort,
  [data-band="dark"] .lw-table-sort { background: var(--lw-on-dark-wash); border-bottom-color: var(--lw-on-dark-line); }
  .lw-band-dark .lw-table-collapse td[data-part="title"],
  [data-band="dark"] .lw-table-collapse td[data-part="title"] { color: var(--lw-on-dark-1); }
  .lw-band-dark .lw-table-collapse td[data-part="meta"],
  [data-band="dark"] .lw-table-collapse td[data-part="meta"] { color: var(--lw-on-dark-2); }
  .lw-band-dark .lw-table-collapse td[data-part="detail"]::before,
  [data-band="dark"] .lw-table-collapse td[data-part="detail"]::before,
  .lw-band-dark .lw-table-collapse .lw-row-more > summary,
  [data-band="dark"] .lw-table-collapse .lw-row-more > summary { color: var(--lw-on-dark-3); }
}
@media (prefers-reduced-motion: reduce) {
  .lw-table-collapse .lw-row-more > summary > svg,
  .lw-table-collapse .lw-row-more > summary > .lw-icon { transition: none; }
}

/* PROMOTED from product.css in v1.3.0, verbatim and in source order. A contact
   form is not an app surface, so the field, the shared control face and the two
   label-wrapped controls belong in the layer nobody drops.

   Placed HERE — after the pills/cards block, before the console — so the
   consolidated pointer-affordance list at the FOOT of this file still comes last.
   That list names `.lw-switch`, `.lw-check`, `.lw-select` and `.lw-segmented
   button`, and a later `cursor: default` silently cancels an earlier pointer.

   Their `:is(.dark, …)`-scoped patches came too, at the FOOT of this file — see
   "The promoted controls on a dark ground". (This paragraph used to say the
   patches stayed behind, which was fb2d3ad's reasoning; v1.3.0 moved them and
   the comment was not updated with them. Position is not load-bearing;
   PRESENCE is.) */
/* ---- Forms -----------------------------------------------------------------
   The layer the system was missing entirely. A FIELD is the unit: label, the
   control, and one slot that holds either help text or an error — never both,
   because a field with both is a field whose error is easy to miss. */

.lw-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.lw-label { font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium); color: var(--lw-fg); line-height: 1.3; display: flex; align-items: center; gap: 6px; }
/* Required is marked on the LABEL and echoed in `required` on the control — a
   red asterisk alone is invisible to a screen reader. */
.lw-label .req { color: var(--lw-danger-on); font-weight: var(--lw-fw-regular); }
.lw-label .opt { color: var(--lw-fg-subtle); font-weight: var(--lw-fw-regular); font-size: var(--lw-text-xs); }
.lw-help { font-size: var(--lw-text-xs); line-height: 1.5; color: var(--lw-fg-subtle); text-wrap: pretty; }
.lw-error { font-size: var(--lw-text-xs); line-height: 1.5; color: var(--lw-danger-on); display: flex; align-items: flex-start; gap: 5px; }
.lw-error::before { content: ""; width: 5px; height: 5px; margin-top: 6px; border-radius: 50%; background: currentColor; flex: none; }

/* The shared control face. Input, textarea and select are one surface with one
   focus treatment, so a form never looks assembled from three libraries. */
.lw-input, .lw-textarea, .lw-select {
  width: 100%;
  font-family: inherit;
  font-size: var(--lw-text-sm);
  line-height: 1.4;
  color: var(--lw-fg);
  background: var(--lw-bg);
  border: 1px solid var(--lw-line-control);
  border-radius: var(--lw-radius-sm);
  transition: border-color var(--lw-dur-xs) var(--lw-ease-out), box-shadow var(--lw-dur-xs) var(--lw-ease-out), background var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-input, .lw-select { height: var(--lw-control-h-md); padding: 0 var(--lw-field-pad-x); }
.lw-textarea { padding: 10px var(--lw-field-pad-x); min-height: 96px; resize: vertical; }
/* Placeholders and every other informational use take fg-SUBTLE (4.83 on white),
   not fg-faint. fg-faint measures 2.54 on white — it is declared decorative in
   tokens.css and 14 rules used it as text anyway, which is the one AA hole the
   token pairs could not show, because the pair was never wrong: the usage was.
   fg-faint stays below on the DISABLED states, which WCAG exempts and which are
   supposed to read as unavailable. */
.lw-input::placeholder, .lw-textarea::placeholder { color: var(--lw-fg-subtle); }
.lw-input:hover:not(:disabled), .lw-textarea:hover:not(:disabled), .lw-select:hover:not(:disabled) { border-color: var(--lw-fg-faint); }
/* ANY focus takes the brand border; the RING is reserved for :focus-visible.
   Clicking a <select> (and a control focused programmatically) does not match
   :focus-visible in Chromium, so a mouse-focused control fell through to the UA
   outline — a ring in the browser's colour, not the brand's. Killing the outline
   without replacing the border is how a control ends up with NO focus signal, so
   the two always ship together. */
.lw-input:focus, .lw-textarea:focus, .lw-select:focus { outline: none; border-color: var(--lw-brand-500); }
/* [data-focus-demo] is a review hook: the states card cannot hold real focus in
   four cells at once, so it borrows THIS declaration rather than restating the
   values — a repainted focus ring in a specimen is a ring that drifts. */
.lw-input:focus-visible, .lw-textarea:focus-visible, .lw-select:focus-visible,
.lw-input[data-focus-demo], .lw-textarea[data-focus-demo], .lw-select[data-focus-demo],
.lw-input-group[data-focus-demo] { outline: none; border-color: var(--lw-brand-500); box-shadow: var(--lw-focus-ring); }

/* `background-COLOR`, never the shorthand. `background:` resets background-image
   too, and the one control in this group that HAS a background-image is the select
   — so a disabled select lost its chevron completely and, with `appearance: none`
   suppressing the platform arrow as well, rendered as a disabled TEXT INPUT. Two
   controls a user cannot tell apart, from a one-word difference in a declaration
   that was only ever meant to set a fill. `.lw-input` and `.lw-textarea` carry no
   image, so nothing else in the selector changes. */
.lw-input:disabled, .lw-textarea:disabled, .lw-select:disabled { background-color: var(--lw-bg-muted); color: var(--lw-fg-faint); cursor: not-allowed; border-color: var(--lw-line); }
/* Invalid is driven by aria-invalid, not a class: the attribute is what the
   screen reader reads, so binding the colour to it makes the two impossible to
   desync. The ring goes red too — a red border with a cyan focus ring reads as
   "fixed" the moment it is focused. */
.lw-input[aria-invalid="true"], .lw-textarea[aria-invalid="true"], .lw-select[aria-invalid="true"] { border-color: var(--lw-danger-on); }
.lw-input[aria-invalid="true"]:focus-visible, .lw-textarea[aria-invalid="true"]:focus-visible, .lw-select[aria-invalid="true"]:focus-visible { border-color: var(--lw-danger-on); box-shadow: var(--lw-focus-ring-danger); }
.lw-input-sm { height: var(--lw-control-h-sm); padding: 0 var(--lw-field-pad-x); font-size: var(--lw-text-xs); }
.lw-input-lg { height: var(--lw-control-h-lg); padding: 0 calc(var(--lw-field-pad-x) + 2px); font-size: var(--lw-text-body); }

/* Select needs its own chevron: the native arrow cannot be tokenised, and it is
   the one control whose default styling gives the platform away.

   THE CHEVRON IS 10px WIDE — two 5px halves — and `background-position` places a
   layer's LEFT edge. Written as `calc(100% - 12px)` the right half therefore ENDS
   7px from the edge, not 12: the arrow sat 5px closer to the border than the text
   sat to the opposite one, on every select in every consumer, and a padding value
   that reads 34px next to a position that reads 12px looks self-consistent right
   up until you measure the gap. The gutter and the two positions are now all
   derived from ONE token, so the asymmetry cannot come back and the arrow tracks
   [data-density] — which the hard-coded pair never did. */
.lw-select {
  appearance: none;
  /* Local to this rule, NOT a token: the chevron's width is the geometry of one
     decoration, not a role a consumer should be able to re-point. It exists as a
     variable only so the gutter and the two layer positions cannot drift apart. */
  --lw-select-chevron-w: 10px;
  /* PHYSICAL, and coupled: this padding reserves room for the chevron, which is
     painted by background-position below — and background-position has no
     logical form. Convert one without the other and RTL gets the gap on the
     start edge with the chevron still on the right. They move together or not at
     all; today they are a self-consistent physical pair. */
  padding-right: calc(var(--lw-field-pad-x) * 2 + var(--lw-select-chevron-w));
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - var(--lw-field-pad-x) - var(--lw-select-chevron-w)) center,
    calc(100% - var(--lw-field-pad-x) - var(--lw-select-chevron-w) / 2) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}

/* An input with an affix — a search glyph, a unit, a paste button. The BOX owns
   the border and the focus ring; the input inside is bare, so the ring wraps
   the whole control rather than a rectangle inside it. */
.lw-input-group { display: flex; align-items: center; gap: 8px; height: var(--lw-control-h-md); padding: 0 var(--lw-field-pad-x); background: var(--lw-bg); border: 1px solid var(--lw-line-control); border-radius: var(--lw-radius-sm); transition: border-color var(--lw-dur-xs) var(--lw-ease-out), box-shadow var(--lw-dur-xs) var(--lw-ease-out); }
.lw-input-group:focus-within { border-color: var(--lw-brand-500); box-shadow: var(--lw-focus-ring); }
/* The group owns the border, so it has to own the INVALID border too. Without this
   rule aria-invalid on the inner input announced an error that nothing showed —
   the one control whose "invalid is an attribute" contract silently did not hold,
   because the attribute sits on a child of the element that draws the frame.
   :has() keeps the attribute as the single source of truth: still no .error class. */
.lw-input-group:has(> input[aria-invalid="true"]) { border-color: var(--lw-danger-on); }
.lw-input-group:has(> input[aria-invalid="true"]):focus-within { border-color: var(--lw-danger-on); box-shadow: var(--lw-focus-ring-danger); }
/* The input STRETCHES to the group's height: with `align-items: center` and no height
   of its own it measured 21.7px inside a 44px frame, and a tap 6px from the frame's top
   focused nothing — half of every PasswordInput was dead on a phone. */
.lw-input-group > input { flex: 1; min-width: 0; align-self: stretch; height: auto; border: 0; background: none; font: inherit; font-size: var(--lw-text-sm); color: var(--lw-fg); }
.lw-input-group > input:focus { outline: none; }
.lw-input-group .affix { color: var(--lw-fg-subtle); font-size: 14px; display: inline-flex; align-items: center; flex: none; }
.lw-input-group .affix.mono { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); }

/* SWITCH — for a setting that applies immediately. A checkbox is for a value
   you submit; the two are not interchangeable and the shapes say which is which. */
.lw-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; }
.lw-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.lw-switch .track { position: relative; width: var(--lw-switch-w); height: var(--lw-switch-h); flex: none; border-radius: var(--lw-radius-pill); background: var(--lw-bg-inset); border: 1px solid var(--lw-line-control); transition: background var(--lw-dur-sm) var(--lw-ease-out), border-color var(--lw-dur-sm) var(--lw-ease-out); }
.lw-switch .track::after { content: ""; position: absolute; top: var(--lw-switch-inset); left: var(--lw-switch-inset); width: var(--lw-switch-thumb); height: var(--lw-switch-thumb); border-radius: 50%; background: var(--lw-bg); box-shadow: var(--lw-shadow-sm); transition: transform var(--lw-dur-sm) var(--lw-ease-out); }
.lw-switch input:checked + .track { background: var(--lw-brand-500); border-color: var(--lw-brand-500); }
.lw-switch input:checked + .track::after { transform: translateX(16px); }
.lw-switch input:focus-visible + .track,
.lw-switch input[data-focus-demo] + .track {
  outline: 2px solid var(--lw-brand-500); outline-offset: 2px; box-shadow: none;
}
.lw-switch input:disabled + .track { opacity: 0.5; cursor: not-allowed; }
.lw-switch .lw-switch-text { font-size: var(--lw-text-sm); color: var(--lw-fg); }
/* Radix-shaped switch (v2.0, the shadcn registry's switch.tsx): a `button[role=switch]`
   carrying `data-state`, no `<input>`, so the sibling selectors above never fire. Same
   track, same pseudo thumb, keyed off the attribute instead. `.thumb` is Radix's own
   Thumb element; the pseudo draws the thumb, so it stays hidden — one thumb, not two. */
.lw-switch[role="switch"] { appearance: none; border: 0; background: none; padding: 0; font: inherit; }
.lw-switch[role="switch"] .thumb { display: none; }
.lw-switch[data-state="checked"] .track { background: var(--lw-brand-500); border-color: var(--lw-brand-500); }
.lw-switch[data-state="checked"] .track::after { transform: translateX(16px); }
.lw-switch[role="switch"]:focus-visible { outline: none; }
.lw-switch[role="switch"]:focus-visible .track { outline: 2px solid var(--lw-brand-500); outline-offset: 2px; box-shadow: none; }
.lw-switch[role="switch"]:disabled .track { opacity: 0.5; cursor: not-allowed; }

/* CHECKBOX / RADIO — same 18px box, different radius. The check is a border
   trick rather than an SVG so it inherits ink and needs no asset.
   Note the class names: a design system cannot own a name as generic as
   `.lbl` — a consumer page will have one, and whichever loads last wins. */
.lw-check { display: inline-flex; align-items: flex-start; gap: 9px; cursor: pointer; font-size: var(--lw-text-sm); color: var(--lw-fg); line-height: 1.45; }
.lw-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.lw-check .box { position: relative; width: 18px; height: 18px; margin-top: 1px; flex: none; border: 1px solid var(--lw-line-control); border-radius: var(--lw-radius-xs); background: var(--lw-bg); transition: background var(--lw-dur-xs) var(--lw-ease-out), border-color var(--lw-dur-xs) var(--lw-ease-out); }
.lw-check.radio .box { border-radius: 50%; }
.lw-check input:checked + .box { background: var(--lw-brand-500); border-color: var(--lw-brand-500); }
.lw-check input:checked + .box::after { content: ""; position: absolute; left: 5px; top: 1px; width: 5px; height: 10px; border: solid var(--lw-on-brand); border-width: 0 2px 2px 0; transform: rotate(43deg); }
.lw-check.radio input:checked + .box::after { left: 4px; top: 4px; width: 8px; height: 8px; border: 0; border-radius: 50%; background: var(--lw-on-brand); transform: none; }
.lw-check input:focus-visible + .box,
.lw-check input[data-focus-demo] + .box {
  outline: 2px solid var(--lw-brand-500); outline-offset: 2px; box-shadow: none;
}
.lw-check input:disabled + .box { opacity: 0.5; }
.lw-check input:disabled ~ .lw-check-text { color: var(--lw-fg-faint); }

/* SEGMENTED — 2-4 mutually exclusive views. Same shell as .lw-theme-toggle. */
.lw-segmented { display: inline-flex; gap: 2px; padding: 3px; background: var(--lw-bg-inset); border: 1px solid var(--lw-line); border-radius: var(--lw-radius-md); }
.lw-segmented button { appearance: none; border: 0; background: none; font: inherit; font-size: var(--lw-text-xs); font-weight: var(--lw-fw-medium); color: var(--lw-fg-subtle); padding: 5px 12px; border-radius: var(--lw-radius-sm); cursor: pointer; white-space: nowrap; transition: background var(--lw-dur-xs) var(--lw-ease-out), color var(--lw-dur-xs) var(--lw-ease-out); }
.lw-segmented button:hover { color: var(--lw-fg); }
/* aria-CHECKED is the live one: v1.1.7 moved Segmented and ThemeToggle to a
   radiogroup, because aria-pressed describes N INDEPENDENT toggles and these are
   a single exclusive choice. aria-pressed and aria-selected stay in the selector
   for the vanilla consumers still authoring the old markup — the CSS layer is
   the contract both consumers render against, so it has to span the migration. */
.lw-segmented button[aria-checked="true"], .lw-segmented button[aria-pressed="true"], .lw-segmented button[aria-selected="true"] { background: var(--lw-bg); color: var(--lw-fg); box-shadow: var(--lw-shadow-xs); }
.lw-segmented button:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }

@media (pointer: coarse) {
  /* Control HEIGHTS are raised on the density tokens in tokens.css, not here: a
     height written on the control would outrank the token the touch block already
     lifted. Only what has no token left stays. */
  .lw-segmented button { padding: 10px 14px; min-height: var(--lw-control-h-sm); }
  .lw-tabs [role="tab"] { min-height: var(--lw-control-h-sm); } /* measured 43px: 1px under the floor */
  .lw-skip { min-height: var(--lw-control-h-sm); display: inline-flex; align-items: center; } /* 41px measured */
  /* REAL size, not the ::after overlay, for two controls the overlay cannot serve:
     the AppBar menu button sits in `.lw-appbar-lead`, whose `overflow: hidden`
     (needed so the brand truncates) CLIPS the pseudo-element to 36×28 with no
     symptom; and the compact locale toggle sits 16px from the account button,
     so two overlays overlap and the later one in the DOM takes the tap. */
  .lw-appbar-lead > .lw-icon-btn, .lw-locale-compact { width: var(--lw-control-h-sm); height: var(--lw-control-h-sm); }
  /* ^ the token is 44px under a coarse pointer; a 12.5px label with 10px padding is
     33px, and the first v2 consumer measured its language toggles at 29px. */
}

/* The one-button form of `ThemeToggle` (v1.3.4, `compact`). Shape and ink come
   from `.lw-icon-btn`; only what that class cannot supply is stated here.
   `position: relative` is REQUIRED and not decoration — `.lw-hit`'s 44px target
   is an absolutely positioned ::after, which anchors to the nearest positioned
   ancestor, so without this the touch target silently escapes to whatever box
   above it happens to be positioned. */
.lw-theme-compact { position: relative; }

/* ---- Console ("browser frame" motif) ------------------------------------- */

.lw-console {
  background: var(--lw-bg);
  border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-lg);
  overflow: hidden;
  box-shadow: var(--lw-shadow-lg);
  /* A flex or grid ITEM floors at its own min-content unless told otherwise, and
     a console's min-content is the widest thing anyone ever logged into it. Put
     one in a `.lw-cluster` — which is what the frame is for — and it stopped
     shrinking at 314px, so on a 320px phone it pushed past its ~280px column and
     the hero's `overflow: hidden` simply cut the last field off every line. That
     is the worst shape of layout bug: the frame looks intact, so nothing reads as
     broken, and the missing column is only visible to someone who knows what the
     log said. The zero floor lets the frame shrink and the grid inside it wrap. */
  min-inline-size: 0;
}
/* macOS window chrome: the traffic lights sit OUT of flow on the left so the
   address bar can centre on the frame, not on the space left over beside them. */
.lw-console-h {
  position: relative;
  display: flex;
  align-items: center;
  padding: 10px 14px;
  background: linear-gradient(180deg, var(--lw-bg), var(--lw-bg-muted));
  border-bottom: 1px solid var(--lw-line);
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-2xs);
  letter-spacing: 0.06em;
  color: var(--lw-fg-subtle);
}
.lw-console-h .left {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
}
.lw-console-h .lights { display: inline-flex; gap: 8px; }
/* macOS window controls, not brand — a token ramp here would make them wrong.
   They are still tokens (--lw-sys-mac-*, declared beside --lw-logo-cyan) so the
   no-raw-hex lint holds and the exception is declared where it is reviewable. */
.lw-console-h .lights i {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--lw-sys-mac-amber);
}
.lw-console-h .lights i:first-child { background: var(--lw-sys-mac-red); }
.lw-console-h .lights i:last-child { background: var(--lw-sys-mac-green); }
/* The address bar: centred on the frame at about half its width, URL centred
   inside it — Safari's toolbar, not a full-width pill. */
.lw-console-h .url {
  width: min(52%, 320px);
  margin: 0 auto;
  min-width: 0;
  justify-content: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: var(--lw-bg-inset);
  border: 1px solid var(--lw-line);
  padding: 4px 10px;
  border-radius: 7px;
  /* fg-subtle on bg-inset measured below AA; fg-muted is the secondary-text token
     and clears 4.5:1 on the inset surface in both themes. */
  color: var(--lw-fg-muted);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.lw-console-h .url::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--lw-success);
}
.lw-console-body { background: var(--lw-bg-subtle); }
.lw-console-foot {
  padding: 8px 14px;
  background: var(--lw-bg-subtle);
  border-top: 1px solid var(--lw-line);
  display: flex;
  justify-content: space-between;
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-2xs);
  color: var(--lw-fg-subtle);
  letter-spacing: 0.06em;
}

/* ---- Console: log variant (v0.3.0) ---------------------------------------
   A mono stream body for the browser-frame motif. Additive — the base
   .lw-console classes above are untouched. */

/* v0.4.0: the stream is styled on .lw-console-log — the element Console.jsx
   actually renders. It used to hang off `.lw-console.log`, a modifier nothing
   ever set, so every log console shipped with zero padding and its lines flush
   against the frame. `.log` is kept as an alias for hand-written markup. */
.lw-console-log,
.lw-console.log .lw-console-body {
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-xs);
  line-height: 1.6;
  padding: var(--lw-space-16);
  display: flex;
  flex-direction: column;
  gap: 7px;
}
/* v0.4.1: the gutter is a real grid column shared by every line (subgrid), so a
   prefix of ANY length — "$" or "09:14:02" — sizes it and nothing collides.
   It used to be a 1ch absolute box, which timestamps overflowed into the text. */
.lw-console-log {
  display: grid;
  /* Both tracks carry a ZERO floor, and neither is decoration.
     `max-content` alone lets the gutter demand its longest prefix whatever the
     console's width, and a bare `1fr` is `minmax(auto, 1fr)` — an `auto` minimum
     is the stream's MIN-CONTENT. Together they made the log's intrinsic minimum
     the sum of two unwrappable columns, so on a 320px phone the console sat
     314px wide inside a 280px container and its right edge was simply cut off by
     the hero's `overflow: hidden`. Nothing scrolled, nothing warned; the last
     column of every log line was gone. The floors let both tracks wrap instead —
     `.lw-console-line` already carries `word-break: break-word` for exactly that. */
  grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
  column-gap: var(--lw-space-8);
  row-gap: 7px;
  align-content: start;
}
.lw-console-line {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  color: var(--lw-fg-muted);
  white-space: pre-wrap;
  word-break: break-word;
}
/* A line with no prefix keeps the stream column, so it still lines up. */
.lw-console-line > *:only-child { grid-column: 2; }
/* Cells are real columns, so they must not carry the line's pre-wrap: a field
   that wraps mid-value defeats the alignment the grid just established. The
   last cell is the free-running one and is allowed to wrap. */
/* A free-running line in a columned log takes the whole stream, not column one.
   Squeezed into a single max-content field it would wrap to one word per line.
   Matched two ways on purpose: by class for current markup, and structurally for
   any build that predates the class — so the rule cannot arrive before the
   markup it styles. `.t` is excluded because the gutter IS column one. */
.lw-console-span,
.lw-console-line > span:not(.lw-console-cell):not(.t) { grid-column: 2 / -1; white-space: pre-wrap; min-width: 0; }
.lw-console-cell { white-space: nowrap; min-width: 0; }
/* --lw-bp-sm (479.98px). The alignment the `nowrap` above protects is worth
   less than the field itself: on a phone the columns cannot all hug, so a cell
   that refuses to wrap is a cell whose tail is clipped by the frame. Wrapping
   loses the tidy column edge; not wrapping loses the value. `anywhere` rather
   than `break-word` because these are identifiers and paths, which have no
   spaces to break at. */
@media (max-width: 479.98px) {
  .lw-console-cell { white-space: normal; overflow-wrap: anywhere; }
}
.lw-console-cell:last-child { white-space: pre-wrap; }
.lw-console-cell.num { text-align: end; font-variant-numeric: tabular-nums; }
/* A tone owns the whole line, so a muted cell must not out-rank it — muting is
   for the neutral stream only. */
.lw-console-cell.muted { color: var(--lw-fg-faint); }
.lw-console-line.success .lw-console-cell.muted,
.lw-console-line.warning .lw-console-cell.muted,
.lw-console-line.danger .lw-console-cell.muted { color: inherit; opacity: 0.72; }
.lw-console-line .t {
  white-space: pre;
  color: var(--lw-fg-subtle);
}
@supports not (grid-template-columns: subgrid) {
  .lw-console-line { display: flex; gap: var(--lw-space-8); }
  .lw-console-line .t { flex: 0 0 auto; }
  /* Without subgrid the columns cannot align across lines; the cells still read
     as separated fields rather than collapsing into one run. */
  .lw-console-cell:last-child { flex: 1; }
}
.lw-console-line.success { color: var(--lw-success-on); }
.lw-console-line.warning { color: var(--lw-warning-on); }
.lw-console-line.danger { color: var(--lw-danger-on); }

/* ---- Spacing utilities --------------------------------------------------- */

.lw-mt-2 { margin-top: var(--lw-space-8); }
.lw-mt-3 { margin-top: var(--lw-space-12); }
.lw-mt-4 { margin-top: var(--lw-space-16); }
.lw-mt-6 { margin-top: var(--lw-space-24); }
.lw-mt-8 { margin-top: var(--lw-space-32); }
.lw-measure { max-width: var(--lw-measure); }
.lw-measure-sm { max-width: var(--lw-measure-sm); }

/* FIGURE — a diagram or an image with a caption that is ABOUT it.
   `<figcaption>` is not a paragraph that happens to follow a picture; a screen
   reader announces it as the figure's caption, which is a different statement.

   ⚠ The margin reset is the load-bearing line. The UA gives `<figure>` a
   `margin: 1em 40px` — a **40px inline indent**, which is 25% of the content
   width on a 320px phone — and `reset.css` deliberately does not normalise it:
   its nine rules stop at `img`, and the presence gate forbids a bare element
   selector outside that file. So every consumer that reaches for a real
   `<figure>` inherits the indent silently, exactly the way `<a class="lw-btn">`
   inherited an underline before v1.2. A class, not an element rule, so a
   Tailwind consumer's preflight and this can both be right.

   The caption's own measure is wider than `.lw-measure`: it sits under a
   full-bleed drawing rather than in a column of prose, and 60ch under a 900px
   diagram reads as a stray sentence rather than as its caption. */
.lw-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--lw-space-16);
}
/* ⚠ `block-size: auto`, and it is not cosmetic. `reset.css` gives every image
   `max-width: 100%` but deliberately not a height rule, so an `<img>` carrying
   real `width`/`height` attributes — which it must, or the page reflows on load —
   narrows below its intrinsic width and keeps its attribute height. The picture
   stretches. `.lw-prose img` has always carried this; a figure outside prose had
   nowhere to get it. */
.lw-figure img {
  block-size: auto;
  max-inline-size: 100%;
}

.lw-figcaption {
  font-size: var(--lw-text-xs);
  line-height: 1.5;
  color: var(--lw-fg-subtle);
  text-wrap: pretty;
  max-inline-size: 76ch;
}

/* MEDIA PLATE (v2.3.0) — the mat under a raster whose own background is FIXED.

   A screenshot is pixels: its white ground does not re-point when the page
   theme does. Dropped straight onto a themed ground it is a hard slab with
   square corners, bleeding to the edge of the column — the single worst
   artifact a dark page can carry, and no gate here can see it, because an
   image is opaque to axe and to the contrast gate alike.

   ⚠ This is NOT `.lw-browser-frame`, and the difference is a claim, not a
   style. Chrome — a toolbar, dots, an address pill — asserts that you are
   looking at a whole application window. Around a CROP that is a small lie
   told by a decoration. The plate asserts nothing: it says only "this is a
   picture", the way a mount says it in a book. So there is no bar, no dots,
   no glow, and there is deliberately no variant that adds one.

   The surface is a ROLE (`--lw-bg-inset`), so it re-points with the theme and
   needs no `prefers-color-scheme` query — which would be wrong anyway wherever
   the theme is chosen by `[data-theme]` on `<html>` rather than by the OS. On
   light it sits one step below the page; on dark it sits one step ABOVE it
   (`--lw-navy-inset` is lighter than `--lw-navy-paper`), so the plate reads as
   lifted on both grounds rather than as a hole in one of them.

   The inner radius is what actually kills the slab: `border-radius` on the
   plate alone leaves the raster's own four square corners sitting inside it.
   The child rule is `>` and lists the media elements rather than using `*`,
   so a plate holding a caption row or a badge does not round it too.

     <figure class="lw-figure">
       <span class="lw-pill">Sample output</span>
       <div class="lw-media-plate"><img src="…" alt="…" width="990" height="235"></div>
       <figcaption class="lw-figcaption">…</figcaption>
     </figure>

   `--lw-media-plate-pad` is the one knob: a dense app surface can tighten it
   without re-authoring the rule. It defaults to the same 16px `.lw-figure`
   uses as its gap, so the mat and the rhythm around it are one number. */
.lw-media-plate {
  padding: var(--lw-media-plate-pad, var(--lw-space-16));
  background: var(--lw-bg-inset);
  border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-lg);
}
.lw-media-plate > img,
.lw-media-plate > svg,
.lw-media-plate > video,
.lw-media-plate > picture > img {
  display: block;
  block-size: auto;
  max-inline-size: 100%;
  margin-inline: auto;
  border-radius: var(--lw-radius-md);
}

/* The flexible gap in a row of controls — the thing that pushes what follows to
   the trailing edge. It was written six ways: inline in Artifact's footer, and
   as .lw-dgrid-selbar-spacer, .lw-pag-spacer, .lw-editor-spacer and a bare
   `.spacer` scoped under two different parents. Six homes for one declaration,
   and v3.0.0 removed the last of the component-scoped aliases — this is now the
   only spelling. Deliberately a slot rather than `margin-inline-start: auto` on
   the next child: the caller drops it into a run of actions without having to
   know which action happens to be first on the trailing side. */
.lw-spacer { flex: 1; }

/* =============================================================================
   Cross-product ergonomics, harvested from rag-service.
   These were solved once there and re-solved nowhere else; putting them in the
   package means every product inherits them.
   ============================================================================= */

/* ---- Motion utilities (v0.3.2) ---------------------------------------------
   Interaction affordances shared across products. All motion is gated behind
   prefers-reduced-motion: no-preference; the static state is complete. */

/* Hover lift + brand glow for cards. Opt-in class beside .lw-card etc. */
.lw-card-glow { transition: transform var(--lw-dur-xs) var(--lw-ease-out),
                            border-color var(--lw-dur-xs) var(--lw-ease-out),
                            box-shadow var(--lw-dur-xs) var(--lw-ease-out); }
.lw-card-glow:hover {
  border-color: hsl(var(--lw-brand-500-c) / 0.55);
  box-shadow: var(--lw-shadow-md), var(--lw-brand-glow);
}
@media (prefers-reduced-motion: no-preference) {
  .lw-card-glow:hover { transform: translateY(-3px); }
}

/* Row highlight for data tables. */
.lw-row-hover tbody tr { transition: background var(--lw-dur-xs) var(--lw-ease-out); }
.lw-row-hover tbody tr:hover td { background: var(--lw-bg-subtle); }

/* Grow a ≥44×44px tap target on small controls WITHOUT resizing them visually.
   Touch only — a mouse does not need it and it would eat hover areas. */
@media (pointer: coarse) {
  .lw-hit::after,
  /* v1.3.4. Everything below measured UNDER 44px on a real touch viewport and
   * is a control the package draws itself, so opting each consumer in by hand
   * would just be the same list written five times:
   *
   *   .lw-topbar .brand         111×24  — the home link, one target for mark+name
   *   .lw-topbar nav a           70×34  — visible on a touch iPad from --lw-bp-lg up
   *   .lw-segmented button       44×40  — 4px short after the coarse padding step
   *   .lw-pill-link             101×29
   *
   * The ::after and not real padding, and the distinction matters: every one of
   * these sits in a ROW, so the growth is vertical only and lands in space that
   * is already empty. Real padding would grow the 56px bar that three of them
   * live in. The opposite call is right for a stacked list — `.lw-footer-link`
   * takes real height in marketing.css, because 44px hit boxes on 33px rows
   * would overlap and each link would steal from its neighbour. Choose by
   * whether the control has a neighbour in the direction it needs to grow. */
  .lw-topbar .brand::after,
  .lw-navmenu > summary::after,
  .lw-topbar nav > a::after,
  .lw-segmented button::after,
  .lw-pill-link::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 100%; height: 100%;
    min-width: 44px; min-height: 44px;
  }
  /* The anchor the ::after resolves against. Without it the box escapes to
     whatever ancestor happens to be positioned and the target lands somewhere
     else on the page — silently, since nothing is painted. */
  /* ⚠️ `.lw-hit` ANCHORS ITSELF. Its 44px target is an absolutely positioned
     ::after, which resolves against the nearest POSITIONED ancestor — so without
     this the target silently escapes to whatever box above it happens to have a
     position, and grows to that box's size.

     The note beside `.lw-theme-compact` has said so since v1.3.4, and the fix
     was applied to the package's own five controls and never to the class
     itself. Every consumer that reached for `.lw-hit` therefore got the bug the
     comment describes: a `PasswordInput` reveal button inside a `.lw-card` took
     the WHOLE CARD as its tap target, and `elementFromPoint` at the centre of
     the Sign in button returned the reveal toggle — the sign-in button was
     untappable on every touch device. Found in leanwise-inspect's login.

     `.lw-hit` is opt-in, so establishing a containing block on it cannot move
     anything that was not already asking for one. */
  .lw-hit { position: relative; }
  .lw-topbar .brand, .lw-topbar nav > a, .lw-navmenu > summary, .lw-segmented button, .lw-pill-link { position: relative; }
  /* REAL width for a short nav label, because the ::after cannot supply it here.
     A locale switch is two 39px links side by side: grown to 44px each, their
     hit boxes OVERLAP, and the one on top takes the shared strip — measured at
     40px of usable width apiece, so neither reaches the floor and a tap near the
     seam opens the wrong language. Hit-tested, not assumed; a bounding box
     cannot show it, because the boxes are still 39px either way. Growth in the
     axis where a control has a neighbour has to be real, or it is a target two
     controls both claim. Only short labels are affected — the bar's own links
     are already wider than this. */
  .lw-topbar nav > a,
  .lw-navmenu > summary { min-inline-size: 44px; }
}

/* ---- Code block surface ---------------------------------------------------
   An always-dark mono surface (navy-deep) with syntax token spans. The spans
   use theme-invariant brand/cta/on-dark tokens, so they clear AA on the
   navy-deep ground regardless of the site theme — verified against the same
   navy-deep the dark hero sits on. No raw fills-as-text anywhere. */
.lw-code {
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-sm);
  line-height: var(--lw-lh-relaxed);
  tab-size: 2;
  background: var(--lw-navy-deep);
  color: var(--lw-on-dark-1);
  padding: var(--lw-space-16);
  border: 1px solid var(--lw-on-dark-line);
  border-radius: var(--lw-radius-md);
  overflow-x: auto;
  -webkit-font-smoothing: antialiased;
}
.lw-code ::selection { background: hsl(var(--lw-brand-500-c) / 0.35); color: var(--lw-on-dark); }
/* Token spans. comment = the muted tier (on-dark-3 ≈ 4.8:1 on navy-deep); the
   accent tiers (brand-300/400, cta-400) are well above AA on the dark ground. */
.lw-code .tok-comment      { color: var(--lw-on-dark-3); font-style: italic; }
.lw-code .tok-keyword      { color: var(--lw-brand-300); }
.lw-code .tok-string       { color: var(--lw-cta-400); }
.lw-code .tok-number       { color: var(--lw-brand-300); }
.lw-code .tok-function     { color: var(--lw-brand-400); }
.lw-code .tok-punctuation  { color: var(--lw-on-dark-2); }
.lw-code .tok-tag          { color: var(--lw-cta-400); }
.lw-code .tok-attr-name    { color: var(--lw-brand-400); }
.lw-code .tok-attr-value   { color: var(--lw-cta-400); }

/* ---- Disclosure (native <details>) -----------------------------------------
   The FAQ / "show the detail" pattern, complete with ZERO JavaScript. That is
   why it is <details>/<summary> and not a scripted accordion: the static state
   is the complete state, so the page opens and closes every row before
   hydration, with a failed bundle, or with JS off entirely. A scripted
   accordion on any of those pages is a list of dead headings.

   No height animation and no keyframe. <details> cannot animate its own height
   portably (the closed box has no computed height to interpolate from), and the
   motion policy here is a 100-200ms state change with no entrance
   choreography anyway — so only the chevron moves.

   The chevron is `<Icon name="chevron-down">` in the markup, never a CSS-drawn
   triangle: README rule 8, one drawing with one owner. The rule below rotates
   whatever glyph the summary carries; it does not draw one. */
.lw-disclosure { border-block-end: 1px solid var(--lw-line); }
.lw-disclosure > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--lw-space-16);
  padding-block: var(--lw-space-16);
  color: var(--lw-fg);
  font-weight: var(--lw-fw-medium);
  /* Both spellings are needed: `list-style: none` is the standard suppression
     and is what Firefox and Chromium read; WebKit still exposes the marker as a
     pseudo-element, and left alone it paints a second triangle beside the one
     the Icon draws. */
  list-style: none;
}
/* A flex child defaults to `min-width: auto`, so a long label with `truncate` does
   not truncate — it widens the summary, then the page (measured at 855px on a 768
   viewport in the first v2 consumer). Zero lets the label shrink and ellipsise. */
.lw-disclosure > summary > * { min-width: 0; }
.lw-disclosure > summary::-webkit-details-marker { display: none; }
.lw-disclosure > summary:focus-visible {
  outline: none;
  box-shadow: var(--lw-focus-ring);
  border-radius: var(--lw-radius-sm);
}
.lw-disclosure > summary > svg {
  flex: none;
  color: var(--lw-fg-subtle);
  transition: transform var(--lw-dur-sm) var(--lw-ease-out);
}
.lw-disclosure[open] > summary > svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .lw-disclosure > summary > svg { transition: none; }
}
.lw-disclosure-body {
  color: var(--lw-fg-muted);
  padding-block-end: var(--lw-space-16);
  line-height: var(--lw-lh-normal);
}

/* ---- Prose — the read surface for sanitized markdown HTML ------------------
   This is the one block in the package that styles DESCENDANTS of markup the
   system did not author: a blog post, a docs page, a release note — whatever
   the consumer's sanitizer let through. So it cannot assume which elements
   appear, and every rule has to be correct when the element is absent.

   It needs NO dark-ground rules, and that is the argument for role-over-tier
   stated as a specimen rather than as prose: every value below is a ROLE
   (--lw-fg, --lw-fg-muted, --lw-line, --lw-brand-text, --lw-bg-inset), so
   `.lw-band-dark` re-points the entire surface with nothing written for it.
   One --lw-text-2 in here and the article would freeze light-mode ink on navy
   — which is exactly the failure README rule 11 names.

   The measure lives on --lw-prose-max so a consumer can widen it for a docs
   column without overriding a rule; `.lw-prose-narrow` is the shipped second
   value, the same `--lw-measure-sm` as `.lw-measure-sm`. */
.lw-prose {
  max-inline-size: var(--lw-prose-max, var(--lw-measure-prose));
  color: var(--lw-fg);
  font-size: var(--lw-text-body);
  line-height: var(--lw-lh-relaxed);
}
.lw-prose-narrow { --lw-prose-max: var(--lw-measure-sm); }

/* Clear the UA's own block margins on the direct children FIRST, so the owl
   below is the only thing spacing this surface. Left in place they add to it,
   and they are em-based — so the gap between two blocks would change with the
   font size of whichever one happened to be larger. */
.lw-prose > * { margin-block: 0; }
/* The owl selector, deliberately: the spacing belongs to the boundary BETWEEN
   two blocks, not to either block, and prose has no wrapper to hang a flex gap
   on. It is also the only form that cannot put a leading margin on the first
   child or a trailing one on the last. */
.lw-prose > * + * { margin-block-start: var(--lw-space-20); }

/* h4 is body size + semibold. There is deliberately no --lw-text-h4 token: one
   rule does not justify a scale step, and a token nothing else reads is a
   token that will be misused by the next person who finds it. */
.lw-prose h4 { font-size: var(--lw-text-body); font-weight: var(--lw-fw-semibold); line-height: var(--lw-lh-snug); }
/* The scroll-margin is load-bearing, not polish: `.lw-topbar` is position:
   sticky, so a #fragment link lands the heading UNDER the bar without it, and
   the reader arrives at a section whose title is invisible.

   `--lw-anchor-offset` since v3.1.0, where this said `--lw-space-64` and three
   other files said three other numbers for the same clearance. The value is
   unchanged (56px bar + 8px gap = the 64px this always was); what changed is
   that an announcement bar now moves it here too, instead of needing the
   separate `:root:has(.lw-announce)` override marketing.css used to carry. */
.lw-prose :is(h2, h3, h4) {
  margin-block-start: var(--lw-space-40);
  scroll-margin-block-start: var(--lw-anchor-offset);
}

.lw-prose p { text-wrap: pretty; }
.lw-prose a {
  color: var(--lw-brand-text);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.lw-prose a:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs); }

.lw-prose :is(ul, ol) { padding-inline-start: var(--lw-space-24); }
.lw-prose li + li { margin-block-start: var(--lw-space-8); }
.lw-prose ul > li::marker { color: var(--lw-brand-text); }

.lw-prose blockquote {
  border-inline-start: 2px solid var(--lw-brand-line);
  padding-inline-start: var(--lw-space-16);
  color: var(--lw-fg-muted);
}

/* Same header geometry as `.lw-table th` — the v1.2 --lw-th-* tokens exist so
   the two cannot disagree — but the muted ink rather than the subtle tier,
   because a prose table has no tinted header band to sit on. */
.lw-prose table { width: 100%; border-collapse: collapse; }
.lw-prose th {
  text-align: start;
  font-family: var(--lw-font-mono);
  font-size: var(--lw-th-text);
  letter-spacing: var(--lw-th-tracking);
  word-spacing: var(--lw-word-spacing-caps);
  text-transform: uppercase;
  font-weight: var(--lw-fw-medium);
  color: var(--lw-fg-muted);
}
.lw-prose :is(th, td) {
  padding: var(--lw-space-8) var(--lw-space-12);
  border-block-end: 1px solid var(--lw-line);
}

.lw-prose :is(img, video, iframe) {
  display: block;
  max-inline-size: 100%;
  block-size: auto;
  border-radius: var(--lw-radius-lg);
  border: 1px solid var(--lw-line);
}
.lw-prose figcaption {
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-xs);
  color: var(--lw-fg-subtle);
}

.lw-prose hr {
  border: 0;
  border-block-start: 1px solid var(--lw-line);
  margin-block: var(--lw-space-40);
}

/* CODE — the highest-risk rule in this section, and it has already failed a
   gate once. Block code is `.lw-code` above: an ALWAYS-dark navy surface whose
   tok-* spans are tuned to it, and which the consumer emits itself. When this
   package last promoted a BARE `code` rule, it painted a light chip behind that
   dark surface and `check:a11y` failed the run (REVIEW.md, "the system caught
   its own regressions, twice"). So the block form gets SPACING and nothing
   else, and the chip is guarded to the inline case by `:not(pre) > code`. The
   guard is the rule — without it this is the same regression again. */
.lw-prose pre { margin-block: var(--lw-space-24); }
.lw-prose :not(pre) > code {
  font-family: var(--lw-font-mono);
  font-size: 0.9em;
  color: var(--lw-fg);
  background: var(--lw-bg-inset);
  padding: 1px 4px;
  border-radius: var(--lw-radius-xs);
}

/* The one highlight pair the system already ships — `.lw-story-result` paints
   the same two tokens, so a <mark> and a story result cannot drift. */
.lw-prose mark { background: var(--lw-cta-soft); color: var(--lw-cta-text); }

/* ---- Theme toggle ---------------------------------------------------------
   REMOVED in v1.0. It was a second segmented control with its own pill radius
   and its own selected-segment fill, which meant the same interaction looked
   like two different components depending on which file a dev happened to
   copy from. The one segmented control is .lw-segmented — in product.css until
   v1.3.0, in THIS file above since; ThemeToggle renders that. Two treatments of
   one control is not a style
   choice, it is a bug that takes a year to notice. */

/* ---- Tabs -----------------------------------------------------------------
   `.lw-code-tabs` was REMOVED in v1.0. A tab strip above a code panel is a tab
   strip; it did not need its own class, and having two meant the same
   interaction looked like two different components depending on which file a
   dev copied from. There is one `.lw-tabs` (roving tabindex in the React
   component) plus the six rules it needs on the dark code surface.

   PROMOTED from product.css in v1.3.1, verbatim. A category filter above an
   article index is the documented marketing recipe, and a tab strip is not an
   app surface — `Tabs` is in `components/nav/`, beside `TopBar`, which v1.3.0
   already moved here for the same reason.

   Placed HERE, not with the other promotions higher up, for two reasons that
   are both cascade facts rather than tidiness: `.lw-code` is declared ~250
   lines above, so the six dark-code-surface rules sit downstream of the surface
   they patch exactly as they did in product.css; and the consolidated
   pointer-affordance list at the FOOT of this file names `.lw-tabs [role="tab"]`,
   so it still comes last.

   v2.0: `Tabs` renders Radix Tabs (Root asChild merged into List, so the DOM
   is unchanged: div.lw-tabs[role="tablist"] > button[role="tab"]). Radix marks
   the selected trigger with BOTH `aria-selected="true"` and
   `data-state="active"`; the selected-tab rules match either, so a vanilla
   consumer that writes only the ARIA attribute still paints. */
.lw-tabs { display: flex; gap: var(--lw-space-24); border-bottom: 1px solid var(--lw-line); overflow-x: auto; scrollbar-width: none; }
.lw-tabs::-webkit-scrollbar { display: none; }
.lw-tabs [role="tab"] { appearance: none; border: 0; background: none; font: inherit; font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium); color: var(--lw-fg-subtle); padding: 10px 0; cursor: pointer; white-space: nowrap; border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color var(--lw-dur-xs) var(--lw-ease-out), border-color var(--lw-dur-xs) var(--lw-ease-out); }
.lw-tabs [role="tab"]:hover { color: var(--lw-fg); }
.lw-tabs [role="tab"]:is([aria-selected="true"], [data-state="active"]) { color: var(--lw-brand-text); border-bottom-color: var(--lw-brand-500); }
.lw-tabs [role="tab"]:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs); }
.lw-tabs [role="tab"] .count { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-subtle); margin-inline-start: 6px; }

/* .lw-tabs inside the dark code surface. This is why .lw-code-tabs existed as
   a separate class; six rules is cheaper than a second component. */
.lw-code .lw-tabs { padding: 0 var(--lw-space-16); border-bottom-color: var(--lw-on-dark-line); }
.lw-code .lw-tabs [role="tab"] { color: var(--lw-on-dark-3); padding: 10px 0; }
.lw-code .lw-tabs [role="tab"]:hover { color: var(--lw-on-dark-1); }
.lw-code .lw-tabs [role="tab"]:is([aria-selected="true"], [data-state="active"]) { color: var(--lw-brand-300); border-bottom-color: var(--lw-brand-400); }
.lw-code .lw-tabs [role="tab"]:focus-visible { box-shadow: 0 0 0 2px var(--lw-brand-400); }
.lw-code:has(.lw-tabs) { padding: 0; }
.lw-code:has(.lw-tabs) pre { margin: 0; padding: var(--lw-space-16); }

/* ---- Pagination -------------------------------------------------------------
   PROMOTED from product.css in v1.3.1, verbatim. The count is not decoration:
   "1–25 of 1,284" is the only thing that tells a user whether their filter did
   anything. Mono and tabular, so the numbers do not shuffle as the page changes.

   A blog index paginates. `Pagination` is named in the marketing article-index
   recipe alongside `Tabs` and `EmptyState`, and all three were in the layer that
   recipe tells you to drop.

   `.lw-pag-size` MUST stay downstream of the form controls, and that is the one
   real ordering hazard in this promotion: it is `.lw-pag-size { width: auto }`
   against `.lw-select { width: 100% }` — equal specificity, so source order
   alone decides. It won across the file boundary (product after base) and it
   still wins here, because the forms block is ~700 lines above. Move either one
   and the rows-per-page select silently grows to fill its row. */
.lw-pagination { display: flex; align-items: center; gap: var(--lw-space-8); flex-wrap: wrap; }
.lw-pagination .lw-pag-info { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); font-variant-numeric: tabular-nums; }
/* The rows-per-page select sizes to its options. Every other .lw-input fills its
   column; this one sits in a control row where a full-width field would push the
   page buttons off the end. */
.lw-pag-size { width: auto; }
.lw-pag-btn {
  min-width: var(--lw-control-h-sm); height: var(--lw-control-h-sm); padding: 0 var(--lw-space-8);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--lw-line); border-radius: var(--lw-radius-sm);
  background: var(--lw-bg); color: var(--lw-fg);
  font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); font-variant-numeric: tabular-nums;
  cursor: pointer; transition: background var(--lw-dur-xs) var(--lw-ease-out), border-color var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-pag-btn:hover { background: var(--lw-bg-subtle); }
.lw-pag-btn:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-pag-btn[aria-current="page"] { background: var(--lw-brand-500); border-color: var(--lw-brand-500); color: var(--lw-on-brand); }
.lw-pag-btn:disabled { color: var(--lw-fg-faint); cursor: not-allowed; background: var(--lw-bg-muted); }
.lw-pag-gap { padding: 0 2px; color: var(--lw-fg-faint); font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); }

/* ---- Status chip (Live / Pilot) -------------------------------------------
   A small status pill. Default is neutral; variants via data-variant. Uses the
   semantic -soft fill + -on text pair ONLY (never a raw fill behind text). */
.lw-status-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  font-family: var(--lw-font-mono);
  font-size: var(--lw-text-eyebrow);
  font-weight: var(--lw-fw-medium);
  letter-spacing: var(--lw-tracking-eyebrow);
  word-spacing: var(--lw-word-spacing-caps);
  text-transform: uppercase;
  border-radius: var(--lw-radius-pill);
  background: var(--lw-bg-inset);
  color: var(--lw-fg-muted);
}
.lw-status-chip::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.lw-status-chip[data-variant="live"] {
  background: var(--lw-success-soft);
  color: var(--lw-success-on);
}
.lw-status-chip[data-variant="pilot"] {
  background: var(--lw-warning-soft);
  color: var(--lw-warning-on);
}
.lw-status-chip[data-variant="error"] {
  background: var(--lw-danger-soft);
  color: var(--lw-danger-on);
}

/* PROMOTED from product.css in v1.3.0, verbatim. The site header is not an app
   surface — a marketing site needs one too — and the component was already half
   here: `.lw-topbar .brand-mark` sat in this file while everything else that made
   a top bar a top bar sat in the layer a marketing page is told to drop. The
   brand-mark rules follow immediately below, so the component is one place. */
/* ON THIS PAGE (promoted from product.css in v1.4.0) ------------------------
   Position is free here: every declaration is on `.lw-toc` / `.lw-toc a`, names
   nothing else in the package uses, so there is no equal-specificity competitor
   to sit before or after. It sits with the other promoted navigation — `.lw-tabs`
   and `.lw-pagination` came over in v1.3.1 — and above the pointer-affordance
   list, which must stay last in the file. */
/* ON THIS PAGE — the docs contents panel. A floating card rather than a bare
   column of links: the trail of sections is a peer of the content, not chrome
   welded to the page edge, and a border gives the sticky panel an edge to hold
   as prose scrolls under it. */
.lw-toc { display: flex; flex-direction: column; gap: 1px; padding: var(--lw-space-12); background: var(--lw-bg); border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg); }
.lw-toc .lw-toc-h { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.14em; word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase; color: var(--lw-fg-subtle); padding: 2px var(--lw-space-8) var(--lw-space-8); }
.lw-toc a { font-size: 12.5px; line-height: 1.4; text-decoration: none; padding: 6px var(--lw-space-8); border-radius: var(--lw-radius-sm); color: var(--lw-fg-muted); }
.lw-toc a:hover { color: var(--lw-fg); background: var(--lw-bg-subtle); }
.lw-toc a:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
/* Reading position is a STATE, so it hangs off aria-current — the same attribute
   that tells a screen reader where it is. One source for one fact, and no holed
   style property (a hole cannot paint until the logic class lands).

   NO transition on colour or ground here, deliberately. Scroll-spy re-targets
   this row on every scroll step, so an easing curve on the highlight is mush
   rather than polish — and a transitioned property reports its START value to
   getComputedStyle until frames advance, which makes the highlight untestable
   and cost this panel several rounds of misdiagnosis. Hover keeps its snap. */
.lw-toc a[aria-current] { color: var(--lw-brand-on); font-weight: var(--lw-fw-medium); background: var(--lw-brand-soft); }

/* STICKY, and only in a two-column layout (v3.1.0). Every consumer that has
   shipped a contents rail has written this block locally, and `leanwise-ai`
   wrote it twice — once wrongly.

   ⚠ The gate is not optional, and the comment that used to sit in the consumer
   argued the opposite: "below that width the rail's grid area is exactly its
   own height, so a sticky box with nowhere to travel never moves." That is
   wrong. A sticky box travels within its CONTAINING BLOCK, which is the
   `.lw-split` grid container, not the rail's own cell. Once the split collapses
   to one column the container spans every section on the page, so the rail
   pinned itself and painted a 350x329 panel over the page content for the whole
   scroll at 390px. No gate saw it: the rail is inside the viewport, the page
   still fits, and axe has no rule about one box covering another.

   1024px is `--lw-bp-lg` spelled literally, because @media cannot read a custom
   property — the same five numbers every hand-written query in these layers
   uses. It matches `.lw-split`'s own collapse point by construction.

   The height cap earns its place on a short viewport: a rail taller than the
   screen would otherwise pin its own overflow out of reach. `dvh`, not `vh`,
   for the mobile toolbar. */
@media (min-width: 1024px) {
  .lw-toc-sticky {
    position: sticky;
    inset-block-start: calc(var(--lw-anchor-offset) + var(--lw-space-16));
    max-block-size: calc(100dvh - var(--lw-anchor-offset) - var(--lw-space-32));
    overflow-y: auto;
  }
}

/* TOP BAR — the app chrome. Sticky, hairline, never a shadow: a shadow under a
   1px bar reads as a rendering bug on a light ground. */
.lw-topbar { position: sticky; top: 0; z-index: var(--lw-z-nav); display: flex; align-items: center; gap: var(--lw-space-16); height: var(--lw-topbar-h); padding: 0 var(--lw-space-20); background: hsl(var(--lw-bg-c) / 0.86); backdrop-filter: blur(12px) saturate(1.4); -webkit-backdrop-filter: blur(12px) saturate(1.4); border-bottom: 1px solid var(--lw-line); }
.lw-topbar .brand { display: flex; align-items: center; gap: 9px; font-weight: var(--lw-fw-semibold); font-size: var(--lw-text-sm); letter-spacing: -0.012em; color: var(--lw-fg); text-decoration: none; flex: none; }
.lw-topbar .spacer { flex: 1; }
/* As a link the brand is one target covering mark and name. Opacity, not a
   colour change: the name is already the brand colour, so darkening it on hover
   would read as a different brand rather than a hover state. */
a.lw-topbar .brand, .lw-topbar a.brand { cursor: pointer; transition: opacity var(--lw-dur-sm) var(--lw-ease-out); }
.lw-topbar a.brand:hover { opacity: 0.72; }
.lw-topbar a.brand:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-sm); }
@media (prefers-reduced-motion: reduce) { .lw-topbar a.brand { transition: none; } }
/* The name beside the mark is brand-coloured; the plain text brand stays fg so
   app chrome does not shout. Scoped with :has so it needs no extra element. */
.lw-topbar .brand:has(.brand-mark) { color: var(--lw-brand-text); }
/* `nav-center` centres the nav on the BAR, not in the space left between brand
   and actions — absolute so its position never shifts when either side changes
   width. Reverts to flow on narrow bars, where a centred nav would collide. */
.lw-topbar.nav-center nav { position: absolute; left: 50%; transform: translateX(-50%); }
/* --lw-bp-lg (1023.98px), RAISED from md in v1.3.4 — see the collapse note below
   the nav rules. The old comment measured a bar carrying a nav and nothing else;
   the bar every consumer actually ships also carries a control cluster and a CTA,
   and those live on the same row. */
@media (max-width: 1023.98px) { .lw-topbar.nav-center nav { position: static; left: auto; transform: none; } }
.lw-topbar nav { display: flex; align-items: center; gap: var(--lw-space-4); }
.lw-topbar nav > a { font-size: var(--lw-text-sm); color: var(--lw-fg-muted); text-decoration: none; padding: 6px 10px; border-radius: var(--lw-radius-sm); transition: color var(--lw-dur-xs) var(--lw-ease-out), background var(--lw-dur-xs) var(--lw-ease-out); }
.lw-topbar nav > a:hover { color: var(--lw-fg); background: var(--lw-bg-subtle); }
.lw-topbar nav > a[aria-current="page"] { color: var(--lw-fg); font-weight: var(--lw-fw-medium); }
.lw-topbar nav > a:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }

/* ---- Nav dropdown (v1.5.0) -------------------------------------------------
   A taxonomy panel hanging off one item in the bar's own nav. Native <details>,
   so it opens with JavaScript disabled and the component stays server-safe.

   ⚠ `position: absolute`, never `fixed`. `.lw-topbar` carries `backdrop-filter`,
   which makes it a containing block for `position: fixed` DESCENDANTS — a fixed
   panel would resolve `inset` against the 56px bar and paint inside it. That bug
   has shipped from this package once; the same note is on `.lw-topbar-panel`.

   Start-anchored to its TRIGGER rather than spanning the bar: a bar-wide mega
   panel is ~900px of surface for a five-item taxonomy, and it cannot be clamped
   against the viewport edge in pure CSS. The trade is that the dropdown item has
   to come first in `links`, which the .d.ts says. */
.lw-navmenu { position: relative; }
.lw-navmenu > summary {
  display: flex; align-items: center; gap: var(--lw-space-4);
  font-size: var(--lw-text-sm); color: var(--lw-fg-muted);
  padding: 6px 10px; border-radius: var(--lw-radius-sm);
  list-style: none; white-space: nowrap;
  transition: color var(--lw-dur-xs) var(--lw-ease-out), background var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-navmenu > summary::-webkit-details-marker { display: none; }
.lw-navmenu > summary:hover { color: var(--lw-fg); background: var(--lw-bg-subtle); }
.lw-navmenu > summary:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
/* The chevron is drawn, not typed: a glyph would be read aloud as punctuation. */
.lw-navmenu-chevron {
  inline-size: 8px; block-size: 8px; flex: none;
  border-inline-end: 1.5px solid currentColor; border-block-end: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-navmenu[open] > summary { color: var(--lw-fg); }
.lw-navmenu[open] .lw-navmenu-chevron { transform: translateY(1px) rotate(225deg); }

/* OPAQUE. The page ground's layers sit at z-index -2/-1, so a translucent panel
   would show the honeycomb through the taxonomy. */
.lw-navmenu-panel {
  position: absolute; inset-block-start: calc(100% + var(--lw-space-8)); inset-inline-start: 0;
  z-index: var(--lw-z-local-1);
  display: flex; flex-direction: column; gap: var(--lw-space-16);
  min-inline-size: 320px; max-inline-size: min(92vw, 560px);
  padding: var(--lw-space-16);
  background: var(--lw-bg);
  border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg);
  box-shadow: var(--lw-shadow-md);
}
.lw-navmenu-group { display: flex; flex-direction: column; gap: 2px; }
.lw-navmenu-group-h {
  font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.14em;
  word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase;
  color: var(--lw-fg-subtle); padding: 0 var(--lw-space-8) var(--lw-space-4);
}
.lw-navmenu-item {
  display: flex; flex-direction: column; gap: 1px;
  padding: var(--lw-space-8); border-radius: var(--lw-radius-sm);
  text-decoration: none; color: var(--lw-fg);
  transition: background var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-navmenu-item:hover { background: var(--lw-bg-subtle); }
.lw-navmenu-item:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-navmenu-item .t { display: flex; align-items: center; gap: var(--lw-space-8); font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium); }
.lw-navmenu-item .d { font-size: var(--lw-text-xs); color: var(--lw-fg-muted); line-height: 1.45; }
.lw-navmenu-status {
  font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--lw-fg-subtle); font-weight: var(--lw-fw-regular);
}

/* Inside the narrow disclosure panel the same component runs IN FLOW — one
   interaction responding to width, not two treatments of one interaction. */
@media (max-width: 1023.98px) {
  .lw-topbar-panel .lw-navmenu-panel {
    position: static; min-inline-size: 0; max-inline-size: none;
    padding-inline: 0; border: 0; box-shadow: none; background: none;
  }
}
/* --lw-bp-lg (1023.98px) — the links drop; the app is expected to supply its own
   narrow-bar nav, because a horizontally scrolling topbar hides its own tail.

   ── Why this moved from md (768px) to lg in v1.3.4 ────────────────────────────
   The md figure was measured against a bar that carried a nav and nothing else.
   No consumer ships that bar. The real one carries brand + nav + a control
   cluster + a CTA, all on one 56px row that does not wrap and whose children do
   not shrink, so its width is the SUM of its parts — and the sum on the flagship
   marketing site is 933px. Between 769px and 932px the bar therefore overflowed
   the viewport and, because `.lw-topbar` is not a scroll container, dragged the
   whole DOCUMENT sideways: every page on the site scrolled horizontally on an
   iPad in portrait. Measured at 820px (iPad Air), not inferred.

   The failure is silent in the one direction that matters. A too-wide bar does
   not clip, wrap or warn — the tail simply sits past the fold, the page gains
   ~113px of scroll width, and each page still screenshots correctly at whatever
   fixed viewport a visual gate happens to shoot. Nothing here can see it.

   1023.98 rather than 1023: `max-width: 1023px` and `min-width: 1024px` leave a
   sub-pixel hole on a fractional-DPR viewport where NEITHER matches, and the bar
   then has no navigation at all. The two halves must meet, not merely adjoin. */
@media (max-width: 1023.98px) { .lw-topbar nav { display: none; } }

/* The two halves of the bar's WIDTH contract, for anything that is not the nav.
 *
 * The bar's actions are the other half of the width problem the note above
 * measures, and the package cannot rank them: on a marketing site the CTA is the
 * conversion moment and must survive every width, while a theme picker is a
 * setting that can afford to shrink. So the package supplies the mechanism and
 * the app supplies the ranking.
 *
 *   .lw-topbar-collapse  — present only on a WIDE bar. The roomy affordance.
 *   .lw-topbar-narrow    — present only on a NARROW bar. Its compact twin.
 *
 * Render both and let the breakpoint choose: a three-segment `ThemeToggle` in the
 * bar beside a one-button `<ThemeToggle compact>`, or a full control beside a
 * link into `NavToggle`'s panel. The pair is stated as two rules rather than one,
 * so a consumer cannot get half of it.
 *
 * `display: none` and not `visibility: hidden`: a hidden-but-laid-out action
 * still occupies its width, which is the entire defect this exists to remove.
 * It is fully out of the accessibility tree at the width where it is not the
 * live copy — which is the point. Two of the same control both reachable, one of
 * them invisible, is a worse outcome than one.
 *
 * Not scoped to `.lw-topbar`, deliberately: the twin frequently lives in the
 * PANEL, which is a sibling subtree, and a consumer should not have to know
 * which of the two selectors applies where. */
@media (max-width: 1023.98px) { .lw-topbar-collapse { display: none; } }
@media (min-width: 1024px) { .lw-topbar-narrow { display: none; } }

/* A phone bar is tight by nature, and every pixel here is one the CTA does not
   have. Measured at 320px — the narrowest width worth supporting: brand (111) +
   a compact theme button (28) + the CTA (94) + the nav toggle (28) is 261px of
   content, which clears 320 only once the gaps and the inline padding come down.
   At the shipped 16/20 it needs 349px and the bar drags the document sideways.
   --lw-bp-sm (479.98px). */
@media (max-width: 479.98px) {
  .lw-topbar { gap: var(--lw-space-8); padding-inline: var(--lw-space-12); }
}

/* Below 360px the mark stands alone.
 *
 * The tightening above buys 30px and the narrowest real phone still needs more:
 * at 320px a Vietnamese CTA (longer than its English twin — a translated bar is
 * not the same width as the one it was designed on) put the row 7px past the
 * viewport, which is a full-page sideways scroll for seven pixels nobody can
 * see. The wordmark is the only remaining item big enough to matter and the only
 * one that loses nothing when it goes: the mark is still the home link, the link
 * still carries "<brand> — home" as its accessible name, and both survive.
 *
 * 359.98px rather than a token: --lw-bp-sm is 480px, and applying this there
 * would strip the wordmark from every phone in ordinary use. 360 is the floor of
 * the common Android viewport, so this reaches only the devices that genuinely
 * cannot fit it. A literal with a measured reason beats a token with the wrong
 * value. */
@media (max-width: 359.98px) {
  .lw-topbar .brand-name { display: none; }
}

/* ---- Brand mark in the top bar (v0.6.3) -----------------------------------
   The MARK only, beside the name as live text. The combined lockup
   (assets/logo-lockup.svg) is 2775:1000 — fit to a 56px bar it puts the wordmark
   caps near 6px, so v0.6.2's lockup-as-one-image was unreadable however it was
   sized. Splitting them lets the name set in the DS font at the DS size.

   Two artwork files swapped by background-image, the pattern this package uses
   for hex-lattice/hex-lattice-ink and hero-mark/hero-mark-ink: the mark's navy
   end vanishes on a dark bar, so the dark ground takes the lifted file. Not a
   CSS mask — mask alpha is dropped in capture and export paths, which is how
   v0.6.1 shipped the brand as a solid gradient block. */
.lw-topbar .brand-mark, .brand-mark {
  width: auto;
  height: 24px;
  aspect-ratio: 871.1 / 1000;
  flex: none;
  background: url("assets/logo-mark.svg") no-repeat center / contain;
}
.dark .lw-topbar .brand-mark,
[data-band="dark"] .lw-topbar .brand-mark,
.lw-band-dark .lw-topbar .brand-mark,
.dark .brand-mark,
[data-band="dark"] .brand-mark,
.lw-band-dark .brand-mark {
  background-image: url("assets/hero-mark.svg");
}

/* ---- Mobile nav disclosure (v1.3.0) ----------------------------------------
   Below --lw-bp-md the bar's `nav` is display:none (rule above), on the stated
   grounds that "the app is expected to supply its own narrow-bar nav". Every
   consumer then supplied one, and the marketing site's was broken. This is that
   nav, shipped once.

   It is deliberately NOT `Drawer`, and the distinction is a real one rather
   than a naming preference. `Drawer` is a modal <dialog> promoted to the TOP
   LAYER: it makes the rest of the page inert, so it owns a focus trap, a scrim,
   a return-focus contract and an Escape handler it cannot delegate. A nav
   disclosure hangs UNDER the bar, leaves the page visible and interactive, and
   needs none of that. Two different interactions, so README rule 9 — one
   interaction must not have two treatments — is not in play here.

   This is also what removes the last reason a marketing site would ever import
   product.css: after fb2d3ad the header was the only app-shaped thing left on a
   marketing page, and the toggle was the missing half of it. */

/* Scoped under `.lw-topbar`, and that is LOAD-BEARING rather than tidy: the
   toggle composes `.lw-icon-btn`, whose `display: inline-flex` still lives in
   product.css. Equal specificity, loading after this file — so an unscoped
   `.lw-topbar-toggle { display: none }` would lose, and the toggle would show
   on every desktop app page. The descendant selector outranks it both ways.
   The ink and the ring are `.lw-icon-btn`'s own values, restated only because
   that class has not been promoted yet (REVIEW.md, open item). */
.lw-topbar .lw-topbar-toggle {
  display: none;
  /* `.lw-hit` grows the 44px coarse-pointer target with an absolutely
     positioned ::after, which needs a positioned host to anchor to. */
  position: relative;
  flex: none;
  color: var(--lw-fg-subtle);
}
.lw-topbar .lw-topbar-toggle:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
/* --lw-bp-lg (1023.98px) — the same breakpoint at which `.lw-topbar nav` drops,
   and it has to be the same one: the toggle must appear in the frame the links
   disappear in, or the bar has no navigation at all at some width. Raised from
   md with the nav in v1.3.4; changing one of these four queries without the
   other three is how a bar ends up with neither nav nor toggle. */
@media (max-width: 1023.98px) {
  .lw-topbar .lw-topbar-toggle { display: inline-flex; align-items: center; justify-content: center; }
}

/* The panel hangs off the bar itself. `.lw-topbar` is position: sticky, which
   is already a positioned ancestor, so `inset-block-start: 100%` is the
   underside of the bar and `inset-inline: 0` is its width — no fixed
   positioning, and therefore no containing-block trap. That trap is worth
   naming, because the consumer's hand-rolled version shipped broken on it: the
   bar carries `backdrop-filter`, which makes it a containing block for
   `position: fixed` DESCENDANTS, so a fixed overlay nested inside resolved
   `inset: 0` against the 56px bar instead of the viewport and painted its
   background in that strip only. An absolute panel under a sticky bar is the
   arrangement that has no such failure — but if this is ever reworked to
   `position: fixed`, the panel has to become a SIBLING of the <header>. */
.lw-topbar-panel {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 100%;
  z-index: var(--lw-z-local-1);
  display: flex;
  flex-direction: column;
  gap: var(--lw-space-4);
  background: var(--lw-bg);
  color: var(--lw-fg);
  border-block-end: 1px solid var(--lw-line);
  box-shadow: var(--lw-shadow-lg);
  /* 56px is the bar's own height, declared as a literal on `.lw-topbar` above;
     dvh rather than vh so the panel is not overlapped by a mobile URL bar. */
  max-block-size: calc(100dvh - 56px);
  overflow: auto;
  padding: var(--lw-space-16);
  padding-block-end: calc(var(--lw-space-16) + var(--lw-safe-bottom));
}
/* MANDATORY, and it fails SILENTLY without it: the `display: flex` above is an
   author rule, so it outranks the UA's `[hidden] { display: none }` and the
   panel stays painted while its own attribute says it is hidden. Every
   `display:`-carrying element that is toggled with `hidden` needs this line. */
.lw-topbar-panel[hidden] { display: none; }
/* The panel's own actions row — where a `.lw-topbar-collapse` twin goes.
 *
 * Separated from the links by a hairline rather than by space alone: the rows
 * above are destinations and the controls below are settings, and on a panel
 * that is one flat column nothing else distinguishes them. */
.lw-topbar-panel-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--lw-space-12);
  margin-block-start: var(--lw-space-12);
  padding-block-start: var(--lw-space-12);
  border-block-start: 1px solid var(--lw-line);
}
/* The bar's own <nav> is display:none below md, and the panel sits inside the
   bar — so the panel's nav has to opt back in. Two selector levels rather than
   one: at equal specificity this would depend on source order, which is not a
   thing a consumer should have to know. */
@media (max-width: 1023.98px) {
  .lw-topbar .lw-topbar-panel nav { display: flex; flex-direction: column; gap: var(--lw-space-4); }
  /* A 12px block pad around a ~20px line is a 44px row — the coarse-pointer
     minimum, reached with padding rather than a height so a wrapped label
     grows the row instead of overflowing it. */
  .lw-topbar .lw-topbar-panel nav a { padding-block: var(--lw-space-12); }
}
@media (prefers-reduced-motion: no-preference) {
  .lw-topbar-panel { animation: lwNavPanelIn var(--lw-dur-sm) var(--lw-ease-out); }
}
/* Prefixed `lw`, and verified unused before it was added: a keyframe name is
   GLOBAL and last-wins, and no gate can see a collision (CLAUDE.md — `lwPulse`
   was defined twice and product.css quietly won for every consumer). */
@keyframes lwNavPanelIn {
  from { opacity: 0; transform: translateY(-4px); }
}

/* ---- The promoted controls on a dark ground (v1.3.0) -----------------------
   fb2d3ad moved the form/control face into this file and LEFT these patches in
   product.css, on the correct observation that they outrank the promoted rules
   on specificity, so their POSITION is not load-bearing. What that reasoning
   missed is the case the promotion exists for: a page loading base + marketing
   never sees product.css at all, so it got the controls with no dark-band
   treatment — and the consumer site puts its contact form on a dark ground.
   Position is not load-bearing; PRESENCE is.

   Three selectors, as everywhere: `.dark` (Tailwind), `[data-band="dark"]` (a
   dark band inside a light page), `.lw-band-dark` (the explicit opt-in). Only
   the PROMOTED controls moved. The patches for `.lw-prompt`, `[data-focus-demo]`
   and the table/kpi/empty/eyebrow family are app surfaces or were never
   promoted, and stay in product.css — see its tombstone. */

:is(.dark, [data-band="dark"], .lw-band-dark) .lw-input,
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-textarea,
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-select,
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-input-group { background-color: var(--lw-bg-subtle); }
/* background-COLOR, never the shorthand: the shorthand resets background-image
   and would erase the two gradients that draw .lw-select's chevron, leaving the
   select on dark indistinguishable from a text input. */

:is(.dark, [data-band="dark"], .lw-band-dark) .lw-input:focus-visible,
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-textarea:focus-visible,
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-select:focus-visible,
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-input-group:focus-within { border-color: var(--lw-brand-400); box-shadow: var(--lw-focus-ring); }

/* The switch knob is one of the two places a lifted ink is correct on both
   grounds — it sits on the brand fill, whose ink never flips. */
.dark .lw-switch .track::after, [data-band="dark"] .lw-switch .track::after, .lw-band-dark .lw-switch .track::after { background: var(--lw-on-dark-1); }

/* Segmented is the only control whose shell is an inset TRACK rather than a
   field, so the input patch above misses it — on a dark band it kept a light
   grey track and a white active pill, the one control that stayed light on
   navy. On light the selected segment is LIGHTER than its track (raised out of
   a well); dark keeps that same figure/ground rather than inverting it, so the
   control reads identically in both themes. Painted from the --lw-on-dark-*
   family, not raw rgba — that family exists to stop exactly that sprawl. */
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-segmented { background: var(--lw-on-dark-fill); border-color: var(--lw-on-dark-line); }
.dark .lw-segmented button, [data-band="dark"] .lw-segmented button, .lw-band-dark .lw-segmented button { color: var(--lw-on-dark-2); }
.dark .lw-segmented button:hover, [data-band="dark"] .lw-segmented button:hover, .lw-band-dark .lw-segmented button:hover { color: var(--lw-on-dark-1); }
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-segmented button:is([aria-checked="true"], [aria-pressed="true"], [aria-selected="true"]) { background: var(--lw-on-dark-fill-strong); color: var(--lw-on-dark); box-shadow: inset 0 0 0 1px var(--lw-on-dark-line); }

/* The two offset-ring controls take the tier swap the focus ring takes
   everywhere on dark: the 500 ring disappears into the navy. */
.dark .lw-switch input:focus-visible + .track, [data-band="dark"] .lw-switch input:focus-visible + .track, .lw-band-dark .lw-switch input:focus-visible + .track,
.dark .lw-switch input[data-focus-demo] + .track, [data-band="dark"] .lw-switch input[data-focus-demo] + .track, .lw-band-dark .lw-switch input[data-focus-demo] + .track,
.dark .lw-check input:focus-visible + .box, [data-band="dark"] .lw-check input:focus-visible + .box, .lw-band-dark .lw-check input:focus-visible + .box,
.dark .lw-check input[data-focus-demo] + .box, [data-band="dark"] .lw-check input[data-focus-demo] + .box, .lw-band-dark .lw-check input[data-focus-demo] + .box { outline-color: var(--lw-brand-400); }
.dark .lw-switch[role="switch"]:focus-visible .track, [data-band="dark"] .lw-switch[role="switch"]:focus-visible .track, .lw-band-dark .lw-switch[role="switch"]:focus-visible .track { outline-color: var(--lw-brand-400); }

/* The knob's travel is the promoted control's only motion, so its reduced-motion
   stand-down comes with it. The rest of that rule (`.lw-sidebar`,
   `.lw-confidence .rail > i`) is app surface and stayed behind. */
@media (prefers-reduced-motion: reduce) {
  .lw-switch .track::after { transition: none; }
}

/* ---- Pointer affordance ---------------------------------------------------
   Anything that responds to a click must say so. `a[href]` gets the pointer from
   the UA; label-wrapped controls, <select> and sortable headers do not, and
   without this they read as text while behaving as controls.

   ⚠️ AND NEITHER DOES `<button>`. Every UA sheet — Chromium, Gecko, WebKit — gives
   a button `cursor: default`; only the link is special-cased. This block asserted
   the opposite for eleven releases, which is why nobody looked: the comment WAS
   the audit. So `.lw-btn` — the most-clicked class in the package, in all seven
   consumers — read as inert text under the mouse, and it is the one affordance no
   screenshot gate can see, because a cursor is not in the screenshot. `:disabled`
   is (0,2,0) against this rule's (0,1,0), so the not-allowed state above still
   wins; the button family's own `pointer-events: none` decides what a pointer
   over a disabled button shows. */
.lw-btn, .lw-switch, .lw-check, .lw-select, .lw-switch input, .lw-check input { cursor: pointer; }
.lw-switch:has(input:disabled), .lw-check:has(input:disabled),
.lw-select:disabled, .lw-switch input:disabled, .lw-check input:disabled { cursor: not-allowed; }
.lw-table th[aria-sort] { cursor: pointer; }
/* Numerics are a column, not prose: a wrapped "0.60 – 0.79" reads as two rows. */
.lw-table th.num, .lw-table td.num { white-space: nowrap; }
.lw-chip[role="button"]:focus-visible, .lw-pill[role="button"]:focus-visible,
.lw-chip[tabindex]:focus-visible, .lw-pill[tabindex]:focus-visible {
  outline: none; box-shadow: var(--lw-focus-ring);
}

.lw-tabs [role="tab"], .lw-segmented button, .lw-nav-item, .lw-chip[role="button"],
.lw-pill[role="button"], .lw-code-tabs button,
/* `.lw-source-chip` was a name this system never shipped, so that selector matched
   nothing for two releases. The citation atom is `.lw-source`. Everything listed
   here is a link or a button that a pointer can activate; anything NOT clickable
   is deliberately absent, because a pointer over inert text promises a click that
   never happens. */
/* `<summary>` is the one control the UA gives a TEXT cursor: it is not a button
   and not a link, so nothing in the UA sheet makes it read as clickable. It
   belongs on this list rather than carrying a local `cursor: pointer`, because
   a later `cursor: default` on a base class silently cancels an earlier one and
   this list is what stays last in the file. */
.lw-disclosure > summary,
.lw-navmenu > summary,
/* The collapsed row's disclosure. Same reason as the two above: a <summary> is
   neither a button nor a link, so nothing in the UA sheet makes it read as
   clickable, and this list is what stays last in the file. */
.lw-row-more > summary,
.lw-source, .lw-source-item, .lw-toc a, .lw-crumbs a, .lw-pill-link, .lw-icon-btn, .lw-dialog-close,
.lw-card-interactive, .lw-feature-interactive, .lw-story-interactive,
.lw-stat-tile.lw-card-interactive, .lw-stat-tile[role="button"],
.lw-kpi[role="button"], .lw-msg-actions button, .lw-table th[aria-sort]:not([aria-sort="none"]) button { cursor: pointer; }

/* -----------------------------------------------------------------------------
   Windows High Contrast Mode (forced-colors).

   Forced-colors strips author background-image, box-shadow and most colors and
   substitutes a small system palette. That takes out the entire focus story of
   this package: every focus indicator is `outline: none` PLUS
   `box-shadow: var(--lw-focus-ring)` — 41 sites across the three layers. The
   outline survives, the box-shadow does not, so a forced-colors user gets no
   focus indicator at all. That is a WCAG 2.4.7 failure that the contrast gate
   cannot see (it measures tokens, and here the tokens are ignored) and the a11y
   gate cannot see (axe does not emulate forced-colors).

   The rule is deliberately broad: restore a real outline wherever we removed
   one, and opt the two glyphs drawn with borders/backgrounds out of forcing so
   they stay visible.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible,
  :where(.lw-input-group, .lw-combo, .lw-prompt):focus-within {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }

  /* The checkmark is a rotated border and the switch knob a background circle —
     both are erased by forcing. Opt them out so the control still reads as on. */
  .lw-check input, .lw-check input::after,
  .lw-switch input, .lw-switch input::after {
    forced-color-adjust: none;
  }

  /* The chevron is a CSS gradient, which forcing removes entirely, leaving a
     select that looks like a text field. */
  .lw-select { background-image: none; padding-inline-end: var(--lw-space-12); }

  /* Selected/active state is carried by a tinted background or an inset ring in
     the normal palette; neither survives. Give it a border the system keeps. */
  .lw-card.is-selected, .lw-card[data-selected="true"], .lw-nav-item[aria-current], .lw-menu-item[data-active],
  [role="tab"][aria-selected="true"], .lw-segmented button:is([aria-checked="true"], [aria-pressed="true"]) {
    border: 2px solid Highlight;
  }

  /* Decorative washes only add noise once the palette is forced — but the
     ground and the aurora are CONTENT WRAPPERS, not layers. `.lw-page-ground`
     is `body > div` around the whole page on the flagship consumer (a `<main>`
     and ~360 descendants), and `.lw-aurora` puts its children at
     `z-index: var(--lw-z-raised)` on purpose. So the decoration is the
     PSEUDO-ELEMENTS, and only those may be hidden.

     ⚠ Hiding the elements themselves blanks the page. Through v2.3.0 this list
     read `.lw-ground, .lw-aurora, .lw-sheen`, of which two named no rule at all —
     so it hid the aurora's content and nothing else, and the phantoms made the
     whole line look like it was doing more than it was. v3.0.0 corrected the
     names to real ones and shipped the blanking for one deploy. Verified in a
     browser at `forcedColors: active`, which is the only way to see any of
     this: no gate here emulates forced-colors. */
  :is(.lw-page-dark, .lw-page-light, .lw-page-ground)::before,
  :is(.lw-page-dark, .lw-page-light, .lw-page-ground)::after,
  :is(.lw-page-dark, .lw-page-light, .lw-page-ground) .lw-hero-dark::before,
  :is(.lw-page-dark, .lw-page-light, .lw-page-ground) .lw-hero-dark::after,
  .lw-hero-dark::before, .lw-hero-dark::after,
  .lw-aurora::before, .lw-aurora::after { display: none; }
}


/* @leanwise/design — PRODUCT layer
 *
 * App surfaces: data, overlays, the app shell, the AI surfaces, the mobile bars.
 * This was app.css.
 *
 * v1.3.0 moved three blocks OUT of here and into base.css — the layout
 * primitives, the form field/control face, and `.lw-topbar`. None of the three is
 * an app surface: a marketing site that correctly drops this layer was getting
 * correct buttons, cards and heroes with unstyled layout and an unstyled header.
 * Tombstones mark where each one was. This is the same correction that put
 * `.lw-btn` in base.css.
 *
 * Requires the BASE layer (tokens → base → product). It deliberately does NOT
 * @import base itself: an @import here would apply those rules a second time in a
 * page that also loads the marketing layer, reordering the cascade between them.
 * Load order belongs to the consumer; ds-base.js does it for the templates.
 *
 * WHAT LEFT THIS FILE FOR base.css (one list; the substantive tombstones for the
 * layout primitives, the forms, the table, the TOC and the tabs stay in place):
 *   v1.3.0  `.lw-sr-only` (was defined twice under two names); `.lw-topbar` with
 *           every descendant/state rule and BOTH `@media (max-width: 767.98px)`
 *           blocks (it had been split across two layers — `.brand-mark` was
 *           already in base); `.lw-icon-btn` (composed by `.lw-topbar-toggle`,
 *           AnnounceBar's dismiss and SiteFooter's social row; REVIEW.md item 6);
 *           the `.lw-segmented` dark patch (the shell is an inset TRACK, not a
 *           field, so the input patch misses it); the offset-ring tier swap and
 *           the reduced-motion stand-down for `.lw-switch`/`.lw-check`; the
 *           `.lw-eyebrow` dark hexagon patch (its duplicate lingered here until
 *           v1.13.0 — same rule, second home).
 *   v1.3.1  `.lw-avatar` + `img` + size variants (Byline → Avatar, ArticleCard →
 *           Byline: two MARKETING components); `.lw-empty` with its three
 *           dark-band patches (EmptyState is in the marketing article-index
 *           recipe); `.lw-icon` (the whole layout contract for the icon set —
 *           without it an <svg> falls to `display: inline` and a footer link
 *           with an `external` marker measured 39px instead of 27px — found by
 *           `check:visual` only because `marketing.card.html` and
 *           `site-chrome.card.html` stopped loading this file in the same
 *           commit; every icon specimen had been loading product.css);
 *           `.lw-pagination`/`.lw-pag-*` (a blog index paginates).
 *   v1.7.0  `.lw-table-wrap`'s two header/cell dark patches, with the table.
 */

/* ---- Layout primitives -----------------------------------------------------
   MOVED to base.css in v1.3.0, verbatim and in this order. `.lw-page`,
   `.lw-stack`, `.lw-cluster`, `.lw-grid`, `.lw-split`/`.lw-split-start` and
   `.lw-scroll` are ARRANGEMENT, not app surface: a marketing page that correctly
   drops this layer still has to lay itself out, and it was getting nothing. They
   now live in the layer nobody drops. */

/* WIDE-TRACKED CAPS: tracking opens every gap by one step, which is a huge
   proportional change to a near-zero letter gap and a small one to an already
   wide space. Letters close in on words until the word boundary stops reading.
   So every uppercase mono label here pairs its letter-spacing with
   --lw-word-spacing-caps, which OPENS the word space and keeps word white at
   roughly 5x letter white. Tightening the space instead makes it worse. */

/* ---- Forms -----------------------------------------------------------------
   MOVED to base.css in v1.3.0, verbatim. `.lw-field`/`.lw-label`/`.lw-help`/
   `.lw-error`, the shared `.lw-input`/`.lw-textarea`/`.lw-select` control face and
   every one of its states, `.lw-input-sm`/`-lg`, `.lw-input-group`, `.lw-switch`,
   `.lw-check`, `.lw-segmented` and the `@media (pointer: coarse)` block that closed
   the section. A contact form is not an app surface; a marketing page that dropped
   this layer got unstyled fields.

   They sit between base.css's pills section and its console section — deliberately
   ABOVE the consolidated pointer-affordance list at the foot of base.css, because a
   later `cursor: default` silently cancels an earlier pointer.

   The on-dark patches for these controls stayed below through fb2d3ad, on the
   correct observation that they are `:is(.dark, …)`-scoped and so outrank the
   promoted rules on specificity, making their position not load-bearing. That
   left a base-only page with the controls and none of their dark-band treatment,
   which is the whole failure the promotion existed to fix — position is not
   load-bearing, PRESENCE is. They followed the controls into base.css in the same
   v1.3.0; see the tombstone under "Dark ground". */

/* ---- Data display ---------------------------------------------------------- */

/* MOVED to base.css in v1.7.0, verbatim and in source order, with its dark-band
   patches in the same commit: the whole `.lw-table` family. A table is a data
   display, and the consumer that needed it most — `leanwise-ai`'s published
   accuracy figures at `/product#evidence` — is a MARKETING page that loads base +
   marketing and never sees this file. It had rendered as a bare UA table since
   the split. What stays here is genuinely app surface: the KPI tile below, and
   `.lw-kpi` / `.lw-source-item`'s share of the dark-ground rule at the foot. */

/* KPI TILE — one number, its label, and at most one delta. Anything more is a
   card, not a KPI. The number is mono + tabular so a row of tiles aligns. */
.lw-kpi { display: flex; flex-direction: column; gap: 8px; padding: var(--lw-space-20); background: var(--lw-bg); border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg); min-width: 0; }
/* Label left, subject icon right. The icon is a tinted chip: it identifies the
   tile at a glance and must never compete with the figure for attention.
   The label WRAPS rather than truncating — the chip costs 46px of a ~200px tile,
   and "Grounded answers" clipped to "Grounded answ…" is a label that no longer
   names anything. Align to the top so a two-line label stays level with a one-
   line one across the row. */
.lw-kpi-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--lw-space-12); min-width: 0; }
/* A tinted chip, not a loose glyph: at label size a bare 16px stroke icon reads
   as a stray mark. The chip gives it a footprint the tile can balance against
   the number, and the family tint says what the tile is about before the label
   is read. Both tint and ink are role tokens, so dark theme re-points them and
   the pair stays legible — a soft tint left at its light value is the exact
   trap tokens.css documents for badges. */
/* Two selectors, deliberately. `span.` is what keeps the box rule off the Icon
   <svg> — an svg carries its own inline width/height, which would outrank this
   and paint a tinted 16px square. Qualifying the tag solves that WITHOUT a
   rename, so the currently-compiled component and the next one are both styled.
   Renaming instead guarantees exactly one turn where neither name matches. */
span.lw-kpi-icon, .lw-kpi-badge {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--lw-radius-md);
  background: var(--lw-brand-soft); color: var(--lw-brand-on);
}
span.lw-kpi-icon[data-accent="success"], .lw-kpi-badge[data-accent="success"] { background: var(--lw-success-soft); color: var(--lw-success-on); }
span.lw-kpi-icon[data-accent="danger"], .lw-kpi-badge[data-accent="danger"] { background: var(--lw-danger-soft); color: var(--lw-danger-on); }
span.lw-kpi-icon[data-accent="warning"], .lw-kpi-badge[data-accent="warning"] { background: var(--lw-warning-soft); color: var(--lw-warning-on); }
span.lw-kpi-icon[data-accent="neutral"], .lw-kpi-badge[data-accent="neutral"] { background: var(--lw-neutral-soft); color: var(--lw-neutral-on); }
/* If a build ever lands the class on the svg itself, it stays a plain glyph. */
svg.lw-kpi-icon { background: none; color: var(--lw-fg-faint); }
/* The number and its delta are ONE statement, so they share a baseline and a
   row. Stacked, they read as two unrelated figures. The delta wraps beneath
   only when the tile is too narrow to hold both. */
.lw-kpi-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px var(--lw-space-8); min-width: 0; }
.lw-kpi .k { min-width: 0; text-wrap: pretty; font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.12em; word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase; color: var(--lw-fg-subtle); }
.lw-kpi .v { font-family: var(--lw-font-mono); font-variant-numeric: tabular-nums; font-size: clamp(26px, 3vw, 34px); font-weight: var(--lw-fw-medium); letter-spacing: var(--lw-tracking-tight); color: var(--lw-fg); line-height: 1.05; }
/* The delta is an arrow, a number, and the window it is measured against. The
   window may fall to its own line on a narrow tile, but never mid-phrase —
   "vs last / wk" reads as two facts when it is one. */
.lw-kpi .d { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 5px; font-size: var(--lw-text-xs); font-variant-numeric: tabular-nums; color: var(--lw-fg-subtle); }
.lw-kpi .d > span { white-space: nowrap; }
/* Direction and judgement are two different facts. The ARROW says which way the
   number moved; the INK says whether that is good. They agree for revenue and
   disagree for latency, so a component that fuses them paints every latency
   improvement red. Tone defaults to direction; pass `tone` to override.
   And direction is never colour alone — the arrow icon carries it too. */
.lw-kpi .d .lw-icon, .lw-stat-delta .lw-icon { align-self: center; flex: none; }
/* The NOTE wraps; the delta does not. `.d > span { white-space: nowrap }` above
   exists so a figure like "0.4 pt" never breaks across lines — and it caught the
   note too, which is prose. A one-sentence note then could not wrap and pushed
   its own tile past the viewport. Measured at 1440px in leanwise-inspect. */
.lw-kpi .d .w { color: var(--lw-fg-subtle); white-space: normal; }
.lw-kpi .d.success { color: var(--lw-success-on); }
.lw-kpi .d.danger { color: var(--lw-danger-on); }

/* SKELETON — a loading placeholder shaped like the thing it replaces. The sweep
   is the one ambient animation the restrained motion policy allows, because it
   is the signal itself; reduced motion gets a static tint. */
.lw-skeleton { display: block; background: var(--lw-bg-muted); border-radius: var(--lw-radius-sm); position: relative; overflow: hidden; }
.lw-skeleton.text { height: 0.75em; margin: 0.2em 0; border-radius: var(--lw-radius-xs); }
.lw-skeleton.circle { border-radius: 50%; }
@media (prefers-reduced-motion: no-preference) {
  .lw-skeleton::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, hsl(var(--lw-fg-c) / 0.06), transparent); animation: lwShimmer var(--lw-dur-loop) var(--lw-ease-in-out) infinite; }
}
@keyframes lwShimmer { to { transform: translateX(100%); } }

/* ON THIS PAGE — MOVED to base.css in v1.4.0, verbatim: `.lw-toc`,
   `.lw-toc .lw-toc-h`, the three `.lw-toc a` rules and the `[aria-current]`
   state, with the rationale comment explaining why that rule carries no
   transition.

   Half the component was already there: `.lw-toc a` sits on the consolidated
   pointer-affordance list at the foot of base.css — the same tell
   `.lw-topbar .brand-mark` was in v1.3.0. A table of contents is navigation,
   not an app surface, and `.lw-split` (the layout it composes with) was
   promoted in v1.3.0 while marketing.css has said since then that "the article
   + table-of-contents layout is `.lw-split`". Half that layout was unreachable
   from a marketing page, which loads tokens + base + marketing and no product. */

/* BREADCRUMB — mono, so it reads as a path rather than as prose. */
.lw-crumbs { display: flex; flex-wrap: nowrap; min-width: 0; overflow: hidden; align-items: center; gap: 6px; font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); }
.lw-crumbs a { color: var(--lw-fg-subtle); text-decoration: none; border-radius: var(--lw-radius-xs); transition: color var(--lw-dur-xs) var(--lw-ease-out); }
.lw-crumbs a:hover { color: var(--lw-brand-text); }
.lw-crumbs a:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-crumbs .sep { color: var(--lw-fg-subtle); flex: none; }
/* The trail gives up the CURRENT page first: ancestors are short, and they are
   what a user navigates BY, so they hold their width and the leaf absorbs the
   whole shortfall. flex:none on the links is what implements that — the initial
   0 1 auto would shrink every crumb proportionally and truncate the ancestors
   alongside the leaf. The floor keeps the ellipsis from eating the label whole:
   below ~4ch the leaf stops being a name and becomes punctuation. */
.lw-crumbs a { flex: none; }
.lw-crumbs [aria-current="page"] { color: var(--lw-fg); flex: 0 1 auto; min-width: 4ch; }
/* One line that truncates — a wrapped trail changes the height of whatever holds
   it as the user navigates. min-width:0 is what lets the ellipsis engage: a flex
   item's automatic minimum is its min-content width, so without it the last
   crumb refused to shrink and was clipped mid-word by the toolbar beside it. */
.lw-crumbs > * { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }

/* ---- Navigation ------------------------------------------------------------ */

/* TABS — MOVED to base.css in v1.3.1, verbatim: `.lw-tabs`, its scrollbar rule,
   the five `[role="tab"]` rules and ALL SIX `.lw-code .lw-tabs` rules. A
   category filter over an article index is the documented marketing recipe, and
   `Tabs` lives in `components/nav/` beside `TopBar` — which v1.3.0 moved for
   exactly this reason. The dark-code-surface rules went with it: `.lw-code` has
   been in base.css since v1.1, so the patch and the surface are now one place
   instead of split across the layer boundary. */

/* SIDEBAR — the product rail. Collapses to icons via [data-collapsed], which is
   a state on the element rather than a second class, so it is one source. */
.lw-sidebar { display: flex; flex-direction: column; gap: var(--lw-space-4); width: var(--lw-sidebar-w, 236px); flex: none; padding: var(--lw-space-16) var(--lw-space-12); background: var(--lw-bg-subtle); border-inline-end: 1px solid var(--lw-line); transition: width var(--lw-dur-md) var(--lw-ease-out); }
/* The rail's footer sinks to the bottom of the column whatever the nav above it
   is doing. `margin-block-start: auto` on the slot, not `justify-content:
   space-between` on the rail — the rail has a gap, and space-between fights it
   the moment the nav is short enough to leave slack between every item. */
.lw-sidebar-foot { margin-block-start: auto; }
.lw-sidebar[data-collapsed="true"] { --lw-sidebar-w: 60px; }
/* THE RAIL HAD NO RESPONSIVE STORY AT ALL, and `flex: none` means it never
   yields — a 236px rail is 63% of a 375px viewport. Every consumer hand-wrote
   the same `max-md:hidden`, so it is offered here, OPT-IN via an attribute so
   no existing layout moves.

   `hide`, deliberately not "collapse to icons". A collapsed rail leans on the
   native `title` for its labels, and `title` does not exist on touch — so
   auto-collapsing at the mobile breakpoint produces a column of unnamed glyphs
   on exactly the devices that cannot read them. The mobile answer is a Drawer
   (or BottomNav), which is the consumer's to place. */
@media (max-width: 767.98px) {
  .lw-sidebar[data-responsive="hide"] { display: none; }
}
/* When an app frame owns the rail — its own width, header and footer rows — the
   nav fills that frame and drops its own edge and ground. Two width owners never
   agree, and the visible symptom is a nav inset from the rail it lives in. */
.lw-sidebar[data-embedded="true"] { width: 100%; padding-top: var(--lw-space-12); background: none; border-inline-end: 0; min-height: 0; overflow: auto; }
.lw-sidebar[data-collapsed="true"] .lw-nav-item .lw-nav-text, .lw-sidebar[data-collapsed="true"] .lw-nav-group, .lw-sidebar[data-collapsed="true"] .lw-nav-item .badge { display: none; }
.lw-sidebar[data-collapsed="true"] .lw-nav-item { justify-content: center; padding-inline: 0; min-height: var(--lw-control-h-sm); }
/* THE MIN-HEIGHT IS THE POINT, not the centring. A collapsed rail hides
   `.lw-nav-text` and `.badge`, so a row given no `icon` had nothing left in it
   and shrank to its padding — a ~16px blank strip that is still clickable, has
   no name on touch (the label survives only as a native `title`), and in review
   looks exactly like rail padding. It cannot be made to say anything the rail
   was never given, but it can be made to have a real target and be visible as a
   gap. Give every row an icon; this is the floor when someone does not. */
/* Symmetric inset so the icon column lands on the rail's centre line, not 2px
   left of it — the rail's own padding is asymmetric to the item's tint. */
.lw-sidebar[data-collapsed="true"] { padding-inline: 6px; }
/* Anything the rail should drop when collapsed opts in, rather than the rail
   knowing about every kind of child an app puts in it. */
[data-collapsed="true"] [data-collapse-hide] { display: none; }
/* A row that becomes a centred icon slot when the rail collapses — the rail does
   not need to know which of its rows those are. */
[data-collapsed="true"] [data-collapse-center] { justify-content: center; padding-inline: 0; gap: 0; }
/* An app frame wrapping its own chrome around the rail sets the same state
   attribute and reads the same width var, so collapse has one source. */
[data-collapsed="true"] { --lw-sidebar-w: 68px; }
.lw-nav-group { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.14em; word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase; color: var(--lw-fg-subtle); padding: var(--lw-space-16) 10px 6px; }
.lw-nav-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--lw-radius-sm); font-size: var(--lw-text-sm); color: var(--lw-fg-muted); text-decoration: none; cursor: pointer; border: 0; background: none; font-family: inherit; width: 100%; text-align: start; transition: background var(--lw-dur-xs) var(--lw-ease-out), color var(--lw-dur-xs) var(--lw-ease-out); }
.lw-nav-item .ic { width: 20px; height: 20px; flex: none; display: inline-flex; align-items: center; justify-content: center; font-size: 17px; opacity: 0.9; }
.lw-nav-item .lw-nav-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lw-nav-item .badge { margin-inline-start: auto; font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-muted); }
@media (pointer: coarse) {
  /* 37px rows on a touch tablet; the floor is the density token, like every control. */
  .lw-nav-item { min-height: var(--lw-control-h-sm); }
}
.lw-nav-item:hover { background: var(--lw-bg-muted); color: var(--lw-fg); }
.lw-nav-item:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
/* Active is a tint plus brand ink — not a left accent bar, which is the one
   marker this system deliberately does not use. */
.lw-nav-item[aria-current="page"], .lw-nav-item.is-active { background: var(--lw-brand-soft); color: var(--lw-brand-on); font-weight: var(--lw-fw-medium); }
.lw-nav-item[aria-current="page"] .ic, .lw-nav-item.is-active .ic { opacity: 1; }

/* ICON — MOVED to base.css in v1.3.1; see the header block. */

/* ---- Overlays -------------------------------------------------------------- */

/* LAYER — the box every Radix-backed overlay portals into (components/overlays/
   _layer.js, v2.0.0). Fixed and full-viewport so a portal's `position: fixed`
   content measures against the viewport; pointer-events off on the layer and
   back on for its children, so an EMPTY layer never eats a click; `isolation`
   so the popover/backdrop z-tokens inside stay local to this layer instead of
   competing with page furniture. `color` is stated because the layer may carry
   a mirrored `.dark` / `.lw-band-dark` class (tokens.css re-derives every role
   on that element) — a portal on document.body has no ancestor to inherit ink
   from. Modal layers ride `--lw-z-modal`; non-modal `--lw-z-overlay`. */
.lw-layer { position: fixed; inset: 0; pointer-events: none; z-index: var(--lw-z-overlay); isolation: isolate; color: var(--lw-fg);
  /* The layer carries the trigger's mirrored band class, and tokens.css's band block
     paints `background: var(--lw-bg)` on whatever carries it — on a fixed, inset-0 layer
     that is the whole viewport. The band re-points the CHANNELS for the descendants;
     the paint is not wanted here. (0,1,0) beats the :where() band rule's zero. */
  background: transparent;
}
.lw-layer-modal { z-index: var(--lw-z-modal); }
.lw-layer > * { pointer-events: auto; }
/* The scrim, as an ELEMENT rather than `::backdrop`: a portal-rendered dialog
   is not a top-layer <dialog>, so the UA pseudo does not exist for it. This is
   the ONE scrim — Dialog, Drawer and CommandPalette all render it. */
.lw-backdrop { position: fixed; inset: 0; z-index: var(--lw-z-local-1); background: var(--lw-scrim); backdrop-filter: blur(2px); }
/* Rendered by OverlayProvider: no box of its own, so it can sit anywhere in
   the branded subtree without moving a pixel. */
.lw-layer-root { display: contents; }
@media (prefers-reduced-motion: no-preference) {
  .lw-backdrop[data-state="open"] { animation: lwFadeIn var(--lw-dur-sm) var(--lw-ease-out) both; }
}

/* TOMBSTONE (v2.0.0): `.lw-dialog::backdrop`, `.lw-drawer::backdrop` and
   `.lw-cmdk::backdrop` are RETIRED, with the `[open]`-scoped display and
   animation rules beside them. The three components render Radix `Dialog`
   into `.lw-layer-modal` and paint `.lw-backdrop` above; the UA pseudo does
   not exist for a portal-rendered panel, and `[open]` is a <dialog> attribute
   no longer set — `data-state="open"` is the Radix equivalent. */

/* DIALOG — Radix `Dialog.Content` (v2.0.0), portal-rendered inside
   `.lw-layer-modal`, so focus trap, Esc, outside-click and the scroll lock are
   Radix's. Centred by `inset: 0; margin: auto` against the fixed layer rather
   than a transform, so lwDialogIn's transform does not fight the placement.
   `z-index` is LOCAL to the isolated layer: local-2 sits over the backdrop's
   local-1 and never competes with page furniture.
   A column with a bounded height: the head and foot stay put and only the body
   scrolls, so a long list cannot push the close control off-screen. */
.lw-dialog { position: fixed; inset: 0; margin: auto; height: fit-content; z-index: var(--lw-z-local-2); display: flex; flex-direction: column; width: min(calc(100vw - 32px), var(--lw-dialog-w, 480px)); max-height: calc(100dvh - 64px); overflow: hidden; padding: 0; border: 1px solid var(--lw-line); border-radius: var(--lw-radius-xl); background: var(--lw-bg); color: var(--lw-fg); box-shadow: var(--lw-shadow-xl); }
.lw-dialog-head { flex: none; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--lw-space-16); padding: var(--lw-space-20) var(--lw-space-24) var(--lw-space-12); }
.lw-dialog-title { font-size: var(--lw-text-h3); font-weight: var(--lw-fw-semibold); letter-spacing: var(--lw-tracking-tight); line-height: 1.25; margin: 0; }
/* Bare glyph, like every other dismiss in the system — a boxed close competes
   with the dialog's own actions for the eye. (`.lw-icon-btn` MOVED to base.css
   in v1.3.0 — see the header block.)

   `.lw-dialog-close` STAYED, because a dialog is an app surface. It is now a
   DELTA on `.lw-icon-btn` rather than a second copy of it — `Dialog.jsx` emits
   both classes, and all that is left here is the optical margin that pulls the
   glyph into the head's padding. The two were never two treatments; they were
   one face written under two names, which is the shape CONTRIBUTING opens with. */
.lw-dialog-close { margin: -2px -4px 0 0; }
.lw-dialog-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; padding: 0 var(--lw-space-24) var(--lw-space-20); font-size: var(--lw-text-sm); line-height: 1.6; color: var(--lw-fg-muted); text-wrap: pretty; }
/* Long words and paths inside a bounded dialog wrap rather than widening it. */
.lw-dialog-body * { min-width: 0; overflow-wrap: anywhere; }
.lw-dialog-foot { flex: none; display: flex; align-items: center; justify-content: flex-end; gap: var(--lw-space-8); padding: var(--lw-space-16) var(--lw-space-24); border-top: 1px solid var(--lw-line); background: var(--lw-bg-subtle); border-radius: 0 0 var(--lw-radius-xl) var(--lw-radius-xl); }
@media (prefers-reduced-motion: no-preference) {
  .lw-dialog[data-state="open"] { animation: lwDialogIn var(--lw-dur-sm) var(--lw-ease-out); }
}
@keyframes lwDialogIn { from { opacity: 0; transform: translateY(6px) scale(0.99); } }
@keyframes lwFadeIn { from { opacity: 0; } }

/* TOAST — status is a WORD plus semantic ink, never a tinted surface: the tint
   belongs to Chip, and a stack of three tinted toasts is louder than any of the
   three messages. The mono label carries the status for a colour-blind reader. */
.lw-toast-region { position: fixed; bottom: var(--lw-space-24); right: var(--lw-space-24); z-index: var(--lw-z-toast); display: flex; flex-direction: column; gap: var(--lw-space-8); width: min(360px, calc(100vw - 32px)); }
/* A toast in an app that has a BOTTOM NAV. Two pieces of this package fought each
   other: the region is fixed 24px from the bottom and `.lw-bottom-nav` is
   `56px + safe-bottom` tall and sticky above it, so on a phone the toast landed
   underneath the bar. An `data-` attribute rather than a modifier CLASS on purpose —
   a consumer's presence gate treats an unknown `lw-*` class as an invented one, and
   this is layout the app declares about itself, not a role it is inventing. */
.lw-toast-region[data-above-nav] { bottom: calc(var(--lw-bottom-nav-h) + var(--lw-safe-bottom) + var(--lw-space-12)); }
.lw-toast { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; background: var(--lw-bg); border: 1px solid var(--lw-line); border-radius: var(--lw-radius-md); box-shadow: var(--lw-shadow-lg); font-size: var(--lw-text-sm); color: var(--lw-fg); }
.lw-toast .k { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.12em; word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase; padding-top: 3px; flex: none; color: var(--lw-fg-subtle); }
/* One tone vocabulary — see components/_tone.js. The abbreviated spellings
   (ok/warn/err) were retired at v3.0.0 along with the prop aliases. */
.lw-toast.success .k { color: var(--lw-success-on); }
.lw-toast.warning .k { color: var(--lw-warning-on); }
.lw-toast.danger .k { color: var(--lw-danger-on); }
.lw-toast .msg { flex: 1; min-width: 0; line-height: 1.5; text-wrap: pretty; }
.lw-toast .lw-icon-btn { width: 22px; height: 22px; margin: -1px -4px 0 0; }
@media (prefers-reduced-motion: no-preference) { .lw-toast { animation: lwToastIn var(--lw-dur-sm) var(--lw-ease-out); } }
@keyframes lwToastIn { from { opacity: 0; transform: translateY(8px); } }

/* TOOLTIP — Radix Tooltip content since v2.0, portalled into a `.lw-layer`.
   For hints, never for content a user needs: it does not exist on touch.
   Navy-900 + --lw-on-dark are both TIERS (theme-invariant), so the surface is
   the same 15.75:1 white-on-navy in every scope, including a dark band — the
   pair the contrast gate measures as "navy-deep ground / on-dark". */
.lw-tooltip {
  padding: 5px 9px; border-radius: var(--lw-radius-sm);
  background: var(--lw-navy-900); color: var(--lw-on-dark);
  font-size: var(--lw-text-xs); line-height: 1.35; white-space: nowrap; max-width: 28ch;
  z-index: var(--lw-z-overlay);
  transform-origin: var(--radix-tooltip-content-transform-origin);
}
@media (prefers-reduced-motion: no-preference) {
  /* Radix states: closed | delayed-open | instant-open. Own keyframe name: a
     keyframe is global and last-wins, so this does not borrow the popover's. */
  .lw-tooltip[data-state="delayed-open"], .lw-tooltip[data-state="instant-open"] {
    animation: lwTooltipIn var(--lw-dur-xs) var(--lw-ease-out) both;
  }
  @keyframes lwTooltipIn { from { opacity: 0; transform: translateY(-2px); } }
}

/* ---- AI surfaces -----------------------------------------------------------
   The components a generic system does not have and an AI product cannot ship
   without. They are here because retrieval, streaming and provenance are the
   product's actual vocabulary. */

/* PROMPT — the app's primary input. A framed box, not a bare textarea: the
   attachments row and the send action live inside the same focus ring. */
.lw-prompt { display: flex; flex-direction: column; gap: 8px; padding: 12px; background: var(--lw-bg); border: 1px solid var(--lw-line-control); border-radius: var(--lw-radius-lg); box-shadow: var(--lw-shadow-sm); transition: border-color var(--lw-dur-xs) var(--lw-ease-out), box-shadow var(--lw-dur-xs) var(--lw-ease-out); }
/* The prompt is the product's hero input, so its focus state is a deliberate
   brand ring — the same one every other control uses. The soft outer halo this
   rule used to carry was the bloom it warned against: two stops of one hue with
   the outer translucent, which on a large framed surface reads as a glow. */
.lw-prompt:focus-within { border-color: var(--lw-brand-500); box-shadow: var(--lw-focus-ring); }
.lw-prompt textarea { width: 100%; border: 0; background: none; resize: none; font: inherit; font-size: var(--lw-text-body); line-height: 1.55; color: var(--lw-fg); min-height: 48px; max-height: 200px; field-sizing: content; overflow-y: auto; }
.lw-prompt textarea:focus { outline: none; }
.lw-prompt textarea::placeholder { color: var(--lw-fg-subtle); }
.lw-prompt-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.lw-prompt-foot .spacer { flex: 1; }
.lw-prompt-hint { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-subtle); letter-spacing: 0.04em; white-space: nowrap; }

/* MESSAGE — the answer stream. Role is a mono label above the body, not a
   coloured bubble: bubbles cap line length and this product answers in prose. */
.lw-msg { display: flex; align-items: flex-start; gap: 12px; padding: var(--lw-space-16) 0; }
.lw-msg .lw-msg-avatar { width: 28px; height: 28px; flex: none; border-radius: var(--lw-radius-sm); display: grid; place-items: center; font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); font-weight: var(--lw-fw-semibold); }
.lw-msg.ai .lw-msg-avatar { background: var(--lw-brand-500); color: var(--lw-on-brand); }
.lw-msg.user .lw-msg-avatar { background: var(--lw-bg-muted); color: var(--lw-fg-muted); border: 1px solid var(--lw-line); }
.lw-msg .lw-msg-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.lw-msg .who { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.14em; word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase; color: var(--lw-fg-subtle); }
.lw-msg.ai .who { color: var(--lw-brand-text); }
.lw-msg .body { min-width: 0; font-size: var(--lw-text-body); line-height: var(--lw-lh-normal); color: var(--lw-fg); text-wrap: pretty; }
/* The user's turn mirrors — avatar right, body a bounded bubble. The assistant
   stays left and full width, because prose carrying citations needs the measure
   and a bubble would cap it. Alignment separates the turns, so no rules between. */
.lw-msg.user { flex-direction: row-reverse; }
.lw-msg.user .lw-msg-main { align-items: flex-end; }
.lw-msg.user .body { max-width: min(100%, 52ch); padding: 10px 14px; background: var(--lw-bg-subtle); border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg) var(--lw-radius-lg) var(--lw-radius-sm) var(--lw-radius-lg); position: relative; }
/* The user's actions hang under the bubble rather than inside it: reserved space
   in a bubble sized to its text reads as a dent when the actions are hidden. */
.lw-msg.user .lw-msg-actions { position: absolute; top: 100%; right: 0; margin-top: 2px; }
/* Per-turn actions appear on hover or keyboard focus, so a long thread reads as
   prose instead of a column of button rows. Always visible where there is no
   hover to reveal them with. */
.lw-msg-actions { display: flex; align-items: center; gap: 2px; opacity: 0; transition: opacity var(--lw-dur-xs) var(--lw-ease-out); }
.lw-msg:hover .lw-msg-actions, .lw-msg:focus-within .lw-msg-actions { opacity: 1; }
@media (hover: none) { .lw-msg-actions { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .lw-msg-actions { transition: none; } }
.lw-msg .body > * + * { margin-top: 0.7em; }
/* The caret marks a stream still arriving; it disappears the moment it stops. */
/* The caret belongs at the end of the LAST LINE of prose. Hung off .body it landed
   after the footer row as a stray block, because a block sibling can never sit
   inline. It attaches to the closing paragraph, and to .body only when there is none. */
.lw-msg[data-streaming="true"] .body > p:last-of-type::after,
.lw-msg[data-streaming="true"] .body:not(:has(p))::after { content: "\258B"; margin-inline-start: 2px; color: var(--lw-brand-text); }
@media (prefers-reduced-motion: no-preference) {
  .lw-msg[data-streaming="true"] .body > p:last-of-type::after,
  .lw-msg[data-streaming="true"] .body:not(:has(p))::after { animation: lwCaret var(--lw-dur-loop-fast) steps(1) infinite; }
}
@keyframes lwCaret { 50% { opacity: 0; } }

/* SOURCE — the citation atom. A numbered mono chip so a claim in the answer and
   the document behind it share one identifier. */
.lw-source { display: inline-flex; align-items: center; gap: 5px; height: 18px; padding: 0 6px; border-radius: var(--lw-radius-xs); background: var(--lw-brand-soft); color: var(--lw-brand-on); font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); line-height: 1; text-decoration: none; vertical-align: 2px; border: 0; cursor: pointer; transition: background var(--lw-dur-xs) var(--lw-ease-out); }
.lw-source:hover { background: hsl(var(--lw-brand-500-c) / 0.24); }
.lw-source:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-source-list { display: flex; flex-direction: column; gap: var(--lw-space-8); }
.lw-source-item { display: flex; gap: 10px; padding: 10px 12px; border: 1px solid var(--lw-line); border-radius: var(--lw-radius-md); background: var(--lw-bg); text-decoration: none; color: inherit; transition: border-color var(--lw-dur-xs) var(--lw-ease-out), background var(--lw-dur-xs) var(--lw-ease-out); }
.lw-source-item:hover { border-color: var(--lw-brand-line); background: var(--lw-bg-subtle); }
.lw-source-item:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-source-item .n { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-brand-text); padding-top: 2px; flex: none; }
/* The text column needs min-width:0 or the title's ellipsis never fires — a flex
   item's automatic minimum size is its content, so a long title pushes the row
   wider instead of truncating. Named, not `> span:not(.n)`: the row grows a
   trailing affordance sooner or later and a positional rule would land on it. */
.lw-source-item .lw-source-main { min-width: 0; }
.lw-source-item .t { display: block; font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium); color: var(--lw-fg); line-height: 1.35; }
.lw-source-item .m { display: block; font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-subtle); margin-top: 3px; }

/* CONFIDENCE — a retrieval score. Mono number plus a bar, because a bar alone
   is unquotable and a number alone is unscannable. */
.lw-confidence { display: flex; align-items: center; gap: 8px; font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-subtle); font-variant-numeric: tabular-nums; }
.lw-confidence .rail { width: 48px; height: 4px; border-radius: var(--lw-radius-pill); background: var(--lw-bg-inset); overflow: hidden; flex: none; }
.lw-confidence .rail > i { display: block; height: 100%; width: var(--lw-confidence, 0%); background: var(--lw-brand-500); border-radius: inherit; transition: width var(--lw-dur-lg) var(--lw-ease-out); }
.lw-confidence.low .rail > i { background: var(--lw-fg-faint); }

/* TRACE — the agent's steps. The vertical hairline is the spine; each step's
   state is a dot on it. Users trust what they can watch. */
.lw-trace { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.lw-trace li { position: relative; display: flex; flex-direction: column; gap: 2px; padding: 0 0 var(--lw-space-16) var(--lw-space-24); }
.lw-trace li::before { content: ""; position: absolute; left: 4px; top: 14px; bottom: -2px; width: 1px; background: var(--lw-line); }
.lw-trace li:last-child { padding-bottom: 0; }
.lw-trace li:last-child::before { display: none; }
.lw-trace li::after { content: ""; position: absolute; left: 0; top: 5px; width: 9px; height: 9px; border-radius: 50%; border: 1px solid var(--lw-line-strong); background: var(--lw-bg); }
.lw-trace li[data-state="done"]::after { background: var(--lw-success-on); border-color: var(--lw-success-on); }
.lw-trace li[data-state="active"]::after { background: var(--lw-brand-500); border-color: var(--lw-brand-500); }
.lw-trace li[data-state="error"]::after { background: var(--lw-danger-on); border-color: var(--lw-danger-on); }
@media (prefers-reduced-motion: no-preference) { .lw-trace li[data-state="active"]::after { animation: lwTracePulse var(--lw-dur-loop) var(--lw-ease-in-out) infinite; } }
@keyframes lwTracePulse { 50% { box-shadow: 0 0 0 4px hsl(var(--lw-brand-500-c) / 0.18); } }
.lw-trace .step { font-size: var(--lw-text-sm); color: var(--lw-fg); line-height: 1.4; }
.lw-trace .meta { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-subtle); line-height: 1.5; overflow-wrap: anywhere; }

/* CODE BLOCK header. `.lw-code` in base.css is the padded dark surface; a figure
   with a caption needs the padding moved onto the <pre> so the header rule can
   run edge to edge. Both live here because only docs and app surfaces use it. */
.lw-code { margin: 0; }
/* With a caption the <pre> is the scroller, so the figure must CLIP, not scroll.
   `overflow-x: auto` on .lw-code (base.css) forces overflow-y to compute to auto
   as well — that is the spec, not a browser quirk — which wrapped every
   captioned block in a second, redundant scrollbar pair. Clipping also keeps the
   radius cropping the pre's corners. */
.lw-code:has(.lw-code-head) { padding: 0; overflow: hidden; }
.lw-code-head { display: flex; align-items: center; justify-content: space-between; gap: var(--lw-space-16); padding: 9px var(--lw-space-16); border-bottom: 1px solid var(--lw-on-dark-line); font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.06em; color: var(--lw-on-dark-3); }
.lw-code-head .fn { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lw-code-head .end { display: flex; align-items: center; gap: var(--lw-space-12); flex: none; }
.lw-code-head .lang { text-transform: uppercase; letter-spacing: 0.12em; word-spacing: var(--lw-word-spacing-caps); color: var(--lw-brand-300); flex: none; }
/* On the code block's navy ground the shared icon button would inherit app ink. */
.lw-code-head .lw-icon-btn { width: 24px; height: 24px; color: var(--lw-on-dark-3); }
.lw-code-head .lw-icon-btn:hover { color: var(--lw-on-dark-1); }
.lw-code:has(.lw-code-head) pre { margin: 0; padding: var(--lw-space-16); overflow-x: auto; }
.lw-code pre { margin: 0; }

/* ---- Dark ground -----------------------------------------------------------
   Three selectors, same as base.css: `.dark` (Tailwind), `[data-band="dark"]` (a
   dark band inside a light page), `.lw-band-dark` (the explicit opt-in). Most
   components need no rule here at all — they are built from role tokens, which
   already re-point. What is listed below is only what role tokens cannot fix. */

/* MOVED to base.css in v1.3.0: the dark-ground patches for the controls that
   fb2d3ad promoted — `.lw-input`, `.lw-textarea`, `.lw-select`,
   `.lw-input-group`, `.lw-switch`, `.lw-check`, `.lw-segmented`. That commit
   left them here because they outrank the promoted rules on specificity, so
   their POSITION is not load-bearing; what it missed is that a page loading
   base + marketing never sees this file at all, and so had the controls with no
   dark-band treatment — on a site that puts its contact form on a dark ground.
   Position is not load-bearing; presence is. What stays below is what was
   never promoted: `.lw-prompt` (an AI surface), the bare `[data-focus-demo]`
   review hook, and the kpi/eyebrow family. The table's share went to base.css in
   v1.7.0, for the same reason and after the same failure. */
:is(.dark, [data-band="dark"], .lw-band-dark) .lw-prompt { background-color: var(--lw-bg-subtle); }
/* background-COLOR, never the shorthand: the shorthand resets background-image
   and would erase the two gradients that draw .lw-select's chevron, leaving the
   select on dark indistinguishable from a text input. */

:is(.dark, [data-band="dark"], .lw-band-dark) .lw-prompt:focus-within,
:is(.dark, [data-band="dark"], .lw-band-dark) [data-focus-demo] { border-color: var(--lw-brand-400); box-shadow: var(--lw-focus-ring); }

/* (v2.0.0) There is no `color` rule for the band itself any more. Through
   v1.x this read `color: var(--lw-on-dark-2)` — a 9.42:1 ink written before
   tokens.css re-derived `--lw-fg` inside every band member. The token band's
   `--lw-fg` (15.78:1) is the ink now; the override only ever lowered it. */
.lw-band-dark .lw-kpi, [data-band="dark"] .lw-kpi,
.lw-band-dark .lw-source-item, [data-band="dark"] .lw-source-item { background: var(--lw-on-dark-wash); border-color: var(--lw-on-dark-line); }
.lw-band-dark .lw-kpi .v, [data-band="dark"] .lw-kpi .v { color: var(--lw-on-dark-1); }
.lw-band-dark .lw-kpi .k, [data-band="dark"] .lw-kpi .k { color: var(--lw-on-dark-3); }
/* .lw-eyebrow dark patch: MOVED to base.css (v1.3.0). */

@media (prefers-reduced-motion: reduce) {
  .lw-sidebar, .lw-confidence .rail > i { transition: none; }
}


/* ---- Popover ----------------------------------------------------------------
   The floating surface every other control is built on: menu, combobox, date
   picker, filter panel, column chooser. ONE surface, so those five cannot each
   invent a radius and a shadow (README rule 9).

   Since v2.0.0 the panel is Radix Popover / DropdownMenu content, portalled
   into the nearest OverlayProvider layer (or body) and positioned by Radix —
   no `position: fixed` here: Radix's popper wrapper carries the position and
   COPIES this rule's computed z-index onto itself, so `--lw-z-overlay` still
   decides the stack. The theme/band scope of the trigger is mirrored onto the
   `.lw-layer` around it, which is what keeps a popover on a dark band dark.
   `[data-state]` is Radix's; `--radix-*` are the vars it sets on the content
   (the popover family for Popover, the dropdown-menu family for Menu — the
   fallback chain reads whichever exists). */
.lw-popover {
  margin: 0;
  z-index: var(--lw-z-overlay);
  min-width: 180px; max-width: min(92vw, 360px);
  max-height: min(var(--radix-popover-content-available-height, var(--radix-dropdown-menu-content-available-height, 70vh)), 480px);
  overflow: auto; overscroll-behavior: contain;
  padding: var(--lw-space-8);
  background: var(--lw-bg); color: var(--lw-fg);
  border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-lg);
  box-shadow: var(--lw-shadow-lg);
  transform-origin: var(--radix-popover-content-transform-origin, var(--radix-dropdown-menu-content-transform-origin));
}
.lw-popover:focus-visible { outline: none; box-shadow: var(--lw-shadow-lg), var(--lw-focus-ring); }
/* Floor the panel to the anchor: comboboxes and selects. */
.lw-popover[data-match-width] { min-width: var(--radix-popover-trigger-width, var(--radix-dropdown-menu-trigger-width)); }
/* A plain content popover wants the card's padding, not the menu's gutter. */
.lw-popover-pad { padding: var(--lw-space-16); }
@media (prefers-reduced-motion: no-preference) {
  .lw-popover[data-state="open"] { animation: lwPopIn var(--lw-dur-xs) var(--lw-ease-out); }
  @keyframes lwPopIn { from { opacity: 0; transform: translateY(-2px); } }
}

/* ---- Menu (inside .lw-popover) ---------------------------------------------
   Lives inside .lw-popover; it owns rows, never the surface. Row height comes
   from the density scale, so a menu in a compact workbench is compact with it. */
.lw-menu { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.lw-menu-item {
  display: flex; align-items: center; gap: var(--lw-space-8);
  width: 100%; min-height: var(--lw-control-h-sm);
  padding: 0 var(--lw-space-8);
  border: 0; border-radius: var(--lw-radius-sm);
  background: none; color: var(--lw-fg);
  font-family: var(--lw-font-sans); font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium);
  text-align: start; text-decoration: none; line-height: 1.3;
  cursor: pointer;
  transition: background var(--lw-dur-xs) var(--lw-ease-out), color var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-menu-item:hover, .lw-menu-item[data-highlighted] { background: var(--lw-bg-subtle); }
.lw-menu-item:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-menu-item .lw-menu-lead { flex: none; display: inline-flex; width: 16px; justify-content: center; color: var(--lw-fg-subtle); }
.lw-menu-item .lw-menu-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The shortcut is mono so it reads as keys, and subtle so it never competes with
   the label it belongs to. */
.lw-menu-item .lw-menu-kbd { flex: none; font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); font-variant-numeric: tabular-nums; }
.lw-menu-item[data-state="checked"] .lw-menu-lead { color: var(--lw-brand-text); }
/* Destructive rows take the danger ROLE ink, which re-points per theme — not the
   raw tier, which would freeze to the light value on the dark ground. */
.lw-menu-item.danger { color: var(--lw-danger-on); }
.lw-menu-item.danger:hover, .lw-menu-item.danger[data-highlighted] { background: var(--lw-danger-soft); }
.lw-menu-item.danger .lw-menu-lead { color: currentColor; }
.lw-menu-item[aria-disabled="true"], .lw-menu-item[data-disabled] { color: var(--lw-fg-faint); cursor: not-allowed; pointer-events: none; }
/* Radix keeps focus ON the row, so :focus-visible paints the ring; the highlight
   itself is a background, which forced colours erase — give it an edge. */
@media (forced-colors: active) {
  .lw-menu-item[data-highlighted] { outline: 1px solid Highlight; outline-offset: -1px; }
}
.lw-menu-sep { height: 1px; margin: var(--lw-space-4) calc(var(--lw-space-8) * -1); background: var(--lw-line); border: 0; }
.lw-menu-label {
  padding: var(--lw-space-8) var(--lw-space-8) var(--lw-space-4);
  font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.14em;
  word-spacing: var(--lw-word-spacing-caps); text-transform: uppercase;
  color: var(--lw-fg-subtle);
}

/* ---- Combobox ---------------------------------------------------------------
   The field is the ANCHOR and the input inside it is bare, so the focus ring
   wraps the whole control — the same arrangement as .lw-input-group, for the
   same reason. Min-height rather than height: a multi-select grows with its
   tokens, and a fixed height would clip the second row instead of showing it. */
.lw-combo {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--lw-space-4);
  width: 100%; min-height: var(--lw-control-h-md);
  padding: 3px var(--lw-space-8) 3px var(--lw-field-pad-x);
  background: var(--lw-bg); color: var(--lw-fg);
  border: 1px solid var(--lw-line-control); border-radius: var(--lw-radius-sm);
  font-size: var(--lw-text-sm); cursor: text;
  transition: border-color var(--lw-dur-xs) var(--lw-ease-out), box-shadow var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-combo:focus-within { border-color: var(--lw-brand-500); box-shadow: var(--lw-focus-ring); }
/* The invalid state selects on the ATTRIBUTE the screen reader reads, never a
   class — and it has to be :has(), because the frame is drawn by the parent. */
.lw-combo:has([aria-invalid="true"]) { border-color: var(--lw-danger-on); }
.lw-combo-sm { min-height: var(--lw-control-h-sm); font-size: var(--lw-text-xs); }
.lw-combo-lg { min-height: var(--lw-control-h-lg); font-size: var(--lw-text-body); }
.lw-combo input {
  flex: 1; min-width: 60px; align-self: stretch;
  border: 0; background: none; outline: none; font: inherit; color: inherit; padding: 0;
}
.lw-combo input::placeholder { color: var(--lw-fg-subtle); }
.lw-combo[data-disabled="true"] { background: var(--lw-bg-muted); color: var(--lw-fg-faint); cursor: not-allowed; }
.lw-combo-chev { flex: none; display: inline-flex; color: var(--lw-fg-subtle); margin-inline-start: auto; padding-inline-start: var(--lw-space-4); }
/* A token is a REMOVABLE selection, so it carries its own control rather than
   relying on backspace — backspace is the shortcut, not the affordance. */
.lw-combo-token {
  display: inline-flex; align-items: center; gap: var(--lw-space-4);
  height: 22px; padding: 0 var(--lw-space-4) 0 var(--lw-space-8);
  border-radius: var(--lw-radius-xs);
  background: var(--lw-brand-soft); color: var(--lw-brand-on);
  font-size: var(--lw-text-xs); font-weight: var(--lw-fw-medium); max-width: 200px;
}
.lw-combo-token span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lw-combo-token button {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; border: 0; border-radius: var(--lw-radius-xs);
  background: none; color: inherit; cursor: pointer;
}
.lw-combo-token button:hover { background: hsl(var(--lw-brand-500-c) / 0.22); }
.lw-combo-token button:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }

/* ---- Listbox (inside .lw-popover) ------------------------------------------- */
.lw-listbox { display: flex; flex-direction: column; gap: 1px; margin: 0; padding: 0; list-style: none; }
.lw-option {
  display: flex; align-items: center; gap: var(--lw-space-8);
  min-height: var(--lw-control-h-sm); padding: 0 var(--lw-space-8);
  border-radius: var(--lw-radius-sm); font-size: var(--lw-text-sm); color: var(--lw-fg);
  cursor: pointer; scroll-margin: var(--lw-space-4);
}
/* The active option is styled from data-active, not :hover — the keyboard is what
   moves it, and a hover highlight competing with the keyboard one shows two
   "current" rows at once. */
.lw-option[data-active="true"] { background: var(--lw-bg-subtle); }
.lw-option[aria-selected="true"] { color: var(--lw-brand-text); font-weight: var(--lw-fw-medium); }
.lw-option[aria-disabled="true"] { color: var(--lw-fg-faint); cursor: not-allowed; }
.lw-option .lw-option-lead { flex: none; display: inline-flex; width: 16px; justify-content: center; }
.lw-option .lw-option-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lw-option .lw-option-meta { flex: none; font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); }
.lw-listbox-empty { padding: var(--lw-space-12) var(--lw-space-8); font-size: var(--lw-text-sm); color: var(--lw-fg-subtle); }

/* ---- Drawer ------------------------------------------------------------------
   The same Radix `Dialog.Content` as `.lw-dialog` (v2.0.0), in the same layer
   with the same `.lw-backdrop`; a drawer is a modal that enters from an edge,
   not a different kind of thing, so only the geometry below differs. */
/* `data-side="start"/"end"` is a LOGICAL api, so it is implemented logically.
   It was left/right underneath, which meant an RTL consumer asked for a drawer
   on the start edge and got it on the end one — the API name was the only thing
   that was correct. --lw-dir flips the enter animation, because a keyframe
   translate cannot mirror itself the way inset-inline can. */
.lw-drawer {
  position: fixed; inset-block: 0; inset-inline: auto 0; margin: 0; max-width: 100vw; max-height: 100vh;
  z-index: var(--lw-z-local-2); display: flex; flex-direction: column;
  width: var(--lw-drawer-w, 420px); height: 100%;
  padding: 0; border: 0; border-inline-start: 1px solid var(--lw-line);
  background: var(--lw-bg); color: var(--lw-fg);
  box-shadow: var(--lw-shadow-xl);
  --lw-dir: 1;
}
[dir="rtl"] .lw-drawer { --lw-dir: -1; }
.lw-drawer[data-side="start"] { inset-block: 0; inset-inline: 0 auto; border-inline-start: 0; border-inline-end: 1px solid var(--lw-line); }
.lw-drawer[data-side="bottom"] {
  inset-block: auto 0; inset-inline: 0; width: 100%; height: auto;
  max-height: min(88vh, 720px); border: 0; border-block-start: 1px solid var(--lw-line);
  border-start-start-radius: var(--lw-radius-xl); border-start-end-radius: var(--lw-radius-xl);
  border-end-start-radius: 0; border-end-end-radius: 0;
}
.lw-drawer-head {
  flex: none; display: flex; align-items: flex-start; gap: var(--lw-space-12);
  padding: var(--lw-card-pad); border-bottom: 1px solid var(--lw-line);
}
.lw-drawer-title { flex: 1; min-width: 0; margin: 0; font-size: var(--lw-text-h3); font-weight: var(--lw-fw-semibold); letter-spacing: var(--lw-tracking-tight); line-height: 1.25; color: var(--lw-fg); text-wrap: pretty; }
.lw-drawer-body { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; padding: var(--lw-card-pad); }
.lw-drawer-foot { flex: none; display: flex; justify-content: flex-end; gap: var(--lw-space-8); padding: var(--lw-card-pad); border-top: 1px solid var(--lw-line); }
/* The grabber is the bottom sheet's only affordance for "this is draggable-ish"
   and its visual anchor; a side drawer has an edge and does not need one. */
.lw-drawer[data-side="bottom"] .lw-drawer-head::before {
  content: ""; position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
  width: 36px; height: 4px; border-radius: var(--lw-radius-pill); background: var(--lw-line-strong);
}
.lw-drawer[data-side="bottom"] .lw-drawer-head { position: relative; padding-top: calc(var(--lw-card-pad) + 6px); }
@media (prefers-reduced-motion: no-preference) {
  .lw-drawer[data-state="open"] { animation: lwDrawerIn var(--lw-dur-md) var(--lw-ease-out); }
  .lw-drawer[data-state="open"][data-side="start"] { animation-name: lwDrawerInStart; }
  .lw-drawer[data-state="open"][data-side="bottom"] { animation-name: lwDrawerInBottom; }
  @keyframes lwDrawerIn { from { transform: translateX(calc(100% * var(--lw-dir, 1))); } }
  @keyframes lwDrawerInStart { from { transform: translateX(calc(-100% * var(--lw-dir, 1))); } }
  @keyframes lwDrawerInBottom { from { transform: translateY(100%); } }
}
@media (max-width: 479.98px) { /* --lw-bp-sm */
  .lw-drawer:not([data-side="bottom"]) { width: min(var(--lw-drawer-w, 420px), 92vw); }
}

/* ---- Calendar ---------------------------------------------------------------
   A grid of real buttons, not a table of divs: a day is a control, and the only
   thing that makes a date grid usable by keyboard is that each cell can hold
   focus. Roving tabindex, so Tab enters and leaves the grid once. */
.lw-cal { display: flex; flex-direction: column; gap: var(--lw-space-8); width: 244px; }
.lw-cal-head { display: flex; align-items: center; gap: var(--lw-space-4); }
.lw-cal-month { flex: 1; text-align: center; font-size: var(--lw-text-sm); font-weight: var(--lw-fw-semibold); color: var(--lw-fg); }
/* The grid gained a row/rowgroup layer so role="grid" owns role="row" owns
   role="gridcell" — required by ARIA, and what gives a screen reader row and
   column position. The seven-column track therefore moved from the grid onto
   each row; the grid itself is now a vertical stack of rows. */
.lw-cal-grid { display: flex; flex-direction: column; gap: 2px; }
.lw-cal-dow-row,
.lw-cal-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.lw-cal-weeks { display: flex; flex-direction: column; gap: 2px; }
.lw-cal-dow {
  height: 22px; display: grid; place-items: center;
  font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--lw-fg-subtle);
}
.lw-cal-day {
  height: 30px; padding: 0; border: 0; border-radius: var(--lw-radius-sm);
  background: none; color: var(--lw-fg);
  font-family: var(--lw-font-mono); font-size: var(--lw-text-xs);
  font-variant-numeric: tabular-nums; cursor: pointer;
  transition: background var(--lw-dur-xs) var(--lw-ease-out), color var(--lw-dur-xs) var(--lw-ease-out);
}
.lw-cal-day:hover { background: var(--lw-bg-subtle); }
.lw-cal-day:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
/* `--lw-fg-subtle`, NOT `--lw-fg-faint`. A day from the adjacent month is a
   real, clickable date, so it is interactive text and owes AA — fg-faint is
   documented "decorative only, fails AA as body text" and is excused by the
   contrast gate only behind a :disabled / aria-disabled selector, which this is
   not. Found at v3.0.0 the first time a specimen painted the panel OPEN: the
   old DatePicker card demoed a CLOSED Popover, so eight cells per month have
   been failing at 2.28 in every consumer with nothing able to see it. */
.lw-cal-day[data-outside="true"] { color: var(--lw-fg-subtle); }
/* Today is a RING, selected is a FILL — two different facts, so they must be able
   to show at once. Today-and-selected is one cell, not a conflict to resolve. */
.lw-cal-day[data-today="true"] { box-shadow: inset 0 0 0 1px var(--lw-line-control); }
.lw-cal-day[data-in-range="true"] { background: var(--lw-brand-soft); border-radius: 0; }
.lw-cal-day[aria-selected="true"] {
  background: var(--lw-brand-500); color: var(--lw-on-brand); font-weight: var(--lw-fw-semibold);
}
.lw-cal-day[aria-selected="true"]:focus-visible { box-shadow: none; outline: 2px solid var(--lw-brand-500); outline-offset: 2px; }
/* Logical for the same reason as the drawer: data-edge names a logical edge. */
.lw-cal-day[data-edge="start"] {
  border-start-start-radius: var(--lw-radius-sm); border-end-start-radius: var(--lw-radius-sm);
  border-start-end-radius: 0; border-end-end-radius: 0;
}
.lw-cal-day[data-edge="end"] {
  border-start-end-radius: var(--lw-radius-sm); border-end-end-radius: var(--lw-radius-sm);
  border-start-start-radius: 0; border-end-start-radius: 0;
}
.lw-cal-day:disabled,
.lw-cal-day[aria-disabled="true"] { color: var(--lw-fg-faint); background: none; cursor: not-allowed; }
.lw-cal-day[aria-disabled="true"]:hover { background: none; }
.lw-cal-wrap { display: flex; gap: var(--lw-space-12); }
.lw-cal-presets {
  display: flex; flex-direction: column; gap: 2px;
  padding-inline-end: var(--lw-space-12); border-inline-end: 1px solid var(--lw-line); min-width: 116px;
}
.lw-cal-preset {
  height: var(--lw-control-h-sm); padding: 0 var(--lw-space-8);
  border: 0; border-radius: var(--lw-radius-sm); background: none;
  color: var(--lw-fg); font-size: var(--lw-text-sm); text-align: start; cursor: pointer;
}
.lw-cal-preset:hover { background: var(--lw-bg-subtle); }
.lw-cal-preset:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
/* aria-current, not aria-pressed: a preset is a shortcut ACTION that applies a
   range and closes the panel. As toggles they announced four buttons, three of
   them "not pressed", for a set where usually none is current. */
.lw-cal-preset[aria-current="true"], .lw-cal-preset[aria-pressed="true"] { color: var(--lw-brand-text); font-weight: var(--lw-fw-medium); }

/* DATE FIELD — the trigger the popover hangs off. It carries .lw-input as well,
   so the box, the height, the focus ring and the disabled face are the shared
   control's; this adds only the row inside it. The value truncates rather than
   wrapping, because "1 Jan 2026 – 31 Mar 2026" outgrows the field long before it
   outgrows the reader's patience.
   Placeholder ink is a DATA state, not a second class: the element is the same
   element either way, and data-placeholder keeps the choice out of the caller. */
.lw-input.lw-datefield { display: flex; align-items: center; gap: var(--lw-space-8); text-align: start; cursor: pointer; }
/* Restated because .lw-input:disabled has the same specificity and loses on
   source order — a disabled field would otherwise keep the pointer cursor. */
.lw-input.lw-datefield:disabled { cursor: not-allowed; }
.lw-datefield .lw-datefield-ic { flex: none; color: var(--lw-fg-subtle); }
.lw-datefield .lw-datefield-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--lw-fg); }
.lw-datefield[data-placeholder="true"] .lw-datefield-text { color: var(--lw-fg-subtle); }

/* ---- Pagination -------------------------------------------------------------
   MOVED to base.css in v1.3.1 — see the header block.

   One ordering fact travelled with them and is restated at the new site:
   `.lw-pag-size { width: auto }` beats `.lw-select { width: 100% }` on SOURCE
   ORDER alone, not specificity. It won across the file boundary and it still
   wins inside base.css only because the forms block is far above it. */

/* ---- Toolbar + applied filters ----------------------------------------------
   Applied filters are shown as REMOVABLE chips, never only as state inside a
   panel the user has to reopen to read. A filter you cannot see is a filter you
   forget you set, and then the empty result looks like a broken product. */
.lw-toolbar { display: flex; align-items: center; gap: var(--lw-space-8); flex-wrap: wrap; }
.lw-toolbar .lw-toolbar-grow { flex: 1; min-width: 180px; }
.lw-filters { display: flex; align-items: center; gap: var(--lw-space-4); flex-wrap: wrap; }
.lw-filter-chip {
  display: inline-flex; align-items: center; gap: var(--lw-space-4);
  height: 24px; padding: 0 var(--lw-space-4) 0 var(--lw-space-8);
  border: 1px solid var(--lw-line); border-radius: var(--lw-radius-pill);
  background: var(--lw-bg-subtle); font-size: var(--lw-text-xs); color: var(--lw-fg);
}
.lw-filter-chip .k { font-family: var(--lw-font-mono); color: var(--lw-fg-subtle); }
.lw-filter-chip button {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--lw-fg-subtle); cursor: pointer;
}
.lw-filter-chip button:hover { background: var(--lw-bg-inset); color: var(--lw-fg); }
.lw-filter-chip button:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-filter-clear { border: 0; background: none; padding: 0 var(--lw-space-4); color: var(--lw-brand-text); font-size: var(--lw-text-xs); font-weight: var(--lw-fw-medium); cursor: pointer; }
.lw-filter-clear:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs); }

/* ---- File upload ------------------------------------------------------------
   A dropzone is a LABEL wrapping a real file input, so the click target, the
   keyboard path and the a11y name are the platform's and not a div with a
   handler. Drag is the enhancement; click and Enter are the control. */
.lw-dropzone {
  display: grid; place-items: center; gap: var(--lw-space-8);
  padding: var(--lw-space-32) var(--lw-space-24); text-align: center;
  border: 1px dashed var(--lw-line-strong); border-radius: var(--lw-radius-lg);
  background: var(--lw-bg-subtle); color: var(--lw-fg-muted); cursor: pointer;
  transition: border-color var(--lw-dur-sm) var(--lw-ease-out), background var(--lw-dur-sm) var(--lw-ease-out);
}
.lw-dropzone:hover { border-color: var(--lw-fg-subtle); }
.lw-dropzone[data-over="true"] { border-color: var(--lw-brand-500); background: var(--lw-brand-soft); color: var(--lw-brand-on); }
.lw-dropzone:focus-within { outline: none; box-shadow: var(--lw-focus-ring); border-color: var(--lw-brand-500); }
.lw-dropzone[data-disabled="true"] { background: var(--lw-bg-muted); color: var(--lw-fg-faint); cursor: not-allowed; }
.lw-dropzone input[type="file"] { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); }
.lw-dropzone .lw-dz-title { font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium); color: var(--lw-fg); }
.lw-dropzone .lw-dz-hint { font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); }
/* Spacing between the zone and the message it produced. A sibling relationship,
   not a class: the vanilla consumer writing <label class="lw-dropzone"> followed
   by <div class="lw-error"> gets the gap without knowing a second name. */
.lw-dropzone + .lw-error { margin-top: var(--lw-space-8); }
.lw-file-list { display: flex; flex-direction: column; }
.lw-file-row {
  display: flex; align-items: center; gap: var(--lw-space-12);
  min-height: var(--lw-row-h); padding: var(--lw-space-8) 0;
  border-bottom: 1px solid var(--lw-line); font-size: var(--lw-text-sm);
}
.lw-file-row:last-child { border-bottom: 0; }
.lw-file-row .lw-file-ic { flex: none; color: var(--lw-fg-subtle); }
.lw-file-row .lw-file-main { flex: 1; min-width: 0; display: grid; gap: 4px; }
.lw-file-row .lw-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lw-file-row .lw-file-meta { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); font-variant-numeric: tabular-nums; }
.lw-file-row .lw-file-bar { height: 3px; border-radius: var(--lw-radius-pill); background: var(--lw-bg-inset); overflow: hidden; }
.lw-file-row .lw-file-bar > i { display: block; height: 100%; width: var(--lw-file-pct, 0%); background: var(--lw-brand-500); border-radius: inherit; transition: width var(--lw-dur-md) var(--lw-ease-out); }
.lw-file-row[data-state="error"] .lw-file-meta { color: var(--lw-danger-on); }
.lw-file-row[data-state="error"] .lw-file-ic { color: var(--lw-danger-on); }
.lw-file-row[data-state="done"] .lw-file-ic { color: var(--lw-success-on); }

/* ---- DataGrid ---------------------------------------------------------------
   NOT an extension of .lw-table. A grid is a different contract: it owns its
   own scroll box, its header is sticky, its columns are resizable and pinnable,
   and it may be windowed. Table stays the simple static one — merging them
   would make every static table pay for machinery it never uses. */
.lw-dgrid { position: relative; border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg); background: var(--lw-bg); overflow: hidden; }
.lw-dgrid-scroll { overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--lw-line-strong) transparent; }
.lw-dgrid table { width: 100%; border-collapse: separate; border-spacing: 0; table-layout: fixed; font-size: var(--lw-text-sm); }
.lw-dgrid th, .lw-dgrid td {
  height: var(--lw-row-h); padding: 0 var(--lw-space-12);
  text-align: start; vertical-align: middle;
  border-bottom: 1px solid var(--lw-line);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lw-dgrid thead th {
  position: sticky; top: 0; z-index: var(--lw-z-local-2);
  height: var(--lw-control-h-md);
  background: var(--lw-bg-subtle); color: var(--lw-fg-subtle);
  font-size: var(--lw-text-xs); font-weight: var(--lw-fw-medium);
  border-bottom: 1px solid var(--lw-line-strong);
}
/* A pinned cell has to carry its own background or the scrolling rows show
   through it — sticky positions the box, it does not make it opaque. */
.lw-dgrid th[data-pin], .lw-dgrid td[data-pin] { position: sticky; z-index: var(--lw-z-local-1); background: var(--lw-bg); }
.lw-dgrid thead th[data-pin] { z-index: var(--lw-z-local-3); background: var(--lw-bg-subtle); }
/* The edge shadow that separates the pinned block from the scrolling one. There
   is no logical box-shadow, so the offset is flipped by direction rather than by
   property — the pinned block sits on the START edge, and in RTL that is the
   right-hand side of the box. */
.lw-dgrid th[data-pin-last="true"], .lw-dgrid td[data-pin-last="true"] { box-shadow: 1px 0 0 var(--lw-line); }
.lw-dgrid:dir(rtl) th[data-pin-last="true"], .lw-dgrid:dir(rtl) td[data-pin-last="true"] { box-shadow: -1px 0 0 var(--lw-line); }
[dir="rtl"] .lw-dgrid th[data-pin-last="true"], [dir="rtl"] .lw-dgrid td[data-pin-last="true"] { box-shadow: -1px 0 0 var(--lw-line); }
.lw-dgrid td.num, .lw-dgrid th.num { text-align: end; font-family: var(--lw-font-mono); font-variant-numeric: tabular-nums; }
.lw-dgrid tbody tr:last-child td { border-bottom: 0; }
.lw-dgrid tbody tr:hover td { background: var(--lw-bg-subtle); }
.lw-dgrid tbody tr:hover td[data-pin] { background: var(--lw-bg-subtle); }
.lw-dgrid tbody tr[aria-selected="true"] td { background: var(--lw-brand-soft); }
.lw-dgrid tbody tr[aria-selected="true"] td[data-pin] { background: var(--lw-brand-soft); }
.lw-dgrid-sort { display: inline-flex; align-items: center; gap: var(--lw-space-4); border: 0; background: none; padding: 0; color: inherit; font: inherit; cursor: pointer; }
.lw-dgrid-sort:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs); }
.lw-dgrid-sort[aria-sort] { color: var(--lw-fg); }
/* The resize handle is 9px wide and centred on the column edge: a 1px target is
   a target nobody hits, and the cursor has to change before the drag starts or
   the affordance is invisible. */
.lw-dgrid-resize {
  position: absolute; top: 0; inset-inline-end: -5px; width: 9px; height: 100%;
  cursor: col-resize; background: none; border: 0; padding: 0; z-index: var(--lw-z-local-4);
}
.lw-dgrid-resize:hover::after, .lw-dgrid-resize:focus-visible::after {
  content: ""; position: absolute; top: 0; inset-inline-start: 4px; width: 1px; height: 100%; background: var(--lw-brand-500);
}
.lw-dgrid th { position: relative; }
/* The checkbox holder centres the control in its column, so the label inside it
   is the box alone — no room for the gap .lw-check reserves for text it does not
   have here. */
.lw-dgrid-check { display: grid; place-items: center; width: 100%; }
.lw-dgrid-check .lw-check { gap: 0; }
/* The windowing spacers. They carry only a height and must contribute no ink:
   the cell padding and border would otherwise draw two extra hairlines at the
   ends of the visible slice, which reads as a missing row. */
.lw-dgrid tr.lw-dgrid-pad > td { padding: 0; border: 0; }
/* A row is only a pointer target when the caller gave it somewhere to go. */
.lw-dgrid tbody tr[data-clickable="true"] { cursor: pointer; }
.lw-dgrid-empty { padding: var(--lw-space-48) var(--lw-space-24); text-align: center; color: var(--lw-fg-subtle); font-size: var(--lw-text-sm); }
/* The selection bar REPLACES the toolbar rather than stacking under it: two rows
   of controls where one was is how a table jumps a row height on every click. */
.lw-dgrid-selbar {
  display: flex; align-items: center; gap: var(--lw-space-12);
  min-height: var(--lw-control-h-lg); padding: 0 var(--lw-space-12);
  background: var(--lw-brand-soft); border-bottom: 1px solid var(--lw-brand-line);
  font-size: var(--lw-text-sm); color: var(--lw-brand-text);
}
.lw-dgrid-selbar .count { font-family: var(--lw-font-mono); font-weight: var(--lw-fw-semibold); font-variant-numeric: tabular-nums; }

/* ---- Stepper -----------------------------------------------------------------
   The step number becomes a CHECK once done — the marker carries the state, so
   progress survives greyscale and does not depend on the connector's tint. */
.lw-stepper { display: flex; align-items: flex-start; gap: 0; width: 100%; }
.lw-stepper-step { flex: 1; display: flex; flex-direction: column; align-items: center; gap: var(--lw-space-8); position: relative; text-align: center; min-width: 0; }
.lw-stepper-step::before, .lw-stepper-step::after {
  content: ""; position: absolute; top: 13px; height: 1px; background: var(--lw-line-strong);
}
.lw-stepper-step::before { inset-inline: 0 50%; margin-inline-end: 20px; }
.lw-stepper-step::after { inset-inline: 50% 0; margin-inline-start: 20px; }
.lw-stepper-step:first-child::before, .lw-stepper-step:last-child::after { display: none; }
.lw-stepper-step[data-state="done"]::after, .lw-stepper-step[data-state="current"]::before { background: var(--lw-brand-500); }
.lw-stepper-step[data-state="done"]::before { background: var(--lw-brand-500); }
.lw-stepper-marker {
  width: 28px; height: 28px; flex: none; display: grid; place-items: center;
  border-radius: 50%; border: 1px solid var(--lw-line-strong);
  background: var(--lw-bg); color: var(--lw-fg-subtle);
  font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); font-variant-numeric: tabular-nums;
  position: relative; z-index: var(--lw-z-local-1);
}
.lw-stepper-step[data-state="done"] .lw-stepper-marker { background: var(--lw-brand-500); border-color: var(--lw-brand-500); color: var(--lw-on-brand); }
.lw-stepper-step[data-state="current"] .lw-stepper-marker { border-color: var(--lw-brand-500); color: var(--lw-brand-on); box-shadow: 0 0 0 3px var(--lw-brand-soft); }
.lw-stepper-step[data-state="error"] .lw-stepper-marker { background: var(--lw-danger); border-color: var(--lw-danger); color: var(--lw-on-danger); }
.lw-stepper-label { font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium); color: var(--lw-fg-muted); max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.lw-stepper-step[data-state="current"] .lw-stepper-label { color: var(--lw-fg); }
.lw-stepper-hint { font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); }
button.lw-stepper-step { border: 0; background: none; padding: 0; cursor: pointer; font: inherit; }
button.lw-stepper-step:disabled { cursor: default; }
button.lw-stepper-step:focus-visible { outline: none; }
button.lw-stepper-step:focus-visible .lw-stepper-marker { box-shadow: var(--lw-focus-ring); }
.lw-stepper-vertical { flex-direction: column; align-items: stretch; gap: var(--lw-space-16); }
.lw-stepper-vertical .lw-stepper-step { flex-direction: row; align-items: flex-start; gap: var(--lw-space-12); text-align: start; }
.lw-stepper-vertical .lw-stepper-step::before, .lw-stepper-vertical .lw-stepper-step::after { display: none; }

/* ---- Charts ------------------------------------------------------------------
   A thin tokenised layer, not a charting engine. The SVG carries role="img" and
   an accessible name, and every chart ships a data table behind .lw-sr-only —
   a picture of numbers is not readable, and a summary is not the data. */
.lw-chart { display: block; width: 100%; overflow: visible; }
.lw-chart-wrap { display: flex; flex-direction: column; gap: var(--lw-space-12); }
.lw-chart .grid line { stroke: var(--lw-chart-grid); stroke-width: 1; }
.lw-chart .axis { fill: var(--lw-chart-axis); font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); }
.lw-chart .bar { transition: opacity var(--lw-dur-xs) var(--lw-ease-out); }
.lw-chart .bar:hover { opacity: .82; }
.lw-chart .line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lw-chart .dot { stroke: var(--lw-bg); stroke-width: 2; }
.lw-chart-legend { display: flex; flex-wrap: wrap; gap: var(--lw-space-12); font-size: var(--lw-text-xs); color: var(--lw-fg-muted); }
.lw-chart-legend span { display: inline-flex; align-items: center; gap: var(--lw-space-4); }
.lw-chart-legend i { width: 9px; height: 9px; border-radius: 2px; flex: none; background: var(--lw-swatch); }
/* `--lw-swatch` is set inline by Legend (chart-parts.jsx); the paint stays here
   so the colour is a CSS declaration the contrast gate can see, not a JSX style. */

/* ---- Activity feed / notifications -------------------------------------------
   Unread is a DOT plus weight, never a tint alone: a row that is only slightly
   bluer than its neighbour is not a state anyone reads. */
.lw-feed { display: flex; flex-direction: column; }
.lw-feed-group { padding: var(--lw-space-12) var(--lw-space-4) var(--lw-space-4); font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.14em; text-transform: uppercase; word-spacing: var(--lw-word-spacing-caps); color: var(--lw-fg-subtle); }
.lw-feed-item {
  display: flex; align-items: flex-start; gap: var(--lw-space-12);
  width: 100%; padding: var(--lw-space-12) var(--lw-space-8);
  border: 0; border-bottom: 1px solid var(--lw-line); border-radius: var(--lw-radius-sm);
  background: none; color: var(--lw-fg); font: inherit; font-size: var(--lw-text-sm);
  text-align: start; text-decoration: none;
}
a.lw-feed-item, button.lw-feed-item { cursor: pointer; }
a.lw-feed-item:hover, button.lw-feed-item:hover { background: var(--lw-bg-subtle); }
.lw-feed-item:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-feed-item .lw-feed-ic { flex: none; display: grid; place-items: center; width: 28px; height: 28px; border-radius: var(--lw-radius-md); background: var(--lw-bg-inset); color: var(--lw-fg-muted); }
.lw-feed-item[data-tone="success"] .lw-feed-ic { background: var(--lw-success-soft); color: var(--lw-success-on); }
.lw-feed-item[data-tone="warning"] .lw-feed-ic { background: var(--lw-warning-soft); color: var(--lw-warning-on); }
.lw-feed-item[data-tone="danger"] .lw-feed-ic { background: var(--lw-danger-soft); color: var(--lw-danger-on); }
.lw-feed-main { flex: 1; min-width: 0; display: grid; gap: 2px; }
.lw-feed-title { line-height: 1.45; text-wrap: pretty; }
.lw-feed-meta { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); }
.lw-feed-item[data-unread="true"] .lw-feed-title { font-weight: var(--lw-fw-medium); }
.lw-feed-item[data-unread="true"]::before { content: ""; flex: none; width: 6px; height: 6px; margin-top: 11px; border-radius: 50%; background: var(--lw-brand-500); }
.lw-feed-item[data-unread="true"] { padding-inline-start: var(--lw-space-4); gap: var(--lw-space-8); }
.lw-feed-item:not([data-unread="true"])::before { content: ""; flex: none; width: 6px; margin-top: 11px; }

/* ---- Command palette ---------------------------------------------------------
   A dialog, not a popover: it is modal, it takes the whole keyboard, and it
   should make the page behind it inert. Same Radix `Dialog` shell and layer as
   `.lw-dialog` (v2.0.0); it hangs from the top of the viewport rather than
   centring, so the list grows downward as the query narrows it. */
.lw-cmdk { position: fixed; top: 12vh; inset-inline: 0; margin-inline: auto; z-index: var(--lw-z-local-2); display: flex; flex-direction: column; width: min(560px, 92vw); max-height: 60vh; padding: 0; border: 0; border-radius: var(--lw-radius-xl); background: var(--lw-bg); box-shadow: var(--lw-shadow-xl); overflow: hidden; }
@media (prefers-reduced-motion: no-preference) {
  .lw-cmdk[data-state="open"] { animation: lwDialogIn var(--lw-dur-sm) var(--lw-ease-out); }
}
.lw-cmdk-input { flex: none; display: flex; align-items: center; gap: var(--lw-space-12); padding: 0 var(--lw-space-16); height: 52px; border-bottom: 1px solid var(--lw-line); color: var(--lw-fg-subtle); }
.lw-cmdk-input input { flex: 1; border: 0; background: none; outline: none; font: inherit; font-size: var(--lw-text-body); color: var(--lw-fg); }
.lw-cmdk-input input::placeholder { color: var(--lw-fg-subtle); }
.lw-cmdk-list { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; padding: var(--lw-space-8); margin: 0; list-style: none; }
.lw-cmdk-foot { flex: none; display: flex; gap: var(--lw-space-16); padding: var(--lw-space-8) var(--lw-space-16); border-top: 1px solid var(--lw-line); font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-subtle); }

/* ---- Tool call ----------------------------------------------------------------
   AgentTrace says a step RAN; this says what it did. Collapsed by default,
   because the argument blob is evidence a user opens when the answer looks
   wrong — not something to read on every turn. */
.lw-tool { border: 1px solid var(--lw-line); border-radius: var(--lw-radius-md); background: var(--lw-bg-subtle); overflow: hidden; }
.lw-tool + .lw-tool { margin-top: var(--lw-space-8); }
.lw-tool-head {
  display: flex; align-items: center; gap: var(--lw-space-8); width: 100%;
  min-height: var(--lw-control-h-sm); padding: var(--lw-space-8) var(--lw-space-12);
  border: 0; background: none; color: var(--lw-fg); font: inherit; font-size: var(--lw-text-sm); text-align: start; cursor: pointer;
}
.lw-tool-head:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }
.lw-tool-name { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg); }
.lw-tool-sum { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--lw-fg-subtle); font-size: var(--lw-text-xs); }
.lw-tool-dur { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); font-variant-numeric: tabular-nums; }
.lw-tool-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--lw-line-strong); }
.lw-tool[data-state="running"] .lw-tool-dot { background: var(--lw-brand-500); }
.lw-tool[data-state="ok"] .lw-tool-dot { background: var(--lw-success-on); }
.lw-tool[data-state="error"] .lw-tool-dot { background: var(--lw-danger-on); }
@media (prefers-reduced-motion: no-preference) {
  .lw-tool[data-state="running"] .lw-tool-dot { animation: lwToolPulse var(--lw-dur-loop) var(--lw-ease-out) infinite; }
  @keyframes lwToolPulse { 50% { opacity: .35; } }
}
.lw-tool-body { padding: 0 var(--lw-space-12) var(--lw-space-12); display: grid; gap: var(--lw-space-8); }
.lw-tool-body pre { margin: 0; padding: var(--lw-space-8) var(--lw-space-12); border-radius: var(--lw-radius-sm); background: var(--lw-bg-inset); font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); line-height: 1.55; overflow-x: auto; color: var(--lw-fg-muted); }
.lw-tool-body .k { font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: .12em; text-transform: uppercase; color: var(--lw-fg-subtle); }
/* The error body is the ONE pre with an ink of its own. Arguments and results are
   neutral evidence; the error is the finding, and it is what the user opened the
   call to read. --lw-danger-on, not --lw-danger: this is text on the inset
   ground, and the fill tier does not carry AA there. */
.lw-tool-body pre.err { color: var(--lw-danger-on); }

/* ---- Diff review ---------------------------------------------------------------
   Accept/reject per hunk. The gutter carries the sign as WELL as the ground, so
   the diff reads in greyscale and to a colour-blind reader. */
.lw-diff { border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg); overflow: hidden; background: var(--lw-bg); }
.lw-diff-hunk + .lw-diff-hunk { border-top: 1px solid var(--lw-line); }
.lw-diff-head { display: flex; align-items: center; gap: var(--lw-space-8); padding: var(--lw-space-8) var(--lw-space-12); background: var(--lw-bg-subtle); border-bottom: 1px solid var(--lw-line); }
.lw-diff-ic { flex: none; color: var(--lw-fg-subtle); }
.lw-diff-file { flex: 1; min-width: 0; font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-muted); overflow: hidden; text-overflow: ellipsis; }
.lw-diff-lines { margin: 0; font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); line-height: 1.65; }
.lw-diff-line { display: grid; grid-template-columns: 34px 14px 1fr; align-items: baseline; }
.lw-diff-line .n { text-align: end; padding-inline-end: var(--lw-space-8); color: var(--lw-fg-muted); user-select: none; }
.lw-diff-line .s { text-align: center; color: var(--lw-fg-muted); user-select: none; }
.lw-diff-line .t { padding-inline-end: var(--lw-space-12); white-space: pre-wrap; word-break: break-word; color: var(--lw-fg); }
.lw-diff-line[data-kind="add"] { background: var(--lw-diff-add); box-shadow: inset 2px 0 0 var(--lw-diff-add-line); }
.lw-diff-line[data-kind="del"] { background: var(--lw-diff-del); box-shadow: inset 2px 0 0 var(--lw-diff-del-line); }
.lw-diff-line[data-kind="mod"] { background: var(--lw-diff-mod); box-shadow: inset 2px 0 0 var(--lw-diff-mod-line); }
.lw-diff-foot { display: flex; align-items: center; gap: var(--lw-space-8); padding: var(--lw-space-8) var(--lw-space-12); border-top: 1px solid var(--lw-line); }
.lw-diff-foot .lw-diff-state { flex: 1; font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); }
.lw-diff-hunk[data-decision="accepted"] { opacity: .72; }
.lw-diff-hunk[data-decision="rejected"] { opacity: .52; }

/* ---- Artifact panel -------------------------------------------------------------
   Generated output has a version and a way back. An artifact you cannot revert
   is a document the model can overwrite. */
.lw-artifact { display: flex; flex-direction: column; min-height: 0; border: 1px solid var(--lw-line); border-radius: var(--lw-radius-lg); background: var(--lw-bg); overflow: hidden; }
.lw-artifact-head { flex: none; display: flex; align-items: center; gap: var(--lw-space-8); padding: var(--lw-space-8) var(--lw-space-12); border-bottom: 1px solid var(--lw-line); background: var(--lw-bg-subtle); }
.lw-artifact-title { flex: 1; min-width: 0; font-size: var(--lw-text-sm); font-weight: var(--lw-fw-medium); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The leading glyph is a type marker, not a control: it recedes and never flexes.
   Named rather than matched as `> .lw-icon` — the head grows a second glyph the
   moment anyone adds a status dot, and a positional rule moves onto it silently. */
.lw-artifact-head .lw-artifact-ic { flex: none; color: var(--lw-fg-subtle); }
.lw-artifact-ver { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); font-variant-numeric: tabular-nums; }
.lw-artifact-body { flex: 1; min-height: 0; overflow: auto; padding: var(--lw-card-pad); }
.lw-artifact-foot { flex: none; display: flex; align-items: center; gap: var(--lw-space-8); padding: var(--lw-space-8) var(--lw-space-12); border-top: 1px solid var(--lw-line); }

/* ---- Feedback ------------------------------------------------------------------- */
.lw-feedback { display: flex; align-items: center; gap: var(--lw-space-4); }
.lw-feedback .lw-icon-btn[aria-pressed="true"] { color: var(--lw-brand-on); background: var(--lw-brand-soft); }
.lw-feedback-note { font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); }
/* The correction path. A rating with nowhere to say what was wrong collects a
   number nobody can act on, so the form is part of the component, not an add-on
   the product bolts beside it. */
.lw-feedback-form { display: grid; gap: var(--lw-space-8); margin-top: var(--lw-space-8); }
.lw-feedback-form .lw-feedback-actions { display: flex; gap: var(--lw-space-8); justify-content: flex-end; }

/* ---- State views ------------------------------------------------------------------
   Empty, loading, error, offline and no-permission are ONE set. Shipping only the
   empty one is how the other four get invented per product, each with its own
   tone and its own idea of whether there is an action. */
.lw-state { display: grid; justify-items: center; gap: var(--lw-space-12); padding: var(--lw-space-48) var(--lw-space-24); text-align: center; }
.lw-state-ic { display: grid; place-items: center; width: 44px; height: 44px; border-radius: var(--lw-radius-lg); background: var(--lw-bg-inset); color: var(--lw-fg-subtle); }
.lw-state[data-variant="error"] .lw-state-ic { background: var(--lw-danger-soft); color: var(--lw-danger-on); }
.lw-state[data-variant="offline"] .lw-state-ic, .lw-state[data-variant="denied"] .lw-state-ic { background: var(--lw-warning-soft); color: var(--lw-warning-on); }
.lw-state-title { font-size: var(--lw-text-body); font-weight: var(--lw-fw-semibold); color: var(--lw-fg); }
.lw-state-desc { max-width: 46ch; font-size: var(--lw-text-sm); color: var(--lw-fg-muted); line-height: 1.6; text-wrap: pretty; }
.lw-state-actions { display: flex; gap: var(--lw-space-8); flex-wrap: wrap; justify-content: center; }
/* Loading is the one variant that is not a centred message: it is a skeleton of
   the content that is coming, so it stretches to the column and reads from the
   start edge like the prose it stands in for. Centring a skeleton makes the real
   content appear to jump sideways when it lands. */
.lw-state[data-variant="loading"] { justify-items: stretch; text-align: start; }

/* ---- Job row (export / long-running work) --------------------------------------
   The status drives ONE data attribute on the row and the tints hang off it, so a
   template never has to compute a colour — a colour computed in a logic class is
   a colour the stylesheet cannot re-point on the dark ground. */
.lw-job-ic { flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: var(--lw-radius-md); background: var(--lw-bg-inset); color: var(--lw-fg-subtle); }
.lw-job[data-status="running"] .lw-job-ic { background: var(--lw-brand-soft); color: var(--lw-brand-on); }
.lw-job[data-status="done"] .lw-job-ic { background: var(--lw-success-soft); color: var(--lw-success-on); }
.lw-job[data-status="failed"] .lw-job-ic { background: var(--lw-danger-soft); color: var(--lw-danger-on); }
.lw-job-meta { font-family: var(--lw-font-mono); font-size: var(--lw-text-xs); color: var(--lw-fg-subtle); font-variant-numeric: tabular-nums; }
/* Determinate progress. The rail is complete on its own — an empty rail before the
   first value is a correct rendering, not a broken one — and `Progress` writes the
   fill width inline, because a percentage is a live value and belongs to the
   component that owns it rather than to a caller's style attribute. */
.lw-progress { display: block; width: 100%; height: 3px; border-radius: var(--lw-radius-pill); background: var(--lw-bg-inset); overflow: hidden; }
.lw-progress > i { display: block; height: 100%; width: 0; border-radius: inherit; background: var(--lw-brand-500); transition: width var(--lw-dur-md) var(--lw-ease-out); }
.lw-progress[data-tone="success"] > i { background: var(--lw-success-on); }
.lw-progress[data-tone="warning"] > i { background: var(--lw-warning-on); }
.lw-progress[data-tone="danger"] > i { background: var(--lw-danger-on); }
@media (prefers-reduced-motion: reduce) { .lw-progress > i { transition: none; } }
.lw-job[data-status="failed"] .lw-job-meta { color: var(--lw-danger-on); }

/* ---- Password strength ---------------------------------------------------------
   Four segments filled from a data attribute. Never colour alone: the label beside
   it says the word, because "three green bars" means nothing without a scale. */
/* ONE-TIME CODE — one input that LOOKS segmented. See OtpInput.jsx for why it
   is not six boxes. The tracking is what does the work; `ch` so the spacing
   follows the mono figure width rather than a guessed pixel value, and the
   inline-start padding cancels the trailing space the last character carries so
   the string still reads centred. */
.lw-otp { width: 100%; height: var(--lw-control-h-lg); padding: 0 var(--lw-field-pad-x); padding-inline-start: calc(var(--lw-field-pad-x) + 0.34ch); background: var(--lw-bg); border: 1px solid var(--lw-line-control); border-radius: var(--lw-radius-sm); font-family: var(--lw-font-mono); font-size: var(--lw-text-h3); letter-spacing: 0.34ch; text-align: center; color: var(--lw-fg); transition: border-color var(--lw-dur-xs) var(--lw-ease-out), box-shadow var(--lw-dur-xs) var(--lw-ease-out); }
.lw-otp:focus-visible { outline: none; border-color: var(--lw-brand-500); box-shadow: var(--lw-focus-ring); }
.lw-otp[aria-invalid="true"] { border-color: var(--lw-danger-on); }
.lw-otp[aria-invalid="true"]:focus-visible { box-shadow: var(--lw-focus-ring-danger); }

/* PASSWORD — the reveal sits inside the group, so the group owns the ring. */
.lw-pw-caps { min-height: 1lh; color: var(--lw-warning-on); }
.lw-pw-caps:not([data-on]) { visibility: hidden; }
.lw-pw-strength { display: flex; flex-direction: column; gap: var(--lw-space-4); }
.lw-pw-strength .lw-help { display: flex; align-items: center; gap: 6px; }
.lw-pwmeter { display: flex; gap: 4px; }
.lw-pwmeter span { flex: 1; height: 3px; border-radius: var(--lw-radius-pill); background: var(--lw-bg-inset); }
.lw-pwmeter[data-level="1"] span:nth-child(-n+1) { background: var(--lw-danger); }
.lw-pwmeter[data-level="2"] span:nth-child(-n+2) { background: var(--lw-warning); }
.lw-pwmeter[data-level="3"] span:nth-child(-n+3) { background: var(--lw-success); }
.lw-pwmeter[data-level="4"] span:nth-child(-n+4) { background: var(--lw-success); }

/* ---- Bottom navigation (mobile) ---------------------------------------------------
   The touch answer to the sidebar. Three to five destinations, never actions — a
   tab bar that mixes "Home" with "New" teaches nobody where they are.

   It reserves the home indicator with --lw-safe-bottom rather than a magic 34px:
   the inset is 0 on a device without one, so the same rule is correct everywhere. */
.lw-bottom-nav {
  position: sticky; bottom: 0; z-index: var(--lw-z-nav);
  display: flex; align-items: stretch;
  height: calc(var(--lw-bottom-nav-h) + var(--lw-safe-bottom));
  padding-bottom: var(--lw-safe-bottom);
  background: hsl(var(--lw-bg-c) / 0.92); backdrop-filter: blur(8px);
  border-top: 1px solid var(--lw-line);
}
.lw-bottom-nav a, .lw-bottom-nav button {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  /* 44px minimum comes from the bar height, not from padding — a target that
     depends on padding shrinks the moment someone tightens the label. */
  min-height: 44px; padding: 0 var(--lw-space-4);
  border: 0; background: none; color: var(--lw-fg-subtle);
  font: inherit; font-size: var(--lw-text-2xs); font-weight: var(--lw-fw-medium);
  text-decoration: none; cursor: pointer;
}
.lw-bottom-nav [aria-current="page"] { color: var(--lw-brand-text); }
.lw-bottom-nav a:focus-visible, .lw-bottom-nav button:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-sm); }
.lw-bottom-nav .lw-bn-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
/* A mobile shell reserves the notch at the top the same way. */
.lw-mobile-top { padding-top: var(--lw-safe-top); }
/* PHYSICAL ON PURPOSE — do not "fix" these to padding-inline. `env(safe-area-
   inset-left/right)` describe a notch and a rounded corner, which are physical
   features of the panel and do not swap when the writing direction does. Pairing
   a logical padding with a physical inset would put the gap on the wrong edge in
   RTL, which is the bug, not the fix. */
.lw-safe-x { padding-left: var(--lw-safe-left); padding-right: var(--lw-safe-right); }

/* ---- Rich text -------------------------------------------------------------------
   The CHROME is the system's; the editing engine is not. This is a toolbar and a
   prose surface sized on the type scale — swap the engine underneath (TipTap,
   ProseMirror) without the toolbar changing, which is the only way an editor
   stops being a fork of the design system. */
.lw-editor { display: flex; flex-direction: column; border: 1px solid var(--lw-line-control); border-radius: var(--lw-radius-md); background: var(--lw-bg); overflow: hidden; }
.lw-editor:focus-within { border-color: var(--lw-brand-500); box-shadow: var(--lw-focus-ring); }
.lw-editor-bar { flex: none; display: flex; align-items: center; gap: 2px; flex-wrap: wrap; padding: var(--lw-space-4) var(--lw-space-4); border-bottom: 1px solid var(--lw-line); background: var(--lw-bg-subtle); }
.lw-editor-bar .sep { width: 1px; height: 18px; margin: 0 var(--lw-space-4); background: var(--lw-line); }
.lw-editor-bar .lw-icon-btn[aria-pressed="true"] { background: var(--lw-brand-soft); color: var(--lw-brand-on); }
/* The body IS `.lw-prose` (v2.0.0): RichText emits `lw-prose lw-editor-body`,
   so the type, the rhythm, the headings, lists, quotes, code and links are the
   read surface's rules in base.css — one home. Through v1.x this block carried
   a second, slightly different copy of all of it (REVIEW open item 5). What is
   left here is the EDITOR delta only: the box, the caret and the placeholder.
   The prose measure is lifted because an editor fills its frame. */
.lw-editor-body {
  --lw-prose-max: none;
  padding: var(--lw-space-16); min-height: 160px; max-height: 420px; overflow: auto;
  outline: none; caret-color: var(--lw-brand-500);
}
/* Prose puts 40px above every heading, first child included — right for an
   article under a title, wrong for a box whose first line IS the heading. */
.lw-editor-body > :first-child { margin-block-start: 0; }
.lw-editor-body:empty::before { content: attr(data-placeholder); color: var(--lw-fg-subtle); }
.lw-editor-foot { flex: none; display: flex; align-items: center; gap: var(--lw-space-12); padding: var(--lw-space-4) var(--lw-space-12); border-top: 1px solid var(--lw-line); font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); color: var(--lw-fg-subtle); }

/* ---- App bar -----------------------------------------------------------------
   The brand + breadcrumb holder, which five templates each hand-wrote and two of
   them got wrong. It is a class rather than five copies of an inline style for
   the reason README rule 9 gives: two treatments of one thing is a bug that takes
   a year to surface, because nothing renders both side by side until someone
   builds a page that does.

   `flex: 0 1 auto`, not `flex: 1`. TopBar ships its own `.spacer { flex: 1 }`, so
   a holder that also claims the free space splits it 50/50 — the holder ends up
   narrower than its content and .lw-crumbs pays for the shortfall by ellipsising
   the last crumb while 178px of the row sits empty. Taking content width lets the
   spacer absorb all the slack, and nothing truncates until the row genuinely runs
   out. */
.lw-appbar-lead {
  order: -1;
  display: flex; align-items: center; gap: var(--lw-space-12);
  flex: 0 1 auto; min-width: 0; margin-inline-end: var(--lw-space-16);
  overflow: hidden; white-space: nowrap;
}
/* The brand never shrinks and never wraps: it is the shortest thing in the row and
   the first thing to read, so it is the wrong place to recover space from. */
.lw-appbar-brand {
  flex: none; display: flex; align-items: center; gap: 9px; white-space: nowrap;
  font-size: var(--lw-text-sm); font-weight: var(--lw-fw-semibold);
  letter-spacing: -0.012em; color: var(--lw-brand-text); text-decoration: none;
}
.lw-appbar-brand:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs); }

/* Editor chrome that was inline in RichText.jsx. Styling lives in the CSS layer:
   that is what keeps the React and vanilla consumers from drifting, and an
   inline style cannot be re-pointed by a theme or a density. */
.lw-editor-glyph { font-size: 13px; font-weight: var(--lw-fw-medium); }
.lw-editor-glyph[data-glyph="bold"] { font-weight: var(--lw-fw-bold); }
.lw-editor-glyph[data-glyph="italic"] { font-style: italic; }
.lw-editor-count[data-over="true"] { color: var(--lw-danger-on); }

/* Skeleton's multi-line form. Was an inline flex column plus an inline 62% on the
   last line — both static styling, and the inline `className` was dropped
   entirely on that branch. The short last line is what makes a skeleton read as
   text rather than as three bars. */
.lw-skeleton-lines { display: flex; flex-direction: column; gap: var(--lw-space-4); }
.lw-skeleton-lines > .lw-skeleton { width: 100%; }
.lw-skeleton-lines > .lw-skeleton:last-child { width: 62%; }


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

/* Theme toggle: one glyph at a time - the mode a click BRINGS, not the one
   you are in. The script flips .is-dark on the button. */
#theme-toggle .ic-sun, #theme-toggle .ic-moon { display: none; line-height: 0; }
#theme-toggle.is-dark .ic-sun { display: inline-flex; }
#theme-toggle:not(.is-dark) .ic-moon { display: inline-flex; }

/* ---- Shell: the console frame, four sections instead of six --------------
   ONE grid. The rail is a full-height column spanning every row; the topbar
   and footer live in the content column only. STRUCTURE IS LOAD-BEARING:
   every "~" rule below needs the radios, the navs and all four .tabpanel
   sections to be DIRECT CHILDREN of .shell, in that order. Wrap any of them
   one level deeper and the panels stay display:none for every reader with
   JS on, while the markup still contains every word a test greps for. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
body { display: flex; flex-direction: column; min-height: 100dvh; }
main { flex: 1; width: 100%; }
.shell {
  display: grid;
  grid-template-columns: var(--lw-sidebar-w, 236px) minmax(0, 1fr);
  grid-template-rows: var(--lw-topbar-h) minmax(0, 1fr) auto;
  min-height: 100dvh;
}
.shell > input { position: absolute; opacity: 0; pointer-events: none; }
.shell > .lw-sidebar {
  grid-column: 1; grid-row: 1 / span 3;
  align-self: start; position: sticky; top: 0;
  height: 100dvh; overflow: auto;
}
.shell > .lw-topbar {
  grid-column: 2; grid-row: 1 / span 3;
  align-self: start; position: sticky; top: 0;
  padding-inline: var(--lw-pad-x);
}
.shell > .tabpanel {
  grid-column: 2; grid-row: 2; min-width: 0;
  padding: var(--lw-space-24) var(--lw-pad-x) var(--lw-space-48);
}
.shell > .shell-foot {
  grid-column: 2; grid-row: 3; padding: var(--lw-space-16) var(--lw-pad-x) var(--lw-space-24);
  color: var(--lw-fg-subtle); font-size: var(--lw-text-2xs);
}
.shell:has(.lw-sidebar[data-collapsed="true"]) { grid-template-columns: 60px minmax(0, 1fr); }
.lw-sidebar[data-collapsed="true"] .brand { justify-content: center; padding-inline: 0; }
.lw-sidebar[data-collapsed="true"] .brand .brand-name { display: none; }
.shell:has(.lw-sidebar[data-collapsed="true"]) #sidebar-toggle svg { transform: rotate(0deg); }
#sidebar-toggle svg { transition: transform var(--lw-dur-sm) var(--lw-ease-out); transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { #sidebar-toggle svg { transition: none; } }
.no-js #sidebar-toggle { display: none; }
.lw-sidebar .brand {
  display: flex; align-items: center; gap: 9px; padding: 4px 10px 14px;
  font-size: 0.875rem; font-weight: 600; letter-spacing: -0.012em;
  color: var(--lw-brand-text); text-decoration: none; white-space: nowrap;
}
.lw-sidebar .brand svg { display: block; flex: none; }
.lw-sidebar .brand:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); border-radius: var(--lw-radius-xs); }
.shell .lw-nav-item { -webkit-user-select: none; user-select: none; }
.crumbs { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0;
  font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs); letter-spacing: 0.08em; }
.crumb-here { color: var(--lw-fg); font-weight: var(--lw-fw-medium); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.shell > .lw-bottom-nav { display: none; }
.shell > .lw-bottom-nav label {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  min-height: 44px; padding: 0 var(--lw-space-4);
  border-radius: var(--lw-radius-sm);
  color: var(--lw-fg-subtle);
  font: inherit; font-size: var(--lw-text-2xs); font-weight: var(--lw-fw-medium);
  text-align: center; text-decoration: none; cursor: pointer;
}
#tab-overview:checked ~ .lw-sidebar .lw-nav-item[for="tab-overview"],
#tab-categories:checked ~ .lw-sidebar .lw-nav-item[for="tab-categories"],
#tab-merchants:checked ~ .lw-sidebar .lw-nav-item[for="tab-merchants"],
#tab-installments:checked ~ .lw-sidebar .lw-nav-item[for="tab-installments"] {
  background: var(--lw-brand-soft); color: var(--lw-brand-on); font-weight: var(--lw-fw-medium);
}
#tab-overview:checked ~ .lw-bottom-nav [for="tab-overview"],
#tab-categories:checked ~ .lw-bottom-nav [for="tab-categories"],
#tab-merchants:checked ~ .lw-bottom-nav [for="tab-merchants"],
#tab-installments:checked ~ .lw-bottom-nav [for="tab-installments"] { color: var(--lw-brand-text); }
#tab-overview:checked ~ #panel-overview,
#tab-categories:checked ~ #panel-categories,
#tab-merchants:checked ~ #panel-merchants,
#tab-installments:checked ~ #panel-installments { display: block; }
#tab-overview:focus-visible ~ .lw-sidebar [for="tab-overview"],
#tab-overview:focus-visible ~ .lw-bottom-nav [for="tab-overview"],
#tab-categories:focus-visible ~ .lw-sidebar [for="tab-categories"],
#tab-categories:focus-visible ~ .lw-bottom-nav [for="tab-categories"],
#tab-merchants:focus-visible ~ .lw-sidebar [for="tab-merchants"],
#tab-merchants:focus-visible ~ .lw-bottom-nav [for="tab-merchants"],
#tab-installments:focus-visible ~ .lw-sidebar [for="tab-installments"],
#tab-installments:focus-visible ~ .lw-bottom-nav [for="tab-installments"] { outline: none; box-shadow: var(--lw-focus-ring); }
.tabpanel { display: none; }
@media (max-width: 1023.98px) {
  .shell { display: block; }
  .shell > .lw-sidebar { display: none; }
  #sidebar-toggle { display: none; }
  .shell > .lw-topbar nav { display: flex; }
  .shell > .lw-bottom-nav { display: flex; position: fixed; inset-inline: 0; bottom: 0; }
  .shell { padding-bottom: calc(var(--lw-bottom-nav-h) + var(--lw-safe-bottom)); }
  .shell > .tabpanel { padding: var(--lw-space-16) var(--lw-pad-x) var(--lw-space-32); }
}
/* Scripting off: nothing can check a radio, so show every panel rather than
   trapping the reader on one. */
.no-js .shell { display: block; }
.no-js .shell > .lw-sidebar, .no-js .shell > .lw-bottom-nav { display: none; }
.no-js .shell > .lw-topbar { position: static; }
.no-js .tabpanel { display: block; }

/* ---- The signed-out / error column --------------------------------------
   The design system ships controls, not page layout: .wrap and the auth card
   are this app's, the same way mcp-manage owns its own. */
.wrap { width: 100%; max-width: 28rem; margin: 0 auto; padding: 0 var(--lw-space-16); }
.auth-pad { padding: var(--lw-space-48) 0 var(--lw-space-48); }
.auth-card { margin: 0; }
/* h1 inside the card is a card title, not a hero: the base layer's display
   size is set for a marketing page. */
.auth-card h1 { font-size: var(--lw-text-xl); margin: 0 0 var(--lw-space-8); letter-spacing: -0.015em; }
.auth-card p { color: var(--lw-fg-muted); font-size: var(--lw-text-sm); margin: 0 0 var(--lw-space-16); }
.auth-card .asof { display: block; margin-bottom: var(--lw-space-4); }
.auth-card .lw-btn { width: 100%; }

/* ---- Pointer affordance --------------------------------------------------
   The design system gives .lw-nav-item and .lw-bottom-nav a/button a hand,
   but not buttons, selects, or the bottom-nav LABELS this shell uses in place
   of links - and a control that does not say it is clickable reads as decor.
   Listed explicitly rather than blanket-applied: a hand on non-interactive
   text is the opposite mistake. */
.lw-btn, .lw-icon-btn, button, summary, label[for], select, .chip {
  cursor: pointer;
}
button:disabled, .lw-btn:disabled, select:disabled { cursor: not-allowed; }
/* The monthly-spend bars open that month in the table below. Nothing else on
   a canvas is clickable, so the hand goes on that one chart only. */
#cByCard { cursor: pointer; }

/* ---- The account area in the topbar -------------------------------------- */
.spacer { flex: 1; }
.who {
  font-family: var(--lw-font-mono); font-size: var(--lw-text-2xs);
  color: var(--lw-fg-muted); max-width: 18ch; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.header-nav { display: flex; align-items: center; gap: var(--lw-space-8); }
.header-nav form { margin: 0; }

/* ---- Data surfaces -------------------------------------------------------
   Cards, chips and buttons come from the system (.lw-card, .lw-chip,
   .lw-btn); what follows is only the arrangement this dashboard needs. */
.section-head {
  display: flex; align-items: baseline; gap: var(--lw-space-12);
  flex-wrap: wrap; margin: 0 0 var(--lw-space-12);
}
.section-head h2 { margin: 0; font-size: var(--lw-text-sm); font-weight: var(--lw-fw-semibold); }
.asof { color: var(--lw-fg-subtle); font-size: var(--lw-text-2xs); font-family: var(--lw-font-mono); }
.loading { color: var(--lw-fg-muted); font-size: var(--lw-text-sm); padding: var(--lw-space-32) 0; text-align: center; }

.filters { display: flex; gap: var(--lw-space-8); align-items: center; flex-wrap: wrap;
  margin-bottom: var(--lw-space-12); }
.chip {
  border: 1px solid var(--lw-line); background: var(--lw-bg-subtle); color: var(--lw-fg-muted);
  border-radius: var(--lw-radius-full, 999px); padding: 0 var(--lw-space-12);
  min-height: 34px; cursor: pointer; font: inherit; font-size: var(--lw-text-2xs);
}
.chip:hover { border-color: var(--lw-line-strong); color: var(--lw-fg); }
.chip[aria-pressed="true"] {
  border-color: var(--lw-brand-text); background: var(--lw-brand-soft);
  color: var(--lw-brand-on); font-weight: var(--lw-fw-medium);
}
.chip:focus-visible { outline: none; box-shadow: var(--lw-focus-ring); }

.coverage { display: flex; gap: var(--lw-space-8); flex-wrap: wrap; margin: 0 0 var(--lw-space-16); }
.cov-chip {
  font-size: var(--lw-text-2xs); color: var(--lw-fg-muted);
  background: var(--lw-bg-inset); border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-sm); padding: var(--lw-space-4) var(--lw-space-8);
}
.cov-chip b { color: var(--lw-fg); font-weight: var(--lw-fw-medium); }

.banner {
  background: var(--lw-danger-soft); border: 1px solid var(--lw-danger-on);
  color: var(--lw-fg); border-radius: var(--lw-radius-md);
  padding: var(--lw-space-8) var(--lw-space-12); font-size: var(--lw-text-2xs);
  margin-bottom: var(--lw-space-12);
}

.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--lw-space-12); margin-bottom: var(--lw-space-16); }
.tile {
  background: var(--lw-bg-subtle); border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-lg); padding: var(--lw-space-12) var(--lw-space-16);
}
.tile .label { color: var(--lw-fg-subtle); font-size: var(--lw-text-2xs); }
.tile .value { font-size: var(--lw-text-2xl); font-weight: var(--lw-fw-semibold);
  letter-spacing: -0.02em; margin-top: 2px; }
.tile .note { color: var(--lw-fg-muted); font-size: var(--lw-text-2xs); margin-top: 2px; }

.panel {
  background: var(--lw-bg-subtle); border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-lg); padding: var(--lw-space-16);
  margin-bottom: var(--lw-space-16);
}
.panel h2 { font-size: var(--lw-text-xs); margin: 0 0 var(--lw-space-8);
  color: var(--lw-fg-muted); font-weight: var(--lw-fw-semibold); }
.panel h2 .sub { color: var(--lw-fg-subtle); font-weight: var(--lw-fw-regular); }
.chart-box { position: relative; height: 300px; }
.chart-box.tall { height: 340px; }

table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
th { text-align: right; color: var(--lw-fg-subtle); font-weight: var(--lw-fw-medium);
  font-size: var(--lw-text-2xs); padding: var(--lw-space-4) var(--lw-space-8);
  border-bottom: 1px solid var(--lw-line); }
th:first-child, td:first-child { text-align: left; }
td { padding: var(--lw-space-4) var(--lw-space-8); border-bottom: 1px solid var(--lw-line);
  text-align: right; font-size: var(--lw-text-xs); }
tbody tr:hover td { background: var(--lw-bg-inset); }
.scroll { max-height: 420px; overflow-y: auto; overflow-x: auto; }
.scroll.short { max-height: 340px; }
.empty { color: var(--lw-fg-subtle); font-size: var(--lw-text-xs);
  padding: var(--lw-space-24) 0; text-align: center; }

.plans { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--lw-space-12); }
.plan { background: var(--lw-bg-subtle); border: 1px solid var(--lw-line);
  border-radius: var(--lw-radius-lg); padding: var(--lw-space-12) var(--lw-space-16); }
.plan .m { font-weight: var(--lw-fw-semibold); }
.plan .meta { color: var(--lw-fg-muted); font-size: var(--lw-text-2xs); margin: var(--lw-space-4) 0; }
.bar { height: 6px; border-radius: var(--lw-radius-sm); background: var(--lw-bg-inset);
  margin: var(--lw-space-8) 0 var(--lw-space-4); overflow: hidden; }
.bar i { display: block; height: 100%; background: var(--lw-brand-text); }
.conf { font-size: var(--lw-text-2xs); padding: 1px 7px; border-radius: var(--lw-radius-full, 999px);
  border: 1px solid var(--lw-line); color: var(--lw-fg-muted); }

/* Phones: two KPI tiles per row, shorter charts, and the transaction table
   re-flowed into stacked records - a six-column table is unreadable at 360px. */
@media (max-width: 40rem) {
  .kpis { grid-template-columns: 1fr 1fr; gap: var(--lw-space-8); }
  .tile { padding: var(--lw-space-8) var(--lw-space-12); }
  .tile .value { font-size: var(--lw-text-lg); }
  .tile .note { display: none; }
  .panel { padding: var(--lw-space-12); }
  .chart-box { height: 240px; }
  .chart-box.tall { height: 280px; }
  .plans { grid-template-columns: 1fr; }
  .coverage { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .coverage::-webkit-scrollbar { display: none; }
  .cov-chip { white-space: nowrap; flex: 0 0 auto; }
  .scroll { overflow-x: hidden; }
  .scroll.short { max-height: 60vh; }
  table.stack thead { display: none; }
  table.stack tr { display: grid; grid-template-columns: auto 1fr; gap: 0 var(--lw-space-8);
    padding: var(--lw-space-8) 0; border-bottom: 1px solid var(--lw-line); }
  table.stack td { border: none; padding: 1px 0; text-align: left; }
  table.stack td[data-l="amount"] { grid-column: 2; text-align: right;
    font-weight: var(--lw-fw-semibold); margin-top: -18px; }
  table.stack td[data-l="date"] { color: var(--lw-fg-subtle); font-size: var(--lw-text-2xs); }
  table.stack td[data-l="desc"] { grid-column: 1 / -1; }
  table.stack td[data-l="meta"] { grid-column: 1 / -1; color: var(--lw-fg-subtle);
    font-size: var(--lw-text-2xs); }
}
@media (min-width: 40.0625rem) { table.stack td[data-l="meta"] .sep { display: none; } }

/* ---- Chart series palette (the documented literal exception) --------------
   Eight categorical hues that must stay distinguishable FROM EACH OTHER,
   including under protanopia/deuteranopia/tritanopia - a job no ink/ground
   role performs. Validated with the dataviz validator against both surfaces:
   worst adjacent CVD delta-E 9.1 light / 8.4 dark (>=8), worst normal-vision
   19.6 / 19.3 (>=15). Re-run that validator before changing any of them. */
:root {
  --s1: #2a78d6; --s2: #eb6834; --s3: #1baf7a; --s4: #eda100;
  --s5: #e87ba4; --s6: #008300; --s7: #4a3aa7; --s8: #e34948;
  --series-other: #898781;
}
:root[data-theme="dark"] {
  --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
  --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
    --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
  }
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-latin-4.woff2.19f9c925.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-latin-ext-3.woff2.824f485b.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-vietnamese-2.woff2.8fa40e5d.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Geist Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/geist-mono-latin-10.woff2.684ad5b5.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/geist-mono-latin-ext-9.woff2.1a189eb9.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Geist Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/geist-mono-vietnamese-8.woff2.d39b6088.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}