/**
 * Keystone — Style Framework
 * ============================================================
 * Design tokens + component classes. Single self-contained file.
 *
 *   <link rel="stylesheet" href="keystone.css">
 *
 * Works standalone. Does NOT require keystone-icons.css or keystone.js.
 * All custom properties are namespaced --ks-*; all classes are .ks-*.
 *
 * Sections:
 *   1. Tokens (primitives + semantic)
 *   2. Reset / base
 *   3. Layout helpers
 *   4. Components
 */

/* ============================================================
   1. TOKENS
   ============================================================ */

:root {

  /* ---- Color primitives ---- */

  /* Primary — Steel blue (muted; no navy, no cyan) */
  --ks-blue-50:  #F1F4F8;
  --ks-blue-100: #E3E9F0;
  --ks-blue-200: #C9D4E1;
  --ks-blue-300: #A7B7CC;
  --ks-blue-400: #8198B5;
  --ks-blue-500: #627CA0;
  --ks-blue-600: #466A99;  /* signature steel blue */
  --ks-blue-600-rgb: 70 106 153;  /* #466A99 as an rgb() triplet for alpha compositing */
  --ks-blue-700: #3D5A82;
  --ks-blue-800: #34496A;
  --ks-blue-900: #2B3A53;
  --ks-blue-950: #20293A;

  /* Secondary ramp — warm grey (replaces cyan; no cyan anywhere) */
  --ks-cyan-50:  #F2F2F3;
  --ks-cyan-100: #E5E6E7;
  --ks-cyan-200: #D2D3D5;
  --ks-cyan-300: #B5B7BA;
  --ks-cyan-400: #909397;
  --ks-cyan-500: #74777B;
  --ks-cyan-600: #595D61;  /* warm grey */
  --ks-cyan-700: #4A4D51;
  --ks-cyan-800: #3C3F42;
  --ks-cyan-900: #303234;

  /* Accent — Teal */
  --ks-teal-50:  #ECFDF5;
  --ks-teal-100: #D1FAE5;
  --ks-teal-200: #A7F3D0;
  --ks-teal-300: #6EE7B7;
  --ks-teal-400: #34D399;
  --ks-teal-500: #10B981;
  --ks-teal-600: #059669;
  --ks-teal-700: #047857;
  --ks-teal-800: #065F46;
  --ks-teal-900: #064E3B;

  /* Neutral — Grey (true neutral, no blue cast) */
  --ks-slate-0:   #FFFFFF;
  --ks-slate-50:  #F6F6F7;
  --ks-slate-100: #ECECEE;
  --ks-slate-200: #DEDEE1;
  --ks-slate-300: #C6C6CB;
  --ks-slate-400: #95959C;
  --ks-slate-500: #6A6A72;
  --ks-slate-600: #515158;
  --ks-slate-700: #3D3D43;
  --ks-slate-800: #27272B;
  --ks-slate-900: #191A1E;
  --ks-slate-950: #0E0F12;

  /* ---- Semantic surface ----
     canvas = the recessed page; default = a raised surface that catches light.
     The tonal gap (canvas vs default) + shadow is what separates them. */
  --ks-canvas:           #E9E9EC;
  --ks-surface-default:  var(--ks-slate-0);
  --ks-surface-subtle:   var(--ks-slate-50);
  --ks-surface-muted:    var(--ks-slate-100);
  --ks-surface-emphasis: var(--ks-slate-200);
  --ks-surface-inverse:  var(--ks-slate-900);
  --ks-surface-overlay:  rgba(14, 15, 18, 0.5);
  /* Flat: no sheen gradient (kept as a token so component rules stay simple) */
  --ks-surface-sheen: none;
  /* Neutral page wash — a faint light pool at the top, no colour cast */
  --ks-canvas-wash: radial-gradient(110% 75% at 50% 0%, rgb(255 255 255 / 0.55), transparent 62%);

  /* ---- Semantic text ---- */
  --ks-text-default:    var(--ks-slate-900);
  --ks-text-muted:      var(--ks-slate-500);
  --ks-text-subtle:     var(--ks-slate-400);
  --ks-text-disabled:   var(--ks-slate-300);
  --ks-text-inverse:    var(--ks-slate-0);
  --ks-text-link:       var(--ks-blue-800);
  --ks-text-link-hover: var(--ks-blue-900);
  --ks-text-on-brand:   var(--ks-slate-0);

  /* ---- Semantic border ---- */
  --ks-border-default:  var(--ks-slate-200);
  --ks-border-muted:    var(--ks-slate-100);
  --ks-border-emphasis: var(--ks-slate-300);
  --ks-border-focus:    var(--ks-blue-700);
  --ks-border-brand:    var(--ks-blue-700);

  /* ---- Brand ---- */
  --ks-brand-primary:        var(--ks-blue-700);
  --ks-brand-primary-hover:  var(--ks-blue-800);
  --ks-brand-primary-active: var(--ks-blue-900);
  --ks-brand-primary-subtle: var(--ks-blue-50);
  --ks-brand-primary-muted:  var(--ks-blue-100);

  /* Secondary = warm grey (pairs with the steel-blue primary) */
  --ks-brand-secondary:        #595D61;
  --ks-brand-secondary-hover:  #4A4D51;
  --ks-brand-secondary-active: #3C3F42;
  --ks-brand-secondary-subtle: #ECECEE;
  --ks-brand-secondary-muted:  #D8D9DB;

  --ks-brand-accent:        #2C8474;  /* muted teal (was teal-600 #059669) — calmer, pairs with steel */
  --ks-brand-accent-hover:  #24705F;  /* was teal-700 #047857 */
  --ks-brand-accent-active: #1C594B;  /* was teal-800 #065F46 */
  /* subtle/muted DERIVE from the accent (see the re-declaration on
     ':root, [data-ks-accent]' just below the accent presets) so every preset
     and any custom --ks-brand-accent gets matching tints for free, in light
     and dark. Alpha-over-surface reads on both themes. */
  --ks-brand-accent-subtle: color-mix(in srgb, var(--ks-brand-accent) 14%, transparent);
  --ks-brand-accent-muted:  color-mix(in srgb, var(--ks-brand-accent) 26%, transparent);

  /* ---- State ---- */
  --ks-success-default: #10B981;
  --ks-success-hover:   #059669;
  --ks-success-surface: #ECFDF5;
  --ks-success-text:    #065F46;
  --ks-success-border:  #6EE7B7;

  --ks-warning-default: #F59E0B;
  --ks-warning-hover:   #D97706;
  --ks-warning-surface: #FFFBEB;
  --ks-warning-text:    #92400E;
  --ks-warning-border:  #FCD34D;

  --ks-error-default: #D9564F;  /* muted danger red (was #EF4444) */
  --ks-error-hover:   #C44840;  /* was #DC2626 */
  --ks-error-surface: #FEF2F2;
  --ks-error-text:    #7F1D1D;
  --ks-error-border:  #FECACA;

  --ks-info-default: #3B82F6;
  --ks-info-hover:   #2563EB;
  --ks-info-surface: #EFF6FF;
  --ks-info-text:    #1E3A8A;
  --ks-info-border:  #BFDBFE;

  /* ---- Typography ---- */
  --ks-font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
  --ks-font-mono: ui-monospace, 'SFMono-Regular', 'SF Mono', 'Cascadia Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  --ks-text-xs:   0.75rem;
  --ks-text-sm:   0.875rem;
  --ks-text-base: 1rem;
  --ks-text-lg:   1.125rem;
  --ks-text-xl:   1.25rem;
  --ks-text-2xl:  1.5rem;
  --ks-text-3xl:  1.875rem;
  --ks-text-4xl:  2.25rem;
  --ks-text-5xl:  3rem;
  --ks-text-6xl:  3.75rem;
  --ks-text-7xl:  4.5rem;

  --ks-weight-regular:   400;
  --ks-weight-medium:    500;
  --ks-weight-semibold:  600;
  --ks-weight-bold:      700;
  --ks-weight-extrabold: 800;

  --ks-leading-none:    1;
  --ks-leading-tight:   1.25;
  --ks-leading-snug:    1.375;
  --ks-leading-normal:  1.5;
  --ks-leading-relaxed: 1.625;
  --ks-leading-loose:   2;

  --ks-tracking-tighter: -0.05em;
  --ks-tracking-tight:   -0.025em;
  --ks-tracking-normal:  0;
  --ks-tracking-wide:    0.025em;
  --ks-tracking-wider:   0.05em;
  --ks-tracking-widest:  0.1em;

  /* Type roles */
  --ks-type-display-xl-size:     var(--ks-text-7xl);
  --ks-type-display-xl-weight:   var(--ks-weight-extrabold);
  --ks-type-display-xl-leading:  var(--ks-leading-none);
  --ks-type-display-xl-tracking: var(--ks-tracking-tight);

  --ks-type-display-size:     var(--ks-text-5xl);
  --ks-type-display-weight:   var(--ks-weight-bold);
  --ks-type-display-leading:  var(--ks-leading-tight);
  --ks-type-display-tracking: var(--ks-tracking-tight);

  --ks-type-h1-size: var(--ks-text-4xl);  --ks-type-h1-weight: var(--ks-weight-bold);     --ks-type-h1-leading: var(--ks-leading-tight);
  --ks-type-h2-size: var(--ks-text-3xl);  --ks-type-h2-weight: var(--ks-weight-semibold); --ks-type-h2-leading: var(--ks-leading-snug);
  --ks-type-h3-size: var(--ks-text-2xl);  --ks-type-h3-weight: var(--ks-weight-semibold); --ks-type-h3-leading: var(--ks-leading-snug);
  --ks-type-h4-size: var(--ks-text-xl);   --ks-type-h4-weight: var(--ks-weight-semibold); --ks-type-h4-leading: var(--ks-leading-normal);
  --ks-type-h5-size: var(--ks-text-lg);   --ks-type-h5-weight: var(--ks-weight-semibold); --ks-type-h5-leading: var(--ks-leading-normal);
  --ks-type-h6-size: var(--ks-text-base); --ks-type-h6-weight: var(--ks-weight-semibold); --ks-type-h6-leading: var(--ks-leading-normal);

  --ks-type-body-lg-size:  var(--ks-text-lg);   --ks-type-body-lg-leading: var(--ks-leading-relaxed);
  --ks-type-body-size:     var(--ks-text-base); --ks-type-body-leading:    var(--ks-leading-normal);
  --ks-type-body-sm-size:  var(--ks-text-sm);   --ks-type-body-sm-leading: var(--ks-leading-normal);

  --ks-type-label-size:     var(--ks-text-sm);
  --ks-type-label-weight:   var(--ks-weight-medium);
  --ks-type-label-tracking: var(--ks-tracking-wide);

  --ks-type-caption-size:    var(--ks-text-xs);
  --ks-type-caption-leading: var(--ks-leading-normal);

  --ks-type-code-size: var(--ks-text-sm);

  /* ---- Spacing ---- */
  --ks-space-0:    0;
  --ks-space-px:   1px;
  --ks-space-0\.5: 0.125rem;
  --ks-space-1:    0.25rem;
  --ks-space-1\.5: 0.375rem;
  --ks-space-2:    0.5rem;
  --ks-space-2\.5: 0.625rem;
  --ks-space-3:    0.75rem;
  --ks-space-3\.5: 0.875rem;
  --ks-space-4:    1rem;
  --ks-space-5:    1.25rem;
  --ks-space-6:    1.5rem;
  --ks-space-7:    1.75rem;
  --ks-space-8:    2rem;
  --ks-space-10:   2.5rem;
  --ks-space-12:   3rem;
  --ks-space-14:   3.5rem;
  --ks-space-16:   4rem;
  --ks-space-20:   5rem;
  --ks-space-24:   6rem;
  --ks-space-32:   8rem;

  --ks-gap-xs:  var(--ks-space-1);
  --ks-gap-sm:  var(--ks-space-2);
  --ks-gap-md:  var(--ks-space-4);
  --ks-gap-lg:  var(--ks-space-6);
  --ks-gap-xl:  var(--ks-space-8);
  --ks-gap-2xl: var(--ks-space-12);

  --ks-pad-xs:  var(--ks-space-2);
  --ks-pad-sm:  var(--ks-space-3);
  --ks-pad-md:  var(--ks-space-4);
  --ks-pad-lg:  var(--ks-space-6);
  --ks-pad-xl:  var(--ks-space-8);

  /* ---- Radius ---- */
  /* Edgy, low-radius geometry — sharp but never fully square */
  --ks-radius-none: 0;
  --ks-radius-sm:   2px;
  --ks-radius-md:   3px;
  --ks-radius-lg:   4px;
  --ks-radius-xl:   5px;
  --ks-radius-2xl:  6px;
  --ks-radius-3xl:  8px;
  --ks-radius-full: 9999px;

  --ks-radius-button:  var(--ks-radius-md);
  --ks-radius-input:   var(--ks-radius-md);
  --ks-radius-card:    var(--ks-radius-xl);
  --ks-radius-badge:   var(--ks-radius-sm);
  --ks-radius-chip:    var(--ks-radius-md);
  --ks-radius-tooltip: var(--ks-radius-sm);
  --ks-radius-modal:   var(--ks-radius-2xl);

  /* ---- Depth & texture ----
     Surfaces are told apart by light, shade and elevation — not borders.
     Shadows are cool-tinted (slate, not pure black) and layered
     (a tight contact shadow + a soft ambient one) for realistic depth. */
  --ks-shadow-color: 24 24 28;   /* neutral dark, the shade source (no blue) */

  --ks-shadow-none:  none;
  --ks-shadow-xs:    0 1px 1px rgb(var(--ks-shadow-color) / 0.05);
  --ks-shadow-sm:    0 1px 2px rgb(var(--ks-shadow-color) / 0.05),
                     0 2px 6px -1px rgb(var(--ks-shadow-color) / 0.07);
  --ks-shadow-md:    0 1px 2px rgb(var(--ks-shadow-color) / 0.06),
                     0 6px 16px -2px rgb(var(--ks-shadow-color) / 0.10);
  --ks-shadow-lg:    0 2px 4px rgb(var(--ks-shadow-color) / 0.06),
                     0 12px 30px -4px rgb(var(--ks-shadow-color) / 0.14);
  --ks-shadow-xl:    0 4px 8px rgb(var(--ks-shadow-color) / 0.07),
                     0 24px 50px -8px rgb(var(--ks-shadow-color) / 0.20);
  --ks-shadow-2xl:   0 8px 16px rgb(var(--ks-shadow-color) / 0.10),
                     0 40px 80px -12px rgb(var(--ks-shadow-color) / 0.28);
  --ks-shadow-inner: inset 0 2px 4px rgb(var(--ks-shadow-color) / 0.07);
  --ks-shadow-brand: 0 6px 20px -4px rgb(38 119 171 / 0.45);

  /* Flat surfaces — no glossy top highlight (kept as no-op shadows so they
     stay valid inside box-shadow lists). */
  --ks-light-top:   inset 0 0 0 0 transparent;
  --ks-light-faint: inset 0 0 0 0 transparent;
  /* Crisp hairline edge used to define flat surfaces instead of gloss */
  --ks-edge:      inset 0 0 0 1px rgb(var(--ks-shadow-color) / 0.14);
  --ks-edge-soft: inset 0 0 0 1px rgb(var(--ks-shadow-color) / 0.10);
  /* Recessed well — flat inset for inputs/tracks */
  --ks-well: inset 0 0 0 1px rgb(var(--ks-shadow-color) / 0.16);
  /* Focus ring — hard and solid (no soft glow): a crisp brand ring with a gap */
  --ks-glow-focus: 0 0 0 2px var(--ks-surface-default), 0 0 0 4px var(--ks-blue-600);

  /* Film-grain noise — self-contained inline SVG turbulence, no assets.
     Used as an alpha MASK over an ink layer so it reads on any surface:
     dark speckle on light, light speckle on dark (see --ks-grain-ink). */
  --ks-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");
  --ks-grain-ink: 24 24 28;   /* dark grains on light surfaces (neutral) */
  --ks-grain-opacity: 0.05;

  /* ---- Border width ---- */
  --ks-border-width:   1px;
  --ks-border-width-2: 2px;
  --ks-border-width-4: 4px;

  /* ---- Z-index ---- */
  --ks-z-below:    -1;
  --ks-z-base:      0;
  --ks-z-raised:    1;
  --ks-z-dropdown:  1000;
  --ks-z-sticky:    1020;
  --ks-z-overlay:   1030;
  --ks-z-modal:     1040;
  --ks-z-popover:   1050;
  --ks-z-tooltip:   1060;

  /* ---- Transitions ---- */
  --ks-duration-instant: 50ms;
  --ks-duration-fast:    100ms;
  --ks-duration-normal:  200ms;
  --ks-duration-slow:    350ms;

  --ks-ease-default: cubic-bezier(0.4, 0, 0.2, 1);
  --ks-ease-in:      cubic-bezier(0.4, 0, 1, 1);
  --ks-ease-out:     cubic-bezier(0, 0, 0.2, 1);
  --ks-ease-spring:  cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Motion ladder, continued. Duration follows distance and importance:
     fast/normal for a control's own state, slow for an element entering,
     slower for things that travel (a sorted row), deliberate for DATA — a bar
     resizing or a ring filling is information, and the reader needs the time
     to see where it went. Emphasized = a long, decisive settle for big moves;
     exit accelerates away, because leaving needs no arrival. */
  --ks-duration-slower:     500ms;
  --ks-duration-deliberate: 700ms;
  --ks-ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
  --ks-ease-exit:       cubic-bezier(0.4, 0, 1, 1);
  /* Sequence: the gap between staggered siblings, and how far an entering
     element rises. Small on purpose — arrival, not a fly-in. */
  --ks-stagger-step: 40ms;
  --ks-rise:         8px;

  --ks-transition-colors:    color var(--ks-duration-fast) var(--ks-ease-default),
                             background-color var(--ks-duration-fast) var(--ks-ease-default),
                             border-color var(--ks-duration-fast) var(--ks-ease-default);
  --ks-transition-shadow:    box-shadow var(--ks-duration-normal) var(--ks-ease-default);
  --ks-transition-transform: transform var(--ks-duration-normal) var(--ks-ease-default);
  --ks-transition-all:       all var(--ks-duration-normal) var(--ks-ease-default);

  /* ---- Sizing ---- */
  --ks-icon-xs:   12px;
  --ks-icon-sm:   16px;
  --ks-icon-md:   20px;
  --ks-icon-base: 24px;
  --ks-icon-lg:   32px;
  --ks-icon-xl:   48px;

  --ks-control-height-xs: 24px;
  --ks-control-height-sm: 32px;
  --ks-control-height-md: 40px;
  --ks-control-height-lg: 48px;
  --ks-control-height-xl: 56px;

  /* ---- Density (Style-only) ----
     Scale control heights, padding and font for data-dense screens WITHOUT
     re-theming. Set data-ks-density="compact" | "comfortable" on <html> or ANY
     container; it cascades and nests (a compact <table> inside a comfortable
     page just works). Everything derives from a single --ks-density-scale, so
     the DEFAULT (attribute unset, scale 1) is pixel-identical to before:
     every calc(base * 1) === base, and every max(32px, base * 1) === base
     because the base control heights are already >= 32px.

     Only --ks-density-scale changes per mode. The derived tokens below CANNOT
     live on :root alone: a var() reference is resolved (substituted) once, in
     the scope where the property is DECLARED, and the resolved value then
     inherits down. So if the derived tokens were declared only on :root, their
     calc(... * var(--ks-density-scale)) would bake in scale=1 at :root and that
     fixed length would inherit into every descendant — a container that merely
     overrides --ks-density-scale would NOT re-resolve them, so its buttons and
     table padding would never change. The fix: RE-DECLARE the derived tokens on
     ':root, [data-ks-density]' (below). Any element carrying data-ks-density
     re-declares them, so their calc() re-resolves against THAT element's
     --ks-density-scale, and the freshly resolved lengths inherit to its
     descendants. That is what makes density work on any container and nest.

     Interactive tap targets are floored at 32px via max() so compact never
     drops a button/input below the accessible minimum, and focus rings (drawn
     with box-shadow) are untouched by any of this. */
  --ks-density-scale: 1;

  --ks-sidebar-width:     260px;
  --ks-topbar-height:     60px;
  --ks-content-max-width: 900px;
  --ks-prose-max-width:   65ch;
}

