/* Freeday, tokens + components in one file. GENERATED by tokens/build.mjs. */
/* Freeday tokens. GENERATED by tokens/build.mjs. Do not edit by hand. */
:root {
  --azure-50: #eff4ff;
  --azure-100: #dbe6fe;
  --azure-200: #bdd2fd;
  --azure-300: #90b3fb;
  --azure-400: #5c8bf6;
  --azure-500: #3467ec;
  --azure-600: #2050d8;
  --azure-700: #1c40b0;
  --azure-800: #1c398c;
  --azure-900: #1c336e;
  --azure-950: #131e42;
  --primary-50: var(--azure-50);
  --primary-100: var(--azure-100);
  --primary-300: var(--azure-300);
  --primary-400: var(--azure-400);
  --primary-500: var(--azure-500);
  --primary-600: var(--azure-600);
  --primary-700: var(--azure-700);
  --primary-800: var(--azure-800);
  --slate-0: #ffffff;
  --slate-50: #f7f8fa;
  --slate-100: #eef0f4;
  --slate-200: #e0e3ea;
  --slate-300: #cbd0da;
  --slate-400: #99a1b3;
  --slate-450: #798295;
  --slate-500: #6b7488;
  --slate-600: #4d5568;
  --slate-700: #3a4152;
  --slate-800: #262b38;
  --slate-900: #171b26;
  --slate-950: #0e111a;
  --sky-50: #eff6fd;
  --sky-100: #d6e9fb;
  --sky-200: #b0d4f6;
  --sky-300: #7bbdef;
  --sky-400: #47a1e6;
  --sky-500: #1a86dc;
  --sky-600: #0078d4;
  --sky-700: #0061ac;
  --sky-800: #004d88;
  --sky-900: #003a67;
  --red-100: #fee2e2;
  --red-300: #fca5a5;
  --red-400: #f87171;
  --red-500: #ef4444;
  --red-600: #dc2626;
  --red-700: #b91c1c;
  --amber-50: #fffbeb;
  --amber-100: #fef3c7;
  --amber-300: #fcd34d;
  --amber-400: #fbbf24;
  --amber-500: #f59e0b;
  --amber-600: #d97706;
  --amber-700: #b45309;
  --amber-800: #92400e;
  --green-50: #f0fdf4;
  --green-100: #dcfce7;
  --green-300: #86efac;
  --green-400: #4ade80;
  --green-500: #22c55e;
  --green-600: #16a34a;
  --green-700: #15803d;
  --green-800: #166534;
  --blue-50: #eff6ff;
  --blue-100: #dbeafe;
  --blue-300: #93c5fd;
  --blue-400: #60a5fa;
  --blue-500: #3b82f6;
  --blue-600: #2563eb;
  --blue-700: #1d4ed8;
  --blue-800: #1e40af;
  --emerald-50: #ecfdf5;
  --emerald-100: #d1fae5;
  --emerald-300: #6ee7b7;
  --emerald-400: #34d399;
  --emerald-500: #10b981;
  --emerald-600: #059669;
  --emerald-700: #047857;
  --emerald-800: #065f46;
  --lime-50: #f7fee7;
  --lime-100: #ecfccb;
  --lime-300: #bef264;
  --lime-400: #a3e635;
  --lime-500: #84cc16;
  --lime-600: #65a30d;
  --lime-700: #4d7c0f;
  --lime-800: #3f6212;
  --orange-50: #fff7ed;
  --orange-100: #ffedd5;
  --orange-300: #fdba74;
  --orange-400: #fb923c;
  --orange-500: #f97316;
  --orange-600: #ea580c;
  --orange-700: #c2410c;
  --orange-800: #9a3412;
  --yellow-50: #fefce8;
  --yellow-100: #fef9c3;
  --yellow-300: #fde047;
  --yellow-400: #facc15;
  --yellow-500: #eab308;
  --yellow-600: #ca8a04;
  --yellow-700: #a16207;
  --yellow-800: #854d0e;
  --yellow-900: #713f12;
  --teal-50: #f0fdfa;
  --teal-100: #ccfbf1;
  --teal-300: #5eead4;
  --teal-400: #2dd4bf;
  --teal-500: #14b8a6;
  --teal-600: #0d9488;
  --teal-700: #0f766e;
  --teal-800: #115e59;
  --cyan-50: #ecfeff;
  --cyan-100: #cffafe;
  --cyan-300: #67e8f9;
  --cyan-400: #22d3ee;
  --cyan-500: #06b6d4;
  --cyan-600: #0891b2;
  --cyan-700: #0e7490;
  --cyan-800: #155e75;
  --indigo-50: #eef2ff;
  --indigo-100: #e0e7ff;
  --indigo-200: #c7d2fe;
  --indigo-300: #a5b4fc;
  --indigo-400: #818cf8;
  --indigo-500: #6366f1;
  --indigo-600: #4f46e5;
  --indigo-700: #4338ca;
  --indigo-800: #3730a3;
  --violet-50: #f5f3ff;
  --violet-100: #ede9fe;
  --violet-200: #ddd6fe;
  --violet-300: #c4b5fd;
  --violet-400: #a78bfa;
  --violet-500: #8b5cf6;
  --violet-600: #7c3aed;
  --violet-700: #6d28d9;
  --violet-800: #5b21b6;
  --purple-50: #faf5ff;
  --purple-100: #f3e8ff;
  --purple-300: #d8b4fe;
  --purple-400: #c084fc;
  --purple-500: #a855f7;
  --purple-600: #9333ea;
  --purple-700: #7e22ce;
  --purple-800: #6b21a8;
  --fuchsia-50: #fdf4ff;
  --fuchsia-100: #fae8ff;
  --fuchsia-300: #f0abfc;
  --fuchsia-400: #e879f9;
  --fuchsia-500: #d946ef;
  --fuchsia-600: #c026d3;
  --fuchsia-700: #a21caf;
  --fuchsia-800: #86198f;
  --pink-50: #fdf2f8;
  --pink-100: #fce7f3;
  --pink-300: #f9a8d4;
  --pink-400: #f472b6;
  --pink-500: #ec4899;
  --pink-600: #db2777;
  --pink-700: #be185d;
  --pink-800: #9d174d;
  --rose-50: #fff1f2;
  --rose-100: #ffe4e6;
  --rose-300: #fda4af;
  --rose-400: #fb7185;
  --rose-500: #f43f5e;
  --rose-600: #e11d48;
  --rose-700: #be123c;
  --rose-800: #9f1239;
  --font-display: 'Sora', ui-sans-serif, system-ui, sans-serif;
  --font-body: 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5625rem;
  --text-3xl: 1.9375rem;
  --text-4xl: 2.4375rem;
  --text-5xl: 3.0625rem;
  --leading-tight: 1.2;
  --leading-snug: 1.35;
  --leading-normal: 1.55;
  --leading-loose: 1.75;
  --tracking-tighter: -0.03em;
  --tracking-tight: -0.02em;
  --tracking-normal: 0;
  --tracking-wide: 0.06em;
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;
  --space-0: 0;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;
  --radius-xs: 3px;
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 10px;
  --radius-xl: 14px;
  --radius-full: 999px;
  --shadow-1: 0 1px 2px rgba(16,14,30,.06);
  --shadow-2: 0 2px 6px rgba(16,14,30,.07),0 1px 2px rgba(16,14,30,.05);
  --shadow-3: 0 4px 16px -2px rgba(16,14,30,.14),0 1px 3px rgba(16,14,30,.10);
  --shadow-4: 0 24px 48px -12px rgba(16,14,30,.26),0 2px 8px rgba(16,14,30,.10);
  --shadow-lift: 0 1px 2px rgba(16,14,30,.05),0 16px 34px -14px rgba(16,14,30,.24);
  --shadow-lift-hover: 0 2px 4px rgba(16,14,30,.06),0 30px 50px -16px rgba(16,14,30,.33);
  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 280ms;
  --ease-standard: cubic-bezier(.2,0,0,1);
  --ease-spring: cubic-bezier(.2,.8,.2,1);
  --control-h: 2.5rem;
  --control-box: 1.15rem;
  --control-switch-w: 2.4rem;
  --control-switch-h: 1.35rem;
  --bw: 1px;
  --color-primary: var(--primary-600);
  --color-primary-hover: var(--primary-700);
  --color-primary-active: var(--primary-800);
  --color-on-primary: var(--slate-0);
  --color-primary-soft: var(--primary-50);
  --color-primary-border: var(--primary-100);
  --color-accent: var(--sky-600);
  --color-accent-hover: var(--sky-700);
  --color-on-accent: var(--slate-0);
  --color-on-danger: var(--slate-0);
  --color-danger-btn: var(--red-600);
  --color-surface: var(--slate-0);
  --color-surface-2: var(--slate-50);
  --color-surface-3: var(--slate-100);
  --color-surface-stripe: #fbfbfc;
  --color-surface-raised: var(--color-surface);
  --color-scrim: rgba(10,12,25,.5);
  --color-text: var(--slate-900);
  --color-text-muted: var(--slate-600);
  --color-text-subtle: var(--slate-500);
  --color-on-brand: var(--slate-0);
  --color-border: var(--slate-200);
  --color-border-strong: var(--slate-300);
  --color-border-muted: var(--slate-100);
  --color-control-border: var(--slate-450);
  --color-switch-thumb: var(--slate-0);
  --color-shadow-inset: rgba(16,14,30,.06);
  --color-inverse-surface: var(--slate-900);
  --color-inverse-text: var(--slate-0);
  --color-success: var(--green-600);
  --color-success-soft: var(--green-100);
  --color-warning: #c2740a;
  --color-warning-soft: var(--amber-100);
  --color-danger: var(--red-600);
  --color-danger-soft: var(--red-100);
  --color-info: var(--blue-600);
  --color-info-soft: var(--blue-100);
  --color-danger-border: #de979c;
  --color-primary-strong: var(--primary-700);
  --color-success-strong: var(--green-800);
  --color-warning-strong: var(--amber-800);
  --color-danger-strong: var(--red-700);
  --color-info-strong: var(--blue-700);
  --color-primary-rgb: var(--primary-600-rgb);
  --color-on-primary-rgb: var(--slate-0-rgb);
  --color-danger-rgb: var(--red-600-rgb);
  --color-danger-btn-rgb: var(--red-600-rgb);
  --color-success-rgb: var(--green-600-rgb);
  --color-warning-rgb: 194,116,10;
  --color-info-rgb: var(--blue-600-rgb);
  --color-surface-rgb: var(--slate-0-rgb);
  --color-inverse-text-rgb: var(--slate-0-rgb);
  --chart-1: #2a78d6;
  --chart-2: #eb6834;
  --chart-3: #1baf7a;
  --chart-4: #eda100;
  --chart-5: #e87ba4;
  --chart-6: #008300;
  --chart-7: #4a3aa7;
  --chart-8: #e34948;
  --chart-grid: var(--slate-200);
  --chart-tick: var(--slate-400);
  --tone-1: var(--chart-1);
  --tone-2: var(--chart-2);
  --tone-3: var(--chart-3);
  --tone-4: var(--chart-4);
  --tone-5: var(--chart-5);
  --tone-6: var(--chart-6);
  --tone-7: var(--chart-7);
  --tone-8: var(--chart-8);
  --tone-1-bg: #d9e7f8;
  --tone-2-bg: #fbe4da;
  --tone-3-bg: #d6f1e7;
  --tone-4-bg: #fceed1;
  --tone-5-bg: #fbe7ef;
  --tone-6-bg: #d1e9d1;
  --tone-7-bg: #dedcef;
  --tone-8-bg: #fadede;
  --tone-1-ink: #214a7e;
  --tone-2-ink: #81422d;
  --tone-3-ink: #196550;
  --tone-4-ink: #825e13;
  --tone-5-ink: #804b65;
  --tone-6-ink: #0c4f13;
  --tone-7-ink: #312b67;
  --tone-8-ink: #7d3237;
  --tone-1-border: #a9c3e4;
  --tone-2-border: #e3beb3;
  --tone-3-border: #a5d3c8;
  --tone-4-border: #e4cfa4;
  --tone-5-border: #e2c4d5;
  --tone-6-border: #9dc6a4;
  --tone-7-border: #b3b0d6;
  --tone-8-border: #e1b5b9;
  --tone-1-border-soft: rgba(42,120,214,.26);
  --tone-2-border-soft: rgba(235,104,52,.26);
  --tone-3-border-soft: rgba(27,175,122,.26);
  --tone-4-border-soft: rgba(237,161,0,.26);
  --tone-5-border-soft: rgba(232,123,164,.26);
  --tone-6-border-soft: rgba(0,131,0,.26);
  --tone-7-border-soft: rgba(74,58,167,.26);
  --tone-8-border-soft: rgba(227,73,72,.26);
  --surface-filter: none;
  --surface-inset: inset 0 0 0 0 transparent;
  --focus-ring: var(--primary-600);
  --focus-ring-width: 2px;
  --amber-500-rgb: 245,158,11;
  --blue-400-rgb: 96,165,250;
  --blue-600-rgb: 37,99,235;
  --green-500-rgb: 34,197,94;
  --green-600-rgb: 22,163,74;
  --primary-400-rgb: 92,139,246;
  --primary-500-rgb: 52,103,236;
  --primary-600-rgb: 32,80,216;
  --red-400-rgb: 248,113,113;
  --red-600-rgb: 220,38,38;
  --slate-0-rgb: 255,255,255;
  --slate-900-rgb: 23,27,38;
  --slate-950-rgb: 14,17,26;
}
/* The SYSTEM default stays root-scoped: it is a statement about the document, and dropping :root
 * here would match every element that is not itself [data-theme="light"], which would re-darken
 * the children of a light panel, since they carry no attribute of their own. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  --shadow-1: 0 1px 2px rgba(0,0,0,.4);
  --shadow-2: 0 2px 6px rgba(0,0,0,.45);
  --shadow-3: 0 6px 20px -2px rgba(0,0,0,.6),0 1px 3px rgba(0,0,0,.5);
  --shadow-4: 0 28px 56px -12px rgba(0,0,0,.72),0 2px 8px rgba(0,0,0,.5);
  --shadow-lift: 0 1px 2px rgba(0,0,0,.5),0 18px 38px -14px rgba(0,0,0,.72);
  --shadow-lift-hover: 0 3px 6px rgba(0,0,0,.55),0 32px 56px -16px rgba(0,0,0,.82);
  --color-primary: var(--primary-500);
  --color-primary-hover: var(--primary-400);
  --color-primary-active: var(--primary-300);
  --color-on-primary: var(--slate-0);
  --color-primary-soft: rgba(var(--primary-600-rgb), .2);
  --color-primary-border: rgba(var(--primary-400-rgb), .28);
  --color-accent: var(--sky-400);
  --color-accent-hover: var(--sky-300);
  --color-on-accent: #06233f;
  --color-on-danger: var(--slate-0);
  --color-danger-btn: var(--red-600);
  --color-surface: var(--slate-950);
  --color-surface-2: var(--slate-900);
  --color-surface-3: var(--slate-800);
  --color-surface-stripe: #131721;
  --color-surface-raised: var(--color-surface);
  --color-text: var(--slate-100);
  --color-text-muted: var(--slate-400);
  --color-text-subtle: var(--slate-450);
  --color-on-brand: var(--slate-0);
  --color-border: var(--slate-800);
  --color-border-strong: var(--slate-700);
  --color-border-muted: #1c2130;
  --color-control-border: var(--slate-450);
  --color-inverse-surface: var(--slate-100);
  --color-inverse-text: var(--slate-900);
  --color-success: var(--green-500);
  --color-success-soft: rgba(34,197,94,.18);
  --color-warning: var(--amber-500);
  --color-warning-soft: rgba(234,179,8,.18);
  --color-danger: var(--red-400);
  --color-danger-soft: rgba(248,113,113,.18);
  --color-info: var(--blue-400);
  --color-info-soft: rgba(96,165,250,.18);
  --color-danger-border: #7a474f;
  --color-primary-strong: var(--primary-300);
  --color-success-strong: var(--green-400);
  --color-warning-strong: var(--amber-400);
  --color-danger-strong: var(--red-300);
  --color-info-strong: var(--blue-300);
  --color-primary-rgb: var(--primary-500-rgb);
  --color-danger-rgb: var(--red-400-rgb);
  --color-success-rgb: var(--green-500-rgb);
  --color-warning-rgb: var(--amber-500-rgb);
  --color-info-rgb: var(--blue-400-rgb);
  --color-surface-rgb: var(--slate-950-rgb);
  --color-inverse-text-rgb: var(--slate-900-rgb);
  --chart-1: #3987e5;
  --chart-2: #d95926;
  --chart-3: #199e70;
  --chart-4: #c98500;
  --chart-5: #d55181;
  --chart-6: #008300;
  --chart-7: #9085e9;
  --chart-8: #e66767;
  --chart-grid: var(--slate-800);
  --chart-tick: var(--slate-600);
  --tone-1-bg: #16263f;
  --tone-2-bg: #331e1c;
  --tone-3-bg: #102a29;
  --tone-4-bg: #302615;
  --tone-5-bg: #321d2d;
  --tone-6-bg: #0b2615;
  --tone-7-bg: #25263f;
  --tone-8-bg: #352028;
  --tone-1-ink: #94bced;
  --tone-2-ink: #e4a58d;
  --tone-3-ink: #84c7b2;
  --tone-4-ink: #dcbb7a;
  --tone-5-ink: #e2a1bb;
  --tone-6-ink: #77ba7a;
  --tone-7-ink: #bfbbef;
  --tone-8-ink: #eaacae;
  --tone-1-border: #2c476c;
  --tone-2-border: #5c3933;
  --tone-3-border: #224e49;
  --tone-4-border: #574627;
  --tone-5-border: #5b364e;
  --tone-6-border: #1b4527;
  --tone-7-border: #46466d;
  --tone-8-border: #603d46;
  --tone-1-border-soft: rgba(57,135,229,.26);
  --tone-2-border-soft: rgba(217,89,38,.26);
  --tone-3-border-soft: rgba(25,158,112,.26);
  --tone-4-border-soft: rgba(201,133,0,.26);
  --tone-5-border-soft: rgba(213,81,129,.26);
  --tone-7-border-soft: rgba(144,133,233,.26);
  --tone-8-border-soft: rgba(230,103,103,.26);
  --focus-ring: var(--primary-400);
  }
}
/* The two EXPLICIT opt-ins are deliberately NOT scoped to :root, for the same reason as density
 * below: these are inheriting custom properties, so data-theme on any ancestor re-themes just that
 * subtree, a dark brand panel beside a light form is an ordinary layout, and it should not require
 * re-colouring each component by hand. The root still matches, so data-theme on <html> is unchanged.
 * Both keep the same specificity (0,1,0) as :root and come after it, so they still win there; and
 * --light after --dark means a light island inside a dark region wins in turn. */
[data-theme="dark"] {
  --shadow-1: 0 1px 2px rgba(0,0,0,.4);
  --shadow-2: 0 2px 6px rgba(0,0,0,.45);
  --shadow-3: 0 6px 20px -2px rgba(0,0,0,.6),0 1px 3px rgba(0,0,0,.5);
  --shadow-4: 0 28px 56px -12px rgba(0,0,0,.72),0 2px 8px rgba(0,0,0,.5);
  --shadow-lift: 0 1px 2px rgba(0,0,0,.5),0 18px 38px -14px rgba(0,0,0,.72);
  --shadow-lift-hover: 0 3px 6px rgba(0,0,0,.55),0 32px 56px -16px rgba(0,0,0,.82);
  --color-primary: var(--primary-500);
  --color-primary-hover: var(--primary-400);
  --color-primary-active: var(--primary-300);
  --color-on-primary: var(--slate-0);
  --color-primary-soft: rgba(var(--primary-600-rgb), .2);
  --color-primary-border: rgba(var(--primary-400-rgb), .28);
  --color-accent: var(--sky-400);
  --color-accent-hover: var(--sky-300);
  --color-on-accent: #06233f;
  --color-on-danger: var(--slate-0);
  --color-danger-btn: var(--red-600);
  --color-surface: var(--slate-950);
  --color-surface-2: var(--slate-900);
  --color-surface-3: var(--slate-800);
  --color-surface-stripe: #131721;
  --color-surface-raised: var(--color-surface);
  --color-text: var(--slate-100);
  --color-text-muted: var(--slate-400);
  --color-text-subtle: var(--slate-450);
  --color-on-brand: var(--slate-0);
  --color-border: var(--slate-800);
  --color-border-strong: var(--slate-700);
  --color-border-muted: #1c2130;
  --color-control-border: var(--slate-450);
  --color-inverse-surface: var(--slate-100);
  --color-inverse-text: var(--slate-900);
  --color-success: var(--green-500);
  --color-success-soft: rgba(34,197,94,.18);
  --color-warning: var(--amber-500);
  --color-warning-soft: rgba(234,179,8,.18);
  --color-danger: var(--red-400);
  --color-danger-soft: rgba(248,113,113,.18);
  --color-info: var(--blue-400);
  --color-info-soft: rgba(96,165,250,.18);
  --color-danger-border: #7a474f;
  --color-primary-strong: var(--primary-300);
  --color-success-strong: var(--green-400);
  --color-warning-strong: var(--amber-400);
  --color-danger-strong: var(--red-300);
  --color-info-strong: var(--blue-300);
  --color-primary-rgb: var(--primary-500-rgb);
  --color-danger-rgb: var(--red-400-rgb);
  --color-success-rgb: var(--green-500-rgb);
  --color-warning-rgb: var(--amber-500-rgb);
  --color-info-rgb: var(--blue-400-rgb);
  --color-surface-rgb: var(--slate-950-rgb);
  --color-inverse-text-rgb: var(--slate-900-rgb);
  --chart-1: #3987e5;
  --chart-2: #d95926;
  --chart-3: #199e70;
  --chart-4: #c98500;
  --chart-5: #d55181;
  --chart-6: #008300;
  --chart-7: #9085e9;
  --chart-8: #e66767;
  --chart-grid: var(--slate-800);
  --chart-tick: var(--slate-600);
  --tone-1-bg: #16263f;
  --tone-2-bg: #331e1c;
  --tone-3-bg: #102a29;
  --tone-4-bg: #302615;
  --tone-5-bg: #321d2d;
  --tone-6-bg: #0b2615;
  --tone-7-bg: #25263f;
  --tone-8-bg: #352028;
  --tone-1-ink: #94bced;
  --tone-2-ink: #e4a58d;
  --tone-3-ink: #84c7b2;
  --tone-4-ink: #dcbb7a;
  --tone-5-ink: #e2a1bb;
  --tone-6-ink: #77ba7a;
  --tone-7-ink: #bfbbef;
  --tone-8-ink: #eaacae;
  --tone-1-border: #2c476c;
  --tone-2-border: #5c3933;
  --tone-3-border: #224e49;
  --tone-4-border: #574627;
  --tone-5-border: #5b364e;
  --tone-6-border: #1b4527;
  --tone-7-border: #46466d;
  --tone-8-border: #603d46;
  --tone-1-border-soft: rgba(57,135,229,.26);
  --tone-2-border-soft: rgba(217,89,38,.26);
  --tone-3-border-soft: rgba(25,158,112,.26);
  --tone-4-border-soft: rgba(201,133,0,.26);
  --tone-5-border-soft: rgba(213,81,129,.26);
  --tone-7-border-soft: rgba(144,133,233,.26);
  --tone-8-border-soft: rgba(230,103,103,.26);
  --focus-ring: var(--primary-400);
}
[data-theme="light"] {
  --shadow-1: 0 1px 2px rgba(16,14,30,.06);
  --shadow-2: 0 2px 6px rgba(16,14,30,.07),0 1px 2px rgba(16,14,30,.05);
  --shadow-3: 0 4px 16px -2px rgba(16,14,30,.14),0 1px 3px rgba(16,14,30,.10);
  --shadow-4: 0 24px 48px -12px rgba(16,14,30,.26),0 2px 8px rgba(16,14,30,.10);
  --shadow-lift: 0 1px 2px rgba(16,14,30,.05),0 16px 34px -14px rgba(16,14,30,.24);
  --shadow-lift-hover: 0 2px 4px rgba(16,14,30,.06),0 30px 50px -16px rgba(16,14,30,.33);
  --color-primary: var(--primary-600);
  --color-primary-hover: var(--primary-700);
  --color-primary-active: var(--primary-800);
  --color-on-primary: var(--slate-0);
  --color-primary-soft: var(--primary-50);
  --color-primary-border: var(--primary-100);
  --color-accent: var(--sky-600);
  --color-accent-hover: var(--sky-700);
  --color-on-accent: var(--slate-0);
  --color-on-danger: var(--slate-0);
  --color-danger-btn: var(--red-600);
  --color-surface: var(--slate-0);
  --color-surface-2: var(--slate-50);
  --color-surface-3: var(--slate-100);
  --color-surface-stripe: #fbfbfc;
  --color-surface-raised: var(--color-surface);
  --color-text: var(--slate-900);
  --color-text-muted: var(--slate-600);
  --color-text-subtle: var(--slate-500);
  --color-on-brand: var(--slate-0);
  --color-border: var(--slate-200);
  --color-border-strong: var(--slate-300);
  --color-border-muted: var(--slate-100);
  --color-control-border: var(--slate-450);
  --color-inverse-surface: var(--slate-900);
  --color-inverse-text: var(--slate-0);
  --color-success: var(--green-600);
  --color-success-soft: var(--green-100);
  --color-warning: #c2740a;
  --color-warning-soft: var(--amber-100);
  --color-danger: var(--red-600);
  --color-danger-soft: var(--red-100);
  --color-info: var(--blue-600);
  --color-info-soft: var(--blue-100);
  --color-danger-border: #de979c;
  --color-primary-strong: var(--primary-700);
  --color-success-strong: var(--green-800);
  --color-warning-strong: var(--amber-800);
  --color-danger-strong: var(--red-700);
  --color-info-strong: var(--blue-700);
  --color-primary-rgb: var(--primary-600-rgb);
  --color-danger-rgb: var(--red-600-rgb);
  --color-success-rgb: var(--green-600-rgb);
  --color-warning-rgb: 194,116,10;
  --color-info-rgb: var(--blue-600-rgb);
  --color-surface-rgb: var(--slate-0-rgb);
  --color-inverse-text-rgb: var(--slate-0-rgb);
  --chart-1: #2a78d6;
  --chart-2: #eb6834;
  --chart-3: #1baf7a;
  --chart-4: #eda100;
  --chart-5: #e87ba4;
  --chart-6: #008300;
  --chart-7: #4a3aa7;
  --chart-8: #e34948;
  --chart-grid: var(--slate-200);
  --chart-tick: var(--slate-400);
  --tone-1-bg: #d9e7f8;
  --tone-2-bg: #fbe4da;
  --tone-3-bg: #d6f1e7;
  --tone-4-bg: #fceed1;
  --tone-5-bg: #fbe7ef;
  --tone-6-bg: #d1e9d1;
  --tone-7-bg: #dedcef;
  --tone-8-bg: #fadede;
  --tone-1-ink: #214a7e;
  --tone-2-ink: #81422d;
  --tone-3-ink: #196550;
  --tone-4-ink: #825e13;
  --tone-5-ink: #804b65;
  --tone-6-ink: #0c4f13;
  --tone-7-ink: #312b67;
  --tone-8-ink: #7d3237;
  --tone-1-border: #a9c3e4;
  --tone-2-border: #e3beb3;
  --tone-3-border: #a5d3c8;
  --tone-4-border: #e4cfa4;
  --tone-5-border: #e2c4d5;
  --tone-6-border: #9dc6a4;
  --tone-7-border: #b3b0d6;
  --tone-8-border: #e1b5b9;
  --tone-1-border-soft: rgba(42,120,214,.26);
  --tone-2-border-soft: rgba(235,104,52,.26);
  --tone-3-border-soft: rgba(27,175,122,.26);
  --tone-4-border-soft: rgba(237,161,0,.26);
  --tone-5-border-soft: rgba(232,123,164,.26);
  --tone-7-border-soft: rgba(74,58,167,.26);
  --tone-8-border-soft: rgba(227,73,72,.26);
  --focus-ring: var(--primary-600);
}
/* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit,
 * putting the attribute on any ancestor (a route wrapper, one section) densifies just that
 * subtree, which is how density is actually decided: per screen, not per app. The root still
 * matches, so setting data-density on the html element keeps working exactly as before. */
