/* @toc Reset */
/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */

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

html {
    font-family:
        system-ui,
        'Segoe UI',
        Roboto,
        Helvetica,
        Arial,
        sans-serif,
        'Apple Color Emoji',
        'Segoe UI Emoji';
    line-height: 1.15;
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
}

body {
    margin: 0;
}

b,
strong {
    font-weight: bolder;
}

code,
kbd,
samp,
pre {
    font-family:
        ui-monospace,
        SFMono-Regular,
        Consolas,
        'Liberation Mono',
        Menlo,
        monospace;
    font-size: 1em;
}

small {
    font-size: 80%;
}

sub,
sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
}

sub {
    bottom: -0.25em;
}

sup {
    top: -0.5em;
}

table {
    border-color: currentcolor;
}

button,
input,
optgroup,
select,
textarea {
    font-family: inherit;
    font-size: 100%;
    line-height: 1.15;
    margin: 0;
}

button,
[type='button'],
[type='reset'],
[type='submit'] {
    -webkit-appearance: button;
}

legend {
    padding: 0;
}

progress {
    vertical-align: baseline;
}

summary {
    display: list-item;
}

/* @toc Fonts */
/* @font-face for Geist + Geist Mono. Hand-written for now; TypographyService
 * will generate these by scanning the fonts folder. Files in public/fonts/. */

@font-face {
    font-family: 'Geist';
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/geist/Geist-Regular.ttf') format('truetype');
}

@font-face {
    font-family: 'Geist';
    font-weight: 700;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/geist/Geist-Bold.ttf') format('truetype');
}

@font-face {
    font-family: 'Geist';
    font-weight: 900;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/geist/Geist-Black.ttf') format('truetype');
}

@font-face {
    font-family: 'Geist Mono';
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/geist-mono/GeistMono-Regular.ttf') format('truetype');
}

@font-face {
    font-family: 'Geist Mono';
    font-weight: 700;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/geist-mono/GeistMono-Bold.ttf') format('truetype');
}

/* Client supplied 127 Saloon faces. They stay tenant local in use even though the bytes share the font catalog. */
@font-face {
    font-family: 'Goliad';
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/goliad/THGoliad-Clean.ttf') format('truetype');
}

@font-face {
    font-family: 'Rama';
    font-weight: 600;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/rama/rama-semibold.ttf') format('truetype');
}

/* ── #1247: the two self-hosted OFL families for Dante ────────────────────────────────────────────────
 * Declared in config/css/fonts.php (the manifest) and asserted against it by TypographyCatalogTest, so
 * these blocks and that file cannot drift apart. Self-hosted from public/fonts/ — NO runtime font CDN.
 *
 * `font-display: swap` throughout, matching Geist: text is readable in the fallback immediately and
 * re-renders when the face arrives. For a rare display face that is strictly better than a blocking
 * period where the heading is invisible.
 */

/* IM FELL Great Primer — RARE DISPLAY. Static 400 only, regular + italic: upstream ships no other weight,
   and a synthesised bold would smear letterforms whose irregularity is the entire point of the face. */
@font-face {
    font-family: 'IM FELL Great Primer';
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/im-fell-great-primer/IMFeGPrm28P.ttf') format('truetype');
}

@font-face {
    font-family: 'IM FELL Great Primer';
    font-weight: 400;
    font-style: italic;
    font-display: swap;
    src: url('/fonts/im-fell-great-primer/IMFeGPit28P.ttf') format('truetype');
}

/* Cormorant Garamond — EDITORIAL/LORE, VARIABLE on weight.
   `font-weight: 300 700` is a RANGE, not a typo: it tells the browser this one file covers the whole axis,
   so it interpolates any weight in between. Declaring a single number here would waste the variable file and
   make the browser synthesise everything else. The filename's [wght] brackets are literal — they are the
   upstream google/fonts name, kept so the bytes verify against source, and they are URL-safe unquoted. */
@font-face {
    font-family: 'Cormorant Garamond';
    font-weight: 300 700;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/cormorant-garamond/CormorantGaramond[wght].ttf') format('truetype-variations');
}

@font-face {
    font-family: 'Cormorant Garamond';
    font-weight: 300 700;
    font-style: italic;
    font-display: swap;
    src: url('/fonts/cormorant-garamond/CormorantGaramond-Italic[wght].ttf') format('truetype-variations');
}

/* Libre Baskerville is a resume-local serif. The catalog keeps its role null so global pickers do not offer it. */
@font-face {
    font-family: 'Libre Baskerville';
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/libre-baskerville/LibreBaskerville-Variable.ttf') format('truetype-variations');
}

/* Zilla Slab — EDITORIAL. The approved Iron Owl static bold face is shared so every appearance surface
   loads the same bytes; the tenant's legacy asset URL remains a compatibility alias. */
@font-face {
    font-family: 'Iron Owl Zilla Slab';
    font-weight: 700;
    font-style: normal;
    font-display: swap;
    src: url('/fonts/zilla-slab/zilla-slab-bold.woff2') format('woff2');
}

/* @toc Palettes */
:root {
    --palette-color-names: red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose;
    --palette-neutral-names: slate gray zinc neutral stone;
}
:root {
    --palette-amber-50: #fffbeb;
    --palette-amber-100: #fef3c7;
    --palette-amber-200: #fde68a;
    --palette-amber-300: #fcd34d;
    --palette-amber-400: #fbbf24;
    --palette-amber-500: #f59e0b;
    --palette-amber-600: #d97706;
    --palette-amber-700: #b45309;
    --palette-amber-800: #92400e;
    --palette-amber-900: #78350f;
    --palette-amber-950: #451a03;
}
:root {
    --palette-blue-50: #eff6ff;
    --palette-blue-100: #dbeafe;
    --palette-blue-200: #bfdbfe;
    --palette-blue-300: #93c5fd;
    --palette-blue-400: #60a5fa;
    --palette-blue-500: #3b82f6;
    --palette-blue-600: #2563eb;
    --palette-blue-700: #1d4ed8;
    --palette-blue-800: #1e40af;
    --palette-blue-900: #1e3a8a;
    --palette-blue-950: #172554;
}
:root {
    --palette-cyan-50: #ecfeff;
    --palette-cyan-100: #cffafe;
    --palette-cyan-200: #a5f3fc;
    --palette-cyan-300: #67e8f9;
    --palette-cyan-400: #22d3ee;
    --palette-cyan-500: #06b6d4;
    --palette-cyan-600: #0891b2;
    --palette-cyan-700: #0e7490;
    --palette-cyan-800: #155e75;
    --palette-cyan-900: #164e63;
    --palette-cyan-950: #083344;
}
:root {
    --palette-emerald-50: #ecfdf5;
    --palette-emerald-100: #d1fae5;
    --palette-emerald-200: #a7f3d0;
    --palette-emerald-300: #6ee7b7;
    --palette-emerald-400: #34d399;
    --palette-emerald-500: #10b981;
    --palette-emerald-600: #059669;
    --palette-emerald-700: #047857;
    --palette-emerald-800: #065f46;
    --palette-emerald-900: #064e3b;
    --palette-emerald-950: #022c22;
}
:root {
    --palette-fuchsia-50: #fdf4ff;
    --palette-fuchsia-100: #fae8ff;
    --palette-fuchsia-200: #f5d0fe;
    --palette-fuchsia-300: #f0abfc;
    --palette-fuchsia-400: #e879f9;
    --palette-fuchsia-500: #d946ef;
    --palette-fuchsia-600: #c026d3;
    --palette-fuchsia-700: #a21caf;
    --palette-fuchsia-800: #86198f;
    --palette-fuchsia-900: #701a75;
    --palette-fuchsia-950: #4a044e;
}
:root {
    --palette-gray-50: #f9fafb;
    --palette-gray-100: #f3f4f6;
    --palette-gray-200: #e5e7eb;
    --palette-gray-300: #d1d5db;
    --palette-gray-400: #9ca3af;
    --palette-gray-500: #6b7280;
    --palette-gray-600: #4b5563;
    --palette-gray-700: #374151;
    --palette-gray-800: #1f2937;
    --palette-gray-900: #111827;
    --palette-gray-950: #030712;
}
:root {
    --palette-green-50: #f0fdf4;
    --palette-green-100: #dcfce7;
    --palette-green-200: #bbf7d0;
    --palette-green-300: #86efac;
    --palette-green-400: #4ade80;
    --palette-green-500: #22c55e;
    --palette-green-600: #16a34a;
    --palette-green-700: #15803d;
    --palette-green-800: #166534;
    --palette-green-900: #14532d;
    --palette-green-950: #052e16;
}
:root {
    --palette-indigo-50: #eef2ff;
    --palette-indigo-100: #e0e7ff;
    --palette-indigo-200: #c7d2fe;
    --palette-indigo-300: #a5b4fc;
    --palette-indigo-400: #818cf8;
    --palette-indigo-500: #6366f1;
    --palette-indigo-600: #4f46e5;
    --palette-indigo-700: #4338ca;
    --palette-indigo-800: #3730a3;
    --palette-indigo-900: #312e81;
    --palette-indigo-950: #1e1b4b;
}
:root {
    --palette-lime-50: #f7fee7;
    --palette-lime-100: #ecfccb;
    --palette-lime-200: #d9f99d;
    --palette-lime-300: #bef264;
    --palette-lime-400: #a3e635;
    --palette-lime-500: #84cc16;
    --palette-lime-600: #65a30d;
    --palette-lime-700: #4d7c0f;
    --palette-lime-800: #3f6212;
    --palette-lime-900: #365314;
    --palette-lime-950: #1a2e05;
}
:root {
    --palette-neutral-50: #fafafa;
    --palette-neutral-100: #f5f5f5;
    --palette-neutral-200: #e5e5e5;
    --palette-neutral-300: #d4d4d4;
    --palette-neutral-400: #a3a3a3;
    --palette-neutral-500: #737373;
    --palette-neutral-600: #525252;
    --palette-neutral-700: #404040;
    --palette-neutral-800: #262626;
    --palette-neutral-900: #171717;
    --palette-neutral-950: #0a0a0a;
}
:root {
    --palette-orange-50: #fff7ed;
    --palette-orange-100: #ffedd5;
    --palette-orange-200: #fed7aa;
    --palette-orange-300: #fdba74;
    --palette-orange-400: #fb923c;
    --palette-orange-500: #f97316;
    --palette-orange-600: #ea580c;
    --palette-orange-700: #c2410c;
    --palette-orange-800: #9a3412;
    --palette-orange-900: #7c2d12;
    --palette-orange-950: #431407;
}
:root {
    --palette-pink-50: #fdf2f8;
    --palette-pink-100: #fce7f3;
    --palette-pink-200: #fbcfe8;
    --palette-pink-300: #f9a8d4;
    --palette-pink-400: #f472b6;
    --palette-pink-500: #ec4899;
    --palette-pink-600: #db2777;
    --palette-pink-700: #be185d;
    --palette-pink-800: #9d174d;
    --palette-pink-900: #831843;
    --palette-pink-950: #500724;
}
:root {
    --palette-purple-50: #faf5ff;
    --palette-purple-100: #f3e8ff;
    --palette-purple-200: #e9d5ff;
    --palette-purple-300: #d8b4fe;
    --palette-purple-400: #c084fc;
    --palette-purple-500: #a855f7;
    --palette-purple-600: #9333ea;
    --palette-purple-700: #7e22ce;
    --palette-purple-800: #6b21a8;
    --palette-purple-900: #581c87;
    --palette-purple-950: #3b0764;
}
:root {
    --palette-red-50: #fef2f2;
    --palette-red-100: #fee2e2;
    --palette-red-200: #fecaca;
    --palette-red-300: #fca5a5;
    --palette-red-400: #f87171;
    --palette-red-500: #ef4444;
    --palette-red-600: #dc2626;
    --palette-red-700: #b91c1c;
    --palette-red-800: #991b1b;
    --palette-red-900: #7f1d1d;
    --palette-red-950: #450a0a;
}
:root {
    --palette-rose-50: #fff1f2;
    --palette-rose-100: #ffe4e6;
    --palette-rose-200: #fecdd3;
    --palette-rose-300: #fda4af;
    --palette-rose-400: #fb7185;
    --palette-rose-500: #f43f5e;
    --palette-rose-600: #e11d48;
    --palette-rose-700: #be123c;
    --palette-rose-800: #9f1239;
    --palette-rose-900: #881337;
    --palette-rose-950: #4c0519;
}
:root {
    --palette-sky-50: #f0f9ff;
    --palette-sky-100: #e0f2fe;
    --palette-sky-200: #bae6fd;
    --palette-sky-300: #7dd3fc;
    --palette-sky-400: #38bdf8;
    --palette-sky-500: #0ea5e9;
    --palette-sky-600: #0284c7;
    --palette-sky-700: #0369a1;
    --palette-sky-800: #075985;
    --palette-sky-900: #0c4a6e;
    --palette-sky-950: #082f49;
}
:root {
    --palette-slate-50: #f8fafc;
    --palette-slate-100: #f1f5f9;
    --palette-slate-200: #e2e8f0;
    --palette-slate-300: #cbd5e1;
    --palette-slate-400: #94a3b8;
    --palette-slate-500: #64748b;
    --palette-slate-600: #475569;
    --palette-slate-700: #334155;
    --palette-slate-800: #1e293b;
    --palette-slate-900: #0f172a;
    --palette-slate-950: #020617;
}
:root {
    --palette-stone-50: #fafaf9;
    --palette-stone-100: #f5f5f4;
    --palette-stone-200: #e7e5e4;
    --palette-stone-300: #d6d3d1;
    --palette-stone-400: #a8a29e;
    --palette-stone-500: #78716c;
    --palette-stone-600: #57534e;
    --palette-stone-700: #44403c;
    --palette-stone-800: #292524;
    --palette-stone-900: #1c1917;
    --palette-stone-950: #0c0a09;
}
:root {
    --palette-teal-50: #f0fdfa;
    --palette-teal-100: #ccfbf1;
    --palette-teal-200: #99f6e4;
    --palette-teal-300: #5eead4;
    --palette-teal-400: #2dd4bf;
    --palette-teal-500: #14b8a6;
    --palette-teal-600: #0d9488;
    --palette-teal-700: #0f766e;
    --palette-teal-800: #115e59;
    --palette-teal-900: #134e4a;
    --palette-teal-950: #042f2e;
}
:root {
    --palette-violet-50: #f5f3ff;
    --palette-violet-100: #ede9fe;
    --palette-violet-200: #ddd6fe;
    --palette-violet-300: #c4b5fd;
    --palette-violet-400: #a78bfa;
    --palette-violet-500: #8b5cf6;
    --palette-violet-600: #7c3aed;
    --palette-violet-700: #6d28d9;
    --palette-violet-800: #5b21b6;
    --palette-violet-900: #4c1d95;
    --palette-violet-950: #2e1065;
}
:root {
    --palette-yellow-50: #fefce8;
    --palette-yellow-100: #fef9c3;
    --palette-yellow-200: #fef08a;
    --palette-yellow-300: #fde047;
    --palette-yellow-400: #facc15;
    --palette-yellow-500: #eab308;
    --palette-yellow-600: #ca8a04;
    --palette-yellow-700: #a16207;
    --palette-yellow-800: #854d0e;
    --palette-yellow-900: #713f12;
    --palette-yellow-950: #422006;
}
:root {
    --palette-zinc-50: #fafafa;
    --palette-zinc-100: #f4f4f5;
    --palette-zinc-200: #e4e4e7;
    --palette-zinc-300: #d4d4d8;
    --palette-zinc-400: #a1a1aa;
    --palette-zinc-500: #71717a;
    --palette-zinc-600: #52525b;
    --palette-zinc-700: #3f3f46;
    --palette-zinc-800: #27272a;
    --palette-zinc-900: #18181b;
    --palette-zinc-950: #09090b;
}
:root[data-primary="amber"] {
    --primary-50: #fffbeb;
    --primary-100: #fef3c7;
    --primary-200: #fde68a;
    --primary-300: #fcd34d;
    --primary-400: #fbbf24;
    --primary-500: #f59e0b;
    --primary-600: #d97706;
    --primary-700: #b45309;
    --primary-800: #92400e;
    --primary-900: #78350f;
    --primary-950: #451a03;
}
:root[data-primary="blue"] {
    --primary-50: #eff6ff;
    --primary-100: #dbeafe;
    --primary-200: #bfdbfe;
    --primary-300: #93c5fd;
    --primary-400: #60a5fa;
    --primary-500: #3b82f6;
    --primary-600: #2563eb;
    --primary-700: #1d4ed8;
    --primary-800: #1e40af;
    --primary-900: #1e3a8a;
    --primary-950: #172554;
}
:root[data-primary="cyan"] {
    --primary-50: #ecfeff;
    --primary-100: #cffafe;
    --primary-200: #a5f3fc;
    --primary-300: #67e8f9;
    --primary-400: #22d3ee;
    --primary-500: #06b6d4;
    --primary-600: #0891b2;
    --primary-700: #0e7490;
    --primary-800: #155e75;
    --primary-900: #164e63;
    --primary-950: #083344;
}
:root[data-primary="emerald"] {
    --primary-50: #ecfdf5;
    --primary-100: #d1fae5;
    --primary-200: #a7f3d0;
    --primary-300: #6ee7b7;
    --primary-400: #34d399;
    --primary-500: #10b981;
    --primary-600: #059669;
    --primary-700: #047857;
    --primary-800: #065f46;
    --primary-900: #064e3b;
    --primary-950: #022c22;
}
:root[data-primary="fuchsia"] {
    --primary-50: #fdf4ff;
    --primary-100: #fae8ff;
    --primary-200: #f5d0fe;
    --primary-300: #f0abfc;
    --primary-400: #e879f9;
    --primary-500: #d946ef;
    --primary-600: #c026d3;
    --primary-700: #a21caf;
    --primary-800: #86198f;
    --primary-900: #701a75;
    --primary-950: #4a044e;
}
:root[data-neutral="gray"] {
    --neutral-50: #f9fafb;
    --neutral-100: #f3f4f6;
    --neutral-200: #e5e7eb;
    --neutral-300: #d1d5db;
    --neutral-400: #9ca3af;
    --neutral-500: #6b7280;
    --neutral-600: #4b5563;
    --neutral-700: #374151;
    --neutral-800: #1f2937;
    --neutral-900: #111827;
    --neutral-950: #030712;
}
:root[data-primary="green"] {
    --primary-50: #f0fdf4;
    --primary-100: #dcfce7;
    --primary-200: #bbf7d0;
    --primary-300: #86efac;
    --primary-400: #4ade80;
    --primary-500: #22c55e;
    --primary-600: #16a34a;
    --primary-700: #15803d;
    --primary-800: #166534;
    --primary-900: #14532d;
    --primary-950: #052e16;
}
:root[data-primary="indigo"] {
    --primary-50: #eef2ff;
    --primary-100: #e0e7ff;
    --primary-200: #c7d2fe;
    --primary-300: #a5b4fc;
    --primary-400: #818cf8;
    --primary-500: #6366f1;
    --primary-600: #4f46e5;
    --primary-700: #4338ca;
    --primary-800: #3730a3;
    --primary-900: #312e81;
    --primary-950: #1e1b4b;
}
:root[data-primary="lime"] {
    --primary-50: #f7fee7;
    --primary-100: #ecfccb;
    --primary-200: #d9f99d;
    --primary-300: #bef264;
    --primary-400: #a3e635;
    --primary-500: #84cc16;
    --primary-600: #65a30d;
    --primary-700: #4d7c0f;
    --primary-800: #3f6212;
    --primary-900: #365314;
    --primary-950: #1a2e05;
}
:root[data-neutral="neutral"] {
    --neutral-50: #fafafa;
    --neutral-100: #f5f5f5;
    --neutral-200: #e5e5e5;
    --neutral-300: #d4d4d4;
    --neutral-400: #a3a3a3;
    --neutral-500: #737373;
    --neutral-600: #525252;
    --neutral-700: #404040;
    --neutral-800: #262626;
    --neutral-900: #171717;
    --neutral-950: #0a0a0a;
}
:root[data-primary="orange"] {
    --primary-50: #fff7ed;
    --primary-100: #ffedd5;
    --primary-200: #fed7aa;
    --primary-300: #fdba74;
    --primary-400: #fb923c;
    --primary-500: #f97316;
    --primary-600: #ea580c;
    --primary-700: #c2410c;
    --primary-800: #9a3412;
    --primary-900: #7c2d12;
    --primary-950: #431407;
}
:root[data-primary="pink"] {
    --primary-50: #fdf2f8;
    --primary-100: #fce7f3;
    --primary-200: #fbcfe8;
    --primary-300: #f9a8d4;
    --primary-400: #f472b6;
    --primary-500: #ec4899;
    --primary-600: #db2777;
    --primary-700: #be185d;
    --primary-800: #9d174d;
    --primary-900: #831843;
    --primary-950: #500724;
}
:root[data-primary="purple"] {
    --primary-50: #faf5ff;
    --primary-100: #f3e8ff;
    --primary-200: #e9d5ff;
    --primary-300: #d8b4fe;
    --primary-400: #c084fc;
    --primary-500: #a855f7;
    --primary-600: #9333ea;
    --primary-700: #7e22ce;
    --primary-800: #6b21a8;
    --primary-900: #581c87;
    --primary-950: #3b0764;
}
:root[data-primary="red"] {
    --primary-50: #fef2f2;
    --primary-100: #fee2e2;
    --primary-200: #fecaca;
    --primary-300: #fca5a5;
    --primary-400: #f87171;
    --primary-500: #ef4444;
    --primary-600: #dc2626;
    --primary-700: #b91c1c;
    --primary-800: #991b1b;
    --primary-900: #7f1d1d;
    --primary-950: #450a0a;
}
:root[data-primary="rose"] {
    --primary-50: #fff1f2;
    --primary-100: #ffe4e6;
    --primary-200: #fecdd3;
    --primary-300: #fda4af;
    --primary-400: #fb7185;
    --primary-500: #f43f5e;
    --primary-600: #e11d48;
    --primary-700: #be123c;
    --primary-800: #9f1239;
    --primary-900: #881337;
    --primary-950: #4c0519;
}
:root[data-primary="sky"] {
    --primary-50: #f0f9ff;
    --primary-100: #e0f2fe;
    --primary-200: #bae6fd;
    --primary-300: #7dd3fc;
    --primary-400: #38bdf8;
    --primary-500: #0ea5e9;
    --primary-600: #0284c7;
    --primary-700: #0369a1;
    --primary-800: #075985;
    --primary-900: #0c4a6e;
    --primary-950: #082f49;
}
:root[data-neutral="slate"] {
    --neutral-50: #f8fafc;
    --neutral-100: #f1f5f9;
    --neutral-200: #e2e8f0;
    --neutral-300: #cbd5e1;
    --neutral-400: #94a3b8;
    --neutral-500: #64748b;
    --neutral-600: #475569;
    --neutral-700: #334155;
    --neutral-800: #1e293b;
    --neutral-900: #0f172a;
    --neutral-950: #020617;
}
:root[data-neutral="stone"] {
    --neutral-50: #fafaf9;
    --neutral-100: #f5f5f4;
    --neutral-200: #e7e5e4;
    --neutral-300: #d6d3d1;
    --neutral-400: #a8a29e;
    --neutral-500: #78716c;
    --neutral-600: #57534e;
    --neutral-700: #44403c;
    --neutral-800: #292524;
    --neutral-900: #1c1917;
    --neutral-950: #0c0a09;
}
:root[data-primary="teal"] {
    --primary-50: #f0fdfa;
    --primary-100: #ccfbf1;
    --primary-200: #99f6e4;
    --primary-300: #5eead4;
    --primary-400: #2dd4bf;
    --primary-500: #14b8a6;
    --primary-600: #0d9488;
    --primary-700: #0f766e;
    --primary-800: #115e59;
    --primary-900: #134e4a;
    --primary-950: #042f2e;
}
:root[data-primary="violet"] {
    --primary-50: #f5f3ff;
    --primary-100: #ede9fe;
    --primary-200: #ddd6fe;
    --primary-300: #c4b5fd;
    --primary-400: #a78bfa;
    --primary-500: #8b5cf6;
    --primary-600: #7c3aed;
    --primary-700: #6d28d9;
    --primary-800: #5b21b6;
    --primary-900: #4c1d95;
    --primary-950: #2e1065;
}
:root[data-primary="yellow"] {
    --primary-50: #fefce8;
    --primary-100: #fef9c3;
    --primary-200: #fef08a;
    --primary-300: #fde047;
    --primary-400: #facc15;
    --primary-500: #eab308;
    --primary-600: #ca8a04;
    --primary-700: #a16207;
    --primary-800: #854d0e;
    --primary-900: #713f12;
    --primary-950: #422006;
}
:root[data-neutral="zinc"] {
    --neutral-50: #fafafa;
    --neutral-100: #f4f4f5;
    --neutral-200: #e4e4e7;
    --neutral-300: #d4d4d8;
    --neutral-400: #a1a1aa;
    --neutral-500: #71717a;
    --neutral-600: #52525b;
    --neutral-700: #3f3f46;
    --neutral-800: #27272a;
    --neutral-900: #18181b;
    --neutral-950: #09090b;
}
/* @toc Planner categorical label tokens (R13, vetted) */
:root {
    --planner-fam-slate: #64748b;
    --planner-fam-slate-active: #475569;
    --planner-fam-gray: #6b7280;
    --planner-fam-gray-active: #4b5563;
    --planner-fam-zinc: #71717a;
    --planner-fam-zinc-active: #52525b;
    --planner-fam-neutral: #737373;
    --planner-fam-neutral-active: #525252;
    --planner-fam-stone: #78716c;
    --planner-fam-stone-active: #57534e;
    --planner-fam-red: #ef4444;
    --planner-fam-red-active: #dc2626;
    --planner-fam-orange: #f97316;
    --planner-fam-orange-active: #c2410c;
    --planner-fam-amber: #f59e0b;
    --planner-fam-amber-active: #b45309;
    --planner-fam-yellow: #eab308;
    --planner-fam-yellow-active: #a16207;
    --planner-fam-lime: #84cc16;
    --planner-fam-lime-active: #4d7c0f;
    --planner-fam-green: #22c55e;
    --planner-fam-green-active: #15803d;
    --planner-fam-emerald: #10b981;
    --planner-fam-emerald-active: #047857;
    --planner-fam-teal: #14b8a6;
    --planner-fam-teal-active: #0f766e;
    --planner-fam-cyan: #06b6d4;
    --planner-fam-cyan-active: #0e7490;
    --planner-fam-sky: #0ea5e9;
    --planner-fam-sky-active: #0369a1;
    --planner-fam-blue: #3b82f6;
    --planner-fam-blue-active: #2563eb;
    --planner-fam-indigo: #6366f1;
    --planner-fam-indigo-active: #4f46e5;
    --planner-fam-violet: #8b5cf6;
    --planner-fam-violet-active: #7c3aed;
    --planner-fam-purple: #a855f7;
    --planner-fam-purple-active: #9333ea;
    --planner-fam-fuchsia: #d946ef;
    --planner-fam-fuchsia-active: #c026d3;
    --planner-fam-pink: #ec4899;
    --planner-fam-pink-active: #db2777;
    --planner-fam-rose: #f43f5e;
    --planner-fam-rose-active: #e11d48;
}
/* @toc Annotation slot tokens (reader, vetted) */
:root[data-theme="light"], [data-theme-scope="light"] {
    --annotation-1: #be123c;
    --annotation-1-surface: #ffe4e6;
    --annotation-2: #047857;
    --annotation-2-surface: #d1fae5;
    --annotation-3: #1d4ed8;
    --annotation-3-surface: #dbeafe;
    --annotation-4: #6d28d9;
    --annotation-4-surface: #ede9fe;
    --annotation-5: #c2410c;
    --annotation-5-surface: #ffedd5;
    --annotation-6: #be185d;
    --annotation-6-surface: #fce7f3;
}
:root[data-theme="dark"], [data-theme-scope="dark"] {
    --annotation-1: #fb7185;
    --annotation-1-surface: #4c0519;
    --annotation-2: #34d399;
    --annotation-2-surface: #022c22;
    --annotation-3: #60a5fa;
    --annotation-3-surface: #172554;
    --annotation-4: #a78bfa;
    --annotation-4-surface: #2e1065;
    --annotation-5: #fb923c;
    --annotation-5-surface: #431407;
    --annotation-6: #f472b6;
    --annotation-6-surface: #500724;
}
/* @toc Note color tokens (Keep-notes, vetted) */
:root[data-theme="light"], [data-theme-scope="light"] {
    --note-default: #f5f5f5;
    --note-red: #fee2e2;
    --note-orange: #ffedd5;
    --note-yellow: #fef9c3;
    --note-green: #d1fae5;
    --note-teal: #ccfbf1;
    --note-blue: #dbeafe;
    --note-purple: #ede9fe;
}
:root[data-theme="dark"], [data-theme-scope="dark"] {
    --note-default: #262626;
    --note-red: #450a0a;
    --note-orange: #431407;
    --note-yellow: #422006;
    --note-green: #022c22;
    --note-teal: #042f2e;
    --note-blue: #172554;
    --note-purple: #2e1065;
}
/* @toc Utilities */
/* Utilities — shared CSS variables that don't belong to a palette, to typography,
 * to a component, to a page, or to reset: the structural scales. Theme-neutral
 * primitives, cribbed pared-down from leadthink's utilities. A catch-all by
 * definition, but a justified one — every var here is a reusable primitive a
 * component, global, or the typography config can space / round / size / elevate
 * to. */

:root {
    /* @toc Spacing — 4px base; padding, margin, gap. Tailwind's rungs: the integer ladder is complete 1–12
     * (13/15 don't exist in Tailwind), then 14/16/20/24/28/32 (28/32 added for the marketing-section scale —
     * section vertical padding runs to 128px). This is the ONE structural scale — width/height read
     * it too (no separate ladders), and config facets can name `space-*` bare (CssService::mapValue). */
    --space-0: 0;
    --space-px: 1px;
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-7: 28px;
    --space-8: 32px;
    --space-9: 36px;
    --space-10: 40px;
    --space-11: 44px;
    --space-12: 48px;
    --space-14: 56px;
    --space-16: 64px;
    --space-20: 80px;
    --space-24: 96px;
    --space-28: 112px;
    --space-32: 128px;
    --space-40: 160px;

    /* @toc Type scale — rungs the typography config maps elements to. */
    --text-2xs: 0.625rem;
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.25rem;
    --text-2xl: 1.5rem;
    --text-3xl: 1.875rem;
    --text-4xl: 2.25rem;
    --text-5xl: 3rem;

    /* @toc Border radius — rungs global / components round to. */
    --radius-sm: 6px;
    --radius: 8px;
    --radius-md: 10px;
    --radius-lg: 12px;
    --radius-xl: 16px;
    --radius-2xl: 22px;
    --radius-full: 9999px;

    /* @toc Semantic radius — the ruled TWO-TIER system (R1, ruling sheet 2026-07-14): CONTROLS (buttons,
     * inputs, chips, small interactive) round to 6; CONTAINERS (cards, panels, dialogs, menus) to 8.
     * Components consume these two — not the raw rungs or off-scale literals (card 14 / dropdown 12 / tabs 7)
     * — so the system stays coherent. Ticket B migrates the components onto them. */
    --radius-control: 6px;
    --radius-container: 8px;

    /* @toc Interaction — the ruled disabled dim (R3): ONE house-wide opacity for a disabled control,
     * unifying the scattered 0.5 / 0.55. Consumed as `opacity: var(--disabled-opacity)`. */
    --disabled-opacity: 0.55;

    /* @toc Max width — containers / prose. */
    --max-w-sm: 384px;
    --max-w-md: 448px;
    --max-w-lg: 512px;
    --max-w-xl: 576px;
    --max-w-2xl: 672px;
    --max-w-3xl: 768px;
    --max-w-4xl: 896px;
    --max-w-prose: 65ch;

    /* @toc Shadows — NOT here: elevation reads differently on a dark surface than a light one, so the
     * --shadow-* set lives in config/theme.php (light/dark), remapped per data-theme like --surface/--text.
     * Same names (var(--shadow-md) …), Tailwind's two-layer values, higher alpha in dark. */

    /* @toc Opacity — ZERO consumers today (kept: the scale is tiny and cheap). First real use adopts it;
     * if none arrives it's a clean prune. Recorded here so its status isn't a mystery. */
    --opacity-0: 0;
    --opacity-25: 0.25;
    --opacity-50: 0.5;
    --opacity-75: 0.75;
    --opacity-100: 1;

    /* @toc Z-index — stacking ladder. */
    --z-10: 10;
    --z-20: 20;
    --z-30: 30;
    --z-40: 40;
    --z-50: 50;
    --z-overlay: 100;
    --z-modal: 200;
    --z-popover: 300;
    --z-tooltip: 400;

    /* @toc Transitions */
    --transition-fast: 150ms ease-in-out;
    --transition-default: 300ms ease-in-out;
    --transition-slow: 500ms ease-in-out;
}

/* @toc Typography */
:root {
    --font-sans: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
    --font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
    --font-display: 'IM FELL Great Primer', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
    --font-editorial: 'Iron Owl Zilla Slab', Rockwell, Georgia, serif;
    --h1-size: var(--text-3xl);
    --h1-weight: 500;
    --h1-leading: 1.2;
    --h2-size: var(--text-2xl);
    --h2-weight: 500;
    --h2-leading: 1.25;
    --h3-size: var(--text-xl);
    --h3-weight: 500;
    --h3-leading: 1.3;
    --h4-size: var(--text-lg);
    --h4-weight: 500;
    --h4-leading: 1.35;
    --h5-size: var(--text-base);
    --h5-weight: 600;
    --h5-leading: 1.4;
    --h6-size: var(--text-sm);
    --h6-weight: 600;
    --h6-leading: 1.4;
    --body-size: var(--text-base);
    --body-weight: 400;
    --body-leading: 1.6;
}
/* Typography — applies the config-driven type system to the base elements. The
 * families and per-element size/weight/line-height come from
 * config/css/typography.php, emitted as --font-* and --<el>-* by
 * TypographyService::vars(); this file just wires those vars onto the elements
 * and adds the structural bits the config doesn't carry (heading margins, links,
 * mono). The raw --text-* scale lives in utilities.css. Font files: fonts.css. */

body {
    font-family: var(--font-sans);
    font-size: var(--body-size);
    line-height: var(--body-leading);
}

h1,
h2,
h3,
h4,
h5,
h6 {
    margin: 0 0 0.4em;
    font-family: var(--font-sans);
}

h1 {
    font-size: var(--h1-size);
    font-weight: var(--h1-weight);
    line-height: var(--h1-leading);
}

h2 {
    font-size: var(--h2-size);
    font-weight: var(--h2-weight);
    line-height: var(--h2-leading);
}

h3 {
    font-size: var(--h3-size);
    font-weight: var(--h3-weight);
    line-height: var(--h3-leading);
}

h4 {
    font-size: var(--h4-size);
    font-weight: var(--h4-weight);
    line-height: var(--h4-leading);
}

h5 {
    font-size: var(--h5-size);
    font-weight: var(--h5-weight);
    line-height: var(--h5-leading);
}

h6 {
    font-size: var(--h6-size);
    font-weight: var(--h6-weight);
    line-height: var(--h6-leading);
}

p {
    margin: 0 0 0.8em;
    line-height: var(--body-leading);
}

a {
    color: var(--link);
    text-decoration: underline;
    text-underline-offset: 2px;
    transition: opacity var(--transition-fast);
}

a:hover {
    opacity: 0.8;
}

/* @toc Animations */
/* Animations — reusable, swappable entrance animations a component can opt into
 * (a card fading / rising / sliding / zooming in). Apply a class; the keyframes +
 * sane defaults are below, with duration/easing exposed as --anim-* so a component
 * can override per use. Cribbed pared-down from leadthink's animation set,
 * entrance-only. Honours prefers-reduced-motion. */

:root {
    --anim-duration: 500ms;
    --anim-easing: cubic-bezier(0.16, 1, 0.3, 1);
}

.anim-fade-in {
    animation: anim-fade-in var(--anim-duration) var(--anim-easing) both;
}

.anim-rise {
    animation: anim-rise var(--anim-duration) var(--anim-easing) both;
}

.anim-scale-in {
    animation: anim-scale-in var(--anim-duration) var(--anim-easing) both;
}

.anim-slide-in {
    animation: anim-slide-in var(--anim-duration) var(--anim-easing) both;
}

.anim-zoom-in {
    animation: anim-zoom-in var(--anim-duration) var(--anim-easing) both;
}

@keyframes anim-fade-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@keyframes anim-rise {
    from {
        opacity: 0;
        transform: translateY(16px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes anim-scale-in {
    from {
        opacity: 0;
        transform: scale(0.96);
    }
    to {
        opacity: 1;
        transform: scale(1);
    }
}

@keyframes anim-slide-in {
    from {
        opacity: 0;
        transform: translateX(24px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

@keyframes anim-zoom-in {
    from {
        opacity: 0;
        transform: scale(0.8) translateY(12px);
    }
    to {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .anim-fade-in,
    .anim-rise,
    .anim-scale-in,
    .anim-slide-in,
    .anim-zoom-in {
        animation: none;
    }
}

/* Beyond the entrance classes: neutralise ALL motion for users who ask for it -
 * component transitions (tabs indicator, drawer, tooltip, swatches, chevron) and
 * the login shake alike. The !important duration override even beats the inline
 * animation login.js sets, so no per-component JS gate is needed (the one exception
 * is JS scrollIntoView, which takes a behavior arg dev-tools.js checks itself). */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* @toc Globals */
:root {
    --global-border-radius: var(--radius-lg);
    --global-border-width: 1px;
    --global-border-style: solid;
}
/* @toc Theme + component vars */
:root {
    --dot-landscape-background-color: var(--neutral-50);
    --dot-landscape-border-radius: var(--radius-container);
    --dot-landscape-fallback-background-color: var(--neutral-100);
    --dot-landscape-fallback-color: var(--neutral-900);
    --dot-landscape-control-inset: var(--space-3);
    --dot-landscape-control-font-size: var(--text-sm);
    --page-section-max-width: 72rem;
    --product-finish-preview-border-radius: var(--radius-container);
    --product-finish-preview-gap: var(--space-4);
    --product-finish-preview-padding: var(--space-4);
    --product-finish-preview-label-font-size: var(--text-lg);
    --product-finish-preview-note-font-size: var(--text-sm);
    --button-border-radius: 8px;
    --button-padding-y: 9px;
    --button-padding-x: 18px;
    --button-font-size: var(--text-sm);
    --button-font-weight: 500;
    --button-primary-background-color: var(--primary-600);
    --button-primary-background-color-hover: var(--primary-700);
    --button-primary-background-color-active: var(--primary-800);
    --button-primary-color: var(--on-primary);
    --button-focus-ring: var(--primary);
    --button-icon-control-size: 44px;
    --button-icon-glyph-size: 20px;
    --link-focus-ring: var(--primary);
    --link-border-radius: 2px;
    --link-underline-thickness: 1px;
    --link-underline-offset: 2px;
    --link-icon-size: 0.9em;
    --input-border-radius: 8px;
    --input-padding-y: 10px;
    --input-padding-x: 12px;
    --input-font-size: var(--text-sm);
    --input-label-font-size: var(--text-sm);
    --input-border: var(--neutral-500);
    --input-focus-border: var(--primary);
    --textarea-border-radius: 8px;
    --textarea-padding-y: 10px;
    --textarea-padding-x: 12px;
    --textarea-font-size: var(--text-sm);
    --textarea-line-height: 1.5;
    --textarea-label-font-size: var(--text-sm);
    --textarea-border: var(--neutral-500);
    --textarea-focus-border: var(--primary);
    --textarea-resize: vertical;
    --textarea-min-height: 80px;
    --select-border-radius: 8px;
    --select-padding-y: 10px;
    --select-padding-x: 12px;
    --select-font-size: var(--text-sm);
    --select-label-font-size: var(--text-sm);
    --select-border: var(--neutral-500);
    --select-focus-border: var(--primary);
    --select-chevron-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%23000'%3E%3Cpath fill-rule='evenodd' d='M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z' clip-rule='evenodd' /%3E%3C/svg%3E");
    --combobox-border-radius: var(--radius-control);
    --combobox-border-color: var(--neutral-500);
    --combobox-focus-color: var(--primary);
    --combobox-option-active-background-color: var(--primary);
    --combobox-option-active-text-color: var(--on-primary);
    --combobox-label-font-size: var(--text-sm);
    --combobox-padding-block: var(--space-2);
    --combobox-padding-inline: var(--space-3);
    --combobox-option-padding-block: var(--space-2);
    --combobox-option-padding-inline: var(--space-3);
    --combobox-popup-gap: var(--space-1);
    --combobox-popup-max-height: 240px;
    --combobox-viewport-padding: 8px;
    --checkbox-size: 18px;
    --checkbox-border-radius: 5px;
    --checkbox-gap: 10px;
    --checkbox-font-size: var(--text-sm);
    --checkbox-dot-size: 8px;
    --checkbox-check: var(--on-primary);
    --checkbox-checked-background-color: var(--primary);
    --checkbox-focus-ring: var(--primary);
    --switch-gap: 10px;
    --switch-label-font-size: var(--text-sm);
    --switch-width: 40px;
    --switch-height: 22px;
    --switch-knob-size: 16px;
    --switch-knob-gap: 2px;
    --switch-track-background-color-on: var(--primary);
    --switch-knob-background-color-on: var(--on-primary);
    --switch-focus-ring: var(--primary);
    --slider-label-font-size: var(--text-sm);
    --slider-value-font-size: var(--text-sm);
    --slider-track-height: 6px;
    --slider-track-border-radius: 999px;
    --slider-fill: var(--primary);
    --slider-thumb-size: 18px;
    --slider-thumb-background-color: var(--primary);
    --slider-thumb-border-width: 2px;
    --slider-focus-ring: var(--primary);
    --slider-focus-ring-width: 3px;
    --fieldset-padding: 16px;
    --fieldset-border-radius: 8px;
    --fieldset-gap: 4px;
    --fieldset-focus-border: var(--primary);
    --fieldset-legend-font-size: var(--text-sm);
    --fieldset-legend-font-weight: 500;
    --fieldset-legend-padding-x: 6px;
    --multiple-choice-gap: var(--space-4);
    --multiple-choice-option-border-radius: 8px;
    --canvas-surface-background-color: var(--neutral-950);
    --canvas-surface-border-radius: var(--radius-container);
    --card-border-radius: var(--radius-container);
    --card-padding: 20px;
    --badge-border-radius: var(--radius-control);
    --badge-font-weight: 500;
    --swatch-height: 44px;
    --swatch-border-radius: 8px;
    --swatch-hover-scale: 1.12;
    --swatch-ring-width: 2px;
    --breadcrumb-font-size: var(--text-sm);
    --breadcrumb-gap: 8px;
    --breadcrumb-current-font-weight: 500;
    --breadcrumb-border-radius: var(--radius-sm);
    --code-block-background-color: var(--palette-slate-950);
    --code-block-color: var(--palette-slate-300);
    --code-block-comment: var(--palette-slate-400);
    --code-block-string: var(--palette-sky-300);
    --code-block-keyword: var(--palette-red-400);
    --code-block-number: var(--palette-blue-400);
    --code-block-property: var(--palette-blue-400);
    --code-block-operator: var(--palette-red-400);
    --code-block-punctuation: var(--palette-slate-400);
    --code-block-tag: var(--palette-emerald-300);
    --code-block-attribute: var(--palette-sky-400);
    --code-block-blade: var(--palette-violet-300);
    --figure-border-radius: 0;
    --figure-border-radius-rounded: 12px;
    --figure-caption-font-size: var(--text-sm);
    --figure-caption-gap: 8px;
    --figure-caption-align: start;
    --tabs-tab-hover-background-color: var(--neutral-200);
    --tabs-accent: var(--primary);
    --tabs-pill-track: var(--neutral-200);
    --tabs-pill-active: var(--neutral-50);
    --tabs-vertical-strip: 13rem;
    --tabs-vertical-gap: 18px;
    --details-border-radius: var(--radius);
    --details-padding-y: 12px;
    --details-padding-x: 14px;
    --details-summary-font-size: var(--text-sm);
    --details-focus-ring: var(--primary);
    --details-body-font-size: var(--text-sm);
    --accordion-focus-ring: var(--primary);
    --accordion-border-radius: var(--radius-control);
    --accordion-icon-color: var(--primary);
    --dialog-width: 512px;
    --dialog-border-radius: 8px;
    --dialog-padding: 16px;
    --dialog-title-font-size: var(--text-base);
    --dialog-focus-ring: var(--primary);
    --drawer-width: 360px;
    --drawer-border-radius: 12px;
    --drawer-padding: 20px;
    --drawer-duration: 240ms;
    --drawer-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
    --dropdown-surface: var(--neutral-50);
    --dropdown-border-radius: var(--radius-control);
    --dropdown-min-width: 12rem;
    --dropdown-shadow: var(--shadow-lg);
    --dropdown-item-hover-background-color: var(--neutral-200);
    --emoji-picker-border-radius: var(--radius-lg);
    --emoji-picker-width: 352px;
    --emoji-picker-grid-height: 260px;
    --emoji-picker-cell-size: 40px;
    --emoji-picker-cell-border-radius: var(--radius);
    --emoji-picker-cell-selected: var(--primary);
    --emoji-picker-focus-ring: var(--primary);
    --toolbar-gap: var(--space-1);
    --toolbar-border-radius: var(--radius-control);
    --toolbar-item-size: 2.5rem;
    --tree-indent: 16px;
    --tree-row-height: 28px;
    --tree-border-radius: 6px;
    --tree-row-hover-background-color: var(--neutral-200);
    --tree-row-selected-background-color: var(--neutral-200);
    --tooltip-background-color: var(--neutral-900);
    --tooltip-text: var(--neutral-50);
    --tooltip-z-index: var(--z-tooltip);
    --tooltip-padding-block: 4px;
    --tooltip-padding-inline: 8px;
    --tooltip-border-radius: 6px;
    --tooltip-font-size: var(--text-xs);
    --tooltip-font-weight: 500;
    --tooltip-line-height: 1.25;
    --tooltip-max-width: 16rem;
    --tooltip-gap: 8px;
    --tooltip-viewport-padding: 6px;
    --tooltip-caret-size: 8px;
    --tooltip-motion-distance: 3px;
    --tooltip-motion-distance-negative: -3px;
    --tooltip-motion-easing: ease-out;
    --tooltip-show-delay-seconds: 0.035;
    --tooltip-motion-duration-seconds: 0.08;
    --progress-height: 8px;
    --progress-width: 100%;
    --progress-border-radius: var(--radius-full);
    --progress-value: var(--primary);
    --progress-focus-ring: var(--primary);
    --skeleton-border-radius: var(--radius-control);
    --flowchart-background-color: var(--neutral-100);
    --flowchart-node-background-color: var(--neutral-50);
    --flowchart-node-border: var(--neutral-300);
    --flowchart-node-text: var(--neutral-900);
    --flowchart-note-text: var(--neutral-500);
    --flowchart-edge: var(--neutral-400);
    --flowchart-edge-label: var(--neutral-500);
    --flowchart-accent-background-color: var(--primary-600);
    --flowchart-accent-text: var(--on-primary);
    --theme-picker-border-radius: 8px;
    --footer-padding: 2rem 1rem;
    --footer-gap: 1rem;
}
:root[data-theme="light"], [data-theme-scope="light"] {
    --page-bg: var(--neutral-100);
    --surface: #ffffff;
    --surface-raised: var(--neutral-50);
    --surface-muted: var(--neutral-50);
    --overlay: color-mix(in srgb, var(--neutral-100) 75%, transparent);
    --text: var(--neutral-900);
    --text-muted: var(--neutral-600);
    --paper: #ffffff;
    --ink: #000000;
    --success: var(--palette-green-700);
    --success-surface: var(--palette-green-100);
    --danger: var(--palette-red-700);
    --danger-surface: var(--palette-red-100);
    --warning: var(--palette-amber-700);
    --warning-surface: var(--palette-amber-100);
    --info: var(--palette-blue-700);
    --info-surface: var(--palette-blue-100);
    --text-danger: var(--danger);
    --line: var(--neutral-300);
    --line-strong: var(--neutral-400);
    --primary: var(--primary-500);
    --on-primary: var(--neutral-50);
    --link: var(--primary-700);
    --scrollbar-thumb: var(--neutral-300);
    --scrollbar-thumb-hover: var(--neutral-400);
    --scrollbar-track: transparent;
    --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
    --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
    --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
    --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
    --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
    --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
    --dot-landscape-border-color: var(--line);
    --dot-landscape-control-background-color: var(--surface-raised);
    --dot-landscape-control-color: var(--text);
    --product-finish-preview-background-color: var(--surface);
    --product-finish-preview-border-color: var(--line);
    --product-finish-preview-stage-background-color: var(--surface-raised);
    --product-finish-preview-label-color: var(--text);
    --product-finish-preview-note-color: var(--text-muted);
    --button-ghost-color: var(--text);
    --button-ghost-border: var(--line);
    --button-ghost-border-hover: var(--text-muted);
    --button-ghost-background-color-hover: var(--surface-raised);
    --button-ghost-background-color-active: var(--line);
    --button-icon-color: var(--text);
    --button-icon-background-color-hover: var(--surface-raised);
    --button-icon-background-color-active: var(--line);
    --link-color: var(--link);
    --link-muted-color: var(--text-muted);
    --link-muted-color-hover: var(--text);
    --input-label-color: var(--text-muted);
    --input-background-color: var(--surface);
    --input-color: var(--text);
    --textarea-label-color: var(--text-muted);
    --textarea-background-color: var(--surface);
    --textarea-color: var(--text);
    --select-label-color: var(--text-muted);
    --select-background-color: var(--surface);
    --select-color: var(--text);
    --select-chevron: var(--text-muted);
    --combobox-background-color: var(--surface);
    --combobox-popup-background-color: var(--surface-raised);
    --combobox-text-color: var(--text);
    --combobox-label-color: var(--text-muted);
    --combobox-muted-color: var(--text-muted);
    --combobox-group-border-color: var(--line);
    --combobox-error-color: var(--danger);
    --combobox-popup-shadow: var(--shadow-lg);
    --checkbox-background-color: var(--surface);
    --checkbox-border: var(--line);
    --checkbox-border-hover: var(--text-muted);
    --checkbox-label-color: var(--text);
    --switch-label-color: var(--text);
    --switch-track-background-color: var(--surface-raised);
    --switch-track-border: var(--line);
    --switch-knob-background-color: var(--text-muted);
    --slider-label-color: var(--text-muted);
    --slider-value-color: var(--text);
    --slider-track-background-color: var(--line);
    --slider-thumb-border: var(--surface);
    --fieldset-border: var(--line);
    --fieldset-background-color: var(--surface);
    --fieldset-legend-color: var(--text);
    --canvas-surface-border-color: var(--line);
    --canvas-surface-caption-color: var(--text-muted);
    --card-background-color: var(--surface);
    --card-color: var(--text);
    --card-media-background-color: var(--surface-raised);
    --card-description-color: var(--text-muted);
    --card-border: var(--line);
    --table-head-text: var(--text-muted);
    --table-border: var(--line);
    --table-row-hover: var(--surface-raised);
    --table-positive: var(--success);
    --table-negative: var(--danger);
    --breadcrumb-link: var(--text-muted);
    --breadcrumb-link-hover: var(--text);
    --breadcrumb-current: var(--text);
    --breadcrumb-separator: var(--line);
    --figure-border: var(--line);
    --figure-background-color: var(--surface-raised);
    --figure-caption-color: var(--text-muted);
    --embed-background-color: var(--surface-raised);
    --tabs-tab-text: var(--text-muted);
    --tabs-tab-text-hover: var(--text);
    --tabs-tab-text-active: var(--text);
    --tabs-line: var(--line);
    --details-border: var(--line);
    --details-background-color: var(--surface-raised);
    --details-color: var(--text);
    --details-summary-hover: var(--surface);
    --details-marker-color: var(--text-muted);
    --details-body-color: var(--text-muted);
    --accordion-line: var(--line);
    --accordion-color: var(--text);
    --accordion-marker-color: var(--text-muted);
    --accordion-body-color: var(--text);
    --dialog-background-color: var(--surface-raised);
    --dialog-color: var(--text);
    --dialog-border: var(--line);
    --dialog-shadow: var(--shadow-lg);
    --dialog-backdrop: var(--overlay);
    --dialog-close-color: var(--text-muted);
    --dialog-close-background-color-hover: var(--line);
    --drawer-backdrop: var(--overlay);
    --drawer-shadow: var(--shadow-lg);
    --dropdown-line: var(--line);
    --dropdown-item-text: var(--text);
    --dropdown-item-text-muted: var(--text-muted);
    --dropdown-danger-text: var(--danger);
    --emoji-picker-surface: var(--surface-raised);
    --emoji-picker-border: var(--line);
    --emoji-picker-cell-hover: var(--surface);
    --toolbar-background-color: var(--surface);
    --toolbar-border-color: var(--line);
    --tree-icon: var(--text-muted);
    --tree-twist: var(--text-muted);
    --tree-status-new: var(--info);
    --tree-status-updated: var(--text-muted);
    --tree-status-deleted: var(--danger);
    --progress-track: var(--surface-raised);
    --skeleton-fill: var(--line);
}
:root[data-theme="dark"], [data-theme-scope="dark"] {
    --page-bg: var(--neutral-950);
    --surface: var(--neutral-900);
    --surface-raised: var(--neutral-800);
    --surface-muted: var(--neutral-800);
    --overlay: color-mix(in srgb, var(--neutral-800) 75%, transparent);
    --text: var(--neutral-50);
    --text-muted: var(--neutral-400);
    --paper: #ffffff;
    --ink: #000000;
    --success: var(--palette-green-400);
    --success-surface: var(--palette-green-950);
    --danger: var(--palette-red-400);
    --danger-surface: var(--palette-red-950);
    --warning: var(--palette-amber-400);
    --warning-surface: var(--palette-amber-950);
    --info: var(--palette-blue-400);
    --info-surface: var(--palette-blue-950);
    --text-danger: var(--danger);
    --line: var(--neutral-700);
    --line-strong: var(--neutral-600);
    --primary: var(--primary-500);
    --on-primary: var(--neutral-50);
    --link: var(--primary-400);
    --scrollbar-thumb: var(--neutral-700);
    --scrollbar-thumb-hover: var(--neutral-600);
    --scrollbar-track: transparent;
    --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
    --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.5), 0 1px 2px -1px rgb(0 0 0 / 0.5);
    --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.5), 0 2px 4px -2px rgb(0 0 0 / 0.5);
    --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.6), 0 4px 6px -4px rgb(0 0 0 / 0.6);
    --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.6), 0 8px 10px -6px rgb(0 0 0 / 0.6);
    --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.7);
    --dot-landscape-border-color: var(--line);
    --dot-landscape-control-background-color: var(--surface-raised);
    --dot-landscape-control-color: var(--text);
    --product-finish-preview-background-color: var(--surface);
    --product-finish-preview-border-color: var(--line);
    --product-finish-preview-stage-background-color: var(--surface-raised);
    --product-finish-preview-label-color: var(--text);
    --product-finish-preview-note-color: var(--text-muted);
    --button-ghost-color: var(--text);
    --button-ghost-border: var(--line);
    --button-ghost-border-hover: var(--text-muted);
    --button-ghost-background-color-hover: var(--surface-raised);
    --button-ghost-background-color-active: var(--line);
    --button-icon-color: var(--text);
    --button-icon-background-color-hover: var(--surface-raised);
    --button-icon-background-color-active: var(--line);
    --link-color: var(--link);
    --link-muted-color: var(--text-muted);
    --link-muted-color-hover: var(--text);
    --input-label-color: var(--text-muted);
    --input-background-color: var(--surface);
    --input-color: var(--text);
    --textarea-label-color: var(--text-muted);
    --textarea-background-color: var(--surface);
    --textarea-color: var(--text);
    --select-label-color: var(--text-muted);
    --select-background-color: var(--surface);
    --select-color: var(--text);
    --select-chevron: var(--text-muted);
    --combobox-background-color: var(--surface);
    --combobox-popup-background-color: var(--surface-raised);
    --combobox-text-color: var(--text);
    --combobox-label-color: var(--text-muted);
    --combobox-muted-color: var(--text-muted);
    --combobox-group-border-color: var(--line);
    --combobox-error-color: var(--danger);
    --combobox-popup-shadow: var(--shadow-lg);
    --checkbox-background-color: var(--surface);
    --checkbox-border: var(--line);
    --checkbox-border-hover: var(--text-muted);
    --checkbox-label-color: var(--text);
    --switch-label-color: var(--text);
    --switch-track-background-color: var(--surface-raised);
    --switch-track-border: var(--line);
    --switch-knob-background-color: var(--text-muted);
    --slider-label-color: var(--text-muted);
    --slider-value-color: var(--text);
    --slider-track-background-color: var(--line);
    --slider-thumb-border: var(--surface);
    --fieldset-border: var(--line);
    --fieldset-background-color: var(--surface);
    --fieldset-legend-color: var(--text);
    --canvas-surface-border-color: var(--line);
    --canvas-surface-caption-color: var(--text-muted);
    --card-background-color: var(--surface);
    --card-color: var(--text);
    --card-media-background-color: var(--surface-raised);
    --card-description-color: var(--text-muted);
    --card-border: var(--line);
    --table-head-text: var(--text-muted);
    --table-border: var(--line);
    --table-row-hover: var(--surface-raised);
    --table-positive: var(--success);
    --table-negative: var(--danger);
    --breadcrumb-link: var(--text-muted);
    --breadcrumb-link-hover: var(--text);
    --breadcrumb-current: var(--text);
    --breadcrumb-separator: var(--line);
    --figure-border: var(--line);
    --figure-background-color: var(--surface-raised);
    --figure-caption-color: var(--text-muted);
    --embed-background-color: var(--surface-raised);
    --tabs-tab-text: var(--text-muted);
    --tabs-tab-text-hover: var(--text);
    --tabs-tab-text-active: var(--text);
    --tabs-line: var(--line);
    --tabs-tab-hover-background-color: var(--neutral-800);
    --tabs-pill-track: var(--neutral-900);
    --tabs-pill-active: var(--neutral-800);
    --details-border: var(--line);
    --details-background-color: var(--surface-raised);
    --details-color: var(--text);
    --details-summary-hover: var(--surface);
    --details-marker-color: var(--text-muted);
    --details-body-color: var(--text-muted);
    --accordion-line: var(--line);
    --accordion-color: var(--text);
    --accordion-marker-color: var(--text-muted);
    --accordion-body-color: var(--text);
    --dialog-background-color: var(--surface-raised);
    --dialog-color: var(--text);
    --dialog-border: var(--line);
    --dialog-shadow: var(--shadow-lg);
    --dialog-backdrop: var(--overlay);
    --dialog-close-color: var(--text-muted);
    --dialog-close-background-color-hover: var(--line);
    --drawer-backdrop: var(--overlay);
    --drawer-shadow: var(--shadow-lg);
    --dropdown-line: var(--line);
    --dropdown-item-text: var(--text);
    --dropdown-item-text-muted: var(--text-muted);
    --dropdown-danger-text: var(--danger);
    --dropdown-surface: var(--neutral-900);
    --dropdown-item-hover-background-color: var(--neutral-800);
    --emoji-picker-surface: var(--surface-raised);
    --emoji-picker-border: var(--line);
    --emoji-picker-cell-hover: var(--surface);
    --toolbar-background-color: var(--surface);
    --toolbar-border-color: var(--line);
    --tree-icon: var(--text-muted);
    --tree-twist: var(--text-muted);
    --tree-status-new: var(--info);
    --tree-status-updated: var(--text-muted);
    --tree-status-deleted: var(--danger);
    --tree-row-hover-background-color: var(--neutral-800);
    --tree-row-selected-background-color: var(--neutral-800);
    --tooltip-background-color: var(--neutral-100);
    --tooltip-text: var(--neutral-900);
    --progress-track: var(--surface-raised);
    --skeleton-fill: var(--line);
    --flowchart-background-color: var(--neutral-950);
    --flowchart-node-background-color: var(--neutral-900);
    --flowchart-node-border: var(--neutral-700);
    --flowchart-node-text: var(--neutral-50);
    --flowchart-note-text: var(--neutral-400);
    --flowchart-edge: var(--neutral-600);
    --flowchart-edge-label: var(--neutral-400);
}
/* @toc Base */
/*
 * Base — the document skeleton (html/body as a flex column) and the one generic
 * text helper. Layout moved to its proper homes: the public shell is app-page.css,
 * the authed shell is app-shell.css, and page-specific styles live in pages/.
 */

html,
body {
    padding: 0;
}

body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--page-bg);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
}

/* Each consumer sets its accent on the same element, so palette and theme changes stay live. */
:where(
    .planner-chip-label,
    .floating-dock [data-floating-dock-tool][aria-pressed="true"],
    .floating-dock [data-floating-dock-tool][aria-expanded="true"],
    .floating-dock [data-floating-dock-contribution-control][aria-pressed="true"],
    .floating-dock [data-floating-dock-contribution-control][aria-expanded="true"],
    .app-header-actions .app-header-actions-overflow-trigger[aria-expanded="true"],
    .navigation-panel .navigation-items-link.is-active,
    .navigation-panel .navigation-items-sublink.is-active,
    .navigation-panel .navigation-items-project-leaf[aria-pressed="true"]
) {
    --accent-label-foreground: color-mix(in srgb, var(--accent-label-color) 46%, var(--text));
    --accent-label-background: color-mix(in srgb, var(--accent-label-color) 15%, transparent);
    --accent-label-border: color-mix(in srgb, var(--accent-label-color) 28%, transparent);
}

/* Themed scrollbars — ONE base treatment for every overflow container. scrollbar-color inherits, but
 * scrollbar-width does NOT — so both go on * to reach every scroller (thin + themed everywhere, not
 * just the root). Modern engines use these standard properties (MDN-anchored); the ::-webkit-scrollbar
 * fallback below gives older Chromium/Safari the rounded thumb + hover polish. Ramp-tokened
 * (config/theme.php) → flips per theme + tenant. Opt a scroller OUT with scrollbar-width: none (see
 * tabs' nav and the dev-tools chip rows). */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: var(--scrollbar-track);
}

::-webkit-scrollbar-thumb {
    background: var(--scrollbar-thumb);
    border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--scrollbar-thumb-hover);
}

.muted {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* Keyboard focus — one consistent ring on every focusable control. :where() keeps
 * it zero-specificity, so a component can still draw its own (e.g. .button) without
 * fighting this. A real outline (not box-shadow) so it survives forced-colors mode.
 * tabindex="-1" targets (skip-link's main, modal panels) are programmatic focus,
 * not keyboard stops, so they're excluded. */
:where(a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* Suppress the browser-default outline on non-keyboard focus (mouse / programmatic) -
 * Safari and some Chromium builds draw a stray blue ring there, e.g. tracing the
 * scrollable sidebar list against its divider. The keyboard :focus-visible ring above
 * is untouched, so this only removes the default, never the intentional one. */
:focus:not(:focus-visible) {
    outline: none;
}

/* Visually hidden, still read by assistive tech — for labels and aria-live regions. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* THE SHARED LABEL RECIPE (R5-B, James 2026-07-14 — PROVISIONAL). One place for the small-heading label
   treatment every label surface shares: table column headers, dropdown/menu group labels, docs props-table
   headers, planner meta labels. Consumers apply class="ui-label" (ticket B migrates them), so the treatment
   lives in ONE rule.

   Ruled to Nuxt's NORMAL-CASE 14/600 --text, replacing the old small-caps "eyebrow" (uppercase + tracking +
   muted). James ruled this "for now, not married", so the REVERSIBILITY RIDER is load-bearing:

     >>> To revert to the small-caps eyebrow, this is the ONE edit — swap .ui-label's four body lines for:
     >>>     font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
     >>>     letter-spacing: 0.05em; color: var(--text-muted);
     >>> No consumer changes — every label surface flips at once. That single-edit reversibility IS the ruling. */
.ui-label {
    font-size: var(--text-sm);   /* 14px */
    font-weight: 600;
    text-transform: none;        /* normal-case (was: uppercase) */
    letter-spacing: normal;      /* (was: ~0.05em tracking) */
    color: var(--text);          /* (was: --text-muted) */
}

/* A keyboard key, styled as a physical keycap — the Key column of the shared keyboard-table
   partial (/docs + /accessibility). Ramp tokens only, so it flips per theme. */
kbd {
    display: inline-block;
    padding: 1px 7px;
    font-size: 0.85em;
    line-height: 1.6;
    color: var(--text);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: 5px;
    box-shadow: 0 1px 0 var(--line);
}

/* The em-dash placeholder for a component with no keyboard interaction (e.g. a presentational callout). */
.kbd-none {
    color: var(--text-muted);
}

/* @toc Accordion */
/*
 * x-accordion — APG Accordion primitive. STRUCTURAL styling (the decorative skin waits on the direction pick;
 * the FAQ section refines the frame/padding): the a11y-critical focus ring, the divided-row list, the chevron,
 * and the reduced-motion-guarded open/close. A single-column vertical stack that never reflows on its own box
 * width, so — per the Section-Authoring Law — it needs NO @container and NO viewport @media (the layout simply
 * does not vary by width; only text wraps). Colour is semantic tokens only. --accordion-* vars come from the
 * css facet; the open/close duration comes from the JS island (accordion.js sets --accordion-duration), with a
 * 200ms fallback here.
 */

.accordion-header {
    margin: 0;
    /* Every header carries a top rule: the first is the list's top edge, the rest are the dividers between
       items (spec: rows split by 1px --line, last row unruled — no bottom rules anywhere). */
    border-block-start: 1px solid var(--accordion-line, var(--line));
}

.accordion-trigger {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding-block: var(--space-4);
    padding-inline: 0;
    border: none;
    background: transparent;
    color: var(--accordion-color, var(--text));
    font-size: var(--text-base);
    font-weight: 500;
    line-height: 1.4;
    text-align: start;
    cursor: pointer;
}

.accordion-trigger:hover {
    /* A whisper-subtle neutral tint — no literal colour, so it stays theme-safe (color-mix on a token). */
    background: color-mix(in srgb, var(--text) 4%, transparent);
}

.accordion-trigger:focus-visible {
    outline: 2px solid transparent;
    outline-offset: -2px;
    box-shadow: inset 0 0 0 2px var(--accordion-focus-ring, var(--primary));
    border-radius: var(--accordion-border-radius, var(--radius-control));
}

.accordion-icon {
    display: inline-flex;
    flex: none;
    color: var(--accordion-icon-color, var(--primary));
}

.accordion-icon svg {
    width: 1.25rem;
    height: 1.25rem;
}

.accordion-title {
    flex: 1;
    min-width: 0;
}

.accordion-marker {
    display: inline-flex;
    flex: none;
    margin-inline-start: var(--space-2);
    color: var(--accordion-marker-color, var(--text-muted));
}

.accordion-marker svg {
    width: 1.25rem;
    height: 1.25rem;
}

/* THE UNIVERSAL PLUS/MINUS (#1953, matching x-panel): both glyphs ship in the markup and CSS shows one off
   aria-expanded, so the mark is derived from the state and cannot drift. Expanded (the default) shows MINUS
   (what activating it does — close); collapsed shows PLUS. No rotation: plus and minus are different marks, not
   one shape turned, so there is nothing to tween between them. */
.accordion-marker-expand {
    display: none;
}

.accordion-trigger[aria-expanded="false"] .accordion-marker-expand {
    display: inline-flex;
}

.accordion-trigger[aria-expanded="false"] .accordion-marker-collapse {
    display: none;
}

/* The panel animates open/close via a grid-template-rows 0fr → 1fr tween (the modern unknown-height technique).
   accordion.js adds/removes .is-open and toggles the `hidden` attribute at the animation boundary (a collapsed
   panel is `hidden` = removed from the AT tree, per APG). */
.accordion-panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--accordion-duration, 200ms) ease;
}

/* A collapsed panel is `hidden` — removed from layout AND the AT tree. This explicit rule is REQUIRED because
   the author `display: grid` above otherwise overrides the UA [hidden]{display:none}, leaving a clipped sliver
   of the panel visible. The JS un-hides BEFORE animating open and re-hides AFTER the collapse tween, so a panel
   is never `hidden` mid-animation — this never fights the open/close machinery. */
.accordion-panel[hidden] {
    display: none;
}

.accordion-panel.is-open {
    grid-template-rows: 1fr;
}

.accordion-panel-inner {
    overflow: hidden;
    padding-block-end: var(--space-4);
    color: var(--accordion-body-color, var(--text));
}

.accordion-panel-inner > :first-child {
    margin-block-start: 0;
}

/* Reduced motion: instant open/close. The plus/minus swap is already instant (no rotation to disable). The
   global animations.css blanket already zeroes all transitions; this local block states the intent for honesty
   (the dialog.css precedent). */
@media (prefers-reduced-motion: reduce) {
    .accordion-panel {
        transition: none;
    }
}

/* @toc Annotated text */
/*
 * Annotated text — the §1 reader runs-renderer. The block is inline body text; a REVEALED issue-run is a
 * <button> mark: a tinted, underlined phrase carrying a superscript issue number. Three cues, never colour
 * alone: number + underline + tint (forced-colors strips the tint but keeps the number + underline). The two
 * hues come STRAIGHT from the emitted annotation tokens (PaletteService::annotationVars, AA-vetted both
 * themes; NEVER hand-authored — a per-component hue is a contract violation):
 *   --annotation-<n>          the STRONG hue → the underline + the marker number.
 *   --annotation-<n>-surface  the span TINT fill.
 * Anatomy dims are --annotated-text-* config, with fallbacks here so the structure renders before the domain
 * manifest emits them; the currentColor token fallbacks are a belt-and-braces net (the tokens are always bundled).
 */

.annotated-text {
    line-height: var(--annotated-text-line-height, 1.7);
    color: var(--text);
}

.annotated-text-p {
    margin-block: 0 var(--annotated-text-paragraph-gap, 1em);
}

.annotated-text-p:last-child {
    margin-block-end: 0;
}

/* Direction B's deposition-style margin paragraph-number; rendered only when numbered-paragraphs is on. */
.annotated-text-pnum {
    display: inline-block;
    min-width: 1.5em;
    margin-inline-end: 0.5em;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    user-select: none;
}

/* The revealed mark — a <button> reset to inline text; the tint + underline live on the inner text span so
   the underline hugs the phrase, not the trailing superscript number. */
.annotated-text-mark {
    display: inline;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: inherit;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
}

/* Slot hue + tint (1..6), each from its emitted annotation token pair. currentColor / transparent fallbacks =
   a visible neutral mark if a token is ever absent. */
.annotated-text-mark[data-annotation="1"] {
    --_hue: var(--annotation-1, currentColor);
    --_surface: var(--annotation-1-surface, transparent);
}

.annotated-text-mark[data-annotation="2"] {
    --_hue: var(--annotation-2, currentColor);
    --_surface: var(--annotation-2-surface, transparent);
}

.annotated-text-mark[data-annotation="3"] {
    --_hue: var(--annotation-3, currentColor);
    --_surface: var(--annotation-3-surface, transparent);
}

.annotated-text-mark[data-annotation="4"] {
    --_hue: var(--annotation-4, currentColor);
    --_surface: var(--annotation-4-surface, transparent);
}

.annotated-text-mark[data-annotation="5"] {
    --_hue: var(--annotation-5, currentColor);
    --_surface: var(--annotation-5-surface, transparent);
}

.annotated-text-mark[data-annotation="6"] {
    --_hue: var(--annotation-6, currentColor);
    --_surface: var(--annotation-6-surface, transparent);
}

.annotated-text-mark-text {
    padding-inline: 0.1em;
    border-bottom: var(--annotated-text-underline-width, 2px) solid var(--_hue, currentColor);
    border-radius: var(--annotated-text-span-border-radius, 3px) var(--annotated-text-span-border-radius, 3px) 0 0;
    /* The vetted span tint; linear-gradient(colour, colour) is the cross-engine mark-fill trick. */
    background-image: linear-gradient(var(--_surface, transparent), var(--_surface, transparent));
}

.annotated-text-mark-num {
    margin-inline-start: 0.1em;
    color: var(--_hue, currentColor);
    font-size: var(--annotated-text-marker-font-size, 0.6875rem);
    font-weight: var(--annotated-text-marker-font-weight, 600);
    line-height: 1;
    vertical-align: super;
    /* Keep the decorative marker digit out of selection/copy (the pnum precedent), so verbatim copy stays clean. */
    user-select: none;
}

/* Use the GLOBAL :focus-visible ring (base.css); only nudge the offset so it clears the tinted phrase. */
.annotated-text-mark:focus-visible {
    outline-offset: 2px;
    border-radius: var(--annotated-text-span-border-radius, 3px);
}

/* Forced-colors: the OS strips the tint background — keep the number + underline as the surviving cues. */
@media (forced-colors: active) {
    .annotated-text-mark-text {
        border-bottom-color: currentColor;
    }

    .annotated-text-mark-num {
        color: currentColor;
    }
}

/* @toc App header actions */
/*
 * AppHeaderActions keeps a bounded direct action row and composes Dropdown for responsive overflow.
 * The server selects each responsive set. CSS exposes the correct set at the current width.
 * Registered Details supplies the no-script link disclosure without duplicating command-menu behavior.
 */
.app-header-actions,
.app-header-actions-direct {
    display: flex;
    align-items: center;
    gap: var(--app-header-actions-gap);
    min-inline-size: 0;
}

.app-header-actions {
    flex-shrink: 0;
}

.app-header-action,
.app-header-actions-overflow {
    flex-shrink: 0;
}

.app-header-actions .app-header-action-control,
.app-header-actions .app-header-actions-overflow-trigger {
    inline-size: var(--app-header-actions-control-size);
    block-size: var(--app-header-actions-control-size);
    min-inline-size: var(--app-header-actions-control-size);
    min-block-size: var(--app-header-actions-control-size);
    padding: 0;
    border-color: transparent;
    color: var(--text);
    background: transparent;
    box-shadow: none;
    justify-content: center;
}

.app-header-actions .app-header-action-control:hover,
.app-header-actions .app-header-actions-overflow-trigger:hover {
    border-color: transparent;
    background: var(--surface-raised);
}

.app-header-actions .app-header-action-control:active,
.app-header-actions .app-header-action-control[aria-expanded="true"],
.app-header-actions .app-header-actions-overflow-trigger:active {
    background: var(--line);
}

.app-header-actions .app-header-actions-overflow-trigger[aria-expanded="true"] {
    --accent-label-color: var(--app-header-actions-selected-accent);
    border-color: var(--accent-label-border);
    color: var(--accent-label-foreground);
    background: var(--accent-label-background);
}

@media (hover: hover) and (pointer: fine) {
    .app-header-actions .app-header-actions-overflow-trigger[aria-expanded="true"]:hover {
        border-color: var(--accent-label-border);
        color: var(--accent-label-foreground);
        background: var(--accent-label-background);
    }
}

.app-header-actions .app-header-action-control:focus-visible,
.app-header-actions .app-header-actions-overflow-trigger:focus-visible {
    outline: 2px solid var(--app-header-actions-focus-ring);
    outline-offset: 2px;
}

.app-header-action-icon,
.app-header-actions-overflow-trigger > .dropdown-trigger-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    inline-size: var(--app-header-actions-icon-size);
    block-size: var(--app-header-actions-icon-size);
}

.app-header-action-icon svg,
.app-header-actions-overflow-trigger > .dropdown-trigger-icon svg {
    inline-size: var(--app-header-actions-icon-size);
    block-size: var(--app-header-actions-icon-size);
}

.app-header-actions-overflow-trigger .dropdown-caret {
    display: none;
}

.app-header-actions .dropdown-menu {
    max-inline-size: calc(100vw - 16px);
    max-block-size: max(120px, calc(100dvh - var(--app-chrome-row-height, 61px) - 16px));
    overflow-y: auto;
}

.app-header-actions .dropdown-item,
.app-header-actions .dropdown-item-label {
    min-inline-size: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.app-header-actions .dropdown-item-label {
    overflow: visible;
    text-overflow: clip;
}

.app-header-actions-direct .app-header-action-control > .dropdown-caret {
    display: none;
}

.app-header-actions-overflow-compact {
    display: none;
}

.app-header-actions .app-header-actions-no-js-compact {
    display: none;
}

.app-header-actions-no-js-menu {
    display: grid;
    gap: var(--app-header-actions-gap);
    list-style: none;
    margin: 0;
    padding: 0;
}

.app-header-actions-no-js-link {
    display: flex;
    align-items: center;
    min-block-size: var(--app-header-actions-control-size);
}

@media (max-width: 768px), (max-height: 600px) {
    .app-header-action:not(.is-compact-primary),
    .app-header-actions-overflow-desktop {
        display: none;
    }

    .app-header-actions-overflow-compact {
        display: block;
    }

    .app-header-actions .app-header-actions-no-js-desktop {
        display: none;
    }

    .app-header-actions .app-header-actions-no-js-compact {
        display: inline-block;
    }

}

/* @toc App header bar */
/* App header bar — the app-shell's ONE top bar (#1490).
 *
 * It was the page-title strip, sitting under a separate mobile-only bar that carried the brand and
 * hamburger. Those merged into this one: on mobile it reads `[hamburger] [mark] [title] … [actions]`, on
 * desktop `[title] … [actions]` with the mark and hamburger hidden (they live in the sidebar head).
 *
 * Frame owns placement in the viewport and its compact sticky behavior. This file owns only the header's
 * visual identity, content, actions, responsive typography, and navigation controls.
 */

:root {
    --app-chrome-control-size: 44px;
    --app-chrome-icon-size: 24px;
    --app-chrome-control-gap: 8px;
    --app-chrome-row-padding: 8px;
    --app-chrome-inline-padding: 32px;
    --app-chrome-row-height: 61px;
}

.app-header-bar,
.app-shell-bar {
    --app-header-navigation-brand-offset: -6px;
    --app-header-brand-title-offset: 4px;

    display: flex;
    align-items: center;
    gap: var(--app-chrome-control-gap);
    min-height: var(--app-chrome-row-height);
    padding: var(--app-chrome-row-padding) var(--app-chrome-inline-padding);
    background: var(--page-bg);
    border-bottom: 1px solid var(--line);
}

/* The title block takes the slack, so actions sit hard right and the title truncates rather than shoving
   them off the bar. `justify-content: space-between` used to do this; it cannot survive the mark and
   hamburger joining the row, because it would space all four children evenly instead. */
.app-header-bar-content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.app-header-bar-title {
    margin: 0;
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--text);
    /* min-width:0 on the parent is what makes this bite — without it the flex item's automatic minimum size
       is its content, and a long title widens the bar instead of ellipsing. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-header-bar-subtitle {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.app-header-bar-actions,
.app-shell-bar-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    flex-shrink: 0;
    max-inline-size: calc(100% - (2 * var(--app-chrome-control-size)));
}

.admin-impersonation-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.admin-impersonation-controls form {
    flex: 0 0 auto;
    margin: 0;
}

.admin-impersonation-controls .dropdown {
    min-width: 0;
}

.admin-impersonation-controls .dropdown-menu {
    max-width: calc(100vw - 16px);
}

.admin-impersonation-controls .dropdown-item,
.admin-impersonation-controls .dropdown-item-label {
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.admin-impersonation-identity.dropdown-trigger {
    min-width: 44px;
    min-height: 44px;
    max-width: min(260px, 34vw);
}

.admin-impersonation-controls--dock .admin-impersonation-identity > .dropdown-caret {
    display: none;
}

.admin-impersonation-identity-label {
    display: inline-flex;
    gap: 4px;
    min-width: 0;
    max-width: 100%;
    white-space: nowrap;
}

.admin-impersonation-identity-prefix {
    flex: 0 0 auto;
}

.admin-impersonation-identity-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-impersonation-exit.button {
    min-width: 44px;
    min-height: 44px;
}

.admin-impersonation-controls--nav {
    padding: 8px;
}

.admin-impersonation-controls--nav .admin-impersonation-identity.dropdown-trigger {
    max-width: 164px;
}

/* DESKTOP: the sidebar head owns the brand and the one sidebar control, so neither appears here. */
.app-header-bar-brand,
.app-header-bar-menu,
.app-shell-bar-brand,
.app-shell-bar-menu {
    display: none;
}

/* NavigationPanel frames keep the one inert brand in the full-width top bar at every viewport. */
body.app-frame-modern .app-header-bar-brand,
body.app-frame-modern .app-shell-bar-brand {
    display: inline-flex;
    flex-shrink: 0;
    line-height: 0;
}

body.app-frame-modern .app-header-bar-menu,
body.app-frame-modern .app-shell-bar-menu {
    display: inline-flex;
}

/* A titleless page still needs the one Navigation control on desktop. The shell bar is therefore real app
   chrome for modern Frames instead of a compact-only fallback. */
body.app-frame-modern .app-shell-bar {
    display: flex;
}

/* The menu glyph is centered inside its 44px target, while the logo and title are bare content. Balance
   their visible edges without shrinking the target or changing the shared chrome gap used by the dock. */
.app-header-bar-menu + .app-header-bar-brand,
.app-shell-bar-menu + .app-shell-bar-brand {
    margin-inline-start: var(--app-header-navigation-brand-offset);
    margin-inline-end: var(--app-header-brand-title-offset);
}

.app-header-bar-menu,
.app-shell-bar-menu {
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--app-chrome-control-size);
    height: var(--app-chrome-control-size);
    margin-inline-start: 0;
    padding: 0;
    appearance: none;
    border: 0;
    background: none;
    color: var(--text);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.app-header-bar-menu:focus,
.app-shell-bar-menu:focus {
    outline: none;
}

.app-header-bar-menu:focus-visible,
.app-shell-bar-menu:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.app-header-bar-menu svg,
.app-shell-bar-menu svg {
    width: var(--app-chrome-icon-size);
    height: var(--app-chrome-icon-size);
}

.app-header-bar-navigation-icon,
.app-shell-bar-navigation-icon {
    display: inline-flex;
}

body.app-frame-modern .app-header-bar-menu[data-navigation-launcher],
body.app-frame-modern .app-shell-bar-menu[data-navigation-launcher] {
    display: grid;
    place-items: center;
}

.navigation-toggle-stroke {
    stroke-dasharray: 100;
    stroke-dashoffset: 0;
    transition: none;
}

.navigation-toggle-stroke--middle {
    stroke-dashoffset: 25;
}

.navigation-toggle-stroke--bottom {
    stroke-dashoffset: 50;
}

:is(.app-header-bar-menu, .app-shell-bar-menu)[data-navigation-launcher][data-navigation-icon-ready]
    .navigation-toggle-stroke {
    transition: stroke-dashoffset 180ms cubic-bezier(0.4, 0, 0.2, 1);
}

:is(.app-header-bar-menu, .app-shell-bar-menu)[data-navigation-launcher][aria-expanded="true"]
    .navigation-toggle-stroke {
    stroke-dashoffset: 0;
}

/* NAV-CHROME-ONLY BAR — a legacy page that withheld `heading` keeps this compact-only. A modern Frame overrides
   it above because the one Navigation launcher must remain reachable at every viewport. */
.app-shell-bar {
    display: none;
}

@media (max-width: 768px), (max-height: 600px) {
    .app-header-bar,
    .app-shell-bar {
        min-height: var(--app-chrome-row-height);
        --app-chrome-inline-padding: 16px;
        --app-header-navigation-brand-offset: -2px;
        --app-header-brand-title-offset: 8px;

        gap: 4px;
        padding: var(--app-chrome-row-padding) var(--app-chrome-inline-padding);
        /* The notch/status-bar inset in portrait, and the rounded-corner insets in landscape. max() rather
           than adding, so a device with no inset keeps exactly the padding above. */
        padding-top: max(var(--app-chrome-row-padding), env(safe-area-inset-top));
        padding-inline: max(var(--app-chrome-inline-padding), env(safe-area-inset-left)) max(var(--app-chrome-inline-padding), env(safe-area-inset-right));
    }

    .app-header-bar-title {
        font-size: var(--text-lg);
    }

    .app-header-bar-actions,
    .app-shell-bar-actions {
        margin-inline-start: auto;
    }

    .admin-impersonation-controls--header .admin-impersonation-identity.dropdown-trigger {
        max-width: min(150px, 35vw);
        padding-inline: 8px;
    }

    body[data-admin-impersonating] .app-header-bar {
        gap: 4px;
        padding-inline: max(8px, env(safe-area-inset-left)) max(8px, env(safe-area-inset-right));
    }

    body[data-admin-impersonating] .app-header-bar-brand {
        display: none;
    }

    body[data-admin-impersonating] .app-header-bar-content {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
    }

    body[data-admin-impersonating] .app-header-bar-actions {
        gap: 4px;
    }

    body[data-admin-impersonating] .admin-impersonation-controls {
        gap: 4px;
    }

    body[data-admin-impersonating] .admin-impersonation-controls--header .admin-impersonation-identity.dropdown-trigger {
        max-width: 86px;
        padding-inline: 6px;
    }

    body[data-admin-impersonating] .admin-impersonation-controls--header .dropdown-menu {
        position: fixed;
        inset-block-start: calc(max(var(--app-chrome-row-padding), env(safe-area-inset-top)) + var(--app-chrome-control-size));
        inset-inline: 8px;
        width: auto;
        min-width: 0;
        max-width: none;
    }

    body[data-admin-impersonating] .app-header-actions,
    body[data-admin-impersonating] .app-header-actions-direct {
        gap: 4px;
    }

    .app-header-bar-brand {
        display: inline-flex;
        flex-shrink: 0;
        line-height: 0;
    }

    /* ★ BARE GLYPH — no box, no border, no plate, in any state (James, explicit). The control this replaced
       carried `border: 1px solid var(--line); border-radius: 8px`, which is exactly the square the ruling
       removes.

       The tap highlight is the other half of it: without that reset a touch paints a translucent rectangle
       over the glyph — a box the CSS never drew, which is why dropping the border alone does not satisfy the
       ruling.

       :focus-visible ONLY, never :focus. A keyboard user keeps the ring (non-negotiable); a pointer user
       never sees one. `outline`, not a border or shadow, so the ring draws outside the box and cannot
       reflow the bar. */
    .app-header-bar-menu,
    .app-shell-bar-menu {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        flex-shrink: 0;
        width: 44px;
        height: 44px;
        margin-inline-start: 0;
        padding: 0;
        appearance: none;
        border: 0;
        background: none;
        color: var(--text);
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
    }

    .app-header-bar-menu:focus,
    .app-shell-bar-menu:focus {
        outline: none;
    }

    .app-header-bar-menu:focus-visible,
    .app-shell-bar-menu:focus-visible {
        outline: 2px solid var(--primary-500);
        outline-offset: 2px;
        border-radius: var(--radius-sm);
    }

    .app-header-bar-menu svg,
    .app-shell-bar-menu svg {
        width: var(--app-chrome-icon-size);
        height: var(--app-chrome-icon-size);
    }
}

@media (prefers-reduced-motion: reduce) {
    :is(.app-header-bar-menu, .app-shell-bar-menu)[data-navigation-launcher][data-navigation-icon-ready]
        .navigation-toggle-stroke {
        transition: none;
    }
}

.app-header-bar .app-header-action-icon,
.app-header-bar .app-header-action-icon svg,
.app-header-bar .app-header-actions-overflow-trigger > .dropdown-trigger-icon,
.app-header-bar .app-header-actions-overflow-trigger > .dropdown-trigger-icon svg {
    inline-size: var(--app-chrome-icon-size);
    block-size: var(--app-chrome-icon-size);
}

body.app-frame-modern :is(.app-header-bar, .app-shell-bar):has(.app-header-actions .dropdown.is-open) {
    z-index: var(--z-overlay);
}

@media (max-width: 768px), (max-height: 600px) {
    /* On a phone the nav-only bar comes back — mark + hamburger, no title. That is the pre-merge
       .app-shell-bar's exact job, kept rather than rediscovered. */
    .app-shell-bar {
        display: flex;
    }

    .app-shell-bar-brand {
        display: inline-flex;
        flex-shrink: 0;
        line-height: 0;
    }
}

/* @toc App page */
/* Public shell layout — a topbar (logo / login row, right-aligned) above a
 * centered main. The shell for the public pages (landing, login); the authed app
 * uses app-shell.css instead. Used by /resources/views/components/app-page.blade.php. */

.topbar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 18px 24px;
}

.topbar-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
}

.topbar-nav form {
    margin: 0;
}

.main {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: safe center;
    padding: 24px;
}

@media (max-width: 768px) {
    .topbar {
        padding: 14px 16px;
    }

    .main {
        padding: 20px 16px;
    }
}

/* @toc App shell */
/* App shell keeps application-specific sidebar identity and drawer actions. Frame owns all public workspace
 * geometry, including the navigation host and skip-link placement used by standalone exports.
 * This stylesheet pairs the legacy drawer state with the shared Frame without redefining Frame layout. */

body.app-frame-host > .sidebar-nav,
body.app-frame-host > [data-frame] > .frame-available > .frame-region--left > .sidebar-nav {
    transition:
        width var(--app-frame-transition-duration, 260ms) ease,
        opacity var(--app-frame-transition-duration, 260ms) ease,
        transform var(--app-frame-transition-duration, 260ms) ease,
        visibility 0s linear var(--app-frame-transition-duration, 260ms);
}

.app-shell-backdrop {
    display: none;
}

@media (max-width: 768px), (max-height: 600px) {
    body.app-shell.nav-open {
        overflow: hidden;
    }

    .sidebar-nav {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        width: 264px;
        max-width: 82vw;
        transform: translateX(-100%);
        visibility: hidden;
        transition: transform 220ms ease, visibility 0s linear 220ms;
        z-index: 50;
    }

    body.nav-open .sidebar-nav {
        transform: translateX(0);
        visibility: visible;
        transition: transform 220ms ease;
    }

    .app-shell-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 40;
        background: rgba(0, 0, 0, 0.5);
        opacity: 0;
        visibility: hidden;
        transition: opacity 220ms ease, visibility 0s linear 220ms;
    }

    body.nav-open .app-shell-backdrop {
        opacity: 1;
        visibility: visible;
        transition: opacity 220ms ease;
    }
}

@media (prefers-reduced-motion: reduce) {
    body.app-frame-host > .sidebar-nav,
    body.app-frame-host > [data-frame] > .frame-available > .frame-region--left > .sidebar-nav {
        transition: none;
    }
}

/* @toc Badge */
/* Badge — a small, static status/label chip: the promotion of the bare .badge utility (table.css, R4)
 * into a real primitive, grounded on Harry's badge.md extraction. A rounded-rect on the ruled
 * --radius-control (6px, not the old 999px pill), font-weight 500 (Nuxt font-medium; the util was 600).
 * Colours are the categorical status tokens + a themed neutral; variants are soft (the AA-vetted
 * --<c>-surface + --<c> pair) and outline (--<c> text + a 1px ring). Static + decorative — no states,
 * no JS (badge.md §5-8). Config: config/components/badge.php (--badge-border-radius / --badge-font-weight).
 * View: badge.blade.php.
 *
 * CLASS NAMESPACE — `.ui-badge*`, NOT `.badge`: the legacy `.badge` util (+ .badge-success/.badge-secondary)
 * still lives in table.css for its 7 hand-rolled sites (the migration is a separate, later ticket, fenced
 * here). Sharing `.badge` would collide — the util loads after this sheet (alphabetical glob) and would
 * override it, AND would restyle those 7 bare-util sites. A distinct namespace keeps the primitive correct
 * and the legacy sites untouched; the migration ticket can reclaim `.badge` once the util is removed. */

.ui-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);   /* md: 4px / 8px (extraction: py-1 px-2) */
    border-radius: var(--badge-border-radius, var(--radius-control));   /* 6px; fallback so it renders before the config emits */
    font-size: var(--text-xs);                 /* md: 12px */
    font-weight: var(--badge-font-weight, 500);   /* Nuxt font-medium; fallback per the house convention */
    line-height: 1;
    white-space: nowrap;
    vertical-align: middle;
}

/* Optional leading glyph — inherits the badge text colour, sized to the badge. */
.ui-badge-icon {
    display: inline-flex;
    flex-shrink: 0;
}

.ui-badge-icon svg {
    display: block;
    width: 14px;
    height: 14px;
}

/* --- Colours: each sets --badge-fg (text + ring) and --badge-surface (the soft fill), from the
       categorical status tokens (AA-vetted --<c> / --<c>-surface) + a themed neutral. The variant decides
       how they apply, so colour and variant COMPOSE (custom props resolve per element, order-free). --- */
.ui-badge-neutral {
    --badge-fg: var(--text-muted);
    --badge-surface: var(--surface-muted);
}

.ui-badge-success {
    --badge-fg: var(--success);
    --badge-surface: var(--success-surface);
}

.ui-badge-warning {
    --badge-fg: var(--warning);
    --badge-surface: var(--warning-surface);
}

.ui-badge-danger {
    --badge-fg: var(--danger);
    --badge-surface: var(--danger-surface);
}

.ui-badge-info {
    --badge-fg: var(--info);
    --badge-surface: var(--info-surface);
}

/* --- Variants --- */
/* soft: the tinted, self-contained AA-vetted pair (--<c> text on --<c>-surface). */
.ui-badge-soft {
    color: var(--badge-fg);
    background: var(--badge-surface);
}

/* outline: --<c> text (4.5:1 on the light surfaces) + a 1px inset --<c> accent ring, transparent fill.
   A box-shadow ring adds no layout box, so the 1px never nudges the text. */
.ui-badge-outline {
    color: var(--badge-fg);
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--badge-fg);
}

/* --- Sizes (Harry's scale, house-scoped to sm / md / lg; md is the base rule above). The radius is the
       same ruled 6px across sizes — only the text, padding, gap, and icon scale. --- */
.ui-badge-sm {
    gap: 3px;
    padding: 2px 6px;
    font-size: var(--text-2xs);   /* 10px */
}

.ui-badge-sm .ui-badge-icon svg {
    width: 12px;
    height: 12px;
}

.ui-badge-lg {
    gap: 6px;
    padding: var(--space-1) 10px;   /* 4px / 10px */
    font-size: var(--text-sm);      /* 14px */
}

.ui-badge-lg .ui-badge-icon svg {
    width: 16px;
    height: 16px;
}

/* @toc Barbruiser create */
/* BarBruiser create widget — the prompt block + the paste/drop JSON form. Theme tokens
 * throughout. View: barbruiser-create.blade.php; behaviour: barbruiser-create.js. */

.barbruiser-create {
    max-width: var(--max-w-3xl);
}

.barbruiser-create-step {
    margin-bottom: 28px;
}

.barbruiser-create-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.barbruiser-create-h {
    margin: 0 0 4px;
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text);
}

.barbruiser-create-prompt {
    max-height: 320px;
    overflow: auto;
    margin-top: 10px;
    white-space: pre-wrap;
    word-break: break-word;
}

/* Drop zone — a dashed target that also opens the file picker on tap (it wraps a
 * hidden file input), so it works on desktop drag and mobile alike. */
.barbruiser-drop {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 22px;
    margin-bottom: 12px;
    border: 2px dashed var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.barbruiser-drop:hover,
.barbruiser-drop.is-over {
    border-color: var(--primary);
    color: var(--text);
}

.barbruiser-create-actions {
    margin-top: 12px;
}

/* Type tabs — switch which prompt (essay / theory) is shown + copied. */
.barbruiser-create-tabs {
    display: flex;
    gap: 6px;
    margin: 10px 0;
}

.barbruiser-create-tab {
    padding: 6px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius-full, 999px);
    background: var(--surface-raised);
    color: var(--text-muted);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    cursor: pointer;
}

.barbruiser-create-tab.is-active {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
}

.barbruiser-create-prompt[hidden] {
    display: none;
}

/* @toc Barbruiser page */
/* BarBruiser tenant shell + essay reader (ticket 1016). Light tenant (x-barbruiser-page sets data-theme=light,
   blue primary, slate neutrals). Every colour is a ramp/semantic token; layout uses logical properties + a
   viewport-responsive reader grid. Bundled via CssService's components glob, so it loads on the eightythree host
   during the /barbruiser preview (the MPS approach; a per-host css_namespace is the launch-cutover step). */

body.barbruiser { background: var(--surface); color: var(--text); }

/* Content max-width inside the shared app-shell main (which owns the outer padding). */
.bb-shell { max-width: 72rem; margin-inline: auto; }

/* Brand logo in the shared app-shell sidebar (+ mobile bar). BarBruiser's mark is a WIDE wordmark lockup, so it
   overrides the square 30–40px .brand-logo sizing with height-based sizing (via its own class, not .brand-logo). */
.sidebar-nav-brand .bb-sidebar-logo svg { height: 1.9rem; width: auto; max-width: 100%; display: block; }
.app-shell-brand .bb-sidebar-logo svg { height: 1.5rem; width: auto; display: block; }

/* Same mark, on the public /login page (ticket 1027) — bigger since it stands alone above the card,
   not sharing a bar with other chrome. */
.login-brand .bb-sidebar-logo svg { height: 2.5rem; width: auto; max-width: 100%; display: block; }

/* (The "coming soon" dimming + badge lived here for MBE. MBE went live — it has an index of its own — so the
   nav no longer emits those classes and the rules went with them.) */

/* .bb-logo-acc styles the wordmark accent in the home hero (index.blade.php). */
.bb-logo-acc { color: var(--primary-600); }

/* home */
.bb-hero { padding-block: 2.5rem 1.5rem; max-width: 44rem; }
.bb-hero-title { font-size: clamp(2rem, 5vw, 2.75rem); font-weight: 800; letter-spacing: -0.02em; margin: 0 0 0.5rem; }
.bb-hero-sub { font-size: 1.1rem; color: var(--text-muted); margin: 0; }
.bb-home-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; margin-block-start: 1.5rem; }
.bb-home-tile { display: block; padding: 1.5rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-raised); color: var(--text); text-decoration: none; transition: border-color 0.15s, transform 0.15s; }
.bb-home-tile:hover { border-color: var(--primary-500); transform: translateY(-2px); }
.bb-home-tile h2 { margin: 0 0 0.5rem; font-size: 1.3rem; }
.bb-home-tile p { margin: 0 0 1rem; color: var(--text-muted); }
.bb-home-cta { color: var(--primary-600); font-weight: 600; }
.bb-parked { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 0.1rem 0.4rem; vertical-align: middle; }

/* list */
.bb-practice-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-block-end: var(--space-4);
}

.bb-practice-nav-link,
.ui-badge.bb-reader-taxonomy-pill {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-muted);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    text-decoration: none;
    white-space: nowrap;
}

/* Reader taxonomy is the static sibling of #1176's practice links: identical pill presentation without the
   link-only hover/current affordance. The line fits inside a coarse reader block; a genuinely long skill
   wraps inside its own pill rather than creating page-level horizontal scroll. */
.ui-badge.bb-reader-taxonomy-pill {
    max-inline-size: 100%;
    line-height: 1.25;
    white-space: normal;
    overflow-wrap: anywhere;
}

.ui-badge.bb-reader-taxonomy-status {
    color: var(--info);
    background: var(--info-surface);
    border-color: var(--info);
}

.bb-practice-nav-link:hover {
    color: var(--text);
    border-color: var(--primary-600);
}

.bb-practice-nav-link.is-active {
    color: var(--on-primary);
    background: var(--primary-600);
    border-color: var(--primary-600);
}

/* The reader title remains page-owned. This flat line is only labelled, read-only taxonomy followed by the
   existing metadata + phase row; no new surface or selectable Editor child. */
.bb-reader-topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-inline-size: 0;
}

/* Specificity keeps the token gap when the existing page-level topbar rules load after component CSS. */
.essay-topbar.bb-reader-topbar,
.pt-topbar.bb-reader-topbar {
    gap: var(--space-3);
}

.bb-reader-taxonomy {
    display: flex;
    flex: 1 0 100%;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    min-inline-size: 0;
}

.bb-reader-taxonomy-group,
.bb-reader-taxonomy-values {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-inline-size: 0;
}

.bb-reader-taxonomy-group {
    flex: 0 0 auto;
    max-inline-size: 100%;
}

.bb-reader-taxonomy-label {
    flex-shrink: 0;
    white-space: nowrap;
}

.bb-reader-topbar > .reveal-phase {
    flex-shrink: 0;
}

.bb-mbe-meta {
    margin-block-end: var(--space-3);
}

.bb-page-head { padding-block: 1.5rem 1rem; }
.bb-page-head h1 { margin: 0 0 0.4rem; font-size: clamp(1.6rem, 4vw, 2.1rem); }
.bb-page-sub { color: var(--text-muted); margin: 0; max-width: 44rem; }
.bb-eyebrow { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--primary-600); font-weight: 700; }
.bb-essay-index { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); gap: 1rem; }
.bb-essay-tile { display: flex; flex-direction: column; gap: 0.35rem; padding: 1.25rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-raised); text-decoration: none; color: var(--text); transition: border-color 0.15s; }
.bb-essay-tile:hover { border-color: var(--primary-500); }
.bb-essay-tile-title { font-weight: 700; font-size: 1.05rem; }
.bb-essay-tile-meta { color: var(--text-muted); font-size: 0.85rem; }

/* essay reader */
.bb-back { margin-block: 0.5rem 0; }
.bb-back a { color: var(--text-muted); text-decoration: none; }
.bb-back a:hover { color: var(--text); }
.bb-essay-head { padding-block: 0.5rem 1.5rem; }
.bb-essay-title { margin: 0.25rem 0; font-size: clamp(1.5rem, 4vw, 2rem); }
.bb-essay-sub { color: var(--text-muted); margin: 0; }

.bb-reader { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
@media (min-width: 60rem) {
    .bb-reader { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: start; }
    .bb-question { position: sticky; top: 1rem; }
}
.bb-answers { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
@media (min-width: 48rem) {
    .bb-answers { grid-template-columns: 1fr 1fr; }
}
.bb-lead { font-weight: 600; }
.bb-stem { line-height: 1.6; }
.bb-calls { list-style: none; padding: 0; margin-block: 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.bb-calls li { font-weight: 600; padding-inline-start: 0.75rem; border-inline-start: 3px solid var(--primary-500); }
.bb-instruction { color: var(--text-muted); font-style: italic; margin-block-start: 1rem; }
.bb-band { font-size: 0.8rem; color: var(--text-muted); margin: 0 0 0.75rem; }
.bb-answer-text { line-height: 1.6; }

/* issue checklist */
.bb-issues { margin-block-start: 2rem; }
.bb-issues-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem; margin-block-end: 1rem; }
.bb-h2 { margin: 0; font-size: 1.35rem; }
.bb-derived { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); font-weight: 600; }
.bb-filter { display: flex; gap: 0.4rem; }
.bb-issue-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.bb-issue { padding: 1rem 1.25rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-raised); }
.bb-issue-top { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.6rem; margin-block-end: 0.5rem; }
.bb-weight { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 0.15rem 0.5rem; border-radius: var(--radius-sm); }
.bb-weight--must-hit { color: var(--primary-700); background: color-mix(in srgb, var(--primary-500) 14%, var(--surface)); }
.bb-weight--nice-to-have { color: var(--text-muted); background: color-mix(in srgb, var(--neutral-500) 12%, var(--surface)); }
.bb-issue-label { font-weight: 700; }
.bb-call-tag { margin-inline-start: auto; font-size: 0.75rem; color: var(--text-muted); }
.bb-rules { margin: 0.5rem 0 0; padding-inline-start: 1.2rem; line-height: 1.5; }
.bb-rules li { margin-block: 0.2rem; }
.bb-issue-note { margin-block: 0.5rem 0; color: var(--warning); font-size: 0.9rem; }
.bb-seen { margin-block: 0.5rem 0 0; font-size: 0.8rem; color: var(--text-muted); }
.bb-issues-foot { margin-block-start: 1rem; color: var(--text-muted); font-size: 0.85rem; }

/* coming-soon state (MBE tab) — a deliberate, centered "not built yet" state, not a thin note */
.bb-coming-soon { max-width: 40rem; margin-inline: auto; padding-block: clamp(2rem, 8vw, 5rem); text-align: center; }
.bb-coming-soon-badge { display: inline-block; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--primary-700); background: color-mix(in srgb, var(--primary-500) 14%, var(--surface)); border-radius: var(--radius-sm); padding: 0.3rem 0.7rem; margin-block-end: 1rem; }
.bb-coming-soon-title { font-size: clamp(1.75rem, 5vw, 2.5rem); margin: 0 0 0.75rem; letter-spacing: -0.02em; }
.bb-coming-soon-lead { color: var(--text-muted); font-size: 1.05rem; line-height: 1.6; margin: 0 auto 1.75rem; max-width: 34rem; }
.bb-coming-soon-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; }

/* @toc Brand logo */
/* Brand logo — sizes inline SVG and supplies safe live-role defaults for registered tenant artwork. */

.brand-logo {
    --brand-background: var(--primary);
    --brand-accent: var(--primary);
    --brand-foreground: var(--on-primary);

    color: var(--text);
}

.brand-logo svg {
    display: block;
    width: 200px;
    max-width: 100%;
    height: auto;
}

/* ★ THE MARK IS ONE SIZE IN SHELL CHROME — ONE RULE, BOTH SLOTS (#1473).
 *
 * The phone top bar and the desktop sidebar previously each carried their own rule, in their own file, with
 * their own number (26px and 36px). Two independently-tuned numbers for one mark is a thing that drifts:
 * whoever changes the size next has to find both, and if they find one the two slots disagree. James asked
 * for one size in both slots, so this is one rule matching both — the duplication is removed rather than
 * kept in sync, which is why it lives here, in the file that owns the mark's sizing, instead of twice in the
 * two shell files that consume it.
 *
 * ★ SIZED BY HEIGHT, NOT WIDTH (#1471, and this is still the load-bearing part). A fixed WIDTH makes a
 * mark's visual weight depend on its aspect ratio — the wider the mark, the shorter the same rule renders
 * it, which is how a wordmark ended up NINE PIXELS tall beside a 38px menu button:
 *
 *     eightythree   128 x 117    1.09:1   at width:30px -> 30 x 27
 *     Dante         1005 x 298   3.37:1   at width:30px -> 30 x  9
 *
 * Height-first is aspect-independent, so a mark reads at the weight it was given whatever its shape.
 *
 * ★ WHY 23px: it is #1471's 26px trimmed ~10%, which is James's own call after seeing 26 on his phone. It
 * stays well under the menu button's 38px, so the two read as peers AND the bar keeps its 59px height — the
 * bar is sized by its tallest child plus 10px padding either side plus its 1px bottom border, and that
 * tallest child remains the 38px button (verified by measurement, not assumed).
 *
 * MEASURED off the rendered pixels at 23px:
 *
 *     Dante         78 x 23  (top bar and sidebar alike — the point of the change)
 *     eightythree   25 x 23
 *     BarBruiser    unchanged — its mark does not carry .brand-logo, so this rule never matches it
 *     Meal Prep     unchanged — its own (0,3,1) override outranks this, and is deliberately left alone
 *
 * Specificity is (0,2,1): above the base .brand-logo rule above, below the tenant overrides, so no result
 * here depends on bundle order. */
/* #1490 renamed the phone bar's slot from .app-shell-brand to .app-header-bar-brand when the two bars
 * merged, and added .sidebar-nav-brand-mark for the collapsed rail. Both are listed here rather than given
 * their own numbers, because the whole point of this rule is that the mark is ONE size in ONE place.
 *
 * This is not hypothetical tidiness: the rename alone shipped a mark at its INTRINSIC size — roughly 200px
 * of a 375px bar, half the phone screen, with the page title truncated to "Whitebo…" beside it. Every gate
 * passed. It was visible the moment the screenshot was opened and invisible every other way. */
.app-header-bar-brand .brand-logo svg,
.app-shell-bar-brand .brand-logo svg,
.sidebar-nav-brand .brand-logo svg,
.sidebar-nav-brand-mark .brand-logo svg {
    block-size: 23px;
    inline-size: auto;
    max-inline-size: 100%;
}

/* ★ A BARE MARK TAKES THE PAGE'S COLOUR (#1552).
 *
 * When a tenant declares `brand.nav_plate => false` its nav mark is composed with no plate behind it
 * (BrandService::composeMark), so the glyph sits directly on whatever the page supplies. The artwork cannot
 * be trusted to cooperate: Dante's supplied mark paints `fill="white"` straight onto its path, which is
 * correct against its own dark plate and INVISIBLE on a light page once the plate is gone — and no
 * structural check can see it, because a white-on-white mark is still a rendered mark.
 *
 * A fill/stroke PRESENTATION ATTRIBUTE loses to any css rule, which is the whole reason this works where
 * dropping the inline `color` did not. `none` is left alone deliberately: it is how a stroke-only glyph says
 * "do not fill me", and forcing a fill there turns an outline into a solid blob.
 *
 * Only the bare seam is touched. A plated mark keeps the declared foreground against its declared plate, and
 * the rasterized favicon never carries this class — it has no stylesheet behind it to read one. */
.brand-mark-bare [fill]:not([fill="none"]) {
    fill: currentColor;
}

.brand-mark-bare [stroke]:not([stroke="none"]) {
    stroke: currentColor;
}

.brand-logo-wordmark {
    color: var(--text);
}

/* @toc Breadcrumb */
/* Breadcrumb. A horizontal trail of links ending in the current page. Separators
 * are CSS-only chevrons inserted between items (::before on every item but the
 * first), so they carry no text and never reach the accessibility tree. All
 * --breadcrumb-* tokens come from config/components/breadcrumb.php['css']. */

.breadcrumb {
    font-size: var(--breadcrumb-font-size, var(--text-sm));
}

.breadcrumb-list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--breadcrumb-gap, 8px);
    margin: 0;
    padding: 0;
    list-style: none;
}

.breadcrumb-item {
    display: flex;
    align-items: center;
    gap: var(--breadcrumb-gap, 8px);
}

/* The separator: a chevron drawn from a border, rotated 45°. Decorative only —
 * it lives in ::before so it never enters the document text or the a11y tree. */
.breadcrumb-item + .breadcrumb-item::before {
    content: "";
    width: 6px;
    height: 6px;
    border-top: 1.5px solid var(--breadcrumb-separator, var(--line));
    border-right: 1.5px solid var(--breadcrumb-separator, var(--line));
    transform: rotate(45deg);
    flex-shrink: 0;
}

.breadcrumb-link {
    color: var(--breadcrumb-link, var(--text-muted));
    text-decoration: none;
    border-radius: var(--breadcrumb-border-radius, var(--radius-sm));
    transition: color 0.15s ease;
}

.breadcrumb-link:hover {
    color: var(--breadcrumb-link-hover, var(--text));
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Keyboard focus reuses the global :focus-visible ring (base.css); offset a touch
 * so the rounded ring clears the text. */
.breadcrumb-link:focus-visible {
    outline-offset: 3px;
}

.breadcrumb-current {
    color: var(--breadcrumb-current, var(--text));
    font-weight: var(--breadcrumb-current-font-weight, 500);
}

/* @toc Button */
/*
 * Button. Base + variants (primary, ghost, icon) + states (hover, active,
 * focus-visible, disabled). Every token is a --button-* var emitted from
 * config/components/button.php['css'] — this file references only those plus
 * structural rules, so the config is the component's full theming surface.
 */

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: var(--button-padding-y) var(--button-padding-x);
    border: 1px solid transparent;
    border-radius: var(--button-border-radius);
    font-size: var(--button-font-size);
    font-weight: var(--button-font-weight);
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
}

/* The visible ring is the box-shadow (hugs the rounded border). The transparent
 * outline is a forced-colors fallback: box-shadow is dropped in high-contrast mode,
 * so the outline gets promoted to a system colour and focus stays visible. */
.button:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--button-focus-ring);
}

/* primary */
.button-primary {
    background: var(--button-primary-background-color);
    border-color: var(--button-primary-background-color);
    color: var(--button-primary-color);
}

.button-primary:hover {
    background: var(--button-primary-background-color-hover);
    border-color: var(--button-primary-background-color-hover);
}

.button-primary:active {
    background: var(--button-primary-background-color-active);
    border-color: var(--button-primary-background-color-active);
}

/* ghost */
.button-ghost {
    background: transparent;
    border-color: var(--button-ghost-border);
    color: var(--button-ghost-color);
}

.button-ghost:hover {
    border-color: var(--button-ghost-border-hover);
    background: var(--button-ghost-background-color-hover);
}

.button-ghost:active {
    background: var(--button-ghost-background-color-active);
}

/* icon — the shared compact app-chrome action. It is deliberately separate
 * from ghost so ordinary secondary buttons keep their border and padding. */
.button-icon,
.button[data-app-header-icon-action] {
    inline-size: var(--button-icon-control-size);
    block-size: var(--button-icon-control-size);
    min-inline-size: var(--button-icon-control-size);
    min-block-size: var(--button-icon-control-size);
    flex: 0 0 var(--button-icon-control-size);
    padding: 0;
    border-color: transparent;
    color: var(--button-icon-color);
    background: transparent;
    box-shadow: none;
}

/* Icon actions can be state-owned before or after enhancement. Keep their native hidden contract stronger
 * than the shared button display rule without changing hidden behavior for ordinary primary/ghost buttons. */
.button-icon[hidden],
.button[data-app-header-icon-action][hidden] {
    display: none;
}

.button-icon:hover,
.button[data-app-header-icon-action]:hover {
    border-color: transparent;
    background: var(--button-icon-background-color-hover);
}

.button-icon:active,
.button-icon[aria-expanded="true"],
.button[data-app-header-icon-action]:active,
.button[data-app-header-icon-action][aria-expanded="true"] {
    background: var(--button-icon-background-color-active);
}

.button-icon:focus-visible,
.button[data-app-header-icon-action]:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--button-focus-ring);
}

.button-icon svg,
.button[data-app-header-icon-action] svg {
    display: block;
    inline-size: var(--button-icon-glyph-size);
    block-size: var(--button-icon-glyph-size);
}

/* Planner swaps state glyphs inside the existing shared action. The label stays available to assistive
 * technology while every visual state keeps the same compact header geometry. */
.button[data-planner-refresh] [data-planner-refresh-icon][hidden] {
    display: none;
}

.button[data-planner-refresh] [data-planner-refresh-icon="pending"] svg {
    animation: planner-refresh-spin 800ms linear infinite;
}

@keyframes planner-refresh-spin {
    to {
        transform: rotate(1turn);
    }
}

@media (prefers-reduced-motion: reduce) {
    .button[data-planner-refresh] [data-planner-refresh-icon="pending"] svg {
        animation: none;
    }
}

/* disabled / inactive — both <button> and <a> */
.button:disabled,
.button.is-disabled {
    opacity: var(--disabled-opacity);
    cursor: not-allowed;
    pointer-events: none;
}

/* @toc Callout */
/* Callout — a note box whose variant colours the left border (and the optional
 * leading icon); the surface and text are theme tokens, so it adapts to light/dark.
 * Variants pull the categorical status tokens (--success/--warning/--info), so the
 * accent is contrast-vetted and self-flips per theme. View: callout.blade.php. */

.callout {
    display: flex;
    gap: 10px;
    margin: 14px 0;
    padding: 11px 15px;
    border-inline-start: 4px solid var(--callout-accent, var(--line));
    border-radius: var(--radius-sm);
    background: var(--surface-raised);
    color: var(--text);
    font-size: var(--text-sm);
    line-height: 1.55;
}

.callout-icon {
    flex-shrink: 0;
    margin-top: 1px;
    color: var(--callout-accent, var(--text-muted));
}

.callout-icon svg {
    display: block;
    width: 18px;
    height: 18px;
}

.callout-body {
    flex: 1;
    min-width: 0;
}

.callout-body :first-child { margin-top: 0; }
.callout-body :last-child { margin-bottom: 0; }

.callout a {
    color: var(--link);
}

.callout-success { --callout-accent: var(--success); }
.callout-warning { --callout-accent: var(--warning); }
.callout-info { --callout-accent: var(--info); }
.callout-note { --callout-accent: var(--text-muted); }

/* @toc Canvas surface */
/* Canvas surface — the shared <figure> chrome every canvas in the app draws inside: a scrolling
 * viewport, the canvas element, and an optional caption. It owns the box, never the drawing; whatever
 * holds the 2D context decides what appears.
 *
 * The canvas is NEVER scaled to fit. `max-width: none` plus a scrolling viewport means it keeps the
 * size it was authored at and the box scrolls instead, so one canvas pixel keeps a fixed relationship
 * to one world pixel — a shrunk-to-fit canvas resamples and quietly lies about what was drawn.
 *
 * `touch-action: none` hands the pointer to the surface's own gesture handler; without it a one-finger
 * drag pans the page away from the thing being drawn on. `overscroll-behavior: contain` stops a pan
 * that reaches the edge from scrolling the page behind it.
 *
 * `.is-pixelated` (the `pixelated` prop) turns smoothing off for pixel art — a 128px sprite drawn at
 * 4x with smoothing on is a blur. View: canvas-surface.blade.php; colours: --canvas-surface-* (config).
 * Consumers: sprite-canvas.css, sprite-proof.css. */
.canvas-surface {
    display: grid;
    gap: var(--space-2);
    margin: 0;
    min-width: 0;
}

.canvas-surface-viewport {
    display: grid;
    place-items: center;
    min-width: 0;
    overflow: auto;
    background: var(--canvas-surface-background-color, var(--neutral-950));
    border: 1px solid var(--canvas-surface-border-color, var(--line));
    border-radius: var(--canvas-surface-border-radius, var(--radius-container));
    overscroll-behavior: contain;
    touch-action: none;
}

.canvas-surface-canvas {
    display: block;
    max-width: none;
}

.canvas-surface.is-pixelated .canvas-surface-canvas {
    image-rendering: pixelated;
}

.canvas-surface-caption {
    color: var(--canvas-surface-caption-color, var(--text-muted));
    font-size: var(--text-xs);
    text-align: center;
}

/* @toc Card */
/* Card. All --card-* tokens come from config/components/card.php['css'];
 * --card-border swaps per theme (css.light/dark). */

.card {
    background: var(--card-background-color);
    color: var(--card-color);
    border: 1px solid var(--card-border);
    border-radius: var(--card-border-radius);
    overflow: hidden;
}

.card-media {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: var(--card-media-background-color);
}

/* Both picture tiers share one optical size (#1897): the illustration tier is INLINED, so it is an <svg>
   element rather than an <img>, and swapping delivery must not resize the card. `height: auto` lets the
   file's own viewBox ratio govern, which is what keeps a non-square illustration undistorted. */
.card-media img,
.card-media svg {
    display: block;
    width: 84px;
    height: auto;
}

.card-body {
    padding: var(--card-padding);
}

.card-title {
    margin: 0 0 8px;
    font-size: var(--text-base);
    font-weight: 500;
}

.card-desc {
    margin: 0;
    color: var(--card-description-color);
    font-size: var(--text-sm);
    line-height: 1.5;
}

/* @toc Checkbox */
/*
 * Checkbox / radio. The native <input> is .sr-only (kept for behaviour + a11y);
 * .checkbox-box is the painted control and reacts to the input's state through
 * sibling selectors. Every token is a --checkbox-* var (config/components/
 * checkbox.php['css']) with a global-token fallback, so the config is the full
 * theming surface. Dark-first — colours come from the semantic tokens, which
 * already flip per theme.
 */

.checkbox {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--checkbox-gap, 10px);
    cursor: pointer;
    user-select: none;
    line-height: 1.4;
}

.checkbox-box {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--checkbox-size, 18px);
    height: var(--checkbox-size, 18px);
    border: 1px solid var(--checkbox-border, var(--line));
    border-radius: var(--checkbox-border-radius, 5px);
    background: var(--checkbox-background-color, var(--surface));
    color: var(--checkbox-check, var(--on-primary));
    transition: background 0.15s ease, border-color 0.15s ease;
}

/* The radio is the same box, fully round. */
.checkbox-radio .checkbox-box {
    border-radius: 50%;
}

.checkbox-label {
    color: var(--checkbox-label-color, var(--text));
    font-size: var(--checkbox-font-size, var(--text-sm));
}

/* The tick (checkbox) — hidden until the input is checked. */
.checkbox-check {
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: opacity 0.12s ease;
}

/* The dot (radio) — drawn as a pseudo-element so radios need no inline SVG. */
.checkbox-radio .checkbox-box::after {
    content: "";
    width: var(--checkbox-dot-size, 8px);
    height: var(--checkbox-dot-size, 8px);
    border-radius: 50%;
    background: var(--checkbox-check, var(--on-primary));
    transform: scale(0);
    transition: transform 0.12s ease;
}

/* --- Checked: fill the box with the accent, reveal the mark --- */
.checkbox-input:checked ~ .checkbox-box {
    background: var(--checkbox-checked-background-color, var(--primary));
    border-color: var(--checkbox-checked-background-color, var(--primary));
}

.checkbox-input:checked ~ .checkbox-box .checkbox-check {
    opacity: 1;
}

.checkbox-radio .checkbox-input:checked ~ .checkbox-box::after {
    transform: scale(1);
}

/* --- Hover (only when the row is interactive) --- */
.checkbox:not(.is-disabled):hover .checkbox-box {
    border-color: var(--checkbox-border-hover, var(--text-muted));
}

.checkbox:not(.is-disabled):hover .checkbox-input:checked ~ .checkbox-box {
    border-color: var(--checkbox-checked-background-color, var(--primary));
}

/* --- Keyboard focus — the ring rides the painted box, not the hidden input.
 * Transparent outline is the forced-colors fallback (box-shadow is dropped in
 * high-contrast mode, so the outline gets promoted to a system colour). --- */
.checkbox-input:focus-visible ~ .checkbox-box {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--checkbox-focus-ring, var(--primary));
}

/* --- Disabled --- */
.checkbox.is-disabled {
    cursor: not-allowed;
    opacity: var(--disabled-opacity);
}

/* @toc Checklist */
/*
 * Checklist — a labelled native-checkbox list with a computed n/m badge. Extracted from the planner
 * card-open "Acceptance" list (#838, was .cardo-check* in planner-card.css). Colour is semantic tokens
 * only, so it self-flips per theme; the ticked-item dim uses --text-muted, and the row hover is an alpha
 * tint of --text (decoration, not glyph colour), both within the ramp law.
 */

.checklist {
    display: block;
}

.checklist-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    margin-block-end: var(--space-2);
}

.checklist-label {
    margin: 0;
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.checklist-progress {
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.checklist-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

/* Ordered is opt-in. The list marker is native, so the authored row order is also the announced order;
   the default .checklist-items remains the same marker-free <ul> used by every existing consumer. */
.checklist-items-ordered {
    list-style: decimal;
    padding-inline-start: 2rem;
}

.checklist-items-ordered > .checklist-item::marker {
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.checklist-item {
    border-radius: 7px;
    transition: background 100ms ease;
}

.checklist-item:hover {
    background: color-mix(in srgb, var(--text) 5%, transparent);
}

.checklist-item-label {
    display: flex;
    gap: 9px;
    align-items: flex-start;
    padding: 6px 8px;
    font-size: var(--text-sm);
    line-height: 1.45;
    cursor: pointer;
}

.checklist-box {
    margin-top: 2px;
    accent-color: var(--primary-500);
    flex-shrink: 0;
    cursor: pointer;
}

/* Ticked rows dim + strike through so progress reads at a glance. */
.checklist-item.is-done .checklist-item-text {
    color: var(--text-muted);
    text-decoration: line-through;
}

/* A whole-list read-only view: the native disabled boxes lose the pointer affordance. */
.checklist.is-disabled .checklist-item-label,
.checklist.is-disabled .checklist-box {
    cursor: default;
}

/* Folded mode (opt-in): the done rows collapse behind a disclosure so the list foregrounds what is left. The
   toggle is a native <button> (Enter/Space free); colour is semantic tokens only, matching the muted heading. */
.checklist-fold {
    margin-block-start: var(--space-1);
}

/* The fold scaffold renders even with zero done rows (so a live tick has a migration target), and hides itself
   until the first row lands in it — checklist.js toggles .is-empty as the count crosses zero. */
.checklist-fold.is-empty {
    display: none;
}

.checklist-fold-toggle {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 6px 8px;
    border: none;
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    text-align: start;
    cursor: pointer;
    border-radius: 7px;
}

.checklist-fold-toggle:hover {
    background: color-mix(in srgb, var(--text) 5%, transparent);
}

.checklist-fold-toggle:focus-visible {
    outline: 2px solid transparent;
    outline-offset: -2px;
    box-shadow: inset 0 0 0 2px var(--primary);
}

.checklist-fold-marker {
    display: inline-flex;
    flex: none;
    color: var(--text-muted);
    transition: transform var(--checklist-duration, 200ms) ease;
}

.checklist-fold-marker svg {
    width: 1rem;
    height: 1rem;
}

.checklist-fold-toggle[aria-expanded="true"] .checklist-fold-marker {
    transform: rotate(90deg);
}

/* The region animates open/close via a grid-template-rows 0fr → 1fr tween (the modern unknown-height technique).
   checklist.js sets --checklist-duration from the JS island, adds/removes .is-open, and toggles the `hidden`
   attribute at the animation boundary (a collapsed region is `hidden` = removed from the AT tree). */
.checklist-fold-region {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--checklist-duration, 200ms) ease;
}

/* Required: the author `display: grid` overrides the UA [hidden]{display:none}, so state it explicitly or a
   collapsed region bleeds a clipped sliver. The JS un-hides before opening and re-hides after collapsing, so
   the region is never `hidden` mid-animation. */
.checklist-fold-region[hidden] {
    display: none;
}

.checklist-fold-region.is-open {
    grid-template-rows: 1fr;
}

.checklist-fold-region > .checklist-items {
    overflow: hidden;
}

/* Reduced motion: instant open/close and no chevron spin (the global animations.css already zeroes transitions;
   this local block states the intent, the accordion precedent). */
@media (prefers-reduced-motion: reduce) {
    .checklist-fold-region,
    .checklist-fold-marker {
        transition: none;
    }
}

/* @toc Chip */
/*
 * Chip composes sibling Button controls inside one bounded visual container.
 * Labels wrap without stealing the fixed removal target in narrow layouts.
 * Focus and each action target remain visible and usable without hover.
 */
.ui-chip {
    display: inline-flex;
    align-items: stretch;
    max-inline-size: 100%;
    min-inline-size: 0;
    overflow: hidden;
    color: var(--chip-label-color, var(--text));
    background: var(--chip-background-color, var(--surface-raised));
    border: 1px solid var(--chip-border-color, var(--line));
    border-radius: var(--chip-border-radius, var(--radius-control));
    vertical-align: middle;
}

.ui-chip > .ui-chip-label,
.ui-chip .ui-chip-label {
    min-inline-size: 0;
    overflow-wrap: anywhere;
}

.ui-chip > .ui-chip-label {
    display: flex;
    align-items: center;
    min-block-size: var(--chip-target-size, 44px);
    padding-inline: var(--chip-padding-inline, var(--space-3));
    padding-block: var(--space-2);
}

.ui-chip .ui-chip-action {
    min-block-size: var(--chip-target-size, 44px);
    min-inline-size: var(--chip-target-size, 44px);
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

.ui-chip .ui-chip-activate {
    flex: 1 1 auto;
    justify-content: flex-start;
    max-inline-size: 100%;
    padding-inline: var(--chip-padding-inline, var(--space-3));
    color: inherit;
}

.ui-chip .ui-chip-action:hover:not(:disabled) {
    background: var(--chip-action-background-color-hover, var(--surface-muted));
}

.ui-chip .ui-chip-action:focus-visible {
    position: relative;
    z-index: 1;
    outline: 2px solid var(--chip-focus-ring, var(--primary));
    outline-offset: -3px;
}

.ui-chip .ui-chip-remove {
    flex: 0 0 var(--chip-target-size, 44px);
    border-inline-start: 1px solid var(--chip-border-color, var(--line));
}

.ui-chip .ui-chip-remove svg {
    display: block;
    inline-size: var(--chip-icon-size, 20px);
    block-size: var(--chip-icon-size, 20px);
}

.ui-chip.is-disabled {
    opacity: 0.55;
}

@media (forced-colors: active) {
    .ui-chip .ui-chip-action:focus-visible {
        outline-color: Highlight;
    }
}

/* @toc Code block */
/*
 * Code block — a <pre> tokenised by code-block.js for IDE-style highlighting (no
 * library). Always-dark surface; every colour is a --code-block-* token from
 * config/components/code-block.php. Used by the docs and available as
 * <x-code-block language code>.
 */

.code-block {
    margin: 0;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--code-block-background-color);
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1.6;
    color: var(--code-block-color);
    white-space: pre;
}

/* Token colours — scoped to the code node so BOTH the default block and the file variant get them. */
.code-block-code .tok-comment {
    color: var(--code-block-comment);
    font-style: italic;
}

.code-block-code .tok-string { color: var(--code-block-string); }
.code-block-code .tok-keyword { color: var(--code-block-keyword); }
.code-block-code .tok-number { color: var(--code-block-number); }
.code-block-code .tok-property { color: var(--code-block-property); }
.code-block-code .tok-operator { color: var(--code-block-operator); }
.code-block-code .tok-punctuation { color: var(--code-block-punctuation); }
.code-block-code .tok-tag { color: var(--code-block-tag); }
.code-block-code .tok-attr { color: var(--code-block-attribute); }
.code-block-code .tok-blade { color: var(--code-block-blade); }

/* File-sized variant — the card-open Code pane portal: a filename header, a scrollable full-height window,
   and a line-number gutter. Same type metrics as .code-block so the gutter aligns with the code lines. */
.code-block-file {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    border: 1px solid var(--line);
    border-radius: 8px;
    overflow: hidden;
    background: var(--code-block-background-color);
}

.code-block-head {
    flex: 0 0 auto;
    padding: 8px 14px;
    border-bottom: 1px solid color-mix(in oklab, var(--code-block-color) 18%, transparent);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--code-block-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.code-block-scroll {
    flex: 1 1 auto;
    display: flex;
    align-items: flex-start;
    min-height: 0;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1.6;
}

.code-block-gutter {
    flex: 0 0 auto;
    position: sticky;
    inset-inline-start: 0;
    padding-block: 14px;
    padding-inline: 14px 8px;
    text-align: end;
    white-space: pre;
    color: var(--code-block-comment);
    background: var(--code-block-background-color);
    user-select: none;
}

.code-block-file .code-block-pre {
    flex: 1 1 auto;
    margin: 0;
    padding-block: 14px;
    padding-inline: 8px 16px;
    background: transparent;
    color: var(--code-block-color);
    white-space: pre;
}

/* @toc Combobox */
/*
 * Combobox styles the native Select fallback and the enhanced editable listbox inside one bounded root.
 * The popup wraps long labels, scrolls within the viewport, and can open above when JavaScript detects less room below.
 * Every state uses semantic tokens plus text or ARIA, so selection, errors, disabled options, and emptiness do not rely on color.
 */
.combobox {
    position: relative;
    inline-size: 100%;
    max-inline-size: 100%;
    margin-block-end: var(--space-4);
}

.combobox [hidden] {
    display: none;
}

.combobox-label {
    display: block;
    margin-block-end: var(--space-2);
    color: var(--combobox-label-color, var(--text-muted));
    font-size: var(--combobox-label-font-size, var(--text-sm));
    font-weight: 600;
}

.combobox-required {
    margin-inline-start: var(--space-1);
    color: var(--combobox-error-color, var(--danger));
}

.combobox-select,
.combobox-input {
    inline-size: 100%;
    min-inline-size: 0;
    min-block-size: 44px;
    padding-block: var(--combobox-padding-block, var(--space-2));
    padding-inline: var(--combobox-padding-inline, var(--space-3));
    border: 1px solid var(--combobox-border-color, var(--neutral-500));
    border-radius: var(--combobox-border-radius, var(--radius-control));
    background: var(--combobox-background-color, var(--surface));
    color: var(--combobox-text-color, var(--text));
    font: inherit;
}

.combobox-select:focus-visible,
.combobox-input:focus-visible {
    border-color: var(--combobox-focus-color, var(--primary));
}

.combobox-select:disabled,
.combobox-input:disabled {
    cursor: not-allowed;
    opacity: var(--disabled-opacity);
}

.combobox-popup {
    position: absolute;
    z-index: var(--z-popover);
    inset-block-start: calc(100% + var(--combobox-popup-gap, var(--space-1)));
    inset-inline: 0;
    inline-size: 100%;
    max-inline-size: min(100%, calc(100vw - (var(--combobox-viewport-padding, 8px) * 2)));
    max-block-size: min(var(--combobox-popup-max-height, 240px), calc(100dvh - (var(--combobox-viewport-padding, 8px) * 2)));
    overflow: auto;
    border: 1px solid var(--combobox-border-color, var(--neutral-500));
    border-radius: var(--combobox-border-radius, var(--radius-control));
    background: var(--combobox-popup-background-color, var(--surface-raised));
    box-shadow: var(--combobox-popup-shadow, var(--shadow-lg));
}

.combobox.opens-above .combobox-popup {
    inset-block: auto calc(100% + var(--combobox-popup-gap, var(--space-1)));
}

.combobox-group + .combobox-group {
    border-block-start: 1px solid var(--combobox-group-border-color, var(--line));
}

.combobox-group-label {
    padding: var(--space-2) var(--combobox-option-padding-inline, var(--space-3)) var(--space-1);
    color: var(--combobox-muted-color, var(--text-muted));
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.combobox-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    min-block-size: 44px;
    padding-block: var(--combobox-option-padding-block, var(--space-2));
    padding-inline: var(--combobox-option-padding-inline, var(--space-3));
    color: var(--combobox-text-color, var(--text));
    cursor: pointer;
}

.combobox-option-label {
    min-inline-size: 0;
    overflow-wrap: anywhere;
}

.combobox-option-state {
    flex: 0 0 auto;
    color: var(--combobox-muted-color, var(--text-muted));
    font-size: var(--text-xs);
}

.combobox-option:hover:not([aria-disabled="true"]),
.combobox-option.is-active {
    background: var(--combobox-option-active-background-color, var(--primary));
    color: var(--combobox-option-active-text-color, var(--on-primary));
}

.combobox-option.is-active .combobox-option-state {
    color: inherit;
}

.combobox-option[aria-selected="true"] .combobox-option-label {
    font-weight: 700;
}

.combobox-option[aria-disabled="true"] {
    cursor: not-allowed;
    opacity: var(--disabled-opacity);
}

.combobox-message,
.combobox-notice,
.combobox-error {
    margin-block: var(--space-2) 0;
    font-size: var(--text-sm);
}

.combobox-message {
    padding: var(--space-3);
    color: var(--combobox-muted-color, var(--text-muted));
}

.combobox-notice,
.combobox-error {
    color: var(--combobox-error-color, var(--danger));
}

.combobox [aria-invalid="true"] {
    border-color: var(--combobox-error-color, var(--danger));
}

@media (forced-colors: active) {
    .combobox-option.is-active {
        outline: 2px solid Highlight;
        outline-offset: -3px;
    }
}

/* @toc Data table */
/* DataTable pattern - the chrome around the <x-table> primitive: a bulk-action bar
 * above and a footer (pager) below. The table itself is themed by table.css; this
 * file only lays out the surrounding grid + the injected selection column.
 * Component: app/View/Components/DataTable.php; view: data-table.blade.php. */

.data-table {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.data-table-bulk {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

.data-table-footer {
    display: flex;
    flex-direction: column;
}

/* Selection column (added by `selectable`) - keep it to its checkbox width. */
.data-table .table th:first-child,
.data-table .table td.data-table-select {
    width: 1%;
    white-space: nowrap;
}

/* @toc Details */
/*
 * Details — a themed native <details>/<summary> disclosure. The native element
 * supplies the open/close behaviour; this file only themes the chrome. Every
 * --details-* token has a global-token fallback (config/components/details.php
 * is the theming surface). Stack several for an accordion. Dark-first.
 */

.details {
    border: 1px solid var(--details-border, var(--line));
    border-radius: var(--details-border-radius, var(--radius));
    background: var(--details-background-color, var(--surface-raised));
    color: var(--details-color, var(--text));
    overflow: hidden;
}

/* The summary is the disclosure trigger. list-style:none (+ the WebKit pseudo)
 * drops the UA's default triangle so our chevron is the only marker. */
.details-summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: var(--details-padding-y, 12px) var(--details-padding-x, 14px);
    font-size: var(--details-summary-font-size, var(--text-sm));
    font-weight: 500;
    list-style: none;
    cursor: pointer;
    user-select: none;
}

.details-summary::-webkit-details-marker {
    display: none;
}

.details-summary:hover {
    background: var(--details-summary-hover, var(--surface));
}

/* Keyboard focus ring on the summary (it's the focusable element). Mirrors the
 * other primitives: box-shadow ring + a transparent outline as the
 * forced-colors / high-contrast fallback. */
.details-summary:focus-visible {
    outline: 2px solid transparent;
    outline-offset: -2px;
    box-shadow: inset 0 0 0 2px var(--details-focus-ring, var(--primary));
}

/* The chevron points right when closed and rotates down when [open]. */
.details-marker {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--details-marker-color, var(--text-muted));
    transition: transform 0.15s ease;
}

.details-marker svg {
    display: block;
    width: 16px;
    height: 16px;
}

.details[open] .details-marker {
    transform: rotate(90deg);
}

.details-summary-text {
    flex: 1;
    min-width: 0;
}

.details-body {
    padding: 0 var(--details-padding-x, 14px) var(--details-padding-y, 12px);
    color: var(--details-body-color, var(--text-muted));
    font-size: var(--details-body-font-size, var(--text-sm));
    line-height: 1.55;
}

.details-body :first-child {
    margin-top: 0;
}

.details-body :last-child {
    margin-bottom: 0;
}

/* @toc Device frame */
/* Device-frame (Editor E2) — the preview chrome + the scaled screen. The iframe selector is
 * descendant-qualified (.device-frame .device-frame-iframe) so the frame's own sizing beats x-embed's
 * `.embed` base by SPECIFICITY, not bundle order (device-frame.css emits before embed.css alphabetically).
 * Tokens only. --df-w / --df-h / --df-scale are set inline from props + updated by device-frame.js. */

.device-frame {
    display: block;
    max-width: 100%;
    /* Safety net: device-frame.js auto-fits the scale to the pane; this guarantees no body h-scroll even
       for the one frame before JS runs. */
    overflow-x: hidden;
}

/* Toolbar — the viewport-preset selector + the dimensions readout. */
.device-frame-bar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-block-end: var(--space-2);
}

.device-frame-presets {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.device-frame-preset {
    padding-block: 3px;
    padding-inline: var(--space-2);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}

.device-frame-preset:hover {
    color: var(--text);
}

.device-frame-preset.is-active {
    background: color-mix(in srgb, var(--primary) 16%, transparent);
    color: var(--primary);
}

/* AA on the 16% tint: bare --primary (primary-500) is only ~3:1 on the tint at 12px — step to a neighbouring
   rung per theme so the SELECTED viewport is readable (the ramp law: contrast fix = step a rung, own the flip). */
:root[data-theme="light"] .device-frame-preset.is-active {
    color: var(--primary-700);
}

:root[data-theme="dark"] .device-frame-preset.is-active {
    color: var(--primary-300);
}

.device-frame-preset:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 1px;
}

.device-frame-dims {
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

/* Device chrome — a bezel around the screen; sizes to the (scaled) screen it wraps. */
.device-frame-chrome {
    display: inline-block;
    max-width: 100%;
    padding: var(--device-frame-bezel);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--device-frame-border-radius);
}

/* The screen reserves the SCALED size (w*scale × h*scale) so the transformed iframe fills its box exactly —
 * no overflow, no gap. */
.device-frame-screen {
    width: calc(var(--df-w) * var(--df-scale));
    height: calc(var(--df-h) * var(--df-scale));
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--surface);
}

/* The iframe renders at its NATURAL preset size (its true internal viewport, so the framed page's @media
 * respond honestly) and is scaled DOWN for display only. Descendant-qualified to beat .embed by specificity. */
.device-frame .device-frame-iframe {
    width: var(--df-w);
    /* Override x-embed's inherited `.embed { max-width: 100% }`: the containing block is the SCALED screen
       (df-w * scale), so max-width:100% would clamp the iframe's layout width to the scaled value — and then
       transform:scale would shrink it AGAIN + drop the internal viewport to the wrong width (the narrowed-div
       lie this component exists to avoid). `none` restores the true df-w viewport; transform does the sizing. */
    max-width: none;
    height: var(--df-h);
    border: 0;
    display: block;
    transform: scale(var(--df-scale));
    transform-origin: top left;
    background: var(--surface);
}

/* @toc Dialog */
/*
 * Dialog. The native <dialog> element does the heavy lifting (centred-in-viewport
 * positioning, focus trap, Escape, top-layer stacking, the ::backdrop pseudo); this
 * file only themes the panel + backdrop. Every token is a --dialog-* var emitted from
 * config/components/dialog.php['css'], each with a global-token fallback so the panel
 * reads on both themes.
 */

.dialog {
    width: min(var(--dialog-width), calc(100vw - 32px));
    max-height: calc(100vh - 64px);
    padding: 0;
    border: 1px solid var(--dialog-border, var(--line));
    border-radius: var(--dialog-border-radius);
    background: var(--dialog-background-color, var(--surface-raised));
    color: var(--dialog-color, var(--text));
    box-shadow: var(--dialog-shadow);
    /* Open experience (Card-open v2): a fast, smooth entrance the close MIRRORS. The native <dialog>
     * is position:fixed / top-layer, so nothing on the page shifts. Base = the open state; the closed
     * :not([open]) state and @starting-style share the from-state, and the display/overlay
     * allow-discrete transitions keep the element painted through the EXIT so closing reverses the
     * open. Older engines (no @starting-style) fall back to an instant show/hide. */
    opacity: 1;
    transform: none;
    transition:
        opacity 0.18s ease,
        transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
        overlay 0.18s ease allow-discrete,
        display 0.18s ease allow-discrete;
}

/* Closed state = the exit end-state (allow-discrete keeps it painted while it animates out). */
.dialog:not([open]) {
    opacity: 0;
    transform: translateY(6px) scale(0.985);
}

/* Entrance from-state (the first paint, before-open). */
@starting-style {
    .dialog[open] {
        opacity: 0;
        transform: translateY(6px) scale(0.985);
    }
}

/* ::backdrop is the dimming layer the browser paints behind a modal dialog — tint it so the page
 * behind recedes, and fade it with the panel (open + close). */
.dialog::backdrop {
    background: var(--dialog-backdrop);
    opacity: 1;
    transition:
        opacity 0.18s ease,
        overlay 0.18s ease allow-discrete,
        display 0.18s ease allow-discrete;
}

.dialog:not([open])::backdrop {
    opacity: 0;
}

@starting-style {
    .dialog[open]::backdrop {
        opacity: 0;
    }
}

/* Reduced motion: instant swap — the shell appears immediately, no slide / scale / fade. */
@media (prefers-reduced-motion: reduce) {
    .dialog,
    .dialog::backdrop {
        transition: none;
    }

    .dialog:not([open]) {
        transform: none;
    }
}

/* Responsive padding — Nuxt's p-4 sm:p-6: 16px on mobile (the config default), 24px from 640px up. The
   header / body / footer all read --dialog-padding, so bumping it here re-spaces them together. */
@media (min-width: 640px) {
    .dialog {
        --dialog-padding: 24px;
    }
}

.dialog-panel {
    display: flex;
    flex-direction: column;
    position: relative;   /* anchors the absolute top-right close */
    min-height: 0;
    max-height: calc(100vh - 64px);
}

/* Header — parted from the body by a full-width rule (Nuxt's divide-y). Top-aligned; the close sits absolute
   in the corner (below), so the heading reserves right space to clear it. */
.dialog-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 6px;
    padding: var(--dialog-padding);
    padding-right: calc(var(--dialog-padding) + 32px);
    border-bottom: 1px solid var(--dialog-border, var(--line));
    flex-shrink: 0;
}

.dialog-heading {
    min-width: 0;
}

.dialog-title {
    margin: 0;
    font-size: var(--dialog-title-font-size);
    font-weight: 600;
    line-height: 1.3;
    color: var(--dialog-color, var(--text));
}

/* Subtitle under the title (the `description` prop) — Nuxt's mt-1 (4px, R10), 14px, muted. */
.dialog-description {
    margin: 4px 0 0;
    font-size: var(--text-sm);
    line-height: 1.45;
    color: var(--text-muted);
}

/* Close affordance — a borderless icon button in the header. */
.dialog-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 16px;
    right: 16px;
    padding: 4px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--dialog-close-color, var(--text-muted));
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.dialog-close:hover {
    background: var(--dialog-close-background-color-hover, var(--line));
    color: var(--dialog-color, var(--text));
}

.dialog-close svg {
    display: block;
    width: 20px;
    height: 20px;
}

/* Keyboard focus ring — same box-shadow ring as the button, with a transparent
 * outline as the forced-colors / high-contrast fallback. */
.dialog-close:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--dialog-focus-ring, var(--primary));
}

.dialog-body {
    padding: var(--dialog-padding);
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
    font-size: var(--text-sm);
    line-height: 1.6;
}

/* Flush body (padded=false) — full-bleed content (tabs, tables, media) sits edge-to-edge. */
.dialog-body-flush {
    padding: 0;
}

.dialog-body :first-child {
    margin-top: 0;
}

.dialog-body :last-child {
    margin-bottom: 0;
}

/* Footer — the actions area, parted from the body by a rule; actions sit end-aligned (the primary last). */
.dialog-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 8px;
    padding: var(--dialog-padding);
    border-top: 1px solid var(--dialog-border, var(--line));
    flex-shrink: 0;
}

/* Size variants — the primary axis. Each sets --dialog-width, read by `.dialog { width: min(...) }`; an
   id/class width override (e.g. #planner-card-modal) still wins by specificity, so bespoke widths are untouched.
   md keeps the config default width, so an un-sized dialog is unchanged. */
.dialog-sm { --dialog-width: 384px; }   /* Nuxt max-w-sm */
.dialog-md { --dialog-width: 512px; }   /* Nuxt max-w-lg (the default) */
.dialog-lg { --dialog-width: 672px; }   /* Nuxt max-w-2xl */

/* Fullscreen — edge-to-edge, no radius/border; overrides the width + height directly. */
.dialog-fullscreen {
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    max-height: 100vh;
    border: none;
    border-radius: 0;
}

.dialog-fullscreen .dialog-panel {
    height: 100vh;
    max-height: 100vh;
}

/* @toc Dot landscape */
/*
 * Dot landscape pattern. CanvasSurface owns the accessible canvas element. This file gives that surface a
 * responsive image sized box and layers the no JavaScript description and motion control above it.
 * Every selector stays local so the shared CanvasSurface and Button styles remain intact.
 */

.dot-landscape {
    position: relative;
    display: block;
    container-type: inline-size;
    inline-size: 100%;
    min-inline-size: 0;
    aspect-ratio: var(--dot-landscape-aspect, 5 / 3);
    overflow: hidden;
    background: var(--dot-landscape-background-color, var(--neutral-50));
    border: 1px solid var(--dot-landscape-border-color, var(--line));
    border-radius: var(--dot-landscape-border-radius, var(--radius-container));
}

.dot-landscape .dot-landscape-surface,
.dot-landscape .canvas-surface-viewport {
    block-size: 100%;
    inline-size: 100%;
    margin: 0;
}

.dot-landscape .canvas-surface-viewport {
    overflow: hidden;
    background: transparent;
    border: 0;
    border-radius: 0;
    touch-action: auto;
}

.dot-landscape .canvas-surface-canvas {
    display: block;
    block-size: 100%;
    inline-size: 100%;
}

.dot-landscape-fallback {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    margin: 0;
    padding: var(--space-5);
    background: var(--dot-landscape-fallback-background-color, var(--neutral-100));
    color: var(--dot-landscape-fallback-color, var(--neutral-900));
    text-align: center;
}

.dot-landscape.is-painted .dot-landscape-fallback {
    display: none;
}

.dot-landscape-toggle {
    position: absolute;
    z-index: 1;
    inset-block-end: var(--dot-landscape-control-inset, var(--space-3));
    inset-inline-end: var(--dot-landscape-control-inset, var(--space-3));
    max-inline-size: calc(100% - 2 * var(--dot-landscape-control-inset, var(--space-3)));
    background: var(--dot-landscape-control-background-color, var(--surface-raised));
    color: var(--dot-landscape-control-color, var(--text));
    font-size: var(--dot-landscape-control-font-size, var(--text-sm));
}

.dot-landscape-toggle[hidden] {
    display: none;
}

@container (max-width: 24rem) {
    .dot-landscape-toggle {
        padding-block: var(--space-1);
        padding-inline: var(--space-2);
    }
}

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

/* @toc Drawer */
/*
 * Drawer — the slide-out (overlay) / docked side-panel primitive. Promoted from the hand-rolled
 * off-canvas patterns (the app-shell mobile nav, the study-reader rail, the dev-tools overlay).
 *
 * A CUSTOM element, NOT the native <dialog> x-dialog rides — a docked drawer is an in-flow layout
 * column, which <dialog> cannot be. So the overlay's modal a11y (focus trap, Escape, focus-return,
 * scroll-lock) is wired in drawer.js, and the closed overlay hides with visibility:hidden (out of
 * the a11y tree AND the tab order) rather than the native display:none. The open state is the
 * .is-open CLASS (so it is assertable by the keyboard runner's class shape). Every token is a
 * Drawer geometry comes from --drawer-* vars. The nested x-panel owns frame, header, text, rule, and close.
 */

/* Shared Panel composition (both modes). */
.drawer-panel {
    min-height: 0;
}

/* The [hidden] guard (contract checklist #3): the panel sets display:flex, which DEFEATS the HTML
 * [hidden] attribute's UA display:none — so restore it explicitly, wherever a consumer (or a
 * framework) hard-hides the drawer without animation. */
.drawer[hidden],
.drawer-panel[hidden] {
    display: none;
}

/* ============================ OVERLAY MODE — the slide-out sheet ============================ */
.drawer-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    /* Closed state (checklist #2): visibility:hidden takes the whole overlay OUT of the a11y tree
     * AND the tab order — a translated-offscreen panel alone stays focusable. The flip is DELAYED
     * one duration so the panel still animates OUT (the app-shell nav-open trick); it flips to
     * visible instantly on open. */
    visibility: hidden;
    transition: visibility 0s linear var(--drawer-duration, 240ms);
}

.drawer-overlay.is-open {
    visibility: visible;
    transition: visibility 0s linear 0s;
}

/* Backdrop scrim — the shared, theme-flipping --overlay tint, faded with the panel. A pseudo-element
 * so it needs no extra DOM; a click on it lands on the .drawer root, which drawer.js reads as a
 * dismiss (the pseudo cannot be an event target, so the click registers on the host). */
.drawer-overlay::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--drawer-backdrop, var(--overlay));
    opacity: 0;
    transition: opacity var(--drawer-duration, 240ms) ease;
}

.drawer-overlay.is-open::before {
    opacity: 1;
}

/* The sheet itself — pinned to an edge, full height, parked off-canvas when closed. The body scrolls,
 * not the panel. */
.drawer-overlay .drawer-panel {
    position: absolute;
    inset-block: 0;
    width: min(var(--drawer-width), 100vw);
    max-height: 100vh;
    box-shadow: var(--drawer-shadow);
    overflow: hidden;
    transition: transform var(--drawer-duration, 240ms) var(--drawer-ease, cubic-bezier(0.2, 0.8, 0.2, 1));
}

/* Side: the physical edge the sheet pins to and slides from (the prop is physical: left|right). */
.drawer-overlay.drawer-right .drawer-panel {
    right: 0;
    border-left: 1px solid var(--panel-border-color, var(--line));
    transform: translateX(100%);
}

.drawer-overlay.drawer-left .drawer-panel {
    left: 0;
    border-right: 1px solid var(--panel-border-color, var(--line));
    transform: translateX(-100%);
}

.drawer-overlay.is-open .drawer-panel {
    transform: none;
}

/* Full-width sheet under the drawer breakpoint (page chrome -> the --bp-* viewport seam, substituted
 * to px at emission by CssService; per the modernity contract this stays a viewport @media). */
@media (max-width: 768px) {
    .drawer-overlay .drawer-panel {
        width: 100vw;
        max-width: 100vw;
    }
}

/* Scroll-lock while ANY overlay drawer is open — pure CSS via :has() (a sanctioned modern feature),
 * so there is no JS scroll-lock bookkeeping to drift. Mirrors the app-shell body-overflow lock. */
html:has(.drawer-overlay.is-open) {
    overflow: hidden;
}

/* ============================ DOCKED MODE — an in-flow layout column ============================ */
.drawer-docked {
    width: min(var(--drawer-width), 100%);
}

.drawer-docked .drawer-panel {
    height: 100%;
    border: 1px solid var(--panel-border-color, var(--line));
    border-radius: var(--drawer-border-radius);
    overflow: hidden;
}

/* ============================ PANEL HEADER / BODY (shared) ============================ */
.drawer-panel > .panel-head {
    flex: 0 0 auto;
    margin: 0;
    padding: var(--drawer-padding);
    border-block-end-color: var(--panel-border-color, var(--line));
}

.drawer-panel > .panel-body,
.drawer-panel > .panel-body > .panel-body-inner {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

.drawer-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--drawer-padding);
    font-size: var(--text-sm);
    line-height: 1.6;
}

/* Flush body (padded=false) — full-bleed content sits edge-to-edge (a grouped schedule with rules,
 * a table, media). */
.drawer-body-flush {
    padding: 0;
}

.drawer-body :first-child {
    margin-top: 0;
}

.drawer-body :last-child {
    margin-bottom: 0;
}

/* ============================ SIZE VARIANTS — the panel width ============================ */
.drawer-sm { --drawer-width: 300px; }
.drawer-md { --drawer-width: 360px; }   /* the study-reader rail precedent (the config default) */
.drawer-lg { --drawer-width: 460px; }

/* ============================ REDUCED MOTION ============================ */
/* Instant show / hide — no slide, fade, or delayed-visibility flip (checklist #3). */
@media (prefers-reduced-motion: reduce) {
    .drawer-overlay,
    .drawer-overlay::before,
    .drawer-overlay .drawer-panel {
        transition: none;
    }
}

/* @toc Dropdown */
/* Dropdown — the APG menu-button. A relatively-positioned wrapper holds the trigger + an absolutely-
 * positioned flyout; closed = the flyout carries [hidden] (display:none — gone from the AT tree), open =
 * dropdown.js removes it and adds .is-open to the wrapper. Colours are --dropdown-* (config facet):
 * ramp-lawful surface/line, item text via semantic tokens, danger = the destructive token. Behaviour +
 * focus movement: dropdown.js. */

.dropdown {
    position: relative;
    display: inline-block;
}

.dropdown-trigger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    appearance: none;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 500;
    padding: 7px 12px;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.dropdown-trigger:hover {
    background: var(--dropdown-item-hover-background-color);
}

.dropdown-trigger-icon,
.dropdown-caret {
    display: inline-flex;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.dropdown-caret {
    color: var(--text-muted);
    transition: transform 0.15s ease;
}

.dropdown.is-open .dropdown-caret {
    transform: rotate(180deg);
}

.dropdown-trigger-icon svg,
.dropdown-caret svg,
.dropdown-item-icon svg,
.dropdown-item-check svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* The flyout */
.dropdown-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: var(--z-popover);
    min-width: var(--dropdown-min-width);
    margin: 0;
    padding: 4px;
    background: var(--dropdown-surface);
    border: 1px solid var(--dropdown-line);
    border-radius: var(--dropdown-border-radius);
    box-shadow: var(--dropdown-shadow);
}

.dropdown-menu[hidden] {
    display: none;
}

.dropdown[data-align="end"] .dropdown-menu {
    left: auto;
    right: 0;
}

/* Items */
.dropdown-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    box-sizing: border-box;
    appearance: none;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--dropdown-item-text);
    font-family: inherit;
    font-size: var(--text-sm);
    text-align: left;
    text-decoration: none;
    padding: 7px 10px;
    cursor: pointer;
    white-space: nowrap;
}

.dropdown-item:hover,
.dropdown-item:focus-visible {
    background: var(--dropdown-item-hover-background-color);
    outline: none;
}

.dropdown-item:focus-visible {
    box-shadow: inset 0 0 0 2px var(--primary);
}

.dropdown-item.is-danger {
    color: var(--dropdown-danger-text);
}

.dropdown-item[aria-disabled="true"] {
    color: var(--dropdown-item-text-muted);
    opacity: var(--disabled-opacity);
    cursor: default;
}

.dropdown-item-icon {
    display: inline-flex;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: var(--dropdown-item-text-muted);
}

/* The checkbox/radio tick — reserved space always; shown only when aria-checked. */
.dropdown-item.is-toggle {
    padding-left: 8px;
}

.dropdown-item-check {
    display: inline-flex;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: var(--primary);
    visibility: hidden;
}

.dropdown-item[aria-checked="true"] .dropdown-item-check {
    visibility: visible;
}

.dropdown-item-label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dropdown-item-badge {
    flex: 0 0 auto;
    min-inline-size: 18px;
    justify-content: center;
}

.dropdown-item-badge[data-empty="true"],
.dropdown-item-badge[hidden] {
    display: none;
}

/* Groups + derived separators */
/* Label TREATMENT comes from the shared .ui-label recipe (R5-B, applied in dropdown.blade.php); this rule
   keeps only STRUCTURAL styles (never a label property — one-edit revert lives in base.css's .ui-label). */
.dropdown-group-label {
    margin: 0;
    padding: 6px 10px 2px;
}

.dropdown-sep {
    height: 1px;
    margin: 4px 0;
    background: var(--dropdown-line);
}

/* @toc Dynamic filter bar */
/*
 * Dynamic Filter Bar presents applied Chips and one searchable Combobox inside a compact shared surface.
 * Its single anchored editor preserves draft state without adding permanent page height or product-specific chrome.
 * Flexible sizing keeps labels, popup choices, actions, and validation inside narrow containers in either theme.
 */
.dynamic-filter-bar {
    position: relative;
    min-inline-size: 0;
    color: var(--dynamic-filter-bar-text-color, var(--text));
}

.dynamic-filter-bar [hidden] {
    display: none;
}

.dynamic-filter-bar-fallback,
.dynamic-filter-bar-fallback-field,
.dynamic-filter-bar-editor {
    display: grid;
    gap: var(--space-3);
    min-inline-size: 0;
}

.dynamic-filter-bar-fallback {
    padding: var(--dynamic-filter-bar-editor-padding, var(--space-4));
    background: var(--dynamic-filter-bar-background-color, var(--surface));
    border: 1px solid var(--dynamic-filter-bar-border-color, var(--line));
    border-radius: var(--dynamic-filter-bar-border-radius, var(--radius-control));
}

.dynamic-filter-bar-fallback-field + .dynamic-filter-bar-fallback-field {
    padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--dynamic-filter-bar-border-color, var(--line));
}

.dynamic-filter-bar-enhanced {
    position: relative;
    min-inline-size: 0;
}

.dynamic-filter-bar-surface {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--dynamic-filter-bar-gap, var(--space-2));
    min-inline-size: 0;
    padding: var(--dynamic-filter-bar-padding, var(--space-2));
    background: var(--dynamic-filter-bar-background-color, var(--surface));
    border: 1px solid var(--dynamic-filter-bar-border-color, var(--neutral-500));
    border-radius: var(--dynamic-filter-bar-border-radius, var(--radius-control));
}

.dynamic-filter-bar-surface:focus-within {
    border-color: var(--dynamic-filter-bar-focus-color, var(--primary));
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--dynamic-filter-bar-focus-color, var(--primary)) 24%, transparent);
}

.dynamic-filter-bar-chips,
.dynamic-filter-bar-draft-values {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-inline-size: 0;
}

.dynamic-filter-bar-chips:empty {
    display: none;
}

.dynamic-filter-bar-chips .ui-chip,
.dynamic-filter-bar-draft-values .ui-chip {
    max-inline-size: 100%;
    overflow-wrap: anywhere;
}

.dynamic-filter-bar-picker {
    flex: 1 1 var(--dynamic-filter-bar-picker-width, 16rem);
    min-inline-size: min(100%, 10rem);
    max-inline-size: 100%;
}

.dynamic-filter-bar-picker .combobox {
    margin: 0;
}

.dynamic-filter-bar-picker .combobox-label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.dynamic-filter-bar-picker .combobox-input {
    padding-inline: var(--space-2);
    background: transparent;
    border-color: transparent;
}

.dynamic-filter-bar-picker .combobox-input:focus-visible {
    border-color: transparent;
    outline: 0;
}

.dynamic-filter-bar-picker .combobox-popup {
    inline-size: min(30rem, 100%);
    max-inline-size: 100%;
}

.dynamic-filter-bar-choice-group,
.dynamic-filter-bar-date-group {
    display: grid;
    gap: var(--space-2);
    min-inline-size: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.dynamic-filter-bar-choice-group legend,
.dynamic-filter-bar-date-group legend,
.dynamic-filter-bar-editor h3 {
    font-weight: 700;
}

.dynamic-filter-bar-choice-group p,
.dynamic-filter-bar-draft-count,
.dynamic-filter-bar-context,
.dynamic-filter-bar-editor h3 {
    margin: 0;
}

.dynamic-filter-bar-choice-group p,
.dynamic-filter-bar-draft-count,
.dynamic-filter-bar-context {
    color: var(--dynamic-filter-bar-muted-color, var(--text-muted));
    font-size: var(--text-sm);
}

.dynamic-filter-bar-choice-group .checkbox {
    max-inline-size: 100%;
}

.dynamic-filter-bar-editor {
    position: absolute;
    z-index: var(--z-popover);
    inset-block-start: calc(100% + var(--space-2));
    inset-inline-start: 0;
    inline-size: min(var(--dynamic-filter-bar-editor-width, 32rem), 100%);
    max-inline-size: calc(100vw - 2rem);
    max-block-size: min(32rem, calc(100dvh - 2rem));
    padding: var(--dynamic-filter-bar-editor-padding, var(--space-4));
    overflow: auto;
    background: var(--dynamic-filter-bar-editor-background-color, var(--surface-raised));
    border: 1px solid var(--dynamic-filter-bar-editor-border-color, var(--line));
    border-radius: var(--dynamic-filter-bar-editor-border-radius, var(--radius-control));
    box-shadow: var(--shadow-lg);
}

.dynamic-filter-bar-date-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
    min-inline-size: 0;
}

.dynamic-filter-bar-editor-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.dynamic-filter-bar-error {
    margin: 0;
    color: var(--dynamic-filter-bar-error-color, var(--danger));
    font-size: var(--text-sm);
}

.dynamic-filter-bar .combobox,
.dynamic-filter-bar .input {
    min-inline-size: 0;
    margin-block-end: 0;
}

@media (max-width: 30rem) {
    .dynamic-filter-bar-surface {
        padding: var(--dynamic-filter-bar-narrow-padding, var(--space-2));
    }

    .dynamic-filter-bar-chips,
    .dynamic-filter-bar-picker {
        flex-basis: 100%;
    }

    .dynamic-filter-bar-date-grid {
        grid-template-columns: 1fr;
    }

    .dynamic-filter-bar-editor {
        inline-size: 100%;
        max-inline-size: 100%;
        padding: var(--dynamic-filter-bar-narrow-editor-padding, var(--space-3));
    }

    .dynamic-filter-bar-editor-actions .button {
        flex: 1 1 8rem;
    }
}

@media (forced-colors: active) {
    .dynamic-filter-bar-surface,
    .dynamic-filter-bar-editor {
        border-color: CanvasText;
    }

    .dynamic-filter-bar-surface:focus-within {
        outline: 2px solid Highlight;
        outline-offset: 2px;
        box-shadow: none;
    }
}

/* @toc Editor tools */
/*
 * The Editor tools pane (E3 / map-F6) — now the LIBRARY (#1310): the component catalog, promoted to the left
 * rail. A search box, then one taxonomy group per docs-group (Primitives · Patterns · Sections), each a flat
 * list of component rows. A row is a SELECT button (the type name → the Inspector) beside an INSERT button
 * (click, or drag via SortableJS → place an instance).
 *
 * Colour comes from the ramp only — semantic tokens first (--text-muted, --line, --surface-raised), never a
 * literal. The pane sizes to its host and scrolls its own overflow rather than pushing the page.
 *
 * SCOPED TO .editor-tools-pane, NOT .editor-tools: the editor frame's Tools REGION already carries
 * `class="editor-region editor-tools"` (resources/views/editor/show.blade.php), so styling the bare
 * `.editor-tools` here would reach up and restyle that region — this component's box, applied to its own host.
 * The tokens stay --editor-tools-* (they are named for the component, not the selector).
 *
 * THE [hidden] TRAP. The search (editor-tools.js) hides non-matching rows AND empty groups by toggling their
 * `hidden` property. An author `display` on either would WIN over the UA `[hidden] { display: none }` and defeat
 * the filter — so every `display` on a row or group is guarded `:not([hidden])`.
 */

/* The `:not([hidden])` guard is THE TRAP ABOVE, applied to the pane itself (#1318). The comment warned about
 * rows and groups because that is where the search filter bites — but the Inspector reuses this class on every
 * type→config pane (editor/_inspector.blade.php adds it for target scope), and editor-live.js hides those by
 * setting `hidden`. An unguarded author `display` beat the UA rule, so all ~70 stayed on screen: the landing
 * page's Config listed a BarBruiser study sheet, and the document ran 38,000px tall. The pane's own rule needed
 * the same guard its rows already had. */
.editor-tools-pane:not([hidden]) {
    display: flex;
    flex-direction: column;
    gap: var(--editor-tools-gap, var(--space-4));
    padding: var(--editor-tools-padding, var(--space-4));
    block-size: 100%;
    overflow-y: auto;
}

/* The Library sits tighter than the base pane: the group hairlines are the section dividers (James's "lines,
 * not cards"), so the inter-child gap shrinks and lets those borders do the separating. */
.editor-library-pane {
    --editor-tools-gap: var(--space-2);
}

/*
 * KILL THE CARD, KEEP THE DISCLOSURE (James, 2026-07-16): "I don't want the cards anymore, they take up too
 * much space and honestly aren't the language of the app... use lines to separate the sections."
 *
 * A taxonomy group is an <x-details>, and THE CARD IS THAT PRIMITIVE'S OWN SKIN — .details paints a border, a
 * radius and --surface-raised behind every disclosure. Strip it to a flat section divided by a hairline. Fill,
 * radius and padding come from the primitive's OWN --details-* theming surface (custom properties inherit, so
 * setting them on the row reaches .details-summary and .details-body without touching either). The border is a
 * real override because a merely transparent one would still occupy its 1px of layout.
 *
 * Scoped under .editor-tools-pane (0,2,0) so it beats .details (0,1,0) on SPECIFICITY, not on bundle order.
 * editor-tools.css does happen to bundle after details.css today, but that is an accident of the manifest's
 * order — a reshuffle must never silently restore the boxes.
 *
 * The group carries ONLY .editor-library-group, never also .editor-tools-group: that class sets display:flex,
 * which on a <details> both mislays the summary/body and (being an author display) defeats the search's hide.
 */
.editor-tools-pane .editor-library-group {
    --details-background-color: transparent;
    --details-border-radius: 0;
    --details-padding-x: 0;
    --details-padding-y: var(--space-2);
    border: 0;
    border-block-end: 1px solid var(--line);
    padding-block-end: 0;
}

/* The last group is unruled — the pane edge is the divider (the accordion's flat-divided-list language). */
.editor-tools-pane .editor-library-group:last-of-type {
    border-block-end: 0;
}

/* A flat list of component rows inside the group's disclosure body. role="list" in the markup keeps the list
 * semantics that list-style:none otherwise strips in some engines. */
.editor-library-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* A component row: the type name grows, Insert sits at the end. Display is guarded :not([hidden]) so the
 * search's `item.hidden = true` genuinely hides the row. */
.editor-library-item:not([hidden]) {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

/* SELECT: the type name fills the row. Ghost buttons centre their label; a list row reads left-aligned, and the
 * item shrinks (min-inline-size:0) so a long label can't force the row wider than the pane. Its selected state
 * (aria-pressed, set by editor-live.js when this type owns the Inspector) gets a raised fill + weight. */
.editor-library-select {
    flex: 1;
    min-inline-size: 0;
    justify-content: flex-start;
    text-align: start;
}

.editor-library-select[aria-pressed="true"] {
    background: var(--surface-raised);
    font-weight: 600;
}

/* INSERT: the row's trailing action — never grows. It is also the SortableJS drag source (editor-drag.js binds
 * [data-editor-insert]); click stays the primary, keyboard-operable path (WCAG 2.2 AA), drag the enhancement. */
.editor-library-insert {
    flex: none;
}

/* The search's empty state. No `display` declared, so its markup `hidden` (and the JS toggling it) works. */
.editor-library-no-results {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* The tenant-has-nothing state (the @empty branch) — distinct from a no-search-match, and never hidden. */
.editor-tools-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* @toc Embed */
/* Embed — the iframe primitive. Structural + a subtle load-time surface; an optional aspect-ratio
 * rides the --embed-ratio custom property the view sets per instance. The 4 migrated call sites keep
 * their own sizing class (.uploads-view-frame / .inv-view-embed / .uploads-modal-frame / the
 * .mps-recipe-video wrapper), each of which is authored AFTER this in the bundle (or more specific),
 * so their border / radius / background win — this base only fills the gaps a bare <iframe> leaves. */

.embed {
    display: block;
    width: 100%;
    max-width: 100%;
    border: 0;
    background: var(--embed-background-color);
    aspect-ratio: var(--embed-ratio, auto);
}

/* @toc Emoji picker */
/*
 * Emoji picker (#844) — a floating panel: search + tone above, category strip, then the scrolling result grid.
 *
 * Every --emoji-picker-* var is emitted from config/components/emoji-picker.php's css facet, each with a
 * semantic-token fallback so the component themes per tenant and still renders if a token is absent. The picker
 * is a RAISED surface (it floats over a page), unlike the flat editor chrome — that is the one place it departs
 * from the workspace's flatness, and deliberately.
 *
 * Only the GRID scrolls. The search, tone selector and category strip stay put, so the thing you are typing into
 * never scrolls away from you — the same containment lesson as the editor rail (#1298).
 */

.emoji-picker {
    display: flex;
    flex-direction: column;
    inline-size: var(--emoji-picker-width, 352px);
    max-inline-size: 100%;
    background: var(--emoji-picker-surface, var(--surface-raised));
    border: 1px solid var(--emoji-picker-border, var(--line));
    border-radius: var(--emoji-picker-border-radius, var(--radius-lg));
    overflow: hidden;
}

.emoji-picker-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    border-block-end: 1px solid var(--line);
}

.emoji-picker-search {
    flex: 1 1 auto;
    min-inline-size: 0;
}

.emoji-picker-search-input {
    inline-size: 100%;
    padding: var(--space-2) var(--space-3);
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    font-size: var(--text-sm);
}

.emoji-picker-search-input:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--emoji-picker-focus-ring, var(--primary));
}

/* ── skin tone ─────────────────────────────────────────────────────────────────────────────────── */

.emoji-picker-tones {
    display: flex;
    flex-shrink: 0;
    gap: var(--space-1);
}

.emoji-picker-tone {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-1);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius);
    cursor: pointer;
}

.emoji-picker-tone:hover {
    background: var(--emoji-picker-cell-hover, var(--surface));
}

.emoji-picker-tone:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--emoji-picker-focus-ring, var(--primary));
}

/* The chosen tone reads as pressed — a border, not just a background, so it survives forced-colors mode. */
.emoji-picker-tone[aria-pressed="true"] {
    border-color: var(--emoji-picker-cell-selected, var(--primary));
}

/* The swatch IS the emoji at that tone (see the view) — no hand-authored colour, and it renders exactly as
   the platform will render the picked tone. */
.emoji-picker-tone-swatch {
    display: block;
    font-size: 15px;
    line-height: 1;
}


/* ── categories ────────────────────────────────────────────────────────────────────────────────── */

.emoji-picker-categories {
    display: flex;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-3);
    overflow-x: auto;
    border-block-end: 1px solid var(--line);
}

.emoji-picker-category {
    flex-shrink: 0;
    padding: var(--space-1) var(--space-2);
    color: var(--text-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius);
    font-size: var(--text-xs);
    white-space: nowrap;
    cursor: pointer;
}

.emoji-picker-category:hover {
    color: var(--text);
    background: var(--emoji-picker-cell-hover, var(--surface));
}

.emoji-picker-category:focus-visible {
    outline: 2px solid transparent;
    outline-offset: -2px;
    box-shadow: inset 0 0 0 2px var(--emoji-picker-focus-ring, var(--primary));
}

.emoji-picker-category[aria-selected="true"] {
    color: var(--text);
    background: var(--surface);
}

/* ── the grid ──────────────────────────────────────────────────────────────────────────────────── */

.emoji-picker-grid {
    block-size: var(--emoji-picker-grid-height, 260px);
    padding: var(--space-2);
    overflow-y: auto;
}

.emoji-picker-section-label {
    margin: var(--space-2) 0 var(--space-1);
    padding-inline: var(--space-1);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.emoji-picker-row {
    display: flex;
}

.emoji-picker-cell {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--emoji-picker-cell-size, 40px);
    block-size: var(--emoji-picker-cell-size, 40px);
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: var(--emoji-picker-cell-border-radius, var(--radius));
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
}

.emoji-picker-cell:hover {
    background: var(--emoji-picker-cell-hover, var(--surface));
}

/* The roving-tabindex cell is the ONE tab stop; its focus ring is inset so it never clips against a neighbour. */
.emoji-picker-cell:focus-visible {
    outline: 2px solid transparent;
    outline-offset: -2px;
    box-shadow: inset 0 0 0 2px var(--emoji-picker-focus-ring, var(--primary));
}

.emoji-picker-message {
    margin: 0;
    padding: var(--space-4) var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-align: center;
}

/* Reduced motion: nothing here animates, stated for honesty rather than as a no-op — the picker's only motion
   would be a hover tint, which is instant by design. */

/* @toc Feedback glyph */
/*
 * FeedbackGlyph presentation.
 * The component uses one registered speech-bubble outline for its empty, plus, and numbered states.
 * Semantic surface, current-color, and shared type tokens keep the decorative mark theme-aware.
 */
.feedback-glyph {
    display: block;
    inline-size: 1em;
    block-size: 1em;
    overflow: visible;
    color: currentColor;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.feedback-glyph-bubble {
    fill: var(--surface);
}

.feedback-glyph-plus {
    fill: none;
    /* The registered plus is scaled to fit the bubble. Compensate its source stroke so the rendered
       1.5px mark matches the unscaled bubble instead of becoming optically lighter. */
    stroke-width: 2.5;
}

.feedback-glyph-number {
    fill: currentColor;
    stroke: none;
    font-family: var(--font-sans);
    font-size: 8px;
    font-weight: 700;
    line-height: 1;
}

.feedback-glyph[data-feedback-glyph-digits="3"] .feedback-glyph-number {
    font-size: 6.5px;
}

/* @toc Fieldset */
/* Fieldset. All --fieldset-* tokens come from config/components/fieldset.php['css'];
 * each var carries the matching global-token fallback so the file reads on its own. */

.fieldset {
    /* Outer spacing rides the --space-* scale (F5): the old 0 0 14px was off-scale and
     * contract-invisible; snapped to the nearest rung (--space-4 = 16px, matching the
     * component's own 16px padding) so a builder reading the contract sees a named token. */
    margin: 0 0 var(--space-4);
    padding: var(--fieldset-padding, 16px);
    border: 1px solid var(--fieldset-border, var(--line));
    border-radius: var(--fieldset-border-radius, 8px);
    background: var(--fieldset-background-color, var(--surface));
    min-inline-size: 0;
}

.fieldset-legend {
    padding: 0 var(--fieldset-legend-padding-x, 6px);
    color: var(--fieldset-legend-color, var(--text));
    font-size: var(--fieldset-legend-font-size, var(--text-sm));
    font-weight: var(--fieldset-legend-font-weight, 500);
}

.fieldset-body {
    display: flex;
    flex-direction: column;
    gap: var(--fieldset-gap, 4px);
}

/* Keyboard focus on a control inside the group lifts the border to the focus
 * colour as a grouping cue; the controls keep their own :focus-visible rings. */
.fieldset:focus-within {
    border-color: var(--fieldset-focus-border, var(--primary));
}

/* Native disabled cascades to nested controls; dim the group to match. */
.fieldset:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* @toc Figure */
/* Figure. All --figure-* tokens come from config/components/figure.php['css'];
 * structural rules aside, the config is the component's full theming surface. */

.figure {
    margin: 0;
    display: inline-block;
    max-width: 100%;
}

.figure-img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--figure-border, var(--line));
    border-radius: var(--figure-border-radius, 0);
    background: var(--figure-background-color, var(--surface-raised));
}

/* The rounded variant — opt-in soft corners on the image. */
.figure.is-rounded .figure-img {
    border-radius: var(--figure-border-radius-rounded, 12px);
}

.figure-caption {
    margin-top: var(--figure-caption-gap, 8px);
    color: var(--figure-caption-color, var(--text-muted));
    font-size: var(--figure-caption-font-size, var(--text-sm));
    line-height: 1.5;
    text-align: var(--figure-caption-align, start);
}

/* @toc Financials import */
/* Financials import widget - the account field + paste/drop CSV form, and the
 * recent-imports section on the page. Themes off shared tokens (cloned from
 * barbruiser-create.css). The recent-imports table reuses .fin-table from the ledger.
 * View: financials-import.blade.php; behaviour: financials-import.js. */

.financials-import {
    max-width: var(--max-w-3xl);
}

/* Drop zone - a dashed target that also opens the file picker on tap (it wraps a
 * hidden file input), so desktop drag and mobile tap both work. .is-over matches
 * the class toggled in financials-import.js. */
.fin-import-drop {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 22px;
    margin-bottom: 12px;
    border: 2px dashed var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.fin-import-drop:hover,
.fin-import-drop.is-over {
    border-color: var(--primary);
    color: var(--text);
}

/* Keyboard focus — the file <input> is .sr-only (focusable but off-screen), so its focus ring must ride
 * the visible drop-zone label (WCAG 2.4.7). :focus-visible = keyboard only; the transparent outline is the
 * forced-colors fallback (box-shadow is dropped in high-contrast, so the outline gets promoted). */
.fin-import-drop:has(input:focus-visible) {
    border-color: var(--primary);
    color: var(--text);
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--primary);
}

/* The CSV paste box is the textarea primitive; only override to a monospace face
 * so pasted columns line up (border / bg / focus come from textarea.css). */
.textarea-field.fin-import-textarea {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1.5;
}

.fin-import-actions {
    margin-top: 12px;
}

/* Recent imports (page section below the widget) */
.fin-import-recent {
    margin-top: var(--space-10);
}

.fin-import-recent-h {
    margin: 0 0 var(--space-3);
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text);
}

.fin-import-undo {
    display: inline;
}

.fin-import-recent .table-wrap {
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}

.fin-import-recent .table th:first-child,
.fin-import-recent .table td:first-child {
    padding-inline-start: var(--space-4);
}

.fin-import-recent .table th:last-child,
.fin-import-recent .table td:last-child {
    padding-inline-end: var(--space-4);
}

/* @toc Financials ledger */
/* Financials ledger - the bulk-categorize controls and the per-row
 * cells inside the DataTable pattern. The table chrome, sortable headers, selection
 * column and pager come from the table / data-table / pagination components; this
 * file styles only the Finance-specific bits: amount colours and the category cell
 * with its inline select. Filters use the shared DynamicFilterBar stylesheet.
 * View: financials-ledger.blade.php; JS: financials-ledger.js. */

.financials-ledger {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    container-type: inline-size;
}

/* Bulk-categorize bar (the DataTable `bulk` slot): the picker + count + save. */
.data-table-bulk .select {
    flex: 0 1 200px;
}

.fin-bulk-count {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.fin-bulk-save {
    margin-left: auto;
}

/* Amount cell - tabular, green for income / red for expense (right-aligned by the
 * table primitive's table-right class on the cell). */
.fin-cell-amount {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.fin-cell-amount.is-positive {
    color: var(--financials-ledger-amount-positive);
}

.fin-cell-amount.is-negative {
    color: var(--financials-ledger-amount-negative);
}

.fin-cell-desc {
    max-width: 360px;
    white-space: normal;
    color: var(--text);
}

.fin-cell-account {
    color: var(--text-muted);
}

/* Category cell - a colour dot + the inline select primitive. */
.fin-cell-cat {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.fin-cell-cat .checkbox {
    position: relative;
}

.fin-cell-cat-split {
    flex-wrap: wrap;
    min-width: 180px;
    font-size: var(--text-sm);
}

.fin-split-badge {
    padding: 0 var(--space-2);
    border-radius: var(--radius-full);
    color: var(--financials-ledger-amount-positive);
    background: color-mix(in srgb, var(--financials-ledger-amount-positive) 12%, transparent);
    font-size: var(--text-xs);
    font-weight: 700;
}

.fin-cell-cat .select {
    flex: 1;
    min-width: 150px;
}

.fin-cat-dot {
    flex-shrink: 0;
    width: 9px;
    height: 9px;
    border-radius: var(--radius-full);
    background: var(--dot, var(--financials-ledger-chip-empty));
}

/* A per-row select changed since load (client-side) gets a subtle accent so unsaved
 * marks are visible before hitting Save. */
.select-field.fin-cat-select.is-dirty {
    border-color: var(--primary);
}

.fin-split-editors {
    display: grid;
    gap: var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
}

.fin-split-editors > h2,
.fin-split-editors > p {
    margin: 0;
}

.fin-split-editor {
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

.fin-split-editor > summary {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    cursor: pointer;
    font-weight: 600;
}

.fin-split-editor > .details-summary .details-summary-text {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    align-items: center;
}

.fin-split-editor:target,
.fin-split-editor[open] {
    border-color: var(--primary);
}

.fin-split-summary-amount,
.fin-split-remaining {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.fin-split-body {
    display: grid;
    gap: var(--space-4);
    padding: 0 var(--space-4) var(--space-4);
}

.fin-split-editor > .details-body {
    padding: 0;
    color: inherit;
    font-size: inherit;
    line-height: inherit;
}

.fin-split-lines {
    display: grid;
    gap: var(--space-3);
}

.fin-split-line.fieldset {
    margin: 0;
    padding: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.fin-split-line > .fieldset-legend {
    font-size: var(--text-sm);
    font-weight: 700;
}

.fin-split-line > .fieldset-body {
    display: grid;
    grid-template-columns: 1fr;
    align-items: stretch;
    gap: var(--space-3);
}

.fin-split-line .input,
.fin-split-line .select {
    width: 100%;
}

.fin-split-remaining {
    margin: 0;
    color: var(--text-muted);
    font-weight: 600;
}

.fin-split-remaining[data-state="balanced"] {
    color: var(--financials-ledger-amount-positive);
}

.fin-split-remaining[data-state="invalid"],
.fin-split-remaining[data-state="unbalanced"],
.fin-link-warning {
    color: var(--financials-ledger-amount-negative);
}

.fin-link-warning {
    margin: 0;
    font-size: var(--text-xs);
}

.fin-split-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.fin-remove-split {
    padding-top: var(--space-3);
    border-top: 1px solid var(--line);
}

@container (min-width: 36rem) {
    .fin-split-actions {
        flex-direction: row;
        justify-content: flex-end;
    }
}

@container (min-width: 62rem) {
    .fin-split-line > .fieldset-body {
        grid-template-columns: minmax(9rem, 1fr) minmax(8rem, .7fr) minmax(11rem, 1.3fr) minmax(7rem, .65fr) minmax(9rem, .8fr) auto;
        align-items: end;
    }
}

/* Empty state */
.fin-empty {
    padding: var(--space-12) var(--space-4);
    text-align: center;
    color: var(--text-muted);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}

@container (max-width: 48rem) {
    .fin-split-editor > .details-summary .details-summary-text,
    .fin-split-actions {
        align-items: stretch;
        flex-direction: column;
    }

    .fin-split-line > .fieldset-body {
        grid-template-columns: 1fr;
    }
}

/* @toc Financials overview */
/* Financials Budget presents one completed month as an exact cash table and two supporting pies.
 * The shared module navigation stays compact, scrollable and keyboard reachable at narrow widths.
 * Container queries let controls and charts respond to the content width rather than the viewport. */

.fin-module-nav {
    display: flex;
    gap: var(--space-1);
    max-inline-size: 100%;
    min-inline-size: 0;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    margin-block-end: var(--space-6);
    padding-block-start: var(--space-1);
    padding-inline: var(--space-1);
    border-block-end: 1px solid var(--line);
    scroll-padding-inline: var(--space-1);
    scrollbar-width: none;
}

.fin-module-nav::-webkit-scrollbar {
    display: none;
}

.fin-module-nav a {
    flex: none;
    padding-block: var(--space-3);
    padding-inline: var(--space-3);
    border-block-end: 2px solid transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: 600;
    text-decoration: none;
}

.fin-module-nav a:hover,
.fin-module-nav a:focus-visible {
    color: var(--text);
}

.fin-module-nav a[aria-current="page"] {
    border-block-end-color: var(--primary);
    color: var(--primary);
}

.fin-budget {
    container-type: inline-size;
    display: grid;
    gap: var(--space-6);
}

.fin-budget-heading {
    display: grid;
    gap: var(--space-5);
}

.fin-budget-heading h2,
.fin-budget-heading p,
.fin-budget-chart p {
    margin: 0;
}

.fin-budget-kicker {
    color: var(--primary);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.fin-budget-heading h2 {
    margin-block: var(--space-1);
}

.fin-budget-heading > div > p:last-child,
.fin-budget-chart p,
.fin-budget-months {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.fin-budget-controls {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
    align-items: end;
}

.fin-budget-controls .input,
.fin-budget-controls .select {
    inline-size: 100%;
}

.fin-budget-months {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
    align-items: center;
}

.fin-budget-table-wrap {
    overflow-x: auto;
}

.fin-budget-table-wrap > .table {
    inline-size: 100%;
    border-collapse: collapse;
}

.fin-budget-table-wrap > .table th,
.fin-budget-table-wrap > .table td {
    padding: var(--space-3);
    border-block-end: 1px solid var(--line);
    text-align: start;
    vertical-align: middle;
}

.fin-budget-table-wrap > .table th:last-child,
.fin-budget-table-wrap > .table td:last-child,
.fin-budget-section td,
.fin-budget-total td {
    text-align: end;
    font-variant-numeric: tabular-nums;
}

.fin-budget-section {
    background: var(--surface);
    font-weight: 800;
}

.fin-budget-table-wrap > .table tbody .fin-budget-section,
.fin-budget-table-wrap > .table tbody .fin-budget-section:hover {
    background: var(--surface);
}

.fin-budget-row > td {
    padding: 0;
}

.fin-budget-details.details {
    border: 0;
    border-radius: 0;
    background: transparent;
}

.fin-budget-details > .details-summary {
    padding: var(--space-3);
    background: transparent;
}

.fin-budget-row-heading {
    display: flex;
    flex: 1;
    justify-content: space-between;
    gap: var(--space-4);
    align-items: center;
}

.fin-budget-row-heading > span:first-child {
    display: grid;
    gap: var(--space-1);
}

.fin-budget-row-heading small {
    color: var(--text-muted);
    font-weight: 400;
}

.fin-budget-amount {
    flex: none;
    font-variant-numeric: tabular-nums;
}

.fin-budget-details > .details-body {
    padding: 0 var(--space-3) var(--space-4);
    color: inherit;
}

.fin-budget-entries {
    display: grid;
    gap: var(--space-2);
}

.fin-budget-entry {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: var(--space-3);
    align-items: baseline;
    padding-block: var(--space-2);
    border-block-end: 1px solid var(--line);
    font-size: var(--text-sm);
}

.fin-budget-entry > span:first-child {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-2);
}

.fin-budget-entry time,
.fin-budget-entry small {
    color: var(--text-muted);
}

.fin-budget-total {
    font-weight: 900;
}

.fin-budget-charts {
    display: grid;
    gap: var(--space-5);
}

.fin-budget-chart.figure {
    display: grid;
    gap: var(--space-4);
    margin: 0;
    padding: var(--space-5);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

.fin-budget-chart > .figure-caption {
    order: -1;
    margin-top: 0;
    color: inherit;
    font-size: inherit;
    font-weight: 800;
}

.fin-budget-chart-layout {
    display: grid;
    grid-template-columns: minmax(7rem, 9rem) minmax(0, 1fr);
    gap: var(--space-5);
    align-items: center;
}

.fin-budget-donut {
    inline-size: 100%;
    transform: rotate(-90deg);
}

.fin-budget-donut-track,
.fin-budget-donut-slice {
    fill: none;
    stroke-width: 7;
}

.fin-budget-donut-track {
    stroke: var(--line);
}

.fin-budget-donut-slice {
    stroke: var(--primary);
}

.fin-budget-donut-slice:nth-of-type(3n + 2) {
    stroke: var(--success);
}

.fin-budget-donut-slice:nth-of-type(3n) {
    stroke: var(--warning);
}

.fin-budget-chart ul {
    display: grid;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.fin-budget-chart li {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    font-size: var(--text-sm);
}

.fin-budget-chart li strong {
    font-variant-numeric: tabular-nums;
}

@container (min-width: 42rem) {
    .fin-budget-heading {
        grid-template-columns: minmax(0, 1fr) minmax(28rem, auto);
        align-items: end;
    }

    .fin-budget-controls {
        grid-template-columns: minmax(12rem, 1fr) minmax(10rem, auto) auto;
    }

    .fin-budget-charts {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (max-width: 24rem) {
    .fin-budget-row-heading {
        flex-wrap: wrap;
    }

    .fin-budget-amount {
        margin-inline-start: auto;
    }

    .fin-budget-entry,
    .fin-budget-chart-layout {
        grid-template-columns: minmax(0, 1fr);
    }

    .fin-budget-entry > :not(:first-child) {
        justify-self: start;
    }
}

/* @toc Financials pnl */
/* Financials P&L - summary tiles + monthly and by-category tables. Colours from
 * --financials-pnl-* (config); the tables reuse the ledger's .fin-table classes.
 * View: financials-pnl.blade.php. */

.financials-pnl {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

/* Book toggle - a segmented control switching the eightythree (business) and Household views. */
.fin-book-toggle {
    display: inline-flex;
    align-self: flex-start;
    gap: 4px;
    padding: 4px;
    background: var(--surface-sunken, var(--neutral-200));
    border-radius: 10px;
}

.fin-book-toggle a {
    padding: 8px 18px;
    border-radius: 7px;
    font-weight: 700;
    font-size: var(--text-sm);
    text-decoration: none;
    color: var(--text-muted);
}

.fin-book-toggle a:hover {
    color: var(--text);
}

.fin-book-toggle a.is-active {
    background: var(--surface, var(--neutral-50));
    color: var(--text);
}

.fin-pnl-note {
    margin: 10px 2px 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
    line-height: 1.5;
}

/* Summary tiles - total income / expense / net for the whole period. */
.fin-pnl-summary {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
}

.fin-pnl-tile {
    flex: 1 1 160px;
    padding: var(--space-4) var(--space-5);
    background: var(--financials-pnl-tile-background-color);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}

.fin-pnl-tile-label {
    margin: 0 0 var(--space-2);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 700;
    color: var(--text-muted);
}

.fin-pnl-tile-value {
    margin: 0;
    font-size: var(--text-2xl);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

.fin-pnl-tile-value.is-income {
    color: var(--financials-pnl-income);
}

.fin-pnl-tile-value.is-expense {
    color: var(--financials-pnl-expense);
}

.fin-pnl-section-h {
    margin: 0 0 var(--space-3);
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text);
}

/* A type subheading row inside the by-category table (cell-level, since the table
 * primitive renders rows from arrays - every cell in the row carries the class). */
td.fin-pnl-grouprow {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 700;
    color: var(--text-muted);
    background: var(--surface-raised);
}

.fin-pnl-amount {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.fin-pnl-amount.is-income {
    color: var(--financials-pnl-income);
}

.fin-pnl-amount.is-expense {
    color: var(--financials-pnl-expense);
}

.fin-pnl-cat {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.fin-pnl-dot {
    flex-shrink: 0;
    width: 9px;
    height: 9px;
    border-radius: var(--radius-full);
    background: var(--dot, var(--text-muted));
}

td.fin-pnl-subtotal {
    font-weight: 700;
    color: var(--text);
    border-top: 2px solid var(--line);
}

/* @toc Floating dock */
/*
 * FloatingDock is the one shared bottom bar. It uses the app chrome's flat surface and spacing grammar,
 * reaches the viewport edge, and publishes its measured physical bounds for collision consumers.
 * Fixed-height layouts use a separate full-width reservation, which becomes zero behind the corner recovery
 * control while retaining the visual-viewport offset. Natural-scroll and overlay consumers keep physical bounds.
 */
:root {
    --app-frame-transition-duration: 260ms;
    --floating-dock-control-min-inline-size: 52px;
    --floating-dock-control-max-inline-size: max(112px, 7rem);
    --floating-dock-control-block-size: 58px;
    --floating-dock-overflow-fade-size: 20px;
    --app-bottom-bar-initial-occupied-height: calc(
        var(--floating-dock-control-block-size, 58px)
        + var(--app-chrome-row-padding, 8px)
        + max(var(--app-chrome-row-padding, 8px), env(safe-area-inset-bottom, 0px))
        + 1px
    );
    --app-visual-viewport-top: 0px;
    --app-visible-content-top: var(--app-visual-viewport-top, 0px);
    --app-bottom-bar-viewport-offset: 0px;
    --app-bottom-bar-layout-reservation: 0px;
    --app-bottom-bar-layout-block-end: calc(
        var(--app-bottom-bar-layout-reservation, 0px)
        + var(--app-bottom-bar-viewport-offset, 0px)
    );
    --app-bottom-bar-occupied-block-end: calc(
        var(--app-bottom-bar-occupied-height, var(--app-bottom-bar-initial-occupied-height))
        + var(--app-bottom-bar-viewport-offset, 0px)
    );
}

/* The authored expanded state reserves a row before enhancement measures it. An authored collapsed state and
   a page with no bar reserve no full-width row; the small recovery control is intentionally an overlay. */
:root:has([data-shared-bottom-bar]:not(.is-collapsed):not(.is-frame-hidden):not(.is-empty)) {
    --app-bottom-bar-layout-reservation: var(--app-bottom-bar-initial-occupied-height);
}

:root:has(body.app-frame-hidden) {
    --app-bottom-bar-layout-reservation: 0px;
}

.floating-dock {
    position: fixed;
    z-index: 30;
    inset-inline: 0;
    inset-block-end: var(--app-bottom-bar-viewport-offset, 0px);
    box-sizing: border-box;
    display: grid;
    grid-template-columns:
        minmax(var(--floating-dock-control-min-inline-size, 52px), 1fr)
        minmax(0, max-content)
        minmax(var(--floating-dock-control-min-inline-size, 52px), 1fr);
    align-items: center;
    column-gap: var(--app-chrome-control-gap, 8px);
    inline-size: auto;
    min-inline-size: 0;
    min-block-size: var(--app-bottom-bar-initial-occupied-height);
    margin: 0;
    padding-block: var(--app-chrome-row-padding, 8px) max(var(--app-chrome-row-padding, 8px), env(safe-area-inset-bottom));
    padding-inline: max(var(--app-chrome-inline-padding, 32px), env(safe-area-inset-left)) max(var(--app-chrome-inline-padding, 32px), env(safe-area-inset-right));
    border: 0;
    border-block-start: 1px solid var(--line);
    border-radius: 0;
    color: var(--text);
    background: var(--page-bg);
    box-shadow: none;
    transition:
        inset-inline-start var(--app-frame-transition-duration) ease,
        inline-size var(--app-frame-transition-duration) ease,
        min-block-size var(--app-frame-transition-duration) ease,
        padding var(--app-frame-transition-duration) ease,
        background-color var(--app-frame-transition-duration) ease,
        opacity var(--app-frame-transition-duration) ease;
}

/* Frame recovery is dormant while the frame is expanded. If no page tools remain, the bar therefore owns
   no visible occupancy at all. A hidden Frame removes this condition through .is-frame-hidden and keeps the
   measured branded recovery control available without reserving content space. */
.floating-dock[data-floating-dock-frame].is-empty:not(.is-frame-hidden) {
    display: none;
    min-block-size: 0;
    padding: 0;
    border: 0;
    box-shadow: none;
}

.floating-dock[data-floating-dock-frame] {
    grid-template-columns:
        minmax(0, 1fr)
        minmax(0, max-content)
        minmax(0, 1fr);
}

.floating-dock[data-floating-dock-frame]:not(:has(> .floating-dock-persistent)) {
    column-gap: 0;
}

/* The base outer tracks reserve one fixed control. Expand both tracks only when the persistent region carries
   the two-control identity surface, so Back reserves one control while fitted actions stay truly centred. */
.floating-dock:has(> .floating-dock-persistent [data-admin-impersonation-controls]) {
    grid-template-columns:
        minmax(calc(var(--floating-dock-control-min-inline-size, 52px) + var(--floating-dock-control-min-inline-size, 52px) + var(--app-chrome-control-gap, 8px)), 1fr)
        minmax(0, max-content)
        minmax(calc(var(--floating-dock-control-min-inline-size, 52px) + var(--floating-dock-control-min-inline-size, 52px) + var(--app-chrome-control-gap, 8px)), 1fr);
}

body.app-shell > .floating-dock {
    inset-inline-start: var(--sidebar-nav-width, 240px);
}

body.app-shell.app-frame-modern > .floating-dock {
    inset-inline-start: 0;
}

body.app-shell.nav-collapsed > .floating-dock {
    inset-inline-start: var(--sidebar-nav-collapsed-width, 64px);
}

body.app-shell.nav-open > .floating-dock,
body.app-shell.nav-open [data-workspace-tool],
body.app-shell.nav-open [data-notes-workbench-bubble],
body.app-shell.nav-open [data-workbench-panel].is-sheet-open {
    pointer-events: none;
}

body.app-shell.nav-open [data-workspace-tool],
body.app-shell.nav-open [data-notes-workbench-bubble],
body.app-shell.nav-open [data-workbench-panel].is-sheet-open {
    visibility: hidden;
}

.floating-dock-center {
    display: flex;
    grid-column: 2;
    grid-row: 1;
    justify-self: center;
    align-items: center;
    gap: var(--app-chrome-control-gap, 8px);
    max-inline-size: 100%;
    min-inline-size: 0;
}

.floating-dock-actions {
    display: flex;
    order: 1;
    align-items: center;
    gap: var(--app-chrome-control-gap, 8px);
    max-inline-size: 100%;
    min-inline-size: 0;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-inline: contain;
    scrollbar-width: thin;
    scroll-padding-inline: var(--floating-dock-overflow-fade-size, 20px);
}

.floating-dock-actions::before,
.floating-dock-actions::after {
    position: sticky;
    z-index: 1;
    align-self: stretch;
    flex: 0 0 var(--floating-dock-overflow-fade-size, 20px);
    inline-size: var(--floating-dock-overflow-fade-size, 20px);
    content: '';
    opacity: 0;
    pointer-events: none;
}

.floating-dock-actions::before {
    inset-inline-start: 0;
    margin-inline-end: calc(-1 * var(--floating-dock-overflow-fade-size, 20px));
    background: linear-gradient(to right, var(--page-bg), transparent);
}

.floating-dock-actions::after {
    inset-inline-end: 0;
    margin-inline-start: calc(-1 * var(--floating-dock-overflow-fade-size, 20px));
    background: linear-gradient(to left, var(--page-bg), transparent);
}

.floating-dock-actions[data-floating-dock-overflow-start]::before,
.floating-dock-actions[data-floating-dock-overflow-end]::after {
    opacity: .8;
}

.floating-dock > .floating-dock-actions {
    grid-column: 2;
    grid-row: 1;
    justify-self: center;
}

.floating-dock-pinned:empty,
.floating-dock-feedback:empty,
.floating-dock-persistent:empty {
    display: none;
}

.floating-dock-pinned,
.floating-dock-feedback,
.floating-dock-persistent {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: var(--app-chrome-control-gap, 8px);
    min-inline-size: 0;
}

/* Persistent controls and the disclosure occupy equal outer tracks sized for the controls that are present.
   The action scroller therefore stays centered against the bar instead of the remaining free space. */
.floating-dock > .floating-dock-persistent {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    max-inline-size: 100%;
    overflow: visible;
}

.floating-dock-center > .floating-dock-pinned:not(:empty) {
    order: 2;
}

.floating-dock-center > .floating-dock-feedback:not(:empty) {
    order: 3;
}

.floating-dock > .floating-dock-pinned:not(:empty) {
    grid-column: 2;
    grid-row: 2;
    justify-self: center;
}

.floating-dock > .floating-dock-feedback:not(:empty) {
    grid-column: 2;
    grid-row: 3;
    justify-self: center;
}

.floating-dock-pinned[hidden],
.floating-dock-feedback[hidden],
.floating-dock-actions[hidden] {
    display: none !important;
}

.floating-dock :is(.floating-dock-pinned, .floating-dock-feedback)[data-floating-dock-separated] {
    margin-inline-start: var(--app-chrome-control-gap, 8px);
    padding-inline-start: var(--app-chrome-control-gap, 8px);
    border-inline-start: 1px solid var(--line);
}

.floating-dock .floating-dock-pinned > button,
.floating-dock .floating-dock-pinned > a,
.floating-dock .floating-dock-feedback > button,
.floating-dock .floating-dock-feedback > a,
.floating-dock .floating-dock-actions > button,
.floating-dock .floating-dock-actions > a,
.floating-dock > .floating-dock-persistent > [data-floating-dock-back],
.floating-dock > .floating-dock-persistent > [data-navigation-launcher],
.floating-dock [data-admin-impersonation-identity],
.floating-dock [data-admin-impersonation-exit],
.floating-dock .notes-workbench-trigger,
.floating-dock .workbench-bar-button,
.floating-dock .floating-dock-frame-toggle,
.floating-dock > .floating-dock-disclosure {
    position: relative;
    box-sizing: border-box;
    display: inline-flex;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    inline-size: fit-content;
    min-inline-size: max(var(--app-chrome-control-size, 44px), var(--floating-dock-control-min-inline-size, 52px));
    block-size: auto;
    min-block-size: var(--floating-dock-control-block-size, 58px);
    max-inline-size: var(--floating-dock-control-max-inline-size, max(112px, 7rem));
    padding: 5px 6px;
    gap: 2px;
    overflow: visible;
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    color: inherit;
    background: transparent;
    font: inherit;
    text-decoration: none;
    cursor: pointer;
    touch-action: manipulation;
}

.floating-dock-control-icon {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    min-inline-size: var(--app-chrome-icon-size, 24px);
    block-size: var(--app-chrome-icon-size, 24px);
    line-height: 1;
}

.floating-dock-control-label {
    display: block;
    max-inline-size: 100%;
    overflow: hidden;
    font-size: .6875rem;
    font-weight: 600;
    line-height: .875rem;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Availability is consumer-owned. The author-level control metrics above must not override HTML's hidden state. */
.floating-dock [data-floating-dock-contribution-control][hidden] {
    display: none !important;
}

.floating-dock [data-navigation-launcher][hidden] {
    display: none !important;
}

.floating-dock svg {
    inline-size: var(--app-chrome-icon-size, 24px);
    block-size: var(--app-chrome-icon-size, 24px);
}

.floating-dock :is(
    [data-floating-dock-tool][aria-pressed="true"],
    [data-floating-dock-tool][aria-expanded="true"],
    [data-floating-dock-contribution-control][aria-pressed="true"],
    [data-floating-dock-contribution-control][aria-expanded="true"]
) {
    --accent-label-color: var(--floating-dock-selected-accent);
    border-color: var(--accent-label-border);
    color: var(--accent-label-foreground);
    background: var(--accent-label-background);
}

.floating-dock :is(
    [data-floating-dock-tool][aria-pressed="true"],
    [data-floating-dock-tool][aria-expanded="true"],
    [data-floating-dock-contribution-control][aria-pressed="true"],
    [data-floating-dock-contribution-control][aria-expanded="true"]
) :is(.floating-dock-control-icon, .floating-dock-control-label, svg) {
    color: inherit;
}

@media (hover: hover) and (pointer: fine) {
    .floating-dock :is(
        [data-floating-dock-tool][aria-pressed="true"],
        [data-floating-dock-tool][aria-expanded="true"],
        [data-floating-dock-contribution-control][aria-pressed="true"],
        [data-floating-dock-contribution-control][aria-expanded="true"]
    ):hover {
        border-color: var(--accent-label-border);
        color: var(--accent-label-foreground);
        background: var(--accent-label-background);
    }

    .floating-dock :is(
        [data-floating-dock-tool]:not([aria-pressed="true"]):not([aria-expanded="true"]),
        [data-floating-dock-back],
        [data-admin-impersonation-identity],
        [data-admin-impersonation-exit],
        .floating-dock-disclosure
    ):hover {
        color: var(--text);
        background: color-mix(in srgb, var(--text) 8%, transparent);
    }
}

.floating-dock :is([data-floating-dock-tool], .floating-dock-disclosure):disabled {
    opacity: .5;
    cursor: not-allowed;
}

.floating-dock :is(
    [data-floating-dock-tool],
    [data-floating-dock-back],
    [data-admin-impersonation-identity],
    [data-admin-impersonation-exit],
    .floating-dock-disclosure
):focus-visible {
    outline: 3px solid var(--floating-dock-focus-ring);
    outline-offset: -3px;
}

.floating-dock > .floating-dock-disclosure {
    grid-column: 3;
    grid-row: 1;
    justify-self: end;
    flex: 0 0 auto !important;
}

.floating-dock-frame-recovery {
    order: 4;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    min-inline-size: var(--floating-dock-control-min-inline-size, 52px);
    min-block-size: var(--floating-dock-control-block-size, 58px);
    opacity: 1;
    transition: opacity 80ms ease;
}

/* Frame composition moves Collapse into the header menu. Keep the existing standalone frame-toggle API
   intact, but remove its expanded copy from a real shared Frame and restore it only as hidden-frame recovery. */
[data-frame] .floating-dock[data-floating-dock-frame] .floating-dock-frame-recovery {
    display: none;
}

.floating-dock-frame-brand {
    display: none;
    flex: 0 0 var(--app-chrome-control-size, 44px);
    align-items: center;
    justify-content: center;
    inline-size: var(--app-chrome-control-size, 44px);
    block-size: var(--app-chrome-control-size, 44px);
    line-height: 0;
}

.floating-dock-frame-brand .brand-logo svg {
    block-size: 23px;
    inline-size: auto;
    max-inline-size: 100%;
}

.floating-dock-disclosure-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--app-chrome-icon-size, 24px);
    block-size: var(--app-chrome-icon-size, 24px);
}

.floating-dock-frame-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--app-chrome-icon-size, 24px);
    block-size: var(--app-chrome-icon-size, 24px);
}

.floating-dock-frame-icon > svg {
    inline-size: 100%;
    block-size: 100%;
}

.floating-dock-frame-icon--hidden {
    display: none;
}

.floating-dock-disclosure-icon--show {
    display: none;
}

.floating-dock .admin-impersonation-controls--dock {
    gap: var(--app-chrome-control-gap, 8px);
}

.floating-dock .admin-impersonation-controls--dock .dropdown-menu {
    inset-block: auto calc(100% + 4px);
}

.admin-impersonation-identity-icon {
    display: inline-flex;
}

.floating-dock.is-collapsed,
body.app-shell > .floating-dock.is-collapsed,
body.app-shell.nav-collapsed > .floating-dock.is-collapsed {
    display: flex;
    inset-inline-start: auto;
    inset-inline-end: 0;
    gap: 0;
    inline-size: auto;
    min-inline-size: calc(
        var(--floating-dock-control-min-inline-size, 52px)
        + max(var(--app-chrome-inline-padding, 32px), env(safe-area-inset-right))
    );
    min-block-size: calc(
        var(--floating-dock-control-block-size, 58px)
        + max(var(--app-chrome-row-padding, 8px), env(safe-area-inset-bottom))
    );
    margin: 0;
    padding:
        0
        max(var(--app-chrome-inline-padding, 32px), env(safe-area-inset-right))
        max(var(--app-chrome-row-padding, 8px), env(safe-area-inset-bottom))
        0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}

.floating-dock.is-collapsed .floating-dock-pinned,
.floating-dock.is-collapsed .floating-dock-feedback,
.floating-dock.is-collapsed .floating-dock-persistent,
.floating-dock.is-collapsed .floating-dock-actions,
.floating-dock.is-collapsed .floating-dock-disclosure-icon--hide {
    display: none;
}

.floating-dock.is-collapsed .floating-dock-disclosure {
    border-color: var(--line);
    border-radius: var(--radius-control);
    background: var(--surface-raised);
    box-shadow: var(--shadow-sm);
}

.floating-dock.is-collapsed .floating-dock-disclosure-icon--show {
    display: inline-flex;
}

@media (max-width: 768px), (max-height: 600px) {
    .floating-dock,
    body.app-shell > .floating-dock,
    body.app-shell.nav-collapsed > .floating-dock {
        --app-chrome-inline-padding: 16px;

        inset-inline-start: 0;
    }

    .floating-dock.is-collapsed,
    body.app-shell > .floating-dock.is-collapsed,
    body.app-shell.nav-collapsed > .floating-dock.is-collapsed {
        inset-inline-start: auto;
    }

    /* The two impersonation controls occupy the persistent start track. Navigation and the frame disclosure
       share the middle cluster, so only that cluster can flex at compact widths. */
    .floating-dock:has(> .floating-dock-persistent [data-admin-impersonation-controls]) {
        grid-template-columns:
            minmax(
                calc(
                    var(--floating-dock-control-min-inline-size, 52px)
                    + var(--floating-dock-control-min-inline-size, 52px)
                    + var(--app-chrome-control-gap, 8px)
                ),
                max-content
            )
            minmax(0, 1fr)
            var(--floating-dock-control-min-inline-size, 52px);
    }
}

/* Authenticated shells disclose the complete application frame per visit. This is distinct from standalone
   bottom-bar collapse: identity and Exit stay beside the rightmost arrow control, while hidden actions are inert. */
.floating-dock.is-frame-hidden,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] {
    display: flex;
    inset-inline-start: auto;
    inset-inline-end: 0;
    gap: var(--app-chrome-control-gap, 8px);
    inline-size: auto;
    max-inline-size: 100vw;
    min-inline-size: var(--floating-dock-control-min-inline-size, 52px);
    min-block-size: calc(
        var(--app-chrome-control-size, 44px)
        + max(var(--app-chrome-row-padding, 8px), env(safe-area-inset-bottom))
    );
    margin: 0;
    padding:
        0
        max(var(--app-chrome-inline-padding, 32px), env(safe-area-inset-right))
        max(var(--app-chrome-row-padding, 8px), env(safe-area-inset-bottom))
        0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}

.floating-dock.is-frame-hidden .floating-dock-pinned,
.floating-dock.is-frame-hidden .floating-dock-feedback,
.floating-dock.is-frame-hidden .floating-dock-actions,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-pinned,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-feedback,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-actions {
    display: none;
}

.floating-dock.is-frame-hidden .floating-dock-persistent,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-persistent {
    display: flex;
    order: 1;
    overflow: visible;
}

.floating-dock.is-frame-hidden .floating-dock-center,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-center {
    display: flex;
    align-self: flex-end;
    order: 2;
    max-inline-size: none;
    overflow: visible;
}

.floating-dock.is-frame-hidden :is(
    [data-admin-impersonation-identity],
    [data-admin-impersonation-exit]
),
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] :is(
    [data-admin-impersonation-identity],
    [data-admin-impersonation-exit]
) {
    border-color: var(--line);
    color: var(--text);
    background: var(--surface-raised);
    box-shadow: var(--shadow-sm);
}

@media (hover: hover) and (pointer: fine) {
    .floating-dock.is-frame-hidden :is(
        [data-admin-impersonation-identity],
        [data-admin-impersonation-exit]
    ):hover,
    body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] :is(
        [data-admin-impersonation-identity],
        [data-admin-impersonation-exit]
    ):hover {
        color: var(--text);
        background: var(--surface);
    }
}

.frame.is-frame-hidden .floating-dock[data-floating-dock-frame] .floating-dock-frame-recovery,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-recovery {
    display: inline-flex;
    gap: 0;
    min-inline-size: 78px;
    min-block-size: var(--app-chrome-control-size, 44px);
    padding-inline: 11px 0;
    border: 0;
    border-radius: var(--radius-control);
    background: var(--surface-raised);
    box-shadow: inset 0 0 0 1px var(--line), var(--shadow-sm);
}

.floating-dock.is-frame-hidden .floating-dock-frame-brand,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-brand {
    display: inline-flex;
    flex-basis: 23px;
    inline-size: 23px;
    block-size: var(--app-chrome-control-size, 44px);
}

.floating-dock.is-frame-hidden .floating-dock-frame-toggle,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-toggle {
    gap: 0;
    inline-size: var(--app-chrome-control-size, 44px);
    min-inline-size: var(--app-chrome-control-size, 44px);
    max-inline-size: var(--app-chrome-control-size, 44px);
    block-size: var(--app-chrome-control-size, 44px);
    min-block-size: var(--app-chrome-control-size, 44px);
    padding: 10px;
    border: 0;
    background: transparent;
    box-shadow: none;
}

.floating-dock.is-frame-hidden .floating-dock-frame-toggle > .floating-dock-control-label,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-toggle > .floating-dock-control-label {
    display: none;
}

.floating-dock.is-frame-hidden.is-frame-scrolling .floating-dock-frame-recovery,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame].is-frame-scrolling .floating-dock-frame-recovery {
    opacity: .2;
}

.floating-dock.is-frame-hidden.is-frame-interacting .floating-dock-frame-recovery,
.floating-dock.is-frame-hidden .floating-dock-frame-recovery:has(.floating-dock-frame-toggle:focus-visible),
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame].is-frame-interacting .floating-dock-frame-recovery,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-recovery:has(.floating-dock-frame-toggle:focus-visible) {
    opacity: 1;
    transition-duration: 0ms;
}

@media (hover: hover) and (pointer: fine) {
    .floating-dock.is-frame-hidden .floating-dock-frame-recovery:hover,
    body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-recovery:hover {
        opacity: 1;
        transition-duration: 0ms;
    }
}

.floating-dock.is-frame-hidden .floating-dock-frame-icon--shown,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-icon--shown {
    display: none;
}

.floating-dock.is-frame-hidden .floating-dock-frame-icon--hidden,
body.app-shell.app-frame-hidden [data-frame-region="bottom"] > .floating-dock[data-floating-dock-frame] .floating-dock-frame-icon--hidden {
    display: inline-flex;
}

@media (prefers-reduced-motion: reduce) {
    .floating-dock,
    .floating-dock * {
        scroll-behavior: auto !important;
        transition: none !important;
    }
}

/* @toc Flow */
/* Flow — server-rendered SVG flow diagram for the docs (our no-library take on
 * Mermaid). A vertical stack of connected nodes; every colour is a --flow-* token
 * from config/components/flow.php, so it themes like any component. No JS.
 * View: /resources/views/components/flow.blade.php. */

.flow {
    margin: 0;
}

.flow-svg {
    display: block;
    max-width: 100%;
    height: auto;
    font-family: var(--font-sans);
}

.flow-box {
    fill: var(--flow-node-background-color);
    stroke: var(--flow-node-border);
    stroke-width: 1;
}

.flow-box.is-terminal {
    fill: var(--flow-accent-background-color);
    stroke: var(--flow-accent-background-color);
}

.flow-label {
    fill: var(--flow-node-text);
    font-size: var(--text-sm);
    font-weight: 500;
}

.flow-label.is-terminal {
    fill: var(--flow-accent-text);
}

.flow-note {
    fill: var(--flow-note-text);
    font-size: var(--text-xs);
}

.flow-edge {
    fill: none;
    stroke: var(--flow-edge);
    stroke-width: 1.5;
}

.flow-arrowhead {
    fill: none;
    stroke: var(--flow-edge);
    stroke-width: 1.5;
}

.flow-caption {
    margin-top: 10px;
    font-size: var(--text-xs);
    color: var(--text-muted);
    text-align: center;
}

/* @toc Flowchart */
/* Flowchart — a no-library, server-laid-out SVG flow diagram. Nodes in top-down
 * layers with shapes (process / start-end pill / decision diamond / cylinder) and
 * curved connectors; every colour is a --flowchart-* token (config). The viewport
 * is a window flowchart.js upgrades to pan/zoom (without JS it just scrolls).
 * View: /resources/views/components/flowchart.blade.php. */

.flowchart {
    margin: 0;
}

.flowchart-viewport {
    position: relative;
    overflow: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--flowchart-background-color);
}

.flowchart.is-grab .flowchart-viewport {
    cursor: grab;
    overflow: hidden;
    block-size: min(var(--flowchart-natural-block-size), 70dvh);
}

.flowchart.is-grabbing .flowchart-viewport {
    cursor: grabbing;
}

.flowchart-canvas {
    transform-origin: 0 0;
}

.flowchart-svg {
    display: block;
    max-width: 100%;
    height: auto;
    font-family: var(--font-sans);
}

.flowchart-node {
    fill: var(--flowchart-node-background-color);
    stroke: var(--flowchart-node-border);
    stroke-width: 1.5;
}

.flowchart-node.is-accent {
    fill: var(--flowchart-accent-background-color);
    stroke: var(--flowchart-accent-background-color);
}

.flowchart-cylinder-top {
    fill: none;
    stroke: var(--flowchart-node-border);
    stroke-width: 1.5;
}

.flowchart-label {
    fill: var(--flowchart-node-text);
    font-size: var(--text-sm);
    font-weight: 500;
}

.flowchart-label.is-accent {
    fill: var(--flowchart-accent-text);
}

.flowchart-note {
    fill: var(--flowchart-note-text);
    font-size: var(--text-xs);
}

.flowchart-note.is-accent {
    fill: var(--flowchart-accent-text);
}

.flowchart-edge {
    fill: none;
    stroke: var(--flowchart-edge);
    stroke-width: 1.5;
}

.flowchart-arrowhead {
    fill: none;
    stroke: var(--flowchart-edge);
    stroke-width: 1.5;
}

.flowchart-edge-label {
    fill: var(--flowchart-edge-label);
    font-size: var(--text-xs);
}

.flowchart-caption {
    margin-top: 10px;
    font-size: var(--text-xs);
    color: var(--text-muted);
    text-align: center;
}

/* @toc Footer */
.footer {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--footer-gap, 1rem);
    padding: var(--footer-padding, 2rem 1rem);
    color: var(--text-muted);
    font-size: 0.85rem;
}

.footer-link {
    color: inherit;
}

/* @toc Frame */
/*
 * Frame styles the public five-region document chassis and its available content rectangle.
 * Component regions supply content while this file owns shared geometry and visibility transitions.
 * Visibility selectors stay scoped to the controlling Frame so embedded previews cannot control the page.
 */

/* Application-host geometry. AppShell contributes sidebar behavior; Frame alone owns the viewport and the
   available center rectangle shared by ordinary pages, flush workspaces, docks, and generated previews. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: var(--z-overlay);
}

.skip-link:focus {
    left: 8px;
    top: 8px;
    padding: 8px 12px;
    background: var(--page-bg);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

body.app-shell {
    flex-direction: row;
    height: 100vh;
    overflow: hidden;
}

body.app-shell > [data-frame] {
    flex: 1 1 auto;
}

body.app-shell.app-frame-modern {
    --app-frame-header-block-end: var(--app-chrome-row-height, 60px);
    flex-direction: column;
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
}

.app-frame-middle {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    display: flex;
    overflow: hidden;
}

.app-frame-navigation-fallback {
    flex: 0 0 auto;
    flex-direction: column;
    inline-size: clamp(220px, 19vw, 280px);
    min-width: 0;
    min-height: 0;
    max-block-size: 100%;
    overflow: hidden;
    display: flex;
    transition: inline-size var(--app-frame-transition-duration, 260ms) ease;
}

html[data-navigation-panel-rail-seed] .app-frame-navigation-fallback,
.app-frame-navigation-fallback:has(> [data-navigation-panel][data-navigation-state="rail"]) {
    inline-size: var(--navigation-panel-rail-width, 64px);
}

.app-frame-navigation-fallback:not(:has(> [data-navigation-panel])) {
    display: none;
}

body.app-frame-modern .app-frame-navigation-fallback > [data-navigation-panel] {
    align-self: stretch;
    min-block-size: 0;
}

[data-app-frame-native-host] > [data-navigation-panel] {
    align-self: stretch;
}

[data-navigation-panel][aria-hidden="true"] {
    display: none;
}

body.app-frame-modern .app-shell-wrap {
    height: 100%;
    min-height: 0;
}

body.app-frame-modern > :is(.app-header-bar, .app-shell-bar),
body.app-frame-modern > [data-frame] > .frame-region--top > :is(.app-header-bar, .app-shell-bar),
body.app-frame-host > .app-shell-wrap > .app-header-bar,
body.app-frame-host > [data-frame] > .frame-region--top > .app-header-bar {
    inline-size: 100%;
    overflow: visible;
    transition:
        max-block-size var(--app-frame-transition-duration, 260ms) ease,
        min-block-size var(--app-frame-transition-duration, 260ms) ease,
        padding var(--app-frame-transition-duration, 260ms) ease,
        opacity var(--app-frame-transition-duration, 260ms) ease,
        transform var(--app-frame-transition-duration, 260ms) ease,
        visibility 0s linear var(--app-frame-transition-duration, 260ms);
}

body.app-shell.app-frame-modern:has([data-shared-bottom-bar]) .app-frame-navigation-fallback {
    padding-block-end: var(--app-bottom-bar-layout-block-end, var(--app-bottom-bar-initial-occupied-height, 61px));
}

.app-shell-wrap {
    flex: 1;
    min-width: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transition: width var(--app-frame-transition-duration, 260ms) ease;
}

.app-shell-main {
    position: relative;
    flex: 1;
    overflow-y: auto;
    padding: 28px 32px;
}

.app-shell-feedback-dock:empty {
    display: none;
}

.app-shell-main:has(> .app-shell-feedback-dock > .workspace-tool--feedback.is-docked) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(288px, min(370px, 34vw));
    column-gap: var(--space-4);
}

.app-shell-main:has(> .app-shell-feedback-dock > .workspace-tool--feedback.is-docked)
    > :not(.app-shell-feedback-dock) {
    grid-column: 1;
}

.app-shell-feedback-dock:has(> .workspace-tool--feedback.is-docked) {
    position: sticky;
    top: 0;
    grid-column: 2;
    grid-row: 1 / span 999;
    align-self: start;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    border-inline-start: 1px solid var(--line);
    padding-inline-start: var(--space-4);
}

.app-shell-feedback-dock > .workspace-tool--feedback.is-docked {
    height: 100%;
}

body.app-shell:has([data-shared-bottom-bar]) .app-shell-main {
    padding-block-end: calc(28px + var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)));
}

.app-shell-main:has(> .workout[data-workout-sheet]) {
    padding-block-start: 0;
}

.app-shell-main > .workout[data-workout-sheet] {
    padding-block-start: var(--space-2);
}

.app-shell-main-flush {
    padding: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

body.app-shell:has([data-shared-bottom-bar]) .app-shell-main-flush {
    padding: 0;
    padding-block-end: var(--app-bottom-bar-layout-block-end, var(--app-bottom-bar-initial-occupied-height, 61px));
}

/* Header identity remains AppHeaderBar's. Its relationship to the viewport and Frame tracks lives here. */
.app-header-bar,
.app-shell-bar {
    position: relative;
    z-index: var(--z-overlay);
    flex-shrink: 0;
    min-inline-size: 0;
    inline-size: 100%;
    overflow: visible;
}

@media (max-width: 768px), (max-height: 600px) {
    html[data-navigation-panel-rail-seed] .app-frame-navigation-fallback,
    .app-frame-navigation-fallback:has(> [data-navigation-panel][data-navigation-state="rail"]) {
        inline-size: 0;
    }

    body.app-frame-modern.app-frame-navigation-wired .app-frame-navigation-fallback {
        display: block;
        width: 0;
        height: 0;
        min-width: 0;
        min-height: 0;
        margin: 0;
        padding: 0;
        border: 0;
        overflow: visible;
    }

    body.app-shell {
        flex-direction: column;
        height: auto;
        min-height: 100vh;
        overflow-x: hidden;
        overflow-y: visible;
    }

    body.app-shell.app-frame-modern {
        height: 100dvh;
        min-height: 0;
        overflow: hidden;
    }

    body.app-frame-modern :is(.app-frame-middle, .app-shell-wrap) {
        height: 100%;
        min-height: 0;
        overflow: hidden;
    }

    /* Navigation's no-JS fallback stays in document order before a flush center. A bounded column lets the
       authored Navigation keep its natural height while the workspace consumes the remaining Frame block. */
    body.app-frame-modern:not(.app-frame-navigation-wired)
        .frame-available:has(> .frame-region--left > .app-frame-navigation-fallback > .navigation-panel):has(> .frame-region--center > .app-shell-wrap > .app-shell-main-flush) {
        display: flex;
        flex-direction: column;
    }

    body.app-frame-modern:not(.app-frame-navigation-wired)
        .frame-available:has(> .frame-region--left > .app-frame-navigation-fallback > .navigation-panel):has(> .frame-region--center > .app-shell-wrap > .app-shell-main-flush)
        > .frame-region--left {
        flex: 0 0 auto;
        max-block-size: 50%;
        overflow-y: auto;
    }

    body.app-frame-modern:not(.app-frame-navigation-wired)
        .frame-available:has(> .frame-region--left > .app-frame-navigation-fallback > .navigation-panel)
        > .frame-region--center:has(> .app-shell-wrap > .app-shell-main-flush) {
        flex: 1 1 0;
        min-block-size: 0;
    }

    body.app-frame-modern .app-shell-main:not(.app-shell-main-flush) {
        overflow-y: auto;
    }

    body.app-shell:has(> .app-shell-wrap > .app-shell-main-flush),
    body.app-shell:has(> [data-frame] > .frame-available > .frame-region--center > .app-shell-wrap > .app-shell-main-flush) {
        height: 100dvh;
        min-height: 100dvh;
        overflow: hidden;
    }

    .app-shell-wrap {
        height: auto;
        overflow: visible;
    }

    .app-shell-main {
        padding: 20px 16px;
    }

    .app-shell-main:has(> .app-shell-feedback-dock > .workspace-tool--feedback.is-docked) {
        grid-template-columns: minmax(0, 1fr) minmax(288px, min(370px, 42vw));
    }

    body.app-shell:has([data-shared-bottom-bar]) .app-shell-main {
        padding-block-end: calc(20px + var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)));
    }

    .app-shell-main:has(> .workout[data-workout-sheet]) {
        padding-inline: var(--space-3);
    }

    .app-shell-wrap:has(> .app-shell-main-flush) {
        height: 100dvh;
        min-height: 0;
        overflow: hidden;
    }

    .app-shell-main-flush,
    body.app-shell:has([data-shared-bottom-bar]) .app-shell-main-flush {
        padding: 0;
    }

    body.app-shell:has([data-shared-bottom-bar]) .app-shell-main-flush {
        padding-block-end: var(--app-bottom-bar-layout-block-end, var(--app-bottom-bar-initial-occupied-height, 61px));
    }

    .app-header-bar,
    .app-shell-bar {
        position: sticky;
        top: 0;
        z-index: 30;
    }
}

/* Reversible application-Frame state. Content remains mounted; only controlling Frame chrome and geometry
   withdraw, so inputs, scroll, Project iframe identity, and authored panel state survive restoration. */
body.app-shell.app-frame-hidden > .sidebar-nav,
body.app-shell.app-frame-hidden > [data-frame] > .frame-available > .frame-region--left > .sidebar-nav {
    width: 0;
    min-width: 0;
    border: 0;
    opacity: 0;
    transform: translateX(-100%);
    visibility: hidden;
    pointer-events: none;
}

body.app-shell.app-frame-modern.app-frame-hidden > :is(.app-header-bar, .app-shell-bar),
body.app-shell.app-frame-modern.app-frame-hidden > [data-frame] > .frame-region--top > :is(.app-header-bar, .app-shell-bar),
body.app-shell.app-frame-hidden > .app-shell-wrap > .app-header-bar,
body.app-shell.app-frame-hidden > [data-frame] > .frame-region--top > .app-header-bar {
    max-block-size: 0;
    min-block-size: 0;
    padding-block: 0;
    border: 0;
    opacity: 0;
    transform: translateY(-100%);
    visibility: hidden;
    pointer-events: none;
}

body.app-shell.app-frame-modern.app-frame-hidden > .app-frame-middle > .app-frame-navigation-fallback,
body.app-shell.app-frame-modern.app-frame-hidden > [data-frame] > .frame-available > .frame-region--left > .app-frame-navigation-fallback,
body.app-shell.app-frame-modern.app-frame-hidden > [data-frame] > .frame-available > .frame-region--left > [data-app-frame-native-host] > [data-navigation-panel] {
    width: 0;
    min-width: 0;
    border: 0;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

body.app-shell.app-frame-hidden > .app-shell-backdrop,
body.app-shell.app-frame-hidden > [data-frame] > .frame-available > .frame-region--left > .app-shell-backdrop {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

body.app-shell.app-frame-hidden > .app-shell-wrap,
body.app-shell.app-frame-hidden > [data-frame] > .frame-available > .frame-region--center > .app-shell-wrap {
    width: 100%;
    min-height: 0;
}

body.app-shell.app-frame-hidden:has([data-shared-bottom-bar]) .app-shell-main:not(.app-shell-main-flush) {
    padding-block-end: calc(28px + var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)));
}

body.app-shell.app-frame-hidden:has(> .app-shell-wrap > .app-shell-main-flush),
body.app-shell.app-frame-hidden:has(> .app-shell-wrap > .app-shell-main-flush) > .app-shell-wrap,
body.app-shell.app-frame-hidden:has(> [data-frame] > .frame-available > .frame-region--center > .app-shell-wrap > .app-shell-main-flush),
body.app-shell.app-frame-hidden:has(> [data-frame] > .frame-available > .frame-region--center > .app-shell-wrap > .app-shell-main-flush) > [data-frame] > .frame-available > .frame-region--center > .app-shell-wrap {
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
}

body.app-shell.app-frame-hidden .app-shell-main-flush,
body.app-shell.app-frame-hidden:has([data-shared-bottom-bar]) .app-shell-main-flush {
    padding: 0;
}

@media (max-width: 768px), (max-height: 600px) {
    body.app-shell.app-frame-hidden:has([data-shared-bottom-bar]) .app-shell-main:not(.app-shell-main-flush) {
        padding-block-end: calc(20px + var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)));
    }
}

.frame {
    inline-size: 100%;
    block-size: 100%;
    min-inline-size: 0;
    min-block-size: 0;
    display: grid;
    grid-template-rows: max-content minmax(0, 1fr) max-content;
    overflow: hidden;
}

.frame-available {
    min-inline-size: 0;
    min-block-size: 0;
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr) max-content;
    overflow: hidden;
}

.frame-region {
    min-inline-size: 0;
    min-block-size: 0;
}

.frame-region--top,
.frame-region--bottom {
    grid-column: 1 / -1;
}

.frame-region--left,
.frame-region--right {
    display: flex;
    max-block-size: 100%;
    overflow: hidden;
}

.frame-region--center {
    display: flex;
    overflow: hidden;
}

.frame-region--center > .app-shell-wrap {
    flex: 1 1 auto;
    inline-size: 100%;
}

.frame-region:empty {
    display: none;
}

.frame[data-frame].is-frame-hidden > .frame-region--top,
.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--left,
.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--right {
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.frame[data-frame].is-frame-hidden > .frame-region--top {
    max-block-size: 0;
}

.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--left,
.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--right {
    inline-size: 0;
}

.frame[data-frame].is-frame-hidden > .frame-available {
    grid-template-columns: 0 minmax(0, 1fr) 0;
}

@media (max-width: 768px), (max-height: 600px) {
    .frame[data-frame] {
        block-size: 100dvh;
    }

    .frame[data-frame] > .frame-available {
        grid-template-columns: minmax(0, 1fr);
    }

    .frame[data-frame].is-frame-hidden > .frame-available {
        grid-template-columns: minmax(0, 1fr);
    }

    .frame[data-frame] > .frame-available > .frame-region--left,
    .frame[data-frame] > .frame-available > .frame-region--right {
        position: fixed;
        z-index: var(--z-overlay);
    }
}

@media (prefers-reduced-motion: no-preference) {
    .frame,
    .frame-available,
    .frame-region {
        transition:
            grid-template-columns var(--app-frame-transition-duration, 260ms) ease,
            max-block-size var(--app-frame-transition-duration, 260ms) ease,
            inline-size var(--app-frame-transition-duration, 260ms) ease,
            opacity var(--app-frame-transition-duration, 260ms) ease,
            visibility 0s linear var(--app-frame-transition-duration, 260ms);
    }
}

@media (prefers-reduced-motion: reduce) {
    .frame,
    .frame-available,
    .frame-region {
        transition-duration: 0ms !important;
    }

    body.app-frame-host > .app-shell-wrap,
    body.app-frame-host > .app-shell-wrap > .app-header-bar,
    body.app-frame-host > [data-frame] > .frame-available > .frame-region--center > .app-shell-wrap,
    body.app-frame-host > [data-frame] > .frame-region--top > .app-header-bar,
    body.app-frame-modern > :is(.app-header-bar, .app-shell-bar),
    body.app-frame-modern > [data-frame] > .frame-region--top > :is(.app-header-bar, .app-shell-bar) {
        transition: none;
    }
}

/* Frame's private panel layout — four authored docks around one center stage. A grid of four edge DOCKS around one center STAGE,
   holding panels that re-dock, fold and reset through one Options menu. Structural only: no skin of its own, colour straight
   from the semantic tokens (--line, --surface, --text, --text-muted, --primary), so it themes per tenant and
   flips with the theme like everything else.

   TWO RULES GOVERN THIS FILE AND BOTH ARE LOAD-BEARING.

   (1) EVERY RULE THAT HIDES A PANEL IS SCOPED UNDER `.workbench.is-wired.has-shared-bottom-bar`, the complete
   atomic projection frame-panels.js adds. With JavaScript off or an incomplete projection nothing here hides
   anything, so the page degrades to a readable stack instead of a phone with no working control.

   (2) A DOCK IS NEVER `display: none` WHILE IT COULD HOLD A FIXED SHEET. Below the workspace breakpoint the docks
   collapse to zero size but stay rendered, because a fixed-position child cannot lay out inside a
   display:none parent — the bug the wireframe hit twice, where every bottom sheet measured 0x0. */

.workbench {
    display: grid;
    grid-template-areas:
        "top    top    top"
        "left   stage  right"
        "bottom bottom bottom";
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr) auto;
    column-gap: var(--space-4);
    min-height: 0;
    transition:
        grid-template-columns var(--app-frame-transition-duration, 260ms) ease,
        grid-template-rows var(--app-frame-transition-duration, 260ms) ease,
        column-gap var(--app-frame-transition-duration, 260ms) ease;
}

.frame-panels {
    flex: 1 1 auto;
    inline-size: 100%;
    block-size: 100%;
}

.workbench-stage {
    grid-area: stage;
    min-width: 0;
    min-height: 0;
}

/* A raw Frame may place the established application scroll wrapper in its center stage. Preserve the same
   definite-height relationship it had as Frame's direct center child so page-end controls remain reachable. */
.workbench-stage > .app-shell-wrap {
    block-size: 100%;
    min-block-size: 0;
    overflow: hidden;
}

.workbench-dock {
    display: flex;
    min-width: 0;
    min-height: 0;
    transition:
        width var(--app-frame-transition-duration, 260ms) ease,
        margin var(--app-frame-transition-duration, 260ms) ease,
        padding var(--app-frame-transition-duration, 260ms) ease,
        opacity var(--app-frame-transition-duration, 260ms) ease,
        visibility 0s linear var(--app-frame-transition-duration, 260ms);
}

/* Frame hiding releases all authored docks without changing their DOM order, presence, fold, input, or stored
   layout. Compact sheets close through Frame's private panel runtime; desktop panels remain suspended in place for restoration. */
.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--center > .frame-panels.workbench,
.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--center > .app-shell-wrap > .app-shell-main > .frame-panels.workbench {
    grid-template-columns: 0 minmax(0, 1fr) 0;
    grid-template-rows: 0 minmax(0, 1fr) 0;
    column-gap: 0;
}

.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--center > .frame-panels > .workbench-dock,
.frame[data-frame].is-frame-hidden > .frame-available > .frame-region--center > .app-shell-wrap > .app-shell-main > .frame-panels > .workbench-dock {
    width: 0;
    margin: 0;
    padding: 0;
    overflow: hidden;
    border: 0;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* The side rails: a scrolling column of panels. `clamp` keeps a rail readable without letting it eat the
   stage; min-width:0 kills the grid min-content floor, so one nowrap line inside a panel can never widen
   the whole rail. */
.workbench-dock[data-workbench-dock="left"],
.workbench-dock[data-workbench-dock="right"] {
    flex-direction: column;
    gap: var(--space-5);
    width: clamp(220px, 19vw, 280px);
    max-height: 100%;
    overflow-y: auto;
    scrollbar-width: thin;
}

.workbench-dock[data-workbench-dock="left"] {
    grid-area: left;
    padding-inline-end: var(--space-4);
    border-inline-end: 1px solid var(--line);
}

.workbench-dock[data-workbench-dock="left"]:has(> [data-navigation-panel][data-navigation-adopted]) {
    padding-inline-end: 0;
}

.workbench-dock[data-workbench-dock="left"]:has(> [data-navigation-panel][data-navigation-adopted])
    > :is(.workbench-panel:not([hidden]), .workspace-tool--feedback[data-workspace-tool-dock-item]) {
    margin-inline-end: var(--space-4);
}

.workbench-dock[data-workbench-dock="left"]:has(> [data-navigation-panel][data-navigation-adopted]):not(:has(> :is(.workbench-panel:not([hidden]), .workspace-tool--feedback[data-workspace-tool-dock-item]))) {
    overflow-y: hidden;
}

.workbench-dock[data-workbench-dock="right"] {
    grid-area: right;
    padding-inline-start: var(--space-4);
    border-inline-start: 1px solid var(--line);
}

/* The top and bottom docks are rows: their panels share the width evenly, with a 240px basis so a third
   panel wraps rather than crushing the other two. */
.workbench-dock[data-workbench-dock="top"],
.workbench-dock[data-workbench-dock="bottom"] {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-6);
}

.workbench-dock[data-workbench-dock="top"] {
    grid-area: top;
    margin-block-end: var(--space-3);
    padding-block-end: var(--space-2);
    border-block-end: 1px solid var(--line);
}

.workbench-dock[data-workbench-dock="bottom"] {
    grid-area: bottom;
    margin-block-start: var(--space-3);
    padding-block-start: var(--space-2);
    border-block-start: 1px solid var(--line);
}

.workbench-dock[data-workbench-dock="top"] > .workbench-panel,
.workbench-dock[data-workbench-dock="bottom"] > .workbench-panel {
    flex: 1 1 240px;
}

/* An empty dock costs no room. Deliberate placement commands can still target every authored dock. */
.workbench-dock:not(:has(> :is([data-workbench-dock-item], [data-workspace-tool-dock-item], [data-navigation-panel][data-navigation-adopted]))) {
    display: none;
}

/* Presence is enhanced state. Hidden panels stay in their dock DOM so saved order, content, and input
   remain intact, while a dock with no present panel releases its grid space. */
.workbench.is-wired.has-shared-bottom-bar .workbench-panel[hidden],
.workbench.is-wired.has-shared-bottom-bar .workbench-dock[data-workbench-empty="true"] {
    display: none !important;
}

/* ── the panel ───────────────────────────────────────────────────────────────────────────────────────── */

.workbench-panel {
    min-width: 0;
    max-width: 100%;
    min-height: 0;
}

/* Every present direct side-dock item enters the same sizing set. Authored Panels, adopted Navigation, and
   Feedback keep their own content composition, while Frame owns only their shared dock geometry. */
.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"])
    > :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    ) {
    flex: 0 0 auto;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    min-height: 0;
    block-size: auto;
}

.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"])
    > .panel[data-panel-content-layout="consumer"] {
    --frame-stacked-panel-block-size: 44vh;
}

.workbench-dock[data-workbench-dock="left"]:has(> [data-navigation-panel][data-navigation-adopted])
    > .workspace-tool--feedback[data-workspace-tool-dock-item] {
    align-self: stretch;
    width: auto;
}

.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"])
    > .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    > .workspace-tool-dialog {
    height: auto;
    max-height: 44vh;
}

/* A dock with one present item delegates overflow to that item's content scroller. A folded Panel remains at
   its natural header height; an open Panel or Feedback surface consumes the available dock height. */
.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"]):has(
    > :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    )
):not(:has(
    > :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    ) ~ :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    )
)) {
    overflow-y: hidden;
}

.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"]):has(
    > :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    )
):not(:has(
    > :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    ) ~ :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    )
))
    > :is(
        .workbench-panel:not([hidden]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible])),
        .panel[data-panel-dock-peer]:not([hidden]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible])),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden]):has(
            > .workspace-tool-dialog > [data-workspace-tool-panel] > .panel-body:not([hidden])
        )
    ) {
    flex: 1 1 auto;
}

.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"]):has(
    > :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    )
):not(:has(
    > :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    ) ~ :is(
        .workbench-panel:not([hidden]),
        [data-navigation-panel][data-navigation-adopted]:not([hidden]),
        .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
    )
))
    > .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden]):has(
        > .workspace-tool-dialog > [data-workspace-tool-panel] > .panel-body:not([hidden])
    )
    > .workspace-tool-dialog {
    height: 100%;
    max-height: none;
}

/* A stack bounds every open Panel frame and lets each owned content region scroll independently. The dock itself
   scrolls only when the combined direct-item stack exceeds the available edge. */
.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"]):where(
    :has(
        > :is(
            .workbench-panel:not([hidden]),
            [data-navigation-panel][data-navigation-adopted]:not([hidden]),
            .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
        ) ~ :is(
            .workbench-panel:not([hidden]),
            [data-navigation-panel][data-navigation-adopted]:not([hidden]),
            .workspace-tool--feedback[data-workspace-tool-dock-item]:not([hidden])
        )
    )
)
    > :is(.workbench-panel, .panel[data-panel-dock-peer]):not([hidden]):where(:not(.is-sheet-open):not([data-panel-sheet-open]):not([data-workbench-grow]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible]))) {
    block-size: var(--frame-stacked-panel-block-size, fit-content);
    max-block-size: 44vh;
    overflow: hidden;
}

.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"])
    > :is(.workbench-panel, .panel[data-panel-dock-peer]):not([hidden]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible]))
    > .panel-body {
    flex: 1 1 auto;
    min-block-size: 0;
    block-size: auto;
}

/* A fixed panel's body is ordinarily a block. So is an SSR-open collapsible body before panel.js enhances it.
   Make both fallback forms bounded flex columns so their inner scroller consumes the available body height
   instead of painting its full contents beyond the panel. The enhanced collapsible body keeps Panel's grid. */
.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"])
    > :is(.workbench-panel, .panel[data-panel-dock-peer]):not([hidden]):is(:not([data-panel-collapsible]), :not([data-panel-enhanced]):has(> .panel-body.is-open))
    > .panel-body {
    display: flex;
    flex-direction: column;
}

.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"])
    > :is(.workbench-panel, .panel[data-panel-dock-peer]):not([hidden]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible]))
    > .panel-body
    > .panel-body-inner {
    flex: 1 1 auto;
    min-block-size: 0;
    max-block-size: none;
}

/* Panel-owned content uses the shared inner scroller. Consumer-owned composition keeps that wrapper clipped and
   scrolls only its designated descendant, so fixed Navigation/account regions and similar siblings stay fixed. */
.workbench-dock:is([data-workbench-dock="left"], [data-workbench-dock="right"])
    > :is(.workbench-panel, .panel[data-panel-dock-peer]):not([data-panel-content-layout="consumer"]):not([hidden]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible]))
    > .panel-body
    > .panel-body-inner {
    overflow-y: auto;
}

/* A panel marked `grow` fills its rail's remaining height and scrolls inside itself — the tree case: the
   thing must reach the bottom of the screen rather than stop at an arbitrary ceiling. */
.workbench-dock[data-workbench-dock="left"]
    > .workbench-panel[data-workbench-grow]:not([hidden]):not(.is-sheet-open):not([data-panel-sheet-open]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible])),
.workbench-dock[data-workbench-dock="right"]
    > .workbench-panel[data-workbench-grow]:not([hidden]):not(.is-sheet-open):not([data-panel-sheet-open]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible])) {
    flex: 1 1 auto;
    block-size: auto;
    max-block-size: none;
}

.workbench-panel[data-workbench-grow]:not([hidden]):is(:has(> .panel-body.is-open), :not([data-panel-collapsible]))
    > .panel-body {
    flex: 1 1 auto;
    min-height: 0;
}

.workbench-panel[data-workbench-grow]:not([data-panel-content-layout="consumer"]):not([hidden])
    > .panel-body.is-open
    > .panel-body-inner,
.workbench-panel[data-workbench-grow]:not([data-panel-content-layout="consumer"]):not([data-panel-collapsible]):not([hidden])
    > .panel-body
    > .panel-body-inner {
    max-height: none;
    overflow-y: auto;
}

/* ── the server-rendered shared-bar trigger registry ─────────────────────────────────────────────────── */

/* Server-rendered, but offered only once JavaScript has wired it: a trigger that cannot open its sheet is
   worse than no trigger. */
.workbench-rail {
    display: none;
}

.workbench-bar-button .workbench-rail-icon,
.workbench-bar-button .workbench-rail-icon svg {
    display: block;
    width: var(--app-chrome-icon-size, 24px);
    height: var(--app-chrome-icon-size, 24px);
}

/* Frame panels use Frame's one compact decision: width <= 768px OR height <= 600px. */
@media (max-width: 768px), (max-height: 600px) {
    .workbench:where([data-app-frame-workbench]) {
        display: block;
        column-gap: 0;
    }

    .workbench:where([data-app-frame-workbench]) > .workbench-dock[data-workbench-dock="left"],
    .workbench:where([data-app-frame-workbench]) > .workbench-dock[data-workbench-dock="right"] {
        width: auto;
        max-height: none;
        overflow-y: visible;
    }

    .workbench:where([data-app-frame-workbench]) > .workbench-dock {
        gap: var(--space-5);
        margin: 0;
        padding: 0;
        border: 0;
    }

    /* Before the shared compact launchers are available, Planner's fallback is one natural-height document
       whose outer Workbench owns vertical scrolling. Desktop stack caps would split that fallback into nested
       44vh scrollers and leave later controls outside the outer viewport. Fully wired sheets keep their bounded
       geometry below. */
    .workbench:where([data-app-frame-workbench]):not(.is-wired.has-shared-bottom-bar)
        > .workbench-dock
        > :is(.workbench-panel, .panel[data-panel-dock-peer]):not([hidden]) {
        block-size: auto;
        max-block-size: none;
        overflow: visible;
    }

    .workbench:where([data-app-frame-workbench]).is-wired.has-shared-bottom-bar > .workbench-dock {
        display: block;
        width: 0;
        height: 0;
        min-width: 0;
        min-height: 0;
        margin: 0;
        padding: 0;
        border: 0;
        overflow: visible;
    }

    /* Once the shared launchers exist, keep Frame's center track definite. The former nested Workbench could
       grow with page content; Frame now owns the viewport and its center must remain the page scroller. */
    .workbench:where([data-app-frame-workbench]).is-wired.has-shared-bottom-bar {
        display: grid;
        grid-template-columns: 0 minmax(0, 1fr) 0;
        grid-template-rows: 0 minmax(0, 1fr) 0;
    }

    .workbench:where([data-app-frame-workbench]).is-wired.has-shared-bottom-bar > .workbench-dock > .workbench-panel {
        display: none;
    }

    .workbench:where([data-app-frame-workbench]).is-wired.has-shared-bottom-bar > .workbench-dock > .workbench-panel.is-sheet-open,
    .workbench:where([data-app-frame-workbench]).is-wired.has-shared-bottom-bar > .workbench-dock > .workbench-panel[data-panel-sheet-open][data-panel-presence="leaving"] {
        display: flex;
        flex: 0 1 auto;
        block-size: auto;
        max-block-size: max(
            0px,
            calc(100dvh - var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)) - var(--app-visible-content-top, var(--app-visual-viewport-top, 0px)))
        );
        overflow: hidden;
    }
}

/* Reduced motion: the sheet settles instantly. The global animations.css blanket already zeroes
   transitions; this states the intent locally, per the accordion/panel precedent. */
@media (prefers-reduced-motion: reduce) {
    .workbench-panel,
    .workbench-rail,
    .workbench,
    .workbench-dock {
        transition: none;
    }
}

/* @toc Game catalog */
/*
 * x-game-catalog is mobile-first and container-first. Its immutable HTML reflows on the component's own width;
 * no line clamp hides long game copy, and every state keeps a visible text label independent of colour.
 */
.game-catalog {
    container-type: inline-size;
    color: var(--text);
}

.game-catalog-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-5);
}

.game-catalog-card {
    position: relative;
    min-width: 0;
    overflow: visible;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--global-border-radius);
}

.game-catalog-card-link,
.game-catalog-card-body {
    display: flex;
    flex-direction: column;
    height: 100%;
    color: inherit;
    text-decoration: none;
}

.game-catalog-card-link {
    border-radius: inherit;
}

.game-catalog-card-link:hover .game-catalog-title {
    color: var(--link);
    text-decoration: underline;
    text-underline-offset: 0.15em;
}

.game-catalog-card-link:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 3px;
}

.game-catalog-decoration {
    position: absolute;
    z-index: 1;
    inset-block-start: var(--space-3);
    inset-inline-end: var(--space-3);
    width: 5rem;
    height: auto;
    color: var(--text-muted);
    pointer-events: none;
}

.game-catalog-media {
    display: grid;
    min-height: 10rem;
    place-items: center;
    overflow: hidden;
    background: var(--surface-muted);
    border-block-end: 1px solid var(--line);
    border-start-start-radius: var(--global-border-radius);
    border-start-end-radius: var(--global-border-radius);
}

.game-catalog-image {
    display: block;
    width: 100%;
    height: auto;
    max-height: 20rem;
    object-fit: contain;
}

.game-catalog-no-image {
    max-width: 18ch;
    margin: 0;
    padding: var(--space-6) var(--space-4);
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: 600;
    text-align: center;
}

.game-catalog-copy {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
    padding: var(--space-5);
}

.game-catalog-eyebrow,
.game-catalog-title,
.game-catalog-summary,
.game-catalog-production-label {
    margin: 0;
    overflow-wrap: anywhere;
}

.game-catalog-eyebrow {
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.game-catalog-title {
    color: var(--text);
    font-size: var(--text-xl);
    line-height: 1.2;
}

.game-catalog-summary {
    color: var(--text-muted);
    line-height: 1.6;
}

.game-catalog-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.game-catalog-chip {
    display: inline-flex;
    align-items: center;
    min-height: 1.75rem;
    padding-inline: var(--space-2);
    background: var(--surface-muted);
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--text);
    font-size: var(--text-xs);
    font-weight: 700;
}

.game-catalog-chip.is-info,
.game-catalog-chip.is-primary {
    background: var(--info-surface);
    border-color: var(--info);
}

.game-catalog-chip.is-success {
    background: var(--success-surface);
    border-color: var(--success);
}

.game-catalog-chip.is-warning {
    background: var(--warning-surface);
    border-color: var(--warning);
}

.game-catalog-chip.is-danger {
    background: var(--danger-surface);
    border-color: var(--danger);
}

.game-catalog-production {
    display: grid;
    gap: var(--space-2);
    margin-block-start: auto;
    padding-block-start: var(--space-2);
    border-block-start: 1px solid var(--line);
}

.game-catalog-production-label {
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: 600;
}

.game-catalog-progress {
    width: 100%;
    accent-color: var(--primary);
}

.game-catalog-empty {
    margin: 0;
    padding: var(--space-6);
    color: var(--text-muted);
    text-align: center;
    background: var(--surface-muted);
    border: 1px dashed var(--line);
    border-radius: var(--global-border-radius);
}

@container (min-width: 40rem) {
    .game-catalog-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (min-width: 64rem) {
    .game-catalog-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (forced-colors: active) {
    .game-catalog-chip,
    .game-catalog-card,
    .game-catalog-empty {
        border-color: CanvasText;
    }
}

/* @toc Game */
/* <x-game> — the canvas-island chrome (X-GAME-EMBED-CONTRACT.md §7). Non-threaded, so ZERO page headers
   (§8). The island is authored container-first: the aspect box reserves space before boot (no layout shift
   when a multi-MB export loads), and the chrome reflows on the island's OWN box via @container — never a
   viewport media query. Colors come only from the ramp/semantic tokens; every --game-* token has a fallback
   so the island themes even before `game` is added to a domain manifest. */

.game {
    display: block;
    container-type: inline-size;
    inline-size: 100%;
}

.game-viewport {
    position: relative;
    inline-size: 100%;
    aspect-ratio: var(--game-aspect, 16 / 9);
    max-block-size: 80vh;
    overflow: hidden;
    border-radius: var(--game-border-radius, 8px);
    background: var(--game-poster-background-color, var(--surface));
    color: var(--game-poster-color, var(--text));
}

/* The game surface — shown only once running; never in the tab order, focusable for the Escape hatch. */
.game-canvas {
    position: absolute;
    inset: 0;
    display: none;
    inline-size: 100%;
    block-size: 100%;
    touch-action: none;
    outline: none;
}

/* Poster + overlays fill the box and stack their content centered; the state machine reveals exactly one. */
.game-poster,
.game-overlay {
    position: absolute;
    inset: 0;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--game-chrome-gap, var(--space-2));
    padding: var(--game-chrome-padding, var(--space-3));
    text-align: center;
}

.game-poster {
    background-color: var(--game-poster-background-color, var(--surface));
    background-size: cover;
    background-position: center;
}

/* A scrim so text/controls read over a poster image — bg color-mix on a ramp token, within the color law. */
.game-poster::before {
    content: "";
    position: absolute;
    inset: 0;
    background: color-mix(in oklab, var(--game-overlay, var(--neutral-950)) 45%, transparent);
}

.game-poster-body {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--game-chrome-gap, var(--space-2));
    max-inline-size: 44ch;
}

.game-name {
    margin: 0;
    font-weight: 600;
    font-size: clamp(1rem, 2.5cqi, 1.375rem);
}

.game-prose {
    margin: 0;
    color: var(--text-muted);
    font-size: clamp(0.8125rem, 1.8cqi, 0.9375rem);
    line-height: 1.5;
}

.game-overlay {
    background: color-mix(in oklab, var(--surface) 86%, transparent);
    backdrop-filter: blur(2px);
}

.game-overlay-text {
    margin: 0;
    font-weight: 600;
}

.game-error-text {
    color: var(--danger);
    max-inline-size: 46ch;
}

/* THE DESKTOP KEY LEGEND (#1743). On the island's bottom edge, OUTSIDE the letterboxed picture, so it
   answers "what are the keys" without covering the thing it is describing. Horizontal and scrollable rather
   than wrapping: a second wrapped line would grow the island and push the picture, which is the one thing a
   legend must not do. */
.game-legend {
    display: flex;
    gap: var(--space-4);
    margin: 0;
    padding: var(--space-2) var(--space-3);
    overflow-x: auto;
    list-style: none;
    border-block-start: 1px solid var(--line);
    color: var(--text-muted);
    font-size: 0.78rem;
}

.game-legend-row {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    white-space: nowrap;
}

.game-legend-keys {
    color: var(--text);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.74rem;
}

/* Build identity (#1745). Monospace because it is a hash people compare character by character, and muted
   because it answers a question only asked when something looks wrong — it must be legible without
   competing with the status line above it. */
.game-build {
    margin: 0;
    color: var(--text-muted);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
}

/* The channel/dirty-tree diagnostics. Warning rather than danger: the game is still about to play, so
   this is "read this before you trust what you see", not a failure. */
.game-diagnostic {
    margin: 0;
    color: var(--warning);
    max-inline-size: 46ch;
    font-size: 0.85rem;
    text-align: center;
}

/* Loading bar — determinate while a total is known, native indeterminate animation otherwise (§3). */
.game-progress {
    inline-size: min(60%, 20rem);
    block-size: 0.5rem;
    appearance: none;
    border: none;
    border-radius: var(--radius-full, 999px);
    background: var(--game-progress-track, var(--line));
    overflow: hidden;
}

.game-progress::-webkit-progress-bar {
    background: var(--game-progress-track, var(--line));
}

.game-progress::-webkit-progress-value {
    background: var(--game-progress-fill, var(--primary));
    transition: inline-size 0.15s linear;
}

.game-progress::-moz-progress-bar {
    background: var(--game-progress-fill, var(--primary));
}

/* Chrome — real <x-button>s, top-inline-end; shown while running/paused so it rides fullscreen. */
.game-chrome {
    position: absolute;
    inset-block-start: var(--game-chrome-padding, var(--space-3));
    inset-inline-end: var(--game-chrome-padding, var(--space-3));
    display: none;
    gap: var(--game-chrome-gap, var(--space-2));
    z-index: 2;
}

.game-icon {
    display: block;
    inline-size: 1.25rem;
    block-size: 1.25rem;
}

/* Paused host control — non-covering, explicit player intent; the game's own menu still owns the canvas. */
.game-paused-control {
    position: absolute;
    inset-block-start: var(--game-chrome-padding, var(--space-3));
    inset-inline-start: var(--game-chrome-padding, var(--space-3));
    display: none;
    align-items: center;
    gap: var(--game-chrome-gap, var(--space-2));
    padding-block: var(--space-1);
    padding-inline: var(--space-2);
    border-radius: var(--game-border-radius, 8px);
    background: color-mix(in oklab, var(--surface) 85%, transparent);
    color: var(--text);
    font-size: 0.75rem;
    font-weight: 600;
    z-index: 2;
}

.game-paused-badge {
    margin: 0;
}

/* The keyboard hint; flashes on the Escape release (§4). */
.game-hint {
    position: absolute;
    inset-block-end: var(--game-chrome-padding, var(--space-3));
    inset-inline-start: 50%;
    transform: translateX(-50%);
    margin: 0;
    padding-block: var(--space-1);
    padding-inline: var(--space-2);
    border-radius: var(--game-border-radius, 8px);
    background: color-mix(in oklab, var(--surface) 85%, transparent);
    color: var(--text-muted);
    font-size: 0.75rem;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 0.2s ease;
    pointer-events: none;
}

.game[data-state="poster"] .game-hint,
.game[data-state="running"] .game-hint {
    opacity: 0.85;
}

.game:fullscreen .game-hint {
    opacity: 0.85;
}

.game-hint.is-flash {
    animation: game-hint-flash 1.1s ease;
}

@keyframes game-hint-flash {
    0% {
        opacity: 1;
        transform: translateX(-50%) scale(1.04);
    }

    100% {
        opacity: 0.85;
        transform: translateX(-50%) scale(1);
    }
}

/* The state machine — one visible layer per state. */
.game[data-state="poster"] .game-poster {
    display: flex;
}

.game[data-state="booting"] .game-loading,
.game[data-state="loading"] .game-loading {
    display: flex;
}

.game[data-state="running"] .game-canvas,
.game[data-state="pausing"] .game-canvas,
.game[data-state="paused"] .game-canvas,
.game[data-state="resuming"] .game-canvas {
    display: block;
}

.game[data-state="paused"] .game-paused-control {
    display: flex;
}

.game[data-state="exited"] .game-exited {
    display: flex;
}

.game[data-state="error"] .game-error {
    display: flex;
}

.game[data-state="running"] .game-chrome,
.game[data-state="pausing"] .game-chrome,
.game[data-state="paused"] .game-chrome,
.game[data-state="resuming"] .game-chrome {
    display: flex;
}

/* Fullscreen: the OUTER island goes fullscreen so the live status stays in the top layer (§7.3). */
.game:fullscreen {
    inline-size: 100%;
    block-size: 100%;
    background: var(--game-poster-background-color, var(--surface));
}

.game:fullscreen .game-viewport {
    max-block-size: none;
    aspect-ratio: auto;
    inline-size: 100%;
    block-size: 100%;
    border-radius: 0;
    background: var(--game-poster-background-color, var(--surface));
}

/* Screen-reader-only status line (announces state transitions). */
.game-status {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Chrome reflow on the island's own box (component-internal responsiveness — the modernity contract). */
@container (max-width: 420px) {
    .game-poster-body {
        gap: var(--space-1);
    }

    .game-hint {
        font-size: 0.6875rem;
        white-space: normal;
        max-inline-size: 80cqi;
        text-align: center;
    }
}

@media (prefers-reduced-motion: reduce) {
    .game-hint,
    .game-hint.is-flash {
        animation: none;
        transition: none;
    }

    .game-progress::-webkit-progress-value {
        transition: none;
    }
}

/* ---- the rotate prompt (#1458) --------------------------------------------------------------

   A landscape game on a portrait phone. The engine gates play itself and draws its own message,
   but inside its letterboxed band — on a 19.5:9 screen that band is about a fifth of the height,
   so the instruction ends up as small as the problem. This one is the page's, across the whole
   island, at page text size.

   Driven by `data-rotate` on the wrapper rather than a media query, because the condition is
   "coarse pointer AND portrait" — a narrow desktop window is still a mouse and must never see it,
   and CSS alone cannot express the pairing as cheaply as the island already computes it. */

.game[data-rotate] .game-rotate {
    display: flex;
    /* Above every other layer: it is the reason nothing else is worth reading right now. */
    z-index: 3;
    /* ★ IT INFORMS, IT DOES NOT INTERCEPT (#1460). Covering the island made it swallow every tap
       underneath — including the fullscreen button, which on a portrait iPhone is the one control the
       player needs to reach. Found by driving the real button in WebKit: the click never landed because
       this element was on top of it. There is nothing interactive in here, so it has no business taking
       pointer events at all. */
    pointer-events: none;
    /* OPAQUE, not a scrim. Over the poster the Play button and the hint ghosted through behind the
       message — two sets of instructions at once, one of which you cannot act on yet. The same
       token the poster uses, so the island keeps one background. */
    background: var(--game-poster-background-color, var(--surface));
}

.game-rotate-detail {
    margin: var(--space-2) 0 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-align: center;
    text-wrap: balance;
}

/* ---- immersive mode (#1460) -------------------------------------------------------------------

   iOS Safari implements no element Fullscreen API at all — not the standard one, not a prefixed
   one (measured on WebKit, with Chromium as a control). So on an iPhone the fullscreen button was
   a silent no-op. This is the fallback the button uses wherever the API is missing: not a
   simulation of fullscreen, just the game filling the viewport, which is what was actually asked
   for.

   ★ 100dvh, NEVER 100vh. On iOS `100vh` is the viewport with the URL bar COLLAPSED, so a 100vh
   layer is taller than what you can see and its bottom edge sits behind the browser chrome — which
   is precisely where the touch controls live. `dvh` tracks the dynamic viewport, so the controls
   stay on screen while the bar shows and hides. */

.game[data-immersive] {
    position: fixed;
    inset: 0;
    z-index: 999;
    width: 100%;
    height: 100dvh;
    max-width: none;
    margin: 0;
    border-radius: 0;
    background: var(--neutral-950);
}

/* The aspect box is what makes the island a letterboxed card in the page. In immersive the viewport
   IS the box, so the ratio is released and the canvas takes the whole layer. */
.game[data-immersive] .game-viewport {
    aspect-ratio: auto;
    width: 100%;
    height: 100%;
    /* ★ RELEASE THE 80vh CAP — THIS ONE LINE IS THE WHOLE FULLSCREEN WIN (#1463).
       The base rule carries `max-block-size: 80vh` so an embedded island never dominates a scrolling page.
       In immersive the island IS the page, and that cap was still applying: MEASURED at 844x390, the
       immersive layer correctly filled 100dvh = 390, and the viewport inside it was 312 — exactly 80% of
       390. Those 78 CSS px are OURS, not browser chrome, and they are not cosmetic.
       Integer scaling picks floor(min(bufferW/960, bufferH/540)). At 312 css the buffer is 936 device px,
       936/540 = 1.7, so scale 1 → the picture renders 960x540. At the full 390 the buffer is 1170,
       1170/540 = 2.2, so scale 2 → 1920x1080. One CSS line quadruples the picture's area, with 960x540,
       120px Dante and integer scaling all untouched. */
    max-block-size: none;
}

/* THE WAY OUT AND THE HINT BOTH LIVE INSIDE THE SAFE AREA. Without this the exit control sits under
   the notch on a landscape iPhone and the hint under the home indicator — the button that gets you
   out being the one the hardware covers.

   ★ THIS OVERRIDES THE PADDING SEAM, NOT THE POSITIONS. Setting `left`/`right` here broke the hint,
   which centres itself with `inset-inline-start: 50%` + a translate — the override fought the
   centring and left the text clipped against the edge (caught in the iPhone screenshot). Both the
   chrome and the hint already read `--game-chrome-padding`, so feeding that one value is enough and
   cannot fight their own layout. One conservative value covers every edge: the largest inset the
   device reports, so nothing lands under the notch, the home indicator, or a rounded corner. */
.game[data-immersive] {
    --game-chrome-padding: max(
        var(--space-3),
        env(safe-area-inset-top),
        env(safe-area-inset-right),
        env(safe-area-inset-bottom),
        env(safe-area-inset-left)
    );
}

/* The page behind must not scroll under the layer while it is up. */
:root:has(.game[data-immersive]) body {
    overflow: hidden;
}

/* ---- the indeterminate loading bar (#1460) ----------------------------------------------------

   James: "the loading bar doesn't even work when you click play". It does not, and this is why.

   Godot's `onProgress` reports total=0 whenever the response carries no Content-Length (gzip and
   chunked transfers, which is the normal case over a network), so the island correctly drops the
   `value` attribute and the element becomes INDETERMINATE. But `appearance: none` above — needed to
   style the bar at all — also removes the browser's own indeterminate animation, and an
   indeterminate <progress> has no value for `::-webkit-progress-value` to fill. The result is a
   static empty track for the entire download: on a ~40 MB payload over mobile data that is
   indistinguishable from a hang, and he closes the tab.

   Measured, not assumed: a real boot traced `indeterminate` for every sample from click to running.

   So the indeterminate state gets its own motion. It cannot say HOW far along it is — we genuinely
   do not know — but it must say that something is happening, which is the honest claim. */

.game-progress:not([value]) {
    background-image: linear-gradient(
        90deg,
        transparent 0%,
        var(--game-progress-fill, var(--primary)) 50%,
        transparent 100%
    );
    background-repeat: no-repeat;
    background-size: 40% 100%;
    animation: game-progress-sweep 1.1s linear infinite;
}

@keyframes game-progress-sweep {
    from { background-position: -40% 0; }
    to { background-position: 140% 0; }
}

/* Reduced motion still needs to distinguish "working" from "stalled", so it keeps a visible fill
   and drops only the movement. */
@media (prefers-reduced-motion: reduce) {
    .game-progress:not([value]) {
        animation: none;
        background-position: 50% 0;
    }
}

/* ★ [hidden] MUST BEAT x-button, AT EVERY WIDTH (#1462). `<x-button>` sets `display: inline-flex` as an
   AUTHOR rule, and an author rule beats the UA stylesheet's `[hidden] { display: none }` — so a control
   the island had hidden still measured `inline-flex` and still took space. The first fix put this
   neutraliser inside a `max-width` media block, which meant it simply did not apply in landscape: the
   controls were hidden in portrait and visible-but-wrong above 768px. Top level, no media query. */
.game-chrome [hidden] {
    display: none;
}

/* ★ THE CHROME ICONS HAD NO SIZE (#1462). `.game-icon` sets 1.25rem but nothing ever applied that class —
   MediaService::icon() emits a bare <svg>, so every chrome button rendered as an EMPTY PILL. Pre-existing,
   not new: the fullscreen control has been a blank capsule since it shipped, and it only became obvious
   once three more buttons sat beside it. Sized here rather than by threading a class through the seam, so
   the rule holds for whatever the icon seam emits next. */
.game-chrome svg {
    inline-size: 1.25rem;
    block-size: 1.25rem;
}

/* @toc Generation candidates */
/*
 * <x-generation-candidates> (#1375) — the candidate-slot grid and the verdict pass.
 *
 * MOBILE FIRST, AND THE BAR IS 320 CSS PIXELS. James reviews on a phone, so the base rules ARE the small layout
 * and wider screens are the enhancement. Every token is read with a fallback, so the component still renders
 * legibly on a host that has not manifested it yet — the failure mode that makes a surface look structurally
 * broken when it is only unthemed.
 *
 * Colour comes from the ramp: semantic tokens first, and the checker squares are neutral rungs rather than
 * hand-mixed greys.
 */

.gencand {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* ── DELIVERY: the numbered slot grid ─────────────────────────────────────────────────────────────────────── */

/* TWO UP AT 320 is the base, declared as an explicit track count rather than auto-fill: auto-fill re-derives the
   column count from whatever width it is given, and at 320 minus page padding that silently collapses to one —
   which is a list, not a grid. An explicit count cannot drift when a container's padding changes. */
.gencand-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (min-width: 600px) {
    .gencand-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 900px) {
    .gencand-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.gencand-slot {
    display: flex;
    min-inline-size: 0;
}

/*
 * THE SLOT YOU CAN ACTUALLY DELIVER TO TAKES THE WHOLE ROW. Found by looking at 320, not by measuring: the
 * native file control needs ~198px of content and a half-width cell gives it 114, so "No file chosen" was
 * clipped mid-word and the control read as broken. It never OVERFLOWED — every parent-overflow check passed —
 * which is exactly why the picture was the thing that caught it.
 *
 * Widening the one actionable slot is also the honest hierarchy: it is the slot being worked on, so it earns
 * the room, and every other slot stays a scannable tile.
 */
.gencand-slot-wide {
    grid-column: 1 / -1;
}

/*
 * A CLOSED slot is EXCLUDED, not outstanding. The owner deliberately ended the round without it, so nothing
 * will ever arrive — and it counts as resolved for the job state. Dimming it says "settled, and not part of
 * the set" in a way a neutral badge alone does not; a closed slot that still looks pending sends someone
 * hunting for a delivery that is never coming.
 *
 * Dimmed, never hidden: the slot and its thesis remain readable, because "we deliberately skipped this one"
 * is part of the round's record and removing it would quietly rewrite what was explored.
 */
.gencand-slot-closed .gencand-slot-body {
    opacity: 0.6;
}

.gencand-slot-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    inline-size: 100%;
    min-inline-size: 0;
    padding: var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.gencand-slot-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.gencand-slot-number {
    font-size: var(--text-sm);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
}

/* A SQUARE, RESERVED picture box. It holds its size whether or not a candidate has landed, so a grid mid-delivery
   does not reflow under the reader as images arrive one slot at a time. */
.gencand-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    border-radius: var(--radius-md);
    background-color: var(--neutral-100);
    background-image:
        linear-gradient(45deg, var(--neutral-200) 25%, transparent 25%),
        linear-gradient(-45deg, var(--neutral-200) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--neutral-200) 75%),
        linear-gradient(-45deg, transparent 75%, var(--neutral-200) 75%);
    background-size: 16px 16px;
    background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}

.gencand-thumb img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: contain;
}

.gencand-thumb-empty {
    padding: var(--space-2);
    font-size: var(--text-xs);
    font-weight: 600;
    text-align: center;
    color: var(--text-muted);
}

.gencand-thesis {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--text);
}

.gencand-claim {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.gencand-qa-list {
    margin: 0;
    padding-inline-start: var(--space-4);
    font-size: var(--text-xs);
}

.gencand-slot-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-block-start: auto;
}

/* 44px is the acceptance floor for a touch target, so it is a min-block-size rather than padding that a longer
   label could quietly eat into. */
.gencand-tap {
    min-block-size: 44px;
}

.gencand-deliver {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* ── REVIEW: one candidate at a time ──────────────────────────────────────────────────────────────────────── */

.gencand-review {
    display: flex;
    flex-direction: column;
}

.gencand-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.gencand-actor {
    margin: 0;
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

.gencand-thesis-lead {
    font-size: var(--text-base);
    font-weight: 600;
}

/* The candidate sits over a transparency checker because the output contract REQUIRES a real alpha channel — a
   flat backdrop would conceal exactly the defect the reviewer is checking for. */
.gencand-stage {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    border-radius: var(--radius-md);
    border: 1px solid var(--line);
    background-color: var(--neutral-100);
    background-image:
        linear-gradient(45deg, var(--neutral-200) 25%, transparent 25%),
        linear-gradient(-45deg, var(--neutral-200) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--neutral-200) 75%),
        linear-gradient(-45deg, transparent 75%, var(--neutral-200) 75%);
    background-size: 20px 20px;
    background-position: 0 0, 0 10px, 10px -10px, -10px 0;
}

.gencand-stage img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: contain;
}

/*
 * THE VERDICT BAR STAYS IN THUMB REACH AND ABOVE THE DEVICE SAFE AREA (acceptance item 1). It is sticky rather
 * than fixed so it cannot overlay the shortlist below it, and the bottom padding is max(space, safe-area) so a
 * phone with a home indicator does not bury the No button under the gesture strip.
 *
 * The three actions share the row equally: they are pressed by thumb, repeatedly, and an uneven row makes the
 * middle one the accidental default.
 */
.gencand-verdicts {
    position: sticky;
    inset-block-end: 0;
    z-index: 1;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
    padding-block: var(--space-2);
    padding-block-end: max(var(--space-2), env(safe-area-inset-bottom));
    background: var(--surface);
    border-block-start: 1px solid var(--line);
}

.gencand-verdict {
    inline-size: 100%;
}

/* ── the closing shortlist ────────────────────────────────────────────────────────────────────────────────── */

.gencand-shortlist-head {
    margin: var(--space-4) 0 var(--space-2);
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

.gencand-shortlist-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.gencand-shortlist-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    padding: var(--space-2) var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.gencand-shortlist-thesis {
    flex: 1 1 8rem;
    min-inline-size: 0;
    font-size: var(--text-sm);
}

.gencand-full img {
    inline-size: 100%;
    block-size: auto;
}

.gencand-full figcaption {
    margin-block-start: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* @toc Input */
/* Input. All --input-* tokens come from config/components/input.php['css']. */

.input {
    margin-bottom: 14px;
}

.input-label {
    display: block;
    margin-bottom: 6px;
    color: var(--input-label-color);
    font-size: var(--input-label-font-size);
}

.input-field {
    width: 100%;
    padding: var(--input-padding-y) var(--input-padding-x);
    border: 1px solid var(--input-border);
    border-radius: var(--input-border-radius);
    background: var(--input-background-color);
    color: var(--input-color);
    font-size: var(--input-font-size);
    font-family: var(--font-sans);
}

/* opacity:1 stops the UA re-fading the placeholder below the token's contrast. */
.input-field::placeholder {
    color: var(--text-muted);
    opacity: 1;
}

/* Invalid / hint / required affordances. Error colour is the categorical --danger
 * token (red-700 light / red-400 dark) — self-flipping and menu-hover-certified, so
 * the text clears 4.5:1 and the border 3:1 on every surface without a per-theme rule. */
.input-field[aria-invalid="true"] {
    border-color: var(--danger);
}

.input-error {
    margin-top: 6px;
    font-size: var(--text-xs);
    color: var(--danger);
}

.input-hint {
    margin-top: 6px;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.input-required {
    margin-left: 2px;
    color: var(--danger);
}

/* Keyboard focus recolors the border as a secondary cue; the global :focus-visible
 * ring (base.css) supplies the primary indicator. :focus-visible (not :focus) so a
 * mouse click doesn't trigger either. */
.input-field:focus-visible {
    border-color: var(--input-focus-border);
}

/* Disabled — a native attribute (it arrives via the passthrough channel). Dim + not-allowed so the field
 * reads inert against the tokened bg/fg instead of the UA default. No color literals: opacity carries it,
 * and disabled controls are exempt from the contrast minimum (WCAG 1.4.3). */
.input-field:disabled {
    opacity: var(--disabled-opacity);
    cursor: not-allowed;
}

/* @toc Install app */
/* InstallApp (#1470) — the install affordance. Almost no styling by design: the control is an <x-button>
 * and the sheet is an <x-dialog>, so both already carry the platform's theming. What is left is the sheet's
 * two-step list and the hidden default.
 *
 * [hidden] needs the explicit rule because the wrapper is a flex container: `display` on an author rule beats
 * the [hidden] attribute's UA default, which is exactly how a "hidden" element ends up visible.
 */

.install-app {
    display: inline-flex;
}

.install-app[hidden] {
    display: none;
}

.install-app-steps {
    display: grid;
    gap: var(--install-app-gap);
    margin: 0;
    padding-inline-start: 1.4em;
}

.install-app-steps li {
    color: var(--install-app-step-color);
    font-size: var(--install-app-step-font-size);
}

.install-app-note {
    color: var(--install-app-note-color);
    font-size: var(--install-app-note-font-size);
    margin-block: var(--install-app-gap) 0;
}

/* @toc Invoice doc */
/* Invoice document — the print-ready 83 Consulting invoice + engineering log, authored as a themed
 * page: real palette vars (var(--neutral-*)/var(--primary-*)) from PaletteService, so it themes per
 * tenant and stays consistent on screen and on paper. Rasterized to a TAGGED PDF by browser/render.js.
 * Neutral ink on white; only the brand mark carries primary. @media print drops the app chrome. */

.invoice-doc {
    max-width: 780px;
    margin: 0 auto;
    padding: 40px 44px;
    background: var(--paper);
    color: var(--neutral-900);
    border: 1px solid var(--line);
    border-radius: 12px;
    font-size: 14px;
    line-height: 1.5;
    font-variant-numeric: tabular-nums;
}

.invoice-doc p {
    margin: 0;
}

/* Page 1 fills the printable area (letter content box = 792pt - 2·54pt margin ≈ 684pt; trimmed a hair
 * to avoid a rounding overflow onto a blank page), so the footer can sit at the foot of the page. */
.inv-page {
    display: flex;
    flex-direction: column;
    min-height: 672pt;
}

/* --- header --- */
.inv-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 24px;
    padding-bottom: 22px;
    border-bottom: 1.5px solid var(--neutral-900);
}

.inv-brand {
    display: flex;
    align-items: center;
    gap: 13px;
}

.inv-brand .brand-logo svg,
.inv-log-brand .brand-logo svg {
    display: block;
    width: 40px;
    height: 40px;
}

.inv-issuer-name {
    font-size: 17px;
    font-weight: 700;
}

.inv-issuer {
    text-align: right;
    color: var(--neutral-500);
    font-size: 12px;
    line-height: 1.5;
}

.inv-title {
    font-size: 26px;
    font-weight: 800;
    color: var(--neutral-900);
    letter-spacing: -0.01em;
    margin-bottom: 6px !important;
}

/* --- parties + meta --- */
.inv-parties {
    display: flex;
    justify-content: space-between;
    gap: 32px;
    margin: 26px 0 30px;
}

.inv-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--neutral-500);
    margin-bottom: 8px !important;
}

.inv-client {
    font-weight: 700;
    font-size: 15px;
}

.inv-billto p {
    color: var(--neutral-700);
}

.inv-meta {
    min-width: 250px;
    margin: 0;
    font-size: 13px;
}

.inv-meta > div {
    display: flex;
    justify-content: space-between;
    gap: 28px;
    padding: 4px 0;
}

.inv-meta dt {
    margin: 0;
    color: var(--neutral-500);
}

.inv-meta dd {
    margin: 0;
    text-align: right;
    color: var(--neutral-700);
    white-space: nowrap;
}

/* --- items: the line-items table is now <x-table>; re-dress its .table markup as the invoice
   document table (invoice-scoped, so the app data-table look never leaks in and vice-versa). The
   print surface forces the light theme, so the neutral rungs read as a dark bar on white paper. --- */
.invoice-doc .table-wrap {
    overflow: visible;
}

.invoice-doc .table {
    width: 100%;
    border-collapse: collapse;
    font-size: inherit;
}

.invoice-doc .table th {
    padding: 8px 14px;
    font-size: 10.5px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    text-align: left;
    color: var(--paper);
    background: var(--neutral-900);
    border-bottom: none;
    white-space: nowrap;
}

.invoice-doc .table th.table-right {
    text-align: right;
}

.invoice-doc .table td {
    padding: 13px 14px;
    border-bottom: 1px solid var(--neutral-200);
    vertical-align: top;
}

.invoice-doc .table td.table-right {
    text-align: right;
    white-space: nowrap;
}

/* The stacked "Items" cell (Cell::stacked): the description bold over the muted period. */
.invoice-doc .cell-stacked > div:first-child {
    font-weight: 700;
}

.invoice-doc .cell-stacked > div + div {
    margin-top: 2px;
    color: var(--neutral-500);
    font-size: 13px;
}

/* --- totals --- */
.inv-totals-wrap {
    display: flex;
    justify-content: flex-end;
    margin-top: 20px;
}

.inv-totals {
    min-width: 250px;
    margin: 0;
}

.inv-total-row {
    display: flex;
    justify-content: space-between;
    gap: 24px;
    padding: 5px 14px;
}

.inv-total-row dt {
    margin: 0;
    color: var(--neutral-500);
}

.inv-total-row dd {
    margin: 0;
    text-align: right;
    color: var(--neutral-700);
    white-space: nowrap;
}

/* amount due — a highlighted bar (gray fill, no border) spanning the label + figure row */
.inv-totals-due {
    margin-top: 5px;
    padding-top: 10px;
    padding-bottom: 10px;
    background: var(--neutral-100);
}

.inv-totals-due dt {
    color: var(--neutral-900);
    font-weight: 700;
    font-size: 15px;
}

.inv-totals-due dd {
    color: var(--neutral-900);
    font-weight: 800;
    font-size: 15px;
}

/* --- footer (foot of the invoice page) --- */
.inv-foot {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 24px;
    margin-top: auto;
    padding-top: 14px;
    border-top: 1px solid var(--neutral-200);
    font-size: 11px;
    color: var(--neutral-500);
}

.inv-foot-issuer {
    text-align: right;
    font-weight: 600;
}

/* --- engineering log — its own page --- */
.inv-log {
    break-before: page;
    padding-top: 4px;
}

.inv-log-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 26px;
}

.inv-log-brand {
    display: inline-flex;
    align-items: center;
    gap: 11px;
    font-size: 13px;
    font-weight: 700;
}

.inv-log-brand .brand-logo svg {
    width: 30px;
    height: 30px;
}

.inv-log-num {
    color: var(--neutral-500);
    font-size: 12px;
}

.inv-log-h {
    margin: 0 0 4px;
    font-size: 22px;
    font-weight: 800;
    color: var(--neutral-900);
}

.inv-log-sub {
    margin: 0 0 18px !important;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--neutral-200);
    color: var(--neutral-500);
    font-size: 12px;
}

.inv-log-entry {
    margin-bottom: 16px;
    break-inside: avoid;
}

.inv-log-title {
    font-weight: 700;
    font-size: 13px;
    margin-bottom: 5px !important;
}

.inv-log-bullets {
    margin: 0;
    padding-left: 18px;
    color: var(--neutral-700);
    font-size: 12px;
    line-height: 1.5;
}

.inv-log-bullets li {
    margin-bottom: 3px;
}

/* --- screen chrome: toolbar, list, form (all theme-tokened; hidden or irrelevant on print) --- */
.inv-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: var(--space-5);
}

.inv-tb-back {
    font-size: var(--text-sm);
    color: var(--text-muted);
    text-decoration: none;
}

.inv-tb-back:hover {
    color: var(--text);
}

.inv-tb-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.inv-tb-actions form {
    margin: 0;
}

.inv-tb-btn {
    padding: 8px 14px;
    font-size: var(--text-sm);
    font-weight: 600;
    background: var(--surface-raised, var(--neutral-100));
    border: 1px solid var(--line);
    border-radius: 8px;
    color: var(--text);
    text-decoration: none;
    cursor: pointer;
}

.inv-tb-btn:hover {
    border-color: var(--line-strong, var(--neutral-400));
}

.inv-tb-btn.is-primary {
    background: var(--primary-600, var(--palette-rose-600));
    border-color: transparent;
    color: var(--on-primary);
}

/* The undo/revert action (Revert to draft) — lower-emphasis than the solid forward
   buttons: muted text + a dashed border set it apart as a step back, not forward. */
.inv-tb-btn.is-undo {
    color: var(--text-muted);
    border-style: dashed;
}

.inv-tb-btn.is-undo:hover {
    color: var(--text);
}

.inv-status {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 3px 9px;
    border-radius: 20px;
}

.inv-status.is-draft {
    background: var(--neutral-200, #e5e5e5);
    color: var(--text-muted);
}

.inv-status.is-sent {
    background: var(--warning-surface);
    color: var(--warning);
}

.inv-status.is-paid {
    background: var(--success-surface);
    color: var(--success);
}

.inv-list-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: var(--space-5);
}

.inv-list {
    display: flex;
    flex-direction: column;
}

.inv-row {
    display: block;
    padding: 14px 12px;
    border-bottom: 1px solid var(--line);
    border-radius: 8px;
    color: var(--text);
    text-decoration: none;
}

.inv-row:hover,
.inv-row:active {
    background: var(--surface-raised, var(--neutral-100));
}

.inv-row-main {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.inv-row-num {
    font-size: 15px;
    font-weight: 700;
}

.inv-row-amt {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.inv-row-sub {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 4px;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.inv-form {
    max-width: 720px;
}

.inv-form-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: var(--space-4);
}

.inv-form-num {
    font-size: 20px;
    font-weight: 800;
}

.inv-form-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    margin-bottom: 16px;
}

.inv-form label {
    display: flex;
    flex-direction: column;
    gap: 5px;
    font-size: var(--text-sm);
    font-weight: 600;
}

.inv-form input,
.inv-form textarea {
    padding: 9px 12px;
    font-size: var(--text-sm);
    font-weight: 400;
    background: var(--surface-raised, var(--neutral-100));
    border: 1px solid var(--line);
    border-radius: 8px;
    color: var(--text);
    font-family: inherit;
}

.inv-form input:focus,
.inv-form textarea:focus {
    outline: none;
    border-color: var(--primary-500, var(--palette-rose-500));
}

.inv-form-log {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.inv-form-log textarea {
    resize: vertical;
    line-height: 1.5;
}

.inv-form-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 18px;
}

/* --- invoice view page: mobile-friendly summary + desktop PDF embed --- */
.inv-view {
    max-width: 720px;
}

.inv-view-card {
    padding: 20px 22px;
    background: var(--surface-raised, var(--neutral-100));
    border: 1px solid var(--line);
    border-radius: 14px;
}

.inv-view-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
}

.inv-view-logo {
    display: inline-flex;
}

.inv-view-logo svg {
    display: block;
    width: 44px;
    height: auto;
    border-radius: 8px;
}

.inv-view-num {
    margin: 0;
    font-size: 22px;
    font-weight: 800;
}

.inv-view-sub {
    margin: 2px 0 20px;
    color: var(--text-muted);
}

.inv-view-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin: 0 0 22px;
}

.inv-view-amtbox {
    grid-column: 1 / -1;
}

.inv-view-grid dt {
    margin-bottom: 4px;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.inv-view-grid dd {
    margin: 0;
    font-weight: 600;
}

.inv-view-amt {
    font-size: 26px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.inv-view-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.inv-view-actions form {
    margin: 0;
}

.inv-view-back {
    margin: 16px 0 0;
}

.inv-view-back a {
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-decoration: none;
}

.inv-view-embed {
    display: none;
    width: 100%;
    border: 1px solid var(--line);
    border-radius: 12px;
}

/* 900px: invoice document layout breakpoint; document-specific, not the app seam. */
@media (min-width: 900px) {
    .inv-view {
        max-width: 900px;
    }

    .inv-view-embed {
        display: block;
        height: 1000px;
        margin-top: 24px;
    }
}

/* --- print: drop the app chrome, print just the document --- */
@media print {
    .app-shell-bar,
    .sidebar-nav,
    .app-shell-backdrop,
    .app-header-bar,
    .inv-toolbar {
        display: none !important;
    }

    .app-shell-main,
    .app-shell-wrap {
        padding: 0 !important;
        margin: 0 !important;
    }

    .invoice-doc {
        max-width: none;
        border: none;
        border-radius: 0;
        padding: 0;
    }
}

/* @toc Issue card */
/* Issue card (BarBruiser reader core-four #2) — the D4 merged issue. The card is a native disclosure
 * (x-details); this sheet is the card's own skin over that. STRUCTURE is locked; the ring/radius/padding +
 * teach typography are TBD-direction (they tune at James's A/B/C pick — B seams cards as sections inside ONE
 * ring container, a shell concern, so the per-card contract here is unchanged). Tokens only. */

/* Qualified with the element (the card root IS a <details> from x-details) so the card's own ring/radius/bg
 * beat x-details' `.details` chrome by SPECIFICITY (0,1,1 > 0,1,0), not by bundle order — otherwise the
 * override (and the trap ring below) would silently depend on issue-card.css emitting after details.css. */
details.issue-card {
    border: 1px solid var(--issue-card-ring-color);
    border-radius: var(--issue-card-border-radius);
    background: var(--surface);
    overflow: hidden;
}

/* We compose x-details, whose .details-body already carries its own padding. Zero it so the card's own
 * .issue-card-body is the SINGLE padded container — otherwise the two nest and the body gutter compounds. */
.issue-card .details-body {
    padding: 0;
}

/* The summary header — the weight badge + the "Issue {n}: {label}" name, laid inline. x-details owns the
 * chevron + the toggle; we only lay out the label row. */
.issue-card-head {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.issue-card-name {
    font-weight: 600;
    color: var(--text);
}

/* Trap identity: the danger badge + triangle lead it (locked); the card takes a quiet danger-tinted ring so
 * the trap reads at a glance without a second colour decision. */
details.issue-card-trap {
    border-color: color-mix(in srgb, var(--danger) 32%, var(--line));
}

/* The body sits below the summary, inside the card padding, as a stack of the rule list, the teach, the
 * evidence chips, and the note. */
.issue-card-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--issue-card-padding);
    padding-block-start: var(--space-2);
}

/* Rules — the black-letter list; each row is the rule text with an optional trailing law-mark. */
.issue-card-rules {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.issue-card-rule {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.issue-card-rule-text {
    color: var(--text);
    line-height: 1.5;
}

/* Law-mark — the success-family affordance (Addendum D row 4). A success-tinted CHIP (text on -surface, the
 * AA-vetted pair — bare success text is not menu-certified), leading with the book icon. Emits issuecard:law. */
.issue-card-lawmark {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding-block: 2px;
    padding-inline: var(--space-2);
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    background: var(--success-surface);
    color: var(--success);
    font-size: var(--text-xs);
    font-weight: 600;
    cursor: pointer;
}

.issue-card-lawmark:hover {
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
}

.issue-card-lawmark:focus-visible {
    outline: 2px solid var(--success);
    outline-offset: 2px;
}

.issue-card-lawmark-icon {
    display: inline-flex;
}

.issue-card-lawmark-icon svg {
    width: 14px;
    height: 14px;
}

/* Teach — the IRAC (rule/apply/conclude) or the trap string pair; the reading rhythm is the tunable knob. */
.issue-card-teach {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    line-height: var(--issue-card-teach-line-height);
    color: var(--text);
}

.issue-card-irac {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.issue-card-irac-label {
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

/* Lang-pair (following the x-lang-pair contract): both nodes render; the RU node carries lang="ru" and is
 * shown by default (teach surfaces default en-ru). The reader-level data-lang toggle (set by the shell on
 * the reader root) collapses to EN only. */
.issue-card-lang {
    display: block;
}

.issue-card-lang-ru {
    color: var(--text-muted);
}

:where([data-lang="en"]) .issue-card-lang-ru {
    display: none;
}

/* Evidence chips — the answers this issue was seen in; neutral badges (never status colours). */
.issue-card-evidence {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.issue-card-evidence-label {
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.issue-card-note {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
    font-style: italic;
}

/* @toc Job application editor */
/* Job application editor layout. Shared form controls own field styling and validation states.
 * This file groups the ordinary server form into a readable container-responsive structure.
 * One DOM structure becomes one column when its own container cannot support two columns. */
.job-application-editor {
    display: grid;
    gap: var(--space-5);
    container-type: inline-size;
}

.job-application-editor-form {
    display: grid;
    gap: var(--space-5);
}

.job-application-editor-group {
    display: grid;
    gap: var(--space-4);
    margin: 0;
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

.job-application-editor-group legend {
    padding-inline: var(--space-2);
    font-weight: 700;
}

.job-application-editor-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
}

.job-application-editor-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.job-application-editor-lifecycle {
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--line);
}

@container (max-width: 38rem) {
    .job-application-editor-grid {
        grid-template-columns: 1fr;
    }

    .job-application-editor-actions {
        align-items: stretch;
        flex-direction: column;
    }
}

/* @toc Job application tracker */
/* Job application tracker layout. DataTable owns the table surface and native semantics.
 * This file arranges the prepared filters, lifecycle views, and narrow-layout controls.
 * Inline-size constraints keep the wide table inside its own scroll wrapper on narrow containers. */
.job-application-tracker {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    min-inline-size: 0;
    gap: var(--space-5);
    container-type: inline-size;
}

.job-application-tracker-actions,
.job-application-tracker-filter-actions,
.job-application-tracker-views {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.job-application-tracker-actions {
    justify-content: space-between;
}

.job-application-tracker-actions p {
    margin: 0;
    color: var(--text-muted);
}

.job-application-tracker-views a {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
}

.job-application-tracker-views a[aria-current="page"] {
    color: var(--on-primary);
    background: var(--primary);
    text-decoration: none;
}

.job-application-tracker-filters {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(10rem, 1fr) auto;
    align-items: end;
    gap: var(--space-3);
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

.job-application-company,
.job-application-position {
    min-width: 10rem;
    white-space: normal;
}

.job-application-tracker > .data-table,
.job-application-tracker .table-wrap {
    min-inline-size: 0;
    max-inline-size: 100%;
}

@container (max-width: 42rem) {
    .job-application-tracker-actions,
    .job-application-tracker-filter-actions {
        align-items: stretch;
        flex-direction: column;
    }

    .job-application-tracker-filters {
        grid-template-columns: 1fr;
    }
}

/* @toc Legend schedule */
/* Legend schedule — the span-bearing issues as a collapsible, activatable schedule (number · swatch ·
 * label · call). STRUCTURE is locked; skin (fills, treatments) waits on the direction pick. The swatch reads
 * the emitted --annotation-<n> slot token by data-annotation — NEVER a per-component hue (PaletteService is
 * the governed home). Config: config/components/legend-schedule.php (--legend-schedule-swatch-size / -radius).
 * View: legend-schedule.blade.php; row dispatch: legend-schedule.js. */

.legend-schedule {
    border: 1px solid var(--line);
    border-radius: var(--legend-schedule-border-radius, var(--radius-control));
    background: var(--surface-raised);
    overflow: hidden;
}

.legend-summary {
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
}

.legend-summary-static {
    margin: 0;
    cursor: default;
}

.legend-count {
    color: var(--text-muted);
    font-weight: 400;
}

.legend-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
}

.legend-table thead th {
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-xs);
    font-weight: 600;
    text-align: start;
    color: var(--text-muted);
    border-block: 1px solid var(--line);
}

.legend-cell-issue {
    padding: 0;
    font-weight: 400;
    text-align: start;
    border-bottom: 1px solid var(--line);
}

.legend-call {
    padding-inline: var(--space-3);
    text-align: start;
    color: var(--text-muted);
    white-space: nowrap;
    border-bottom: 1px solid var(--line);
}

/* The row activation target — a full-width button (swatch · number · label). */
.legend-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-2) var(--space-3);
    background: transparent;
    border: 0;
    color: var(--text);
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.legend-row:hover {
    background: color-mix(in srgb, var(--text) 6%, transparent);
}

.legend-number {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--text-muted);
}

.legend-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The swatch — decorative; its hue IS the annotation slot token (data-annotation = the marker number 1..6),
 * so it reads --annotation-<n> from PaletteService. --line is the out-of-range fallback. */
.legend-swatch {
    flex-shrink: 0;
    width: var(--legend-schedule-swatch-size, 12px);
    height: var(--legend-schedule-swatch-size, 12px);
    border-radius: var(--radius-sm);
    background: var(--line);
}

.legend-swatch[data-annotation="1"] { background: var(--annotation-1); }
.legend-swatch[data-annotation="2"] { background: var(--annotation-2); }
.legend-swatch[data-annotation="3"] { background: var(--annotation-3); }
.legend-swatch[data-annotation="4"] { background: var(--annotation-4); }
.legend-swatch[data-annotation="5"] { background: var(--annotation-5); }
.legend-swatch[data-annotation="6"] { background: var(--annotation-6); }

/* @toc Line chart */
/*
 * The native line chart styles a scrollable server-rendered SVG.
 * Series colors come from the component config and retain distinct dashes and markers.
 * The same figure also contains a responsive exact-values disclosure and table.
 */

.line-chart {
    min-width: 0;
    max-width: 100%;
    margin: 0;
    color: var(--line-chart-text-color);
    container-type: inline-size;
}

.line-chart-caption {
    font-size: var(--text-lg);
    font-weight: 600;
}

.line-chart-summary,
.line-chart-axis-context {
    margin: var(--space-2) 0 0;
    color: var(--line-chart-muted-text-color);
}

.line-chart-axis-context {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
    font-size: var(--text-sm);
}

.line-chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
    margin: var(--space-4) 0;
    padding: 0;
    list-style: none;
}

.line-chart-legend-item {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--line-chart-text-color);
    font-size: var(--text-sm);
}

.line-chart-legend-key {
    position: relative;
    display: inline-flex;
    width: 28px;
    height: 14px;
    align-items: center;
    color: var(--line-chart-series-color);
}

.line-chart-legend-line {
    display: block;
    width: 28px;
    height: 12px;
}

.line-chart-legend-line line {
    stroke: currentColor;
    stroke-width: 2;
    stroke-dasharray: var(--line-chart-series-dash);
    vector-effect: non-scaling-stroke;
}

.line-chart-legend-marker {
    position: absolute;
    top: 1px;
    left: 8px;
    width: 12px;
    height: 12px;
}

.line-chart-legend-marker > * {
    fill: var(--line-chart-background-color);
    stroke: currentColor;
    stroke-width: 1.7;
    vector-effect: non-scaling-stroke;
}

.line-chart-scroll {
    max-width: 100%;
    overflow-x: auto;
    border: 1px solid var(--line-chart-border-color);
    border-radius: var(--radius-lg);
    background: var(--line-chart-background-color);
    scrollbar-color: var(--line-chart-scrollbar-thumb-color) var(--line-chart-scrollbar-track-color);
}

.line-chart-scroll:focus-visible {
    outline: 2px solid var(--line-chart-focus-ring-color);
    outline-offset: 2px;
}

.line-chart-svg {
    display: block;
    width: max(100%, var(--line-chart-svg-width));
    height: auto;
    font-family: var(--font-sans);
}

/* Opt-in dashboard mode keeps the complete history in one bounded SVG. The component samples only decorative
   x-axis labels; points, null gaps, paths, chronological extent, and exact-value rows remain complete. */
.line-chart-fit-scroll {
    overflow-x: hidden;
}

.line-chart-fit .line-chart-svg {
    width: 100%;
    min-width: 0;
    max-width: 100%;
}

.line-chart-fit .line-chart-path {
    stroke-width: 1;
    stroke-linecap: butt;
}

.line-chart-fit .line-chart-marker > * {
    stroke-width: 0.35;
}

.line-chart-compact .line-chart-legend {
    gap: var(--space-1) var(--space-3);
    margin-block: 0 var(--space-2);
}

.line-chart-compact .line-chart-legend-item {
    font-size: var(--text-xs);
}

.line-chart-compact .line-chart-data {
    margin-top: var(--space-2);
}

.line-chart-grid line {
    stroke: var(--line-chart-grid-line-color);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

.line-chart-grid text,
.line-chart-axes text {
    fill: var(--line-chart-muted-text-color);
    font-size: var(--text-xs);
}

.line-chart-axes line {
    stroke: var(--line-chart-axis-color);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
}

.line-chart-axes .line-chart-axis-label {
    fill: var(--line-chart-text-color);
    font-size: var(--text-sm);
    font-weight: 600;
}

.line-chart-zero-line {
    stroke: var(--line-chart-zero-line-color);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
}

.line-chart-data-series {
    color: var(--line-chart-series-color);
}

.line-chart-path {
    fill: none;
    stroke: currentColor;
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: var(--line-chart-series-dash);
    vector-effect: non-scaling-stroke;
}

.line-chart-marker > * {
    fill: var(--line-chart-background-color);
    stroke: currentColor;
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
}

.line-chart-series-1 {
    --line-chart-series-color: var(--line-chart-series-1-color);
    --line-chart-series-dash: none;
}

.line-chart-series-2 {
    --line-chart-series-color: var(--line-chart-series-2-color);
    --line-chart-series-dash: 9 5;
}

.line-chart-series-3 {
    --line-chart-series-color: var(--line-chart-series-3-color);
    --line-chart-series-dash: 3 4;
}

.line-chart-series-4 {
    --line-chart-series-color: var(--line-chart-series-4-color);
    --line-chart-series-dash: 12 4 3 4;
}

.line-chart-series-5 {
    --line-chart-series-color: var(--line-chart-series-5-color);
    --line-chart-series-dash: 2 3;
}

.line-chart-series-6 {
    --line-chart-series-color: var(--line-chart-series-6-color);
    --line-chart-series-dash: 6 3 1 3;
}

.line-chart-empty {
    margin: 0;
    padding: var(--space-8) var(--space-4);
    border: 1px solid var(--line-chart-border-color);
    border-radius: var(--radius-lg);
    background: var(--line-chart-empty-background-color);
    color: var(--line-chart-muted-text-color);
    text-align: center;
}

.line-chart-data {
    margin-top: var(--space-4);
}

@container (max-width: 30rem) {
    .line-chart-axis-context,
    .line-chart-legend {
        flex-direction: column;
        align-items: flex-start;
    }
}

/* @toc Link */
/*
 * Link. A themed native <a>: accent colour, underline-on-hover, a visible
 * :focus-visible ring, and a trailing external-link icon. Every token is a
 * --link-* var (emitted from config/components/link.php['css']) with a global
 * token fallback, so the config is the full theming surface. Dark-first.
 */

.link {
    color: var(--link-color, var(--link));
    text-decoration: none;
    border-radius: var(--link-border-radius, 2px);
    transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

.link:hover {
    text-decoration: underline;
    text-decoration-thickness: var(--link-underline-thickness, 1px);
    text-underline-offset: var(--link-underline-offset, 2px);
}

/* muted — sits quietly inline; reads as text until hovered, then it accents. */
.link-muted {
    color: var(--link-muted-color, var(--text-muted));
}

.link-muted:hover {
    color: var(--link-muted-color-hover, var(--text));
}

/* The visible ring is a box-shadow (hugs the text box). The transparent outline
 * is a forced-colors fallback: box-shadow is dropped in high-contrast mode, so
 * the outline gets promoted to a system colour and focus stays visible. */
.link:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--link-focus-ring, var(--primary));
}

/* External affordance — a trailing icon that inherits the link colour, aligned
 * to the text baseline and never wrapping away from the last word. */
.link-external-icon {
    display: inline-block;
    margin-left: 2px;
    vertical-align: -0.125em;
    white-space: nowrap;
}

.link-external-icon svg {
    display: inline-block;
    width: var(--link-icon-size, 0.9em);
    height: var(--link-icon-size, 0.9em);
}

/* @toc Login */
/* Login component. All --login-* tokens from config/components/login.php['css']. */

.login {
    width: 100%;
    max-width: 360px;
    padding: var(--login-padding);
    background: var(--login-background-color);
    color: var(--login-color);
    border: 1px solid var(--login-border);
    border-radius: var(--login-border-radius);
}

.login-title {
    margin: 0 0 18px;
    font-size: var(--text-xl);
    font-weight: 500;
}

.login-error {
    margin-bottom: 12px;
    color: var(--login-error-color);
    font-size: var(--text-sm);
}

.login .checkbox {
    margin: 14px 0 4px;
    font-size: var(--text-sm);
}

.login .button {
    width: 100%;
    justify-content: center;
    margin-top: 6px;
}

@keyframes login-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-6px); }
    75% { transform: translateX(6px); }
}

/* @toc Lottie */
/*
 * Lottie. The wrapper is a positioning context holding three stacked things: the <x-canvas-surface> the
 * player draws into, an optional poster behind it, and the pause control. Every token is a --lottie-* var
 * from config/components/lottie.php['css']; --lottie-aspect is the exception, set per instance from the
 * width/height props so the box reserves the right shape before the animation loads.
 *
 * THE POSTER SITS ON TOP, NOT BEHIND. It covers the canvas until the first frame is painted, then fades —
 * which is what stops the blank-canvas flash between element and first paint. With JS off nothing ever ADDS
 * `is-painted`, so the poster simply stays, and that is the intended no-JS rendering rather than a failure.
 *
 * TWO VALUES HERE ARE LITERALS RATHER THAN TOKENS, on purpose: the poster's fade duration and the
 * transparent backdrop. Neither has an honest type in css:types' closed vocabulary (there is no duration
 * type, and `transparent` is not a colour TOKEN), and an untyped config key fails that gate. A literal in
 * the stylesheet is the honest form; a mistyped token would be a false declaration.
 */

.lottie {
    position: relative;
    display: block;
    /*
     * SIZED LIKE AN <img>, WHICH IS THE MENTAL MODEL AUTHORS ALREADY HAVE: it shrinks to fit a narrower
     * container but never grows past its own intrinsic width. The intrinsic value is the `width` prop, set
     * per instance below. Without that cap a 320px animation stretched to fill a 1280px column — every
     * gate passed and it looked absurd, which is the whole reason the render gets looked at.
     */
    inline-size: 100%;
    max-inline-size: var(--lottie-intrinsic-inline-size, 100%);
    aspect-ratio: var(--lottie-aspect, 1 / 1);
    border-radius: var(--lottie-border-radius, var(--radius-md, 8px));
    overflow: hidden;
}

/* The canvas primitive fills the wrapper; its own figure margin would otherwise offset the drawing. */
.lottie .canvas-surface,
.lottie .canvas-surface-viewport {
    block-size: 100%;
    inline-size: 100%;
    margin: 0;
}

/*
 * ★ THE INHERITED SURFACE IS A DRAWING SURFACE, AND AN ANIMATION IS NOT ONE. <x-canvas-surface> defaults to
 * a neutral-950 fill and a border because its first consumers were the sprite editors, where an opaque dark
 * board and a visible edge are the point. Inherited here it rendered every animation as a black box — fine
 * in dark, obviously wrong on a light page, and wrong in both if the artwork has its own background.
 *
 * OVERRIDDEN SCOPED TO .lottie RATHER THAN BY CHANGING THE PRIMITIVE: canvas-surface is shared (C7), and
 * its default is correct for the surfaces that asked for it. Transparent is the right default HERE because
 * a Lottie carries its own artwork and should sit on whatever the page is. The wrapper owns the rounding,
 * so the inner radius goes.
 */
.lottie .canvas-surface-viewport {
    background: transparent;
    border: none;
    border-radius: 0;
}

.lottie .canvas-surface-canvas {
    display: block;
    block-size: 100%;
    inline-size: 100%;
}

.lottie-poster {
    position: absolute;
    inset: 0;
    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
    opacity: 1;
    transition: opacity 0.3s ease;
}

/*
 * Painted → fade the poster out. lottie.js ADDS `is-painted` on the player's first frame.
 *
 * ★ THE POLARITY IS LOAD-BEARING AND EASY TO GET BACKWARDS. Hiding on the ABSENCE of a class
 * (`:not(.is-loading)`) would hide the poster on exactly the machines that need it most — no JS means no
 * class, which would mean no poster and an empty box. Hiding only on a class the island ADDS is what makes
 * the no-JS rendering the poster rather than a hole.
 */
.lottie.is-painted .lottie-poster {
    opacity: 0;
    /* Once invisible it must stop swallowing pointer events aimed at the control beneath it. */
    pointer-events: none;
}

/* The control is a plain <x-button variant="ghost"> scaled down here — x-button has no size prop, so the
 * smaller footprint is this rule's job rather than an attribute's. */
.lottie-toggle {
    position: absolute;
    padding-block: var(--lottie-toggle-padding-block, var(--space-1, 4px));
    padding-inline: var(--lottie-toggle-padding-inline, var(--space-2, 8px));
    font-size: var(--lottie-toggle-font-size, var(--text-sm, 0.875rem));
    inset-block-end: var(--lottie-toggle-inset, var(--space-2, 8px));
    inset-inline-end: var(--lottie-toggle-inset, var(--space-2, 8px));
    /* Above the poster, which is itself above the canvas. */
    z-index: 1;
    background-color: var(--lottie-toggle-background-color, var(--surface-raised));
    color: var(--lottie-toggle-color, var(--text));
    opacity: var(--lottie-toggle-opacity, 0.85);
}

.lottie-toggle:hover,
.lottie-toggle:focus-visible {
    opacity: 1;
}

/*
 * Reduced motion. THE REAL WORK HAPPENS IN lottie.js, NOT HERE, and that is worth being explicit about:
 * this animation is pixels drawn into a canvas by WASM, so no CSS rule can stop it — the island reads the
 * same preference and declines to autoplay, leaving the first frame painted. What CSS still owns is the
 * chrome around it, so the poster cross-fade is dropped to an instant swap.
 *
 * The control is deliberately NOT hidden under reduced-motion: a visitor with the preference set can still
 * choose to play, and removing their way to do that would be the opposite of respecting the preference.
 */
@media (prefers-reduced-motion: reduce) {
    .lottie-poster {
        transition: none;
    }
}

/* @toc Media preview */
/* MediaPreview — the full-screen inspector. The <dialog> chrome comes from dialog.css; everything here is
 * the stage, the image transform and the backing controls.
 *
 * The stage fills the dialog and clips, so a zoomed image is inspected through a fixed window rather than
 * growing the page. `touch-action: none` is load-bearing: without it the browser claims the drag for its own
 * scroll/pinch and neither pan nor pinch-zoom ever reaches the island.
 */

.media-preview .dialog-panel {
    block-size: 100dvh;
    display: flex;
    flex-direction: column;
    inline-size: 100vw;
    max-block-size: 100dvh;
    max-inline-size: 100vw;
}

.media-preview-stage {
    align-items: center;
    cursor: grab;
    display: flex;
    flex: 1 1 auto;
    justify-content: center;
    min-block-size: 0;
    overflow: hidden;
    touch-action: none;
}

.media-preview-stage:active {
    cursor: grabbing;
}

/* THE THREE BACKINGS — an alpha-edge instrument. A pale fringe invisible on checker is obvious on black,
   which is the entire reason these exist. */
.media-preview-stage[data-backing='checker'] {
    background-color: var(--media-preview-checker-base);
    background-image:
        linear-gradient(45deg, var(--media-preview-checker) 25%, transparent 25%),
        linear-gradient(-45deg, var(--media-preview-checker) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--media-preview-checker) 75%),
        linear-gradient(-45deg, transparent 75%, var(--media-preview-checker) 75%);
    background-position: 0 0, 0 12px, 12px -12px, -12px 0;
    background-size: 24px 24px;
}

.media-preview-stage[data-backing='white'] {
    background: var(--media-preview-white);
}

.media-preview-stage[data-backing='black'] {
    background: var(--media-preview-black);
}

/* transform-origin centre + no transition: a transition on transform makes a drag lag behind the pointer. */
.media-preview-image {
    display: block;
    max-block-size: none;
    max-inline-size: none;
    transform-origin: center center;
    will-change: transform;
}

.media-preview-controls {
    align-items: center;
    border-block-start: 1px solid var(--media-preview-line, var(--line));
    display: flex;
    flex-wrap: wrap;
    gap: var(--media-preview-gap, 8px);
    justify-content: space-between;
    padding: var(--media-preview-controls-padding, 12px);
}

.media-preview-backings {
    border: 0;
    display: flex;
    gap: var(--media-preview-gap, 8px);
    margin: 0;
    padding: 0;
}

.media-preview-unbuilt {
    color: var(--media-preview-unbuilt-color, var(--text-muted));
    margin: 0;
    padding: var(--media-preview-controls-padding, 12px);
}

/* ── THE PROVENANCE PANEL (#1457) ──────────────────────────────────────────────────────────────────────
 * ONE element, placed by CSS at the container width: a bottom sheet on a phone, a side drawer on desktop.
 * The DOM never changes across widths (the Section-Authoring Law), which is also what keeps the open/closed
 * state from being lost when a rotation crosses the breakpoint.
 *
 * Mobile-first, so the base rules ARE the sheet.
 */
.media-preview-provenance {
    background: var(--media-preview-panel-background);
    border-block-start: 1px solid var(--media-preview-line);
    inline-size: 100%;
    max-block-size: 60dvh;
    overflow-y: auto;
    padding: var(--media-preview-controls-padding);
}

.media-preview-provenance[hidden] {
    display: none;
}

@container (min-width: 48rem) {
    /* Desktop: the same element pinned to the inline end as a side drawer. */
    .media-preview-provenance {
        border-block-start: 0;
        border-inline-start: 1px solid var(--media-preview-line);
        block-size: 100%;
        inline-size: var(--media-preview-panel-width);
        inset-block: 0;
        inset-inline-end: 0;
        max-block-size: none;
        position: absolute;
    }
}

.media-preview-actions {
    display: flex;
    gap: var(--media-preview-gap);
}

.media-preview-icon {
    block-size: 1em;
    inline-size: 1em;
}

.media-preview-h {
    color: var(--media-preview-heading-color);
    font-size: var(--media-preview-heading-font-size);
    margin-block: var(--media-preview-gap) 4px;
}

/* pre-wrap, because a prompt is authored text whose line breaks carry meaning — and it must wrap rather
   than force the panel to scroll sideways on a phone. */
.media-preview-prompt {
    background: var(--media-preview-prompt-background);
    border-radius: 6px;
    font-size: var(--media-preview-body-font-size);
    margin: 0;
    padding: var(--media-preview-gap);
    white-space: pre-wrap;
    word-break: break-word;
}

.media-preview-fields {
    display: grid;
    gap: 4px;
    margin: var(--media-preview-gap) 0 0;
}

.media-preview-field {
    display: flex;
    font-size: var(--media-preview-body-font-size);
    gap: var(--media-preview-gap);
    justify-content: space-between;
}

.media-preview-field dt {
    color: var(--media-preview-label-color);
}

.media-preview-field dd {
    margin: 0;
    overflow-wrap: anywhere;
    text-align: end;
}

/* A recorded absence, styled as information rather than as a gap. */
.media-preview-null {
    color: var(--media-preview-label-color);
    font-style: italic;
}

.media-preview-empty,
.media-preview-note {
    color: var(--media-preview-label-color);
    font-size: var(--media-preview-body-font-size);
    margin: 4px 0 0;
}

.media-preview-references {
    font-size: var(--media-preview-body-font-size);
    margin: 0;
    padding-inline-start: 1.2em;
}

/* @toc Media strip */
/* MediaStrip — one asset row: required slots above, generated candidates below.
 *
 * MOBILE-FIRST AND CONTAINER-FIRST, per the Section-Authoring Law: the base rules ARE the 390px layout and
 * the tile grows with the container rather than the viewport, so the row previews truthfully at any pane
 * width. The tile is sized in `cqi` with a clamp, which is what keeps an image large enough to JUDGE on a
 * phone — the whole point of the surface — without a media query.
 *
 * ★ THE TWO TRACKS SHARE ONE TILE RULE. Required boxes and candidates are the same geometry by construction,
 * not by two rules kept in sync; the row's entire meaning is the visual comparison between them.
 */

.media-strip {
    container-type: inline-size;
    display: flex;
    flex-direction: column;
    gap: var(--media-strip-gap, 8px);
    padding-block: var(--media-strip-row-padding, 16px);
    border-block-end: 1px solid var(--media-strip-line, var(--line));
}

.media-strip-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.media-strip-name {
    color: var(--media-strip-name-color, var(--text));
    font-size: var(--media-strip-name-font-size, var(--text-sm));
    font-weight: 600;
    margin: 0;
}

.media-strip-purpose {
    color: var(--media-strip-purpose-color, var(--text-muted));
    font-size: var(--media-strip-purpose-font-size, var(--text-xs));
    margin: 0;
}

/* The row scrolls sideways INSIDE itself. `min-width: 0` is what stops a long track forcing the page wide —
   without it a flex child refuses to shrink below its content and the whole page scrolls horizontally, which
   the card explicitly forbids. */
.media-strip-track {
    display: flex;
    gap: var(--media-strip-gap, 8px);
    min-width: 0;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    padding-block-end: 4px;
}

.media-strip-tile {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 4px;
    scroll-snap-align: start;
}

/* ONE geometry, both strips. */
.media-strip-frame {
    aspect-ratio: 1;
    background-color: var(--media-strip-tile-background, var(--surface-raised));
    border: 1px solid var(--media-strip-line, var(--line));
    border-radius: var(--media-strip-border-radius, 8px);
    display: block;
    /* SIZED SO A PHONE CAN JUDGE. At 390px this lands around 160px — roughly two tiles in view, which is the
       deliberate trade: a strip that fits four tiles on a phone shows four images too small to tell apart, and
       scanning sideways is cheap while squinting is not. The ceiling stops a desktop row becoming a wall. */
    inline-size: clamp(132px, 46cqi, 200px);
    overflow: hidden;
    padding: 0;
}

/* Checker is the ORDINARY tile background — it is how an alpha edge reads at a glance, before anyone opens
   the preview. The preview's white/black controls exist to interrogate the same edge more harshly. */
.media-strip-tile img {
    background-image:
        linear-gradient(45deg, var(--media-strip-checker) 25%, transparent 25%),
        linear-gradient(-45deg, var(--media-strip-checker) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--media-strip-checker) 75%),
        linear-gradient(-45deg, transparent 75%, var(--media-strip-checker) 75%);
    background-position: 0 0, 0 8px, 8px -8px, -8px 0;
    background-size: 16px 16px;
    block-size: 100%;
    inline-size: 100%;
    object-fit: contain;
}

button.media-strip-frame {
    cursor: zoom-in;
}

button.media-strip-frame:focus-visible {
    outline: 2px solid var(--media-strip-focus, var(--primary-500));
    outline-offset: 2px;
}

/* An unfilled required slot: a dashed outline over nothing. `transparent` is a keyword rather than a token
   because there is no colour here to theme — the tile is deliberately empty, and a token would imply a
   configurable fill that should never be set. */
.media-strip-tile-empty .media-strip-frame {
    background-color: transparent;
    border-style: dashed;
}

/* OUTSIDE the artwork, lower-right — never painted onto the image being judged. */
.media-strip-number {
    color: var(--media-strip-number-color, var(--text-muted));
    font-size: var(--media-strip-number-font-size, var(--text-xs));
    font-variant-numeric: tabular-nums;
    margin: 0;
    text-align: end;
}

.media-strip-empty {
    color: var(--media-strip-purpose-color, var(--text-muted));
    font-size: var(--media-strip-purpose-font-size, var(--text-xs));
    margin: 0;
}

/* @toc Mps ingredient */
/* Meal Prep Sundays — the Food Library (index + detail). Green/light, mobile-first.
 * Colours from the --palette-* catalog so it themes under the eightythree.com host. */

/* --- index --- */
.mps-ings {
    padding: 40px 24px 72px;
}

.mps-ings-title {
    margin: 0 0 10px;
    font-size: 36px;
    font-weight: 900;
    color: var(--palette-slate-900);
}

.mps-ings-lead {
    margin: 0 0 28px;
    max-width: 60ch;
    font-size: 18px;
    line-height: 1.5;
    color: var(--palette-slate-500);
}

.mps-ings-cat {
    margin: 28px 0 12px;
    font-size: 14px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--palette-green-700);
}

/* THE WALL (#1365) — mobile-first, and the phone IS the test: James reviews on one.
   TWO COLUMNS AT 375 IS THE BASE, declared explicitly rather than left to auto-fill. The previous
   `auto-fill, minmax(180px, 1fr)` gave ONE column on a phone — 2 tracks needed 372px and the content box is
   327px at 375 (24px of .mps-ings padding each side) — so the wall rendered as a single-file list, which is
   what this card exists to fix. An explicit track count cannot drift if that padding changes; auto-fill would
   silently fall back to one column again. Widths climb from there. */
.mps-ings-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

@media (min-width: 560px) {
    .mps-ings-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 820px) {
    .mps-ings-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

@media (min-width: 1100px) {
    .mps-ings-grid {
        grid-template-columns: repeat(5, minmax(0, 1fr));
    }
}

/* The card is PICTURE-LED: a square image is the card, the name sits beneath it. It was a 48px thumb beside
   the name, which reads as a list row — "cards, not a list" (James). The picture carries the recognition, so
   the wall is scanned by sight rather than read line by line. */
.mps-ing-card {
    display: flex;
    flex-direction: column;
    background: #fff;
    border: 1px solid var(--palette-slate-200);
    border-radius: 12px;
    overflow: hidden;
    text-decoration: none;
    transition: border-color 0.12s ease, transform 0.12s ease;
}

.mps-ing-card:hover {
    border-color: var(--palette-green-400);
    transform: translateY(-2px);
}

/* A SQUARE picture slot: aspect-ratio keeps every card the same height whether the picture is there or not,
   so a row never goes ragged where the 35 artwork-less staples fall. */
.mps-ing-card-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: var(--palette-green-50);
}

.mps-ing-card-thumb img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
}

/* NO PHOTO YET — an admitted gap, not a blank tile. A bare empty square reads as broken or still-loading,
   which states something false about the ingredient; naming the absence is the picture-side equivalent of
   omitting the calorie line. Muted on purpose: it must recede behind the real photographs beside it. */
.mps-ing-card-nophoto {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    color: var(--palette-green-700);
    opacity: 0.55;
}

.mps-ing-card-nophoto-icon svg {
    inline-size: 22px;
    block-size: 22px;
}

.mps-ing-card-nophoto-text {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.mps-ing-card-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-inline-size: 0;
    padding: 10px 12px 12px;
}

/* The longest name in the set is 22 characters ("Whole wheat bread loaf"), which wraps to two lines in a
   ~150px card — so the name is allowed to wrap and the card grows; it is never truncated, because a clipped
   ingredient name is a worse failure than an uneven row. */
.mps-ing-card-name {
    font-size: 15px;
    font-weight: 800;
    line-height: 1.25;
    color: var(--palette-slate-900);
}

.mps-ing-card-cal {
    font-size: 13px;
    color: var(--palette-slate-500);
}

/* --- detail --- */
.mps-ing {
    padding: 28px 24px 72px;
}

.mps-ing-back {
    display: inline-block;
    margin-bottom: 16px;
    font-size: 14px;
    font-weight: 700;
    color: var(--palette-green-700);
    text-decoration: none;
}

.mps-ing-back:hover {
    text-decoration: underline;
}

.mps-ing-cat {
    margin: 0 0 4px;
    font-size: 13px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--palette-green-600);
}

.mps-ing-title {
    margin: 0 0 8px;
    font-size: 38px;
    font-weight: 900;
    color: var(--palette-slate-900);
}

.mps-ing-aliases {
    margin: 0 0 8px;
    font-size: 15px;
    color: var(--palette-slate-500);
}

.mps-ing-cols {
    display: grid;
    grid-template-columns: 1fr;
    gap: 28px;
    margin-top: 24px;
}

/* 880px: MPS content-column breakpoint (intentional, shared across the recipe layout); not the app --bp-* seam. */
@media (min-width: 880px) {
    .mps-ing-cols {
        grid-template-columns: 1fr 280px;
    }
}

.mps-ing-h2 {
    margin: 28px 0 10px;
    font-size: 20px;
    font-weight: 800;
    color: var(--palette-slate-900);
}

.mps-ing-h2 span {
    font-weight: 500;
    font-size: 15px;
    color: var(--palette-slate-400);
}

.mps-ing-note {
    margin: 0 0 10px;
    max-width: 62ch;
    font-size: 16px;
    line-height: 1.6;
    color: var(--palette-slate-600);
}

.mps-ing-src {
    color: var(--palette-green-700);
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.mps-ing-src:hover {
    color: var(--palette-green-800);
}

/* Nutrition — a <dl> now rather than divs of <b>/<span>: each cell is a value and its label, which is what a
   description list is for, and it reads correctly to a screen reader instead of as four bold orphans.
   TWO ACROSS ON A PHONE, four once there is room: at 375px four cells give ~78px each, which crushes
   "protein (g)" onto three lines. Mobile-first, so two is the base and four is the enhancement. */
.mps-ing-nut {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    max-width: 480px;
    margin: 0;
}

.mps-ing-nut-cell {
    display: flex;
    flex-direction: column;
    padding: 14px;
    background: var(--palette-green-50);
    border: 1px solid var(--palette-green-100);
    border-radius: 12px;
}

/* dd first, dt second — the number leads and the label explains it. Reset the dd's UA indent. */
.mps-ing-nut-cell dd {
    margin: 0;
    font-size: 24px;
    font-weight: 900;
    color: var(--palette-green-700);
    font-variant-numeric: tabular-nums;
}

.mps-ing-nut-cell dt {
    font-size: 12px;
    color: var(--palette-slate-500);
}

@media (min-width: 480px) {
    .mps-ing-nut {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* By weight — also a <dl>, replacing a two-column <table>. It was never tabular data: it is a list of terms
   ("1 slice") and their definitions ("≈ 28 g"), and the table also tripped the raw-markup census. */
.mps-ing-units {
    margin: 12px 0 0;
    max-width: 320px;
}

.mps-ing-unit {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    padding: 8px 0;
    border-bottom: 1px solid var(--palette-slate-100);
}

.mps-ing-unit dt {
    color: var(--palette-slate-700);
}

.mps-ing-unit dd {
    margin: 0;
    font-weight: 700;
    color: var(--palette-slate-900);
    font-variant-numeric: tabular-nums;
}

/* 880px: MPS content-column breakpoint (intentional, shared across the recipe layout); not the app --bp-* seam. */
@media (min-width: 880px) {
    .mps-ing-side {
        position: sticky;
        top: 88px;
        align-self: start;
    }
}

/* Picture slot */
/* SQUARE, because the artwork is square (256/512/1024 are all 1:1). The old 16/10 slot cropped a third off a
   square photo of an ingredient, which is the part of the picture you actually want. Capped so it does not
   dominate a phone screen: the picture introduces the ingredient, the words are the page. */
.mps-ing-photo {
    aspect-ratio: 1 / 1;
    max-inline-size: 22rem;
    margin-bottom: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--palette-green-100);
    border-radius: 16px;
    overflow: hidden;
}

.mps-ing-photo img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
}

.mps-ing-photo-empty {
    font-size: 14px;
    font-weight: 700;
    color: var(--palette-green-700);
    opacity: 0.7;
}

/* Blurb */
.mps-ing-blurb {
    margin: 0 0 8px;
    max-width: 64ch;
    font-size: 18px;
    line-height: 1.6;
    color: var(--palette-slate-700);
}

/* Tips rail (pick / store / cook / grow) */
.mps-ing-side {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.mps-ing-tip {
    padding: 16px;
    background: #fff;
    border: 1px solid var(--palette-slate-200);
    border-radius: 14px;
}

.mps-ing-tip-h {
    margin: 0 0 6px;
    font-size: 12px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--palette-green-700);
}

.mps-ing-tip-b {
    margin: 0;
    font-size: 14px;
    line-height: 1.55;
    color: var(--palette-slate-600);
}

/* --- recipes index (#1355) ---------------------------------------------------------------------
 * The browse page that did not exist, so a "Recipes" nav item had somewhere to point. Reuses the
 * .mps-recipe-card the home grid already renders (mps-recipe.css); only the page frame lives here, and the
 * grid is a rule rather than a style attribute so this page and home can converge later without unpicking
 * inline CSS. */

.mps-recipes {
    padding: 40px 24px 72px;
}

.mps-recipes-title {
    margin: 0 0 10px;
    font-size: 36px;
    font-weight: 900;
    color: var(--palette-slate-900);
}

.mps-recipes-lead {
    margin: 0 0 28px;
    max-width: 60ch;
    font-size: 17px;
    line-height: 1.55;
    color: var(--palette-slate-500);
}

.mps-recipes-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 16px;
}

.mps-recipes-empty {
    margin: 0;
    color: var(--palette-slate-500);
}

/* @toc Mps page */
/* Meal Prep Sundays tenant shell — Satoshi font, light/green theme (data-primary="green"
 * + data-neutral="slate"), a sticky header and a 2-tier footer. Scoped to body.mps so it
 * never touches eightythree.com. Brand greens come from the always-on --palette-green-*
 * catalog (the orange accent will use --palette-orange-* / --palette-amber-*). */

@font-face {
    font-family: 'Satoshi';
    src: url('/fonts/satoshi/Satoshi-Variable.woff2') format('woff2'),
         url('/fonts/satoshi/Satoshi-Variable.ttf') format('truetype');
    font-weight: 300 900;
    font-style: normal;
    font-display: swap;
}

/* #1361 — MPS renders inside <x-shell> now, so the BODY LAYOUT belongs to the shell and this rule keeps only
   the tenant's skin: type, ink, ground.
   What was removed and why it mattered: `display:flex; flex-direction:column; min-height:100vh` stacked the old
   header/main/footer document. `body.app-shell` sets `flex-direction:row; height:100vh` for the sidebar-beside-
   main frame — and both selectors are (0,1,1), so the winner is decided by BUNDLE ORDER alone. CssService globs
   `css/components/*.css` alphabetically, which puts mps-page.css after app-shell.css: the column would have won
   and dropped the sidebar above the content on every MPS page. Deleting the layout here is the fix; overriding
   it back would have left two rules fighting over one box. */
body.mps {
    margin: 0;
    font-family: 'Satoshi', system-ui, -apple-system, sans-serif;
    color: var(--palette-slate-800, #1e293b);
    background: var(--palette-green-50, #f0fdf4);
    -webkit-font-smoothing: antialiased;
}

/* #1361 — the brand mark in the shell's two chrome slots (the desktop sidebar, the phone header bar).
   Sized by HEIGHT with an auto inline-size, the same way BarBruiser sizes its own: the shell's defaults are
   `.app-shell-brand .brand-logo svg { width: 30px }` and `.sidebar-nav-brand .brand-logo svg { width: 40px }`,
   which are right for eightythree's SQUARE mark and wrong for a WORDMARK — the MPS logo is 223x20, so a fixed
   width squashes it to a few pixels tall.

   THE SELECTOR CARRIES .brand-logo TOO, on purpose. Without it these read (0,2,1) — exactly the specificity of
   the shell's own rules — so the winner would be decided by BUNDLE ORDER, and CssService globs
   `css/components/*.css` alphabetically: mps-page.css lands BEFORE sidebar-nav.css and AFTER app-shell.css.
   The header brand would size correctly and the sidebar brand would silently keep its 40px, rendering the
   wordmark squashed to 40x17 against a natural 187x17 — which is precisely what it did until this was measured.
   Adding the class makes these (0,3,1): they win on merit, in both files, whatever the manifest order becomes. */
.sidebar-nav-brand .brand-logo.mps-brand svg,
.app-shell-brand .brand-logo.mps-brand svg {
    display: block;
    block-size: 1.05rem;
    inline-size: auto;
    max-inline-size: 100%;
}

.mps-shell {
    width: 100%;
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 24px;
}

/* Header */
.mps-header {
    position: sticky;
    top: 0;
    z-index: 20;
    background: #fff;
    border-bottom: 2px solid var(--palette-slate-900, #0f172a);
}

.mps-header-inner {
    height: 72px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.mps-logo {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

/* The wordmark scales with the viewport, clamped to a sensible min/max. */
.mps-logo img {
    display: block;
    width: clamp(150px, 22vw, 196px);
    height: auto;
}

.mps-nav {
    display: flex;
    gap: 28px;
}

.mps-nav a {
    font-weight: 700;
    font-size: 16px;
    text-decoration: none;
    color: var(--palette-slate-900, #0f172a);
}

.mps-nav a:hover {
    color: var(--palette-green-600, #16a34a);
}

/* Main — grows to fill so the footer sits at the bottom, never a gap below it. */
.mps-main {
    flex: 1 0 auto;
}

/* Footer */
.mps-footer {
    flex-shrink: 0;
}

.mps-footer-links {
    background: var(--palette-green-800, #166534);
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 14px 28px;
    padding: 28px 24px;
}

.mps-footer-links a {
    color: #fff;
    font-size: 14px;
    text-decoration: none;
}

.mps-footer-links a:hover {
    text-decoration: underline;
}

.mps-footer-copy {
    background: var(--palette-green-900, #14532d);
    color: rgba(255, 255, 255, 0.85);
    text-align: center;
    font-size: 12px;
    padding: 14px 24px;
}

@media (max-width: 640px) {
    .mps-header-inner {
        height: 56px;
    }

    .mps-shell {
        padding: 0 16px;
    }
}

/* --- responsive menu: inline nav on desktop, a hamburger drawer on mobile --- */
.mps-burger {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    padding: 11px 9px;
    cursor: pointer;
    background: #fff;
    border: 2px solid var(--palette-slate-900, #0f172a);
    border-radius: 8px;
    box-shadow: 2px 2px 0 var(--palette-slate-900, #0f172a);
}

.mps-burger span {
    display: block;
    height: 2px;
    background: var(--palette-slate-900, #0f172a);
    border-radius: 2px;
    transition: transform 0.18s, opacity 0.18s;
}

.mps-burger[aria-expanded="true"] span:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
}

.mps-burger[aria-expanded="true"] span:nth-child(2) {
    opacity: 0;
}

.mps-burger[aria-expanded="true"] span:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
}

.mps-drawer {
    display: flex;
    flex-direction: column;
    background: #fff;
    border-bottom: 2px solid var(--palette-slate-900, #0f172a);
}

.mps-drawer[hidden] {
    display: none;
}

.mps-drawer a {
    padding: 14px 24px;
    font-weight: 800;
    font-size: 18px;
    color: var(--palette-slate-900, #0f172a);
    text-decoration: none;
    border-top: 1px solid var(--palette-green-100, #dcfce7);
}

.mps-drawer a:hover {
    background: var(--palette-green-50, #f0fdf4);
}

@media (max-width: 768px) {
    .mps-nav {
        display: none;
    }

    .mps-burger {
        display: flex;
    }
}

@media (min-width: 769px) {
    .mps-drawer {
        display: none;
    }
}

/* --- neo-brutalist contrast (MPS only): thick near-black borders + hard offset shadows, no blur,
   so the cards/buttons/table have real punch. Scoped to body.mps; eightythree.com is untouched. --- */
body.mps {
    --mps-ink: var(--palette-slate-900, #0f172a);
    --mps-shadow: 4px 4px 0 var(--palette-slate-900, #0f172a);
    --mps-shadow-sm: 2px 2px 0 var(--palette-slate-900, #0f172a);
}

body.mps .mps-scaler,
body.mps .mps-nutrition,
body.mps .mps-mealkit,
body.mps .mps-scale-anchor,
body.mps .mps-modal,
body.mps .table-wrap {
    border: 2px solid var(--mps-ink);
    border-radius: 10px;
    box-shadow: var(--mps-shadow);
}

body.mps .mps-stepper-btn,
body.mps .mps-quick,
body.mps .mps-mealkit-cta,
body.mps .mps-modal-submit,
body.mps .mps-modal-cancel {
    border: 2px solid var(--mps-ink);
    box-shadow: var(--mps-shadow-sm);
}

body.mps .mps-stepper-btn:active,
body.mps .mps-quick:active,
body.mps .mps-mealkit-cta:active,
body.mps .mps-modal-submit:active,
body.mps .mps-modal-cancel:active {
    box-shadow: none;
    transform: translate(2px, 2px);
}

body.mps .table thead th {
    border-bottom: 2px solid var(--mps-ink);
    color: var(--mps-ink);
}

/* Tabs — a black rule under the bar, a thicker indicator, bolder active tab. */
body.mps .tabs-nav {
    border-bottom: 2px solid var(--mps-ink);
}

body.mps .tabs-tab.is-active {
    color: var(--mps-ink);
}

body.mps .tabs-tab.is-active .tabs-tab-label {
    font-weight: 800;
}

body.mps .tabs-indicator {
    height: 3px;
    background: var(--palette-green-600, #16a34a);
}

/* --- distinctive recipe card (the gallery): bordered, hard-shadow, image-top, highlighter badge --- */
.mps-recipe-card {
    display: block;
    background: #fff;
    border: 2px solid var(--palette-slate-900, #0f172a);
    border-radius: 12px;
    box-shadow: 4px 4px 0 var(--palette-slate-900, #0f172a);
    text-decoration: none;
    overflow: hidden;
    transition: transform 0.12s, box-shadow 0.12s;
}

.mps-recipe-card:hover {
    transform: translate(-2px, -2px);
    box-shadow: 6px 6px 0 var(--palette-slate-900, #0f172a);
}

.mps-recipe-card-img {
    display: block;
    width: 100%;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-bottom: 2px solid var(--palette-slate-900, #0f172a);
    background: var(--palette-green-100, #dcfce7);
}

.mps-recipe-card-body {
    padding: 16px 18px;
}

.mps-recipe-card-title {
    display: block;
    font-weight: 900;
    font-size: 20px;
    line-height: 1.15;
    color: var(--palette-slate-900, #0f172a);
}

/* Green highlighter badge — the signature MPS marker swipe. */
.mps-recipe-card-badge {
    display: inline-block;
    margin-top: 10px;
    font-weight: 800;
    font-size: 13px;
    color: var(--palette-green-900, #14532d);
    background: var(--palette-green-200, #bbf7d0);
    padding: 3px 8px;
}

/* --- "Grow" tab: the recipe's home-growable produce (the vegetable class) --- */
.mps-grow-lead {
    margin: 0 0 12px;
    font-weight: 800;
    color: var(--palette-slate-900, #0f172a);
}

.mps-grow-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.mps-grow-list li {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 16px;
    color: var(--palette-slate-800, #1e293b);
}

.mps-grow-class {
    font-size: 12px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--palette-green-800, #166534);
    background: var(--palette-green-100, #dcfce7);
    padding: 2px 7px;
    border-radius: 999px;
}

/* @toc Mps post */
/* Meal Prep Sundays posts — the ported archive: the /posts index grid and the article page.
 *
 * COLOUR COMES FROM THE RAMP HERE, not from --palette-*. The older mps-*.css files reach straight into the
 * palette catalog (they predate the colour law and are grandfathered in the css:check baseline); this file
 * does not need to, because MPS declares primary=green + neutral=slate in its manifest, so --primary-600 IS
 * green-600 and --neutral-900 IS slate-900. Identical pixels, and the tenant can be re-skinned from its
 * manifest without anyone editing this file.
 *
 * THE NEO-BRUTALIST SKIN IS REUSED, NOT RE-DECLARED: --mps-ink / --mps-shadow / --mps-shadow-sm are already
 * defined on body.mps in mps-page.css. A second definition of the same look is a second thing to keep in step.
 *
 * MOBILE FIRST. The base rules are the phone; the only widening is the grid, which does it with auto-fill
 * rather than a breakpoint, so it needs no media query and no assumption about the viewport at all. */

/* ── /posts index ─────────────────────────────────────────────────────────────────────────────────────── */

.mps-posts {
    padding-block: var(--space-10) var(--space-16);
}

.mps-posts-title {
    margin: 0 0 var(--space-3);
    font-size: clamp(2rem, 8vw, 2.75rem);
    font-weight: 900;
    line-height: 1.05;
    color: var(--text);
}

.mps-posts-lead {
    margin: 0 0 var(--space-8);
    max-inline-size: 60ch;
    font-size: 1.0625rem;
    line-height: 1.55;
    color: var(--text-muted);
}

.mps-posts-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: var(--space-4);
}

.mps-posts-empty {
    color: var(--text-muted);
}

/* The card matches .mps-recipe-card's shape deliberately — a post and a recipe sit in the same nav and must
   read as the same kind of object. It is a separate class rather than a shared one because "recipe card" is a
   name that would then be lying on half its uses. */
.mps-post-card {
    display: block;
    background: var(--surface);
    border: 2px solid var(--mps-ink);
    border-radius: 12px;
    box-shadow: var(--mps-shadow);
    text-decoration: none;
    overflow: hidden;
    transition: transform 0.12s, box-shadow 0.12s;
}

.mps-post-card:hover {
    transform: translate(-2px, -2px);
    box-shadow: 6px 6px 0 var(--mps-ink);
}

.mps-post-card-img {
    display: block;
    inline-size: 100%;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-block-end: 2px solid var(--mps-ink);
    background: var(--primary-100);
}

.mps-post-card-body {
    padding: var(--space-4) var(--space-4) var(--space-5);
}

.mps-post-card-title {
    display: block;
    font-size: 1.1875rem;
    font-weight: 900;
    line-height: 1.15;
    color: var(--text);
}

/* The green highlighter swipe — the tenant's signature marker, same as the recipe card's badge. */
.mps-post-card-badge {
    display: inline-block;
    margin-block-start: var(--space-3);
    padding: 3px var(--space-2);
    font-size: 0.8125rem;
    font-weight: 800;
    color: var(--primary-900);
    background: var(--primary-200);
}

/* ── the article ──────────────────────────────────────────────────────────────────────────────────────── */

/* The reading column. Sits INSIDE .mps-shell (which owns the page gutter and the 1200px cap), so the two
   never argue about one box's max-width — see the note in post.blade.php. */
.mps-post {
    max-inline-size: 640px;
    padding-block: var(--space-6) var(--space-16);
}

.mps-post-back {
    display: inline-block;
    margin-block-end: var(--space-6);
    font-size: 0.875rem;
    font-weight: 700;
    color: var(--link);
    text-decoration: none;
}

.mps-post-back:hover {
    text-decoration: underline;
}

.mps-post-by {
    margin: 0 0 var(--space-2);
    font-size: 0.8125rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--primary-700);
}

.mps-post-title {
    margin: 0 0 var(--space-6);
    font-size: clamp(1.875rem, 7vw, 2.5rem);
    font-weight: 900;
    line-height: 1.08;
    color: var(--text);
}

/* THE HERO AND THE VIDEO ARE THE SAME WIDTH, which is what decided the 640px column above.
 *
 * The legacy heroes are 492x328 — smaller than a desktop reading column — so this upscales one by about 1.3x.
 * I built it capped at 492 first and LOOKED at it: a hero visibly narrower than the video directly beneath it
 * reads as a broken page, which is worse than a photo that is slightly soft. 1.3x is soft; two different
 * widths is wrong. The real fix is bigger source images, which belongs with the media pipeline, not here.
 *
 * The declared width/height still reserve the space, so the article does not jump when the picture lands. */
.mps-post-hero {
    display: block;
    inline-size: 100%;
    block-size: auto;
    margin-block-end: var(--space-6);
    border: 2px solid var(--mps-ink);
    border-radius: 12px;
    box-shadow: var(--mps-shadow);
    background: var(--primary-100);
}

.mps-post-video {
    inline-size: 100%;
    margin-block-end: var(--space-8);
    border: 2px solid var(--mps-ink);
    border-radius: 12px;
    box-shadow: var(--mps-shadow);
}

.mps-post-h2 {
    margin: var(--space-8) 0 var(--space-4);
    font-size: clamp(1.25rem, 5vw, 1.5rem);
    font-weight: 900;
    line-height: 1.2;
    color: var(--text);
}

.mps-post-p {
    margin: 0 0 var(--space-4);
    font-size: 1.0625rem;
    line-height: 1.65;
    color: var(--text);
}

/* Numbered by the browser, not by the text. The importer strips the legacy "1. " prefixes precisely so this
   can be a real ordered list — which is also what a screen reader announces as "list, 6 items". */
.mps-post-list {
    margin: 0 0 var(--space-6);
    padding-inline-start: var(--space-6);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    font-size: 1.0625rem;
    line-height: 1.6;
    color: var(--text);
}

.mps-post-list li::marker {
    font-weight: 800;
    color: var(--primary-700);
}

/* The unordered lists carry their own marker in the content — "🥔 Potatoes" — so a disc beside it would be
   two bullets on one line. Dropping the marker is also what the live pages look like. The blade adds
   role="list" to keep the semantics WebKit strips from a marker-less <ul>. */
.mps-post-list-plain {
    padding-inline-start: 0;
    list-style: none;
}

.mps-post-more {
    margin-block-start: var(--space-12);
    padding: var(--space-5) var(--space-5) var(--space-6);
    background: var(--surface);
    border: 2px solid var(--mps-ink);
    border-radius: 12px;
    box-shadow: var(--mps-shadow);
}

.mps-post-more-h {
    margin: 0 0 var(--space-3);
    font-size: 1.0625rem;
    font-weight: 900;
    color: var(--text);
}

.mps-post-more-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.mps-post-more-list a {
    font-size: 1rem;
    font-weight: 700;
    color: var(--link);
    text-decoration: none;
}

.mps-post-more-list a:hover {
    text-decoration: underline;
}

/* @toc Mps recipe */
/* Meal Prep Sundays — the recipe (weight-scaler) page. Light/green, mobile-first, two
 * columns on desktop (recipe + a sticky nutrition rail). Colours from the --palette-*
 * catalog so it themes correctly even when served under the eightythree.com host. */

.mps-recipe {
    padding: 28px 24px 72px;
}

.mps-recipe-back {
    display: inline-block;
    margin-bottom: 16px;
    font-size: 14px;
    font-weight: 700;
    color: var(--palette-green-700);
    text-decoration: none;
}

.mps-recipe-back:hover {
    text-decoration: underline;
}

.mps-recipe-title {
    margin: 0 0 10px;
    font-size: 36px;
    font-weight: 900;
    line-height: 1.1;
    color: var(--palette-slate-900);
}

.mps-recipe-desc {
    margin: 0 0 12px;
    max-width: 60ch;
    font-size: 18px;
    line-height: 1.5;
    color: var(--palette-slate-600);
}

.mps-recipe-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.mps-tag {
    font-size: 12px;
    font-weight: 700;
    color: var(--palette-green-700);
    background: var(--palette-green-100);
    padding: 3px 10px;
    border-radius: 999px;
}

.mps-recipe-video {
    aspect-ratio: 16 / 9;
    margin: 20px 0;
    border-radius: 14px;
    overflow: hidden;
}

.mps-recipe-video iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

.mps-recipe-body {
    display: grid;
    grid-template-columns: 1fr;
    gap: 28px;
    margin-top: 24px;
}

/* 880px: MPS content-column breakpoint (intentional, shared across the recipe layout); not the app --bp-* seam. */
@media (min-width: 880px) {
    .mps-recipe-body {
        grid-template-columns: 1fr 300px;
    }
}

/* --- the scaler control --- */
.mps-scaler {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px 16px;
    margin-bottom: 8px;
    padding: 16px;
    background: #fff;
    border: 1px solid var(--palette-slate-200);
    border-radius: 14px;
}

.mps-scaler-label {
    font-weight: 700;
    color: var(--palette-slate-700);
}

.mps-stepper {
    display: flex;
    align-items: center;
    gap: 6px;
}

.mps-stepper-btn {
    width: 40px;
    height: 40px;
    font-size: 22px;
    font-weight: 700;
    color: #fff;
    background: var(--palette-green-600);
    border: 0;
    border-radius: 10px;
    cursor: pointer;
}

.mps-stepper-btn:hover {
    background: var(--palette-green-700);
}

.mps-stepper-val {
    min-width: 120px;
    text-align: center;
    font-weight: 800;
    color: var(--palette-slate-900);
}

.mps-stepper-val output {
    font-size: 22px;
    font-variant-numeric: tabular-nums;
}

.mps-scaler-quick {
    display: flex;
    gap: 6px;
}

.mps-quick {
    width: 38px;
    height: 38px;
    font-weight: 700;
    color: var(--palette-green-700);
    background: var(--palette-green-50);
    border: 1px solid var(--palette-green-200);
    border-radius: 10px;
    cursor: pointer;
}

.mps-quick:hover {
    background: var(--palette-green-100);
}

.mps-recipe-h2 {
    margin: 28px 0 12px;
    font-size: 22px;
    font-weight: 800;
    color: var(--palette-slate-900);
}

/* --- ingredient cells — the table chrome (scroll, header, zebra, borders) comes from the shared
   .table component; here we only style the recipe-specific cell content + the tap-to-scale affordance. */

/* Amount = imperial · metric in one cell, and the tap-to-scale target. */
.mps-ing-measure {
    white-space: nowrap;
    cursor: pointer;
    border-radius: 8px;
    font-weight: 700;
    color: var(--palette-slate-900);
}

.mps-ing-measure.is-volume {
    color: var(--palette-green-700);
}

.mps-ing-measure:hover,
.mps-ing-measure:focus-visible {
    background: var(--palette-green-50);
    outline: none;
}

/* Count = the friendly piece count (green). When a size equivalent exists it's a clickable button. */
.mps-ing-count {
    font-weight: 800;
    color: var(--palette-green-700);
}

.mps-ing-count.is-clickable {
    font: inherit;
    font-weight: 800;
    color: var(--palette-green-700);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    text-decoration: underline dotted var(--palette-green-400);
    text-underline-offset: 3px;
}

/* The revealed size equivalent ("about a baseball"). */
.mps-ing-size {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--palette-slate-500);
}

/* Placeholder copy for the stubbed tabs (History / Grow / empty Procedure). */
.mps-tab-empty {
    padding: 24px 4px;
    color: var(--palette-slate-500);
    font-size: 14px;
}

/* "Now scaling off X" banner, shown after a scale-by-ingredient. */
.mps-scale-anchor {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0 0 16px;
    padding: 10px 14px;
    background: var(--palette-green-100);
    border: 1px solid var(--palette-green-300);
    border-radius: 10px;
    font-weight: 700;
    color: var(--palette-green-800);
}

.mps-scale-anchor[hidden] {
    display: none;
}

.mps-scale-reset {
    margin-left: auto;
    font-size: 13px;
    font-weight: 700;
    color: var(--palette-green-700);
    background: none;
    border: none;
    cursor: pointer;
    text-decoration: underline;
}

/* Scale-by-ingredient modal (native <dialog>). */
.mps-modal {
    border: none;
    border-radius: 16px;
    padding: 0;
    max-width: 380px;
    width: calc(100% - 32px);
    background: #fff;
    box-shadow: 0 20px 60px rgba(15, 23, 42, 0.25);
}

.mps-modal::backdrop {
    background: rgba(15, 23, 42, 0.5);
}

.mps-modal-card {
    padding: 24px;
}

.mps-modal-title {
    margin: 0 0 6px;
    font-size: 20px;
    font-weight: 900;
    color: var(--palette-slate-900);
}

.mps-modal-title span {
    color: var(--palette-green-700);
    text-transform: capitalize;
}

.mps-modal-sub {
    margin: 0 0 14px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--palette-slate-500);
}

.mps-modal-fields {
    display: flex;
    gap: 12px;
}

.mps-modal-field {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.mps-modal-unit {
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--palette-slate-500);
}

.mps-modal-input {
    width: 100%;
    box-sizing: border-box;
    font-size: 18px;
    font-weight: 700;
    padding: 10px 12px;
    border: 1px solid var(--palette-slate-300);
    border-radius: 10px;
}

.mps-modal-input:focus {
    outline: none;
    border-color: var(--palette-green-500);
}

.mps-modal-actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
    margin-top: 16px;
}

.mps-modal-cancel,
.mps-modal-submit {
    font-weight: 700;
    padding: 9px 16px;
    border-radius: 10px;
    cursor: pointer;
    border: 1px solid transparent;
}

.mps-modal-cancel {
    background: none;
    color: var(--palette-slate-600);
    border-color: var(--palette-slate-300);
}

.mps-modal-submit {
    background: var(--palette-green-600);
    color: #fff;
}

.mps-modal-submit:hover {
    background: var(--palette-green-700);
}

.mps-ing-hint {
    margin: 0 0 14px;
    font-size: 13px;
    color: var(--palette-slate-500);
}

.mps-ing-name {
    display: block;
    color: var(--palette-slate-900);
    text-transform: capitalize;
}

/* Prep ("diced") — a quiet cooking note under the name, separated from the WHAT you shop for. */
.mps-ing-prep {
    display: block;
    margin-top: 1px;
    font-size: 13px;
    color: var(--palette-slate-500);
}

/* Ingredient -> its Food Library page. Subtle (inherits colour) — a green underline signals
   it's clickable without turning the list into a wall of links. */
.mps-ing-link {
    color: inherit;
    text-decoration: underline;
    text-decoration-color: var(--palette-green-300);
    text-underline-offset: 2px;
}

.mps-ing-link:hover {
    text-decoration-color: var(--palette-green-600);
}

.mps-ing-buy {
    display: inline-block;
    margin-top: 6px;
    font-size: 12px;
    font-weight: 700;
    color: var(--palette-green-700);
    border: 1px solid var(--palette-green-300);
    border-radius: 8px;
    padding: 4px 10px;
    text-decoration: none;
}

.mps-ing-buy:hover {
    background: var(--palette-green-50);
}

/* --- instructions --- */
.mps-steps {
    margin: 0;
    padding-left: 22px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.mps-steps li {
    padding-left: 4px;
    color: var(--palette-slate-700);
    line-height: 1.55;
}

/* --- nutrition + meal-kit rail --- */
.mps-recipe-side {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* 880px: MPS content-column breakpoint (intentional, shared across the recipe layout); not the app --bp-* seam. */
@media (min-width: 880px) {
    .mps-recipe-side {
        position: sticky;
        top: 88px;
        align-self: start;
    }
}

.mps-nutrition {
    padding: 18px;
    background: var(--palette-green-700);
    color: #fff;
    border-radius: 14px;
}

.mps-nutrition-h {
    margin: 0 0 12px;
    font-size: 14px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.mps-nutrition-h span {
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    opacity: 0.8;
}

.mps-nutrition-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.mps-nut {
    display: flex;
    flex-direction: column;
}

.mps-nut-v {
    font-size: 26px;
    font-weight: 900;
    font-variant-numeric: tabular-nums;
}

.mps-nut-k {
    font-size: 12px;
    opacity: 0.85;
}

.mps-mealkit {
    padding: 18px;
    background: #fff;
    border: 1px solid var(--palette-slate-200);
    border-radius: 14px;
}

.mps-mealkit-lead {
    margin: 0 0 4px;
    font-weight: 800;
    color: var(--palette-slate-900);
}

.mps-mealkit-body {
    margin: 0 0 12px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--palette-slate-600);
}

.mps-mealkit-cta {
    display: block;
    text-align: center;
    font-weight: 800;
    color: #fff;
    background: var(--palette-amber-500);
    padding: 11px;
    border-radius: 10px;
    text-decoration: none;
}

.mps-mealkit-cta:hover {
    background: var(--palette-amber-600);
}

.mps-mealkit-disc {
    margin: 8px 0 0;
    font-size: 11px;
    text-align: center;
    color: var(--palette-slate-400);
}

/* @toc Multiple choice */
/*
 * Multiple choice.
 *
 * FIVE TIERS, read top to bottom: the stem asks · the answer cards offer · the verdict answers · the
 * explanation teaches · the note qualifies. Each is a distinct object with its own weight and breathing room —
 * the hierarchy is the point, because the reader meets this at hour nine of a study day.
 *
 * Colour comes from the categorical status tokens (--success / --danger / --info with their AA-vetted -surface
 * tints), never a hand-authored value — so a right/wrong marking is compliant by construction and flips with
 * the theme.
 *
 * The reveal is CSS reacting to ONE attribute the JS sets (data-mc-state per option). Nothing here is a
 * JS-authored style, so the revealed state is reachable server-side by any caller that wants to render it
 * already open — which is also why the ✓/✕ marks are real heroicons in the markup rather than CSS glyphs.
 *
 * Correctness is never carried by colour ALONE (WCAG 1.4.1): the revealed card carries an ICON and the WORDS
 * "Correct answer" / "Your answer", and the rationale says why.
 *
 * BILINGUAL IS ONE RESPONSIVE DESIGN, not two. Both languages are always in the DOM; @container decides how
 * they sit. Narrow: English leads and Russian is on tap (the toggle). Wide: the pair stands as two co-equal
 * columns, because a translation read BESIDE its original is a different — better — study act than one read
 * underneath it. @container, not @media: the component reflows to its OWN box. It sits in a card, in a rail,
 * on a docs page, and the viewport tells it nothing useful about the width it actually has.
 */

.multiple-choice {
    display: flex;
    flex-direction: column;
    gap: var(--multiple-choice-gap, var(--space-4));

    /* The query container for every @container below — the component responds to its own box. */
    container-type: inline-size;
    container-name: mc;
}

/* ---- TIER 1 — the stem ----------------------------------------------------------------------------- */

.multiple-choice-stem {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.multiple-choice-question {
    margin: 0;
    color: var(--text);
    font-size: var(--text-lg);
    font-weight: 600;
    line-height: 1.5;
    text-wrap: pretty;
}

/* ---- the language control -------------------------------------------------------------------------- */

.multiple-choice-langs {
    display: inline-flex;
    align-self: start;
    gap: 2px;
    padding: 2px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.multiple-choice-lang {
    padding: 4px 10px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    font: inherit;
    font-size: var(--text-xs);
    font-weight: 600;
    cursor: pointer;
}

.multiple-choice-lang:hover {
    color: var(--text);
}

/* aria-pressed IS the state — styling from it means the pressed look and the announced state cannot drift. */
.multiple-choice-lang[aria-pressed="true"] {
    background: var(--surface-raised);
    color: var(--text);
}

.multiple-choice-lang:focus-visible {
    outline: 2px solid var(--link);
    outline-offset: 1px;
}

/* ---- TIER 2 — the answer cards --------------------------------------------------------------------- */

/* The fieldset is the radiogroup; it needs no chrome of its own here. */
.multiple-choice-options {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    border: 0;
    margin: 0;
    padding: 0;
}

/* A card, not text-with-a-dot: a bordered object you can aim at, scan, and tell apart at a glance. */
.multiple-choice-option {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: start;
    gap: var(--space-3);
    padding: var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--multiple-choice-option-border-radius, 8px);
}

.multiple-choice-option:hover {
    border-color: var(--text-muted);
}

/* The card the reader is on. :has() reaches the card from the input's OWN state, so the platform stays the
   source of truth for selection — no JS class, nothing to keep in sync.

   :not([data-mc-state]) because this ring is a PRE-REVEAL affordance: it says "you are on this one", and once
   the verdict is in, the verdict owns the card. Without it the selection ring outlives its meaning and beats
   the reveal on specificity — `:has(input:checked)` scores (0,2,1) against `[data-mc-state="wrong"]`'s (0,2,0)
   — which paints the reader's wrong answer with a danger BACKGROUND inside a link-blue BORDER. Caught in the
   browser: it renders as a muddle that says two things at once. */
.multiple-choice-option:has(input:checked):not([data-mc-state]) {
    border-color: var(--link);
}

.multiple-choice-option:has(input:focus-visible) {
    outline: 2px solid var(--link);
    outline-offset: 2px;
}

/* The A–D medallion. */
.multiple-choice-letter {
    display: grid;
    place-items: center;
    inline-size: 28px;
    block-size: 28px;
    border-radius: 50%;
    background: var(--surface);
    border: 1px solid var(--line);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 1;
}

/* Same reasoning as the ring above: the medallion's "you are here" fill yields to the verdict's once it lands. */
.multiple-choice-option:has(input:checked):not([data-mc-state]) .multiple-choice-letter {
    background: var(--link);
    border-color: var(--link);
    color: var(--surface);
}

.multiple-choice-option-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-inline-size: 0;
}

/* ---- TIER 3 — the verdict -------------------------------------------------------------------------- */

.multiple-choice-result {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 700;
}

.multiple-choice-result:empty {
    display: none;
}

.multiple-choice[data-mc-revealed] .multiple-choice-result {
    color: var(--text);
}

/* ---- TIER 4 — the explanation ---------------------------------------------------------------------- */

.multiple-choice-explanation {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}

.multiple-choice-explanation-text {
    margin: 0;
    color: var(--text);
    font-size: var(--text-sm);
    line-height: 1.7;
}

/* ---- TIER 5 — the jurisdiction note ---------------------------------------------------------------- */

/* x-callout carries the --info accent; this only seats it as the last tier inside the revealed block. */
.multiple-choice-note {
    margin: 0;
}

.multiple-choice-note-text {
    margin: 0;
    color: var(--text);
    font-size: var(--text-sm);
    line-height: 1.6;
}

/* ---- the reveal ------------------------------------------------------------------------------------ */

.multiple-choice-option[data-mc-state="correct"] {
    border-color: var(--success);
    background: var(--success-surface);
}

.multiple-choice-option[data-mc-state="correct"] .multiple-choice-letter {
    background: var(--success);
    border-color: var(--success);
    color: var(--success-surface);
}

.multiple-choice-option[data-mc-state="wrong"] {
    border-color: var(--danger);
    background: var(--danger-surface);
}

.multiple-choice-option[data-mc-state="wrong"] .multiple-choice-letter {
    background: var(--danger);
    border-color: var(--danger);
    color: var(--danger-surface);
}

/* The marks ship in the markup and are shown by state — never built by JS. An option the reader did not pick
   and that is not correct (state="other") shows neither: it teaches through its rationale, without carrying a
   verdict it has no business carrying. */
.multiple-choice-mark {
    display: none;
    align-items: center;
    gap: 6px;
    font-size: var(--text-xs);
    font-weight: 700;
    white-space: nowrap;
}

.multiple-choice-option[data-mc-state="correct"] .multiple-choice-mark-correct {
    display: inline-flex;
    color: var(--success);
}

.multiple-choice-option[data-mc-state="wrong"] .multiple-choice-mark-wrong {
    display: inline-flex;
    color: var(--danger);
}

.multiple-choice-mark-icon {
    display: inline-flex;
}

.multiple-choice-mark-icon svg {
    inline-size: 16px;
    block-size: 16px;
}

.multiple-choice-why {
    margin: 0;
    padding-block-start: var(--space-2);
    border-block-start: 1px dashed var(--line);
}

.multiple-choice-why-text {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.6;
}

.multiple-choice-check {
    align-self: start;
    margin-block-start: var(--space-2);
}

/* ---- bilingual: ONE responsive design -------------------------------------------------------------- */

/* The toggle hides text that is ALWAYS present — nothing is fetched, and no state is mirrored client-side. */
.multiple-choice[data-mc-lang="en"] [lang="ru"],
.multiple-choice[data-mc-lang="ru"] [lang="en"] {
    display: none;
}

/* NARROW (the base — mobile-first): English leads, Russian follows quieter. The pair reads as one thing,
   stacked, and the toggle is how Russian is summoned or dismissed. */
.multiple-choice-option-alt {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.5;
}

.multiple-choice-question[lang="ru"],
.multiple-choice-why-text[lang="ru"],
.multiple-choice-explanation-text[lang="ru"],
.multiple-choice-note-text[lang="ru"] {
    color: var(--text-muted);
}

.multiple-choice-langpair {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* WIDE — the pair stands as two co-equal columns. ONLY when both are showing: narrowed to one language, a
   two-column grid would leave a half-empty row, so `both` is the only value that opens the columns. */
@container mc (min-width: 640px) {
    .multiple-choice[data-mc-lang="both"] .multiple-choice-stem,
    .multiple-choice[data-mc-lang="both"] .multiple-choice-langpair,
    .multiple-choice[data-mc-lang="both"] .multiple-choice-why,
    .multiple-choice[data-mc-lang="both"] .multiple-choice-explanation {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: var(--space-4);
        align-items: start;
    }

    /* The note spans the explanation's columns: it qualifies BOTH languages, so it is not a third column. */
    .multiple-choice[data-mc-lang="both"] .multiple-choice-note {
        grid-column: 1 / -1;
    }

    /* Co-equal: read side by side, the translation is not the junior partner it is when stacked underneath. */
    .multiple-choice[data-mc-lang="both"] .multiple-choice-option-alt,
    .multiple-choice[data-mc-lang="both"] .multiple-choice-question[lang="ru"],
    .multiple-choice[data-mc-lang="both"] .multiple-choice-why-text[lang="ru"],
    .multiple-choice[data-mc-lang="both"] .multiple-choice-explanation-text[lang="ru"] {
        color: var(--text);
    }

    .multiple-choice[data-mc-lang="both"] .multiple-choice-option-alt {
        font-size: var(--text-base);
    }

    .multiple-choice[data-mc-lang="both"] .multiple-choice-question[lang="ru"] {
        font-weight: 600;
    }
}

/* @toc Navigation items */
/* This file styles the reusable authorized navigation list.
   Shell drawer, rail, and panel chrome stay outside this renderer.
   Adapter variables can change row geometry without changing authorization or link behavior. */

.navigation-items {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.navigation-items-list {
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    margin: 0;
    padding: 4px 10px;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.navigation-items-list[hidden],
.navigation-items-scope[hidden] {
    display: none;
}

/* The return and scope title form one fixed header. Only the authorized destination list below them scrolls. */
.navigation-items-scope {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.navigation-items-scope-head {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: 4px 10px 10px;
    border-block-end: 1px solid var(--line);
}

.navigation-items-scope-heading {
    margin: 0;
    padding-inline: 12px;
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: 700;
}

.navigation-items-scope-heading:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 4px;
    border-radius: var(--radius-sm);
}

.navigation-items-scope-list {
    flex: 1 1 0;
    min-height: 0;
    margin: 0;
    padding: 0 10px 8px;
    overflow-y: auto;
    overscroll-behavior: contain;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.navigation-items-scope-link {
    overflow-wrap: anywhere;
}

.navigation-items-scope-empty {
    flex: 1 1 0;
    min-height: 0;
    margin: 0;
    padding: 8px 22px;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.navigation-items-scope-back {
    gap: 8px;
}

.navigation-items-main-menu {
    padding-inline: 12px;
    color: var(--text-muted);
    font-size: var(--text-xs);
    text-decoration: none;
}

.navigation-items-main-menu:hover {
    color: var(--text);
    text-decoration: underline;
}

.navigation-items-link {
    display: flex;
    align-items: center;
    gap: var(--navigation-items-row-gap, 10px);
    min-height: var(--navigation-items-row-min-height, auto);
    box-sizing: border-box;
    padding: 8px 12px;
    border: 1px solid transparent;
    border-radius: 8px;
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-decoration: none;
}

button.navigation-items-link {
    width: 100%;
    appearance: none;
    border: 1px solid transparent;
    background: transparent;
    font-family: inherit;
    text-align: start;
    cursor: pointer;
}

button.navigation-items-link:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.navigation-items-project-leaf[aria-pressed="true"],
.navigation-items-link.is-active {
    color: var(--text);
    background: var(--line);
}

.navigation-items-link:hover {
    color: var(--text);
    background: var(--line);
}

.navigation-panel :is(
    .navigation-items-link.is-active,
    .navigation-items-sublink.is-active,
    .navigation-items-project-leaf[aria-pressed="true"]
) {
    --accent-label-color: var(--navigation-panel-selected-accent, var(--primary));
    border-color: var(--accent-label-border);
    color: var(--accent-label-foreground);
    background: var(--accent-label-background);
}

.navigation-panel :is(
    .navigation-items-link.is-active,
    .navigation-items-sublink.is-active,
    .navigation-items-project-leaf[aria-pressed="true"]
):hover {
    border-color: var(--accent-label-border);
    color: var(--accent-label-foreground);
    background: var(--accent-label-background);
}

.navigation-panel :is(
    .navigation-items-link.is-active,
    .navigation-items-sublink.is-active,
    .navigation-items-project-leaf[aria-pressed="true"]
) :is(.navigation-items-icon, .navigation-items-marker, .navigation-items-jump, svg) {
    color: inherit;
}

.navigation-items-icon {
    display: inline-flex;
    width: var(--navigation-items-leading-icon-size, 20px);
    height: var(--navigation-items-leading-icon-size, 20px);
    flex-shrink: 0;
}

.navigation-items-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}

.navigation-items-label {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

.navigation-items-divider {
    height: 0;
    margin: 8px 12px;
    border-block-start: 1px solid var(--line);
    list-style: none;
}

.navigation-items-jump {
    display: inline-flex;
    width: var(--navigation-items-trailing-icon-size, 15px);
    height: var(--navigation-items-trailing-icon-size, 15px);
    flex-shrink: 0;
    color: var(--text-muted);
}

.navigation-items-jump svg {
    width: 100%;
    height: 100%;
}

.navigation-items-site:hover .navigation-items-jump {
    color: var(--text);
}

.navigation-items-section {
    width: 100%;
    appearance: none;
    border: none;
    background: transparent;
    font-family: inherit;
    text-align: start;
    cursor: pointer;
}

.navigation-items-marker {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--text-muted);
}

.navigation-items-marker svg {
    width: var(--navigation-items-trailing-icon-size, 15px);
    height: var(--navigation-items-trailing-icon-size, 15px);
}

.navigation-items-marker-collapse {
    display: none;
}

.navigation-items-item.is-expanded .navigation-items-marker-expand {
    display: none;
}

.navigation-items-item.is-expanded .navigation-items-marker-collapse {
    display: inline-flex;
}

.navigation-items-sublist {
    margin: 2px 0 6px;
    padding-inline-start: 30px;
    list-style: none;
    display: none;
    flex-direction: column;
    gap: 1px;
}

.navigation-items-item.is-expanded .navigation-items-sublist {
    display: flex;
}

.navigation-items-sublink {
    display: block;
    min-height: var(--navigation-items-row-min-height, auto);
    box-sizing: border-box;
    padding: 6px 10px;
    border: 1px solid transparent;
    border-radius: 6px;
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-decoration: none;
    overflow-wrap: anywhere;
}

.navigation-items-sublink:hover {
    color: var(--text);
    background: var(--line);
}

.navigation-items-sublink.is-active {
    color: var(--text);
    background: var(--line);
}

.navigation-items-subhead {
    margin: 10px 0 2px;
    padding: 0 10px;
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    opacity: 0.75;
}

.navigation-items-subhead:first-child {
    margin-top: 0;
}

/* Either shell adapter may present this one authorized renderer as a desktop icon rail. Labels remain in the
   DOM as accessible names; collapsed-only Tooltip hints expose them visually without duplicating expanded copy. */
.navigation-items[data-navigation-items-rail] .navigation-items-label,
.navigation-items[data-navigation-items-rail] .navigation-items-marker,
.navigation-items[data-navigation-items-rail] .navigation-items-jump,
.navigation-items[data-navigation-items-rail] .navigation-items-subhead,
.navigation-items[data-navigation-items-rail] .navigation-items-item .navigation-items-sublist {
    display: none;
}

.navigation-items[data-navigation-items-rail] .navigation-items-link {
    justify-content: center;
    padding-inline: 0;
}

.navigation-items[data-navigation-items-rail] .navigation-items-list {
    padding-inline: 6px;
}

/* A grouped icon opens its existing child list as one transient panel. The shared renderer owns this geometry
   so legacy SidebarNav and NavigationPanel cannot drift into two different flyout implementations. */
.navigation-items[data-navigation-items-rail] .navigation-items-item.is-panel-open .navigation-items-sublist {
    position: fixed;
    inset-block: auto;
    inset-inline: auto;
    inset-block-start: var(--navigation-items-panel-block-start, 8px);
    inset-inline-start: var(--navigation-items-panel-inline-start, 72px);
    z-index: var(--z-popover);
    display: flex;
    inline-size: min(260px, calc(100vw - 16px));
    max-block-size: var(--navigation-items-panel-max-block-size, calc(100vh - 16px));
    box-sizing: border-box;
    margin: 0;
    padding: 8px;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-raised);
    box-shadow: var(--shadow-lg);
}

.navigation-items[data-navigation-items-rail] .navigation-items-item.is-panel-open .navigation-items-subhead {
    display: block;
}

/* A trigger hint and its child panel must never compete for the same space. */
body:has(.navigation-items[data-navigation-items-rail] .navigation-items-item.is-panel-open) [data-tooltip-host] {
    display: none;
}

/* @toc Navigation panel */
/* NavigationPanel owns its independently scrolling destinations and locked utilities.
   Panel and the active dock host own all frame, heading, action, desktop, and compact-sheet presentation.
   One configured row contract aligns expanded destinations and utilities while preserving nested content. */
.navigation-panel {
    --navigation-items-row-gap: var(--navigation-panel-row-gap, 10px);
    --navigation-items-row-min-height: var(--navigation-panel-row-min-height, 44px);
    --navigation-items-leading-icon-size: var(--navigation-panel-icon-size, 20px);
    --navigation-items-trailing-icon-size: var(--navigation-panel-trailing-icon-size, 15px);
    inline-size: clamp(220px, 19vw, 280px);
    max-inline-size: 100%;
    overflow-x: hidden;
}

.navigation-panel > .panel-body,
.navigation-panel > .panel-body > .panel-body-inner {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-inline-size: 0;
    inline-size: 100%;
    min-block-size: 0;
    block-size: 100%;
    overflow: hidden;
}

.navigation-panel-body {
    flex: 1 1 auto;
    min-inline-size: 0;
    inline-size: 100%;
    min-height: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.navigation-panel-nav {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    min-inline-size: 0;
    inline-size: 100%;
    min-height: 0;
    overflow: hidden;
}

.navigation-panel-utilities {
    flex: 0 0 auto;
    padding-block: var(--navigation-panel-row-block-inset, var(--space-2));
    padding-inline: var(--navigation-panel-content-gutter, var(--space-4));
    border-top: 1px solid var(--line);
}

.navigation-panel .navigation-items-list,
.navigation-panel .navigation-items-scope-list {
    margin: 0;
    padding-block: var(--navigation-panel-row-block-inset, var(--space-2));
    padding-inline: var(--navigation-panel-content-gutter, var(--space-4));
    list-style: none;
}

.navigation-panel .navigation-items-scope-head {
    padding-block: var(--space-1) var(--navigation-panel-row-block-inset, var(--space-2));
    padding-inline: var(--navigation-panel-content-gutter, var(--space-4));
}

.navigation-panel :is(.navigation-items-scope-heading, .navigation-items-main-menu) {
    padding-inline: var(--navigation-panel-row-inline-inset, var(--space-3));
}

.navigation-panel .navigation-items-link {
    padding-block: var(--space-2);
    padding-inline: var(--navigation-panel-row-inline-inset, var(--space-3));
    color: var(--text);
    text-decoration: none;
}

.navigation-panel .navigation-items-sublink {
    display: flex;
    align-items: center;
    padding: var(--space-2) var(--navigation-panel-row-inline-inset, var(--space-3));
    color: var(--text);
}

.navigation-panel .navigation-items-sublist {
    padding-inline-start: calc(
        var(--navigation-panel-row-inline-inset, var(--space-3))
        + var(--navigation-panel-icon-size, 20px)
        + var(--navigation-panel-row-gap, 10px)
    );
}

.navigation-panel .navigation-items-icon {
    flex: 0 0 var(--navigation-panel-icon-size, 20px);
    inline-size: var(--navigation-panel-icon-size, 20px);
    block-size: var(--navigation-panel-icon-size, 20px);
}

.navigation-panel .navigation-items-icon svg {
    inline-size: 100%;
    block-size: 100%;
}

.navigation-panel .navigation-items-label {
    min-width: 0;
    flex: 1 1 auto;
}

.navigation-panel-utilities .sidebar-nav-footer-row {
    gap: var(--navigation-panel-row-gap, 10px);
    min-height: var(--navigation-panel-row-min-height, 44px);
    box-sizing: border-box;
    padding-inline: var(--navigation-panel-row-inline-inset, var(--space-3));
    border: 1px solid transparent;
}

.navigation-panel-utilities .sidebar-nav-footer-form {
    width: 100%;
}

.navigation-panel-utilities .sidebar-nav-icon {
    flex: 0 0 var(--navigation-panel-icon-size, 20px);
    inline-size: var(--navigation-panel-icon-size, 20px);
    block-size: var(--navigation-panel-icon-size, 20px);
}

.navigation-panel-utilities .sidebar-nav-icon svg {
    display: block;
    inline-size: 100%;
    block-size: 100%;
}

.navigation-panel-utilities .sidebar-nav-label {
    min-width: 0;
    flex: 1 1 auto;
}

.navigation-panel .navigation-items-link:focus-visible,
.navigation-panel .navigation-items-sublink:focus-visible,
.navigation-panel-utilities .sidebar-nav-footer-action:focus-visible,
.navigation-panel-utilities .sidebar-nav-stash-toggle:focus-visible {
    outline: 2px solid var(--link);
    outline-offset: -2px;
}

/* Navigation's desktop collapse is a usable rail, not x-panel's body fold. The parser-time seed applies this
   same presentation before the footer island loads; the runtime state takes over without an expanded flash. */
html[data-navigation-panel-rail-seed] .navigation-panel,
.navigation-panel[data-navigation-state="rail"] {
    inline-size: var(--navigation-panel-rail-width, 64px);
    min-inline-size: var(--navigation-panel-rail-width, 64px);
    max-inline-size: var(--navigation-panel-rail-width, 64px);
}

html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-label,
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-marker,
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-jump,
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-subhead,
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-item .navigation-items-sublist,
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-label,
.navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-label {
    display: none;
}

html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-link,
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-footer-row,
.navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-footer-row {
    justify-content: center;
    padding-inline: 0;
}

html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-list {
    padding-inline: 6px;
}

.navigation-panel[data-navigation-state="rail"] .navigation-items-list {
    padding-inline: 6px;
}

html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-user-row,
.navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-user-row {
    flex-wrap: wrap;
    gap: var(--space-1);
}

html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-stash-toggle,
.navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-stash-toggle {
    margin-inline-end: 0;
}

/* A 64px rail keeps a 44px utility target around the centered 20px mark. The expanded row inset cannot carry
   into this state because it would leave only 20px for the row even though the icon itself stayed centered. */
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities,
.navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities {
    padding-inline: calc(
        (var(--navigation-panel-rail-width, 64px) - var(--navigation-panel-row-min-height, 44px)) / 2
    );
}

html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-footer-row,
html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-footer-form,
.navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-footer-row,
.navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-footer-form {
    inline-size: var(--navigation-panel-row-min-height, 44px);
    min-inline-size: var(--navigation-panel-row-min-height, 44px);
}

@media (max-width: 768px), (max-height: 600px) {
    /* Before enhancement, keep Navigation and the page in document flow instead of leaving the fixed left
       Frame region without a measured sheet. Destinations and utilities remain reachable when scripts fail. */
    body.app-frame-modern:not(.app-frame-navigation-wired)
        .frame-available:has(> .frame-region--left > .app-frame-navigation-fallback > .navigation-panel) {
        display: block;
        overflow: auto;
    }

    body.app-frame-modern:not(.app-frame-navigation-wired)
        .frame-available > .frame-region--left:has(> .app-frame-navigation-fallback > .navigation-panel) {
        position: static;
        max-block-size: none;
        overflow: visible;
    }

    body.app-frame-modern:not(.app-frame-navigation-wired) .app-frame-navigation-fallback {
        inline-size: 100%;
        max-block-size: none;
        overflow: visible;
    }

    body.app-frame-modern:not(.app-frame-navigation-wired) .navigation-panel[data-panel-dock-peer],
    body.app-frame-modern:not(.app-frame-navigation-wired) .navigation-panel > .panel-body:not([hidden]),
    body.app-frame-modern:not(.app-frame-navigation-wired) .navigation-panel > .panel-body > .panel-body-inner,
    body.app-frame-modern:not(.app-frame-navigation-wired) .navigation-panel-body,
    body.app-frame-modern:not(.app-frame-navigation-wired) .navigation-panel-nav {
        display: block;
        block-size: auto;
        overflow: visible;
    }

    .navigation-panel {
        inline-size: 100%;
        min-inline-size: 0;
        max-inline-size: none;
    }

    html[data-navigation-panel-rail-seed] .navigation-panel,
    .navigation-panel[data-navigation-state="rail"] {
        inline-size: auto;
        min-inline-size: 0;
        max-inline-size: none;
    }

    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-label,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-marker,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-jump,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-subhead,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-label {
        display: initial;
    }

    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-link,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-footer-row {
        justify-content: initial;
        padding-inline: var(--navigation-panel-row-inline-inset, var(--space-3));
    }

    .navigation-panel .navigation-items-list,
    .navigation-panel .navigation-items-scope-list,
    .navigation-panel .navigation-items-scope-head,
    .navigation-panel .navigation-panel-utilities,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-items-list,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities,
    .navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities {
        padding-inline: var(--navigation-panel-content-gutter, var(--space-4));
    }

    /* Consumer-owned compact spacing preserves the accepted former Panel + Navigation block rhythm without
       restoring a second wrapper inset: 12px sheet edge + 8px list edge becomes one 20px Navigation edge. */
    .navigation-panel .navigation-items-list {
        padding-block-start: calc(var(--space-3) + var(--navigation-panel-row-block-inset, var(--space-2)));
    }

    .navigation-panel .navigation-items-scope-head {
        padding-block-start: calc(var(--space-3) + var(--space-1));
    }

    .navigation-panel .navigation-panel-utilities {
        padding-block-end: calc(var(--space-3) + var(--navigation-panel-row-block-inset, var(--space-2)));
    }

    .navigation-panel .navigation-items-link:focus-visible,
    .navigation-panel .navigation-items-sublink:focus-visible,
    .navigation-panel-utilities .sidebar-nav-footer-action:focus-visible,
    .navigation-panel-utilities .sidebar-nav-stash-toggle:focus-visible {
        outline-offset: -2px;
    }

    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-footer-row,
    html[data-navigation-panel-rail-seed] .navigation-panel .navigation-panel-utilities .sidebar-nav-footer-form,
    .navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-footer-row,
    .navigation-panel[data-navigation-state="rail"] .navigation-panel-utilities .sidebar-nav-footer-form {
        inline-size: 100%;
        min-inline-size: 0;
    }
}

/* @toc Note board */
/* The masonry board of self-sizing note tiles. It styles from shared tokens and literal grid values, so it
   hand-authors no colour and no note-colour custom property (the NoteColorTokenTest invariant, shared by the
   notes family). It server-renders as a plain fixed-track grid — a usable board with no JS — and
   note-board.js upgrades it to row-span masonry. Row-span, not CSS columns: columns re-order the tiles down
   each column, but the reading order of a board is its order, so the first note must stay top-left. */

.note-board-groups {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    inline-size: 100%;
    min-inline-size: 0;
}

.note-board-group {
    min-inline-size: 0;
}

.note-board-group-title {
    margin: 0 0 var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.note-board {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: var(--space-4);
    inline-size: 100%;
    min-inline-size: 0;
    /* LOAD-BEARING. A grid item stretches to fill its area by default, so a tile would grow to the height of
       the span note-board.js just gave it, and measuring that stretched height back would feed it into the
       next span, forever. The two symptoms are one bug: every tile receives an identical span, and Playwright
       reports "element is not stable". `align-items: start` makes a tile's height its CONTENT's height, the
       number the span is derived from, which is the only thing that breaks the loop. */
    align-items: start;
}

@media (max-width: 20rem) {
    .note-board {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* note-board.js adds is-masonry, then gives each tile a `grid-row-end: span N` from its measured height. The
   row track becomes a small unit and the row gap folds into the span (note-board.js adds the column gap to
   the measured height), so the tiles stagger instead of aligning to the tallest tile in each row. The masonry
   unit (--space-1) and the gap (the column gap) are read back from the computed style, never hand-duplicated. */
.note-board.is-masonry {
    grid-auto-rows: var(--space-1);
    row-gap: 0;
}

/* @toc Note composer */
/* The note composer: a surface that grows a note in place. It styles from shared semantic tokens (--surface,
   --line, --text, --text-muted, --space-*, --radius-*), so it hand-authors no colour and no note-colour
   custom property (the NoteColorTokenTest invariant, shared by every notes-family component). The body is an
   auto-grow textarea: overflow is hidden and note-composer.js sets the height to the content, so it grows with
   its content and never shows an inner scrollbar. */

.note-composer {
    inline-size: 100%;
    min-inline-size: 0;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    box-shadow: var(--shadow-sm);
}

/* Shut: one row until the user asks for more. Hidden while the form is open (note-composer.js). */
.note-composer.is-open .note-composer-shut {
    display: none;
}

.note-composer-shut {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
}

.note-composer-take {
    flex: 1 1 auto;
    text-align: start;
    padding: var(--space-2) 0;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: text;
    font-size: 0.9375rem;
    font-family: inherit;
    min-block-size: 2.75rem;
}

.note-composer-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
}

.note-composer-title {
    width: 100%;
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: 0.9375rem;
    font-weight: 600;
    font-family: inherit;
}

.note-composer-title::placeholder,
.note-composer-text::placeholder {
    color: var(--text-muted);
}

.note-composer-title:focus-visible,
.note-composer-text:focus-visible,
.note-composer-rowtext:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
}

.note-composer-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* Auto-grow: no resize handle and no inner scrollbar; the JS grows the height to the content. */
.note-composer-text {
    width: 100%;
    resize: none;
    overflow: hidden;
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: 0.875rem;
    line-height: 1.5;
    font-family: inherit;
}

.note-composer-rows {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* A component display rule outranks the UA [hidden] { display: none }, so a hidden form (the shut state), the
   rows, and the add-row button would still show. Restore the hide explicitly. */
.note-composer-form[hidden],
.note-composer-rows[hidden],
.note-composer-addrow[hidden] {
    display: none;
}

.note-composer-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.note-composer-check-hit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
    cursor: pointer;
}

.note-composer-rowbox {
    flex: none;
    inline-size: 1.125rem;
    block-size: 1.125rem;
    margin: 0;
}

.note-composer-rowtext {
    flex: 1 1 auto;
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: 0.875rem;
    font-family: inherit;
}

.note-composer-rowdel {
    flex: none;
    display: inline-flex;
    padding: var(--space-1);
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    opacity: 0;
    transition: opacity 120ms ease;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
    align-items: center;
    justify-content: center;
}

.note-composer-row:hover .note-composer-rowdel,
.note-composer-row:focus-within .note-composer-rowdel {
    opacity: 1;
}

@media (hover: none) {
    .note-composer-rowdel {
        opacity: 1;
    }
}

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

.note-composer-addrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) 0;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 0.8125rem;
    min-block-size: 2.75rem;
}

.note-composer-foot {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    margin-top: var(--space-1);
}

.note-composer-write {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text-muted);
    font-size: 0.8125rem;
}

.note-composer-write-action {
    border: 0;
    background: transparent;
    color: var(--text);
    cursor: pointer;
    font: inherit;
    text-decoration: underline;
    min-block-size: 2.75rem;
}

.note-composer-write-action[hidden],
.note-composer-write [hidden] {
    display: none;
}

.note-composer-btn,
.note-composer-take-list {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-2);
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    cursor: pointer;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
}

.note-composer-btn:hover,
.note-composer-take-list:hover {
    background: color-mix(in srgb, var(--text) 8%, transparent);
    color: var(--text);
}

.note-composer-btn[aria-pressed="true"] {
    color: var(--text);
}

.note-composer-close {
    margin-inline-start: auto;
    padding: var(--space-2) var(--space-3);
    background: transparent;
    border: 0;
    border-radius: var(--radius-control);
    color: var(--text-muted);
    cursor: pointer;
    font-size: 0.875rem;
    font-weight: 500;
    font-family: inherit;
    min-block-size: 2.75rem;
}

.note-composer-close:hover {
    background: color-mix(in srgb, var(--text) 8%, transparent);
    color: var(--text);
}

.note-composer-btn:focus-visible,
.note-composer-take:focus-visible,
.note-composer-take-list:focus-visible,
.note-composer-rowdel:focus-visible,
.note-composer-addrow:focus-visible,
.note-composer-close:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 1px;
}

.note-composer-icon {
    display: inline-flex;
    line-height: 0;
}

.note-composer-icon svg {
    width: 1.125rem;
    height: 1.125rem;
}

/* @toc Note editor */
/* The open note editor. The note colour is the surface: the view sets --tile-surface inline from the vetted
   note-colour set, so this file hand-authors no colour and no note-colour custom property (the
   NoteColorTokenTest invariant). Text stays on the semantic tokens the note surfaces are AA-vetted against:
   --text, --text-muted, and the --line hairline. Full screen on a phone, filled to the edges; a centred sheet
   on desktop. The dialog is a viewport overlay, so the phone-to-desktop switch is a viewport @media, not a
   container query. */

.note-editor {
    width: 100%;
    max-width: 100%;
    height: var(--editor-viewport-height, 100dvh);
    max-height: var(--editor-viewport-height, 100dvh);
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text);
}

.note-editor::backdrop {
    background: var(--overlay);
}

.note-editor-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    height: var(--editor-viewport-height, 100dvh);
    background: var(--tile-surface);
    color: var(--text);
    overflow: hidden;
    padding-block-start: env(safe-area-inset-top);
    padding-block-end: env(safe-area-inset-bottom);
}

/* Desktop: a centred sheet instead of a full-screen surface. */
@media (min-width: 40rem) {
    .note-editor {
        width: min(600px, 92vw);
        height: auto;
        max-height: 88vh;
        margin: auto;
        border-radius: var(--radius-container);
        overflow: hidden;
    }

    .note-editor-panel {
        height: auto;
        max-height: 88vh;
        border-radius: var(--radius-container);
        padding-block: 0;
    }
}

.note-editor-form {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

/* Top bar: a back arrow on the left, the note actions on the right. No close button. */
.note-editor-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2);
    flex: none;
}

.note-editor-topbar-actions {
    display: flex;
    gap: var(--space-1);
}

/* Every bar control is a native button: transparent, round, revealed by hover/focus state on the surface. */
.note-editor-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-2);
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    cursor: pointer;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
}

.note-editor-btn:hover {
    background: color-mix(in srgb, var(--text) 8%, transparent);
    color: var(--text);
}

.note-editor-btn[aria-pressed="true"] {
    color: var(--text);
}

.note-editor-btn:focus-visible,
.note-editor-swatch:focus-visible,
.note-editor-rowdel:focus-visible,
.note-editor-rowmove:focus-visible,
.note-editor-addrow:focus-visible,
.note-editor-fold-toggle:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 1px;
}

.note-editor-title {
    width: 100%;
    padding: var(--space-2) var(--space-4);
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: 1.25rem;
    font-weight: 600;
    font-family: inherit;
}

.note-editor-title::placeholder {
    color: var(--text-muted);
}

.note-editor-title:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: -2px;
}

.note-editor-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0 var(--space-4) var(--space-2);
    overscroll-behavior: contain;
    scroll-padding-block-end: calc(5rem + env(safe-area-inset-bottom));
}

.note-editor-text {
    width: 100%;
    min-height: 8rem;
    resize: none;
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: 0.9375rem;
    line-height: 1.5;
    font-family: inherit;
}

.note-editor-text::placeholder {
    color: var(--text-muted);
}

.note-editor-text:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
}

.note-editor-rows {
    list-style: none;
    margin: 0;
    padding: 0;
}

.note-editor-row {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) 0;
}

.note-editor-row[draggable="true"] {
    cursor: grab;
}

.note-editor-row.is-dragging {
    opacity: 0.55;
}

.note-editor-grip {
    display: inline-flex;
    color: var(--text-muted);
    cursor: grab;
    opacity: 0;
    transition: opacity 120ms ease;
}

.note-editor-check-hit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
    cursor: pointer;
}

.note-editor-rowbox {
    flex: none;
    inline-size: 1.125rem;
    block-size: 1.125rem;
    margin: 0;
}

.note-editor-rowtext {
    flex: 1 1 auto;
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: 0.9375rem;
    font-family: inherit;
}

.note-editor-rowtext:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 1px;
}

.note-editor-row.is-done .note-editor-rowtext {
    color: var(--text-muted);
    text-decoration: line-through;
}

.note-editor-rowdel {
    flex: none;
    display: inline-flex;
    padding: var(--space-1);
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    opacity: 0;
    transition: opacity 120ms ease;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
    align-items: center;
    justify-content: center;
}

.note-editor-rowmove {
    display: inline-flex;
    padding: var(--space-1);
    background: transparent;
    border: 0;
    border-radius: var(--radius-control);
    color: var(--text-muted);
    cursor: pointer;
    opacity: 0;
    transition: opacity 120ms ease;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
    align-items: center;
    justify-content: center;
}

.note-editor-row:hover .note-editor-grip,
.note-editor-row:focus-within .note-editor-grip,
.note-editor-row:hover .note-editor-rowdel,
.note-editor-row:focus-within .note-editor-rowdel,
.note-editor-row:hover .note-editor-rowmove,
.note-editor-row:focus-within .note-editor-rowmove {
    opacity: 1;
}

@media (hover: none) {
    .note-editor-grip,
    .note-editor-rowdel,
    .note-editor-rowmove {
        opacity: 1;
    }
}

.note-editor-addrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 0.875rem;
    min-block-size: 2.75rem;
}

.note-editor-rule {
    border: 0;
    border-top: 1px solid var(--line);
    margin: var(--space-2) 0;
}

.note-editor-fold.is-empty {
    display: none;
}

.note-editor-fold-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) 0;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 0.8125rem;
    min-block-size: 2.75rem;
}

.note-editor-fold-chevron {
    display: inline-flex;
    transition: transform 120ms ease;
}

.note-editor-fold-toggle[aria-expanded="true"] .note-editor-fold-chevron {
    transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
    .note-editor-grip,
    .note-editor-rowdel,
    .note-editor-rowmove,
    .note-editor-fold-chevron {
        transition: none;
    }
}

.note-editor-labels {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    padding: 0 var(--space-4) var(--space-2);
}

.note-editor-labels .ui-badge {
    max-inline-size: 100%;
    min-inline-size: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.note-editor-label-panel {
    margin: 0 var(--space-4) var(--space-2);
    padding: var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.note-editor-label-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-bottom: var(--space-2);
}

.note-editor-label-field {
    display: flex;
    align-items: center;
    gap: var(--space-1);
}

.note-editor-label-field input {
    flex: 1 1 auto;
    min-width: 0;
    padding: var(--space-2);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--text);
}

.note-editor-label-field button,
.note-editor-label-list button {
    padding: var(--space-1) var(--space-2);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--text);
    cursor: pointer;
    min-block-size: 2.75rem;
}

.note-editor-write {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    min-height: 1.5rem;
    padding: 0 var(--space-4);
    color: var(--text-muted);
    font-size: 0.8125rem;
}

.note-editor-write [hidden] {
    display: none;
}

.note-editor-write [data-editor-status] {
    margin: 0;
}

.note-editor-conflict {
    display: inline-flex;
    gap: var(--space-2);
}

.note-editor-bottombar {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-2);
    padding-block-end: max(var(--space-2), env(safe-area-inset-bottom));
    flex: none;
}

/* The more menu sits in the bottom bar, so open its x-dropdown flyout UPWARD: a downward flyout would be
   clipped by the panel or pushed off a short sheet. */
.note-editor-bottombar .dropdown-menu {
    top: auto;
    bottom: calc(100% + 4px);
}

/* The colour swatches popover is anchored to the panel and hidden until explicitly opened. */
.note-editor-swatches {
    position: absolute;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    max-width: 14rem;
    inset-block-end: calc(var(--space-16) + env(safe-area-inset-bottom));
    inset-inline-start: var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    padding: var(--space-1);
}

/* A component display rule outranks the UA [hidden] { display: none }, so a hidden popover would still show.
   Restore the hide explicitly. */
.note-editor-swatches[hidden] {
    display: none;
}

.note-editor-swatch {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--radius-full);
    background: var(--tile-surface);
    border: 1px solid var(--line);
    cursor: pointer;
}

.note-editor-swatch[aria-pressed="true"] {
    outline: 2px solid var(--text);
    outline-offset: 1px;
}

.note-editor-icon {
    display: inline-flex;
    line-height: 0;
}

.note-editor-icon svg {
    width: 1.25rem;
    height: 1.25rem;
}

.note-editor-fold-chevron svg,
.note-editor-grip svg {
    width: 1rem;
    height: 1rem;
}

@media (max-width: 39.999rem) {
    .note-editor {
        inset-block-start: var(--editor-viewport-top, 0px);
        inset-inline: 0;
    }

    .note-editor-row {
        min-inline-size: 0;
    }

    .note-editor-rowtext {
        min-inline-size: 0;
    }

}

/* @toc Note tile */
/* Note tile: the board's base unit. The note colour is the surface. The view sets --tile-surface inline from
   the vetted note-colour token set, so this file hand-authors no colour and no note-colour custom property
   (the NoteColorTokenTest invariant). Text stays on the semantic tokens the note surfaces are AA-vetted against:
   --text, --text-muted, and the --line hairline. The action row is always in the DOM, so its space is
   reserved and a hover never changes the tile's height. It is revealed on hover AND focus, and it stays
   visible under @media (hover: none), because hover cannot reveal a control on a device that has none. */

.note-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-4);
    background: var(--tile-surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    color: var(--text);
    min-inline-size: 0;
}

/* The title clears the pin in its corner. An untitled note pads its first content instead. */
.note-tile-title {
    margin: 0;
    padding-right: var(--space-6);
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--text);
    overflow-wrap: anywhere;
}

.note-tile.is-untitled .note-tile-text,
.note-tile.is-untitled .note-tile-checklist {
    padding-right: var(--space-6);
}

.note-tile-text {
    margin: 0;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--text-muted);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.note-tile-checklist {
    margin: 0;
}

.note-tile-labels {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    overflow-wrap: anywhere;
}

.note-tile-labels .ui-badge {
    max-inline-size: 100%;
    min-inline-size: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.note-tile .checklist-item-label,
.note-tile .checklist-fold-toggle {
    min-block-size: 2.75rem;
}

/* The pin sits in the note's corner. The action row sits at the foot. Both reserve their space and reveal
   on hover or focus; a pinned note keeps its pin. */
.note-tile-pin {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    opacity: 0;
    transition: opacity 120ms ease;
}

.note-tile-acts {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-top: var(--space-1);
    opacity: 0;
    transition: opacity 120ms ease;
}

.note-tile:hover .note-tile-pin,
.note-tile:focus-within .note-tile-pin,
.note-tile:hover .note-tile-acts,
.note-tile:focus-within .note-tile-acts {
    opacity: 1;
}

/* A pinned note always shows its pin, so pinned state never reads by position alone. */
.note-tile-pin[aria-pressed="true"] {
    opacity: 1;
}

@media (hover: none) {
    .note-tile-pin,
    .note-tile-acts {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .note-tile-pin,
    .note-tile-acts {
        transition: none;
    }
}

/* The pin and every action are native buttons. The board wires their behaviour. */
.note-tile-pin,
.note-tile-act,
.note-tile-open {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-1);
    background: transparent;
    border: 0;
    border-radius: var(--radius-control);
    color: var(--text-muted);
    cursor: pointer;
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
}

.note-tile-pin:hover,
.note-tile-act:hover,
.note-tile-open:hover {
    background: color-mix(in srgb, var(--text) 8%, transparent);
    color: var(--text);
}

.note-tile-pin:focus-visible,
.note-tile-act:focus-visible,
.note-tile-open:focus-visible,
.note-tile-swatch:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 1px;
}

.note-tile-pin[aria-pressed="true"] {
    color: var(--text);
}

.note-tile-open {
    gap: var(--space-1);
    font-size: 0.8125rem;
}

.note-tile-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    padding: var(--space-1);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
}

.note-tile-swatches[hidden] {
    display: none;
}

.note-tile-swatch {
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    background: var(--tile-surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    cursor: pointer;
}

.note-tile-swatch[aria-pressed="true"] {
    outline: 2px solid var(--text);
    outline-offset: 1px;
}

.note-tile-status {
    min-height: 1em;
    margin: 0;
    color: var(--text-muted);
    font-size: 0.75rem;
}

.note-tile-icon {
    display: inline-flex;
    line-height: 0;
}

.note-tile-icon svg {
    width: 1rem;
    height: 1rem;
}

/* @toc Notes workbench */
/*
 * NotesWorkbench presentation.
 * Styles the shared Feedback action, content, and page-anchor layer.
 * WorkspaceTool retains ownership of the panel's outer geometry.
 */
.notes-workbench {
    display: inline-flex;
    min-width: 0;
}

.notes-workbench--bar {
    position: absolute;
    inline-size: 0;
    block-size: 0;
    pointer-events: none;
}

.notes-workbench--bar .workspace-tool-dialog::backdrop {
    pointer-events: none;
}

.notes-workbench--bar :is(
    [data-workspace-tool-panel],
    .notes-workbench-bubble,
    .notes-workbench-pin,
    .notes-workbench-noscript
) {
    pointer-events: auto;
}

/* Feedback keeps its established compact sheet geometry while Panel owns the visible surface. The semantic
   dialog fills the viewport without intercepting input; the Panel stays above the shared bar and within the
   visual-viewport height published by WorkspaceTool. */
.workspace-tool--feedback.is-sheet [data-workspace-tool-panel] {
    position: fixed;
    inset-block: var(--workspace-tool-sheet-top, 40dvh) auto;
    inset-inline: var(--workspace-tool-visual-left, 0) auto;
    inline-size: var(--workspace-tool-visual-width, 100vw);
    block-size: var(--workspace-tool-sheet-height, 52dvh);
    max-block-size: 70dvh;
}

.notes-workbench--bar[data-frame-feedback-hidden] {
    visibility: hidden;
}

.notes-workbench > .workspace-tool {
    width: 0;
}

.workspace-tool--feedback .workspace-tool-trigger {
    display: none;
}

.notes-workbench:not(.notes-workbench--bar) .notes-workbench-trigger.button {
    position: relative;
    width: 44px;
    min-width: 44px;
    height: 44px;
    min-height: 44px;
    padding: 0;
}

.notes-workbench-trigger-icon {
    position: relative;
}

.notes-workbench-count.ui-badge {
    position: absolute;
    inset-block-start: -6px;
    inset-inline-end: -7px;
    min-width: 18px;
    justify-content: center;
    padding-inline: 4px;
}

.notes-workbench-count[data-empty="true"] {
    display: none;
}

.notes-workbench-content {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    width: 100%;
    min-height: 0;
}

.notes-workbench-add[aria-pressed="true"] {
    color: var(--on-primary);
    background: var(--primary);
}

.notes-workbench-composer,
.notes-workbench-noscript-form {
    flex: 0 0 auto;
    padding: var(--space-4);
}

.notes-workbench-composer[hidden] {
    display: none;
}

.notes-workbench-composer label,
.notes-workbench-noscript-form label {
    display: block;
    margin-block-end: 7px;
    font-size: var(--text-sm);
    font-weight: 600;
}

.notes-workbench-composer textarea,
.notes-workbench-noscript-form textarea {
    display: block;
    width: 100%;
    min-height: 96px;
    padding: 10px 12px;
    resize: vertical;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--text);
    background: var(--surface);
    line-height: 1.45;
}

.notes-workbench-visibility {
    margin-block-start: 12px;
}

.notes-workbench-visibility select {
    width: 100%;
    min-height: 44px;
}

:where(.notes-workbench, .workspace-tool--feedback) .note-row-pin {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 24px;
    height: 24px;
    color: var(--primary);
    font-size: 24px;
}

:where(.notes-workbench, .workspace-tool--feedback) .composer-actions,
:where(.notes-workbench, .workspace-tool--feedback) .note-row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

:where(.notes-workbench, .workspace-tool--feedback) .composer-actions {
    flex-wrap: nowrap;
    justify-content: flex-end;
    margin-block-start: var(--space-3);
}

.notes-workbench-list {
    flex: 1 1 auto;
    min-height: 0;
    padding-block-end: max(var(--space-4), env(safe-area-inset-bottom));
    overflow-y: auto;
    overscroll-behavior: contain;
    scroll-padding-block-end: max(var(--space-4), env(safe-area-inset-bottom));
}

.notes-workbench-note {
    display: grid;
    grid-template-columns: 30px minmax(0, 1fr);
    gap: 10px;
    padding: var(--space-4);
    border-block-end: 1px solid var(--line);
    background: var(--surface);
}

.notes-workbench-note.is-resolved {
    opacity: .62;
}

.notes-workbench-note.is-deleted {
    background: var(--surface-muted);
}

.notes-workbench-note.is-selected {
    background: color-mix(in srgb, var(--primary) 8%, var(--surface));
}

:where(.notes-workbench, .workspace-tool--feedback) .note-row-main {
    min-width: 0;
}

:where(.notes-workbench, .workspace-tool--feedback) .note-row-open {
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    color: inherit;
    background: transparent;
    text-align: start;
    cursor: pointer;
}

:where(.notes-workbench, .workspace-tool--feedback) .note-row-meta {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-block-end: 5px;
    color: var(--text-muted);
    font-size: 11px;
}

:where(.notes-workbench, .workspace-tool--feedback) .note-row-body {
    display: -webkit-box;
    margin: 0;
    overflow: hidden;
    overflow-wrap: anywhere;
    color: var(--text);
    font-size: var(--text-sm);
    line-height: 1.45;
    white-space: pre-wrap;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
}

:where(.notes-workbench, .workspace-tool--feedback) .notes-workbench-note.is-selected .note-row-body {
    display: block;
    overflow: visible;
    -webkit-line-clamp: unset;
}

:where(.notes-workbench, .workspace-tool--feedback) .note-row-actions {
    margin-block-start: 10px;
}

:where(.notes-workbench, .workspace-tool--feedback) .notes-workbench-note > .note-row-main > .note-row-actions {
    flex-wrap: nowrap;
    width: calc(100% + 40px);
    margin-inline-start: -40px;
}

:where(.notes-workbench, .workspace-tool--feedback) .notes-workbench-edit {
    margin-block-start: var(--space-2);
}

:where(.notes-workbench, .workspace-tool--feedback) .notes-workbench-edit-form {
    margin-block-start: var(--space-2);
    padding: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface-muted);
}

:where(.notes-workbench, .workspace-tool--feedback) .notes-workbench-edit-form[hidden] {
    display: none;
}

.notes-workbench-visibility-label {
    display: block;
    margin-block-end: 5px;
    color: var(--text-muted);
    font-size: 11px;
}

.notes-workbench-empty {
    margin: 0;
    padding: 36px 22px;
    color: var(--text-muted);
    text-align: center;
}

.notes-workbench-form-feedback {
    margin-block-start: var(--space-3);
}

.notes-workbench-form-feedback [data-notes-workbench-form-status] {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.notes-workbench-form-recovery {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: var(--space-2);
}

.notes-workbench-form-feedback [hidden] {
    display: none;
}

.notes-workbench-noscript {
    position: fixed;
    z-index: var(--notes-workbench-status-z-index, var(--z-overlay));
    inset-block-start: 76px;
    inset-inline-end: 12px;
    width: min(370px, calc(100vw - 24px));
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    color: var(--text);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
}

.notes-workbench-noscript > summary {
    min-height: 44px;
    padding: 12px 16px;
    font-weight: 600;
    cursor: pointer;
}

.notes-workbench-bubble {
    --notes-workbench-safe-top: env(safe-area-inset-top, 0px);
    --notes-workbench-safe-right: env(safe-area-inset-right, 0px);
    --notes-workbench-safe-bottom: env(safe-area-inset-bottom, 0px);
    --notes-workbench-safe-left: env(safe-area-inset-left, 0px);
    position: fixed;
    z-index: calc(var(--notes-workbench-pin-z-index, var(--z-popover)) + 1);
    inline-size: min(360px, var(--notes-workbench-bubble-max-width, calc(100vw - 24px)));
    max-block-size: var(--notes-workbench-bubble-max-height, calc(100dvh - 24px));
    border-radius: var(--radius-container);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.notes-workbench-bubble[hidden] {
    display: none;
}

:where(.notes-workbench, .workspace-tool--feedback) .notes-workbench-refresh[hidden],
.notes-workbench-bubble [data-notes-workbench-bubble-back][hidden] {
    display: none;
}

.notes-workbench-bubble-panel {
    display: flex;
    flex-direction: column;
    max-block-size: var(--notes-workbench-bubble-max-height, calc(100dvh - 24px));
    border: 1px solid var(--panel-border-color, var(--line));
    border-radius: var(--radius-container);
    overflow: hidden;
}

.notes-workbench-bubble-panel > .panel-head {
    flex: 0 0 auto;
    margin-inline: var(--space-3);
    padding-block-start: var(--space-2);
}

.notes-workbench-bubble-panel > .panel-body,
.notes-workbench-bubble-panel > .panel-body > .panel-body-inner {
    display: flex;
    min-block-size: 0;
}

.notes-workbench-bubble-panel > .panel-body {
    flex: 1 1 auto;
    overflow: hidden;
}

.notes-workbench-bubble-panel > .panel-body > .panel-body-inner {
    flex: 1 1 auto;
    flex-direction: column;
    overflow: hidden;
}

.notes-workbench-bubble-body {
    min-block-size: 0;
    overflow: auto;
    overscroll-behavior: contain;
}

.notes-workbench-bubble .notes-workbench-composer,
.notes-workbench-bubble .notes-workbench-note {
    border-block-end: 0;
}

.notes-workbench-bubble .notes-workbench-note {
    min-block-size: 0;
}

.notes-workbench-bubble .note-row-body {
    display: block;
    overflow: visible;
    -webkit-line-clamp: unset;
}

.is-placing-note #main-content {
    cursor: crosshair;
}

.notes-workbench-marker-layer {
    position: fixed;
    z-index: var(--notes-workbench-pin-z-index, var(--z-popover));
    inset: 0;
    overflow: hidden;
    pointer-events: none;
}

.notes-workbench-pin {
    position: absolute;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: var(--radius-full, 9999px);
    color: var(--primary);
    background: transparent;
    filter: drop-shadow(0 4px 5px color-mix(in srgb, var(--text) 25%, transparent));
    font-size: 30px;
    pointer-events: auto;
    transform: translate(-50%, -87.5%);
    cursor: pointer;
}

.notes-workbench-pin[hidden] {
    display: none;
}

.notes-workbench-pin.is-pending {
    pointer-events: none;
}

.notes-workbench-pin.is-selected,
.project-anchor-marker.is-selected {
    box-shadow: 0 0 0 3px var(--surface), 0 0 0 6px var(--primary);
}

.notes-workbench-pin:focus-visible {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}

.notes-workbench-placement-status {
    position: fixed;
    z-index: var(--notes-workbench-status-z-index, var(--z-overlay));
    inset-inline-start: 50%;
    inset-block-end: calc(var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)) + 16px);
    max-width: calc(100vw - 24px);
    margin: 0;
    padding: 8px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--text);
    background: var(--surface-raised);
    box-shadow: var(--shadow-lg);
    font-size: var(--text-xs);
    font-weight: 600;
    text-align: center;
    pointer-events: none;
    transform: translateX(-50%);
}

@media (max-width: 760px) {
    .notes-workbench-bubble .notes-workbench-composer,
    .notes-workbench-bubble .notes-workbench-note,
    .notes-workbench-noscript-form {
        max-block-size: calc(var(--notes-workbench-bubble-max-height, 70dvh) - 54px);
        padding-block-end: max(16px, env(safe-area-inset-bottom));
        overflow-y: auto;
        overscroll-behavior: contain;
    }
}

@media (prefers-reduced-motion: reduce) {
    .notes-workbench-pin,
    .notes-workbench-placement-status {
        scroll-behavior: auto;
        transition: none;
    }
}

/* @toc Page section */
/*
 * x-page-section — the shared section chassis (Section-Authoring Law: mobile-first + container-first).
 * .page-section is the full-bleed container-query CONTEXT; .page-section-inner is the centered, gutter-padded
 * content column, a flex column whose 32px rhythm spaces the header / items / body / footer blocks. The
 * @container (min-width) queries ENHANCE the vertical padding and the items grid on the section's OWN inline
 * size (never the viewport — no @media, no --bp-*). Type is fluid via clamp() + cqi. Every colour is a
 * semantic token so the section themes per tenant. --page-section-* vars come from the css facet.
 */

.page-section {
    container-type: inline-size;
}

.page-section-inner {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    max-width: var(--page-section-max-width);
    margin-inline: auto;
    padding-block: var(--space-16);
    padding-inline: var(--space-6);
}

/* Desktop is a pure ENHANCEMENT — the section reflows on its own box width (min-width polarity, standardized).
   Vertical padding 64 (base) → 96 (sm ≥640) → 128 (lg ≥1024). */
@container (min-width: 640px) {
    .page-section-inner {
        padding-block: var(--space-24);
    }
}

@container (min-width: 1024px) {
    .page-section-inner {
        padding-block: var(--space-32);
    }
}

/* The header — one grammar for every section: eyebrow · title · description · actions. Left-aligned (start)
   by default; a section that centres (e.g. a CTA band) overrides text-align + align-items in its own CSS. */
.page-section-head {
    display: flex;
    flex-direction: column;
    align-items: start;
    text-align: start;
}

.page-section-eyebrow {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.page-section-eyebrow + .page-section-title {
    margin-block-start: var(--space-3);
}

.page-section-title {
    margin: 0;
    font-size: clamp(1.875rem, 1.5rem + 2cqi, 3rem);
    font-weight: 700;
    letter-spacing: -0.025em;
    line-height: 1.1;
    text-wrap: balance;
    color: var(--text);
}

.page-section-description {
    max-width: var(--max-w-2xl);
    margin-block: var(--space-4) 0;
    font-size: clamp(1rem, 0.95rem + 0.4cqi, 1.125rem);
    line-height: 1.6;
    color: var(--text-muted);
}

.page-section-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-6);
    margin-block-start: var(--space-6);
}

/* The items grid — the standard repeating-content region (features, stats, pricing all compose it): 1 → 2 → 3
   columns on the section's own width. A section overrides the desktop column count via --page-section-items-cols
   (the author-facing knob), so it composes the chassis with zero chassis edits. */
.page-section-items {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

@container (min-width: 640px) {
    .page-section-items {
        grid-template-columns: repeat(2, 1fr);
    }
}

@container (min-width: 1024px) {
    .page-section-items {
        grid-template-columns: repeat(var(--page-section-items-cols, 3), 1fr);
    }
}

.page-section-footer {
    color: var(--text-muted);
}

/* @toc Pagination */
/* Pagination — count line + prev/page/next, laid out as a row. Reuses .button
 * (button-ghost + is-disabled) for the controls; only layout + muted text here. */

.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-top: 16px;
}

.pagination-info {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.pagination-controls {
    display: flex;
    align-items: center;
    gap: 10px;
}

.pagination-page {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* @toc Panel */
/* x-panel (1211) — the house panel grammar. One semantic frame surface with no prescribed radius, shadow, or
   outer border (consumers still own geometry). The HEADER BAR is a flex row [.ui-label title · right-aligned actions · disclosure control, each optional]
   over a thin --line rule; the body flows beneath and — after disclosure enhancement — folds behind the control
   (the grid-rows disclosure technique, mirroring x-accordion). Structural only: colour is semantic tokens, never a
   per-consumer knob. The one motion knob (--panel-duration) is set by the
   island (panel.js) from window.PANEL_CONFIG, with a 200ms fallback here. */
.panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--panel-background-color, var(--page-bg));
    color: var(--panel-color, var(--text));
}

/* The header bar: a single flex row. The title grows so an actions slot is pushed to the end; the toggle and the
   actions keep their natural size. Rendered whenever there is a title, actions, or a collapse toggle. */
.panel-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-block-end: var(--space-2);
    padding-block-end: var(--space-2);
    border-block-end: 1px solid var(--panel-border-color, var(--line));
    background: var(--panel-header-background-color, var(--panel-background-color, var(--page-bg)));
}

/* The title rides the shared .ui-label recipe (a word); this resets the heading margin and lets it grow so an
   actions slot right-aligns. */
.panel-title {
    flex: 1;
    min-width: 0;
    margin: 0;
    overflow-wrap: anywhere;
}

/* Panel icon actions compose Button's one named, borderless icon appearance. The panel only tightens that
   shared 44px action to the 28px desktop header rhythm; coarse-pointer targets return to Button's full size. */
.panel-toggle.button-icon,
.panel-head > .panel-actions > .button-icon {
    inline-size: var(--space-7);
    block-size: var(--space-7);
    min-inline-size: var(--space-7);
    min-block-size: var(--space-7);
    flex-basis: var(--space-7);
}

/* Header glyphs stay on Button's canonical 20px mark even when a Panel is temporarily contained by a host whose
   broad icon recipe targets every descendant SVG. This applies only to direct Panel header actions; toolbar icons
   in panel bodies keep their own registered dimensions. */
.panel > .panel-head > .panel-actions > .button-icon svg,
.panel > .panel-head > .panel-toggle.button-icon svg {
    inline-size: var(--button-icon-glyph-size);
    block-size: var(--button-icon-glyph-size);
}

.panel-toggle.button-icon {
    color: var(--text-muted);
}

/* aria-expanded is state, not a selected mode. Keep the open disclosure clear at rest; hover and active
   still come from Button, as does the visible focus ring. */
.panel-toggle.button-icon[aria-expanded="true"]:not(:hover):not(:active) {
    background: transparent;
}

.panel-marker {
    display: inline-flex;
}

.panel-marker svg {
    width: var(--button-icon-glyph-size);
    height: var(--button-icon-glyph-size);
}

/* THE UNIVERSAL PLUS/MINUS (#1953): expanded shows MINUS (what activating it will do — close), collapsed
   shows PLUS. Both glyphs are in the markup and this picks one off `aria-expanded`, so the mark is derived
   from the state rather than written alongside it and the two cannot drift apart.

   No transition, and no rotation: the chevron used to rotate between the two states, which only reads as
   motion because a chevron IS the same shape turned. Plus and minus are different marks, so animating
   between them would be a tween with nothing to say. */
.panel-marker-expand {
    display: none;
}

.panel-toggle[aria-expanded="false"] .panel-marker-expand {
    display: inline-flex;
}

.panel-toggle[aria-expanded="false"] .panel-marker-collapse {
    display: none;
}

/* The actions slot: a right-aligned row of surface-owned controls (ghost icon buttons, a count badge). x-panel
   provides the row + spacing only; each control carries its own label + behaviour (C9 — injected, never owned).
   `margin-inline-start: auto` right-aligns the slot UNCONDITIONALLY — a no-op when the flex:1 title is present, but
   it holds the contract for a titleless header (actions-only, or toggle + actions) where nothing else grows. */
.panel-actions {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--space-1);
    margin-inline-start: auto;
}

/* Dock-capable panel consumers share one compact action grammar. The consumer-specific classes and data hooks
   remain behaviour adapters only; Panel owns the common Options trigger, caret suppression, and action spacing
   so the same controls render identically in Planner, ordinary app pages, and exported full-page Projects. */
.panel-dock-options .dropdown-caret {
    display: none;
}

.dropdown-trigger.panel-dock-options {
    inline-size: var(--space-7);
    block-size: var(--space-7);
    min-inline-size: var(--space-7);
    min-block-size: var(--space-7);
    flex-basis: var(--space-7);
    justify-content: center;
    padding: 0;
    border-color: transparent;
    background: transparent;
    color: var(--text-muted);
}

.dropdown-trigger.panel-dock-options:hover {
    background: var(--button-icon-background-color-hover);
    color: var(--text);
}

.dropdown-trigger.panel-dock-options:active,
.dropdown-trigger.panel-dock-options[aria-expanded="true"] {
    background: var(--button-icon-background-color-active);
}

.panel-dock-options .dropdown-trigger-icon {
    width: var(--button-icon-glyph-size);
    height: var(--button-icon-glyph-size);
}

/* Capability-driven Panel menus remain usable in compact and short viewports. The menu stays anchored to its
   own trigger, wraps long commands, and scrolls inside the visible viewport rather than being clipped by a sheet. */
.panel > .panel-head > .panel-actions > .dropdown:has(> [data-panel-options]) > .dropdown-menu {
    max-inline-size: calc(100vw - (2 * var(--space-2)));
    max-block-size: min(
        60dvh,
        var(--panel-options-available-block, calc(100dvh - var(--app-visible-content-top, 0px) - var(--space-4)))
    );
    overflow: auto;
    overscroll-behavior: contain;
}

.panel > .panel-head > .panel-actions > .dropdown[data-panel-menu-placement="up"]:has(> [data-panel-options]) > .dropdown-menu {
    top: auto;
    bottom: calc(100% + 4px);
}

.panel > .panel-head > .panel-actions > .dropdown:has(> [data-panel-options]) .dropdown-item {
    min-inline-size: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.panel > .panel-head > .panel-actions > .dropdown:has(> [data-panel-options]) .dropdown-item[hidden] {
    display: none;
}

.panel[data-panel-dock-options] > .panel-head > [data-panel-toggle] {
    display: none;
}

.panel-dock-body-actions {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-block-end: var(--space-3);
}

/* A dock peer is a direct child of its host. The host owns placement and width; Panel owns the one bounded
   body chain that lets long content scroll without changing the shared heading or action grammar. */
.panel[data-panel-dock-peer] {
    flex: 0 0 auto;
    min-inline-size: 0;
    min-block-size: 0;
    block-size: auto;
}

.panel[data-panel-dock-peer]:not([data-panel-collapsible]),
.panel[data-panel-dock-peer]:has(> .panel-body.is-open) {
    flex: 1 1 auto;
    block-size: 100%;
}

.panel[data-panel-dock-peer] > .panel-body:not([hidden]) {
    flex: 1 1 auto;
    min-block-size: 0;
    block-size: 0;
    overflow: hidden;
}

.panel[data-panel-dock-peer] > .panel-body:not([hidden]) > .panel-body-inner {
    min-block-size: 0;
    block-size: 100%;
}

/* Workbench and Navigation use this same transient panel frame. The owning adapters decide when it exists;
   this rule owns only the shared geometry and presentation between the top and bottom application bars. */
.panel[data-panel-sheet-open],
.panel.is-sheet-open {
    display: flex;
    position: fixed;
    inset-inline: 0;
    bottom: var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px));
    z-index: 25;
    max-block-size: max(
        0px,
        calc(100dvh - var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)) - var(--app-visible-content-top, var(--app-visual-viewport-top, 0px)))
    );
    overflow: hidden;
    block-size: auto;
    padding: var(--space-3) var(--space-4);
    background: var(--panel-background-color, var(--page-bg));
    border-block-start: 1px solid var(--panel-border-color, var(--line));
    box-shadow: 0 -12px 40px color-mix(in srgb, var(--text) 22%, transparent);
}

.panel[data-panel-sheet-open] > .panel-body:not([hidden]),
.panel.is-sheet-open > .panel-body:not([hidden]) {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-block-size: 0;
    block-size: auto;
    overflow: hidden;
}

.panel[data-panel-sheet-open] > .panel-body:not([hidden]) > .panel-body-inner,
.panel.is-sheet-open > .panel-body:not([hidden]) > .panel-body-inner {
    flex: 1 1 auto;
    min-block-size: 0;
    block-size: auto;
    max-block-size: none;
    overflow-y: auto;
}

/* A composed surface can explicitly own its content insets and scrolling. The sheet keeps its standard padding,
   so a header retains the house inset; only the direct body breaks through the inline and lower content edges.
   A headerless body also reaches the upper edge. Direct-child selectors keep nested Panels independent. */
.panel[data-panel-content-layout="consumer"] > .panel-body:not([hidden]) > .panel-body-inner {
    overflow: hidden;
}

.panel[data-panel-content-layout="consumer"]:is([data-panel-sheet-open], .is-sheet-open)
    > .panel-body:not([hidden]) {
    inline-size: calc(100% + (2 * var(--space-4)));
    margin-inline: calc(-1 * var(--space-4));
    margin-block-end: calc(-1 * var(--space-3));
}

.panel[data-panel-content-layout="consumer"]:is([data-panel-sheet-open], .is-sheet-open):not(:has(> .panel-head))
    > .panel-body:not([hidden]) {
    margin-block-start: calc(-1 * var(--space-3));
}

.panel-dock-close {
    display: none;
}

.panel[data-panel-sheet-open] > .panel-head > [data-panel-toggle],
.panel.is-sheet-open > .panel-head > [data-panel-toggle] {
    display: none;
}

.panel[data-panel-direct-close-visible] > .panel-head > .panel-actions > .panel-dock-close:not([hidden]) {
    display: inline-flex;
    flex: none;
}

@media (pointer: coarse) {
    .panel-toggle.button-icon,
    .panel-head > .panel-actions > .button-icon,
    .dropdown-trigger.panel-dock-options {
        inline-size: var(--button-icon-control-size);
        block-size: var(--button-icon-control-size);
        min-inline-size: var(--button-icon-control-size);
        min-block-size: var(--button-icon-control-size);
        flex-basis: var(--button-icon-control-size);
    }
}

.panel-body {
    min-height: 0;
}

/* Panel's direct body wrapper owns hidden descendants as well as its declared scrolling. A screen-reader-only
   control or caption therefore stays inside this Panel instead of extending the outer document. */
.panel-body-inner {
    position: relative;
}

/* Panel-owned PRESENCE is separate from disclosure. Hosts update semantic/requested state immediately, then use
   this cancellable opacity bridge while they retain outgoing geometry. A leaving panel is inert in JS and cannot
   receive pointer input; final hiding or remounting remains the host adapter's responsibility. */
.panel[data-panel-presence] {
    transition: opacity var(--panel-duration, 200ms) ease;
}

.panel[data-panel-presence="preparing"],
.panel[data-panel-presence="leaving"],
.panel[data-panel-presence="absent"] {
    opacity: 0;
}

.panel[data-panel-presence="entering"],
.panel[data-panel-presence="present"] {
    opacity: 1;
}

.panel[data-panel-presence][data-panel-presence-starting] {
    opacity: 0;
    transition: none;
}

.panel[data-panel-presence="leaving"],
.panel[data-panel-presence="absent"] {
    pointer-events: none;
}

/* The parser-time seed suppresses an authored or persisted closed body before the footer island can run. With
   JavaScript disabled the seed is never set, so the server body remains readable. Successful enhancement removes
   it, and the inline load fallback also removes it when the island fails. */
.panel[data-panel-boot-collapsed] > .panel-body {
    display: none;
}

/* Enhanced collapsible fold: the direct body animates open/close via a grid-template-rows 0fr → 1fr tween;
   panel.js adds/removes .is-open and toggles the `hidden` attribute at the animation boundary (a collapsed body is
   `hidden` = out of layout AND the AT tree). Before enhancement, and for a fixed panel, the body stays an ordinary
   readable block. Direct-child selectors keep nested Panel and Tabs bodies independent. */
.panel[data-panel-collapsible][data-panel-enhanced] > .panel-body {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--panel-duration, 200ms) ease;
}

.panel[data-panel-collapsible][data-panel-enhanced] > .panel-body.is-open {
    grid-template-rows: 1fr;
}

/* Required: the author display:grid above otherwise overrides the UA [hidden]{display:none}, leaving a clipped
   sliver visible. panel.js un-hides BEFORE animating open and re-hides AFTER the collapse tween, so the body is
   never `hidden` mid-animation. */
.panel[data-panel-collapsible][data-panel-enhanced] > .panel-body[hidden] {
    display: none;
}

/* The inner wrapper clips content during the 0fr phase (the grid-rows technique needs an overflow:hidden child). */
.panel[data-panel-collapsible][data-panel-enhanced] > .panel-body > .panel-body-inner {
    overflow: hidden;
}

/* Reduced motion makes the fold instant. The global animations.css blanket also zeroes transitions; this local
   block states the component contract. */
@media (prefers-reduced-motion: reduce) {
    .panel[data-panel-presence],
    .panel[data-panel-collapsible][data-panel-enhanced] > .panel-body,
    .panel-marker {
        transition: none;
    }
}

/* @toc Planner board */
/* Planner board — a Trello-style kanban. Lists scroll horizontally (scroll-snap one
 * per screen on mobile); cards are dragged with a single unified pointer drag (mouse +
 * touch) that leaves a placeholder gap; the card back is a full detail dialog. Colours
 * ride the semantic tokens; layout knobs come through --planner-board-* with fallbacks.
 * View: planner-board.blade.php · JS: planner-board.js */

.planner-board {
    --planner-lane-min: 16rem;
    --planner-lane-max: 18rem;
    display: flex;
    container: planner-board / inline-size;
    gap: var(--space-4);
    /* STRETCH, not flex-start: a lane shrink-wrapped to its cards leaves the board short of the floor
       however the height above is computed, so a short lane would still stop at its content. */
    align-items: stretch;
    /* The board IS the canvas's height rather than guessing at it — `flex: 1` claims the room the
       stage already hands down, and `min-height: 0` is what lets a flex child actually scroll inside
       it instead of growing the page. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-x: auto;
    padding-block-end: var(--space-3);
    scroll-padding-inline: var(--space-4);
    /* The board scrolls horizontally, lists scroll vertically. A drag captures its
     * pointer (setPointerCapture in the JS) the moment it starts, so neither scroller
     * can steal the gesture mid-drag. The scrollbar itself is the global themed one (base.css). */
}

/* While a drag is live, kill text selection + native scroll gestures board-wide (the
 * descendants too, so a touch can't start a scroll mid-drag). */
.planner-board.is-dragging,
.planner-board.is-dragging * {
    touch-action: none;
    user-select: none;
}

.planner-board.is-dragging {
    cursor: grabbing;
    /* Permit edge auto-scroll while the drag is active. Scroll snap would change its target. */
    scroll-snap-type: none;
    /* Scripted edge movement must finish in the frame that measured it. Native anchoring or smooth
       scrolling would leave the next frame's hit targets behind the visible board. */
    scroll-behavior: auto;
    overflow-anchor: none;
}

.planner-board.is-dragging .planner-list-scroll {
    scroll-behavior: auto;
    overflow-anchor: none;
}

/* --- a list (lane) — an elevated panel that sits above the page --- */
.planner-list {
    flex: 0 0 clamp(var(--planner-lane-min), 24cqi, var(--planner-lane-max));
    display: flex;
    flex-direction: column;
    min-width: 0;
    /*
     * ★ NO HEIGHT CONSTANT HERE. This was `max-height: calc(100dvh - 184px)` — 184px being a hard-coded
     * guess at the app-shell topbar plus .planner-workbench's padding plus the board's padding-block-end.
     * Whenever the real chrome was shorter than the guess every lane stopped short of the floor by the
     * difference, and because the board is `overflow-x: auto` its horizontal scrollbar sat under the
     * tallest CONTENT rather than on the bottom edge of the window. That floating scrollbar was the
     * reported symptom.
     *
     * The height was always being handed down correctly (.planner-canvas is `height: 100%`); the constant
     * threw it away and recomputed it. `min-height: 0` is the whole replacement — it lets the card list
     * below become the scroller instead of the lane growing to fit its cards.
     */
    min-height: 0;
}

/* The panel FILLS the lane rather than inheriting a cap. This was `max-height: inherit`, which inherited
   the constant removed above — left as-is it would have inherited nothing and quietly done nothing. */
.planner-list > .planner-list-panel {
    height: 100%;
    min-height: 0;
}

.planner-list > .planner-list-panel > .panel-head {
    margin: 0;
    padding-inline: var(--space-3);
}

.planner-list > .planner-list-panel > .panel-body {
    flex: 1;
    min-height: 0;
}

.planner-list > .planner-list-panel > .panel-body > .panel-body-inner {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.planner-list-count {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    min-width: 1.5rem;
    text-align: center;
}

.planner-list-scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    min-height: var(--space-3);
}

.planner-list-cards {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 0;
    padding: 0;
}

/* --- a card — elevated above the list, with a tactile hover lift + press --- */
.planner-card,
.planner-list-continuation .planner-list-continuation-control {
    position: relative;
    display: flex;
    width: 100%;
    min-width: 0;
    padding: var(--space-2) var(--space-3);
    background: transparent;
    border: 0;
    border-block-end: 1px solid var(--line);
    border-radius: 0;
    box-shadow: none;
    transition: background-color 130ms ease, border-color 130ms ease;
}

.planner-card {
    flex-direction: column;
    gap: var(--space-1);
    cursor: grab;
    /* iOS: the long-press that starts a drag must NOT trigger text selection or the
     * copy/define callout magnifier; also kill the grey tap-flash. */
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
}

.planner-card:hover,
.planner-list-continuation .planner-list-continuation-control:hover {
    background: color-mix(in srgb, var(--primary) 5%, transparent);
    border-block-end-color: color-mix(in srgb, var(--primary) 42%, var(--line));
}

/* Press it and it pushes in — comes after :hover so the push wins while held. */
.planner-card:active,
.planner-list-continuation .planner-list-continuation-control:active {
    background: color-mix(in srgb, var(--primary) 8%, transparent);
}

.planner-card:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
}

/* Settle animation when a card lands from a drop. */
.planner-card.just-dropped {
    background: color-mix(in srgb, var(--primary) 7%, transparent);
}

/* Live board: a card whose lane just changed (or that just appeared) gets a fading primary ring,
   so a move the poller swapped in is visible. Decoration only (ramp rung + alpha via color-mix) —
   the primary ramp flips per tenant, so this themes for free. */
@keyframes planner-card-live-move {
    0% {
        box-shadow: 0 0 0 2px var(--primary-500), 0 0 14px 2px color-mix(in srgb, var(--primary-500) 45%, transparent);
    }
    100% {
        box-shadow: 0 0 0 2px transparent, 0 0 0 0 transparent;
    }
}

.planner-card.is-live-moved {
    animation: planner-card-live-move 1.6s ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .planner-card.is-live-moved {
        animation: none;
        box-shadow: 0 0 0 2px var(--primary-500);
    }

    /* JS cancels active FLIP animations when this preference changes. This rule also keeps the post-drop
       highlight instantaneous. */
    .planner-card.is-flip-moving,
    .planner-card.just-dropped {
        transition: none !important;
    }
}

/* (The old Trello colour-bar "label dot" retired with Tag model v2 — the project is now a full chip.) */

.planner-card-title,
.planner-list-continuation-label {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 500;
    line-height: 1.35;
    color: var(--text);
    word-break: break-word;
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.planner-list-continuation {
    flex: 0 0 auto;
}

.planner-list-continuation .planner-list-continuation-control[hidden] {
    display: none;
}

.planner-list-continuation .planner-list-continuation-control {
    justify-content: flex-start;
    gap: var(--space-2);
    min-block-size: 2.75rem;
    color: var(--text);
    line-height: 1.35;
    text-decoration: none;
}

.planner-list-continuation .planner-list-continuation-control:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
    box-shadow: none;
}

.planner-list-continuation-icon,
.planner-list-continuation-icon svg {
    display: block;
    inline-size: 1rem;
    block-size: 1rem;
    flex: none;
}

/* Card foot — the bottom meta row: checklist progress + the "has a description" glyph. */
.planner-card-identity {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
}

.planner-card-number {
    flex: none;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
}

.planner-card-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-1) var(--space-2);
}

.planner-card-footer-start {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
}

.planner-card-footer-end {
    display: inline-flex;
    min-width: 0;
    margin-inline-start: auto;
}

.planner-card-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-block-start: var(--space-1);
}

/* Checklist progress (Card anatomy v2) — a scannable done/total badge on the card face. */
.planner-card-progress {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.planner-card-progress svg {
    width: 13px;
    height: 13px;
}

/* Checklist badge — three honest states by colour (Card redesign, item 3): complete = green, partial =
   orange, none-done = red. Purely the checklist's own state (independent of lane). Each pairs its status
   token with its AA-vetted -surface tint (the categorical status set — compliant by construction, NO new
   colours); the done/total number carries the state too, so the tint reinforces rather than being the sole
   signal. */
.planner-card-progress.is-complete {
    color: var(--success);
}

.planner-card-progress.is-partial {
    color: var(--warning);
}

.planner-card-progress.is-none {
    color: var(--danger);
}

/* --- Card redesign: four-corner metadata (reporter · assignee top; checklist · commit bottom). Each row
   is a 2-slot grid so the right corner stays pinned right even when the left is empty (an empty corner is
   the honest "not yet" signal). --- */
.planner-card-corners {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 8px;
}

/* The bottom row carries THREE slots — checklist (left) · file-count (middle) · commit (right, Done only) —
   so it's a 3-column grid; the top row stays the 2-column reporter/assignee split. */
.planner-card-corners-foot {
    grid-template-columns: 1fr auto 1fr;
    margin-top: 2px;
}

.planner-corner-start {
    justify-self: start;
    min-width: 0;
}

.planner-corner-mid {
    justify-self: center;
}

.planner-corner-end {
    justify-self: end;
    text-align: end;
}

/* File-count badge (bottom-middle) — a file icon + the ticket's files[] count; a glance-able size signal. */
.planner-card-filecount {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.planner-card-filecount svg {
    width: 13px;
    height: 13px;
    flex-shrink: 0;
}

/* Shared actor badge (reporter + assignee) — a SECONDARY pill: a quiet neutral-tinted chip with the actor
   icon + name (planner._actor-badge). "De-throned" means secondary, NOT stripped to plain text — it keeps the
   badge affordance but drops the accent-hero treatment the old holder badge had. POSITION (top-left vs
   top-right) carries the role, so no overline label. Names truncate. Mirrors the .planner-chip tint (AA-safe). */
.planner-actor {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    padding: 0;
    border: 0;
    border-radius: 0;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--text-muted);
    background: transparent;
}

.planner-actor > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* One consistent icon size across every actor icon (person / sparkle / lock) — fixes the oversized lock. */
.planner-actor svg {
    width: 13px;
    height: 13px;
    flex-shrink: 0;
}

/* The assignee POPS in the tenant PRIMARY (James's ruling, ticket 1046) — the primary of the two people,
   and primary (not a status hue) themes per tenant + avoids status-semantics collision. Mirrors the vetted
   .planner-chip mix, primary-substituted: AA text via the blend toward --text, a light primary surface tint,
   a primary border. The live-checkout LOCK icon inherits this colour (currentColor). Reporter stays quiet. */
.planner-actor-assignee {
    color: color-mix(in srgb, var(--primary) 46%, var(--text));
    background: transparent;
    border-color: transparent;
}

/* Commit corner (Done only) — the short hash over its git date, stacked and right-aligned. */
.planner-card-commit {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.25;
}

.planner-card-commit code {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 11px;
    color: var(--success);
}

.planner-card-commit-date {
    font-size: 10px;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

/* Bottom block (Done) — one coherent row: checklist · commit message · commit id. Non-Done cards show the
   lone progress badge; both get a little top breathing room from the chips. */
.planner-card > .planner-card-progress,
.planner-card-done {
    margin-top: 7px;
}

.planner-card-done {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.planner-card-commit-msg {
    flex: 1 1 100%;
    min-width: 0;
    overflow: hidden;
    font-size: 11px;
    line-height: 1.4;
    color: var(--text-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* "Has a description" hint glyph. */
.planner-card-desc-flag {
    display: inline-flex;
    color: var(--text-muted);
}

.planner-card-desc-flag svg {
    width: 14px;
    height: 14px;
}

/* The card being dragged: hidden in place, a clone (ghost) follows the pointer. */
.planner-card.is-source {
    display: none;
}

/* A CARD THE LABEL FILTER EXCLUDES (#1826) — and this rule is REQUIRED, not decoration.
 *
 * `.planner-card` sets `display: flex` above, and an AUTHOR display beats the UA stylesheet's
 * `[hidden] { display: none }`. So the whole filter — server-rendered hidden state and every client toggle
 * alike — is a silent no-op without this one line: the attribute lands, the DOM says hidden, the card stays
 * on screen, and nothing anywhere reports it. `.planner-card-readonly` re-declares `display: flex` a second
 * time, so the qualified selector's (0,2,0) is what puts this ahead of both.
 *
 * The attribute rather than a class, deliberately: `hidden` is what an assistive technology reads, so one
 * write takes the card out of the visual board AND out of the AT tree. A class would have needed a second
 * write to stay honest, which is a second thing to leave stale. */
.planner-card[hidden] {
    display: none;
}

/* The gap the ghost will drop into — a recessed slot, not a dashed outline. */
.planner-card-placeholder {
    border-radius: var(--radius-control);
    background: color-mix(in srgb, var(--primary) 9%, var(--surface-muted));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 28%, var(--line));
    flex: 0 0 auto;
}

/* The element that follows the pointer. JS always supplies essential translation and adds velocity tilt
 * plus lift scale only when motion is allowed — don't compete with either transform here. */
.planner-card-ghost {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1000;
    width: var(--ghost-w, 260px);
    margin: 0;
    pointer-events: none;
    background: var(--surface-raised);
    border: 1px solid color-mix(in srgb, var(--primary) 45%, var(--line));
    border-radius: var(--radius-control);
    box-shadow: var(--shadow-lg);
}

/* --- composer (add a card) --- */
.planner-list-composer {
    padding: 0;
}

.planner-list-composer:has(> .planner-composer:not([hidden])) {
    padding: var(--space-2) var(--space-3) var(--space-3);
}

/* The lane's new-ticket action composes Button's icon appearance and Panel supplies its 28px desktop /
   44px coarse-pointer geometry. This file owns only the icon wrapper needed by Planner's live replacement. */
.planner-list-add-icon {
    display: inline-flex;
}

.planner-list-add-icon svg {
    width: var(--button-icon-glyph-size);
    height: var(--button-icon-glyph-size);
}

.planner-composer {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.planner-composer[hidden] {
    display: none;
}

.planner-composer-input {
    width: 100%;
    resize: none;
    font: inherit;
    font-size: var(--text-sm);
    line-height: 1.35;
    color: var(--text);
    background: var(--surface-raised);
    border: 1px solid var(--primary-500);
    border-radius: 10px;
    padding: 9px 11px;
    box-shadow: var(--shadow-sm);
}

.planner-composer-input::placeholder {
    color: var(--text-muted);
}

.planner-composer-input:focus-visible {
    outline: none;
}

.planner-composer-label {
    width: 100%;
    min-height: 38px;
    color: var(--text);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding-inline: 10px;
}

.planner-composer-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.planner-composer-add {
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--on-primary);
    background: var(--primary-600);
    border: none;
    border-radius: 8px;
    padding: 7px 14px;
    cursor: pointer;
}

.planner-composer-add:hover {
    background: var(--primary-700);
}

.planner-composer-cancel {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    color: var(--text-muted);
    background: transparent;
    border: none;
    border-radius: 8px;
    cursor: pointer;
}

.planner-composer-cancel:hover {
    color: var(--text);
    background: color-mix(in srgb, var(--text) 8%, transparent);
}

.planner-composer-cancel svg {
    width: 20px;
    height: 20px;
}

/* --- card back (the detail dialog) --- */
/* Card-open v2 — the card back widens on desktop to hold two columns. It's a composed x-dialog, so we
 * set --dialog-width and let `.dialog { width: min(var(--dialog-width), calc(100vw - 32px)) }` read it.
 * NO #id `width:` override here — id specificity would beat the var() and pin the old single-column
 * width (the bug an earlier pass shipped). Mobile full-page sizing is set in the max-width:768px block
 * below. (The card-detail MODAL sizes itself by #planner-card-modal in the modal block near the foot.) */
#planner-card-back {
    --dialog-width: 840px;
}

/* Two views — the content (title + description + checklist) and the structural rail (label ·
   move · actions). On desktop they sit side-by-side; on mobile they are <x-tabs> panels (Details |
   Info & actions), so a tall card back is two short tabs, not one long scroll. Tabs are the default
   (mobile) presentation; the min-width rule below UNWRAPS them into the two-column grid — hides the
   tablist, lays both panels out side-by-side. */
.planner-back {
    min-width: 0;
}

.planner-back-content,
.planner-back-rail {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

/* Desktop unwrap. The seam is 768/769 — the app's ONE breakpoint (sidebar-nav.js's drawer, the board's
   mobile rules, all use 768). Above it the card back is two columns (tablist hidden, panels gridded
   side-by-side); at/below it the panels are real <x-tabs> (the max-width:768px block below). The same
   769/768 pair governs the card-back editor's full-page takeover. */
@media (min-width: 769px) {
    .planner-back .tabs-nav {
        display: none;
    }

    .planner-back .tabs-body {
        display: grid;
        grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
        gap: 22px;
        align-items: start;
        padding-top: 0;
    }

    .planner-back .tabs-panel {
        display: block;
    }

    .planner-back-rail {
        padding-left: 20px;
        border-left: 1px solid var(--line);
    }
}

.planner-back-title {
    width: 100%;
    resize: none;
    overflow: hidden;
    font: inherit;
    font-size: var(--text-lg);
    font-weight: 700;
    line-height: 1.3;
    color: var(--text);
    background: transparent;
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 6px 8px;
    margin: 0;
}

.planner-back-title:hover {
    background: color-mix(in srgb, var(--text) 5%, transparent);
}

.planner-back-title:focus-visible {
    outline: none;
    border-color: var(--primary-500);
    background: var(--surface-raised);
}

.planner-back-section-label {
    margin: 0;
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted);
}

.planner-back-labels {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: -6px;
}

/* Card-back label picker buttons — the PILL pattern: at rest, adaptive category-tinted text on
   a faint category bg (color-mix toward --text, so it self-flips and clears AA in BOTH themes); SELECTED
   (.is-active) fills solid with the family's --planner-active-fill + white text + a white ring. The old
   white-text-at-opacity-0.72 shape failed AA — dimmed white floors at ~2.5:1 over a light surface — so
   adaptive text is the color-law-correct fix. It used to cite .planner-pill as the passing reference; that
   rule was deleted with the filter pills (#1758), and the same mix now lives on `.planner-label` in
   css/pages/planner.css — go there for the values rather than re-deriving them here. */
.planner-back-chip {
    font: inherit;
    font-size: var(--text-xs);
    font-weight: 600;
    color: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 45%, var(--text));
    background: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 12%, var(--surface-raised));
    border: 2px solid color-mix(in srgb, var(--planner-cat, var(--text-muted)) 30%, var(--line));
    border-radius: 8px;
    padding: 7px 14px;
    cursor: pointer;
    transition: color 100ms ease, background 100ms ease, border-color 100ms ease;
}

.planner-back-chip:hover {
    color: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 62%, var(--text));
    border-color: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 52%, var(--line));
}

.planner-back-chip.is-active {
    color: var(--on-primary);
    background: var(--planner-active-fill, var(--text-muted));
    border-color: color-mix(in srgb, var(--on-primary) 65%, transparent);
}

.planner-back-desc {
    width: 100%;
    resize: vertical;
    min-height: 96px;
    font: inherit;
    font-size: var(--text-sm);
    line-height: 1.45;
    color: var(--text);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 10px 12px;
    margin-top: -6px;
}

.planner-back-desc:focus-visible {
    outline: none;
    border-color: var(--primary-500);
}

/* Card-back checklist (Card anatomy v2) — clickable rows derived from the `- [ ]` lines above. */
.planner-back-checklist {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin-top: -6px;
}

.planner-back-check {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 5px 7px;
    border-radius: 7px;
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--text);
    cursor: pointer;
}

.planner-back-check:hover {
    background: color-mix(in srgb, var(--text) 5%, transparent);
}

.planner-back-check input {
    margin: 2px 0 0;
    accent-color: var(--primary-500);
    cursor: pointer;
    flex-shrink: 0;
}

.planner-back-check.is-done span {
    color: var(--text-muted);
    text-decoration: line-through;
}

.planner-back-move {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: -6px;
}

.planner-back-move-btn {
    font: inherit;
    font-size: var(--text-sm);
    color: var(--text);
    background: color-mix(in srgb, var(--text) 7%, transparent);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 6px 14px;
    cursor: pointer;
}

.planner-back-move-btn:hover {
    background: color-mix(in srgb, var(--text) 12%, transparent);
}

/* The "you are here" lane button. Its primary-tinted text must clear 4.5:1 on the 12%-primary wash over
   BOTH surfaces, so the rung owns the light/dark flip (color-law rule 2): primary-700 on the light wash,
   primary-400 on the dark one — raw primary-500 sat at ~3.2 (light) / ~4.2 (dark). */
.planner-back-move-btn.is-current {
    color: var(--primary-700);
    border-color: var(--primary-500);
    background: color-mix(in srgb, var(--primary-500) 12%, transparent);
    cursor: default;
}

:root[data-theme="dark"] .planner-back-move-btn.is-current {
    color: var(--primary-400);
}

.planner-back-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 4px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

.planner-back-delete {
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--danger);
    background: transparent;
    border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
    border-radius: 8px;
    padding: 7px 14px;
    cursor: pointer;
}

.planner-back-delete:hover {
    background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.planner-back-save {
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--on-primary);
    /* The on-primary token over primary-600 clears 4.5:1 and flips per tenant. */
    background: var(--primary-600);
    border: none;
    border-radius: 8px;
    padding: 8px 20px;
    cursor: pointer;
}

.planner-back-save:hover {
    background: var(--primary-700);
}

/* The workbench rail owns the lower clearance below its workspace breakpoint. The board keeps only its real
   horizontal scrollbar in this mode. */
@media (max-width: 999.98px) {
    .planner-board {
        padding-block-end: 0;
    }
}

/* --- mobile: one list per screen, swipe between; full-screen card back --- */
@media (max-width: 768px) {
    .planner-board {
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        margin-inline: calc(var(--space-4) * -1);
    }

    .planner-list {
        scroll-snap-align: start;
        /* The phone's own copy of the same guess (168px) — removed for the same reason. The lane inherits the
           stretched height here too, which keeps the workbench rail at the bottom clear. */
        min-height: 0;
        /* ONE LANE PER SCREEN (#1351). The snap above was only ever half the mechanism: it lands you ON a
           lane, but the lane kept its desktop basis (clamp(16rem, 24cqi, 18rem) ≈ 256px), so a 375px phone
           showed one lane plus most of the next and still read as a two-axis scroll. The heading above this
           block has claimed "one list per screen" since it was written; this is the line that makes it true.

           86%, not 100%, ON PURPOSE — the sliver of the next lane is the same honesty rule the filter rail
           follows: you can SEE there is another lane, rather than discovering it by accident. A lane that
           fills the screen edge-to-edge looks like the only lane. */
        flex: 0 0 86%;
    }

    .planner-list:first-child {
        margin-inline-start: var(--space-4);
    }

    .planner-list:last-child {
        margin-inline-end: var(--space-4);
    }
    /* The card-back editor's mobile full-page takeover lives in the unified max-width:768px block below
       (#planner-card-back) — one seam, no 621–768 hybrid. */
}

/* A read-only database ticket: a link to the five-pane view, laid out as a stack without the raw anchor
   underline/colour. The base .planner-card gives the surface + hover. */
.planner-card-readonly {
    display: flex;
    flex-direction: column;
    gap: 6px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
}

@media (pointer: coarse) {
    .planner-list-add {
        width: 2.75rem;
        height: 2.75rem;
    }
}

.planner-card-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.planner-chip {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.5;
    padding: 0 7px 0 6px;
    border-radius: var(--radius-full, 999px);
    color: var(--text-muted);
    background: color-mix(in srgb, var(--text-muted) 15%, transparent);
}

/* Tag scent icon (Card anatomy v2) — inherits the chip's themed text colour via currentColor. */
.planner-chip svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
}

.planner-chip-decision {
    color: var(--primary-600, var(--primary-500));
    background: color-mix(in srgb, var(--primary-500) 16%, transparent);
}

.planner-chip-committed {
    font-family: var(--font-mono, monospace);
    color: var(--success);
    background: var(--success-surface);
}

/* Builder BADGE (Card anatomy v3) — top-left, ABOVE the title: an accent-tinted pill with a Heroicon lock
   (the HOLDER, in-progress/review) or user (BUILT-BY, Done) + the builder's name. A block-level flex on its
   own line (width: fit-content) so it sits above the title; the card-open banner (.cardo-holder) mirrors it. */
.planner-card-badge {
    display: flex;
    align-items: center;
    gap: 4px;
    width: fit-content;
    max-width: 100%;
    margin-bottom: 5px;
    padding: 1px 8px 1px 6px;
    border-radius: var(--radius-full, 999px);
    font-size: 11px;
    font-weight: 600;
    color: color-mix(in srgb, var(--primary-500) 52%, var(--text));
    background: color-mix(in srgb, var(--primary-500) 15%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary-500) 28%, transparent);
}

.planner-card-badge svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
}

.planner-card-badge > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* CATEGORICAL colours. Every card's label chip plus
   the filter pills colour-code from the VETTED --planner-fam-* categorical tokens (R13; emitted by
   PaletteService::plannerFamVars, engine-contrast-vetted), deliberately NOT the tenant ramp: these are
   identities that must stay stable across tenants. A .planner-fam-<family> class sets --planner-cat; the
   chip/pill rules color-mix it into a tinted background + border with contrast-safe text that flips per
   theme by blending toward --text. No two chips/pills in one render share a family (PlanBoard::familyMap). */
.planner-chip-label {
    --accent-label-color: var(--planner-cat, var(--text-muted));
    color: var(--accent-label-foreground);
    background: var(--accent-label-background);
    border: 1px solid var(--accent-label-border);
}

/* Family → the vetted --planner-fam-* tokens (R13). Each family sets --planner-cat (the identity tint
   base for inactive pills/chips = --planner-fam-<fam>) AND --planner-active-fill (the SOLID rung a pill
   fills with when SELECTED under white text = --planner-fam-<fam>-active, engine-picked ≥4.5:1 on white).
   PaletteService::plannerFamVars emits both per family, so any family config/planner.php picks resolves —
   no raw --palette-* in this file anymore (R13's whole point: one vetted, governed home). */
.planner-fam-slate   { --planner-cat: var(--planner-fam-slate);   --planner-active-fill: var(--planner-fam-slate-active); }
.planner-fam-gray    { --planner-cat: var(--planner-fam-gray);    --planner-active-fill: var(--planner-fam-gray-active); }
.planner-fam-zinc    { --planner-cat: var(--planner-fam-zinc);    --planner-active-fill: var(--planner-fam-zinc-active); }
.planner-fam-neutral { --planner-cat: var(--planner-fam-neutral); --planner-active-fill: var(--planner-fam-neutral-active); }
.planner-fam-stone   { --planner-cat: var(--planner-fam-stone);   --planner-active-fill: var(--planner-fam-stone-active); }
.planner-fam-red     { --planner-cat: var(--planner-fam-red);     --planner-active-fill: var(--planner-fam-red-active); }
.planner-fam-orange  { --planner-cat: var(--planner-fam-orange);  --planner-active-fill: var(--planner-fam-orange-active); }
.planner-fam-amber   { --planner-cat: var(--planner-fam-amber);   --planner-active-fill: var(--planner-fam-amber-active); }
.planner-fam-yellow  { --planner-cat: var(--planner-fam-yellow);  --planner-active-fill: var(--planner-fam-yellow-active); }
.planner-fam-lime    { --planner-cat: var(--planner-fam-lime);    --planner-active-fill: var(--planner-fam-lime-active); }
.planner-fam-green   { --planner-cat: var(--planner-fam-green);   --planner-active-fill: var(--planner-fam-green-active); }
.planner-fam-emerald { --planner-cat: var(--planner-fam-emerald); --planner-active-fill: var(--planner-fam-emerald-active); }
.planner-fam-teal    { --planner-cat: var(--planner-fam-teal);    --planner-active-fill: var(--planner-fam-teal-active); }
.planner-fam-cyan    { --planner-cat: var(--planner-fam-cyan);    --planner-active-fill: var(--planner-fam-cyan-active); }
.planner-fam-sky     { --planner-cat: var(--planner-fam-sky);     --planner-active-fill: var(--planner-fam-sky-active); }
.planner-fam-blue    { --planner-cat: var(--planner-fam-blue);    --planner-active-fill: var(--planner-fam-blue-active); }
.planner-fam-indigo  { --planner-cat: var(--planner-fam-indigo);  --planner-active-fill: var(--planner-fam-indigo-active); }
.planner-fam-violet  { --planner-cat: var(--planner-fam-violet);  --planner-active-fill: var(--planner-fam-violet-active); }
.planner-fam-purple  { --planner-cat: var(--planner-fam-purple);  --planner-active-fill: var(--planner-fam-purple-active); }
.planner-fam-fuchsia { --planner-cat: var(--planner-fam-fuchsia); --planner-active-fill: var(--planner-fam-fuchsia-active); }
.planner-fam-pink    { --planner-cat: var(--planner-fam-pink);    --planner-active-fill: var(--planner-fam-pink-active); }
.planner-fam-rose    { --planner-cat: var(--planner-fam-rose);    --planner-active-fill: var(--planner-fam-rose-active); }

/* --- filter pills — ONE wrapping row (ticket 1217, supersedes the R12 two-segment layout of 1066). Each
   label pills flex-WRAP to the next line as the bar narrows (no horizontal scroll) — reclaiming the full
   second row of height the old layout spent, pulling the board up. Container-first: flex-wrap sizes to the
   bar's own width, so no viewport media query. --- */
/* The filter rail (#1351) — positioning context for the overflow fade, and the owner of the bar's outer
   margin. A small negative top trims the shell main's top padding so the bar (and the board under it) sits up.

   THE BOTTOM HAIRLINE (#1357) gives the row an edge like the rest of the board, and gives the horizontal
   scrollbar something to sit against instead of floating in space.

   #1357 shipped this as two rules holding RAW rungs (neutral-300 / neutral-700), because --line was then
   toned against --surface while the shell actually paints --page-bg — so the shared token was too faint to
   use here. That comment carried its own retirement instruction: when the shared token is fixed, come back
   to var(--line). #1359 fixed it (--line stepped one rung in both themes, to exactly the two values this
   rule had hard-coded), so the local override is now redundant BY VALUE — it is retired here rather than
   left as a second place the same decision lives. One rule, one token, and the light/dark flip goes back to
   being the token's job. */
/* THE FILTER PILLS ARE RETIRED, ALL OF THEM (#1351 → #1537 → #1758), and the trail is worth one note because
   three cards in a row deleted a layer of the same control.

   #1537 took the one-line sideways-scrolling RAIL (`.planner-pills-rail`, `.planner-pills`, the
   .is-overflowing fade mask that made its hidden overflow honest) when the pills moved into a rail panel and
   wrapped. #1758 took the pills THEMSELVES — `.planner-pill` and its family tint, `.planner-pill-count`,
   `.planner-pills-divider` (the former rule between two filter groups),
   and the whole `.planner-pill-wrap` / `.planner-pill-toggle` exclude control (#1533), whose four decisions
   about revealing on :focus-within and never on hover-grows only existed because a second control shared the
   row. One control per row now: click the label to hide it, click again to bring it back.

   THE REPLACEMENT IS `.planner-label` in css/pages/planner.css, and it deliberately carries two things
   forward rather than re-deriving them — the same --planner-cat family color-mix chrome (the categorical
   families are vetted identities under the no-reuse law), and the same two-signal OFF state, struck plus
   dimmed, never a dim alone. What it does NOT carry forward is the opacity on the count, and that is the one
   thing to reread before adding any: the pill count dimmed AA-verified text to 66% and axe measured every
   count between 4.08 and 4.26, with "all" at 3.5 — under the 4.5 floor. Opacity is the loophole in the colour
   law, hand-authoring a colour without ever writing a hex. #1537's settings panel made the identical mistake
   independently. Two authors, one pattern, so it is written down rather than just deleted. */

/* ===== Card-detail MODAL — the ONE detail view, opened as a fast overlay over the board. The five-pane
   body is AJAX-injected (planner-board.js) into an x-dialog; wide enough for the desktop five-column
   layout, and on mobile it's the x-tabs one-pane-at-a-time view (the same body the deep-link page uses).
   Sized by ID — x-dialog keeps its literal class="dialog", so a passed class attribute is dropped (the
   other dialogs set their width by #id too). */
#planner-card-modal {
    /* Two tabs replaced the five-panes-in-one-wide-view, so the modal no longer needs near-fullscreen —
       comfortably wide is enough (the .dialog min() still caps it to the viewport). */
    --dialog-width: 1040px;
}

#planner-card-modal .dialog-body {
    display: flex;
    min-height: 0;
    padding-top: 4px;
}

.planner-card-modal-body {
    flex: 1;
    min-width: 0;
    min-height: 45vh;   /* holds the loading state + keeps the modal from collapsing before the fetch lands */
}

/* CREW MARKS (James, 2026-08-05) — provider + account marks sit as a PAIR, so they read as one badge rather
   than two unrelated glyphs: tighten mark-to-mark, keep the inherited gap between the pair and the name. The
   13px sizing above already applies (`.planner-actor svg`), so the marks match every other actor icon. */
.planner-actor svg + svg {
    margin-inline-start: -2px;
}

/* @toc Pricing table */
/*
 * x-pricing-table — the pricing skin over the x-table structure it composes (#1212, spec §6). x-table owns the
 * <table>, the <caption>, the <th scope="col"> tier headers, and the horizontal-scroll container (.table-wrap
 * overflow-x) that lets the matrix fit any container without a second DOM — so this file only skins the matrix:
 * centre the tier columns, draw the highlighted column, and style the price / ✓ / — cells.
 *
 * SECTION-AUTHORING LAW: one immutable DOM; the table fits its container by scrolling (container-first, no
 * --bp-*, no viewport assumption). (§6's stacked-cards-below-md is a display PREFERENCE that would need x-table
 * itself to gain a responsive-cards mode — a shared-primitive change, tracked separately; the scroll container
 * meets the law here.) COLOUR: role tokens only; the highlight tint is color-mix on the ramp, never a literal.
 */

/* Tier columns (every column but the lead feature-label column) centre their header and their cells. `center`
   is not a physical value, so it needs no logical form. */
.pricing-table th:not(:first-child) {
    text-align: center;
}

.pricing-table-cell {
    text-align: center;
}

/* ── the price row ────────────────────────────────────────────────────────────────────────────────────── */
.pricing-table-row-label {
    font-weight: 500;
    color: var(--text-muted);
}

.pricing-table-badge {
    display: block;
    width: fit-content;
    margin-inline: auto;
    margin-block-end: var(--space-2);
}

.pricing-table-price {
    display: block;
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text);
}

.pricing-table-period {
    font-size: var(--text-sm);
    font-weight: 400;
    color: var(--text-muted);
}

/* ── the ✓ / — / value cells ─────────────────────────────────────────────────────────────────────────── */
/* ✓ — the shared check heroicon, --primary, decorative (the sr-only "Included" carries the meaning). */
.pricing-table-yes {
    display: inline-flex;
    width: 1.25rem;
    height: 1.25rem;
    color: var(--primary);
}

/* — the absence glyph, muted (the sr-only "Not included" carries the meaning). */
.pricing-table-no {
    color: var(--text-muted);
}

.pricing-table-value {
    font-size: var(--text-sm);
    color: var(--text);
}

/* ── the highlighted tier column (ring + badge law: the tint + rule is the ring, the badge is the label) ──
   The tint rides x-table's per-cell class passthrough (is-highlight on every cell of the featured column); the
   top/bottom cells add the block borders + the radius-8 corners so the column reads as one ruled panel. */
.pricing-table-cell.is-highlight {
    background: color-mix(in srgb, var(--primary) 6%, var(--surface));
    border-inline: 1px solid var(--line);
}

.pricing-table-cell.is-highlight-top {
    border-block-start: 1px solid var(--line);
    border-start-start-radius: var(--global-border-radius);
    border-start-end-radius: var(--global-border-radius);
}

.pricing-table-cell.is-highlight-bottom {
    border-block-end: 1px solid var(--line);
    border-end-start-radius: var(--global-border-radius);
    border-end-end-radius: var(--global-border-radius);
}

/* @toc Product finish preview */
/*
 * Product finish preview. Figure, Fieldset, and native radio primitives retain their own semantics while this
 * pattern arranges them as one responsive, isolated selection surface.
 * Its container query targets the child layout so the root supplies the correct query size.
 */

.product-finish-preview {
    display: grid;
    gap: var(--product-finish-preview-gap, var(--space-4));
    container-type: inline-size;
    inline-size: 100%;
    min-inline-size: 0;
    padding: var(--product-finish-preview-padding, var(--space-4));
    overflow: hidden;
    background: var(--product-finish-preview-background-color, var(--surface));
    border: 1px solid var(--product-finish-preview-border-color, var(--line));
    border-radius: var(--product-finish-preview-border-radius, var(--radius-container));
}

.product-finish-preview-layout {
    display: grid;
    gap: var(--product-finish-preview-gap, var(--space-4));
    min-inline-size: 0;
}

.product-finish-preview-stage {
    display: grid;
    place-items: center;
    min-inline-size: 0;
    overflow: hidden;
    background: var(--product-finish-preview-stage-background-color, var(--surface-raised));
    border-radius: var(--product-finish-preview-border-radius, var(--radius-container));
}

.product-finish-preview-figure {
    display: block;
    inline-size: 100%;
}

.product-finish-preview-figure .figure-img {
    aspect-ratio: 1;
    inline-size: 100%;
    object-fit: cover;
}

.product-finish-preview-controls {
    display: grid;
    align-content: center;
    gap: var(--space-3);
    min-inline-size: 0;
}

.product-finish-preview-label {
    margin: 0;
    color: var(--product-finish-preview-label-color, var(--text));
    font-size: var(--product-finish-preview-label-font-size, var(--text-lg));
    font-weight: 700;
}

.product-finish-preview-picker.fieldset {
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
}

.product-finish-preview-picker .fieldset-body {
    display: flex;
    flex-flow: row wrap;
    gap: var(--space-2);
}

.product-finish-preview-picker .checkbox {
    min-block-size: 44px;
    padding-block: var(--space-2);
    padding-inline: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
}

.product-finish-preview-picker .checkbox:has(.checkbox-input:checked) {
    border-color: var(--primary);
}

.product-finish-preview-note {
    margin: 0;
    color: var(--product-finish-preview-note-color, var(--text-muted));
    font-size: var(--product-finish-preview-note-font-size, var(--text-sm));
}

@container (min-width: 36rem) {
    .product-finish-preview-layout {
        grid-template-columns: minmax(0, 3fr) minmax(12rem, 2fr);
    }
}

/* @toc Production checklist */
/*
 * x-production-checklist — mobile-first and container-first. The base is the truthful 320px stack; one
 * min-width container enhancement aligns label, note, and status without changing the DOM. Semantic tokens and
 * existing Progress/Badge primitives own every colour/status treatment. No raster, viewport query, or motion.
 */

.production-checklist {
    position: relative;
    container-type: inline-size;
    padding: var(--space-5);
    color: var(--text);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
}

.production-checklist-decoration-rule::before {
    content: '';
    position: absolute;
    inset-block-start: 0;
    inset-inline: var(--space-4);
    block-size: 2px;
    background: var(--primary-600);
    pointer-events: none;
}

.production-checklist-header {
    display: grid;
    gap: var(--space-3);
    margin-block-end: var(--space-4);
}

.production-checklist-heading {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
}

.production-checklist-title {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 700;
    line-height: 1.3;
}

.production-checklist-summary {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.production-checklist-progress {
    inline-size: 100%;
}

.production-checklist-stages {
    display: grid;
    gap: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

.production-checklist-stage {
    display: grid;
    gap: var(--space-2);
    min-inline-size: 0;
    padding-block: var(--space-4);
    border-block-start: 1px solid var(--line);
}

.production-checklist-stage-label {
    min-inline-size: 0;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.production-checklist-stage-state {
    justify-self: start;
}

.production-checklist-stage-note {
    min-inline-size: 0;
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

.production-checklist-empty {
    margin: 0;
    padding-block: var(--space-4);
    border-block-start: 1px solid var(--line);
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

.production-checklist-compact {
    padding: var(--space-3);
}

.production-checklist-compact .production-checklist-stage {
    padding-block: var(--space-2);
}

@container (min-width: 40rem) {
    .production-checklist-stage {
        grid-template-columns: minmax(10rem, 1fr) minmax(0, 2fr) auto;
        align-items: start;
        column-gap: var(--space-4);
    }

    .production-checklist-stage-label {
        grid-column: 1;
        grid-row: 1;
    }

    .production-checklist-stage-state {
        grid-column: 3;
        grid-row: 1;
    }

    .production-checklist-stage-note {
        grid-column: 2;
        grid-row: 1;
    }
}

/* @toc Progress */
/*
 * Progress. A themed native <progress>. Every token is a --progress-* var from
 * config/components/progress.php['css']; this file only references those plus the
 * structural rules the element needs. Styling <progress> means resetting the UA
 * appearance, then painting the track (-webkit-progress-bar / -moz the host) and
 * the fill (-webkit-progress-value / -moz-progress-bar) separately. An element
 * with no value attribute is indeterminate — the browser animates it for free.
 */

.progress {
    /* Reset the UA chrome so the track/value pseudo-elements paint cleanly. */
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    display: block;
    width: var(--progress-width, 100%);
    height: var(--progress-height, 8px);
    border: none;
    border-radius: var(--progress-border-radius, var(--radius-full, 9999px));
    /* Firefox paints the track on the host element, not a -bar pseudo. */
    background: var(--progress-track, var(--surface-raised));
    overflow: hidden;
    color: var(--progress-value, var(--primary));
}

/* WebKit / Blink: the track. */
.progress::-webkit-progress-bar {
    background: var(--progress-track, var(--surface-raised));
    border-radius: var(--progress-border-radius, var(--radius-full, 9999px));
}

/* WebKit / Blink: the filled portion. */
.progress::-webkit-progress-value {
    background: var(--progress-value, var(--primary));
    border-radius: var(--progress-border-radius, var(--radius-full, 9999px));
    transition: width 0.25s ease;
}

/* Firefox: the filled portion. */
.progress::-moz-progress-bar {
    background: var(--progress-value, var(--primary));
    border-radius: var(--progress-border-radius, var(--radius-full, 9999px));
}

/* Indeterminate: no value attribute. WebKit ignores ::-webkit-progress-value
 * here, so we drive a looping sweep on the track itself. */
.progress:indeterminate {
    background-image:
        linear-gradient(
            90deg,
            transparent 0%,
            var(--progress-value, var(--primary)) 50%,
            transparent 100%
        );
    background-size: 40% 100%;
    background-repeat: no-repeat;
    background-position: -40% 0;
    animation: progress-indeterminate 1.2s infinite ease-in-out;
}

/* Firefox draws a solid -moz-progress-bar while indeterminate; blank it so only
 * the host's looping sweep shows. */
.progress:indeterminate::-moz-progress-bar {
    background: transparent;
}

@keyframes progress-indeterminate {
    0% {
        background-position: -40% 0;
    }
    100% {
        background-position: 140% 0;
    }
}

/* Respect reduced-motion: hold the sweep mid-track instead of looping. */
@media (prefers-reduced-motion: reduce) {
    .progress:indeterminate {
        animation: none;
        background-position: 50% 0;
    }

    .progress::-webkit-progress-value {
        transition: none;
    }
}

/* Keyboard focus ring, matching the other primitives. The transparent outline is
 * the forced-colors fallback (box-shadow is dropped in high-contrast mode). */
.progress:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--progress-focus-ring, var(--primary));
}

/* @toc Project workbench */
/*
 * ProjectWorkbench layout.
 * Keeps the promoted iframe isolated. A host-owned boundary protects full-screen
 * Project chrome from same-origin package selectors scoped to the legacy workbench root.
 */
.project-workbench-host {
    position: relative;
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

.project-workbench {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 0;
    grid-template-rows: minmax(0, 1fr);
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    background: var(--project-workbench-background-color, var(--surface));
}

.project-workbench.has-docked-tool {
    grid-template-columns: minmax(0, 1fr) var(--workspace-tool-dock-width, 360px);
}

.project-workbench:has(> .project-workbench-feedback-dock > .workspace-tool--feedback.is-docked) {
    grid-template-columns: minmax(0, 1fr) minmax(288px, min(370px, 34vw));
}

.project-workbench-feedback-dock {
    grid-column: 2;
    grid-row: 1;
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    border-inline-start: 1px solid var(--line);
}

.project-workbench-feedback-dock:empty {
    display: none;
}

.project-workbench-feedback-dock > .workspace-tool--feedback.is-docked {
    height: 100%;
}

.project-workbench-stage {
    position: relative;
    grid-column: 1;
    grid-row: 1;
    height: 100%;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

.project-workbench.has-floating-dock .project-workbench-stage {
    box-sizing: border-box;
    padding-block-end: var(--app-bottom-bar-layout-block-end, var(--app-bottom-bar-initial-occupied-height, 61px));
}

.project-workbench-frame {
    display: block;
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    border: 0;
    background: var(--project-workbench-frame-background-color, var(--surface-raised));
}

.project-workbench[data-project-shell-hosted="true"] {
    flex: 1 1 auto;
}

.project-anchor-layer {
    position: absolute;
    z-index: var(--project-workbench-control-z-index, var(--z-overlay));
    inset: 0;
    pointer-events: none;
}

.project-workbench-host .notes-workbench-bubble {
    z-index: calc(var(--project-workbench-control-z-index, var(--z-overlay)) + 1);
}

.project-workbench-host .workspace-tool--notes:is(.is-docked, .is-sheet) .workspace-tool-dialog {
    z-index: calc(var(--project-workbench-control-z-index, var(--z-overlay)) + 1);
}

.project-anchor-marker {
    position: absolute;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-full, 9999px);
    color: var(--project-workbench-marker-background-color, var(--info));
    background: transparent;
    filter: drop-shadow(0 4px 5px color-mix(in srgb, var(--text) 25%, transparent));
    font: inherit;
    font-size: 30px;
    transform: translate(-50%, -87.5%);
    pointer-events: auto;
    cursor: pointer;
}

.project-anchor-marker:hover,
.project-anchor-marker:focus-visible {
    scale: 1.12;
}

.project-anchor-marker.is-unavailable {
    color: var(--text-muted);
}

.project-anchor-marker.is-pending {
    color: var(--primary);
    cursor: default;
}

.project-anchor-marker:focus-visible {
    outline: 3px solid var(--project-workbench-focus-ring, var(--on-primary));
    outline-offset: 3px;
}

.project-workbench-host > .floating-dock,
.project-workbench-host > .floating-dock * {
    box-sizing: border-box;
}

.project-workbench-host > .floating-dock {
    z-index: var(--project-workbench-dock-z-index, var(--z-popover));
}

.project-workbench-host > .floating-dock .floating-dock-persistent,
.project-workbench-host > .floating-dock .admin-impersonation-controls--dock {
    min-width: 0;
}

.project-workbench-host > .floating-dock .admin-impersonation-controls--dock .dropdown {
    position: static;
}

.project-workbench-host > .floating-dock [data-admin-impersonation-menu-placement="up"] .dropdown-menu {
    inset-inline: 0;
    width: auto;
    min-width: 0;
}

.project-workbench-dock-action {
    position: relative;
    overflow: visible !important;
}

.project-workbench-dock-action > span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.project-workspace-control[hidden] {
    display: none !important;
}

.project-workspace-control-icon > svg {
    display: block;
    overflow: visible;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.project-workbench-dock-icon {
    position: relative;
}

.project-workbench-dock-icon > svg {
    display: block;
    overflow: visible;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.project-note-placement-prompt {
    position: fixed;
    z-index: var(--project-workbench-control-z-index, var(--z-overlay));
    inset-inline-start: 50%;
    inset-block-end: calc(var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)) + 12px);
    max-inline-size: calc(100vw - 24px);
    margin: 0;
    padding: 8px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    color: var(--text);
    background: var(--surface-raised);
    box-shadow: var(--shadow-lg);
    font-size: var(--text-sm);
    text-align: center;
    transform: translateX(-50%);
}

.project-note-placement-prompt[hidden] {
    display: none;
}

@media (max-width: 768px), (max-height: 600px) {
    .project-workbench,
    .project-workbench.has-docked-tool {
        grid-template-columns: minmax(0, 1fr) 0;
    }

}

@media (prefers-reduced-motion: reduce) {
    .project-anchor-marker {
        scroll-behavior: auto !important;
        transition: none !important;
    }
}

/* @toc Pt answer field */
/* x-pt-answer-field (#1349) — the writing surface. Structure + the field's own reading treatment; the panel
 * around it owns the outer chrome. Colour comes only from semantic tokens + the ramp. */

.pt-answer-field {
    display: flex;
    flex-direction: column;
    gap: var(--pt-answer-field-gap, 8px);
}

/* The class lands on the <textarea> ITSELF — x-textarea ends its markup with $attributes->merge on the field,
 * not on its wrapper. (A descendant selector here matches nothing, which is how the first build shipped a box
 * still at its four-row default.)
 *
 * IT FILLS ITS PANEL ON PURPOSE. This is the exercise — a 90-minute exam answer — and a short box says the
 * opposite. `resize: vertical` leaves the final say with the person writing.
 *
 * SCOPED THROUGH THE ROOT ON PURPOSE. x-textarea styles this same element with `.textarea-field` — one class,
 * exactly the specificity a bare `.pt-answer-field-input` would have — and CssService bundles component sheets
 * alphabetically, so textarea.css lands AFTER this file and would win the tie. The root makes it (0,2,0).
 *
 * THIS RULE WAS SILENTLY DROPPED TWICE, both times by comment punctuation, and it is worth knowing how because
 * the symptom is indistinguishable from losing a specificity fight — the token emits 1rem and the element
 * computes 14px either way. First a stray continuation line sat outside a closed comment; then, writing THIS
 * note, a comment-close sequence inside the prose ended the block early and the remaining text became garbage
 * that swallowed the selector after it. Never put a comment-close sequence in comment text. And to tell the two
 * causes apart, read the APPLIED RULE LIST off document.styleSheets — an absent rule is a parse failure, a
 * present-but-overridden one is a cascade problem, and the computed value cannot distinguish them. */
.pt-answer-field .pt-answer-field-input {
    min-block-size: var(--pt-answer-field-min-height, 28rem);
    font-size: var(--pt-answer-field-font-size, var(--text-base));
    line-height: var(--pt-answer-field-line-height, 1.75);
    resize: vertical;
}

/* The status line RESERVES ITS HEIGHT even when empty, so the first "Saving…" does not shove the textarea up
   the page while she is typing into it. */
.pt-answer-field-status {
    margin: 0;
    min-block-size: 1.2em;
    font-size: var(--pt-answer-field-status-font-size, var(--text-xs));
    color: var(--pt-answer-field-status-color, var(--text-muted));
}

/* A failed save is the one state worth colouring — it is the only one that asks her to do something. Status
   meaning comes from the categorical token, never a hand-authored colour. */
.pt-answer-field-status[data-state="error"] {
    color: var(--pt-answer-field-error-color, var(--danger));
    font-weight: 600;
}

/* @toc Reveal phase */
/* Reveal phase — the study reader's phase control: an APG radio group rendered as a segmented control (native
 * radio inputs; the C direction reskins it as a numbered stepper — same semantics). STRUCTURE is locked; the
 * decorative skin waits on the direction pick. The real <input> is .sr-only; the active segment is driven by
 * :has(:checked) and the focus ring by :has(:focus-visible) — no JS for the visual state. Config:
 * config/components/reveal-phase.php (--reveal-phase-border-radius). View: reveal-phase.blade.php; dispatch +
 * persistence: reveal-phase.js. */

.reveal-phase {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    background: var(--surface-muted);
    border: 1px solid var(--line);
    border-radius: var(--reveal-phase-border-radius, var(--radius-control));
}

.reveal-phase-segment {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 5px var(--space-3);
    border-radius: calc(var(--reveal-phase-border-radius, var(--radius-control)) - 2px);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-muted);
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
}

.reveal-phase-segment:hover {
    color: var(--text);
}

/* The active segment (:has the checked input) — a raised fill that pops from the recessed track. */
.reveal-phase-segment:has(.reveal-phase-input:checked) {
    color: var(--text);
    background: var(--surface);
    box-shadow: 0 1px 2px color-mix(in srgb, var(--text) 12%, transparent);
}

/* The focus ring rides the sr-only input's :focus-visible onto its segment (the visible control). */
.reveal-phase-segment:has(.reveal-phase-input:focus-visible) {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}

/* @toc Room canvas */
/* @toc Room canvas */

/* room-canvas (#1465) — the room authoring surface.
 *
 * MOBILE-FIRST, AND THAT IS THE CARD RATHER THAN A POLISH PASS. The base rules ARE the 390px layout: the
 * toolbar sits above the canvas, the canvas takes the width it is given, and every control is finger-sized.
 * Desktop is the min-width enhancement.
 *
 * ★ IT RESPONDS TO ITS CONTAINER, NEVER THE VIEWPORT. A landscape phone is 844px wide, so a width-only media
 * query classifies it as a desktop and hands a thumb a layout drawn for a mouse. `container-type: inline-size`
 * plus @container means the surface answers to the box it is actually in — which is also what makes it
 * truthful inside the editor's device frame and beside a docked 240px sidebar. */
/* ★ THE CONTAINER AND THE THING THAT RESPONDS MUST BE DIFFERENT ELEMENTS. `.room-canvas` establishes the
 * container; `@container` rules style its DESCENDANTS, never itself — so a `display: grid` written on this
 * element inside the query is silently ignored while its children's rules apply normally. That is exactly how
 * the first 844px shot came out: the toolbar stacked (a descendant rule, applied) but the two-column layout
 * never happened (a self rule, dropped), giving a landscape phone six full-width buttons above the canvas.
 *
 * ★ HALF THE RULE APPLYING IS WHAT MADE IT LOOK DELIBERATE. Nothing errored, every gate stayed green, and the
 * result was a plausible-looking layout — so the only thing that could catch it was opening the screenshot.
 * If you are tempted to "simplify" this wrapper away, that is the bug you will reintroduce.
 * `.room-canvas-layout` exists solely to be a descendant that can answer. */
.room-canvas {
    container-type: inline-size;
    min-block-size: var(--room-canvas-min-block-size, 320px);
}

.room-canvas-layout {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* The toolbar WRAPS rather than scrolls. A horizontally scrolling toolbar hides its own controls at exactly
 * the width where there is least room to discover them. */
.room-canvas-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--room-canvas-toolbar-gap, var(--space-2));
    padding: var(--room-canvas-toolbar-padding, var(--space-2));
    background-color: var(--room-canvas-toolbar-background-color, var(--surface-raised));
    border-radius: var(--room-canvas-border-radius, var(--radius-container));
}

.room-canvas-surface {
    inline-size: 100%;
    /* The room's own 16:9 space, so the canvas never letterboxes differently from the geometry inside it. */
    aspect-ratio: 16 / 9;
    /* ★ A HEIGHT CAP, because 16:9 of a WIDE viewport is TALLER THAN A SHORT ONE. At 844px wide the canvas
     * wants 475px, which does not fit a 390px-tall landscape phone — the surface would run off the bottom
     * with the toolbar pushed off with it. This is the one place a VIEWPORT unit is right: the constraint
     * genuinely is the screen's height, and no container query can report it. `svh` rather than `vh` so a
     * mobile browser's retracting address bar cannot make the canvas taller than the space it actually has. */
    max-block-size: 68svh;
    background-color: var(--room-canvas-background-color, var(--surface-muted));
    border: 1px solid var(--room-canvas-border-color, var(--line));
    border-radius: var(--room-canvas-border-radius, var(--radius-container));
    /* The gesture handler owns the pointer. Without this the browser pans the page instead of the room, and a
     * one-finger drag scrolls away from the surface being drawn on. */
    touch-action: none;
}

.room-canvas-surface:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.room-canvas-bounds {
    fill: none;
    stroke: var(--room-canvas-border-color, var(--line));
    stroke-width: 1;
}

/* Geometry, coloured by what it MEANS rather than by family: a solid is mass, an anchor is a destination, a
 * hazard is a warning. The status tokens already name those and are AA by construction. */
.room-canvas-item {
    cursor: pointer;
}

.room-canvas-item-solids {
    fill: var(--room-canvas-solid-fill, var(--neutral-500));
    stroke: var(--room-canvas-solid-border-color, var(--neutral-300));
    stroke-width: 1;
}

.room-canvas-item-anchors {
    fill: var(--room-canvas-anchor-fill, var(--info));
    fill-opacity: 0.55;
    stroke: var(--room-canvas-anchor-fill, var(--info));
    stroke-width: 1;
}

.room-canvas-item-hazards {
    fill: var(--room-canvas-hazard-fill, var(--warning));
    fill-opacity: 0.45;
    stroke: var(--room-canvas-hazard-fill, var(--warning));
    stroke-width: 1;
}

/* Selection is a STROKE, never a fill change — a filled selection hides the thing being selected, and on a
 * phone the finger is already covering half of it. */
.room-canvas-item[data-room-selected] {
    stroke: var(--room-canvas-selected-border-color, var(--primary));
    stroke-width: var(--room-canvas-selected-border-width, 2px);
}

.room-canvas-item:focus-visible {
    outline: none;
    stroke: var(--primary);
    stroke-width: var(--room-canvas-selected-border-width, 2px);
}

/* A handle is drawn small and hit large. The visible square is 12px so it does not bury the geometry; the
 * transparent companion is 44px because that is what a finger needs. */
.room-canvas-handle {
    fill: var(--room-canvas-handle-fill, var(--primary));
}

.room-canvas-handle-hit {
    fill: transparent;
    cursor: grab;
}

.room-canvas-empty {
    margin: 0;
    padding: var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-align: center;
}

.room-canvas-create {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* DESKTOP IS THE ENHANCEMENT — min-width polarity, keyed on the CONTAINER. At 40rem the toolbar can sit
 * beside the canvas because there is room for it to, not because the window is wide. */
@container (min-width: 40rem) {
    .room-canvas-layout {
        display: grid;
        /* `max-content` rather than `auto`: an auto track lets the toolbar's buttons stretch and eat the row,
         * which is how the first attempt handed a landscape phone six full-width buttons. */
        grid-template-columns: max-content 1fr;
        align-items: start;
    }

    .room-canvas-toolbar {
        flex-direction: column;
        position: sticky;
        inset-block-start: var(--space-2);
    }

    .room-canvas-empty {
        grid-column: 2;
    }
}

/* @toc Section cards */
/*
 * x-section-cards — the card-display section's card skin (#1228). The RESPONSIVE GRID is the x-page-section
 * chassis's (.page-section-items, 1 → N on the section's OWN box via @container), so this file skins only the
 * card and needs no @media / @container of its own: the card is intrinsically fluid and the chassis owns the
 * columns. SECTION-AUTHORING LAW: one immutable DOM, container-first, no --bp-*, no viewport assumption.
 * COLOUR: semantic role tokens only (surface / line / text / text-muted / primary); the hover lift is a
 * color-mix on the ramp, never a literal.
 */

/* The card — a surface tile, a full-height flex column so the chips sit at a consistent baseline whatever the
   summary length. A whole-card LINK (<a>) carries the hover + focus affordance; a plain <div> card (a row with
   no safe href) shares the skin without the interaction. */
.section-cards-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    height: 100%;
    padding: var(--space-4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--global-border-radius);
    color: var(--text);
    text-decoration: none;
}

/* The whole-card link affordance — a[href] only (a plain-div card is not interactive). */
a.section-cards-card {
    transition: border-color 150ms ease, background-color 150ms ease;
}

a.section-cards-card:hover {
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 4%, var(--surface));
}

a.section-cards-card:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.section-cards-eyebrow {
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.section-cards-title {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--text);
}

/* The preview line — muted, clamped to three lines so the cards keep an even height in the grid. */
.section-cards-summary {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* Pin the chips to the card foot regardless of summary length, so a grid of cards aligns its badge row. */
.section-cards-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: auto;
    padding-block-start: var(--space-1);
}

.section-cards-empty {
    color: var(--text-muted);
}

/* @toc Section cta */
/*
 * x-section-cta — the CTA band (spec §3). Composes the x-page-section chassis with ZERO chassis edits: every
 * rule here is scoped under .section-cta, so page-section.css is untouched. The chassis explicitly sanctions
 * this seam ("a section that centres (e.g. a CTA band) overrides text-align + align-items in its own CSS").
 *
 * Section-Authoring Law: the BASE rules ARE the 375 layout (one column, --space-* rhythm, clamp() type); desktop
 * is a pure @container (min-width) ENHANCEMENT on the section's OWN inline size — no --bp-*, no @media, no
 * matchMedia. ONE immutable DOM: `orientation` and `reverse` rearrange the same markup by CSS alone.
 *
 * THE PANEL is the chassis's .page-section-inner — the centred content column, and the only region that holds
 * BOTH the header (title · description · actions) and the body (the demo pane), which the band's panel must.
 * Its padding IS the panel's padding, re-scaled to the CTA's values (X 24 → 48 → 64, Y 48 → 96); the section's
 * vertical rhythm against its neighbours comes from their own padding. Colour is semantic tokens only, so the
 * band themes per tenant in both schemes.
 */

/* The panel — radius one tier rounder than a card (12 vs 8) because the band is a page-level object, and
   overflow:hidden so a demo island/media pane clips to the rounded corner. */
.section-cta .page-section-inner {
    overflow: hidden;
    border-radius: var(--radius-lg);
    padding-block: var(--space-12);
    padding-inline: var(--space-6);
}

@container (min-width: 640px) {
    .section-cta .page-section-inner {
        padding-block: var(--space-24);
        padding-inline: var(--space-12);
    }
}

@container (min-width: 1024px) {
    .section-cta .page-section-inner {
        padding-inline: var(--space-16);
    }
}

/* Variants — the surface treatment. `solid` (the inverted panel) is DEFERRED (spec §3 / ruling R7): it needs the
   inverse-surface token and is deliberately NOT faked with raw ramp rungs, so the set ships four. Alpha rides
   color-mix on a BACKGROUND only, which the colour law sanctions. */
.section-cta--outline .page-section-inner {
    background: var(--surface);
    border: 1px solid var(--line);
}

.section-cta--soft .page-section-inner {
    background: color-mix(in srgb, var(--surface-raised) 50%, transparent);
}

.section-cta--subtle .page-section-inner {
    background: color-mix(in srgb, var(--surface-raised) 50%, transparent);
    border: 1px solid var(--line);
}

.section-cta--naked .page-section-inner {
    background: transparent;
}

/* The band's title runs one tier shorter than a section header (30 → 36, not the chassis's 30 → 48) — a closing
   prompt, not a page title. Fluid on the section's OWN width (cqi), per the law. */
.section-cta .page-section-title {
    font-size: clamp(1.875rem, 1.6rem + 1.2cqi, 2.25rem);
}

/* The band's internal rhythm: title → description 32 and description → actions 32 (the chassis ships 16 / 24).
   With no description, the title → actions gap is the same 32. The actions row keeps the chassis's 12/24 gaps. */
.section-cta .page-section-description {
    margin-block-start: var(--space-8);
}

.section-cta .page-section-actions {
    margin-block-start: var(--space-8);
}

/* Vertical orientation (default) — the band centres its title, description and actions. */
.section-cta--vertical .page-section-head {
    align-items: center;
    text-align: center;
}

.section-cta--vertical .page-section-description {
    margin-inline: auto;
}

.section-cta--vertical .page-section-actions {
    justify-content: center;
}

/* Horizontal orientation — content beside the demo pane. Stacked at the base (the chassis's flex column, whose
   32 gap IS the content↔media gap); a 2-col grid once the section's own box is wide enough, gap 64. `reverse`
   swaps the columns by order alone — the DOM is identical either way. */
@container (min-width: 1024px) {
    .section-cta--horizontal .page-section-inner {
        display: grid;
        grid-template-columns: 1fr 1fr;
        align-items: center;
        gap: var(--space-16);
    }

    /* Grid children may hold a wide island — min-width:0 keeps it from blowing the column out. */
    .section-cta--horizontal .page-section-head,
    .section-cta--horizontal .page-section-body {
        min-width: 0;
    }

    .section-cta--reverse .page-section-head {
        order: 2;
    }
}

/* @toc Section features */
/*
 * x-section-features — the feature grid's ITEM SHAPES (spec §2). The x-page-section chassis owns the header
 * grammar AND the responsive items grid (1 → 2 → 3 columns on the section's own width, gap 32), so there is
 * deliberately NO grid CSS here — composing the chassis with zero edits is the whole point of the pattern.
 *
 * Section-Authoring Law: the base rules ARE the 375 layout; the only desktop delta (card padding 16 → 24) is a
 * pure @container (min-width) ENHANCEMENT that resolves against the chassis's .page-section container context,
 * so the section reflows on its OWN box and never viewport-assumes (no --bp-*, no @media). ONE immutable DOM at
 * every width: the SAME flat item markup (icon · title · description · footer) is stacked by the card shape and
 * rearranged into a row by the line shape — CSS only, never markup.
 *
 * Colour is semantic tokens only (--surface, --line, --text, --text-muted, --primary for the decorative icon,
 * --link for hovered link text), so the section themes per tenant in both schemes. Token-sourced is NOT the
 * same as contrast-compliant, so the two are picked per ROLE: --primary is judged as non-text (3:1) and only
 * ever tints the aria-hidden icon; text that changes colour uses --link, which is rung-stepped per theme to
 * clear 4.5:1. No left-rail accents (the anti-slop law) — the card is a clean bordered surface.
 *
 * Two spec values sit OFF the --space-/--radius- ladders (icon↔text 10, line radius 4). They are written as
 * literals rather than snapped to a neighbouring rung (8/12, 6) because the spec fixes them and picking a
 * different number would be authoring skin — flagged for the design pass, not silently normalized.
 *
 * CHASSIS SURFACE TOUCHED (the section-tier ruling): NONE — no --page-section-* knob is overridden and no
 * .page-section-* anatomy class is styled here. Every selector below is this section's OWN .section-features-*
 * surface. The chassis's default items grid already IS spec §2's grid, so there is nothing to reach for.
 */

/* The item, both shapes. A linked item must not inherit the UA anchor look; its keyboard ring is the house
   :focus-visible outline from base.css (a real outline, so it survives forced-colors and follows the radius) —
   no ring is authored here. */
.section-features-item {
    color: inherit;
    text-decoration: none;
}

.section-features-icon {
    display: inline-flex;
    width: var(--space-5);
    height: var(--space-5);
    flex-shrink: 0;
    color: var(--primary);
}

/* The ILLUSTRATION tier (#1897) — drawn art, not a glyph, so it gets its own box instead of the 20px icon
   square that would crush it. Deliberately NO `color`: a heroicon inherits currentColor, whereas a tokenised
   illustration carries its own var() fills and tinting the box would fight them. 48px is the card shape's
   scale; the LINE shape's compact scale is a design-pass question, flagged like the two literals above. */
.section-features-illustration {
    display: inline-flex;
    width: var(--space-12);
    height: var(--space-12);
    flex-shrink: 0;
}

.section-features-icon svg,
.section-features-illustration svg {
    display: block;
    width: 100%;
    height: 100%;
}

.section-features-title {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.5;
    color: var(--text);
    transition: color 150ms;
}

.section-features-description {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--text-muted);
}

/* A linked item gets the house hover delta on its title (their theme ships none; ours adds the standard 150ms
   colour hover). Colour-only, so there is no motion to guard for reduced-motion.
   --link, NOT --primary: this tints REAL 16px/600 text, which is normal-size under WCAG, so it needs 4.5:1.
   Bare --primary (primary-500) is only 3.76:1 on the light --surface with the live ramp — the exact miss the
   purpose-built --link token exists to fix (A11y-D: primary-700 light / primary-400 dark, stepping opposite
   ways per theme). The decorative icon above keeps --primary: it is aria-hidden non-text, judged at 3:1. */
a.section-features-item:hover .section-features-title {
    color: var(--link);
}

/* ---- CARD (rich): a bordered surface — icon → title → description, with the footer pinned to the bottom.
   The item is a flex column and grid stretches it to the row height, so margin-block-start:auto floats the
   footer to the card's base regardless of how much copy the item carries. ---- */
.section-features-card .section-features-item {
    display: flex;
    flex-direction: column;
    align-items: start;
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    background-color: var(--surface);
}

@container (min-width: 640px) {
    .section-features-card .section-features-item {
        padding: var(--space-6);
    }
}

.section-features-card .section-features-icon + .section-features-title,
.section-features-card .section-features-illustration + .section-features-title {
    margin-block-start: 10px;
}

.section-features-card .section-features-title + .section-features-description {
    margin-block-start: var(--space-4);
}

.section-features-card .section-features-footer {
    margin-block-start: auto;
    padding-block-start: var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* ---- LINE (compact): the same flat DOM, rearranged — the icon takes a leading column and the title sits over
   its one-line description. For dense lists. Radius 4 shapes the focus target's ring. ---- */
.section-features-line .section-features-item {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 10px;
    align-items: start;
    border-radius: 4px;
}

/* Both leading tiers take the same cell — an illustration that missed this would land in the title's column
   and push the copy out of the grid. */
.section-features-line .section-features-icon,
.section-features-line .section-features-illustration {
    grid-column: 1;
    grid-row: 1;
}

.section-features-line .section-features-title {
    grid-column: 2;
    grid-row: 1;
}

.section-features-line .section-features-description {
    grid-column: 2;
    grid-row: 2;
}

/* @toc Section hero */
/*
 * x-section-hero — the flagship marketing section (SPEC 2 §1). Composes x-page-section; every rule below is
 * scoped to .section-hero and restyles only the chassis's PUBLISHED anatomy — the section-layer contract in
 * the component docblock (KNOBS FIRST, then documented anatomy only, and the law still binds).
 *
 * Mobile-first + container-first (the Section-Authoring Law): the 375 base IS the chassis's single column
 * carrying the hero's deeper rhythm and larger type; the content | demo 2-col layout is a pure
 * @container (min-width: 1024px) ENHANCEMENT on the section's OWN inline size. ONE immutable DOM — `reverse`
 * swaps the columns by grid placement, never by re-ordering markup. No --bp-*, no @media, no matchMedia.
 *
 * SCALE NOTE: the hero runs one padding tier deeper than the chassis (96 → 128 → 160 against its
 * 64 → 96 → 128). The 160 tier reads --space-40 — the contiguous Tailwind rung added to the scale for
 * exactly this (the chassis's --space-28/32 precedent: the section tier extends the ladder as it needs it).
 *
 * The chassis's actions row already ships the hero's gaps (row 12 / column 24), so it is deliberately not
 * overridden here.
 */

/* Base (375) — single column, hero rhythm: 96 padding, 64 between the content and the demo. */
.section-hero .page-section-inner {
    gap: var(--space-16);
    padding-block: var(--space-24);
}

/* Hero type — the section's own scale (48 → 72), a step above the chassis's 30 → 48. `pretty` over the
   chassis's `balance`: a hero title runs long enough that orphan control beats line-length evening. */
.section-hero .page-section-title {
    font-size: clamp(3rem, 2.25rem + 3.2cqi, 4.5rem);
    text-wrap: pretty;
}

.section-hero .page-section-description {
    margin-block-start: var(--space-10);
    font-size: clamp(1.125rem, 1.05rem + 0.4cqi, 1.25rem);
    line-height: 1.6;
}

/* Hero rhythm inside the content column: eyebrow → title 16, title → body 40, body → actions 40. */
.section-hero .page-section-eyebrow + .page-section-title {
    margin-block-start: var(--space-4);
}

.section-hero .page-section-actions {
    margin-block-start: var(--space-10);
}

/* The island pane. The hero gives the cell and nothing else — no radius, border, shadow, or sizing (SPEC §1:
   the hero never constrains, sizes, or styles the island's internals). min-width: 0 only so a wide island can
   shrink inside its grid track instead of forcing overflow. */
.section-hero .page-section-body {
    min-width: 0;
}

/* Stacked enhancement (≥640): the rhythm opens up before the layout ever splits. */
@container (min-width: 640px) {
    .section-hero .page-section-inner {
        gap: var(--space-24);
        padding-block: var(--space-32);
    }
}

/* The 2-col enhancement (≥1024) — content | demo on the section's own width. */
@container (min-width: 1024px) {
    .section-hero .page-section-inner {
        display: grid;
        grid-template-columns: 1fr 1fr;
        align-items: center;
        gap: var(--space-16);
        padding-block: var(--space-40);
    }

    /* `reverse` — demo leads, content follows. Explicit placement (not `order`) so the auto-placed footer
       cannot be dragged ahead of them. */
    .section-hero--reverse .page-section-head {
        grid-column: 2;
        grid-row: 1;
    }

    .section-hero--reverse .page-section-body {
        grid-column: 1;
        grid-row: 1;
    }

    /* The footer trails the whole section, never a single column. */
    .section-hero .page-section-footer {
        grid-column: 1 / -1;
    }
}

/* @toc Section logos */
/*
 * x-section-logos — the trusted-by strip (the smallest credibility section). Authored mobile-first +
 * container-first (the Section-Authoring Law): every rule here IS the 375 base, and the only width steps are
 * @container (min-width) enhancements measured on the SECTION's own inline size — never the viewport (no
 * --bp-*, no width @media, no matchMedia). The one @media is prefers-reduced-motion, a user preference rather
 * than a viewport assumption, and it is the house convention for exactly this.
 *
 * Both variants style the SAME DOM — the .section-logos-list of marks — so the skin choice never forks the
 * markup: `default` flows the marks as a wrapped row aligned to the header, `grid` sets them in bordered tiles.
 * The quiet treatment (grayscale + dimmed, colour restored on hover/focus) is the DEFAULT, because a wall of
 * full-colour third-party marks fights the page it is meant to support; the `quiet` prop turns it off.
 * Colour is semantic tokens only, so the strip themes per tenant; box properties are logical, so an RTL tenant
 * reads correctly for free.
 */

/* --- The list: one DOM, shared by both variants ---------------------------------------------------------- */

/* Start-aligned, NOT centred: the chassis header is `align-items: start`, so a centred strip floats away from
   the title it belongs to and the band reads as two unrelated things. The chassis's own rule is that a section
   which centres overrides alignment in its own CSS — this one does not centre. */
.section-logos-list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: start;
    gap: var(--space-6);
    margin: 0;
    padding: 0;
    list-style: none;
}

.section-logos-item {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* The mark itself. Two constraints have to hold at once.
   (1) block-size is DEFINITE, never auto. Most logo SVGs ship with a viewBox and no width/height attributes, so
       they have an intrinsic RATIO but no intrinsic SIZE — sized `auto` on both axes they collapse and the
       strip renders empty, with no error anywhere. One definite axis is what makes such a mark resolve.
   (2) Real marks arrive at wildly different ratios (a wordmark can be 11:1 beside a square monogram), so the
       inline axis is capped: at a definite height an 11:1 wordmark would otherwise run several hundred px wide.
   object-fit then letterboxes whatever does not fit, so nothing is ever distorted, and the min() keeps the cap
   from overflowing a narrow grid tile. */
.section-logos-mark {
    display: block;
    block-size: var(--section-logos-mark-block-size);
    inline-size: auto;
    max-inline-size: min(var(--section-logos-mark-max-inline-size), 100%);
    object-fit: contain;
}

.section-logos-link {
    display: flex;
}

/* --- The quiet treatment: on by default, colour on hover/focus ------------------------------------------- */

.section-logos-is-quiet .section-logos-mark {
    filter: grayscale(1);
    opacity: 0.7;
    transition: filter 150ms ease, opacity 150ms ease;
}

/* Only a LINKED mark restores colour — an unlinked one has no interaction to reward, so it stays quiet. */
.section-logos-is-quiet .section-logos-link:hover .section-logos-mark,
.section-logos-is-quiet .section-logos-link:focus-visible .section-logos-mark {
    filter: grayscale(0);
    opacity: 1;
}

/* --- The grid variant: bordered tiles (the logo wall) ---------------------------------------------------- */

.section-logos-grid .section-logos-list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
}

.section-logos-grid .section-logos-item {
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--section-logos-border-radius);
    background: var(--surface);
}

/* --- Width enhancements: the section's OWN box, min-width polarity, standardized -------------------------- */

@container (min-width: 640px) {
    .section-logos-list {
        gap: var(--space-10);
    }

    .section-logos-grid .section-logos-list {
        grid-template-columns: repeat(3, 1fr);
        gap: var(--space-4);
    }

    .section-logos-grid .section-logos-item {
        padding: var(--space-6);
    }
}

@container (min-width: 1024px) {
    .section-logos-grid .section-logos-list {
        grid-template-columns: repeat(4, 1fr);
    }
}

/* --- Motion is an enhancement, never a requirement -------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .section-logos-is-quiet .section-logos-mark {
        transition: none;
    }
}

/* @toc Section pricing */
/*
 * x-section-pricing — the plan-card shape that fills the x-page-section chassis's items grid (spec §6).
 *
 * SECTION-AUTHORING LAW: mobile-first + container-first. The base rules ARE the 375 layout (one column — the
 * chassis's items grid is 1-col until its own width earns more); the desktop delta is a pure CONTAINER response,
 * carried by clamp() keyed to `cqi` (the chassis owns container-type, so cqi resolves against the SECTION's
 * inline size — fluid padding/type with no --bp-*, no @media, no viewport assumption). ONE immutable DOM: this
 * styles the plan cards as descendants; nothing here changes structure by width.
 *
 * COLOUR: role tokens only — text --text / --text-muted, border --line, fill --surface, the highlight RING
 * --primary (a non-text 2px outline, judged against the 3:1 non-text threshold). No hand-authored colour.
 */

.section-pricing-plan {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    /* radius 8 (the container knob), 1px rule, fluid padding 24 → 40 (the spec's 24 → 32 → 40 as a container
       curve). position:relative anchors the highlight badge. */
    position: relative;
    padding: clamp(1.5rem, 1.25rem + 2cqi, 2.5rem);
    border: 1px solid var(--line);
    border-radius: var(--section-pricing-border-radius);
    background: var(--surface);
}

/* HIGHLIGHT: a 2px inset --primary ring AND a badge (never colour alone). Inset box-shadow, so it adds no box
   and causes no layout shift between a plain and a featured plan. */
.section-pricing-plan.is-highlight {
    box-shadow: inset 0 0 0 2px var(--primary);
}

/* The "Popular" / per-plan badge sits at the top of the card, before the title in the reading order. */
.section-pricing-badge {
    align-self: flex-start;
}

.section-pricing-title {
    margin: 0;
    font-size: clamp(1.5rem, 1.35rem + 1.2cqi, 1.875rem);   /* 24 → 30 */
    font-weight: 600;
    color: var(--text);
}

.section-pricing-description {
    margin: 0;
    font-size: var(--text-base);
    color: var(--text-muted);
}

/* The price line: current amount is the loud figure; a struck former price sits before it; the period trails. */
.section-pricing-price {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    margin: 0;
}

.section-pricing-amount {
    font-size: clamp(1.875rem, 1.65rem + 1.8cqi, 2.25rem);   /* 30 → 36 */
    font-weight: 600;
    color: var(--text);
}

.section-pricing-was {
    font-size: clamp(1.25rem, 1.15rem + 0.8cqi, 1.5rem);   /* 20 → 24, struck */
    color: var(--text-muted);
}

.section-pricing-period {
    font-size: var(--text-base);
    color: var(--text-muted);
}

.section-pricing-billing {
    margin: 0;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--text-muted);
}

.section-pricing-features {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.section-pricing-feature {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* The ✓ glyph — 20px, --primary; decorative (aria-hidden on the wrapper), the label carries the meaning. */
.section-pricing-check {
    display: inline-flex;
    flex-shrink: 0;
    width: 1.25rem;
    height: 1.25rem;
    color: var(--primary);
}

.section-pricing-feature-label {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* The CTA pins to the card bottom so buttons line up across plans of uneven copy, and spans the card width. */
.section-pricing-cta {
    margin-block-start: auto;
    justify-content: center;
    width: 100%;
}

.section-pricing-terms {
    margin: 0;
    font-size: var(--text-xs);
    line-height: 1.7;
    color: var(--text-muted);
}

/* @toc Section services */
/*
 * x-section-services — the "what we offer" band's ITEM SHAPE (#1898). The x-page-section chassis owns the header
 * grammar AND the responsive items grid (1 → 2 → 3 columns on the section's own width), so there is deliberately
 * NO grid CSS here — composing the chassis with zero edits is the whole point of the pattern.
 *
 * Section-Authoring Law: the base rules ARE the 375 layout; every desktop delta is a pure @container (min-width)
 * enhancement resolving against the chassis's .page-section container context, so the section reflows on its OWN
 * box and never viewport-assumes (no --bp-*, no @media). ONE immutable DOM at every width.
 *
 * ★ THE ONE RULE WORTH READING BEFORE EDITING ANYTHING BELOW — THE BODY SWAP AT 640. The item ships BOTH a
 * `summary` and a `points` list and this stylesheet chooses: the summary shows while the grid is a SINGLE COLUMN,
 * the list from two columns up. 640 is not a magic number and is not a viewport breakpoint — it is the chassis's
 * own 1 → 2 column threshold (page-section.css), so the rule states exactly "narrow layouts get the sentence,
 * wide ones get the detail" and cannot drift away from the grid it describes.
 *
 * It queries the CHASSIS's box, NOT the card's own, and that is deliberate. A card is ~335px wide on a phone and
 * ~380px wide in a three-across desktop grid — 45px apart — so the card's own width cannot separate the two
 * cases in any meaningful way, while the section's width separates them cleanly. Do not "correct" this to a
 * per-card container query: it would show the summary on desktop, which is the opposite of the intent.
 *
 * display:none, never a clip or a visually-hidden: a display:none subtree leaves the ACCESSIBILITY tree as well,
 * so exactly one body is announced. Any hide-visually technique here would announce both.
 *
 * Colour is semantic tokens only (--surface, --line, --text, --text-muted, --link, --primary), so the section
 * themes per tenant in both schemes, and the two hover deltas are picked per ROLE: the title is real text and
 * uses --link (rung-stepped per theme to clear 4.5:1), the border is non-text and uses --primary (judged at
 * 3:1). The shape this was promoted from washed the card with 2% --neutral-950 on hover; that is not carried,
 * because a fixed dark wash needs a per-theme flip to read at all in dark mode and the house hover delta is a
 * token colour change that flips itself.
 *
 * CHASSIS SURFACE TOUCHED: NONE — no --page-section-* knob is overridden and no .page-section-* anatomy class is
 * styled here. Every selector below is this section's OWN .section-services-* surface. A caller that wants a
 * different desktop column count sets --page-section-items-cols, the chassis's own documented knob.
 */

/* The item, linked or not. A linked item must not inherit the UA anchor look; its keyboard ring is the house
   :focus-visible outline from base.css (a real outline, so it survives forced-colors and follows the radius) —
   no ring is authored here. The flex column plus grid's stretch is what lets the affordance float to the card's
   base regardless of how much copy sits above it. */
.section-services-item {
    align-items: start;
    background-color: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    color: inherit;
    display: flex;
    flex-direction: column;
    padding: var(--space-4);
    text-decoration: none;
    transition: border-color 150ms;
}

@container (min-width: 640px) {
    .section-services-item {
        padding: var(--space-6);
    }
}

/* Drawn art, not a glyph, so it gets a box with room rather than the 20px icon square that would crush it.
   Deliberately NO `color`: a heroicon inherits currentColor, whereas a tokenised illustration carries its own
   var() fills and tinting the box would fight them. */
.section-services-illustration {
    block-size: var(--space-12);
    display: inline-flex;
    flex-shrink: 0;
    inline-size: var(--space-12);
}

.section-services-illustration svg {
    block-size: 100%;
    display: block;
    inline-size: 100%;
}

.section-services-title {
    color: var(--text);
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.5;
    margin: 0;
    transition: color 150ms;
}

.section-services-illustration + .section-services-title {
    margin-block-start: var(--space-3);
}

.section-services-summary {
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.6;
    margin: 0;
}

/* ---- THE BODY SWAP. See the header block: 640 is the chassis's own 1 → 2 column threshold, and the query is
   against the chassis's box on purpose. ---- */
.section-services-points {
    color: var(--text-muted);
    display: none;
    font-size: var(--text-sm);
    line-height: 1.6;
    margin: 0;
    padding-inline-start: var(--space-5);
}

@container (min-width: 640px) {
    .section-services-summary {
        display: none;
    }

    .section-services-points {
        display: block;
    }
}

.section-services-title + .section-services-summary,
.section-services-title + .section-services-points {
    margin-block-start: var(--space-4);
}

.section-services-points li + li {
    margin-block-start: var(--space-1);
}

/* A point's optional lead-in is the term it names, so it steps UP to full --text while the rest of the bullet
   stays muted — the contrast difference is what makes a scanned list readable. */
.section-services-points strong {
    color: var(--text);
    font-weight: 600;
}

/* The affordance. margin-block-start:auto floats it to the card's base whatever the copy above it does, and
   align-items:start on the item keeps the rule under the label rather than across the whole card. It is a
   <span>, so it is never a second tab stop — the whole card is already the one link. */
.section-services-more {
    align-items: center;
    border-block-end: 1px solid currentColor;
    color: var(--link);
    display: inline-flex;
    font-size: var(--text-sm);
    gap: var(--space-2);
    margin-block-start: auto;
    padding-block-start: var(--space-4);
}

.section-services-more svg {
    block-size: 1em;
    inline-size: 1em;
}

/* The hover deltas, both token-driven so both flip themselves per theme. --link on the title because that is
   real 16px/600 text and needs 4.5:1; --primary on the border because a border is non-text and is judged at
   3:1. Colour-only, so there is no motion to guard for reduced-motion. */
a.section-services-item:hover {
    border-color: var(--primary);
}

a.section-services-item:hover .section-services-title {
    color: var(--link);
}

/* @toc Section stats */
/*
 * x-section-stats — the stat band + the receipt variant (spec §4 of the marketing-section library). Authored
 * mobile-first + container-first (the Section-Authoring Law): every rule here IS the 375 base, and the only
 * desktop step is an @container (min-width) enhancement measured on the SECTION's own inline size — never the
 * viewport (no --bp-*, no @media, no matchMedia). The default variant adds NO grid of its own: the chassis's
 * .page-section-items already lays out 1 → 2 → 3 at gap 32, so this styles only what is inside a card. Type is
 * fluid via clamp() + cqi; every colour is a semantic token, so the band themes per tenant; box properties are
 * logical, so an RTL tenant reads correctly for free.
 */

/* --- Default variant: one card per stat, laid out by the chassis's items grid ---------------------------- */

.section-stats-card {
    display: flex;
    flex-direction: column;
    align-items: start;
    gap: var(--space-2);
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--section-stats-border-radius);
    background: var(--surface);
}

.section-stats-icon {
    display: inline-flex;
    flex: none;
    inline-size: var(--space-5);
    block-size: var(--space-5);
    color: var(--primary);
}

.section-stats-icon svg {
    inline-size: 100%;
    block-size: 100%;
}

/* The figure — the pricing-price scale (30 → 36), adopted as our stat scale and made fluid on the section's box. */
.section-stats-value {
    margin: 0;
    font-size: clamp(1.875rem, 1.65rem + 1cqi, 2.25rem);
    font-weight: 600;
    line-height: 1.1;
    color: var(--text);
}

.section-stats-label {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--text-muted);
}

/* The citation line — the source law made visible. Muted and small, but never absent. */
.section-stats-source {
    margin: 0;
    font-size: var(--text-xs);
    line-height: 1.5;
    color: var(--text-muted);
}

.section-stats-marker {
    margin-inline-end: var(--space-1);
    font-weight: 600;
}

.section-stats-trend {
    margin-inline-start: var(--space-2);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-muted);
}

/* --- Receipt variant: the band rendered as a receipt (the signature) ------------------------------------- */

.section-stats-receipt {
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--section-stats-border-radius);
    background: var(--surface);
}

.section-stats-rows {
    margin: 0;
}

/* Ruled line-items: a dashed rule between rows; the last row is unruled so the band closes clean. */
.section-stats-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding-block: var(--space-3);
    border-block-end: 1px dashed var(--line);
}

.section-stats-row:first-child {
    padding-block-start: 0;
}

.section-stats-row:last-child {
    padding-block-end: 0;
    border-block-end: 0;
}

.section-stats-row-label {
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--text-muted);
}

/* Mono figures, aligned to the end — the receipt's tell. `end`, not `right`, so RTL reads correctly. */
.section-stats-row-value {
    margin: 0;
    font-family: var(--font-mono);
    font-size: clamp(1.875rem, 1.65rem + 1cqi, 2.25rem);
    font-weight: 600;
    line-height: 1.1;
    text-align: end;
    color: var(--text);
}

/* The citations, set as receipt line-items. The ordered-list numbering IS each row's marker. */
.section-stats-sources {
    margin-block-start: var(--space-4);
    padding-block-start: var(--space-4);
    border-block-start: 1px dashed var(--line);
}

.section-stats-sources-title {
    margin: 0 0 var(--space-2);
    font-size: var(--text-xs);
    line-height: 1.4;
    color: var(--text-muted);
}

.section-stats-sources-list {
    margin: 0;
    padding-inline-start: var(--space-5);
    font-size: var(--text-xs);
    line-height: 1.6;
    color: var(--text-muted);
}

/* --- The one desktop enhancement: the section's own box crosses 640 → the roomier padding step (16 → 24). --- */

@container (min-width: 640px) {
    .section-stats-card {
        padding: var(--space-6);
    }

    .section-stats-receipt {
        padding: var(--space-6);
    }
}

/* @toc Section steps */
/*
 * x-section-steps — the how-it-works band. Authored mobile-first + container-first (the Section-Authoring Law):
 * every rule here IS the 375 base — a vertical run of steps — and the horizontal rail is the one @container
 * (min-width) enhancement, measured on the SECTION's own inline size, never the viewport (no --bp-*, no width
 * @media, no matchMedia).
 *
 * THE NUMBER IS A COUNTER, NOT CONTENT. `counter-increment` on the item and `content: counter(...)` on its
 * ::before means the badge always shows the step's real position: reorder the data, or delete a step, and the
 * rest renumber themselves because no number is stored anywhere. The <ol> carries the same ordering to
 * assistive technology, so the two can never disagree.
 *
 * Both variants style the SAME DOM and differ only in which axis the run and its connector follow. Colour is
 * semantic tokens only, so the band themes per tenant; box properties are logical, so an RTL tenant reads
 * correctly for free — including the connector, which follows the inline axis rather than "left".
 */

.section-steps-list {
    counter-reset: section-steps;
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    margin: 0;
    padding: 0;
    /* The marker is removed because the badge replaces it — which is exactly why the view restates
       role="list" (Safari drops list semantics from a markerless list). */
    list-style: none;
}

/* The step sits in a column beside its badge: the badge is absolutely placed in the gutter this padding
   reserves, so a description of any length wraps against the title rather than under the number. */
.section-steps-item {
    counter-increment: section-steps;
    position: relative;
    padding-inline-start: calc(var(--section-steps-badge-size) + var(--space-4));
}

.section-steps-item::before {
    content: counter(section-steps);
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--section-steps-badge-size);
    block-size: var(--section-steps-badge-size);
    border: 1px solid var(--line);
    border-radius: var(--section-steps-badge-border-radius);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* THE CONNECTOR — the thread that makes a list of cards read as a sequence. Drawn from the badge's centre line
   down into the gap below, so it meets the next badge; the last step has none, because a sequence that appears
   to continue past its end is a lie about the process. Decorative, and generated content, so it is never in the
   accessibility tree — the <ol> is what communicates the ordering. */
.section-steps-item:not(:last-child)::after {
    content: '';
    position: absolute;
    inset-block-start: var(--section-steps-badge-size);
    inset-block-end: calc(var(--space-8) * -1);
    inset-inline-start: calc(var(--section-steps-badge-size) / 2);
    border-inline-start: 1px solid var(--line);
}

/* THE PICTURE TIERS SHARE ONE BOX, because illustration and image differ in DELIVERY, not in layout — an
   inlined <svg> and a referenced <img> must occupy the same space, or a band would reflow depending on which
   tier a step happened to use. Sized to the column rather than to a fixed box, so it stays right when the
   horizontal rail turns each step into a narrower one. */
.section-steps-media {
    display: block;
    /* BOUNDED, because a step's picture is a LEADING VISUAL and not a hero. In the vertical run a step spans the
       whole section, so an unbounded picture renders metres tall and buries the sequence it was meant to
       introduce — caught in a render, not by any gate. Structural like the gaps and the connector, so
       deliberately NOT a facet knob. */
    max-inline-size: 280px;
    margin-block-end: var(--space-3);
}

.section-steps-media img,
.section-steps-media svg {
    inline-size: 100%;
    block-size: auto;
    border-radius: var(--section-steps-media-border-radius);
}

.section-steps-icon {
    display: inline-flex;
    inline-size: var(--space-5);
    block-size: var(--space-5);
    margin-block-end: var(--space-2);
    color: var(--primary);
}

.section-steps-icon svg {
    inline-size: 100%;
    block-size: 100%;
}

.section-steps-title {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.3;
    color: var(--text);
}

.section-steps-description {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--text-muted);
}

/* --- The horizontal rail: the one width enhancement, on the section's OWN box --------------------------- */

@container (min-width: 720px) {
    /* The steps become columns and the badge moves above the text, so the connector can run ALONG the rail.
       The DOM is untouched — this is the same ordered list, laid out on the other axis. */
    .section-steps-horizontal .section-steps-list {
        grid-auto-flow: column;
        grid-auto-columns: 1fr;
        gap: var(--space-6);
    }

    .section-steps-horizontal .section-steps-item {
        padding-inline-start: 0;
        padding-block-start: calc(var(--section-steps-badge-size) + var(--space-4));
    }

    /* The rail's columns are already narrow, so the picture takes its column instead of the vertical cap —
       which is what makes the three-across approach band read as one row of equals. */
    .section-steps-horizontal .section-steps-media {
        max-inline-size: none;
    }

    /* The connector turns with the layout: it now runs from the badge's edge across the gap to the next step's
       badge, along the badge's own centre line. */
    .section-steps-horizontal .section-steps-item:not(:last-child)::after {
        inset-block-start: calc(var(--section-steps-badge-size) / 2);
        inset-block-end: auto;
        inset-inline-start: var(--section-steps-badge-size);
        inset-inline-end: calc(var(--space-6) * -1);
        border-inline-start: 0;
        border-block-start: 1px solid var(--line);
    }
}

/* @toc Section team */
/*
 * x-section-team — the people grid (the about-page staple). Authored mobile-first + container-first (the
 * Section-Authoring Law): every rule here IS the 375 base — one column — and the only width steps are
 * @container (min-width) enhancements measured on the SECTION's own inline size, never the viewport (no
 * --bp-*, no @media, no matchMedia).
 *
 * Both variants style the SAME DOM — the .section-team-list of cards — so the skin choice never forks the
 * markup: `default` stacks the portrait above the name, `compact` sets it beside, denser and more per row.
 * Colour is semantic tokens only, so the roster themes per tenant; box properties are logical, so an RTL
 * tenant reads correctly for free.
 */

/* --- The roster: one DOM, its own grid (the chassis body slot holds it) ---------------------------------- */

/* Tracks size INTRINSICALLY rather than stepping through fixed column counts. A person card is narrow (a
   portrait over two short lines), so three equal thirds of a wide section strand each card in a column twice
   its width and the roster reads as scattered rather than composed — visible immediately in a truth shot, and
   invisible to every gate. auto-fill packs as many tracks of at least --section-team-column-min-inline-size as
   fit, so the cards sit next to each other at every width and a short roster clusters instead of spreading.
   This is still container-driven — more so than a fixed step ladder — and it collapses to ONE column at 320
   without a query. The min() guard stops a track exceeding a container narrower than the minimum. */
.section-team-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(var(--section-team-column-min-inline-size), 100%), 1fr));
    gap: var(--space-8);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* --- One person ------------------------------------------------------------------------------------------ */

/* A grid, not a flex column, for ONE reason: the portrait row is declared even when a person has no portrait,
   so a photoless entry's name still lines up with the named faces beside it. Without it a mixed roster
   staircases — the defect x-section-testimonials hit with the same data shape. The reserve is scoped by :has()
   to a band that ACTUALLY has portraits, so an all-text roster pays nothing for it. */
.section-team-card {
    display: grid;
    gap: var(--space-3);
    align-content: start;
}

/* The reserve applies ONLY once two tracks can fit — 440px is the section box at which 2 × the track minimum
   plus the gap clears the chassis gutters. Below that the roster is a single column, where there is no
   neighbour to line up WITH, and reserving the row just punches a portrait-sized hole above a photoless
   person's name. Aligning to a row that does not exist is not alignment; it is a gap. Found in a 320 truth
   shot, not by any gate. */
@container (min-width: 440px) {
    .section-team:has(.section-team-avatar) .section-team-card {
        grid-template-rows: var(--section-team-avatar-size) auto;
    }

    /* Pinned to the second row so the credit never slides up into the reserved portrait row when the portrait
       is absent — which is the whole point of reserving it. */
    .section-team:has(.section-team-avatar) .section-team-credit {
        grid-row: 2;
    }
}

.section-team-avatar {
    inline-size: var(--section-team-avatar-size);
    block-size: var(--section-team-avatar-size);
    border-radius: var(--section-team-avatar-border-radius);
    object-fit: cover;
    background: var(--surface);
}

.section-team-name {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.3;
    color: var(--text);
}

.section-team-role {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--text-muted);
}

/* --- The compact variant: portrait beside the name, denser ----------------------------------------------- */

/* A horizontal card needs a wider track than a stacked one. The whole track expression is restated rather than
   reassigning the base custom property through a second one: a var whose only job is to alias another var
   reads as indirection, and the naming gate cannot see through it to tell whether the aliased key is used at
   all. Each key is now referenced where it means something. */
.section-team-compact .section-team-list {
    grid-template-columns: repeat(auto-fill, minmax(min(var(--section-team-compact-column-min-inline-size), 100%), 1fr));
    gap: var(--space-6);
}

/* Flex overrides the grid wholesale, so the reserved portrait row does not apply here — a horizontal card has
   no staircase to prevent. */
.section-team-compact .section-team-card {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.section-team-compact .section-team-avatar {
    inline-size: var(--section-team-avatar-compact-size);
    block-size: var(--section-team-avatar-compact-size);
    flex: none;
}

/* --- Width: no query needed ------------------------------------------------------------------------------
   The roster has no @container step because the grid above already reflows on the section's OWN inline size —
   auto-fill IS the container response, and it is continuous rather than stepped. The Section-Authoring Law's
   ban is on viewport assumptions (--bp-*, width @media, matchMedia), and there are none here; a section that
   needs no breakpoint to reflow correctly is the law's intent reached, not skipped. */

/* @toc Section testimonials */
/*
 * x-section-testimonials — the social-proof band + the featured variant. Authored mobile-first + container-first
 * (the Section-Authoring Law): every rule here IS the 375 base, and the only desktop steps are @container
 * (min-width) enhancements measured on the SECTION's own inline size — never the viewport (no --bp-*, no @media,
 * no matchMedia). The default variant adds NO grid of its own: the chassis's .page-section-items already lays out
 * 1 → 2 → 3 at gap 32, so this styles only what is inside a card. Type is fluid via clamp() + cqi; every colour is
 * a semantic token, so the band themes per tenant; box properties are logical, so an RTL tenant reads correctly
 * for free.
 */

/* --- The card: shared by both variants, so a quote looks like a quote wherever it sits ------------------- */

.section-testimonials-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--section-testimonials-border-radius);
    background: var(--surface);
}

.section-testimonials-blockquote {
    margin: 0;
    /* The quote sets its own measure so a long testimonial never runs past a comfortable line length, even in
       the featured variant where the card is the full content column. */
    max-width: var(--max-w-2xl);
}

.section-testimonials-text {
    margin: 0;
    font-size: clamp(1rem, 0.95rem + 0.4cqi, 1.125rem);
    line-height: 1.6;
    text-wrap: pretty;
    color: var(--text);
}

/* --- The attribution: who said it. Never optional — the component throws without a name. ----------------- */

.section-testimonials-attribution {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    /* Pushes the attribution to the bottom of a card whose neighbours hold longer quotes, so a row of cards
       lines its names up instead of floating them at ragged heights. */
    margin-block-start: auto;
}

/* Bottom-aligning is only half of it: in a band where SOME rows carry a portrait and some do not, the taller
   avatar block and the shorter text-only block bottom-align at different name heights, and the row reads
   ragged. So when the band contains any portrait at all, every attribution reserves the portrait's height and
   the names line up across the row. Scoped by :has() to the band's own root, so a band with no portraits pays
   nothing for the rule. */
.section-testimonials:has(.section-testimonials-avatar) .section-testimonials-attribution {
    min-block-size: var(--space-12);
}

.section-testimonials-avatar {
    flex: none;
    inline-size: var(--space-12);
    block-size: var(--space-12);
    border-radius: 50%;
    object-fit: cover;
}

.section-testimonials-credit {
    min-inline-size: 0;
}

.section-testimonials-name {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1.4;
    color: var(--text);
}

.section-testimonials-role {
    margin: 0;
    font-size: var(--text-xs);
    line-height: 1.5;
    color: var(--text-muted);
}

/* --- Featured variant: one stacked column, the lead quote set large ------------------------------------- */

/* One column on a phone: the lead, then each supporting quote beneath it. The desktop enhancement below sets
   the supporting quotes side by side, which is why this is a grid rather than a flex column — the DOM is the
   same at every width, and only the track count changes. This is the featured variant's OWN layout in the
   chassis's freeform body region (the receipt precedent); the chassis's items grid is untouched. */
.section-testimonials-featured .section-testimonials-stack {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

/* The lead is the same card with a bigger voice: no border or surface, because a pull-quote that sits in a box
   reads as one item in a list rather than as the thing the band is saying. */
.section-testimonials-card.is-lead {
    gap: var(--space-6);
    padding: 0;
    border: 0;
    background: none;
}

.section-testimonials-card.is-lead .section-testimonials-text {
    font-size: clamp(1.25rem, 1.05rem + 1.4cqi, 1.875rem);
    font-weight: 500;
    line-height: 1.35;
    text-wrap: balance;
}

.section-testimonials-card.is-lead .section-testimonials-avatar {
    inline-size: var(--space-16);
    block-size: var(--space-16);
}

.section-testimonials-card.is-lead .section-testimonials-name {
    font-size: var(--text-base);
}

.section-testimonials-card.is-lead .section-testimonials-role {
    font-size: var(--text-sm);
}

/* --- The desktop enhancements, both measured on the section's OWN box (min-width polarity) ---------------- */

@container (min-width: 640px) {
    .section-testimonials-card {
        padding: var(--space-6);
    }

    /* The supporting quotes go side by side once there is room, so the featured band fills its width instead of
       leaving a column of short cards in a wide space. The lead keeps the full measure by spanning every track,
       which is also what makes this hold at any track count. */
    .section-testimonials-featured .section-testimonials-stack {
        grid-template-columns: repeat(2, 1fr);
    }

    .section-testimonials-card.is-lead {
        grid-column: 1 / -1;
        padding: 0;
    }
}

/* @toc Select */
/* Select. All --select-* tokens come from config/components/select.php['css'].
 * We restyle only the box (appearance:none) and overlay a chevron — the native
 * option list, keyboard, and typeahead are left to the platform. */

.select {
    margin-bottom: 14px;
}

.select-label {
    display: block;
    margin-bottom: 6px;
    color: var(--select-label-color);
    font-size: var(--select-label-font-size);
}

/* The box wraps the field so the chevron can sit over it. The chevron itself is
 * a masked pseudo-element, so its colour is a real token (var) and themes with
 * the page — a background-image data-URI can't read CSS vars. pointer-events are
 * off so clicks fall through to the native control underneath. */
.select-box {
    position: relative;
    display: block;
}

.select-box::after {
    content: "";
    position: absolute;
    top: 50%;
    right: var(--select-padding-x);
    width: 16px;
    height: 16px;
    transform: translateY(-50%);
    pointer-events: none;
    background-color: var(--select-chevron);
    -webkit-mask: var(--select-chevron-image) center / contain no-repeat;
    mask: var(--select-chevron-image) center / contain no-repeat;
}

.select-field {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    /* Extra right padding clears the chevron. */
    padding: var(--select-padding-y) calc(var(--select-padding-x) * 2 + 16px) var(--select-padding-y) var(--select-padding-x);
    border: 1px solid var(--select-border);
    border-radius: var(--select-border-radius);
    background: var(--select-background-color);
    color: var(--select-color);
    font-size: var(--select-font-size);
    font-family: var(--font-sans);
    line-height: 1.2;
    cursor: pointer;
}

/* The placeholder (disabled first option) reads as muted while it's the value. */
.select-field:has(option[value=""]:checked) {
    color: var(--text-muted);
}

/* Native option list: paint it from tokens so it isn't bright-white on dark UAs
 * that honour these (most do not, and that's fine — the box is the surface we own). */
.select-field option {
    background: var(--select-background-color);
    color: var(--text);
}

/* Invalid affordance — the categorical --danger token (red-700 light / red-400 dark),
 * self-flipping, so the border clears 3:1 on each theme without a per-theme rule. */
.select-field[aria-invalid="true"] {
    border-color: var(--danger);
}

.select-error {
    margin-top: 6px;
    font-size: var(--text-xs);
    color: var(--danger);
}

.select-required {
    margin-left: 2px;
    color: var(--danger);
}

/* Keyboard focus recolors the border as a secondary cue; the global :focus-visible
 * ring (base.css) supplies the primary indicator. :focus-visible so a mouse open
 * doesn't trigger it. */
.select-field:focus-visible {
    border-color: var(--select-focus-border);
}

.select-field:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* @toc Sidebar nav */
/* Sidebar nav — brand (top), nav items (middle, scrolls), footer (bottom: theme + logout). */

.sidebar-nav {
    width: var(--sidebar-nav-width, 240px);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--page-bg);
    border-inline-end: 1px solid var(--line);
}

/* THE HEAD (#1490) — `[mark · wordmark …space… hamburger]`. One control governs this sidebar at every
   breakpoint: on mobile the hamburger closes the drawer, on desktop it collapses the rail. */
.sidebar-nav-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 20px 16px 12px;
}

.sidebar-nav-brand {
    display: block;
    /* The head owns the padding now, so the brand carries none — it used to be the head. */
    flex: 1;
    min-width: 0;
    line-height: 0;
    text-decoration: none;
}

/* The square tier, shown only when the rail is collapsed. Both marks render; CSS chooses, because the
   collapse is a class toggle with no server round-trip to re-render a different one. */
.sidebar-nav-brand-mark {
    display: none;
    line-height: 0;
}

/* ★ BARE GLYPH, same law as the bar's hamburger — no box, no border, no plate, in any state, and no tap
   highlight rectangle. :focus-visible only, so keyboard users keep a ring and pointer users never see one. */
.sidebar-nav-burger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    margin-inline-end: -6px;
    padding: 0;
    appearance: none;
    border: 0;
    background: none;
    color: var(--text-muted);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.sidebar-nav-burger:hover {
    color: var(--text);
}

.sidebar-nav-burger:focus {
    outline: none;
}

.sidebar-nav-burger:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.sidebar-nav-burger svg {
    width: 20px;
    height: 20px;
}

/* The mark's SIZE is not set here. It is one rule covering this slot and the phone bar's, in
   brand-logo.css — the mark is one size in both, so there is one number in one place (#1473). */

/* Authorized list and scoped-list styling lives in navigation-items.css. This sheet adapts that renderer to
   the shell rail only: collapse, drawer, transient flyouts, footer, and width persistence. A consumer that
   mounts the renderer in a panel does not inherit any of these presentation rules. */

/* Legacy shell consumers may still provide a hand-authored list directly instead of the shared renderer. */
.sidebar-nav > .sidebar-nav-list {
    flex: 1;
    overflow-y: auto;
    margin: 0;
    padding: 4px 10px;
    list-style: none;
}

/* The unchanged universal footer and admin secondary row are adapter chrome, not renderer content. They
   continue to use these legacy classes, so keep their 20px glyph slot and flexible label here. */
.sidebar-nav-icon {
    display: inline-flex;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.sidebar-nav-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}

.sidebar-nav-label {
    flex: 1;
}

/* Footer — theme controls, user, and logout. Each is a uniform [icon] [label]
 * row that matches the nav items above: the same 20px .sidebar-nav-icon, the same
 * text size, the same left alignment, so every icon and label lines up. Theme
 * adds its toggle + dropper on the right; logout is its own clickable row.
 * Stays position: relative so the theme-colors popup anchors to the footer. */
.sidebar-nav-footer {
    position: relative;
    margin-top: auto;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 8px 10px;
    border-top: 1px solid var(--line);
}

.sidebar-nav-footer-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 40px;
    padding: 0 12px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--text-muted);
    font-family: inherit;
    font-size: var(--text-sm);
    text-align: start;
    /* The Admin row is an <a> - kill the default underline in every state so it
     * reads like the button rows beside it (no link/visited decoration). */
    text-decoration: none;
}

.sidebar-nav-footer-row .sidebar-nav-label {
    flex: 1;
    min-width: 0;
}

.sidebar-nav-username {
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sidebar-nav-footer-controls {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

.sidebar-nav-footer-form {
    margin: 0;
}

.sidebar-nav-footer-action {
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.sidebar-nav-footer-action:hover {
    color: var(--text);
    background: var(--line);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE FOOTER STASH (#1490)
 *
 * Six rows (~270px of rail) became one: `[avatar · James …space… +]`. Pressing + reveals the rest ABOVE the
 * row and the control becomes − (never an ×: this collapses, it does not dismiss).
 *
 * The stash's closed state is the `hidden` ATTRIBUTE, set in the markup and toggled by sidebar-nav.js, not a
 * display rule here — so the rows leave the tab order and the a11y tree with the reveal. A CSS-only hide
 * would leave a keyboard user tabbing into rows that look shut.
 * ───────────────────────────────────────────────────────────────────────────── */

.sidebar-nav-stash {
    display: flex;
    flex-direction: column;
}

/* Belt and braces on top of the attribute: `display: flex` above would otherwise beat the UA's
   `[hidden] { display: none }`, which is the classic way a `hidden` element stays on screen. */
.sidebar-nav-stash[hidden] {
    display: none;
}

.sidebar-nav-user-row {
    gap: 10px;
}

.sidebar-nav-user-row .sidebar-nav-username {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Bare glyph, third instance of the same law. Sized to a comfortable touch target while drawing nothing. */
.sidebar-nav-stash-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    margin-inline-end: -6px;
    padding: 0;
    appearance: none;
    border: 0;
    background: none;
    color: var(--text-muted);
    font-size: var(--text-lg);
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.sidebar-nav-stash-toggle:hover {
    color: var(--text);
}

.sidebar-nav-stash-toggle:focus {
    outline: none;
}

.sidebar-nav-stash-toggle:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE COLLAPSED DESKTOP RAIL (#1490)
 *
 * The hamburger's desktop meaning. The rail narrows to icons, labels go, and the head restacks into a
 * COLUMN `[mark / burger]` — the choreography James locked in the lab: the burger slides under the logo,
 * which drops to its mark.
 *
 * Scoped to `body.nav-collapsed` and NOT applied inside the mobile media block, because on mobile the same
 * class means nothing — the sidebar there is a drawer that is either open or off-canvas, and a half-width
 * icon rail sliding in would be a third state nobody asked for. sidebar-nav.js drops the class when the
 * layout crosses into compact for exactly that reason.
 * ───────────────────────────────────────────────────────────────────────────── */

body.nav-collapsed .sidebar-nav {
    width: var(--sidebar-nav-collapsed-width, 64px);
}

body.nav-collapsed .sidebar-nav-head {
    flex-direction: column;
    gap: 6px;
    padding-inline: 0;
}

/* The wordmark goes, the mark arrives. */
body.nav-collapsed .sidebar-nav-brand {
    display: none;
}

body.nav-collapsed .sidebar-nav-brand-mark {
    display: block;
}

body.nav-collapsed .sidebar-nav-burger {
    margin-inline-end: 0;
}

/* NavigationItems owns the destination row and grouped flyout rail presentation. The legacy adapter retains
   only its shell-specific footer rows and the mark/hamburger geometry above. */
body.nav-collapsed .sidebar-nav-footer-row {
    justify-content: center;
    padding-inline: 0;
}

body.nav-collapsed .sidebar-nav-label {
    display: none;
}

/* The footer degrades to avatar-only with the same toggle, per the spec. The toggle keeps its own row so it
   stays reachable — a collapsed rail that could not be un-stashed would strand Log out. */
body.nav-collapsed .sidebar-nav-user-row {
    flex-wrap: wrap;
    gap: 4px;
}

body.nav-collapsed .sidebar-nav-stash-toggle {
    margin-inline-end: 0;
}

/* A direct Projects route renders its authorized scope on the server. If JavaScript is unavailable at the
   compact seam, keep that chooser on canvas instead of leaving the shell's closed drawer transform as the
   only copy. The island marks enhanced navigation before it starts; enhanced mobile therefore retains the
   normal drawer, while this fallback stays a bounded, independently scrolling chooser above the shared bar. */
@media (max-width: 768px), (max-height: 600px) {
    .sidebar-nav:not([data-nav-enhanced]):has([data-nav-scope]:not([hidden])) {
        position: static;
        width: 100%;
        max-width: none;
        height: calc(100dvh - var(--app-bottom-bar-occupied-block-end, 0px));
        min-height: min(360px, calc(100dvh - var(--app-bottom-bar-occupied-block-end, 0px)));
        transform: none;
        visibility: visible;
        transition: none;
        z-index: auto;
    }

    .sidebar-nav:not([data-nav-enhanced]):has([data-nav-scope]:not([hidden])) .sidebar-nav-burger {
        display: none;
    }

    /* A flush app-chrome Project still needs its package viewport. Share the fixed compact viewport between
       a bounded chooser and the existing shell wrap; do not let either region push the other off canvas. */
    body.app-shell:has(> .app-shell-wrap > .app-shell-main-flush)
        > .sidebar-nav:not([data-nav-enhanced]):has([data-nav-scope]:not([hidden])) {
        height: min(42dvh, 300px);
        min-height: 160px;
        border-block-end: 1px solid var(--line);
    }

    body.app-shell:has(> .sidebar-nav:not([data-nav-enhanced]):has([data-nav-scope]:not([hidden])))
        > .app-shell-wrap:has(> .app-shell-main-flush) {
        flex: 1 1 0;
        height: auto;
        min-height: 0;
    }
}

/* @toc Skeleton */
/*
 * Skeleton — a loading placeholder. A neutral filled shape (text line / block / avatar circle) that pulses
 * softly while content loads. SIZELESS by default — the consumer sets width/height (inline) to mock the
 * incoming layout. The fill is a neutral rung (--line, no ramp), and the pulse is GATED behind
 * prefers-reduced-motion: a visitor who prefers reduced motion sees a STATIC placeholder, never the
 * animation (the house-law win the source pattern lacked).
 */

.skeleton {
    display: block;
    background: var(--skeleton-fill, var(--line));
    border-radius: var(--skeleton-border-radius, var(--radius-control));
    animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes skeleton-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* House-law win: NO pulse under reduced motion — a static placeholder, never the animation. */
@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
    }
}

/* ---- Block (the default): a rectangle, house 8px container corner. width/height come from the props. ---- */
.skeleton-block {
    width: 100%;
    height: 1rem;
    border-radius: var(--radius-container);
}

/* ---- Avatar: a circle. ---- */
.skeleton-avatar {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius-full);
}

/* ---- Text: a stack of pill lines (a paragraph placeholder); the width prop sizes the stack. ---- */
.skeleton-text {
    display: flex;
    flex-direction: column;
    gap: 0.55em;
}

.skeleton-line {
    height: 0.7em;
    width: 100%;
    border-radius: var(--radius-full);
}

/* The last line of a multi-line block is ragged (shorter), like real text. */
.skeleton-line.is-last {
    width: 60%;
}

/* @toc Slider */
/*
 * Slider — themed native <input type="range">. All --slider-* tokens come from
 * config/components/slider.php['css']. We only paint the track + thumb; the
 * browser keeps the drag, keyboard stepping, and value semantics. Track/thumb
 * pseudo-elements must each get their own rule — a combined -webkit-/-moz-
 * selector list is dropped wholesale if the engine doesn't know one half.
 */

.slider {
    margin-bottom: 14px;
}

.slider-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}

.slider-label {
    color: var(--slider-label-color);
    font-size: var(--slider-label-font-size);
}

.slider-value {
    color: var(--slider-value-color);
    font-size: var(--slider-value-font-size);
    font-variant-numeric: tabular-nums;
}

/* Reset the UA chrome so the track + thumb rules below are the whole surface. */
.slider-range {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: var(--slider-thumb-size);
    margin: 0;
    background: transparent;
    cursor: pointer;
}

/* Track — WebKit/Blink. */
.slider-range::-webkit-slider-runnable-track {
    height: var(--slider-track-height);
    border-radius: var(--slider-track-border-radius);
    background: var(--slider-track-background-color);
}

/* Track — Firefox. */
.slider-range::-moz-range-track {
    height: var(--slider-track-height);
    border-radius: var(--slider-track-border-radius);
    background: var(--slider-track-background-color);
}

/* Filled portion before the thumb — Firefox only (WebKit has no native fill). */
.slider-range::-moz-range-progress {
    height: var(--slider-track-height);
    border-radius: var(--slider-track-border-radius);
    background: var(--slider-fill);
}

/* Thumb — WebKit/Blink. margin-top recentres it on the thinner track. */
.slider-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: var(--slider-thumb-size);
    height: var(--slider-thumb-size);
    margin-top: calc((var(--slider-track-height) - var(--slider-thumb-size)) / 2);
    border: var(--slider-thumb-border-width) solid var(--slider-thumb-border);
    border-radius: 50%;
    background: var(--slider-thumb-background-color);
    transition: box-shadow 0.15s ease, background 0.15s ease;
}

/* Thumb — Firefox. */
.slider-range::-moz-range-thumb {
    width: var(--slider-thumb-size);
    height: var(--slider-thumb-size);
    border: var(--slider-thumb-border-width) solid var(--slider-thumb-border);
    border-radius: 50%;
    background: var(--slider-thumb-background-color);
    transition: box-shadow 0.15s ease, background 0.15s ease;
}

/* Keyboard focus ring on the thumb — mirrors the button :focus-visible ring.
   The transparent outline is the forced-colors fallback (box-shadow is dropped
   in high-contrast mode, so the outline gets promoted to a system colour). */
.slider-range:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
}

.slider-range:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 0 0 var(--slider-focus-ring-width) var(--slider-focus-ring);
}

.slider-range:focus-visible::-moz-range-thumb {
    box-shadow: 0 0 0 var(--slider-focus-ring-width) var(--slider-focus-ring);
}

.slider-range:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* @toc Sprite canvas */
/* Sprite canvas (#1476) — the authoring surface of the Sprite Editor: a tool bar with a zoom readout
 * above a checkerboard work area holding the 128x128 ruled box.
 *
 * The pixel grid is CSS, never drawn pixels — repeating gradients on the viewport's ::after, cell-sized
 * from --sprite-editor-cell, with the box outline as an inset shadow. That is the point: nothing here
 * can leak into the saved PNG. It switches off at 1x, where a 1px cell grid is solid noise, and
 * whenever the author turns the grid layer off. Zoom is integer-only by law (1x/2x/4x/8x), so sizes
 * come from --sprite-editor-canvas-size rather than any fit-to-box maths.
 *
 * ★ TWO DECLARATIONS ARE WHAT KEEP THIS USABLE ON A PHONE, AND FIXING ONE ALONE LOOKS EXACTLY AS
 * BROKEN (#1476 import fix, both marked inline): the single column is capped so it cannot size itself
 * to the 512px canvas, and the tool bar's toolbar is allowed to shrink below its content width.
 * Together they hand the overflow to the work area's own `overflow: auto`, so the canvas scrolls and
 * pans as intended instead of being clipped by .sprite-editor's `overflow: hidden`.
 *
 * View: sprite-canvas.blade.php; canvas chrome: canvas-surface.css; behaviour: sprite-editor.js. */
.sprite-canvas {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    /* Cap the single column (#1476 import fix). The rows were constrained but the implicit COLUMN was not, so
     * it sized to the widest child — the 512px (128 x 4) canvas plus its padding, 602px in all. Every grid item
     * then stretched to 602 inside a 342px phone, and `.sprite-editor`'s `overflow: hidden` clipped the excess.
     * `minmax(0, 1fr)` lets the column shrink, which is what finally hands the overflow to the work area's own
     * `overflow: auto` — so the canvas SCROLLS and pans as intended instead of being cut off. */
    grid-template-columns: minmax(0, 1fr);
    min-width: 0;
    min-height: 0;
    height: 100%;
    background: var(--sprite-canvas-background-color, var(--neutral-950));
}

.sprite-canvas-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-2);
    border-block-end: 1px solid var(--line);
}

/* The toolbar must then be allowed to SHRINK (#1476 import fix, the second half). Capping the column above is
 * not enough on its own: a flex item defaults to `min-width: auto`, so the toolbar still refuses to go below
 * its content width — 534px of tools inside a 342px bar — and overflows again. `toolbar.css` already ships
 * `overflow: auto`; this is the declaration that lets it do its job, making every tool reachable on a phone.
 * Fixing only one of the two leaves the surface looking exactly as broken, which is why both are here. */
.sprite-canvas-bar .toolbar {
    flex: 1 1 auto;
    min-width: 0;
}

.sprite-canvas-zoom {
    flex: 0 0 auto;
    min-width: 2.75rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    text-align: end;
}

.sprite-canvas-workarea {
    display: grid;
    place-items: center;
    min-height: var(--sprite-canvas-workarea-min-height, 32rem);
    overflow: auto;
    padding: var(--space-6);
    background-image:
        linear-gradient(45deg, color-mix(in srgb, var(--neutral-800) 45%, transparent) 25%, transparent 25%),
        linear-gradient(-45deg, color-mix(in srgb, var(--neutral-800) 45%, transparent) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, color-mix(in srgb, var(--neutral-800) 45%, transparent) 75%),
        linear-gradient(-45deg, transparent 75%, color-mix(in srgb, var(--neutral-800) 45%, transparent) 75%);
    background-position: 0 0, 0 8px, 8px -8px, -8px 0;
    background-size: 16px 16px;
}

.sprite-canvas .canvas-surface-viewport {
    position: relative;
    overflow: visible;
}

.sprite-canvas .canvas-surface-viewport::after {
    position: absolute;
    inset: 0;
    width: var(--sprite-editor-canvas-size, 512px);
    height: var(--sprite-editor-canvas-size, 512px);
    pointer-events: none;
    content: "";
    background-image:
        repeating-linear-gradient(
            to right,
            transparent 0 calc(var(--sprite-editor-cell, 4px) - 1px),
            color-mix(in srgb, var(--sprite-canvas-grid-minor-color, var(--neutral-800)) 55%, transparent)
                calc(var(--sprite-editor-cell, 4px) - 1px) var(--sprite-editor-cell, 4px)
        ),
        repeating-linear-gradient(
            to bottom,
            transparent 0 calc(var(--sprite-editor-cell, 4px) - 1px),
            color-mix(in srgb, var(--sprite-canvas-grid-minor-color, var(--neutral-800)) 55%, transparent)
                calc(var(--sprite-editor-cell, 4px) - 1px) var(--sprite-editor-cell, 4px)
        );
    box-shadow: inset 0 0 0 1px var(--sprite-canvas-grid-major-color, var(--primary));
}

.sprite-canvas .canvas-surface-canvas {
    width: var(--sprite-editor-canvas-size, 512px);
    height: var(--sprite-editor-canvas-size, 512px);
    cursor: crosshair;
}

.sprite-canvas[data-tool="pan"] .canvas-surface-canvas {
    cursor: grab;
}

.sprite-canvas[data-tool="anchor"] .canvas-surface-canvas {
    cursor: crosshair;
}

.sprite-canvas[data-layer-grid="false"] .canvas-surface-viewport::after {
    display: none;
}

.sprite-canvas[data-zoom="1"] .canvas-surface-viewport::after {
    background-image: none;
}

/* @toc Sprite editor */
/* Sprite editor (#1476) — the shell of the last step of the Generate flow (candidate → Aha → confirm →
 * prepare sprite): command bar, status line, the three-part workspace, and the mobile panel rail.
 *
 * MOBILE-FIRST, and the docks are where that shows. The left panel (abstraction + palette) and the
 * right one (inspector, origins, readiness) are absolutely-positioned overlays opened from the bottom
 * rail; only at the 60rem CONTAINER width do they become static columns of a grid, and the rail goes
 * away with them. One DOM at every width, rearranged by CSS alone.
 *
 * It answers to its own container rather than the viewport, so it previews truthfully inside the
 * editor's device frame and beside a dock — a landscape phone is wider than most desktop breakpoints
 * and a width-only query would hand a thumb the three-column layout.
 *
 * `overflow: hidden` on the root is what keeps the shell still while the canvas scrolls inside it, and
 * is therefore also why any child that cannot shrink gets CLIPPED rather than scrolled (see
 * sprite-canvas.css). The parent Frame region owns the available block size; this composition only divides
 * that definite region among its chrome, work area, and rail. View: sprite-editor.blade.php; document state +
 * save/stage/bless: sprite-editor.js. */
.sprite-editor {
    --sprite-editor-zoom: 4;
    --sprite-editor-box: 128;
    container-type: inline-size;
    position: relative;
    flex: 1 1 0;
    display: grid;
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    inline-size: 100%;
    block-size: 100%;
    min-inline-size: 0;
    min-block-size: 0;
    max-block-size: 100%;
    overflow: hidden;
    background: var(--sprite-editor-background-color, var(--surface-muted));
}

.sprite-editor.is-desktop-layout {
    grid-template-rows: auto auto minmax(0, 1fr);
}

.sprite-editor-commandbar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
    padding: var(--space-2) var(--space-3);
    background: var(--surface);
    border-block-end: 1px solid var(--sprite-editor-border-color, var(--line));
}

.sprite-editor .sprite-editor-back.button {
    display: inline-grid;
    flex: 0 0 2.5rem;
    width: 2.5rem;
    height: 2.5rem;
    place-items: center;
    padding: 0;
}

.sprite-editor-back svg,
.sprite-editor-mobile-rail svg {
    width: 1.125rem;
    height: 1.125rem;
}

.sprite-editor-title {
    display: grid;
    min-width: 0;
    margin-inline-end: auto;
}

.sprite-editor-title span {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.sprite-editor-command-actions {
    display: flex;
    gap: var(--space-2);
}

.sprite-editor-status {
    min-height: 1.75rem;
    padding: var(--space-1) var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-xs);
    background: var(--surface);
    border-block-end: 1px solid var(--line);
}

.sprite-editor-status.is-error {
    color: var(--danger);
    background: var(--danger-surface);
}

.sprite-editor-workspace {
    display: grid;
    min-width: 0;
    min-height: 0;
}

.sprite-editor-main {
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

/* SpriteCanvas is also a standalone docs component, where its authored 32rem work-area floor remains useful.
 * Only the editor composition may release that floor so the canvas becomes the internal scroller of a short
 * Frame region instead of enlarging or escaping that region. */
.sprite-editor .sprite-canvas-workarea {
    min-block-size: 0;
}

.sprite-editor-left,
.sprite-editor-right {
    background: var(--panel-background-color, var(--page-bg));
}

.sprite-editor-left,
.sprite-editor-right,
.sprite-editor-proof-panel {
    position: absolute;
    z-index: 8;
    inset-inline: var(--space-3);
    inset-block-start: var(--space-2);
    inset-block-end: calc(var(--sprite-editor-rail-height, 4rem) + var(--space-2));
    display: none;
    min-block-size: 0;
    max-block-size: none;
    overflow: auto;
    border: 1px solid var(--panel-border-color, var(--line));
    border-radius: var(--radius-container);
    box-shadow: var(--shadow-xl);
}

.sprite-editor-left.is-mobile-open,
.sprite-editor-right.is-mobile-open,
.sprite-editor-proof-panel.is-mobile-open {
    display: block;
}

.sprite-editor-proof-panel > .panel-head {
    flex: 0 0 auto;
    margin: 0;
    padding: var(--space-3);
}

.sprite-editor-proof-panel > .panel-body {
    min-block-size: 0;
    overflow: auto;
}

.sprite-editor-fields {
    display: grid;
    gap: var(--space-3);
    padding: var(--space-3);
}

.sprite-editor-fixed-facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-3) 0;
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.sprite-editor-fixed-facts span + span::before {
    margin-inline-end: var(--space-2);
    content: "·";
}

.sprite-editor-layer-toggles {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
}

.sprite-editor-coordinate-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
    padding: var(--space-3);
}

.sprite-editor-gates {
    display: grid;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-3) var(--space-3) var(--space-3) 2.5rem;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.sprite-editor-mobile-rail {
    position: sticky;
    z-index: 10;
    inset-block-end: 0;
    display: flex;
    gap: var(--space-2);
    min-width: 0;
    height: var(--sprite-editor-rail-height, 4rem);
    padding: var(--space-2);
    overflow-x: auto;
    background: var(--surface);
    border-block-start: 1px solid var(--line);
    scroll-snap-type: inline mandatory;
}

.sprite-editor-mobile-rail .button {
    flex: 0 0 3rem;
    width: 3rem;
    padding: 0;
    scroll-snap-align: center;
}

.sprite-editor-mobile-rail .button[aria-expanded="true"] {
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 14%, var(--surface));
}

@container (max-width: 44rem) {
    .sprite-editor-commandbar {
        align-items: flex-start;
        flex-wrap: wrap;
    }

    .sprite-editor-command-actions {
        flex: 1 0 100%;
        overflow-x: auto;
    }
}

@container (min-width: 60rem) {
    .sprite-editor-workspace {
        grid-template-columns:
            var(--sprite-editor-left-dock-width, 16rem)
            minmax(24rem, 1fr)
            var(--sprite-editor-right-dock-width, 18rem);
    }

    .sprite-editor-left,
    .sprite-editor-right {
        position: static;
        display: block;
        min-block-size: 0;
        max-block-size: 100%;
        overflow: auto;
        border: 0;
        border-radius: 0;
        box-shadow: none;
    }

    .sprite-editor-left {
        border-inline-end: 1px solid var(--line);
    }

    .sprite-editor-right {
        border-inline-start: 1px solid var(--line);
    }

    .sprite-editor-proof-panel.is-mobile-open {
        inset-inline: auto var(--space-3);
        inset-block: var(--space-3);
        width: min(62rem, calc(100% - 6rem));
        max-block-size: none;
    }

    .sprite-editor-mobile-rail {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sprite-editor *,
    .sprite-editor *::before,
    .sprite-editor *::after {
        scroll-behavior: auto !important;
    }
}

/* @toc Sprite palette */
/* Sprite palette (#1476) — the constrained colour set for one sprite: an auto-filling swatch grid, a
 * colour picker with an Add button, and a live count.
 *
 * A swatch carries its own colour inline as --sprite-swatch, which is the one place around here that a
 * literal colour is right: these are the ARTWORK's pixels, author-chosen and stored in the sprite
 * document, not themed UI — they cannot come from the tenant's ramp and must not flip with the theme.
 * Everything that IS chrome (borders, the selected ring, the count) still comes from tokens.
 *
 * The count is a TARGET, not a limit. 15 is the publish goal, so overshooting tints the line with the
 * warning pair rather than blocking the author; the hard ceiling is 32 swatches, enforced in the view
 * and the JS, not here. Selection is a ring, never a fill change — filling a swatch would hide the
 * colour it exists to show. View: sprite-palette.blade.php; behaviour: sprite-editor.js. */
.sprite-palette {
    display: grid;
    gap: var(--sprite-palette-gap, var(--space-2));
    padding: var(--space-3);
}

.sprite-palette-swatches {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--sprite-palette-swatch-size, 2rem), 1fr));
    gap: var(--sprite-palette-gap, var(--space-2));
}

.sprite-palette-swatch.button {
    width: 100%;
    min-width: var(--sprite-palette-swatch-size, 2rem);
    height: var(--sprite-palette-swatch-size, 2rem);
    min-height: var(--sprite-palette-swatch-size, 2rem);
    padding: 3px;
    border-color: var(--sprite-palette-border-color, var(--line));
}

.sprite-palette-swatch > span {
    display: block;
    width: 100%;
    height: 100%;
    background: var(--sprite-swatch);
    border-radius: calc(var(--radius-control) / 2);
}

.sprite-palette-swatch[aria-pressed="true"] {
    box-shadow: 0 0 0 2px var(--primary);
}

.sprite-palette-controls {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--space-2);
}

.sprite-palette-count {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.sprite-palette-count.is-over-target {
    color: var(--warning);
    background: var(--warning-surface);
}

/* @toc Sprite player */
/*
 * Sprite player — the living-preview canvas island. A poster→canvas stage plus a play/pause control. The
 * pixel law holds on both the poster and the canvas (crisp integer scaling; the JS also disables canvas
 * smoothing). Every colour is a token; the play/pause glyph is CSS-drawn (no icon dependency).
 */

.sprite-player {
    display: inline-flex;
    flex-direction: column;
    gap: var(--space-2);
    align-items: flex-start;
}

.sprite-player-stage {
    display: inline-flex;
    padding: var(--space-3);
    background: var(--sprite-player-stage-background-color, var(--surface-muted));
    border: 1px solid var(--sprite-player-stage-border, var(--line));
    border-radius: var(--sprite-player-border-radius, var(--radius-container));
}

/* Poster (pre-upgrade / no-JS) + canvas share the stage; the JS hides the poster and shows the canvas. Both
 * render at natural size × the integer scale, so the stage stays the same size across the swap. */
.sprite-player-poster {
    display: block;
    image-rendering: pixelated;
    zoom: var(--sprite-scale, 4);
}

.sprite-player-canvas {
    display: block;
    image-rendering: pixelated;
}

/* The frame sources are a data/preload source only — never laid out (the [hidden] attr covers it too). */
.sprite-player-frames {
    display: none;
}

.sprite-player-controls {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.sprite-player-label {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.sprite-player-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.sprite-player-toggle:hover {
    border-color: var(--text-muted);
}

.sprite-player-toggle:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--primary);
}

/* CSS-drawn play / pause glyphs. Play = a triangle (clip-path); pause = two bars (a gradient). The control's
 * aria-pressed selects which shows — kept in sync with playback by sprite-player.js. */
.sprite-player-glyph {
    width: 11px;
    height: 11px;
    background: currentColor;
}

.sprite-player-glyph-play {
    clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.sprite-player-glyph-pause {
    display: none;
    background: linear-gradient(to right, currentColor 0 35%, transparent 35% 65%, currentColor 65% 100%);
}

.sprite-player-toggle[aria-pressed="true"] .sprite-player-glyph-play {
    display: none;
}

.sprite-player-toggle[aria-pressed="true"] .sprite-player-glyph-pause {
    display: block;
}

/* @toc Sprite proof */
/* Sprite proof (#1476) — the diagnostic panel beside the authoring canvas: the current revision drawn
 * at 1x, switchable between colour, grayscale and silhouette.
 *
 * DELIBERATELY NOT A ROOM. It is a flat 960x540 canvas on a dark ground with no room geometry at all,
 * because the real-room proof belongs to the existing Godot room — two surfaces both claiming to be it
 * is how a sprite gets blessed against a mockup instead of the game. The caption in the view says so
 * out loud, and this file stays plain on purpose so nothing here can be mistaken for scenery.
 *
 * 1x is the whole point: "does the silhouette read" and "does it survive grayscale" are only honest
 * questions at the size the game actually draws. Nothing scales the canvas — the viewport scrolls.
 * View: sprite-proof.blade.php; canvas chrome: canvas-surface.css; mode switching: sprite-editor.js. */
.sprite-proof {
    display: grid;
    gap: var(--space-3);
    padding: var(--space-3);
    background: var(--sprite-proof-background-color, var(--neutral-950));
}

.sprite-proof-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.sprite-proof .canvas-surface-viewport {
    overflow: auto;
    border-color: var(--sprite-proof-border-color, var(--line));
}

.sprite-proof .canvas-surface-canvas {
    width: 960px;
    height: 540px;
}

/* @toc Sprite strip */
/*
 * Sprite strip — the animation QA row. A frame-per-cell track (scrolls horizontally on overflow), the pixel
 * law on the frame imgs (crisp nearest-neighbour, integer scale), status chips + anchor marks, and an
 * ONION-SKIN mode that stacks the frames translucently to check their alignment. Every colour is a token.
 */

.sprite-strip {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.sprite-strip-caption {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text);
}

.sprite-strip-label {
    font-weight: 600;
}

.sprite-strip-count {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

/* The frame track — a row that scrolls horizontally when the frames overflow. */
.sprite-strip-track {
    display: flex;
    gap: var(--sprite-strip-gap, var(--space-3));
    overflow-x: auto;
    padding-block-end: var(--space-1);
    align-items: flex-start;
}

.sprite-strip-frame {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
}

.sprite-strip-cell {
    display: inline-flex;
    padding: var(--space-2);
    background: var(--sprite-strip-cell-background-color, var(--surface-muted));
    border: 1px solid var(--sprite-strip-cell-border, var(--line));
    border-radius: var(--sprite-strip-border-radius, var(--radius-control));
}

/* The pixel law: crisp nearest-neighbour scaling. `zoom` (not transform) scales the LAYOUT box too, so an
 * integer-scaled frame reserves its own space — the honest way to enlarge pixel art without knowing its
 * natural size in CSS. Integer factor only (the prop is an int). */
.sprite-strip-img {
    display: block;
    image-rendering: pixelated;
    zoom: var(--sprite-scale, 2);
}

.sprite-strip-meta {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.sprite-strip-ord {
    font-variant-numeric: tabular-nums;
}

.sprite-strip-anchor {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text);
}

.sprite-strip-controls {
    display: flex;
}

.sprite-strip-onion {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 5px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.sprite-strip-onion:hover {
    border-color: var(--text-muted);
}

.sprite-strip-onion[aria-pressed="true"] {
    border-color: var(--primary);
    box-shadow: inset 0 0 0 1px var(--primary);
}

.sprite-strip-onion:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--primary);
}

/* ---- Onion-skin mode: stack every frame in one grid cell, translucent, anchor kept opaque ---- */
.sprite-strip.is-onion .sprite-strip-track {
    display: grid;
    justify-items: center;
    overflow: visible;
}

.sprite-strip.is-onion .sprite-strip-frame {
    grid-area: 1 / 1;   /* all frames occupy the same cell → they stack */
}

.sprite-strip.is-onion .sprite-strip-img {
    opacity: 0.35;
}

.sprite-strip.is-onion .sprite-strip-frame.is-anchor .sprite-strip-img {
    opacity: 1;
}

.sprite-strip.is-onion .sprite-strip-meta {
    display: none;
}

/* @toc Study issue */
/* Study issue — the bilingual checklist card (BarBruiser answer key). A left-accent by
 * type (must = success, nice = neutral, trap = danger), then a two-column EN | RU grid
 * (stacks on mobile). Surface + text are theme tokens; the type/lang accents are the
 * app's status tokens. View: study-issue.blade.php. */

.study-issue {
    container-type: inline-size;
    margin: 9px 0;
    padding: 11px 14px;
    border-inline-start: 4px solid var(--study-issue-accent, var(--success));
    border-radius: var(--radius);
    background: var(--surface-raised);
}

.study-issue-must { --study-issue-accent: var(--success); }
.study-issue-nice { --study-issue-accent: var(--text-muted); }
.study-issue-trap { --study-issue-accent: var(--danger); }

.study-issue-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.study-issue-mark {
    display: inline-flex;
    align-items: center;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    color: var(--study-issue-accent);
}

.study-issue-mark svg {
    width: 100%;
    height: 100%;
}

.study-issue-title {
    font-weight: 700;
    font-size: var(--text-sm);
    color: var(--text);
}

.study-issue-bilingual {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.study-issue-lang {
    background: var(--surface);
    border-inline-start: 3px solid var(--study-lang-accent, var(--line));
    border-radius: var(--radius-sm);
    padding: 9px 11px;
    font-size: var(--text-sm);
}

.study-issue-en { --study-lang-accent: var(--success); }
.study-issue-ru { --study-lang-accent: var(--warning); }

.study-issue-lang p {
    margin: 6px 0;
    line-height: 1.5;
}

.study-issue-lang p:first-of-type { margin-top: 0; }
.study-issue-lang p:last-of-type { margin-bottom: 0; }

.study-issue-lang strong {
    color: var(--text);
}

.study-issue-langlabel {
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 700;
    color: var(--text-muted);
    margin-bottom: 5px;
}

/* 720px: the card's OWN inline size, via a container query — NOT the app --bp-* viewport seam (with which it
   coexists). The EN | RU split collapses to one column whenever the card itself is narrow (e.g. dropped into a
   disclosure or a narrow slot), independent of the viewport width. .study-issue sets container-type above. */
@container (max-width: 720px) {
    .study-issue-bilingual {
        grid-template-columns: 1fr;
    }
}

/* @toc Study reader */
/*
 * Study reader — the §4b two-pane teaching shell. Stage (the stimulus) and rail (the study aside) are
 * SIMULTANEOUS ≥ the drawer breakpoint — the simultaneity is the teaching mechanic. Below it, the rail
 * collapses to a non-modal off-canvas drawer toggled by the peek button, gated by a @CONTAINER query (the
 * component-internal responsive seam, per the modernity contract — not a viewport media query). Layout dims
 * are --study-reader-* config (TBD-direction; only the 860px breakpoint is LOCKED). The reader root is the
 * query container; data-phase (set by the phase control) drives per-phase visibility in CSS elsewhere.
 */

.study-reader {
    container-type: inline-size;
    container-name: study-reader;
    display: flex;
    flex-direction: column;
    gap: var(--study-reader-gutter, 24px);
    color: var(--text);
}

/* ---- Header ---- */
.study-reader-head {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.study-reader-title {
    margin: 0;
    font-size: var(--text-xl);
}

.study-reader-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: var(--space-2);
}

/* Honesty band (minimal stopgap for x-provenance-band) — a quiet labelled strip, never hidden. */
.study-reader-provenance {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-2) var(--space-3);
    border-inline-start: 3px solid var(--line-strong);
    background: var(--surface-muted);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.study-reader-provenance-level {
    font-weight: 600;
    color: var(--text);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.75em;
}

/* ---- Layout: stage | rail, simultaneous by default ---- */
.study-reader-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--study-reader-rail-width, 360px);
    gap: var(--study-reader-gutter, 24px);
    align-items: start;
}

.study-reader-stage {
    max-width: var(--study-reader-stage-measure, 68ch);
}

.study-reader-rail {
    min-width: 0;
}

/* The peek button is drawer chrome — hidden while stage + rail are simultaneous. */
.study-reader-peek {
    display: none;
    align-items: center;
    gap: var(--space-2);
    align-self: flex-start;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--space-2);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    cursor: pointer;
}

.study-reader-peek-icon {
    display: inline-flex;
    width: 1.25em;
    height: 1.25em;
}

/* ---- Drawer: below the LOCKED breakpoint, the rail becomes a non-modal off-canvas panel ---- */
@container study-reader (max-width: 860px) {
    .study-reader-layout {
        grid-template-columns: minmax(0, 1fr);
    }

    .study-reader-peek {
        display: inline-flex;
    }

    .study-reader-rail {
        position: fixed;
        inset-block: 0;
        inset-inline-end: 0;
        z-index: var(--z-popover);
        width: min(360px, 85vw);
        padding: var(--space-4);
        overflow-y: auto;
        background: var(--surface);
        border-inline-start: 1px solid var(--line);
        box-shadow: -8px 0 24px color-mix(in srgb, var(--neutral-950) 20%, transparent);
        transform: translateX(100%);
        /* Closed = off the tab order + AT tree (visibility), not just off-screen — so a keyboard/SR user can't
           Tab into the invisible rail. visibility is delayed until the slide-out finishes so it still animates. */
        visibility: hidden;
        transition: transform 0.2s ease, visibility 0s linear 0.2s;
    }

    .study-reader.is-drawer-open .study-reader-rail {
        transform: translateX(0);
        visibility: visible;
        transition: transform 0.2s ease, visibility 0s linear 0s;
    }
}

/* ---- Flash: the shell's legend:activate feedback on the matching spans (transient, in the mark's hue) ---- */
.study-reader .annotated-text-mark.is-flash {
    border-radius: var(--annotated-text-span-border-radius, 3px);
    animation: study-reader-flash 1.2s ease-out;
}

@keyframes study-reader-flash {
    0% {
        box-shadow: 0 0 0 3px var(--_hue, currentColor);
    }

    100% {
        box-shadow: 0 0 0 3px transparent;
    }
}

/* Reduced motion: no drawer slide, no flash (the shell also skips the flash class — belt and braces). */
@media (prefers-reduced-motion: reduce) {
    .study-reader-rail {
        transition: none;
    }

    .study-reader .annotated-text-mark.is-flash {
        animation: none;
    }
}

/* @toc Study sheet */
/* Study sheet — the BarBruiser bar-essay renderer. Head banner, trigger legend, the
 * fact pattern with colour-coded trigger spans, and the collapsible checklist. The
 * trigger hues (t1-t6) map to the app's status tokens (danger/success/info/warning,
 * each self-flipping via its own -surface tint) plus the primary/neutral ramp for the
 * two hues with no status equivalent (t4/t6 — an explicit [data-theme="dark"] override
 * owns their flip). Boxes (source/instructions/call/hook) are x-callout; cards are
 * x-study-issue. View: study-sheet.blade.php. */

.study-sheet {
    max-width: var(--max-w-3xl);
    margin: 0 auto;
}

/* HEAD */
.study-sheet-head {
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 16px 20px;
    margin-bottom: 16px;
}

.study-sheet-sub {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* LEGEND */
.study-legend {
    background: var(--surface-raised);
    border-radius: var(--radius);
    padding: 12px 16px;
    margin: 14px 0;
}

.study-legend-title {
    margin: 0 0 8px;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
}

.study-legend-list {
    margin: 0;
    padding-left: 18px;
    font-size: var(--text-sm);
}

.study-legend-list li {
    margin-bottom: 5px;
}

/* A solid colour chip (its own classes, not the trigger-* tint, so the theme
 * override doesn't fade it). The hue matches the matching fact-pattern highlight. */
.study-swatch {
    display: inline-block;
    width: 26px;
    height: 12px;
    border-radius: 3px;
    margin-right: 8px;
    vertical-align: middle;
}

.study-swatch-t1 { background: var(--danger); }
.study-swatch-t2 { background: var(--success); }
.study-swatch-t3 { background: var(--info); }
.study-swatch-t4 { background: var(--primary-500); }
.study-swatch-t5 { background: var(--warning); }
.study-swatch-t6 { background: var(--neutral-500); }

/* FACT PATTERN */
.study-fact {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 16px 20px;
    margin: 14px 0;
    font-size: var(--text-base);
    line-height: 1.65;
}

.study-fact-label {
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 700;
    color: var(--text-muted);
    margin-bottom: 6px;
}

.study-fact p {
    margin: 8px 0;
}

/* The inline trigger highlight + the legend swatch share the .trigger-* colours. */
.trigger {
    padding: 1px 4px;
    border-radius: 3px;
    font-weight: 500;
}

.trigger-t1 { background: var(--danger-surface); border-bottom: 2px solid var(--danger); }
.trigger-t2 { background: var(--success-surface); border-bottom: 2px solid var(--success); }
.trigger-t3 { background: var(--info-surface); border-bottom: 2px solid var(--info); }
.trigger-t4 { background: var(--primary-100); border-bottom: 2px solid var(--primary-500); }
.trigger-t5 { background: var(--warning-surface); border-bottom: 2px solid var(--warning); }
.trigger-t6 { background: var(--neutral-100); border-bottom: 2px solid var(--neutral-500); }

/* t1/t2/t3/t5 already self-flip via their -surface tokens above; only t4/t6 (the two
 * hues with no status equivalent, on the raw primary/neutral ramp) need an owned flip. */
:root[data-theme="dark"] .trigger-t4 { background: var(--primary-950); }
:root[data-theme="dark"] .trigger-t6 { background: var(--neutral-700); }

/* CALL */
.study-call-list {
    margin: 8px 0 0 20px;
    padding: 0;
}

.study-call-list > li {
    margin-bottom: 6px;
}

.study-call-ru {
    margin-top: 8px;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* CHECKLIST — now the x-details primitive (themed disclosure); no bespoke styles. */

/* @toc Swatch */
/*
 * x-swatch — a colour chip that copies its token (#761, promoted from the docs Palette page's raw <button>s).
 *
 * ZERO COLOUR LITERALS, deliberately. The FILL is the one arbitrary colour and it arrives through the
 * --swatch-color custom property set by the consumer (the swatch-picker exception, exactly as theme-panel takes
 * its fill). Everything else is a semantic token — and that is only possible because the rung label sits BESIDE
 * the fill rather than on it. The raw markup this replaces put its step number over the colour, which forced a
 * hard-coded white plus a text-shadow to stay legible on an arbitrary background; theme-panel.css had already
 * recorded that a mark legible over an arbitrary palette colour is not achievable from tokens. Moving the label
 * onto the page surface keeps the number readable (a palette is for SCANNING) with ordinary --text-muted.
 */

.swatch {
    position: relative;   /* the copied confirmation is positioned against the control */
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 2px;
    min-inline-size: 0;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
}

/* The colour itself. The hover lift is on the FILL, not the whole control, so the rung label stays put and
   legible while the chip grows. */
.swatch-fill {
    display: block;
    block-size: var(--swatch-height, 44px);
    border-radius: var(--swatch-border-radius, 8px);
    background: var(--swatch-color);
    transition: transform 120ms ease, box-shadow 120ms ease;
}

.swatch:hover .swatch-fill {
    transform: scale(var(--swatch-hover-scale, 1.12));
    box-shadow: var(--shadow-lg);
}

.swatch:hover {
    z-index: 2;
}

/* The rung, on the page surface — the whole reason the fill carries no text. */
.swatch-step {
    font-size: var(--text-2xs);
    color: var(--text-muted);
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* State is a RING, never a colour change — the fill is the content, so it can never also be the state cue. */
.swatch:focus-visible {
    outline: var(--swatch-ring-width, 2px) solid var(--primary);
    outline-offset: 2px;
    border-radius: var(--swatch-border-radius, 8px);
    z-index: 2;
}

/*
 * The copied confirmation, also on the page surface. Hidden until swatch.js writes into it; :empty keeps it out
 * of the layout so a row of chips never shifts when one is used.
 */
.swatch-feedback {
    position: absolute;
    inset-block-start: calc(100% + 4px);
    inset-inline: 50% auto;
    translate: -50% 0;
    padding: 2px 6px;
    border-radius: var(--swatch-border-radius, 8px);
    background: var(--surface);
    border: 1px solid var(--line);
    color: var(--text-muted);
    font-size: var(--text-2xs);
    white-space: nowrap;
    pointer-events: none;
    z-index: 3;
}

.swatch-feedback:empty {
    display: none;
}

@media (max-width: 768px) {
    .swatch-fill {
        block-size: 34px;
    }
}

/* @toc Switch */
/*
 * Switch. A native <input type="checkbox" role="switch"> drives a decorative
 * track + knob: the real input is visually hidden (it keeps behaviour, focus and
 * keyboard), and its :checked / :focus-visible state styles the sibling pill. All
 * --switch-* tokens come from config/components/switch.php['css']. Dark-first.
 */

.switch {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--switch-gap, 10px);
    cursor: pointer;
    font-size: var(--switch-label-font-size, var(--text-sm));
    color: var(--switch-label-color, var(--text));
    line-height: 1.3;
    -webkit-user-select: none;
    user-select: none;
}

/* The real control: present for behaviour + a11y, visually replaced by the pill.
 * Not display:none — that would drop it from the tab order and kill keyboard. */
.switch-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

.switch-track {
    position: relative;
    display: inline-block;
    flex: none;
    width: var(--switch-width, 40px);
    height: var(--switch-height, 22px);
    border: 1px solid var(--switch-track-border, var(--line));
    border-radius: var(--radius-full);
    background: var(--switch-track-background-color, var(--surface-raised));
    transition: background 0.18s ease, border-color 0.18s ease;
}

.switch-knob {
    position: absolute;
    top: 50%;
    left: var(--switch-knob-gap, 2px);
    width: var(--switch-knob-size, 16px);
    height: var(--switch-knob-size, 16px);
    border-radius: var(--radius-full);
    background: var(--switch-knob-background-color, var(--text-muted));
    transform: translateY(-50%);
    transition: transform 0.18s ease, background 0.18s ease;
}

/* Checked: tint the track and slide the knob to the right edge. The travel is the
 * track width minus the knob and both gaps, so it never clips the rounded end. */
.switch-input:checked + .switch-track {
    background: var(--switch-track-background-color-on, var(--primary));
    border-color: var(--switch-track-background-color-on, var(--primary));
}

.switch-input:checked + .switch-track .switch-knob {
    background: var(--switch-knob-background-color-on, var(--on-primary));
    transform: translateY(-50%) translateX(calc(var(--switch-width, 40px) - var(--switch-knob-size, 16px) - (var(--switch-knob-gap, 2px) * 2) - 2px));
}

/* Keyboard focus ring on the pill, mirroring the other primitives' :focus-visible.
 * The transparent outline is the forced-colors fallback (box-shadow is dropped in
 * high-contrast mode, so the outline gets promoted and focus stays visible). */
.switch-input:focus-visible + .switch-track {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--switch-focus-ring, var(--primary));
}

.switch.is-disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

/* Submit-on-change fallback button (rendered only with `submit`; switch.js hides it and auto-submits on
 * change). It's a composed <x-button> — all styling is the button primitive's; this only positions it. */
.switch-apply {
    margin-left: 10px;
}

/* @toc Table */
/* Table — generic data table. Themeable via --table-* (config), falling back to
 * the global theme tokens. Signed/numeric cells use .is-positive / .is-negative. */

.table-wrap {
    overflow-x: auto;
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
}

/* The label TREATMENT (size/weight/case/tracking/colour) now comes from the shared .ui-label recipe
   (R5-B) applied in table.blade.php — revert-to-eyebrow is that one rule, base.css. .table th keeps only
   its STRUCTURAL styles here (never re-declares a label property, or it would defeat the one-edit revert). */
.table th {
    padding: 10px 12px;
    border-bottom: 1px solid var(--table-border, var(--line));
    text-align: start;
    white-space: nowrap;
}

.table td {
    padding: 10px 12px;
    border-bottom: 1px solid var(--table-border, var(--line));
    color: var(--text);
}

.table tbody tr:hover {
    background: var(--table-row-hover, var(--surface-raised));
}

.table-right {
    text-align: end;
}

.table-sort {
    color: inherit;
    text-decoration: none;
}

.table-sort:hover {
    color: var(--text);
}

.table th.is-sorted-asc .table-sort::after {
    content: " \2191";
}

.table th.is-sorted-desc .table-sort::after {
    content: " \2193";
}

.table .is-positive {
    color: var(--table-positive, var(--success));
}

.table .is-negative {
    color: var(--table-negative, var(--danger));
}

.table-empty {
    padding: 40px 16px;
    text-align: center;
    color: var(--text-muted);
}

/* --- polish (adapted from leadthink): zebra, scrollbar, and the Cell formatters --- */

/* Horizontal scroll on mobile; the scrollbar itself is the global themed one (base.css). */
.table-wrap {
    -webkit-overflow-scrolling: touch;
}

/* Zebra + hover, theme-agnostic (a tint of the current text colour works on light or dark). The
   hover rule is last so it wins over the stripe. */
.table tbody tr:nth-child(2n) {
    background: color-mix(in srgb, currentColor 3%, transparent);
}

.table tbody tr:hover {
    background: color-mix(in srgb, currentColor 7%, transparent);
}

/* Cell formatters (App\Support\Cell): numeric alignment, negatives, badges, stacked lines. */
.cell-num {
    font-variant-numeric: tabular-nums;
}

.cell-negative {
    color: var(--table-negative, var(--danger));
    font-weight: 600;
}

.cell-stacked {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}

.cell-stacked > div + div {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: var(--text-xs);
    font-weight: 600;
}

.badge-success {
    background: var(--success-surface);
    color: var(--success);
}

.badge-secondary {
    background: var(--neutral-100);
    color: var(--text-muted);
}

/* @toc Tabs */
/* Tabs — a horizontal tab list with a sliding indicator. tabs.js measures the
 * active trigger and sets the indicator's transform + width; CSS transitions them
 * and styles them per variant (set on the root):
 *   .tabs-underline (default) - a thin bar slides along a bottom rule; triggers
 *                               get a faint rounded hover highlight.
 *   .tabs-pill                - a segmented control; a filled pill slides behind
 *                               the active trigger.
 * Triggers take an optional icon (recolours with the trigger; accent when active).
 * States: active / inactive (hover) / .is-empty (dimmed). Colours: --tabs-*
 * (config). View: tabs.blade.php; switcher + indicator: tabs.js. */

.tabs-nav {
    position: relative;
    display: flex;
    gap: 2px;
    /* Scroll the tab row when it overflows (e.g. 6 facet tabs on mobile) rather
     * than wrapping or squashing; the scrollbar is hidden. */
    overflow-x: auto;
    scrollbar-width: none;
}

.tabs-nav::-webkit-scrollbar {
    display: none;
}

.tabs-tab {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    flex-shrink: 0;
    appearance: none;
    border: none;
    border-radius: var(--radius-control);   /* R1: a tab trigger is a CONTROL → 6px (was the off-scale 7px) */
    background: transparent;
    color: var(--tabs-tab-text);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 500;
    padding: 8px 12px;
    white-space: nowrap;
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.tabs-tab:hover:not(.is-active):not(.is-empty) {
    color: var(--tabs-tab-text-hover);
}

.tabs-tab.is-active {
    color: var(--tabs-tab-text-active);
}

.tabs-tab.is-empty {
    opacity: 0.4;
    cursor: default;
}

.tabs-tab.is-empty.is-active {
    opacity: 1;
}

/* Icon — inherits the trigger colour (currentColor), turns accent when active. */
.tabs-tab-icon {
    display: inline-flex;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    transition: color 0.15s ease;
}

.tabs-tab-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}

.tabs-tab.is-active .tabs-tab-icon {
    color: var(--tabs-accent);
}

/* The sliding indicator (one per nav). tabs.js sets width + translateX; the
 * variant rules below give it its position + look. */
.tabs-indicator {
    position: absolute;
    left: 0;
    width: 0;
    transform: translateX(0);
    transition: transform 0.2s ease, width 0.2s ease;
    pointer-events: none;
}

/* underline variant */
.tabs-underline > .tabs-nav {
    border-bottom: 1px solid var(--tabs-line);
}

.tabs-underline > .tabs-nav > .tabs-tab:hover:not(.is-active):not(.is-empty) {
    background: var(--tabs-tab-hover-background-color);
}

.tabs-underline > .tabs-nav > .tabs-indicator {
    /* bottom: 0 (not -1px) so the horizontal-scroll overflow doesn't clip it. */
    bottom: 0;
    height: 2px;
    background: var(--tabs-accent);
    border-radius: 2px;
}

/* pill variant */
.tabs-pill > .tabs-nav {
    gap: 0;
    padding: 4px;
    border-radius: var(--radius-lg);
    background: var(--tabs-pill-track);
}

.tabs-pill > .tabs-nav > .tabs-tab {
    padding: 7px 14px;
}

.tabs-pill > .tabs-nav > .tabs-indicator {
    top: 4px;
    bottom: 4px;
    background: var(--tabs-pill-active);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

.tabs-body {
    padding-top: 18px;
}

/* ── VERTICAL (#1349): the strip runs down the side, the panel sits BESIDE it ───────────────────
 * Added because a tab count can come from DATA. A horizontal strip is right for the three or four tabs this
 * component was built for; the PT reader's source list is 4–13 documents from the record in a half-width panel,
 * where horizontal either scrolls (hiding tabs behind the deliberately-invisible scrollbar) or wraps to three
 * rows — and a 135px-tall strip pushes its panel content that far below the sibling panel it is supposed to sit
 * level with. Vertical holds its shape at any count: the strip grows DOWN, which is the axis with room.
 *
 * `aria-orientation` + Up/Down keys come with it (tabs.blade.php + tabs.js key off the same value), so this is
 * an APG-shaped option rather than a skin.
 */
.tabs-vertical {
    display: grid;
    /* The strip takes what its labels need up to a cap, then wraps text rather than starving the panel. */
    grid-template-columns: minmax(0, var(--tabs-vertical-strip, 13rem)) minmax(0, 1fr);
    gap: var(--tabs-vertical-gap, 18px);
    align-items: start;
}

.tabs-vertical > .tabs-nav {
    flex-direction: column;
    /* The horizontal default scrolls sideways and hides its scrollbar; down this axis the strip simply grows,
       so the scroller has to be undone or it clips the column. */
    overflow-x: visible;
}

/* Left-aligned, full-width triggers: a vertical strip reads as a list, and centred labels in a column look
   like a mistake. `inline-flex` from the base rule would shrink-wrap each one. */
.tabs-vertical > .tabs-nav > .tabs-tab {
    display: flex;
    justify-content: flex-start;
    text-align: start;
    /* A long document title wraps instead of being clipped — the whole point of the cap above. */
    white-space: normal;
}

/* The panel is a grid sibling now, so the top padding that separated it from a strip ABOVE would only push it
   out of alignment with the strip BESIDE it — which is the bug this variant exists to fix. */
.tabs-vertical > .tabs-body {
    padding-top: 0;
}

/* The strip's rule moves to its inner edge — a divider between the list and the panel, where the horizontal
   variant puts it under the tabs. */
.tabs-vertical.tabs-underline > .tabs-nav {
    border-bottom: 0;
    border-inline-end: 1px solid var(--tabs-line);
}

/* The indicator becomes a marker down the LEADING edge of the active tab, not the divider edge: it marks the
   ROW you are on, which is how a vertical list reads, and it stays clear of the border it would otherwise sit
   on top of. `inset-inline-start` rather than the base rule's physical `left`, so it flips under RTL — and it
   has to be stated, because leaving both a start and an end inset set is over-constrained and the physical
   `left` silently wins (which is how this first rendered on the wrong edge from what the CSS claimed).
   tabs.js drives height + translateY, so the transition needs `height` too — the base only animates width. */
.tabs-vertical.tabs-underline > .tabs-nav > .tabs-indicator {
    bottom: auto;
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 2px;
    height: auto;
    border-radius: 2px;
    transition: transform 0.2s ease, height 0.2s ease;
}

.tabs-panel {
    display: none;
}

.tabs-panel.is-active {
    display: block;
}

/* @toc Textarea */
/* Textarea. All --textarea-* tokens come from config/components/textarea.php['css']. */

.textarea {
    margin-bottom: 14px;
}

.textarea-label {
    display: block;
    margin-bottom: 6px;
    color: var(--textarea-label-color);
    font-size: var(--textarea-label-font-size);
}

.textarea-field {
    width: 100%;
    padding: var(--textarea-padding-y) var(--textarea-padding-x);
    border: 1px solid var(--textarea-border);
    border-radius: var(--textarea-border-radius);
    background: var(--textarea-background-color);
    color: var(--textarea-color);
    font-size: var(--textarea-font-size);
    font-family: var(--font-sans);
    line-height: var(--textarea-line-height);
    /* Vertical-only drag handle: width stays on the layout grid, height grows with content. */
    resize: var(--textarea-resize);
    min-height: var(--textarea-min-height);
}

/* opacity:1 stops the UA re-fading the placeholder below the token's contrast. */
.textarea-field::placeholder {
    color: var(--text-muted);
    opacity: 1;
}

/* Code variant — a monospace face + tighter type for pasting JSON, config, or code. */
.textarea-field-code {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
}

/* Invalid / hint / required affordances. Error colour is the categorical --danger
 * token (red-700 light / red-400 dark) — self-flipping and menu-hover-certified, so
 * the text clears 4.5:1 and the border 3:1 on every surface without a per-theme rule. */
.textarea-field[aria-invalid="true"] {
    border-color: var(--danger);
}

.textarea-error {
    margin-top: 6px;
    font-size: var(--text-xs);
    color: var(--danger);
}

.textarea-hint {
    margin-top: 6px;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.textarea-required {
    margin-left: 2px;
    color: var(--danger);
}

/* Keyboard focus recolors the border as a secondary cue; the global :focus-visible
 * ring (base.css) supplies the primary indicator. :focus-visible (not :focus) so a
 * mouse click doesn't trigger either. */
.textarea-field:focus-visible {
    border-color: var(--textarea-focus-border);
}

/* @toc Theme colors */
/* Accent/surface colour picker — a dropper button with a popover of swatch
 * grids, sits in the sidebar footer beside the light/dark theme-picker.
 *
 * The swatch fill (background: var(--swatch-color)) is the load-bearing line:
 * theme-colors.js sets --swatch-color to var(--palette-<name>-500) per swatch,
 * pulling from the catalog ramps CssService emits.
 *
 * The popup is absolutely positioned and needs a wider positioned ancestor than
 * the 34px dropper, so it anchors to .sidebar-nav-footer (which is position:
 * relative). Used by /resources/views/components/theme-colors.blade.php.
 */

.theme-colors {
    display: inline-flex;
}

.theme-colors-dropper {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.theme-colors-dropper:hover {
    color: var(--text);
    background: var(--line);
}

.theme-colors-dropper svg {
    width: 17px;
    height: 17px;
}

.theme-colors-popup {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% + 8px);
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--page-bg);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
    transform: scale(0.96);
    transform-origin: bottom center;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: transform 160ms ease, opacity 160ms ease, visibility 0s linear 160ms;
}

.theme-colors-popup.is-open {
    transform: scale(1);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: transform 160ms ease, opacity 160ms ease, visibility 0s;
}

.theme-colors-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.theme-colors-label {
    color: var(--text-muted);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.theme-colors-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 6px;
}

.theme-colors-swatch {
    appearance: none;
    width: 100%;
    aspect-ratio: 1 / 1;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 5px;
    background: var(--swatch-color);
    cursor: pointer;
    outline: 2px solid transparent;
    outline-offset: 1px;
    transition: transform 120ms ease, outline-color 120ms ease;
}

.theme-colors-swatch:hover {
    transform: scale(1.08);
}

.theme-colors-swatch.is-active {
    outline-color: var(--text);
}

/* @toc Theme panel */
/*
 * Theme panel — the blueprint schedule inside the x-drawer. Hairline-ruled sections with .ui-label heads,
 * compact rhythm, ring-not-border selected states, every colour from a token (the one exception: the colour
 * SWATCH fills are raw --palette-* values — the sanctioned swatch-picker exception to the colour law). No
 * shadow-heavy cards, no left-stroke accents (the anti-slop law — this panel is a taste showcase).
 *
 * This file ALSO carries the data-radius axis (the :root[data-radius] blocks at the end) — a global static
 * axis, bundled with the component that drives it, so utilities.css stays untouched.
 */

/* ---- The schedule: hairline-ruled sections ---- */
.theme-panel-section {
    padding: var(--space-4) 20px;
    border-block-end: 1px solid var(--line);
}

.theme-panel-section:last-child {
    border-block-end: none;
}

.theme-panel-head {
    margin: 0 0 var(--space-3);
}

/* ---- 1 · Theme list ---- */
.theme-panel-themes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.theme-panel-theme {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 7px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.theme-panel-theme:hover {
    border-color: var(--text-muted);
}

.theme-panel-theme[aria-pressed="true"] {
    border-color: var(--primary);
    box-shadow: inset 0 0 0 1px var(--primary);
}

.theme-panel-theme-icon {
    display: inline-flex;
    line-height: 0;
    color: var(--text-muted);
}

.theme-panel-theme-icon svg {
    width: 16px;
    height: 16px;
}

.theme-panel-theme[aria-pressed="true"] .theme-panel-theme-icon {
    color: var(--primary);
}

.theme-panel-theme:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--primary);
}

/* ---- 2 + 3 · Swatch grids (built by theme-panel.js) ---- */
.theme-panel-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.theme-panel-swatch {
    position: relative;
    width: var(--theme-panel-swatch-size, 26px);
    height: var(--theme-panel-swatch-size, 26px);
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    /* The raw palette fill — the swatch-picker exception (a --palette-<family>-500 catalog value). */
    background: var(--swatch-color);
    cursor: pointer;
    transition: box-shadow 0.15s ease, transform 0.1s ease;
}

.theme-panel-swatch:hover {
    transform: scale(1.08);
}

/* Selected = a RING offset by the surface then the primary token (a shape cue, not the fill colour alone) +
 * aria-pressed. A check GLYPH over the swatch was considered, but a check legible on an arbitrary --palette
 * fill needs a fixed light colour, which the colour law forbids without a Planner-baselined literal — the
 * ring is the honest, on-token non-colour cue (a corner check badge is a demand-driven refinement). */
.theme-panel-swatch[aria-pressed="true"] {
    box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--primary);
}

.theme-panel-swatch:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--primary);
}

/* ---- 4 · Radius segmented row ---- */
.theme-panel-radius {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.theme-panel-radius-opt {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--text-xs);
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, color 0.15s ease;
}

.theme-panel-radius-opt:hover {
    border-color: var(--text-muted);
}

.theme-panel-radius-opt[aria-pressed="true"] {
    border-color: var(--primary);
    color: var(--text);
    box-shadow: inset 0 0 0 1px var(--primary);
}

/* The corner-preview glyph — a small square whose top-left corner shows the preset's rounding. */
.theme-panel-radius-glyph {
    width: 20px;
    height: 20px;
    border: 2px solid currentColor;
    border-radius: 0;
    /* Only the top-left corner is rounded, so the preset reads at a glance. */
    border-block-end-color: transparent;
    border-inline-end-color: transparent;
}

.theme-panel-radius-glyph-none { border-start-start-radius: 0; }
.theme-panel-radius-glyph-small { border-start-start-radius: 3px; }
.theme-panel-radius-glyph-default { border-start-start-radius: 6px; }
.theme-panel-radius-glyph-large { border-start-start-radius: 10px; }

.theme-panel-radius-opt[aria-pressed="true"] .theme-panel-radius-glyph {
    color: var(--primary);
}

.theme-panel-radius-opt:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--primary);
}

/* ---- 5 · Font (read-only current pairing) ---- */
.theme-panel-font {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    padding: 8px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
}

.theme-panel-font-specimen {
    font-family: var(--font-sans);
    font-size: var(--text-2xl);
    font-weight: 600;
    line-height: 1;
    color: var(--text);
}

.theme-panel-font-name {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* ---- 6 · Icons fact row ---- */
.theme-panel-fact {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.theme-panel-fact-value {
    color: var(--text);
    font-weight: 500;
}

/* =================================================================================================
 * The data-radius axis — a global static axis (parallels data-primary/data-neutral, but its presets are
 * fixed, not ramp-derived, so it needs no service). The base :root values (utilities.css) ARE the default
 * R1 6/8 scale, so ABSENCE of data-radius = Default (the "default" preset carries no attribute). We remap
 * the RAW rungs too (not just --radius-control/-container): most components still consume the raw rungs, so
 * a control/container-only remap would move almost nothing. --radius-full stays 9999 (pills never square).
 * ================================================================================================= */
:root[data-radius="none"] {
    --radius-control: 0;
    --radius-container: 0;
    --radius-sm: 0;
    --radius: 0;
    --radius-md: 0;
    --radius-lg: 0;
    --radius-xl: 0;
    --radius-2xl: 0;
}

:root[data-radius="small"] {
    --radius-control: 3px;
    --radius-container: 4px;
    --radius-sm: 3px;
    --radius: 4px;
    --radius-md: 6px;
    --radius-lg: 8px;
    --radius-xl: 10px;
    --radius-2xl: 14px;
}

:root[data-radius="large"] {
    --radius-control: 10px;
    --radius-container: 14px;
    --radius-sm: 10px;
    --radius: 14px;
    --radius-md: 18px;
    --radius-lg: 22px;
    --radius-xl: 28px;
    --radius-2xl: 36px;
}

/* @toc Theme picker */
/* Theme picker — a small segmented control. --theme-picker-* from config. */

.theme-picker {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    border: 1px solid var(--line);
    border-radius: var(--theme-picker-border-radius, 8px);
    background: transparent;
}

.theme-picker-opt {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: var(--text-muted);
    padding: 5px 8px;
    border-radius: 6px;
    cursor: pointer;
}

.theme-picker-opt svg {
    width: 16px;
    height: 16px;
}

.theme-picker-opt:hover:not(.is-active) {
    color: var(--text);
}

.theme-picker-opt.is-active {
    background: var(--surface-raised);
    color: var(--text);
}

/* @toc Theory sheet */
/* Theory study sheet — bilingual rule-sheet (Theory mode) + interactive MBE drill
 * (Drills mode). App design tokens throughout; no hardcoded artifact colours.
 * View: theory-sheet.blade.php; behaviour: theory-sheet.js.
 *
 * Bilingual primitive: .theory-bilingual is EN | RU side-by-side on desktop and
 * stacked (EN over RU, divider between) on mobile. EN is authoritative; RU is the
 * condensed gloss (slightly muted). */

/* A recessed panel surface, one step behind --surface (like --page-bg, which already
 * has exactly that relationship in both themes) — for chips/badges/letters that need
 * to read as sunken rather than flush with the card they sit on. */
:root {
    --surface-sunken: var(--page-bg);
}

.theory-sheet {
    max-width: var(--max-w-4xl);
}

/* ---- meta head ---- */
.theory-head {
    margin-bottom: 18px;
}

.theory-sub {
    margin: 0;
    color: var(--text);
    font-weight: 600;
}

.theory-sub-ru {
    display: block;
    color: var(--text-muted);
    font-weight: 400;
    font-size: var(--text-sm);
}

.theory-arc {
    margin: 6px 0 0;
    color: var(--text-muted);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.6px;
}

.theory-prov-ru {
    display: block;
    opacity: 0.8;
    font-size: var(--text-xs);
}

.theory-next {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 10px 0 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.theory-next svg {
    width: 15px;
    height: 15px;
}

/* The Theory/Drills mode switch is the shared <x-tabs> (pill variant) — its own
 * component owns the toggle look + behaviour. This sheet only reinstates the
 * mobile-sticky tab bar (see the responsive block). */

/* ---- bilingual primitive ---- */
.theory-bilingual {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.theory-lang {
    padding: 12px 14px;
    border-radius: var(--radius);
    background: var(--surface-raised);
    border-left: 3px solid var(--info);
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--text);
}

.theory-lang-ru {
    border-left-color: var(--success);
    color: var(--text-muted);
}

.theory-lang-label {
    font-size: var(--text-2xs, 10px);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-muted);
    margin-bottom: 5px;
}

.theory-lang strong {
    color: var(--text);
}

.theory-lang em {
    color: var(--primary);
    font-style: italic;
}

/* ---- section heads ---- */
.theory-section-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 26px 0 12px;
    padding-bottom: 8px;
    border-bottom: 2px solid var(--accent, var(--line));
}

.theory-section-icon {
    display: inline-flex;
    width: 20px;
    height: 20px;
    color: var(--accent, var(--primary));
}

.theory-section-icon svg {
    width: 100%;
    height: 100%;
}

.theory-section-title {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--text);
}

.theory-section-ru {
    display: block;
    font-size: var(--text-xs);
    font-weight: 400;
    color: var(--text-muted);
}

.theory-tier {
    margin-left: auto;
    flex-shrink: 0;
    padding: 3px 10px;
    border-radius: var(--radius-full, 999px);
    font-size: var(--text-2xs, 10px);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
}

.theory-tier-1 { background: color-mix(in srgb, var(--danger) 16%, var(--surface)); color: var(--danger); border: 1px solid var(--danger); }
.theory-tier-2 { background: color-mix(in srgb, var(--warning) 16%, var(--surface)); color: var(--warning); border: 1px solid var(--warning); }
.theory-tier-3 { background: var(--surface-sunken); color: var(--text-muted); border: 1px solid var(--line); }

/* ---- tier grid (the master comparison) ---- */
.theory-tiergrid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
    margin: 12px 0;
}

.theory-tiercard {
    padding: 14px;
    border-radius: var(--radius-lg);
    border: 2px solid var(--accent, var(--line));
    background: color-mix(in srgb, var(--accent, var(--primary)) 8%, var(--surface));
}

.theory-tiercard-name {
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    text-align: center;
    color: var(--accent, var(--text));
    margin-bottom: 8px;
}

.theory-tiercard-formula {
    font-size: var(--text-xs);
    line-height: 1.5;
    padding: 8px;
    border-radius: var(--radius);
    background: var(--surface-sunken);
    color: var(--text);
}

.theory-tiercard-formula strong { color: var(--accent, var(--text)); }

.theory-tiercard-burden {
    font-size: var(--text-2xs, 11px);
    font-style: italic;
    color: var(--text-muted);
    margin: 8px 0 6px;
}

.theory-tiercard-triggers {
    list-style: none;
    padding: 0;
    margin: 0;
}

.theory-tiercard-triggers li {
    font-size: var(--text-xs);
    padding: 2px 0;
    color: var(--text);
}

.theory-tiercard-triggers li::before {
    content: "▸ ";
    color: var(--accent, var(--primary));
}

/* ---- checklist ---- */
.theory-checklist {
    margin: 14px 0;
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.theory-checklist-title {
    margin: 0 0 12px;
    font-size: var(--text-base);
    font-weight: 700;
    color: var(--text);
}

.theory-checklist-steps {
    margin: 0;
    padding-left: 22px;
}

.theory-checklist-steps li {
    margin-bottom: 10px;
}

.theory-checklist-example {
    margin-top: 12px;
}

/* ---- mnemonic ---- */
.theory-mnemonic {
    margin: 14px 0;
    padding: 14px 16px;
    text-align: center;
    border: 2px solid var(--warning);
    border-radius: var(--radius-lg);
    background: color-mix(in srgb, var(--warning) 10%, var(--surface));
}

.theory-mnemonic-text {
    font-size: var(--text-lg);
    font-weight: 700;
    letter-spacing: 1px;
    color: var(--warning);
}

.theory-mnemonic-gloss {
    margin-top: 6px;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.theory-trap-tested {
    margin-top: 8px;
    font-size: var(--text-2xs, 11px);
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--text-muted);
}

/* ---- cases ---- */
.theory-case {
    margin: 10px 0;
    padding: 10px 14px;
    border-left: 3px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-raised);
}

.theory-case-key {
    border-left-color: var(--primary);
}

.theory-case-cite {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
    color: var(--text);
}

.theory-case-cite svg { width: 14px; height: 14px; color: var(--primary); }

/* ---- examples ---- */
.theory-examples { margin: 12px 0; }

.theory-example {
    display: flex;
    gap: 8px;
    margin-bottom: 8px;
}

.theory-example-mark { flex-shrink: 0; }
.theory-example-mark svg { width: 18px; height: 18px; }
.theory-example-win .theory-example-mark { color: var(--success); }
.theory-example-fail .theory-example-mark { color: var(--danger); }
.theory-example .theory-bilingual { flex: 1; }

/* ===================== QUIZ ===================== */
.theory-quiz-bar {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    margin-bottom: 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.theory-quiz-pct {
    font-weight: 800;
    color: var(--primary);
}

.theory-q {
    display: none;
    padding: 20px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.theory-q.is-active { display: block; }

.theory-q-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.theory-q-topic, .theory-q-source, .theory-q-diff {
    font-size: var(--text-2xs, 10px);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    padding: 3px 9px;
    border-radius: var(--radius-full, 999px);
}

.theory-q-topic { background: color-mix(in srgb, var(--primary) 14%, var(--surface)); color: var(--primary); border: 1px solid var(--primary); }
.theory-q-source { background: var(--surface-sunken); color: var(--text-muted); border: 1px solid var(--line); }
.theory-q-diff-easy { background: color-mix(in srgb, var(--success) 14%, var(--surface)); color: var(--success); }
.theory-q-diff-medium { background: color-mix(in srgb, var(--warning) 14%, var(--surface)); color: var(--warning); }
.theory-q-diff-hard { background: color-mix(in srgb, var(--danger) 14%, var(--surface)); color: var(--danger); }
.theory-q-num { margin-left: auto; font-size: var(--text-xs); color: var(--text-muted); }

.theory-q-question { margin-bottom: 14px; font-size: var(--text-base); }

.theory-q-options {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.theory-q-option {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.theory-q-option:hover:not(:disabled) { border-color: var(--primary); }
.theory-q-option:disabled { cursor: default; }

.theory-q-option .theory-bilingual { flex: 1; gap: 8px; }
.theory-q-option .theory-lang { padding: 0; background: none; border: none; }

.theory-q-letter {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    line-height: 22px;
    text-align: center;
    border-radius: 50%;
    background: var(--surface-sunken);
    font-size: var(--text-xs);
    font-weight: 700;
}

.theory-q-option.is-correct { border-color: var(--success); background: color-mix(in srgb, var(--success) 12%, var(--surface)); }
/* SOLID-fill status = the INVERTED vetted pair: fill on the main token, text on its -surface companion.
   Contrast is symmetric, so each ratio is the already-vetted tint number read the other way — success
   4.57 light / 8.55 dark, danger 5.30 / 5.84 — and the pair flips with the theme, so no on-fill token is
   needed. Replaces emerald-600 + --on-primary (3.77, a real AA fail): white on a -600 fill only ever
   passed for the deep red/blue hues, which is why the drift went unnoticed. Zero new tokens. */
.theory-q-option.is-correct .theory-q-letter { background: var(--success); color: var(--success-surface); }
.theory-q-option.is-incorrect { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, var(--surface)); }
.theory-q-option.is-incorrect .theory-q-letter { background: var(--danger); color: var(--danger-surface); }

.theory-q-feedback { margin-top: 14px; }

.theory-q-verdict { font-weight: 700; margin-bottom: 10px; }
.theory-q-verdict.is-correct { color: var(--success); }
.theory-q-verdict.is-incorrect { color: var(--danger); }

.next-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 12px;
    padding: 9px 18px;
    border: none;
    border-radius: var(--radius);
    background: var(--primary);
    color: var(--on-primary);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

/* ---- score screen ---- */
.theory-quiz-screen {
    text-align: center;
    padding: 32px 20px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.theory-quiz-final {
    font-size: 56px;
    font-weight: 800;
    color: var(--primary);
    line-height: 1;
}

.theory-quiz-detail { margin-top: 6px; color: var(--text-muted); }
.theory-quiz-msg { margin: 18px auto; max-width: 480px; color: var(--text); line-height: 1.6; }

/* ===================== RESPONSIVE ===================== */
/* 760px: local component breakpoint, intentionally a touch before --bp-md (768); not the app seam. */
@media (max-width: 760px) {
    /* Bilingual stacks: EN over RU with a divider, not side-by-side. */
    .theory-bilingual { grid-template-columns: 1fr; gap: 8px; }
    .theory-lang-ru { border-left: none; border-top: 2px solid var(--success); border-radius: 0; padding-top: 10px; }
    .theory-q-option .theory-lang-ru { border-top: 1px solid var(--line); padding-top: 6px; }

    /* Tier cards stack instead of cramming 3-up. */
    .theory-tiergrid { grid-template-columns: 1fr; }

    /* Mode switch (the <x-tabs> nav) pins to the top so she can flip without
     * scrolling back — the old .theory-switch sticky behaviour, scoped to the
     * tablist inside this sheet only. */
    .theory-sheet .tabs-nav {
        position: sticky;
        top: 0;
        z-index: 10;
        background: var(--surface);
    }
    .theory-sheet .tabs-pill > .tabs-nav > .tabs-tab { flex: 1; justify-content: center; }
}

/* @toc Toolbar */
/* Toolbar — the skin of the APG toolbar primitive: a strip of square icon buttons, horizontal by
 * default and `.toolbar-vertical` when docked down the side of a surface.
 *
 * It SCROLLS rather than wraps — the opposite call to room-canvas's own toolbar, and deliberate: this
 * one is a fixed-height bar inside somebody else's layout, so a second wrapped row would shove the
 * surface below it off the screen. That only works if the consumer lets it shrink: a flex parent's
 * default `min-width: auto` pins it to its content width and the `overflow: auto` here never gets to
 * fire (sprite-canvas.css carries the matching declaration for exactly this reason).
 *
 * The pressed tool is a `color-mix` tint on the primary ramp rather than a `-surface` token, because
 * no primary one exists — a phantom var renders as nothing and the active tool would look identical
 * to the inactive ones (#1476 import fix, spelled out below).
 *
 * Items are x-buttons in the ghost variant, so their square sizing is set here rather than inherited.
 * View: toolbar.blade.php; roving focus + arrow keys: toolbar.js; colours: --toolbar-* (config). */
.toolbar {
    display: inline-flex;
    align-items: center;
    gap: var(--toolbar-gap, var(--space-1));
    max-width: 100%;
    padding: var(--space-1);
    overflow: auto;
    background: var(--toolbar-background-color, var(--surface));
    border: 1px solid var(--toolbar-border-color, var(--line));
    border-radius: var(--toolbar-border-radius, var(--radius-control));
    scrollbar-width: thin;
}

.toolbar-vertical {
    flex-direction: column;
}

.toolbar .toolbar-item.button {
    flex: 0 0 var(--toolbar-item-size, 2.5rem);
    width: var(--toolbar-item-size, 2.5rem);
    height: var(--toolbar-item-size, 2.5rem);
    min-height: var(--toolbar-item-size, 2.5rem);
    padding: 0;
}

/* The pressed tool. `--primary-surface` does NOT exist (#1476 import fix): only success/warning/danger/info
 * carry a `-surface` tint, and the drop reached for a primary one by analogy. A phantom var renders as
 * nothing, so the active tool would have looked identical to the inactive ones — the single most important
 * state on a toolbar, silently absent.
 *
 * `color-mix` on the ramp rather than a new token: the colour law sanctions alpha on BACKGROUNDS, the tint
 * follows the tenant's own primary, and this needs no addition to config/theme.php from an import card. */
.toolbar-item[aria-pressed="true"] {
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 14%, transparent);
}

.toolbar-icon,
.toolbar-icon svg {
    display: block;
    width: 1.125rem;
    height: 1.125rem;
}

/* @toc Tooltip */
/* Tooltip — one shared, event-delegated floating-label host. tooltip.js calculates four-way placement,
 * flips and clamps at viewport edges, and writes the caret coordinates; this sheet owns only presentation.
 * Every adjustable spacing, type, timing, caret, stacking, and inverted light/dark value comes through the
 * --tooltip-* surface emitted from config/components/tooltip.php. */

.tooltip {
    position: fixed;
    top: 0;
    left: 0;
    z-index: var(--tooltip-z-index);
    max-width: min(var(--tooltip-max-width), calc(100vw - var(--tooltip-viewport-padding) - var(--tooltip-viewport-padding)));
    padding: var(--tooltip-padding-block) var(--tooltip-padding-inline);
    border-radius: var(--tooltip-border-radius);
    background: var(--tooltip-background-color);
    color: var(--tooltip-text);
    font-family: var(--font-sans);
    font-size: var(--tooltip-font-size);
    font-weight: var(--tooltip-font-weight);
    line-height: var(--tooltip-line-height);
    white-space: normal;
    overflow-wrap: anywhere;
    pointer-events: none;
    opacity: 0;
    transform: translate(var(--tooltip-enter-x, 0), var(--tooltip-enter-y, 0));
    transition: none;
}

.tooltip::after {
    position: absolute;
    width: var(--tooltip-caret-size);
    height: var(--tooltip-caret-size);
    background: var(--tooltip-background-color);
    content: '';
    transform: rotate(45deg);
}

.tooltip[data-placement='above'] {
    --tooltip-enter-x: 0;
    --tooltip-enter-y: var(--tooltip-motion-distance);
}

.tooltip[data-placement='below'] {
    --tooltip-enter-x: 0;
    --tooltip-enter-y: var(--tooltip-motion-distance-negative);
}

.tooltip[data-placement='right'] {
    --tooltip-enter-x: var(--tooltip-motion-distance-negative);
    --tooltip-enter-y: 0;
}

.tooltip[data-placement='left'] {
    --tooltip-enter-x: var(--tooltip-motion-distance);
    --tooltip-enter-y: 0;
}

.tooltip[data-placement='above']::after {
    top: 100%;
    left: var(--tooltip-caret-inline);
    transform: translateY(-50%) rotate(45deg);
}

.tooltip[data-placement='below']::after {
    bottom: 100%;
    left: var(--tooltip-caret-inline);
    transform: translateY(50%) rotate(45deg);
}

.tooltip[data-placement='right']::after {
    top: var(--tooltip-caret-block);
    right: 100%;
    transform: translateX(50%) rotate(45deg);
}

.tooltip[data-placement='left']::after {
    top: var(--tooltip-caret-block);
    left: 100%;
    transform: translateX(-50%) rotate(45deg);
}

.tooltip.is-visible {
    opacity: 1;
    transform: translate(0, 0);
    transition:
        opacity calc(var(--tooltip-motion-duration-seconds) * 1s) var(--tooltip-motion-easing) calc(var(--tooltip-show-delay-seconds) * 1s),
        transform calc(var(--tooltip-motion-duration-seconds) * 1s) var(--tooltip-motion-easing) calc(var(--tooltip-show-delay-seconds) * 1s);
}

/* @toc Tracking consent */
/* TrackingConsent styles the first-party visit-statistics notice and its choice or stop control.
 * It consumes component-generated tokens and applies them to the compact panel, actions, and manage button.
 * Its output is a compact, non-modal surface that remains usable within the viewport at narrow sizes.
 * Visibility state belongs to the component markup and JavaScript; this sheet owns presentation only. */

.tracking-consent {
    inset-block-end: max(16px, env(safe-area-inset-bottom));
    inset-inline-end: max(16px, env(safe-area-inset-right));
    max-inline-size: min(var(--tracking-consent-max-width), calc(100vw - 32px));
    position: fixed;
    z-index: var(--z-popover);
}

.tracking-consent-panel {
    background: var(--tracking-consent-background-color);
    border: 1px solid var(--tracking-consent-border-color);
    border-radius: var(--tracking-consent-border-radius);
    box-shadow: var(--tracking-consent-shadow);
    color: var(--tracking-consent-color);
    display: grid;
    gap: var(--tracking-consent-gap);
    max-block-size: calc(100vh - 32px);
    overflow-y: auto;
    padding: var(--tracking-consent-padding);
}

.tracking-consent-panel[hidden],
.tracking-consent-manage[hidden] {
    display: none;
}

.tracking-consent-title,
.tracking-consent-copy {
    margin: 0;
}

.tracking-consent-title {
    font-size: var(--text-lg);
}

.tracking-consent-copy {
    color: var(--tracking-consent-muted-color);
    font-size: var(--text-sm);
    line-height: 1.5;
}

.tracking-consent-actions {
    display: grid;
    gap: 8px;
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.tracking-consent-action,
.tracking-consent-manage {
    background: var(--tracking-consent-background-color);
    border: 1px solid var(--tracking-consent-border-color);
    border-radius: var(--radius-control);
    color: var(--tracking-consent-color);
    cursor: pointer;
    font: inherit;
    min-block-size: 44px;
    padding: 8px 12px;
}

.tracking-consent-action:hover,
.tracking-consent-manage:hover {
    border-color: var(--tracking-consent-focus-ring);
}

.tracking-consent-action:focus-visible,
.tracking-consent-manage:focus-visible {
    outline: 3px solid var(--tracking-consent-focus-ring);
    outline-offset: 2px;
}

.tracking-consent-manage {
    box-shadow: var(--tracking-consent-shadow);
}

.tracking-consent[data-consent-required="false"] {
    inline-size: min(var(--tracking-consent-max-width), calc(100% - 32px));
    inset: auto;
    margin: 16px auto;
    max-inline-size: none;
    position: static;
    z-index: auto;
}

.tracking-consent-disclosure {
    box-shadow: none;
    display: block;
    max-block-size: none;
    overflow: visible;
    padding: 0;
}

.tracking-consent-summary {
    align-items: center;
    cursor: pointer;
    display: flex;
    gap: 12px;
    justify-content: space-between;
    min-block-size: 44px;
    padding: 8px 12px;
}

.tracking-consent-summary .tracking-consent-title {
    font-size: var(--text-sm);
    font-weight: 700;
}

.tracking-consent-summary-detail {
    color: var(--tracking-consent-muted-color);
    font-size: var(--text-sm);
}

.tracking-consent-disclosure-body {
    border-block-start: 1px solid var(--tracking-consent-border-color);
    display: grid;
    gap: 8px;
    padding: 10px 12px 12px;
}

.tracking-consent-stop {
    justify-self: start;
    white-space: nowrap;
}

.tracking-consent-stop:disabled {
    cursor: default;
    opacity: 0.72;
}

@media (max-width: 480px) {
    .tracking-consent {
        inset-inline: 16px;
        max-inline-size: none;
    }

    .tracking-consent[data-consent-required="false"] {
        inline-size: calc(100% - 32px);
        inset-inline: auto;
    }

    .tracking-consent-stop {
        inline-size: 100%;
    }
}

/* @toc Tree */
/* Tree — the APG Tree View primitive. Indentation via --tree-depth (set per node); collapse hides the
   child group (out of the a11y tree); the folder icon swaps closed↔open on aria-expanded and the chevron
   twists; leaf status (new/updated/deleted) reads at a glance via a glyph + row treatment. Ramp-lawful. */
.tree,
.tree-group {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Collapsed folder — its children leave the tree entirely (not just visually). */
.tree-node[aria-expanded="false"] > .tree-group {
    display: none;
}

.tree-row {
    display: flex;
    align-items: center;
    gap: 6px;
    height: var(--tree-row-height);
    padding-inline-end: 8px;
    /* Depth costs from the level BELOW the root, so the top row sits flush with its container (#1330). It used
       to be `depth * indent`, which charged the root a full step it had not earned: the container already pads
       (the editor's panel pads by 16px — exactly one step), so level one paid for the same space twice and the
       whole tree looked shoved right. The `- 1` removes only the LEADING space; every gap between levels is
       still one full --tree-indent, so nothing below the root shifts relative to anything else. */
    padding-inline-start: calc(6px + (var(--tree-depth, 1) - 1) * var(--tree-indent));
    border-radius: var(--tree-border-radius);
    cursor: pointer;
    color: var(--text);
    user-select: none;
}

.tree-row:hover {
    background: var(--tree-row-hover-background-color);
}

/* Roving focus lands on the <li> (role=treeitem); ring the row. */
.tree-node:focus {
    outline: none;
}

.tree-node:focus-visible > .tree-row,
.tree-node:focus > .tree-row {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
}

.tree-node.is-selected > .tree-row {
    background: var(--tree-row-selected-background-color);
    font-weight: 600;
}

/* Chevron twist — points right when collapsed, down when expanded. Leaves get an inert spacer so their
   labels align with folder labels. */
.tree-twist {
    display: inline-flex;
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    color: var(--tree-twist);
    transition: transform 0.12s ease;
}

.tree-twist svg {
    width: 14px;
    height: 14px;
}

.tree-node[aria-expanded="true"] > .tree-row > .tree-twist {
    transform: rotate(90deg);
}

.tree-twist-leaf {
    visibility: hidden;
}

/* Folder icon swaps on state; leaf shows the document glyph. */
.tree-icon {
    display: inline-flex;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    color: var(--tree-icon);
}

.tree-icon svg {
    width: 16px;
    height: 16px;
}

.tree-node[aria-expanded="true"] > .tree-row > .tree-icon-closed,
.tree-node[aria-expanded="false"] > .tree-row > .tree-icon-open {
    display: none;
}

.tree-label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-sm);
}

/* Leaf status — the change each file carries, readable at a glance. */
.tree-status {
    flex: 0 0 auto;
    width: 1.1em;
    text-align: center;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.tree-status-new {
    color: var(--tree-status-new);
}

.tree-status-updated {
    color: var(--tree-status-updated);
}

.tree-status-deleted {
    color: var(--tree-status-deleted);
}

/* new = additive accent on the icon; deleted = struck + dimmed name + danger accent. */
.tree-row-new .tree-icon {
    color: var(--tree-status-new);
}

.tree-row-deleted .tree-label {
    text-decoration: line-through;
    color: var(--text-muted);
}

.tree-row-deleted .tree-icon {
    color: var(--tree-status-deleted);
}

/* @toc Typography panel */
/*
 * Typography panel — the Editor's type authoring surface, the sibling of theme-panel's colour surface. Same
 * hairline-ruled schedule grammar (sections with .ui-label heads, compact rhythm) so the two panels read as
 * one system in the rail. Every colour comes from a semantic token; there is no swatch exception to make here,
 * because the specimen previews a FONT, not a fill.
 *
 * Container-first per the CSS-modernity contract: the panel lives in a rail whose width the user can change by
 * docking, so the element axes reflow on the PANEL's own inline size (@container), never the viewport. Base
 * rules are the narrow layout; the wider arrangement is the enhancement, min-width polarity.
 */

.typography-panel {
    container-type: inline-size;
}

/* ---- The schedule: hairline-ruled sections ---- */
.typography-panel-section {
    padding: var(--space-4) 20px;
    border-block-end: 1px solid var(--line);
}

.typography-panel-section:last-child {
    border-block-end: none;
}

.typography-panel-head {
    margin: 0 0 var(--space-3);
}

/* ---- 1 · Font families ---- */
.typography-panel-family {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.typography-panel-family + .typography-panel-family {
    margin-block-start: var(--typography-panel-row-gap, var(--space-2));
}

/*
 * The specimen previews the family by rendering in it. Fixed box so a wide face (or a fallback that resolves
 * to something unexpected) cannot shove the select around as the value changes.
 */
.typography-panel-specimen {
    flex: none;
    display: grid;
    place-items: center;
    inline-size: 40px;
    block-size: 40px;
    font-size: var(--typography-panel-specimen-font-size, 20px);
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.typography-panel-family .typography-panel-control,
.typography-panel-family > .dropdown {
    flex: 1 1 auto;
    min-inline-size: 0;
}

.typography-panel-family > .dropdown > .typography-panel-control {
    inline-size: 100%;
    justify-content: space-between;
}

/* ---- 2 · Per-element type ---- */
.typography-panel-element + .typography-panel-element {
    margin-block-start: var(--space-4);
}

.typography-panel-element-name {
    margin: 0 0 var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/*
 * Base (narrow rail): the three axes stack. min-inline-size: 0 lets a select shrink inside the grid instead of
 * forcing an overflow — without it the native control's intrinsic width wins and the panel scrolls sideways.
 */
.typography-panel-axes {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-2);
}

.typography-panel-axes > * {
    min-inline-size: 0;
}

/* The enhancement: once the PANEL itself is wide enough, the three axes sit on one line. */
@container (min-width: 22rem) {
    .typography-panel-axes {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* ---- Honest empty state ---- */
.typography-panel-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* @toc Uploads gallery */
/* Uploads gallery — the bulk file dropper, the upload grid, and the modal viewer.
 * Self-contained (no cross-component class deps); theme tokens throughout.
 * View: uploads-gallery.blade.php; behaviour: uploads-gallery.js. */

.uploads-gallery {
    max-width: var(--max-w-4xl);
}

.uploads-gallery-upload {
    margin-bottom: 28px;
}

/* The big bulk drop zone — the whole uploader is this one click/drop target. */
.uploads-drop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 190px;
    padding: 48px 24px;
    text-align: center;
    border: 2px dashed var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    color: var(--text-muted);
    font-size: var(--text-base);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.uploads-drop:hover,
.uploads-drop.is-over {
    border-color: var(--primary);
    color: var(--text);
}

/* Keyboard focus — the file <input> is .sr-only (focusable but off-screen), so its focus ring must ride
 * the visible drop-zone label (WCAG 2.4.7). :focus-visible = keyboard only; the transparent outline is the
 * forced-colors fallback (box-shadow is dropped in high-contrast, so the outline gets promoted). */
.uploads-drop:has(input:focus-visible) {
    border-color: var(--primary);
    color: var(--text);
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--primary);
}

/* While the batch posts, dim the zone and stop further drops. */
.uploads-drop.is-busy {
    opacity: 0.6;
    pointer-events: none;
}

.uploads-drop-icon {
    display: inline-flex;
    width: 30px;
    height: 30px;
    color: var(--text-muted);
}

.uploads-drop-icon svg {
    width: 100%;
    height: 100%;
}

.uploads-drop-hint {
    font-weight: 600;
    color: var(--text);
}

.uploads-drop-sub {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.uploads-empty {
    margin-top: 8px;
}

/* The grid of uploaded files. */
.uploads-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 14px;
    margin-top: 8px;
}

.uploads-tile {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    padding: 16px;
    text-align: left;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    color: var(--text);
    cursor: pointer;
    font: inherit;
    transition: border-color 0.15s ease;
}

.uploads-tile:hover,
.uploads-tile:focus-visible {
    border-color: var(--primary);
}

.uploads-tile-icon {
    display: inline-flex;
    width: 22px;
    height: 22px;
    color: var(--text-muted);
}

.uploads-tile-icon svg {
    width: 100%;
    height: 100%;
}

.uploads-tile-title {
    font-weight: 600;
    line-height: 1.3;
    word-break: break-word;
}

.uploads-tile-meta {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
}

/* Modal viewer — a reusable <x-dialog>. The default .dialog is content-width with a
 * padded scrolling body; these id/class-scoped overrides make the viewer large and let
 * the iframe fill it. .uploads-viewer lands on the native <dialog> (class merge). */
.uploads-viewer {
    width: min(1000px, calc(100vw - 32px));
}

.uploads-viewer .dialog-panel {
    height: min(90vh, calc(100vh - 64px));
}

.uploads-viewer .dialog-body {
    padding: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.uploads-viewer-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
}

.uploads-modal-title {
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.uploads-viewer .uploads-modal-frame {
    flex: 1 1 auto;
    width: 100%;
    border: 0;
}

/* @toc Whiteboard canvas */
/* WhiteboardCanvas (W2) — the plane. Geometry only; the toolbar (W3) and shell own everything around it.
 *
 * THE BOARD OWNS SCROLLING AND BOUNDS, because the page is <x-app-shell :flush="true"> and the shell does not
 * scroll. So this is the scroll container, and `overflow: hidden` is deliberate: panning is a transform, not a
 * scrollbar — a scrollable board would give two ways to move the same content that disagree.
 */

.wb-board {
    --wb-safe-area-top: env(safe-area-inset-top);
    --wb-safe-area-right: env(safe-area-inset-right);
    --wb-safe-area-bottom: env(safe-area-inset-bottom);
    --wb-safe-area-left: env(safe-area-inset-left);
    background-color: var(--whiteboard-canvas-background);
    /* The dotted plane, straight from the signed-off wireframe. */
    background-image: radial-gradient(circle, var(--whiteboard-canvas-dot) 1px, transparent 1px);
    background-size: var(--whiteboard-canvas-dot-spacing) var(--whiteboard-canvas-dot-spacing);
    /* ★ FILLS A CONSTRAINED PARENT, BUT CAN NEVER COLLAPSE TO NOTHING. `block-size: 100%` alone resolves to
       ZERO whenever no ancestor has a definite height — which is every context except the flush shell, and it
       renders a completely blank board with every gate still green. Found by looking at a truth shot, not by a
       test. The floor is what makes the component honest anywhere it is placed (a docs page, design:render, a
       narrower host); inside the flush shell the 100% still wins and it fills exactly as before. */
    block-size: 100%;
    inline-size: 100%;
    min-block-size: var(--whiteboard-canvas-min-height);
    overflow: hidden;
    position: relative;
    /* Load-bearing: without it the browser claims every drag for its own scroll/pinch and neither pan nor
       pinch-zoom ever reaches the island. */
    touch-action: none;
    overscroll-behavior: contain;
}

.wb-board[data-wb-tool="hand"],
.wb-board[data-wb-space] {
    cursor: grab;
}

.wb-board.is-panning {
    cursor: grabbing;
}

.wb-board:focus-visible {
    outline: 2px solid var(--whiteboard-canvas-focus);
    outline-offset: -2px;
}

/* transform-origin 0 0 so the pan/zoom maths is a plain affine map from the board's top-left — a centred
   origin would put a second offset into every screen↔world conversion. */
.wb-plane {
    inset-block-start: 0;
    inset-inline-start: 0;
    position: absolute;
    transform-origin: 0 0;
    will-change: transform;
}

.wb-ink,
.wb-objects {
    inset-block-start: 0;
    inset-inline-start: 0;
    overflow: visible;
    position: absolute;
}

.wb-ink {
    /* Ordinary strokes stay decorative. Connector hit paths opt back into pointer input below. */
    block-size: 100%;
    inline-size: 100%;
    pointer-events: none;
}

.wb-connector-hit {
    cursor: pointer;
    pointer-events: stroke;
}

.wb-connector[data-wb-locked],
.wb-connector[data-wb-locked] .wb-connector-hit {
    pointer-events: none;
}

.wb-connector-path,
.wb-connector-arrow,
.wb-connector-label {
    pointer-events: none;
}

.wb-connector.is-selected .wb-connector-path {
    filter: drop-shadow(0 0 2px var(--whiteboard-canvas-selection));
}

.wb-connector-endpoint {
    fill: var(--whiteboard-canvas-background);
    opacity: 0;
    pointer-events: none;
    stroke: var(--whiteboard-canvas-selection);
    stroke-width: 2px;
}

.wb-connector.is-selected:not([data-wb-locked]) .wb-connector-endpoint {
    cursor: crosshair;
    opacity: 1;
    pointer-events: all;
}

.wb-attachment-ports circle {
    fill: var(--whiteboard-canvas-background);
    pointer-events: none;
    stroke: var(--whiteboard-canvas-selection);
    stroke-width: 2px;
}

.wb-obj {
    position: absolute;
    user-select: none;
}

/* ★ A LOCKED OBJECT IS TRANSPARENT TO HIT-TESTING (James's ruling). Not "harder to select" — untouchable, so a
   click passes straight through to the plane and pan and draw work over a pinned photo. */
.wb-obj[data-wb-locked] {
    pointer-events: none;
}

.wb-obj.is-selected {
    outline: 2px solid var(--whiteboard-canvas-selection);
    outline-offset: 2px;
}

.wb-group.is-selected {
    box-shadow: 0 0 0 2px var(--whiteboard-canvas-selection);
}

.wb-obj img {
    block-size: 100%;
    display: block;
    inline-size: 100%;
    /* contain, so an image placed at its natural aspect is never silently distorted by a resize. */
    object-fit: contain;
}

.wb-note {
    background: var(--wb-object-note-background, var(--whiteboard-canvas-note-background));
    border-radius: var(--whiteboard-canvas-note-border-radius);
    box-shadow: var(--whiteboard-canvas-note-shadow);
    padding: var(--whiteboard-canvas-note-padding);
}

.wb-obj-text {
    color: var(--wb-object-text-color, var(--whiteboard-canvas-text-color));
    font-size: var(--wb-object-text-size, var(--whiteboard-canvas-text-font-size));
    inline-size: 100%;
    overflow-wrap: anywhere;
    word-break: break-word;
    white-space: pre-wrap;
}

.wb-note > .wb-obj-text,
.wb-rect > .wb-obj-text,
.wb-ellipse > .wb-obj-text {
    block-size: 100%;
    overflow: hidden;
}

.wb-rect > .wb-obj-text,
.wb-ellipse > .wb-obj-text {
    padding: var(--whiteboard-canvas-note-padding);
}

.wb-text[data-wb-size="sm"] .wb-obj-text {
    font-size: var(--whiteboard-canvas-text-sm-font-size);
}

.wb-text[data-wb-size="md"] .wb-obj-text {
    font-size: var(--whiteboard-canvas-text-md-font-size);
}

.wb-text[data-wb-size="lg"] .wb-obj-text {
    font-size: var(--whiteboard-canvas-text-lg-font-size);
}

/* The button owns the pointer target. Its pseudo-element is the fixed 8px visible handle. */
.wb-resize-handle {
    appearance: none;
    background: transparent;
    border: 0;
    block-size: calc(24px / var(--wb-view-scale, 1));
    inline-size: calc(24px / var(--wb-view-scale, 1));
    margin: 0;
    padding: 0;
    position: absolute;
    z-index: 2;
}

.wb-resize-handle::after {
    background: var(--whiteboard-canvas-background);
    border: calc(2px / var(--wb-view-scale, 1)) solid var(--whiteboard-canvas-selection);
    block-size: calc(8px / var(--wb-view-scale, 1));
    box-sizing: border-box;
    content: "";
    inline-size: calc(8px / var(--wb-view-scale, 1));
    inset: 50% auto auto 50%;
    position: absolute;
    transform: translate(-50%, -50%);
}

.wb-resize-handle[data-wb-handle="nw"] {
    cursor: nwse-resize;
    inset-block-start: calc(-12px / var(--wb-view-scale, 1));
    inset-inline-start: calc(-12px / var(--wb-view-scale, 1));
}

.wb-resize-handle[data-wb-handle="ne"] {
    cursor: nesw-resize;
    inset-block-start: calc(-12px / var(--wb-view-scale, 1));
    inset-inline-end: calc(-12px / var(--wb-view-scale, 1));
}

.wb-resize-handle[data-wb-handle="se"] {
    cursor: nwse-resize;
    inset-block-end: calc(-12px / var(--wb-view-scale, 1));
    inset-inline-end: calc(-12px / var(--wb-view-scale, 1));
}

.wb-resize-handle[data-wb-handle="sw"] {
    cursor: nesw-resize;
    inset-block-end: calc(-12px / var(--wb-view-scale, 1));
    inset-inline-start: calc(-12px / var(--wb-view-scale, 1));
}

@media (pointer: coarse) {
    .wb-resize-handle {
        block-size: calc(44px / var(--wb-view-scale, 1));
        inline-size: calc(44px / var(--wb-view-scale, 1));
    }

    .wb-resize-handle[data-wb-handle="nw"] {
        inset-block-start: calc(-22px / var(--wb-view-scale, 1));
        inset-inline-start: calc(-22px / var(--wb-view-scale, 1));
    }

    .wb-resize-handle[data-wb-handle="ne"] {
        inset-block-start: calc(-22px / var(--wb-view-scale, 1));
        inset-inline-end: calc(-22px / var(--wb-view-scale, 1));
    }

    .wb-resize-handle[data-wb-handle="se"] {
        inset-block-end: calc(-22px / var(--wb-view-scale, 1));
        inset-inline-end: calc(-22px / var(--wb-view-scale, 1));
    }

    .wb-resize-handle[data-wb-handle="sw"] {
        inset-block-end: calc(-22px / var(--wb-view-scale, 1));
        inset-inline-start: calc(-22px / var(--wb-view-scale, 1));
    }
}

/* The zoom pill's styles are GONE with the pill. Its controls, its readout and Copy link all live in the
   toolbar rail now — one home for every board control — so there is nothing here to style. */

/* The pill's phone-lift rule is gone with the pill. It was a `@container` query that never fired (nothing
 * on this chain declares `container-type`), fixed to viewport media in the previous card, and now deleted
 * outright — the rail it was avoiding is the thing that absorbed it. */

/* The editor is in SCREEN space (not the plane), so it stays a readable size at any zoom and can be clamped
   into the visualViewport when the keyboard opens. */
.wb-editor {
    background: var(--whiteboard-canvas-note-background);
    border: 1px solid var(--whiteboard-canvas-selection);
    border-radius: var(--whiteboard-canvas-note-border-radius);
    color: var(--whiteboard-canvas-text-color);
    /* 16px minimum or iOS zooms the whole page on focus — which would fight the board's own zoom. */
    font-size: max(16px, var(--whiteboard-canvas-text-font-size));
    padding: var(--whiteboard-canvas-note-padding);
    position: absolute;
    resize: none;
    z-index: 3;
}

.wb-editor[hidden],
.wb-marquee[hidden],
.wb-status[hidden],
.wb-save-status [hidden] {
    display: none;
}

.wb-marquee {
    background: var(--whiteboard-canvas-marquee-fill);
    border: 1px solid var(--whiteboard-canvas-selection);
    pointer-events: none;
    position: absolute;
    z-index: 1;
}

.wb-status {
    background: var(--whiteboard-canvas-problem-background);
    border: 1px solid var(--whiteboard-canvas-problem-line);
    border-radius: var(--whiteboard-canvas-note-border-radius);
    color: var(--whiteboard-canvas-problem-color);
    inset-block-start: var(--whiteboard-canvas-gap);
    inset-inline: var(--whiteboard-canvas-gap);
    margin: 0 auto;
    max-inline-size: 44rem;
    padding: var(--whiteboard-canvas-note-padding);
    position: absolute;
    z-index: 4;
}

/* Save truth stays compact and above the plane. It reports server acknowledgement separately from the local
   recovery copy, with ordinary buttons for retry and conflict choices. */
.wb-save-status {
    align-items: center;
    background: var(--whiteboard-canvas-pill-background);
    border: 1px solid var(--whiteboard-canvas-pill-line);
    border-radius: var(--whiteboard-canvas-note-border-radius);
    display: flex;
    flex-wrap: wrap;
    font-size: var(--whiteboard-canvas-label-font-size);
    gap: calc(var(--whiteboard-canvas-gap) * 2);
    inset-block-end: var(--whiteboard-canvas-gap);
    inset-inline-start: var(--whiteboard-canvas-gap);
    max-inline-size: calc(100% - var(--whiteboard-canvas-gap) * 2);
    padding: calc(var(--whiteboard-canvas-gap) * 2);
    position: absolute;
    z-index: 4;
}

.wb-save-status[data-wb-save-state="attention"] {
    background: var(--whiteboard-canvas-problem-background);
    border-color: var(--whiteboard-canvas-problem-line);
    color: var(--whiteboard-canvas-problem-color);
}

.wb-conflict-actions,
.wb-save-status [data-wb-recovery-picker]:not([hidden]) {
    display: flex;
    flex-wrap: wrap;
    gap: var(--whiteboard-canvas-gap);
}

@media (max-width: 768px), (max-height: 600px) {
    .wb-save-status {
        inset-block-end: var(--wb-toolbar-occupied-bottom, var(--whiteboard-canvas-dock-clearance));
    }
}

/* THE SHAPE OUTLINE. Only the STYLE lives here — the width and the colour are per-object data the canvas sets
   inline from the scene, the same split the strokes use. Until #1535 this rule did not exist at all: a rect was
   a bare positioned div with no border and no background, so a perfectly valid shape rendered as nothing, and
   the box tool looked broken even after it started minting one. The fallbacks matter because a scene may hold
   a shape drawn before that fix, whose element the canvas styles the same way but which predates nothing —
   they simply keep an unstyled shape visible rather than invisible. */
.wb-rect,
.wb-ellipse {
    border-color: var(--whiteboard-canvas-ink-black, currentColor);
    border-style: solid;
    border-width: 2px;
}

/* An ellipse is a rect with a full radius — same box, same stroke/fill, one declaration's difference. */
.wb-ellipse {
    border-radius: 50%;
}

/* Stored board ink keeps its authored color in every theme. Every drawable kind uses this one color ramp. */
.wb-board {
    --whiteboard-canvas-ink-black: #000000;
    --whiteboard-canvas-ink-white: #ffffff;
    --whiteboard-canvas-ink-red: var(--palette-red-500);
    --whiteboard-canvas-ink-orange: var(--palette-orange-500);
    --whiteboard-canvas-ink-amber: var(--palette-amber-500);
    --whiteboard-canvas-ink-yellow: var(--palette-yellow-500);
    --whiteboard-canvas-ink-lime: var(--palette-lime-500);
    --whiteboard-canvas-ink-green: var(--palette-green-500);
    --whiteboard-canvas-ink-emerald: var(--palette-emerald-500);
    --whiteboard-canvas-ink-teal: var(--palette-teal-500);
    --whiteboard-canvas-ink-cyan: var(--palette-cyan-500);
    --whiteboard-canvas-ink-sky: var(--palette-sky-500);
    --whiteboard-canvas-ink-blue: var(--palette-blue-500);
    --whiteboard-canvas-ink-indigo: var(--palette-indigo-500);
    --whiteboard-canvas-ink-violet: var(--palette-violet-500);
    --whiteboard-canvas-ink-purple: var(--palette-purple-500);
    --whiteboard-canvas-ink-fuchsia: var(--palette-fuchsia-500);
    --whiteboard-canvas-ink-pink: var(--palette-pink-500);
    --whiteboard-canvas-ink-rose: var(--palette-rose-500);
    --whiteboard-canvas-ink-slate: var(--palette-slate-500);
    --whiteboard-canvas-ink-gray: var(--palette-gray-500);
    --whiteboard-canvas-ink-zinc: var(--palette-zinc-500);
    --whiteboard-canvas-ink-neutral: var(--palette-neutral-500);
    --whiteboard-canvas-ink-stone: var(--palette-stone-500);
}

/* ★ THE HEIGHT CHAIN THE PAGE IS MISSING (#1489).
 *
 * W1 landed `whiteboard-board.blade.php` with `.wb` and `.wb-viewport` but no page stylesheet — nothing in
 * resources/css styles either class. So both size to content, no ancestor offers a definite height, and
 * `.wb-board`'s own `min-block-size` floor is the only thing left standing: measured at 375, the board was
 * exactly 384px, which is that 24rem floor rather than a layout.
 *
 * The board owns bounds (spec §1), so it takes them here. `1px` basis with `flex: 1` is what makes the child
 * fill the flex parent instead of its content: a percentage height would need a definite height above it,
 * which is the thing that is missing.
 *
 * SCOPED TO THE WHITEBOARD PAGE, and declared in the canvas's own sheet because there is no page sheet to put
 * it in — if W1 (or #1490) adds one, this belongs there instead. */
.wb {
    /* Stretches to the flush main (a flex column), rather than a percentage that cannot resolve. */
    display: flex;
    flex: 1 1 1px;
    flex-direction: column;
    min-block-size: 0;

    /* ★ THE POSITIONING CONTEXT FOR BOARD CHROME (W3). The toolbar's desktop rail is `position: absolute`,
       and this is the ancestor it means — the board root, exactly as `.wbw-board` is in the signed-off
       wireframe. Without it the rail finds NO positioned ancestor, falls through to the initial containing
       block, and docks to the left edge of the VIEWPORT: measured on /whiteboard/dante, it rendered on top of
       the shell's sidebar. `.wb-board` being relative does not help — it sits inside `.wb-viewport`, a SIBLING
       of the toolbar slot, so it is not on the rail's ancestor chain at all.

       It lives here rather than in the toolbar's own sheet so that every `.wb` rule stays in one place; if a
       whiteboard page stylesheet is ever added, this block moves there with the height chain above it. */
    position: relative;
}

.frame-panels[data-wb-workbench] {
    block-size: 100%;
    flex: 1 1 1px;
    min-block-size: 0;
}

.frame-panels[data-wb-workbench] > .workbench-stage {
    display: flex;
    flex-direction: column;
    min-block-size: 0;
}

@media (max-width: 768px), (max-height: 600px) {
    /* Workbench becomes a block layout below its workspace breakpoint. Give its stage the established Whiteboard
       height explicitly so the flex viewport does not collapse to its one-pixel canvas border. */
    .frame-panels[data-wb-workbench] > .workbench-stage {
        block-size: 100%;
    }

    /* Without the complete Frame panel projection, its server-rendered docks stay in normal flow after the
       stage. Make the Whiteboard root its reachable scroll owner and restore the canvas's standalone floor. */
.frame-panels[data-wb-workbench]:not(.is-wired.has-shared-bottom-bar) {
    display: block;
    block-size: 100%;
    overflow-y: auto;
}

.frame-panels[data-wb-workbench]:not(.is-wired.has-shared-bottom-bar) > .workbench-stage,
.frame-panels[data-wb-workbench]:not(.is-wired.has-shared-bottom-bar) > .workbench-stage > .wb {
    block-size: auto;
    min-block-size: 100%;
    overflow: visible;
}

    .frame-panels[data-wb-workbench]:not(.is-wired.has-shared-bottom-bar) .wb-viewport > .wb-board {
        flex-basis: auto;
        min-block-size: var(--whiteboard-canvas-min-height);
    }
}

.wb-viewport {
    display: flex;
    flex: 1 1 1px;
    min-block-size: 0;
}

.wb-viewport > .wb-board {
    /* Beats the standalone floor: inside the page the board is a flex child, not a min-height box. */
    flex: 1 1 1px;
    min-block-size: 0;
}

/* Feedback placement owns the page gesture. The document-level Feedback listener can still locate and record
   the page point, while the canvas receives no selection, drawing or click-placement pointer event. */
body.is-placing-note .wb-board,
body.is-placing-note .wb-board * {
    pointer-events: none !important;
}

/* ── GROUPS ─────────────────────────────────────────────────────────────────────────────────────────────
 * A boundary is a derived rectangle drawn behind its members, plus a name chip. It lives inside the plane, so
 * it pans and zooms with what it bounds.
 */
.wb-groups {
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 100%;
    position: absolute;
    /* The layer is a container for boundaries, not a surface: clicks must reach the objects it sits over. The
       chip re-enables pointer events for itself, because it is the one thing here you can actually press. */
    pointer-events: none;
}

.wb-group {
    border: 1px dashed var(--whiteboard-canvas-group-line);
    border-radius: var(--whiteboard-canvas-note-border-radius);
    position: absolute;
}

/* The chip hangs ABOVE the boundary rather than inside it, so it never covers a member's own content — the
   objects are the thing being read, and the label is only how the group is addressed. */
.wb-group-chip {
    background: var(--whiteboard-canvas-group-chip-background);
    border: 1px solid var(--whiteboard-canvas-group-line);
    border-radius: 999px;
    color: var(--whiteboard-canvas-group-chip-color);
    cursor: text;
    font-size: var(--whiteboard-canvas-label-font-size);
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 100%;
    line-height: 20px;
    max-inline-size: 100%;
    min-block-size: var(--whiteboard-canvas-group-header-min);
    overflow-wrap: anywhere;
    padding: 4px 10px;
    pointer-events: auto;
    position: absolute;
    text-align: start;
    white-space: normal;
}

.wb-group-chip::before {
    block-size: max(100%, calc(44px / var(--wb-view-scale, 1)));
    content: "";
    inline-size: max(100%, calc(44px / var(--wb-view-scale, 1)));
    inset: 50% auto auto 50%;
    position: absolute;
    transform: translate(-50%, -50%);
}

.wb-group-chip:focus-visible {
    outline: 2px solid var(--whiteboard-canvas-focus);
    outline-offset: 2px;
}

/* The copied confirmation moved to the toolbar with the button. A ` ✓` appended to a text label made sense on
   the old pill's "Copy link" button; the rail's control is a 44px icon square, so the rail styles it its own
   way. Nothing here owns that element any more. */

/* W7 (#1488) — the flowchart kit. A NODE (rect/ellipse with a label) centers its text; the label rides the
   shape's own box, so it moves and resizes with it for free. Flex, not text-align, so multi-line labels
   stay centered on both axes. */
.wb-rect .wb-obj-text,
.wb-ellipse .wb-obj-text {
    align-items: center;
    block-size: 100%;
    display: flex;
    justify-content: center;
    text-align: center;
    padding: var(--whiteboard-canvas-note-padding);
}

/* The connector's small midpoint label, in the ink SVG. It inherits the world transform (the layer is
   transformed as a whole), stays out of hit-testing like every stroke, and reads at the note text colour so
   it follows the theme, never the connector's ink. */
.wb-connector-label {
    font-size: var(--whiteboard-canvas-text-font-size);
    pointer-events: none;
}

.wb-connector-label rect {
    fill: var(--whiteboard-canvas-note-background);
}

.wb-connector-label text {
    fill: var(--whiteboard-canvas-text-color);
}

.wb-clipboard-dialog {
    max-inline-size: calc(100% - var(--space-6));
}

.wb-clipboard-dialog .textarea-field {
    max-block-size: 45dvh;
}

.wb-board[data-wb-click-mode] {
    cursor: crosshair;
}

/* @toc Whiteboard images */
/* Whiteboard images — the drop affordance. The component renders one hidden input, so this styles the
 * feedback only: while a file is over the board, the board says so. */

.whiteboard-images {
    display: contents;
}

/* Applied to [data-wb-root] by the island while a file is dragged over it. An inset outline rather than a
   border: a border would change the board's box and shift the plane under the cursor mid-drag. */
.is-drop-target {
    outline: 2px dashed var(--whiteboard-images-drop-outline, var(--primary));
    outline-offset: -4px;
    /* The tint is DERIVED from the same token, not a second one — alpha on a background via color-mix is
       sanctioned by the colour law, and it means the highlight can never disagree with its own outline. */
    background-color: color-mix(in srgb, var(--whiteboard-images-drop-outline, var(--primary)) 12%, transparent);
}

/* The input is `sr-only` in the markup — a real, focusable control that is merely out of view. This is the
   standard pattern rather than `display: none`, which would make it unreachable by keyboard and unreliable
   to `.click()` from script. */

/* @toc Whiteboard toolbar */
/* Whiteboard toolbar (W3) — the tool rail and its contextual panel, built to the ratified wireframe artifact.
 *
 * MOBILE-FIRST: the base rules below ARE the phone dock, and the desktop rail is the enhancement layered on at
 * `--bp-md-up`. The artifact writes it the other way round (a bespoke `max-width: 700px`); the polarity and the
 * seam are the house standard and the rendering is identical either side of the boundary.
 *
 * On DESKTOP the rail and the panel are siblings positioned independently against the board, so the root is
 * `display: contents` and carries no box. On the PHONE they are one stack and the root IS that stack — see
 * the dock rule immediately below for why that had to change. */

/* ★ THE PHONE DOCK IS ONE STACK, and it stopped being two independently-positioned elements the moment the
 * rail could be more than one row tall.
 *
 * Both used to be `position: fixed` with their own `inset-block-end`, and the popover cleared the rail by
 * adding a literal `64px` — one row's height. Once the rail WRAPS that number is a guess: measured at 320 the
 * rail is 163px tall, so the panel opened 99px INSIDE it, and the swatches under the overlap could not be
 * pressed at all. The press landed on the rail, counted as an outside-press, and closed the panel — a colour
 * that silently refused to change, with nothing in the console.
 *
 * A stack has no arithmetic to get wrong. `column-reverse` puts the rail (first in the DOM, because it is
 * first for a reader) at the BOTTOM and stacks any open panel above it, whatever height either turns out to
 * be. The magic number is deleted rather than corrected. */
.whiteboard-toolbar-root {
    position: fixed;
    z-index: 2;
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    gap: var(--space-2);

    /* ★ CENTRED BY AUTO MARGINS, NOT BY `left: 50%` + `translateX(-50%)`, and the difference is 160px of rail.
     * A fixed element sized shrink-to-fit gets its available width from its inset edge to the containing
     * block's far edge — so `left: 50%` offered the dock only the RIGHT HALF of the viewport to wrap inside.
     * Measured at 375: the rail came out 188px wide and 246px tall, five rows, while 351px of room sat unused
     * on the left. `inset-inline` + `margin-inline: auto` offers the FULL width and centres the result, so
     * the rail wraps into the fewest rows the viewport can hold. Same visual intent, honest arithmetic. */
    inset-inline-start: calc(env(safe-area-inset-left) + var(--space-3));
    inset-inline-end: calc(env(safe-area-inset-right) + var(--space-3));
    margin-inline: auto;
    inline-size: fit-content;
    /* The rail keeps its measured flex height. Open panels share the remaining real stack height and scroll. */
    max-block-size: min(var(--wb-toolbar-available-block, 100dvh),
        calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom)
            - var(--whiteboard-toolbar-dock-gap, var(--space-5)) - var(--space-3)));

    /* THE PHONE RULING: pinned to the VIEWPORT bottom, never the board's own bottom edge — the wireframe
       reproduced exactly the bug the spec warns about, where chrome docked to the canvas scrolls away with it.
       ★ ADDITIVE, NOT `max()` (James, 2026-08-02: breathing room "on top of the safe-area inset rather than
       instead of it"). `max(space-3, safe-area)` picks ONE — on a device with an inset the 12px gap simply
       disappeared into the home-indicator clearance, so the rail read as jammed against the floor on exactly
       the phones that have one. `calc(inset + gap)` keeps both: the inset clears the hardware, the gap is the
       visible space above it. On a device with no inset the term is 0 and this is just the gap. */
    inset-block-end: calc(env(safe-area-inset-bottom) + var(--whiteboard-toolbar-dock-gap, var(--space-5)));
}

.whiteboard-toolbar,
.whiteboard-toolbar-popover {
    display: flex;
    align-items: center;
    background: var(--whiteboard-toolbar-surface, var(--surface-raised));
    border: 1px solid var(--whiteboard-toolbar-border, var(--line));
    border-radius: var(--whiteboard-toolbar-border-radius, var(--radius-container));
    /* The rail has to read as floating above the plane. The artifact hard-codes one rgba; --shadow-lg is the
       theme's own floating-surface elevation and SELF-FLIPS (0.1 alpha on light, 0.5 on dark), so the same
       rail lifts correctly off a white board and off a black one. */
    box-shadow: var(--whiteboard-toolbar-shadow, var(--shadow-lg));

    flex-direction: row;

    /* ★ IT WRAPS. IT DOES NOT SCROLL — measured, and the scroll was the whole problem.
     *
     * At 320 the rail held 894px of controls in a 294px window: SEVEN of seventeen visible, with undo, redo,
     * image, group and zoom all behind a horizontal swipe. A swipe to an unknown position is not a control
     * you can reach; it is a control you have to go looking for.
     *
     * Wrapping makes every control visible with ZERO actions, which is what the acceptance actually asks
     * for, and the dock sits OVER a pannable board — so an extra row COVERS board rather than removing it,
     * and anything underneath can be panned out. That is the trade, and it is a much better one than hiding
     * half the toolbar. */
    max-inline-size: 100%;
    flex-wrap: wrap;
    justify-content: center;
}

.whiteboard-toolbar {
    flex-shrink: 0;
    gap: var(--whiteboard-toolbar-gap, var(--space-1));
    padding: var(--space-1);
}

.whiteboard-toolbar-popover {
    gap: var(--space-1);
    min-block-size: 0;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    padding: var(--space-2);
}

/* `hidden` must win. Without this the display:flex above would keep a hidden panel visible — the classic
   author-display-beats-[hidden] trap, and here it would leave dead chrome floating over the board. */
.whiteboard-toolbar-popover[hidden] {
    display: none;
}

.whiteboard-toolbar-popover[data-wb-legacy-connector-panel],
.whiteboard-toolbar-popover[data-wb-legacy-appearance-panel],
.whiteboard-arrange-controls {
    align-items: stretch;
    flex-direction: column;
    inline-size: min(22rem, calc(100vw - 2 * var(--space-3)));
}

.whiteboard-toolbar-popover[data-wb-legacy-connector-panel] > legend,
.whiteboard-toolbar-popover[data-wb-legacy-appearance-panel] > legend {
    color: var(--text);
    font-weight: 700;
    padding-inline: var(--space-1);
}

.whiteboard-connector-controls label,
.whiteboard-appearance-controls label {
    color: var(--text-muted);
    display: grid;
    font-size: var(--text-sm);
    gap: var(--space-1);
}

.whiteboard-connector-controls input,
.whiteboard-connector-controls select,
.whiteboard-connector-controls textarea,
.whiteboard-appearance-controls select,
.whiteboard-connector-delete {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--text);
    min-block-size: var(--whiteboard-toolbar-target-size, 44px);
    padding-inline: var(--space-2);
}

.whiteboard-appearance-controls [hidden] {
    display: none;
}

.whiteboard-context-panel,
.whiteboard-context-panel [data-wb-appearance-fields],
.whiteboard-context-panel [data-wb-connector-fields] {
    display: grid;
    gap: var(--space-3);
    min-inline-size: 0;
}

.whiteboard-context-panel [hidden] {
    display: none;
}

.whiteboard-context-empty,
.whiteboard-connector-hint {
    color: var(--text-muted);
    margin: 0;
}

.whiteboard-arrange-controls > button {
    background: transparent;
    border: 0;
    border-radius: var(--whiteboard-toolbar-control-border-radius, var(--radius-control));
    color: var(--text);
    cursor: pointer;
    min-block-size: var(--whiteboard-toolbar-target-size, 44px);
    padding: var(--space-2) var(--space-3);
    text-align: start;
}

.whiteboard-arrange-controls > button:hover:not(:disabled) {
    background: var(--surface);
}

.whiteboard-arrange-controls > button:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.whiteboard-arrange-controls > button:disabled,
.whiteboard-toolbar-tool:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

.whiteboard-arrange-hint {
    color: var(--text-muted);
    font-size: var(--text-xs);
    margin: 0;
    padding: var(--space-2);
}

.whiteboard-connector-controls textarea {
    padding-block: var(--space-2);
    resize: vertical;
}

.whiteboard-connector-delete {
    cursor: pointer;
}

.whiteboard-connector-hint {
    color: var(--text-muted);
    font-size: var(--text-xs);
    margin: 0;
}

/* ★ AND THE SAME RULE FOR CONTROLS, WHICH WAS MISSING — so "contextual" never actually happened.
 *
 * `.whiteboard-toolbar-tool` sets `display: inline-flex`, which out-ranks the UA's `[hidden] { display: none }`
 * exactly as it does for the panel above. The JS has always set `btn.hidden = true` on the colour and width
 * pickers while a non-drawing tool is armed, and it always had no effect: the property was true, the buttons
 * painted anyway, and they stayed in the accessibility tree too (a11y hidden is computed from `display`, not
 * from the attribute). So the rail was 15 controls at rest, not 13, and every claim that the pickers appear
 * only while you can draw was false in the browser while being true in the DOM.
 *
 * ★ IT SURVIVED BECAUSE EVERY CHECK READ `el.hidden` — the property — instead of what the element paints.
 * The property agreed with the JS, so measurement after measurement said 13 while the screenshot said 15, and
 * the number was the thing being trusted. It was the SCREENSHOT that caught it: counting the icons in the
 * picture disagreed with counting the elements in the DOM. Measure the artifact, not a proxy for it. */
.whiteboard-toolbar-tool[hidden] {
    display: none;
}

/* Every target is >= 44px on EVERY device, not only on the phone — one size, so the desktop and phone targets
   cannot drift apart as the rail changes. */
.whiteboard-toolbar-tool,
.whiteboard-toolbar-swatch,
.whiteboard-toolbar-width {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    inline-size: var(--whiteboard-toolbar-target-size, 44px);
    block-size: var(--whiteboard-toolbar-target-size, 44px);
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text);
    cursor: pointer;
}

.whiteboard-toolbar-tool,
.whiteboard-toolbar-width {
    border-radius: var(--whiteboard-toolbar-control-border-radius, var(--radius-control));
}

.whiteboard-toolbar-tool:hover,
.whiteboard-toolbar-width:hover {
    background: var(--surface);
}

.whiteboard-toolbar-tool:focus-visible,
.whiteboard-toolbar-width:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* The fallback rail retains its filled active tool. Controls mounted into FloatingDock leave this ancestor,
   so the shared dock alone owns their selected icon, caption, tint, border, hover, and focus treatment. */
.whiteboard-toolbar .whiteboard-toolbar-tool[aria-pressed="true"],
.whiteboard-toolbar .whiteboard-toolbar-tool[aria-pressed="true"]:hover {
    background: var(--primary-600);
    color: var(--on-primary);
}

/* ★ THE STICKY COPY-LINK CAP IS GONE, because the thing it worked around is gone.
 *
 * It pinned Copy link to the trailing edge so the hero flow's last step was not behind a horizontal swipe.
 * The rail no longer scrolls — it wraps — so every control including this one is simply visible, and a
 * sticky element in a non-scrolling container is a background and a stacking context for no reason.
 *
 * A workaround outliving its problem is how a component accretes; this one lasted exactly as long as it was
 * needed. */

/* COPY LINK's confirmation. The old pill appended a ` ✓` to the words "Copy link"; the rail's control is a
   44px icon square with no text to append to, so it confirms by turning the success colour for a moment.
   `--success` and `--success-surface` are the vetted status pair, so the contrast is settled by construction.
   It must out-rank the sticky background above, which is why it is written after it. */
.whiteboard-toolbar-tool[data-wb-copied],
.whiteboard-toolbar-tool[data-wb-copied]:hover,
.floating-dock .floating-dock-actions > .whiteboard-toolbar-tool[data-wb-copied],
.floating-dock .floating-dock-actions > .whiteboard-toolbar-tool[data-wb-copied]:hover {
    background: var(--success-surface);
    color: var(--success);
}

/* The fallback rail keeps its filled open picker. A mounted picker inherits FloatingDock's shared expanded
   state treatment while aria-expanded remains the single behavioral source of truth. */
.whiteboard-toolbar .whiteboard-toolbar-picker[aria-expanded="true"],
.whiteboard-toolbar .whiteboard-toolbar-picker[aria-expanded="true"]:hover {
    background: var(--primary-600);
    color: var(--on-primary);
}

/* The CURRENT COLOUR, shown on the closed control. Same 18px dot as an option so the closed control and the
   thing it stands for look identical — you are reading the value, not a summary of it. */
.whiteboard-toolbar-picker-swatch {
    inline-size: var(--whiteboard-toolbar-swatch-size, 18px);
    block-size: var(--whiteboard-toolbar-swatch-size, 18px);
    border-radius: 50%;
    background: var(--swatch, var(--text));
    border: 2px solid var(--surface);
    outline: 1px solid var(--line);
}

.whiteboard-toolbar-icon {
    display: inline-flex;
}

.whiteboard-toolbar-icon svg {
    inline-size: 20px;
    block-size: 20px;
}

/* The one glyph control: text is drawn as a literal T, because no icon says "text" as plainly as the letter. */
.whiteboard-toolbar-tool-glyph {
    font-size: 1.05rem;
    font-weight: 700;
}

/* Decorative only — the groups either side of it carry the real boundary as labelled groups. */
/* ★ A FULL-WIDTH RULE, NOT A VERTICAL TICK — because the row it divided now wraps.
   As a 1px vertical line it worked only while the rail was a single row: once the controls wrapped, the
   divider landed wherever the wrap left it, hanging off the end of a row as a stray tick that divided
   nothing. Visible in the 320 screenshots, in the dock and again between the swatch sections.
   `flex-basis: 100%` makes it take a line of its own, which both draws a real boundary and forces the group
   after it to start a new row — so the dock reads as tools, rule, actions rather than as a run of icons. */
.whiteboard-toolbar-sep {
    flex: 0 0 100%;
    inline-size: auto;
    block-size: 1px;
    background: var(--line);
    margin-block: var(--space-1);
}

/* ★ THE SECTIONS WRAP TOO. Eight swatches in an unwrapped row is 352px, and the phone popover has 296 at 320
   — so a whole section hung off the panel's edge and the colours out there could not be pressed. It read as
   "the colour will not change": the press landed on the board, counted as an outside-press, and closed the
   panel with nothing selected and nothing logged.
   Found by driving a real press at rose and reading back which colour was selected, rather than by looking at
   a screenshot of a panel that appeared to be fine. */
.whiteboard-toolbar-swatches,
.whiteboard-toolbar-widths {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
}

/* The visible dot is --swatch-size, but the TARGET is the full 44px: the padding is the hit area, so a small
   swatch never becomes a small target (the artifact's own swatch is 18px square, which fails even WCAG 2.5.8's
   24px floor). */
.whiteboard-toolbar-swatch::before {
    content: "";
    inline-size: var(--whiteboard-toolbar-swatch-size, 18px);
    block-size: var(--whiteboard-toolbar-swatch-size, 18px);
    border-radius: 50%;
    /* --swatch is set per-button in the view: a palette family's -500, or the black/white literal. */
    background: var(--swatch);
    border: 2px solid var(--surface);
    outline: 1px solid var(--line);
}

.whiteboard-toolbar-swatch:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -4px;
    border-radius: var(--whiteboard-toolbar-control-border-radius, var(--radius-control));
}

/* Selection is a RING plus the announced aria-pressed — never colour alone, which would be invisible on the
   swatch whose own colour is the ring's. */
.whiteboard-toolbar-swatch[aria-selected="true"]::before {
    outline: 2px solid var(--text);
    outline-offset: 1px;
}

.whiteboard-toolbar-width[aria-selected="true"] .whiteboard-toolbar-dot {
    outline: 2px solid var(--text-muted);
    outline-offset: 2px;
}

/* The dot shows the width without labelling it. It is drawn at `dot`, not at the stored `px` — at 3px a
   truthful dot would be a speck. */
.whiteboard-toolbar-dot {
    inline-size: var(--dot);
    block-size: var(--dot);
    border-radius: 50%;
    background: var(--text);
}

/* THE DOCS PREVIEW BOX. The rail is viewport-pinned by design, and `.doc-example-demo` is an unpositioned box
   — so without this a docs page with three examples would stack three rails at the bottom of the BROWSER
   WINDOW instead of in their boxes, and the desktop rail would resolve against whatever ancestor happened to
   be positioned. `contain: paint` makes this the containing block for fixed and absolute descendants, which is
   exactly the job it exists for, and the dots stand in for the plane so the rail is read against what it sits
   on. Used only by this component's own examples. */
.whiteboard-toolbar-demo {
    position: relative;
    contain: paint;
    /* Phone height: the dock is one row, so the box only has to be tall enough to read as a board. The
       desktop rule below grows it, because a vertical rail of eleven controls is ~34rem and `contain` CLIPS
       what does not fit — the first render of this box cut select and pen clean off the top. */
    block-size: 18rem;
    border-radius: var(--radius-container);
    background-color: var(--surface);
    background-image: radial-gradient(circle, var(--line) 1px, transparent 1px);
    background-size: 24px 24px;
}

/* The zoom percentage. Sized to the rail's own width rather than the text, so "100%" and "25%" occupy the
   same box and the buttons either side never shift as you zoom — a readout that moves its neighbours is a
   readout you cannot press next to. `tabular-nums` for the same reason at the digit level. */
.whiteboard-toolbar-readout {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    inline-size: var(--whiteboard-toolbar-target-size, 44px);
    min-block-size: var(--whiteboard-toolbar-readout-height, 24px);
    /* ★ INHERITS THE BUTTON'S COLOUR, and must. The readout used to be a bare span sitting BETWEEN controls,
       so `--text-muted` was right for it. It is now the zoom control's own label — and while that control is
       open the button paints itself `--primary-600` / `--on-primary`, which left muted grey text on a filled
       primary square: a pair nobody vetted, on the one control whose whole job is to be read. Inheriting
       keeps it on the button's own vetted pair in every state, at rest and expanded. */
    color: inherit;
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    /* The label, not a target: the press belongs to the button around it, so this never intercepts one. */
    pointer-events: none;
    user-select: none;
}

.whiteboard-toolbar-empty {
    margin: 0;
    padding: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ── desktop: the vertical rail ───────────────────────────────────────────────────────────────────────
   Viewport media on the `--bp-*` seam, not a container query: the rail is page/layout chrome positioned
   against the board, which is the split the CSS-modernity contract draws (@container is for a component's own
   box). ABSOLUTE, not fixed — the rail belongs to the board's box, so it sits beside the canvas rather than
   over the shell's sidebar. It requires the board root to be positioned (W2's plane, as in the artifact).

   HEIGHT IS PART OF THE CONDITION, not just width — the mobile-first inverse of the shell's own
   `max-width: md, max-height: short`. An 844x390 landscape iPhone is WIDER than md-up, so a width-only query
   would hand it the vertical rail plus a ~450px panel inside a 390px-tall viewport. A short viewport is a
   phone however wide it is, and gets the bottom dock. */
@media (min-width: 769px) and (min-height: 601px) {
    /* THE STACK IS A PHONE SHAPE. On desktop the rail is a left palette and the panel opens BESIDE it, so the
       two go back to positioning themselves against the board and the root goes back to carrying no box.

       ★ THE RAIL'S WIDTH IS DECLARED ONCE, HERE, and the panel reads the same value to sit beside it. It was
       a literal `60px` in the panel's offset, which was right only while the rail was one 44px column — the
       moment it became two the panel opened on top of it. One number, one place. */
    .whiteboard-toolbar-root {
        display: contents;
        max-block-size: none;
        --wb-rail-inline-size: calc(2 * var(--whiteboard-toolbar-target-size, 44px)
            + var(--whiteboard-toolbar-gap, var(--space-1)) + 2 * var(--space-1) + 2px);
    }

    .whiteboard-toolbar,
    .whiteboard-toolbar-popover {
        position: absolute;
        z-index: 2;
        flex-direction: column;
        inset-block-start: 50%;
        inset-block-end: auto;
        inset-inline-start: var(--space-3);
        inset-inline-end: auto;
        max-block-size: calc(100% - 2 * var(--space-3));
        max-inline-size: none;
        transform: translateY(-50%);
    }

    /* ★ TWO COLUMNS ON PURPOSE — a palette, not a column that happened to wrap.
     *
     * Fifteen 44px controls is a 739px column, and a 1280x800 laptop leaves 740px of board. It "fits" by one
     * pixel, so in practice it wrapped: fourteen controls in the left column and COPY LINK ALONE in a second
     * one, floating vertically centred beside the others. That is what the screenshot showed, and it reads as
     * broken rather than as a second column.
     *
     * Declaring the grid makes the shape intentional and much smaller: 8 rows instead of 15, ~380px instead
     * of 691px, so the palette stops dominating the left third of the board and fits every desktop height
     * rather than only tall ones. Two columns of icon buttons is the classic tool-palette shape, and it is
     * the same answer the phone dock gives — everything visible, nothing behind a scroll.
     *
     * `overflow-y` stays as the last resort beneath the bound, for a rail longer than any board. */
    .whiteboard-toolbar {
        display: grid;
        grid-template-columns: repeat(2, var(--whiteboard-toolbar-target-size, 44px));
        justify-items: center;
        inline-size: var(--wb-rail-inline-size);
        max-block-size: calc(100% - 2 * var(--space-3));
        overflow-y: auto;
        overscroll-behavior-y: contain;
    }

    .whiteboard-toolbar-popover {
        /* Beside the rail, reading the rail's own declared width plus a gap — never a literal. */
        inset-inline-start: calc(var(--space-3) + var(--wb-rail-inline-size) + var(--space-2));
    }

    /* The rule spans BOTH grid columns — a divider sitting in one cell would be a half-width dash, which
       reads as a stray line rather than as a boundary. In the POPOVER, which is a flex COLUMN here, the
       phone's `flex-basis: 100%` would resolve on the main axis and make the rule full HEIGHT, so the basis
       goes back to auto and `align-self` gives it the width instead. */
    .whiteboard-toolbar-sep {
        grid-column: 1 / -1;
        flex: 0 0 auto;
        align-self: stretch;
        margin-block: var(--space-1);
    }

    /* ★ BACK TO THE ARTIFACT'S TWO COLUMNS — the constraint that forced four is gone.
       Four was never a preference. The old strip was ALWAYS OPEN beside a vertical rail, so its height was
       height the board permanently lost: at 44px targets, two columns made 24 swatches 528px and the strip
       ~700px, which does not fit an 800px laptop once centred. Four columns traded width for height to fit.
       A POPOVER is not bound by that. It is transient, it is not beside anything, and it may be tall and
       scroll on its own without pushing the board around — so the arithmetic was RE-DERIVED under the new
       constraint rather than inherited, and two columns is what reads best (approved 🧠 Franny 2026-08-02).
       The `max-block-size` + `overflow-y` below is what makes "tall is fine" actually true. */
    .whiteboard-toolbar-swatches {
        display: grid;
        grid-template-columns: repeat(2, auto);
        justify-items: center;
    }

    /* Belt and braces: even at four columns the panel must never exceed the board it floats over, so it
       scrolls rather than clipping if a future colour set outgrows the viewport. */
    .whiteboard-toolbar-popover {
        max-block-size: calc(100% - 2 * var(--space-3));
        overflow-y: auto;
    }

    .whiteboard-toolbar-widths {
        flex-direction: column;
    }

    /* Room for the full vertical rail plus the contextual panel beside it — see the note on the phone height. */
    .whiteboard-toolbar-demo {
        block-size: 38rem;
    }

}

/* The zoom popover is three commands in a row — no grid, no sections, nothing to scroll. */
.whiteboard-toolbar-popover-zoom {
    flex-direction: row;
}

/* The standalone Objects fallback uses the same x-panel frame as the real editor's Workbench panel. */
.whiteboard-objects-panel {
    align-self: stretch;
    border: 1px solid var(--panel-border-color, var(--line));
    border-radius: var(--whiteboard-toolbar-border-radius, var(--radius-container));
    box-shadow: var(--whiteboard-toolbar-shadow, var(--shadow-lg));
    inline-size: min(34rem, calc(100vw - var(--space-6)));
    max-block-size: min(70dvh, 42rem);
    overflow: hidden;
    overscroll-behavior: contain;
    z-index: 3;
}

.whiteboard-objects-panel[hidden] {
    display: none;
}

.whiteboard-objects-panel > .panel-head {
    flex: 0 0 auto;
    margin: 0;
    padding: var(--space-3);
}

.whiteboard-objects-panel > .panel-body {
    min-block-size: 0;
    overflow-y: auto;
}

.whiteboard-objects-panel-body {
    display: grid;
    gap: var(--space-3);
    min-inline-size: 0;
}

.whiteboard-objects-panel .whiteboard-objects-panel-body {
    padding: var(--space-3);
}

.whiteboard-objects-actions {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.whiteboard-objects-panel fieldset,
.whiteboard-objects-panel p,
.whiteboard-objects-panel-body fieldset,
.whiteboard-objects-panel-body p {
    margin: 0;
}

.whiteboard-objects-panel button,
.whiteboard-objects-panel-body button,
.whiteboard-object-geometry input {
    min-block-size: 44px;
}

.whiteboard-object-list {
    block-size: min(18rem, 40dvh);
    display: grid;
    flex: 0 0 min(18rem, 40dvh);
    gap: var(--space-1);
    list-style-position: inside;
    margin: 0;
    overflow-y: auto;
    padding: 0;
}

.whiteboard-object-row {
    border-block-end: 1px solid var(--line);
    overflow-wrap: anywhere;
    padding-block: var(--space-2);
}

.whiteboard-object-row > label {
    align-items: flex-start;
    display: flex;
    gap: var(--space-2);
}

.whiteboard-object-row input[type="checkbox"] {
    block-size: 24px;
    flex: 0 0 24px;
    inline-size: 24px;
}

.whiteboard-object-members {
    color: var(--text-muted);
    margin-block-start: var(--space-1);
    padding-inline-start: var(--space-6);
}

.whiteboard-object-editing,
.whiteboard-click-actions {
    display: grid;
    gap: var(--space-2);
}

.whiteboard-object-geometry {
    display: grid;
    gap: var(--space-2);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.whiteboard-object-geometry label {
    display: grid;
    gap: var(--space-1);
}

.whiteboard-object-geometry button {
    grid-column: 1 / -1;
}

@media (min-width: 769px) and (min-height: 601px) {
    .whiteboard-objects-panel {
        inset-block-start: var(--space-3);
        inset-inline-end: var(--space-3);
        position: absolute;
    }
}

/* The shell bar is the actual editor's toolbar. Keep the old rail as an honest fallback until the public
   contribution mount succeeds, then remove its empty shell and anchor transient pickers above the real bar. */
.whiteboard-toolbar-root[data-wb-shared-bar].is-shared-bar-mounted {
    display: contents;
}

.whiteboard-toolbar-root[data-wb-shared-bar].is-shared-bar-mounted > .whiteboard-toolbar {
    display: none;
}

.whiteboard-toolbar-root[data-wb-shared-bar].is-shared-bar-mounted > .whiteboard-toolbar-popover {
    position: fixed;
    z-index: 22;
    inset-block-start: auto;
    inset-block-end: max(
        calc(var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height, 61px)) + var(--space-3)),
        var(--wb-save-control-occupied-block-end, 0px)
    );
    inset-inline: var(--space-3);
    margin-inline: auto;
    max-block-size: min(
        60dvh,
        calc(100dvh - max(
            var(--app-bottom-bar-occupied-block-end, 61px),
            var(--wb-save-control-occupied-block-end, 0px)
        ) - var(--space-6))
    );
    max-inline-size: min(36rem, calc(100vw - 2 * var(--space-3)));
    inline-size: fit-content;
    transform: none;
}

[data-shared-bottom-bar] .whiteboard-toolbar-sep {
    align-self: stretch;
    flex: 0 0 1px;
    inline-size: 1px;
    block-size: auto;
    margin: var(--space-1);
}

/* @toc Wishlist */
/* Wishlist — a core, standalone intake box: a short compose form plus the author's (or, for
 * admins, everyone's) list of wishes with a New/Doing/Done status. Built on primitives
 * (x-textarea / x-select / x-input / x-button), so this sheet themes only the wishlist's own
 * composition + the status chips. View: wishlist.blade.php. No JS — plain server forms. */

.wishlist {
    max-width: var(--max-w-3xl);
    margin-top: 32px;
}

.wishlist-head {
    margin-bottom: 12px;
}

.wishlist-h {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text);
}

.wishlist-h-ru {
    margin-inline-start: 8px;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-muted);
}

.wishlist-lead {
    margin: 4px 0 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.wishlist-form {
    margin: 14px 0 24px;
}

.wishlist-form-actions {
    margin-top: 10px;
}

/* The list of wishes — newest first, a coloured leading (inline-start) edge per status. */
.wishlist-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.wishlist-item {
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-inline-start: 3px solid var(--text-muted);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.wishlist-item-doing {
    border-inline-start-color: var(--warning);
}

.wishlist-item-done {
    border-inline-start-color: var(--success);
}

.wishlist-item-body {
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
}

.wishlist-item-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 8px;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.wishlist-status {
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: var(--radius-full, 999px);
    color: var(--text-muted);
    background: color-mix(in srgb, var(--text-muted) 16%, transparent);
}

/* Active-status chips ride the vetted self-contrasting token pair (token on its own
 * -surface tint is AA by construction), replacing the ad-hoc palette tints. The New
 * chip above stays a neutral --text-muted wash — it carries no status hue. */
.wishlist-status-doing {
    color: var(--warning);
    background: var(--warning-surface);
}

.wishlist-status-done {
    color: var(--success);
    background: var(--success-surface);
}

/* Admin's reply back to the author. */
.wishlist-item-note {
    display: flex;
    gap: 8px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--line);
    font-size: var(--text-sm);
    color: var(--text);
}

.wishlist-item-note-label {
    flex-shrink: 0;
    font-weight: 600;
    color: var(--text-muted);
}

/* Admin status control — a compact row of primitives (status select + reply + save). The
 * primitives theme themselves; this only lays them out, growing the note input to fill. */
.wishlist-admin {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
}

.wishlist-admin .input {
    flex: 1;
    min-width: 160px;
}

.wishlist-empty {
    margin-top: 4px;
}

/* @toc Workout */
/*
 * Workout styles the gated page's active session, immutable history, and setup.
 * Set tables remain fully visible at phone widths; composed line charts retain their own scroll regions.
 * Color decisions come from config/components/workout.php for both theme modes.
 */
.workout {
    container-type: inline-size;
    display: grid;
    gap: 0.75rem;
    min-width: 0;
    color: var(--workout-text-color);
}

.workout-save-status {
    margin: 0;
    color: var(--workout-muted-text-color);
    font-size: 0.875rem;
    font-weight: 600;
}

.workout-save-status:not([data-state="error"]),
.workout-dialog-status:not([data-state="error"]),
.workout-set-state:not([data-state="failed"]) {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

.workout-save-status[data-state="saving"] { color: var(--workout-accent-color); }
.workout-save-status[data-state="saved"] { color: var(--workout-success-color); }
.workout-save-status[data-state="error"] { color: var(--workout-danger-color); }

.workout-dialog-status {
    margin: 0;
    padding: 0.75rem 1rem;
    color: var(--workout-danger-color);
    font-weight: 600;
}

.workout-empty,
.workout-navigation,
.workout-exercise,
.workout-options-section,
.workout-import,
.workout-cycle-row {
    border-block: 1px solid var(--workout-border-color);
    border-inline: 0;
    border-radius: 0;
    background: transparent;
}

.workout-empty {
    display: grid;
    justify-items: start;
    gap: 0.75rem;
    padding: clamp(1.25rem, 5cqi, 3rem);
}

.workout-empty h2,
.workout-empty p,
.workout-page-intro h2,
.workout-page-intro p,
.workout-program-row h3,
.workout-program-row p,
.workout-cycle-group h2,
.workout-cycle-row h3,
.workout-cycle-row h4,
.workout-cycle-row p,
.workout-progress h2,
.workout-progress p,
.workout-session-head h2,
.workout-completion h3,
.workout-exercise h3,
.workout-options-section h3,
.workout-import h3 {
    margin: 0;
}

.workout-program-row h2 {
    margin: 0;
}

.workout-kicker {
    margin: 0;
    color: var(--workout-accent-color);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.workout-return-nav,
.workout-row-actions,
.workout-cycle-schedule nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.workout-return-nav {
    margin-bottom: 1.25rem;
    color: var(--workout-muted-text-color);
    font-size: 0.875rem;
}

.workout-page-intro,
.workout-cycle-group {
    display: grid;
    gap: 0.75rem;
}

.workout-program-list,
.workout-cycle-group {
    display: grid;
    gap: 0;
    min-width: 0;
}

.workout-cycle-row {
    min-width: 0;
    padding: 1rem;
}

.workout-program-row,
.workout-cycle-row > header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.workout-dashboard {
    gap: 0;
}

.workout-dashboard .workout-program-row {
    align-items: center;
    min-width: 0;
    padding: 0;
    border: 0;
}

.workout-program-row > div:first-child,
.workout-cycle-row > header > div {
    display: grid;
    gap: 0.35rem;
    min-width: 0;
}

.workout-cycle-group > header {
    display: grid;
    gap: 0.35rem;
    padding-block: 0.5rem;
}

.workout-cycle-row {
    display: grid;
    gap: 1rem;
}

.workout-cycle-dates {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.75rem;
    margin: 0;
}

.workout-cycle-dates div {
    display: grid;
    gap: 0.15rem;
}

.workout-cycle-dates dt,
.workout-cycle-schedule h4 {
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
}

.workout-cycle-dates dd {
    margin: 0;
    font-weight: 600;
}

.workout-cycle-schedule {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border-top: 1px solid var(--workout-border-color);
}

.workout-cycle-schedule section {
    display: grid;
    gap: 0.5rem;
    min-width: 0;
    padding: 0.75rem;
    border-inline-end: 1px solid var(--workout-border-color);
}

.workout-cycle-schedule section:last-child {
    border-inline-end: 0;
}

.workout-cycle-schedule a {
    display: grid;
    gap: 0.15rem;
    min-height: 2.75rem;
    padding: 0.65rem 0.25rem;
    border-bottom: 1px solid transparent;
    color: var(--workout-text-color);
    font-size: 0.875rem;
    text-decoration: none;
}

.workout-cycle-schedule a small {
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
}

.workout-schedule-form {
    display: grid;
    grid-template-columns: minmax(12rem, 20rem) max-content;
    align-items: end;
    gap: 0.75rem;
}

.workout-cycle-schedule a:hover,
.workout-cycle-schedule a:focus-visible {
    border-color: var(--workout-accent-color);
    color: var(--workout-accent-color);
}

.workout-navigation {
    display: grid;
    gap: 0;
    padding: 0 0 0.25rem;
    border: 0;
}

.workout-cycle-nav,
.workout-day-nav {
    display: grid;
    gap: 0;
    max-width: 100%;
    padding: 0;
}

.workout-day-nav {
    grid-template-columns: repeat(5, minmax(0, 1fr));
}

.workout-cycle-nav a,
.workout-day-nav a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    min-height: 2.75rem;
    padding: 0.55rem 0.7rem;
    color: var(--workout-text-color);
    font-family: var(--font-sans);
    font-size: 0.875rem;
    line-height: 1.15;
    text-decoration: none;
    text-align: center;
}

.workout-day-nav a {
    min-width: 0;
    padding-inline: 0.25rem;
    font-size: clamp(0.625rem, 3.2cqi, 0.875rem);
}

.workout-day-nav .workout-nav-label {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.workout-nav-date {
    margin-top: 0.2rem;
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
    font-weight: 500;
    white-space: nowrap;
}

.workout-day-nav .workout-nav-date {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.workout-day-nav a[data-workout-today] .workout-nav-label {
    color: var(--workout-accent-color);
    font-weight: 700;
}

.workout-cycle-nav a:hover,
.workout-day-nav a:hover {
    border-color: var(--workout-accent-color);
}

.workout-cycle-nav a[aria-current="page"],
.workout-day-nav a[aria-current="page"] {
    border-color: var(--workout-accent-color);
    background: transparent;
    color: var(--workout-accent-color);
    font-weight: 700;
}

.workout-cycle-nav a:focus-visible,
.workout-day-nav a:focus-visible,
.workout-set-form input:focus-visible,
.workout-setup-form input:focus-visible,
.workout-create-form input:focus-visible,
.workout-import input:focus-visible {
    outline: 2px solid var(--workout-focus-ring-color);
    outline-offset: 2px;
}

.workout-cycle-nav {
    margin-top: 0.5rem;
}

.workout-cycle-nav-group {
    padding: 0.5rem 0.7rem 0.2rem;
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
    font-weight: 700;
}

.workout-cycles-host {
    min-width: 0;
    min-height: 0;
}

[data-workout-cycles-tool].workspace-tool--notes {
    --workspace-tool-notes-width: 24rem;
}

[data-workout-cycles-tool] .workspace-tool-dialog,
[data-workout-cycles-tool] .workspace-tool-dialog:not([open]) {
    transform: none;
}

@starting-style {
    [data-workout-cycles-tool] .workspace-tool-dialog[open] {
        transform: none;
    }
}

[data-workout-cycles-tool].workspace-tool--notes [data-tooltip]::after {
    inset-inline-start: auto;
    inset-inline-end: 0;
    transform: translateY(4px);
}

[data-workout-cycles-tool].workspace-tool--notes [data-tooltip]:is(:hover, :focus-visible)::after {
    transform: none;
}

.workout-cycles-panel {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 1rem;
    min-width: 0;
    min-height: 0;
    padding: 1rem;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.workout-cycles-panel .accordion,
.workout-cycles-panel .accordion-header,
.workout-cycles-panel .accordion-trigger,
.workout-cycles-panel .accordion-panel,
.workout-cycles-panel .accordion-panel-inner {
    min-width: 0;
}

.workout-cycles-panel .accordion-trigger {
    width: 100%;
}

.workout-cycles-panel .accordion-title {
    min-width: 0;
    overflow-wrap: anywhere;
    text-align: start;
}

.workout-cycle-weeks {
    display: grid;
    gap: 0;
    min-width: 0;
}

.workout-cycle-weeks a {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.2rem;
    min-width: 0;
    min-height: 44px;
    padding: 0.65rem 0.75rem;
    border-inline-start: 2px solid transparent;
    color: var(--workout-text-color);
    text-decoration: none;
}

.workout-cycle-weeks a span:last-child {
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
}

.workout-cycle-weeks a:is(:hover, :focus-visible, [aria-current="page"]) {
    border-inline-start-color: var(--workout-accent-color);
    color: var(--workout-accent-color);
}

.workout-cycle-weeks a[aria-current="page"] {
    background: var(--workout-muted-background-color);
    font-weight: 700;
}

.workout-cycle-weeks a:focus-visible,
.workout-cycle-panel-action:focus-visible {
    outline: 2px solid var(--workout-focus-ring-color);
    outline-offset: -2px;
}

.workout-cycles-actions {
    display: grid;
    gap: 0.5rem;
    margin-top: auto;
    padding-top: 1rem;
    border-top: 1px solid var(--workout-border-color);
}

.workout-cycle-panel-action {
    min-width: 0;
    min-height: 44px;
    padding: 0.65rem 0.75rem;
    border: 1px solid var(--workout-border-color);
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--workout-text-color);
    font: inherit;
    font-weight: 600;
    text-align: start;
    cursor: pointer;
}

.workout-cycle-panel-action:is(:hover, :focus-visible) {
    border-color: var(--workout-accent-color);
    color: var(--workout-accent-color);
}

.workout-cycles-empty {
    margin: 0;
    color: var(--workout-muted-text-color);
}

.floating-dock .workout-cycles-launcher,
.floating-dock .workout-cycles-launcher[aria-expanded="true"] {
    inset: auto;
    visibility: visible;
}

.floating-dock .workout-cycles-launcher > span {
    display: inline-flex;
}

@media (min-width: 761px) {
    [data-workout-cycles-tool].workspace-tool--notes.is-docked .workspace-tool-dialog {
        inset-block-start: var(--app-visible-content-top, 0);
        inset-block-end: var(--app-bottom-bar-occupied-block-end, var(--app-bottom-bar-initial-occupied-height));
        height: auto;
    }
}

.workout-max-grid div {
    display: grid;
    gap: 0.15rem;
}

.workout-max-grid dt {
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
}

.workout-max-grid dd {
    margin: 0;
    font-weight: 600;
}

.workout-exercises {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    gap: 0.75rem;
    min-width: 0;
}

.workout-exercise {
    min-width: 0;
    padding: 0.5rem 0 0;
    border: 0;
    border-radius: 0;
    overflow: clip;
}

.workout-exercise > header {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    margin-bottom: 0.375rem;
    background: var(--workout-background-color);
}

.workout-exercise > header h3 {
    color: var(--workout-muted-text-color);
    font-family: var(--font-sans);
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.workout-lift-glyph {
    width: 2.5rem;
    height: 2.5rem;
    flex: 0 0 auto;
}

.workout-lift-glyph path {
    fill: none;
    stroke: var(--workout-muted-text-color);
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 2.6;
}

.workout-table-scroll {
    min-width: 0;
    max-width: 100%;
    overflow-x: clip;
}

.workout-table,
.workout-preview-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.workout-table {
    inline-size: min(100%, 24rem);
    table-layout: fixed;
    border-collapse: separate;
    border-spacing: 0 var(--space-2);
}

.workout-table thead th:nth-child(1) { width: 5rem; }
.workout-table thead th:nth-child(2) { width: auto; }
.workout-table thead th:nth-child(3) { width: 8.25rem; }
.workout-table thead th:nth-child(4) { width: 2.8125rem; }

.workout-table th,
.workout-table td,
.workout-preview-table th,
.workout-preview-table td {
    padding: 0.3125rem 0.5rem 0.3125rem 0;
    text-align: start;
    vertical-align: middle;
}

.workout-preview-table td {
    border-top: 1px solid var(--workout-border-color);
}

.workout-table thead th,
.workout-preview-table thead th {
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.workout-table thead th {
    font-size: 0.625rem;
    letter-spacing: 0.02em;
}

.workout-table td.workout-actions-cell,
.workout-table td.workout-reps-cell {
    padding-inline: 0;
}

.workout-table td.workout-reps-cell {
    text-align: end;
}

.workout-table .workout-actions-heading {
    padding-inline: 0;
    text-align: center;
}

.workout-table .workout-weight-heading {
    padding-inline-start: var(--space-2);
}

.workout-table .workout-reps-heading {
    padding-inline: 0 var(--space-2);
    text-align: end;
}

.workout-preview-table tbody tr:last-child td,
.workout-preview-table tbody tr:last-child th {
    border-bottom: 0;
}

.workout-set-row > td {
    border-block: 1px solid color-mix(in srgb, var(--workout-accent-color) 30%, var(--workout-border-color));
    background: color-mix(in srgb, var(--workout-accent-color) 9%, var(--workout-set-frame-background-color));
}

.workout-set-row > td:first-child {
    border-inline-start: 1px solid color-mix(in srgb, var(--workout-accent-color) 30%, var(--workout-border-color));
    border-start-start-radius: var(--radius-control, 0.375rem);
    border-end-start-radius: var(--radius-control, 0.375rem);
    padding-inline-start: var(--space-2);
}

.workout-set-row > td:last-child {
    border-inline-end: 1px solid color-mix(in srgb, var(--workout-accent-color) 30%, var(--workout-border-color));
    border-start-end-radius: var(--radius-control, 0.375rem);
    border-end-end-radius: var(--radius-control, 0.375rem);
    padding-inline-end: 0;
}

.workout-set-row[data-complete] > td {
    border-block-color: var(--workout-accent-color);
    background: color-mix(in srgb, var(--workout-accent-color) 22%, var(--workout-set-frame-background-color));
    color: color-mix(in srgb, var(--workout-accent-color) 62%, var(--workout-text-color));
}

.workout-set-row[data-complete] > td:first-child {
    border-inline-start-color: var(--workout-accent-color);
}

.workout-set-row[data-complete] > td:last-child {
    border-inline-end-color: var(--workout-accent-color);
}

.workout-set-row[data-complete] :is(
    .workout-equipment,
    .workout-reps-step.button,
    .workout-set-actions .checkbox
) {
    color: inherit;
}

.workout-phase-row th {
    padding: 0.5rem 0 0.25rem;
    background: transparent;
    color: var(--workout-muted-text-color);
    font-family: var(--font-sans);
    font-size: 0.625rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    text-align: start;
    text-transform: uppercase;
}

.workout-table td small {
    display: block;
    margin-top: 0.25rem;
    color: var(--workout-muted-text-color);
}

.workout-equipment {
    display: block;
    min-width: 0;
    color: var(--workout-muted-text-color);
}

.workout-weight-cell,
.workout-equipment-cell {
    min-width: 0;
}

.workout-set-weight {
    display: block;
    font-family: var(--font-sans);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.workout-barbell {
    display: block;
    width: min(100%, 8rem);
    min-width: 0;
}

.workout-bar-load {
    display: block;
    width: 100%;
    height: 2.25rem;
}

.workout-bar-metal {
    fill: var(--workout-bar-color);
}

.workout-plate {
    fill: var(--workout-plate-color);
    stroke: var(--workout-plate-border-color);
    stroke-width: 1;
}

.workout-equipment-icon {
    display: block;
    width: min(100%, 8rem);
    height: 2.25rem;
    fill: none;
    stroke: var(--workout-muted-text-color);
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 3;
}

.workout-cycle-facts {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.75rem;
    margin: 0;
}

.workout-cycle-facts div {
    display: grid;
    gap: 0.15rem;
}

.workout-cycle-facts dt {
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
}

.workout-cycle-facts dd {
    margin: 0;
    font-size: 0.875rem;
}

.workout-bodyweight-mark {
    stroke: var(--workout-accent-color);
}

.workout-bodyweight,
.workout-muted {
    color: var(--workout-muted-text-color);
}

.workout-set-form {
    position: relative;
    display: grid;
    gap: 0.25rem;
    min-width: 0;
}

.workout-set-actions {
    display: grid;
    grid-template-columns: repeat(3, 2.75rem);
    gap: 0;
    width: max-content;
}

.workout-set-actions .checkbox,
.workout-reps-step.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    width: 2.75rem;
    min-width: 2.75rem;
    min-height: 2.75rem;
    border-radius: 0;
}

.workout-set-actions .checkbox {
    color: var(--workout-muted-text-color);
}

.workout-set-actions .checkbox-box {
    width: 1rem;
    height: 1rem;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: currentColor;
}

.workout-set-actions .checkbox-check {
    opacity: 1;
}

.workout-set-actions .checkbox-input:checked ~ .checkbox-box {
    border-color: transparent;
    background: transparent;
    color: var(--workout-accent-color);
}

.workout-set-actions .checkbox-input:checked ~ .checkbox-box .checkbox-check {
    opacity: 1;
}

.workout-set-actions .checkbox:not(.is-disabled):hover .checkbox-box,
.workout-set-actions .checkbox:not(.is-disabled):hover .checkbox-input:checked ~ .checkbox-box {
    border-color: transparent;
}

.workout-reps-step.button {
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--workout-muted-text-color);
    font-size: 1.1rem;
}

.workout-reps-step.button:active {
    background: var(--workout-muted-background-color);
    color: var(--workout-accent-color);
}

.workout-set-state {
    color: var(--workout-muted-text-color);
    font-size: 0.75rem;
}

.workout-set-form[data-save-state="failed"] .workout-set-state {
    color: var(--workout-danger-color);
}

.workout-set-form[data-save-state="saved"] .workout-set-state {
    color: var(--workout-success-color);
}

.workout-reps-native-input {
    width: 100%;
    min-height: 2.75rem;
    border: 1px solid var(--workout-border-color);
    border-radius: 0.375rem;
    background: var(--workout-input-background-color);
    color: var(--workout-text-color);
    font-family: var(--font-sans);
    text-align: center;
}

.workout[data-workout-enhanced] .workout-reps-native-input {
    position: absolute;
    width: 1px;
    height: 1px;
    min-height: 0;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

.workout-completion-status {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    min-width: 2.75rem;
    min-height: 2.75rem;
    color: var(--workout-success-color);
    font-weight: 700;
}

.workout-actions-cell > .workout-completion-status {
    margin-inline: auto;
}

.workout-reps-count {
    display: inline-flex;
    min-inline-size: 2.75rem;
    min-height: 2.75rem;
    align-items: center;
    justify-content: flex-end;
    font-family: var(--font-sans);
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    width: 100%;
    padding: 0 var(--space-2) 0 0;
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
}

.workout-reps-count strong {
    font: inherit;
}

.workout-reps-cell small {
    display: block;
    color: var(--workout-muted-text-color);
    font-size: 0.625rem;
    font-family: var(--font-sans);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.workout-max-inputs input {
    min-height: 2.5rem;
    border: 1px solid var(--workout-border-color);
    border-radius: 0.375rem;
    background: var(--workout-input-background-color);
    color: var(--workout-text-color);
    font: inherit;
}

.workout-set-form input[type="number"] {
    padding: 0;
    border: 0;
    border-inline: 1px solid var(--workout-border-color);
    background: transparent;
    color: var(--workout-text-color);
    font-family: var(--font-sans);
    min-height: 2.75rem;
    text-align: center;
    appearance: textfield;
}

.workout-set-form input[type="number"]::-webkit-inner-spin-button,
.workout-set-form input[type="number"]::-webkit-outer-spin-button {
    margin: 0;
    appearance: none;
}

.workout-progress {
    display: grid;
    gap: 1rem;
    min-width: 0;
}

.workout-progress > header {
    display: grid;
    gap: 0.5rem;
}

.workout-chart-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    min-width: 0;
}

.workout-chart-grid > * {
    min-width: 0;
}

.workout-options-section,
.workout-import,
.workout-setup-form,
.workout-create-cycle,
.workout-create-form {
    display: grid;
    gap: 1rem;
    padding: 1rem;
}

.workout-create-cycle h3,
.workout-create-cycle p {
    margin: 0;
}

.workout-create-cycle .workout-max-grid small {
    display: block;
    color: var(--workout-muted-text-color);
    font-weight: 600;
}

.workout-create-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

.workout-max-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.75rem;
    margin: 0;
}

.workout-max-inputs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
}

.workout-max-inputs label {
    display: grid;
    gap: 0.35rem;
    color: var(--workout-muted-text-color);
    font-size: 0.875rem;
    font-weight: 600;
}

.workout-max-inputs input {
    width: 100%;
    padding: 0.55rem 0.65rem;
}

.workout-plates {
    min-width: 0;
    border: 1px solid var(--workout-border-color);
    border-radius: 0;
    padding: 0.75rem;
}

.workout-plates legend {
    padding: 0 0.35rem;
    font-weight: 700;
}

.workout-plates > div {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

.workout-plates label {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.workout-import {
    margin-top: 1rem;
    background: var(--workout-muted-background-color);
}

.workout-import form {
    display: grid;
    justify-items: start;
    gap: 0.75rem;
}

.workout-import-recovery {
    display: grid;
    grid-template-columns: max-content minmax(14rem, 1fr);
    align-items: end;
    gap: 1rem;
    margin-top: 1rem;
}

.workout-import-confirm .checkbox {
    max-width: 42rem;
}

@container (max-width: 48rem) {
    .workout-exercise-heading {
        position: sticky;
        z-index: 1;
        inset-block-start: 0;
        min-block-size: 2.75rem;
        padding-block: var(--space-1);
        box-shadow: 0 1px 0 var(--workout-border-color);
    }

    .workout-chart-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .workout-session-head,
    .workout-completion {
        align-items: stretch;
        flex-direction: column;
    }

    .workout-max-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .workout-cycle-dates {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .workout-schedule-form {
        grid-template-columns: minmax(0, 1fr);
        align-items: stretch;
    }

    .workout-import-recovery {
        grid-template-columns: minmax(0, 1fr);
        align-items: stretch;
    }

    .workout-cycle-schedule {
        grid-template-columns: minmax(0, 1fr);
    }

    .workout-cycle-schedule section {
        border-inline-end: 0;
        border-bottom: 1px solid var(--workout-border-color);
    }

    .workout-cycle-schedule section:last-child {
        border-bottom: 0;
    }
}

@container (min-width: 56rem) {
    .workout-exercises {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (max-width: 24rem) {
    .workout {
        gap: 0.5rem;
    }

    .workout-navigation,
    .workout-session-head,
    .workout-completion {
        padding: 0.35rem 0;
    }

    .workout-session-facts,
    .workout-max-inputs,
    .workout-max-grid,
    .workout-cycle-facts,
    .workout-cycle-dates {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
    }

    .workout-program-row,
    .workout-cycle-row > header {
        align-items: stretch;
        flex-direction: column;
    }

    .workout-table,
    .workout-preview-table {
        font-size: 0.75rem;
    }

    .workout-table th,
    .workout-table td {
        padding: 0.3rem 0.1875rem 0.3rem 0;
    }

    .workout-table .workout-weight-cell {
        padding-inline-end: 0;
    }

    .workout-table thead th:nth-child(1) { width: 4.375rem; }
    .workout-table thead th:nth-child(2) { width: auto; }
    .workout-table thead th:nth-child(3) { width: 8.25rem; }
    .workout-table thead th:nth-child(4) { width: 2.8125rem; }

    .workout-exercise {
        padding-inline: 0;
    }

    .workout-equipment {
        font-size: 0.625rem;
    }

    .workout-equipment strong {
        font-size: 0.625rem;
    }

    .workout-bar-load,
    .workout-equipment-icon {
        width: 100%;
    }
}
.workout-enhanced-settings {
    display: none;
}

.workout[data-workout-enhanced] .workout-enhanced-settings {
    display: grid;
}

.workout-native-plate-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.workout-native-plate-actions form {
    margin: 0;
}

.workout:not([data-workout-enhanced]) #workout-management-dialog {
    display: block;
    position: static;
    width: 100%;
    max-height: none;
    margin: var(--space-4) 0 0;
    opacity: 1;
    transform: none;
}

.workout:not([data-workout-enhanced]) #workout-management-dialog .dialog-panel {
    max-height: none;
}

.workout:not([data-workout-enhanced]) #workout-management-dialog .dialog-close,
.workout:not([data-workout-enhanced]) #workout-management-dialog .workout-progress {
    display: none;
}

/* @toc Workspace dock */
/*
 * WorkspaceDock lays out the standalone rail and panel column without covering adjacent desktop content.
 * Expanded and rail widths use registered component tokens while Panel keeps ownership of body folds.
 * Accepted compact coordination changes the source to a zero-width layer with bounded shared stacking.
 */
.workspace-dock {
    --workspace-dock-expanded-width: var(--workspace-dock-width, 320px);
    --workspace-dock-rail-width: var(--workspace-dock-rail-size, 52px);
    --workspace-dock-gap: var(--workspace-dock-space, 8px);
    --workspace-dock-control-size: var(--workspace-dock-target-size, 44px);
    display: grid;
    grid-template-columns: var(--workspace-dock-rail-width) minmax(0, 1fr);
    align-content: start;
    inline-size: var(--workspace-dock-expanded-width);
    max-inline-size: 100%;
    min-inline-size: 0;
    border: 1px solid var(--line);
    background: var(--neutral-900);
    color: var(--text);
}

.workspace-dock[data-workspace-dock-edge="right"] {
    grid-template-columns: minmax(0, 1fr) var(--workspace-dock-rail-width);
}

.workspace-dock.is-empty {
    display: none;
}

.workspace-dock-rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--workspace-dock-gap);
    min-inline-size: 0;
    padding: var(--workspace-dock-gap) 4px;
    border-inline-end: 1px solid var(--line);
}

.workspace-dock[data-workspace-dock-edge="right"] > .workspace-dock-rail {
    grid-column: 2;
    grid-row: 1;
    border-inline: 1px solid var(--line) 0;
}

.workspace-dock-tools,
.workspace-dock-panel-launchers {
    display: grid;
    gap: var(--workspace-dock-gap);
}

.workspace-dock-panel-launchers {
    padding-block-start: var(--workspace-dock-gap);
    border-block-start: 1px solid var(--line);
}

.workspace-dock-tool,
.workspace-dock-panel-launcher,
.workspace-dock-disclosure {
    min-inline-size: var(--workspace-dock-control-size);
    min-block-size: var(--workspace-dock-control-size);
}

.workspace-dock-tool svg,
.workspace-dock-panel-launcher svg,
.workspace-dock-disclosure svg {
    inline-size: 20px;
    block-size: 20px;
}

.workspace-dock-disclosure {
    margin-block-start: auto;
}

.workspace-dock-chevron {
    display: none;
}

.workspace-dock[data-workspace-dock-edge="left"]:not(.is-rail) .workspace-dock-chevron-left,
.workspace-dock[data-workspace-dock-edge="left"].is-rail .workspace-dock-chevron-right,
.workspace-dock[data-workspace-dock-edge="right"]:not(.is-rail) .workspace-dock-chevron-right,
.workspace-dock[data-workspace-dock-edge="right"].is-rail .workspace-dock-chevron-left {
    display: inline-flex;
}

.workspace-dock-panels {
    display: grid;
    align-content: start;
    gap: var(--workspace-dock-gap);
    min-inline-size: 0;
    padding: var(--workspace-dock-gap);
}

.workspace-dock-panel-slot {
    min-width: 0;
    min-height: 0;
    height: 100%;
}

.workspace-dock[data-workspace-dock-edge="right"] > .workspace-dock-panels {
    grid-column: 1;
    grid-row: 1;
}

.workspace-dock.is-rail {
    grid-template-columns: var(--workspace-dock-rail-width);
    inline-size: var(--workspace-dock-rail-width);
}

.workspace-dock.is-rail > .workspace-dock-rail {
    grid-column: 1;
    border-inline: 0;
}

.workspace-dock.is-rail > .workspace-dock-panels {
    display: none;
}

/* A compact dock remains ordinary reachable content until a future frame coordinator accepts registration. */
.workspace-dock[data-workspace-dock-compact]:not([data-workspace-dock-coordinated]):not(.is-empty) {
    display: block;
    inline-size: 100%;
}

.workspace-dock[data-workspace-dock-compact]:not([data-workspace-dock-coordinated]):not(.is-empty) > .workspace-dock-rail {
    display: flex;
    flex-direction: row;
    inline-size: 100%;
    border-inline: 0;
    border-block-end: 1px solid var(--line);
}

.workspace-dock[data-workspace-dock-compact]:not([data-workspace-dock-coordinated]):not(.is-empty) > .workspace-dock-rail > .workspace-dock-panel-launchers,
.workspace-dock[data-workspace-dock-compact]:not([data-workspace-dock-coordinated]):not(.is-empty) > .workspace-dock-rail > .workspace-dock-disclosure {
    display: none;
}

.workspace-dock[data-workspace-dock-compact]:not([data-workspace-dock-coordinated]):not(.is-empty) > .workspace-dock-panels {
    display: grid;
}

/* Once a coordinator accepts compact ownership, this standalone source reserves no layout width. */
.workspace-dock[data-workspace-dock-compact][data-workspace-dock-coordinated] {
    position: relative;
    inline-size: 0;
    max-inline-size: 0;
    min-inline-size: 0;
    border: 0;
    background: transparent;
    overflow: visible;
}

.workspace-dock[data-workspace-dock-compact][data-workspace-dock-coordinated] > .workspace-dock-rail {
    position: absolute;
    z-index: var(--z-10, 10);
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: var(--workspace-dock-rail-width);
    border: 1px solid var(--line);
    background: var(--neutral-900);
}

.workspace-dock[data-workspace-dock-compact][data-workspace-dock-coordinated][data-workspace-dock-edge="right"] > .workspace-dock-rail {
    inset-inline: auto 0;
}

.workspace-dock[data-workspace-dock-compact][data-workspace-dock-coordinated]:not(.is-rail) > .workspace-dock-panels {
    position: absolute;
    z-index: var(--z-10, 10);
    inset-block-start: 0;
    inset-inline-start: var(--workspace-dock-rail-width);
    inline-size: min(var(--workspace-dock-expanded-width), calc(100vw - var(--workspace-dock-rail-width)));
    border: 1px solid var(--line);
    background: var(--neutral-900);
}

.workspace-dock[data-workspace-dock-compact][data-workspace-dock-coordinated][data-workspace-dock-edge="right"]:not(.is-rail) > .workspace-dock-panels {
    inset-inline: auto var(--workspace-dock-rail-width);
}

.workspace-dock :focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .workspace-dock,
    .workspace-dock * {
        scroll-behavior: auto;
        transition-duration: 0.01ms !important;
    }
}

/* @toc Workspace tool */
/*
 * WorkspaceTool presentation.
 * Styles one native dialog as a default top-layer overlay/right dock, or as the
 * registered contextual Feedback list at desktop and compact sizes.
 */
.workspace-tool {
    grid-column: 2;
    width: 0;
    min-width: 0;
    min-height: 0;
}

.workspace-tool.is-docked {
    width: var(--workspace-tool-dock-width, 360px);
}

.workspace-tool--notes.is-docked {
    width: 0;
}

/* Feedback has one launcher in the shared bar. Its internal WorkspaceTool opener remains the programmatic
   fallback, but it never becomes a second visible control when the tool root leaves NotesWorkbench. */
.workspace-tool--feedback > .workspace-tool-trigger {
    display: none;
}

.workspace-tool--contextual:is(.is-docked, .is-sheet) {
    width: 0;
}

.workspace-tool--notes {
    --workspace-tool-safe-top: env(safe-area-inset-top, 0px);
    --workspace-tool-safe-right: env(safe-area-inset-right, 0px);
    --workspace-tool-safe-bottom: env(safe-area-inset-bottom, 0px);
    --workspace-tool-safe-left: env(safe-area-inset-left, 0px);
}

.workspace-tool--contextual {
    --workspace-tool-contextual-left: 0px;
    --workspace-tool-contextual-top: 0px;
    --workspace-tool-contextual-width: 0px;
    --workspace-tool-contextual-height: 0px;
    --workspace-tool-contextual-sheet-height: 0px;
}

.workspace-tool-trigger {
    position: fixed;
    z-index: var(--workspace-tool-trigger-z-index, var(--z-overlay));
    inset-inline-end: calc(16px + env(safe-area-inset-right));
    inset-block-end: calc(16px + env(safe-area-inset-bottom));
    min-height: 44px;
    padding: 0 var(--workspace-tool-control-padding, 16px);
    border: 1px solid var(--line);
    border-radius: var(--workspace-tool-border-radius, var(--radius-md));
    background: var(--workspace-tool-accent-background-color, var(--primary));
    color: var(--workspace-tool-accent-color, var(--on-primary));
    box-shadow: var(--workspace-tool-shadow, var(--shadow-lg));
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.project-workbench-host.has-floating-dock .workspace-tool-trigger {
    display: none;
}

.workspace-tool-trigger[aria-expanded="true"] {
    visibility: hidden;
}

.workspace-tool-dialog {
    width: min(var(--workspace-tool-overlay-width, 420px), calc(100vw - 32px));
    max-width: none;
    height: min(var(--workspace-tool-overlay-height, 720px), calc(100vh - 32px));
    max-height: none;
    margin: auto max(16px, env(safe-area-inset-right)) auto auto;
    padding: 0;
    border: 1px solid var(--panel-border-color, var(--line));
    border-radius: var(--workspace-tool-border-radius, var(--radius-md));
    background: var(--panel-background-color, var(--page-bg));
    color: var(--panel-color, var(--text));
    box-shadow: var(--workspace-tool-shadow, var(--shadow-lg));
    overflow: hidden;
    opacity: 1;
    transform: none;
    transition:
        opacity var(--workspace-tool-duration, 180ms) ease,
        transform var(--workspace-tool-duration, 180ms) ease,
        overlay var(--workspace-tool-duration, 180ms) ease allow-discrete,
        display var(--workspace-tool-duration, 180ms) ease allow-discrete;
}

.workspace-tool-dialog:not([open]) {
    opacity: 0;
    transform: translateX(12px);
}

@starting-style {
    .workspace-tool-dialog[open] {
        opacity: 0;
        transform: translateX(12px);
    }
}

/* Feedback delegates presence motion to its shared x-panel. The native dialog remains the semantic/state host,
   but contributes no competing opacity or sideways movement. Ordinary modal tools retain the dialog transition. */
.workspace-tool--feedback .workspace-tool-dialog,
.workspace-tool--feedback .workspace-tool-dialog:not([open]) {
    opacity: 1;
    transform: none;
    transition: none;
}

@starting-style {
    .workspace-tool--feedback .workspace-tool-dialog[open] {
        opacity: 1;
        transform: none;
    }
}

.workspace-tool-dialog::backdrop {
    background: var(--workspace-tool-backdrop, var(--overlay));
    opacity: 1;
    transition:
        opacity var(--workspace-tool-duration, 180ms) ease,
        overlay var(--workspace-tool-duration, 180ms) ease allow-discrete,
        display var(--workspace-tool-duration, 180ms) ease allow-discrete;
}

.workspace-tool-dialog:not([open])::backdrop {
    opacity: 0;
}

@starting-style {
    .workspace-tool-dialog[open]::backdrop {
        opacity: 0;
    }
}

.workspace-tool.is-docked .workspace-tool-dialog {
    position: static;
    display: block;
    width: var(--workspace-tool-dock-width, 360px);
    height: 100%;
    margin: 0;
    border-block: 0;
    border-inline-end: 0;
    border-radius: 0;
    box-shadow: var(--workspace-tool-dock-shadow, var(--shadow-md));
    transform: none;
}

.workspace-tool--notes:is(.is-docked, .is-sheet) .workspace-tool-dialog {
    box-sizing: border-box;
    position: fixed;
    z-index: calc(var(--notes-workbench-pin-z-index, var(--z-popover)) + 1);
    inset-block: 0;
    inset-inline: auto 0;
    display: flex;
    width: min(var(--workspace-tool-notes-width, 370px), 100vw);
    height: 100dvh;
    margin: 0;
    border: 0;
    border-inline-start: 1px solid var(--panel-border-color, var(--line));
    border-radius: 0;
    box-shadow: none;
}

/* A desktop Feedback host reserves real layout space. The same root moves into that host, so the native
   dialog becomes an in-flow panel instead of a viewport sidebar. Compact presentation does not carry the
   dock-item marker and keeps the established fixed sheet rules below. */
.workspace-tool--feedback.is-docked[data-workspace-tool-dock-item] {
    box-sizing: border-box;
    display: flex;
    flex: 0 0 auto;
    grid-column: auto;
    width: 100%;
    min-width: 0;
    min-height: 0;
}

.workspace-tool--feedback.is-docked[data-workspace-tool-dock-item] .workspace-tool-dialog {
    position: static;
    inset: auto;
    z-index: auto;
    display: flex;
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    margin: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    transform: none;
}

.workspace-tool--feedback.is-docked[data-workspace-tool-dock-item] [data-workspace-tool-panel] {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    min-height: 0;
}

.workspace-tool--contextual:is(.is-docked, .is-sheet) .workspace-tool-dialog {
    box-sizing: border-box;
    position: fixed;
    z-index: var(--workspace-tool-trigger-z-index, var(--z-overlay));
    inset-block-end: auto;
    inset-inline-end: auto;
    display: flex;
    margin: 0;
    border: 1px solid var(--panel-border-color, var(--line));
    background: var(--panel-background-color, var(--page-bg));
    box-shadow: var(--workspace-tool-shadow, var(--shadow-lg));
}

.workspace-tool--contextual.is-docked:not([data-project-panel-placement="left"]) .workspace-tool-dialog {
    inset-block-start: var(--workspace-tool-contextual-top);
    inset-inline-start: calc(
        var(--workspace-tool-contextual-left)
        + var(--workspace-tool-contextual-width)
        - min(var(--workspace-tool-contextual-width), var(--workspace-tool-contextual-panel-width, 370px))
    );
    width: min(var(--workspace-tool-contextual-width), var(--workspace-tool-contextual-panel-width, 370px));
    height: var(--workspace-tool-contextual-height);
    border-radius: var(--workspace-tool-border-radius, var(--radius-md)) 0 0 var(--workspace-tool-border-radius, var(--radius-md));
}

.workspace-tool--contextual.is-docked[data-project-panel-placement="left"] .workspace-tool-dialog {
    inset-block-start: var(--workspace-tool-contextual-top);
    inset-inline-start: var(--workspace-tool-contextual-left);
    width: min(var(--workspace-tool-contextual-width), var(--workspace-tool-contextual-panel-width, 370px));
    height: var(--workspace-tool-contextual-height);
    border-radius: 0 var(--workspace-tool-border-radius, var(--radius-md)) var(--workspace-tool-border-radius, var(--radius-md)) 0;
}

.workspace-tool--contextual.is-sheet .workspace-tool-dialog {
    inset-block-start: calc(
        var(--workspace-tool-contextual-top)
        + var(--workspace-tool-contextual-height)
        - var(--workspace-tool-contextual-sheet-height)
    );
    inset-inline-start: var(--workspace-tool-contextual-left);
    width: var(--workspace-tool-contextual-width);
    height: var(--workspace-tool-contextual-sheet-height);
    border-block-end: 0;
    border-radius: var(--workspace-tool-border-radius, var(--radius-md)) var(--workspace-tool-border-radius, var(--radius-md)) 0 0;
}

.workspace-tool-panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-width: 0;
    min-height: 0;
}

.workspace-tool:not(.workspace-tool--notes) .workspace-tool-default-panel > .panel-head {
    flex: 0 0 auto;
    flex-wrap: wrap;
    margin: 0;
    padding:
        max(var(--workspace-tool-padding, 16px), env(safe-area-inset-top))
        max(var(--workspace-tool-padding, 16px), env(safe-area-inset-right))
        var(--workspace-tool-padding, 16px)
        max(var(--workspace-tool-padding, 16px), env(safe-area-inset-left));
    border-block-end: 1px solid var(--panel-border-color, var(--line));
}

.workspace-tool:not(.workspace-tool--notes) .workspace-tool-default-panel > .panel-head > .panel-actions {
    flex: 1 1 100%;
    min-width: 0;
}

.workspace-tool:not(.workspace-tool--notes) .workspace-tool-default-panel > .panel-body,
.workspace-tool:not(.workspace-tool--notes) .workspace-tool-default-panel > .panel-body > .panel-body-inner {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.workspace-tool--notes .workspace-tool-notes-panel {
    flex: 1 1 auto;
    width: 100%;
}

/* The non-Feedback project-notes alias remains available to established consumers such as Workout cycles. */
.workspace-tool--notes:not(:where(.workspace-tool--feedback)) .workspace-tool-notes-panel .panel-head {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-height: 60px;
    margin: 0;
    padding:
        max(8px, env(safe-area-inset-top))
        max(12px, env(safe-area-inset-right))
        8px
        max(16px, env(safe-area-inset-left));
    border-block-end: 1px solid var(--panel-border-color, var(--line));
}

.workspace-tool--notes:not(:where(.workspace-tool--feedback)) .workspace-tool-notes-panel .panel-title {
    margin: 0;
    color: var(--panel-color, var(--text));
    font-size: var(--workspace-tool-notes-title-font-size, 14px);
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: normal;
    text-transform: none;
}

.workspace-tool--notes:not(:where(.workspace-tool--feedback)) .workspace-tool-notes-panel .panel-actions {
    gap: 0;
}

.workspace-tool--contextual .workspace-tool-contextual-panel {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    min-height: 0;
}

.workspace-tool--contextual .workspace-tool-contextual-panel > .panel-head {
    padding-inline-start: max(var(--workspace-tool-padding, 16px), env(safe-area-inset-left));
    padding-inline-end: max(var(--workspace-tool-padding, 16px), env(safe-area-inset-right));
}

/* Panel's shared action policy chooses Options, direct Close, or neither from the current WorkspaceTool
   presentation. WorkspaceTool contributes only its adapter hooks and never overrides that shared decision. */

.workspace-tool--contextual .workspace-tool-contextual-panel .panel-body,
.workspace-tool--contextual .workspace-tool-contextual-panel .panel-body-inner {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
}

.workspace-tool--contextual .workspace-tool-body {
    display: block;
    width: 100%;
    min-width: 0;
    overflow: auto;
    overscroll-behavior: contain;
}

.workspace-tool--contextual [data-project-panel-title-icon] svg,
.workspace-tool--contextual .workspace-tool-close svg {
    width: 20px;
    height: 20px;
}

.workspace-tool--notes .workspace-tool-notes-panel .panel-body,
.workspace-tool--notes .workspace-tool-notes-panel .panel-body-inner {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

.workspace-tool-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
    width: 100%;
    min-width: 0;
}

.workspace-tool-action {
    min-height: 44px;
    padding: 0 12px;
    border: 1px solid var(--line);
    border-radius: var(--workspace-tool-control-border-radius, var(--radius-sm));
    background: var(--workspace-tool-control-background-color, var(--surface));
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.workspace-tool-width-label {
    display: none;
    align-items: center;
    gap: 8px;
    font-size: var(--text-xs);
}

.workspace-tool.is-docked .workspace-tool-width-label {
    display: flex;
}

.workspace-tool-width-label input {
    width: 96px;
}

.workspace-tool-trigger:focus-visible,
.workspace-tool-action:focus-visible,
.workspace-tool-width-label input:focus-visible,
.workspace-tool-body:focus-visible {
    outline: 3px solid var(--workspace-tool-focus-ring, var(--primary));
    outline-offset: 2px;
}

.workspace-tool-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding:
        var(--workspace-tool-padding, 16px)
        max(var(--workspace-tool-padding, 16px), env(safe-area-inset-right))
        max(var(--workspace-tool-padding, 16px), env(safe-area-inset-bottom))
        max(var(--workspace-tool-padding, 16px), env(safe-area-inset-left));
}

.workspace-tool--notes .workspace-tool-body {
    display: flex;
    flex: 1 1 auto;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    padding: 0;
}

.workspace-tool-body > :first-child {
    margin-top: 0;
}

.workspace-tool-body > :last-child {
    margin-bottom: 0;
}

@media (max-width: 768px), (max-height: 600px) {
    .workspace-tool {
        width: 0;
    }

    .workspace-tool:not(.workspace-tool--notes):not(.workspace-tool--contextual) .workspace-tool-dialog {
        width: 100vw;
        height: 100dvh;
        margin: 0;
        border: 0;
        border-radius: 0;
    }

    .workspace-tool-width-label,
    .workspace-tool.is-docked .workspace-tool-width-label,
    .workspace-tool-action[data-workspace-tool-dock] {
        display: none;
    }

}

.workspace-tool--notes:not(.workspace-tool--feedback).is-sheet .workspace-tool-dialog {
    position: fixed;
    z-index: calc(var(--notes-workbench-pin-z-index, var(--z-popover)) + 1);
    inset-block-start: var(--workspace-tool-sheet-top, 40dvh);
    inset-block-end: auto;
    inset-inline-start: var(--workspace-tool-visual-left, 0);
    inset-inline-end: auto;
    display: flex;
    inline-size: var(--workspace-tool-visual-width, 100vw);
    max-inline-size: none;
    block-size: var(--workspace-tool-sheet-height, 52dvh);
    max-block-size: 70dvh;
    margin: 0;
    border: 1px solid var(--panel-border-color, var(--line));
    border-block-end: 0;
    border-radius: var(--workspace-tool-border-radius, var(--radius-md)) var(--workspace-tool-border-radius, var(--radius-md)) 0 0;
    box-shadow: var(--workspace-tool-shadow, var(--shadow-lg));
    overscroll-behavior: contain;
}

.workspace-tool--notes:not(.workspace-tool--feedback).is-sheet .workspace-tool-body {
    padding-block-end: env(safe-area-inset-bottom);
}

/* Compact Feedback uses Panel's one shared sheet frame. The dialog fills the viewport as a transparent semantic
   host so it cannot add a second border, radius, shadow, or clipping layer around the fixed Panel surface. */
.workspace-tool--feedback.is-sheet .workspace-tool-dialog {
    position: fixed;
    inset: 0;
    display: block;
    inline-size: 100vw;
    max-inline-size: none;
    block-size: 100dvh;
    max-block-size: none;
    margin: 0;
    overflow: visible;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    pointer-events: none;
}

.workspace-tool--feedback.is-sheet [data-workspace-tool-panel] {
    pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
    .workspace-tool-dialog,
    .workspace-tool-dialog::backdrop {
        transition: none;
    }

    .workspace-tool-dialog:not([open]) {
        transform: none;
    }
}

/* @toc Home */
/*
 * Purpose: preserve the approved Iron Owl Dot landscape presentation on public and private native surfaces.
 * Input: semantic application tokens, declared typography roles, and native component markup.
 * Output: one mobile-first, container-responsive page matching original.html.
 * Constraints: every recipe stays under .iron-owl-page; no private URL enters public CSS.
 */
.iron-owl-page {
    --io-ink: var(--text);
    --io-muted: var(--text-muted);
    --io-paper: var(--surface);
    --io-paper-soft: var(--surface-muted);
    --io-white: var(--on-primary);
    --io-blue: var(--primary-500);
    --io-blue-strong: var(--link);
    --io-line: var(--line);
    --io-space: clamp(1.375rem, 5cqi, 5.5rem);
    margin: 0;
    background: var(--io-paper);
    color: var(--io-ink);
    font-family: var(--font-sans, Arial, sans-serif);
    font-size: 1rem;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

.iron-owl-page *,
.iron-owl-page *::before,
.iron-owl-page *::after {
    box-sizing: border-box;
}

.iron-owl-page :where(h1, h2, h3, p, figure) {
    margin: 0;
}

.iron-owl-page :where(h1, h2, h3) {
    font-family: var(--font-editorial, Rockwell, Georgia, serif);
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -0.035em;
}

.iron-owl-page :focus-visible {
    outline: 3px solid var(--io-blue-strong);
    outline-offset: 5px;
}

.iron-owl-page .iron-owl-site {
    inline-size: 100%;
    min-block-size: 100vh;
    overflow-x: clip;
    container-type: inline-size;
    background-color: var(--io-paper);
    background-image:
        linear-gradient(color-mix(in srgb, var(--text) 3%, transparent) 1px, transparent 1px),
        linear-gradient(90deg, color-mix(in srgb, var(--text) 3%, transparent) 1px, transparent 1px);
    background-size: 2rem 2rem;
}

.iron-owl-page .iron-owl-skip {
    position: fixed;
    inset-block-start: -6.25rem;
    inset-inline-start: 1rem;
    z-index: 100;
    padding: 0.75rem 1.125rem;
    background: var(--io-white);
    color: var(--io-ink);
}

.iron-owl-page .iron-owl-skip:focus {
    inset-block-start: 1rem;
}

.iron-owl-page .iron-owl-header {
    position: sticky;
    inset-block-start: 0;
    z-index: 10;
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    padding: 1rem max(var(--io-space), calc((100% - 77.5rem) / 2));
    border-block-end: 1px solid var(--io-line);
    background: color-mix(in srgb, var(--io-paper) 96%, transparent);
    backdrop-filter: blur(12px);
}

.iron-owl-page .iron-owl-wordmark.link {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    min-block-size: 44px;
    color: var(--io-ink);
    font-size: 22px;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
}

.iron-owl-page .iron-owl-brand-symbol {
    display: inline-flex;
    inline-size: 80px;
    aspect-ratio: 1190 / 570;
    flex-shrink: 0;
}

.iron-owl-page .iron-owl-brand-symbol svg {
    display: block;
    inline-size: 100%;
    block-size: auto;
}

.iron-owl-page .iron-owl-brand-type {
    display: grid;
    grid-template-columns: max-content;
    gap: 2.5px;
}

.iron-owl-page .iron-owl-brand-name {
    font-weight: 900;
    font-kerning: normal;
    letter-spacing: -0.05em;
}

.iron-owl-page .iron-owl-brand-descriptor {
    display: flex;
    justify-content: space-between;
    inline-size: 0;
    min-inline-size: 100%;
    font-size: 0.6em;
    font-weight: 900;
    letter-spacing: 0;
    line-height: 1;
}

.iron-owl-page .iron-owl-footer .link:hover {
    color: var(--io-blue-strong);
}

.iron-owl-page .iron-owl-shop.button {
    min-block-size: 44px;
    border: 1px solid var(--io-ink);
    border-radius: 0;
    background: var(--io-ink);
    color: var(--io-paper);
    font-weight: 700;
    text-decoration: none;
}

.iron-owl-page .iron-owl-shop.button:hover {
    background: color-mix(in srgb, var(--io-ink) 88%, var(--io-paper));
    color: var(--io-paper);
}

.iron-owl-page .iron-owl-shop--header.button {
    min-block-size: 44px;
    padding: 0.625rem;
    border-color: var(--line-strong);
    background: transparent;
    color: var(--io-ink);
    font-size: 0.6875rem;
}

.iron-owl-page .iron-owl-shop--header.button:hover {
    background: var(--surface-muted);
    color: var(--io-ink);
}

.iron-owl-page .iron-owl-link-icon,
.iron-owl-page .iron-owl-link-icon svg {
    display: inline-flex;
    inline-size: 1.15rem;
    block-size: 1.15rem;
}

.iron-owl-page .page-section {
    --page-section-max-width: 77.5rem;
    scroll-margin-block-start: 6rem;
    border-block-start: 1px solid var(--io-line);
    background: transparent;
}

.iron-owl-page .page-section-inner {
    padding-inline: var(--io-space);
    padding-block: 3.5rem;
}

.iron-owl-page .page-section-head {
    gap: 1rem;
    margin-block-end: 2rem;
}

.iron-owl-page .page-section-title {
    color: var(--io-ink);
    font-family: var(--font-editorial, Rockwell, Georgia, serif);
    font-size: clamp(2.2rem, 7cqi, 3.875rem);
    line-height: 1.05;
    letter-spacing: -0.035em;
}

.iron-owl-page .page-section-description {
    max-inline-size: 29.5rem;
    color: var(--io-muted);
    font-size: 0.9375rem;
}

.iron-owl-page .iron-owl-hero {
    position: relative;
    min-block-size: 30.9375rem;
    overflow: hidden;
    border-block-start: 0;
    color: var(--io-ink);
    background: var(--io-paper);
    isolation: isolate;
}

.iron-owl-page .iron-owl-hero .page-section-inner {
    position: relative;
    display: grid;
    align-items: stretch;
    max-inline-size: none;
    min-block-size: inherit;
    margin-inline: 0;
    padding: 0;
    overflow: hidden;
}

.iron-owl-page .iron-owl-hero .page-section-inner::after {
    position: absolute;
    z-index: 1;
    inset: 0;
    background: linear-gradient(
        180deg,
        color-mix(in srgb, var(--io-paper) 50%, transparent) 0%,
        color-mix(in srgb, var(--io-paper) 33%, transparent) 44%,
        transparent 66%
    );
    content: '';
    pointer-events: none;
}

.iron-owl-page .iron-owl-hero .page-section-head {
    position: relative;
    z-index: 2;
    align-self: stretch;
    justify-content: flex-start;
    inline-size: 100%;
    min-inline-size: 0;
    margin: 0;
    padding-block: 2.5rem clamp(14.0625rem, 65cqi, 19.375rem);
    padding-inline: var(--io-space);
}

.iron-owl-page .iron-owl-hero .page-section-title {
    max-inline-size: 11ch;
    font-size: clamp(2.625rem, 10.6cqi, 5.875rem);
    line-height: 1.01;
}

.iron-owl-page .iron-owl-hero .page-section-description {
    max-inline-size: 34ch;
    margin-block-start: 0;
    color: var(--io-muted);
    font-size: 0.875rem;
}

.iron-owl-page .iron-owl-hero .page-section-actions {
    margin-block-start: 0.25rem;
}

.iron-owl-page .iron-owl-shop--hero.button {
    min-block-size: 2.875rem;
    border-color: var(--io-ink);
    background: var(--io-ink);
    color: var(--io-paper);
    font-size: 0.8125rem;
}

.iron-owl-page .iron-owl-hero .page-section-body {
    position: absolute;
    z-index: 0;
    inset: 0;
    min-inline-size: 0;
    block-size: 100%;
}

.iron-owl-page .iron-owl-landscape.dot-landscape {
    min-block-size: 100%;
    block-size: 100%;
    aspect-ratio: auto;
    border: 0;
    border-radius: 0;
    background: var(--io-paper);
}

.iron-owl-page .iron-owl-landscape .dot-landscape-surface,
.iron-owl-page .iron-owl-landscape .canvas-surface,
.iron-owl-page .iron-owl-landscape canvas {
    inline-size: 100%;
    block-size: 100%;
}

.iron-owl-page .iron-owl-product-layout {
    display: grid;
    gap: 1.75rem;
    align-items: center;
    margin-block-end: 1.5rem;
}

.iron-owl-page .iron-owl-finish-preview {
    min-inline-size: 0;
    padding: 1.375rem;
    border: 1px solid var(--io-line);
    background: var(--io-paper-soft);
}

.iron-owl-page .iron-owl-finish-preview .product-finish-preview-layout {
    display: block;
}

.iron-owl-page .iron-owl-finish-preview .product-finish-preview-stage,
.iron-owl-page .iron-owl-finish-preview .product-finish-preview-controls {
    inline-size: 100%;
}

.iron-owl-page .iron-owl-finish-preview .figure-img {
    inline-size: 100%;
    max-inline-size: 25.625rem;
    aspect-ratio: 1;
    margin-inline: auto;
    object-fit: contain;
    mix-blend-mode: multiply;
}

.iron-owl-page .iron-owl-finish-preview .product-finish-preview-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-block-start: 1rem;
    border-block-start: 1px solid var(--io-line);
}

.iron-owl-page .iron-owl-finish-preview .product-finish-preview-label {
    color: var(--io-ink);
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: -0.03em;
}

.iron-owl-page .iron-owl-finish-preview .fieldset-body {
    display: flex;
    gap: 0.25rem;
}

.iron-owl-page .iron-owl-finish-preview .checkbox {
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
}

.iron-owl-page .iron-owl-finish-preview .product-finish-preview-note {
    margin-block-start: 0.8rem;
    color: var(--io-muted);
    font-size: 0.6875rem;
}

.iron-owl-page .iron-owl-features {
    min-inline-size: 0;
}

.iron-owl-page .iron-owl-feature {
    padding-block: 1.5rem;
    border-block-start: 1px solid var(--io-line);
}

.iron-owl-page .iron-owl-feature h3,
.iron-owl-page .iron-owl-step h3 {
    margin-block-end: 0.65rem;
    color: var(--io-ink);
    font-size: 1.6rem;
}

.iron-owl-page .iron-owl-feature p,
.iron-owl-page .iron-owl-step p {
    max-inline-size: 25rem;
    color: var(--io-muted);
    font-size: 0.875rem;
}

.iron-owl-page .iron-owl-product > .page-section-inner > .page-section-body > .link,
.iron-owl-page .iron-owl-setup .link,
.iron-owl-page .iron-owl-safety .link {
    display: inline-flex;
    align-items: center;
    min-block-size: 2.75rem;
    color: var(--io-ink);
    font-size: 0.8125rem;
    font-weight: 700;
    text-decoration: none;
    border-block-end: 1px solid currentColor;
}

.iron-owl-page .iron-owl-setup-layout {
    display: grid;
    gap: 1.5rem;
    align-items: center;
}

.iron-owl-page .iron-owl-step {
    display: flex;
    gap: 1.25rem;
    padding-block: 1.375rem;
    border-block-start: 1px solid var(--io-line);
}

.iron-owl-page .iron-owl-step-number {
    padding-block-start: 0.4rem;
    color: var(--io-blue-strong);
    font-family: var(--font-mono, Consolas, monospace);
    font-size: 0.6875rem;
}

.iron-owl-page .iron-owl-file-label {
    padding: 0.125rem 0.3rem;
    border: 1px solid var(--io-line);
    font-family: var(--font-mono, Consolas, monospace);
    font-size: 0.625rem;
}

.iron-owl-page .iron-owl-installation-figure .figure-img {
    inline-size: 100%;
    aspect-ratio: 600 / 440;
    object-fit: contain;
}

.iron-owl-page .iron-owl-safety {
    background: var(--io-paper-soft);
}

.iron-owl-page .iron-owl-safety .page-section-inner {
    padding-block: 2.5rem;
}

.iron-owl-page .iron-owl-safety-copy {
    display: grid;
    gap: 1rem;
    max-inline-size: 42rem;
}

.iron-owl-page .iron-owl-safety-copy p {
    color: var(--io-muted);
    font-size: 0.875rem;
}

.iron-owl-page .iron-owl-safety-copy .iron-owl-safety-lead {
    color: var(--io-ink);
    font-size: 1rem;
}

.iron-owl-page .iron-owl-questions .accordion {
    border-block-end: 1px solid var(--io-line);
}

.iron-owl-page .iron-owl-questions .accordion-item {
    border-block-start: 1px solid var(--io-line);
}

.iron-owl-page .iron-owl-closing {
    background-color: var(--io-paper);
    background-image: radial-gradient(color-mix(in srgb, var(--io-blue-strong) 16%, transparent) 0.8px, transparent 0.8px);
    background-size: 9px 9px;
}

.iron-owl-page .iron-owl-closing.section-cta {
    --section-cta-border-color: var(--io-line);
    --section-cta-background: transparent;
}

.iron-owl-page .iron-owl-footer {
    padding: 2.25rem max(var(--io-space), calc((100% - 77.5rem) / 2)) 1.5rem;
    border-block-start: 1px solid var(--io-line);
    background: var(--io-paper);
}

.iron-owl-page .iron-owl-footer-main {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 1.5rem;
    align-items: center;
}

.iron-owl-page .iron-owl-footer-main > .link:not(.iron-owl-wordmark) {
    display: inline-flex;
    align-items: center;
    min-block-size: 2.75rem;
    color: var(--io-ink);
    font-size: 0.75rem;
    text-decoration: none;
}

.iron-owl-page .iron-owl-wordmark--footer.link {
    font-size: 30px;
}

.iron-owl-page .iron-owl-wordmark--footer .iron-owl-brand-symbol {
    inline-size: 106px;
}

.iron-owl-page .iron-owl-footer-meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 1rem;
    margin-block-start: 1.625rem;
    padding-block-start: 1.375rem;
    border-block-start: 1px solid var(--io-line);
    color: var(--io-muted);
    font-family: var(--font-mono, Consolas, monospace);
    font-size: 0.625rem;
}

.iron-owl-page .iron-owl-footer-meta .link {
    color: inherit;
    font: inherit;
}

@container (min-width: 40rem) {
    .iron-owl-page .iron-owl-shop--header.button {
        padding-inline: 0.875rem;
        font-size: 0.75rem;
    }

    .iron-owl-page .iron-owl-hero .page-section-inner::after {
        background: linear-gradient(
            90deg,
            color-mix(in srgb, var(--io-paper) 40%, transparent) 0%,
            color-mix(in srgb, var(--io-paper) 13%, transparent) 45%,
            transparent 62%
        );
    }

    .iron-owl-page .iron-owl-hero {
        min-block-size: 25.625rem;
    }

    .iron-owl-page .iron-owl-hero .page-section-inner {
        grid-template-columns: minmax(0, 1fr);
    }

    .iron-owl-page .iron-owl-hero .page-section-head {
        justify-content: center;
        inline-size: 44%;
        padding-block: 2.25rem 2rem;
        padding-inline-end: 0;
    }

    .iron-owl-page .iron-owl-hero .page-section-title {
        font-size: clamp(2.875rem, 6.5cqi, 5.875rem);
    }

    .iron-owl-page .iron-owl-product-layout,
    .iron-owl-page .iron-owl-setup-layout {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 2.5rem;
    }

    .iron-owl-page .iron-owl-safety .page-section-inner {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
        gap: 3.125rem;
    }

    .iron-owl-page .iron-owl-safety .page-section-head {
        margin: 0;
    }

    .iron-owl-page .iron-owl-footer-main {
        gap: 2.2rem;
    }
}

@container (min-width: 62.5rem) {
    .iron-owl-page .iron-owl-header {
        padding-block: 1.25rem;
    }

    .iron-owl-page .iron-owl-wordmark.link {
        font-size: 26px;
    }

    .iron-owl-page .iron-owl-wordmark:not(.iron-owl-wordmark--footer) .iron-owl-brand-symbol {
        inline-size: 92px;
    }

    .iron-owl-page .iron-owl-wordmark--footer.link {
        font-size: 30px;
    }

    .iron-owl-page .page-section {
        scroll-margin-block-start: 6.5rem;
    }

    .iron-owl-page .page-section-inner {
        padding-block: 5.5rem;
    }

    .iron-owl-page .iron-owl-hero {
        min-block-size: 30.625rem;
    }

    .iron-owl-page .iron-owl-hero .page-section-description {
        font-size: 1rem;
    }

    .iron-owl-page .iron-owl-product-layout {
        grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr);
        gap: 5rem;
    }

    .iron-owl-page .iron-owl-setup-layout {
        gap: 5rem;
    }

    .iron-owl-page .iron-owl-feature,
    .iron-owl-page .iron-owl-step {
        padding-block: 1.75rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .iron-owl-page {
        scroll-behavior: auto;
    }
}

/* @toc Accessibility page */
/*
 * /accessibility page — landing-grade presentation (hero, icon cards, stat band, status card, chip
 * nav). All class-scoped .a11y-*; no JS. Same sections/copy/derivation as the restructure.
 *
 * COLOR LAW (this page's original axe violations were opacity-muted text): NEVER de-emphasize text
 * with opacity on glyphs, and NEVER hand-author a colour. Every colour is a ramp/semantic token
 * (--text, --text-muted, --line, --surface, --surface-raised, --primary-*); for a tint, color-mix on
 * a BACKGROUND only. No hex, no --palette-*. Decorative icons are aria-hidden (MediaService), so their
 * colour is free of contrast rules; real text stays on tokens that hold AA.
 *
 * RHYTHM: a wide container (~1080px) lets the bands (hero, cards, stat band) breathe; prose + the
 * contract ledger stay at a readable measure. De-emphasis is size/weight/spacing, never glyph opacity.
 */

.a11y {
    max-width: 1080px;
    margin-inline: auto;
}

.a11y section {
    margin: 3rem 0;
}

.a11y section > h2 {
    font-size: var(--text-2xl);
    margin: 0 0 1.1rem;
    scroll-margin-top: 5rem;
}

/* Prose + ledger keep a readable measure; the bands above break wider. */
.a11y section > p,
.a11y-list,
.a11y-audit-caveat,
.a11y-rules,
.a11y-fineprint p {
    max-width: 66ch;
}

/* ── 1. Hero band ─────────────────────────────────────────────────────────── */
.a11y-hero {
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 2.75rem 2.25rem;
    margin-bottom: 2.5rem;
}

.a11y-kicker {
    font-size: var(--text-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    /* --text-muted (not --primary-500, which is only 4.02:1 on the dark surface) so the eyebrow holds
       AA on both tenants; the brand red still carries the card icons, which are decorative. */
    color: var(--text-muted);
    margin: 0 0 0.85rem;
}

.a11y-lead {
    font-size: var(--text-3xl);
    line-height: 1.2;
    font-weight: 650;
    color: var(--text);
    margin: 0 0 1rem;
    max-width: 24ch;
}

.a11y-aim {
    font-size: var(--text-lg);
    color: var(--text-muted);
    margin: 0 0 1.5rem;
    max-width: 56ch;
}

.a11y-cta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.65rem 1rem;
    margin: 0 0 1.5rem;
}

.a11y-cta-email {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* The hero shows the address as readable TEXT (the button is the mailto action) — full-ink so it holds
   AA, and it avoids a second brand-red link that wouldn't. */
.a11y-email {
    color: var(--text);
    font-weight: 600;
}

/* ── Chips (status row, audit date, mirror source, group counts) ──────────── */
.a11y-chiprow {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.a11y-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    font-size: var(--text-sm);
    color: var(--text-muted);
    white-space: nowrap;
}

/* ── 2. On this page — pill-chip nav, CSS-only sticky on desktop ───────────── */
.a11y-toc {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0 0 2.5rem;
}

.a11y-toc a {
    display: inline-flex;
    padding: 0.35rem 0.85rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface-raised);
    font-size: var(--text-sm);
    color: var(--text-muted);
    text-decoration: none;
}

.a11y-toc a:hover,
.a11y-toc a:focus-visible {
    color: var(--text);
    border-color: var(--primary-500);
}

@media (min-width: 768px) {
    .a11y-toc {
        position: sticky;
        top: 0;
        z-index: 5;
        margin-inline: -0.75rem;
        padding: 0.75rem;
        background: color-mix(in srgb, var(--surface) 88%, transparent);
        backdrop-filter: blur(6px);
        border-radius: 12px;
    }
}

/* ── 3. What you can expect — icon cards ──────────────────────────────────── */
.a11y-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 1rem;
    margin: 0 0 1.25rem;
}

.a11y-card {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1.35rem;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: 12px;
}

.a11y-card-icon {
    display: inline-flex;
    color: var(--primary-500);
}

.a11y-card-icon svg {
    width: 26px;
    height: 26px;
}

.a11y-card p {
    margin: 0;
    color: var(--text);
    line-height: 1.45;
}

.a11y-honest {
    color: var(--text-muted);
    font-style: italic;
    margin: 0;
}

/* ── 4/5. Callouts (report + audit status card) ───────────────────────────── */
.a11y section > .callout {
    max-width: 66ch;
}

.a11y .callout p:first-child {
    margin: 0;
}

.a11y-hint {
    font-size: var(--text-sm);
    color: var(--text-muted);
    margin: 0.5rem 0 0;
}

/* Audit as a status card: big count beside the meta; the callout supplies the variant edge. */
.a11y-audit-card .callout-body {
    display: flex;
    align-items: center;
    gap: 1.15rem;
}

.a11y-audit-num {
    font-size: var(--text-4xl);
    font-weight: 700;
    line-height: 1;
    color: var(--text);
}

.a11y-audit-meta {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.a11y-audit-headline {
    margin: 0;
    font-weight: 600;
    color: var(--text);
}

.a11y-audit-sub {
    margin: 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.a11y-audit-caveat {
    font-size: var(--text-sm);
    color: var(--text-muted);
    margin: 0.9rem 0 0;
}

.a11y-rules {
    margin: 1rem 0 0;
    padding-left: 1.15rem;
}

.a11y-rules li {
    margin: 0.35rem 0;
    font-size: var(--text-sm);
    color: var(--text);
}

.a11y-audit-details {
    margin-top: 1rem;
    max-width: 66ch;
}

/* ── How-it's-built list ──────────────────────────────────────────────────── */
.a11y-list {
    margin: 0;
    padding-left: 1.2rem;
}

.a11y-list li {
    margin: 0.6rem 0;
    color: var(--text);
    line-height: 1.55;
}

/* ── 8. Stat band (contractStats) ─────────────────────────────────────────── */
.a11y-lead-sub {
    font-size: var(--text-lg);
    color: var(--text-muted);
    margin: 0 0 1.5rem;
    max-width: 56ch;
}

.a11y-statband {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1rem;
    margin: 0 0 2.5rem;
}

.a11y-stat-card {
    padding: 1.5rem;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: 12px;
}

.a11y-stat-num {
    display: block;
    font-size: var(--text-4xl);
    font-weight: 700;
    line-height: 1;
    color: var(--text);
    margin-bottom: 0.4rem;
}

.a11y-stat-label {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* ── 5/7. Contract ledger — rows, not cards; group headers with count badges ── */
.a11y-group {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: var(--text-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin: 2rem 0 0.4rem;
}

.a11y-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--text) 8%, transparent);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0;
    color: var(--text-muted);
}

.a11y-ledger {
    max-width: 72ch;
    border-top: 1px solid var(--line);
}

/* Strip the <x-details> card chrome → ledger rows (1px rule between), with a subtle hover tint. */
.a11y-row.details {
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    background: transparent;
}

.a11y-row.details:hover {
    background: color-mix(in srgb, var(--text) 4%, transparent);
}

.a11y-kind {
    display: inline-block;
    margin-left: 0.55rem;
    padding: 0.05rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    font-size: 0.85rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    vertical-align: middle;
}

.a11y-adherence {
    margin: 0 0 0.5rem;
    font-size: 0.92rem;
    color: var(--text);
}

.a11y-desc {
    margin: 0 0 0.5rem;
    font-size: 0.9rem;
    color: var(--text-muted);
}

.a11y-points {
    margin: 0.4rem 0 0;
    padding-left: 1.15rem;
}

.a11y-points li {
    margin: 0.3rem 0;
    font-size: 0.9rem;
    color: var(--text);
}

/* "Keyboard" label above the shared keyboard-table in a contract row. */
.a11y-kbd-label {
    margin: 0.85rem 0 0.35rem;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.a11y-anchor {
    display: inline-block;
    margin-top: 0.65rem;
    font-size: 0.9rem;
}

/* The per-page mirror's intro carries a chip; keep it on a readable measure. */
.a11y-stat {
    color: var(--text-muted);
    margin: 0 0 1rem;
    max-width: 66ch;
}

/* ── 9. Fine print ────────────────────────────────────────────────────────── */
.a11y-fineprint {
    margin-top: 3rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--line);
}

.a11y-fineprint p {
    font-size: var(--text-sm);
    color: var(--text-muted);
    margin: 0 0 0.5rem;
}

/* @toc Admin page */
/* Full-height Admin access workspace. The page owns only master-detail layout and uses shared controls. */

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

.admin-workspace {
    flex: 1;
    display: grid;
    grid-template-columns: 260px minmax(0, 1fr);
    width: 100%;
    min-width: 0;
    min-height: 0;
    color: var(--text);
    background: var(--page-bg);
    overflow: hidden;
}

.admin-workspace [data-admin-enhancement][hidden] {
    display: none;
}

.admin-directory {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    border-inline-end: 1px solid var(--line);
    background: var(--page-bg);
    overflow: hidden;
}

.admin-directory-panel {
    flex: 1;
    overflow: hidden;
}

.admin-directory-panel > .panel-head {
    flex: none;
    min-height: 52px;
    margin: 0;
    padding: 8px 16px;
}

.admin-directory-panel > .panel-body {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

.admin-directory-panel > .panel-body > .panel-body-inner {
    display: flex;
    flex: 1;
    flex-direction: column;
    width: 100%;
    min-width: 0;
    min-height: 0;
}

.admin-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 52px;
    padding-inline: 16px;
    border-block-end: 1px solid var(--line);
}

.admin-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 20px;
    padding-inline: 6px;
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.admin-directory-search {
    padding: 12px 16px;
}

.admin-filter {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: 8px;
}

.admin-filter .input {
    min-width: 0;
}

.admin-filter-clear.button {
    min-height: 36px;
}

.admin-directory-search .input-label,
.admin-project-search .input-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

.admin-directory-search .input-field,
.admin-project-search .input-field {
    height: 36px;
}

.admin-person-list {
    flex: 1;
    display: grid;
    align-content: start;
    min-height: 0;
    padding-block-end: 8px;
    overflow-y: auto;
}

.admin-person-option {
    position: relative;
    margin: 0;
}

.admin-person.button {
    justify-content: flex-start;
    width: 100%;
    min-height: 52px;
    padding: 8px 16px;
    border-width: 0 0 1px;
    border-color: var(--line);
    border-radius: 0;
    color: var(--text);
    background: transparent;
    text-align: start;
    white-space: normal;
}

.admin-person.button.has-unsaved {
    padding-inline-end: 86px;
}

.admin-person-unsaved {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-end: 16px;
    translate: 0 -50%;
    padding: 3px 7px;
    border: 1px solid var(--primary);
    border-radius: var(--radius-full);
    color: var(--text);
    background: var(--surface);
    font-size: var(--text-xs);
    font-weight: 600;
    pointer-events: none;
}

.admin-person-option:first-child .admin-person.button {
    border-block-start: 1px solid var(--line);
}

.admin-person.button:hover,
.admin-person.button:focus-visible {
    background: var(--surface-muted);
}

.admin-person.button.is-selected {
    border-color: var(--line);
    background: var(--surface);
}

.admin-person-copy {
    display: block;
    min-width: 0;
}

.admin-person-copy strong,
.admin-person-copy span {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-person-copy strong {
    font-size: var(--text-sm);
    font-weight: 600;
}

.admin-person-copy span {
    margin-block-start: 2px;
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 400;
}

.admin-empty {
    margin: 0;
    padding: 16px;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.admin-editor {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    height: 100%;
    overflow: hidden;
}

.admin-mobile-person {
    display: none;
}

.admin-workspace[data-admin-enhanced="true"] .admin-mobile-person-submit {
    display: none;
}

.admin-saved {
    position: absolute;
    z-index: var(--z-popover);
    inset-block-start: 72px;
    inset-inline-end: 16px;
    margin: 0;
    padding: 8px 12px;
    border: 1px solid var(--success);
    border-radius: var(--radius-control);
    color: var(--text);
    background: var(--success-surface);
}

.admin-access-form,
.admin-person-workspace {
    flex: 1;
    min-width: 0;
    min-height: 0;
}

.admin-access-form {
    display: flex;
    flex-direction: column;
}

.admin-person-workspace {
    flex-direction: column;
    overflow: hidden;
}

.admin-person-workspace:not([hidden]) {
    display: flex;
}

.admin-person-panel {
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

.admin-person-panel > .panel-head {
    flex: none;
    align-items: center;
    gap: 18px;
    min-height: 68px;
    margin: 0;
    padding: 12px 16px;
}

.admin-person-panel > .panel-head > .panel-title {
    min-width: 0;
    overflow-wrap: anywhere;
}

.admin-person-panel > .panel-body {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

.admin-person-panel > .panel-body > .panel-body-inner {
    display: flex;
    flex: 1;
    flex-direction: column;
    width: 100%;
    min-width: 0;
    min-height: 0;
}

.admin-person-access {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.admin-person-panel > .panel-head > .panel-actions {
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
}

.admin-person-panel > .panel-head > .panel-actions .button {
    min-height: 44px;
}

.admin-action-icon,
.admin-action-icon svg {
    display: block;
    width: 16px;
    height: 16px;
}

.admin-access-tabs {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.admin-access-tabs > .tabs-nav {
    flex: none;
    align-items: stretch;
    min-height: 45px;
    padding-inline: 10px;
}

.admin-access-tabs > .tabs-nav > .tabs-tab {
    min-height: 44px;
    padding: 0 12px;
}

.admin-access-tabs > .tabs-body {
    flex: 1;
    min-width: 0;
    min-height: 0;
    padding-block-start: 0;
    overflow: hidden;
}

.admin-access-tabs > .tabs-body > .tabs-panel {
    height: 100%;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
}

/* Before the page initializer runs, both access groups stay available. The shared tabs
 * script restores its ordinary one-panel presentation once the workspace is enhanced. */
.admin-workspace:not([data-admin-enhanced="true"]) .admin-access-tabs > .tabs-body {
    overflow-y: auto;
}

.admin-workspace:not([data-admin-enhanced="true"]) .admin-access-tabs > .tabs-body > .tabs-panel {
    display: block;
    height: auto;
    overflow: visible;
}

.admin-section-header {
    position: sticky;
    z-index: 2;
    inset-block-start: 0;
    justify-content: flex-end;
    background: var(--page-bg);
}

.admin-clear-sections.button {
    min-height: 44px;
    padding-inline: 10px;
}

.admin-permission-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.admin-permission-row {
    display: flex;
    align-items: center;
    min-width: 0;
    min-height: 52px;
    padding-inline: 16px;
    border-block-end: 1px solid var(--line);
}

.admin-permission-row .switch {
    justify-content: space-between;
    width: 100%;
}

.admin-permission-row .switch-track {
    order: 2;
}

.admin-project-section-header {
    padding-inline-end: 12px;
}

.admin-project-search {
    width: min(240px, 45%);
}

.admin-project-head,
.admin-project-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 120px;
    align-items: center;
    min-width: 0;
}

.admin-project-head {
    min-height: 38px;
    padding-inline: 16px;
    border-block-end: 1px solid var(--line);
    background: var(--page-bg);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
}

.admin-project-head span:not(:first-child) {
    text-align: center;
}

.admin-project-row {
    min-height: 58px;
    padding: 8px 16px;
    border-block-end: 1px solid var(--line);
}

.admin-project-row[hidden] {
    display: none;
}

.admin-project-name {
    min-width: 0;
}

.admin-project-name strong,
.admin-project-name span {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-project-name strong {
    font-size: var(--text-sm);
    font-weight: 600;
}

.admin-project-name span {
    margin-block-start: 2px;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
}

.admin-project-control {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 41px;
}

.admin-project-control .switch-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

.admin-editor-footer {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    min-height: 62px;
    padding: 12px 16px;
    border-block-start: 1px solid var(--line);
    background: var(--page-bg);
}

.admin-editor-footer p {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.admin-editor-actions {
    display: flex;
    gap: 8px;
}

.admin-editor-actions .button {
    min-height: 44px;
}

.admin-login-status {
    min-height: 1.5em;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.admin-login-result {
    display: grid;
    gap: 10px;
}

.admin-login-result[hidden] {
    display: none;
}

.admin-login-result p {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.admin-view-as {
    display: grid;
    justify-items: end;
    gap: 0;
    min-width: 0;
}

.admin-view-as-effect,
.admin-view-as-status {
    max-inline-size: 360px;
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.35;
    text-align: end;
}

.admin-view-as-status {
    min-height: 1.25em;
}

.admin-view-as-status:empty {
    display: none;
}

.admin-view-as-form {
    display: none;
}

@media (max-width: 768px) {
    .admin-workspace {
        display: flex;
        flex-direction: column;
    }

    .admin-directory {
        display: none;
    }

    .admin-mobile-person {
        display: flex;
        flex: none;
        align-items: end;
        gap: 8px;
        padding: 8px 12px;
        border-block-end: 1px solid var(--line);
        background: var(--page-bg);
    }

    .admin-mobile-person .select {
        flex: 1;
        min-width: 0;
    }

    .admin-mobile-person .select-field,
    .admin-project-search .input-field,
    .admin-project-search .admin-filter-clear.button {
        min-height: 44px;
        font-size: 16px;
    }

    .admin-person-panel > .panel-head {
        min-height: 76px;
    }

    .admin-permission-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .admin-permission-row {
        padding-inline: 12px;
    }

    .admin-project-head {
        display: none;
    }

    .admin-project-row {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 12px;
        padding: 6px 12px;
    }

    .admin-project-name {
        min-width: 0;
    }

    .admin-project-name strong,
    .admin-project-name span {
        overflow: visible;
        text-overflow: clip;
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .admin-project-control {
        justify-content: flex-end;
        min-height: 44px;
    }

    .admin-project-control .switch {
        justify-content: flex-end;
        min-height: 44px;
    }

    .admin-project-control .switch-track {
        order: 2;
    }
}

/* Short mobile and landscape viewports need one ordinary scroll sequence. Keeping a fixed action row here
 * would leave less than one permission-row height between the person header and footer. */
@media (max-width: 768px) and (max-height: 600px) {
    .admin-editor {
        overflow-y: auto;
    }

    .admin-access-form,
    .admin-person-workspace,
    .admin-person-panel,
    .admin-person-panel > .panel-body,
    .admin-person-panel > .panel-body > .panel-body-inner,
    .admin-access-tabs,
    .admin-access-tabs > .tabs-body {
        flex: none;
        overflow: visible;
    }

    .admin-person-workspace:not([hidden]) {
        display: block;
    }

    .admin-access-tabs > .tabs-body > .tabs-panel {
        height: auto;
        overflow: visible;
    }
}

@media (max-width: 560px) {
    .admin-mobile-person {
        padding-block: 4px;
        padding-inline:
            max(12px, env(safe-area-inset-left))
            max(12px, env(safe-area-inset-right));
    }

    .admin-person-panel > .panel-head {
        align-items: stretch;
        min-height: 0;
        padding: 6px 12px;
    }

    .admin-person-panel > .panel-head > .panel-title {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .admin-person-panel > .panel-head > .panel-actions {
        align-items: flex-start;
        flex: 1;
        width: 100%;
    }

    .admin-view-as,
    .admin-person-panel > .panel-head > .panel-actions > .admin-login-trigger {
        flex: 1 1 calc(50% - 4px);
    }

    .admin-view-as .button,
    .admin-person-panel > .panel-head > .panel-actions > .admin-login-trigger {
        width: 100%;
    }

    .admin-view-as {
        justify-items: stretch;
    }

    .admin-view-as-effect {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .admin-view-as-status {
        max-inline-size: none;
        text-align: start;
    }

    .admin-access-tabs > .tabs-nav {
        padding-inline: 4px;
    }

    .admin-access-tabs > .tabs-nav > .tabs-tab {
        flex: 1;
        justify-content: center;
        min-width: 0;
        padding-inline: 8px;
    }

    .admin-project-section-header {
        height: auto;
        padding: 4px 12px;
    }

    .admin-project-search {
        width: 100%;
    }

    .admin-editor-footer {
        align-items: stretch;
        flex-direction: column;
        min-height: 0;
        padding:
            10px
            max(12px, env(safe-area-inset-right))
            max(10px, env(safe-area-inset-bottom))
            max(12px, env(safe-area-inset-left));
    }

    .admin-editor-actions,
    .admin-editor-actions .button {
        width: 100%;
    }

    .admin-editor-actions .button {
        flex: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .admin-workspace *,
    .admin-workspace *::before,
    .admin-workspace *::after {
        scroll-behavior: auto;
        transition-duration: 0.01ms;
        animation-duration: 0.01ms;
        animation-iteration-count: 1;
    }
}

/* @toc Barbruiser essay page */
/* BarBruiser Essay / SA reader — TWO PANELS, EACH WITH ITS OWN TABS (#1343): the Question on the left, one tab
 * per model answer on the right, standing as two columns at --bp-lg and stacking below it. The tab strips are
 * real at every width; nothing here hides one. The direct sibling of the PT reader (barbruiser-pt.css), which is
 * the same layout with three tabs on its left. No card skin inside a panel — a tab group already draws it. The
 * issue rail composes x-issue-card, so no bespoke issue markup lives here. Page CSS, auto-bundled by CssService.
 * Colour comes only from semantic tokens + the ramp; spacing in px matches barbruiser.css. */

.essay-reader {
    display: flex;
    flex-direction: column;
    gap: 18px;
}

/* Topbar — the meta line and the read/compare control, sharing a row (wrapping when tight). */
.essay-topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.essay-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.essay-meta-item {
    white-space: nowrap;
}

/* A trailing separator after the last item is harmless; hide it so the line never ends on a middot. */
.essay-meta-sep {
    color: var(--line);
}

.essay-meta-item:last-child + .essay-meta-sep,
.essay-meta-sep:last-child {
    display: none;
}

/* The two-artifact "study this" affordance. */
.essay-studysheet {
    margin: 0;
    font-size: var(--text-sm);
}

.essay-studysheet a {
    color: var(--link);
    font-weight: 600;
    text-decoration: none;
}

.essay-studysheet a:hover {
    text-decoration: underline;
}

/* ── THE TWO PANELS ───────────────────────────────────────────────────────────────────────────
 * (#1343) Two panels side by side, EACH CARRYING ITS OWN TABS: the Question on the left, one tab per model
 * answer on the right. The tab strips are real at every width — this replaces the `unwrap="1024"` layout,
 * where tabs.js deleted the tab strip above the seam and this file grid-laid the panels out instead.
 *
 * WHAT THAT MEANS FOR THIS FILE: the old block hid `.tabs-nav`, gridded `.tabs-body`, and force-showed every
 * `.tabs-panel` (to defeat the one-at-a-time gating). ALL THREE ARE GONE — with two independent tab groups
 * the component's own gating is exactly what we want, and hiding a tab strip is the bug being fixed. What
 * remains is only the two-column arrangement of the panels themselves.
 *
 * VIEWPORT @media, not @container, and still deliberate: this is PAGE CHROME (does the reader stand in one
 * column or two?), which is where the --bp-* seam belongs. It no longer has to agree with any JS value —
 * tabs.js reads no breakpoint here now — so the seam is free to be a pure layout choice.
 */
.essay-panels {
    display: grid;
    gap: 16px;
    margin-block: 4px;
}

/* A grid child that scrolls must be allowed to be narrower than its content, or a long unbroken line in the
   fact pattern widens the column and the two panels stop being equal. */
.essay-panel {
    min-inline-size: 0;
}

.essay-panel-scroll {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Layout only — the text treatment (14/600 normal-case) comes from the composed .ui-label (R5's shared label
   recipe, base.css), so a future label restyle lands in ONE rule, never here. */
.essay-panel-heading {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
}

@media (min-width: 1024px) {
    /* Two columns — LEFT the Question, RIGHT the answers (the essay's classic reading). `align-items: start`
       so a short answer panel does not stretch to the fact pattern's height. */
    .essay-panels {
        grid-template-columns: 1fr 1fr;
        align-items: start;
    }

    /* Independent scroll, now that they are side by side: read the question and the answers in parallel.
       The cap is on the SCROLL REGION rather than the panel, so the tab strip stays pinned above it and a long
       fact pattern scrolls under a strip that never leaves — which is the point of tabs you can still reach. */
    .essay-panel-scroll {
        overflow-y: auto;
        max-block-size: 62vh;
    }
}

/* ── QUESTION PANEL ──────────────────────────────────────────────────────────────────────────── */

.essay-lead {
    margin: 0;
    font-weight: 600;
    color: var(--text);
}

.essay-calls-section {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.essay-calls {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Weighted text carries the calls — the "Calls" heading is the emphasis. No accent rule on a self-numbering
   list (the anti-slop tell). */
.essay-calls li {
    font-weight: 600;
    color: var(--text);
}

.essay-instruction {
    margin: 0;
    font-style: italic;
    color: var(--text-muted);
}

/* ── MODEL ANSWERS PANEL ─────────────────────────────────────────────────────────────────────── */

.essay-answer {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.essay-answer-meta {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.essay-answer-meta-sep {
    margin-inline: 2px;
}

/* Honest-empty (C3): five essays in the corpus publish no selected answers, so the right panel says so
   rather than rendering a tab strip with nothing behind it. */
.essay-answers-empty {
    margin: 0;
    padding: 16px 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.essay-answer-note {
    margin: 0;
    font-size: var(--text-sm);
    font-style: italic;
    color: var(--text-muted);
}

/* Verbatim answer text — preserve the source line breaks, keep a readable measure; break long unbroken
   tokens rather than force a pane scrollbar (the PT reader's .pt-prose rule). */
.essay-answer-text {
    white-space: pre-line;
    overflow-wrap: break-word;
    max-inline-size: 75ch;   /* a readable measure for the long answer prose (core study content) */
    font-size: var(--text-sm);
    line-height: 1.65;
    color: var(--text);
}

/* ── STUDY REVEAL — the derived issue checklist ──────────────────────────────────────────────── */

.essay-reveal {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.essay-checklist-note {
    margin: 0 0 12px;
    font-size: var(--text-sm);
    font-style: italic;
    color: var(--text-muted);
}

.essay-issues {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
    padding: 0;
}

/* @toc Barbruiser mbe page */
/* BarBruiser MBE index (/barbruiser/mbes) — the section's front door: 137 questions as pickable cards,
 * grouped by subject. Sibling of the PT index (barbruiser-pt.css's .pts-index), deliberately: same rhythm,
 * same group-then-grid shape, so the two browse screens read as one system.
 *
 * The CARD SKIN is x-card's, not this file's — these rules own only the link wrapper, the card's internal
 * type, and the grid. Page CSS, auto-bundled by CssService; colour from semantic tokens only.
 */

.mbes-index {
    display: flex;
    flex-direction: column;
    gap: 22px;
}

.mbes-intro {
    margin: 0;
    max-inline-size: 68ch;
    color: var(--text-muted);
    line-height: 1.6;
}

.mbes-group-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px;
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--text);
}

/* How many questions this subject holds — the reader is choosing where to spend an hour. */
.mbes-group-count {
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--text-muted);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 1px 6px;
}

.mbes-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 12px;
}

/* The whole card is the link. It carries no chrome of its own — x-card inside is the surface — so this is
   just the hit area + the hover/focus affordance. */
.mbes-card {
    display: block;
    text-decoration: none;
    border-radius: var(--card-border-radius);
}

.mbes-card .card {
    block-size: 100%;
    transition: border-color 0.15s ease;
}

.mbes-card:hover .card {
    border-color: var(--link);
}

/* Focus lands on the anchor, but the CARD is what the eye sees — so put the ring on the card. */
.mbes-card:focus-visible {
    outline: none;
}

.mbes-card:focus-visible .card {
    outline: 2px solid var(--focus, var(--link));
    outline-offset: 2px;
}

.mbes-card .card-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.mbes-card-topic {
    font-weight: 700;
    color: var(--text);
    line-height: 1.4;
}

.mbes-card-stem {
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.5;
}

.mbes-card-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-block-start: auto;
    padding-block-start: 4px;
}

.mbes-empty {
    margin: 0;
    color: var(--text-muted);
}

/* @toc Barbruiser pt page */
/* BarBruiser PT reader — TWO VIEWS OF TWO PANELS (#1349, James's settled design). A [Test | Answer] switch over
 * two 50/50 panels: TEST is every source as a tab beside the place she writes; ANSWER is what she wrote beside
 * the model answers, the issue checklist and the metadata.
 *
 * NOTHING HERE HIDES A TAB STRIP. The `unwrap` block that used to (and the per-pane details folds, the pinned
 * Task strip, the three header bars) is gone with the layout that needed it. Page CSS, auto-bundled by
 * CssService. Colour comes only from semantic tokens + the ramp; spacing in px matches barbruiser.css. */

.pt-reader {
    display: flex;
    flex-direction: column;
    gap: 18px;
}

/* ── THE TOP BAR: TITLE · SWITCH · TIMER, ONE ROW ─────────────────────────────────────────────
 * James asked for this twice: "the top bar is for title and actions". The h1 was 381px wide with 585px of dead
 * space beside it while the switch sat on its own 33px row below. Now all three share row 1.
 *
 * THE MECHANISM, and it is the interesting part. The switch must stay a real x-tabs — the alternative is
 * re-authoring tab behaviour and losing the roles and keyboard contract — but x-tabs renders its tablist and its
 * panels as ONE component, and tabs.js reads both with `:scope >`. So the tablist cannot be lifted into the
 * shell's header bar (a different DOM subtree, outside main) without severing it from the panels it controls.
 *
 * Instead this page owns its own h1 (x-barbruiser-page's documented pattern for content pages) and the x-tabs
 * root becomes `display: contents`, so the component's nav and body are promoted into THIS grid. The nav lands
 * in the title row; the body spans the row beneath. Nothing about x-tabs changes, and its box is safe to
 * dissolve because `.tabs` carries no background, border or padding of its own — only its children do.
 *
 * Grid areas are EXPLICIT rather than auto-flowed: the timer sits before the tabs in the DOM (so the reading
 * order is title → time-remaining → controls) while belonging in the last column.
 */
.pt-topline {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: baseline;
    column-gap: 18px;
}

.pt-title {
    grid-area: 1 / 1;
    margin: 0;
    font-size: var(--text-2xl);
    font-weight: 700;
    color: var(--text);
}

.pt-topline > .tabs {
    display: contents;
}

.pt-topline > .tabs > .tabs-nav {
    grid-area: 1 / 2;
    /* The strip is two tabs; it should sit with the title, not stretch across the gap to the timer. */
    justify-self: start;
    align-self: end;
    /* No bottom rule here: this row already reads as a header, and the underline's rule would draw a line
       through the title row rather than under a strip. */
    border-bottom: 0;
}

.pt-timer {
    grid-area: 1 / 3;
    display: flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: 8px;
    margin: 0;
    white-space: nowrap;
}

.pt-topline > .tabs > .tabs-body {
    grid-area: 2 / 1 / 3 / 4;
}

/* MOBILE: three things do not fit on a 375px row. Collapse to one column and let them stack in DOM order —
   title, time, switch — which is also the order of what she needs to know. */
@media (max-width: 768px) {
    .pt-topline {
        grid-template-columns: minmax(0, 1fr);
    }

    .pt-title,
    .pt-timer,
    .pt-topline > .tabs > .tabs-nav {
        grid-area: auto;
        justify-self: stretch;
    }

    .pt-timer {
        justify-content: flex-start;
    }

    .pt-topline > .tabs > .tabs-body {
        grid-area: auto;
    }
}

.pt-timer-label {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
}

/* Tabular figures so the digits do not jitter once this counts down. */
.pt-timer-value {
    font-size: var(--text-lg);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

/* ── THE TWO PANELS ───────────────────────────────────────────────────────────────────────────
 * 50/50, as drawn. `minmax(0, 1fr)` on each track rather than a bare `1fr` is what actually lets a scrolling
 * child be narrower than its content — without it one long unbroken line in a File document widens its track
 * and the split stops being even.
 *
 * ONE COLUMN below --bp-lg: a phone cannot hold a document beside a writing surface, and stacking them keeps
 * both usable. The panels stay in DOM order, so the sources come first and what she writes follows.
 */
.pt-views {
    display: grid;
    gap: 16px;
    margin-block: 4px;
}

.pt-view-panel {
    min-inline-size: 0;
}

@media (min-width: 1024px) {
    .pt-views {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-items: start;
    }

    /* THE TEST VIEW IS NOT 50/50, and the numbers are why. Its source side holds TWO columns — the vertical
       strip plus the document — so an even split leaves the document 298px, which is 36 characters at reading
       type: below the 45–75 band and below the 42 that had Daria printing the Library (#1346). Weighting it
       1.4:1 gives the document ~392px (~49 characters) and still leaves the writing surface ~400px (~50).
       Both sides clear the band; an even split cleared it on neither. Measured, not chosen.
       SCOPED TO THE TEST VIEW: the ANSWER view's two panels are both plain prose, so the extra width would be
       borrowed from the model answers for no reason — there, 50/50 is the honest split and the card's own. */
    [data-panel="test"] .pt-views {
        grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    }

    /* Independent scroll, now that they are side by side — read a source and write against it in parallel, the
       way the exam software does. The cap sits on the SCROLL REGION, not the panel, so each tab strip stays
       pinned above its own scrolling content and never leaves the screen. Below the seam the page scrolls
       instead: a nested scroller on a phone is the classic trap. */
    .pt-panel-scroll {
        overflow-y: auto;
        max-block-size: 62vh;
    }
}

.pt-panel-scroll {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* ── THE SOURCE STRIP RUNS DOWN THE SIDE ──────────────────────────────────────────────────────
 * Every source is visible at once AND the document sits level with the writing panel beside it. Both of those
 * failed before: x-tabs' horizontal nav scrolls with a hidden scrollbar (so 4 of 7 tabs were unreachable
 * without a gesture), and making it wrap instead produced a 135px three-row strip that pushed the document to
 * y=278 while the writing box started at y=178 — the memorandum read as sitting UNDER the layout rather than
 * beside the answer, which is exactly what James said.
 *
 * The fix is the primitive's own `orientation="vertical"` (#1349, added on x-tabs with aria-orientation and
 * Up/Down keys). A vertical strip grows down — the axis with room — so it holds its shape from 4 sources to 13,
 * and the panel content starts at the strip's own top.
 *
 * THE STRIP IS NARROWER HERE THAN THE COMPONENT DEFAULT (13rem) because it lives inside a half-width panel:
 * every pixel it takes comes off the document's reading measure, which is the thing #1346 was raised to protect.
 */
.pt-view-sources .tabs {
    --tabs-vertical-strip: 9rem;
    --tabs-vertical-gap: 14px;
}

/* BELOW THE SEAM the whole reader is one column, and a 144px strip beside a document inside a 343px column
   would leave ~185px of prose — 23 characters. So the strip goes FULL WIDTH above the document instead: still a
   vertical list, still Up/Down keys, so `aria-orientation="vertical"` stays truthful; only the two boxes stop
   sharing a row. Page chrome, so it rides the --bp-* seam. */
@media (max-width: 768px) {
    .pt-view-sources .tabs-vertical {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* The tab labels are truncated document titles, so they need to wrap inside a narrow strip and sit tight. */
.pt-view-sources .tabs-tab {
    font-size: var(--text-xs);
    line-height: 1.35;
}

/* ── A SOURCE DOCUMENT ───────────────────────────────────────────────────────────────────────── */

.pt-doc {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.pt-doc-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* The FULL title lives here — the tab label is truncated to keep the strip readable, so the untruncated text
   has to be somewhere, and the panel heading is where you look once the tab is open. */
.pt-doc-title {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 700;
    color: var(--text);
}

.pt-doc-meta {
    margin: 0;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
}

.pt-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.pt-tag {
    padding: 2px 8px;
    font-size: var(--text-xs);
    color: var(--text-muted);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
}

.pt-auth-statutes {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.pt-auth-statutes-label {
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin-inline-end: 4px;
    color: var(--text);
}

.pt-instructions-note {
    margin: 0;
    font-size: var(--text-sm);
    font-style: italic;
    color: var(--text-muted);
}

/* Verbatim document/answer text — preserve the source line breaks. The reader is content-agnostic, so break
 * long unbroken tokens (URLs, long cites) rather than force a pane scrollbar. */
.pt-prose {
    white-space: pre-line;
    overflow-wrap: break-word;
    font-size: var(--text-sm);
    line-height: 1.65;
    color: var(--text);
}

/* ── READING A DOCUMENT (#1346, kept through the #1349 rebuild) ────────────────────────────────
 * Daria was PRINTING the library. Measured at 1280x720: the reading column was 299px of 14px text — 42
 * characters a line, against a comfortable band of 45-75 and a sheet of paper at ~90. Six words a line for
 * thousands of words returns your eye to the left margin twice as often as it should. Paper won.
 *
 * THE MEASURE IS DERIVED, AND IT IS IN `em` FOR A REASON. A measure is a character count, so it has to be
 * relative to the text's OWN size: 1 average character is 0.505em in this font (8.08px at 16px, measured), so
 * 36.4em is ~72 characters AT ANY FONT SIZE. That is the whole point of em here.
 *
 * I had it in `rem` first and measured the consequence: 36.5rem is 584px, which is 72 characters at the prose's
 * 16px but **96** at the tags' 12px — one absolute width cannot serve two type sizes. em fixes it by
 * construction rather than by a second hand-tuned number.
 *
 * WHY NOT `ch`, which looks like the obvious unit: `ch` is the width of the "0" GLYPH (10.61px here at 16px),
 * not of an average character (8.08px) — a ratio of 0.762, so `Nch` holds about N x 1.31 characters. A
 * natural-looking `70ch` computes to 742px, ~92 characters, well past the band, and at that width it never
 * binds at all so it looks like it works. Measured, not assumed.
 *
 * IT APPLIES TO THE TAGS TOO, caught by measuring rather than looking: capping only `.pt-prose` left the
 * holding tags spanning the full pane at 12px — past the ceiling in the other direction. A reading column is
 * one column: everything you read in it shares the measure.
 *
 * THE TYPE BUMP IS NOT UNCONDITIONAL. At 375px the pane is ~343px, where 16px yields 42 characters and 14px
 * yields 48 — and 42 is the number that had the Library being printed. Bigger type in a column that cannot grow
 * buys legibility by spending measure, and on a phone there is no measure to spend. So the bump starts where
 * there is width to carry it and below that the prose keeps `.pt-prose`'s own --text-sm.
 */
.pt-view-sources .tabs-body > [data-panel] .pt-prose,
.pt-view-sources .tabs-body > [data-panel] .pt-tag,
.pt-view-sources .tabs-body > [data-panel] .pt-auth-statutes {
    max-inline-size: 36.4em;
}

@media (min-width: 1024px) {
    .pt-view-sources .tabs-body > [data-panel] .pt-prose {
        font-size: var(--text-base);
        line-height: 1.75;
    }
}

/* WHERE SHE WRITES — the surface itself is x-pt-answer-field, which owns its own CSS (it is a component
 * now, not page markup). All this page contributes is letting the panel body stack its parts. */
.pt-write-panel .panel-body-inner {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* ── THE ANSWER VIEW ─────────────────────────────────────────────────────────────────────────── */

.pt-answer {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.pt-answer-meta {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.pt-answer-meta-sep {
    margin-inline: 2px;
    color: var(--line);
}

/* Honest-empty (C3): one PT in the corpus publishes no selected answers, and she has not written an answer to
   most of them — both say so plainly rather than rendering an empty region. */
.pt-answers-empty {
    margin: 0;
    padding: 16px 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.pt-issues {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* ── INFO — the record's metadata, stated ─────────────────────────────────────────────────────── */

.pt-about {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0;
}

.pt-about-row {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.pt-about-row dt {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
}

.pt-about-row dd {
    margin: 0;
    font-size: var(--text-sm);
    overflow-wrap: break-word;
    color: var(--text);
}

/* The source URL is long and unbreakable; let it wrap anywhere rather than widen the panel. */
.pt-about-link {
    color: var(--link);
    overflow-wrap: anywhere;
}

.pt-about + .bb-reader-taxonomy {
    margin-block-start: 14px;
}

/* ── PT index (/barbruiser/pts) — browse by task type ──────────────────────────────────────── */

.pts-index {
    display: flex;
    flex-direction: column;
    gap: 22px;
}

.pts-intro {
    margin: 0;
    max-inline-size: 68ch;
    color: var(--text-muted);
    line-height: 1.6;
}

.pts-group-title {
    margin: 0 0 12px;
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--text);
}

.pts-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 14px;
}

.pts-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 16px;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    text-decoration: none;
    transition: border-color 0.15s ease;
}

.pts-card:hover {
    border-color: var(--primary);
}

.pts-card:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.pts-card-type {
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--primary);
}

.pts-card-title {
    font-size: var(--text-lg);
    font-weight: 700;
    font-style: italic;
    color: var(--text);
}

.pts-card-exam {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.pts-card-skills {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 6px 0 0;
    padding: 0;
    list-style: none;
}

.pts-card-skill {
    padding: 2px 8px;
    font-size: var(--text-xs);
    color: var(--text-muted);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
}

.pts-empty {
    margin: 0;
    color: var(--text-muted);
}

/* @toc Barbruiser page */
/* BarBruiser index — study sheets in two segments (Essays / Theory), each a responsive
 * grid of cards. Page CSS, bundled by CssService. The sheet renderers are the
 * study-sheet (essay) and theory-sheet components. */

.barbruiser-actions {
    margin-bottom: 8px;
}

.barbruiser-segment {
    margin-top: 22px;
}

.barbruiser-segment-title {
    margin: 0 0 12px;
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--text);
}

.barbruiser-segment-empty {
    margin: 0;
}

.barbruiser-index {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 14px;
}

.barbruiser-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 16px;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    text-decoration: none;
    transition: border-color 0.15s ease;
}

.barbruiser-card:hover {
    border-color: var(--primary);
}

.barbruiser-card-subject {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
}

.barbruiser-card-title {
    font-weight: 600;
    color: var(--text);
}

/* ── Subject folders (Theory) — Daria's wish #1 ──────────────────────────────────────────────────
 * The disclosure itself is x-details: border, radius, chevron rotation, hover and the focus ring all
 * come from the primitive, so nothing here re-implements a control. These rules do only the two
 * things the primitive cannot know about this usage — how folders stack, and that the thing inside
 * is a card grid rather than prose.
 *
 * Both are set through the primitive's OWN --details-* theming surface rather than by overriding
 * .details-summary / .details-body from a page sheet, so the folder is themed the way the component
 * asks to be themed and keeps working if its internals are refactored. */
.barbruiser-folder {
    /* The subject IS this folder's name, so the summary should read as a heading, not as a control. */
    --details-summary-font-size: var(--text-base);
    /* The body holds CARDS, not prose: the primitive's smaller muted body type would shrink the
     * titles inside a folder and leave them looking different from the same cards in Essays. */
    --details-body-font-size: var(--text-base);
    --details-body-color: var(--text);
}

.barbruiser-folder + .barbruiser-folder {
    margin-block-start: var(--space-3);
}

/* The primitive pads its body for prose; a card grid brings its own rhythm, so the top gap would
 * otherwise read as a double space under the summary row. */
.barbruiser-folder > .details-body {
    padding-block-start: var(--space-1);
}

/* The create card — dashed to read as an action, not a sheet. */
.barbruiser-card-new {
    border-style: dashed;
    justify-content: center;
}

.barbruiser-card-new .barbruiser-card-title {
    color: var(--text-muted);
}

.barbruiser-card-new:hover .barbruiser-card-title {
    color: var(--text);
}

/* @toc Demo page */
/* Demo page — the living style guide layout. */

.demo {
    width: 100%;
    max-width: 760px;
}

.demo-sec {
    padding: 26px 0;
    border-bottom: 0.5px solid var(--line);
}

.demo-sec:last-child {
    border-bottom: none;
}

.demo-h {
    margin: 0 0 16px;
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.04em;
}

.demo-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
}

.demo-narrow {
    max-width: 360px;
}

.demo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
}

.demo-mono {
    font-family: var(--font-mono);
    color: var(--text-muted);
}

/* @toc Docs page */
/* Documentation pages (/docs/<page>) — Palette ramps, the Typography scale, and
 * the tabbed component docs. Each page's body is resources/views/docs/<key>.blade.php.
 * Code blocks are the x-code-block component (see code-block.css). */

/* Palette showcase — the three reference forms, the live active ramps, and the
 * full catalog as rows of click-to-copy chips. */
.palette-ref {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 12px;
    margin: 0 0 32px;
}

.palette-ref-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 10px;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface);
}

.palette-ref-swatch {
    width: 28px;
    height: 28px;
    flex: none;
    border-radius: 7px;
    border: 1px solid var(--line);
}

.palette-ref-item code {
    font-size: var(--text-sm);
}

.palette-ref-note {
    flex-basis: 100%;
    color: var(--text-muted);
    font-size: var(--text-xs);
    line-height: 1.5;
}

.palette-active,
.palette-catalog {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.palette-active {
    margin-bottom: 32px;
}

.palette-active-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.palette-active-label {
    flex: none;
    width: 84px;
    text-align: right;
    font-size: var(--text-sm);
    color: var(--text-muted);
    text-transform: capitalize;
}

.palette-ramp {
    display: flex;
    flex: 1;
    min-width: 0;
    gap: 4px;
}

/* The palette chips are <x-swatch> (#761) — the chip's look, hover, focus ring and copied confirmation all
   live in resources/css/components/swatch.css now. The two rules that used to sit here carried the only colour
   literals on this page (a hard-coded #fff + rgba text-shadow, needed to keep a step number legible over an
   arbitrary fill); the component drops the over-the-fill label entirely, so those literals are simply gone
   rather than relocated. */

@media (max-width: 768px) {
    .palette-active-label { width: 60px; }
}

/* Click-to-copy button on code blocks, and the copy toast (palette chips + code). */
.doc-copy-btn {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 3px 10px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--text-xs);
    cursor: pointer;
    opacity: 0;
    transition: opacity 120ms ease, color 120ms ease, border-color 120ms ease;
}

.code-block:hover .doc-copy-btn,
.doc-copy-btn:focus-visible {
    opacity: 1;
}

.doc-copy-btn:hover {
    color: var(--text);
    border-color: var(--text-muted);
}

.doc-copy-toast {
    position: fixed;
    left: 50%;
    bottom: 24px;
    transform: translate(-50%, 10px);
    padding: 8px 16px;
    border-radius: 999px;
    background: var(--text);
    color: var(--surface);
    font-size: var(--text-sm);
    opacity: 0;
    pointer-events: none;
    transition: opacity 150ms ease, transform 150ms ease;
    z-index: 1000;
}

.doc-copy-toast.is-visible {
    opacity: 1;
    transform: translate(-50%, 0);
}

/* Typography: a sample per scale step with a mono spec line under it. */
/* Typography showcase — family cards over a live type-scale specimen. */
.type-families {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
    margin: 0 0 32px;
}

.type-family {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 16px 18px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface);
}

.type-family-sample {
    flex: none;
    font-size: 40px;
    line-height: 1;
    color: var(--text);
}

.type-family-meta {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.type-family-meta code {
    cursor: pointer;
}

.type-family-meta span {
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-family: var(--font-mono);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.type-scale-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 24px;
    padding: 14px 0;
    border-bottom: 1px solid var(--line);
}

.type-scale-row:last-child {
    border-bottom: none;
}

.type-scale-sample {
    margin: 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.type-scale-meta {
    flex: none;
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-family: var(--font-mono);
    white-space: nowrap;
}

/* Component doc: each component page renders inside an x-tabs (Example / View /
 * CSS / JS); these are the panel-content styles. */
.doc-body > .tabs {
    width: 100%;
    min-width: 0;
    max-width: 100%;
}

.doc-body > .tabs > .tabs-nav {
    max-width: 100%;
}

.doc-example {
    padding: 0 0 28px;
}

.doc-example-label {
    margin: 0 0 10px;
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

/* The example "canvas" — a raised, padded surface so a live demo reads as a
 * deliberate preview, not markup dropped on the page. */
.doc-example-demo {
    padding: 28px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface-raised);
}

/* A section head. docs._subhead now renders these as a real <h2> (it is the page's section outline, and the
   TOC rail links to it), so the weight is pinned here rather than inherited from the UA's bold h2 default —
   the look is unchanged from when this was a <div>. The one-<h1> law is untouched: the page's h1 is in the
   app header bar. scroll-margin keeps a jumped-to head clear of the sticky app chrome. */
.doc-subhead {
    margin: 0 0 8px;
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    scroll-margin-top: var(--space-6);
}

.doc-subhead:not(:first-child) {
    margin-top: 20px;
}

/* ── "On this page" (#846) — the docs page splits into body + a TOC rail ───────────────────────────────── */

/* The rail is LAST in the DOM (the stack can only be complete after the content renders) and CSS puts it
   alongside. Order matters for narrow widths too: it stacks UNDER the content rather than gating it. */
.doc-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 12rem;
    gap: var(--space-6);
    align-items: start;
}

/* A page whose sections pushed nothing (a short prose page) gets NO empty rail — one column, honestly. */
.doc-layout:not(:has(.doc-toc-item)) {
    grid-template-columns: minmax(0, 1fr);
}

.doc-layout:not(:has(.doc-toc-item)) .doc-toc {
    display: none;
}

.doc-body {
    min-width: 0;
}

.doc-toc {
    position: sticky;
    top: var(--space-4);
    min-width: 0;
    padding-inline-start: var(--space-4);
    border-inline-start: 1px solid var(--line);
}

.doc-toc-title {
    margin: 0 0 var(--space-2);
}

.doc-toc-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin: 0;
    padding: 0;
    list-style: none;
}

.doc-toc-link {
    display: block;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.35;
    text-decoration: none;
}

.doc-toc-link:hover {
    color: var(--text);
}

.doc-toc-link:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* Narrow: one column, the rail reads as a plain jump list under the content (no sticky, no rule). */
@media (max-width: 768px) {
    .doc-layout {
        grid-template-columns: minmax(0, 1fr);
    }

    .doc-toc {
        position: static;
        padding-inline-start: 0;
        border-inline-start: 0;
        border-block-start: 1px solid var(--line);
        padding-block-start: var(--space-4);
    }
}

.doc-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* Intro paragraph above a component doc's tab strip (meta.description). */
.doc-intro {
    margin: 0 0 20px;
    max-width: 68ch;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.6;
}

/* A one-line lead-in inside a tab panel (e.g. the HTML tab's "compiles to"). */
.doc-lead {
    margin: 0 0 12px;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* Section accents — one catalog hue per section, used by the home cards, the
 * per-page eyebrow badge, and card hovers. Catalog colours are theme-independent. */
.docs-accent-foundations { --section: var(--palette-emerald-500); }
.docs-accent-primitives  { --section: var(--palette-violet-500); }
.docs-accent-patterns    { --section: var(--palette-amber-500); }
.docs-accent-sections    { --section: var(--palette-rose-500); }
.docs-accent-internals   { --section: var(--palette-sky-500); }

/* /docs home — the section-card front door. */
.docs-home-lead {
    margin: 0 0 24px;
    color: var(--text-muted);
    font-size: var(--text-base);
}

.docs-home-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 16px;
}

.docs-home-card {
    display: block;
    padding: 20px;
    border: 1px solid var(--line);
    border-radius: 14px;
    background: var(--surface);
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.15s ease;
}

.docs-home-card:hover {
    border-color: var(--section);
}

.docs-home-card-head {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
}

.docs-home-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 10px;
    background: var(--surface-raised);
    color: var(--section);
}

.docs-home-icon svg {
    width: 24px;
    height: 24px;
}

.docs-home-card-title {
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text);
}

.docs-home-card-count {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.docs-home-blurb {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.6;
}

/* Per-page section eyebrow badge. */
.doc-eyebrow {
    margin: 0 0 20px;
}

.doc-eyebrow-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 11px 3px 8px;
    border: 1px solid var(--section);
    border-radius: 999px;
    color: var(--section);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.doc-eyebrow-icon {
    display: inline-flex;
}

.doc-eyebrow-icon svg {
    width: 14px;
    height: 14px;
}

/* Provenance — "based on the <button> element", grounding a primitive in MDN. */
.doc-basedon {
    margin: -8px 0 20px;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.doc-basedon code {
    color: var(--text);
}

.doc-basedon-link {
    white-space: nowrap;
}

/* "Composed of" — a Pattern's Primitive ingredients, derived from its Blade source. */
.doc-composed {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 24px;
}

.doc-composed-label {
    font-size: var(--text-xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.doc-composed-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    border: 1px solid var(--line);
    border-radius: 999px;
    font-size: var(--text-sm);
    color: var(--text);
    text-decoration: none;
}

.doc-composed-chip:hover {
    border-color: var(--text-muted);
    background: var(--surface-raised);
}

.doc-composed-count {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

/* Props/attributes table at the top of a component doc. */
.doc-props {
    display: block;
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    margin: 0 0 24px;
    border-collapse: collapse;
    font-size: var(--text-sm);
}

.doc-props th {
    /* label typography now rides the shared .ui-label recipe (R5-B, one-place-reversible) */
    padding: 8px 12px;
    border-bottom: 1px solid var(--line);
    text-align: left;
}

.doc-props td {
    padding: 8px 12px;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
}

.doc-props td:nth-child(4) {
    color: var(--text-muted);
}

.doc-props-req {
    color: var(--primary-500);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Per-component accessibility notes, below the tabs. */
.doc-a11y {
    margin-top: 28px;
}

.doc-a11y-list {
    margin: 0 0 10px;
    padding-left: 20px;
}

.doc-a11y-list li {
    margin-bottom: 6px;
    font-size: var(--text-sm);
    line-height: 1.6;
}

.doc-a11y-link {
    font-size: var(--text-sm);
}

/* Prev/next pager at the foot of every doc page. */
.doc-pager {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    margin-top: 48px;
    padding-top: 20px;
    border-top: 1px solid var(--line);
}

.doc-pager-link {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 16px;
    border: 1px solid var(--line);
    border-radius: 10px;
    text-decoration: none;
    max-width: 48%;
}

.doc-pager-next {
    margin-left: auto;
    text-align: right;
}

.doc-pager-link:hover {
    border-color: var(--text-muted);
    background: var(--surface-raised);
}

.doc-pager-dir {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.doc-pager-label {
    color: var(--text);
    font-weight: 500;
}

/* The sibling's own description (#846) — clamped to two lines so a long one can't unbalance the two cards. */
.doc-pager-blurb {
    display: -webkit-box;
    overflow: hidden;
    margin-block-start: 2px;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    color: var(--text-muted);
    font-size: var(--text-xs);
    line-height: 1.4;
}

/* Internals reference — sectioned services/models/routes map. */
.doc-internals {
    max-width: 80ch;
}

.doc-internals-head {
    margin: 32px 0 4px;
    font-size: var(--text-lg);
}

.doc-internals-head span {
    margin-left: 8px;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 400;
    color: var(--text-muted);
}

.doc-internals-list {
    margin: 0 0 8px;
    padding-left: 20px;
}

.doc-internals-list li {
    margin-bottom: 10px;
    line-height: 1.6;
}

/* ── Markup Element Ledger (the /docs/markup census page) ─────────────────────────────────
   Reusable so a future JS/CSS census page adopts the same shell. Colours from the ramp + semantic
   tokens only; badge/dot tints via color-mix (decoration alpha, sanctioned). */
.ledger-accounting {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 18px;
    margin: 20px 0 8px;
    padding: 14px 18px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface-raised);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.ledger-stat strong {
    color: var(--text);
    font-size: var(--text-base);
}

.ledger-stat-clean {
    color: var(--primary);
    font-weight: 600;
}

.ledger-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
    position: sticky;
    top: 0;
    z-index: 1;
    padding: 12px 0;
    background: var(--page-bg);
}

.ledger-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.ledger-chip {
    padding: 4px 12px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
}

.ledger-chip:hover {
    border-color: var(--line-strong);
    color: var(--text);
}

.ledger-chip.is-active {
    border-color: var(--primary);
    background: color-mix(in oklab, var(--primary) 14%, transparent);
    color: var(--primary);
    font-weight: 600;
}

.ledger-search {
    flex: 1 1 220px;
    max-width: 320px;
    padding: 6px 12px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
}

.ledger-count {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.ledger-cat {
    margin-top: 28px;
}

.ledger-cat-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 10px;
}

.ledger-cat-n {
    font-size: var(--text-sm);
    font-weight: 400;
    color: var(--text-muted);
}

/* Disposition badges — one accent per state, all ramp-lawful. */
.ledger-badge {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    border: 1px solid var(--line);
    font-size: var(--text-xs);
    font-weight: 600;
    white-space: nowrap;
    text-transform: lowercase;
    color: var(--text-muted);
}

.ledger-badge-covered {
    color: var(--primary);
    border-color: color-mix(in oklab, var(--primary) 40%, transparent);
    background: color-mix(in oklab, var(--primary) 12%, transparent);
}

.ledger-badge-deferred {
    color: var(--primary);
    border-color: color-mix(in oklab, var(--primary) 40%, transparent);
}

.ledger-badge-watch {
    color: var(--text-danger);
    border-color: color-mix(in oklab, var(--text-danger) 40%, transparent);
}

.ledger-badge-raw-ok {
    color: var(--text);
    border-color: var(--line-strong);
}

.ledger-badge-not-used {
    color: var(--text-muted);
    border-style: dashed;
}

/* Baseline tiers (the CSS census, #900) — the cross-browser-safety layer, on the categorical status
   tokens: widely = success, newly = info, limited = warning. Text on its own -surface tint is the
   AA-vetted-by-construction pairing (a color-mix alpha tint is NOT vetted and drops green/amber below AA
   in light — measured). The border is the token at 35% over the surface, a 3:1 UI edge. Not lowercased —
   these are proper labels ("Widely available"), so the badge opts out of the base text-transform. */
.ledger-baseline-widely,
.ledger-baseline-newly,
.ledger-baseline-limited {
    text-transform: none;
}

.ledger-baseline-widely {
    color: var(--success);
    background: var(--success-surface);
    border-color: color-mix(in oklab, var(--success) 35%, var(--success-surface));
}

.ledger-baseline-newly {
    color: var(--info);
    background: var(--info-surface);
    border-color: color-mix(in oklab, var(--info) 35%, var(--info-surface));
}

.ledger-baseline-limited {
    color: var(--warning);
    background: var(--warning-surface);
    border-color: color-mix(in oklab, var(--warning) 35%, var(--warning-surface));
}

.ledger-type,
.ledger-guidance {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.ledger-guidance {
    display: block;
    max-width: 46ch;
    line-height: 1.5;
}

.ledger-muted {
    color: var(--text-muted);
}

/* Link-health dot — brand for reachable, danger for broken, muted-hollow for unchecked. */
.ledger-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    vertical-align: middle;
    background: var(--primary);
}

.ledger-dot-bad {
    background: var(--text-danger);
}

.ledger-dot-unknown {
    background: transparent;
    border: 1px solid var(--line-strong);
}

.ledger-health {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    margin-top: 28px;
    padding-top: 16px;
    border-top: 1px solid var(--line);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.ledger-health-form {
    margin-left: auto;
}

/* ── Typography ROLES + self-hosted families (#1247) ──────────────────────────────────────────────
 * Roles stack rather than sit in a grid: each carries a sentence of rule, and the rule is the content —
 * a card grid would crop exactly the part that stops someone setting body copy in a display face. */
.type-roles {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0 0 32px;
}

.type-role {
    padding: 16px 18px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface);
}

/* The specimen is the point of the row, so it leads at a size where the face is actually legible. */
.type-role-sample {
    margin: 0 0 8px;
    font-size: 28px;
    line-height: 1.2;
    color: var(--text);
}

.type-role-name {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
    margin: 0 0 4px;
}

.type-role-label {
    font-weight: 600;
    color: var(--text);
}

.type-role-group,
.type-font-role {
    padding: 1px 8px;
    font-size: var(--text-xs);
    color: var(--text-muted);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
}

.type-role-desc {
    margin: 0;
    max-width: 68ch;
    font-size: var(--text-sm);
    line-height: 1.55;
    color: var(--text-muted);
}

.type-fonts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 12px;
    margin: 0 0 32px;
}

.type-font {
    padding: 16px 18px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface);
}

.type-font-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
    margin: 0 0 8px;
    color: var(--text);
}

/* A family with no role is a real state (Satoshi ships without one), so it reads as a stated fact rather
   than an error — muted and italic, not a warning colour. */
.type-font-role-none {
    font-style: italic;
}

.type-font-files {
    margin: 0 0 8px;
    padding-inline-start: 18px;
    font-size: var(--text-xs);
    line-height: 1.7;
    color: var(--text-muted);
}

.type-font-var {
    font-weight: 600;
    color: var(--text);
}

.type-font-meta {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.type-font-nolicence {
    font-style: italic;
}

/* THE ROADMAP band (James, 2026-08-05 — card #1514): decided-not-yet-built components under the group grid.
   Dimmed on purpose — a promise is not a page — and unlinked, so nothing here reads as navigable. */
.docs-roadmap {
    margin-block-start: 40px;
    padding-block-start: 24px;
    border-block-start: 1px solid var(--line);
}

.docs-roadmap-title {
    margin: 0 0 4px;
    font-size: var(--text-lg);
}

.docs-roadmap-lead {
    margin: 0 0 16px;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.docs-roadmap-group {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-block-end: 8px;
}

.docs-roadmap-group-label {
    flex-shrink: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: 600;
}

.docs-roadmap-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.docs-roadmap-entry {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border: 1px dashed var(--line);
    border-radius: 999px;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.docs-roadmap-entry-key {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

/* @toc Drafts page */
/* Drafts — the design render seam (F1). Two surfaces: the admin index/form (inside the app shell) and the
 * standalone preview's slim review bar. Tokens only — the preview links the LIVE sheet, so these read the
 * same ramp every other page does. */

/* ── The preview review bar (drafts/show) ──────────────────────────────────────────────────────────────
 * A slim, in-flow bar above the canvas (never fixed — it must not overlap the rendered draft). It is chrome,
 * so it stays quiet: surface background, a hairline rule, muted text. */
.draft-bar {
    display: flex;
    /* WRAP, never overflow: the bar is chrome, so at a narrow shot width (320 is where layouts break first)
     * it must fold onto a second line rather than push the document wider than the viewport — an overflowing
     * bar became noise in every narrow measurement taken through it. design:render also renders the draft
     * with ?chrome=0, so the bar is absent there entirely; this keeps the human review view honest too. */
    flex-wrap: wrap;
    align-items: center;
    gap: 16px;
    padding: 8px 16px;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    font-size: var(--text-sm);
}

.draft-bar svg {
    width: 15px;
    height: 15px;
}

.draft-bar-back,
.draft-bar-edit {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--text-muted);
    text-decoration: none;
    font-weight: 600;
    transition: color 120ms ease;
}

.draft-bar-back:hover,
.draft-bar-edit:hover {
    color: var(--text);
}

.draft-bar-name {
    /* The one variable-width item: let it shrink and truncate rather than force the bar past the viewport. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    color: var(--text);
}

.draft-bar-edit {
    margin-inline-start: auto;
}

.draft-bar-themes {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.draft-bar-theme {
    padding: 2px 10px;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    text-decoration: none;
    font-weight: 600;
    transition: background 120ms ease, color 120ms ease;
}

.draft-bar-theme:hover {
    color: var(--text);
}

.draft-bar-theme.is-active {
    background: color-mix(in srgb, var(--primary) 16%, transparent);
    color: var(--primary);
}

/* The canvas — room around the rendered draft so components aren't jammed to the edge. */
.draft-canvas {
    padding: 24px;
}

/* ── The admin index (drafts/index) ────────────────────────────────────────────────────────────────────*/
.draft-index-lead,
.draft-form-lead {
    max-width: 72ch;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.6;
    margin-bottom: 16px;
}

.draft-index-lead code,
.draft-form-lead code {
    font-family: var(--font-mono);
    font-size: 0.9em;
    color: var(--text);
}

.draft-index-actions {
    margin-bottom: 20px;
}

.draft-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.draft-list-item {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 12px 16px;
    background: var(--surface);
    transition: background 100ms ease;
}

.draft-list-item:hover {
    background: color-mix(in srgb, var(--text) 4%, var(--surface));
}

.draft-list-name {
    font-weight: 600;
    color: var(--text);
    text-decoration: none;
}

.draft-list-name:hover {
    color: var(--primary);
}

.draft-list-meta {
    font-size: var(--text-xs);
    color: var(--text-muted);
    margin-inline-start: auto;
}

.draft-list-edit {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-muted);
    text-decoration: none;
}

.draft-list-edit:hover {
    color: var(--text);
}

/* ── The minimal form (drafts/form) ────────────────────────────────────────────────────────────────────*/
.draft-form-body {
    display: flex;
    flex-direction: column;
    gap: 18px;
    max-width: 720px;
}

.draft-form-actions {
    display: flex;
    align-items: center;
    gap: 16px;
}

.draft-form-cancel {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-muted);
    text-decoration: none;
}

.draft-form-cancel:hover {
    color: var(--text);
}

/* @toc Editor page */
/*
 * The Editor workspace — a FULL-BLEED, FLAT, Figma-grade 3-column workspace (structure | preview | tools), NOT a
 * scrolling page and NOT card-per-region panels (1209, James's direction via the designer). The app-shell runs
 * `:flush` so this fills the main edge-to-edge; the regions meet at 1px `--line` HAIRLINES — no gaps, no radius,
 * no shadows, no per-panel backgrounds. The ONE element that reads as a raised object is the device frame in the
 * preview; flat chrome everywhere else is what makes it pop. It fills the app-shell main's viewport height
 * (height:100%) and each column scrolls on its OWN (overflow:auto; min-height:0). At/below --bp-md the grid
 * collapses to one column and the page scrolls again (full phone-width EDITING is a later pass). Semantic tokens.
 */

.editor-frame {
    display: flex;     /* left rail | preview (flexes) | right rail — flex, so a COLLAPSED rail yields its room (1210) */
    align-items: stretch;
    height: 100%;      /* fill the app-shell main — the workspace, never a scrolling page */
    min-height: 0;     /* let the columns bound their own scroll */
}

/* A RAIL (1210) — a dock of panels. Fixed width, scrolls its own overflow, and a 1px hairline where it meets the
   preview (the flat-workspace division). An EMPTY rail COLLAPSES to nothing so the preview takes the room. */
.editor-rail {
    display: flex;
    flex-direction: column;
    flex: 0 0 320px;
    min-width: 0;
    min-height: 0;
    overflow: auto;
    transition:
        flex-basis var(--app-frame-transition-duration, 260ms) ease,
        opacity var(--app-frame-transition-duration, 260ms) ease,
        visibility 0s linear var(--app-frame-transition-duration, 260ms);
}

/* App-frame hiding releases both Editor rails without rewriting the saved rail order or any panel state. */
body.app-frame-hidden .editor-rail {
    flex-basis: 0;
    width: 0;
    overflow: hidden;
    border: 0;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.editor-rail[data-editor-rail="left"]:not(.is-empty) {
    border-inline-end: 1px solid var(--line);
}

.editor-rail[data-editor-rail="right"]:not(.is-empty) {
    border-inline-start: 1px solid var(--line);
}

.editor-rail.is-empty {
    flex-basis: 0;
    overflow: hidden;
}

/* The fixed Preview Panel fills the space between the rails. The device frame is its only raised object. */
.editor-region {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    padding: var(--space-4);
}

/* ── a dockable PANEL (1210) — a flat headed unit; panels within a rail meet at a hairline ─────────────────── */
.editor-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding: var(--space-4);
}

/* Navigation carries its own 32px leading icon axis. Extra Editor panel padding would shift that axis and
   make the same control jump when the coordinator adopts it into this host. */
.editor-rail[data-app-frame-native-host] > [data-navigation-panel][data-navigation-adopted] {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding: 0;
}

/* 1298: a rail's non-collapsible panels (Navigator, Layers) SHARE its bounded height and each
   scrolls its OWN body — so the Navigator's whole inventory (35 primitives, all sections open)
   scrolls inside the Navigator instead of overflowing visibly and painting over the Layers panel
   below it (which made Layers unclickable). Equal flex shares the rail; min-height:0 lets a panel
   shrink below its content; the panel body is the scroll box. Collapsible panels (the right rail)
   keep their content-height fold and are excluded. */
.editor-panel:not([data-panel-collapsible]) {
    flex: 1 1 0;
}

.editor-panel:not([data-panel-collapsible]) > .panel-body {
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
}

.editor-panel + .editor-panel {
    border-block-start: 1px solid var(--line);
}

.editor-rail > .workspace-tool--feedback[data-workspace-tool-dock-item] {
    flex: 0 0 auto;
    width: 100%;
    min-width: 0;
    min-height: 0;
    padding: var(--space-4);
    border-block-start: 1px solid var(--line);
}

.editor-rail > .workspace-tool--feedback[data-workspace-tool-dock-item] > .workspace-tool-dialog {
    height: auto;
    max-height: 44vh;
}

.editor-rail > .workspace-tool--feedback[data-workspace-tool-dock-item]:only-child {
    flex: 1 1 auto;
}

.editor-rail > .workspace-tool--feedback[data-workspace-tool-dock-item]:only-child > .workspace-tool-dialog {
    height: 100%;
    max-height: none;
}

.editor-rail > .workspace-tool--feedback[data-workspace-tool-dock-item]:first-child {
    border-block-start: 0;
}

/* Dock panels use the shared Options trigger; freehand panel dragging is intentionally absent. */

/* A panel heading — the shared .ui-label recipe (a word) over a thin rule, never a titled card. A heading that
   is not the first in its column gets top space so stacked sub-panels (Site / Pages / Layers) read apart. */
.editor-panel-label {
    margin: 0 0 var(--space-2);
    padding-block-end: var(--space-2);
    border-block-end: 1px solid var(--line);
}

.editor-panel-label:not(:first-child) {
    margin-block-start: var(--space-4);
}

.editor-reset-layout {
    flex-shrink: 0;
}

.editor-reset-layout[hidden] {
    display: none;
}

/* The target identity strip (tenant · section/page · theme axes) beside the preview title. */
.editor-target {
    margin: 0 0 var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
    overflow-wrap: anywhere;
}

.editor-target-tenant,
.editor-target-page {
    color: var(--text);
}

.editor-target-sep {
    margin-inline: var(--space-1);
}

/* The fixed Panel body carries the remaining height to the preview mount. The mount owns desktop scrolling. */
.editor-preview > .panel-body,
.editor-preview > .panel-body > .panel-body-inner {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

/* #1226 Navigator: one x-tabs body inside the dockable panel. The panel itself is the container boundary, so
   custom-width rails yield the hint before any label and only the tablist scrolls below the compact four-up fit. */
/* #1293: ONE panel of stacked sections, not a tabset — the whole hierarchy visible at once (an IDE project-
   explorer). The LEFT RAIL already owns the scroll (it is overflow:auto), so the navigator does NOT nest a
   second scroll — a long section is tamed by COLLAPSING it, not by an inner scrollbar. container-type keeps the
   rows responsive to the panel's own width (a rail the user narrows), never the viewport. */
.editor-navigator {
    min-width: 0;
    container-type: inline-size;
}

/* Each category is a collapsible <x-details>, default OPEN so nothing is hidden on load; the author collapses
   Primitives (35 rows) when it is in the way. Strip the primitive's card chrome to the navigator's tight
   rhythm and separate sections with a hairline. */
.editor-navigator-section.details {
    border: 0;
    border-radius: 0;
    background: transparent;
    /* 1298: the base .details clips with overflow:hidden (for its rounded corners) — but these sections have
       no border/radius, and that clip would trap the sticky section label inside its own short section instead
       of letting it stick against the scrolling panel body. Visible restores the sticky (label below). */
    overflow: visible;
}

.editor-navigator-section + .editor-navigator-section {
    margin-block-start: var(--space-2);
    border-block-start: 1px solid var(--line);
}

/* The section label reuses the group-label idiom — small, muted, uppercase ui-label voice — over the native
   disclosure chevron (which rotates on [open]). Compact padding keeps four labels legible without a tab bar. */
.editor-navigator-section > .details-summary {
    gap: var(--space-1);
    padding: var(--space-2) var(--space-1);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    /* 1298: sticky within the scrolling Navigator body — a long section (Primitives is 35 rows) stays oriented
       as you scroll past it, the label pinned to the top until the next section pushes it up. The page-bg fill
       (what the transparent Navigator sits on) keeps scrolled rows from showing through the label. */
    position: sticky;
    inset-block-start: 0;
    z-index: 1;
    background: var(--page-bg);
}

.editor-navigator-section > .details-body {
    padding: var(--space-1) 0 0;
    color: var(--text);
}

.editor-navigator-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
}

/* One flat row language: decorative 16px icon → durable label → yielding mono hint → optional state. Links and
   buttons keep their native keyboard activation; no second roving/listbox model is introduced. */
.editor-navigator-row.button,
.editor-navigator-row.link {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    min-width: 0;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: 500;
    line-height: 1.25;
    text-align: start;
    text-decoration: none;
}

.editor-navigator-row.link:visited {
    color: var(--text);
}

.editor-navigator-row:hover {
    background: var(--surface-raised);
    color: var(--text);
}

.editor-navigator-row:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.editor-navigator-row.is-selected {
    background: color-mix(in srgb, var(--primary) 8%, transparent);
}

.editor-navigator-row.is-selected::before {
    position: absolute;
    inset-block: var(--space-1);
    inset-inline-start: 0;
    width: 2px;
    border-radius: var(--radius-control);
    background: var(--primary);
    content: "";
}

.editor-navigator-icon {
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--text-muted);
}

.editor-navigator-icon svg {
    display: block;
    width: 1rem;
    height: 1rem;
}

.editor-navigator-label {
    flex: 0 1 auto;
    min-width: 0;
}

.editor-navigator-hint {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 400;
    text-align: end;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.editor-navigator-status {
    width: 0.5rem;
    height: 0.5rem;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--text-muted);
}

.editor-navigator-row[data-status="draft"] .editor-navigator-status {
    background: var(--warning);
}

.editor-navigator-row[data-status="published"] .editor-navigator-status {
    background: var(--success);
}

.editor-navigator-chip {
    flex: 0 0 auto;
    padding: var(--space-1);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 400;
}

.editor-navigator-chrome {
    margin-block-start: var(--space-3);
    padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--line);
}

.editor-navigator-group-label {
    margin: 0 0 var(--space-1);
}

.editor-navigator-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

@container (max-width: 18rem) {
    .editor-navigator-tabs > .tabs-nav {
        grid-template-columns: none;
        grid-auto-columns: minmax(4.5rem, 1fr);
        grid-auto-flow: column;
        overflow-x: auto;
    }

    .editor-navigator-hint {
        display: none;
    }
}

/* The preview mount centers the mobile-first device frame in the (taller) preview region; the frame owns
   its own dimensions, so the mount just gives it room + overflow to scroll if the frame exceeds the pane. */
.editor-preview-mount {
    display: flex;
    flex: 1;
    align-items: flex-start;
    justify-content: center;
    overflow: auto;
    padding-block: var(--space-2);
}

/* ── the block canvas: the layers list ─────────────────────────────────────────────────────────────
   The DRAG SURFACE (editor-drag.js binds SortableJS here) + the palette-drag drop target. Now the body of the
   dockable Layers panel (1210); its rail scrolls, so the list flows at natural height. The iframe shows what the
   stack LOOKS like; this shows what it IS — and it's the half you can grab (SortableJS cannot reach the iframe). */
.editor-layers {
    min-height: 0;
}

.editor-layers-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin: 0;
    padding: 0;
    list-style: none;
}

.editor-layer {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--global-border-radius);
    background: var(--surface);
    font-size: var(--text-sm);
    color: var(--text);
}

/* Inline heroicons in the editor chrome (1206) — the layer grip/reorder + the repeater reorder. The heroicon
   SVGs carry a Tailwind size class we don't use, so size them here (the details-marker convention). */
.editor-icon {
    display: inline-flex;
}

.editor-icon svg {
    display: block;
    width: 1rem;
    height: 1rem;
}

/* The grip is the drag handle — editor-drag.js binds the reorder gesture to it — so the grab cursor is here. */
.editor-layer-grip {
    line-height: 1;
    color: var(--text-muted);
    cursor: grab;
}

.editor-layer-grip:active {
    cursor: grabbing;
}

/* The keyboard reorder controls (WCAG 2.1.1 / 2.5.7 — the non-drag path). Compact ghost icon buttons pinned to
   the row end; the drag grip stays the pointer affordance. Server-rendered as x-button + minted identically by
   renderLayers, so the .button base plus this compaction apply to both. */
.editor-layer-move-group {
    display: inline-flex;
    flex-shrink: 0;
    gap: var(--space-1);
}

.editor-layer-move {
    min-width: 1.75rem;
    padding-block: var(--space-1);
    padding-inline: var(--space-1);
    line-height: 1;
}

/* SortableJS's placeholder for the row being dragged — dimmed rather than hidden, so the list keeps its
   height and the drop target doesn't jump under the cursor. */
.editor-layer-ghost {
    opacity: 0.4;
}

.editor-layers-empty {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* ── the Theme tab groups (Color · Typography) ─────────────────────────────────────────────────────
   In the Theme tab (1209), Color is FIRST (no top rule); Typography follows, ruled off by a hairline. */
.editor-tools-typography {
    margin-block-start: var(--space-4);
    border-block-start: 1px solid var(--line);
    padding-block-start: var(--space-3);
}

/* Typography is honest-empty (C3): a one-line reason, no controls, until per-page type controls land. */
.editor-tools-empty-note {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* The preview's honest fallback (a data-driven target can't render from config) — its own standalone doc,
   centered through the live sheet so it sits in the device frame like any preview. */
.editor-unavailable {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    min-height: 100vh;
    padding: var(--space-6);
    text-align: center;
    color: var(--text);
}

.editor-unavailable-icon {
    display: inline-flex;
    width: 2.5rem;
    height: 2.5rem;
    margin: 0;
    color: var(--text-muted);
}

.editor-unavailable-title {
    margin: 0;
    font-size: var(--text-lg);
}

.editor-unavailable-body {
    margin: 0;
    max-width: 42ch;
    color: var(--text-muted);
}

/* The bare /editor's tenant-scoped empty state (#1251) — a registered tenant with no editable pages yet. App
   chrome, not the iframe fallback above, so it centers inside the card rather than the viewport. */
.editor-empty-site {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding-block: var(--space-8);
    text-align: center;
}

.editor-empty-site-icon {
    display: inline-flex;
    width: 2.5rem;
    height: 2.5rem;
    margin: 0;
    color: var(--text-muted);
}

.editor-empty-site-title {
    margin: 0;
    font-size: var(--text-lg);
}

.editor-empty-site-body {
    margin: 0;
    max-width: 52ch;
    color: var(--text-muted);
}

/* Mobile boundary: the three columns stack (structure, preview, tools) and the PAGE scrolls again (a phone can't
   hold full-height columns). Each region drops its internal scroll so its content flows into the page, and the
   column hairline becomes a horizontal rule between stacked regions. */
@media (max-width: 768px), (max-height: 600px) {
    .editor-frame {
        flex-direction: column;   /* stack: left rail · preview · right rail — the page scrolls again */
        height: auto;
    }

    .editor-rail,
    .editor-region {
        flex: none;
        overflow: visible;
    }

    /* 1298: the desktop panel-share-and-internal-scroll needs a BOUNDED rail. Stacked, the rail is unbounded
       (flex:none) and the PAGE scrolls, so each panel takes its natural height and its body must not trap a
       second scroll — undo the desktop share here so the Navigator's sections flow into the page like before. */
    .editor-panel:not([data-panel-collapsible]),
    .editor-panel:not([data-panel-collapsible]) > .panel-body {
        flex: none;
    }

    .editor-panel:not([data-panel-collapsible]) > .panel-body {
        overflow-y: visible;
    }

    /* Stacked: the side hairlines become TOP hairlines between sections; the first section (left rail) has none. */
    .editor-rail[data-editor-rail="left"]:not(.is-empty) {
        border-inline-end: 0;
    }

    .editor-rail[data-editor-rail="right"]:not(.is-empty) {
        border-inline-start: 0;
        border-block-start: 1px solid var(--line);
    }

    .editor-region {
        border-block-start: 1px solid var(--line);
    }

    .editor-preview {
        min-height: 50vh;
    }

    .editor-preview-mount {
        overflow: visible;
    }

    /* The rail dock is absent when Editor panels stack, so its entire desktop menu is absent too. editor-dock.js
       closes any open flyout, transfers focus to the panel region, and transiently exposes collapsed bodies. */
    .editor-panel > .panel-head > .panel-actions > .dropdown {
        display: none;
    }
}

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

/* ── The inspector (1171) — selection + the per-block properties schedule ─────────────────────────────────
   A blueprint/technical-drawing identity: hairline-ruled rows, compact rhythm, the ring (not a border) for
   selection, mono as the part-number voice. All colour through tokens. */

/* Canvas selection RING — an outline (no layout shift), painted on the block wrapper inside the preview iframe
   (this sheet bundles into /css/styles.css, which the iframe loads). Hover = a faint tier so blocks read clickable. */
.editor-block-wrap {
    position: relative;
}

.editor-block-wrap:hover {
    outline: 1px solid var(--line-strong);
    outline-offset: -1px;
    cursor: pointer;
}

.editor-block-selected,
.editor-block-selected:hover {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
}

/* The layers row's SELECT trigger — the name, now a button that fills the row and left-aligns.

   BORDERLESS AT REST (1213): the ghost variant carries a resting border, which nested a visible chip inside the
   already-bordered row — a double outline, worst in light. Transparent (not `border: 0`) keeps the 1px in the box
   model, so nothing shifts when hover paints it back. Hover is inherited from .button-ghost:hover, which outranks
   this rule; focus is restated below because .button:focus-visible styles the RING, not the border. */
.editor-layer-select {
    flex: 1 1 auto;
    min-width: 0;
    justify-content: flex-start;
    text-align: start;
    overflow: hidden;
    text-overflow: ellipsis;
    border-color: transparent;
}

.editor-layer-select:focus-visible {
    border-color: var(--button-ghost-border-hover);
}

.editor-layer.is-selected {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
    border-radius: var(--radius-md);
}

.editor-inspectors {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.editor-inspector-empty-state {
    margin: 0;
    padding-block: var(--space-4);
    color: var(--text-muted);
    font-size: 0.875rem;
}

/* The inspector — a properties schedule. */
.editor-inspector-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding-block-end: var(--space-3);
    border-block-end: 1px solid var(--line);
}

.editor-inspector-name {
    font-weight: 600;
}

/* The breadcrumb "<block> on <page>" (fixture S4) — the block's scope at a glance; the page reads muted. */
.editor-inspector-breadcrumb {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin: 0;
}

.editor-inspector-breadcrumb-sep,
.editor-inspector-page {
    color: var(--text-muted);
    font-weight: 400;
}

/* Scope footer (fixture S4): "Applies to this block on this page." — quiet, ruled off from the controls above. */
.editor-inspector-scope {
    margin: 0;
    padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--line);
    color: var(--text-muted);
    font-size: 0.75rem;
}

/* The control + its trailing info glyph, side by side (the info-tooltip recipe, #1220): the control takes the
   width, the glyph sits at the end. */
.editor-inspector-control {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.editor-inspector-control > :first-child {
    flex: 1;
    min-width: 0;
}

/* The SHARED control field (the Inspector-polish foundation): an optional orienting icon, the primitive, an
   optional display-only unit adornment, and the info affordance — one flex row both instrument panes render
   (editor/_control-field), so icon + unit land in ONE place. Replaces the per-pane control wrapper's flex. */
.editor-control-field {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.editor-control-field-icon {
    flex: none;
    display: inline-flex;
    width: 1rem;
    height: 1rem;
    color: var(--text-muted);
}

.editor-control-field-body {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: flex-end;
    gap: var(--space-1);
}

/* the primitive grows; the unit suffix stays its own size, aligned to the input row (the primitive's field). */
.editor-control-field-body > :first-child {
    flex: 1;
    min-width: 0;
}

.editor-control-field-unit {
    flex: none;
    padding-block-end: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1;
}

/* An ordered named GROUP section (the group/order keys). Ungrouped controls render in a headerless section. */
.editor-control-group-heading {
    margin-block: var(--space-3) var(--space-1);
    color: var(--text-muted);
}

.editor-control-group:first-child > .editor-control-group-heading {
    margin-block-start: 0;
}

/* SINGLE-GROUP heading suppression (the #1213 precedent, applied to control groups): a controls list of exactly
   one section prints a heading that only repeats what the panel already says — suppress it, so a component with
   0 or 1 group reads flat, exactly as before this foundation. :only-child IS "exactly one section". */
.editor-inspector-controls > .editor-control-group:only-child > .editor-control-group-heading {
    display: none;
}

/* The info button — a quiet focusable tooltip trigger. x-tooltip reveals on hover OR focus; the associated
   control separately keeps its persistent aria-describedby to the sr-only carrier. */
.editor-field-info {
    flex: none;
    padding: var(--space-1);
    color: var(--text-muted);
    cursor: help;
}

.editor-field-info svg {
    width: 1rem;
    height: 1rem;
}

.editor-inspector-id {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    color: var(--text-muted);
}

.editor-inspector-actions {
    display: inline-flex;
    gap: var(--space-2);
    margin-inline-start: auto;
}

.editor-inspector-controls {
    display: flex;
    flex-direction: column;
}

/* The read-only reason line (#1211/#1269): shown above the controls on an unpreviewable target. Each control
   carries its own native `disabled` state (dimmed + inert) — the reason names WHY, in plain language, so it is
   never a silently dead control. The container itself is not additionally dimmed (that would double-dim the
   already-greyed controls); `.is-readonly` is the state hook. */
.editor-inspector-readonly-note {
    margin-block: 0 var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.editor-inspector-row {
    padding-block: var(--space-3);
    border-block-end: 1px solid var(--line);
}

.editor-inspector-row.is-not-saved {
    border-inline-start: 2px solid var(--danger);
    padding-inline-start: var(--space-2);
}

.editor-inspector-row.is-not-saved::after {
    content: "not saved — retrying on next edit";
    display: block;
    margin-block-start: var(--space-1);
    color: var(--danger);
    font-size: 0.75rem;
}

/* Clear-to-base (§3.1): a row overriding the default carries a modified dot + a Clear action, laid out beside
   the control. A row at base renders neither (and stays a plain block). */
.editor-inspector-row.is-modified {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
}

.editor-inspector-control {
    flex: 1 1 auto;
    min-width: 0;
}

.editor-inspector-row-meta {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    flex: 0 0 auto;
    padding-block-end: var(--space-1);
}

.editor-inspector-modified {
    inline-size: 0.375rem;
    block-size: 0.375rem;
    border-radius: 50%;
    background: var(--primary);
}

.editor-inspector-uneditable {
    margin: 0;
    padding-block: var(--space-3);
    border-block-end: 1px solid var(--line);
    color: var(--text-muted);
    font-size: 0.8125rem;
}

.editor-inspector-uneditable-key {
    display: block;
    color: var(--text);
    font-weight: 500;
}

.editor-inspector-empty {
    margin: 0;
    padding-block: var(--space-4);
    color: var(--text-muted);
}

/* The list control — a REPEATER (§3.2): rows of the element's scalar controls, with Add / Remove / move. Each
   row is a hairline-ruled card; the cells flow, the row actions sit at the end. */
.editor-inspector-list-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-end: var(--space-2);
}

.editor-inspector-list-hint {
    color: var(--text-muted);
    font-size: 0.75rem;
}

.editor-inspector-list-items {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.editor-inspector-list-item {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding: var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.editor-inspector-list-cells {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    flex: 1 1 12rem;
    min-width: 0;
}

.editor-inspector-list-cell {
    flex: 1 1 8rem;
    min-width: 0;
}

/* A GROUP field (1206 — section-stats' source) is a nested sub-group: it spans the row (its own scalar
   sub-fields flow inside it), ruled off so it reads as one grouped unit, not two more loose cells. */
.editor-inspector-list-cell-group {
    flex: 1 1 100%;
    padding: var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.editor-inspector-list-group-label {
    display: block;
    margin-block-end: var(--space-1);
}

.editor-inspector-list-group-fields {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.editor-inspector-list-group-fields > .input,
.editor-inspector-list-group-fields > .select {
    flex: 1 1 8rem;
    min-width: 0;
}

.editor-inspector-list-row-actions {
    display: inline-flex;
    gap: var(--space-1);
    flex: 0 0 auto;
}

.editor-inspector-list-empty,
.editor-inspector-list-floor {
    margin: var(--space-2) 0 0;
    color: var(--text-muted);
    font-size: 0.75rem;
}
/* The floor-guard line stays --text-muted (inherited above): it is GUIDANCE about a disabled state,
   not an error the user caused — danger is reserved for .is-not-saved, which IS one (the design
   review's one required fix; GOV.UK grammar: error = something went wrong). */

.editor-inspector-list-add {
    margin-block-start: var(--space-2);
}

/* The Add template is inert (native <template> is display:none) — guarded here in case a reset unhides it. */
[data-editor-list-template] {
    display: none;
}

/* Honest-read-only list (a nested-field list, pending the nested-field seam) — current values, no editing. */
.editor-inspector-list-readonly .editor-inspector-list-item {
    font-size: 0.8125rem;
}

.editor-inspector-list-cell-ro {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-1);
}

.editor-inspector-list-cell-key {
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
}

.editor-inspector-list-note {
    margin: var(--space-2) 0 0;
    color: var(--text-muted);
    font-size: 0.75rem;
}

/* REGION 2 (#1292) — the read-only PROP REFERENCE for a browsed component (the spec sheet). A value-as-text row
   per prop, never a form: label · value · optional icon/unit. Set off from region 1 by a rule + heading so the
   editable controls above and the reference below read as two distinct regions. */
.editor-inspector-reference {
    margin-block-start: var(--space-4);
    padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--line);
}

.editor-inspector-reference-heading {
    margin: 0;
}

.editor-inspector-reference-note {
    margin: var(--space-1) 0 var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.editor-inspector-reference-row {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    padding-block: var(--space-2);
    border-block-end: 1px solid var(--line);
}

.editor-inspector-reference-label {
    flex: none;
    color: var(--text-muted);
}

.editor-inspector-reference-value {
    flex: 1;
    min-width: 0;
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-1);
    color: var(--text);
    word-break: break-word;
}

/* #1227 — tenant navigation is another Inspector LIST state, never another rail panel. The accepted fixture is a
   compact stack: grip · label + one route hint · pencil, with the existing list-tier Add ghost beneath it. */
.editor-nav-inspector[hidden] {
    display: none;
}

.editor-nav-row {
    align-items: center;
    flex-wrap: nowrap;
}

.editor-nav-row.is-dragging {
    opacity: 0.45;
}

.editor-nav-grip,
.editor-nav-edit,
.editor-nav-move,
.editor-nav-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    padding: var(--space-1);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
}

.editor-nav-grip {
    cursor: grab;
}

.editor-nav-grip:active {
    cursor: grabbing;
}

.editor-nav-grip:hover,
.editor-nav-edit:hover,
.editor-nav-move:hover:not(:disabled),
.editor-nav-remove:hover:not(:disabled) {
    background: var(--surface-raised);
    color: var(--text);
}

.editor-nav-grip:focus-visible,
.editor-nav-edit:focus-visible,
.editor-nav-move:focus-visible,
.editor-nav-remove:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.editor-nav-grip svg,
.editor-nav-edit svg,
.editor-nav-move svg,
.editor-nav-remove svg {
    inline-size: 1rem;
    block-size: 1rem;
}

.editor-nav-summary {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    flex: 1 1 auto;
    min-width: 0;
}

.editor-nav-label {
    overflow: hidden;
    color: var(--text);
    font-size: 0.8125rem;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.editor-nav-route {
    overflow: hidden;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.editor-nav-fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
    flex: 1 1 100%;
    inline-size: 100%;
    padding-block-start: var(--space-2);
    border-block-start: 1px solid var(--line);
}

.editor-nav-fields[hidden] {
    display: none;
}

.editor-nav-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
    color: var(--text-muted);
    font-size: 0.6875rem;
}

.editor-nav-input {
    min-width: 0;
    inline-size: 100%;
    padding: var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
}

.editor-nav-row-actions {
    display: inline-flex;
    gap: var(--space-1);
    flex: 0 0 auto;
}

.editor-nav-move:disabled,
.editor-nav-remove:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.editor-nav-footer-note {
    padding-block-start: var(--space-2);
    border-block-start: 1px solid var(--line);
}

.editor-nav-status {
    min-block-size: 1.25rem;
    margin: var(--space-2) 0 0;
    color: var(--text-muted);
    font-size: 0.75rem;
}

.editor-nav-status.is-ok {
    color: var(--success);
}

.editor-nav-status.is-error {
    color: var(--danger);
}

@container (max-width: 24rem) {
    .editor-nav-fields {
        grid-template-columns: 1fr;
    }

    .editor-nav-route {
        display: none;
    }
}
/* The application-frame Navigation Panel is a direct peer outside editor panel persistence. */
.editor-rail[data-app-frame-native-host] > [data-navigation-panel] {
    min-height: 0;
    max-height: 100%;
}

/* @toc Games dante page */
/* Games · Dante dashboard (/games/dante) — the slots view. Entities in dense bordered rows (dense-list
   law, not cards). Empty slots are the emphasised state (dashed, neutral) so the gaps itch. Status
   colour rides the categorical token set only (via x-badge); the empty chip is neutral + a dashed line. */

.game-dash {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.game-dash-tagline {
    margin: 0;
    max-width: 70ch;
    color: var(--text-muted);
}

.game-dash-fill {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    max-width: 32rem;
}

.game-dash-fill-track {
    height: 10px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    overflow: hidden;
}

.game-dash-fill-done {
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--success);
}

.game-dash-fill-label {
    font-size: 0.8125rem;
    color: var(--text-muted);
}

.game-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.game-section-title {
    margin: 0;
    font-size: 1.125rem;
}

.game-rows {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.game-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-4);
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
}

.game-row-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    flex: 1 1 40%;
    min-width: 0;
}

.game-row-name {
    font-weight: 600;
}

.game-row-oneliner {
    color: var(--text-muted);
    font-size: 0.875rem;
}

.game-slots {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    flex: 2 1 55%;
}

.game-slot-empty {
    display: inline-flex;
    align-items: center;
    padding-block: var(--space-1);
    padding-inline: var(--space-2);
    border: 1px dashed var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
    color: var(--text-muted);
    font-size: 0.75rem;
}

.game-row-chevron {
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--text-muted);
}

.game-row-chevron svg {
    width: 1rem;
    height: 1rem;
}

/* @toc Games entity page */
/* Games · entity page (/games/dante/{type}/{slug}) — hero + stats + graduated frame strips. Sprite
   imgs carry the pixel law (image-rendering: pixelated) and render at natural size: the cuts are
   box-composed to a union rect, so equal, un-scaled frames keep a strip's animation aligned. */

.game-entity {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.game-entity-hero {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-6);
}

.game-entity-art {
    flex: 0 0 auto;
    width: min(240px, 100%);
}

.game-entity-art-img {
    width: 100%;
    height: auto;
    border-radius: var(--radius-lg);
    background: var(--surface-muted);
}

.game-entity-art-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1 / 1;
    padding: var(--space-6);
    border: 1px dashed var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-muted);
    color: var(--text-muted);
    text-align: center;
    font-size: 0.875rem;
}

.game-entity-identity {
    flex: 1 1 320px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.game-entity-role {
    margin: 0;
    color: var(--text-muted);
    font-size: 1rem;
}

.game-entity-zone {
    margin: 0;
}

.game-entity-zone-long {
    color: var(--text-muted);
    font-size: 0.875rem;
}

.game-entity-lore {
    margin: 0;
    max-width: 65ch;
    line-height: 1.6;
}

.game-entity-quote {
    margin: 0;
    padding-inline-start: var(--space-4);
    border-inline-start: 3px solid var(--line);
    color: var(--text-muted);
    font-style: italic;
}

.game-entity-quote p {
    margin: 0 0 var(--space-2);
}

.game-entity-quote cite {
    font-size: 0.8125rem;
    font-style: normal;
}

.game-entity-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.game-entity-section h2 {
    margin: 0;
    font-size: 1.25rem;
}

.game-empty {
    margin: 0;
    color: var(--text-muted);
}

.game-strip-block {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.game-strip-title {
    margin: 0;
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    font-size: 1rem;
    text-transform: capitalize;
}

.game-strip-count {
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--text-muted);
    text-transform: none;
}

.game-strip {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--space-4);
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-muted);
    overflow-x: auto;
}

.game-frame {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    flex: 0 0 auto;
}

.game-pixel {
    image-rendering: pixelated;
}

.game-frame-img {
    display: block;
    width: auto;
    height: auto;
    max-width: none;
    border-radius: var(--radius-sm);
    background: var(--surface);
}

.game-frame-tag {
    display: inline-flex;
}

.game-frame-ord {
    font-size: 0.75rem;
    color: var(--text-muted);
}

.game-rulings {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.game-ruling {
    margin: 0;
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    color: var(--text-muted);
    font-size: 0.875rem;
}

.game-provenance {
    margin: 0;
    padding-inline-start: var(--space-5);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    color: var(--text-muted);
    font-size: 0.875rem;
}

/* @toc Games index page */
/* Games index (/games) — the section shelf: a reflowing card grid, one card per game. */

.games-index {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

.games-index-lead {
    margin: 0;
    max-width: 60ch;
    color: var(--text-muted);
}

.games-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-5);
}

.games-card-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin-block-start: var(--space-4);
}

.games-fill {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.games-fill-track {
    height: 8px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    overflow: hidden;
}

.games-fill-done {
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--success);
}

.games-fill-label {
    font-size: 0.8125rem;
    color: var(--text-muted);
}

.games-card-foot {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.games-empty {
    margin: 0;
    color: var(--text-muted);
}

/* @toc Generate page */
/* @toc Generate */

/* The Generate surface (#1472) — setup, the job page, and the landing lists.
 *
 * ★ THERE WAS NO `dgen-*` CSS AT ALL. Every one of these classes existed in the blades and none of them was
 * ever defined, so the screens rendered as an undifferentiated column of text and controls. That is half of
 * "you can't actually use the generate screen": the other half was that the setup path could not resolve and
 * the job view-model was broken, and neither of those is visible until the page is legible enough to try.
 *
 * DELIBERATELY ONLY ENOUGH — hierarchy and readability, per the spec's step 4. This is not a visual pass on
 * Generate: no new components, no re-skin, nothing that would have to be re-decided when the surface is
 * designed properly.
 *
 * Every COLOUR and SPACE is a token. The two literals are deliberate and named where they appear: `600` for
 * weight (there is no weight token — `--font-weight-medium` is referenced three times elsewhere and defined
 * nowhere) and `44px` for the touch minimum, which is a physical constant rather than a themed value.
 *
 * MOBILE-FIRST, because this is read on a phone: the base rules ARE the small layout and the one enhancement
 * is keyed on the CONTAINER, not the viewport — a landscape phone is 844px wide and a width-only query would
 * hand it a desktop layout. */

/* `.dgen` must be the FLEX PARENT, not just a wrapper: the requested-detail rule below is `order: -1`, and
 * order does nothing at all inside a block container. Without this the class would apply, the gate would pass
 * and the section would not move — a rule that reads as implemented and is inert. */
.dgen,
.dgen-setup,
.dgen-job-page {
    container-type: inline-size;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* ── setup ──────────────────────────────────────────────────────────────────────────────────────────── */

.dgen-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* The two forms are separate elements (selection is GET, creation is POST) but read as one flow, so the
 * create step sits tight under the plan it is creating rather than floating away from it. */
.dgen-form-create {
    margin-block-start: var(--space-2);
}

.dgen-profile-summary {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}


.dgen-blocked-list {
    margin: 0;
    padding-inline-start: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}


/* Every tappable control on this surface clears the touch minimum. The screens are used on a phone, and a
 * 32px button is the difference between a workflow and a nuisance. */
.dgen-tap {
    min-block-size: 44px;
}

/* ── the job page ───────────────────────────────────────────────────────────────────────────────────── */

.dgen-job-head {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* The Delivery/Review switch. Two links that look identical are not a switch, so the current face carries a
 * filled surface and the other reads as an ordinary link. */
.dgen-job-modes {
    display: flex;
    gap: var(--space-2);
}

.dgen-job-mode {
    min-block-size: 44px;
    display: inline-flex;
    align-items: center;
    padding-inline: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-decoration: none;
}

.dgen-job-mode.is-current {
    background-color: var(--surface-raised);
    border-color: var(--line-strong);
    color: var(--text);
}

.dgen-job-summary {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ── the landing lists ──────────────────────────────────────────────────────────────────────────────── */

.dgen-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.dgen-section-head {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 600;   /* a numeric weight, not var(--font-weight-medium): that token is DEFINED NOWHERE. Three existing uses are grandfathered debt in the phantom baseline; adding a fourth would render as nothing while looking deliberate. */
}

/* A job row is the primary tap target on the landing page: two lines, the actor first because that is what
 * the owner is looking for, and the whole row hittable rather than just the name. */
.dgen-job {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-block-size: 44px;
    padding: var(--space-3);
    background-color: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    text-decoration: none;
    color: var(--text);
}

.dgen-job-actor {
    font-weight: 600;   /* a numeric weight, not var(--font-weight-medium): that token is DEFINED NOWHERE. Three existing uses are grandfathered debt in the phantom baseline; adding a fourth would render as nothing while looking deliberate. */
}

.dgen-job-meta {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.dgen-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ── the requested gallery detail ───────────────────────────────────────────────────────────────────── */

/* ★ WHEN `?group=` ASKS FOR A DETAIL, THAT DETAIL COMES FIRST. Ordered rather than moved: the DOM keeps one
 * order at every width (so nothing about focus or reading order changes), and the flex `order` simply floats
 * the thing that was requested above the generic workflow and job-history sections. Following a link and
 * landing above the fold on something else is how a surface teaches you that its links do not work. */
.dgen-detail-requested {
    order: -1;
}

@container (min-width: 40rem) {
    .dgen-form {
        max-inline-size: 32rem;
    }

    .dgen-job-head {
        display: grid;
        grid-template-columns: auto 1fr;
        align-items: center;
        column-gap: var(--space-4);
    }
}

/* @toc Health page */
/*
 * Health keeps authorized site condition and shared capacity
 * in one compact, container-led dashboard.
 * The page reflows one semantic site table into labelled rows; collection and authorization remain upstream.
 */

.health-page {
    container-type: inline-size;
}

.health-page-inner {
    display: grid;
    gap: var(--space-4);
    max-inline-size: var(--page-section-max-width);
    margin-inline: auto;
}

.health-page-inner > section,
.health-filters,
.health-shared,
.health-capacity-strip,
.health-detail-grid,
.health-chart-grid,
.health-capacity-card,
.health-history-layout,
.health-history-card .card-body {
    min-inline-size: 0;
}

.health-page h2,
.health-page h3,
.health-page h4,
.health-page p,
.health-page dl {
    margin-block-start: 0;
}

.health-filters {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: end;
    gap: var(--space-2);
}

.health-history-filter {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--space-2);
}

.health-section-heading,
.health-collection,
.health-capacity-heading,
.health-capacity-pressure {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
}

.health-section-heading {
    justify-content: space-between;
    margin-block-end: var(--space-3);
}

.health-section-heading h2,
.health-section-heading h3,
.health-section-label,
.health-capacity-heading h4 {
    margin: 0;
}

.health-section-label,
.health-collection,
.health-history-state {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.health-overview {
    display: grid;
    gap: var(--space-3);
    padding: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    background: var(--surface);
}

.health-counts {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2) var(--space-4);
    margin-block-end: 0;
}

.health-counts div {
    display: grid;
    gap: 0;
}

.health-counts dt,
.health-capacity-readings dt,
.health-history-readings dt,
.health-detail-grid dt {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.health-counts dd,
.health-capacity-readings dd,
.health-history-readings dd,
.health-detail-grid dd {
    margin-inline-start: 0;
}

.health-counts dd {
    color: var(--text);
    font-size: var(--text-lg);
    font-weight: 650;
}

.health-shared {
    border-block-start: 1px solid var(--line);
    padding-block-start: var(--space-4);
}

.health-capacity-strip,
.health-detail-grid,
.health-chart-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
}

.health-capacity-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.health-capacity-card {
    display: grid;
    align-content: start;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface-raised);
}

.health-capacity-heading {
    justify-content: space-between;
}

.health-capacity-readings,
.health-history-readings {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
    margin-block-end: 0;
}

.health-capacity-readings div,
.health-history-readings div {
    min-inline-size: 0;
}

.health-capacity-empty {
    grid-column: 1 / -1;
}

.health-capacity-readings dd,
.health-history-readings dd {
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: 600;
    overflow-wrap: anywhere;
}

.health-capacity-primary {
    grid-column: 1 / -1;
}

.health-capacity-primary dd {
    display: flex;
    align-items: baseline;
    gap: var(--space-1);
}

.health-capacity-primary dd > span {
    font-size: clamp(1.5rem, 5cqi, 2rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.035em;
}

.health-capacity-primary small {
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: normal;
}

.health-capacity-pressure {
    margin-block-start: auto;
}

.health-capacity-pressure .ui-badge {
    max-inline-size: 100%;
    line-height: 1.2;
    overflow-wrap: anywhere;
    white-space: normal;
}

.health-sites .table-wrap {
    max-inline-size: 100%;
    overflow: visible;
}

.health-sites .table {
    display: block;
    min-inline-size: 0;
}

.health-sites .table thead {
    position: static;
    display: block;
    inline-size: 100%;
    block-size: auto;
    margin-block-end: var(--space-3);
    padding: 3px;
    overflow-x: auto;
    clip: auto;
    clip-path: none;
    white-space: nowrap;
}

.health-sites .table thead tr {
    display: flex;
    inline-size: max-content;
    gap: var(--space-2);
}

.health-sites .table thead th {
    padding: 0;
    border: 0;
}

.health-sites .table thead .table-sort {
    display: inline-flex;
    align-items: center;
    min-block-size: 44px;
    padding-inline: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
}

.health-sites .table tbody {
    display: grid;
    gap: var(--space-3);
}

.health-sites .table tbody tr {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface);
    overflow: hidden;
}

.health-sites .table td {
    display: block;
    order: 5;
    min-inline-size: 0;
    padding: var(--space-3);
    border: 0;
    vertical-align: top;
    overflow-wrap: anywhere;
}

.health-sites .table td::before {
    content: attr(data-label);
    display: block;
    margin-block-end: var(--space-1);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
}

.health-site-domain,
.health-site-condition,
.health-site-response,
.health-site-errors {
    background: var(--surface-raised);
}

.health-sites .table .health-site-domain {
    order: 1;
}

.health-sites .table .health-site-condition {
    order: 2;
}

.health-sites .table .health-site-response {
    order: 3;
}

.health-sites .table .health-site-errors {
    order: 4;
    overflow-wrap: normal;
    white-space: nowrap;
}

.health-site-domain a {
    font-weight: 650;
}

.health-reading {
    display: grid;
    gap: var(--space-1);
    min-inline-size: 0;
}

.health-reading-value {
    color: var(--text);
    font-weight: 600;
}

.health-reading-state {
    color: var(--text-muted);
    font-size: var(--text-xs);
    line-height: 1.3;
}

.health-reasons {
    margin-block: 0 var(--space-4);
}

.health-site-history {
    margin-block-end: var(--space-4);
}

.health-detail-heading {
    margin-block-end: var(--space-3);
}

.health-detail-grid dl {
    display: grid;
    gap: var(--space-2);
    margin: 0;
}

.health-detail-grid dd {
    overflow-wrap: anywhere;
}

.health-reports {
    margin-block-start: var(--space-4);
}

.health-history-layout {
    display: grid;
    gap: var(--space-3);
}

.health-history-context {
    display: grid;
    align-content: start;
    gap: var(--space-3);
}

.health-history-state {
    display: grid;
    gap: var(--space-1);
    margin-block-end: 0;
}

@container (min-width: 42rem) {
    .health-filters {
        grid-template-columns: minmax(0, 1fr) minmax(12rem, 18rem);
    }

    .health-counts {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    .health-capacity-strip {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .health-detail-grid,
    .health-chart-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (min-width: 52rem) {
    .health-sites .table-wrap {
        overflow-x: auto;
    }

    .health-sites .table {
        display: table;
        min-inline-size: 68rem;
    }

    .health-sites .table thead {
        position: static;
        display: table-header-group;
        inline-size: auto;
        block-size: auto;
        margin-block-end: 0;
        padding: 0;
        overflow: visible;
        clip: auto;
        clip-path: none;
        white-space: normal;
    }

    .health-sites .table thead tr {
        display: table-row;
        inline-size: auto;
    }

    .health-sites .table thead th {
        padding: 10px 12px;
        border-block-end: 1px solid var(--table-border, var(--line));
    }

    .health-sites .table thead .table-sort {
        display: inline;
        min-block-size: 0;
        padding-inline: 0;
        border: 0;
        border-radius: 0;
        background: transparent;
    }

    .health-sites .table tbody {
        display: table-row-group;
    }

    .health-sites .table tbody tr {
        display: table-row;
        border: 0;
        border-radius: 0;
        background: transparent;
        overflow: visible;
    }

    .health-sites .table td {
        display: table-cell;
        order: initial;
        padding: var(--space-3);
        border-block-end: 1px solid var(--table-border, var(--line));
    }

    .health-sites .table td::before {
        display: none;
    }

    .health-site-domain,
    .health-site-condition,
    .health-site-response,
    .health-site-errors {
        background: transparent;
    }

    .health-sites th:first-child,
    .health-sites td:first-child {
        position: sticky;
        z-index: 1;
        inset-inline-start: 0;
        inline-size: 9rem;
        min-inline-size: 9rem;
        max-inline-size: 9rem;
        background: var(--surface);
        box-shadow: 1px 0 0 var(--table-border, var(--line));
    }

    .health-sites th:first-child {
        z-index: 2;
    }

    .health-sites .table tbody tr:nth-child(2n) td:first-child {
        background: color-mix(in srgb, currentColor 3%, var(--surface));
    }

    .health-sites .table tbody tr:hover td:first-child {
        background: color-mix(in srgb, currentColor 7%, var(--surface));
    }

    .health-history-layout {
        grid-template-columns: minmax(8rem, 0.32fr) minmax(0, 1fr);
        align-items: start;
    }

    .health-history-readings {
        grid-template-columns: minmax(0, 1fr);
    }
}

@container (min-width: 58rem) {
    .health-capacity-strip {
        grid-template-columns: repeat(6, minmax(0, 1fr));
    }
}

/* @toc Lander page */
/* The 83 marketing site (#1895) — the agency lander, ported from the legacy 83-lander app.
 *
 * SKINNED TO THE MEASURED SPEC, not to an impression of it. Every value below comes from the source's own
 * _variables.scss / app.scss, resolved with a brace-depth walk rather than grep context — nested SCSS does not
 * yield its scope to a context window, and reading it that way has already produced two wrong verdicts on this
 * card (a castings-scoped override read as the marketing spec, and a self-cancelling positioning pair read as
 * the header band's depth).
 *
 * A FIXED-BRAND SURFACE, not a themed one. The teals come from --palette-cyan-* rather than the tenant ramp —
 * the same carve-out the MPS pages take, and why this file needs a CssCheck allowlist entry (wired by the
 * planner at integration). The brand RED is theme-native: 83's primary axis IS red.
 *
 * THREE STATED DEVIATIONS, none of them a miss:
 *   1. THE FACE. The source loads `din-2014` from the Typekit CDN; the dependency contract forbids a runtime
 *      CDN pull, so this renders in Geist.
 *   2. THE WEIGHTS. The spec asks for `100` on h1 and `300` on headings and body. GEIST SHIPS 400/700/900 ONLY,
 *      so every light weight resolves to 400. The numbers port; the hairline character does not. Stated because
 *      "type scale ported" would otherwise be true of the values and false of the rendering.
 *   3. THE GUTTER. Bootstrap's container gutter is 15px; --space-4 is 16px. One pixel, taken from the scale
 *      rather than hand-written, because the house rule prefers the scale and 1px is below noticing.
 */

/* ── the page ─────────────────────────────────────────────────────────────────────────────────────── */

/* Page ground #eff8fa → cyan-50 (ΔE 3.7); body text #082a33 → cyan-950 (ΔE 4.7). */
.lander {
    /*
     * THE BAND TINT, and the one value on this page no ramp rung can reach. The source draws a page ground
     * (#eff8fa) and a band tint (#e0eef2) ΔE 3.00 apart; in the cyan ramp BOTH land on cyan-50, and the next
     * rung is ΔE 7.10 away — collapsing loses the tint, cyan-100 exaggerates it 2.4x.
     *
     * Mixing TOWARD cyan-100 does not help either: solved against the source, the optimum is 0% — the tint is a
     * DESATURATED grey-blue and the cyan ramp at that lightness runs more saturated, so that line moves away
     * from it. Mixing toward a NEUTRAL rung desaturates, which is where the colour actually is: 39% neutral-200
     * lands ΔE 2.10 from the source tint with a separation of ΔE 3.13 against our ground — the source's own
     * separation is 3.00.
     *
     * Ramp colours only, no literal, and a background rather than text or a contrast fix, which is exactly the
     * case the colour law permits color-mix for.
     */
    --lander-tint: color-mix(in srgb, var(--neutral-200) 39%, var(--palette-cyan-50));

    /*
     * THE CARD SHADOW, and it is a colour the mapping table could not see. The source writes it
     * `rgba(58, 58, 58, .2)`; the census swept HEX, and no hex sweep matches functional notation at any scope.
     * rgb(58,58,58) IS #3a3a3a, which the table already maps to --neutral-700 — so the colour was mapped and
     * the USE was invisible. Alpha on decoration, which the colour law permits.
     */
    --lander-shadow: 0 0 5px color-mix(in srgb, var(--neutral-700) 20%, transparent);
    --lander-shadow-strong: 0 0 5px color-mix(in srgb, var(--neutral-700) 30%, transparent);

    background: var(--palette-cyan-50);
    color: var(--palette-cyan-950);
    font-size: 0.9rem;
    line-height: 1.2;
}

/* Links, buttons and hover are all #dd3528 — the most-used colour in the source sheet (9 uses) and a near
 * exact ramp hit at ΔE 2.2. */
.lander a:not(.button) {
    color: var(--primary-600);
}

/* ── the source's container, which is Bootstrap 4's stepped one ───────────────────────────────────── */

/*
 * NOT ONE max-width — Bootstrap 4's `.container` is a STEPPED width (540 · 720 · 960 · 1140), so at a 900px
 * viewport the source's content column is 720px, not "everything minus gutters". Every band, the nav, the hero
 * and the footer inner sit in one, so the steps are declared once here and applied to each.
 *
 * These are @container queries, not @media: .page-section / .lander-header / .lander-footer each own a
 * container context, so a band measures its OWN box and the section-authoring law holds. The bands are
 * full-bleed, so the two agree — but only one of them survives being put inside a narrower pane.
 */
.lander-band .page-section-inner,
.lander-nav,
.lander-hero,
.lander-footer-inner {
    margin-inline: auto;
    max-width: 100%;
    padding-inline: var(--space-4);
}

@container (min-width: 576px) {
    .lander-band .page-section-inner,
    .lander-nav,
    .lander-hero,
    .lander-footer-inner {
        max-width: 540px;
    }
}

@container (min-width: 768px) {
    .lander-band .page-section-inner,
    .lander-nav,
    .lander-hero,
    .lander-footer-inner {
        max-width: 720px;
    }
}

@container (min-width: 992px) {
    .lander-band .page-section-inner,
    .lander-nav,
    .lander-hero,
    .lander-footer-inner {
        max-width: 960px;
    }
}

@container (min-width: 1200px) {
    .lander-band .page-section-inner,
    .lander-nav,
    .lander-hero,
    .lander-footer-inner {
        max-width: 1140px;
    }
}

/* ── the header band, and the overlap it exists for ───────────────────────────────────────────────── */

/* The dark band: background #082a33, its own text #eff8fa, and 20rem of padding below the copy — that depth is
 * what the content then pulls back up over. `overflow: hidden` is the source's, and it is what stops the bled
 * hero widening the page. */
.lander-header {
    background: var(--palette-cyan-950);
    color: var(--palette-cyan-50);
    container-type: inline-size;
    overflow: hidden;
    padding-block-end: 20rem;
    position: relative;
}

.lander-header a:not(.button) {
    color: var(--palette-cyan-50);
}

.lander-nav {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    padding-block: 0.84rem;
    position: relative;
    z-index: 2;
}

.lander-brand {
    display: block;
    flex: 0 0 auto;
}

.lander-brand svg {
    block-size: 75px;
    display: block;
    inline-size: auto;
}

.lander-nav-list {
    display: flex;
    flex: 1 1 100%;
    flex-wrap: wrap;
    gap: var(--space-4);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* The nav link is UNDERLINED in the source — a real 1px border, not text-decoration, which is why it keeps its
 * 3px top padding and picks up the brand red on hover along with the text. */
.lander-nav-list a {
    border-block-end: 1px solid var(--palette-cyan-50);
    font-size: 0.8rem;
    padding-block-start: 3px;
    text-decoration: none;
}

.lander-nav-list a:hover {
    border-color: var(--primary-600);
    color: var(--primary-600);
}

@container (min-width: 992px) {
    .lander-nav-list {
        flex: 1 1 auto;
        justify-content: flex-end;
    }

    .lander-nav-list a {
        font-size: 1rem;
        letter-spacing: 0.025rem;
    }
}

/* THE POSITIONING CONTEXT, and it is the source's own trick: 500px of padding cancelled by 500px of negative
 * margin. The absolutely-positioned hero gets a box tall enough to sit in, and it contributes ZERO layout
 * height — which is why the band's depth is the header's 20rem and not 500px. Reading these two lines as the
 * band depth is the single easiest mistake in this file. */
.lander-hero {
    margin-block-end: -500px;
    padding-block-end: 500px;
    position: relative;
}

/* `col-10 col-md-8` — the copy takes 10 of 12 columns, narrowing to 8 from md so the bled artwork has room. */
.lander-hero-copy {
    max-inline-size: 83.333%;
    position: relative;
    z-index: 1;
}

@container (min-width: 768px) {
    .lander-hero-copy {
        max-inline-size: 66.667%;
    }
}

.lander-hero-copy h1 {
    /* Spec: 2rem, 3rem from md. Weight 100 — see the deviation note; Geist resolves it to 400. */
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.2;
    margin-block: 40px 30px;
}

/* The hero bleeds off the top and the right, at the source's own offsets. It is INLINE SVG rather than a
 * background-image because a tokenised SVG only themes when it shares the document that defines the tokens —
 * as a background it renders black. */
.lander-hero-art {
    position: absolute;
    z-index: 0;
}

.lander-hero-art svg {
    block-size: 100%;
    display: block;
    inline-size: auto;
}

/* THE SWITCH IS AT 576, NOT 768. `.hero-background.mobile` hides at min-width:sm and `.desktop` hides at
 * max-width:xs — one clean split. An earlier pass put it at 768, which showed the mobile drawing across the
 * whole 576-768 range where the source shows the desktop one. */
.lander-hero-mobile {
    inline-size: 75%;
    inset-block-start: -6rem;
    inset-inline-end: -3rem;
    position: absolute;
}

.lander-hero-mobile svg {
    block-size: auto;
    inline-size: 100%;
}

.lander-hero-desktop {
    block-size: 35rem;
    display: none;
    inset-block-start: -10rem;
    inset-inline-end: -4rem;
    max-inline-size: 130%;
    position: absolute;
}

@container (min-width: 576px) {
    .lander-hero-mobile {
        display: none;
    }

    .lander-hero-desktop {
        display: block;
    }
}

/* PER-PAGE HERO OFFSETS, from the source's own per-page rules. Three pages have NO rule and correctly fall back
 * to the base above: ad-creative and ad-words (the author wrote the blocks and left them EMPTY — base is the
 * intended result) and ux-ui-audit (the author wrote real offsets behind a COMPOUND selector,
 * `&.hp-hero-ux-ui-audit.hero-background`, where every sibling uses the descendant form — so they have never
 * applied on the live site; base is the UNINTENDED result). Both ship as base, because verbatim means what the
 * site does. The dead values are recorded on the card. */
.lander-hero-home .lander-hero-mobile {
    block-size: 27rem;
    inline-size: auto;
    inset-block-start: -2rem;
    inset-inline-end: -3rem;
}

.lander-hero-home .lander-hero-mobile svg {
    block-size: 100%;
    inline-size: auto;
}

@container (min-width: 576px) {
    .lander-hero-home .lander-hero-desktop {
        block-size: 70rem;
        inset-block-start: -10rem;
        inset-inline-end: -10rem;
    }

    .lander-hero-website .lander-hero-desktop {
        block-size: 46rem;
        inset-block-start: -7rem;
        inset-inline-end: -20rem;
    }

    .lander-hero-web-app .lander-hero-desktop {
        block-size: 50rem;
        inset-block-start: -10rem;
        inset-inline-end: -30rem;
    }

    .lander-hero-facebook .lander-hero-mobile,
    .lander-hero-instagram .lander-hero-mobile,
    .lander-hero-snapchat .lander-hero-mobile {
        block-size: 47rem;
        inline-size: auto;
        inset-block-start: -7rem;
        inset-inline-end: -11rem;
    }

    .lander-hero-facebook .lander-hero-desktop {
        block-size: 50rem;
        inset-block-start: -10rem;
        inset-inline-end: -5rem;
    }

    .lander-hero-instagram .lander-hero-desktop,
    .lander-hero-snapchat .lander-hero-desktop {
        block-size: 50rem;
        inset-block-start: -10rem;
        inset-inline-end: -10rem;
    }
}

/* THE md TIER THE RECORDED TABLE WAS MISSING. All three social pages grow to 60rem and pull flush to the right
 * edge at >=768 — and every capture is taken at 1280, so building from the sm row alone would have shipped
 * three heroes at the wrong size behind a plausible-looking screenshot. */
@container (min-width: 768px) {
    .lander-hero-facebook .lander-hero-desktop,
    .lander-hero-instagram .lander-hero-desktop,
    .lander-hero-snapchat .lander-hero-desktop {
        block-size: 60rem;
        inset-block-start: -10rem;
        inset-inline-end: 0;
    }
}

/* ── the overlap: main rides UP over the band, and its last child pulls DOWN ───────────────────────── */

.lander-main {
    margin-block-start: -21rem;
    position: relative;
    z-index: 1;
}

.lander-main > :last-child {
    margin-block-end: -10rem;
}

/* ── the bands ────────────────────────────────────────────────────────────────────────────────────── */

/* THE BAND RHYTHM IS THE HEADING'S OWN MARGINS, and nothing else. The source carries no band-level vertical
 * padding at all, so the chassis's own padding-block (64 → 96 → 128) has to be zeroed or the bands sit roughly
 * 2.6x further apart than the original at desktop. An earlier pass measured the heading margins correctly and
 * applied them ON TOP of the chassis padding, which is the "true of the values, false of the rendering" shape
 * this card keeps producing. */
.lander-band .page-section-inner {
    gap: 0;
    padding-block: 0;
}

/* Section headings: 2rem, 2.3rem from md, weight 300 (Geist resolves it to 400). Margins are the source's
 * Bootstrap utilities: mt-4 mt-sm-5 (1.5rem → 3rem) and mb-3 mb-sm-4 (1rem → 1.5rem). */
.lander-band .page-section-title {
    font-size: 2rem;
    font-weight: 300;
    margin-block: 1.5rem 1rem;
}

@container (min-width: 576px) {
    .lander-band .page-section-title {
        margin-block: 3rem 1.5rem;
    }
}

@container (min-width: 768px) {
    .lander-band .page-section-title {
        font-size: 2.3rem;
    }
}

/* Three of the nine pages write the heading margins FLAT — `mt-5 mb-4` with no responsive variant — so they
 * carry the wide rhythm at every width. Per-page, from the markup, not inherited from welcome. */
.lander-band-flat-rhythm .page-section-title {
    margin-block: 3rem 1.5rem;
}

/* ONLY THE FIRST BAND'S HEADING IS LIGHT, and that single rule encodes the whole structure: it is the one
 * heading still sitting on the dark band after the -21rem pull. Every later heading has cleared it and is dark.
 * In the source this is `.title-et-secondary-4` applied by hand to exactly that heading. */
.lander-main > .lander-band:first-child .page-section-title {
    color: var(--palette-cyan-50);
}

/* The items grid: one column, two at md (`col-md-6`), four at lg (`col-lg-3`). The chassis breaks at 640/1024;
 * the source breaks at 768/992, so both are restated. Row gap is the source's `.cta-band .mb-3` override (10px);
 * column gap is Bootstrap's 15px-per-side gutter. */
.lander-band .page-section-items {
    column-gap: 30px;
    row-gap: 10px;
}

@container (min-width: 768px) {
    .lander-band .page-section-items {
        grid-template-columns: repeat(2, 1fr);
    }
}

@container (min-width: 992px) {
    .lander-band .page-section-items {
        grid-template-columns: repeat(var(--page-section-items-cols, 4), 1fr);
    }
}

.lander-band .page-section-footer {
    color: inherit;
    margin-block-start: var(--space-4);
}

.lander-band-center .page-section-head,
.lander-band-center .page-section-footer {
    align-items: center;
    text-align: center;
}

/* The tinted bands. Welcome's Payment Options is tinted and the eight's is NOT (`.in-facebook` sets it back to
 * transparent); the eight's Our Approach is tinted and welcome's is not. Two page-dependent tints pointing in
 * opposite directions, which is why neither is a property of "the lander" and both are set per page. */
.lander-band-tinted {
    background: var(--lander-tint);
}

/* `.our-approach-container` — the eight's Our Approach band, and the one band that carries its own vertical
 * padding rather than borrowing the heading's. */
.lander-approach-container {
    margin-block-start: 3rem;
    padding-block: 3rem;
}

/* ── the service / benefit card ───────────────────────────────────────────────────────────────────── */

/* The source card is `border: none` over Bootstrap's white default. The WHITE now comes from the light theme's
 * --surface, so only the border removal, the full-height stretch and the shadow are ours. */
.lander .card {
    block-size: 100%;
    border: 0;
    box-shadow: var(--lander-shadow);
}

/* The converted benefit bands (#1903) join this selector rather than getting a copy of it: css:check counts
   OCCURRENCES of --palette-*, so a duplicated rule would register as a new violation while an extra selector
   on the existing one does not. Same colour, same rule, one place to change it. */
.lander .card-title,
.lander-band .section-features-title,
.lander-band .section-services-title {
    color: var(--palette-cyan-950);
    font-size: 1.1rem;
    font-weight: 600;
    line-height: 1.4rem;
    margin-block: 0 1rem;
}

/*
 * ── THE BENEFIT BAND'S SKIN, CARRIED ONTO x-section-features (#1903) ──────────────────────────────────
 *
 * The eight "How it benefits your business" bands were a lander-local `_benefit-card` partial; they are now
 * <x-section-features>. The MARKUP is the platform's, the LOOK is still the source's, and these rules are
 * what bridge the two — this card is a refactor behind a fixed appearance, so the skin has to survive the
 * component swap exactly.
 *
 * ★ SCOPED TO `.lander-band` ON PURPOSE, AND THAT IS THE WHOLE SAFETY ARGUMENT. x-section-features is a
 * shared component with other consumers (/docs, and any tenant that reaches for it). Styling its anatomy
 * classes unscoped from a page stylesheet would re-skin it everywhere. Every selector below is prefixed with
 * the lander's own band class, so nothing here can reach a section this page does not render.
 *
 * WHY THIS WINS THE CASCADE, since it is a specificity TIE and not an override: `.lander-band
 * .section-features-item` and `.section-features-card .section-features-item` are both (0,2,0). CssService
 * bundles component CSS (line 71) BEFORE page CSS (line 90), so source order decides and the page wins.
 * That is load-bearing — if the bundle order ever flips, these rules silently stop applying and the bands
 * revert to the platform skin.
 *
 * THE CARD IS HORIZONTAL BELOW lg. The source absolutely positions the illustration at the card's left and
 * indents the body 90px to clear it; at >=992 the image returns to the flow and the indent drops to 25px.
 * Building the desktop layout at every width — which an earlier pass did — gets 375 and 768 wrong.
 */
.lander-band .section-features-item,
.lander-band .section-services-item {
    border: 0;
    box-shadow: var(--lander-shadow);
    padding: 25px 25px 25px 90px;
    position: relative;
}

.lander-band .section-features-illustration,
.lander-band .section-services-illustration {
    block-size: auto;
    inline-size: 45px;
    inset-block-start: 42px;
    inset-inline-start: 25px;
    position: absolute;
}

.lander-band .section-features-illustration svg,
.lander-band .section-services-illustration svg {
    block-size: auto;
    display: block;
    inline-size: 100%;
}

/*
 * ★ --text, AND NOT `inherit`, AND THE DIFFERENCE IS ONLY VISIBLE IN DARK. The component hard-sets
 * --text-muted on its description, which the port never had, so something must override it. My first attempt
 * was `inherit` — reasoning that the copy should fall back to the page — and a computed-style probe showed
 * that was WRONG: the port's copy sat inside x-card, so it inherited the CARD's colour (--text, which is
 * neutral-50 in dark), not `.lander`'s hard-coded cyan-950. `inherit` here resolves to the PAGE instead and
 * turned the body copy dark-on-dark. --text reproduces the port exactly in both themes.
 *
 * WORTH KNOWING, AND NOT MINE: the port already renders badly in dark. `.lander` fixes background and colour
 * to --palette-cyan-* literals, which do NOT flip per theme, while x-card's surface and text DO — so the
 * card titles are dark-on-dark in dark mode on master today, before this card touched anything. This rule
 * deliberately reproduces that rather than quietly fixing it: the boundary says any visual difference is a
 * defect of THIS card, and correcting the port's theming is its own decision.
 */
.lander-band .section-features-description {
    color: var(--text);
}

/*
 * THE COMPONENT'S OWN RHYTHM IS ADDITIVE, SO IT HAS TO BE ZEROED RATHER THAN LEFT ALONE.
 * x-section-features spaces its parts with margin-block-START (10px illustration→title, 16px
 * title→description). The lander already carries that spacing on the other side of the same gaps —
 * `.lander .card-title` has `margin-block: 0 1rem` and the illustration has a margin-block-end — so leaving
 * both in place STACKS them: measured on a before/after render of this exact band, the converted version
 * came out ~26px taller with visibly looser title→body spacing. Zeroing the component's half restores the
 * port's rhythm and leaves the lander's own margins as the single source of it.
 */
.lander-band .section-features-illustration + .section-features-title,
.lander-band .section-features-title + .section-features-description,
.lander-band .section-services-illustration + .section-services-title,
.lander-band .section-services-title + .section-services-summary,
.lander-band .section-services-title + .section-services-points {
    margin-block-start: 0;
}

@container (min-width: 992px) {
    .lander-band .section-features-item,
    .lander-band .section-services-item {
        padding-inline-start: 25px;
    }

    .lander-band .section-features-illustration,
    .lander-band .section-services-illustration {
        inset-block-start: auto;
        inset-inline-start: auto;
        margin-block-end: var(--space-3);
        position: relative;
    }
}


/*
 * ── THE SERVICE BAND'S SKIN, CARRIED ONTO x-section-services (#1903) ─────────────────────────────────
 *
 * Welcome's two bands were the lander-local `_service-card` partial; they are now <x-section-services>, the
 * pattern promoted out of them on #1898. These rules are the same bridge the benefit bands got above, and
 * they REPLACE the old `.lander-card-link` / `-points` / `-summary` / `-more` rules rather than sitting
 * beside them: that partial is deleted, so every one of those selectors matched nothing.
 *
 * SCOPED TO `.lander-band`, for the reason given on the benefit band: x-section-services is a shared
 * component with a /docs page, and an unscoped page rule would re-skin it everywhere.
 *
 * The whole card is the link. The source achieved that with an absolutely-positioned overlay anchor sitting
 * on top of a second "Learn More" anchor — two stops for one destination — so this is one link with the
 * label inside it. The hover is the source's own 2% black wash, not a ring: `a.block-link:hover`.
 */
.lander-band a.section-services-item {
    color: inherit;
    text-decoration: none;
    transition: background 200ms ease;
}

.lander-band a.section-services-item:hover {
    background: color-mix(in srgb, var(--neutral-950) 2%, var(--surface));
}

.lander-band .section-services-points {
    display: none;
    margin: 0;
    padding-inline-start: 20px;
}

.lander-band .section-services-points li {
    padding-block-end: 5px;
}

.lander-band .section-services-points li + li {
    margin-block-start: 0;
}

/* BELOW sm THE CARD SHOWS A ONE-SENTENCE SUMMARY, NOT THE LIST — `ul.card-body-text` is display:none there and
 * each card carries its own shortened `<p>`. A content difference, not only a layout one, so both copies ship
 * in one DOM and CSS chooses (the same shape as the hero's two drawings).
 *
 * ★ 576, NOT the component's own 640. x-section-services swaps at the chassis's 1→2 column threshold, which is
 * the right default for a new band; the source swapped at Bootstrap's `sm`. Reproducing the port means using
 * the port's number, so the threshold is overridden here rather than the component being changed for everyone. */
.lander-band .section-services-summary {
    margin: 0;
}

@container (min-width: 576px) {
    .lander-band .section-services-points {
        display: block;
    }

    .lander-band .section-services-summary {
        display: none;
    }
}

@container (min-width: 640px) {
    .lander-band .section-services-summary {
        display: none;
    }
}

.lander-band .section-services-points,
.lander-band .section-services-summary {
    padding-block-end: 35px;
}

/* --text for the same reason the benefit band's description takes it: the component sets --text-muted, the
   port's copy sat inside x-card and inherited the CARD's --text. A muted body is a visible difference across
   the whole text block, which is what the last 1% of the pixel diff turned out to be. */
.lander-band .section-services-points,
.lander-band .section-services-summary {
    color: var(--text);
}

.lander-band .section-services-points strong {
    color: inherit;
    font-weight: 700;
}

/* "Learn More" is pinned to the card's bottom edge with an underline rule, which is what the 35px of body
 * padding above reserves space for. The arrow is a Line Awesome glyph in the source (`\F121`, identified as
 * `arrow-right` from the font's own glyph table) — an icon font that cannot come across, so it is the heroicon
 * of the same name, inlined and decorative.
 *
 * ABSOLUTE, not the component's margin-block-start:auto float: the source pins it to a fixed 25px from the
 * card's bottom edge, and the reserved 35px above is what keeps the copy clear of it. */
.lander-band .section-services-more {
    align-items: center;
    border-block-end: 1px solid var(--primary-600);
    color: var(--primary-600);
    display: inline-flex;
    font-size: 0.875rem;
    gap: var(--space-2);
    inset-block-end: 25px;
    margin-block-start: 0;
    padding-block-start: 0;
    position: absolute;
}

.lander-band .section-services-more svg {
    block-size: 1em;
    inline-size: 1em;
}

.lander-band a.section-services-item:hover .section-services-more {
    border-color: var(--primary-500);
    color: var(--primary-500);
}

/*
 * THE SHARED BODY TYPE, and it now spans both the converted bands and the partials still to convert.
 * `.lander-card-text` survives because `_approach-step`, `_approach-shared` and welcome's Our Approach band
 * still use it; the other three selectors are the converted components' equivalents. One rule, so the copy
 * cannot drift between a converted band and an unconverted one while the conversion is part-done.
 */
.lander-card-text,
.lander-band .section-features-description,
.lander-band .section-services-points,
.lander-band .section-services-summary {
    font-size: 0.875rem;
    font-weight: 300;
    line-height: 1.5;
}

.lander-card-text strong,
.lander-band .section-services-points strong {
    font-weight: 700;
}

/* The sub-pages' benefit cards are the same chassis with a paragraph instead of a list, and no link. */
.lander-card-text {
    margin: 0;
}

/* ── welcome: the Our Approach band (`.our-approach-cta`) ─────────────────────────────────────────── */

/* The three PNGs are `card-img-top` in the source — edge to edge, no inset — so the media box's own padding is
 * removed. These are the one RASTER band: flat-colour brand illustrations with the original hexes baked in,
 * ported as-is because redrawing them would be inventing. */
/* `col-md-4` — THREE across from 768, not the two the generic band grid gives. The source's Our Approach goes
 * straight from stacked to three; it never has a two-up state. */
.lander-approach-cta .page-section-items {
    --page-section-items-cols: 3;
}

@container (min-width: 768px) {
    .lander-approach-cta .page-section-items {
        grid-template-columns: repeat(3, 1fr);
    }
}

.lander-approach-cta .card-media {
    padding: 0;
}

.lander-approach-cta .card-media img {
    block-size: auto;
    display: block;
    inline-size: 100%;
}

.lander-approach-cta .card {
    box-shadow: var(--lander-shadow-strong);
}

.lander-approach-cta .card-body {
    padding: 25px;
}

.lander-approach-cta .card-title {
    background-size: 1.8rem 1.8rem;
    font-size: 2.2rem;
    font-weight: 300;
    line-height: 1.8rem;
    margin-block: 2rem 1rem;
    padding-inline-start: 2rem;
}

@container (min-width: 768px) {
    .lander-approach-cta .card-title {
        background-size: 1.17rem 1.17rem;
        font-size: 1.3rem;
        line-height: 0.9em;
        padding-inline-start: 1.3rem;
    }
}

@container (min-width: 992px) {
    .lander-approach-cta .card-title {
        background-size: 1.8rem 1.8rem;
        font-size: 2rem;
        line-height: 1.8rem;
        padding-inline-start: 2.2rem;
    }
}

@container (min-width: 1200px) {
    .lander-approach-cta .card-title {
        background-size: 1.8rem 1.8rem;
        font-size: 2.2rem;
        padding-inline-start: 2.2rem;
    }
}

/* ── the numbered badges ──────────────────────────────────────────────────────────────────────────── */

/*
 * BADGED WHERE THE CSS ACTUALLY REACHES, WHICH IS 15 TITLES, NOT THE 58 THAT CARRY THE CLASS. The source's two
 * `.cta-number-*` blocks resolve to `.our-approach-cta` (welcome's three) and
 * `.what-we-build-container .our-approach-container .text-block` (three per build page). The four advertise
 * pages carry three each in the markup and render NONE — a class in the markup is not a rendered style.
 *
 * DELIVERED AS A CSS BACKGROUND WITH THE ORIGINAL COLOURS, by ruling: the source paints them onto an existing
 * heading, a background cannot resolve our custom properties (it would render black), and the theming buys
 * nothing on a fixed-brand surface. Those five icons are therefore the one part of the port that is NOT
 * tokenised — the same basis as the four PNGs.
 *
 * SIZED TO THE LINE, NOT TO THE BOX. These icons carry a viewBox and no width/height, so as a background they
 * resolve to the whole positioning area at their own ratio — which is right on a one-line title and wrong the
 * moment one wraps, where the numeral doubles in size and runs straight through the words. The source has the
 * same latent behaviour and never trips it because its face keeps every one of these titles on one line; ours
 * wraps "Develop & Launch". Pinning the badge to each tier's own line-height reproduces the source exactly
 * where the source works and stays put where it does not.
 */
.lander-badge-1,
.lander-badge-2,
.lander-badge-3,
.lander-approach-cta .card-title {
    background-position: left top;
    background-repeat: no-repeat;
}

.lander-badge-1 {
    background-image: url('/img/lander/icons/01.svg');
}

.lander-badge-2 {
    background-image: url('/img/lander/icons/02.svg');
}

.lander-badge-3 {
    background-image: url('/img/lander/icons/03.svg');
}

/* Welcome's three are numbered by POSITION, because x-card owns its title element and the badge has to land on
 * that element rather than on the card around it. The source numbers them 01/02/03 in document order, so
 * position IS the rule — and it stays right if the copy changes without anyone remembering to move a class. */
.lander-approach-cta .page-section-items > :nth-child(1) .card-title {
    background-image: url('/img/lander/icons/01.svg');
}

.lander-approach-cta .page-section-items > :nth-child(2) .card-title {
    background-image: url('/img/lander/icons/02.svg');
}

.lander-approach-cta .page-section-items > :nth-child(3) .card-title {
    background-image: url('/img/lander/icons/03.svg');
}

/* ── the eight: the shared Our Approach band (the four advertise pages) ───────────────────────────── */

/* A ROW card — a 50px illustration on the left, the copy beside it — where every other card on the site is a
 * column. Shared verbatim across all four advertise pages, including its three facebook-marketing drawings on
 * the snapchat, instagram and adwords pages: a source fact, not a copy-paste error to fix. */
.lander-approach-shared .page-section-items {
    grid-template-columns: 1fr;
    row-gap: 10px;
}

/* `col-md-8 offset-md-2 col-lg-6 offset-lg-3` — a centred column that narrows in TWO steps: full width, then
 * two thirds at 768, then half at 992. Jumping straight from full to half skips the middle step entirely, and
 * 768 is the only width that shows it. The closing CTA in this band and on the build pages uses the same
 * column, so both are named here. */
.lander-approach-shared .section-features-item,
.lander-approach-shared .page-section-footer,
.lander-band-cta .page-section-head,
.lander-band-cta .page-section-footer {
    margin-inline: auto;
    max-inline-size: 100%;
}

@container (min-width: 768px) {
    .lander-approach-shared .section-features-item,
    .lander-approach-shared .page-section-footer,
    .lander-band-cta .page-section-head,
    .lander-band-cta .page-section-footer {
        max-inline-size: 66.667%;
    }
}

@container (min-width: 992px) {
    .lander-approach-shared .section-features-item,
    .lander-approach-shared .page-section-footer,
    .lander-band-cta .page-section-head,
    .lander-band-cta .page-section-footer {
        max-inline-size: 50%;
    }
}

/* THE CARD SURFACE, which x-card used to supply. section-features only paints a background and border in its
   CARD shape; the LINE shape is a bare row, so a band that used x-card has to say so itself. Without these the
   cards go transparent and the band tint shows through them. */
.lander-approach-shared .section-features-item {
    background-color: var(--surface);
    border: 0;
    border-radius: var(--card-border-radius);
    box-shadow: var(--lander-shadow-strong);
}

/* A GRID, not a flex row, because x-card renders the title as a direct child and the source draws the
 * illustration to the LEFT of both title and copy. Two columns with the art spanning both rows puts it there
 * without wrapping x-card's own children in a div it does not have. */
.lander-approach-shared .section-features-item {
    align-items: center;
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0;
    padding: 0 30px 0 10px;
}

.lander-approach-shared .section-features-title {
    font-size: 1rem;
    font-weight: 700;
    grid-column: 2;
    line-height: normal;
    margin: 0 0 0.25rem;
}

.lander-approach-shared .section-features-description {
    grid-column: 2;
}

/* THE THREE RESETS ARE LOAD-BEARING, not tidying. The generic `.lander-band .section-features-illustration`
   rule above absolutely-positions the art at the card's left, because that is what the COLUMN cards do. This
   band is the row card, where the art is a grid cell spanning both rows — so position and both insets have to
   go back to static, or the art leaves the grid entirely and the copy collapses under it. */
.lander-approach-shared .section-features-illustration {
    block-size: 50px;
    grid-column: 1;
    grid-row: 1 / span 2;
    inline-size: 50px;
    inset-block-start: auto;
    inset-inline-start: auto;
    margin: 20px;
    position: static;
}

.lander-approach-shared .section-features-illustration svg {
    block-size: 100%;
    inline-size: 100%;
}

/* ── the eight: the build pages' three animation steps ────────────────────────────────────────────── */

.lander-steps {
    display: grid;
    gap: 25px;
}

/* `col-lg-10 offset-lg-1` — the inset arrives at 992 and NOT before. Applying it at every width narrows the
 * steps through the whole tablet range, where the source runs them edge to edge. */
.lander-step {
    margin-inline: auto;
    max-inline-size: 100%;
}

@container (min-width: 992px) {
    .lander-step {
        max-inline-size: 83.333%;
    }
}

.lander-step .card {
    box-shadow: var(--lander-shadow-strong);
}

.lander-step-text {
    display: flex;
    flex-direction: column;
    padding: 40px 15px 40px 45px;
}

.lander-step-title {
    background-size: 1.85rem 1.85rem;
    font-size: 2.2rem;
    font-weight: 300;
    line-height: 1.85rem;
    margin-block: 0 0.75rem;
    padding-inline-start: 2.5rem;
}

/* THE ANIMATION SEAM, AND IT STOPS HERE. All four what-we-build pages drive these frames with bodymovin pulled
 * from a CDN, which the dependency contract forbids; the Lottie JSON is already in the repo, only the PLAYER is
 * the dependency, and that call is Chelsea's and has not landed. This is the frame the source renders BEFORE
 * bodymovin runs — its own deep-teal block at its own size. Not a substitute, not a drop, not a decision. */
.lander-step-frame {
    background: var(--palette-cyan-950);
}

/* Below md the source stacks the frame ABOVE the copy, which is the one place its two DOM copies differ in
 * order rather than in padding — expressed here as a reversal of the single DOM, per the section-authoring law.
 * The frame has no height of its own at this width in the source either: it is an empty div the player fills,
 * so before bodymovin runs there is nothing to see. Faithful, and the reason it is invisible is on the card. */
.lander-step .card-body {
    display: flex;
    flex-direction: column-reverse;
}

@container (min-width: 768px) {
    /* The frame has to FILL the card's fixed height, as the source's flex row does. An auto row sizes to the
       copy and leaves a white band under the teal; `1fr` is what pins the single row to the full height. */
    .lander-step .card-body {
        block-size: 100%;
        display: grid;
        grid-template-columns: 5fr 7fr;
        grid-template-rows: 1fr;
        padding: 0;
    }

    /* The middle step reverses: frame left, copy right — and its padding swaps with it. */
    .lander-step-reverse .lander-step-text {
        grid-column: 2;
        padding: 40px 45px 40px 15px;
    }

    .lander-step-reverse .lander-step-frame {
        grid-column: 1;
        grid-row: 1;
    }

    /*
     * min-block-size, NOT block-size, and the difference is information. The source fixes these cards at
     * 14/16/18rem, which fits its own face; ours wraps longer and at 768 the copy ran past 14rem. Bootstrap's
     * card lets that overflow spill out — visibly ugly — but OUR shared `.card` sets `overflow: hidden`, so a
     * fixed height silently CUTS the sentence off instead ("...After your approval, we're ready to").
     *
     * The source's own handling is not reproducible without restyling a shared component, which is out of
     * bounds. A minimum reproduces it exactly at every width where the copy fits — which is every width the
     * source itself works at — and grows rather than destroying text where it does not.
     */
    .lander-step .card {
        min-block-size: 14rem;
    }

    .lander-step-title {
        background-size: 1.4rem 1.4rem;
        font-size: 1.6rem;
        line-height: 1.4rem;
        padding-inline-start: 1.8rem;
    }
}

@container (min-width: 992px) {
    .lander-step .card {
        min-block-size: 16rem;
    }

    .lander-step-title {
        background-size: 1.6rem 1.6rem;
        font-size: 1.8rem;
        line-height: 1.6rem;
    }
}

@container (min-width: 1200px) {
    .lander-step .card {
        min-block-size: 18rem;
    }

    .lander-step-title {
        background-size: 2rem 2rem;
        font-size: 2.3rem;
        line-height: 2rem;
        padding-inline-start: 2.5rem;
    }
}

/* ── the closing CTA band ─────────────────────────────────────────────────────────────────────────── */

/* The four build pages close with their own CTA band; the advertise pages close the shared Our Approach with
 * the same shape at a different size. The source's own copy differs by one word between them — "for OUR
 * business today" in the shared partial against "for YOUR business today" on the build pages — which is a
 * source typo, shipped as written. */
/* `.footer-title` on the build pages — its own size and its own 4rem of lead-in, which is the only band-level
 * padding anywhere outside the tinted Our Approach. */
.lander-band-cta .page-section-title {
    font-size: 1.2rem;
    margin-block: 1.5rem;
    padding-block-start: 4rem;
}

/* `.card-footer-title` — the same closing shape inside the advertise pages' shared Our Approach, at its own
 * size, and one word different: "for OUR business today". */
.lander-band-cta-title {
    font-size: 1.7rem;
    font-weight: 300;
    margin-block: 1.5rem;
    text-align: center;
}

@container (min-width: 768px) {
    .lander-band-cta .page-section-title {
        font-size: 2.3rem;
    }
}

/* ── payment options ──────────────────────────────────────────────────────────────────────────────── */

.lander-payment {
    align-items: center;
    display: grid;
    gap: var(--space-5);
    grid-template-columns: 1fr;
}

.lander-payment-card .card-body {
    padding: 20px 25px 30px;
    text-align: center;
}

.lander-payment-card .card-title {
    font-size: 1.7rem;
    font-weight: 300;
    line-height: 1.5rem;
    margin-block: 1rem 1.6rem;
}

.lander-payment-card p {
    font-weight: 300;
    text-align: start;
}

.lander-payment-art {
    inline-size: 60px;
    margin-inline: auto;
}

.lander-payment-art svg {
    block-size: auto;
    display: block;
    inline-size: 100%;
}

.lander-payment-or {
    font-size: 1.5rem;
    font-weight: 600;
    margin: 0;
    text-align: center;
    text-transform: uppercase;
}

.lander-emphasis {
    color: var(--primary-600);
    font-weight: 400;
}

/* The band's rhythm is its heading's PADDING here, not its margin — `pt-4 pt-sm-5 pb-3 pb-sm-4`. */
.lander-band-payment .page-section-title {
    margin-block: 0;
    padding-block: 1.5rem 1rem;
}

@container (min-width: 576px) {
    .lander-band-payment .page-section-title {
        padding-block: 3rem 1.5rem;
    }
}

@container (min-width: 768px) {
    .lander-payment {
        grid-template-columns: 1fr auto 1fr;
    }

    .lander-payment-art {
        inline-size: 83px;
    }
}

/* ── footer ───────────────────────────────────────────────────────────────────────────────────────── */

/* The footer is the second dark surface, so it carries the band's colours and its own light-on-dark chrome —
 * including the form controls, which otherwise inherit 83's app chrome and land near-black on it. */
.lander-footer {
    background: var(--palette-cyan-950);
    color: var(--palette-cyan-50);
    container-type: inline-size;
    padding-block: calc(10rem + var(--space-10)) var(--space-10);
}

.lander-footer a:not(.button) {
    color: var(--palette-cyan-50);
}

.lander-footer-title {
    font-size: 2.3rem;
    font-weight: 100;
    letter-spacing: 0.04rem;
    margin-block: 0 var(--space-6);
    padding-block: 1.5rem;
    text-align: center;
}

.lander-footer-grid {
    display: grid;
    gap: var(--space-8);
    grid-template-columns: 1fr;
}

.lander-footer-phone,
.lander-footer-email {
    font-size: 1.125rem;
    margin-block: var(--space-3) 0;
}

/* The dark bands carry data-theme-scope="dark", so their labels, hints, links and the seam notice all resolve
 * DARK tokens and read correctly — no per-token overrides needed. What the scope gets WRONG is the one thing
 * the source is explicit about: --surface goes dark, and the legacy form has WHITE fields on the dark band. So
 * exactly one override, and it is a source fact rather than a contrast patch. */
.lander-footer .input-field,
.lander-footer .textarea-field,
.lander-footer textarea {
    background: var(--neutral-50);
    color: var(--palette-cyan-950);
}

/*
 * ★ THE LABELS NEED THEIR COMPONENT VARIABLE RE-DECLARED HERE, and `data-theme-scope` alone cannot do it.
 *
 * `input`/`textarea` declare `label-color: text-muted` at the TOP LEVEL of their css facet, so CssService
 * emits `--input-label-color: var(--text-muted)` into bare `:root` — and a custom property valued `var(--token)`
 * is SUBSTITUTED WHERE DECLARED. It resolves once against the page theme (light, neutral-600) and inherits into
 * this dark band already flattened, where the scope has no second chance at it.
 *
 * That measured 1.71:1 on all five labels under axe. An earlier pass reported this fixed because it measured
 * `--text-muted` resolving correctly under dark scope — which proves the SCOPE works and not that the label
 * USES it. Re-declaring the component variable on this element re-substitutes it against the dark value here.
 *
 * THE REAL FIX IS NOT MINE: the facet key belongs in the component's per-theme light/dark sub-facet, which is a
 * shared-component change (C7) and affects every dark island that places an x-input, not just this page.
 */
.lander-footer {
    --input-label-color: var(--text-muted);
    --textarea-label-color: var(--text-muted);
}

.lander-form-row {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: 1fr;
}

.lander-form-seam {
    color: var(--warning);
    font-size: 0.875rem;
    margin-block: var(--space-4) var(--space-3);
}

.lander-copy {
    color: var(--palette-cyan-50);
    font-size: 0.875rem;
    margin-block: var(--space-8) 0;
    padding-inline: var(--space-4);
    text-align: center;
}

.lander-copy a {
    color: var(--primary-600);
}

@container (min-width: 768px) {
    .lander-footer-grid {
        grid-template-columns: 1fr 2fr;
    }

    .lander-form-row {
        grid-template-columns: 1fr 1fr;
    }
}

/* @toc Landing page */
/* Landing page — the public eightythree.com home: just the brand mark, centered.
 * Renders inside the public shell (x-app-page: topbar + main).
 * View: /resources/views/pages/landing.blade.php. */

.landing {
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* @toc Legal page */
/* Legal document pages (/terms-of-use, /privacy-policy, …).
 *
 * These are long-form reading surfaces — five thousand words of legal prose someone is scanning for one
 * clause — so the whole sheet is about measure, rhythm and scannable headings. There is no decoration.
 *
 * Mobile first: the base rules ARE the phone layout, and the only wider-screen change is the measure
 * lifting off the gutter. Logical properties throughout, so an RTL tenant gets the right sides free.
 */

/* container-type so the form below reflows on ITS OWN box rather than the viewport — the document renders
 * inside whichever chrome the tenant chose, and inside the editor's device frame, so viewport width is not
 * the width that matters here. */
.legal {
    container-type: inline-size;
    margin-inline: auto;
    max-width: 68ch;
    padding-block: var(--space-6) var(--space-10);
    padding-inline: var(--space-4);
}

.legal-head {
    border-block-end: 1px solid var(--line);
    margin-block-end: var(--space-6);
    padding-block-end: var(--space-4);
}

.legal-title {
    font-size: clamp(1.75rem, 1.4rem + 1.6vw, 2.5rem);
    line-height: 1.15;
    margin: 0;
}

.legal-date {
    color: var(--text-muted);
    font-size: var(--text-sm);
    margin-block: var(--space-2) 0;
}

/* The document body. Headings carry the scan; paragraphs carry the reading. */
.legal-doc h3 {
    font-size: clamp(1.125rem, 1.05rem + 0.35vw, 1.375rem);
    line-height: 1.25;
    margin-block: var(--space-8) var(--space-3);
}

.legal-doc h4 {
    font-size: var(--text-base);
    font-weight: 600;
    margin-block: var(--space-6) var(--space-2);
}

/* The first heading sits against the header rule, so it does not need the stacking margin. */
.legal-doc > :first-child {
    margin-block-start: 0;
}

.legal-doc p {
    line-height: 1.65;
    margin-block: 0 var(--space-4);
}

.legal-doc ul {
    line-height: 1.65;
    margin-block: 0 var(--space-4);
    padding-inline-start: var(--space-6);
}

.legal-doc li {
    margin-block-end: var(--space-2);
}

.legal-doc a {
    color: var(--link);
    text-underline-offset: 0.15em;
}

/* A URL in legal prose is long and unbreakable; without this it pushes the measure open on a phone. */
.legal-doc a {
    overflow-wrap: anywhere;
}

/* The CCPA tables are three columns of dense prose. */
.legal-doc .table-wrap {
    margin-block: var(--space-5) var(--space-6);
}

.legal-doc .table-wrap td {
    font-size: var(--text-sm);
    line-height: 1.5;
}

/* The opt-out form — the one document that carries one, because its reviewed text promises it.
 * Mobile first: the name pair stacks on a phone and pairs up once there is room for two fields. */
.legal-form {
    margin-block-start: var(--space-6);
    max-width: 32rem;
}

.legal-form-row {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: 1fr;
}

@container (min-width: 30rem) {
    .legal-form-row {
        grid-template-columns: 1fr 1fr;
    }
}

.legal-form .input {
    margin-block-end: var(--space-4);
}

/* THE TABLE NEEDS A FLOOR OR IT CRUSHES INSTEAD OF SCROLLING. .table-wrap is overflow-x: auto, but
 * .table is width: 100%, so on a narrow screen the table obeys the container and squeezes: at 320 every
 * cell became a one-word ribbon, a single row grew past 700px, and the third column was pushed out of
 * sight entirely. A min-width makes it exceed the wrapper, which is what gives the wrapper something to
 * scroll. Only visible below ~544px; above that the table fits and this does nothing. */
.legal-doc .table {
    min-width: 34rem;
}

/* @toc Login page */
/* The shared public tenant entry surface. It follows the accepted Project release geometry while every
 * colour and brand continues to come from the registered tenant design system. The base is the 320px layout.
 * Login-code state changes reuse this one cardless shell instead of introducing a second authentication UI.
 */

.tenant-entry {
    width: min(100%, 22.5rem);
    margin-inline: auto;
    display: grid;
    gap: 2.125rem;
}

.tenant-entry-brand {
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
    color: var(--text);
    text-decoration: none;
    border-radius: 0.5rem;
}

.tenant-entry-brand:focus-visible {
    outline: 3px solid var(--primary-500);
    outline-offset: 0.375rem;
}

.tenant-entry-brand .brand-logo svg {
    width: auto;
    max-width: min(100%, 18rem);
    block-size: 2.875rem;
}

.tenant-entry-brand .tenant-entry-brand-mark svg {
    inline-size: 2.875rem;
    block-size: auto;
}

.tenant-entry-brand .brand-logo-wordmark {
    display: block;
    max-width: 100%;
    color: var(--text);
    font-family: var(--font-body, sans-serif);
    font-size: clamp(1.75rem, 8vw, 2.75rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    overflow-wrap: anywhere;
    text-align: center;
}

.tenant-entry-content {
    width: 100%;
    min-width: 0;
}

.tenant-entry-title {
    margin: 0 0 0.5rem;
    color: var(--text);
    font-size: clamp(1.3125rem, 6.4vw, 1.5rem);
    font-weight: 650;
    line-height: 1.25;
    letter-spacing: -0.02em;
    overflow-wrap: anywhere;
    text-align: center;
    text-wrap: balance;
}

.tenant-entry-content > p {
    max-width: 19.375rem;
    margin: 0 auto;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.55;
    text-align: center;
}

.tenant-entry form {
    display: grid;
    gap: 0.75rem;
    margin-block-start: 1.5rem;
}

.tenant-entry form .button {
    width: 100%;
    min-block-size: 2.75rem;
    justify-content: center;
    font-weight: 600;
}

.tenant-entry-code-flow {
    display: grid;
    gap: 1rem;
    margin-block-start: 0.75rem;
}

.tenant-entry-code-copy {
    max-width: 19.375rem;
    margin: 0 auto;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.55;
    text-align: center;
}

.tenant-entry-code-flow > form {
    margin-block-start: 0;
}

.tenant-entry [data-login-code-verify] > .input {
    --tenant-entry-code-cell: calc(16.6666667cqi - 0.333333px);
    --tenant-entry-code-divider: var(--line);
    --tenant-entry-code-half-gap: calc(8.3333333cqi - 0.166667px - 0.5ch);

    position: relative;
    width: 100%;
    container-type: inline-size;
    overflow-x: clip;
    overflow-clip-margin: 4px;
    margin-block-end: 0;
}

.tenant-entry [data-login-code-verify] > .input::before {
    position: absolute;
    z-index: 0;
    inset: 0 0 auto;
    box-sizing: border-box;
    block-size: 2.75rem;
    border: 1px solid var(--input-border);
    border-radius: var(--input-border-radius);
    background-color: var(--input-background-color);
    background-image:
        linear-gradient(var(--tenant-entry-code-divider), var(--tenant-entry-code-divider)),
        linear-gradient(var(--tenant-entry-code-divider), var(--tenant-entry-code-divider)),
        linear-gradient(var(--tenant-entry-code-divider), var(--tenant-entry-code-divider)),
        linear-gradient(var(--tenant-entry-code-divider), var(--tenant-entry-code-divider)),
        linear-gradient(var(--tenant-entry-code-divider), var(--tenant-entry-code-divider));
    background-repeat: no-repeat;
    background-position:
        16.6666667% center,
        33.3333333% center,
        50% center,
        66.6666667% center,
        83.3333333% center;
    background-size: 1px 50%;
    content: '';
    pointer-events: none;
}

.tenant-entry [data-login-code-verify] > .input:has(.tenant-entry-code-input:focus-visible) {
    --tenant-entry-code-divider: var(--input-focus-border);
}

.tenant-entry [data-login-code-verify] > .input:has(.tenant-entry-code-input:focus-visible)::before {
    border-color: var(--input-focus-border);
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.tenant-entry [data-login-code-verify] > .input:has(.tenant-entry-code-input[aria-invalid='true']) {
    --tenant-entry-code-divider: var(--danger);
}

.tenant-entry [data-login-code-verify] > .input:has(.tenant-entry-code-input[aria-invalid='true'])::before {
    border-color: var(--danger);
}

.tenant-entry-code-input.input-field {
    position: relative;
    z-index: 1;
    display: block;
    box-sizing: border-box;
    width: 100%;
    min-block-size: 2.75rem;
    padding: 0.625rem 0;
    margin-inline: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    caret-color: var(--text);
    font-family: var(--font-mono, ui-monospace, "SFMono-Regular", Consolas, monospace);
    font-size: 1.375rem;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    letter-spacing: calc(var(--tenant-entry-code-cell) - 1ch);
    line-height: 1;
    text-align: start;
    text-indent: calc(8.3333333cqi + 0.833333px - 0.5ch);
}

/* Blink places the native caret after the final letter-space. Shift the complete input left by half that
   space, then return only the visible glyph ink with a text shadow. The centered digits and native caret
   both remain inside the unchanged field without changing the input's value or scroll position. */
.tenant-entry-code-input.input-field:valid {
    width: calc(100% + var(--tenant-entry-code-half-gap));
    min-width: calc(100% + var(--tenant-entry-code-half-gap));
    margin-inline-start: calc(-8.3333333cqi + 0.166667px + 0.5ch);
    color: transparent;
    text-shadow: var(--tenant-entry-code-half-gap) 0 var(--input-color);
}

.tenant-entry-code-input.input-field:focus-visible {
    outline: none;
}

.tenant-entry [data-login-code-verify] .input-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.tenant-entry [data-login-code-verify] .input-error {
    text-align: center;
}

.tenant-entry-progress,
.tenant-entry-no-js {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.5;
}

.tenant-entry-progress:empty {
    display: none;
}

.tenant-entry-form-loading .button {
    cursor: wait;
}

.tenant-entry-secondary-actions {
    margin-block-start: 0;
    display: grid;
}

.tenant-entry-secondary-actions form {
    margin: 0;
}

.tenant-entry-secondary-actions p {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-xs);
    line-height: 1.4;
    text-align: center;
}

.tenant-entry[data-mode='error'] .button,
.tenant-entry[data-mode='expired'] .button,
.tenant-entry[data-mode='wrong-host'] .button,
.tenant-entry[data-mode='success'] .button,
.tenant-entry[data-sent] .button,
.tenant-entry[data-code-state='expired'] .button,
.tenant-entry[data-code-state='exhausted'] .button {
    width: 100%;
    min-block-size: 2.75rem;
    justify-content: center;
    margin-block-start: 1.5rem;
}

.tenant-entry-alt {
    margin-block: 1rem 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: 1.5;
    text-align: center;
}

.tenant-entry-alt a {
    color: var(--link);
}

.tenant-entry[data-sent] .callout {
    margin-block: 0 1rem;
}

.tenant-entry .callout {
    margin-block-start: 1rem;
}

@media (min-width: 30rem) {
    .tenant-entry {
        gap: 2.375rem;
    }
}

@media (max-width: 15.5rem) {
    .tenant-entry-code-input.input-field {
        font-size: 1rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .tenant-entry *,
    .tenant-entry *::before,
    .tenant-entry *::after {
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }
}

/* @toc Notes page */
/*
 * Notes page layout composes local views, quick capture, search/filter discovery, and responsive boards.
 * Its rules keep Notes controls and cards readable across the supported desktop and phone widths.
 * Component styles own note internals; this sheet owns page-level region spacing and responsive flow.
 */

.notes-page {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    inline-size: 100%;
    min-inline-size: 0;
}

/* Archive and Trash are local Notes views, not application sidebar destinations. */
.notes-views {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0;
}

.notes-views a {
    display: inline-flex;
    align-items: center;
    min-block-size: 2.75rem;
    padding-inline: var(--space-3);
    color: var(--text-muted);
    border-block-end: 2px solid transparent;
    text-decoration: none;
}

.notes-views a:focus-visible,
.notes-label:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
}

.notes-capture,
.notes-discovery,
.notes-board-region {
    min-inline-size: 0;
}

.notes-discovery {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding-block: var(--space-4);
    border-block: 1px solid var(--line);
}

.notes-search {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--space-3);
    inline-size: 100%;
}

.notes-search > * {
    min-inline-size: 0;
}

.notes-search .button {
    min-block-size: 2.75rem;
}

.notes-views a[aria-current="page"] {
    color: var(--text);
    border-block-end-color: currentColor;
}

.note-tile-acts form {
    display: inline-flex;
    margin: 0;
}

.note-tile-acts {
    flex-wrap: wrap;
}

.notes-lifecycle-action {
    inline-size: auto;
    padding-inline: var(--space-2);
}

.note-tile-open {
    text-decoration: none;
}

.note-tile-open[aria-busy="true"] {
    cursor: wait;
}

.note-tile-open-retry {
    inline-size: max-content;
    min-block-size: 2.75rem;
    padding-inline: var(--space-3);
    color: var(--text);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
}

[data-note-editor-host]:empty {
    display: none;
}

.notes-detail {
    display: grid;
    gap: var(--space-4);
    max-inline-size: 48rem;
    margin-inline: auto;
}

.notes-detail-back {
    inline-size: max-content;
}

.notes-detail-card {
    display: grid;
    gap: var(--space-4);
    padding: var(--space-5);
    color: var(--text);
    background: var(--tile-surface, var(--surface-raised));
    border: 1px solid var(--line);
    border-radius: var(--radius-container);
    overflow-wrap: anywhere;
}

.notes-detail-card p {
    margin: 0;
}

.notes-detail-text {
    white-space: pre-wrap;
}

.notes-detail-empty {
    color: var(--text-muted);
}

.notes-detail-labels {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* The filter is a wrapping row of label pills. The <nav> carries no class, so it is matched by its content. */
.notes-labels,
.notes-discovery > nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.notes-label {
    display: inline-flex;
    align-items: center;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
    font-weight: 600;
    border-radius: var(--radius-control, 6px);
    text-decoration: none;
    color: color-mix(in srgb, var(--text-muted) 45%, var(--text));
    background: color-mix(in srgb, var(--text-muted) 9%, var(--surface-raised));
    border: 1px solid color-mix(in srgb, var(--text-muted) 30%, var(--line));
    min-block-size: 2.75rem;
    overflow-wrap: anywhere;
}

.notes-label:hover {
    color: color-mix(in srgb, var(--text-muted) 62%, var(--text));
    border-color: color-mix(in srgb, var(--text-muted) 52%, var(--line));
}

/* Selected — the marked state: every stop pushed one step past resting, the border to the full token. */
.notes-label.is-on {
    color: color-mix(in srgb, var(--text-muted) 62%, var(--text));
    background: color-mix(in srgb, var(--text-muted) 22%, var(--surface-raised));
    border-color: var(--text-muted);
}

[data-notes-empty] {
    margin: 0;
    padding: var(--space-6) 0;
    color: var(--text-muted);
}

@media (max-width: 30rem) {
    .notes-page {
        gap: var(--space-4);
    }

    .notes-search {
        grid-template-columns: minmax(0, 1fr);
    }

    .notes-search .button {
        inline-size: 100%;
    }
}

/* @toc Planner card page */
/* Card-open (#2012) — the full-screen ticket workspace: a SIX-tab body (Description · Requirements · Plan ·
 * Code · Comments · Activity) beside a Team / Labels / Status rail. One column when narrow (the rail below the tabs), two
 * columns from --bp-workspace-up (1000px, the board's own workspace seam) with the rail to the right. The
 * board reaches this as a full page now, not a modal. Tokens only. */

/* Only the noninteractive page heading participates. The live card controls remain uncaptured and usable
   throughout this short browser-owned opacity treatment. */
@media (prefers-reduced-motion: no-preference) {
    ::view-transition-new(planner-page-title) {
        animation: planner-page-in 140ms ease-out both;
    }
}

@keyframes planner-page-in {
    from { opacity: 0; }
}

.card-open {
    max-width: 100%;
}

/* The shell action stays whole on desktop and becomes one labelled icon in the compact header. */
.cardo-back {
    flex-shrink: 0;
    white-space: nowrap;
}

.cardo-back-icon {
    display: inline-flex;
}

.cardo-back-icon svg {
    width: 16px;
    height: 16px;
}

@media (max-width: 768px), (max-height: 600px) {
    .cardo-back {
        padding-inline: var(--button-padding-y);
    }

    .cardo-back-label {
        display: none;
    }
}

/* #2012 — the full-page layout: the tab body (cardo-main) beside the Team/Labels/Status rail
   (cardo-rail-col). One column when narrow (rail below), two columns from --bp-workspace-up (rail right). */
.cardo-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: 20px;
}

@media (min-width: 1000px) {
    .cardo-layout {
        grid-template-columns: minmax(0, 1fr) 280px;
        gap: 28px;
        align-items: start;
    }
}

/* min-width: 0 lets the Code workspace wrap full paths instead of widening the page. */
.cardo-main {
    min-width: 0;
}

.cardo-labels {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.cardo-people {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
}

.cardo-person {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/* One consistent icon size across reporter / assignee / lock — the oversized-lock fix. */
.cardo-person svg {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.cardo-person-name {
    font-weight: 600;
    color: var(--text);
}

/* The assignee POPS in the tenant primary (ticket 1046) so the dialog agrees with the tile; reporter stays
   quiet-neutral. Name + icon (incl. the live-checkout lock) take the primary; AA via the blend toward --text. */
.cardo-person-assignee {
    color: color-mix(in srgb, var(--primary) 46%, var(--text));
}

.cardo-person-assignee .cardo-person-name {
    color: inherit;
}

.cardo-person-role {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 10px;
}

/* The panes own their padding (Description/Requirements/Plan/Comments breathe) while the tab BAR spans full-width.
   The Code tab is the exception — its tree and viewer pad their own cells, so the pane runs full-bleed. */
.card-open .pane {
    padding: 16px 20px;
}

.card-open .pane-code-tab {
    padding: 0;
}

/* Full-width tab bar: its bottom rule spans edge-to-edge; the tab buttons keep a small inset so they're not
   jammed against the flush body's edge. */
.card-open .tabs-nav {
    padding-inline: 12px;
}

.pane-empty {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* Description — only the description prose. (#2012: the status line moved to the Status rail.) */
.pane-prose {
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--text);
}

.pane-prose :is(p, ul, ol) {
    margin: 0 0 10px;
}

/* The Acceptance checklist is the <x-checklist> primitive now (#838) — it owns the list, badge and
   ticked-row styling (checklist.css). This page keeps only the SECTION delineation: a clear gap + a hairline
   rule above it, so the Checklist reads as its own stacked section below the Description prose (the Trello
   card-back model), not a continuation of it. */
/* #1359 — the 70%-transparent thinning is GONE. It created an ad-hoc third tier below --line, and a rule
   that dilutes the token defeats a fix to the token: sampled on the real card back, this line was
   neutral-800 on a neutral-800 dialog — the SAME hex as its own background, 1.00:1, invisible at any
   alpha. Even after the token step it stayed ~1.29:1 while diluted, i.e. still at the faintness James
   reported. Undiluted it lands at 1.46:1 dark / 1.42:1 light and matches every other divider. Subtlety
   is the token's job (--line is already the quiet tier); a per-rule alpha is how a system grows a
   fourth colour nobody named. */
.pane-checklist {
    margin-block-start: 16px;
    padding-block-start: 14px;
    border-top: 1px solid var(--line);
}

/* Settings rail */
.cardo-rail {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 6px 12px;
    margin: 0;
    font-size: var(--text-sm);
}

.cardo-rail dt {
    color: var(--text-muted);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    align-self: center;
}

.cardo-rail dd {
    margin: 0;
    color: var(--text);
}

.cardo-rail-note {
    margin-top: 16px;
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-style: italic;
}

/* Reading panes keep a comfortable measure; Code owns its denser three-region grid below. */
.pane-description,
.pane-plan {
    max-width: 72ch;
}

.cardo-plan-status {
    margin: var(--space-2) 0 0;
    color: var(--danger);
    font-size: var(--text-xs);
}

[data-plan-checklist][aria-busy="true"] {
    cursor: progress;
}

/* Status-rail meta (Source · Size · Created · Commit) — quiet pairs stacked under the status line (#2012). */
.cardo-meta {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 12px 0 0;
}

.cardo-meta-item {
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.cardo-meta dt {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.cardo-meta dd {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text);
}

.cardo-meta code {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: var(--text-xs);
}

/* LeadThink has two independent revision references: reviewed Git work and the submitted P4 changelist.
   Both wrap instead of widening a narrow tile or the detail rail, and their labels remain visible. */
.planner-card-references {
    display: inline-flex;
    flex: 1 1 100%;
    flex-wrap: wrap;
    gap: 3px 8px;
    min-width: 0;
}

.planner-card-reference {
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    min-width: 0;
    color: var(--text-muted);
    font-size: 10px;
}

.planner-card-reference-label {
    flex: none;
    font-weight: 600;
    text-transform: uppercase;
}

.planner-card-reference code,
.cardo-revision-reference code {
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* Code tab — a readable file tree and one viewer. The component stacks at narrow widths and owns both
   scroll regions, so a long repository path never creates horizontal page overflow. */
.pane-code-tab {
    container-type: inline-size;
}

.cardo-code-workspace {
    display: grid;
    grid-template-columns: 1fr;
    min-width: 0;
    max-width: 100%;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.cardo-code-tree,
.cardo-code-view,
.cardo-code-output,
.cardo-code-view .code-block-file,
.cardo-code-view .code-block-scroll {
    min-width: 0;
    max-width: 100%;
}

.cardo-code-tree,
.cardo-code-view {
    background: var(--surface);
    padding: 14px;
}

.cardo-code-tree {
    max-height: min(44dvh, 440px);
    overflow: auto;
    border-block-end: 1px solid var(--line);
}

.cardo-code-group + .cardo-code-group {
    margin-block-start: 18px;
}

.cardo-code-group-title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin: 0 0 6px;
    font-size: var(--text-sm);
    font-weight: 700;
}

.cardo-code-group-count {
    font-variant-numeric: tabular-nums;
}

.cardo-code-group-changed .cardo-code-group-title,
.cardo-code-group-changed :is(.tree-icon, .tree-status) {
    color: var(--info);
}

.cardo-code-group-added .cardo-code-group-title,
.cardo-code-group-added :is(.tree-icon, .tree-status) {
    color: var(--success);
}

.cardo-code-group-deleted .cardo-code-group-title,
.cardo-code-group-deleted :is(.tree-icon, .tree-status) {
    color: var(--danger);
}

.cardo-code-group-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.cardo-code-tree .tree-row {
    min-height: 32px;
    height: auto;
    align-items: flex-start;
}

.cardo-code-tree .tree-label,
.cardo-code-view .code-block-head {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    overflow-wrap: anywhere;
}

.cardo-code-view {
    display: grid;
    align-content: start;
    gap: 10px;
}

.cardo-code-choices {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}

/* Author display rules outrank the browser's hidden default; keep unavailable choices and output out of layout. */
.cardo-code-choices[hidden],
.cardo-code-choice[hidden],
.cardo-code-context[hidden],
.cardo-code-output[hidden] {
    display: none;
}

.cardo-code-choice {
    min-height: 32px;
    padding: 4px 10px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-raised);
    color: var(--text);
    font: inherit;
    font-size: var(--text-xs);
    font-weight: 600;
    cursor: pointer;
}

.cardo-code-choice:hover {
    border-color: var(--primary);
}

.cardo-code-choice:focus-visible {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}

.cardo-code-choice[aria-pressed="true"] {
    border-color: var(--primary);
    background: var(--primary);
    color: var(--on-primary);
}

.cardo-code-context,
.cardo-code-message {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

.cardo-code-output {
    max-height: min(62dvh, 720px);
    overflow: auto;
}

.cardo-code-view .code-block-file {
    height: min(58dvh, 680px);
}

@container (min-width: 720px) {
    .cardo-code-workspace {
        grid-template-columns: minmax(16rem, 2fr) minmax(0, 3fr);
    }

    .cardo-code-tree {
        max-height: min(68dvh, 760px);
        border-block-end: 0;
        border-inline-end: 1px solid var(--line);
    }
}

/* Crew marks on the card detail — same pairing rule as the board tile (see planner-board.css). The 14px
   `.cardo-person svg` sizing above already applies. */
.cardo-person svg + svg {
    margin-inline-start: -3px;
}

.cardo-comments-title {
    margin: 0 0 12px;
    font-size: var(--text-sm);
    font-weight: 600;
}

.cardo-comments-list {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin: 24px 0 0;
    padding: 0;
    list-style: none;
}

[data-comments-load-status] {
    margin: 24px 0 8px;
}

.cardo-comment-head {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-block-end: 4px;
}

.cardo-comment-head svg {
    width: 13px;
    height: 13px;
    flex-shrink: 0;
}

.cardo-comment-head svg + svg {
    margin-inline-start: -2px;
}

.cardo-comment-by {
    font-size: var(--text-sm);
    font-weight: 600;
}

.cardo-comment-at {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.cardo-comment-body {
    color: var(--text);
    font-size: var(--text-sm);
}

.cardo-comment-body > *:first-child { margin-block-start: 0; }
.cardo-comment-body > *:last-child { margin-block-end: 0; }

/* Activity is a compact read trail, not a second conversation surface. Rows use the existing text, muted,
   line and raised-surface tokens. Long recorded summaries wrap in place at 320px. */
.cardo-activity-status {
    margin: 0 0 12px;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.cardo-activity-list {
    display: flex;
    flex-direction: column;
    gap: 0;
    margin: 0;
    padding: 0;
    list-style: none;
    border-block-start: 1px solid var(--line);
}

.cardo-activity-item {
    min-width: 0;
    padding: 12px 0;
    border-block-end: 1px solid var(--line);
}

.cardo-activity-summary,
.cardo-activity-meta {
    margin: 0;
    overflow-wrap: anywhere;
}

.cardo-activity-summary {
    color: var(--text);
    font-size: var(--text-sm);
}

.cardo-activity-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 3px 6px;
    margin-block-start: 4px;
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.cardo-activity-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 16px;
    margin-block-start: 16px;
}

/* The shared button primitive has an explicit display value, so the native hidden attribute needs the
   Activity boundary's stronger selector after a successful load. Retry must disappear when JS hides it. */
.cardo-activity [hidden] {
    display: none;
}

.cardo-composer {
    margin: 0;
}

/* No width query anywhere in here. The card body renders at two very different widths (the deep-link page
   and the board's modal) and the composer is a single stacked column at both, so a breakpoint would only add
   a way to be wrong. `flex-end` is flow-relative already, so the button lands on the correct side under RTL
   without a second rule. Verified at a 320x380 viewport — no sideways scroll, nothing painted over the
   button — which is the phone-with-the-keyboard-up case. */
.cardo-composer-actions {
    display: flex;
    justify-content: flex-end;
}

/* Server-empty; planner-card.js fills it from the 422 body and unhides it. --danger is the categorical
   status token (self-flipping, AA-vetted against this surface), never a hand-authored red. */
.cardo-composer-error {
    margin: 0 0 8px;
    color: var(--danger);
    font-size: var(--text-xs);
}

/* ATTACHMENTS (#1529) — the evidence a card owns, below the trail. Same quiet register as the comments:
   the spec is the substance, this is what was pinned to it. Mobile-first — the row is a flex line that
   wraps, so at 320 a long filename drops under its own thumbnail instead of pushing the card sideways. */
.cardo-attachments {
    margin-block-start: 24px;
    padding-block-start: 16px;
    border-block-start: 1px solid var(--line);
}

.cardo-attachments-title {
    margin: 0 0 12px;
    font-size: var(--text-sm);
    font-weight: 600;
}

.cardo-attachments-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.cardo-attachment {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* A fixed box the image fills by cropping — a portrait screenshot and a wide one line up the same way,
   which is what makes a column of them readable rather than ragged. */
.cardo-attachment-thumb {
    display: block;
    flex-shrink: 0;
    width: 56px;
    height: 56px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    overflow: hidden;
}

/* ★ ANCHORED TO THE TOP, not the centre, and that is not a taste call.
 *
 * The attachment this list mostly holds is a full-page SCREENSHOT — the one I tested with is 1280x3639. A
 * centred cover-crop of that lands somewhere arbitrary in the middle of the page, which for a dark UI
 * screenshot is a near-black square: the thumbnail renders perfectly and reads as an empty box. I lost a
 * cycle to exactly that, diagnosing a loading bug that did not exist.
 *
 * The TOP of a screenshot is its most recognisable part — the header, the title, the first rows — so
 * anchoring there makes a tall image identifiable at 56px instead of a swatch of whatever was mid-page. */
.cardo-attachment-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top;
    display: block;
}

.cardo-attachment-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
    min-width: 0;   /* lets a long filename ellipsize instead of widening the row past the card */
}

.cardo-attachment-name {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: 600;
}

.cardo-attachment-label,
.cardo-attachment-size {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

/* A file the row describes but the disk no longer has — stated, not hidden. */
.cardo-attachment-missing {
    color: var(--warning);
}

/* THE RAIL (#2012) — Team · Labels · Status, a persistent sidebar beside the tab body. Stacked below the
   tabs when narrow (a hairline above it), a bordered column to the right from --bp-workspace-up. */
.cardo-rail-col {
    display: flex;
    flex-direction: column;
    gap: 22px;
    margin-block-start: 20px;
    padding-block-start: 20px;
    border-block-start: 1px solid var(--line);
}

@media (min-width: 1000px) {
    .cardo-rail-col {
        margin-block-start: 0;
        padding-block-start: 0;
        padding-inline-start: 28px;
        border-block-start: 0;
        border-inline-start: 1px solid var(--line);
    }
}

/* Team — the seats stack one per line in the narrow rail (they were an inline row in the old header). */
.cardo-rail-col .cardo-people {
    flex-direction: column;
    gap: 10px;
}

/* Status — the lane in words, the status line, then the quiet meta pairs. */
.cardo-lane {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text);
}

.cardo-status-line {
    margin: 6px 0 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* @toc Planner page */
/* Planner workspace (#1537 → #1921) — the board is a WORKSPACE, not a page with controls bolted above it.
 *
 * #1537 built that shape here by hand, copying the Editor's frame + rail. #1921 replaced the copy with the
 * shared Frame panel chassis, so this file no longer owns a layout at all: the grid, the dock widths, the
 * panel move/fold/persist and shared-bar sheets are the component's, and what is left below is only what the
 * three Planner panels look like INSIDE it.
 *
 * WHAT WENT, AND WHY IT IS NOT MISSING: `.planner-frame`, `.planner-rail` and the phone band that capped the
 * rail at 25vh above the board. The band existed because a 17rem column beside a kanban does not fit a phone;
 * Frame answers that with shared-bar controls opening one sheet at a time, which is the whole reason the
 * board moved onto it. Re-adding any of them would fight the chassis rather than help it.
 *
 * View: resources/views/pages/planner.blade.php · the lanes themselves: planner-board.css */

/* Only the noninteractive page heading participates. The live board and its controls remain uncaptured,
   painted, and hit-testable throughout the browser-owned document swap. */
@media (prefers-reduced-motion: no-preference) {
    ::view-transition-old(planner-page-title) {
        animation: planner-page-out 90ms ease-in both;
    }
}

@keyframes planner-page-out {
    to { opacity: 0; }
}

/* Fill the app-shell main: this is a workspace, never a scrolling page. Navigation owns the left boundary;
   the stage and authored right dock retain their own breathing room without wrapping Navigation in it. */
.planner-workbench {
    --planner-mobile-scroll-mode: 0;

    height: 100%;
    min-height: 0;
    padding: 0;
}

/* THE BOARD'S OWN WINDOW, inside the chassis stage. It keeps its scrolling here rather than leaning on the
   stage, because the board scrolls SIDEWAYS through its lanes and that has to stay independent of anything
   the docks do. `height: 100%` because the stage is a grid area with a definite height to fill. */
.planner-stage {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    height: 100%;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    padding: var(--space-3);
}

.planner-workbench > .workbench-dock[data-workbench-dock="right"] {
    box-sizing: border-box;
    padding-block: var(--space-3);
    padding-inline-end: var(--space-3);
}

.planner-canvas {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: auto;
}

/* ── the Labels panel, and the Statuses panel below it ─────────────────────────────────────────────────────
 *
 * ONE ROW PER LABEL, FULL WIDTH, SINGLE COLUMN (#1758, James). These wrapped as pills until now, and a
 * wrapping blob is the wrong shape for what this actually is: the board is James's PHONE surface, and at 320
 * a blob of 27 chips is a wall you cannot read or hit. A full-width row is a list, and a list is tappable —
 * every row starts in the same place, so the eye scans one column instead of hunting a ragged edge.
 *
 * `grid` with ONE column rather than flex-wrap: with flex a short label still shrink-wraps to its text and
 * only the wrap forces a new line, so "one per line" would be a side effect of running out of room. One
 * declared column makes it the RULE — a row cannot sit beside another however narrow its text is. */
.planner-labels,
.planner-statuses {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-1);
}

/* min-inline-size: 0 lets a long label ellipsize instead of forcing the grid track wider than the rail —
   the one way a single column can still produce a horizontal scrollbar. */
.planner-label,
.planner-status {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-inline-size: 0;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
    font-weight: 600;
    border-radius: var(--radius-control, 6px);
    text-decoration: none;
}

/* THE LABEL ROW KEEPS THE PILL'S FAMILY TINT. A .planner-fam-<family> class on the row sets --planner-cat,
   and these color-mix values are the ones the retired pill used — the categorical families are vetted
   identities (R13, no-reuse law), so re-deriving them here would have been a second place the same colour
   decision lives. Only the SHAPE changed: a full-width row with a control radius instead of a pill. */
.planner-label {
    color: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 45%, var(--text));
    background: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 9%, var(--surface-raised));
    border: 1px solid color-mix(in srgb, var(--planner-cat, var(--text-muted)) 30%, var(--line));
}

.planner-status {
    color: var(--text-muted);
    background: var(--surface-raised);
    border: 1px solid var(--line);
}

/* The count sits hard right on its own, so the column reads as name-then-number down the whole panel rather
   than the number floating wherever the label happens to end. */
.planner-label-count,
.planner-status-count {
    margin-inline-start: auto;
    font-variant-numeric: tabular-nums;
}

/* The label text is the part that gives way when the rail is narrow — never the count, which is one or two
   characters and is the thing being compared down the column. */
.planner-label-text,
.planner-status-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.planner-label svg {
    flex: none;
    inline-size: var(--space-4);
    block-size: var(--space-4);
}

.planner-label:hover {
    color: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 62%, var(--text));
    border-color: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 52%, var(--line));
}

.planner-status:hover {
    color: var(--text);
    border-color: var(--line-strong);
}

/* THE TWO PANELS NOW MARK OPPOSITE STATES, and that is not an inconsistency — they answer opposite questions.
   Statuses is a SUBTRACTION: seven lanes are on by default and the information is which few you switched off,
   so OFF is the marked state and a shown lane just looks like itself. Labels became a SELECTION with #1826:
   nothing is selected by default and the information is which few you picked, so SELECTED is the marked one.
   Marking the majority state in either panel says nothing and costs a wall of decoration.

   THAT INVERSION IS WHY THE LABEL HALF OF THE OLD `:not(.is-on)` RULE IS GONE RATHER THAN RETUNED. Under
   exclude, `is-on` meant "not hidden" and every row carried it, so the struck-and-drained treatment fired on
   the few you had switched off. Under include, `is-on` means SELECTED and a resting board carries it on NO
   row — the identical rule would have opened the panel with all twenty-four labels struck through, reading as
   "everything is off" on a board showing every card. The class kept its name and reversed its meaning, so the
   rule had to be rewritten, not re-pointed.

   ⚠ NO OPACITY MARKS EITHER STATE — the colour changes instead, and this is the third time this surface has
   had to learn it. The obvious way to say "not selected" is `opacity: 0.55`, which is what the excluded pill
   did before #1758 and what the first draft of the old rule did. It is wrong here for two reasons a small
   pill got away with and a full-width row does not. First, opacity composites LIVE TEXT toward the surface
   behind it: these rows are focusable, clickable links, so they are nothing like the disabled control the
   a11y contract exempts, and 55% puts them near 2.5:1, far under the 4.5 floor. Second, opacity fades the
   FOCUS RING with the text, dropping the keyboard indicator under 3:1, which axe cannot see and the gate
   would have passed. `.planner-status-count`'s 0.82 and `.planner-pill-count`'s 0.66 were the same mistake by
   two earlier authors; an ancestor 0.55 would additionally have re-dimmed both counts.

   SO SELECTED DEEPENS THE FAMILY TINT IT ALREADY HAS — the same three color-mix stops the resting row uses,
   each pushed one step, plus the border going to the full family colour. No new value, no alpha, and it
   cannot fail contrast the resting row passes: every stop moves AWAY from the surface, not toward it.

   AND IT IS NOT COLOUR ALONE. A check appears in the row (`.planner-label-check`, below), because "which of
   these twenty-four did I pick" answered only by a hue shift fails WCAG 1.4.1 for anyone who cannot compare
   two tints of the same family. The accessible NAME carries it too — "Remove Planner from the filter" versus
   "Add Planner to the filter" — so the state is legible in three independent channels. */
.planner-status:not(.is-on) {
    color: var(--text-muted);
    background: var(--surface-raised);
    border-color: var(--line);
}

.planner-status:not(.is-on) .planner-status-label {
    text-decoration: line-through;
}

.planner-label.is-on {
    color: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 62%, var(--text));
    background: color-mix(in srgb, var(--planner-cat, var(--text-muted)) 22%, var(--surface-raised));
    border-color: var(--planner-cat, var(--text-muted));
}

/* The check rides in the row and is revealed by the same class the tint keys on, so the two signals cannot
   disagree — there is no second thing for the client to write and therefore nothing to leave stale. */
.planner-label-check {
    display: none;
    flex: none;
    inline-size: var(--space-4);
    block-size: var(--space-4);
}

.planner-label.is-on .planner-label-check {
    display: block;
}

/* CLEAR — the one control that undoes the filter, in the panel head rather than the list. It is a link, not a
   row: putting it in the list would make it the twenty-fifth label-shaped thing in a panel of labels, which
   is how a reset gets clicked by mistake. Hidden while nothing is selected (the blade renders it either way
   so the client can reveal it without rebuilding the panel), so its resting cost is zero. */
.planner-clear {
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    text-decoration: none;
}

.planner-clear:hover {
    color: var(--text);
    text-decoration: underline;
}

/* NO opacity on either count, and nothing above it may reintroduce one. `opacity: 0.82` on the status count
 * is what tipped /planner's contrast gate from 7 nodes to 8: opacity blends text toward the surface behind
 * it, so --text-muted on --surface-raised fell 5.78 -> 4.39 in dark, under the 4.5 AA floor for small text
 * (light scraped 4.60). The count is ALREADY muted by the token it inherits; dimming a muted token again is
 * how a compliant ramp value becomes non-compliant. The pill count made the identical mistake independently
 * (#1533) — two authors, one pattern, which is why this is written down rather than just deleted. */

/* ── roster ────────────────────────────────────────────────────────────────────────────────────────────
 * Frame owns the panel, its dock scrolling, and its mobile sheet. This page owns only the compact,
 * vertical status list inside that panel. Ticket identity stays absent because the list reports seat state. */

.planner-crew-list {
    display: grid;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.planner-crew-seat {
    display: grid;
    gap: var(--space-1);
    min-inline-size: 0;
    padding-inline-start: var(--space-2);
    border-inline-start: 2px solid var(--text-muted);
}

.planner-crew-seat.is-ready { border-inline-start-color: var(--success); }
.planner-crew-seat.is-working,
.planner-crew-seat.is-claimed,
.planner-crew-seat.is-stale { border-inline-start-color: var(--warning); }
.planner-crew-seat.is-conflict { border-inline-start-color: var(--danger); }

.planner-crew-primary {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    min-inline-size: 0;
}

.planner-crew-name {
    overflow: hidden;
    color: var(--text);
    font-size: var(--text-sm);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.planner-crew-state {
    flex: none;
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 700;
}

.planner-crew-seat.is-ready .planner-crew-state { color: var(--success); }
.planner-crew-seat.is-working .planner-crew-state,
.planner-crew-seat.is-claimed .planner-crew-state,
.planner-crew-seat.is-stale .planner-crew-state { color: var(--warning); }
.planner-crew-seat.is-conflict .planner-crew-state { color: var(--danger); }

.planner-crew-summary {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-xs);
    line-height: 1.45;
}

.planner-crew-detail {
    color: var(--text);
}

.planner-crew-lease {
    font-weight: 700;
}

.planner-crew-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ── phone ────────────────────────────────────────────────────────────────────────────────────────────────
 *
 * At Frame's shared compact breakpoint the docks become bottom-bar sheets, so the board has the screen to itself
 * and needs only its clearance: the rail is fixed, so without this the last lane's cards sit under it. The
 * chassis already pads for the safe-area inset, and this adds the rail's own height on top.
 *
 * Viewport media (not a container query) on purpose: this is page-level chrome deciding its own layout,
 * which is exactly the split the CSS-modernity contract draws. */
@media (max-width: 768px), (max-height: 600px) {
    /* The chassis un-grids to display:block below this edge. A percentage height on that block has no definite
       parent height, so one long lane can make the whole document tens of thousands of pixels tall. Mobile
       Safari then restores the document scroll on refresh and strands the Planner controls above the viewport.

       This page-local flex chain gives every scroll owner a definite box. The workbench and stage do not
       scroll, the board owns the horizontal lane swipe, and each lane's card list owns its vertical scroll.
       The custom property exposes this exact responsive mode to planner-board.js without duplicating the
       workspace breakpoint in JavaScript. Other workbench pages and desktop keep the shared chassis contract. */
    .planner-workbench {
        --planner-mobile-scroll-mode: 1;

        display: flex;
        flex: 1 1 0;
        flex-direction: column;
        height: auto;
        max-height: 100%;
        min-width: 0;
        overflow: hidden;
        padding: 0;
    }

    .planner-workbench .workbench-stage {
        display: flex;
        flex: 1 1 0;
        flex-direction: column;
        min-height: 0;
        overflow: hidden;
    }

    .planner-stage {
        padding: 0 var(--space-2);
    }

    .planner-workbench > .workbench-dock[data-workbench-dock="right"] {
        padding: 0;
    }

    .planner-canvas {
        flex: 1 1 0;
        height: auto;
        overflow: hidden;
        padding-block-end: 0;
    }

    /* If the shared-bar projection cannot complete, the authored panels remain after the stage. This root is
       then the one reachable scroll owner: the board keeps one full content box and its existing lane scroll,
       while Labels, Roster, and Statuses follow in normal flow instead of shrinking the stage to zero. */
    .planner-workbench:not(.has-shared-bottom-bar) {
        display: block;
        height: 100%;
        max-height: 100%;
        overflow-x: hidden;
        overflow-y: auto;
    }

    .planner-workbench:not(.has-shared-bottom-bar) .workbench-stage {
        display: block;
        height: 100%;
        min-height: 100%;
        overflow: hidden;
    }
}

/* @toc Projects page */
/*
 * The canonical Projects entry prompt and app-chrome viewer layout.
 * The full-chrome viewer keeps its critical viewport rules inline.
 * The server-rendered views remain the source of truth at every width.
 */

.projects-index-prompt {
    max-width: var(--max-w-2xl);
    margin: 0;
    color: var(--text-muted);
}

.projects-empty {
    margin: 0;
    color: var(--text-muted);
}

.project-viewer {
    flex: 1 1 0;
    width: 100%;
    height: auto;
    min-width: 0;
    min-height: 0;
}

.project-viewer-frame {
    display: block;
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    border: 0;
}

/* @toc Server health page */
/*
 * Server health is a compact shared-resource dashboard. The PageSection container controls reflow so the
 * same six cards stay readable in the app frame, a narrow dock, or the full content area.
 * It reads only accepted cached samples and never initiates collection or provider requests.
 */

.server-health-page .page-section-inner {
    gap: var(--space-6);
    padding-block: var(--space-8);
}

.server-health-page .page-section-body,
.server-health-page .page-section-body > section,
.server-health-grid,
.server-health-card,
.server-health-card .card-body {
    min-inline-size: 0;
}

.server-health-page .page-section-body {
    display: grid;
    gap: var(--space-6);
}

.server-health-page h2,
.server-health-page p {
    margin-block-start: 0;
}

.server-health-status {
    padding: var(--space-4);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
}

.server-health-status-title,
.server-health-card-state {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.server-health-status-title h2 {
    margin: 0;
}

.server-health-facts,
.server-health-card-facts {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-2);
    margin-block: var(--space-3) 0;
}

.server-health-facts > div,
.server-health-card-facts > div {
    min-inline-size: 0;
}

.server-health-facts dt,
.server-health-card-facts dt {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.server-health-facts dd,
.server-health-card-facts dd {
    margin-inline-start: 0;
    font-size: var(--text-sm);
    overflow-wrap: anywhere;
}

.server-health-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-4);
}

.server-health-card-state {
    margin-block-end: var(--space-3);
}

.server-health-card-state strong {
    font-size: var(--text-sm);
}

.server-health-card-facts {
    margin-block-end: var(--space-4);
}

@container (min-width: 640px) {
    .server-health-page .page-section-inner {
        padding-block: var(--space-12);
    }

    .server-health-facts {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .server-health-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .server-health-card-facts {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (min-width: 1024px) {
    .server-health-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* @toc Site quality page */
/*
 * Sites keeps the first screen operational: filters, three totals, and compact site cards lead.
 * The shared PageSection is the container. Wider layouts are enhancements of that container, not the viewport.
 * External checks, origin history, and laboratory evidence remain separate operational signals.
 */

.site-quality-page .page-section-inner {
    gap: var(--space-6);
    padding-block: var(--space-8);
}

.site-quality-page .page-section-body,
.site-quality-page .page-section-body > section,
.site-quality-grid,
.site-quality-chart-grid,
.site-quality-card,
.site-quality-card .card-body {
    min-inline-size: 0;
}

.site-quality-page .page-section-body {
    display: grid;
    gap: var(--space-6);
}

.site-quality-page h2,
.site-quality-page h4,
.site-quality-page p {
    margin-block-start: 0;
}

.site-quality-filters {
    display: grid;
    grid-template-columns: 1fr;
    align-items: end;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
}

.site-quality-counts {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-2);
}

.site-quality-counts > div {
    display: grid;
    gap: var(--space-1);
    min-inline-size: 0;
    padding: var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
}

.site-quality-counts strong {
    font-size: clamp(1.5rem, 1.25rem + 1.5cqi, 2.25rem);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.site-quality-counts span {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.site-quality-grid,
.site-quality-chart-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-4);
}

.site-quality-card-status,
.site-quality-monitoring,
.site-quality-card-action {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.site-quality-monitoring {
    margin-block-start: var(--space-2);
}

.site-quality-reasons {
    margin-block: var(--space-3);
    padding-inline-start: var(--space-5);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.site-quality-metrics,
.site-quality-lab-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-3);
    margin-block: var(--space-4);
}

.site-quality-metrics > div,
.site-quality-lab-grid > section {
    min-inline-size: 0;
    padding: var(--space-3);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
}

.site-quality-metrics dt,
.site-quality-lab-grid h4 {
    font-size: var(--text-sm);
    font-weight: 700;
}

.site-quality-metrics dd,
.site-quality-lab-grid p {
    margin-inline-start: 0;
    margin-block-end: var(--space-1);
    color: var(--text-muted);
    font-size: var(--text-xs);
    overflow-wrap: anywhere;
}

.site-quality-unselected,
.site-quality-reports {
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
}

.site-quality-unselected ul {
    margin-block-end: 0;
}

.site-quality-reports {
    max-inline-size: 100%;
    overflow: auto;
}

.site-quality-reports summary {
    cursor: pointer;
    font-weight: 700;
}

.site-quality-empty {
    color: var(--text-muted);
}

@container (min-width: 640px) {
    .site-quality-page .page-section-inner {
        padding-block: var(--space-12);
    }

    .site-quality-filters {
        grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto;
    }

    .site-quality-counts {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .site-quality-grid,
    .site-quality-chart-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .site-quality-metrics,
    .site-quality-lab-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (min-width: 1024px) {
    .site-quality-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .site-quality-chart-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* @toc Sites page */
/*
 * The authenticated Sites index and native preview viewer.
 * Frame owns application chrome. These rules size the list and its one persistent iframe content surface.
 * The layout uses shared semantic tokens and remains fluid without viewport-specific page variants.
 */

.sites-index {
    display: grid;
    width: min(100%, var(--max-w-4xl));
    margin-inline: auto;
    gap: 24px;
}

.sites-index-intro {
    margin: 0;
    color: var(--text-muted);
}

.sites-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
    gap: 16px;
    padding: 0;
    margin: 0;
    list-style: none;
}

.sites-card {
    display: grid;
    align-content: space-between;
    min-width: 0;
    min-height: 10rem;
    padding: 20px;
    gap: 24px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.sites-card-title {
    margin: 0;
    font-size: var(--text-xl);
}

.sites-card-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
}

.sites-public-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--text-muted);
    text-underline-offset: 0.2em;
}

.sites-public-link:hover {
    color: var(--text);
}

.site-viewer {
    flex: 1 1 0;
    width: 100%;
    height: auto;
    min-width: 0;
    min-height: 0;
    background: var(--surface);
}

.site-viewer-frame {
    display: block;
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    border: 0;
    background: var(--surface);
}

.site-logo-gallery {
    display: grid;
    min-inline-size: 0;
    padding: 0;
    margin: 0;
    gap: 16px;
    list-style: none;
}

.site-logo-card {
    display: grid;
    min-inline-size: 0;
    padding: 12px;
    gap: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.site-logo-name {
    margin: 0;
    font-size: var(--text-base);
}

.site-logo-preview {
    flex-direction: column;
    inline-size: 100%;
    min-inline-size: 0;
    padding: 8px;
}

.site-logo-thumbnail {
    display: grid;
    inline-size: 100%;
    min-inline-size: 0;
    min-block-size: 8rem;
    place-items: center;
    overflow: hidden;
    border-radius: var(--radius-md);
    background-color: var(--surface);
    background-image:
        linear-gradient(45deg, var(--line) 25%, transparent 25%),
        linear-gradient(-45deg, var(--line) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--line) 75%),
        linear-gradient(-45deg, transparent 75%, var(--line) 75%);
    background-position: 0 0, 0 8px, 8px -8px, -8px 0;
    background-size: 16px 16px;
}

.site-logo-thumbnail img {
    display: block;
    inline-size: 100%;
    block-size: auto;
    max-block-size: 12rem;
    object-fit: contain;
}

/* @toc Tracking page */
/*
 * Tracking presents supplied report envelopes without owning data access or refresh behavior.
 * Compact totals lead, followed by one exact-values chart, tables, activity, and drawer detail.
 * Semantic theme tokens keep every state legible in dark and light modes.
 */

.tracking-presentation-example,
.tracking-report,
.tracking-dashboard,
.tracking-report-stack {
    min-inline-size: 0;
    container-type: inline-size;
}

.tracking-report-stack {
    display: grid;
    gap: var(--space-4);
}

.tracking-dashboard {
    display: grid;
    gap: var(--space-4);
}

.tracking-refresh-state {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.tracking-refresh-state[data-state="updating"] {
    color: var(--info);
}

.tracking-refresh-state[data-state="delayed"] {
    color: var(--warning);
}

.tracking-refresh-state[data-state="failed"] {
    color: var(--danger);
}

.tracking-presentation-example {
    display: grid;
    gap: var(--space-6);
    margin-block: var(--space-6);
    padding: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
    background: var(--surface);
}

.tracking-example-notice {
    margin: 0;
    padding: var(--space-3);
    border: 1px solid var(--info);
    border-radius: var(--radius-control);
    background: var(--info-surface);
    color: var(--text);
}

.tracking-report {
    display: grid;
    gap: var(--space-3);
}

.tracking-report-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-3);
}

.tracking-panel-heading {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-2);
}

.tracking-panel-heading h3,
.tracking-panel-heading p {
    margin-block-end: 0;
}

.tracking-report-header h2,
.tracking-panel h3,
.tracking-detail-drawer h3,
.tracking-report p,
.tracking-detail-drawer p {
    margin-block-start: 0;
}

.tracking-eyebrow,
.tracking-updated,
.tracking-context,
.tracking-provenance,
.tracking-provider-meta {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.tracking-eyebrow {
    margin-block-end: var(--space-1);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.tracking-updated {
    margin-block-end: 0;
}

.tracking-state {
    margin-block-end: 0;
    padding: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface-muted);
}

.tracking-state-updating,
.tracking-state-partial {
    border-color: var(--info);
    background: var(--info-surface);
}

.tracking-state-empty {
    background: var(--surface);
    color: var(--text-muted);
}

.tracking-state-delayed {
    border-color: var(--warning);
    background: var(--warning-surface);
}

.tracking-state-failed {
    border-color: var(--danger);
    background: var(--danger-surface);
}

.tracking-state-unavailable {
    border-style: dashed;
    color: var(--text-muted);
}

.tracking-metrics {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0;
    margin: 0;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
    background: var(--surface-raised);
}

.tracking-panel {
    min-inline-size: 0;
    padding: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
    background: var(--surface-raised);
}

.tracking-metric {
    display: grid;
    gap: var(--space-1);
    min-inline-size: 0;
    padding: var(--space-3);
    border-inline-end: 1px solid var(--line);
    border-block-end: 1px solid var(--line);
}

.tracking-metric:nth-child(2n) {
    border-inline-end: 0;
}

.tracking-metric:nth-last-child(-n + 2) {
    border-block-end: 0;
}

.tracking-metric dt {
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: 700;
}

.tracking-metric dd {
    margin: 0;
}

.tracking-metric-value {
    font-size: clamp(1.5rem, 1.3rem + 1.4cqi, 2.25rem);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    line-height: 1;
}

.tracking-metric-state,
.tracking-metric-meta,
.tracking-metric-reason,
.tracking-classification-grid dd:last-child {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.tracking-metric-meta,
.tracking-provider-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
}

.tracking-metric-meta {
    align-items: center;
    justify-content: space-between;
}

.tracking-provenance {
    padding: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface-muted);
}

.tracking-classification-details,
.tracking-secondary,
.tracking-supporting-details {
    padding: var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--card-border-radius);
    background: var(--surface-raised);
}

.tracking-supporting-details > section {
    margin-block-start: var(--space-4);
}

.tracking-supporting-details h3 {
    margin-block: 0;
}

.tracking-classification-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
    margin: var(--space-3) 0 0;
}

.tracking-classification-grid > div {
    min-inline-size: 0;
    padding: var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    background: var(--surface-muted);
}

.tracking-classification-grid dt {
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: 700;
}

.tracking-classification-grid dd {
    margin: var(--space-1) 0 0;
}

.tracking-campaigns {
    display: grid;
    gap: var(--space-3);
}

.tracking-activity-list,
.tracking-secondary-stack {
    display: grid;
    gap: var(--space-4);
}

.tracking-secondary-stack {
    margin-block-start: var(--space-3);
}

.tracking-provenance > summary {
    color: var(--text);
    font-weight: 700;
    cursor: pointer;
}

.tracking-provenance-fields,
.tracking-coverage {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2);
    margin-block: var(--space-3) 0;
}

.tracking-provenance-fields > div,
.tracking-coverage > section {
    min-inline-size: 0;
}

.tracking-ranked-list > div {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding-block-end: var(--space-1);
    border-block-end: 1px solid var(--line);
}

.tracking-ranked-list dd,
.tracking-primary-value {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

.tracking-provenance-fields dt,
.tracking-coverage h4 {
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
}

.tracking-provenance-fields dd {
    margin: 0;
    color: var(--text);
    overflow-wrap: anywhere;
}

.tracking-coverage h3,
.tracking-coverage h4,
.tracking-coverage p {
    margin-block: 0;
}

.tracking-metric[data-metric-state="unavailable"] .tracking-metric-value {
    color: var(--text-muted);
    font-size: var(--text-lg);
}

.tracking-table-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
}

.tracking-panel > :last-child,
.tracking-detail-list,
.tracking-provenance {
    margin-block-end: 0;
}

.tracking-report .data-table,
.tracking-report .table-wrap {
    min-inline-size: 0;
    max-inline-size: 100%;
}

.tracking-report .table-wrap {
    overflow-x: auto;
}

.tracking-chart-panel {
    padding-block-end: var(--space-2);
}

.tracking-detail-control.button {
    min-block-size: 2.75rem;
    padding: var(--space-2) var(--space-3);
}

.tracking-detail-list {
    display: grid;
    gap: var(--space-2);
    padding: 0;
}

.tracking-detail-list > div {
    display: grid;
    gap: var(--space-1);
    padding-block-end: var(--space-2);
    border-block-end: 1px solid var(--line);
}

.tracking-detail-list dt {
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
}

.tracking-detail-list dd {
    margin: 0;
    overflow-wrap: anywhere;
}

.tracking-evidence-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    margin: 0;
    padding-inline-start: var(--space-4);
}

.tracking-detail-drawer [data-tracking-fragment="detail"] {
    display: grid;
    gap: var(--space-5);
}

.tracking-detail-drawer [data-tracking-fragment="detail"] > section {
    min-inline-size: 0;
}

@container (max-width: 35.99rem) {
    .tracking-clicks .table-wrap,
    .tracking-summary > .tracking-table-grid .table-wrap {
        overflow-x: visible;
    }

    .tracking-clicks .table,
    .tracking-summary > .tracking-table-grid .table {
        min-inline-size: 0;
    }

    .tracking-summary > .tracking-table-grid .table thead {
        position: absolute;
        inline-size: 1px;
        block-size: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .tracking-clicks .table thead {
        display: block;
        margin-block-end: var(--space-2);
    }

    .tracking-clicks .table thead tr {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        padding: 0 0 var(--space-2);
    }

    .tracking-clicks .table thead th {
        min-inline-size: 0;
        padding: 0;
        border: 0;
        text-align: start;
        white-space: normal;
    }

    .tracking-clicks .table thead .table-sort {
        display: flex;
        min-block-size: 44px;
        align-items: center;
        justify-content: center;
        padding: var(--space-2);
        border: 1px solid var(--line);
        border-radius: var(--radius-control);
        background: var(--surface-raised);
        text-align: center;
    }

    .tracking-clicks .table thead th:is(.is-sorted-asc, .is-sorted-desc) .table-sort {
        border-color: var(--primary);
        color: var(--text);
    }

    .tracking-clicks .table tbody,
    .tracking-summary > .tracking-table-grid .table tbody {
        display: grid;
        gap: var(--space-2);
    }

    .tracking-clicks .table tr,
    .tracking-summary > .tracking-table-grid .table tr {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: var(--space-2) var(--space-3);
        padding: var(--space-2) 0;
        border-block-end: 1px solid var(--line);
        background: transparent;
    }

    .tracking-clicks .table td,
    .tracking-summary > .tracking-table-grid .table td {
        display: flex;
        min-inline-size: 0;
        align-items: baseline;
        justify-content: space-between;
        gap: var(--space-2);
        padding: 0;
        border: 0;
        text-align: start;
        overflow-wrap: anywhere;
    }

    .tracking-clicks .table td::before,
    .tracking-summary > .tracking-table-grid .table td::before {
        content: attr(data-label);
        flex: none;
        color: var(--text-muted);
        font-size: var(--text-xs);
        font-weight: 700;
    }

    .tracking-clicks .table td:first-child,
    .tracking-clicks .table td:last-child,
    .tracking-summary > .tracking-table-grid .table td:first-child,
    .tracking-summary > .tracking-table-grid .table td:last-child,
    .tracking-clicks .table td[colspan] {
        grid-column: 1 / -1;
    }

    .tracking-clicks .table td:first-child,
    .tracking-summary > .tracking-table-grid .table td:first-child {
        font-weight: 700;
    }

    .tracking-clicks .table td[colspan]::before {
        content: none;
    }

    .tracking-secondary-value {
        color: var(--text-muted);
        font-size: var(--text-xs);
    }

}

@container (min-width: 36rem) {
    .tracking-provenance-fields,
    .tracking-coverage {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (min-width: 48rem) {
    .tracking-metrics {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    .tracking-metric {
        border-block-end: 0;
    }

    .tracking-metric:not(:last-child) {
        border-inline-end: 1px solid var(--line);
    }

    .tracking-metric:last-child {
        border-inline-end: 0;
    }

    .tracking-table-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (min-width: 64rem) {
    .tracking-classification-grid {
        grid-template-columns: repeat(5, minmax(0, 1fr));
    }
}

/* @toc Uploads page */
/* Uploads page — the single-file viewer (/uploads/<slug>). The gallery itself is the
 * uploads-gallery component; this is just the embedded-file page. Page CSS, bundled by
 * CssService. */

.uploads-view {
    display: flex;
    flex-direction: column;
    gap: 12px;
    block-size: 100%;
    min-block-size: 0;
    overflow: hidden;
}

.uploads-view-bar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.uploads-view-meta {
    min-inline-size: 0;
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    overflow-wrap: anywhere;
}

.uploads-view-frame {
    flex: 1 1 0;
    min-inline-size: 0;
    min-block-size: 0;
    inline-size: 100%;
    block-size: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

.app-shell-main:has(> .uploads-view) {
    min-block-size: 0;
    overflow: hidden;
}

/* @toc Website editor page */
/*
 * Restricted website appearance workspace: one Workbench with a pinned Navigator, Theme and Typography
 * feature panels, and a bounded tenant preview. Compact layout delegates panel presentation to Workbench.
 * The shared application bar remains the sole compact entry point for panels and permanent Feedback.
 */
[data-website-editor] [data-frame-panels] {
    min-block-size: 0;
    block-size: 100%;
    padding: var(--space-4);
}

.website-editor-sites ul {
    display: grid;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.website-editor-sites a {
    display: grid;
    gap: 2px;
    padding: var(--space-3);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    color: var(--text);
    text-decoration: none;
}

.website-editor-sites a:is(:hover, :focus-visible) {
    border-color: var(--line);
    outline: none;
}

.website-editor-sites a[aria-current="page"] {
    border-color: var(--primary-500);
    background: color-mix(in srgb, var(--primary) 12%, transparent);
}

.website-editor-sites small {
    color: var(--text-muted);
}

.website-editor-preview {
    display: flex;
    min-inline-size: 0;
    min-block-size: 0;
    block-size: 100%;
    flex-direction: column;
}

.website-editor-preview-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    margin-block-end: var(--space-3);
}

.website-editor-preview-head :is(p, h2) {
    margin: 0;
}

.website-editor-status {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.website-editor-status[data-state="pending"] {
    color: var(--warning);
}

.website-editor-status[data-state="saved"],
.website-editor-status[data-state="published"] {
    color: var(--success);
}

.website-editor-status[data-state="failed"] {
    color: var(--danger);
}

.website-editor-preview > .device-frame {
    flex: 1 1 auto;
    min-block-size: 0;
}

.typography-panel-family > .dropdown {
    flex: 1 1 auto;
    min-inline-size: 0;
}

.typography-panel-family > .dropdown > .typography-panel-control {
    inline-size: 100%;
    justify-content: space-between;
}

@media (max-width: 768px), (max-height: 600px) {
    [data-website-editor] [data-frame-panels] {
        block-size: auto;
        padding: var(--space-3);
    }
}

/* @toc Whiteboard page */
/* The Whiteboard index — the landing screen for the whole feature.
 *
 * ★ THERE WAS NO PAGE SHEET AT ALL. `wb-lead`, `wb-new` and the old `wb-list` were never styled by anything,
 * which is most of why the page read as a prose stack: it was not under-designed, it was undesigned. This is
 * that sheet, and it is the home the canvas component's own comment asks for — the `.wb` / `.wb-viewport`
 * height chain parked in `whiteboard-canvas.css` says "if W1 (or #1490) adds one, this belongs there
 * instead". Left where it is for now: moving it is a change to the canvas card's file, not this one.
 *
 * MOBILE-FIRST AND CONTAINER-FIRST per the Section-Authoring Law: the base rules ARE the 320 layout, the
 * wider ones are `@container` enhancements, and nothing here reads the viewport. */

.wb-lead {
    color: var(--text-muted);
    margin-block-end: var(--space-5);
    max-inline-size: 60ch;
}

/* The create form: stacked on a phone, inline once there is room. `align-items: end` so the button sits on
   the input's baseline rather than floating against its label. */
/* Wraps intrinsically rather than at a breakpoint: the input claims 16rem when it can and drops the button
   to its own line when it cannot. No query, so nothing to get wrong about which box is being measured. */
.wb-new {
    align-items: end;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-block-end: var(--space-6);
}

.wb-new > :first-child {
    flex: 1 1 16rem;
}

.wb-empty {
    color: var(--text-muted);
}

/* ★ THE BOARD GRID — INTRINSIC, WITH NO QUERY AT ALL.
 *
 * `auto-fill` + `minmax` already is the responsive mechanism: the column count falls out of the space
 * available, one column at 320 and as many as fit at 1280, with no breakpoint to pick and nothing to
 * re-tune when the shell changes.
 *
 * It replaced a `@container (min-width: 34rem)` rule that COULD NEVER MATCH. `container-type` was declared
 * on `.wb-boards` and the query then targeted `.wb-boards` — but an element cannot query its own container;
 * the query resolves against the nearest ANCESTOR container, so it silently never applied. The phone shot
 * looked perfect (one column is right at 375 either way) and only the 1280 shot showed the truth: one
 * full-width card per row. A query that cannot match is invisible until the case it was written for. */
.wb-boards {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    list-style: none;
    margin: 0;
    padding: 0;
}

/* `position: relative` is what the stretched link anchors to — the card becomes the hit area. */
.wb-board-card {
    block-size: 100%;
    position: relative;
    transition: border-color 120ms ease, transform 120ms ease;
}

/* ★ THE STRETCHED LINK. A pseudo-element over the whole card, so the TARGET is the card and not the words —
   which is what makes this thumb-usable and clears 44px by construction rather than by padding a phrase.
   The anchor keeps its href and its accessible name, so nothing is lost to a keyboard or a reader. */
.wb-board-link::after {
    content: "";
    inset: 0;
    position: absolute;
}

/* Feedback on the CARD, driven by focus/hover of the link inside it — `:has()` is what lets the parent
   respond, and it is why this needs no JS and no wrapper element. */
.wb-board-card:hover,
.wb-board-card:has(.wb-board-link:focus-visible) {
    border-color: var(--primary);
}

.wb-board-card:has(.wb-board-link:focus-visible) {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: no-preference) {
    .wb-board-card:hover {
        transform: translateY(-2px);
    }
}

/* The preview. A fixed aspect so a grid of boards reads as a grid rather than a ragged column, and the
   surface behind it is the board's own dotted plane at small scale — the same visual language as the thing
   it previews. */
.wb-board-thumb {
    aspect-ratio: 16 / 9;
    background-color: var(--surface);
    background-image: radial-gradient(circle, var(--line) 1px, transparent 1px);
    background-size: 12px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    margin-block: var(--space-3);
    overflow: hidden;
}

.wb-thumb {
    block-size: 100%;
    display: block;
    inline-size: 100%;
}

/* The honest-empty twin of the thumbnail: same box, so an empty board does not change the card's height and
   the grid stays even. */
.wb-board-blank {
    align-items: center;
    aspect-ratio: 16 / 9;
    border: 1px dashed var(--line);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    display: flex;
    font-size: var(--text-sm);
    justify-content: center;
    margin-block: var(--space-3);
}

.wb-board-meta {
    color: var(--text-muted);
    font-size: var(--text-sm);
    margin: 0;
}