/* ===================================================================
   Accent themes — swappable accent color
   -------------------------------------------------------------------
   Set data-ks-accent="<name>" on <html> (or ANY container — it cascades
   and nests, since these only reset custom properties that inherit). Each
   preset overrides only the base/hover/active; the -subtle and -muted tints
   DERIVE from --ks-brand-accent via color-mix (declared on :root above), so
   every preset gets adaptive light+dark tints for free and any custom accent
   works the same way. White text reads on every base below.

   The PRIMARY steel-blue is the house identity and is intentionally NOT
   touched here. Only the accent role changes. The default (no attribute) is
   the muted teal set on :root above; "teal" is an explicit alias of it. */
[data-ks-accent="teal"]     { --ks-brand-accent: #2C8474; --ks-brand-accent-hover: #24705F; --ks-brand-accent-active: #1C594B; }
[data-ks-accent="ocean"]    { --ks-brand-accent: #2F7EA8; --ks-brand-accent-hover: #276A8E; --ks-brand-accent-active: #1F5474; }
[data-ks-accent="indigo"]   { --ks-brand-accent: #5560B4; --ks-brand-accent-hover: #48539C; --ks-brand-accent-active: #3B4482; }
[data-ks-accent="violet"]   { --ks-brand-accent: #7E5BB0; --ks-brand-accent-hover: #6C4D98; --ks-brand-accent-active: #593F7E; }
[data-ks-accent="plum"]     { --ks-brand-accent: #A15691; --ks-brand-accent-hover: #8A497C; --ks-brand-accent-active: #723C66; }
[data-ks-accent="rose"]     { --ks-brand-accent: #C05B77; --ks-brand-accent-hover: #A54D66; --ks-brand-accent-active: #883E53; }
[data-ks-accent="crimson"]  { --ks-brand-accent: #C0504A; --ks-brand-accent-hover: #A5433E; --ks-brand-accent-active: #883633; }
[data-ks-accent="amber"]    { --ks-brand-accent: #B5822F; --ks-brand-accent-hover: #9B6E27; --ks-brand-accent-active: #7F5A20; }
[data-ks-accent="green"]    { --ks-brand-accent: #5B9455; --ks-brand-accent-hover: #4D7E48; --ks-brand-accent-active: #3F683B; }
[data-ks-accent="graphite"] { --ks-brand-accent: #5E6B78; --ks-brand-accent-hover: #4F5B66; --ks-brand-accent-active: #414C55; }

/* Re-derive the tints in every accent scope. A var() inside a custom property
   is substituted once, in the scope where the property is DECLARED, then the
   resolved value inherits down (same mechanism as the density tokens). So if
   --ks-brand-accent-subtle lived only on :root, its color-mix would bake in the
   :root accent and a container carrying data-ks-accent would inherit the ROOT
   tint, not its own. Re-declaring the tints on every [data-ks-accent] element
   re-resolves color-mix against THAT element's --ks-brand-accent, so the tints
   scope and nest just like the base does. */
:root,
[data-ks-accent] {
  --ks-brand-accent-subtle: color-mix(in srgb, var(--ks-brand-accent) 14%, transparent);
  --ks-brand-accent-muted:  color-mix(in srgb, var(--ks-brand-accent) 26%, transparent);
}

/* Density modes only flip the scale. ~0.75x compact, ~1.25x comfortable.
   Works on any element; nests. The scale is the ONLY thing these set — the
   derived tokens are re-declared on ':root, [data-ks-density]' below, so they
   re-resolve against whichever scale is in scope. */
[data-ks-density="compact"]     { --ks-density-scale: 0.75; }
[data-ks-density="comfortable"] { --ks-density-scale: 1.25; }
/* The default scale, named. Two reasons it has to declare the value rather
   than lean on :root: it makes "normal" writable instead of only expressible
   by leaving the attribute off, and — the load-bearing one — density nests, so
   a subtree inside a comfortable page has no way back to 1x without it. An
   element carrying the attribute but no scale would simply inherit 1.25. */
[data-ks-density="default"]     { --ks-density-scale: 1; }

/* Derived density tokens. Declared on BOTH :root and every [data-ks-density]
   element so the calc(... * var(--ks-density-scale)) re-resolves in each
   density scope (see the long note above --ks-density-scale). At scale 1 every
   calc(base * 1) === base and every max(floor, base * 1) === base (the base
   control heights are already >= 32px), so an unstyled page is pixel-identical
   to before this feature existed.

   Canonical roadmap-named tokens. Bases are the current literals:
       row-h  -> --ks-control-height-md (40px, floored at 32px)
       pad-y  -> --ks-space-3  (0.75rem / 12px)   table & list vertical padding
       pad-x  -> --ks-space-3  (0.75rem / 12px)   input horizontal padding
       font   -> --ks-text-sm  (0.875rem / 14px)  control text size */
:root,
[data-ks-density] {
  /* --ks-density-row-h is a PUBLIC token provided for consumers styling their
     own custom rows; built-in components use their per-component heights below.
     Kept for the roadmap-named API surface. */
  --ks-density-row-h: max(32px, calc(var(--ks-control-height-md) * var(--ks-density-scale)));
  --ks-density-pad-y: calc(var(--ks-space-3) * var(--ks-density-scale));
  --ks-density-pad-x: calc(var(--ks-space-3) * var(--ks-density-scale));

  /* Font sizes route through floored tokens so compact text stays legible.
     Floors are below every base, so scale 1 evaluates to the base unchanged:
       font    base 14 -> compact 12 (10.5 floored), comfortable 17.5
       font-xs base 12 -> compact 11 (9    floored), comfortable 15
       font-lg base 16 -> compact 13 (12   floored), comfortable 20 */
  --ks-density-font:    max(12px, calc(var(--ks-text-sm)   * var(--ks-density-scale)));
  --ks-density-font-xs: max(11px, calc(var(--ks-text-xs)   * var(--ks-density-scale)));
  --ks-density-font-lg: max(13px, calc(var(--ks-text-base) * var(--ks-density-scale)));

  /* Per-component derivations for controls whose base differs from the
     canonical pad-x, so each keeps its own current padding at scale 1. */
  --ks-density-btn-h:      max(32px, calc(var(--ks-control-height-md) * var(--ks-density-scale)));
  --ks-density-btn-sm-h:   max(32px, calc(var(--ks-control-height-sm) * var(--ks-density-scale)));
  --ks-density-btn-lg-h:   max(32px, calc(var(--ks-control-height-lg) * var(--ks-density-scale)));
  --ks-density-btn-pad-x:  calc(var(--ks-space-4) * var(--ks-density-scale));   /* 16px base */
  --ks-density-btn-sm-pad-x: calc(var(--ks-space-3) * var(--ks-density-scale)); /* 12px base */
  --ks-density-btn-lg-pad-x: calc(var(--ks-space-6) * var(--ks-density-scale)); /* 24px base */
  --ks-density-input-h:    max(32px, calc(var(--ks-control-height-md) * var(--ks-density-scale)));
  --ks-density-textarea-min-h: max(64px, calc(var(--ks-control-height-md) * 2 * var(--ks-density-scale)));
  --ks-density-table-pad-x: calc(var(--ks-space-3\.5) * var(--ks-density-scale)); /* 14px base */
  --ks-density-list-pad-x: calc(var(--ks-pad-md) * var(--ks-density-scale));    /* 16px base */
  --ks-density-list-gap:   calc(var(--ks-space-3) * var(--ks-density-scale));   /* 12px base */
}

/* ---- Dark theme: apply [data-theme="dark"] to <html> or <body> ----
   In the dark, raised surfaces get LIGHTER (catching light) while the canvas
   sinks. Highlights and grain soften; shadows deepen. */
[data-theme="dark"] {
  --ks-canvas:           #0B0C0F;
  --ks-surface-default:  #191A1E;
  --ks-surface-subtle:   #16171C;
  --ks-surface-muted:    #212227;
  --ks-surface-emphasis: #2B2C32;
  --ks-surface-inverse:  var(--ks-slate-0);
  --ks-surface-overlay:  rgba(0, 0, 0, 0.6);
  --ks-shadow-color: 0 0 0;
  --ks-light-top:    inset 0 0 0 0 transparent;
  --ks-light-faint:  inset 0 0 0 0 transparent;
  --ks-surface-sheen: none;
  --ks-edge:      inset 0 0 0 1px rgb(255 255 255 / 0.10);
  --ks-edge-soft: inset 0 0 0 1px rgb(255 255 255 / 0.06);
  --ks-well: inset 0 0 0 1px rgb(255 255 255 / 0.10);
  --ks-grain-ink: 255 255 255;  /* light grains on dark surfaces */
  --ks-grain-opacity: 0.04;
  --ks-canvas-wash: radial-gradient(110% 75% at 50% 0%, rgb(255 255 255 / 0.04), transparent 62%);

  --ks-text-default:    var(--ks-slate-50);
  --ks-text-muted:      var(--ks-slate-400);
  --ks-text-subtle:     var(--ks-slate-500);
  --ks-text-disabled:   var(--ks-slate-600);
  --ks-text-inverse:    var(--ks-slate-900);
  --ks-text-link:       var(--ks-blue-400);
  --ks-text-link-hover: var(--ks-blue-300);

  --ks-border-default:  var(--ks-slate-700);
  --ks-border-muted:    var(--ks-slate-800);
  --ks-border-emphasis: var(--ks-slate-600);

  --ks-brand-primary-subtle: rgba(70, 106, 153, 0.22);
  --ks-brand-primary-muted:  rgba(70, 106, 153, 0.32);

  --ks-success-surface: rgba(16, 185, 129, 0.10);
  --ks-success-text:    var(--ks-teal-400);
  --ks-warning-surface: rgba(245, 158, 11, 0.10);
  --ks-warning-text:    #FCD34D;
  --ks-error-surface:   rgba(239, 68, 68, 0.10);
  --ks-error-text:      #F87171;
  --ks-info-surface:    rgba(59, 130, 246, 0.10);
  --ks-info-text:       var(--ks-blue-400);
}

/* Dark accent tints still DERIVE from --ks-brand-accent (so presets keep
   working in the dark), just with a bumped alpha so the mix reads on the darker
   canvas. Must also match a [data-ks-accent] container INSIDE dark, else that
   container's own re-declaration (light 14/26 above) would win in the dark. */
[data-theme="dark"],
[data-theme="dark"] [data-ks-accent] {
  --ks-brand-accent-subtle: color-mix(in srgb, var(--ks-brand-accent) 22%, transparent);
  --ks-brand-accent-muted:  color-mix(in srgb, var(--ks-brand-accent) 34%, transparent);
}

/* ---- System mode: honor the OS when no explicit theme is set ----
   The 'system' theme choice removes the data-theme attribute, so these mirror
   the [data-theme="dark"] block for prefers-color-scheme: dark. An explicit
   [data-theme] always wins (this only matches :root when the attribute is
   absent). KEEP THESE TOKENS IN SYNC with the [data-theme="dark"] block above. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --ks-canvas:           #0B0C0F;
    --ks-surface-default:  #191A1E;
    --ks-surface-subtle:   #16171C;
    --ks-surface-muted:    #212227;
    --ks-surface-emphasis: #2B2C32;
    --ks-surface-inverse:  var(--ks-slate-0);
    --ks-surface-overlay:  rgba(0, 0, 0, 0.6);
    --ks-shadow-color: 0 0 0;
    --ks-light-top:    inset 0 0 0 0 transparent;
    --ks-light-faint:  inset 0 0 0 0 transparent;
    --ks-surface-sheen: none;
    --ks-edge:      inset 0 0 0 1px rgb(255 255 255 / 0.10);
    --ks-edge-soft: inset 0 0 0 1px rgb(255 255 255 / 0.06);
    --ks-well: inset 0 0 0 1px rgb(255 255 255 / 0.10);
    --ks-grain-ink: 255 255 255;
    --ks-grain-opacity: 0.04;
    --ks-canvas-wash: radial-gradient(110% 75% at 50% 0%, rgb(255 255 255 / 0.04), transparent 62%);

    --ks-text-default:    var(--ks-slate-50);
    --ks-text-muted:      var(--ks-slate-400);
    --ks-text-subtle:     var(--ks-slate-500);
    --ks-text-disabled:   var(--ks-slate-600);
    --ks-text-inverse:    var(--ks-slate-900);
    --ks-text-link:       var(--ks-blue-400);
    --ks-text-link-hover: var(--ks-blue-300);

    --ks-border-default:  var(--ks-slate-700);
    --ks-border-muted:    var(--ks-slate-800);
    --ks-border-emphasis: var(--ks-slate-600);

    --ks-brand-primary-subtle: rgba(70, 106, 153, 0.22);
    --ks-brand-primary-muted:  rgba(70, 106, 153, 0.32);

    --ks-success-surface: rgba(16, 185, 129, 0.10);
    --ks-success-text:    var(--ks-teal-400);
    --ks-warning-surface: rgba(245, 158, 11, 0.10);
    --ks-warning-text:    #FCD34D;
    --ks-error-surface:   rgba(239, 68, 68, 0.10);
    --ks-error-text:      #F87171;
    --ks-info-surface:    rgba(59, 130, 246, 0.10);
    --ks-info-text:       var(--ks-blue-400);
  }

  /* Accent tints for system-dark; bumped alpha, re-derived per scope.
     Keep in sync with the [data-theme="dark"] accent block above. */
  :root:not([data-theme]),
  :root:not([data-theme]) [data-ks-accent] {
    --ks-brand-accent-subtle: color-mix(in srgb, var(--ks-brand-accent) 22%, transparent);
    --ks-brand-accent-muted:  color-mix(in srgb, var(--ks-brand-accent) 34%, transparent);
  }
}

@media (forced-colors: active) {
  :root {
    --ks-brand-primary: ButtonText;
    --ks-border-focus:  Highlight;
  }
}

/* ============================================================
   2. RESET / BASE
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }

.ks-scope, .ks-scope * , .ks-scope *::before, .ks-scope *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body.ks-body {
  margin: 0;
  font-family: var(--ks-font-sans);
  font-size: var(--ks-type-body-size);
  line-height: var(--ks-type-body-leading);
  color: var(--ks-text-default);
  background-color: var(--ks-canvas);
  background-image: var(--ks-canvas-wash);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* App-like feel: chrome text isn't selectable. Form fields, editable regions,
   code, and anything tagged .ks-selectable opt back in. */
body.ks-body { -webkit-user-select: none; -ms-user-select: none; user-select: none; }
body.ks-body input,
body.ks-body textarea,
body.ks-body [contenteditable]:not([contenteditable="false"]),
body.ks-body pre,
body.ks-body code,
body.ks-body .ks-code,
body.ks-body .ks-selectable {
  -webkit-user-select: text; -ms-user-select: text; user-select: text;
}

/* Links. `.ks-link` is still the class to reach for, but a plain <a> inside a
   Keystone body must never fall back to the UA's blue-and-underlined default —
   that is what unclassed links in tables and cards were doing. Wrapped in
   :where() so the whole rule carries ZERO specificity: it beats the UA sheet
   and inheritance, and loses to every component that colors its own anchors
   (.ks-btn, .ks-navbar-link, a.ks-chip, .ks-pagination a, ...) — those are
   excluded outright, so this can never fight a component, only the UA sheet.
   Everything sits inside :where(), so the rule carries ZERO specificity and any
   app style, however plain, still overrides it. */
:where(body.ks-body a[href]:not([class*="ks-"])) {
  color: var(--ks-text-link);
  text-decoration: none;
  transition: var(--ks-transition-colors);
}
:where(body.ks-body a[href]:not([class*="ks-"]):hover) {
  color: var(--ks-text-link-hover);
  text-decoration: underline;
}
:where(body.ks-body a[href]:not([class*="ks-"]):focus-visible) {
  outline: none;
  border-radius: var(--ks-radius-sm);
  box-shadow: var(--ks-glow-focus);
}

/* Film grain — a fixed, uniform texture over the whole UI to set tone.
   pointer-events:none so it never blocks; blended softly so it reads as
   tactile noise rather than a gray veil. */
body.ks-body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 2147483646;
  pointer-events: none;
  background-color: rgb(var(--ks-grain-ink));
  -webkit-mask-image: var(--ks-grain);
  mask-image: var(--ks-grain);
  -webkit-mask-size: 160px 160px;
  mask-size: 160px 160px;
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  opacity: var(--ks-grain-opacity);
}
.ks-no-grain::after { display: none; }
/* Opt-in stronger grain for a section/surface */
.ks-grain { position: relative; isolation: isolate; }
.ks-grain::before {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none; z-index: 0;
  border-radius: inherit;
  background-color: rgb(var(--ks-grain-ink));
  -webkit-mask-image: var(--ks-grain);
  mask-image: var(--ks-grain);
  -webkit-mask-size: 160px 160px;
  mask-size: 160px 160px;
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  opacity: calc(var(--ks-grain-opacity) * 1.8);
}
.ks-grain > * { position: relative; z-index: 1; }

.ks-h1, .ks-h2, .ks-h3, .ks-h4, .ks-h5, .ks-h6 {
  margin: 0 0 var(--ks-space-3);
  font-family: var(--ks-font-sans);
  color: var(--ks-text-default);
}
.ks-h1 { font-size: var(--ks-type-h1-size); font-weight: var(--ks-type-h1-weight); line-height: var(--ks-type-h1-leading); }
.ks-h2 { font-size: var(--ks-type-h2-size); font-weight: var(--ks-type-h2-weight); line-height: var(--ks-type-h2-leading); }
.ks-h3 { font-size: var(--ks-type-h3-size); font-weight: var(--ks-type-h3-weight); line-height: var(--ks-type-h3-leading); }
.ks-h4 { font-size: var(--ks-type-h4-size); font-weight: var(--ks-type-h4-weight); line-height: var(--ks-type-h4-leading); }
.ks-h5 { font-size: var(--ks-type-h5-size); font-weight: var(--ks-type-h5-weight); line-height: var(--ks-type-h5-leading); }
.ks-h6 { font-size: var(--ks-type-h6-size); font-weight: var(--ks-type-h6-weight); line-height: var(--ks-type-h6-leading); }

.ks-display    { font-size: var(--ks-type-display-size);    font-weight: var(--ks-type-display-weight);    line-height: var(--ks-type-display-leading);    letter-spacing: var(--ks-type-display-tracking); }
.ks-display-xl { font-size: var(--ks-type-display-xl-size); font-weight: var(--ks-type-display-xl-weight); line-height: var(--ks-type-display-xl-leading); letter-spacing: var(--ks-type-display-xl-tracking); }

.ks-text-muted   { color: var(--ks-text-muted); }
.ks-text-subtle  { color: var(--ks-text-subtle); }
.ks-text-sm      { font-size: var(--ks-type-body-sm-size); line-height: var(--ks-type-body-sm-leading); }
.ks-text-lg      { font-size: var(--ks-type-body-lg-size); line-height: var(--ks-type-body-lg-leading); }
.ks-caption      { font-size: var(--ks-type-caption-size); line-height: var(--ks-type-caption-leading); color: var(--ks-text-muted); }
.ks-label {
  font-size: var(--ks-type-label-size);
  font-weight: var(--ks-type-label-weight);
  letter-spacing: var(--ks-type-label-tracking);
  color: var(--ks-text-default);
}

.ks-link {
  color: var(--ks-text-link);
  text-decoration: none;
  transition: var(--ks-transition-colors);
}
.ks-link:hover { color: var(--ks-text-link-hover); text-decoration: underline; }

.ks-code, .ks-kbd {
  font-family: var(--ks-font-mono);
  font-size: var(--ks-type-code-size);
}
.ks-code {
  padding: 0.1em 0.35em;
  background: var(--ks-surface-muted);
  border-radius: var(--ks-radius-sm);
}

.ks-hr {
  border: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgb(var(--ks-shadow-color) / 0.14), transparent);
  margin: var(--ks-space-6) 0;
}

/* Shared focus treatment — a soft brand glow (depth), plus a thin ring for
   forced-colors / high-contrast users. */
.ks-focus-ring:focus-visible,
.ks-btn:focus-visible,
.ks-input:focus-visible,
.ks-select:focus-visible,
.ks-textarea:focus-visible,
.ks-tabs-tab:focus-visible,
.ks-switch input:focus-visible + .ks-switch-track {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: var(--ks-glow-focus);
}

/* Every declaration is !important: a utility that a component's own rule can
   out-cascade isn't hidden. (.ks-select's width: 100% used to win here, so the
   combobox's native <select> stayed full width and pushed pages sideways.) */
.ks-visually-hidden {
  position: absolute !important;
  width: 1px !important; height: 1px !important;
  padding: 0 !important; margin: -1px !important;
  overflow: hidden !important; clip: rect(0 0 0 0) !important; clip-path: inset(50%) !important;
  white-space: nowrap !important; border: 0 !important;
}

/* ============================================================
   3. LAYOUT HELPERS
   ============================================================ */

.ks-container {
  width: 100%;
  max-width: var(--ks-content-max-width);
  margin-inline: auto;
  padding-inline: var(--ks-pad-md);
}
.ks-container-wide  { max-width: 1200px; }
.ks-container-prose { max-width: var(--ks-prose-max-width); }

.ks-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ks-gap-md);
}
.ks-col { flex: 1 1 0; min-width: 0; }

.ks-stack { display: flex; flex-direction: column; gap: var(--ks-gap-md); }
.ks-stack-sm { gap: var(--ks-gap-sm); }
.ks-stack-lg { gap: var(--ks-gap-lg); }

.ks-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ks-gap-sm);
}

.ks-grid {
  display: grid;
  gap: var(--ks-gap-md);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
/* Fixed-track variants: cards stretch to fill the row instead of leaving
   empty trailing tracks (auto-fit/fill bunch a short card count to one side). */
.ks-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ks-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ks-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 860px) {
  .ks-grid--3, .ks-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .ks-grid--2, .ks-grid--3, .ks-grid--4 { grid-template-columns: 1fr; }
}

.ks-center { display: flex; align-items: center; justify-content: center; }
.ks-between { display: flex; align-items: center; justify-content: space-between; }
.ks-spacer { flex: 1 1 auto; }

/* ============================================================
   4. COMPONENTS
   ============================================================ */

/* ---- Buttons ----
   Raised keys: a sheen of light over the surface, a soft drop shadow, and a
   top-edge highlight. They lift on hover and press in on click. */
.ks-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ks-gap-sm);
  height: var(--ks-density-btn-h);        /* density: base --ks-control-height-md (40px), floored 32px */
  padding: 0 var(--ks-density-btn-pad-x); /* density: base --ks-space-4 (16px) */
  font-family: inherit;
  font-size: var(--ks-density-font);      /* density: base --ks-text-sm */
  font-weight: var(--ks-weight-medium);
  line-height: 1;
  color: var(--ks-text-default);
  background-color: var(--ks-surface-default);
  border: 0;
  border-radius: var(--ks-radius-button);
  box-shadow: var(--ks-edge);
  cursor: pointer;
  user-select: none;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--ks-duration-fast) var(--ks-ease-default),
              background-image var(--ks-duration-fast) var(--ks-ease-default),
              filter var(--ks-duration-fast) var(--ks-ease-default),
              color var(--ks-duration-fast) var(--ks-ease-default);
}
/* Hover/active change tone only — never size, shape, position or elevation. */
.ks-btn:hover { background-color: var(--ks-surface-subtle); }
.ks-btn:active { background-color: var(--ks-surface-muted); }
.ks-btn:disabled, .ks-btn.is-disabled { opacity: 0.5; cursor: not-allowed; }
.ks-btn svg, .ks-btn .ks-i { width: 1.1em; height: 1.1em; display: inline-flex; align-items: center; justify-content: center; flex: none; line-height: 1; }