[data-density="compact"] {
  --space-3: 0.625rem;
  --space-4: 0.75rem;
  --space-5: 1rem;
  --space-6: 1.25rem;
  --control-h: 2rem;
}
/* ...and the way back out. Inheritance only runs downhill: once the root is compact every subtree is
 * compact, and before this rule existed a data-density="comfortable" wrapper matched nothing, so the
 * comment above was true in one direction only. An app that is dense overall and carries one region
 * of shared chrome (a header, a toolbar, a footer shared with a sibling product) had to restate the
 * defaults by hand, which is exactly the local copy that goes stale when the kit retunes them. */
[data-density="comfortable"] {
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --control-h: 2.5rem;
}
[data-style="glass"] {
  --color-surface-raised: rgba(255,255,255,.82);
  --surface-filter: blur(14px) saturate(150%);
  --surface-inset: inset 0 1px 0 rgba(255,255,255,.55);
}
[data-style="soft"] {
  --color-surface-raised: var(--color-surface);
  --surface-filter: none;
  --surface-inset: inset 0 0 0 0 transparent;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-style="glass"] {
  --color-surface-raised: rgba(23,27,38,.90);
  --surface-inset: inset 0 1px 0 rgba(255,255,255,.09);
  }
}
[data-theme="dark"][data-style="glass"] {
  --color-surface-raised: rgba(23,27,38,.90);
  --surface-inset: inset 0 1px 0 rgba(255,255,255,.09);
}
[data-theme="light"][data-style="glass"] {
  --color-surface-raised: rgba(255,255,255,.82);
  --surface-inset: inset 0 1px 0 rgba(255,255,255,.55);
}
/* Primary palette axis. 18 options, see $primaries in tokens.json. */
[data-primary="azure"] {
  --primary-50: var(--azure-50);
  --primary-100: var(--azure-100);
  --primary-300: var(--azure-300);
  --primary-400: var(--azure-400);
  --primary-500: var(--azure-500);
  --primary-600: var(--azure-600);
  --primary-700: var(--azure-700);
  --primary-800: var(--azure-800);
  --primary-400-rgb: 92,139,246;
  --primary-500-rgb: 52,103,236;
  --primary-600-rgb: 32,80,216;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="sky"] {
  --primary-50: var(--sky-50);
  --primary-100: var(--sky-100);
  --primary-300: var(--sky-300);
  --primary-400: var(--sky-400);
  --primary-500: var(--sky-500);
  --primary-600: var(--sky-600);
  --primary-700: var(--sky-700);
  --primary-800: var(--sky-800);
  --primary-400-rgb: 71,161,230;
  --primary-500-rgb: 26,134,220;
  --primary-600-rgb: 0,120,212;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="sky"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="sky"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="sky"] {
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="blue"] {
  --primary-50: var(--blue-50);
  --primary-100: var(--blue-100);
  --primary-300: var(--blue-300);
  --primary-400: var(--blue-400);
  --primary-500: var(--blue-500);
  --primary-600: var(--blue-600);
  --primary-700: var(--blue-700);
  --primary-800: var(--blue-800);
  --primary-400-rgb: 96,165,250;
  --primary-500-rgb: 59,130,246;
  --primary-600-rgb: 37,99,235;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="blue"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="blue"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="blue"] {
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="indigo"] {
  --primary-50: var(--indigo-50);
  --primary-100: var(--indigo-100);
  --primary-300: var(--indigo-300);
  --primary-400: var(--indigo-400);
  --primary-500: var(--indigo-500);
  --primary-600: var(--indigo-600);
  --primary-700: var(--indigo-700);
  --primary-800: var(--indigo-800);
  --primary-400-rgb: 129,140,248;
  --primary-500-rgb: 99,102,241;
  --primary-600-rgb: 79,70,229;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="indigo"] {
  --primary-300: var(--indigo-200);
  --primary-400: var(--indigo-300);
  --primary-500: var(--indigo-400);
  --primary-400-rgb: 165,180,252;
  --primary-500-rgb: 129,140,248;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="indigo"] {
  --primary-300: var(--indigo-200);
  --primary-400: var(--indigo-300);
  --primary-500: var(--indigo-400);
  --primary-400-rgb: 165,180,252;
  --primary-500-rgb: 129,140,248;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="indigo"] {
  --primary-300: var(--indigo-300);
  --primary-400: var(--indigo-400);
  --primary-500: var(--indigo-500);
  --primary-400-rgb: 129,140,248;
  --primary-500-rgb: 99,102,241;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="violet"] {
  --primary-50: var(--violet-50);
  --primary-100: var(--violet-100);
  --primary-300: var(--violet-300);
  --primary-400: var(--violet-400);
  --primary-500: var(--violet-500);
  --primary-600: var(--violet-600);
  --primary-700: var(--violet-700);
  --primary-800: var(--violet-800);
  --primary-400-rgb: 167,139,250;
  --primary-500-rgb: 139,92,246;
  --primary-600-rgb: 124,58,237;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="violet"] {
  --primary-300: var(--violet-200);
  --primary-400: var(--violet-300);
  --primary-500: var(--violet-400);
  --primary-400-rgb: 196,181,253;
  --primary-500-rgb: 167,139,250;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="violet"] {
  --primary-300: var(--violet-200);
  --primary-400: var(--violet-300);
  --primary-500: var(--violet-400);
  --primary-400-rgb: 196,181,253;
  --primary-500-rgb: 167,139,250;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="violet"] {
  --primary-300: var(--violet-300);
  --primary-400: var(--violet-400);
  --primary-500: var(--violet-500);
  --primary-400-rgb: 167,139,250;
  --primary-500-rgb: 139,92,246;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="purple"] {
  --primary-50: var(--purple-50);
  --primary-100: var(--purple-100);
  --primary-300: var(--purple-300);
  --primary-400: var(--purple-400);
  --primary-500: var(--purple-500);
  --primary-600: var(--purple-600);
  --primary-700: var(--purple-700);
  --primary-800: var(--purple-800);
  --primary-400-rgb: 192,132,252;
  --primary-500-rgb: 168,85,247;
  --primary-600-rgb: 147,51,234;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="purple"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="purple"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="purple"] {
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="fuchsia"] {
  --primary-50: var(--fuchsia-50);
  --primary-100: var(--fuchsia-100);
  --primary-300: var(--fuchsia-300);
  --primary-400: var(--fuchsia-400);
  --primary-500: var(--fuchsia-500);
  --primary-600: var(--fuchsia-600);
  --primary-700: var(--fuchsia-700);
  --primary-800: var(--fuchsia-800);
  --primary-400-rgb: 232,121,249;
  --primary-500-rgb: 217,70,239;
  --primary-600-rgb: 192,38,211;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="fuchsia"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="fuchsia"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="fuchsia"] {
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="pink"] {
  --primary-50: var(--pink-50);
  --primary-100: var(--pink-100);
  --primary-300: var(--pink-300);
  --primary-400: var(--pink-400);
  --primary-500: var(--pink-500);
  --primary-600: var(--pink-600);
  --primary-700: var(--pink-700);
  --primary-800: var(--pink-800);
  --primary-400-rgb: 244,114,182;
  --primary-500-rgb: 236,72,153;
  --primary-600-rgb: 219,39,119;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="pink"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="pink"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="pink"] {
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="rose"] {
  --primary-50: var(--rose-50);
  --primary-100: var(--rose-100);
  --primary-300: var(--rose-300);
  --primary-400: var(--rose-400);
  --primary-500: var(--rose-500);
  --primary-600: var(--rose-600);
  --primary-700: var(--rose-700);
  --primary-800: var(--rose-800);
  --primary-400-rgb: 251,113,133;
  --primary-500-rgb: 244,63,94;
  --primary-600-rgb: 225,29,72;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="rose"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="rose"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="rose"] {
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="orange"] {
  --primary-50: var(--orange-50);
  --primary-100: var(--orange-100);
  --primary-300: var(--orange-300);
  --primary-400: var(--orange-400);
  --primary-500: var(--orange-500);
  --primary-600: var(--orange-600);
  --primary-700: var(--orange-700);
  --primary-800: var(--orange-800);
  --primary-400-rgb: 251,146,60;
  --primary-500-rgb: 249,115,22;
  --primary-600-rgb: 234,88,12;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-primary="amber"] {
  --primary-50: var(--amber-50);
  --primary-100: var(--amber-100);
  --primary-300: var(--amber-300);
  --primary-400: var(--amber-400);
  --primary-500: var(--amber-500);
  --primary-600: var(--amber-600);
  --primary-700: var(--amber-700);
  --primary-800: var(--amber-800);
  --primary-400-rgb: 251,191,36;
  --primary-500-rgb: 245,158,11;
  --primary-600-rgb: 217,119,6;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-primary="yellow"] {
  --primary-50: var(--yellow-50);
  --primary-100: var(--yellow-100);
  --primary-300: var(--yellow-300);
  --primary-400: var(--yellow-400);
  --primary-500: var(--yellow-500);
  --primary-600: var(--yellow-700);
  --primary-700: var(--yellow-800);
  --primary-800: var(--yellow-900);
  --primary-400-rgb: 250,204,21;
  --primary-500-rgb: 234,179,8;
  --primary-600-rgb: 161,98,7;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="yellow"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="yellow"] {
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="yellow"] {
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
[data-primary="lime"] {
  --primary-50: var(--lime-50);
  --primary-100: var(--lime-100);
  --primary-300: var(--lime-300);
  --primary-400: var(--lime-400);
  --primary-500: var(--lime-500);
  --primary-600: var(--lime-600);
  --primary-700: var(--lime-700);
  --primary-800: var(--lime-800);
  --primary-400-rgb: 163,230,53;
  --primary-500-rgb: 132,204,22;
  --primary-600-rgb: 101,163,13;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-primary="green"] {
  --primary-50: var(--green-50);
  --primary-100: var(--green-100);
  --primary-300: var(--green-300);
  --primary-400: var(--green-400);
  --primary-500: var(--green-500);
  --primary-600: var(--green-600);
  --primary-700: var(--green-700);
  --primary-800: var(--green-800);
  --primary-400-rgb: 74,222,128;
  --primary-500-rgb: 34,197,94;
  --primary-600-rgb: 22,163,74;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-primary="emerald"] {
  --primary-50: var(--emerald-50);
  --primary-100: var(--emerald-100);
  --primary-300: var(--emerald-300);
  --primary-400: var(--emerald-400);
  --primary-500: var(--emerald-500);
  --primary-600: var(--emerald-600);
  --primary-700: var(--emerald-700);
  --primary-800: var(--emerald-800);
  --primary-400-rgb: 52,211,153;
  --primary-500-rgb: 16,185,129;
  --primary-600-rgb: 5,150,105;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-primary="teal"] {
  --primary-50: var(--teal-50);
  --primary-100: var(--teal-100);
  --primary-300: var(--teal-300);
  --primary-400: var(--teal-400);
  --primary-500: var(--teal-500);
  --primary-600: var(--teal-600);
  --primary-700: var(--teal-700);
  --primary-800: var(--teal-800);
  --primary-400-rgb: 45,212,191;
  --primary-500-rgb: 20,184,166;
  --primary-600-rgb: 13,148,136;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-primary="cyan"] {
  --primary-50: var(--cyan-50);
  --primary-100: var(--cyan-100);
  --primary-300: var(--cyan-300);
  --primary-400: var(--cyan-400);
  --primary-500: var(--cyan-500);
  --primary-600: var(--cyan-600);
  --primary-700: var(--cyan-700);
  --primary-800: var(--cyan-800);
  --primary-400-rgb: 34,211,238;
  --primary-500-rgb: 6,182,212;
  --primary-600-rgb: 8,145,178;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-primary="noir"] {
  --primary-50: var(--slate-100);
  --primary-100: var(--slate-200);
  --primary-300: var(--slate-500);
  --primary-400: var(--slate-700);
  --primary-500: var(--slate-800);
  --primary-600: var(--slate-900);
  --primary-700: var(--slate-800);
  --primary-800: var(--slate-950);
  --primary-400-rgb: 58,65,82;
  --primary-500-rgb: 38,43,56;
  --primary-600-rgb: 23,27,38;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-primary="noir"] {
  --primary-50: var(--slate-800);
  --primary-100: var(--slate-700);
  --primary-300: var(--slate-300);
  --primary-400: var(--slate-200);
  --primary-500: var(--slate-100);
  --primary-600: var(--slate-50);
  --primary-700: var(--slate-200);
  --primary-800: var(--slate-300);
  --primary-400-rgb: 224,227,234;
  --primary-500-rgb: 238,240,244;
  --primary-600-rgb: 247,248,250;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
  }
}
[data-theme="dark"][data-primary="noir"] {
  --primary-50: var(--slate-800);
  --primary-100: var(--slate-700);
  --primary-300: var(--slate-300);
  --primary-400: var(--slate-200);
  --primary-500: var(--slate-100);
  --primary-600: var(--slate-50);
  --primary-700: var(--slate-200);
  --primary-800: var(--slate-300);
  --primary-400-rgb: 224,227,234;
  --primary-500-rgb: 238,240,244;
  --primary-600-rgb: 247,248,250;
  --color-on-primary: var(--slate-950);
  --color-on-primary-rgb: var(--slate-950-rgb);
}
[data-theme="light"][data-primary="noir"] {
  --primary-50: var(--slate-100);
  --primary-100: var(--slate-200);
  --primary-300: var(--slate-500);
  --primary-400: var(--slate-700);
  --primary-500: var(--slate-800);
  --primary-600: var(--slate-900);
  --primary-700: var(--slate-800);
  --primary-800: var(--slate-950);
  --primary-400-rgb: 58,65,82;
  --primary-500-rgb: 38,43,56;
  --primary-600-rgb: 23,27,38;
  --color-on-primary: var(--slate-0);
  --color-on-primary-rgb: var(--slate-0-rgb);
}

/* Freeday components. GENERATED by tokens/build.mjs. Do not edit by hand. */
/* Freeday base layer: reset ringan + default via token */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text);
  background: var(--color-surface);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tighter); line-height: var(--leading-snug); margin: 0; }
a { color: var(--color-primary-strong); }
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-width); border-radius: var(--radius-xs); }
:where(button, input, select, textarea) { font: inherit; }
/* The browser hides [hidden] with a user-agent display:none, and any author `display` beats it, so
 * before this `<button class="fdy-btn" hidden>` rendered: 167 of the kit's 495 classes ignored the
 * attribute (browser/hidden-attr.mjs). `!important` because some of what it must beat outranks it:
 * `.fdy-app--nav-overlay .fdy-app__backdrop{display:block}` wins on specificity plus source order
 * without it. And hidden is a statement about the element, not a style to weigh.
 * "until-found" is left to the browser: it hides with content-visibility so find-in-page can still
 * reveal it, and display:none would remove exactly that. */
[hidden]:not([hidden="until-found" i]) { display: none !important; }
/* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
 * has a position, and because it is position:absolute its containing block is the nearest
 * POSITIONED ancestor, and with none, the initial containing block (the document itself).
 *
 * That matters, because `overflow` only clips a descendant whose containing block is inside the
 * overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
 * to name an icon button) parks at its static position, possibly thousands of px to the right —
 * and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
 * wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
 *
 * The fix is not here but on the containers: every clipping/scrolling container in the kit
 * that holds consumer markup declares `position:relative` so it becomes the containing block for
 * its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
 * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not,
 * the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
.fdy-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; }
/* Opt-in list reset. base.css is intentionally a *light* reset: it does NOT strip list/paragraph
 * margins (see the kit's own list components, which each reset themselves). If you run a utility
 * framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
 * bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
.fdy-list-reset { list-style: none; margin: 0; padding: 0; }

/* ===========================================================================
 * Reduce Motion: SETIAP transisi dinetralkan, bukan daftar yang dipelihara tangan.
 *
 * WebKit menjepit durasi transisi ke 0.000001s ketika setelan itu menyala, dan pada durasi itu
 * transisinya tidak pernah berjalan: propertinya tersangkut di nilai AWAL, selamanya. Akibatnya
 * bukan "kurang halus" melainkan keadaan yang salah di layar. Terukur di iPhone 7 tiga kali:
 * scrim drawer yang tinggal gelap sambil tetap menangkap tap, dan centang checkbox yang tak
 * pernah digambar padahal inputnya benar-benar tercentang.
 *
 * Sebelum aturan ini, kit menetralkan sembilan selektor dari lima puluh enam yang mentransisikan
 * sesuatu. Empat puluh delapan sisanya ranjau yang menunggu, dan tiga sudah meledak di tangan satu
 * pemilik app. Menambal yang keempat adalah cara kita sampai di sini.
 *
 * `transition: none`, BUKAN `transition-duration: 0.01ms` yang biasa beredar sebagai reset
 * aksesibilitas: durasi mungil itu persis bentuk yang menyangkut di WebKit. Satu-satunya yang
 * pernah menunggu `transitionend` di kit ini adalah toast, dan ia sudah punya cadangan
 * `setTimeout(finish, 400)` untuk kasus event itu tak pernah datang.
 *
 * ANIMASI sengaja tidak disentuh: kit memasangnya lewat `prefers-reduced-motion: no-preference`,
 * jadi ia opt-in dan sudah mati di sini, sementara `--spinner` dan `--progress` punya perlakuan
 * bernuansa sendiri yang memang harus bertahan.
 * ======================================================================== */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition:none !important;}
}

/* Freeday. Accordion (native <details>/<summary>, zero-JS) */
/* position:relative, containing block for out-of-flow panel content (see base.css). NOT redundant
   with the panel's reveal animation: that animation gives the panel a transform (which happens to
   make it a containing block too), but it lives behind prefers-reduced-motion:no-preference, so
   without this line the escape bug appears ONLY for readers who asked for reduced motion. */
.fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
.fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
.fdy-accordion__item > summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);font-weight:var(--weight-medium);color:var(--color-text);}
.fdy-accordion__item > summary::-webkit-details-marker{display:none;}
.fdy-accordion__item > summary::after{content:"";margin-left:auto;margin-top:-.15rem;flex:none;width:.5rem;height:.5rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(45deg);transition:transform var(--dur-fast) var(--ease-standard);}
.fdy-accordion__item[open] > summary::after{transform:rotate(225deg);margin-top:.1rem;}
.fdy-accordion__item > summary:hover{background:var(--color-surface-2);}
.fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.45);}
.fdy-accordion__panel{padding:0 var(--space-4) var(--space-4);color:var(--color-text-muted);font-size:var(--text-sm);line-height:var(--leading-normal);}
/* Content reveal on open (fade + rise). Native <details> can't animate its own height cross-
 * browser without JS, so we animate the revealed panel instead, cheap, safe, reduced-motion gated. */
