/**
 * 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);

  --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; }

/* 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);
}

.ks-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* ============================================================
   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); }
}