/* Solid, flat color fills — no gradients, no glow. Hover shifts tone. */
.ks-btn--primary   { color: #fff; background-color: var(--ks-blue-700); box-shadow: none; }
.ks-btn--primary:hover  { background-color: var(--ks-blue-800); filter: none; }
.ks-btn--primary:active { background-color: var(--ks-blue-900); filter: none; }

.ks-btn--secondary { color: #fff; background-color: var(--ks-brand-secondary); box-shadow: none; }
.ks-btn--secondary:hover  { background-color: var(--ks-brand-secondary-hover); filter: none; }
.ks-btn--secondary:active { background-color: var(--ks-brand-secondary-active); filter: none; }

.ks-btn--accent    { color: #fff; background-color: var(--ks-brand-accent); box-shadow: none; }
.ks-btn--accent:hover  { background-color: var(--ks-brand-accent-hover); filter: none; }
.ks-btn--accent:active { background-color: var(--ks-brand-accent-active); filter: none; }

.ks-btn--danger    { color: #fff; background-color: var(--ks-error-default); box-shadow: none; }
.ks-btn--danger:hover  { background-color: var(--ks-error-hover); filter: none; }
.ks-btn--danger:active { background-color: #A93B34; filter: none; }

.ks-btn--ghost { background-color: transparent; background-image: none; box-shadow: none; }
.ks-btn--ghost:hover { background-color: rgb(var(--ks-shadow-color) / 0.06); }
.ks-btn--ghost:active { background-color: rgb(var(--ks-shadow-color) / 0.10); }

.ks-btn--sm { height: var(--ks-density-btn-sm-h); padding: 0 var(--ks-density-btn-sm-pad-x); font-size: var(--ks-density-font-xs); } /* density: base --ks-text-xs (12px), floored 11px */
.ks-btn--lg { height: var(--ks-density-btn-lg-h); padding: 0 var(--ks-density-btn-lg-pad-x); font-size: var(--ks-density-font-lg); } /* density: base --ks-text-base (16px), floored 13px */
.ks-btn--icon { width: var(--ks-density-btn-h); padding: 0; }
.ks-btn--icon.ks-btn--sm { width: var(--ks-density-btn-sm-h); }
.ks-btn--block { display: flex; width: 100%; }

.ks-btn-group { display: inline-flex; border-radius: var(--ks-radius-button); box-shadow: var(--ks-edge); }
.ks-btn-group .ks-btn { border-radius: 0; box-shadow: none; }
.ks-btn-group .ks-btn:hover { transform: none; }
.ks-btn-group .ks-btn:not(:last-child) { box-shadow: inset -1px 0 0 rgb(var(--ks-shadow-color) / 0.10); }
.ks-btn-group .ks-btn:first-child { border-radius: var(--ks-radius-button) 0 0 var(--ks-radius-button); }
.ks-btn-group .ks-btn:last-child  { border-radius: 0 var(--ks-radius-button) var(--ks-radius-button) 0; }

/* ---- Forms ----
   Inputs read as carved-in wells: a tonal recess with an inner shadow,
   no outline. Focus pools brand light into the well. */
.ks-field { display: flex; flex-direction: column; gap: var(--ks-space-1\.5); margin-bottom: var(--ks-space-4); }
.ks-field > .ks-label { margin-bottom: var(--ks-space-0\.5); }

.ks-input, .ks-select, .ks-textarea {
  width: 100%;
  height: var(--ks-density-input-h);   /* density: base --ks-control-height-md (40px), floored 32px */
  padding: 0 var(--ks-density-pad-x);  /* density: base --ks-space-3 (12px) */
  font-family: inherit;
  font-size: var(--ks-density-font);   /* density: base --ks-text-sm */
  color: var(--ks-text-default);
  background: var(--ks-surface-subtle);
  border: 0;
  border-radius: var(--ks-radius-input);
  box-shadow: var(--ks-well);
  transition: box-shadow var(--ks-duration-normal) var(--ks-ease-default), background-color var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-textarea { height: auto; min-height: var(--ks-density-textarea-min-h); padding: calc(var(--ks-space-2) * var(--ks-density-scale)) var(--ks-density-pad-x); resize: vertical; } /* density: min-h base 80px floored 64px; pad-y base --ks-space-2 (8px), pad-x --ks-space-3 (12px) */
.ks-input::placeholder, .ks-textarea::placeholder { color: var(--ks-text-subtle); }
.ks-input:focus, .ks-select:focus, .ks-textarea:focus {
  outline: 0;
  background: var(--ks-surface-default);
  box-shadow: var(--ks-well), var(--ks-glow-focus);
}
.ks-input:disabled, .ks-select:disabled, .ks-textarea:disabled { color: var(--ks-text-disabled); cursor: not-allowed; box-shadow: var(--ks-well); opacity: 0.7; }

.ks-field.is-invalid .ks-input,
.ks-field.is-invalid .ks-select,
.ks-field.is-invalid .ks-textarea { box-shadow: var(--ks-well), 0 0 0 2px var(--ks-surface-default), 0 0 0 4px var(--ks-error-default); }

/* Native select — flat, with our own caret */
.ks-select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: var(--ks-space-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236A6A72' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--ks-space-3) center;
  background-size: 16px 16px;
}
.ks-field-error { font-size: var(--ks-text-xs); color: var(--ks-error-text); }

/* Input group — an addon (prefix/suffix) seated flush against a .ks-input so
   the pair reads as ONE recessed well. The addon carries a slightly deeper
   tone (not a border); the whole group pools a single focus ring when the
   inner input is focused. Order the children in markup for left vs. right. */
.ks-input-group {
  display: flex; align-items: stretch; width: 100%;
  border-radius: var(--ks-radius-input);
  box-shadow: var(--ks-well);
  background: var(--ks-surface-subtle);
  transition: box-shadow var(--ks-duration-normal) var(--ks-ease-default), background-color var(--ks-duration-fast) var(--ks-ease-default);
}
/* The inner input sheds its own well + radius; the group owns both. */
.ks-input-group .ks-input {
  flex: 1 1 auto; min-width: 0;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}
.ks-input-group .ks-input:focus { background: transparent; box-shadow: none; }
/* No overflow:hidden clipping — otherwise a focused reveal button's OUTSET focus
   ring gets cut off (WCAG 2.4.7). Instead square every child and round only the
   group's outer corners via its edge children. */
.ks-input-group > * { border-radius: 0; }
.ks-input-group > :first-child { border-top-left-radius: var(--ks-radius-input); border-bottom-left-radius: var(--ks-radius-input); }
.ks-input-group > :last-child  { border-top-right-radius: var(--ks-radius-input); border-bottom-right-radius: var(--ks-radius-input); }
/* One cohesive ring for the whole group. */
.ks-input-group:focus-within {
  background: var(--ks-surface-default);
  box-shadow: var(--ks-well), var(--ks-glow-focus);
}
.ks-input-group-addon {
  display: inline-flex; align-items: center; gap: var(--ks-space-1\.5);
  flex: none; white-space: nowrap;
  padding: 0 var(--ks-density-pad-x);
  font-size: var(--ks-density-font);
  color: var(--ks-text-muted);
  background: var(--ks-surface-emphasis);
  user-select: none;
}
.ks-input-group-addon .ks-i { font-size: 1.1em; }
/* Seam between addon and field — a subtle tonal divider, never a full border. */
.ks-input-group-addon:first-child { box-shadow: inset -1px 0 0 rgb(var(--ks-shadow-color) / 0.10); }
.ks-input-group-addon:last-child  { box-shadow: inset  1px 0 0 rgb(var(--ks-shadow-color) / 0.10); }
.ks-input-group.is-invalid,
.ks-field.is-invalid .ks-input-group { box-shadow: var(--ks-well), 0 0 0 2px var(--ks-surface-default), 0 0 0 4px var(--ks-error-default); }

/* Character counter caption — injected below a maxlength'd field. Muted by
   default; warns tonally as the limit approaches. */
.ks-input-counter {
  align-self: flex-end;
  font-size: var(--ks-text-xs);
  color: var(--ks-text-muted);
  font-variant-numeric: tabular-nums;
  transition: color var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-input-counter.is-near { color: var(--ks-warning-text); }
.ks-input-counter.is-full { color: var(--ks-error-text); }

/* Password reveal — a trailing icon button seated inside the group's addon
   slot. Ghost styling; tone-only hover, per the interaction principle. */
.ks-input-reveal {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; padding: 0 var(--ks-space-2\.5);
  border: 0; background: transparent; cursor: pointer;
  color: var(--ks-text-muted);
  transition: color var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-input-reveal:hover { color: var(--ks-text-default); }
.ks-input-reveal:focus-visible { outline: 0; box-shadow: var(--ks-glow-focus); border-radius: var(--ks-radius-sm); }
.ks-input-reveal svg { width: 1.15em; height: 1.15em; }

.ks-checkbox, .ks-radio { display: inline-flex; align-items: center; gap: var(--ks-space-2\.5); cursor: pointer; font-size: var(--ks-text-sm); }
.ks-checkbox input, .ks-radio input {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  flex: none; position: relative; margin: 0; cursor: pointer;
  width: 1.2rem; height: 1.2rem;
  background: var(--ks-surface-subtle);
  box-shadow: var(--ks-well);
  transition: background-color var(--ks-duration-fast) var(--ks-ease-default), box-shadow var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-checkbox input { border-radius: var(--ks-radius-sm); }
.ks-radio input { border-radius: var(--ks-radius-full); }
/* Checkbox — fills solid steel with a crisp white check. The tick is always
   present but drawn from nothing (scale + fade) so toggling on/off animates the
   mark in and out. The reduced-motion guard (§ Motion preferences) forces these
   durations to ~0, so the mark simply appears. */
.ks-checkbox input:checked {
  background-color: var(--ks-blue-700);
  box-shadow: none;
}
.ks-checkbox input::after {
  content: ""; position: absolute; left: 50%; top: 46%;
  width: 0.32rem; height: 0.6rem;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: translate(-50%, -50%) rotate(45deg) scale(0);
  transform-origin: center;
  opacity: 0;
  transition: transform var(--ks-duration-normal) var(--ks-ease-spring),
              opacity var(--ks-duration-fast) var(--ks-ease-out);
}
.ks-checkbox input:checked::after {
  transform: translate(-50%, -50%) rotate(45deg) scale(1);
  opacity: 1;
}
/* Radio — a steel "donut": ring + steel core on the recessed surface, so it
   reads distinctly from the solid-filled checkbox. The inner dot is always
   rendered but scales in from 0 on check, so toggling springs the core in/out. */
.ks-radio input:checked {
  background: var(--ks-surface-default);
  box-shadow: inset 0 0 0 2px var(--ks-blue-700);
}
.ks-radio input::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 0.55rem; height: 0.55rem; border-radius: var(--ks-radius-full);
  background: var(--ks-blue-700);
  transform: scale(0);
  opacity: 0;
  transition: transform var(--ks-duration-normal) var(--ks-ease-spring),
              opacity var(--ks-duration-fast) var(--ks-ease-out);
}
.ks-radio input:checked::after {
  transform: scale(1);
  opacity: 1;
}
.ks-checkbox input:focus-visible, .ks-radio input:focus-visible { outline: 0; box-shadow: var(--ks-well), var(--ks-glow-focus); }
.ks-checkbox input:disabled, .ks-radio input:disabled { opacity: 0.5; cursor: not-allowed; }
.ks-radio-group { display: flex; flex-direction: column; gap: var(--ks-space-2\.5); }
.ks-radio-group--row { flex-direction: row; flex-wrap: wrap; gap: var(--ks-space-4); }

/* Segmented option buttons — one edgy control split into choices. Wrap radios
   in labels for accessible single-select; the chosen segment becomes a raised
   steel key seated in the recessed track. */
.ks-segmented {
  position: relative;
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--ks-surface-subtle); box-shadow: var(--ks-well);
  border-radius: var(--ks-radius-md);
}
.ks-segmented > label, .ks-segmented > button {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ks-space-1\.5);
  padding: var(--ks-space-1\.5) var(--ks-space-3);
  font-size: var(--ks-text-sm); font-weight: var(--ks-weight-medium);
  color: var(--ks-text-muted); cursor: pointer; white-space: nowrap;
  border: 0; background: transparent; border-radius: var(--ks-radius-sm);
  transition: color var(--ks-duration-fast) var(--ks-ease-default),
              background-color var(--ks-duration-fast) var(--ks-ease-default),
              box-shadow var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-segmented > label input { position: absolute; opacity: 0; pointer-events: none; }
.ks-segmented > label:hover, .ks-segmented > button:hover { color: var(--ks-text-default); }
.ks-segmented > label:has(input:checked),
.ks-segmented > button.is-active {
  background: var(--ks-blue-700); color: #fff; box-shadow: var(--ks-shadow-sm);
}
.ks-segmented > label:has(input:focus-visible) { box-shadow: var(--ks-glow-focus); }

/* Animated sliding thumb — enabled only once the JS `segmented` behavior sets
   data-ks-thumb + measures --ks-seg-x/--ks-seg-w. Without JS the static
   :has(:checked) highlight above remains the fallback, so nothing regresses. */
.ks-segmented[data-ks-thumb]::before {
  content: ""; position: absolute; z-index: 0; top: 3px; bottom: 3px; left: 0;
  width: var(--ks-seg-w, 0); transform: translateX(var(--ks-seg-x, 0));
  background: var(--ks-blue-700); box-shadow: var(--ks-shadow-sm);
  border-radius: var(--ks-radius-sm);
}
/* Animate only after the first measurement (JS adds -ready next frame), so the
   thumb appears in place on load instead of sliding in from the left edge. */
.ks-segmented[data-ks-thumb-ready]::before {
  transition: transform var(--ks-duration-normal) var(--ks-ease-spring),
              width var(--ks-duration-normal) var(--ks-ease-spring);
}
/* With the thumb active, the checked segment no longer paints its own steel key
   (the thumb carries it); the label just flips its text to white. */
.ks-segmented[data-ks-thumb] > label:has(input:checked),
.ks-segmented[data-ks-thumb] > button.is-active {
  background: transparent; box-shadow: none; color: #fff;
}

/* Switch — a squared, low-radius toggle (not a generic pill). The thumb is a
   recessed key that shifts tone off→on; the track flips to solid steel. */
.ks-switch { position: relative; display: inline-flex; align-items: center; gap: var(--ks-space-2); cursor: pointer; }
.ks-switch input { position: absolute; opacity: 0; }
.ks-switch-track {
  width: 44px; height: 22px; border-radius: var(--ks-radius-sm);
  background: var(--ks-surface-subtle); box-shadow: var(--ks-well);
  /* Crossfade the tone AND the recessed well→flat as the toggle flips, so the
     track eases into steel rather than snapping. The knob (::after) slides on a
     spring below. Reduced motion neutralises these durations globally. */
  transition: var(--ks-transition-colors),
              box-shadow var(--ks-duration-normal) var(--ks-ease-default);
  position: relative; flex: none;
}
.ks-switch-track::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: 2px;
  background: var(--ks-text-muted);
  box-shadow: var(--ks-shadow-sm);
  transition: transform var(--ks-duration-normal) var(--ks-ease-spring),
              background-color var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-switch input:checked + .ks-switch-track {
  background-color: var(--ks-blue-700); box-shadow: none;
}
.ks-switch input:checked + .ks-switch-track::after { transform: translateX(22px); background: #fff; }

/* ---- Card ---- */
.ks-card {
  background-color: var(--ks-surface-default);
  border-radius: var(--ks-radius-card);
  box-shadow: var(--ks-shadow-sm), var(--ks-edge-soft);
  overflow: hidden;
}
.ks-card-header { padding: var(--ks-pad-md); font-weight: var(--ks-weight-semibold); box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.06); }
.ks-card-body { padding: var(--ks-pad-md); }
.ks-card-footer { padding: var(--ks-pad-md); background: rgb(var(--ks-shadow-color) / 0.02); box-shadow: inset 0 1px 0 rgb(var(--ks-shadow-color) / 0.05); }
.ks-card--interactive { transition: box-shadow var(--ks-duration-normal) var(--ks-ease-default), background-color var(--ks-duration-fast) var(--ks-ease-default); cursor: pointer; }
.ks-card--interactive:hover { box-shadow: var(--ks-shadow-lg), var(--ks-light-top); }

/* ---- Badge & chip ---- */
.ks-badge {
  display: inline-flex; align-items: center; gap: var(--ks-space-1);
  padding: 0.2em 0.65em; font-size: var(--ks-density-font-xs); font-weight: var(--ks-weight-semibold); /* density: base --ks-text-xs (12px), floored 11px; em padding scales with it */
  line-height: 1.4; border-radius: var(--ks-radius-badge);
  background: var(--ks-surface-muted); color: var(--ks-text-default);
}
.ks-badge--primary { background: var(--ks-brand-primary-subtle); color: var(--ks-brand-primary); }
.ks-badge--success { background: var(--ks-success-surface); color: var(--ks-success-text); }
.ks-badge--warning { background: var(--ks-warning-surface); color: var(--ks-warning-text); }
.ks-badge--error   { background: var(--ks-error-surface); color: var(--ks-error-text); }
.ks-badge--info    { background: var(--ks-info-surface); color: var(--ks-info-text); }

.ks-chip {
  display: inline-flex; align-items: center; gap: var(--ks-space-1\.5);
  height: var(--ks-control-height-sm); padding: 0 var(--ks-space-2\.5);
  font-size: var(--ks-text-sm); font-weight: var(--ks-weight-medium); line-height: 1;
  border-radius: var(--ks-radius-chip);
  background-color: var(--ks-surface-emphasis);
  color: var(--ks-text-default); box-shadow: var(--ks-edge);
}
.ks-chip .ks-i { font-size: 0.95em; color: var(--ks-text-muted); }
/* Interactive chips (a/button or marked clickable) shift tone on hover. */
a.ks-chip, button.ks-chip, .ks-chip--clickable {
  cursor: pointer; text-decoration: none;
  transition: background-color var(--ks-duration-fast) var(--ks-ease-default);
}
a.ks-chip:hover, button.ks-chip:hover, .ks-chip--clickable:hover { background-color: var(--ks-surface-muted); }
.ks-chip--primary { background-color: var(--ks-brand-primary-subtle); color: var(--ks-blue-800); box-shadow: inset 0 0 0 1px rgb(var(--ks-blue-600-rgb, 70 106 153) / 0.25); }
.ks-chip--primary .ks-i { color: var(--ks-blue-700); }
.ks-chip.is-selected { background-color: var(--ks-blue-700); color: #fff; box-shadow: none; }
.ks-chip.is-selected .ks-i { color: rgb(255 255 255 / 0.85); }
/* Removable chip: trailing × */
.ks-chip-remove {
  display: inline-flex; align-items: center; justify-content: center;
  margin-right: calc(var(--ks-space-1) * -1); margin-left: var(--ks-space-0\.5);
  width: 1.05rem; height: 1.05rem; border: 0; padding: 0;
  border-radius: var(--ks-radius-sm); background: transparent; color: inherit;
  cursor: pointer; opacity: 0.6;
  transition: opacity var(--ks-duration-fast) var(--ks-ease-default), background-color var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-chip-remove:hover { opacity: 1; background: rgb(var(--ks-shadow-color) / 0.12); }

/* Chip enter/leave — CSS-first micro-interaction. Any newly-inserted chip
   grows + fades in (add the `.ks-chip-enter` class, or it runs automatically
   on first paint via animation). Removal: add `.ks-chip.is-leaving` to scale
   + fade the chip out, then drop the node on animationend/transitionend. */
.ks-chip {
  transition:
    background-color var(--ks-duration-fast) var(--ks-ease-default),
    opacity var(--ks-duration-normal) var(--ks-ease-default),
    transform var(--ks-duration-normal) var(--ks-ease-default);
}
@keyframes ks-chip-in { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: none; } }
.ks-chip-enter { animation: ks-chip-in var(--ks-duration-normal) var(--ks-ease-spring) both; }
.ks-chip.is-leaving { opacity: 0; transform: scale(0.9); pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .ks-chip-enter { animation: none; }
  .ks-chip.is-leaving { opacity: 0; transform: none; }
}

/* ---- Alert ----
   Tinted surface with a soft colored glow pooling from the leading edge —
   light and shade rather than a hard accent bar. */
.ks-alert {
  display: flex; align-items: flex-start; gap: var(--ks-space-3);
  padding: var(--ks-pad-sm) var(--ks-pad-md);
  border-radius: var(--ks-radius-lg);
  background: var(--ks-surface-subtle);
  box-shadow: var(--ks-shadow-sm), var(--ks-light-top);
}
.ks-alert-body { flex: 1; }
.ks-alert-title { font-weight: var(--ks-weight-semibold); margin-bottom: var(--ks-space-0\.5); }
.ks-alert--success { background: var(--ks-success-surface); color: var(--ks-success-text); box-shadow: var(--ks-shadow-sm), inset 3px 0 0 var(--ks-success-default); }
.ks-alert--warning { background: var(--ks-warning-surface); color: var(--ks-warning-text); box-shadow: var(--ks-shadow-sm), inset 3px 0 0 var(--ks-warning-default); }
.ks-alert--error   { background: var(--ks-error-surface); color: var(--ks-error-text); box-shadow: var(--ks-shadow-sm), inset 3px 0 0 var(--ks-error-default); }
.ks-alert--info    { background: var(--ks-info-surface); color: var(--ks-info-text); box-shadow: var(--ks-shadow-sm), inset 3px 0 0 var(--ks-info-default); }
[data-ks-alert-dismiss] {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; padding: 0; margin-left: auto;
  background: transparent; border: 0; border-radius: var(--ks-radius-sm);
  color: inherit; cursor: pointer; opacity: 0.7;
}
[data-ks-alert-dismiss]:hover { opacity: 1; background: rgb(var(--ks-shadow-color) / 0.08); }

/* ---- Table ----
   Rows are separated by alternating tone, not rules. The header floats
   above the body on a faint crease of shade. */
.ks-table { width: 100%; border-collapse: collapse; font-size: var(--ks-density-font); } /* density: base --ks-text-sm */
.ks-table th, .ks-table td { padding: var(--ks-density-pad-y) var(--ks-density-table-pad-x); text-align: left; vertical-align: middle; } /* density: pad-y base --ks-space-3 (12px), pad-x base --ks-space-3.5 (14px) */
.ks-table th { font-weight: var(--ks-weight-semibold); color: var(--ks-text-muted); background: var(--ks-surface-subtle); box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.06); }
.ks-table th:first-child { border-top-left-radius: var(--ks-radius-md); }
.ks-table th:last-child  { border-top-right-radius: var(--ks-radius-md); }
.ks-table tbody tr:nth-child(even) { background: rgb(var(--ks-shadow-color) / 0.025); }
.ks-table tfoot td { font-weight: var(--ks-weight-semibold); background: var(--ks-surface-subtle); box-shadow: inset 0 1px 0 rgb(var(--ks-shadow-color) / 0.06); }

/* Numeric columns: right-align and line up digits. */
.ks-table th.ks-num, .ks-table td.ks-num,
.ks-table th[data-align="end"], .ks-table td[data-align="end"] { text-align: right; font-variant-numeric: tabular-nums; }
.ks-table th.ks-center, .ks-table td.ks-center { text-align: center; }

/* Variants */
.ks-table--striped tbody tr:nth-child(even) { background: rgb(var(--ks-shadow-color) / 0.04); }
.ks-table--hover tbody tr:hover { background: var(--ks-brand-primary-subtle); }
.ks-table--hover tbody tr { transition: background var(--ks-duration-fast) var(--ks-ease-default); }

/* Bordered: hairline grid via inset shadows (no literal borders). */
.ks-table--bordered { box-shadow: var(--ks-edge); border-radius: var(--ks-radius-md); overflow: hidden; }
.ks-table--bordered th, .ks-table--bordered td { box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.08), inset -1px 0 0 rgb(var(--ks-shadow-color) / 0.06); }
.ks-table--bordered th:last-child, .ks-table--bordered td:last-child { box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.08); }

/* Compact / comfortable density — explicit PER-TABLE overrides. These set fixed
   padding on a specific table and intentionally take precedence over the ambient
   data-ks-density scale (a .ks-table--compact stays compact even inside a
   comfortable container). Use these for one-off tables; use data-ks-density on a
   container to scale everything within it. */
.ks-table--compact th, .ks-table--compact td { padding: var(--ks-space-1\.5) var(--ks-space-2\.5); }
.ks-table--comfortable th, .ks-table--comfortable td { padding: var(--ks-space-4) var(--ks-space-4); }

/* Sticky header — pair with a max-height scroll wrapper (.ks-table-wrap). */
.ks-table--sticky thead th { position: sticky; top: 0; z-index: 1; }

/* Selected / active row */
.ks-table tbody tr.is-selected { background: var(--ks-brand-primary-subtle); box-shadow: inset 2px 0 0 var(--ks-brand-primary); }

/* ---- Table utilities (v1.x) ----
   Cell alignment, sortable headers, sticky first column, actions cell, a
   responsive card-stack mode, plus a toolbar and empty state. All tone/shadow
   based per house style; all degrade to a plain static table without JS. */

/* Cell alignment helpers — small and composable. `.ks-table-num` is the
   preferred name for a right-aligned tabular-numeric cell (mirrors the older
   `.ks-num`); the `--right`/`--center` modifiers align without forcing digits. */
.ks-table .ks-table-num,
.ks-table th.ks-table-num, .ks-table td.ks-table-num { text-align: right; font-variant-numeric: tabular-nums; }
.ks-table th.ks-table-cell--right,  .ks-table td.ks-table-cell--right  { text-align: right; }
.ks-table th.ks-table-cell--center, .ks-table td.ks-table-cell--center { text-align: center; }

/* Fixed column width helper — pair with data-ks-width or an inline width. This
   just stops the cell from growing; use `width`/`style` for the actual value. */
.ks-table th.ks-table-w, .ks-table td.ks-table-w { width: 1%; white-space: nowrap; }

/* Actions cell — right-aligned control cluster with a consistent gap. */
.ks-table .ks-table-actions {
  text-align: right; white-space: nowrap;
}
.ks-table .ks-table-actions > * + * { margin-left: var(--ks-space-2); }
/* When the actions live in an inline flex row, keep them aligned + gapped. */
.ks-table-actions-row {
  display: inline-flex; align-items: center; gap: var(--ks-space-2);
  justify-content: flex-end;
}

/* Sortable headers — the JS `tableSort` behavior turns each sortable <th> into a
   focusable control (a <button class="ks-table-sort"> wrapping the label + an
   inline SVG direction indicator). Without JS the <th> stays a plain label. */
.ks-table-sort {
  display: inline-flex; align-items: center; gap: var(--ks-space-1\.5);
  width: 100%; padding: 0; margin: 0;
  font: inherit; color: inherit; letter-spacing: inherit; text-align: inherit;
  background: none; border: 0; cursor: pointer;
  border-radius: var(--ks-radius-sm);
  transition: color var(--ks-duration-fast) var(--ks-ease-default);
}
/* Numeric / right-aligned headers push their control (label + arrow) to the end. */
.ks-table th.ks-table-num .ks-table-sort,
.ks-table th.ks-table-cell--right .ks-table-sort,
.ks-table th[data-align="end"] .ks-table-sort { justify-content: flex-end; }
.ks-table th.ks-table-cell--center .ks-table-sort { justify-content: center; }
.ks-table-sort:hover { color: var(--ks-text-default); }
.ks-table-sort:focus-visible { outline: 0; box-shadow: var(--ks-glow-focus); }
/* Direction indicator: dim & compact until this column is the active sort. */
.ks-table-sort-ind {
  display: inline-flex; flex: 0 0 auto;
  opacity: 0.35;
  transition: opacity var(--ks-duration-fast) var(--ks-ease-default),
              transform var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-table th[aria-sort="ascending"]  .ks-table-sort,
.ks-table th[aria-sort="descending"] .ks-table-sort { color: var(--ks-text-default); }
.ks-table th[aria-sort="ascending"]  .ks-table-sort-ind,
.ks-table th[aria-sort="descending"] .ks-table-sort-ind { opacity: 1; color: var(--ks-brand-primary); }

/* Sticky first column — freezes column 1 horizontally. Composes with
   --sticky (sticky header) for a frozen top-left corner. Tone + shadow, no
   literal border. Needs a scrolling wrapper (.ks-table-wrap). */
.ks-table--sticky-col th:first-child,
.ks-table--sticky-col td:first-child {
  position: sticky; left: 0; z-index: 1;
  background: var(--ks-surface-default);
  /* Right-hand crease of shade so scrolled-under content reads as behind it. */
  box-shadow: inset -1px 0 0 rgb(var(--ks-shadow-color) / 0.06),
              6px 0 6px -6px rgb(var(--ks-shadow-color) / 0.18);
}
/* Header cells keep their subtle fill; striped body cells match their row tone
   so the frozen column doesn't punch a white stripe over scrolled content. */
.ks-table--sticky-col thead th:first-child { background: var(--ks-surface-subtle); z-index: 3; }
.ks-table--sticky-col tbody tr:nth-child(even) td:first-child { background: color-mix(in srgb, var(--ks-surface-default) 97.5%, rgb(var(--ks-shadow-color))); }
.ks-table--striped.ks-table--sticky-col tbody tr:nth-child(even) td:first-child { background: color-mix(in srgb, var(--ks-surface-default) 96%, rgb(var(--ks-shadow-color))); }
.ks-table--hover.ks-table--sticky-col tbody tr:hover td:first-child { background: var(--ks-brand-primary-subtle); }
.ks-table tbody tr.is-selected.ks-table--sticky-col td:first-child,
.ks-table--sticky-col tbody tr.is-selected td:first-child { background: var(--ks-brand-primary-subtle); }
/* Frozen top-left corner sits above both sticky axes. */
.ks-table--sticky.ks-table--sticky-col thead th:first-child { z-index: 4; }

/* Table toolbar — a flex bar above a table for search / filter / actions. */
.ks-table-toolbar {
  display: flex; align-items: center; gap: var(--ks-space-3);
  flex-wrap: wrap;
  margin-bottom: var(--ks-space-3);
}
.ks-table-toolbar-title { font-weight: var(--ks-weight-semibold); color: var(--ks-text-default); }
/* Push everything after a spacer to the right (search + actions). */
.ks-table-toolbar-spacer { flex: 1 1 auto; }
.ks-table-toolbar-actions { display: inline-flex; align-items: center; gap: var(--ks-space-2); margin-left: auto; }

/* Empty / no-data state — centered. Works as a block under a table OR dropped
   into a full-width <td colspan> row inside the <tbody>. */
.ks-table-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--ks-space-2); text-align: center;
  padding: var(--ks-space-8) var(--ks-space-4);
  color: var(--ks-text-muted);
}
.ks-table-empty-icon { color: var(--ks-text-subtle); }
.ks-table-empty-title { font-weight: var(--ks-weight-semibold); color: var(--ks-text-default); }
.ks-table-empty-message { font-size: var(--ks-text-sm); color: var(--ks-text-muted); max-width: 40ch; }
/* Row form: the <td> is transparent so the flex block centers across the width. */
.ks-table td.ks-table-empty-cell { padding: 0; background: none; }

/* Responsive stack — below a narrow breakpoint each row becomes a labeled card.
   Consumers add data-label="Column name" to each <td>; the header row hides and
   each cell shows its label. Opt-in per table via .ks-table--stack. */
@media (max-width: 640px) {
  .ks-table--stack thead { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .ks-table--stack, .ks-table--stack tbody, .ks-table--stack tr, .ks-table--stack td { display: block; width: auto; }
  .ks-table--stack tbody tr {
    margin-bottom: var(--ks-space-3);
    padding: var(--ks-space-1);
    border-radius: var(--ks-radius-md);
    background: var(--ks-surface-default);
    box-shadow: var(--ks-shadow-sm), var(--ks-light-top);
  }
  .ks-table--stack tbody tr:nth-child(even) { background: var(--ks-surface-default); }
  .ks-table--stack td {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--ks-space-4);
    padding: var(--ks-space-2) var(--ks-space-3);
    text-align: right;
  }
  .ks-table--stack td + td { box-shadow: inset 0 1px 0 rgb(var(--ks-shadow-color) / 0.06); }
  .ks-table--stack td[data-label]::before {
    content: attr(data-label);
    font-weight: var(--ks-weight-semibold);
    color: var(--ks-text-muted);
    text-align: left; margin-right: auto;
  }
  /* Alignment helpers are neutralized in stack mode (label-left, value-right). */
  .ks-table--stack td.ks-table-num,
  .ks-table--stack td.ks-table-cell--right,
  .ks-table--stack td.ks-table-cell--center,
  .ks-table--stack td.ks-table-actions { text-align: right; }
  /* A sticky column can't freeze in a single-column card; drop it back to flow. */
  .ks-table--stack.ks-table--sticky-col td:first-child,
  .ks-table--stack.ks-table--sticky-col th:first-child { position: static; box-shadow: none; }
}

/* ---- Navbar ---- */
.ks-navbar {
  display: flex; align-items: center; gap: var(--ks-gap-md);
  height: var(--ks-topbar-height); padding: 0 var(--ks-pad-md);
  background: var(--ks-surface-default);
  box-shadow: var(--ks-shadow-sm), var(--ks-light-top);
}
.ks-navbar-brand { font-weight: var(--ks-weight-bold); font-size: var(--ks-text-lg); }
.ks-navbar-nav { display: flex; gap: var(--ks-space-2); align-items: center; }
.ks-navbar-link { padding: var(--ks-space-2) var(--ks-space-3); border-radius: var(--ks-radius-md); color: var(--ks-text-muted); text-decoration: none; transition: var(--ks-transition-colors), box-shadow var(--ks-duration-fast) var(--ks-ease-default); }
.ks-navbar-link:hover { color: var(--ks-text-default); background: rgb(var(--ks-shadow-color) / 0.05); }
.ks-navbar-link.is-active { color: var(--ks-text-default); background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen); box-shadow: var(--ks-shadow-xs), var(--ks-light-top); }

/* ---- Tabs ---- (a recessed track; the active tab rises out of it) */
.ks-tabs-list {
  position: relative;
  display: inline-flex; gap: var(--ks-space-1);
  padding: var(--ks-space-1); border-radius: var(--ks-radius-lg);
  background: var(--ks-surface-subtle); box-shadow: var(--ks-well);
}
.ks-tabs-tab {
  position: relative; z-index: 1;
  appearance: none; background: transparent; border: 0; cursor: pointer;
  padding: var(--ks-space-2) var(--ks-space-4); font: inherit; font-size: var(--ks-text-sm);
  color: var(--ks-text-muted); border-radius: var(--ks-radius-md);
  transition: var(--ks-transition-colors), box-shadow var(--ks-duration-fast) var(--ks-ease-default), transform var(--ks-duration-fast) var(--ks-ease-spring);
}
.ks-tabs-tab:hover { color: var(--ks-text-default); }
.ks-tabs-tab.is-active {
  color: var(--ks-text-default); font-weight: var(--ks-weight-medium);
  background-color: var(--ks-surface-default);
  box-shadow: var(--ks-edge);
}
.ks-tabs-panel { padding: var(--ks-pad-md) 0; }

/* Animated sliding indicator — enabled only once the JS `tabs` behavior sets
   data-ks-indicator + measures --ks-tab-x/--ks-tab-w on the list. The raised
   steel key then rides on the ::before instead of the active tab, so it glides
   between tabs. Without JS the .is-active tab keeps its own static key above. */
.ks-tabs-list[data-ks-indicator]::before {
  content: ""; position: absolute; z-index: 0; top: var(--ks-space-1); bottom: var(--ks-space-1);
  left: 0; width: var(--ks-tab-w, 0); transform: translateX(var(--ks-tab-x, 0));
  background-color: var(--ks-surface-default); box-shadow: var(--ks-edge);
  border-radius: var(--ks-radius-md);
}
/* Animate only after the first measurement (JS adds -ready next frame), so the
   indicator appears in place on load instead of sliding in from the left edge. */
.ks-tabs-list[data-ks-indicator-ready]::before {
  transition: transform var(--ks-duration-normal) var(--ks-ease-spring),
              width var(--ks-duration-normal) var(--ks-ease-spring);
}
.ks-tabs-list[data-ks-indicator] .ks-tabs-tab.is-active {
  background-color: transparent; box-shadow: none;
}
/* Soft cross-fade for the tab panel swap driven by document.startViewTransition. */
::view-transition-old(ks-tabpanel),
::view-transition-new(ks-tabpanel) {
  animation-duration: var(--ks-duration-normal);
  animation-timing-function: var(--ks-ease-default);
}

/* ---- Breadcrumb ---- */
.ks-breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ks-space-2); font-size: var(--ks-text-sm); list-style: none; padding: 0; margin: 0; }
.ks-breadcrumb li { display: flex; align-items: center; gap: var(--ks-space-2); color: var(--ks-text-muted); }
.ks-breadcrumb li:not(:last-child)::after { content: "/"; color: var(--ks-text-subtle); }
.ks-breadcrumb a { color: var(--ks-text-link); text-decoration: none; }

/* ---- Pagination ---- */
.ks-pagination { display: inline-flex; gap: var(--ks-space-1\.5); list-style: none; padding: 0; margin: 0; }
.ks-pagination a, .ks-pagination span, .ks-pagination button {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--ks-control-height-sm); height: var(--ks-control-height-sm); padding: 0 var(--ks-space-2);
  border-radius: var(--ks-radius-md);
  background-color: var(--ks-surface-default);
  box-shadow: var(--ks-edge);
  color: var(--ks-text-default); text-decoration: none; font-size: var(--ks-text-sm);
  transition: background-color var(--ks-duration-fast) var(--ks-ease-default), color var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-pagination a:hover, .ks-pagination button:not(:disabled):hover { background-color: var(--ks-surface-subtle); }
.ks-pagination .is-active span, .ks-pagination a.is-active, .ks-pagination button.is-active {
  color: #fff; background-color: var(--ks-blue-700);
  box-shadow: none;
}
/* Generated controls are real <button>s: kill the UA chrome, keep the focus ring. */
.ks-pagination button { font: inherit; font-size: var(--ks-text-sm); border: 0; cursor: pointer; }
.ks-pagination button:disabled { opacity: 0.45; cursor: default; }
.ks-pagination button:focus-visible { outline: 0; box-shadow: var(--ks-glow-focus); }
.ks-pagination button svg { width: 1rem; height: 1rem; }
/* The elision between page-number runs — not a control, so no chip treatment. */
.ks-pagination-gap {
  background: none; box-shadow: none; color: var(--ks-text-subtle);
  min-width: auto; padding: 0 var(--ks-space-1);
}

/* ---- Table pager ----
   The status line is the load-bearing half ("1–25 of 320"): it stays even when
   a single page of rows makes the controls pointless. */
.ks-table-pager {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--ks-space-3);
  margin-top: var(--ks-space-3);
}
.ks-table-pager.is-single { justify-content: flex-start; }
.ks-table-pager-status {
  margin: 0;
  font-size: var(--ks-text-sm); color: var(--ks-text-muted);
  font-variant-numeric: tabular-nums;
}
/* Rows outside the current page. Distinct from [hidden] on purpose — that
   belongs to consumer-side filtering, and unpaging must never reveal a row
   someone else filtered out. */