@media (prefers-reduced-motion:no-preference){.fdy-accordion__item[open] > .fdy-accordion__panel{animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}

/* Freeday. Alert (inline) */
.fdy-alert{display:flex;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);border:var(--bw) solid var(--color-border);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface-2);}
.fdy-alert__icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-top:.05rem;}
.fdy-alert__icon svg{width:100%;height:100%;}
.fdy-alert__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-1);}
.fdy-alert__title{font-size:inherit;font-weight:var(--weight-semibold);color:var(--color-text);margin:0;}
.fdy-alert__text{margin:0;color:var(--color-text);line-height:var(--leading-normal);}
.fdy-alert__close{flex:none;align-self:flex-start;border:0;background:transparent;color:var(--color-text-muted);cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
.fdy-alert__close:hover{color:var(--color-text);}
.fdy-alert__close:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-alert--info{background:var(--color-info-soft);border-color:rgba(var(--color-info-rgb),.26);}
.fdy-alert--info .fdy-alert__title,.fdy-alert--info .fdy-alert__icon{color:var(--color-info-strong);}
.fdy-alert--success{background:var(--color-success-soft);border-color:rgba(var(--color-success-rgb),.26);}
.fdy-alert--success .fdy-alert__title,.fdy-alert--success .fdy-alert__icon{color:var(--color-success-strong);}
.fdy-alert--warning{background:var(--color-warning-soft);border-color:rgba(var(--color-warning-rgb),.26);}
.fdy-alert--warning .fdy-alert__title,.fdy-alert--warning .fdy-alert__icon{color:var(--color-warning-strong);}
.fdy-alert--danger{background:var(--color-danger-soft);border-color:rgba(var(--color-danger-rgb),.26);}
.fdy-alert--danger .fdy-alert__title,.fdy-alert--danger .fdy-alert__icon{color:var(--color-danger-strong);}

/* Freeday. App Shell.
 * Natural document scroll: the shell grows with content and the PAGE scrolls,
 * while the sidebar and topbar are position:sticky so they stay in view. No
 * fixed-height / internal-scroll trickery (that leaves a white gap if the host
 * page can still scroll). Structure: .fdy-app > (.fdy-app__sidebar, .fdy-app__content
 * > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
 * topbar a tall containing block so it can travel, a bare grid cell can't. */
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;height:100dvh;overflow:hidden;flex:none;width:var(--fdy-app-sidebar-w,15.5rem);box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
/* `min-height:0` is the height-axis twin of `min-width:0`, and its absence was invisible for as long
 * as the shell scrolled the PAGE (#055 §2). A consumer that pins the shell to the viewport and
 * scrolls .fdy-app__main instead hits it immediately: a column flex item's automatic minimum size is
 * its content, so the scrolling child pushes this box — and the shell — open instead of scrolling.
 * `flex:none` on the topbar is the same story one line down: it is a flex item in this column, and
 * a topbar whose content wraps past `min-height` is a topbar that can be squeezed. Both consoles
 * wrote these two rules independently, in app.css, before the fifth screen. */
.fdy-app__content{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;}
/* Brand is a fixed header, same height as the topbar so they align, and only
 * the nav below it scrolls. Bare text (no children) renders as one bold
 * display line via the rules on .fdy-app__brand itself. For a mark + two-line
 * title/subtitle, add structured children mirroring .fdy-appbar__brand:
 * <div class="fdy-app__brand"><svg class="fdy-app__brand-mark">…</svg>
 *   <div class="fdy-app__brand-text">
 *     <span class="fdy-app__brand-title">…</span>
 *     <span class="fdy-app__brand-subtitle">…</span>
 *   </div></div> */
.fdy-app__brand{flex:none;display:flex;align-items:center;gap:var(--space-2);box-sizing:border-box;min-height:var(--space-16);font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);text-decoration:none;padding:0 var(--space-4);border-bottom:var(--bw) solid var(--color-border);}
.fdy-app__brand-mark{flex:none;width:1.5rem;height:1.5rem;}
.fdy-app__brand-mark svg{display:block;width:100%;height:100%;}
.fdy-app__brand-text{display:flex;flex-direction:column;min-width:0;}
.fdy-app__brand-title{display:block;font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-app__brand-subtitle{display:block;font-size:var(--text-xs);font-weight:var(--weight-regular);color:var(--color-text-muted);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-nav{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;gap:2px;padding:var(--space-3) var(--space-3) var(--space-6);}
/* Horizontal variant, the SAME nav links laid out as a row, for a top-nav application (one that
 * puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
 * a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
 * is the point, a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
 * ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
 * (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
 * row scrolls; see base.css on why a scroller must be a containing block. */
.fdy-nav--horizontal{position:relative;flex:0 1 auto;flex-direction:row;align-items:center;gap:var(--space-1);padding:0;min-height:0;overflow-x:auto;}
.fdy-nav--horizontal .fdy-nav__item{white-space:nowrap;}
.fdy-nav__item{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);text-decoration:none;cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
.fdy-nav__item:hover{background:var(--color-surface-2);color:var(--color-text);}
/* Active indicator stays in the primary ramp so it never clashes with a re-themed accent. */
.fdy-nav__item[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
.fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
/* Sidebar-menu extras: per-node icon (leading), count badge (trailing), and a
 * text label that truncates. Any of these are optional, a bare text item works. */
.fdy-nav__icon{flex:none;display:inline-flex;width:1.15rem;height:1.15rem;}
.fdy-nav__icon svg{display:block;width:100%;height:100%;}
.fdy-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-nav__badge{flex:none;margin-left:auto;min-width:1.25rem;height:1.25rem;padding:0 6px;display:inline-flex;align-items:center;justify-content:center;font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;border-radius:var(--radius-full);background:var(--color-surface-3);color:var(--color-text-muted);}
.fdy-nav__item[aria-current="page"] .fdy-nav__badge{background:var(--color-primary);color:var(--color-on-primary);}
/* Collapsible section: <details class="fdy-nav__group"><summary class="fdy-nav__grouplabel"> */
.fdy-nav__group{margin:0;}
.fdy-nav__group + .fdy-nav__group,.fdy-nav__item + .fdy-nav__group{border-top:var(--bw) solid var(--color-border-muted);margin-top:var(--space-1);padding-top:var(--space-1);}
/* Prose, and a control name: this is the <summary> of a nav group, at --text-xs uppercase. It is
   the second class found spending the decorative ink on type (after .fdy-eyebrow), and it was
   already brightening to --color-text-muted on hover, which is the rule admitting its resting
   state was too faint to read. */
.fdy-nav__grouplabel{list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:var(--space-3) var(--space-3) var(--space-1);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:var(--tracking-wide);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
.fdy-nav__grouplabel::-webkit-details-marker{display:none;}
.fdy-nav__grouplabel:hover{color:var(--color-text-muted);}
.fdy-nav__grouplabel:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
.fdy-nav__grouplabel::after{content:"";flex:none;width:.45rem;height:.45rem;margin-right:var(--space-1);border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform var(--dur-fast) var(--ease-standard);opacity:.7;}
.fdy-nav__group[open] > .fdy-nav__grouplabel::after{transform:rotate(45deg);}
/* --flat: calmer, non-collapsible grouped nav, hide the caret, drop the
 * inter-group divider and the collapse cursor. Works on native <details> groups
 * (caret just hidden) and on static <div>/<p> groups (never collapsible). */
.fdy-nav--flat .fdy-nav__grouplabel::after{display:none;}
.fdy-nav--flat .fdy-nav__group + .fdy-nav__group,.fdy-nav--flat .fdy-nav__item + .fdy-nav__group{border-top:0;}
.fdy-nav--flat .fdy-nav__grouplabel{cursor:default;}
/* Collapsible item (nested tree): <details class="fdy-nav__tree"><summary class="fdy-nav__item"> */
.fdy-nav__tree{margin:0;}
.fdy-nav__tree > summary{list-style:none;}
.fdy-nav__tree > summary::-webkit-details-marker{display:none;}
.fdy-nav__tree > summary .fdy-nav__caret{flex:none;margin-left:auto;width:.4rem;height:.4rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform var(--dur-fast) var(--ease-standard);opacity:.6;}
.fdy-nav__tree[open] > summary .fdy-nav__caret{transform:rotate(45deg);}
.fdy-nav__sub{display:flex;flex-direction:column;gap:2px;padding-left:calc(var(--space-3) + 1.15rem);margin-top:2px;}
/* Collapse toggle (hamburger), sits in the topbar; collapses the sidebar. */
.fdy-app__navtoggle{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;flex:none;padding:0;border:0;border-radius:var(--radius-md);background:transparent;color:var(--color-text-muted);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
.fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
.fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
.fdy-app__navtoggle svg{display:block;width:1.25rem;height:1.25rem;}
/* --navtoggle-end moves the toggle to the far side of the topbar. The title already
 * carries `margin-right:auto`, so ordering the toggle last is enough; nothing else needs
 * to know. On a large phone held in one hand the top-LEFT corner is the hardest place on
 * the screen for a right thumb to reach, and the toggle is the control most often pressed
 * there — so this is ergonomics, not taste. Left as an opt-in because a desktop shell that
 * also puts an avatar on the right has the opposite problem. */
.fdy-app--navtoggle-end .fdy-app__navtoggle{order:1;}
/* Desktop collapse: animate width -> 0 (content clips via overflow:hidden; the content area
 * flexes in to fill). Not display:none, so the collapse/expand slides instead of snapping. */
@media (min-width:721px){.fdy-app--nav-collapsed .fdy-app__sidebar{width:0;border-right-width:0;}}
/* Desktop OVERLAY: the nav floats over the content instead of displacing it, the arrangement a
 * "menu mode" preference offers on a wide screen. The declarations below are the mobile off-canvas
 * ones repeated, and that repetition is not an oversight: the two conditions are
 * `(max-width:720px)` and `(min-width:721px) and .fdy-app--nav-overlay`, which no single selector
 * can express, and the mobile path has to keep working with no JS and no class at all. What the
 * modifier changes is the LAYOUT; every behaviour (backdrop, Escape, close-on-follow, focus return,
 * inert, the Tab trap) is the same code path the drawer already used. */
@media (min-width:721px){
  .fdy-app--nav-overlay .fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);box-shadow:var(--shadow-4);transition:transform var(--dur-base) var(--ease-standard);}
  .fdy-app--nav-overlay.fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
  .fdy-app--nav-overlay .fdy-app__backdrop{display:block;position:fixed;inset:0;z-index:55;background:var(--color-scrim);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--dur-base) var(--ease-standard),visibility 0s linear var(--dur-base);}
  .fdy-app--nav-overlay.fdy-app--nav-open .fdy-app__backdrop{opacity:1;visibility:visible;pointer-events:auto;transition:opacity var(--dur-base) var(--ease-standard),visibility 0s linear 0s;}
  /* In overlay mode the sidebar is out of flow, so the collapse-to-zero-width rule above must not
     also apply — it would fight the transform for the same panel. */
  .fdy-app--nav-overlay.fdy-app--nav-collapsed .fdy-app__sidebar{width:16rem;border-right-width:var(--bw);}
}
.fdy-app__topbar{position:sticky;top:0;z-index:20;flex:none;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
.fdy-app__title{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-app__main{flex:1;padding:var(--space-8);}
.fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
.fdy-skip:focus{transform:translateY(0);}
/* --static: for a bounded/embedded shell (a demo box, not the whole page),
 * drop sticky so nothing escapes the box; sidebar stretches to fill it. */
.fdy-app--static .fdy-app__sidebar{position:static;height:auto;align-self:stretch;}
.fdy-app--static .fdy-app__topbar{position:static;}
/* `pointer-events` on the backdrop is load-bearing, not a belt-and-braces extra. `visibility` is a
 * TRANSITIONED property here, so a backdrop on its way out stays `visible` for the whole duration
 * and keeps swallowing clicks aimed at the topbar underneath it — and switching INTO overlay mode is
 * worse, because the element goes display:none -> display:block with its visibility animating from
 * the initial `visible`, so a mode switch alone hands the page ~200ms of dead clicks. Measured: with
 * only `visibility:hidden`, elementFromPoint over the nav toggle returned the backdrop.
 * Mobile: the sidebar becomes an off-canvas drawer opened by the topbar toggle. */
/* `cursor:pointer` here is not decoration, and not for the mouse. Safari on iOS only
 * synthesises a `click` from a tap on elements it treats as interactive, and the cursor
 * being a pointer is one of the signals it uses. The backdrop is a plain <div> with a
 * click handler — no button, no role, no tabindex — so without this rule a tap on it
 * produces no click at all: the drawer stays open, the scrim stays dark, and the only way
 * out is reloading the page. Reported from a real iPhone X; not reproducible in Chromium
 * with either mouse click or `touchscreen.tap()`, which is why no test in this kit or in
 * the consuming app had ever caught it. */
/* `visibility` DITUNDA, tidak ditransisikan. Mentransisikannya sebagai properti beranimasi membuat
 * Safari kadang tidak pernah menjalankan fade `opacity` yang menyertainya saat visible -> hidden:
 * kelas sudah lepas, `pointer-events` sudah `none`, sidebar sudah kembali ke luar layar, dan scrim
 * itu TINGGAL TERCAT di atas halaman sampai halaman dimuat ulang. Diukur di iPhone 7 lewat halaman
 * diagnostik yang mencetak gaya terhitungnya: `opacity=1 visibility=visible pointer-events=none`
 * dengan `--nav-open` sudah false. Chromium menjalankannya dengan benar, jadi tak ada uji peramban
 * di kit ini yang bisa melihatnya; yang menjaganya penjaga bentuk CSS di test/css.test.mjs.
 * Dengan jeda, `visibility` berganti diskret: seketika saat membuka, sesudah fade saat menutup. */
.fdy-app__backdrop{display:none;cursor:pointer;}
@media (max-width:720px){
  .fdy-app__sidebar{position:fixed;top:0;left:0;bottom:0;height:100dvh;width:16rem;z-index:60;transform:translateX(-100%);transition:transform var(--dur-base) var(--ease-standard);box-shadow:var(--shadow-4);}
  .fdy-app--nav-open .fdy-app__sidebar{transform:translateX(0);}
  /* --color-scrim, not a literal: this was the last raw colour left in a component stylesheet, and
     it also means the drawer behind the nav dims exactly like every other overlay in the kit. */
  .fdy-app__backdrop{display:block;position:fixed;inset:0;z-index:55;background:var(--color-scrim);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--dur-base) var(--ease-standard),visibility 0s linear var(--dur-base);}
  .fdy-app--nav-open .fdy-app__backdrop{opacity:1;visibility:visible;pointer-events:auto;transition:opacity var(--dur-base) var(--ease-standard),visibility 0s linear 0s;}
  .fdy-app__main{padding:var(--space-5);}
  .fdy-app__topbar{padding:0 var(--space-4);}
}
/* Backdrop ikut dinetralkan, dan ini BUKAN soal selera gerak. WebKit memaksa durasi transisi ke
 * 0.000001s saat Reduce Motion menyala, dan pada durasi itu transisinya tidak pernah berjalan:
 * propertinya tersangkut di nilai AWAL selamanya. Diukur di iPhone 7 dengan setelan itu menyala,
 * tiga detik sesudah drawer terbuka, dengan kelas `--nav-open` SUDAH terpasang di setiap sampel:
 * `opacity` diam di 0 dan `visibility` diam di `hidden`, sementara `pointer-events` sudah `auto`.
 * Scrim yang tak terlihat tetapi menangkap tap, jadi ketukan pertama pengguna hilang dan ketukan
 * kedua yang bekerja. Setelan yang sama dimatikan, deret yang sama berjalan 0.87 lalu 1.00.
 * Sidebar sudah dinetralkan sejak lama; backdrop terlewat, dan ia yang menggerbangi interaksi. */
@media (prefers-reduced-motion:reduce){
  .fdy-app__sidebar{transition:none;}
  /* KEEMPAT selektor yang mendeklarasikan transisi backdrop, bukan hanya yang paling lemah.
     `.fdy-app__backdrop` sendirian berspesifisitas (0,1,0) dan kalah dari keadaan terbukanya,
     `.fdy-app--nav-open .fdy-app__backdrop` (0,2,0), sehingga penetralan ini diam-diam tidak
     berlaku justru pada saat drawer terbuka, yaitu satu-satunya saat ia penting. Diukur dengan
     reduced motion diemulasi: tertutup melaporkan `none`, terbuka melaporkan
     `opacity 1e-06s, visibility 1e-06s`. */
  .fdy-app__backdrop,
  .fdy-app--nav-open .fdy-app__backdrop,
  .fdy-app--nav-overlay .fdy-app__backdrop,
  .fdy-app--nav-overlay.fdy-app--nav-open .fdy-app__backdrop{transition:none;}
}

/* Standalone: the frame for a screen outside the app shell, such as sign in, sign up, a password
 * reset, maintenance, or a 404 for someone not signed in. One panel, centred, on the shell's ground.
 * MIN-height, never height: a panel taller than the screen (a phone with its keyboard up) makes the
 * frame grow and the page scroll. A fixed height would centre it into negative space and put its
 * first field above the top edge, out of reach of any scroll. */
.fdy-standalone{display:flex;flex-direction:column;box-sizing:border-box;min-height:100vh;min-height:100dvh;padding:var(--space-8) var(--space-4);background:var(--color-surface-2);}
.fdy-standalone__panel{box-sizing:border-box;width:100%;max-width:26rem;margin:auto;}

/* Freeday. App bar (standalone top bar: brand · actions · spacer).
 * Distinct from .fdy-app__topbar (which is bound to the shell grid). Use anywhere;
 * sticky / elevated / dense / coloured via modifiers. Pure CSS. */
.fdy-appbar{display:flex;align-items:center;gap:var(--space-3);height:var(--space-16);padding:0 var(--space-4);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);border-bottom:var(--bw) solid var(--color-border);}
.fdy-appbar--sticky{position:sticky;top:0;z-index:90;}
.fdy-appbar--elevated{border-bottom:0;box-shadow:var(--shadow-2),var(--surface-inset);}
.fdy-appbar--dense{height:var(--space-12);}
.fdy-appbar--primary{background:var(--color-primary);color:var(--color-on-primary);border-bottom-color:transparent;}
.fdy-appbar__brand{display:inline-flex;align-items:center;gap:var(--space-2);font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:inherit;white-space:nowrap;}
.fdy-appbar__brand svg{display:block;width:1.5rem;height:1.5rem;}
.fdy-appbar__spacer{flex:1;}
.fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
/* Primary navigation inside the bar: `<nav class="fdy-nav fdy-nav--horizontal">` with
   `.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own, a nav link
   is the same component whether the nav is a sidebar column or a top row. */
/* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
.fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
.fdy-appbar--primary .fdy-btn--ghost:hover{background:rgba(var(--color-on-primary-rgb),.16);color:var(--color-on-primary);transform:none;}
/* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
   the contrast the bar's own token pair guarantees). The current page is carried by the background
   wash plus the semibold weight the base rule already applies, and by `aria-current` itself. */
.fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
.fdy-appbar--primary .fdy-nav__item:hover{background:rgba(var(--color-on-primary-rgb),.16);color:var(--color-on-primary);}
.fdy-appbar--primary .fdy-nav__item[aria-current="page"]{background:rgba(var(--color-on-primary-rgb),.24);color:var(--color-on-primary);}

/* Freeday. Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
 * Enhanced by freeday-autocomplete.js. */
.fdy-autocomplete{position:relative;display:block;max-width:22rem;}
.fdy-autocomplete__listbox{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:120;min-width:100%;max-height:16rem;overflow-y:auto;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
.fdy-autocomplete__listbox[hidden]{display:none;}
.fdy-autocomplete__option{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;white-space:nowrap;}
.fdy-autocomplete__option[hidden]{display:none;}
.fdy-autocomplete__option:hover,.fdy-autocomplete__option.is-highlighted{background:var(--color-surface-3);}
.fdy-autocomplete__option[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
.fdy-autocomplete__empty{padding:var(--space-2) var(--space-3);font-size:var(--text-sm);color:var(--color-text-muted);}
.fdy-autocomplete__empty[hidden]{display:none;}
@media (prefers-reduced-motion:no-preference){.fdy-autocomplete__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}

/* Freeday. Avatar */
.fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;-webkit-user-select:none;user-select:none;}
.fdy-avatar img{width:100%;height:100%;object-fit:cover;}
/* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
   `--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
   crosses the circle. 1.5rem leaves the same ~4px breathing room the kit's controls give their
   icons. Uses `--text-xs` (the smallest type token the kit has, there is no `2xs`). */
.fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
.fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
.fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
/* Square: a mark that is not a person (a company logo, a card's icon chip, #070). */
.fdy-avatar--square{border-radius:var(--radius-md);}
.fdy-avatar-group{display:inline-flex;}
.fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
.fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
/* Decorative identity tones, a soft tint of a categorical chart colour with a theme-aware,
 * text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
 * test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
 * hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
/* One flat token per half, computed in advance by tokens/build.mjs: --tone-N-bg is 18% of the
 * tone over --color-surface, --tone-N-ink is 50% of it toward --color-text, both per theme.
 * They USED to be mixed here, with the hue injected per element through a private custom
 * property — which put var() inside color-mix(), the one shape that cannot be rescued: valid at
 * parse time, invalid at computed-value time, and there `background` and `color` drop to their
 * INITIAL values, not to any fallback. @supports answered it for exactly as long as somebody
 * remembered to write the counterpart; a precomputed token removes the question. */
.fdy-avatar--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);}
.fdy-avatar--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);}
.fdy-avatar--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);}
.fdy-avatar--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);}
.fdy-avatar--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);}
.fdy-avatar--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);}
.fdy-avatar--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);}
.fdy-avatar--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);}

/* Freeday. Badge */
.fdy-badge{display:inline-flex;align-items:center;gap:var(--space-1);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;padding:.35em .7em;border-radius:var(--radius-full);color:var(--color-primary-strong);background:var(--color-primary-soft);border:var(--bw) solid rgba(var(--color-primary-rgb),.22);box-shadow:0 1px 1.5px rgba(16,14,30,.05);}
.fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:rgba(var(--color-success-rgb),.26);}
.fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:rgba(var(--color-warning-rgb),.26);}
.fdy-badge--danger{color:var(--color-danger-strong);background:var(--color-danger-soft);border-color:rgba(var(--color-danger-rgb),.26);}
.fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:rgba(var(--color-info-rgb),.26);}
.fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}

/* Categorical tone badges, a status vocabulary larger than the semantic palette, from the
 * general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
 * .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
 *
 * Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
 * --warning. These are for the rest, the states a workflow distinguishes but a palette of five
 * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
 * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
 * which is the report this shipped for. */
/* Precomputed tone tokens, the same --tone-N-bg / --tone-N-ink the avatar and the chip read, so
 * one contrast assertion still covers all three. --tone-N-border-soft is the badge's own edge:
 * 26% of the tone over transparent, which is a translucent colour and therefore stays rgba()
 * rather than flattening to a hex. See avatar.css for why none of this is mixed in the
 * component any more, and tokens/build.mjs for where the mixing moved to. */
.fdy-badge--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);border-color:var(--tone-1-border-soft);}
.fdy-badge--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);border-color:var(--tone-2-border-soft);}
.fdy-badge--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);border-color:var(--tone-3-border-soft);}
.fdy-badge--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);border-color:var(--tone-4-border-soft);}
.fdy-badge--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);border-color:var(--tone-5-border-soft);}
.fdy-badge--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);border-color:var(--tone-6-border-soft);}
.fdy-badge--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);border-color:var(--tone-7-border-soft);}
.fdy-badge--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);border-color:var(--tone-8-border-soft);}

/* Overlay badge, anchors a small count/dot/icon badge to a corner of any element
 * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
 * sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */
.fdy-badge-wrap{position:relative;display:inline-flex;vertical-align:middle;}
.fdy-badge-ov{position:absolute;top:0;right:0;transform:translate(50%,-50%);z-index:1;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 var(--space-1);border-radius:var(--radius-full);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;color:var(--color-inverse-text);background:var(--color-inverse-surface);border:2px solid var(--color-surface);}
.fdy-badge-ov svg{display:block;width:.75rem;height:.75rem;}
.fdy-badge-ov--dot{min-width:0;width:.75rem;height:.75rem;padding:0;}
/* Solid colours safe for count/text (on-* tokens, AA in light & dark) */
.fdy-badge-ov--primary{background:var(--color-primary);color:var(--color-on-primary);}
.fdy-badge-ov--danger{background:var(--color-danger-btn);color:var(--color-on-danger);}
/* Dot-oriented accents (for dots, plain text on these is not guaranteed AA) */
.fdy-badge-ov--accent{background:var(--color-accent);}
.fdy-badge-ov--info{background:var(--color-info);}
.fdy-badge-ov--success{background:var(--color-success);}
.fdy-badge-ov--warning{background:var(--color-warning);}
/* Anchor position */
.fdy-badge-ov--top-left{right:auto;left:0;transform:translate(-50%,-50%);}
.fdy-badge-ov--bottom-right{top:auto;bottom:0;transform:translate(50%,50%);}
.fdy-badge-ov--bottom-left{top:auto;bottom:0;right:auto;left:0;transform:translate(-50%,50%);}

/* Freeday. Bottom navigation
 *
 * The bar a phone application puts its three-to-five most-used destinations on. The kit
 * had `.fdy-nav--horizontal` for a top-nav application and the off-canvas drawer for a
 * sidebar one, and nothing for the arrangement almost every phone application actually
 * uses — which was only noticed when an app shipped to real phones and its owner reached
 * for a thumb-reachable bar that did not exist.
 *
 * Deliberately NOT part of `.fdy-app`'s markup: it is `position:fixed`, so it works from
 * anywhere inside the shell's content slot, and an application that does not want one
 * pays nothing. `.fdy-app--has-bottomnav` on the shell is what reserves room for it. */
.fdy-bottomnav{display:none;}
@media (max-width:720px){
  .fdy-bottomnav{
    display:flex;position:fixed;left:0;right:0;bottom:0;z-index:50;
    align-items:stretch;justify-content:space-around;gap:var(--space-1);
    padding:var(--space-2) var(--space-2);
    /* The home indicator on a notched phone sits over the bottom of the viewport, and a
     * bar that ignores it puts its labels underneath a physical UI element. Added to the
     * padding rather than substituted for it, so a phone without one is unaffected. */
    padding-bottom:calc(var(--space-2) + env(safe-area-inset-bottom,0px));
    background:var(--color-surface-raised);
    backdrop-filter:var(--surface-filter);
    -webkit-backdrop-filter:var(--surface-filter);
    border-top:var(--bw) solid var(--color-border);
  }
  /* Room for the bar, or the last row of every list on every screen sits under it -- and
     a row you cannot scroll to is a row nobody knows is there. */
  .fdy-app--has-bottomnav .fdy-app__main{
    padding-bottom:calc(var(--space-20) + env(safe-area-inset-bottom,0px));
  }
}
.fdy-bottomnav__item{
  display:flex;flex:1 1 0;min-width:0;flex-direction:column;align-items:center;
  justify-content:center;gap:2px;
  /* 44px is the smallest target a thumb hits reliably; anything under it turns a tap into
   * a retry, and this bar is pressed more than any other control in the application. */
  min-height:2.75rem;padding:var(--space-1);
  border-radius:var(--radius-md);
  font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-medium);
  color:var(--color-text-muted);text-decoration:none;text-align:center;
  transition:background var(--dur-fast) var(--ease-standard),
             color var(--dur-fast) var(--ease-standard);
}
.fdy-bottomnav__item:hover{color:var(--color-text);}
.fdy-bottomnav__item:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
/* The current destination is marked by `aria-current="page"`, the same attribute
 * `.fdy-nav__item` uses -- one contract for "you are here", not two. */
.fdy-bottomnav__item[aria-current="page"]{
  color:var(--color-primary-strong);background:var(--color-primary-soft);
}
.fdy-bottomnav__icon{display:block;width:1.25rem;height:1.25rem;flex:none;}
.fdy-bottomnav__icon svg{display:block;width:100%;height:100%;}
/* Labels are never dropped in favour of icons alone: an icon-only bar asks every user to
 * have already learned what each glyph means, and the people using this kit's applications
 * on a phone are often using them for the first time. Long labels ellipsis rather than
 * wrap, so the bar keeps one height. */
.fdy-bottomnav__label{
  display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  line-height:1.2;
}
@media (prefers-reduced-motion:reduce){.fdy-bottomnav__item{transition:none;}}

/* Freeday. Breadcrumb */
.fdy-breadcrumb{font-size:var(--text-sm);}
.fdy-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);list-style:none;margin:0;padding:0;}
.fdy-breadcrumb__item{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--color-text-muted);}
.fdy-breadcrumb__link{color:var(--color-text-muted);text-decoration:none;border-radius:var(--radius-sm);}
.fdy-breadcrumb__link:hover{color:var(--color-primary-strong);text-decoration:underline;}
.fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
.fdy-breadcrumb__sep{color:var(--color-text-subtle);-webkit-user-select:none;user-select:none;}

/* Freeday. Responsive display utilities (pairs with freeday-breakpoint.js).
 * Breakpoint scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920.
 * Two further widths exist and are NOT ramp steps: `nav` (app-shell.css, off-canvas drawer vs
 * static sidebar) and `filterbar` (filterbar.css, the width its fields stack at). Both are named
 * in tokens/breakpoints.mjs; a rule that has to fire with one of them aligns to it, not to the
 * nearest ramp step, which is a band of broken widths in both directions.
 * NOTE: media-query breakpoints must be literal. CSS can't read custom properties
 * inside @media, so these px are structural, not stray magic numbers. An app that runs PostCSS
 * need not retype them: `@cahyo-dimas/freeday/media` declares every width above as a
 * @custom-media, so it can write `@media (--fdy-below-sm)` and never drift (#058 §2). */
.fdy-hidden{display:none !important;}

/* hide-below-X, hidden when the viewport is narrower than X (content shows at X and up) */
@media (max-width:599.98px){.fdy-hide-below-sm{display:none !important;}}
@media (max-width:959.98px){.fdy-hide-below-md{display:none !important;}}
@media (max-width:1279.98px){.fdy-hide-below-lg{display:none !important;}}

/* hide-above-X, hidden when the viewport is X and wider (content shows below X) */
@media (min-width:600px){.fdy-hide-above-sm{display:none !important;}}
@media (min-width:960px){.fdy-hide-above-md{display:none !important;}}
@media (min-width:1280px){.fdy-hide-above-lg{display:none !important;}}

/* Freeday. Busy overlay */
/* The blocking "the app is working" state, for an operation the reader must not interrupt or
 * double-submit. There is deliberately NO markup contract to hand-write: it is created by
 * `Freeday.busy()` and removed by `Freeday.idle()`, because two of these on screen at once — with
 * two different captions, from two components that each thought they owned it — is the exact bug
 * this component exists to prevent. One owner, enforced by there being only one way in.
 *
 * It is not a dialog. Nothing here asks a question and nothing can be dismissed; interaction is
 * removed with `inert` on everything else, not with a focus trap. */

/* `display` is gated on `.is-open` rather than on `:popover-open`, and that is load-bearing. The UA
 * sheet hides a popover with `[popover]:not(:popover-open){display:none}`, but AUTHOR rules beat UA
 * rules whatever the specificity, so a bare `.fdy-busy{display:flex}` would paint the overlay
 * permanently the moment the element existed. Gating on a class instead also means the overlay
 * still works where the Popover API does not: it falls back to a plain fixed layer at this
 * z-index, which cannot clear an open <dialog> but does everything else. */
.fdy-busy{display:none;position:fixed;inset:0;width:100%;height:100%;max-width:none;max-height:none;margin:0;padding:0;border:0;overflow:hidden;background:var(--color-scrim);z-index:190;}
.fdy-busy.is-open{display:flex;align-items:center;justify-content:center;}
/* The popover's own backdrop would double the dim; this element paints its own. */
.fdy-busy::backdrop{background:transparent;}

/* The caption sits on a solid surface, not on the scrim. A scrim is semi-transparent over unknown
 * content, so text on it has no computable contrast ratio and no gate could ever cover it. On
 * --color-surface it is the same pair the contrast suite already checks.
 *
 * THIS IS ALSO WHY THIS PANEL DOES NOT JOIN THE GLASS AXIS, and the omission is a decision rather
 * than an oversight. Every other raised panel reads --color-surface-raised and frosts under
 * [data-style="glass"]; this one stays opaque. The glass gate asserts its ink against the panel
 * composited over the PAGE GROUND (--color-surface-2), which is what sits behind a card or a modal.
 * Behind this one is the scrim, over arbitrary content — the very case that has no computable
 * ratio. A frosted busy panel would be the one surface in the kit whose caption nothing could
 * verify, while it is up precisely because the app is doing something the reader must not
 * interrupt. Left opaque on purpose; do not "fix" it for consistency. */
.fdy-busy__panel{display:flex;flex-direction:column;align-items:center;gap:var(--space-4);padding:var(--space-6) var(--space-8);border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:var(--shadow-lift);max-width:min(20rem,calc(100vw - var(--space-8)));}
.fdy-busy__mark{display:flex;align-items:center;justify-content:center;min-height:2.5rem;}
.fdy-busy__caption{margin:0;font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);text-align:center;}

/* Nothing travels under reduced motion. The spinner keeps turning (slowed by spinner.css): a
 * loading indicator that stops moving reads as "finished", which is the opposite of the message. */
@media (prefers-reduced-motion:reduce){
  .fdy-busy{transition:none;}
}

/* Freeday. Button */
/* The fill is a FLAT colour with a black veil laid over it, not a gradient between two shades.
 * It used to be `linear-gradient(primary, color-mix(in srgb, #000 10%, primary))`, and that is the
 * same colour at every stop — veiling a solid base with black 0 -> .1 darkens it by exactly 10% —
 * but the mix form carries var(), so on Safari <= 16.1 the whole declaration failed at
 * computed-value time and the button lost its fill rather than falling back. Measured identical to
 * the mix it replaces (within the 1/255 the gradient's own 8-bit rounding costs); see
 * test/color-mix.test.mjs for the rule and tokens/build.mjs for why none of these may use
 * color-mix(). Pressed states veil from the TOP, which is the same trick read upside down. */
.fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
  height:var(--control-h);padding:0 var(--space-4);
  font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);
  line-height:1;white-space:nowrap;cursor:pointer;text-decoration:none;color:var(--color-on-primary);
  background:var(--color-primary) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.1));
  border:var(--bw) solid transparent;border-radius:var(--radius-md);
  box-shadow:0 2px 9px -1px rgba(var(--color-primary-rgb),.5),inset 0 1px 0 rgba(255,255,255,.26);
  transition:background var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard),transform var(--dur-fast) var(--ease-standard);}
.fdy-btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px rgba(var(--color-primary-rgb),.6),inset 0 1px 0 rgba(255,255,255,.26);}
.fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px rgba(var(--color-primary-rgb),.46);}
.fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
/* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
   no class and no new markup contract, and it is what makes .fdy-btn-group a complete segmented
   control: the group joins the borders, this says which segment you are on. Each fill variant
   defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
   background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
   and sink the shadow inward, so a held button reads as depressed, not merely primary-coloured. */
.fdy-btn[aria-pressed="true"]{background:var(--color-primary) linear-gradient(180deg,rgba(0,0,0,.12),rgba(0,0,0,0));box-shadow:inset 0 2px 6px rgba(0,0,0,.22);}
.fdy-btn[aria-pressed="true"]:hover{transform:none;box-shadow:inset 0 2px 6px rgba(0,0,0,.22);}

.fdy-btn--ghost{background:var(--color-surface);color:var(--color-primary-strong);border-color:var(--color-border-strong);box-shadow:var(--shadow-1);}
.fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
.fdy-btn--ghost:active{transform:translateY(1px);}

/* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger`,
   which every reader parses as "quiet destructive", rendered a second SOLID button beside Save.
   That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
   screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
   the pairing rules below bring it to the button. They are more specific than this one and come
   after it, so this rule needs no :not() guard of its own, one mechanism, not two. */
.fdy-btn--danger{background:var(--color-danger-btn) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.12));color:var(--color-on-danger);box-shadow:0 2px 9px -1px rgba(var(--color-danger-btn-rgb),.46),inset 0 1px 0 rgba(255,255,255,.22);}
.fdy-btn--danger:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px rgba(var(--color-danger-btn-rgb),.54),inset 0 1px 0 rgba(255,255,255,.22);}
.fdy-btn--danger:active{transform:translateY(1px);}
/* Quiet destructive: the variant keeps its own ground and only the ink turns. */
.fdy-btn--ghost.fdy-btn--danger{background:transparent;color:var(--color-danger-strong);border-color:rgba(var(--color-danger-rgb),.4);box-shadow:none;}
.fdy-btn--ghost.fdy-btn--danger:hover{background:var(--color-danger-soft);border-color:var(--color-danger);color:var(--color-danger-strong);}
.fdy-btn--text.fdy-btn--danger{background:none;color:var(--color-danger-strong);border-color:transparent;box-shadow:none;}
.fdy-btn--text.fdy-btn--danger:hover{background:var(--color-danger-soft);color:var(--color-danger-strong);}
.fdy-btn--danger[aria-pressed="true"]{background:var(--color-danger-btn) linear-gradient(180deg,rgba(0,0,0,.14),rgba(0,0,0,0));box-shadow:inset 0 2px 6px rgba(0,0,0,.24);}

.fdy-btn--sm{height:calc(var(--control-h) - var(--space-2));padding:0 var(--space-3);font-size:var(--text-xs);border-radius:var(--radius-sm);}
.fdy-btn--lg{height:calc(var(--control-h) + var(--space-2));padding:0 var(--space-6);font-size:var(--text-base);border-radius:var(--radius-lg);}

/* Icon inside a button, scales with the button's font size (works for text+icon & icon-only) */
.fdy-btn__icon{display:inline-flex;flex:none;}
.fdy-btn__icon svg{display:block;width:1.15em;height:1.15em;}

/* Text button, lowest emphasis (no fill/border), colored label, subtle hover tint */
.fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
.fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
.fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
/* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink), the same pairing
   .fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
   the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
.fdy-btn--ghost[aria-pressed="true"],.fdy-btn--text[aria-pressed="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);border-color:var(--color-primary-border);box-shadow:none;}
.fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}

/* Stretched hit area (#007), spread this button's click target over its nearest positioned
 * ancestor, so "one card, one primary action, one escape hatch" needs no click handler on the
 * <article>: the real control stays a real <button>/<a> and keeps its keyboard semantics.
 * `.fdy-card` is already position:relative, so it is the anchor.
 *
 * The transforms MUST go, and this is the whole reason the modifier exists rather than a docs line:
 * a transformed element becomes the containing block for its own absolutely-positioned descendants,
 * so `inset:0` re-anchors from the card to the button's own box the moment the button is hovered or
 * pressed. Measured: mousedown lands on the button, the overlay collapses under the pointer, mouseup
 * lands on some other element, and the browser fires `click` on their common ancestor, the button
 * never gets one. It demos fine on hover for --text/--ghost (their hover is already transform:none)
 * and only a real press reveals it; the default and --danger fills break one step earlier, on hover.
 * Press feedback moves to the card, which is the object actually being pressed (see card.css). */
.fdy-btn--stretch::after{content:'';position:absolute;inset:0;}
.fdy-btn--stretch:hover,.fdy-btn--stretch:active{transform:none;}

/* Icon-only, square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
.fdy-btn--icon{width:var(--control-h);padding:0;}
.fdy-btn--icon.fdy-btn--sm{width:calc(var(--control-h) - var(--space-2));}
.fdy-btn--icon.fdy-btn--lg{width:calc(var(--control-h) + var(--space-2));}

/* Button group, segmented, joined borders, rounded only at the ends */
.fdy-btn-group{display:inline-flex;}
.fdy-btn-group .fdy-btn{border-radius:0;}
.fdy-btn-group .fdy-btn:not(:first-child){margin-left:calc(var(--bw) * -1);}
.fdy-btn-group .fdy-btn:hover,.fdy-btn-group .fdy-btn:focus-visible{z-index:1;}
.fdy-btn-group .fdy-btn:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md);}
.fdy-btn-group .fdy-btn:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);}
/* Stacked: the same joined group turned on its side, every button as wide as the group, for picking
   one of a few with a thumb on a phone. In a row, three names on a 375px screen become three
   clipped columns. The column's stretch is what makes each button full width. Labels start-align because a column of names is read down its left edge.
   Each corner rule sets all four corners, so the row's corners never leak in. */
.fdy-btn-group--stack{display:flex;flex-direction:column;}
.fdy-btn-group--stack .fdy-btn{justify-content:flex-start;text-align:start;}
.fdy-btn-group--stack .fdy-btn:not(:first-child){margin-left:0;margin-top:calc(var(--bw) * -1);}
.fdy-btn-group--stack .fdy-btn:first-child{border-radius:var(--radius-md) var(--radius-md) 0 0;}
.fdy-btn-group--stack .fdy-btn:last-child{border-radius:0 0 var(--radius-md) var(--radius-md);}
.fdy-btn-group--stack .fdy-btn:only-child{border-radius:var(--radius-md);}

/* FAB, circular elevated action button (icon), optional extended pill with label */
.fdy-fab{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);width:3.5rem;height:3.5rem;padding:0;border:var(--bw) solid transparent;border-radius:var(--radius-full);cursor:pointer;color:var(--color-on-primary);background:var(--color-primary) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.1));box-shadow:var(--shadow-3);transition:transform var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
.fdy-fab:hover{transform:translateY(-1px);box-shadow:var(--shadow-4);}
.fdy-fab:active{transform:translateY(1px);}
.fdy-fab:focus-visible{border-radius:var(--radius-full);}
.fdy-fab:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
.fdy-fab svg{display:block;width:1.5rem;height:1.5rem;}
.fdy-fab--sm{width:2.5rem;height:2.5rem;}
.fdy-fab--sm svg{width:1.25rem;height:1.25rem;}
.fdy-fab--extended{width:auto;padding:0 var(--space-5);font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);}
.fdy-fab--danger{background:var(--color-danger-btn) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.12));color:var(--color-on-danger);}
.fdy-fab--accent{background:var(--color-accent) linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.1));color:var(--color-on-accent);}

/* Freeday. Card */
/* position:relative, containing block for out-of-flow card content (see base.css); it also gives
   consumers the anchor a corner ribbon/badge needs. */
.fdy-card{position:relative;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift),var(--surface-inset);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
.fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
.fdy-card--interactive{cursor:pointer;}
.fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
/* Pressing a stretched target (see .fdy-btn--stretch) is pressing the CARD, the button's own
   nudge had to go, and the object under the pointer is the card. Ordered before the reduced-motion
   block and the disabled rules so both still win at equal specificity. */
.fdy-card--interactive:has(.fdy-btn--stretch:active){transform:translateY(-1px);}
/* The global reduced-motion reset kills the ANIMATION; drop the -3px shift too so the card
 * doesn't jump. The shadow still communicates the affordance. */
@media (prefers-reduced-motion:reduce){.fdy-card--interactive:hover,.fdy-card--interactive:has(.fdy-btn--stretch:active){transform:none;}}
/* When the whole card IS the control, a real <button> so the row is keyboard-reachable and
 * announced, reset the UA button box. Note: it does NOT reset background/border, because
 * .fdy-card already sets its own surface + border (resetting them here would strip the card). */
.fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
/* Disabled card-as-control, same contract as .fdy-list__row--button: dim + not-allowed, and the
   --interactive lift is withdrawn so a refusing control stops answering the pointer. */
.fdy-card--button:disabled,.fdy-card--button[aria-disabled="true"],.fdy-card--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
.fdy-card--interactive:disabled:hover,.fdy-card--interactive[aria-disabled="true"]:hover{box-shadow:var(--shadow-lift);transform:none;}
/* Escape hatches stay clickable above a stretched target, automatic, not a documented convention,
   because forgetting it fails SILENTLY: the secondary control looks and hovers exactly as before and
   simply never receives the click, which is the very symptom the pattern exists to cure. Matched by
   shape (anything focusable) so a control the kit does not ship yet is covered too. */
:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
/* The position is a DEFAULT, not an override: z-index needs a positioned box (or a flex/grid item),
   but an app that deliberately pins a control inside the card, a corner dismiss, a favourite star,
   must keep its own `position:absolute`. Wrapped in :where() so this whole selector weighs nothing
   and ANY app rule wins it; measured, because at normal specificity the kit silently dragged an
   absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
:where(:is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch)){position:relative;}
.fdy-card__body{padding:var(--space-5);}
/* Header band (#070): an identity mark, the title, and the card's way out pinned right. Beside the
   title the action sits in one place on every card; under the content it moved each time the
   content grew. Outside __body's padding, so its rule spans the card edge to edge. The mark is an
   .fdy-avatar (--sm --square --tone-N), so its tints are the ones the contrast gate already covers. */
.fdy-card__head{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
.fdy-card__head .fdy-card__title{flex:1 1 auto;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-card__action{flex:none;display:inline-flex;align-items:center;gap:var(--space-2);}
.fdy-card__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0 0 var(--space-1);}
.fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
.fdy-card__footer{padding:var(--space-3) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:linear-gradient(180deg,var(--color-surface),var(--color-surface-2));display:flex;gap:var(--space-2);justify-content:flex-end;}

/* Freeday. Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
.fdy-carousel{position:relative;}
/* position:relative means the arrows are positioned against .fdy-carousel (the parent), so this only
   catches out-of-flow content inside the SLIDES, which would otherwise escape the viewport
   entirely and scroll the page by one slide-offset per slide (see base.css). */
.fdy-carousel__viewport{position:relative;display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;border-radius:var(--radius-lg);scrollbar-width:none;-ms-overflow-style:none;}
.fdy-carousel__viewport::-webkit-scrollbar{display:none;}
.fdy-carousel__slide{flex:0 0 100%;min-width:0;scroll-snap-align:center;}
.fdy-carousel__arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:2;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:0;border-radius:var(--radius-full);background:rgba(var(--color-surface-rgb),.86);color:var(--color-text);box-shadow:var(--shadow-2);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard);}
.fdy-carousel__arrow:hover{background:var(--color-surface);}
.fdy-carousel__arrow:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.4);}
.fdy-carousel__arrow:disabled{opacity:.4;cursor:not-allowed;}
.fdy-carousel__arrow--prev{left:var(--space-3);}
.fdy-carousel__arrow--next{right:var(--space-3);}
.fdy-carousel__arrow svg{display:block;width:1.25rem;height:1.25rem;}
.fdy-carousel__dots{display:flex;justify-content:center;gap:var(--space-2);margin-top:var(--space-3);}
.fdy-carousel__dot{width:.5rem;height:.5rem;padding:0;border:0;border-radius:var(--radius-full);background:var(--color-border-strong);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),width var(--dur-fast) var(--ease-standard);}
.fdy-carousel__dot[aria-current="true"]{background:var(--color-primary);width:1.25rem;}
.fdy-carousel__dot:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.4);}
@media (prefers-reduced-motion:reduce){.fdy-carousel__viewport{scroll-behavior:auto;}}

/* Freeday. Cascade select (hierarchical drill-down picker).
 * Built by freeday-cascade.js from a [data-fdy-cascade] wrapper whose hidden
 * nested <ul> is the data model: a <li> with a child <ul> is a branch, one
 * without is a selectable leaf. The popover shows one level at a time, branches
 * drill in, a back control ascends, a leaf selects (value = full path).
 * Trigger visual matches the combo / date / time control family. */