.ks-table tbody tr.is-paged-out { display: none; }

/* ---- Table row selection ---- */
.ks-table th.ks-table-select-cell, .ks-table td.ks-table-select-cell {
  width: 1%; white-space: nowrap;
  padding-right: var(--ks-space-2);
  text-align: center;
}
.ks-table-select-box { gap: 0; vertical-align: middle; }
/* Right-aligned: this menu hangs off a toolbar button at the right edge, and
   .ks-card clips overflow — a left-aligned menu loses its trailing corner. */
.ks-table-colmenu .ks-dropdown-menu {
  left: auto; right: 0;
  transform-origin: top right;
  max-height: 60vh; overflow-y: auto;
}
/* A checkbox row inside the column menu keeps dropdown-item padding but reads
   as a control, not a command: no accent swap on hover. */
.ks-table-colmenu .ks-dropdown-item:hover { background: var(--ks-surface-subtle); color: var(--ks-text-default); }

/* ---- Overlays: modal + drawer ---- */
[data-ks-modal] {
  position: fixed; inset: 0; z-index: var(--ks-z-modal);
  display: none; align-items: center; justify-content: center;
  padding: var(--ks-space-4);
  background: var(--ks-surface-overlay);
  backdrop-filter: blur(4px) saturate(1.1);
}
[data-ks-modal].is-open { display: flex; }
.ks-modal-dialog {
  width: 100%; max-width: 480px; max-height: 90vh; overflow: auto;
  background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen);
  color: var(--ks-text-default);
  border-radius: var(--ks-radius-modal); box-shadow: var(--ks-shadow-2xl), var(--ks-light-top);
}
.ks-modal-header { display: flex; align-items: center; justify-content: space-between; gap: var(--ks-space-3); padding: var(--ks-pad-md); box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.06); }
.ks-modal-title { font-size: var(--ks-type-h4-size); font-weight: var(--ks-weight-semibold); margin: 0; }
.ks-modal-body { padding: var(--ks-pad-md); }
.ks-modal-footer { display: flex; justify-content: flex-end; gap: var(--ks-space-2); padding: var(--ks-pad-md); background: rgb(var(--ks-shadow-color) / 0.02); box-shadow: inset 0 1px 0 rgb(var(--ks-shadow-color) / 0.05); }
.ks-modal-close, .ks-drawer-close, .ks-toast-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; padding: 0; flex: none;
  background: transparent; border: 0; border-radius: var(--ks-radius-md);
  color: var(--ks-text-muted); cursor: pointer; transition: var(--ks-transition-colors);
}
.ks-modal-close:hover, .ks-drawer-close:hover, .ks-toast-close:hover { background: rgb(var(--ks-shadow-color) / 0.08); color: var(--ks-text-default); }