.fdy-cascade{position:relative;display:inline-block;width:100%;max-width:22rem;}
.fdy-cascade__trigger{display:inline-flex;align-items:center;justify-content:space-between;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
.fdy-cascade__trigger:hover{border-color:var(--color-text-muted);}
.fdy-cascade__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-cascade__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-cascade__trigger:disabled,.fdy-cascade__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
.fdy-cascade__trigger[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
.fdy-cascade__trigger[aria-readonly="true"]:hover{border-color:var(--color-border);}
.fdy-cascade__trigger[aria-readonly="true"]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
.fdy-cascade--error .fdy-cascade__trigger,.fdy-cascade__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
.fdy-cascade--error .fdy-cascade__trigger:focus-visible,.fdy-cascade--error .fdy-cascade__trigger.is-open,.fdy-cascade__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
.fdy-cascade__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-cascade__value--placeholder{color:var(--color-text-subtle);}
.fdy-cascade__trigger::after{content:"";flex:none;width:.5rem;height:.5rem;margin-top:-.15rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(45deg);transition:transform var(--dur-fast) var(--ease-standard);}
.fdy-cascade__trigger.is-open::after{transform:rotate(225deg);margin-top:.1rem;}

.fdy-cascade__panel{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:100%;margin:0;padding:var(--space-1);background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
.fdy-cascade__panel[hidden]{display:none;}
.fdy-cascade__panel:focus-visible{outline:none;}
.fdy-cascade__head{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-1) var(--space-2);margin-bottom:var(--space-1);border-bottom:var(--bw) solid var(--color-border);}
.fdy-cascade__back{display:inline-flex;align-items:center;justify-content:center;flex:none;width:1.5rem;height:1.5rem;padding:0;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);cursor:pointer;}
.fdy-cascade__back:hover{background:var(--color-surface-3);color:var(--color-text);}
.fdy-cascade__back:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
.fdy-cascade__back[hidden]{display:none;}
.fdy-cascade__back svg{display:block;width:1rem;height:1rem;}
.fdy-cascade__crumb{min-width:0;flex:1;font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-cascade__list{margin:0;padding:0;list-style:none;max-height:14rem;overflow-y:auto;}
.fdy-cascade__opt{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;white-space:nowrap;}
.fdy-cascade__opt:hover,.fdy-cascade__opt.is-active{background:var(--color-surface-3);}
.fdy-cascade__opt[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
.fdy-cascade__opt-label{overflow:hidden;text-overflow:ellipsis;}
.fdy-cascade__opt-arrow{flex:none;width:.45rem;height:.45rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(-45deg);}
@media (prefers-reduced-motion:no-preference){.fdy-cascade__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}

/* Freeday. Choose-from-list dialog (searchable master-data picker).
 * Built on .fdy-modal: header + search + scrollable results table + footer.
 * Backs a choose-from-list field (readonly input + .fdy-input-group__btn magnifier). */
.fdy-modal--cfl{width:min(46rem,calc(100vw - var(--space-8)));}
.fdy-modal--cfl .fdy-modal__body{padding:0;display:flex;flex-direction:column;min-height:0;overflow:hidden;}

/* Search bar, single leading-icon field; Enter/typing filters the results. */
.fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
/* The search group spans the bar. Four files wrote this as `style="max-width:none"` — both typed
   adapters and both docs examples — which is the same missing vocabulary as #055 §1, one class over. */
.fdy-cfl__search .fdy-input-group{max-width:none;}

/* Field-less host (#054). With `dialogOnly` the typed wrappers render the dialog and no field, for a
   caller whose trigger is its own — a chip, a table cell, a menu item — which is what the raw path
   has always had in `[data-fdy-cfl-open]`. `display:contents` so the host generates no box at all:
   a zero-size div would still be a flex item or a grid cell in whatever laid the trigger out. */
.fdy-cfl__host{display:contents;}

/* Results, dense rows, sticky header, its own scroll region.
 *
 * `container-type: inline-size` makes this box the query container for the stacked layout below.
 * Safe here and nowhere near free in general: a size container stops contributing intrinsic inline
 * size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
 * takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
 * its own content, so it never sized to the table to begin with. */
.fdy-cfl__results{overflow:auto;max-height:min(24rem,50vh);}
/* Fenced the way composition.css:62 fences its own, so the declaration states the condition it
   needs instead of relying on a browser to drop it. Safari 15.4 to 15.8 is inside the floor the
   README states and has no container queries at all. */
@supports (container-type:inline-size){
  .fdy-cfl__results{container:fdy-cfl / inline-size;}
}
.fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}

.fdy-cfl__row{cursor:pointer;}
.fdy-cfl__row:hover{background:var(--color-surface-2);}
.fdy-cfl__row:focus-visible{outline:none;background:var(--color-primary-soft);box-shadow:inset 2px 0 0 var(--color-primary);}
.fdy-cfl__row[aria-selected="true"]{background:var(--color-primary-soft);}
.fdy-cfl__row[aria-selected="true"] td:first-child{font-weight:var(--weight-semibold);}
.fdy-cfl__check{width:1px;white-space:nowrap;}
.fdy-cfl__check input{pointer-events:none;}

/* Empty result state. */
.fdy-cfl__empty{padding:var(--space-8) var(--space-5);text-align:center;color:var(--color-text-muted);font-size:var(--text-sm);margin:0;}
.fdy-cfl__empty[hidden]{display:none;}

/* Footer, selection count (multi) sits left, actions right. */
.fdy-modal--cfl .fdy-modal__footer{justify-content:space-between;}
.fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
.fdy-cfl__actions{display:flex;gap:var(--space-2);}

/* Narrow: the same rows as list items, not table cells (#053).
 *
 * A 420px add-on panel gives this dialog ~372px. A three- or four-column master-data table in that
 * space either squeezes every column below legibility or scrolls on both axes, in a dialog whose
 * whole job is "search, look, click one". So the first cell becomes the title and the rest become
 * one detail line under it — the shape a picker takes when it is narrow.
 *
 * A CONTAINER query, not a media query: the dialog is not the viewport. The same component is wide
 * on a desktop page and narrow inside a docked panel, and only the container knows which.
 *
 * No ARIA roles come with this, and that is a MEASURED decision rather than an omission. The
 * received wisdom from the responsive-table era is that `display:block` on a table box drops its
 * implicit role; explicit `role="table"/"row"/"cell"` were written into all four stacks here on
 * that belief and then taken out again, because Chrome's own accessibility tree says otherwise:
 * with the rows at `display:flex`, stripping every `role` attribute leaves
 * `table → row → cell` intact (Chromium 133; only `rowgroup`, which associates nothing, is lost).
 * Redundant ARIA is what the kit's own rule calls worse than none, so the guard asserts the
 * OUTCOME — that a stacked row is still a row in the a11y tree — and stays silent about how.
 * `browser/cfl-narrow.mjs` is where a future engine that does dissolve them shows up as red.
 *
 * Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
 * row that costs a third of the width buys nothing when each row is labelled by its own title. */
@supports (container-type:inline-size){
  @container fdy-cfl (max-width: 30rem){
    .fdy-cfl__results .fdy-table,
    .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
    .fdy-cfl__results .fdy-table thead{display:none;}
    .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
    .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
    .fdy-cfl__row > td{display:block;padding:0;border:0;}
    /* The title takes the whole first line, which is what pushes every remaining cell onto the second
       one — no selector has to know how many columns there are, or which of them is last. */
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
    .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
    /* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
    .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
    .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
    .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
    .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
    .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
    /* The checkbox keeps its own line position beside the title rather than above it. */
    .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
  }
}

/* The same block for a browser that has no container queries, which is every Safari from 15.4 to
 * 15.8 and inside the floor the README states. There the @supports above is false, the stacked
 * layout never applied, and a 372px dialog kept a three-column master-data table that scrolls on
 * both axes.
 *
 * The condition is a VIEWPORT width, the only width this path can read. 32rem is derived from the
 * dialog rather than picked: the dialog is min(46rem, 100vw - var(--space-8)) with --space-8 at
 * 2rem, and the results box takes that width, so it sits at or under 30rem exactly when the
 * viewport sits at or under 32rem. What a media query cannot answer is the case the container
 * query exists for, a narrow dialog inside a wide viewport, which is a docked panel on a desktop;
 * on those browsers that one case keeps the scrolling table it has always had.
 *
 * The body is a copy of the body above, because CSS has no way to share one declaration block
 * between two different conditions. test/baseline.test.mjs compares the two, so an edit that
 * reaches one of them and misses the other fails there. */
@supports not (container-type:inline-size){
  @media (max-width:32rem){
    .fdy-cfl__results .fdy-table,
    .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
    .fdy-cfl__results .fdy-table thead{display:none;}
    .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
    .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
    .fdy-cfl__row > td{display:block;padding:0;border:0;}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
    .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
    .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
    .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
    .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
    .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
    .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
    .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
    .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
  }
}

/* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
 * Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */

/* Sparkline / trend line (SVG) */
.fdy-sparkline{display:inline-block;width:8rem;height:2.25rem;vertical-align:middle;color:var(--fdy-chart,var(--color-primary));}
.fdy-sparkline svg{display:block;width:100%;height:100%;}
.fdy-sparkline__area{fill:currentColor;opacity:.14;stroke:none;}
.fdy-sparkline__line{fill:none;stroke:currentColor;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.fdy-sparkline__dot{fill:currentColor;}

/* Bar chart (flex bars) */
.fdy-bars{position:relative;display:flex;align-items:flex-end;gap:var(--space-2);height:9rem;color:var(--fdy-chart,var(--color-primary));}
.fdy-bars__col{flex:1;display:flex;flex-direction:column;align-items:center;min-width:0;height:100%;}
.fdy-bars__track{flex:1;width:100%;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;gap:var(--space-1);min-height:0;}
.fdy-bars__val{font-size:var(--text-xs);font-variant-numeric:tabular-nums;color:var(--color-text-muted);}
.fdy-bars__bar{width:100%;max-width:2.5rem;border-radius:var(--radius-sm) var(--radius-sm) 0 0;background:currentColor;transition:height var(--dur-base) var(--ease-standard),opacity var(--dur-fast) var(--ease-standard);}
.fdy-bars__label{margin-top:var(--space-1);font-size:var(--text-xs);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
/* Hover emphasis: dim all bars while the chart is hovered, keep the hovered one lit. */
@media (hover:hover){.fdy-bars:hover .fdy-bars__bar{opacity:.4;}.fdy-bars__col:hover .fdy-bars__bar{opacity:1;}}

/* Interactive tooltip (shared by bar + donut). Chart.js-style hover readout. */
.fdy-chart__tip{position:absolute;z-index:5;left:0;top:0;pointer-events:none;transform:translate(-50%,calc(-100% - 8px));background:var(--color-inverse-surface);color:var(--color-inverse-text);font-size:var(--text-xs);font-weight:var(--weight-medium);font-variant-numeric:tabular-nums;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);white-space:nowrap;box-shadow:var(--shadow-2);opacity:0;transition:opacity var(--dur-fast) var(--ease-standard);}
.fdy-chart__tip.is-visible{opacity:1;}

/* Donut (conic-gradient ring + legend) */
.fdy-donut{display:inline-flex;align-items:center;gap:var(--space-5);flex-wrap:wrap;}
.fdy-donut__ring{position:relative;width:8rem;height:8rem;border-radius:50%;flex:none;}
.fdy-donut__ring::after{content:"";position:absolute;inset:24%;border-radius:50%;background:var(--color-surface);}
.fdy-donut__center{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;}
.fdy-donut__center b{font-family:var(--font-display);font-size:var(--text-xl);color:var(--color-text);line-height:1;}
.fdy-donut__center span{font-size:var(--text-xs);color:var(--color-text-muted);}
/* Donut hit-layer: transparent SVG sectors over the ring for per-slice hover. */
.fdy-donut__hit{position:absolute;inset:0;z-index:2;width:100%;height:100%;overflow:visible;}
.fdy-donut__seg{fill:transparent;cursor:pointer;transition:fill var(--dur-fast) var(--ease-standard);}
@media (hover:hover){.fdy-donut__seg:hover{fill:rgba(255,255,255,.3);}}
.fdy-chart__legend{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-2);}
.fdy-chart__legend li{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);}
.fdy-chart__swatch{width:.75rem;height:.75rem;border-radius:var(--radius-xs);flex:none;}
/* Horizontal legend for cartesian charts (legend sits above the plot). */
.fdy-chart__legend--row{flex-direction:row;flex-wrap:wrap;gap:var(--space-2) var(--space-4);}

/* Cartesian charts (line / area / multi-series & stacked bar). SVG with themed axes.
 * Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
 * set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;font-size:var(--fdy-chart-tick-size);}
.fdy-chart-xy__plot svg{display:block;width:100%;height:100%;overflow:visible;}
.fdy-chart-xy__grid{stroke:var(--chart-grid);stroke-width:1;shape-rendering:crispEdges;}
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);}
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);}
.fdy-chart-xy__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
.fdy-chart-xy__area{stroke:none;opacity:.14;}
.fdy-chart-xy__dot{stroke:var(--color-surface);stroke-width:1;}
.fdy-chart-xy__bar{transition:opacity var(--dur-fast) var(--ease-standard);}
.fdy-chart-xy__band{fill:transparent;cursor:pointer;}

/* Sama sebabnya dengan `.fdy-app__backdrop`: transisi yang juga menggerbangi `pointer-events` dapat
 * tersangkut di nilai awal ketika WebKit memaksa durasinya ke nol di bawah Reduce Motion, dan yang
 * tersisa adalah elemen tak terlihat yang tetap menangkap tap. Tooltip sudah dinetralkan sejak lama
 * untuk alasan yang sama; tip chart terlewat. */
@media (prefers-reduced-motion:reduce){.fdy-chart__tip{transition:none;}}

/* Freeday. Chip (tag / removable filter) */
.fdy-chip{display:inline-flex;align-items:center;gap:var(--space-2);height:1.75rem;padding:0 var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--weight-medium);color:var(--color-text);background:var(--color-surface-3);border:var(--bw) solid var(--color-border);}
.fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:rgba(var(--color-primary-rgb),.22);}
/* Categorical tone chips, non-semantic category tags / legend swatches, from the general
 * --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
 * gated by test/contrast.test.mjs). */
/* Precomputed tone tokens, the same --tone-N-bg / --tone-N-ink the avatar and the badge read.
 * --tone-N-border is the chip's edge: 30% of the tone over --color-border, opaque, which is a
 * different edge from the badge's translucent one and so a different token. See avatar.css for
 * why the mix left the component, and tokens/build.mjs for where it went. */
.fdy-chip--tone-1{background:var(--tone-1-bg);color:var(--tone-1-ink);border-color:var(--tone-1-border);}
.fdy-chip--tone-2{background:var(--tone-2-bg);color:var(--tone-2-ink);border-color:var(--tone-2-border);}
.fdy-chip--tone-3{background:var(--tone-3-bg);color:var(--tone-3-ink);border-color:var(--tone-3-border);}
.fdy-chip--tone-4{background:var(--tone-4-bg);color:var(--tone-4-ink);border-color:var(--tone-4-border);}
.fdy-chip--tone-5{background:var(--tone-5-bg);color:var(--tone-5-ink);border-color:var(--tone-5-border);}
.fdy-chip--tone-6{background:var(--tone-6-bg);color:var(--tone-6-ink);border-color:var(--tone-6-border);}
.fdy-chip--tone-7{background:var(--tone-7-bg);color:var(--tone-7-ink);border-color:var(--tone-7-border);}
.fdy-chip--tone-8{background:var(--tone-8-bg);color:var(--tone-8-ink);border-color:var(--tone-8-border);}
.fdy-chip__remove{position:relative;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-right:calc(var(--space-1)*-1);border:0;padding:0;border-radius:var(--radius-full);background:transparent;color:currentColor;opacity:.6;cursor:pointer;font-size:var(--text-sm);line-height:1;}
/* The hover wash is 16% of the chip's OWN ink, and the chip's ink is whatever the variant set:
 * --color-text, --color-primary-strong, --tone-N-ink. So no token can stand in for it, which is why
 * this was the last color-mix() in the kit — and the only one whose two-line fallback actually
 * worked, the mix carrying no var() and therefore being rejected at parse time.
 *
 * It is a VEIL now, the same trick the button gradients use (button.css): currentColor at .16 over
 * the chip, which is algebraically the colour the mix produced — `F·.16 + (1-.16)·ground` either
 * way, including at the glyph's antialiased edges, where the veil's colour equals the glyph's and so
 * composites back to itself. No @supports, no fallback line, and nothing newer than Safari 4.
 *
 * Absolutely positioned, so it paints above the × as well as the background; `border-radius:inherit`
 * keeps it inside the pill. No transition: the mix had none, and a veil that fades is a different
 * component. */
.fdy-chip__remove::before{content:"";position:absolute;inset:0;border-radius:inherit;background:currentColor;opacity:0;}
.fdy-chip__remove:hover{opacity:1;}
.fdy-chip__remove:hover::before{opacity:.16;}
.fdy-chip__remove:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
/* Group wrapper for a row of chips (choice / filter). */
.fdy-chips{display:flex;flex-wrap:wrap;gap:var(--space-2);}
/* Interactive chips: choice (toggle) + filter (toggle with a check when on).
 * Rendered as <button aria-pressed> and enhanced by freeday-chip.js. */
.fdy-chip--choice,.fdy-chip--filter{cursor:pointer;font-family:inherit;background:var(--color-surface);border-color:var(--color-border);transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
.fdy-chip--choice:hover,.fdy-chip--filter:hover{background:var(--color-surface-3);border-color:var(--color-text-subtle);}
.fdy-chip--choice:focus-visible,.fdy-chip--filter:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
.fdy-chip--choice[aria-pressed="true"],.fdy-chip--filter[aria-pressed="true"]{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:rgba(var(--color-primary-rgb),.4);}
.fdy-chip--choice:disabled,.fdy-chip--filter:disabled,.fdy-chip--choice[aria-disabled="true"],.fdy-chip--filter[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
.fdy-chip__check{display:none;flex:none;width:.85rem;height:.85rem;margin-left:calc(var(--space-1)*-1);}
.fdy-chip__check svg{display:block;width:100%;height:100%;}
.fdy-chip--filter[aria-pressed="true"] .fdy-chip__check{display:inline-flex;}

/* Freeday. Combobox (custom styled select, WAI-ARIA APG select-only) */
.fdy-combo{position:relative;display:block;max-width:22rem;}
.fdy-combo__button{display:inline-flex;align-items:center;justify-content:space-between;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
.fdy-combo__button:hover{border-color:var(--color-text-muted);}
.fdy-combo__button:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-combo__button.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-combo__button:disabled,.fdy-combo__button[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
/* Read-only: stays focusable & shows its value at full contrast but can't open, muted surface,
   softened border, default cursor, no focus-ring escalation (matches input[readonly], #39). */
.fdy-combo__button[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
.fdy-combo__button[aria-readonly="true"]:hover{border-color:var(--color-border);}
.fdy-combo__button[aria-readonly="true"]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
.fdy-combo--error .fdy-combo__button,.fdy-combo__button[aria-invalid="true"]{border-color:var(--color-danger);}
.fdy-combo--error .fdy-combo__button:focus-visible,.fdy-combo--error .fdy-combo__button.is-open,.fdy-combo__button[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
.fdy-combo__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-combo__value--placeholder{color:var(--color-text-subtle);}
.fdy-combo__button::after{content:"";flex:none;width:.5rem;height:.5rem;margin-top:-.15rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(45deg);transition:transform var(--dur-fast) var(--ease-standard);}
.fdy-combo__button.is-open::after{transform:rotate(225deg);margin-top:.1rem;}
/* Icon variants: --no-icon drops the default caret; add a .fdy-combo__icon element
 * inside the button (with --no-icon) to supply a custom trailing icon. */
.fdy-combo--no-icon .fdy-combo__button::after{display:none;}
.fdy-combo__icon{flex:none;display:inline-flex;color:var(--color-text-subtle);}
.fdy-combo__icon svg{display:block;width:1.05rem;height:1.05rem;}
.fdy-combo__listbox{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:120;min-width:100%;max-height:16rem;overflow-y:auto;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
.fdy-combo__listbox[hidden]{display:none;}
.fdy-combo__option{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;white-space:nowrap;}
.fdy-combo__option.is-highlighted{background:var(--color-surface-3);}
.fdy-combo__option[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
.fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.55);}
/* The tick is CSS, not content (#004 §2). Inside role="option" it joined the option's ACCESSIBLE
   NAME, so the selected option announced "✓August" while every other announced "August": the state
   was read twice, once as aria-selected, once as decoration, and a test asking for an option by
   its own label stopped matching the one that was selected. The `/ ""` is alt text: the glyph is
   painted, the accessibility tree gets nothing. Same reasoning as .fdy-label--required (1.29.0).
   The empty box still reserves its width, so labels stay aligned whether selected or not. */
.fdy-combo__check{display:inline-flex;justify-content:center;width:var(--space-4);flex:none;font-weight:var(--weight-bold);color:var(--color-primary-strong);}
.fdy-combo__option[aria-selected="true"] .fdy-combo__check::after{content:"✓" / "";}
@media (prefers-reduced-motion:no-preference){.fdy-combo__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
@keyframes fdy-combo-in{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}

/* Freeday. Page composition & type roles.
 *
 * The kit stops at the component; these primitives encode *how a page is assembled* so screens
 * built by different people cohere. They carry the usage doctrine (see USAGE.md) in markup: a
 * named type role gets used, a doc gets skimmed. Everything here is token-driven layout, no new
 * colours, no new shadows. Pair with .fdy-app (the shell) and the components inside.
 */

/* ---- Type roles (three title levels + supporting text) -------------------------------------
 * One role per level of hierarchy. Reach for these instead of re-using .fdy-card__title for a
 * page title (the mistake that collapses three levels into one grey mush). */
/* --color-text-muted, not -subtle: an eyebrow is a small label naming the thing below it, i.e.
   prose. -subtle is gated at 3.0 on purpose (placeholders, separators, decorative glyphs) and
   measures 4.41 on the page ground, axe reports it as a serious color-contrast failure. Same
   argument, same ink, same 4.41 as .fdy-stat__label in 1.30.0. */
.fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-muted);margin:0;}
.fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
.fdy-title-section{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-snug);color:var(--color-text);margin:0;text-wrap:balance;}
.fdy-title-card{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-tight);color:var(--color-text);margin:0;}
.fdy-text-muted{color:var(--color-text-muted);}
.fdy-text-subtle{color:var(--color-text-subtle);}
.fdy-text-caption{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;}

/* ---- Page: the vertical stack of sections, with the section rhythm between them ------------- */
.fdy-page{display:flex;flex-direction:column;gap:var(--space-8);}

/* ---- Page header: eyebrow + title + description on the left, primary action(s) on the right -- */
.fdy-page__header{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--space-4);flex-wrap:wrap;}
.fdy-page__heading{display:flex;flex-direction:column;gap:var(--space-2);min-width:0;}
.fdy-page__desc{font-size:var(--text-base);color:var(--color-text-muted);margin:0;max-width:62ch;line-height:var(--leading-normal);}
.fdy-page__actions{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}

/* ---- Page section: a heading (+ optional toolbar) and its body, with the intra-group rhythm --- */
.fdy-page-section{display:flex;flex-direction:column;gap:var(--space-4);}
.fdy-page-section__head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;}

/* ---- Toolbar: a horizontal control row (search + actions), wraps cleanly ---------------------- */
.fdy-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;}
.fdy-toolbar__spacer{flex:1;}
.fdy-toolbar__search{max-width:18rem;flex:1 1 12rem;}

/* ---- Stat tile: a KPI metric that is deliberately NOT a card, so a KPI strip doesn't become an
 * identical-white-card grid. Flat by default; wrap the .fdy-stats grid in one card if you want a
 * single boxed strip, or use .fdy-stats--boxed for shared surface + dividers. */
.fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
.fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
/* --color-text-muted, not -subtle: this is a LABEL, i.e. text, and -subtle is deliberately gated at
   3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41, under AA
   for text, and a stat label is often the only thing naming the number above it. */
.fdy-stat__label{margin:0;font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-muted);}
.fdy-stat__value{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:1;color:var(--color-text);font-variant-numeric:tabular-nums;}
.fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
.fdy-stat__meta{margin:0;font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
/* A stat value is display type sized for a COUNT, "1,284", and a back office puts money in it.
   "IDR 300,000.00" needs 224px at --text-3xl and the grid's own track is 11rem, so it wrapped
   between the currency and the number: two lines of 31px where the design says one. Measured, not
   guessed (#020).
   The value shrinks only when its own column is too narrow to hold it, so a wide dashboard keeps
   exactly the type it has today. Scoped to a stat INSIDE .fdy-stats, where the width comes from the
   grid track, `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
   own content. The unconditional rule above stays as the fallback wherever @container is missing. */
@supports (container-type:inline-size){
  .fdy-stats:not(.fdy-stats--inline)>.fdy-stat{container-type:inline-size;}
  .fdy-stats:not(.fdy-stats--inline)>.fdy-stat .fdy-stat__value{font-size:clamp(var(--text-xl),11cqw,var(--text-3xl));}
}
/* The price of that container, and the reason --inline opts out of it (#050). `container-type`
   applies inline-size CONTAINMENT: the tile's min-content and max-content contributions are both
   ZERO. So the 11rem floor above is not a taste choice, it is the only thing giving these tracks a
   size, and re-tracking the grid on content (`auto`, `max-content`, `minmax(0,…)`) resolves every
   track to 0. `justify-items` is the sharper edge: anything but the default `stretch` sizes the
   tile to that zero contribution even when the track is correctly wide, and three labels paint on
   top of each other. Measured from IDU_EMATE_ACCT_WEB's page header, where a hugging strip came out
   40px wide, all of it gap.
   --inline is the shape that header wanted: a strip that hugs its numbers and sits beside a page
   title, instead of a full-width band whose 11rem floor collapses to one column at 390px. It cannot
   have both, so it drops the container and keeps the flat --text-3xl the base rule already sets;
   a track sized to its own content is never the narrow track the clamp exists for. */
.fdy-stats--inline{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:minmax(0,max-content);}
.fdy-stats--boxed{background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);gap:0;}
.fdy-stats--boxed .fdy-stat{padding:var(--space-5);border-right:var(--bw) solid var(--color-border-muted);}
.fdy-stats--boxed .fdy-stat:last-child{border-right:0;}

/* Icon box (#001 §4). The kit ships no icons on purpose, but every icon slot it DOES ship
   (.fdy-btn__icon, .fdy-nav__icon, .fdy-state__icon) assumes something sensible inside, so a
   standalone icon had no contract and every app invented one. 1em keeps the glyph matched to the
   text beside it at every scale, bring your own paths, use our box. Works on the <svg> itself or
   on a wrapper around it. */
.fdy-icon{display:inline-block;width:1em;height:1em;flex:none;vertical-align:-0.125em;}
.fdy-icon>svg{display:block;width:100%;height:100%;}

/* Inline text that carries STATE (#001 §5). The three roles above all de-emphasise; a sentence like
   "Amount changed from X to Y" or "No approver assigned" needs the opposite, and is neither a badge
   (those are statuses, not sentences) nor an .fdy-alert (those are blocks, not one line inside a
   row). Same -strong inks the badges use, gated by contrast.test.mjs on plain surfaces too. */
.fdy-text-success{color:var(--color-success-strong);}
.fdy-text-warning{color:var(--color-warning-strong);}
.fdy-text-danger{color:var(--color-danger-strong);}
/* Document columns (#065): a form or record page with an optional identity rail on the left
 * (__lead) and a summary rail on the right (__aside). The widths follow which rails are PRESENT,
 * so the layout is decided once here and not again by every screen:
 *   __main alone            one column
 *   __main + __aside        8 / 4         (from md)
 *   __lead + __main         3 / 9         (from md)
 *   all three               3 / 6 / 3     (from lg; between md and lg both rails share the left)
 * Stacked, the summary rail comes FIRST: last, it sits under every section, and its button with it.
 * From md each rail is sticky and scrolls on its own, so the total and the status stay answerable
 * while the main column scrolls with the page, and a rail taller than the screen never hangs its
 * end off it. Screen only: a printed page is not held to a window. */
.fdy-page__columns{--_fdy-rail-top:var(--fdy-page-sticky-top,var(--space-4));display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-6);align-items:start;}
/* Under the shell the sticky topbar is in the way; --fdy-page-sticky-top overrides either default. */
.fdy-app .fdy-page__columns{--_fdy-rail-top:var(--fdy-page-sticky-top,calc(var(--space-16) + var(--space-4)));}
.fdy-page__columns > *{min-width:0;}
@media (max-width:959.98px){.fdy-page__aside{order:-1;}}
@media (min-width:960px){
  .fdy-page__columns:has(> .fdy-page__aside){grid-template-columns:minmax(0,2fr) minmax(0,1fr);}
  .fdy-page__columns:has(> .fdy-page__lead){grid-template-columns:minmax(0,1fr) minmax(0,3fr);}
  /* Both rails, two columns: they stack on the left while __main spans both rows on the right. */
  .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > .fdy-page__main{grid-row:span 2;}
}
@media (min-width:1280px){
  .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside){grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr);}
  .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > .fdy-page__main{grid-row:auto;}
}
/* The padding, taken back by the margin, is room for a focus ring at a card's edge: a scrolling box
   clips whatever draws outside it. */
@media screen and (min-width:960px){
  .fdy-page__lead,.fdy-page__aside{position:sticky;top:var(--_fdy-rail-top);max-height:calc(100dvh - var(--_fdy-rail-top) - var(--space-4));overflow-y:auto;overscroll-behavior:contain;padding:var(--space-1);margin:calc(var(--space-1) * -1);}
}
/* Two sticky rails in ONE column would slide over each other: between md and lg, with both rails
   present, they scroll with the page instead. */
@media screen and (min-width:960px) and (max-width:1279.98px){
  .fdy-page__columns:has(> .fdy-page__lead):has(> .fdy-page__aside) > :is(.fdy-page__lead,.fdy-page__aside){position:static;max-height:none;overflow:visible;}
}

/* Freeday. Date picker (input-styled trigger + calendar popover).
 * Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
.fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
/* Inside a labelled field, a picker fills the field like every other control.
   The 14rem/11rem caps above suit a picker standing on its own; in a form grid
   they leave the date box visibly short of the combo beside it. 224px against
   319px in a two-column layout, and the row reads as ragged. The field's own
   max-width is what should govern there. */
.fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
.fdy-datepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
.fdy-datepicker__trigger:hover{border-color:var(--color-text-muted);}
.fdy-datepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-datepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-datepicker--error .fdy-datepicker__trigger,.fdy-datepicker__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
.fdy-datepicker--error .fdy-datepicker__trigger:focus-visible,.fdy-datepicker--error .fdy-datepicker__trigger.is-open,.fdy-datepicker__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
.fdy-datepicker__trigger:disabled,.fdy-datepicker__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
.fdy-datepicker__trigger[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
.fdy-datepicker__trigger[aria-readonly="true"]:hover{border-color:var(--color-border);}
.fdy-datepicker__trigger[aria-readonly="true"]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
.fdy-datepicker__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-datepicker__value--placeholder{color:var(--color-text-subtle);}
.fdy-datepicker__icon{flex:none;display:inline-flex;color:var(--color-text-subtle);}
.fdy-datepicker__icon svg{display:block;width:1.05rem;height:1.05rem;}
/* Clearable: a reset button overlaid in the icon slot (the trigger is itself a <button>, so the
   clear control is a sibling, not a child). Shown only when clearable + a date is set; the calendar
   icon is hidden (kept in flow so the value's ellipsis boundary doesn't shift). */
.fdy-datepicker__icon.is-hidden{visibility:hidden;}
.fdy-datepicker__clear{position:absolute;top:50%;right:var(--space-3);transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:1.15rem;height:1.15rem;padding:0;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-subtle);cursor:pointer;transition:color var(--dur-fast) var(--ease-standard),background var(--dur-fast) var(--ease-standard);}
.fdy-datepicker__clear:hover{color:var(--color-text);background:var(--color-surface-2);}
.fdy-datepicker__clear:focus-visible{outline:none;color:var(--color-text);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-datepicker__clear svg{display:block;width:.9rem;height:.9rem;}

.fdy-datepicker__panel{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;padding:var(--space-3);background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
.fdy-datepicker__panel[hidden]{display:none;}

/* Calendar grid */
.fdy-cal__head{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2);}
/* The title is a CONTROL now (#004 §1): it drills to the month grid, so it needs the UA button box
   reset and the same hover/focus treatment as the arrows beside it. */
.fdy-cal__title{flex:1;appearance:none;border:0;background:transparent;margin:0;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);text-align:center;font-family:inherit;font-weight:var(--weight-semibold);font-size:var(--text-sm);color:var(--color-text);text-transform:capitalize;cursor:pointer;}
.fdy-cal__title:hover{background:var(--color-surface-2);}
.fdy-cal__title:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-cal__month,.fdy-cal__year{display:inline-flex;align-items:center;justify-content:center;height:2.25rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-family:inherit;font-size:var(--text-sm);text-transform:capitalize;cursor:pointer;}
.fdy-cal__month:hover,.fdy-cal__year:hover{background:var(--color-surface-2);}
.fdy-cal__month:focus-visible,.fdy-cal__year:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-cal__month:disabled,.fdy-cal__year:disabled{opacity:.4;cursor:not-allowed;background:transparent;}
.fdy-cal__month.is-today,.fdy-cal__year.is-today{font-weight:var(--weight-bold);box-shadow:inset 0 0 0 1.5px var(--color-border-strong);}
.fdy-cal__month.is-selected,.fdy-cal__year.is-selected{background:var(--color-primary);color:var(--color-on-primary);font-weight:var(--weight-semibold);}
.fdy-cal__nav{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);font-size:var(--text-lg);line-height:1;cursor:pointer;}
.fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
.fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
/* Month grid. 3×4 over the same track width as the 7-column day grid, so the panel does not resize
   when you drill and the pointer does not have to travel.

   AFTER `.fdy-cal__grid`, and that is the whole rule: the element carries both classes, they weigh
   the same, so source order decides. Declared earlier, the modifier lost to the base it modifies and
   twelve months rendered seven across, the layout the comment above described, never once applied. */
.fdy-cal__grid--months,.fdy-cal__grid--years{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
.fdy-cal__dow{display:flex;align-items:center;justify-content:center;height:1.75rem;font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-transform:capitalize;}
.fdy-cal__day{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-size:var(--text-sm);font-variant-numeric:tabular-nums;cursor:pointer;}
.fdy-cal__day:hover{background:var(--color-surface-2);}
.fdy-cal__day:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-cal__day.is-outside{color:var(--color-text-subtle);opacity:.55;}
.fdy-cal__day.is-today{font-weight:var(--weight-bold);box-shadow:inset 0 0 0 1.5px var(--color-border-strong);}
.fdy-cal__day.in-range{background:var(--color-primary-soft);color:var(--color-primary-strong);border-radius:0;}
.fdy-cal__day.is-selected{background:var(--color-primary);color:var(--color-on-primary);font-weight:var(--weight-semibold);}
.fdy-cal__day.is-selected:hover{background:var(--color-primary-hover);}
.fdy-cal__day:disabled{opacity:.35;cursor:not-allowed;}
.fdy-cal__day:disabled:hover{background:transparent;}

/* Range: two linked pickers */
/* Responsive: each picker may shrink (min-width:0) & wrap so the range never overflows a
   narrow row/phone; inline-flex still hugs to ~23rem at wider widths (look unchanged). */
.fdy-daterange{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);}
/* Each picker shrinks from an 11rem basis but not below ~7rem, enough for "dd/mm/yyyy" + the icon,
   so a squeezed range compresses gracefully (and wraps) instead of clipping the date. */
.fdy-daterange .fdy-datepicker{flex:0 1 11rem;min-width:7rem;}
.fdy-daterange__sep{flex:none;color:var(--color-text-subtle);}

@media (prefers-reduced-motion:no-preference){.fdy-datepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}

/* Freeday. Datetime picker (composed: date picker + time picker → one value).
 * A thin composer (freeday-datetime.js) links a child [data-fdy-datepicker] and
 * [data-fdy-timepicker] and emits a single combined event. Icon variants come from
 * the child pickers (data-fdy-no-icon / <template data-fdy-icon>). */
.fdy-datetimepicker{display:inline-flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
/* Fixed widths so date + time sit side by side (children default to width:100%). */
.fdy-datetimepicker .fdy-datepicker{width:12rem;max-width:none;}
.fdy-datetimepicker .fdy-timepicker{width:8.5rem;max-width:none;}

/* Freeday. Description list (key-value detail view) */
.fdy-dl{display:grid;grid-template-columns:auto 1fr;gap:var(--space-2) var(--space-5);margin:0;}
.fdy-dl dt{color:var(--color-text-muted);font-size:var(--text-sm);}
/* overflow-wrap: an email, an order code or a 13-digit phone number is one unbreakable token, and
   without it the value prints over whatever sits beside it (#066). */
.fdy-dl dd{margin:0;min-width:0;color:var(--color-text);font-size:var(--text-sm);font-weight:var(--weight-medium);overflow-wrap:anywhere;}
.fdy-dl--rows{grid-template-columns:1fr;gap:var(--space-1);}
.fdy-dl--rows dt{margin-top:var(--space-2);}
/* Facts grid (#066): each pair a <div> (allowed inside <dl>), caption above value, as many columns
   as the list's OWN width holds, never more than three. Sized by the element rather than the
   window, so a grid in a narrow side rail stays one column with no modifier: a window breakpoint
   gave a rail two columns of about 110px. min(100%, …) keeps a single column from overflowing a
   rail narrower than the column floor. */
.fdy-dl--grid{grid-template-columns:repeat(auto-fill,minmax(min(100%,max(var(--fdy-dl-col-min,14rem),calc((100% - 2 * var(--space-6)) / 3))),1fr));gap:var(--space-5) var(--space-6);}
.fdy-dl--grid > div{min-width:0;}
.fdy-dl--grid dd{margin-top:var(--space-1);}

/* Freeday. Divider */
.fdy-divider{border:0;border-top:var(--bw) solid var(--color-border);margin:var(--space-4) 0;}
.fdy-divider--vertical{border-top:0;border-left:var(--bw) solid var(--color-border);margin:0 var(--space-4);align-self:stretch;}

/* Freeday. Drawer (temporary side panel on native <dialog>: focus-trap / Esc /
 * backdrop / return-focus are free). Anchors left (default) or right.
 * Enhanced by freeday-drawer.js (open trigger + backdrop-click close). */
/* display is gated behind [open] so the native <dialog> stays hidden until opened.
 * An unconditional display here would beat the UA `dialog:not([open]){display:none}`
 * rule (author origin wins over user-agent) and leave the drawer visible on load. */
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;height:100dvh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-4),var(--surface-inset);overflow:hidden;transform:translateX(-100%);transition-property:transform,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
.fdy-drawer[open]{display:flex;transform:none;}
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition-property:opacity,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
.fdy-drawer__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
.fdy-drawer__title{font-family:var(--font-display);font-weight:var(--weight-semibold);font-size:var(--text-lg);color:var(--color-text);margin:0 auto 0 0;}
.fdy-drawer__close{border:0;background:transparent;color:var(--color-text-muted);font-size:var(--text-xl);line-height:1;cursor:pointer;padding:var(--space-1);border-radius:var(--radius-sm);}
.fdy-drawer__close:hover{background:var(--color-surface-2);color:var(--color-text);}
.fdy-drawer__body{flex:1;min-height:0;overflow:auto;padding:var(--space-4);}
.fdy-drawer__footer{flex:none;display:flex;gap:var(--space-2);justify-content:flex-end;padding:var(--space-4) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:var(--color-surface-2);}
/* Symmetric enter AND exit on the native <dialog>: @starting-style animates the entry;
 * the overlay/display allow-discrete transitions keep the panel painted through the exit so
 * it slides back out on close (not just in). prefers-reduced-motion is honored by the global
 * duration reset in base.css. Unsupported browsers degrade to an instant open/close. */
.fdy-drawer[open]::backdrop{opacity:1;}
@starting-style{
  .fdy-drawer[open]{transform:translateX(-100%);}
  .fdy-drawer--right[open]{transform:translateX(100%);}
  .fdy-drawer[open]::backdrop{opacity:0;}
}

/* Freeday. File upload (drop target + explicit per-file states).
 * Enhanced by freeday-upload.js; states: idle · drag-over · uploading · done · error. */
.fdy-dropzone{display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:var(--space-6);text-align:center;border:2px dashed var(--color-control-border);border-radius:var(--radius-lg);background:var(--color-surface-2);color:var(--color-text-muted);cursor:pointer;transition:border-color var(--dur-fast) var(--ease-standard),background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
.fdy-dropzone:hover{border-color:var(--color-text-muted);}
.fdy-dropzone:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-dropzone.is-dragover{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
.fdy-dropzone[aria-disabled="true"],.fdy-dropzone.is-disabled{opacity:.55;cursor:not-allowed;pointer-events:none;}
.fdy-dropzone__icon{color:var(--color-text-subtle);}
.fdy-dropzone.is-dragover .fdy-dropzone__icon{color:var(--color-primary-strong);}
.fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
.fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
.fdy-dropzone__hint{margin:0;font-size:var(--text-xs);}
/* Wide (horizontal) layout, icon beside the copy instead of stacked, for full-width
 * sections. Wrap the title + hint in .fdy-dropzone__text. */
.fdy-dropzone--row{flex-direction:row;align-items:center;justify-content:center;text-align:left;gap:var(--space-4);}
.fdy-dropzone__text{display:flex;flex-direction:column;gap:var(--space-1);}

/* File rows (list of added/uploaded/rejected files) */
/* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it,
   an "Add files" button, in every upload UI there is, sat flush against the last row: measured
   0px. A block that claims space on one side only is not spacing, it is a lean. */
.fdy-filelist{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-3);margin-bottom:var(--space-3);}
/* Nothing after it, nothing to separate from, so the room is not spent. */
.fdy-filelist:last-child{margin-bottom:0;}
/* Side-by-side rows on wide screens (reflow to one column when narrow). */
.fdy-filelist--grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));}
.fdy-file{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border:var(--bw) solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);}
.fdy-file__icon{flex:none;display:inline-flex;color:var(--color-text-muted);}
.fdy-file__icon svg{display:block;width:1.25rem;height:1.25rem;}
.fdy-file__meta{flex:1;min-width:0;}
.fdy-file__name{font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-file__sub{font-size:var(--text-xs);color:var(--color-text-muted);}
.fdy-file__progress{margin-top:var(--space-2);}
.fdy-file__remove{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);font-size:var(--text-lg);line-height:1;cursor:pointer;}
.fdy-file__remove:hover{background:var(--color-surface-2);color:var(--color-text);}
.fdy-file__remove:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-file--success .fdy-file__icon{color:var(--color-success);}
/* 40% of --color-danger over --color-border, as a token rather than as a mix written here: it blends
 * two ROLES, so no channel triple can state it, and mixing it in the component carried var() into
 * color-mix() — the shape that goes invalid at computed-value time and drops border-color to its
 * initial currentColor, which is the black box an iPhone 7 drew around a failed upload. The blend is
 * now computed per theme by tokens/build.mjs. */
.fdy-file--error{border-color:var(--color-danger-border);}
.fdy-file--error .fdy-file__icon{color:var(--color-danger);}
.fdy-file--error .fdy-file__sub{color:var(--color-danger-strong);}

/* Freeday. Filter bar (a consistent row of filters: search + selects + actions).
 * Solves the "ragged filter row": apps improvised flex rows with ad-hoc
 * flex/min-width, so grow fields ballooned while selects stayed tiny and no two
 * screens matched. Here every field snaps to a shared width rhythm; one field may
 * --w-grow to absorb the slack; trailing actions align to the controls. Fields are
 * the standard .fdy-field (label above control); wraps gracefully on narrow screens. */
.fdy-filterbar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-3) var(--space-4);}
/* Composed with a table toolbar (one element, both classes): the fields decide the alignment.
 * `.fdy-table-toolbar` sets align-items:center (right for bare buttons) at equal specificity, and
 * being later in the bundle it would otherwise win, floating labelled fields' neighbours out of line.
 * This 2-class selector (0,2,0) settles it toward the filterbar's flex-end regardless of source order. */
.fdy-table-toolbar.fdy-filterbar{align-items:flex-end;}
/* Fields are direct children. Drop the standalone field's 22rem cap; default to the
 * medium column width with no unbounded growth (that's what caused the raggedness). */
.fdy-filterbar>.fdy-field{max-width:none;flex:0 0 auto;width:12rem;}
/* Controls fill their field's column: released in input.css for every field, not just this one.
   The rule used to live here, naming the input group and the combo only, which is why an
   .fdy-autocomplete or an .fdy-cascade in a --w-2xl field still stopped 48px short of it (#051). */
/* Column-width rhythm, pick one per field so bars across the app line up. */
.fdy-filterbar>.fdy-field--w-sm{width:9rem;}
.fdy-filterbar>.fdy-field--w-lg{width:15rem;}
.fdy-filterbar>.fdy-field--w-xl{width:18rem;}
/* Wide enough for a two-picker date range (~25rem) so it need not burn the single --w-grow slot. */
.fdy-filterbar>.fdy-field--w-2xl{width:25rem;}
/* One growing field (usually the search) absorbs the remaining width. */
.fdy-filterbar>.fdy-field--w-grow{flex:1 1 16rem;width:auto;min-width:12rem;}
/* Trailing actions (buttons) pushed to the end, aligned with the controls' baseline.
 * Wrap note: a wrapped bar breaks flex lines on each item's *flex-basis* (the grow field's
 * `flex:1 1 16rem` reserves 16rem for line-breaking), NOT its rendered width, so the grow basis,
 * not the visible width, decides the wrap point. Margins can't keep the actions off a lonely wrapped
 * row; to avoid stranded actions, tune the --w-grow basis or drop a field so the bar fits one row. */
/* A control that carries NO label of its own, a checkbox, a switch, a lone
   button, in a bar whose `align-items:flex-end` exists to line up LABELLED
   fields. Flex-end puts such a control's bottom on the inputs' bottom, so a
   20px checkbox sits well below the 32px input's centre line and reads as
   dropped. Giving it the height of a control lands its box exactly where an
   input's box ends, and its own centring then puts it on the input's centre
   line. Raised from IDU_EMATE_APPL_WEB (#014). */
.fdy-filterbar>.fdy-check,.fdy-filterbar>.fdy-radio,.fdy-filterbar>.fdy-switch,.fdy-filterbar>.fdy-btn{min-height:var(--control-h);}
.fdy-filterbar__actions{display:flex;align-items:center;gap:var(--space-2);margin-left:auto;}
/* Opt-in: sit the actions inline right after the last field instead of at the far edge, so when the
 * bar wraps they read as attached to the filters rather than shoved to the right of a lonely row. */
.fdy-filterbar--actions-inline .fdy-filterbar__actions{margin-left:0;}
/* Stack on narrow screens so nothing overflows the row.
 * 640 is deliberately not a ramp step (sm is 600): it is a real phone-landscape width, and moving
 * it now would reflow every filter bar in every consuming app. So it is NAMED instead — `filterbar`
 * in tokens/breakpoints.mjs, `--fdy-filterbar-stacked` in the media sheet — because an app rule
 * that must fire with the stack (releasing a --w-grow field's max-width, say) has to match this
 * exact condition, and aligning it to sm from the docs alone leaves 600–640px half-stacked: the
 * fields are already full width while the app still caps the growing one. Raised from
 * IDU_AI_DOC_SAPB1_CLIENT and IDU_AI_DOC_SAAS (#058 §1). */
@media (max-width:640px){
  .fdy-filterbar>.fdy-field,.fdy-filterbar>.fdy-field--w-sm,.fdy-filterbar>.fdy-field--w-lg,.fdy-filterbar>.fdy-field--w-xl,.fdy-filterbar>.fdy-field--w-2xl,.fdy-filterbar>.fdy-field--w-grow{width:100%;flex:1 1 100%;}
  .fdy-filterbar__actions{width:100%;margin-left:0;}
}

/* Freeday. Form grid (document/header fields laid out in aligned columns).
 * The two-dimensional counterpart to the single-column .fdy-field stack and the
 * one-row .fdy-filterbar: related fields sit in columns that reflow to one on narrow
 * screens. Fields are the standard .fdy-field; a field with --full spans the whole row
 * (e.g. an address or note). Zero JS, pure layout over .fdy-field. */
.fdy-form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));gap:var(--space-4) var(--space-5);}
.fdy-form-grid>.fdy-field{max-width:none;}
.fdy-form-grid>.fdy-field--full{grid-column:1/-1;}
/* Anything that is not a field is a row (CATATAN-FREEDAY §22). auto-fit makes every child a track, so
 * a heading, a help line, an app's own row or a card footer stood as a narrow column beside the
 * fields: a footer at 49% of its card, a row of controls at 31%, and nothing in the markup to say so.
 * A cell that is meant to sit in a column but is not an input (a lone checkbox) goes in a
 * `<div class="fdy-field">`, which keeps it one. */
.fdy-form-grid>:not(.fdy-field){grid-column:1/-1;}

/* Freeday. Input group (prefix/suffix addons, e.g. Rp / % / icon) */
.fdy-input-group{display:flex;align-items:stretch;height:var(--control-h);max-width:22rem;background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);overflow:hidden;transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
.fdy-input-group:hover{border-color:var(--color-text-muted);}
.fdy-input-group:focus-within{border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
/* The group owns the height; the input fills whatever the border leaves. It used to subtract 3px
   for two 1.5px borders, which is arithmetic the engine is free to round: at dpr 1 each border
   resolves to 1px, so the group rendered 1px short of every control beside it (#018). Stretching
   is right at any border width, any rounding, and under compact without a second calc to keep in
   step with --control-h. */
.fdy-input-group .fdy-input{flex:1;min-width:0;border:0;background:transparent;box-shadow:none;border-radius:0;height:auto;align-self:stretch;}
.fdy-input-group .fdy-input:focus{box-shadow:none;}
.fdy-input-group__addon{display:inline-flex;align-items:center;padding:0 var(--space-3);background:var(--color-surface-2);color:var(--color-text-muted);font-size:var(--text-sm);white-space:nowrap;border-right:var(--bw) solid var(--color-border);}
.fdy-input-group__addon:last-child{border-right:0;border-left:var(--bw) solid var(--color-border);}
/* Borderless leading icon (e.g. search magnifier), self-contained like __btn so it
   centers standalone (no base .fdy-input-group__addon required). */
.fdy-input-group__addon--icon{display:inline-flex;align-items:center;background:transparent;border:0;padding:0 0 0 var(--space-3);color:var(--color-text-subtle);}
.fdy-input-group__addon--icon svg{display:block;width:1rem;height:1rem;}
/* The leading icon already provides the left inset, don't let the input add its full left padding on top. */
.fdy-input-group__addon--icon + .fdy-input{padding-left:var(--space-2);}
/* Trailing action button (e.g. choose-from-list magnifier), a real, focusable control. */
.fdy-input-group__btn{display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--control-h);align-self:stretch;border:0;border-left:var(--bw) solid var(--color-border);background:var(--color-surface-2);color:var(--color-text-muted);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
.fdy-input-group__btn:hover{background:var(--color-surface-3);color:var(--color-text);}
.fdy-input-group__btn:focus-visible{outline:none;color:var(--color-text);box-shadow:inset 0 0 0 2px var(--color-primary);}
.fdy-input-group__btn svg{display:block;width:1.1rem;height:1.1rem;}
/* Leading action button (the "−" of a number stepper): the divider belongs on the other side. */
.fdy-input-group__btn:first-child{border-left:0;border-right:var(--bw) solid var(--color-border);}
/* At a bound, or on a disabled/read-only field, the button must look as inert as it acts, same dim
   + not-allowed as .fdy-input:disabled. :hover still matches a disabled button, so it is withdrawn. */
.fdy-input-group__btn:disabled{opacity:.5;cursor:not-allowed;}
.fdy-input-group__btn:disabled:hover{background:var(--color-surface-2);color:var(--color-text-muted);}
/* Error state must win over the neutralizing rules above, promote to the wrapper. */
.fdy-input-group:has(.fdy-input--error),.fdy-input-group:has(.fdy-combo--error),.fdy-input-group:has([aria-invalid="true"]){border-color:var(--color-danger);}
.fdy-input-group:has(.fdy-input--error):focus-within,.fdy-input-group:has(.fdy-combo--error):focus-within,.fdy-input-group:has([aria-invalid="true"]):focus-within{border-color:var(--color-danger);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-danger-rgb),.26);}

/* Freeday. Field & Input */
.fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
/* A field marked --full fills whatever contains it (#055 §1). The cap above is for a field standing
   on its own in a row — a filter bar, a toolbar — and it is right there. But every real form is a
   single-column flex stack (a settings pane, a login card, a dialog at 420px), and the only release
   the kit shipped was `.fdy-form-grid > .fdy-field`, which a flex column can never reach:
   .fdy-form-grid is `repeat(auto-fit,minmax(14rem,1fr))`, which pairs fields up the moment there is
   room. So a stack had no vocabulary at all and said it on the container instead — 23 times across
   two consoles, and four times in this kit's OWN reference page as `style="max-width:none"`.
   Inside .fdy-form-grid this changes nothing (that rule already says it); outside one the modifier
   finally means what its name says, and the grid keeps `grid-column` as its own concern. */
.fdy-field--full{max-width:none;}
/* Inside a labelled field, a control fills the field (#051). The 22rem cap those controls carry is
   for one standing on its own, with no field to take a width from; #017 already released the date
   and time pickers here for exactly this reason, and its comment claims a picker then fills its
   field "like every other control". That was not true of these four: a `--full` row in a
   .fdy-form-grid held a 352px combo under a 600px label, and a 26rem toolbar search field hid 64px
   of dead space INSIDE itself, so an author who wrote `gap:var(--space-3)` measured 76px on screen.
   The field's own max-width above is what governs, and it still caps a field nobody has widened, so
   this changes nothing wherever the cap was already doing its job. */
.fdy-field>.fdy-input-group,.fdy-field>.fdy-combo,.fdy-field>.fdy-autocomplete,.fdy-field>.fdy-cascade{max-width:none;}
/* A grouped field is legitimately a <fieldset> (it needs a <legend>), e.g. a radio/checkbox group.
   Neutralise the UA groove border / padding / min-inline-size so it lays out identically to the
   <div> form (min-inline-size:min-content would otherwise block the filterbar column widths). */
fieldset.fdy-field{border:0;margin:0;padding:0;min-inline-size:0;}
/* The rendered legend is laid out outside the fieldset's flex flow, so `gap` never reaches it,
   measured 0px between legend and first control. This is the spacing contract a grouped field
   needs; it matches .fdy-field's own gap so the group reads as one rhythm. */
fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
.fdy-label{font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);}
.fdy-input,.fdy-textarea{width:100%;height:var(--control-h);padding:0 var(--space-3);font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
.fdy-input::placeholder,.fdy-textarea::placeholder{color:var(--color-text-subtle);}
.fdy-textarea{height:auto;min-height:5.5rem;padding:var(--space-3);line-height:var(--leading-normal);resize:vertical;}
.fdy-input:hover,.fdy-textarea:hover{border-color:var(--color-text-muted);}
.fdy-input:focus,.fdy-textarea:focus{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-input:disabled,.fdy-textarea:disabled,.fdy-input[aria-disabled="true"],.fdy-textarea[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
/* Read-only (#39): the value is real and may be read/copied, only not edited. Full contrast (unlike
   :disabled which dims to placeholder-grey), muted surface + softened border so it still reads as
   inert, default cursor, no focus-ring escalation. */
.fdy-input[readonly],.fdy-textarea[readonly]{background:var(--color-surface-2);border-color:var(--color-border);color:var(--color-text);cursor:default;}
.fdy-input[readonly]:hover,.fdy-textarea[readonly]:hover{border-color:var(--color-border);}
.fdy-input[readonly]:focus-visible,.fdy-textarea[readonly]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
/* Numbers (#44): the user agent draws its own spin buttons inside the field, in OS widget colours no
   theme reaches, on a dark surface they read as a light-grey artefact glued to a themed field, and
   it is the only unthemed control on the page. Removed so .fdy-input looks the same whatever its
   type. `appearance:textfield` is the Firefox half, the pseudo-element the Blink/WebKit half; both
   are needed. This trades away the mouse-only increment, arrow keys on a focused number input still
   work, and [data-fdy-number] gives the affordance back on the kit's terms. */
.fdy-input[type="number"]{appearance:textfield;-webkit-appearance:textfield;}
.fdy-input[type="number"]::-webkit-outer-spin-button,.fdy-input[type="number"]::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}
.fdy-input--error,.fdy-input[aria-invalid="true"],.fdy-textarea[aria-invalid="true"]{border-color:var(--color-danger);}
.fdy-input--error:focus,.fdy-input[aria-invalid="true"]:focus,.fdy-textarea[aria-invalid="true"]:focus{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
/* Required marker. The control already carries `required`, so the asterisk is decoration: the
   `/ ""` is CSS alt text, the glyph is painted but exposes NOTHING to the accessibility tree, so a
   screen reader does not read "star" after every label. That is stricter than a <span> an app has to
   remember to mark aria-hidden, and it cannot be forgotten. */
.fdy-label--required::after{content:"*" / "";margin-left:var(--space-1);color:var(--color-danger);}
.fdy-help{margin:0;font-size:var(--text-xs);color:var(--color-text-muted);}
/* --danger is the tone word everywhere else in the kit; help text takes either (CATATAN-FREEDAY §11). */
.fdy-help--error,.fdy-help--danger{color:var(--color-danger);}

/* Freeday. Keyboard key */
.fdy-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 var(--space-2);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text);background:var(--color-surface-2);border:var(--bw) solid var(--color-border-strong);border-bottom-width:2px;border-radius:var(--radius-sm);}

/* Freeday. List: the FLAT row container.
 *
 * USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
 * container the kit shipped was .fdy-card, which carries --shadow-lift (a real 34px lift). A
 * responsive table that becomes a list below `md` therefore had to choose between a stack of
 * shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
 * the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
 *
 * Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
 * Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
/* position:relative, containing block for out-of-flow row content (see base.css). */
.fdy-list{position:relative;list-style:none;margin:0;padding:0;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;}
/* A row that hosts a stretched target has to be the anchor itself (#008 §1). Without this the
   overlay resolves against .fdy-list, which IS positioned, so EVERY row's target covers the whole
   list and the last one in the DOM wins: measured, a click on row one opens row two, under row one's
   name, which looks exactly like working software. Opt-in via :has(), so a row that doesn't use the
   pattern is untouched and nothing an app absolutely positions inside a plain row starts resolving
   against a new containing block. The escape-hatch raise lives in card.css beside the card's. */
.fdy-list__row:has(.fdy-btn--stretch){position:relative;}
.fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
/* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
 * of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
 * <li>s, a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
.fdy-list__row + .fdy-list__row,
.fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
/* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
 * touching the list surface (same contract as .fdy-card--button). */
.fdy-list__row--button{width:100%;text-align:inherit;color:inherit;font:inherit;background:none;border:0;appearance:none;-webkit-appearance:none;cursor:pointer;}
.fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
.fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
.fdy-list__row--interactive{cursor:pointer;}
/* Disabled row. The reset above adopts the UA button box, but not its disabled state, so a row
   disabled mid-flight (a redirect in progress, say) kept lighting up and kept a pointer cursor:
   a control answering the pointer while refusing input. Same contract as every other disabled
   control in the kit (dim + not-allowed), hover withdrawn, both the native and the aria- form. */
.fdy-list__row--button:disabled,.fdy-list__row--button[aria-disabled="true"],.fdy-list__row--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
.fdy-list__row--button:disabled:hover,.fdy-list__row--button[aria-disabled="true"]:hover,.fdy-list__row--interactive[aria-disabled="true"]:hover{background:none;}
/* Row internals: a title/meta stack that truncates, and a trailing slot pinned right. */
.fdy-list__main{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;flex:1;}
.fdy-list__title{font-family:var(--font-body);font-size:var(--text-base);font-weight:var(--weight-medium);letter-spacing:normal;color:var(--color-text);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fdy-list__meta{margin:0;font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;}
.fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
/* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */

/* Freeday. Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
.fdy-menu-wrap{position:relative;display:inline-flex;}
.fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3),var(--surface-inset);}
.fdy-menu[hidden]{display:none;}
.fdy-menu--end{left:auto;right:0;}
.fdy-menu__item{display:flex;align-items:center;gap:var(--space-2);width:100%;padding:var(--space-2) var(--space-3);border:0;background:transparent;border-radius:var(--radius-sm);font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);text-align:left;cursor:pointer;white-space:nowrap;}
.fdy-menu__item:hover,.fdy-menu__item.is-highlighted{background:var(--color-surface-3);}
/* Focus is not hover. The enhancer moves real DOM focus (freeday-menu.js `.focus()`), so this is
   the only thing a keyboard user has to go on, with the hover fill alone, arrowing through the
   menu is invisible. Same ring as .fdy-nav__item, which got this right. Reported twice. */
.fdy-menu__item:focus-visible{outline:none;background:var(--color-surface-3);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb),.45);}
.fdy-menu__item:disabled{opacity:.5;cursor:not-allowed;background:transparent;}
.fdy-menu__item--danger{color:var(--color-danger-strong);}
.fdy-menu__item svg{display:block;width:1rem;height:1rem;flex:none;}
.fdy-menu__sep{height:0;margin:var(--space-1) 0;border:0;border-top:var(--bw) solid var(--color-border-muted);}
@media (prefers-reduced-motion:no-preference){.fdy-menu:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}

/* Split button, main action + attached menu toggle (caret) */
.fdy-btn-split{position:relative;display:inline-flex;border-radius:var(--radius-md);transition:transform var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
/* One control: the two halves must stay coplanar. Neutralize each segment's own
   lift + drop-shadow (they would misalign the seam / glow a single half); the
   split lifts and casts its elevation as a single unit instead. */
.fdy-btn-split > .fdy-btn{transform:none;box-shadow:inset 0 1px 0 rgba(255,255,255,.26);}
.fdy-btn-split > .fdy-btn:first-child{border-top-right-radius:0;border-bottom-right-radius:0;}
.fdy-btn-split__toggle{padding:0 var(--space-2);margin-left:calc(var(--bw) * -1);border-top-left-radius:0;border-bottom-left-radius:0;}
/* Hairline divider between the halves (outranks the segment rule above). */
.fdy-btn-split > .fdy-btn-split__toggle{box-shadow:inset 0 1px 0 rgba(255,255,255,.26),inset 1px 0 0 rgba(0,0,0,.16);}
.fdy-btn-split:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px rgba(var(--color-primary-rgb),.55);}
.fdy-btn-split:active{transform:translateY(1px);box-shadow:0 1px 4px -1px rgba(var(--color-primary-rgb),.46);}
.fdy-btn-split__toggle:hover,.fdy-btn-split__toggle:focus-visible{z-index:1;}
.fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}

/* Freeday. Modal (native <dialog>) */
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));max-height:calc(100dvh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-lift-hover),var(--surface-inset);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);/* Longhands, not the `transition` shorthand. A shorthand carrying one keyword the engine does not know is INVALID AT PARSE, so the whole declaration is thrown away -- and `allow-discrete` is unknown below Safari 17.4, which is under this kit's floor. The result was a modal and a drawer that opened and closed with no animation at all on an iPhone 7. Split this way only `transition-behavior` is dropped there, and the opacity/transform half survives. The lists align BY INDEX and reproduce the shorthand exactly, including `ease` for the discrete pair and `normal` for the continuous one -- verified by comparing computed styles before and after (#097). */transition-property:opacity,transform,overlay,display;transition-duration:var(--dur-base);transition-timing-function:var(--ease-spring),var(--ease-spring),ease,ease;transition-behavior:normal,normal,allow-discrete,allow-discrete;}
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
.fdy-modal--wide{width:min(60rem,calc(100vw - var(--space-8)));}
.fdy-modal::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition-property:opacity,overlay,display;transition-duration:var(--dur-base);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
.fdy-modal__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
.fdy-modal__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;}
.fdy-modal__close{border:0;background:transparent;color:var(--color-text-muted);font-size:var(--text-xl);line-height:1;cursor:pointer;padding:var(--space-1);border-radius:var(--radius-sm);}
.fdy-modal__close:hover{background:var(--color-surface-2);color:var(--color-text);}
/* `1 1 auto`, NOT `flex:1`. The shorthand's basis is 0%, and `.fdy-modal` has only a max-height, so
 * the percentage resolves against an indefinite height. Current engines read that as `content`;
 * older WebKit read it as 0, sized the dialog to header + footer, and left the body its 40px of
 * padding, with the form overflowing inside it. That was every modal on an iPhone 7 (iOS 15),
 * reported from a real device (CATATAN-FREEDAY §16). A 0px basis reproduces it in Chrome to the
 * pixel. `auto` lays out identically in current engines (measured: 498.7px dialog, 355.7px body,
 * both readings) and leaves no indefinite percentage for any engine to read. min-height:0 is
 * still what lets a long body shrink and scroll once the dialog reaches its max-height. */
.fdy-modal__body{flex:1 1 auto;min-height:0;padding:var(--space-5);overflow:auto;}
.fdy-modal__footer{flex:none;display:flex;gap:var(--space-2);justify-content:flex-end;padding:var(--space-4) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:var(--color-surface-2);}
/* Symmetric enter AND exit (fade + rise + subtle scale) on the native <dialog>. @starting-style
 * animates the entry; overlay/display allow-discrete keep it painted through the exit so it
 * animates out on close too. Reduced motion honored by the global duration reset in base.css. */
.fdy-modal[open]{display:flex;opacity:1;transform:none;}
.fdy-modal[open]::backdrop{opacity:1;}
@starting-style{
  .fdy-modal[open]{opacity:0;transform:translateY(8px) scale(.98);}
  .fdy-modal[open]::backdrop{opacity:0;}
}

/* Freeday. Pagination */
.fdy-pagination__list{display:flex;align-items:center;gap:var(--space-1);list-style:none;margin:0;padding:0;}
.fdy-pagination__link{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;padding:0 var(--space-2);border-radius:var(--radius-md);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);background:transparent;border:var(--bw) solid transparent;text-decoration:none;cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
.fdy-pagination__link:hover{background:var(--color-surface-2);border-color:var(--color-border);}
.fdy-pagination__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-pagination__link[aria-current="page"]{background:var(--color-primary);color:var(--color-on-primary);}
.fdy-pagination__link[aria-disabled="true"],.fdy-pagination__link:disabled{opacity:.5;cursor:not-allowed;pointer-events:none;}
.fdy-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-subtle);}

/* Freeday. Progress */
.fdy-progress{height:.5rem;border-radius:var(--radius-full);background:var(--color-surface-3);overflow:hidden;}
.fdy-progress__bar{height:100%;border-radius:inherit;background:var(--color-primary);transition:width var(--dur-base) var(--ease-standard);}
.fdy-progress--indeterminate{position:relative;}
.fdy-progress--indeterminate .fdy-progress__bar{width:40%;animation:fdy-progress-indet 1.2s var(--ease-standard) infinite;}
@keyframes fdy-progress-indet{0%{margin-left:-40%;}100%{margin-left:100%;}}
@media (prefers-reduced-motion:reduce){.fdy-progress--indeterminate .fdy-progress__bar{animation:none;width:100%;opacity:.5;}}

/* Freeday. Rating (star rating). Interactive = native radio group (arrow keys, form-assoc);
 * filled vs empty differ by fill-vs-outline (not colour alone). Enhanced by freeday-rating.js. */
.fdy-rating{display:inline-flex;gap:var(--space-1);}
.fdy-rating__star{display:inline-flex;line-height:0;cursor:pointer;}
.fdy-rating__star input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
.fdy-rating__star svg{width:1.5rem;height:1.5rem;fill:none;stroke:var(--color-border-strong);stroke-width:1.5;transition:fill var(--dur-fast) var(--ease-standard),stroke var(--dur-fast) var(--ease-standard);}
.fdy-rating__star.is-filled svg{fill:var(--color-warning);stroke:var(--color-warning);}
.fdy-rating__star input:focus-visible + svg{outline:2px solid var(--color-primary);outline-offset:2px;border-radius:var(--radius-xs);}
.fdy-rating--readonly .fdy-rating__star{cursor:default;}
/* Disabled: not editable and greyed (readonly stays full-strength; disabled dims). */
.fdy-rating--disabled .fdy-rating__star{cursor:not-allowed;}
.fdy-rating--disabled .fdy-rating__star svg{opacity:.5;}
/* Invalid: tint the (outline) empty stars danger so a required-but-empty rating reads as an error,
   paired with a help/error message, never colour alone. */
.fdy-rating--error .fdy-rating__star svg{stroke:var(--color-danger);}
.fdy-rating--sm .fdy-rating__star svg{width:1.1rem;height:1.1rem;}

/* Freeday. Selection controls */
/* `flex:none` on every box, track and standalone checkbox is load-bearing, not tidiness.
   `.fdy-check` is an inline-flex row of [box, label], and a flex item with a WIDTH is still
   shrinkable, so as soon as the label wrapped to a second line the box gave up horizontal space
   to it and rendered an 18px-tall rectangle 13px wide. Squashed, off-square, and different from
   every sibling whose label happened to be short: a form of five checkboxes showed three sizes.
   Nothing in the declarations looked wrong, because the width was never overridden, it was
   negotiated away. */
.fdy-check,.fdy-radio,.fdy-switch{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
.fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
.fdy-check input{border-radius:var(--radius-sm);}
.fdy-radio input{border-radius:50%;}
.fdy-check input::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
.fdy-radio input::before{content:"";width:.55rem;height:.55rem;border-radius:50%;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);}
.fdy-check input:checked,.fdy-radio input:checked{background:var(--color-primary);border-color:var(--color-primary);}
.fdy-check input:checked::before,.fdy-radio input:checked::before{transform:scale(1);}
.fdy-check input:focus-visible,.fdy-radio input:focus-visible,.fdy-switch input:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-check input:disabled,.fdy-radio input:disabled,.fdy-switch input:disabled{opacity:.5;cursor:not-allowed;}
.fdy-check:has(input:disabled),.fdy-radio:has(input:disabled),.fdy-switch:has(input:disabled){cursor:not-allowed;}
.fdy-switch input{appearance:none;-webkit-appearance:none;flex:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
.fdy-switch input::before{content:"";position:absolute;top:2px;left:2px;width:calc(var(--control-switch-h) - 4px);height:calc(var(--control-switch-h) - 4px);border-radius:50%;background:var(--color-switch-thumb);box-shadow:var(--shadow-1);transition:transform var(--dur-base) var(--ease-spring);}
.fdy-switch input:checked{background:var(--color-primary);}
.fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
/* State hint (#068): one line saying what the CURRENT state means, never "on means X, off means Y",
   which leaves the reader to work out which half applies. The control's aria-describedby points at
   the .fdy-help WRAPPING both lines: a hidden child of a referenced node is skipped, so a screen
   reader hears only the line on screen. Point it at the two children and both are read. */
.fdy-field:has(:is(.fdy-switch,.fdy-check) input:checked) [data-fdy-when="off"],
.fdy-field:not(:has(:is(.fdy-switch,.fdy-check) input:checked)) [data-fdy-when="on"]{display:none;}

/* Standalone checkbox (e.g. table row/select-all), supports :indeterminate */
.fdy-checkbox{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
.fdy-checkbox::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
.fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
.fdy-checkbox:checked::before{transform:scale(1);}
.fdy-checkbox:indeterminate::before{width:.6rem;height:2px;transform:scale(1);clip-path:none;box-shadow:none;background:var(--color-on-primary);}
.fdy-checkbox:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-checkbox:disabled{opacity:.5;cursor:not-allowed;}

/* Freeday. Skeleton */
.fdy-skeleton{display:block;border-radius:var(--radius-sm);background:var(--color-surface-3);background-image:linear-gradient(90deg,transparent,rgba(var(--color-surface-rgb),.6),transparent);background-size:200% 100%;background-repeat:no-repeat;animation:fdy-skeleton 1.4s ease-in-out infinite;}
.fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
.fdy-skeleton--title{height:1.2rem;width:40%;}
.fdy-skeleton--circle{border-radius:var(--radius-full);}
/* Size-matched to .fdy-avatar (2.5rem) and its --sm/--lg, so a skeleton reserves the exact avatar
 * box and data landing causes no layout shift (a plain --circle has no dimensions of its own). */
.fdy-skeleton--avatar{width:2.5rem;height:2.5rem;border-radius:var(--radius-full);}
.fdy-skeleton--avatar-sm{width:2rem;height:2rem;border-radius:var(--radius-full);}
.fdy-skeleton--avatar-lg{width:3.5rem;height:3.5rem;border-radius:var(--radius-full);}
@keyframes fdy-skeleton{to{background-position:-200% 0;}}
@media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}

/* Freeday. Slider (native <input type="range"> themed via accent-color: keeps native
 * keyboard + ARIA, filled track and thumb come free). Optional value output + scale.
 * freeday-slider.js just mirrors the value into an <output for="…">. */
.fdy-slider{width:100%;max-width:22rem;height:1.5rem;accent-color:var(--color-primary);cursor:pointer;}
.fdy-slider:disabled{cursor:not-allowed;opacity:.6;}
.fdy-slider--accent{accent-color:var(--color-accent);}
.fdy-slider--danger{accent-color:var(--color-danger);}

.fdy-slider-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
.fdy-slider-field__head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3);}
.fdy-slider__value{font-family:var(--font-mono);font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-primary-strong);font-variant-numeric:tabular-nums;}
.fdy-slider__scale{display:flex;justify-content:space-between;font-size:var(--text-xs);color:var(--color-text-muted);font-variant-numeric:tabular-nums;}

/* Freeday. Sortable list (freeday-sortable.js). The kit styles the handle and the drag states only;
 * the list itself is whatever it already was: .fdy-list, a table body, plain rows.
 *
 * The handle is a control-height square so a thumb can hold it on a phone, and `touch-action:none`
 * keeps that drag from scrolling the page. Its glyph is the only thing marking it as a control, so
 * it takes --color-text-muted, not --color-text-subtle: a non-text control still owes 3:1. */
.fdy-sortable__handle{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--control-h);height:var(--control-h);padding:0;border:0;border-radius:var(--radius-md);background:transparent;color:var(--color-text-muted);cursor:grab;touch-action:none;}
.fdy-sortable__handle:hover{background:var(--color-surface-2);color:var(--color-text);}
.fdy-sortable__handle:active{cursor:grabbing;}
.fdy-sortable__handle[aria-disabled="true"]{cursor:not-allowed;opacity:.4;}
.fdy-sortable__handle[aria-disabled="true"]:hover{background:transparent;color:var(--color-text-muted);}
.fdy-sortable.is-sorting{-webkit-user-select:none;user-select:none;}
.fdy-sortable > .is-dragging{opacity:.45;}
/* The landing line. On a table row it is drawn on the cells: a box-shadow on a <tr> is not painted
 * by every engine the kit supports. */
.fdy-sortable > :not(tr).is-drop-before,.fdy-sortable > tr.is-drop-before > *{box-shadow:inset 0 var(--focus-ring-width) 0 var(--color-primary);}
.fdy-sortable > :not(tr).is-drop-after,.fdy-sortable > tr.is-drop-after > *{box-shadow:inset 0 calc(var(--focus-ring-width) * -1) 0 var(--color-primary);}

/* Freeday. Spinner */
.fdy-spinner{display:inline-block;width:1.5rem;height:1.5rem;border:2.5px solid var(--color-border-strong);border-top-color:var(--color-primary);border-radius:var(--radius-full);animation:fdy-spin .7s linear infinite;}
.fdy-spinner--sm{width:1rem;height:1rem;border-width:2px;}
.fdy-spinner--lg{width:2.5rem;height:2.5rem;border-width:3px;}
@keyframes fdy-spin{to{transform:rotate(360deg);}}
@media (prefers-reduced-motion:reduce){.fdy-spinner{animation-duration:2s;}}

/* Freeday. States (empty / loading / error placeholders) */
.fdy-state{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-2);padding:var(--space-10) var(--space-6);}
.fdy-state__icon{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;color:var(--color-text-subtle);}
.fdy-state__icon svg{width:100%;height:100%;}
.fdy-state__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);margin:0;}
.fdy-state__text{margin:0;font-size:var(--text-sm);color:var(--color-text-muted);max-width:24rem;line-height:var(--leading-normal);}
.fdy-state__actions{margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap;justify-content:center;}
.fdy-state--danger .fdy-state__icon{color:var(--color-danger-strong);}
/* Compact (#069): the same state inside a small card, one line of icon and text instead of a
   page-sized block. Danger matters most here: a widget that renders zero for a failed request
   reads as "nothing to do", the opposite of what happened. */
.fdy-state--compact{flex-direction:row;align-items:flex-start;text-align:left;gap:var(--space-2);padding:var(--space-2) 0;}
.fdy-state--compact .fdy-state__icon{width:1rem;height:1rem;flex:none;}
.fdy-state--compact .fdy-state__text{font-size:var(--text-xs);max-width:none;}
.fdy-state--compact .fdy-state__actions{margin:0 0 0 auto;}

/* Freeday. Stepper / Wizard (progress header + optional panels).
 * Enhanced by freeday-stepper.js for prev/next navigation. */
.fdy-stepper{display:flex;list-style:none;margin:0;padding:0;}
.fdy-step{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;text-align:center;min-width:0;}
/* connector from the previous marker's centre to this one (behind the markers) */
.fdy-step:not(:first-child)::before{content:"";position:absolute;top:.875rem;left:-50%;width:100%;height:2px;transform:translateY(-50%);background:var(--color-border-strong);z-index:0;}
.fdy-step.is-active::before,.fdy-step.is-complete::before{background:var(--color-primary);}
.fdy-step__btn{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:0 var(--space-2);background:none;border:0;font:inherit;color:inherit;cursor:pointer;max-width:100%;}
.fdy-step__btn:disabled{cursor:default;}
/* The marker carries the lift itself, NOT `__btn`. The connector above is position:absolute with
   z-index:0, and a positioned box paints after in-flow inline content in the same stacking context,
   so a marker that is merely inline (an indicator-only stepper with no `__btn`) gets painted THROUGH
   by the connector line. Lifting the part that must always exist makes that assembly impossible;
   `__btn`'s own z-index stays, harmlessly, for the navigable case. */
.fdy-step__marker{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--radius-full);background:var(--color-surface);border:2px solid var(--color-border-strong);color:var(--color-text-muted);font-size:var(--text-sm);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;}
.fdy-step__marker svg{display:block;width:1rem;height:1rem;}
.fdy-step.is-active .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
.fdy-step.is-complete .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary);color:var(--color-on-primary);}
.fdy-step.is-error .fdy-step__marker{border-color:var(--color-danger);background:var(--color-danger-btn);color:var(--color-on-danger);}
/* The error badge. Colour alone cannot carry "this step has a problem" — the marker already turns
   red and that is exactly the signal a colour-blind reader loses — so the badge holds a COUNT and
   the step names it in text. Positioned against `.fdy-step` rather than `__btn`, because the
   indicator-only variant has no `__btn` and the badge has to land in the same place in both.
   It is a SIBLING of the marker, never a child: the enhancer rewrites `__marker.innerHTML` on
   every render, so a badge inside it would be destroyed the first time the reader moved a step. */
.fdy-step__badge{position:absolute;top:0;left:50%;z-index:2;transform:translate(.45rem,-.3rem);min-width:1.05rem;height:1.05rem;padding:0 .25rem;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-full);background:var(--color-danger-btn);color:var(--color-on-danger);font-size:var(--text-xs);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;line-height:1;}
.fdy-step__label{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
.fdy-step.is-error .fdy-step__label{color:var(--color-danger-strong);}
/* While a guard is deciding, the whole control is spoken for: the nav buttons are disabled by the
   enhancer, and this dims the header so a second click has nothing to aim at. */
.fdy-stepper[aria-busy="true"]{opacity:.72;}
.fdy-step.is-active .fdy-step__label{color:var(--color-text);font-weight:var(--weight-semibold);}
.fdy-step__btn:focus-visible{outline:none;}
.fdy-step__btn:focus-visible .fdy-step__marker{box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.3);}

.fdy-step-panels{margin-top:var(--space-5);}
.fdy-step-nav{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-5);}

/* Freeday. Table */
/* position:relative on every scroller/clipper, it makes the box the containing block for its own
   absolutely positioned content (.fdy-visually-hidden above all), which `overflow` alone does NOT
   clip. Without it a hidden label in a wide table scrolls the whole page. See base.css. */
.fdy-table-wrap{position:relative;overflow-x:auto;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);}
.fdy-table{width:100%;border-collapse:collapse;font-size:var(--text-sm);}
.fdy-table caption{text-align:left;padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--color-text);}
.fdy-table th{text-align:left;padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:var(--weight-semibold);text-transform:uppercase;letter-spacing:var(--tracking-wide);color:var(--color-text-muted);background:var(--color-surface-2);border-bottom:var(--bw) solid var(--color-border);white-space:nowrap;}
.fdy-table td{padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);color:var(--color-text);}
.fdy-table tbody tr:last-child td{border-bottom:0;}
.fdy-table tbody tr{transition:background-color var(--dur-fast) var(--ease-standard);}
/* Zebra rows. Sits BEFORE :hover and [aria-selected] on purpose: all three land on the same
   specificity (0,2,2), so source order is what decides, and a stripe that won would make hover
   invisible on every second row. The tint is HALF a surface step rather than --color-surface-2
   itself, for the same reason: hover paints the full step, so the two must not be the same colour.
   Exposed as a Tier-3 token through the var() FALLBACK, never as a declaration — a custom property
   set here would outrank a host's own :root override.
   The tint arrives as --color-surface-stripe, already flattened, rather than as a color-mix() of
   two surfaces. A custom property cannot carry color-mix(): the `background` that finally reads it
   contains var(), so on Safari <= 16.1 it failed at computed-value time and the stripe vanished,
   and a preceding declaration is never consulted in that path. The token is also OPAQUE on
   purpose, which the frozen column below depends on. */