/* ---- Promise-based dialogs (confirm / alert / prompt) ---- */
/* Reuse every .ks-modal* rule above; only the variant header icon differs. */
.ks-dialog-icon { flex: none; width: 1.35em; height: 1.35em; color: var(--ks-text-muted); }
.ks-dialog .ks-modal-header { justify-content: flex-start; }
.ks-dialog .ks-modal-title { flex: 1; min-width: 0; }
.ks-dialog--info    .ks-dialog-icon { color: var(--ks-info-default); }
.ks-dialog--success .ks-dialog-icon { color: var(--ks-success-default); }
.ks-dialog--warning .ks-dialog-icon { color: var(--ks-warning-default); }
.ks-dialog--error   .ks-dialog-icon { color: var(--ks-error-default); }
.ks-dialog--danger  .ks-dialog-icon { color: var(--ks-error-default); }
.ks-dialog .ks-modal-body > p { margin: 0; color: var(--ks-text-muted); }
.ks-dialog .ks-field { margin-bottom: 0; margin-top: var(--ks-space-4); }
.ks-dialog .ks-field:first-child { margin-top: 0; }

[data-ks-drawer] {
  position: fixed; inset: 0; z-index: var(--ks-z-modal);
  display: none; background: var(--ks-surface-overlay); backdrop-filter: blur(4px) saturate(1.1);
}
[data-ks-drawer].is-open { display: block; }
.ks-drawer-panel {
  position: absolute; top: 0; bottom: 0; right: 0;
  width: min(360px, 90vw); padding: var(--ks-pad-md);
  background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen);
  box-shadow: var(--ks-shadow-2xl); overflow: auto;
}
[data-ks-drawer][data-ks-drawer-side="left"] .ks-drawer-panel { right: auto; left: 0; }

/* ---- Dropdown ---- */
[data-ks-dropdown] { position: relative; display: inline-block; }
.ks-dropdown-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: var(--ks-z-dropdown);
  display: none; min-width: 184px; padding: var(--ks-space-1\.5);
  background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen);
  border-radius: var(--ks-radius-lg); box-shadow: var(--ks-shadow-lg), var(--ks-light-top);
  /* Menu opens downward from the trigger, so it should grow from its top edge. */
  transform-origin: top left;
}
[data-ks-dropdown].is-open .ks-dropdown-menu { display: block; animation: ks-pop var(--ks-duration-fast) var(--ks-ease-out); }
.ks-dropdown-item {
  display: flex; align-items: center; gap: var(--ks-space-2);
  width: 100%; padding: var(--ks-space-2) var(--ks-space-3);
  font-size: var(--ks-text-sm); color: var(--ks-text-default); text-align: left;
  background: transparent; border: 0; border-radius: var(--ks-radius-md); cursor: pointer; text-decoration: none;
}
.ks-dropdown-item:hover { background: var(--ks-brand-primary-subtle); color: var(--ks-brand-primary); }
.ks-dropdown-divider { height: 1px; margin: var(--ks-space-1\.5) 0; background: rgb(var(--ks-shadow-color) / 0.08); }

/* Overlay entrance — subtle grow + fade + short drop. transform-origin (set per
   component) points the scale at the anchoring edge so it reads as unfolding from
   the trigger. No bounce; --ks-ease-out settles it. */
@keyframes ks-pop { from { opacity: 0; transform: translateY(-4px) scale(0.96); } to { opacity: 1; transform: none; } }

/* ---- Popover ---- */
.ks-popover {
  position: absolute; z-index: var(--ks-z-popover);
  min-width: 200px; padding: var(--ks-pad-sm);
  background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen);
  border-radius: var(--ks-radius-lg); box-shadow: var(--ks-shadow-lg), var(--ks-light-top);
  /* Default (below the trigger); JS flips origin to bottom when it flips above. */
  transform-origin: top center;
}
.ks-popover.is-above { transform-origin: bottom center; }
.ks-popover.is-open { animation: ks-pop var(--ks-duration-fast) var(--ks-ease-out); }

/* ---- Tooltip ---- */
.ks-tooltip {
  z-index: var(--ks-z-tooltip);
  max-width: 240px; padding: var(--ks-space-1\.5) var(--ks-space-2\.5);
  font-size: var(--ks-text-xs); line-height: var(--ks-leading-snug);
  color: var(--ks-slate-0); background: var(--ks-slate-900);
  border-radius: var(--ks-radius-tooltip); box-shadow: var(--ks-shadow-lg);
  /* Rests just below its shown position and grows from the bottom edge — it sits
     above the trigger, so the scale reads as rising out of it. */
  opacity: 0; transform: translateY(2px) scale(0.96); transform-origin: bottom center;
  transition: opacity var(--ks-duration-fast) var(--ks-ease-default), transform var(--ks-duration-fast) var(--ks-ease-out); pointer-events: none;
}
/* Dark theme: a tooltip should stay a dark, elevated chip — not flip to white. */
[data-theme="dark"] .ks-tooltip { background: var(--ks-surface-emphasis); color: var(--ks-text-default); box-shadow: var(--ks-shadow-lg), var(--ks-edge); }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .ks-tooltip { background: var(--ks-surface-emphasis); color: var(--ks-text-default); box-shadow: var(--ks-shadow-lg), var(--ks-edge); } }
.ks-tooltip.is-visible { opacity: 1; transform: none; }

/* ---- Context menu ---- (custom right-click menu, built by the JS) */
.ks-context-menu {
  position: fixed; z-index: var(--ks-z-popover);
  min-width: 168px; padding: var(--ks-space-1);
  background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen);
  border-radius: var(--ks-radius-md); box-shadow: var(--ks-shadow-lg), var(--ks-edge);
  /* Grows from the cursor corner (top-left); JS re-points it when the menu is
     clamped so it doesn't spill off-screen. */
  transform-origin: top left;
  animation: ks-pop var(--ks-duration-fast) var(--ks-ease-out);
  /* Exit (JS adds .is-leaving, then removes on transitionend / a short timeout). */
  transition: opacity var(--ks-duration-fast) var(--ks-ease-in), transform var(--ks-duration-fast) var(--ks-ease-in);
  user-select: none;
}
.ks-context-menu.is-leaving { opacity: 0; transform: scale(0.96); pointer-events: none; }
.ks-context-menu-item {
  display: flex; align-items: center; gap: var(--ks-space-2\.5);
  width: 100%; padding: var(--ks-space-2) var(--ks-space-2\.5);
  font-size: var(--ks-text-sm); color: var(--ks-text-default); text-align: left;
  background: transparent; border: 0; border-radius: var(--ks-radius-sm); cursor: pointer;
}
.ks-context-menu-item .ks-i { color: var(--ks-text-muted); font-size: 1em; }
.ks-context-menu-item:hover, .ks-context-menu-item:focus-visible {
  background: var(--ks-brand-primary-subtle); color: var(--ks-brand-primary); outline: 0;
}
.ks-context-menu-item:hover .ks-i, .ks-context-menu-item:focus-visible .ks-i { color: var(--ks-brand-primary); }
.ks-context-menu-divider { height: 1px; margin: var(--ks-space-1) 0; background: rgb(var(--ks-shadow-color) / 0.08); }

/* ---- Accordion ---- (items parted by a soft crease of shade) */
.ks-accordion-item { box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.06); }
.ks-accordion-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ks-space-3);
  width: 100%; padding: var(--ks-space-3\.5) var(--ks-space-2);
  font: inherit; font-weight: var(--ks-weight-medium); text-align: left;
  background: transparent; border: 0; cursor: pointer; color: var(--ks-text-default);
  border-radius: var(--ks-radius-md);
  transition: background-color var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-accordion-trigger:hover { background: rgb(var(--ks-shadow-color) / 0.03); }
.ks-accordion-caret { transition: transform var(--ks-duration-normal) var(--ks-ease-spring); flex: none; }
.ks-accordion-item.is-open .ks-accordion-caret { transform: rotate(180deg); }
.ks-accordion-content { padding: 0 var(--ks-space-2) var(--ks-space-3\.5); color: var(--ks-text-muted); }

/* Smooth expand/collapse. Once the JS `accordion` behavior wraps the content
   (adding data-ks-accordion-anim + an inner .ks-accordion-content-inner), the
   content becomes a 0fr->1fr grid so its height eases open, no [hidden] needed.
   Without JS the [hidden] attribute still shows/hides instantly. */
.ks-accordion-content[data-ks-accordion-anim] {
  display: grid; grid-template-rows: 0fr;
  padding-top: 0; padding-bottom: 0; opacity: 0;
  transition: grid-template-rows var(--ks-duration-normal) var(--ks-ease-default),
              padding var(--ks-duration-normal) var(--ks-ease-default),
              opacity var(--ks-duration-normal) var(--ks-ease-default);
}
.ks-accordion-content[data-ks-accordion-anim] > .ks-accordion-content-inner {
  min-height: 0; overflow: hidden;
}
.ks-accordion-item.is-open > .ks-accordion-content[data-ks-accordion-anim] {
  grid-template-rows: 1fr; opacity: 1;
  padding-bottom: var(--ks-space-3\.5);
}

/* ---- Toast ---- (floats up on a deep soft shadow; colored glow per variant) */
.ks-toast-region {
  position: fixed; bottom: var(--ks-space-4); right: var(--ks-space-4); z-index: var(--ks-z-tooltip);
  display: flex; flex-direction: column; gap: var(--ks-space-2\.5); width: min(360px, calc(100vw - 2rem));
}
.ks-toast {
  display: flex; align-items: flex-start; gap: var(--ks-space-2\.5);
  padding: var(--ks-pad-sm) var(--ks-pad-md);
  background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen);
  color: var(--ks-text-default);
  border-radius: var(--ks-radius-lg);
  box-shadow: var(--ks-shadow-xl), var(--ks-light-top);
  /* Bounded so the exit can ease max-height -> 0 (browsers can't transition from
     an intrinsic/none height). Comfortably taller than any real toast. */
  max-height: 240px;
  /* Enters sliding in from the region's edge (region sits bottom-right). */
  opacity: 0; transform: translateX(16px) scale(0.98);
  transition: opacity var(--ks-duration-normal) var(--ks-ease-default), transform var(--ks-duration-normal) var(--ks-ease-out);
}
.ks-toast.is-visible { opacity: 1; transform: none; }
/* Exit — fade + slide back out to the edge, then the box collapses (max-height +
   margin) so the remaining toasts in the flex column ease up into the gap it
   leaves (stack reflow without FLIP). JS removes the node when this settles. */
.ks-toast.is-leaving {
  opacity: 0; transform: translateX(16px) scale(0.98);
  max-height: 0; margin-top: calc(-1 * var(--ks-space-2\.5)); padding-top: 0; padding-bottom: 0;
  overflow: hidden; pointer-events: none;
  transition: opacity var(--ks-duration-fast) var(--ks-ease-in),
              transform var(--ks-duration-fast) var(--ks-ease-in),
              max-height var(--ks-duration-normal) var(--ks-ease-default) var(--ks-duration-fast),
              margin-top var(--ks-duration-normal) var(--ks-ease-default) var(--ks-duration-fast),
              padding var(--ks-duration-normal) var(--ks-ease-default) var(--ks-duration-fast);
}
.ks-toast-icon { flex: none; margin-top: 0.1em; }
.ks-toast-body { flex: 1; min-width: 0; }
.ks-toast-title { font-weight: var(--ks-weight-semibold); font-size: var(--ks-text-sm); }
.ks-toast-message { font-size: var(--ks-text-sm); color: var(--ks-text-muted); }
.ks-toast--success { box-shadow: var(--ks-shadow-xl), var(--ks-light-top), inset 14px 0 18px -14px var(--ks-success-default); }
.ks-toast--success .ks-toast-icon { color: var(--ks-success-default); }
.ks-toast--warning { box-shadow: var(--ks-shadow-xl), var(--ks-light-top), inset 14px 0 18px -14px var(--ks-warning-default); }
.ks-toast--warning .ks-toast-icon { color: var(--ks-warning-default); }
.ks-toast--error { box-shadow: var(--ks-shadow-xl), var(--ks-light-top), inset 14px 0 18px -14px var(--ks-error-default); }
.ks-toast--error .ks-toast-icon { color: var(--ks-error-default); }
.ks-toast--info { box-shadow: var(--ks-shadow-xl), var(--ks-light-top), inset 14px 0 18px -14px var(--ks-info-default); }
.ks-toast--info .ks-toast-icon { color: var(--ks-info-default); }

/* ---- Progress & spinner ---- */
.ks-progress { width: 100%; height: 10px; background: var(--ks-surface-emphasis); border-radius: var(--ks-radius-sm); box-shadow: var(--ks-well); overflow: hidden; position: relative; }
.ks-progress-bar { position: relative; height: 100%; background-color: var(--ks-blue-700); border-radius: inherit; overflow: hidden; transition: width var(--ks-duration-normal) var(--ks-ease-default); }
/* Indeterminate — no known percentage: a steel band sweeps the track on a loop.
   Add `.ks-progress--indeterminate` (or `[data-ks-progress-indeterminate]`) to the
   track; the bar's inline width is ignored (a fixed-width band is animated via
   translate instead). Grain rides along so the band reads as material, not a plain
   fill. Reduced motion (§ Motion preferences) stops the sweep — see below for the
   static resting bar. */
.ks-progress--indeterminate,
[data-ks-progress-indeterminate] { }
.ks-progress--indeterminate .ks-progress-bar,
[data-ks-progress-indeterminate] .ks-progress-bar {
  width: 40%;
  transition: none;
  animation: ks-progress-sweep 1.3s var(--ks-ease-default) infinite;
  will-change: transform;
}
/* Faint grain over the sweeping band, so it matches the material language. */
.ks-progress--indeterminate .ks-progress-bar::after,
[data-ks-progress-indeterminate] .ks-progress-bar::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background-color: rgb(var(--ks-grain-ink));
  -webkit-mask-image: var(--ks-grain); mask-image: var(--ks-grain);
  -webkit-mask-size: 160px 160px; mask-size: 160px 160px;
  -webkit-mask-repeat: repeat; mask-repeat: repeat;
  opacity: calc(var(--ks-grain-opacity) * 1.2);
}
@keyframes ks-progress-sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
.ks-spinner {
  display: inline-block; width: 1.25em; height: 1.25em;
  border: 2px solid rgb(var(--ks-shadow-color) / 0.12); border-top-color: var(--ks-brand-primary);
  border-radius: var(--ks-radius-full); animation: ks-spin 0.7s linear infinite; vertical-align: middle;
}
@keyframes ks-spin { to { transform: rotate(360deg); } }

/* ---- Avatar ---- */
.ks-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--ks-icon-xl); height: var(--ks-icon-xl); flex: none;
  border-radius: var(--ks-radius-full); overflow: hidden;
  background-color: var(--ks-surface-emphasis);
  color: var(--ks-text-muted); box-shadow: var(--ks-edge-soft);
  font-weight: var(--ks-weight-semibold); font-size: var(--ks-text-sm);
}
.ks-avatar img { width: 100%; height: 100%; object-fit: cover; }
.ks-avatar--sm { width: var(--ks-icon-lg); height: var(--ks-icon-lg); }

/* ---- List ---- */
.ks-list {
  list-style: none; margin: 0; padding: 0; overflow: hidden;
  border-radius: var(--ks-radius-lg);
  background-color: var(--ks-surface-default); background-image: var(--ks-surface-sheen);
  box-shadow: var(--ks-shadow-md), var(--ks-light-top);
}
.ks-list-item { display: flex; align-items: center; gap: var(--ks-density-list-gap); padding: var(--ks-density-pad-y) var(--ks-density-list-pad-x); } /* density: gap & pad-y base --ks-space-3 (12px), pad-x base --ks-pad-md (16px) */
.ks-list-item:nth-child(even) { background: rgb(var(--ks-shadow-color) / 0.025); }

/* ---- Skeleton ----
   A tonal loading placeholder: a recessed surface that reads as material via a
   faint grain, swept by a slow diagonal highlight. No border — the tone gap and
   inset edge define it. Motion falls back to a static tone (see reduced-motion). */