/* The resting colour is also published as --_fdy-row-rest, so a row that must not answer the
   pointer can hover back to exactly what it was (see --hover-actionable). */
.fdy-table--striped tbody tr:nth-child(even){--_fdy-row-rest:var(--fdy-table-stripe,var(--color-surface-stripe));background:var(--_fdy-row-rest);}
.fdy-table tbody tr:hover{background:var(--_fdy-row-hover,var(--color-surface-2));}
/* Opt-in (#064): only a row that DOES something answers the pointer. A tint on a row with no click
   handler promises a target that is not there: people click the row, nothing happens, and the
   button they had to aim at was elsewhere in it. Striping, not hover, is what keeps the eye on a
   line. The inert row's hover resolves to its own resting colour, so a stripe survives it; a frozen
   cell falls back to its opaque surface, because it scrolls over its neighbours. */
.fdy-table--hover-actionable tbody tr:not(.fdy-table__row--activatable){--_fdy-row-hover:var(--_fdy-row-rest,transparent);}
.fdy-table--hover-actionable tbody tr:not(.fdy-table__row--activatable) td.fdy-table__freeze{--_fdy-row-hover:var(--_fdy-row-rest,var(--color-surface));}
/* Monospace data, ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
   or inline (`<span class="fdy-mono">`), not just numeric columns. */
.fdy-mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums;}
/* Numeric cells are mono + right-aligned. Kept self-contained (not composed from .fdy-mono)
   so existing single-class markup keeps working. */
.fdy-table__num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--font-mono);}
/* Loading / empty state cell (FdyTable spans it across all columns). */
.fdy-table__state{text-align:center;color:var(--color-text-muted);padding:var(--space-6) var(--space-4);}
/* Activatable row (FdyTable rowActivatable): pointer + keyboard focus ring. The hover tint comes from
   the base `.fdy-table tbody tr:hover` rule, which --hover-actionable keeps for these rows only. */
.fdy-table__row--activatable{cursor:pointer;}
.fdy-table__row--activatable:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;}
/* Expandable detail row (FdyTable row-detail slot): a muted panel under its row, no hover tint. */
.fdy-table__detailrow>td,.fdy-table__detailrow:hover>td{background:var(--color-surface-2);}
.fdy-table th[aria-sort]{cursor:pointer;}
.fdy-table th[aria-sort="ascending"]::after{content:" ↑";color:var(--color-primary-strong);}
.fdy-table th[aria-sort="descending"]::after{content:" ↓";color:var(--color-primary-strong);}

/* Sortable header button (accessible click target inside th) */
.fdy-table__sortbtn{appearance:none;border:0;background:transparent;font:inherit;color:inherit;text-transform:inherit;letter-spacing:inherit;font-weight:inherit;cursor:pointer;padding:0;}
.fdy-table__sortbtn:hover{color:var(--color-text);}
.fdy-table__sortbtn:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}

/* Selection column + selected row */
.fdy-table__selcol{width:1px;white-space:nowrap;}
.fdy-table tbody tr[aria-selected="true"],.fdy-table tbody tr[aria-selected="true"]:hover{background:var(--color-primary-soft);}

/* Frozen header row and/or first column, a rate matrix, a timetable, any grid read
   against two axes at once.

   This modifier exists because `position:sticky` alone is NOT enough. With
   `border-collapse:collapse` the shared border belongs to the TABLE, not to either
   cell, so a frozen cell scrolls out from under its own rule and the frozen column
   loses the line that separates it from the data. Separate borders put each border
   back on the cell that sticks. */
.fdy-table--sticky{border-collapse:separate;border-spacing:0;}
.fdy-table--sticky th,.fdy-table--sticky td{border-bottom:var(--bw) solid var(--color-border-muted);}

/* Freezing the HEADER ROW is its own modifier because it needs a vertical
   scrollport (`.fdy-table-scroll--frozen`). A table that only freezes columns
   scrolls with the PAGE, and `top:0` against the page sticks the header under
   the viewport edge, over the app's own top bar, which is worse than not
   freezing it. Ask for the axis you actually have. */
.fdy-table--sticky-head thead th{position:sticky;top:0;z-index:2;border-bottom:var(--bw) solid var(--color-border);}

/* Freezing a COLUMN: put `.fdy-table__freeze` on every cell of it, header
   included, and give each frozen column its own `--fdy-freeze-left`. 0 for the
   first, the summed widths of the frozen columns left of it after that. One
   frozen column needs no variable at all. Mark the last one `--edge` so the
   reader can see where the frozen block ends.

   The offsets are the caller's job on purpose: they depend on rendered column
   widths, which CSS cannot sum, and hard-coding them here would only work for
   tables whose columns are all the same width. */
.fdy-table--sticky .fdy-table__freeze{position:sticky;left:var(--fdy-freeze-left,0);}
/* A frozen body cell slides OVER the data, so it must be OPAQUE, otherwise the
   cells beneath show through it. A `th` already carries the header tint from the
   base rule, and that tint is usually the whole point: it is what marks a column
   as identity rather than data. So only a `td` is given a surface here. A table
   that wants its frozen `th` to read as a body row instead, a rate matrix, where
   a hovered row should band unbroken across the freeze line, says so itself. */
.fdy-table--sticky tbody .fdy-table__freeze{z-index:1;}
.fdy-table--sticky tbody td.fdy-table__freeze{background:var(--color-surface);}
/* A frozen cell has to paint an OPAQUE background of its own (it scrolls over its neighbours), so
   on a striped table it would cut a plain column straight through the zebra. Repaint it with the
   stripe — and deliberately WITHOUT `.fdy-table--sticky` in the selector, which would push this to
   (0,4,3) and beat the hover rule below. At (0,3,3) it ties, and the later rule wins, so a hovered
   row still bands unbroken across the freeze line. */
.fdy-table--striped tbody tr:nth-child(even) td.fdy-table__freeze{background:var(--fdy-table-stripe,var(--color-surface-stripe));}
.fdy-table--sticky tbody tr:hover td.fdy-table__freeze{background:var(--_fdy-row-hover,var(--color-surface-2));}
/* A frozen header cell is stuck on both axes wherever the head is frozen too, so
   it has to outrank both. */
.fdy-table--sticky thead .fdy-table__freeze{z-index:3;}
.fdy-table--sticky .fdy-table__freeze--edge{border-right:var(--bw) solid var(--color-border);}

/* The scrollport a sticky cell sticks to. Both axes scroll here, and the height is a
   custom property so a page can size it without out-specifying this rule. */
.fdy-table-scroll--frozen{position:relative;overflow:auto;max-height:var(--fdy-table-frozen-h,30rem);}

/* Data-table shell (toolbar + scroll + footer stay put while the table scrolls) */
.fdy-datatable{border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);overflow:hidden;}
.fdy-table-scroll{position:relative;overflow-x:auto;}
.fdy-table-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border);}
.fdy-table-toolbar__search{max-width:18rem;}
.fdy-table-toolbar__spacer{flex:1;}
.fdy-table-toolbar__count{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;}
.fdy-table-footer{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-top:var(--bw) solid var(--color-border);}
.fdy-table-footer__info{font-size:var(--text-sm);color:var(--color-text-muted);}
/* Rows per page. `margin-left:auto` eats the free space, so the size control and the pager travel
   together on the right while the range stays left, no wrapper element, which is what keeps the
   footer's DOM byte-identical for the tables that don't offer one (#008). The control is the kit's
   own `.fdy-combo`, never a native <select>: an OS menu is unthemeable, and on macOS it drops a
   dark panel into a light page, which is the reason FdyCombo exists at all. Sized to its content:
   `.fdy-combo` is display:block at max-width 22rem, which in a footer is a bar-wide box for two
   digits. Its HEIGHT is left alone on purpose, `--control-h`, the same as every other control on
   the page. It briefly carried 2.25rem to match the pager links beside it, which made the one
   combobox in a compact app 4px taller than all the others; a pager link is a nav button, and the
   thing this must agree with is the form controls. */
.fdy-table-footer__size{display:inline-flex;align-items:center;gap:var(--space-2);margin-left:auto;font-size:var(--text-sm);color:var(--color-text-muted);}
.fdy-table-footer__size .fdy-combo{width:5.5rem;}

/* Column filter, funnel button injected (enhancer) or rendered (FdyTable) into a filterable <th> */
.fdy-table__filterwrap{display:inline-flex;align-items:center;vertical-align:middle;}
.fdy-table__filterbtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-left:var(--space-1);padding:0;vertical-align:middle;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-subtle);cursor:pointer;}
.fdy-table__filterbtn:hover{background:var(--color-surface-3);color:var(--color-text);}
.fdy-table__filterbtn:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-table__filterbtn.is-active{color:var(--color-primary-strong);}
.fdy-table__filterbtn svg{width:.85rem;height:.85rem;}

/* Filter popover. `position:fixed` because the panel is positioned by freeday-popover.js (and by
   usePopover on the typed path) with inline viewport coordinates, in the top layer, which is what
   escapes a card's or a scroll container's clip; the panel itself is a child of its own <th> on
   both paths, so an open modal <dialog> does not make it inert. It is the one kit panel that is
   fixed rather than absolute, and that is load-bearing: #079 cannot reach a panel which never
   resolves a `top:calc(100% + …)` against the viewport, which browser/filter-parity.mjs measures.
   `min-width` is the panel's own, so neither path may floor it to the width of a 24px funnel.

   The typography block is a RESET of what the panel inherits from that <th>, and it has to name
   every inherited property the header rule sets, not the ones that looked worst. A header is
   uppercase, tracked, semibold, muted, 12px and `white-space:nowrap`; a numeric column adds mono
   and, on the typed path, an inline `text-align:right` that no ancestor rule could outrank anyway.
   None of that belongs on a panel of labels and inputs, and the four it reset from the start were
   the four that SHOWED: an 18rem cap hides a nowrap panel's damage, because the box stays 18rem
   wide while the text runs out of the list, which then scrolls sideways to reach a value nobody
   can read. `font-size` and `font-family` reach only the text that declares neither, the range
   separator and the title, which is exactly why they went unnoticed. Measured as geometry in
   browser/filter-wrap.mjs rather than asserted as declarations. */
.fdy-filter{position:fixed;z-index:130;min-width:14rem;max-width:18rem;padding:var(--space-3);background:var(--color-surface);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);text-align:left;white-space:normal;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-regular);text-transform:none;letter-spacing:normal;color:var(--color-text);}
.fdy-filter__title{font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:var(--tracking-wide);margin-bottom:var(--space-2);}
.fdy-filter__list{display:flex;flex-direction:column;gap:var(--space-1);max-height:12rem;overflow:auto;}
.fdy-filter__check{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
.fdy-filter__range{display:flex;align-items:center;gap:var(--space-2);}
.fdy-filter__range .fdy-input{width:6rem;}
.fdy-filter__foot{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-3);}

/* Bulk-action bar, appears above the table body once rows are selected */
.fdy-table-bulkbar{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-4);background:var(--color-primary-soft);border-bottom:var(--bw) solid var(--color-border);color:var(--color-primary-strong);font-size:var(--text-sm);}
.fdy-table-bulkbar[hidden]{display:none;}
.fdy-table-bulkbar__count{font-weight:var(--weight-semibold);white-space:nowrap;}
.fdy-table-bulkbar__spacer{flex:1;}
.fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}

/* Controls inside a data row. `.fdy-input` and friends are width:100%, which in an auto-layout
   table means they contribute NO intrinsic width, the column shrinks to whatever the header text
   needs and the control collapses with it. A NOTE column ends up a box too narrow to read what you
   typed into it. The floor is per-control rather than on the column, because the table does not
   know which of its columns hold controls. */
.fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}

/* Freeday. Tabs (WAI-ARIA APG) */
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
 *
 * The strip's own line is an INSET SHADOW, not a border, and the tab carries no negative margin
 * (#052). The pair those two replace — `border-bottom` here plus `margin-bottom:-1px` on the tab,
 * to lift the active tab's 2px underline onto the strip's 1px line so the two read as one — was
 * broken by the `overflow-x:auto` beside it, in two ways at once:
 *   1. CSS Overflow §3 gives no way to scroll one axis alone: `overflow-y` computes from `visible`
 *      to `auto` because the other axis is not visible, so the 1px the tab hangs below the content
 *      box became a real scrollport. Measured 47 client / 48 scroll, `scrollTop` settling at 1 —
 *      invisible in a screenshot, and a trackpad gesture over the strip is eaten by it.
 *   2. A scroll container clips its descendants to the padding box, so the pixel the negative
 *      margin pushed out was clipped away and the strip's own line painted in its place: the
 *      active underline rendered ONE pixel of primary, never the two it asks for.
 * An inset shadow paints on the box, not on the scrolled content, so the line stays put while the
 * tabs scroll sideways, and the tab's own border-bottom paints over it — same look, no overflow.
 * To hide the line, override `box-shadow`, not `border-bottom` (guarded in browser/tabs-strip.mjs). */
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);box-shadow:inset 0 calc(var(--bw) * -1) 0 var(--color-border);overflow-x:auto;}
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
.fdy-tabs__tab:hover{color:var(--color-text);}
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
/* `aria-current="page"` is honoured alongside `aria-selected="true"` so the tab look can be used for
   ROUTE-driven sub-navigation (/settings/profile · /settings/billing), where the tabs are real links.
   A link cannot be `aria-selected`, that is invalid ARIA on an anchor; `aria-current` is the only
   correct marker, and it is what a router sets. Use `.fdy-tabs` + `freeday-tabs.js` (roving tabindex,
   a panel per tab) for in-page tabs; use these classes on plain links for routed ones, and
   `.fdy-nav--horizontal` for an application's PRIMARY navigation. */
.fdy-tabs__tab[aria-selected="true"],.fdy-tabs__tab[aria-current="page"]{color:var(--color-primary-strong);border-bottom-color:var(--color-primary);}
.fdy-tabs__tab:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-tabs__panel{padding:var(--space-5) 0;}
.fdy-tabs__panel[hidden]{display:none;}
/* Reveal the newly-shown panel on tab switch (fade + rise), reduced-motion gated. */
@media (prefers-reduced-motion:no-preference){.fdy-tabs__panel:not([hidden]){animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}

/* VERTICAL VARIANT: the same tabs turned down the page, list on the leading side, panel beside it.
 *
 * Two markers, one each, and they are not interchangeable. The CLASS decides the look, because a
 * stylesheet cannot reach a parent from a child and the two-column box belongs to `.fdy-tabs`
 * itself. The ATTRIBUTE `aria-orientation="vertical"` on the tablist decides the keyboard, because
 * that is the thing an assistive technology announces and APG requires it on a vertical tablist
 * anyway. `freeday-tabs.js` writes the attribute when only the class is present, so an app states
 * the axis once and the two can never disagree.
 *
 * Selection is a SOFT FILL here, not a leading edge bar. Sideways, the marker is an underline under
 * the tab it belongs to; turned 90 degrees that line lands between two tabs and points at the wrong
 * one. The kit already has an answer for "one item in a vertical list is where you are" and spends
 * it in four places (`.fdy-nav__item[aria-current]`, the combo, cascade and autocomplete options):
 * `--color-primary-soft` behind `--color-primary-strong`. Both are precomputed tone tokens, so this
 * needs no color-mix() and no alpha of its own.
 *
 * What it deliberately does NOT do: pick a column width (set `--fdy-tabs-list-w`, default `auto`),
 * and collapse to one column on a narrow screen. Whether these tabs are a whole page, a card or a
 * drawer is the page's knowledge, not the component's, so the breakpoint is the page's to write. */
.fdy-tabs--vertical{display:flex;align-items:flex-start;gap:var(--space-5);}
/* `overflow:visible` on both axes, because the base strip scrolls sideways: a scroll container
   clips to its padding box, and a column has no sideways overflow to scroll, only a focus ring to
   lose. `box-shadow:none` drops the strip's own bottom line with it. */
.fdy-tabs--vertical .fdy-tabs__list{flex:none;flex-direction:column;width:var(--fdy-tabs-list-w,auto);overflow:visible;box-shadow:none;}
/* `white-space:normal` and `text-align:start` are corrections, not taste: a tab in a column of
   fixed width that may not wrap will overflow it, and a button centres its label by default. */
.fdy-tabs--vertical .fdy-tabs__tab{border-bottom:0;border-radius:var(--radius-md);text-align:start;white-space:normal;}
.fdy-tabs--vertical .fdy-tabs__tab:focus-visible{border-radius:var(--radius-md);}
.fdy-tabs--vertical .fdy-tabs__tab[aria-selected="true"],.fdy-tabs--vertical .fdy-tabs__tab[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
.fdy-tabs--vertical .fdy-tabs__panel{flex:1;min-width:0;padding:0;}

/* Freeday. Timeline (vertical event feed: dot markers on a connector line). Pure CSS. */
.fdy-timeline{list-style:none;margin:0;padding:0;}
.fdy-timeline__item{position:relative;padding:0 0 var(--space-5) var(--space-6);border-left:2px solid var(--color-border);}
.fdy-timeline__item:last-child{border-left-color:transparent;padding-bottom:0;}
.fdy-timeline__marker{position:absolute;left:0;top:.15rem;transform:translateX(-50%);width:.85rem;height:.85rem;border-radius:var(--radius-full);background:var(--color-primary);border:2px solid var(--color-surface);box-shadow:0 0 0 2px var(--color-border);}
.fdy-timeline__item--success .fdy-timeline__marker{background:var(--color-success);}
.fdy-timeline__item--warning .fdy-timeline__marker{background:var(--color-warning);}
.fdy-timeline__item--danger .fdy-timeline__marker{background:var(--color-danger);}
.fdy-timeline__item--muted .fdy-timeline__marker{background:var(--color-border-strong);}
.fdy-timeline__time{font-size:var(--text-xs);color:var(--color-text-muted);font-variant-numeric:tabular-nums;}
.fdy-timeline__title{margin:0;font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-text);}
.fdy-timeline__text{margin:var(--space-1) 0 0;font-size:var(--text-sm);color:var(--color-text-muted);line-height:var(--leading-normal);}

/* Freeday. Time picker (input-styled trigger + scrollable time-list popover).
 * Built by freeday-timepicker.js from an empty [data-fdy-timepicker] wrapper.
 * Trigger visual mirrors the date picker for a consistent control family. */
.fdy-timepicker{position:relative;display:inline-block;width:100%;max-width:11rem;}
.fdy-timepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
.fdy-timepicker__trigger:hover{border-color:var(--color-text-muted);}
.fdy-timepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-timepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
.fdy-timepicker--error .fdy-timepicker__trigger,.fdy-timepicker__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
.fdy-timepicker--error .fdy-timepicker__trigger:focus-visible,.fdy-timepicker--error .fdy-timepicker__trigger.is-open,.fdy-timepicker__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb),.26);}
.fdy-timepicker__trigger:disabled,.fdy-timepicker__trigger[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
.fdy-timepicker__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums;}
.fdy-timepicker__value--placeholder{color:var(--color-text-subtle);}
.fdy-timepicker__icon{flex:none;display:inline-flex;color:var(--color-text-subtle);}
.fdy-timepicker__icon svg{display:block;width:1.05rem;height:1.05rem;}

.fdy-timepicker__panel{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:100%;max-height:15rem;overflow-y:auto;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
.fdy-timepicker__panel[hidden]{display:none;}
.fdy-timepicker__panel:focus-visible{outline:none;}
.fdy-timepicker__opt{display:flex;align-items:center;padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);font-size:var(--text-sm);color:var(--color-text);font-variant-numeric:tabular-nums;cursor:pointer;white-space:nowrap;}
.fdy-timepicker__opt:hover,.fdy-timepicker__opt.is-active{background:var(--color-surface-3);}
.fdy-timepicker__opt[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
@media (prefers-reduced-motion:no-preference){.fdy-timepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}

/* Freeday. Toast (transient notification) */
/* The region is a POPOVER, not merely a high z-index.
   `.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER, a
   layer above the whole z-index universe, so no number here could ever beat it. A toast raised by
   an action taken inside a modal was painted behind that modal's backdrop: dimmed, unreadable, and
   most of it under the dialog itself. `freeday-toast.js` calls showPopover() as each toast is
   added, which puts the region in the top layer too, and last-in wins there.
   Everything after `pointer-events` is undoing the UA's [popover] defaults, it arrives with
   inset:0, auto margins, a border, padding, a Canvas background and overflow:auto, none of which a
   toast stack wants. Where showPopover() is unavailable the element is still an ordinary fixed
   block at z-index 200, which is exactly what it was before. */
.fdy-toast-region{position:fixed;z-index:200;inset:auto;bottom:var(--space-5);right:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);width:min(24rem,calc(100vw - var(--space-8)));height:auto;max-height:none;margin:0;padding:0;border:0;background:transparent;color:inherit;overflow:visible;pointer-events:none;}
/* A popover gets a ::backdrop as well; a toast must not dim the page behind it. */
.fdy-toast-region::backdrop{background:transparent;}
.fdy-toast{pointer-events:auto;display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);background:var(--color-inverse-surface);color:var(--color-inverse-text);box-shadow:var(--shadow-3);}
.fdy-toast__accent{flex:none;width:.25rem;align-self:stretch;border-radius:var(--radius-full);background:var(--color-primary);}
.fdy-toast--success .fdy-toast__accent{background:var(--color-success);}
.fdy-toast--warning .fdy-toast__accent{background:var(--color-warning);}
.fdy-toast--danger .fdy-toast__accent{background:var(--color-danger);}
.fdy-toast--info .fdy-toast__accent{background:var(--color-info);}
.fdy-toast__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);}
.fdy-toast__title{font-size:inherit;font-weight:var(--weight-semibold);margin:0;}
.fdy-toast__text{opacity:.88;line-height:var(--leading-normal);}
.fdy-toast__close{flex:none;border:0;background:transparent;color:var(--color-inverse-text);opacity:.7;cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
.fdy-toast__close:hover{opacity:1;}
.fdy-toast__close:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 3px rgba(var(--color-inverse-text-rgb),.4);}
.fdy-toast.is-leaving{opacity:0;transform:translateX(16px);transition:opacity var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
@media (prefers-reduced-motion:no-preference){.fdy-toast{animation:fdy-toast-in var(--dur-base) var(--ease-spring);}}
@keyframes fdy-toast-in{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}

/* Freeday. Tooltip (CSS-only, hover/focus) */
.fdy-tooltip-wrap{position:relative;display:inline-flex;}
.fdy-tooltip{position:absolute;bottom:calc(100% + var(--space-2));left:50%;z-index:150;max-width:16rem;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);font-size:var(--text-xs);font-weight:var(--weight-medium);line-height:var(--leading-snug);color:var(--color-inverse-text);background:var(--color-inverse-surface);box-shadow:var(--shadow-2);opacity:0;visibility:hidden;pointer-events:none;transform:translateX(-50%) translateY(4px);transition:opacity var(--dur-fast) var(--ease-standard),transform var(--dur-fast) var(--ease-standard);}
.fdy-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);border:.3rem solid transparent;border-top-color:var(--color-inverse-surface);}
.fdy-tooltip-wrap:hover .fdy-tooltip,.fdy-tooltip-wrap:focus-within .fdy-tooltip{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0);}
@media (prefers-reduced-motion:reduce){.fdy-tooltip{transition:none;transform:translateX(-50%);}}

/* Freeday. Tree view (hierarchy via nested native <details>: keyboard-toggle, zero-JS).
 * Branches expand/collapse; leaves are plain rows. Chevron + optional folder/file icons. */
.fdy-tree{list-style:none;margin:0;padding:0;font-size:var(--text-sm);}
.fdy-tree ul{list-style:none;margin:0;padding-left:var(--space-4);}
.fdy-tree__branch > summary,.fdy-tree__leaf{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);color:var(--color-text);}
.fdy-tree__branch > summary{list-style:none;cursor:pointer;}
.fdy-tree__branch > summary::-webkit-details-marker{display:none;}
.fdy-tree__branch > summary:hover,.fdy-tree__leaf:hover{background:var(--color-surface-2);}
.fdy-tree__branch > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px rgba(var(--color-primary-rgb),.45);}
.fdy-tree__chevron{flex:none;width:.9rem;height:.9rem;color:var(--color-text-subtle);transition:transform var(--dur-fast) var(--ease-standard);}
.fdy-tree__branch[open] > summary .fdy-tree__chevron{transform:rotate(90deg);}
.fdy-tree__icon{flex:none;width:1rem;height:1rem;color:var(--color-text-muted);}
.fdy-tree__leaf{padding-left:calc(var(--space-2) * 2 + .9rem);}
.fdy-tree__leaf .fdy-tree__icon{color:var(--color-text-subtle);}
/* Disabled row (branch summary or leaf): dimmed, no hover, not interactive. */
.fdy-tree__leaf[aria-disabled="true"],.fdy-tree__branch[aria-disabled="true"] > summary{opacity:.5;cursor:not-allowed;pointer-events:none;}

/* Checkbox variant, a selectable tree (cascading parent/child via freeday-tree.js).
 * A native <input class="fdy-checkbox"> sits at the start of every row; a partially
 * selected branch shows the native :indeterminate dash. The checkbox stops click
 * propagation, so toggling selection never expands/collapses the branch. */
.fdy-tree--checkbox .fdy-tree__check{flex:none;}
.fdy-tree--checkbox .fdy-tree__leaf{padding-left:calc(var(--space-2) + .9rem + var(--space-2));}