.ks-skeleton {
  position: relative;
  overflow: hidden;
  display: block;
  border-radius: var(--ks-radius-sm);
  background-color: var(--ks-surface-muted);
  box-shadow: var(--ks-edge-soft), var(--ks-shadow-inner);
  color: transparent;
  user-select: none;
}
/* Grain seasons the base so the flat tone reads as surface, not a grey box. */
.ks-skeleton::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border-radius: inherit;
  background-color: rgb(var(--ks-grain-ink));
  -webkit-mask-image: var(--ks-grain); mask-image: var(--ks-grain);
  -webkit-mask-size: 160px 160px; mask-size: 160px 160px;
  -webkit-mask-repeat: repeat; mask-repeat: repeat;
  opacity: calc(var(--ks-grain-opacity) * 1.2);
}
/* The sweeping highlight — a soft light band travelling left to right. */
.ks-skeleton::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  border-radius: inherit;
  background-image: linear-gradient(
    100deg,
    transparent 30%,
    rgb(255 255 255 / 0.55) 50%,
    transparent 70%
  );
  background-size: 220% 100%;
  background-repeat: no-repeat;
  background-position: 180% 0;
  animation: ks-skeleton-sweep 1.6s var(--ks-ease-default) infinite;
}
.ks-skeleton--text {
  height: 0.75em; margin: 0.2em 0;
  border-radius: var(--ks-radius-sm);
}
.ks-skeleton--text:last-child { width: 62%; }
.ks-skeleton--circle {
  width: var(--ks-icon-xl); height: var(--ks-icon-xl);
  flex-shrink: 0;
  border-radius: var(--ks-radius-full);
}
.ks-skeleton--block {
  width: 100%; height: 120px;
  border-radius: var(--ks-radius-md);
}
@keyframes ks-skeleton-sweep {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}
/* Skeleton → content cross-fade.
   Pattern: keep the skeleton in the DOM while loading; when the real content is
   ready, add `.ks-skeleton--out` to the skeleton to fade it away, then remove it
   on `transitionend` (or after a timeout). Fade the real content in with a
   matching `.ks-reveal.ks-reveal-js.is-revealed` (or just toggle its own opacity).
   For a single atomic swap you can instead wrap the swap in
   `document.startViewTransition` (feature-detected + reduced-motion-guarded, the
   same pattern tabs/modal use). Reduced motion zeroes this transition (§ Motion
   preferences), so the swap is instant. */
.ks-skeleton--out {
  opacity: 0;
  transition: opacity var(--ks-duration-normal) var(--ks-ease-out);
  pointer-events: none;
}
[data-theme="dark"] .ks-skeleton::after {
  background-image: linear-gradient(
    100deg,
    transparent 30%,
    rgb(255 255 255 / 0.08) 50%,
    transparent 70%
  );
}

/* ---- Stat card ----
   A KPI tile built on .ks-card: an uppercase label, a big tabular value, and a
   signed delta that borrows semantic success/error color plus a trend arrow. */
.ks-stat { padding: var(--ks-pad-md); }
.ks-stat-label {
  display: block;
  font-size: var(--ks-type-label-size);
  font-weight: var(--ks-type-label-weight);
  letter-spacing: var(--ks-type-label-tracking);
  text-transform: uppercase;
  color: var(--ks-text-muted);
}
.ks-stat-value {
  display: block;
  margin-top: var(--ks-space-1);
  font-size: var(--ks-type-h2-size);
  font-weight: var(--ks-weight-bold);
  line-height: var(--ks-leading-tight);
  color: var(--ks-text-default);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.ks-stat-delta {
  display: inline-flex; align-items: center; gap: var(--ks-space-1);
  margin-top: var(--ks-space-2);
  font-size: var(--ks-text-sm);
  font-weight: var(--ks-weight-semibold);
  color: var(--ks-text-muted);
  font-variant-numeric: tabular-nums;
}
.ks-stat-delta .ks-i { font-size: 1em; }
.ks-stat-delta--up   { color: var(--ks-success-text); }
.ks-stat-delta--down { color: var(--ks-error-text); }

/* ---- Empty state ----
   Zero-data / no-results / first-run. Vertically centred: a tonal icon well,
   a title, a message, and an optional action row. No border — tone + inset. */
.ks-empty {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; text-align: center;
  gap: var(--ks-space-2);
  padding: var(--ks-pad-xl) var(--ks-pad-lg);
}
.ks-empty-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--ks-space-16); height: var(--ks-space-16);
  margin-bottom: var(--ks-space-1);
  border-radius: var(--ks-radius-lg);
  background-color: var(--ks-surface-muted);
  color: var(--ks-text-subtle);
  box-shadow: var(--ks-edge-soft);
}
.ks-empty-icon .ks-i { font-size: var(--ks-icon-lg); }
.ks-empty-title {
  font-size: var(--ks-type-h5-size);
  font-weight: var(--ks-weight-semibold);
  line-height: var(--ks-type-h5-leading);
  color: var(--ks-text-default);
  text-wrap: balance;
}
.ks-empty-message {
  max-width: 44ch;
  font-size: var(--ks-type-body-sm-size);
  line-height: var(--ks-type-body-sm-leading);
  color: var(--ks-text-muted);
  text-wrap: pretty;
}
.ks-empty-actions {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--ks-space-2);
  margin-top: var(--ks-space-2);
}

/* ---- Timeline ----
   A vertical rail of events. The rail is a tonal inset line (no border); each
   marker is a tonal dot that can adopt a semantic status color. */
.ks-timeline {
  --ks-timeline-dot: 14px;
  --ks-timeline-gutter: var(--ks-space-6);
  list-style: none; margin: 0; padding: 0;
}
.ks-timeline-item {
  position: relative;
  padding-left: var(--ks-timeline-gutter);
  padding-bottom: var(--ks-space-5);
}
/* The connecting rail — a soft tonal line, drawn behind the markers. */
.ks-timeline-item::before {
  content: ""; position: absolute;
  top: calc(var(--ks-timeline-dot) / 2);
  left: calc(var(--ks-timeline-dot) / 2 - 1px);
  bottom: 0;
  width: 2px;
  background-color: rgb(var(--ks-shadow-color) / 0.14);
}
.ks-timeline-item:last-child { padding-bottom: 0; }
.ks-timeline-item:last-child::before { display: none; }
.ks-timeline-marker {
  position: absolute;
  top: calc(var(--ks-timeline-dot) / 2);
  left: 0;
  width: var(--ks-timeline-dot);
  height: var(--ks-timeline-dot);
  transform: translateY(-50%);
  border-radius: var(--ks-radius-full);
  background-color: var(--ks-surface-emphasis);
  box-shadow:
    var(--ks-edge),
    0 0 0 3px var(--ks-surface-default);
}
.ks-timeline-marker--success { background-color: var(--ks-success-default); }
.ks-timeline-marker--warning { background-color: var(--ks-warning-default); }
.ks-timeline-marker--error   { background-color: var(--ks-error-default); }
.ks-timeline-time {
  display: block;
  font-size: var(--ks-text-xs);
  font-weight: var(--ks-weight-medium);
  letter-spacing: var(--ks-tracking-wide);
  text-transform: uppercase;
  color: var(--ks-text-subtle);
  font-variant-numeric: tabular-nums;
}
.ks-timeline-body {
  margin-top: var(--ks-space-1);
  font-size: var(--ks-type-body-sm-size);
  line-height: var(--ks-type-body-sm-leading);
  color: var(--ks-text-default);
}

/* ---- Motion preferences ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
  .ks-btn:hover, .ks-card--interactive:hover, .ks-pagination a:hover { transform: none; }
  /* Skeleton drops its sweeping highlight and rests on a plain static tone. */
  .ks-skeleton::after { display: none; }
  /* Indeterminate progress can't loop under reduced motion (the global guard
     would freeze the sweeping band mid-track). Show an honest static partial
     bar instead — anchored at the start, not translated off-screen. */
  .ks-progress--indeterminate .ks-progress-bar,
  [data-ks-progress-indeterminate] .ks-progress-bar {
    animation: none !important;
    transform: none !important;
    width: 35%;
  }
}

/* ============================================================
   5. NAVIGATION & MOTION
   View Transitions, scroll behaviour, scroll-driven reveals.
   ============================================================ */

/* Cross-document (multi-page) view transitions. Every Keystone page loads
   this stylesheet, so navigating between same-origin pages cross-fades
   smoothly with zero JavaScript. (Chromium today; a graceful no-op elsewhere.) */
@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 160ms;
  animation-timing-function: var(--ks-ease-default);
}
/* Cross-fade ONLY — no page-level translate, so loads / reloads / navigations
   don't visibly slide or "bounce". Shared elements (header/brand) still morph. */
@keyframes ks-vt-out { to   { opacity: 0; } }
@keyframes ks-vt-in  { from { opacity: 0; } }
::view-transition-old(root) { animation-name: ks-vt-out; }
::view-transition-new(root) { animation-name: ks-vt-in; }

/* Give the SAME logical element this class on each page and it persists /
   morphs across navigations instead of fading with the page body. */
.ks-vt-header { view-transition-name: ks-header; }
.ks-vt-brand  { view-transition-name: ks-brand; }

/* Scroll polish: keep anchored targets clear of the sticky header,
   and give the page a refined, unobtrusive scrollbar. */
html { scroll-padding-top: 84px; }
* { scrollbar-width: thin; scrollbar-color: rgb(var(--ks-shadow-color) / 0.30) transparent; }
*::-webkit-scrollbar { width: 12px; height: 12px; }
*::-webkit-scrollbar-thumb {
  background: rgb(var(--ks-shadow-color) / 0.22);
  border-radius: 99px; border: 3px solid transparent; background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgb(var(--ks-shadow-color) / 0.36); background-clip: padding-box; }

/* Scroll-driven reveal — content rises and fades in as it enters the viewport.
   Progressive enhancement: only where supported and motion is welcome. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .ks-reveal {
      animation: ks-reveal-in linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 24%;
    }
    @keyframes ks-reveal-in {
      from { opacity: 0; transform: translateY(30px); }
      to   { opacity: 1; transform: none; }
    }
  }
}

/* JS IntersectionObserver fallback for `.ks-reveal` — used ONLY where
   scroll-timeline is unsupported. The `revealOnScroll` behavior in keystone.js
   adds `.ks-reveal-js` to each element it observes (and only when motion is
   allowed AND animation-timeline is NOT supported), pre-hides it, then swaps in
   `.is-revealed` as it enters the viewport. Gated behind BOTH:
     - `@supports not (animation-timeline: view())` so supported browsers keep
       the pure-CSS scroll animation and never double-animate; and
     - the `.ks-reveal-js` marker so an element is hidden ONLY once JS has taken
       ownership — no JS (or reduced motion, where JS skips) ⇒ content just shows.
   The `no-preference` query is belt-and-suspenders alongside the JS guard. */
@supports not (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .ks-reveal.ks-reveal-js {
      opacity: 0;
      transform: translateY(30px);
      transition: opacity var(--ks-duration-slow) var(--ks-ease-out),
                  transform var(--ks-duration-slow) var(--ks-ease-out);
      will-change: opacity, transform;
    }
    .ks-reveal.ks-reveal-js.is-revealed {
      opacity: 1;
      transform: none;
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ============================================================
   6. TEXTURE UTILITY, CUSTOM SELECT, RIPPLE, RESPONSIVE
   ============================================================ */

/* ---- Texture: apply grain to any element surface ---- */
.ks-textured { position: relative; isolation: isolate; }
.ks-textured::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border-radius: inherit;
  background-color: rgb(var(--ks-grain-ink));
  -webkit-mask-image: var(--ks-grain); mask-image: var(--ks-grain);
  -webkit-mask-size: 160px 160px; mask-size: 160px 160px;
  -webkit-mask-repeat: repeat; mask-repeat: repeat;
  opacity: calc(var(--ks-grain-opacity) * 1.6);
}
.ks-textured > * { position: relative; z-index: 1; }

/* ---- Click ripple — a contained tonal pulse, no movement, no glow ---- */
.ks-btn, [data-ks-ripple] { position: relative; overflow: hidden; }
.ks-ripple {
  position: absolute; border-radius: var(--ks-radius-full); pointer-events: none;
  background: currentColor; opacity: 0.22; transform: scale(0);
  animation: ks-ripple var(--ks-duration-slow) var(--ks-ease-out) forwards;
}
@keyframes ks-ripple { to { transform: scale(2.5); opacity: 0; } }

/* ---- Icon micro-motion ----
   Freshly-injected icons (theme toggle sun/moon/monitor swap) fade + scale in.
   Applied by keystone.js to the new <svg>; single-shot, reduced-motion strips it. */
@keyframes ks-icon-in { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: none; } }
.ks-icon-in { animation: ks-icon-in var(--ks-duration-normal) var(--ks-ease-spring) both; transform-origin: center; }
/* Copy success — the trigger icon pops as it morphs to a check, then back. */
@keyframes ks-icon-pop { 0% { transform: scale(0.6); } 60% { transform: scale(1.18); } 100% { transform: none; } }
.ks-icon-pop { animation: ks-icon-pop var(--ks-duration-slow) var(--ks-ease-spring) both; transform-origin: center; display: inline-block; }
@media (prefers-reduced-motion: reduce) {
  .ks-icon-in, .ks-icon-pop { animation: none; }
}

/* ---- Custom select (JS enhances a native <select data-ks-select>) ---- */
.ks-select-ui { position: relative; }
.ks-select-ui-button {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ks-space-2);
  width: 100%; height: var(--ks-control-height-md); padding: 0 var(--ks-space-3);
  font: inherit; font-size: var(--ks-text-sm); color: var(--ks-text-default); text-align: left;
  background: var(--ks-surface-subtle); border: 0; border-radius: var(--ks-radius-input);
  box-shadow: var(--ks-well); cursor: pointer;
  transition: box-shadow var(--ks-duration-fast) var(--ks-ease-default);
}
.ks-select-ui-button:focus-visible { outline: 0; box-shadow: var(--ks-well), var(--ks-glow-focus); }
.ks-select-ui-value.is-placeholder { color: var(--ks-text-subtle); }
.ks-select-ui-caret { flex: none; color: var(--ks-text-muted); transition: transform var(--ks-duration-fast) var(--ks-ease-default); }
.ks-select-ui.is-open .ks-select-ui-caret { transform: rotate(180deg); }
.ks-select-ui-list {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: var(--ks-z-dropdown);
  max-height: 264px; overflow-y: auto; padding: var(--ks-space-1); margin: 0; list-style: none;
  background: var(--ks-surface-default); border-radius: var(--ks-radius-lg);
  box-shadow: var(--ks-shadow-lg), var(--ks-edge); display: none;
}
.ks-select-ui.is-open .ks-select-ui-list { display: block; animation: ks-pop var(--ks-duration-fast) var(--ks-ease-default); }
.ks-select-ui-option {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ks-space-2);
  padding: var(--ks-space-2) var(--ks-space-3); font-size: var(--ks-text-sm);
  color: var(--ks-text-default); border-radius: var(--ks-radius-sm); cursor: pointer;
}
.ks-select-ui-option.is-active { background: var(--ks-brand-primary-subtle); color: var(--ks-brand-primary); }
.ks-select-ui-option[aria-selected="true"] { font-weight: var(--ks-weight-semibold); }
.ks-select-ui-option .ks-i { opacity: 0; font-size: 1rem; }
.ks-select-ui-option[aria-selected="true"] .ks-i { opacity: 1; }

/* ---- Combobox extensions (searchable / multi-select tags / free entry) ---- */
/* Multi mode: the value area holds wrapping chips, so the control grows to fit
   and top/bottom padding pads the chip rows. */
.ks-select-ui--multi .ks-select-ui-button {
  height: auto; min-height: var(--ks-control-height-md);
  padding-top: var(--ks-space-1\.5); padding-bottom: var(--ks-space-1\.5);
  align-items: flex-start;
}
.ks-select-ui--multi .ks-select-ui-value {
  display: flex; flex-wrap: wrap; gap: var(--ks-space-1\.5); align-items: center; min-height: 1.5rem;
}
.ks-select-ui--multi .ks-select-ui-caret { margin-top: var(--ks-space-1); }
/* Tag chip inside the control — reuses .ks-chip; a touch smaller and squared. */
.ks-select-ui-chip { height: 1.5rem; padding: 0 var(--ks-space-1\.5); font-weight: var(--ks-weight-medium); }

/* Search field row, pinned above the option list inside the popover. */
.ks-select-ui-searchwrap {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: calc(var(--ks-z-dropdown) + 1);
  display: none; align-items: center; gap: var(--ks-space-2);
  padding: var(--ks-space-2) var(--ks-space-2\.5);
  background: var(--ks-surface-default);
  border-top-left-radius: var(--ks-radius-lg); border-top-right-radius: var(--ks-radius-lg);
  box-shadow: var(--ks-shadow-lg), var(--ks-edge);
}
.ks-select-ui.is-open .ks-select-ui-searchwrap { display: flex; }
/* When a search field is present, the list is pushed down and loses its top corners. */
.ks-select-ui.is-open .ks-select-ui-searchwrap + .ks-select-ui-list {
  top: calc(100% + 4px + var(--ks-control-height-md));
  border-top-left-radius: 0; border-top-right-radius: 0;
}
.ks-select-ui-search-icon { flex: none; color: var(--ks-text-muted); }
.ks-select-ui-search {
  flex: 1 1 auto; width: 100%; height: var(--ks-control-height-sm); padding: 0 var(--ks-space-2);
  font: inherit; font-size: var(--ks-text-sm); color: var(--ks-text-default);
  background: var(--ks-surface-subtle); border: 0; border-radius: var(--ks-radius-input);
  box-shadow: var(--ks-well);
}
.ks-select-ui-search:focus-visible { outline: 0; box-shadow: var(--ks-well), var(--ks-glow-focus); }
.ks-select-ui-search::placeholder { color: var(--ks-text-subtle); }

/* Empty / create-hint row. */
.ks-select-ui-empty {
  padding: var(--ks-space-2) var(--ks-space-3); font-size: var(--ks-text-sm);
  color: var(--ks-text-subtle); text-align: left;
}

/* ============================================================
   RICH-TEXT EDITOR (MCE) — .ks-editor
   ============================================================
   A toolbar + contenteditable surface pooled into ONE recessed well, matching
   the .ks-input / .ks-textarea treatment (tone + inset shadow, no border). The
   original <textarea> is hidden (.ks-editor-source) but stays in the form. */
.ks-editor {
  display: flex;
  flex-direction: column;
  background: var(--ks-surface-subtle);
  border-radius: var(--ks-radius-input);
  box-shadow: var(--ks-well);
  overflow: clip;
  transition: box-shadow var(--ks-duration-normal) var(--ks-ease-default), background-color var(--ks-duration-fast) var(--ks-ease-default);
}
/* Pool a single focus glow when the surface is focused (like .ks-input-group). */
.ks-editor:focus-within {
  background: var(--ks-surface-default);
  box-shadow: var(--ks-well), var(--ks-glow-focus);
}
.ks-field.is-invalid .ks-editor {
  box-shadow: var(--ks-well), 0 0 0 2px var(--ks-surface-default), 0 0 0 4px var(--ks-error-default);
}

/* Toolbar — a subtly deeper band (tone, not a border) separating it from the
   writing surface. Wraps on narrow widths; keyboard-operable. */
.ks-editor-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ks-space-0\.5);
  padding: var(--ks-space-1\.5);
  background: rgb(var(--ks-shadow-color) / 0.04);
  box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.10);
}
.ks-editor-toolbar .ks-btn { box-shadow: none; }
/* Active (pressed) format button — a filled tonal state, no movement. */
.ks-editor-toolbar .ks-btn[aria-pressed="true"] {
  background-color: var(--ks-surface-emphasis);
  color: var(--ks-text-default);
}

/* Vertical divider between toolbar groups (a hairline of shadow tone). */
.ks-editor-divider {
  width: 1px;
  align-self: stretch;
  margin: var(--ks-space-0\.5) var(--ks-space-1);
  background: rgb(var(--ks-shadow-color) / 0.14);
}

/* Block-format select sits flush in the toolbar (compact, no extra well). */
.ks-editor-block {
  width: auto;
  min-width: 8.5rem;
  height: var(--ks-density-btn-sm-h);
  padding: 0 var(--ks-space-6) 0 var(--ks-space-2);
  font-size: var(--ks-density-font-xs);
  background-color: transparent;
  box-shadow: none;
  background-position: right var(--ks-space-2) center;
}
.ks-editor-block:hover { background-color: rgb(var(--ks-shadow-color) / 0.06); }
.ks-editor-block:focus-visible { box-shadow: var(--ks-glow-focus); }

/* Writing surface — a prose-styled contenteditable region. */
.ks-editor-content {
  min-height: var(--ks-density-textarea-min-h);
  max-height: 24rem;
  overflow-y: auto;
  padding: var(--ks-space-3) var(--ks-density-pad-x);
  font-family: inherit;
  font-size: var(--ks-density-font);
  line-height: var(--ks-type-body-leading, 1.6);
  color: var(--ks-text-default);
}
.ks-editor-content:focus { outline: 0; }

/* Placeholder — shown only while empty (JS toggles data-ks-empty). */
.ks-editor-content[data-ks-empty]::before {
  content: attr(data-ks-editor-placeholder);
  color: var(--ks-text-subtle);
  pointer-events: none;
}

/* In-editor prose rhythm — tight, readable defaults for the rich content. */
.ks-editor-content > :first-child { margin-top: 0; }
.ks-editor-content > :last-child { margin-bottom: 0; }
.ks-editor-content p { margin: 0 0 var(--ks-space-2); }
.ks-editor-content h1 { font-size: var(--ks-text-2xl); font-weight: 700; margin: var(--ks-space-3) 0 var(--ks-space-2); }
.ks-editor-content h2 { font-size: var(--ks-text-xl); font-weight: 700; margin: var(--ks-space-3) 0 var(--ks-space-2); }
.ks-editor-content h3 { font-size: var(--ks-text-lg); font-weight: 600; margin: var(--ks-space-2) 0 var(--ks-space-1); }
.ks-editor-content ul, .ks-editor-content ol { margin: 0 0 var(--ks-space-2); padding-left: var(--ks-space-6); }
.ks-editor-content li { margin: var(--ks-space-0\.5) 0; }
.ks-editor-content a { color: var(--ks-text-link); text-decoration: underline; }
.ks-editor-content blockquote {
  margin: var(--ks-space-2) 0;
  padding: var(--ks-space-1) 0 var(--ks-space-1) var(--ks-space-4);
  box-shadow: inset 3px 0 0 var(--ks-border-emphasis);
  color: var(--ks-text-muted);
}
.ks-editor-content code {
  font-family: var(--ks-font-mono);
  font-size: var(--ks-type-code-size);
  padding: 0.1em 0.35em;
  background: var(--ks-surface-muted);
  border-radius: var(--ks-radius-sm);
}

/* The original textarea — kept in the DOM (and form) but visually removed once
   the editor is mounted. Without JS this class is never added, so it shows. */
.ks-editor-source {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* ---- Responsive refinement ---- */
.ks-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

@media (max-width: 768px) {
  .ks-container, .ks-container-wide, .ks-container-prose { padding-inline: var(--ks-space-4); }
  .ks-tabs-list { max-width: 100%; overflow-x: auto; }
}

@media (max-width: 560px) {
  .ks-h1 { font-size: var(--ks-text-3xl); }
  .ks-h2 { font-size: var(--ks-text-2xl); }
  .ks-display { font-size: var(--ks-text-4xl); }
  .ks-navbar { gap: var(--ks-space-2); padding-inline: var(--ks-space-3); }
  .ks-modal { padding: 0; align-items: flex-end; }
  .ks-modal-dialog { max-width: 100%; max-height: 92vh; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
  .ks-drawer-panel { width: 100%; }
  .ks-toast-region { left: var(--ks-space-3); right: var(--ks-space-3); width: auto; bottom: var(--ks-space-3); }
  .ks-row { gap: var(--ks-space-3); }
}

/* ============================================================
   COMMAND PALETTE
   ------------------------------------------------------------
   An overlay, so it inherits the modal's backdrop, Esc, focus
   trap and focus restore. It sits higher than a centered dialog
   (people expect ⌘K near the top) and never taller than it needs
   to be — the list scrolls, the search field does not move.
   ============================================================ */
.ks-palette { align-items: flex-start; padding-top: 12vh; }
.ks-palette-dialog { max-width: 560px; display: flex; flex-direction: column; overflow: hidden; }

.ks-palette-search {
  display: flex; align-items: center; gap: var(--ks-space-2\.5);
  padding: var(--ks-space-3\.5) var(--ks-pad-md);
  box-shadow: inset 0 -1px 0 rgb(var(--ks-shadow-color) / 0.08);
}
.ks-palette-search-icon { flex: none; width: 1.125rem; height: 1.125rem; color: var(--ks-text-subtle); }
.ks-palette-input {
  flex: 1 1 auto; min-width: 0;
  font: inherit; font-size: var(--ks-text-lg);
  color: var(--ks-text-default);
  background: none; border: 0; outline: none; padding: 0;
}
.ks-palette-input::placeholder { color: var(--ks-text-subtle); }

.ks-palette-list {
  list-style: none; margin: 0; padding: var(--ks-space-1\.5);
  max-height: min(52vh, 26rem); overflow-y: auto;
}
.ks-palette-item {
  display: flex; align-items: center; gap: var(--ks-space-2\.5);
  padding: var(--ks-space-2\.5) var(--ks-space-3);
  border-radius: var(--ks-radius-md);
  font-size: var(--ks-text-sm); color: var(--ks-text-default);
  cursor: pointer;
}
/* Only ONE row is ever highlighted, and the keyboard owns it — a hover tint
   competing with the active row is how palettes end up with two cursors. */
.ks-palette-item.is-active { background: var(--ks-brand-primary-subtle); color: var(--ks-brand-primary); }
.ks-palette-item-icon { flex: none; width: 1rem; height: 1rem; }
.ks-palette-item-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ks-palette-item-group { flex: none; font-size: var(--ks-text-xs); color: var(--ks-text-subtle); }
.ks-palette-item-keys { flex: none; }
.ks-palette-empty {
  margin: 0; padding: var(--ks-space-6) var(--ks-pad-md);
  text-align: center; font-size: var(--ks-text-sm); color: var(--ks-text-muted);
}

/* ============================================================
   PRINT — the report sheet
   ------------------------------------------------------------
   A dashboard on paper is a report: no grain, no shadows, no
   chrome, light palette whatever the screen theme was, and
   nothing sliced across a page break. Ink is expensive and grey
   boxes are not information.
   ============================================================ */

/* Screen-side halves of the print utilities. */
.ks-print-only { display: none; }

@media print {
  /* Force the light token set regardless of [data-theme] or the OS. Listed
     explicitly so it outranks both dark paths, not just the media query. */
  :root, :root[data-theme="dark"], :root[data-theme="light"] {
    color-scheme: light;
    --ks-canvas: #FFFFFF;
    --ks-canvas-wash: none;
    --ks-surface-default: #FFFFFF;
    --ks-surface-subtle: #FFFFFF;
    --ks-surface-muted: #FFFFFF;
    --ks-surface-emphasis: #FFFFFF;
    --ks-surface-overlay: transparent;
    --ks-text-default: #000000;
    --ks-text-muted: #333333;
    --ks-text-subtle: #555555;
    --ks-shadow-color: 0 0 0;
  }
  body.ks-body {
    background: #FFFFFF !important;
    background-image: none !important;
    color: #000000;
  }
  /* Film grain is a screen affectation; on paper it is a grey wash. */
  body.ks-body::after, .ks-grain { display: none !important; }

  /* Chrome and transient UI never print. */
  .ks-navbar, .ks-toast-region, .ks-context-menu, .ks-dropdown-menu,
  .ks-popover, .ks-palette, [data-ks-modal], [data-ks-drawer],
  .ks-pagination, .ks-table-toolbar-actions, .ks-skeleton, .ks-no-print {
    display: none !important;
  }
  .ks-print-only { display: revert !important; }
  /* The pager's CONTROLS are useless on paper, but its status line is not:
     "1–25 of 320" is the reader's only clue that this is one page of many. */
  .ks-table-pager-status { color: #333333; }

  /* Elevation reads as a grey smear in print — swap it for real hairlines. */
  .ks-card, .ks-alert, .ks-table, .ks-table th, .ks-table td, .ks-stat, .ks-btn {
    box-shadow: none !important;
    background-image: none !important;
  }
  .ks-card, .ks-alert, .ks-stat { border: 1px solid #CCCCCC; }
  .ks-table { border-collapse: collapse; }
  .ks-table th, .ks-table td { border: 1px solid #CCCCCC; }
  .ks-table th { background: #F2F2F2 !important; }
  /* A table crossing pages repeats its header. */
  thead { display: table-header-group; }
  tfoot { display: table-footer-group; }
  /* Scroll containers clip on paper — there is no scrollbar to reach the rest,
     so a max-height wrapper silently truncates the data. Let them run. */
  .ks-table-wrap, .ks-chart-data-body, .ks-editor-content {
    max-height: none !important; overflow: visible !important;
  }
  .ks-table--sticky thead th { position: static; }

  /* Nothing meaningful gets sliced in half. */
  tr, img, figure, .ks-card, .ks-stat, .ks-alert, .ks-timeline-item { break-inside: avoid; }
  h1, h2, h3, .ks-h1, .ks-h2, .ks-h3 { break-after: avoid; }
  .ks-page-break { break-after: page; }

  /* A link's destination is invisible on paper unless you print it. Skip
     in-page anchors and anything whose text already IS the URL. */
  a[href^="http"]:not(.ks-btn)::after {
    content: " (" attr(href) ")";
    font-size: 0.85em; color: #555555; word-break: break-all;
  }

  /* Collapsed disclosures hide content the reader asked to print. */
  details { display: block; }
  details > summary { display: none; }
  details > * { display: revert !important; }

  @page { margin: 16mm; }
}

/* ============================================================
   ASYNC REGIONS — data-ks-async
   ------------------------------------------------------------
   Four slots, one visible at a time, driven by the
   data-ks-async-state attribute Keystone.resource() writes.

   The default (NO state attribute) shows the ready slot: a page
   with server-rendered content and no JS must not blank out, so
   only the three transient slots start hidden.
   ============================================================ */
[data-ks-async] > [data-ks-async-loading],
[data-ks-async] > [data-ks-async-error],
[data-ks-async] > [data-ks-async-empty] { display: none; }

[data-ks-async][data-ks-async-state="loading"] > [data-ks-async-ready],
[data-ks-async][data-ks-async-state="error"] > [data-ks-async-ready],
[data-ks-async][data-ks-async-state="empty"] > [data-ks-async-ready],
[data-ks-async][data-ks-async-state="idle"] > [data-ks-async-ready] { display: none; }

[data-ks-async][data-ks-async-state="loading"] > [data-ks-async-loading],
[data-ks-async][data-ks-async-state="error"] > [data-ks-async-error],
[data-ks-async][data-ks-async-state="empty"] > [data-ks-async-empty] { display: revert; }

/* The "updated 2m ago" stamp. Muted while current; a warning tone once the
   data is past its staleAfter, because "3s ago" and "40m ago" must not look
   the same on a wall-mounted board. */
[data-ks-async-updated] {
  font-size: var(--ks-text-xs);
  color: var(--ks-text-subtle);
  font-variant-numeric: tabular-nums;
}
[data-ks-async-stale] [data-ks-async-updated] { color: var(--ks-warning-text); }
/* A stale region keeps showing its last good data — dimmed just enough to say
   "this is not live", never so much that the numbers become unreadable. */
[data-ks-async-stale] > [data-ks-async-ready] { opacity: 0.72; }

/* ============================================================
   7. MOTION & INFOGRAPHICS
   ------------------------------------------------------------
   7a. In-app reduced motion — [data-ks-motion="reduce"]
   7b. Stagger, change flash, draw
   7c. Meter, ring, steps, proportion bar — CSS-only infographics
   Motion here exists to show CHANGE (DESIGN-PRINCIPLES, "Motion
   principles"); every block degrades to its final state.
   ============================================================ */

/* ---- 7a. In-app reduced motion ----
   Mirrors every prefers-reduced-motion block in this file, scoped to the
   subtree carrying the attribute (on <html>, the whole page). Keystone.motion
   writes it; markup may too. It only ever REMOVES motion — there is no
   "full" value, because an app must not overrule an OS that asked for less.
   Delays are zeroed as well, so a reduced page shows its final state at once
   rather than after a staggered pause. */
[data-ks-motion="reduce"] { scroll-behavior: auto; }
[data-ks-motion="reduce"],
[data-ks-motion="reduce"]::before,
[data-ks-motion="reduce"]::after,
[data-ks-motion="reduce"] *,
[data-ks-motion="reduce"] *::before,
[data-ks-motion="reduce"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  animation-delay: 0s !important;
  transition-duration: 0.01ms !important;
  transition-delay: 0s !important;
}
[data-ks-motion="reduce"] .ks-btn:hover,
[data-ks-motion="reduce"] .ks-card--interactive:hover,
[data-ks-motion="reduce"] .ks-pagination a:hover { transform: none; }
[data-ks-motion="reduce"] .ks-skeleton::after { display: none; }
[data-ks-motion="reduce"] .ks-progress--indeterminate .ks-progress-bar,
[data-ks-motion="reduce"] [data-ks-progress-indeterminate] .ks-progress-bar {
  animation: none !important;
  transform: none !important;
  width: 35%;
}
[data-ks-motion="reduce"] .ks-chip-enter { animation: none; }
[data-ks-motion="reduce"] .ks-chip.is-leaving { opacity: 0; transform: none; }
[data-ks-motion="reduce"] .ks-icon-in,
[data-ks-motion="reduce"] .ks-icon-pop { animation: none; }
/* The scroll-driven reveal is timeline-linked, so a zeroed duration doesn't
   stop it — switch it off outright. */
[data-ks-motion="reduce"] .ks-reveal { animation: none !important; }
:root[data-ks-motion="reduce"]::view-transition-group(*),
:root[data-ks-motion="reduce"]::view-transition-old(*),
:root[data-ks-motion="reduce"]::view-transition-new(*) { animation: none !important; }

/* ---- 7b. Stagger — [data-ks-stagger] ----
   keystone.js takes ownership with .ks-stagger-js (only then is anything
   hidden), stamps --ks-i on each child, and adds .is-staggered on first view.
   .is-settled drops the animation once everyone has arrived, so it can never
   hold a transform over styles a child sets later. Newcomers (.ks-stagger-new)
   rise in on arrival. */
@keyframes ks-rise-in { from { opacity: 0; transform: translateY(var(--ks-rise)); } }
.ks-stagger-js:not(.is-staggered) > * { opacity: 0; }
.ks-stagger-js.is-staggered > *,
.ks-stagger-js.is-staggered > .ks-stagger-new {
  animation: ks-rise-in var(--ks-duration-slow) var(--ks-ease-emphasized) both;
  animation-delay: calc(var(--ks-i, 0) * var(--ks-stagger-step));
}
.ks-stagger-js.is-settled > *:not(.ks-stagger-new) { animation: none; }

/* ---- Change flash — data-ks-flash (with countup / Keystone.countTo) ----
   A tonal wash that says "this just went up / down", fading back to the
   surface. Tone only: no glow, no movement, no size change. Under reduced
   motion it still shows — it is information — just without the fade. */
/* Hold the tone while the number is still moving, then let it go. */
/* The crisp 4px spread (no blur) gives the digits room inside the wash
   without touching layout. */
/* Painted as an INSET shadow over the element's own background (plus the 4px
   spread), never as background-color — animating that would wipe out a badge's
   or a cell's own fill on the way back to transparent. */
@keyframes ks-flash {
  0%, 35% { box-shadow: inset 0 0 0 100vmax var(--ks-flash-tone), 0 0 0 4px var(--ks-flash-tone); }
  to { box-shadow: inset 0 0 0 100vmax transparent, 0 0 0 4px transparent; }
}
.ks-flash-up   { --ks-flash-tone: color-mix(in srgb, var(--ks-success-default) 22%, transparent); }
.ks-flash-down { --ks-flash-tone: color-mix(in srgb, var(--ks-error-default) 22%, transparent); }
.ks-flash-up, .ks-flash-down {
  border-radius: var(--ks-radius-sm);
  animation: ks-flash 1.2s var(--ks-ease-default) both;
}
@media (prefers-reduced-motion: reduce) {
  .ks-flash-up, .ks-flash-down { animation: none; box-shadow: inset 0 0 0 100vmax var(--ks-flash-tone), 0 0 0 4px var(--ks-flash-tone); }
}
[data-ks-motion="reduce"] .ks-flash-up,
[data-ks-motion="reduce"] .ks-flash-down,
[data-ks-motion="reduce"].ks-flash-up,
[data-ks-motion="reduce"].ks-flash-down {
  animation: none !important;
  box-shadow: inset 0 0 0 100vmax var(--ks-flash-tone), 0 0 0 4px var(--ks-flash-tone);
}

/* ---- Draw — .ks-draw ----
   keystone.js normalizes each stroke to pathLength="1" (.ks-draw-stroke) and
   marks fills/text/dashed shapes .ks-draw-fade; .ks-draw-js arms it,
   .is-drawn plays it. Strokes draw in document order (--ks-i); fills and text
   fade in behind the line work. */
.ks-draw { --ks-draw-duration: var(--ks-duration-deliberate); --ks-draw-step: 60ms; }
.ks-draw-js .ks-draw-stroke { stroke-dasharray: 1; stroke-dashoffset: 1; }
/* Only a screen that welcomes motion ever sees the undrawn state — print and
   reduced motion never match, so authored fill-opacity is never overridden. */
@media screen and (prefers-reduced-motion: no-preference) {
  .ks-draw-js:not(.is-drawn) .ks-draw-filled { fill-opacity: 0; }
  .ks-draw-js:not(.is-drawn) .ks-draw-fade { opacity: 0; }
}
.ks-draw-js.is-drawn .ks-draw-stroke {
  stroke-dashoffset: 0;
  transition:
    stroke-dashoffset var(--ks-draw-duration) var(--ks-ease-emphasized) calc(var(--ks-i, 0) * var(--ks-draw-step)),
    fill-opacity var(--ks-duration-slow) var(--ks-ease-default) calc(var(--ks-draw-duration) * 0.6 + var(--ks-i, 0) * var(--ks-draw-step));
}
.ks-draw-js.is-drawn .ks-draw-fade {
  transition: opacity var(--ks-duration-slow) var(--ks-ease-default) calc(var(--ks-draw-duration) * 0.5 + var(--ks-i, 0) * var(--ks-draw-step));
}
@media (prefers-reduced-motion: reduce), print {
  .ks-draw-js .ks-draw-stroke { stroke-dasharray: none; stroke-dashoffset: 0; }
}
[data-ks-motion="reduce"] .ks-draw-js .ks-draw-stroke,
[data-ks-motion="reduce"].ks-draw-js .ks-draw-stroke { stroke-dasharray: none; stroke-dashoffset: 0; }
[data-ks-motion="reduce"] .ks-draw-js:not(.is-drawn) .ks-draw-fade,
[data-ks-motion="reduce"].ks-draw-js:not(.is-drawn) .ks-draw-fade { opacity: 1; }

/* ---- 7c. Meter — <meter class="ks-meter"> ----
   The native element stays the source of truth: value/min/max/low/high/
   optimum, announced by assistive tech, no JS. The browser decides the
   region and Keystone maps it to tone — optimum = brand, sub-optimum =
   warning, worst = error. The unfilled track is a lighter step of the SAME
   tone as the fill, so state reads across the whole bar, and a 2px surface
   gap separates fill from track. Squared, flat, no gradient. A tone that
   means "warning" still wants a word beside it — color is never alone. */
.ks-meter {
  --ks-meter-height: 10px;
  --ks-meter-tone: var(--ks-brand-primary);
  --ks-meter-gap: var(--ks-surface-default);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  display: block;
  inline-size: 100%;
  block-size: var(--ks-meter-height);
  margin: 0;
  border: 0;
  border-radius: var(--ks-radius-sm);
  overflow: hidden;
  /* Firefox paints the track on the element itself. */
  background: color-mix(in srgb, var(--ks-meter-tone) 18%, var(--ks-surface-muted));
  vertical-align: middle;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.ks-meter--sm { --ks-meter-height: 6px; }
.ks-meter--lg { --ks-meter-height: 14px; }
/* WebKit/Blink: the bar holds the value pseudo, which draws the fill and then
   paints the gap and the track as shadows (first shadow on top), clipped by
   the meter's rounded box. One rule per pseudo — an unknown pseudo in a
   selector list would void the whole list in another engine. */
.ks-meter::-webkit-meter-bar {
  /* Blink centres the bar on a half-height grid track, so a percentage height
     resolves to half the meter. An explicit length, pinned to the top, fills it. */
  height: var(--ks-meter-height);
  inset-block-start: 0;
  align-self: start;
  background: transparent;
  border: 0;
  border-radius: inherit;
}
.ks-meter::-webkit-meter-optimum-value {
  --ks-meter-tone: var(--ks-brand-primary);
  background: var(--ks-meter-tone);
  box-shadow: 2px 0 0 var(--ks-meter-gap), 0 0 0 100vmax color-mix(in srgb, var(--ks-meter-tone) 18%, var(--ks-surface-muted));
}
.ks-meter::-webkit-meter-suboptimum-value {
  --ks-meter-tone: var(--ks-warning-default);
  background: var(--ks-meter-tone);
  box-shadow: 2px 0 0 var(--ks-meter-gap), 0 0 0 100vmax color-mix(in srgb, var(--ks-meter-tone) 18%, var(--ks-surface-muted));
}
.ks-meter::-webkit-meter-even-less-good-value {
  --ks-meter-tone: var(--ks-error-default);
  background: var(--ks-meter-tone);
  box-shadow: 2px 0 0 var(--ks-meter-gap), 0 0 0 100vmax color-mix(in srgb, var(--ks-meter-tone) 18%, var(--ks-surface-muted));
}
/* In Blink/WebKit the value pseudo paints the track itself; the element's own
   background (Gecko's track) would only peek out as a hairline at the edges. */
@supports selector(::-webkit-meter-bar) {
  .ks-meter { background: transparent; }
}
/* Gecko: the region pseudo-classes sit on the element, so the tone — and
   with it the track — follows the region. */
.ks-meter:-moz-meter-sub-optimum { --ks-meter-tone: var(--ks-warning-default); }
.ks-meter:-moz-meter-sub-sub-optimum { --ks-meter-tone: var(--ks-error-default); }
.ks-meter::-moz-meter-bar {
  background: var(--ks-meter-tone);
  border-radius: 0;
  box-shadow: 2px 0 0 var(--ks-meter-gap);
}

/* A labelled meter: name left, value right (tabular — these stack in lists). */
.ks-meter-field { display: grid; gap: var(--ks-space-1\.5); min-width: 0; }
.ks-meter-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--ks-space-3);
  font-size: var(--ks-text-sm);
}
.ks-meter-label { min-width: 0; color: var(--ks-text-default); font-weight: var(--ks-weight-medium); overflow-wrap: anywhere; }
.ks-meter-value {
  display: inline-flex; align-items: center; gap: var(--ks-space-1);
  color: var(--ks-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ---- Ring — .ks-ring (CSS-only radial progress) ----
   <div class="ks-ring" style="--ks-value: 72" role="progressbar"
        aria-valuenow="72" aria-valuemin="0" aria-valuemax="100" aria-label="Patched">
     <span class="ks-ring-value">72%</span><span class="ks-ring-label">Patched</span>
   </div>
   --ks-value is a unitless 0–100. It feeds the registered --ks-ring-p, so a
   change TRANSITIONS and first paint fills from zero — no JS. Butt ends
   (conic stops are square by nature), a small gap at each end of the fill,
   and the track a lighter step of the fill's own tone. A ring is round the
   way a spinner is round: the shape IS the meaning. */
@property --ks-ring-p {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}
@keyframes ks-ring-in { from { --ks-ring-p: 0; } }
/* An entrance plays ONCE. Hiding and re-showing an element (a tab, an
   accordion, a re-rendered poll) restarts a CSS animation, which would replay
   a "change" that didn't happen. keystone.js adds .is-settled when the
   entrance ends; without JS it can replay on re-show, which is the honest
   fallback for a stylesheet on its own. */
.ks-ring.is-settled,
.ks-proportion.is-settled,
.ks-step.is-settled::after { animation: none; }
.ks-ring {
  --ks-ring-size: 96px;
  --ks-ring-thickness: 10px;
  --ks-ring-color: var(--ks-brand-primary);
  --ks-ring-track: color-mix(in srgb, var(--ks-ring-color) 16%, var(--ks-surface-muted));
  --ks-ring-gap: 0.8; /* percent of the circumference */
  --ks-ring-p: clamp(0, var(--ks-value, 0), 100);
  position: relative;
  display: inline-grid;
  place-content: center;
  justify-items: center;
  flex: none;
  inline-size: var(--ks-ring-size);
  block-size: var(--ks-ring-size);
  padding: var(--ks-ring-thickness);
  box-sizing: border-box;
  text-align: center;
  border-radius: 50%;
  transition: --ks-ring-p var(--ks-duration-deliberate) var(--ks-ease-emphasized);
  /* `backwards`, never `both`: once the entrance ends the property is the
     declared value again, so a later change can transition. */
  animation: ks-ring-in var(--ks-duration-deliberate) var(--ks-ease-emphasized) backwards;
}
.ks-ring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: conic-gradient(
    var(--ks-ring-color) 0 calc(var(--ks-ring-p) * 1%),
    transparent 0 calc(var(--ks-ring-p) * 1% + var(--ks-ring-gap) * 1%),
    var(--ks-ring-track) 0 calc(100% - var(--ks-ring-gap) * 1%),
    transparent 0
  );
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--ks-ring-thickness) - 0.5px), #000 calc(100% - var(--ks-ring-thickness)));
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--ks-ring-thickness) - 0.5px), #000 calc(100% - var(--ks-ring-thickness)));
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.ks-ring > * { position: relative; }
/* A big standalone figure: proportional, not tabular. */
.ks-ring-value {
  font-size: var(--ks-text-xl);
  font-weight: var(--ks-weight-bold);
  line-height: var(--ks-leading-tight);
  color: var(--ks-text-default);
}
.ks-ring-label {
  margin-top: 2px;
  font-size: var(--ks-text-xs);
  line-height: var(--ks-leading-tight);
  color: var(--ks-text-muted);
}
.ks-ring--sm { --ks-ring-size: 56px; --ks-ring-thickness: 6px; }
.ks-ring--sm .ks-ring-value { font-size: var(--ks-text-sm); }
.ks-ring--sm .ks-ring-label { display: none; }
.ks-ring--lg { --ks-ring-size: 140px; --ks-ring-thickness: 14px; }
.ks-ring--lg .ks-ring-value { font-size: var(--ks-text-3xl); }
.ks-ring--accent  { --ks-ring-color: var(--ks-brand-accent); }
.ks-ring--success { --ks-ring-color: var(--ks-success-default); }
.ks-ring--warning { --ks-ring-color: var(--ks-warning-default); }
.ks-ring--error   { --ks-ring-color: var(--ks-error-default); }

/* ---- Steps — .ks-steps (process / stepper) ----
   <ol class="ks-steps">
     <li class="ks-step is-done"><span class="ks-step-marker"></span>
       <span class="ks-step-body"><span class="ks-step-title">Audit</span>
       <span class="ks-step-desc">Complete</span></span></li>
     <li class="ks-step" aria-current="step">…</li>
   </ol>
   States: upcoming (default), current (.is-current or aria-current="step"),
   done (.is-done), error (.is-error). An empty marker shows its step number
   (a CSS counter); done/error draw a check / cross as a CSS mask — no webfont,
   no JS, no emoji. The connector after a done step fills: in sequence on
   first paint, and live when a step flips to done. Squared markers — the house
   signature, not a generic circle. */
.ks-steps {
  --ks-step-marker: 28px;
  --ks-step-line: 2px;
  --ks-step-track: rgb(var(--ks-shadow-color) / 0.14);
  --ks-step-fill: var(--ks-brand-primary);
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: ks-step;
}
.ks-step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--ks-space-2);
  flex: 1 1 0;
  min-width: 0;
  padding-right: var(--ks-space-2);
  counter-increment: ks-step;
}
.ks-step:last-child { flex: 0 1 auto; max-width: 14rem; padding-right: 0; }
/* Connector: track (::before) + fill (::after), from this marker to the next. */
.ks-step:not(:last-child)::before,
.ks-step:not(:last-child)::after {
  content: "";
  position: absolute;
  top: calc(var(--ks-step-marker) / 2 - var(--ks-step-line) / 2);
  left: calc(var(--ks-step-marker) + var(--ks-space-2));
  right: var(--ks-space-2);
  height: var(--ks-step-line);
  background: var(--ks-step-track);
}
.ks-step:not(:last-child)::after {
  background: var(--ks-step-fill);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--ks-duration-deliberate) var(--ks-ease-emphasized);
}
.ks-step.is-done:not(:last-child)::after {
  transform: none;
  /* First paint: done connectors fill left to right, one after another. */
  animation: ks-step-fill-x var(--ks-duration-slower) var(--ks-ease-emphasized) backwards;
  animation-delay: calc(var(--ks-step-i, 0) * 120ms);
}
@keyframes ks-step-fill-x { from { transform: scaleX(0); } }
@keyframes ks-step-fill-y { from { transform: scaleY(0); } }
.ks-steps > :nth-child(2) { --ks-step-i: 1; }
.ks-steps > :nth-child(3) { --ks-step-i: 2; }
.ks-steps > :nth-child(4) { --ks-step-i: 3; }
.ks-steps > :nth-child(5) { --ks-step-i: 4; }
.ks-steps > :nth-child(6) { --ks-step-i: 5; }
.ks-steps > :nth-child(n+7) { --ks-step-i: 6; }

.ks-step-marker {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  inline-size: var(--ks-step-marker);
  block-size: var(--ks-step-marker);
  border-radius: var(--ks-radius-md);
  background: var(--ks-surface-muted);
  color: var(--ks-text-muted);
  box-shadow: var(--ks-well);
  font-size: var(--ks-text-xs);
  font-weight: var(--ks-weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  transition: background-color var(--ks-duration-normal) var(--ks-ease-default),
              color var(--ks-duration-normal) var(--ks-ease-default),
              box-shadow var(--ks-duration-normal) var(--ks-ease-default);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.ks-step-marker:empty::before { content: counter(ks-step); }
.ks-step-marker .ks-i { font-size: 16px; }
/* Current: a crisp inset ring in the brand tone — solid, not a halo. */
.ks-step.is-current .ks-step-marker,
.ks-step[aria-current="step"] .ks-step-marker {
  background: var(--ks-surface-default);
  color: var(--ks-brand-primary);
  box-shadow: inset 0 0 0 2px var(--ks-brand-primary);
}
.ks-step.is-done .ks-step-marker {
  background: var(--ks-step-fill);
  color: var(--ks-text-on-brand);
  box-shadow: none;
}
.ks-step.is-error .ks-step-marker {
  background: var(--ks-error-default);
  color: #FFFFFF;
  box-shadow: none;
}
/* Done / error glyphs — icon-set geometry as a mask, so they render with only
   keystone.css loaded. */
.ks-step.is-done .ks-step-marker:empty::before,
.ks-step.is-error .ks-step-marker:empty::before {
  content: "";
  inline-size: 16px;
  block-size: 16px;
  background: currentColor;
  -webkit-mask: var(--ks-step-glyph) center / 100% 100% no-repeat;
  mask: var(--ks-step-glyph) center / 100% 100% no-repeat;
}
.ks-step.is-done {
  --ks-step-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'%3E%3Cpolyline points='5 12 10 17 19 8'/%3E%3C/svg%3E");
}
.ks-step.is-error {
  --ks-step-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E");
}
.ks-step-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ks-step-title {
  font-size: var(--ks-text-sm);
  font-weight: var(--ks-weight-semibold);
  line-height: var(--ks-leading-snug);
  color: var(--ks-text-default);
  overflow-wrap: anywhere;
}
.ks-step:not(.is-done):not(.is-current):not(.is-error):not([aria-current="step"]) .ks-step-title { color: var(--ks-text-muted); }
.ks-step-desc {
  font-size: var(--ks-text-xs);
  line-height: var(--ks-leading-snug);
  color: var(--ks-text-muted);
  overflow-wrap: anywhere;
}
/* Vertical: marker left, body right, the connector runs down. */
.ks-steps--vertical { flex-direction: column; }
.ks-steps--vertical > .ks-step {
  flex: none;
  max-width: none;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--ks-space-3);
  padding: 0 0 var(--ks-space-5);
}
.ks-steps--vertical > .ks-step:last-child { padding-bottom: 0; }
.ks-steps--vertical > .ks-step:not(:last-child)::before,
.ks-steps--vertical > .ks-step:not(:last-child)::after {
  top: calc(var(--ks-step-marker) + var(--ks-space-1\.5));
  bottom: var(--ks-space-1\.5);
  left: calc(var(--ks-step-marker) / 2 - var(--ks-step-line) / 2);
  right: auto;
  width: var(--ks-step-line);
  height: auto;
}
.ks-steps--vertical > .ks-step:not(:last-child)::after { transform: scaleY(0); transform-origin: center top; }
.ks-steps--vertical > .ks-step.is-done:not(:last-child)::after { transform: none; animation-name: ks-step-fill-y; }
.ks-steps--vertical .ks-step-body { padding-top: 4px; }
/* Narrow screens: five columns of ~65px can only print titles by breaking
   words in half, so a horizontal stepper turns vertical below 480px — the
   same steps, read top to bottom, with room for every word. Opt out with
   .ks-steps--horizontal (short labels): it stays across, without the detail. */
@media (max-width: 480px) {
  .ks-steps:not(.ks-steps--horizontal) { flex-direction: column; }
  .ks-steps:not(.ks-steps--horizontal) > .ks-step {
    flex: none;
    max-width: none;
    flex-direction: row;
    align-items: flex-start;
    gap: var(--ks-space-3);
    padding: 0 0 var(--ks-space-5);
  }
  .ks-steps:not(.ks-steps--horizontal) > .ks-step:last-child { padding-bottom: 0; }
  .ks-steps:not(.ks-steps--horizontal) > .ks-step:not(:last-child)::before,
  .ks-steps:not(.ks-steps--horizontal) > .ks-step:not(:last-child)::after {
    top: calc(var(--ks-step-marker) + var(--ks-space-1\.5));
    bottom: var(--ks-space-1\.5);
    left: calc(var(--ks-step-marker) / 2 - var(--ks-step-line) / 2);
    right: auto;
    width: var(--ks-step-line);
    height: auto;
  }
  .ks-steps:not(.ks-steps--horizontal) > .ks-step:not(:last-child)::after { transform: scaleY(0); transform-origin: center top; }
  .ks-steps:not(.ks-steps--horizontal) > .ks-step.is-done:not(:last-child)::after { transform: none; animation-name: ks-step-fill-y; }
  .ks-steps:not(.ks-steps--horizontal) .ks-step-body { padding-top: 4px; }
  .ks-steps--horizontal .ks-step-desc { display: none; }
}

/* ---- Proportion bar — .ks-proportion (part-to-whole strip) ----
   <div class="ks-proportion" aria-hidden="true">
     <span class="ks-proportion-seg" style="--ks-share: 412"></span> …
   </div>
   <ul class="ks-proportion-legend"> <li><span class="ks-proportion-key"></span>
     Installed <span class="ks-proportion-value">412</span></li> … </ul>
   Shares are raw counts or percents — flex-grow does the arithmetic. The
   legend is the text twin (what assistive tech reads); the bar is
   aria-hidden. Segments take the categorical chart slots in order (legend
   keys match by position) unless a state modifier says the color MEANS
   something; --ks-proportion-color overrides one segment or key. 2px surface
   gaps separate segments — never a border. The honest replacement for a
   two-slice pie. */
.ks-proportion,
.ks-proportion-legend {
  --ks-proportion-1: var(--ks-chart-1, #386AAA);
  --ks-proportion-2: var(--ks-chart-2, #EB6834);
  --ks-proportion-3: var(--ks-chart-3, #1BAF7A);
  --ks-proportion-4: var(--ks-chart-4, #EDA100);
  --ks-proportion-5: var(--ks-chart-5, #E87BA4);
  --ks-proportion-6: var(--ks-chart-6, #008300);
  --ks-proportion-7: var(--ks-chart-7, #4A3AA7);
  --ks-proportion-8: var(--ks-chart-8, #E34948);
}
/* Dark fallbacks for pages without keystone-charts.css — the same re-stepped
   values the charts module ships. KEEP IN SYNC with keystone-charts.css. */
[data-theme="dark"] .ks-proportion,
[data-theme="dark"] .ks-proportion-legend {
  --ks-proportion-1: var(--ks-chart-1, #588BCD);
  --ks-proportion-2: var(--ks-chart-2, #D95926);
  --ks-proportion-3: var(--ks-chart-3, #199E70);
  --ks-proportion-4: var(--ks-chart-4, #C98500);
  --ks-proportion-5: var(--ks-chart-5, #D55181);
  --ks-proportion-6: var(--ks-chart-6, #008300);
  --ks-proportion-7: var(--ks-chart-7, #9085E9);
  --ks-proportion-8: var(--ks-chart-8, #E66767);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .ks-proportion,
  :root:not([data-theme]) .ks-proportion-legend {
    --ks-proportion-1: var(--ks-chart-1, #588BCD);
    --ks-proportion-2: var(--ks-chart-2, #D95926);
    --ks-proportion-3: var(--ks-chart-3, #199E70);
    --ks-proportion-4: var(--ks-chart-4, #C98500);
    --ks-proportion-5: var(--ks-chart-5, #D55181);
    --ks-proportion-6: var(--ks-chart-6, #008300);
    --ks-proportion-7: var(--ks-chart-7, #9085E9);
    --ks-proportion-8: var(--ks-chart-8, #E66767);
  }
}
@keyframes ks-proportion-in { from { clip-path: inset(0 100% 0 0); } }
.ks-proportion {
  --ks-proportion-height: 12px;
  display: flex;
  gap: 2px;
  inline-size: 100%;
  block-size: var(--ks-proportion-height);
  border-radius: var(--ks-radius-sm);
  overflow: hidden;
  /* A wipe, left to right: segments arrive in reading order. Growing every
     segment at once would show nothing — the ratios never change. */
  animation: ks-proportion-in var(--ks-duration-deliberate) var(--ks-ease-emphasized) backwards;
}
.ks-proportion--sm { --ks-proportion-height: 8px; }
.ks-proportion--lg { --ks-proportion-height: 20px; }
.ks-proportion-seg {
  flex: var(--ks-share, 1) 1 0;
  min-inline-size: 2px;
  background: var(--ks-proportion-color, var(--ks-proportion-slot, var(--ks-proportion-1)));
  transition: opacity var(--ks-duration-fast) var(--ks-ease-default),
              flex-grow var(--ks-duration-deliberate) var(--ks-ease-emphasized);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
/* Tonal hover: siblings recede; nothing moves or grows. */
.ks-proportion:hover .ks-proportion-seg:not(:hover) { opacity: 0.55; }
.ks-proportion > :nth-child(2), .ks-proportion-legend > :nth-child(2) { --ks-proportion-slot: var(--ks-proportion-2); }
.ks-proportion > :nth-child(3), .ks-proportion-legend > :nth-child(3) { --ks-proportion-slot: var(--ks-proportion-3); }
.ks-proportion > :nth-child(4), .ks-proportion-legend > :nth-child(4) { --ks-proportion-slot: var(--ks-proportion-4); }
.ks-proportion > :nth-child(5), .ks-proportion-legend > :nth-child(5) { --ks-proportion-slot: var(--ks-proportion-5); }
.ks-proportion > :nth-child(6), .ks-proportion-legend > :nth-child(6) { --ks-proportion-slot: var(--ks-proportion-6); }
.ks-proportion > :nth-child(7), .ks-proportion-legend > :nth-child(7) { --ks-proportion-slot: var(--ks-proportion-7); }
.ks-proportion > :nth-child(8), .ks-proportion-legend > :nth-child(8) { --ks-proportion-slot: var(--ks-proportion-8); }
/* Past eight, everything folds to the neutral remainder — never a 9th hue. */
.ks-proportion > :nth-child(n+9), .ks-proportion-legend > :nth-child(n+9) { --ks-proportion-slot: var(--ks-chart-axis, var(--ks-slate-300)); }
/* State: the color MEANS good / warning / bad, or is the neutral remainder
   ("unassigned", "other"). On a segment or on a legend key. */
.ks-proportion-seg--success, .ks-proportion-key--success { --ks-proportion-color: var(--ks-success-default); }
.ks-proportion-seg--warning, .ks-proportion-key--warning { --ks-proportion-color: var(--ks-warning-default); }
.ks-proportion-seg--error,   .ks-proportion-key--error   { --ks-proportion-color: var(--ks-error-default); }
.ks-proportion-seg--neutral, .ks-proportion-key--neutral { --ks-proportion-color: var(--ks-surface-emphasis); }

.ks-proportion-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ks-space-1\.5) var(--ks-space-4);
  margin: var(--ks-space-2\.5) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--ks-text-sm);
  color: var(--ks-text-muted);
}
.ks-proportion-legend > li { display: inline-flex; align-items: center; gap: var(--ks-space-2); min-width: 0; }
.ks-proportion-key {
  flex: none;
  inline-size: 10px;
  block-size: 10px;
  border-radius: var(--ks-radius-sm);
  background: var(--ks-proportion-color, var(--ks-proportion-slot, var(--ks-proportion-1)));
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.ks-proportion-value { color: var(--ks-text-default); font-weight: var(--ks-weight-semibold); font-variant-numeric: tabular-nums; }

/* ---- Print: these infographics are data, so their colors print, and an
   entrance that hasn't run yet prints in its final state. ---- */
@media print {
  .ks-stagger-js > * { opacity: 1 !important; animation: none !important; }
  .ks-ring, .ks-proportion, .ks-step:not(:last-child)::after { animation: none !important; }
  .ks-steps, .ks-proportion, .ks-ring, .ks-meter-field { break-inside: avoid; }
}
