/* PublonCore UI — hyper-param design system. KEEP MINIMAL (≤1,000 LOC).
 * See PublonCore.md → "Keeping css / ui / bindings / services lean".
 * Layers: 1 hp roots · 2 derived tokens · 3 dark · 4 reset · 5 utilities
 *         · 6 component skins · 7 themes · 8 responsive + animations */

/* ---- 1. Hyper-parameters — font stacks + every dial. */
:root {
    /* Sans-serif stacks */
    --hp-font-stack-system:    ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --hp-font-stack-inter:     "Inter", "Inter var", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --hp-font-stack-manrope:   "Manrope", "Inter", ui-sans-serif, system-ui, sans-serif;
    --hp-font-stack-dmsans:    "DM Sans", "Inter", ui-sans-serif, system-ui, sans-serif;
    --hp-font-stack-grotesk:   "Space Grotesk", "Söhne", "HK Grotesk", "Inter", ui-sans-serif, sans-serif;
    --hp-font-stack-ibmplex:   "IBM Plex Sans", "Helvetica Neue", system-ui, sans-serif;
    --hp-font-stack-jakarta:   "Plus Jakarta Sans", "Inter", system-ui, sans-serif;
    --hp-font-stack-public:    "Public Sans", "Inter", system-ui, sans-serif;
    --hp-font-stack-albert:    "Albert Sans", "Inter", system-ui, sans-serif;
    --hp-font-stack-outfit:    "Outfit", "Inter", system-ui, sans-serif;
    --hp-font-stack-work:      "Work Sans", "Inter", system-ui, sans-serif;
    --hp-font-stack-roboto:    "Roboto", "Helvetica Neue", system-ui, sans-serif;
    --hp-font-stack-helvetica: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
    --hp-font-stack-humanist:  "Optima", "Lucida Sans", "Trebuchet MS", "Verdana", "Tahoma", system-ui, sans-serif;
    --hp-font-stack-geometric: "Avenir Next", "Avenir", "Futura", "Century Gothic", "URW Gothic", system-ui, sans-serif;
    --hp-font-stack-rounded:   "SF Pro Rounded", "Hiragino Maru Gothic ProN", -apple-system, ui-rounded, system-ui, sans-serif;
    --hp-font-stack-nunito:    "Nunito", "Quicksand", "Comfortaa", system-ui, sans-serif;
    --hp-font-stack-comfortaa: "Comfortaa", "Quicksand", "Nunito", system-ui, sans-serif;
    --hp-font-stack-quicksand: "Quicksand", "Nunito", system-ui, sans-serif;
    --hp-font-stack-condensed: "Roboto Condensed", "Archivo Narrow", "Helvetica Neue Condensed", "Arial Narrow", sans-serif;
    --hp-font-stack-narrow:    "Archivo Narrow", "Roboto Condensed", "Arial Narrow", sans-serif;
    --hp-font-stack-oswald:    "Oswald", "Bebas Neue", "Archivo Narrow", "Impact", sans-serif;
    --hp-font-stack-bebas:     "Bebas Neue", "Oswald", "Impact", sans-serif;
    --hp-font-stack-display:   "SF Pro Display", "Inter", "Helvetica Neue", system-ui, sans-serif;

    /* Serif stacks */
    --hp-font-stack-serif:     ui-serif, "New York", "Iowan Old Style", "Charter", Georgia, "Times New Roman", serif;
    --hp-font-stack-ibmplexs:  "IBM Plex Serif", "Source Serif Pro", Georgia, serif;
    --hp-font-stack-source:    "Source Serif 4", "Source Serif Pro", Georgia, serif;
    --hp-font-stack-georgia:   Georgia, "Iowan Old Style", "Times New Roman", Times, serif;
    --hp-font-stack-times:     "Times New Roman", Times, "Liberation Serif", serif;
    --hp-font-stack-garamond:  "EB Garamond", "Garamond", "Hoefler Text", "Palatino", serif;
    --hp-font-stack-cormorant: "Cormorant Garamond", "EB Garamond", "Hoefler Text", serif;
    --hp-font-stack-caslon:    "Libre Caslon Text", "Big Caslon", "Hoefler Text", serif;
    --hp-font-stack-palatino:  "Palatino", "Palatino Linotype", "Book Antiqua", "URW Palladio L", serif;
    --hp-font-stack-merri:     "Merriweather", "Source Serif Pro", Georgia, serif;
    --hp-font-stack-lora:      "Lora", "Merriweather", Georgia, serif;
    --hp-font-stack-playfair:  "Playfair Display", "Source Serif Pro", "Times New Roman", serif;
    --hp-font-stack-slab:      "Roboto Slab", "Rockwell", Georgia, serif;

    /* Handwritten / display */
    --hp-font-stack-caveat:    "Caveat", "Brush Script MT", cursive;
    --hp-font-stack-kalam:     "Kalam", "Caveat", "Brush Script MT", cursive;

    /* Monospace stacks */
    --hp-font-stack-mono:      ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    --hp-font-stack-jetbrains: "JetBrains Mono", "Fira Code", ui-monospace, "SF Mono", Menlo, monospace;
    --hp-font-stack-fira:      "Fira Code", "Source Code Pro", ui-monospace, Menlo, monospace;
    --hp-font-stack-ibmmono:   "IBM Plex Mono", "JetBrains Mono", ui-monospace, Menlo, monospace;
    --hp-font-stack-source-code: "Source Code Pro", "Fira Code", ui-monospace, Menlo, monospace;
    --hp-font-stack-roboto-mono: "Roboto Mono", "JetBrains Mono", ui-monospace, Menlo, monospace;
    --hp-font-stack-courier:   "Courier New", Courier, "Liberation Mono", monospace;

    /* Defaults */
    --hp-font-body:    var(--hp-font-stack-dmsans);
    --hp-font-heading: var(--hp-font-stack-outfit);
    --hp-font-mono:    var(--hp-font-stack-mono);
    --hp-weight-body:    300;
    --hp-weight-heading: 600;
    --hp-text-scale: 0.82;        /* multiplies every text size */
    --hp-text-ratio: 1.18;        /* type scale (modular) */
    --hp-line-height: 1.5;
    --hp-density: 1;              /* spacing multiplier */
    --hp-pad-base: 0.5rem;        /* base padding unit */
    --hp-margin-base: 0.5rem;     /* base margin unit */
    --hp-search-input-max-w: 14rem; /* sensible cap for short queries (~12 chars) */
    --hp-modal-max-w: 600px;        /* modal max width (clamped by 90vw at runtime) */
    --hp-drawer-w: 420px;           /* drawer width (clamped by 90vw at runtime) */
    --hp-avatar-size: 2.4rem;       /* default avatar circle */
    --hp-active-marker-w: 3px;      /* left/top stripe on selected list/card rows */
    --hp-tab-underline-w: 2px;      /* tab bottom border (active + transparent reserve) */
    --hp-on-primary-muted: rgba(255,255,255,0.92);   /* subtitle / inactive text on dark surface — WCAG AA: ≥0.9 keeps small text ≥4.5:1 on the darkened primary */
    --hp-on-primary-bg-soft: rgba(255,255,255,0.10); /* hover overlay on dark surface */
    --hp-on-primary-bg-strong: rgba(255,255,255,0.18); /* active overlay / brand-mark on dark */
    --hp-on-primary-divider: rgba(255,255,255,0.25); /* subtle 1px line on dark surface */
    --hp-corner-scale: 0;
    --hp-border-scale: 0.4;
    --hp-shadow-intensity: 0.12;
    --hp-transition-speed: 1;
    --hp-hue-1: 205; --hp-sat-1: 88%; --hp-l-shift-1: 0%;
    --hp-hue-2: 35;  --hp-sat-2: 78%; --hp-l-shift-2: 0%;
    --hp-hue-accent: 350; --hp-sat-accent: 78%; --hp-l-shift-accent: 0%;
    --hp-hue-danger:  4;   --hp-sat-danger:  82%;
    --hp-hue-warn:    32;  --hp-sat-warn:    94%;
    --hp-hue-success: 142; --hp-sat-success: 72%;
    --hp-hue-info:    200; --hp-sat-info:    82%;
    --hp-l-bg: 100%; --hp-l-fg: 12%; --hp-l-muted: 40%; --hp-l-border: 88%;
    --hp-l-elev: 99%; --hp-l-sunken: 96%;
}

/* ---- 2. Derived tokens — redeclared on every theme anchor so child
 * overrides of --hp-* re-derive --ui-* tokens (substitution freezes at
 * the declaration site). See ./README.md §Hyper-parameter system. */
:root, [class*="ui-theme-"], [data-theme] {
    --ui-font-body:    var(--hp-font-body);
    --ui-font-heading: var(--hp-font-heading);
    --ui-font-mono:    var(--hp-font-mono);

    --ui-text-2xs: calc(0.65rem  * var(--hp-text-scale));
    --ui-text-xs:  calc(0.75rem  * var(--hp-text-scale));
    --ui-text-sm:  calc(0.875rem * var(--hp-text-scale));
    --ui-text-base:calc(1rem     * var(--hp-text-scale));
    --ui-text-lg:  calc(1rem     * var(--hp-text-scale) * var(--hp-text-ratio));
    --ui-text-xl:  calc(var(--ui-text-lg)  * var(--hp-text-ratio));
    --ui-text-2xl: calc(var(--ui-text-xl)  * var(--hp-text-ratio));
    --ui-text-3xl: calc(var(--ui-text-2xl) * var(--hp-text-ratio));
    --ui-text-4xl: calc(var(--ui-text-3xl) * var(--hp-text-ratio));

    --ui-font-thin:       100;
    --ui-font-extralight: 200;
    --ui-font-light:      300;
    --ui-font-normal:     400;
    --ui-font-medium:     500;
    --ui-font-semibold:   600;
    --ui-font-bold:       700;
    --ui-font-extrabold:  800;
    --ui-font-black:      900;
    --ui-weight-body:    var(--hp-weight-body);
    --ui-weight-heading: var(--hp-weight-heading);

    --ui-line-height: var(--hp-line-height);

    /* Spacing scale — multiples of pad-base × density */
    --ui-space-0: 0;
    --ui-space-1: calc(var(--hp-pad-base) * 0.5  * var(--hp-density));
    --ui-space-2: calc(var(--hp-pad-base) * 1    * var(--hp-density));
    --ui-space-3: calc(var(--hp-pad-base) * 1.5  * var(--hp-density));
    --ui-space-4: calc(var(--hp-pad-base) * 2    * var(--hp-density));
    --ui-space-5: calc(var(--hp-pad-base) * 2.5  * var(--hp-density));
    --ui-space-6: calc(var(--hp-pad-base) * 3    * var(--hp-density));
    --ui-space-7: calc(var(--hp-pad-base) * 4    * var(--hp-density));
    --ui-space-8: calc(var(--hp-pad-base) * 5    * var(--hp-density));
    --ui-space-10:calc(var(--hp-pad-base) * 6    * var(--hp-density));
    --ui-search-input-max-w: var(--hp-search-input-max-w);
    --ui-modal-max-w: var(--hp-modal-max-w);
    --ui-drawer-w: var(--hp-drawer-w);
    --ui-avatar-size: var(--hp-avatar-size);
    --ui-active-marker-w: var(--hp-active-marker-w);
    --ui-tab-underline-w: var(--hp-tab-underline-w);
    --ui-on-primary-muted: var(--hp-on-primary-muted);
    --ui-on-primary-bg-soft: var(--hp-on-primary-bg-soft);
    --ui-on-primary-bg-strong: var(--hp-on-primary-bg-strong);
    --ui-on-primary-divider: var(--hp-on-primary-divider);

    --ui-radius-none: 0;
    --ui-radius-sm:  calc(0.25rem  * var(--hp-corner-scale));
    --ui-radius-md:  calc(0.375rem * var(--hp-corner-scale));
    --ui-radius-lg:  calc(0.5rem   * var(--hp-corner-scale));
    --ui-radius-xl:  calc(0.75rem  * var(--hp-corner-scale));
    --ui-radius-2xl: calc(1rem     * var(--hp-corner-scale));
    --ui-radius-full: 9999px;

    --ui-border-width: calc(1px * var(--hp-border-scale));

    --ui-shadow-none: none;
    --ui-shadow-sm:  0 1px 2px rgba(15,23,42, calc(var(--hp-shadow-intensity) * 1.5));
    --ui-shadow-md:  0 4px 8px rgba(15,23,42, calc(var(--hp-shadow-intensity) * 2));
    --ui-shadow-lg:  0 8px 22px rgba(15,23,42, calc(var(--hp-shadow-intensity) * 2.5));
    --ui-shadow-xl:  0 18px 40px rgba(15,23,42, calc(var(--hp-shadow-intensity) * 3));
    --ui-shadow-inner: inset 0 1px 2px rgba(15,23,42, calc(var(--hp-shadow-intensity) * 2));

    --ui-transition-fast:  calc(120ms * var(--hp-transition-speed));
    --ui-transition-base:  calc(200ms * var(--hp-transition-speed));
    --ui-transition-slow:  calc(380ms * var(--hp-transition-speed));
    --ui-ease: cubic-bezier(0.4, 0, 0.2, 1);

    /* Colour scales — derived from HSL hyper-params + per-color lightness shift */
    --ui-primary-50:  hsl(var(--hp-hue-1), var(--hp-sat-1), calc(97% + var(--hp-l-shift-1)));
    --ui-primary-100: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(92% + var(--hp-l-shift-1)));
    --ui-primary-200: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(83% + var(--hp-l-shift-1)));
    --ui-primary-300: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(70% + var(--hp-l-shift-1)));
    --ui-primary-400: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(60% + var(--hp-l-shift-1)));
    --ui-primary-500: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(52% + var(--hp-l-shift-1)));
    --ui-primary-600: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(45% + var(--hp-l-shift-1)));
    --ui-primary-700: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(38% + var(--hp-l-shift-1)));
    --ui-primary-800: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(28% + var(--hp-l-shift-1)));
    --ui-primary-900: hsl(var(--hp-hue-1), var(--hp-sat-1), calc(18% + var(--hp-l-shift-1)));
    --ui-primary: var(--ui-primary-600);
    --ui-on-primary: #fff;

    --ui-secondary-100: hsl(var(--hp-hue-2), var(--hp-sat-2), calc(92% + var(--hp-l-shift-2)));
    --ui-secondary-500: hsl(var(--hp-hue-2), var(--hp-sat-2), calc(52% + var(--hp-l-shift-2)));
    --ui-secondary-600: hsl(var(--hp-hue-2), var(--hp-sat-2), calc(45% + var(--hp-l-shift-2)));
    --ui-secondary-700: hsl(var(--hp-hue-2), var(--hp-sat-2), calc(38% + var(--hp-l-shift-2)));
    --ui-secondary: var(--ui-secondary-600);
    --ui-on-secondary: #fff;

    --ui-accent-500: hsl(var(--hp-hue-accent), var(--hp-sat-accent), calc(50% + var(--hp-l-shift-accent)));
    --ui-accent-600: hsl(var(--hp-hue-accent), var(--hp-sat-accent), calc(42% + var(--hp-l-shift-accent)));
    --ui-accent: var(--ui-accent-600);

    --ui-danger-100: hsl(var(--hp-hue-danger), var(--hp-sat-danger), 95%);
    --ui-danger-500: hsl(var(--hp-hue-danger), var(--hp-sat-danger), 50%);
    --ui-danger-600: hsl(var(--hp-hue-danger), var(--hp-sat-danger), 45%);
    --ui-danger: var(--ui-danger-600);
    --ui-warn-100:    hsl(var(--hp-hue-warn), var(--hp-sat-warn), 95%);
    --ui-warn-500:    hsl(var(--hp-hue-warn), var(--hp-sat-warn), 50%);
    /* WCAG AA: white text on these as pill/badge backgrounds needs ≥4.5:1, so
       the solid tones are darkened (warn 45→36, success 38→32). */
    --ui-warn:        hsl(var(--hp-hue-warn), var(--hp-sat-warn), 36%);
    /* Alias: every sibling severity token is a full word (danger/success/info)
       — `--ui-warning` kept guessable; var(--ui-warning) resolving INVALID was
       a silent no-colour trap (/fullbind spatial S7). */
    --ui-warning:     var(--ui-warn);
    --ui-success-100: hsl(var(--hp-hue-success), var(--hp-sat-success), 94%);
    --ui-success-500: hsl(var(--hp-hue-success), var(--hp-sat-success), 42%);
    --ui-success:     hsl(var(--hp-hue-success), var(--hp-sat-success), 29%);
    --ui-info-100:    hsl(var(--hp-hue-info), var(--hp-sat-info), 94%);
    --ui-info-500:    hsl(var(--hp-hue-info), var(--hp-sat-info), 50%);
    --ui-info:        hsl(var(--hp-hue-info), var(--hp-sat-info), 42%);

    /* Categorical SERIES palette for charts. Eleven services each carried a
       private hard-coded copy of this exact list (#8), so a re-skin moved the
       interface and left every chart behind. Read it through
       ui.seriesColors(n) — Plotly/vis need concrete colours, not var(). */
    --ui-series-1: hsl(var(--hp-hue-1), 70%, 60%);
    --ui-series-2: hsl(var(--hp-hue-success), 65%, 40%);
    --ui-series-3: hsl(var(--hp-hue-warn), 90%, 50%);
    --ui-series-4: hsl(330, 80%, 60%);
    --ui-series-5: hsl(265, 70%, 60%);
    --ui-series-6: hsl(190, 85%, 45%);
    --ui-series-7: hsl(var(--hp-hue-danger, 0), 80%, 60%);
    --ui-series-8: hsl(85, 65%, 45%);

    /* Greys derived from primary hue (low sat) */
    --ui-gray-50:  hsl(var(--hp-hue-1), 12%, 98%);
    --ui-gray-100: hsl(var(--hp-hue-1), 12%, 96%);
    --ui-gray-200: hsl(var(--hp-hue-1), 10%, 90%);
    --ui-gray-300: hsl(var(--hp-hue-1), 10%, 82%);
    --ui-gray-400: hsl(var(--hp-hue-1), 8%,  64%);
    --ui-gray-500: hsl(var(--hp-hue-1), 8%,  48%);
    --ui-gray-600: hsl(var(--hp-hue-1), 8%,  36%);
    --ui-gray-700: hsl(var(--hp-hue-1), 8%,  26%);
    --ui-gray-800: hsl(var(--hp-hue-1), 8%,  18%);
    --ui-gray-900: hsl(var(--hp-hue-1), 8%,  10%);

    --ui-bg:       hsl(var(--hp-hue-1), 12%, var(--hp-l-bg));
    --ui-bg-elev:  hsl(var(--hp-hue-1), 12%, var(--hp-l-elev));
    --ui-bg-sunken:hsl(var(--hp-hue-1), 12%, var(--hp-l-sunken));
    --ui-bg-muted: var(--ui-gray-100);
    --ui-fg:       hsl(var(--hp-hue-1), 18%, var(--hp-l-fg));
    --ui-fg-muted: hsl(var(--hp-hue-1), 10%, var(--hp-l-muted));
    --ui-border:   hsl(var(--hp-hue-1), 12%, var(--hp-l-border));
    --ui-border-strong: hsl(var(--hp-hue-1), 12%, 78%);
}

/* ---- 3. Dark theme — declarations duplicated in @media so users get
 * either explicit `data-theme="dark"` or system preference. */
[data-theme="dark"], .ui-theme-dark {
    --hp-l-bg: 8%; --hp-l-elev: 12%; --hp-l-sunken: 5%;
    --hp-l-fg: 95%; --hp-l-muted: 65%; --hp-l-border: 22%;
    --hp-shadow-intensity: 0.45;
    --ui-gray-50:  hsl(var(--hp-hue-1), 12%, 12%);
    --ui-gray-100: hsl(var(--hp-hue-1), 12%, 16%);
    --ui-gray-200: hsl(var(--hp-hue-1), 10%, 22%);
    --ui-gray-300: hsl(var(--hp-hue-1), 10%, 32%);
    --ui-bg-muted: var(--ui-gray-100);
    --ui-border-strong: hsl(var(--hp-hue-1), 12%, 32%);
    /* Semantic 100-level (alert/callout/badge) backgrounds need to darken
     * too, otherwise we get pale-on-light text. */
    --ui-primary-100: hsl(var(--hp-hue-1), 35%, 18%);
    --ui-secondary-100: hsl(var(--hp-hue-2), 35%, 18%);
    --ui-info-100:    hsl(var(--hp-hue-info), 35%, 18%);
    --ui-danger-100:  hsl(var(--hp-hue-danger), 35%, 18%);
    --ui-warn-100:    hsl(var(--hp-hue-warn), 35%, 18%);
    --ui-success-100: hsl(var(--hp-hue-success), 35%, 18%);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --hp-l-bg: 8%; --hp-l-elev: 12%; --hp-l-sunken: 5%;
        --hp-l-fg: 95%; --hp-l-muted: 65%; --hp-l-border: 22%;
        --hp-shadow-intensity: 0.45;
        --ui-gray-50:  hsl(var(--hp-hue-1), 12%, 12%);
        --ui-gray-100: hsl(var(--hp-hue-1), 12%, 16%);
        --ui-gray-200: hsl(var(--hp-hue-1), 10%, 22%);
        --ui-gray-300: hsl(var(--hp-hue-1), 10%, 32%);
        --ui-bg-muted: var(--ui-gray-100);
        --ui-border-strong: hsl(var(--hp-hue-1), 12%, 32%);
        --ui-primary-100: hsl(var(--hp-hue-1), 35%, 18%);
        --ui-secondary-100: hsl(var(--hp-hue-2), 35%, 18%);
        --ui-info-100:    hsl(var(--hp-hue-info), 35%, 18%);
        --ui-danger-100:  hsl(var(--hp-hue-danger), 35%, 18%);
        --ui-warn-100:    hsl(var(--hp-hue-warn), 35%, 18%);
        --ui-success-100: hsl(var(--hp-hue-success), 35%, 18%);
    }
}

/* ---- 4. Reset + base ---- */
*, *::before, *::after { box-sizing: border-box; }
body {
    margin: 0; font-family: var(--ui-font-body); font-size: var(--ui-text-base);
    font-weight: var(--ui-weight-body);
    line-height: var(--ui-line-height); color: var(--ui-fg); background: var(--ui-bg);
    -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 { font-family: var(--ui-font-heading); margin: 0 0 var(--ui-space-3); font-weight: var(--ui-weight-heading); line-height: 1.2; letter-spacing: -0.01em; }
h1 { font-size: var(--ui-text-3xl); }
h2 { font-size: var(--ui-text-2xl); }
h3 { font-size: var(--ui-text-xl); }
h4 { font-size: var(--ui-text-lg); }
h5, h6 { font-size: var(--ui-text-base); }
p { margin: 0 0 var(--ui-space-3); }
a { color: var(--ui-primary); text-decoration: none; }
a:hover { text-decoration: underline; }
code, kbd, pre, samp { font-family: var(--ui-font-mono); font-size: 0.92em; }

/* ---- 5. Utilities ---- */
.ui-hidden { display: none !important; }
.ui-flex, .ui-d-flex { display: flex; }
.ui-flex-col { flex-direction: column; }
.ui-flex-wrap { flex-wrap: wrap; }
.ui-flex-1 { flex: 1 1 auto; min-width: 0; min-height: 0; }
.ui-self-start { align-self: flex-start; }   /* compact button in a flex-col (design rule: buttons are content-sized) */
.ui-flex-2 { flex: 2 1 0; min-width: 0; min-height: 0; }
.ui-flex-3 { flex: 3 1 0; min-width: 0; min-height: 0; }
.ui-grid { display: grid; }
.ui-grid-auto { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.ui-grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ui-grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ui-grid-cols-1-2 { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.ui-grid-cols-2-1 { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.ui-readable { max-width: 64rem; width: 100%; }   /* constrain long-form prose to a readable column */
.ui-h-tall { height: clamp(20rem, 50vh, 36rem); }
.ui-h-medium { height: clamp(14rem, 35vh, 24rem); }
.ui-items-center { align-items: center; }
.ui-items-start { align-items: flex-start; }
.ui-items-end { align-items: flex-end; }
.ui-justify-between { justify-content: space-between; }
.ui-justify-center { justify-content: center; }
.ui-justify-end { justify-content: flex-end; }
.ui-min-h-0 { min-height: 0; }
.ui-min-w-0 { min-width: 0; }
.ui-overflow-auto { overflow: auto; }
/* .ui-card sets overflow:hidden (for its rounded corners) and, being defined
 * later at equal specificity, would clobber .ui-overflow-auto on a card that is
 * meant to scroll. This compound selector restores the scroll without !important. */
.ui-card.ui-overflow-auto { overflow: auto; }
.ui-overflow-hidden { overflow: hidden; }
.ui-h-full { height: 100%; }
.ui-w-full { width: 100%; }
.ui-block { display: block; }
.ui-col { display: flex; flex-direction: column; }
.ui-relative { position: relative; }

.ui-gap-1 { gap: var(--ui-space-1); } .ui-gap-2 { gap: var(--ui-space-2); }
.ui-gap-3 { gap: var(--ui-space-3); } .ui-gap-4 { gap: var(--ui-space-4); }
.ui-gap-5 { gap: var(--ui-space-5); } .ui-gap-6 { gap: var(--ui-space-6); }

.ui-p-0 { padding: 0; } .ui-p-1 { padding: var(--ui-space-1); }
.ui-p-2 { padding: var(--ui-space-2); } .ui-p-3 { padding: var(--ui-space-3); }
.ui-p-4 { padding: var(--ui-space-4); } .ui-p-5 { padding: var(--ui-space-5); }
.ui-p-6 { padding: var(--ui-space-6); }
.ui-px-2 { padding-inline: var(--ui-space-2); } .ui-px-3 { padding-inline: var(--ui-space-3); }
.ui-px-4 { padding-inline: var(--ui-space-4); } .ui-px-5 { padding-inline: var(--ui-space-5); }
.ui-py-1 { padding-block: var(--ui-space-1); } .ui-py-2 { padding-block: var(--ui-space-2); }
.ui-py-3 { padding-block: var(--ui-space-3); } .ui-py-4 { padding-block: var(--ui-space-4); }
.ui-m-0 { margin: 0; } .ui-mt-1 { margin-top: var(--ui-space-1); }
.ui-mt-2 { margin-top: var(--ui-space-2); } .ui-mt-3 { margin-top: var(--ui-space-3); }
.ui-mt-4 { margin-top: var(--ui-space-4); } .ui-mb-2 { margin-bottom: var(--ui-space-2); }
.ui-mb-3 { margin-bottom: var(--ui-space-3); } .ui-mb-4 { margin-bottom: var(--ui-space-4); }

.ui-text-xs { font-size: var(--ui-text-xs); } .ui-text-sm { font-size: var(--ui-text-sm); }
.ui-text-base { font-size: var(--ui-text-base); } .ui-text-lg { font-size: var(--ui-text-lg); }
.ui-text-xl { font-size: var(--ui-text-xl); } .ui-text-2xl { font-size: var(--ui-text-2xl); }
.ui-text-3xl { font-size: var(--ui-text-3xl); }
.ui-font-thin       { font-weight: var(--ui-font-thin); }
.ui-font-extralight { font-weight: var(--ui-font-extralight); }
.ui-font-light      { font-weight: var(--ui-font-light); }
.ui-font-normal     { font-weight: var(--ui-font-normal); }
.ui-font-medium     { font-weight: var(--ui-font-medium); }
.ui-font-semibold   { font-weight: var(--ui-font-semibold); }
.ui-font-bold       { font-weight: var(--ui-font-bold); }
.ui-font-extrabold  { font-weight: var(--ui-font-extrabold); }
.ui-font-black      { font-weight: var(--ui-font-black); }
.ui-font-mono { font-family: var(--ui-font-mono); }
.ui-font-heading { font-family: var(--ui-font-heading); }
.ui-font-body { font-family: var(--ui-font-body); }
.ui-text-muted { color: var(--ui-fg-muted); }
.ui-text-success { color: var(--ui-success); }
.ui-text-danger  { color: var(--ui-danger); }
.ui-text-warn    { color: var(--ui-warn); }
.ui-text-center { text-align: center; }
.ui-text-right  { text-align: right; }
.ui-list-plain  { list-style: none; padding-left: 0; }
.ui-truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ui-clickable { cursor: pointer; }

.ui-bg-muted { background: var(--ui-bg-muted); }
.ui-bg-elev  { background: var(--ui-bg-elev); }
.ui-bg-primary { background: var(--ui-primary); color: var(--ui-on-primary); }
.ui-color-primary { color: var(--ui-primary); }
.ui-color-primary-fade { color: var(--ui-primary-700); opacity: 0.85; }
.ui-color-danger  { color: var(--ui-danger); }
.ui-color-success { color: var(--ui-success); }
.ui-color-warn    { color: var(--ui-warn); }

.ui-radius-sm { border-radius: var(--ui-radius-sm); }
.ui-radius-md { border-radius: var(--ui-radius-md); }
.ui-radius-lg { border-radius: var(--ui-radius-lg); }
.ui-shadow-sm { box-shadow: var(--ui-shadow-sm); }
.ui-shadow-md { box-shadow: var(--ui-shadow-md); }
.ui-shadow-lg { box-shadow: var(--ui-shadow-lg); }
.ui-border { border: var(--ui-border-width) solid var(--ui-border); }

/* Status colour helpers used by chips/badges */
.ui-gray-50 { background: var(--ui-gray-50); } .ui-gray-100 { background: var(--ui-gray-100); }
.ui-gray-200 { background: var(--ui-gray-200); } .ui-gray-300 { background: var(--ui-gray-300); }

/* ---- 6. Component skins ---- */

/* Buttons */
.ui-btn {
    display: inline-flex; align-items: center; gap: var(--ui-space-2); white-space: nowrap;
    padding: var(--ui-space-1) var(--ui-space-3);
    border: var(--ui-border-width) solid var(--ui-border);
    border-radius: var(--ui-radius-md); background: var(--ui-bg-elev);
    color: var(--ui-fg); font: inherit; font-weight: var(--ui-font-medium);
    cursor: pointer; transition: background var(--ui-transition-fast) var(--ui-ease),
        border-color var(--ui-transition-fast) var(--ui-ease),
        transform var(--ui-transition-fast) var(--ui-ease),
        box-shadow var(--ui-transition-fast) var(--ui-ease);
    line-height: 1.2; text-decoration: none; user-select: none;
}
.ui-btn:hover { background: var(--ui-gray-100); border-color: var(--ui-border-strong); }
.ui-btn:active { transform: translateY(1px); }
.ui-btn:disabled, .ui-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
.ui-btn-primary {
    background: var(--ui-primary);
    color: var(--ui-on-primary); border-color: var(--ui-primary);
}
.ui-btn-primary:hover {
    background: var(--ui-primary-700); border-color: var(--ui-primary-700);
}
.ui-btn-danger  { background: var(--ui-danger);  color: #fff; border-color: var(--ui-danger); }
.ui-btn-danger:hover  { background: var(--ui-danger-600); }
.ui-btn-secondary { background: var(--ui-primary-50); color: var(--ui-primary-700); border-color: var(--ui-primary-100); }
.ui-btn-secondary:hover { background: var(--ui-primary-100); border-color: var(--ui-primary-500); }
.ui-btn-warning { background: var(--ui-warn-100); color: var(--ui-warn); border-color: var(--ui-warn-100); }
.ui-btn-warning:hover { border-color: var(--ui-warn-500); }
.ui-btn-ghost { background: transparent; border-color: transparent; color: var(--ui-fg-muted); }
.ui-btn-ghost:hover { background: var(--ui-bg-muted); color: var(--ui-fg); }
.ui-btn-sm { padding: var(--ui-space-1) var(--ui-space-3); font-size: var(--ui-text-sm); }
.ui-btn-lg { padding: var(--ui-space-3) var(--ui-space-5); font-size: var(--ui-text-lg); }
.ui-ml-auto { margin-left: auto; }
.ui-btn-icon, .ui-icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2rem; height: 2rem; border: var(--ui-border-width) solid transparent;
    border-radius: var(--ui-radius-md); background: transparent; color: inherit; cursor: pointer;
    transition: background var(--ui-transition-fast) var(--ui-ease);
}
.ui-btn-icon:hover, .ui-icon-btn:hover { background: var(--ui-bg-muted); }

/* Inputs */
.ui-input, .ui-select, .ui-textarea {
    display: block; width: 100%;
    padding: var(--ui-space-1) var(--ui-space-2);
    font: inherit; font-size: var(--ui-text-sm); color: var(--ui-fg); background: var(--ui-bg-elev);
    border: var(--ui-border-width) solid var(--ui-border);
    border-radius: var(--ui-radius-md);
    transition: border-color var(--ui-transition-fast), box-shadow var(--ui-transition-fast);
}
.ui-input:focus, .ui-select:focus, .ui-textarea:focus {
    outline: none; border-color: var(--ui-primary);
    box-shadow: 0 0 0 3px hsl(var(--hp-hue-1) var(--hp-sat-1) 60% / 0.20);
}
.ui-textarea { resize: vertical; min-height: calc(var(--ui-space-4) * 4); }
.ui-datetime { display: flex; gap: var(--ui-space-2); align-items: center; }
.ui-datetime-date { flex: 3 1 0; min-width: 0; }
.ui-datetime-time { flex: 2 1 0; min-width: 0; }
.ui-tablesearch { display: flex; gap: var(--ui-space-1); align-items: center; }
.ui-tablesearch-btn { flex: 1; text-align: left; cursor: pointer; }
.ui-tablesearch-btn.ui-tablesearch-empty { color: var(--ui-fg-muted); }
.ui-tablesearch-clear { background: none; border: 0; cursor: pointer; color: var(--ui-fg-muted); padding: 0 var(--ui-space-1); }
.ui-tablesearch-btn.ui-tablesearch-invalid { border-color: var(--ui-danger); }
.ui-input-search { padding-left: var(--ui-space-7); background-position: var(--ui-space-2) center; background-repeat: no-repeat; }

.ui-checkbox { display: inline-flex; align-items: center; gap: var(--ui-space-2); cursor: pointer; user-select: none; }
.ui-checkbox input { position: absolute; opacity: 0; pointer-events: none; }
.ui-checkbox-mark {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.1rem; height: 1.1rem; border-radius: var(--ui-radius-sm);
    border: var(--ui-border-width) solid var(--ui-border-strong); background: var(--ui-bg-elev);
    transition: background var(--ui-transition-fast), border-color var(--ui-transition-fast);
}
.ui-checkbox input:checked + .ui-checkbox-mark { background: var(--ui-primary); border-color: var(--ui-primary); color: #fff; }
.ui-checkbox input:checked + .ui-checkbox-mark::after { content: "✓"; font-size: 0.85rem; line-height: 1; color: #fff; }

.ui-switch { display: inline-flex; align-items: center; gap: var(--ui-space-2); cursor: pointer; }
.ui-switch input { position: absolute; opacity: 0; }
.ui-switch-knob {
    position: relative; width: 2.4rem; height: 1.3rem; border-radius: var(--ui-radius-full);
    background: var(--ui-gray-300); transition: background var(--ui-transition-base);
}
.ui-switch-knob::after {
    content: ""; position: absolute; top: 0.15rem; left: 0.15rem;
    width: 1rem; height: 1rem; border-radius: var(--ui-radius-full);
    background: #fff; box-shadow: var(--ui-shadow-sm);
    transition: left var(--ui-transition-base) var(--ui-ease);
}
.ui-switch input:checked + .ui-switch-knob { background: var(--ui-primary); }
.ui-switch input:checked + .ui-switch-knob::after { left: 1.25rem; }

/* Containers */
.ui-card {
    background: var(--ui-bg-elev); border: var(--ui-border-width) solid var(--ui-border);
    border-radius: var(--ui-radius-lg); padding: var(--ui-space-4);
    box-shadow: var(--ui-shadow-md);
    transition: box-shadow var(--ui-transition-base) var(--ui-ease),
                transform var(--ui-transition-base) var(--ui-ease);
    position: relative; overflow: hidden;
}
.ui-card:hover { box-shadow: var(--ui-shadow-lg); transform: translateY(-1px); }
/* Nested cards — suppress shadow + border + heavy padding so a card-within-a-card
   looks like a section, not a separate floating element. The outer card already
   carries the visual weight. */
.ui-card .ui-card {
    box-shadow: none; border-color: var(--ui-border-faint, var(--ui-gray-100));
    padding: var(--ui-space-3);
}
.ui-card .ui-card:hover { box-shadow: none; transform: none; }
.ui-card .ui-card .ui-card { border: none; padding: var(--ui-space-2); background: transparent; }

.ui-bind-entity-card:hover { border-left-color: var(--ui-primary-300); }
.ui-bind-entity-card.ui-active {
    border-left-color: var(--ui-primary);
    background: var(--ui-primary-50);
    box-shadow: var(--ui-shadow-lg);
}
.ui-bind-entity-card.ui-deselected { border-left-color: var(--ui-border); }
/* Vivid header variants — use behind .ui-card for emphasis. */
.ui-card-primary   { background: var(--ui-primary-100);   border-color: var(--ui-primary-200); border-left: 4px solid var(--ui-primary); }
.ui-card-secondary { background: var(--ui-secondary-100); border-color: var(--ui-secondary-100); border-left: 4px solid var(--ui-secondary); }
.ui-card-success   { background: var(--ui-success-100);   border-color: var(--ui-success-100);   border-left: 4px solid var(--ui-success); }
.ui-card-warn      { background: var(--ui-warn-100);      border-color: var(--ui-warn-100);      border-left: 4px solid var(--ui-warn); }
.ui-card-danger    { background: var(--ui-danger-100);    border-color: var(--ui-danger-100);    border-left: 4px solid var(--ui-danger); }
.ui-card-flat::before { display: none; }
/* flat = genuinely flat: object chips (theme cards, launcher rows) carry a
   hairline only — no elevation, no hover lift. */
.ui-card-flat, .ui-card-flat:hover { box-shadow: none; transform: none; }
.ui-card-header { display: flex; flex-direction: column; gap: var(--ui-space-1); margin-bottom: var(--ui-space-2); }
.ui-card-icon { font-size: var(--ui-text-xl); line-height: 1; }
.ui-card-title { display: block; font-size: var(--ui-text-lg); font-weight: var(--ui-font-semibold); color: var(--ui-fg); }
.ui-card-subtitle { display: block; color: var(--ui-fg-muted); font-size: var(--ui-text-sm); }
.ui-card-body { color: var(--ui-fg); }
.ui-card-footer { margin-top: var(--ui-space-3); padding-top: var(--ui-space-2); border-top: var(--ui-border-width) solid var(--ui-border); }
.ui-container { display: block; }
/* Centered readable-width column — for standalone forms / single-record views
   that should not sprawl across a wide viewport. Token-overridable max-width. */
.ui-readable { max-width: var(--ui-readable-max-w, 44rem); margin-inline: auto; width: 100%; }
.ui-grid { gap: var(--ui-space-4); }
/* Dense aligned settings/editor grid — snug label column + content fields,
   left-packed, tight gap, vertically centred. For label · value · value rows. */
.ui-grid-settings { display: grid; grid-template-columns: minmax(7rem, 10rem) minmax(0, 18rem) minmax(0, 18rem);
    gap: var(--ui-space-1) var(--ui-space-3); align-items: center; }
.ui-divider { height: 1px; background: var(--ui-border); margin: var(--ui-space-3) 0; border: 0; }

/* Modal + drawer share overlay */
.ui-overlay {
    position: fixed; inset: 0; background: rgba(15,23,42,0.45);
    display: flex; align-items: center; justify-content: center;
    opacity: 0; pointer-events: none; transition: opacity var(--ui-transition-base);
    z-index: 100;
}
.ui-overlay.ui-overlay-visible, .ui-overlay.ui-active { opacity: 1; pointer-events: auto; }
.ui-modal {
    background: var(--ui-bg-elev); border-radius: var(--ui-radius-xl);
    box-shadow: var(--ui-shadow-xl); padding: var(--ui-space-5);
    max-width: min(90vw, var(--ui-modal-max-w)); max-height: 90vh; overflow: auto;
    transform: scale(0.96); transition: transform var(--ui-transition-base) var(--ui-ease);
}
.ui-overlay.ui-active .ui-modal, .ui-overlay.ui-overlay-visible .ui-modal { transform: scale(1); }
/* Wide modal (working surfaces — e.g. AI import): widen via the token so themes can retune. */
.ui-modal-wide { --ui-modal-max-w: min(1200px, 94vw); }
.ui-modal-wide .ui-modal { width: min(1200px, 94vw); }
.ui-drawer {
    position: fixed; top: 0; right: 0; height: 100vh; width: min(90vw, var(--ui-drawer-w));
    background: var(--ui-bg-elev); box-shadow: var(--ui-shadow-xl); padding: var(--ui-space-5);
    transform: translateX(100%); transition: transform var(--ui-transition-base) var(--ui-ease);
    z-index: 110; overflow: auto;
}
.ui-drawer.ui-active { transform: translateX(0); }
/* A drawer mounted through the overlay port (uiOpenModal mode:'drawer') gets
   its open state from the OVERLAY's visible class — without this rule the
   drawer stays at translateX(100%) and every drawer-mode surface opens
   off-screen (found via Publon Bench, 2026-08-25). */
.ui-overlay.ui-overlay-visible .ui-drawer, .ui-overlay.ui-active .ui-drawer { transform: translateX(0); }

/* Badges + chips */
.ui-badge {
    display: inline-flex; align-items: center; gap: var(--ui-space-1);
    padding: 2px var(--ui-space-2); font-size: var(--ui-text-xs); font-weight: var(--ui-font-semibold);
    background: var(--ui-primary); color: var(--ui-on-primary);
    border-radius: var(--ui-radius-full); line-height: 1.4;
    text-transform: uppercase; letter-spacing: 0.04em;
    box-shadow: var(--ui-shadow-sm);
}
.ui-badge-muted   { background: var(--ui-bg-muted); color: var(--ui-fg-muted); text-transform: none; letter-spacing: 0; box-shadow: none; }
.ui-badge-info    { background: var(--ui-info);    color: #fff; }
.ui-badge-success { background: var(--ui-success); color: #fff; }
.ui-badge-warn    { background: var(--ui-warn);    color: #fff; }
.ui-badge-danger  { background: var(--ui-danger);  color: #fff; }
/* Severity scale — Low · Medium · High · Critical. Four bands that must be told
   apart at a glance, so Critical differs from High in WEIGHT and OUTLINE, not
   just shade: two reds side by side read as one. Consumers emit
   `ui-badge-` + severity.toLowerCase(); before these existed that produced an
   unstyled span and Critical looked exactly like High. */
.ui-sev-low, .ui-badge-low           { background: var(--ui-bg-muted);  color: var(--ui-fg-muted); }
.ui-sev-medium, .ui-badge-medium     { background: var(--ui-warn-500);  color: #fff; }
.ui-sev-high, .ui-badge-high         { background: var(--ui-warn);      color: #fff; }
.ui-sev-critical, .ui-badge-critical {
    background: var(--ui-danger); color: #fff; font-weight: 700;
    box-shadow: 0 0 0 2px var(--ui-danger-100), var(--ui-shadow-sm);
}
.ui-chipset { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
.ui-chip {
    display: inline-flex; align-items: center; gap: var(--ui-space-1);
    padding: var(--ui-space-1) var(--ui-space-3); font-size: var(--ui-text-sm);
    background: var(--ui-bg-muted); border-radius: var(--ui-radius-full);
    cursor: pointer; transition: background var(--ui-transition-fast), color var(--ui-transition-fast);
    user-select: none;
}
.ui-chip-on, .ui-chip.ui-active { background: var(--ui-primary); color: var(--ui-on-primary); }
.ui-chip-off { background: var(--ui-bg-muted); color: var(--ui-fg-muted); }
.ui-chip:hover { filter: brightness(0.96); }
.ui-chip-sm { padding: 0 var(--ui-space-2); font-size: var(--ui-text-xs); }

/* Alerts + callouts */
.ui-alert, .ui-callout {
    display: flex; gap: var(--ui-space-3); padding: var(--ui-space-3) var(--ui-space-4);
    border-left: 4px solid var(--ui-info); background: var(--ui-info-100); color: var(--ui-fg);
    border-radius: var(--ui-radius-md); margin-bottom: var(--ui-space-3);
}
.ui-callout-danger, .ui-alert-danger, .ui-alert-error { border-color: var(--ui-danger); background: var(--ui-danger-100); }
.ui-callout-warning, .ui-alert-warning { border-color: var(--ui-warn); background: var(--ui-warn-100); }
.ui-callout-success, .ui-alert-success { border-color: var(--ui-success); background: var(--ui-success-100); }
.ui-alert-icon, .ui-callout-icon { flex-shrink: 0; font-size: var(--ui-text-lg); }
.ui-alert-title, .ui-callout-title { font-weight: var(--ui-font-semibold); margin: 0 0 var(--ui-space-1); }
.ui-alert-body, .ui-callout-body { flex: 1 1 auto; min-width: 0; line-height: var(--ui-leading-normal); }
.ui-alert-body > p:first-child, .ui-callout-body > p:first-child { margin-top: 0; }
.ui-alert-body > p:last-child, .ui-callout-body > p:last-child { margin-bottom: 0; }
.ui-alert-close { margin-left: auto; cursor: pointer; opacity: 0.6; }
.ui-alert-close:hover { opacity: 1; }

/* Spinner + progress */
.ui-spinner {
    display: inline-block; width: 1.25rem; height: 1.25rem;
    border: 2px solid var(--ui-border); border-top-color: var(--ui-primary);
    border-radius: 50%; animation: ui-spin 0.8s linear infinite;
}
@keyframes ui-spin { to { transform: rotate(360deg); } }
.ui-progress {
    height: 6px; background: var(--ui-bg-muted); border-radius: var(--ui-radius-full); overflow: hidden;
}
.ui-progress-bar {
    height: 100%; background: var(--ui-primary); border-radius: inherit;
    transition: width var(--ui-transition-base) var(--ui-ease);
}
.ui-progress-bar.ui-bar-good { background: var(--ui-success); }
.ui-progress-bar.ui-bar-warn { background: var(--ui-warn); }
.ui-progress-bar.ui-bar-bad  { background: var(--ui-danger); }

/* Circular progress ring (progress shape:'ring' + report cards) */
.ui-progress-ring { position: relative; width: 64px; height: 64px; flex: 0 0 64px; }
/* Null-honest ring: unscored renders as a dash, never a 0% ring. */
.ui-ring-nodata { display: flex; align-items: center; justify-content: center; color: var(--ui-gray-400); font-size: var(--ui-text-xl); border: 2px dashed var(--ui-gray-200); border-radius: 50%; }
.ui-ring-svg   { width: 100%; height: 100%; transform: rotate(-90deg); }
.ui-ring-track { fill: none; stroke: var(--ui-bg-muted); stroke-width: 6; }
.ui-ring-fill  { fill: none; stroke-width: 6; stroke-linecap: round; transition: stroke-dashoffset var(--ui-transition-base) var(--ui-ease); }
.ui-ring-good  { stroke: var(--ui-success); }
.ui-ring-warn  { stroke: var(--ui-warn); }
.ui-ring-bad   { stroke: var(--ui-danger); }
.ui-ring-label { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: var(--ui-text-sm); font-weight: var(--ui-font-bold); }

/* Status pill */
.ui-pill         { display: inline-block; padding: 0 var(--ui-space-2); border-radius: var(--ui-radius-full); font-size: var(--ui-text-xs); font-weight: var(--ui-font-semibold); background: var(--ui-bg-muted); color: var(--ui-fg-muted); white-space: nowrap; }
.ui-pill-success { background: var(--ui-success); color: #fff; }
.ui-pill-warn    { background: var(--ui-warn);    color: #fff; }
.ui-pill-danger  { background: var(--ui-danger);  color: #fff; }

/* Report card — header (title + pill), optional ring, metric chips + value bars */
.ui-reportcard        { display: flex; flex-direction: column; gap: var(--ui-space-2); padding: var(--ui-space-3); background: var(--ui-bg); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg); box-shadow: var(--ui-shadow-sm); }
.ui-reportcard-head   { display: flex; align-items: flex-start; gap: var(--ui-space-2); }
.ui-reportcard-titles { flex: 1; min-width: 0; }
.ui-reportcard-title  { font-weight: var(--ui-font-bold); }
.ui-reportcard-sub    { font-size: var(--ui-text-sm); color: var(--ui-fg-muted); }
.ui-reportcard-body   { display: flex; gap: var(--ui-space-3); align-items: center; }
.ui-reportcard-detail { flex: 1; min-width: 0; max-width: 40rem; display: flex; flex-direction: column; gap: var(--ui-space-1); }
.ui-reportcard-metrics { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
.ui-reportcard-metrics .ui-metric-chip { flex: 0 0 auto; }
.ui-reportcard-bar    { display: flex; align-items: center; gap: var(--ui-space-2); }
.ui-reportcard-bar-label { flex: 0 0 9rem; font-size: var(--ui-text-sm); color: var(--ui-fg-muted); }
.ui-reportcard-bar .ui-progress { flex: 1; }
.ui-reportcard-bar-val   { flex: 0 0 3rem; text-align: right; font-size: var(--ui-text-sm); font-weight: var(--ui-font-semibold); }
.ui-reportcard-foot   { font-size: var(--ui-text-xs); color: var(--ui-fg-muted); }

/* Avatar + metric chip + empty state */
/* Avatar — fixed-size circle. Image fills via object-fit; initials centred. */
.ui-avatar {
    display: inline-flex; align-items: center; justify-content: center;
    width: var(--ui-avatar-size); height: var(--ui-avatar-size); flex: 0 0 auto;
    padding: 0; border-radius: var(--ui-radius-full); overflow: hidden;
    background: var(--ui-primary-600); color: #fff;
    font-weight: var(--ui-font-bold); font-size: var(--ui-text-sm); line-height: 1;
    box-shadow: var(--ui-shadow-sm);
    border: 2px solid var(--ui-bg-elev);
    user-select: none;
}
.ui-avatar-sm { width: 2rem; height: 2rem; font-size: var(--ui-text-xs); }
.ui-avatar-lg { width: 3.2rem; height: 3.2rem; font-size: var(--ui-text-lg); }
.ui-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Tone variants — colour an avatar by semantic status (people initials,
   status markers). Reusable across systems. */
.ui-avatar-success { background: var(--ui-success); }
.ui-avatar-warn    { background: var(--ui-warn); }
.ui-avatar-danger  { background: var(--ui-danger); }
.ui-avatar-info    { background: var(--ui-info); }
/* 8-shade hash cycle, 60-30-10 across primary/secondary/accent — colour-rotated
   so a list of people reads as distinct identities, not a column of grey discs. */
.ui-avatar-c0 { background: hsl(calc(var(--hp-hue-1) - 30)      calc(var(--hp-sat-1) * 0.7) 45%); }
.ui-avatar-c1 { background: hsl(var(--hp-hue-1)                  calc(var(--hp-sat-1) * 0.8) 42%); }
.ui-avatar-c2 { background: hsl(calc(var(--hp-hue-1) + 25)      calc(var(--hp-sat-1) * 0.7) 40%); }
.ui-avatar-c3 { background: hsl(calc(var(--hp-hue-1) + 50)      calc(var(--hp-sat-1) * 0.6) 42%); }
.ui-avatar-c4 { background: hsl(calc(var(--hp-hue-2) - 20)      calc(var(--hp-sat-2) * 0.7) 45%); }
.ui-avatar-c5 { background: hsl(var(--hp-hue-2)                  calc(var(--hp-sat-2) * 0.8) 42%); }
.ui-avatar-c6 { background: hsl(calc(var(--hp-hue-2) + 30)      calc(var(--hp-sat-2) * 0.7) 40%); }
.ui-avatar-c7 { background: hsl(var(--hp-hue-accent)             calc(var(--hp-sat-accent) * 0.85) 38%); }
.ui-avatar-success { background: var(--ui-success); }
.ui-avatar-warn    { background: var(--ui-warn); }
.ui-avatar-danger  { background: var(--ui-danger); }
.ui-avatar-info    { background: var(--ui-info); }
/* Text-first metrics (design/ §5): bold number + muted label, hairline
   separators — a metric must never be SHAPED like a control. Tones express
   through the number colour only. */
.ui-metric-chip {
    display: inline-flex; align-items: baseline; gap: var(--ui-space-1);
    padding: 0 var(--ui-space-3) 0 0; font-size: var(--ui-text-sm);
    color: var(--ui-fg-muted);
}
.ui-metric-chip + .ui-metric-chip { border-left: var(--ui-border-width) solid var(--ui-border); padding-left: var(--ui-space-3); }
/* Affordance honesty (2026-08-02): anything chip-shaped that acts is MARKED
   clickable — cursor + hover; active state carries a primary underline. */
.ui-clickable { cursor: pointer; }
.ui-metric-chip.ui-clickable:hover { background: var(--ui-bg-muted); border-radius: var(--ui-radius-md); }
.ui-metric-chip.ui-metric-chip-active { box-shadow: inset 0 -2px 0 var(--ui-primary); }
.ui-metric-chip strong { color: var(--ui-fg); font-weight: var(--ui-font-semibold); font-size: var(--ui-text-base); }
.ui-metric-chip-secondary strong { color: var(--ui-secondary-700); }
.ui-metric-chip-accent strong    { color: var(--ui-accent); }
/* Empty-state chip: data hasn't loaded yet. Muted so it reads as "slot
 * waiting" rather than "loaded with em-dash" — see UIBind.renderMetricChip
 * which applies this modifier when the value is '—'. */
.ui-metric-chip-empty strong    { color: var(--ui-gray-400); font-weight: var(--ui-font-normal); }
.ui-metric-chip-empty .ui-metric-chip-icon { opacity: 0.4; }
.ui-empty {
    text-align: center; padding: var(--ui-space-7) var(--ui-space-4);
    color: var(--ui-fg-muted); font-size: var(--ui-text-sm);
}
.ui-empty-icon { font-size: var(--ui-text-3xl); margin-bottom: var(--ui-space-3); opacity: 0.45; }

/* Tooltip */
.ui-tooltip {
    position: absolute; z-index: 200; padding: var(--ui-space-1) var(--ui-space-2);
    background: var(--ui-gray-800); color: #fff; border-radius: var(--ui-radius-sm);
    font-size: var(--ui-text-xs); pointer-events: none; box-shadow: var(--ui-shadow-md);
    opacity: 0; transition: opacity var(--ui-transition-fast);
}
.ui-tooltip.ui-active { opacity: 1; }

/* Tabs / accordion / ribbon / nav / stepper / segmented / sidebar */
.ui-tabs { display: flex; flex-direction: column; min-height: 0; }
.ui-tabs-bar {
    display: flex; gap: var(--ui-space-1); border-bottom: var(--ui-border-width) solid var(--ui-border);
    padding: 0 var(--ui-space-2); flex-wrap: wrap;
}
.ui-tabs-head, .ui-ribbon-item, .ui-segmented-item, .ui-navbar-item, .ui-sidebar-item, .ui-stepper-item {
    display: inline-flex; align-items: center; gap: var(--ui-space-1);
    padding: var(--ui-space-1) var(--ui-space-2); cursor: pointer;
    color: var(--ui-fg-muted); font-weight: var(--ui-font-medium);
    border-radius: var(--ui-radius-md); transition: background var(--ui-transition-fast), color var(--ui-transition-fast);
    user-select: none; white-space: nowrap; border: none; background: transparent;
}
.ui-tabs-head:hover, .ui-ribbon-item:hover, .ui-navbar-item:hover, .ui-sidebar-item:hover, .ui-segmented-item:hover { background: var(--ui-bg-muted); color: var(--ui-fg); }
.ui-tabs-head { padding-bottom: var(--ui-space-2); border-radius: 0; border-bottom: var(--ui-tab-underline-w) solid transparent; margin-bottom: -1px; }
.ui-tabs-head.ui-active { color: var(--ui-primary); border-bottom-color: var(--ui-primary); background: transparent; }
.ui-ribbon { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); padding: var(--ui-space-2); background: var(--ui-bg-muted); border-radius: var(--ui-radius-md); }
.ui-ribbon-item.ui-active, .ui-segmented-item.ui-active, .ui-navbar-item.ui-active { background: var(--ui-primary); color: var(--ui-on-primary); }
.ui-segmented { display: inline-flex; padding: 2px; background: var(--ui-bg-muted); border-radius: var(--ui-radius-md); }
.ui-navbar { display: flex; flex-wrap: wrap; gap: var(--ui-space-1); padding: var(--ui-space-2); background: var(--ui-bg-elev); border-bottom: var(--ui-border-width) solid var(--ui-border); }
.ui-stepper { display: flex; gap: var(--ui-space-2); padding: var(--ui-space-2); }
.ui-stepper-step { display: flex; align-items: center; gap: var(--ui-space-2); flex: 1; }
.ui-stepper-step::after { content: ""; flex: 1; height: 2px; background: var(--ui-border); }
.ui-stepper-step:last-child::after { display: none; }
.ui-stepper-item.ui-active { color: var(--ui-primary); }

.ui-sidebar { display: flex; flex-direction: column; padding: var(--ui-space-3); gap: var(--ui-space-1); background: var(--ui-bg-elev); border-right: var(--ui-border-width) solid var(--ui-border); }
/* A sidebar is a VERTICAL column, so `white-space: nowrap` — correct for the
   horizontal tabs/ribbon/segmented items that share the rule above — makes long
   labels spill OUT of the column rather than wrap. Measured 2026-07-29 on
   Admin -> Meta data: 3 of 5 import-target labels overflowed the 303px column by
   up to 76px and, because overflow-x is visible, overlapped the pane beside them
   instead of clipping. Wrapping is the natural behaviour for a column. */
.ui-sidebar-item { padding: var(--ui-space-2) var(--ui-space-3); justify-content: flex-start;
    white-space: normal; align-items: flex-start; }
.ui-sidebar-item.ui-active { background: var(--ui-primary-100); color: var(--ui-primary-700); border-left: 3px solid var(--ui-primary); padding-left: calc(var(--ui-space-3) - 3px); font-weight: var(--ui-font-semibold); }
.ui-sidebar-section { margin-top: var(--ui-space-3); }
.ui-sidebar-section-title { font-size: var(--ui-text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ui-fg-muted); padding: var(--ui-space-2) var(--ui-space-3); }

/* gap:0 — items are separated by their border-top (below), so any ui-gap-* passed
 * at instantiation must not open a strip of the elevated bg between collapsed heads. */
.ui-accordion { display: flex; flex-direction: column; gap: 0; border: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-md); overflow: hidden; background: var(--ui-bg-elev); }
.ui-accordion-item + .ui-accordion-item, .ui-accordion-entry + .ui-accordion-entry { border-top: var(--ui-border-width) solid var(--ui-border); }
.ui-accordion-head { display: flex; align-items: center; gap: var(--ui-space-2); padding: var(--ui-space-1) var(--ui-space-3); cursor: pointer; font-size: var(--ui-text-xs); font-weight: var(--ui-font-semibold); text-transform: uppercase; letter-spacing: 0.04em; background: var(--ui-bg-muted); color: var(--ui-fg-muted); transition: background var(--ui-transition-fast), color var(--ui-transition-fast); }
.ui-accordion-head:hover { background: var(--ui-bg-sunken); }
.ui-accordion-head::after { content: "›"; margin-left: auto; font-size: 1.2em; line-height: 1; transition: transform var(--ui-transition-fast); }
.ui-accordion-item.ui-active > .ui-accordion-head::after, .ui-accordion-entry.ui-active > .ui-accordion-head::after { transform: rotate(90deg); }
.ui-accordion-item.ui-active > .ui-accordion-head, .ui-accordion-entry.ui-active > .ui-accordion-head { background: var(--ui-primary); color: var(--ui-on-primary); }
.ui-accordion-body, .ui-accordion-content { padding: var(--ui-space-2); }
.ui-accordion-item:not(.ui-active) > .ui-accordion-body { display: none; }

.ui-breadcrumbs { display: flex; gap: var(--ui-space-2); font-size: var(--ui-text-sm); color: var(--ui-fg-muted); }
.ui-breadcrumbs > * + *::before { content: "›"; margin-right: var(--ui-space-2); color: var(--ui-fg-muted); }

/* ───────────────────────────────────────────────────────────────────────────
   PAGE-FRAME LAYOUT — DOCUMENT-FLOW STICKY FOOTER (read before changing this).

   Want: the footer sits at the bottom of the VIEWPORT when the page is short,
   and is PUSHED DOWN by the content (the whole PAGE scrolls) when the content is
   tall. This is the classic CSS sticky footer — NOT an app-shell with an
   internally-scrolling body. The footer comes after the body in normal flow.

   Three load-bearing rules:
     1. Frame = flex COLUMN, MIN-height of one viewport:
          display:flex; flex-direction:column; min-height:100dvh;
        `min-height` (NOT `height`) is the whole point — the frame must be free to
        grow taller than the screen so the footer travels down with the content.
     2. Header + footer size to content, never grow/shrink:  flex:0 0 auto;
     3. Body grows to fill the gap (pinning the footer to the viewport bottom when
        the page is short) but is NEVER capped and does NOT scroll internally:
          flex:1 0 auto;
        No overflow/min-height:0 on the body — that would trap scrolling inside it
        and pin the footer (the app-shell behaviour we explicitly do NOT want).

   The PAGE is the scroll container. Do NOT reintroduce height:100dvh on the frame
   or overflow:auto on the body — that pins the footer and reads as "fixed", the
   recurring regression. If ONE pane genuinely needs its own scroll, cap THAT pane
   (max-height + overflow) — never the frame.
   ─────────────────────────────────────────────────────────────────────────── */
.ui-pageframe { display: flex; flex-direction: column; min-height: 100dvh; background: var(--ui-bg); }
.ui-pageframe-header, .ui-pageframe-footer { flex: 0 0 auto; }
/* The content region between header and footer: grow to push the footer down; the page scrolls. */
.ui-pageframe > :not(.ui-pageframe-header):not(.ui-pageframe-footer) { flex: 1 0 auto; }
.ui-pageframe .ui-pageframe { min-height: 0; }   /* a nested frame flows; it does not start a 2nd viewport */
/* `.ui-pageframe-app` / `.ui-pageframe-fill` were opt-in "fixed shell" modifiers;
   removed — they trapped scrolling in nested flex containers. The frame is
   document-flow only (above): the page scrolls, the footer is pushed down. */
.ui-pageframe-header {
    display: flex; align-items: center; gap: var(--ui-space-5);
    padding: var(--ui-space-3) var(--ui-space-5);
    background: var(--ui-primary);
    color: var(--ui-on-primary);
    border-bottom: 0;
    box-shadow: var(--ui-shadow-md);
    /* Sticky so the nav stays visible while the PAGE scrolls (document-flow
       sticky-footer model — see the PAGE-FRAME LAYOUT block above). */
    flex-shrink: 0; position: sticky; top: 0; z-index: 5;
}
.ui-pageframe-header .ui-pageframe-title    { color: var(--ui-on-primary); }
.ui-pageframe-header .ui-pageframe-subtitle { color: var(--ui-on-primary-muted); }
.ui-pageframe-header .ui-pageframe-brand-mark {
    background: var(--ui-on-primary-bg-strong); color: var(--ui-on-primary);
    box-shadow: inset 0 0 0 1px var(--ui-on-primary-divider);
}
.ui-pageframe-action {
    color: var(--ui-on-primary); border-radius: var(--ui-radius-md);
    width: 2rem; height: 2rem;
}
.ui-pageframe-action:hover { background: var(--ui-on-primary-bg-strong); color: var(--ui-on-primary); }
.ui-pageframe-action > i.fa { font-size: 1rem; line-height: 1; }
/* Hamburger — hidden on desktop; the ≤640px block shows it + collapses the nav. */
.ui-pageframe-burger { display: none; margin-left: auto; align-items: center; background: transparent;
    border: 0; color: var(--ui-on-primary); font-size: var(--ui-text-xl); line-height: 1; cursor: pointer;
    padding: 0 var(--ui-space-2); border-radius: var(--ui-radius-md); }
.ui-pageframe-burger:hover { background: var(--ui-on-primary-bg-strong); }
.ui-sidebar-item .fa, .ui-tabs-head .fa { font-size: 0.95em; opacity: 0.85; margin-right: 0.25rem; }
.ui-pageframe-brand { display: flex; align-items: center; gap: var(--ui-space-3); line-height: 1.2; min-width: 0; }
.ui-pageframe-brand-mark {
    display: inline-flex; align-items: center; justify-content: center;
    aspect-ratio: 1; padding: var(--ui-space-2);
    border-radius: var(--ui-radius-md);
    background: var(--ui-primary);
    color: #fff; font-weight: var(--ui-font-bold);
    font-size: var(--ui-text-lg); line-height: 1;
    box-shadow: var(--ui-shadow-sm);
    flex-shrink: 0;
}
.ui-pageframe-brand-stack { display: flex; flex-direction: column; min-width: 0; gap: 2px; }
.ui-pageframe-title { font-size: var(--ui-text-lg); font-weight: var(--ui-font-semibold); color: var(--ui-fg); white-space: nowrap; }
.ui-pageframe-subtitle { color: var(--ui-fg-muted); font-size: var(--ui-text-xs); }
.ui-pageframe-actions { margin-left: auto; display: flex; gap: var(--ui-space-2); align-items: center; }
/* In-header tabs — translucent white on primary background. */
.ui-pageframe-tabs { flex: 0 1 auto; background: transparent; display: flex; min-width: 0; }
.ui-pageframe-tabs > * { min-width: 0; }
.ui-pageframe-tabs .ui-tabs-bar { border-bottom: 0; padding: 0; gap: var(--ui-space-2); }
.ui-pageframe-tabs .ui-tabs-head {
    color: var(--ui-on-primary-muted); border-bottom: var(--ui-tab-underline-w) solid transparent;
    margin-bottom: 0;
}
.ui-pageframe-tabs .ui-tabs-head:hover { color: var(--ui-on-primary); background: var(--ui-on-primary-bg-soft); }
.ui-pageframe-tabs .ui-tabs-head.ui-active {
    color: var(--ui-on-primary); background: transparent;
    border-bottom-color: var(--ui-on-primary);
}
/* Padding only — fill/scroll comes from the catch-all rule above (the single
   content child gets flex:1 1 auto; min-height:0; overflow:auto). Do NOT add a
   flex value with `0` shrink here; it re-breaks the sticky footer. */
/* ONE padding level only. The frame nests body > stage and content mounts into
   the stage (or the hub-page-host inside it), so padding BOTH stacked 32px of
   dead space at the top/bottom. Pad the stage; the body is a bare flex wrapper. */
.ui-pageframe-stage { padding: var(--ui-space-4); }

/* Persistent demo/preview banner — full-width strip flush under the header,
   above the stage. Flattens the callout card into an edge-to-edge strip. */
.ui-pageframe-banner {
    border-radius: 0; margin: 0; border-left: 0;
    border-bottom: 1px solid var(--ui-warn);
    padding: var(--ui-space-2) var(--ui-space-4);
    animation: none;
}

/* Login shield host (mounted between header and footer by System._mountShield).
   Layout + visuals only. The card's WIDTH is set inline by System._mountShield
   from `config.shield.width` so a designer can override per-system without
   fighting CSS specificity. */
.ui-system-shield-host {
    display: flex;
    flex-direction: column;   /* login card, then demo personas beneath it */
    align-items: center;
    justify-content: flex-start;
    flex: 1 0 auto;
    padding: var(--ui-space-7) var(--ui-space-4);
    min-height: 60vh;
    background: var(--ui-bg-sunken, transparent);
}
.ui-system-shield-personas { width: 100%; text-align: center; }
.ui-system-shield-personas .ui-flex { justify-content: center; }
.ui-system-shield-host > .ui {
    width: 100%;
    background: var(--ui-bg-elev);
    border: 1px solid var(--ui-border);
    border-radius: var(--ui-radius-lg);
    padding: var(--ui-space-6) var(--ui-space-5);
    box-shadow: var(--ui-shadow-md);
}
.ui-pageframe-footer {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--ui-space-5);
    padding: var(--ui-space-5) var(--ui-space-5);
    background: var(--ui-gray-900);
    color: var(--ui-on-primary-muted);
    border-top: 3px solid var(--ui-accent);
    font-size: var(--ui-text-sm); flex-shrink: 0;
}
.ui-pageframe-footer-column { display: flex; flex-direction: column; gap: var(--ui-space-1); min-width: 0; }
.ui-pageframe-footer-title { font-weight: var(--ui-font-semibold); color: var(--ui-on-primary); margin-bottom: var(--ui-space-2); font-size: var(--ui-text-sm); text-transform: uppercase; letter-spacing: 0.06em; }
.ui-pageframe-footer-link { color: rgba(255,255,255,0.7); transition: color var(--ui-transition-fast); }
.ui-pageframe-footer-link:hover { color: #fff; }
.ui-pageframe-footer-text { color: rgba(255,255,255,0.72); font-size: var(--ui-text-sm); }
.ui-pageframe-footer-copyright { grid-column: 1 / -1; padding-top: var(--ui-space-3); border-top: 1px solid rgba(255,255,255,0.12); margin-top: var(--ui-space-3); text-align: center; font-size: var(--ui-text-xs); color: rgba(255,255,255,0.55); }
.ui-footer { display: flex; align-items: center; gap: var(--ui-space-3); padding: var(--ui-space-3); background: var(--ui-bg-muted); }

/* Control-stage — fluid grid, content sizes the control column. */
.ui-control-stage {
    display: grid;
    grid-template-columns: minmax(min-content, 1fr) 3fr;
    gap: var(--ui-space-4);
    height: 100%; min-height: 0;   /* fill the tab so flex-1 stage children can size */
}
.ui-control-stage-control { display: flex; flex-direction: column; gap: var(--ui-space-3); min-height: 0; overflow: auto; }
.ui-control-stage-stage   { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: auto; }
@media (max-width: 800px) { .ui-control-stage { grid-template-columns: 1fr; } }

/* Forms */
/* §4b — a form is a narrow input column, never a full-stage stretch. Capped at
   the primitive so an over-wide host can't produce an unreadable 900px+ form;
   the cap only limits, so forms already in a 1/3 rail or modal are unaffected. */
.ui-form { display: flex; flex-direction: column; gap: var(--ui-space-2); max-width: 34rem; }
.ui-form-row { flex-direction: row; flex-wrap: wrap; align-items: flex-end; gap: var(--ui-space-3); max-width: none; } /* filter-bar mode: one row, wraps (structure.md Face 2) */
.ui-form-row .ui-form-field { flex: 1 1 11rem; margin: 0; }
/* A form deliberately filling a wide grid cell opts out with .ui-form-wide. */
.ui-form-wide { max-width: none; }
/* §4b — a form column in a split (brief/read beside form): bases at a readable
   ~1/3 width, shrinks if needed, never collapses to content. */
.ui-form-col { flex: 0 1 32rem; min-width: 0; }
.ui-form-field { display: flex; flex-direction: column; gap: 2px; }
.ui-form-label { font-size: var(--ui-text-xs); font-weight: var(--ui-font-medium); color: var(--ui-fg-muted); }
/* Form action row — ONE flex row, right-aligned by default (primary sits
 * rightmost, cancel/extras to its left — the macOS/instrument convention;
 * owner ruling 2026-08-15 after repeated ragged stacked-button forms).
 * uiForm's buttons.align maps to the -left / -between modifiers. */
.ui-form-buttons { display: flex; gap: var(--ui-space-2); margin-top: var(--ui-space-2); justify-content: flex-end; align-items: center; flex-wrap: wrap; }
.ui-form-buttons-left { justify-content: flex-start; }
.ui-form-buttons-between { justify-content: space-between; }

/* Data list / cards / tree / pager */
.ui-data { display: flex; flex-direction: column; min-height: 0; gap: var(--ui-space-2); }
.ui-data-list-content { display: flex; flex-direction: column; min-height: 0; overflow: auto; }
.ui-data-list-row, .ui-bind-list-row {
    display: flex; align-items: center; gap: var(--ui-space-3); padding: var(--ui-space-2) var(--ui-space-3);
    border-bottom: var(--ui-border-width) solid var(--ui-border); cursor: pointer;
    transition: background var(--ui-transition-fast), border-left-color var(--ui-transition-fast);
    border-left: var(--ui-active-marker-w) solid transparent;
}
.ui-data-list-row:hover, .ui-bind-list-row:hover { background: var(--ui-bg-muted); border-left-color: var(--ui-primary-300); }
.ui-data-list-row.ui-active, .ui-bind-list-row.ui-active { background: var(--ui-primary-100); color: var(--ui-primary-700); border-left-color: var(--ui-primary); }
/* WCAG AA: on a selected row the teal-100 tint lifts the background luminance,
   so plain fg-muted (tuned for white) drops below 4.5:1. Inherit the row's
   dark-teal selection colour for muted descendants — the designed 700-on-100 pair. */
.ui-data-list-row.ui-active .ui-text-muted, .ui-bind-list-row.ui-active .ui-text-muted { color: var(--ui-primary-700); }
/* Compact list rows — the DEFAULT for bindSelector/bindSelectEditor lists: dense
   reading-pane rows with a smaller avatar, tighter padding/gap, title + subtitle on
   one line, no row growth. Opt OUT with {compact:false} for roomy two-line rows. */
.ui-bind-list-body { white-space: pre-wrap; overflow-wrap: break-word; margin-top: 2px; }
.ui-bind-list--compact .ui-bind-list-row { padding: 2px var(--ui-space-2); gap: var(--ui-space-2); }
.ui-bind-list--compact .ui-avatar { width: 1.35rem; height: 1.35rem; font-size: 0.58rem; flex: 0 0 auto; }
.ui-bind-list--compact .ui-bind-list-head { flex-direction: row; align-items: baseline; gap: var(--ui-space-2); min-width: 0; }
.ui-bind-list--compact .ui-bind-list-head .ui-text-sm { font-size: var(--ui-text-xs); }
.ui-bind-list--compact .ui-bind-list-pills { transform: scale(0.85); transform-origin: right center; }
.ui-bind-list--compact .ui-icon-btn { padding: 0; width: 1.25rem; height: 1.25rem; }
/* Cards grid — items live in a sub-host (.ui-bind-cards-items) so toolbar
   + pager can sit above/below as flex siblings of the grid, not as grid
   items. auto-fill keeps every column the same width on the last row. */
.ui-bind-cards { display: flex; flex-direction: column; gap: var(--ui-space-2); padding: var(--ui-space-2); }
.ui-data-cards-grid, .ui-bind-cards-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--ui-space-3); align-items: stretch; align-content: start; }
.ui-data-cards-grid > .ui-card, .ui-bind-cards-items > .ui-card { min-width: 0; display: flex; flex-direction: column; }
.ui-data-card, .ui-bind-entity-card {
    background: var(--ui-bg-elev); border: var(--ui-border-width) solid var(--ui-border);
    border-left: var(--ui-active-marker-w) solid transparent;
    border-radius: var(--ui-radius-lg); padding: var(--ui-space-3);
    cursor: pointer; transition: box-shadow var(--ui-transition-fast), transform var(--ui-transition-fast);
}
.ui-data-card:hover, .ui-bind-entity-card:hover { box-shadow: var(--ui-shadow-md); transform: translateY(-1px); }
.ui-bind-entity-card-header, .ui-bind-entity-card-title { font-weight: var(--ui-font-semibold); margin-bottom: var(--ui-space-1); }
.ui-bind-entity-card-body { color: var(--ui-fg-muted); font-size: var(--ui-text-sm); }
.ui-bind-entity-card-actions, .ui-bind-list-actions { display: flex; gap: var(--ui-space-2); margin-top: var(--ui-space-2); }
/* Layout root — minimal. Each template adds its own display rules.
   Charts size themselves from CONTENT (Plotly height = N_bars × bar_height).
   Kanban lanes size themselves from card count. Don't force container
   heights — that squashes bars when N is large or wastes space when N
   is small. */
.ui-bind-layout { min-height: 0; }
/* A chart layout (calendar/kanban/graph…) needs a resolved height to draw into;
   without this the block-level bind-layout collapses to content height and the
   chart's height:100% resolves to ~0. Scoped to chart hosts so list/table/form
   layouts (content-height by design) are untouched. */
.ui-bind-layout:has(> .ui-chart) { height: 100%; }
.ui-paginator { display: flex; align-items: center; gap: var(--ui-space-3); padding: var(--ui-space-2) var(--ui-space-3); border-top: var(--ui-border-width) solid var(--ui-border); font-size: var(--ui-text-sm); }
.ui-paginator:empty { display: none; }   /* single page → no reserved pager space */
.ui-paginator-controls { display: flex; gap: var(--ui-space-1); margin-left: auto; }
.ui-paginator-btn { padding: var(--ui-space-1) var(--ui-space-2); border: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-bg-elev); cursor: pointer; }
.ui-paginator-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.ui-paginator-btn:hover:not(:disabled) { background: var(--ui-bg-muted); }
.ui-paginator-info { color: var(--ui-fg-muted); }

.ui-tree { display: flex; flex-direction: column; gap: var(--ui-space-1); padding: var(--ui-space-2); }
.ui-tree-node, .ui-bind-tree-node {
    position: relative;
    display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--ui-space-1); row-gap: 0;
    padding: 1px var(--ui-space-2); border-radius: var(--ui-radius-sm); font-size: var(--ui-text-sm);
    cursor: pointer; transition: background var(--ui-transition-fast),
                                  border-color var(--ui-transition-fast);
    padding-left: calc(var(--ui-space-2) + var(--ui-tree-depth, 0) * var(--ui-space-4));
    border-left: var(--ui-active-marker-w) solid transparent;
}
.ui-tree-node:hover, .ui-bind-tree-node:hover {
    background: var(--ui-bg-muted);
    border-left-color: var(--ui-primary-300);
}
.ui-tree-node.ui-active, .ui-bind-tree-node.ui-active {
    background: var(--ui-primary-100); color: var(--ui-primary-700);
    border-left-color: var(--ui-primary); font-weight: var(--ui-font-semibold);
}
.ui-bind-tree-node.ui-deselected { border-left-color: var(--ui-border); }
.ui-tree-bullet { color: var(--ui-fg-muted); font-size: var(--ui-text-xs); }
/* L5.6 — per-table colour for chain renderers (tree nodes + chips). Hash 0-7 reuses the avatar palette HSL spec. */
[data-table-hash="0"] { --ui-table-color: hsl(calc(var(--hp-hue-1) - 30) calc(var(--hp-sat-1) * 0.7) 45%); }
[data-table-hash="1"] { --ui-table-color: hsl(var(--hp-hue-1) calc(var(--hp-sat-1) * 0.8) 42%); }
[data-table-hash="2"] { --ui-table-color: hsl(calc(var(--hp-hue-1) + 25) calc(var(--hp-sat-1) * 0.7) 40%); }
[data-table-hash="3"] { --ui-table-color: hsl(calc(var(--hp-hue-1) + 50) calc(var(--hp-sat-1) * 0.6) 42%); }
[data-table-hash="4"] { --ui-table-color: hsl(calc(var(--hp-hue-2) - 20) calc(var(--hp-sat-2) * 0.7) 45%); }
[data-table-hash="5"] { --ui-table-color: hsl(var(--hp-hue-2) calc(var(--hp-sat-2) * 0.8) 42%); }
[data-table-hash="6"] { --ui-table-color: hsl(calc(var(--hp-hue-2) + 30) calc(var(--hp-sat-2) * 0.7) 40%); }
[data-table-hash="7"] { --ui-table-color: hsl(var(--hp-hue-accent) calc(var(--hp-sat-accent) * 0.85) 38%); }
.ui-bind-tree-node[data-table-hash] { border-left-color: var(--ui-table-color); border-left-width: 3px; }
.ui-bind-tree-node[draggable="true"] { cursor: move; }
.ui-bind-tree-node[draggable="true"]:hover { outline: 2px dashed var(--ui-primary-300); outline-offset: -2px; }
.ui-chip[data-table-hash] { color: var(--ui-table-color); border: 1px solid var(--ui-table-color); }
/* R5 — on-state hash chip fills with the hash colour + on-primary text, so the
   label stays legible (the rule above colours only the text, unreadable over
   the .ui-chip-on background). Off chips keep the outline+tint look. */
.ui-chip-on[data-table-hash] { background: var(--ui-table-color); color: var(--ui-on-primary); border-color: var(--ui-table-color); }

/* Tree — twisty + folder/leaf icon in front of the label. */
.ui-bind-tree { padding: var(--ui-space-2); }
.ui-bind-tree-rows { display: flex; flex-direction: column; gap: 1px; }
.ui-bind-tree-twisty { display: inline-flex; align-items: center; justify-content: center; width: 0.9rem; height: 0.9rem; flex-shrink: 0; color: var(--ui-fg-muted); font-size: 0.65rem; line-height: 1; }
.ui-bind-tree-twisty-active { cursor: pointer; }
.ui-bind-tree-twisty-active:hover { color: var(--ui-primary); }
.ui-bind-tree-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 1rem; height: 1rem; line-height: 1; font-size: var(--ui-text-sm); }
.ui-bind-tree-icon-branch { color: var(--ui-primary); }
.ui-bind-tree-icon-leaf   { color: var(--ui-fg-muted); }
.ui-bind-tree-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Charts */
.ui-chart, .ui-chart-frame { display: flex; flex-direction: column; min-height: 0; height: 100%; background: var(--ui-bg-elev); border-radius: var(--ui-radius-lg); border: var(--ui-border-width) solid var(--ui-border); padding: var(--ui-space-3); position: relative; }
/* Containment (2026-08-22, ClassView phone review): a Plotly svg carries an
   explicit pixel width, so a chart drawn while its column was wide holds the
   whole grid track open (min-content deadlock — the ResizeObserver refit can
   never shrink a frame the svg itself is propping up, measured 5,535px on a
   390px viewport). max-width lets the frame collapse with the viewport; the
   existing refit observer then redraws the plot at the real size. */
.ui-chart, .ui-chart-frame, .ui-chart-plot { max-width: 100%; min-width: 0; }
.ui-chart-frame .svg-container, .ui-chart-frame .main-svg, .ui-chart-frame .plot-container { max-width: 100% !important; }
.ui-chart-frame.ui-chart-fixed-h { height: var(--ui-chart-h); }
.ui-chart-frame.ui-chart-flex { flex: 0 0 auto; min-height: 0; height: var(--ui-chart-min-h, clamp(200px, 26vh, 300px)); overflow: hidden; }
.ui-chart-panel { background: var(--ui-bg-elev); border: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-lg); }
.ui-chart-panel .ui-chart-panel-header { padding: var(--ui-space-2) var(--ui-space-3); border-bottom: var(--ui-border-width) solid var(--ui-border); font-weight: var(--ui-font-semibold); }
.ui-chart-panel .ui-chart-panel-body { padding: var(--ui-space-3); height: var(--ui-chart-panel-h, 300px); }
.ui-chart-panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--ui-space-2); }
.ui-chart-panel-body { flex: 1 1 auto; min-height: 0; }
.ui-chart-title { font-weight: var(--ui-font-semibold); }
.ui-chart-empty { display: flex; align-items: center; justify-content: center; min-height: 12rem; color: var(--ui-fg-muted); font-size: var(--ui-text-sm); }

/* Kanban + timeline */
.ui-kanban { display: flex; gap: var(--ui-space-3); padding: var(--ui-space-2); align-items: stretch; overflow-x: auto; min-height: 14rem; }
/* As a pane's central element the board fills the pane — lanes run to the
   bottom edge instead of ending at card height over a white void. min-height
   covers the non-flex .ui-pane-scroll parent (scrollport content height). */
.ui-pane .ui-kanban { flex: 1 1 auto; min-height: 100%; }
/* Lanes grow when few; shrink to a still-readable floor (8.5rem) so a fixed
   board (6-stage pipeline) fits without hidden scroll, then scroll past that. */
.ui-kanban-lane { flex: 1 1 11rem; min-width: 8.5rem; display: flex; flex-direction: column; background: var(--ui-bg-muted); border-radius: var(--ui-radius-lg); padding: var(--ui-space-2); }
.ui-kanban-lane-header { font-weight: var(--ui-font-semibold); padding: var(--ui-space-2); display: flex; justify-content: space-between; gap: var(--ui-space-2); }
.ui-kanban-lane-body { display: flex; flex-direction: column; gap: var(--ui-space-2); flex: 1 1 auto; }
.ui-kanban-card:active { cursor: grabbing; }
/* Proportion bar — a stacked allocation strip (funding split, effort mix).
   Segments size via --seg-pct and colour via --seg-color (the chip/dot
   custom-property pattern, not inline layout). Promoted to the shared layer
   per design §10 — reusable wherever parts sum to a whole. */
.ui-proportion-bar { display: flex; height: 8px; margin-top: var(--ui-space-1); border-radius: var(--ui-radius-full); overflow: hidden; background: var(--ui-bg-muted); }
.ui-proportion-seg { width: var(--seg-pct, 0); background: var(--seg-color, var(--ui-primary)); }
.ui-proportion-gap { background: repeating-linear-gradient(45deg, var(--ui-gray-200), var(--ui-gray-200) 4px, transparent 4px, transparent 8px); }
.ui-kanban-card { background: var(--ui-bg-elev); border-radius: var(--ui-radius-md); padding: var(--ui-space-3); box-shadow: var(--ui-shadow-sm); cursor: grab; transition: transform var(--ui-transition-fast); border-left: 3px solid var(--ui-kanban-card-accent, var(--ui-primary)); }
.ui-kanban-card:hover { transform: translateY(-1px); box-shadow: var(--ui-shadow-md); }
.ui-kanban-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--ui-kanban-dot-color, var(--ui-primary)); }
.ui-timeline { display: flex; flex-direction: column; gap: var(--ui-space-3); padding-left: var(--ui-space-4); border-left: 2px solid var(--ui-border); }
.ui-timeline-item { display: flex; flex-direction: column; gap: var(--ui-space-1); position: relative; }
.ui-timeline-item::before { content: ""; position: absolute; left: calc(-1 * var(--ui-space-4) - 5px); top: 0.4rem; width: 8px; height: 8px; border-radius: 50%; background: var(--ui-primary); }
.ui-timeline-time { font-size: var(--ui-text-xs); color: var(--ui-fg-muted); }
.ui-timeline-body { font-size: var(--ui-text-sm); }

/* Datatables.net integration */
.ui-data-table { width: 100%; border-collapse: collapse; font-size: var(--ui-text-sm); }
.ui-data-table th, .ui-data-table td { padding: var(--ui-space-2) var(--ui-space-3); border-bottom: var(--ui-border-width) solid var(--ui-border); text-align: left; }
.ui-data-table thead th { background: var(--ui-bg-muted); font-weight: var(--ui-font-semibold); color: var(--ui-fg); }
.ui-data-table tbody tr:hover { background: var(--ui-bg-muted); }
.ui-dt-toolbar, .ui-dt-foot { display: flex; align-items: center; gap: var(--ui-space-3); padding: var(--ui-space-2) 0; }
.ui-dt-search input { padding: var(--ui-space-1) var(--ui-space-3); border: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-bg-elev); }
.ui-dt-buttons { display: flex; gap: var(--ui-space-2); }
.ui-dt-paging { margin-left: auto; display: flex; gap: var(--ui-space-1); }
.ui-dt-info { color: var(--ui-fg-muted); font-size: var(--ui-text-xs); }
.ui-dt-length, .ui-dt-body { display: flex; align-items: center; gap: var(--ui-space-2); }

/* Misc */
.ui-print-label-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--ui-space-3); padding: var(--ui-space-2); }
.ui-print-label-sheet { page-break-after: always; break-after: page; }
.ui-print-label-sheet + .ui-print-label-sheet { margin-top: var(--ui-space-4); padding-top: var(--ui-space-3); border-top: 1px dashed var(--ui-border); }
@media print { .ui-print-label-sheet + .ui-print-label-sheet { margin-top: 0; padding-top: 0; border-top: none; } }
.ui-print-label { display: flex; align-items: center; gap: var(--ui-space-3); padding: var(--ui-space-3); border: 1px solid var(--ui-border-strong); border-radius: var(--ui-radius-sm); background: var(--ui-bg-elev); font-size: var(--ui-text-sm); }
.ui-print-label-qr { width: 5rem; height: 5rem; flex-shrink: 0; background: var(--ui-bg-muted); display: flex; align-items: center; justify-content: center; font-family: var(--ui-font-mono); font-size: var(--ui-text-2xs); border-radius: var(--ui-radius-sm); }
.ui-map-marker { width: 1.1rem; height: 1.1rem; border-radius: 50%; background: var(--ui-marker-color, var(--ui-primary)); border: 2px solid var(--ui-bg-elev); box-shadow: var(--ui-shadow-md); cursor: pointer; transition: transform 120ms ease, box-shadow 120ms ease; }
.ui-map-marker:hover { transform: scale(1.2); }
.ui-map-marker.ui-map-marker-highlight { transform: scale(1.4); box-shadow: 0 0 0 3px var(--ui-primary-bg-soft), var(--ui-shadow-lg); z-index: 2; }
.ui-chart.ui-chart-map { height: var(--ui-map-h, clamp(22rem, 65vh, 44rem)); }   /* --ui-map-h: contextual override (e.g. a folded context-strip map) */
.ui-chart.ui-chart-map .ui-chart-frame { flex: 1 1 auto; min-height: 0; }
.ui-chart.ui-chart-graph { height: var(--ui-chart-outer-h, clamp(20rem, 55vh, 36rem)); }
/* R3 — togglegrid two-axis scroll with sticky column + row headers */
.ui-togglegrid-scroll { overflow: auto; max-height: var(--ui-togglegrid-max-h, 60vh); }
.ui-togglegrid-scroll thead th { position: sticky; top: 0; z-index: 2; background: var(--ui-bg-elev); }
.ui-togglegrid-scroll tbody th { position: sticky; left: 0; z-index: 1; background: var(--ui-bg-elev); }
.ui-togglegrid-scroll thead th:first-child { left: 0; z-index: 3; }
.ui-chart.ui-chart-graph .ui-chart-frame { flex: 1 1 auto; min-height: 0; }
/* Calendar sizes to CONTENT — FullCalendar renders the full month grid at
   natural row heights (uiChart passes height:'auto'); the generic flex-frame
   band (200-300px + overflow:hidden) clipped it to two week rows
   (2026-07-02 review: no absolute vertical bands). An explicit numeric
   height still wins via ui-chart-fixed-h. */
.ui-chart.ui-chart-calendar .ui-chart-frame.ui-chart-flex { height: auto; overflow: visible; }
.ui-chart-host { display: flex; flex-direction: column; flex: 1 1 auto; min-height: clamp(18rem, 50vh, 32rem); }
.ui-toggle-grid-table { border-collapse: collapse; }
.ui-toggle-grid-table th, .ui-toggle-grid-table td { padding: var(--ui-space-1) var(--ui-space-2); }
.ui-toggle-grid-rowlabel { font-weight: var(--ui-font-semibold); }
.ui-toggle-grid-cell { text-align: center; cursor: pointer; min-width: 2rem; border-radius: var(--ui-radius-sm); transition: background var(--ui-transition-fast); }
.ui-toggle-grid-cell:hover { background: var(--ui-bg-muted); }
.ui-toggle-grid-cell.ui-active { background: var(--ui-primary); color: var(--ui-on-primary); }
.ui-aggregate-metrics { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); padding: var(--ui-space-2); }
.ui-bind-list-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-space-2); margin-bottom: var(--ui-space-2); }
.ui-bind-list-bar input[type=search] { max-width: none; }
/* Compact search field with an integrated + adder — one unit, not a separate
   text button. The input loses its right radius/border; the + button caps it. */
.ui-bind-search { display: flex; align-items: stretch; flex: 1 1 auto; width: 100%; }
.ui-bind-search .ui-input { flex: 1 1 auto; min-width: 0; max-width: none; padding: var(--ui-space-1) var(--ui-space-2); font-size: var(--ui-text-sm); border-right: 0; border-radius: var(--ui-radius-md) 0 0 var(--ui-radius-md); }
.ui-bind-search-add { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; padding: 0 var(--ui-space-2); border: var(--ui-border-width) solid var(--ui-border); border-radius: 0 var(--ui-radius-md) var(--ui-radius-md) 0; background: var(--ui-bg-elev); color: var(--ui-fg-muted); cursor: pointer; font-size: var(--ui-text-sm); transition: background var(--ui-transition-fast), color var(--ui-transition-fast); }
.ui-bind-search-add:hover { background: var(--ui-primary); color: var(--ui-on-primary); border-color: var(--ui-primary); }
.ui-bind-search-add-solo { border-radius: var(--ui-radius-md); padding: var(--ui-space-1) var(--ui-space-2); }
.ui-bind-collection { display: flex; flex-wrap: wrap; gap: var(--ui-space-3); padding: var(--ui-space-2); align-items: flex-start; }

/* ── Labeller-surface decorators + selector templates ─────────────── */
.ui-pill { display: inline-flex; align-items: center; padding: 1px var(--ui-space-2); border-radius: var(--ui-radius-full); font-size: var(--ui-text-2xs); font-weight: var(--ui-font-semibold); text-transform: uppercase; letter-spacing: 0.04em; color: #fff; background: var(--ui-pill-color, var(--ui-fg-muted)); }
/* Tone classes need to win over the variable-based .ui-pill above (which is
   declared later than the tone rules near .ui-pill-success); use compound
   selectors for higher specificity so the tone colour applies. */
.ui-pill.ui-pill-success { background: var(--ui-success); color: #fff; }
.ui-pill.ui-pill-warn    { background: var(--ui-warn);    color: #fff; }
.ui-pill.ui-pill-danger  { background: var(--ui-danger);  color: #fff; }
.ui-pill.ui-pill-primary { background: var(--ui-primary); color: #fff; }
.ui-bind-list { padding: var(--ui-space-2); }
.ui-bind-list-rows { display: flex; flex-direction: column; }
/* §5c — fixed row anatomy: avatar | head(flex, clipped) | pills(capped). The
   row never wraps; each text line is clipped to one line; pills can't dominate. */
.ui-bind-list-row { position: relative; min-width: 0; display: flex; flex-wrap: nowrap; align-items: center; column-gap: var(--ui-space-2); row-gap: 0; }
.ui-bind-list-head { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; line-height: 1.25; }
/* Clip title + subtitle to one line each, regardless of the call-site shape. */
.ui-bind-list-head > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.ui-bind-list-pills, .ui-bind-card-pills, .ui-bind-tree-pills { display: flex; flex-wrap: nowrap; gap: var(--ui-space-1); margin-left: auto; align-items: center; flex: 0 0 auto; min-width: 0; max-width: 55%; overflow: hidden; }
/* A pill takes the space its label needs and no more; long labels clip. */
.ui-bind-list-pills > *, .ui-bind-tree-pills > * { flex: 0 1 auto; min-width: 0; max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-bind-list-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--ui-bg-muted); }
.ui-bind-list-progress::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: var(--ui-progress-pct, 0%); background: var(--ui-progress-color, var(--ui-primary)); transition: width var(--ui-transition-base) var(--ui-ease); }
.ui-bind-entity-card-header { display: flex; align-items: center; gap: var(--ui-space-3); margin-bottom: var(--ui-space-2); }
.ui-bind-card-progress { margin-top: var(--ui-space-3); }
.ui-bind-entity-card-actions { position: absolute; top: var(--ui-space-2); right: var(--ui-space-2); display: flex; gap: var(--ui-space-1); opacity: 0; transition: opacity var(--ui-transition-fast); }
.ui-bind-entity-card:hover .ui-bind-entity-card-actions { opacity: 1; }
.ui-bind-dropdown { width: 100%; }
.ui-bind-chips     { display: flex; flex-wrap: wrap; gap: var(--ui-space-1); padding: var(--ui-space-2); }
.ui-bind-chips .ui-chip { cursor: pointer; }
.ui-bind-gallery   { display: flex; flex-direction: column; gap: var(--ui-space-2); padding: var(--ui-space-2); }
.ui-bind-gallery-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--ui-space-3); align-content: start; }
.ui-bind-gallery-tile { display: flex; flex-direction: column; cursor: pointer; border-radius: var(--ui-radius-lg); overflow: hidden; background: var(--ui-bg-elev); border: var(--ui-border-width) solid var(--ui-border); box-shadow: var(--ui-shadow-sm); transition: box-shadow var(--ui-transition-base), transform var(--ui-transition-base); }
.ui-bind-gallery-tile:hover { transform: translateY(-2px); box-shadow: var(--ui-shadow-md); }
.ui-bind-gallery-tile.ui-active { outline: 3px solid var(--ui-primary); outline-offset: -3px; }
.ui-bind-gallery-image { aspect-ratio: 1; background-size: cover; background-position: center; background-color: var(--ui-bg-muted); display: flex; align-items: center; justify-content: center; }
.ui-bind-gallery-image-initials .ui-avatar { width: 50%; height: 50%; }
.ui-bind-gallery-caption { padding: var(--ui-space-2) var(--ui-space-3) var(--ui-space-3); }
.ui-bind-compact   { padding: var(--ui-space-1); }
.ui-bind-compact-row { display: flex; align-items: center; gap: var(--ui-space-2); padding: var(--ui-space-1) var(--ui-space-2); cursor: pointer; border-radius: var(--ui-radius-sm); transition: background var(--ui-transition-fast); }
.ui-bind-compact-row:hover { background: var(--ui-bg-muted); }
.ui-bind-compact-row.ui-active { background: var(--ui-primary-100); color: var(--ui-primary-700); font-weight: var(--ui-font-semibold); }
.ui-bind-segmented { display: inline-flex; padding: 2px; gap: 2px; background: var(--ui-bg-muted); border-radius: var(--ui-radius-md); margin: var(--ui-space-2); }
.ui-bind-segmented .ui-segmented-item { cursor: pointer; white-space: nowrap; }
.ui-bind-segmented .ui-segmented-item.ui-active { background: var(--ui-bg-elev); box-shadow: var(--ui-shadow-sm); color: var(--ui-fg); }
.ui-metric-chip-icon { display: inline-flex; align-items: center; }
/* Row actions sit on their OWN line BELOW the label (flex-basis:100% wraps
   them under the item) rather than overlaying it — on a narrow tree/list the
   icons no longer smother the text. The actions div is a direct child of the
   row, so moving the cursor down onto a button keeps the row :hover (no
   disappear-trap); they also stay shown on the active row. Collapsed to zero
   height when idle so quiescent rows keep their single-line density. */
/* Row actions OVERLAY the right edge of the row on hover/active — same line,
   over the text, never a second line and never reflowing the label. The
   overlay carries the row's own background (inherit) so the text it covers is
   masked, and a left fade blends it into the label underneath. */
.ui-bind-list-row, .ui-bind-tree-node { position: relative; }
.ui-bind-list-actions {
    position: absolute; top: 0; bottom: 0; right: 0; order: 0;
    display: flex; align-items: center; gap: var(--ui-space-1);
    margin: 0; padding: 0 var(--ui-space-2) 0 var(--ui-space-5);
    max-height: none; overflow: visible; background: inherit;
    opacity: 0; pointer-events: none; transition: opacity var(--ui-transition-fast);
    -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--ui-space-4));
            mask-image: linear-gradient(to right, transparent, #000 var(--ui-space-4));
}
.ui-bind-list-row:hover > .ui-bind-list-actions, .ui-bind-list-row.ui-active > .ui-bind-list-actions,
.ui-bind-tree-node:hover > .ui-bind-list-actions, .ui-bind-tree-node.ui-active > .ui-bind-list-actions { opacity: 1; pointer-events: auto; }
.ui-bind-tree-node .ui-icon-btn, .ui-bind-list-row .ui-icon-btn { width: 1.1rem; height: 1.1rem; padding: 0; font-size: 0.7rem; }
.ui-mgr-pane { flex: 1 1 0; min-width: clamp(220px, 20vw, 320px); }
.ui-drilldown-list { display: flex; flex-direction: column; }
.ui-drilldown-row { display: flex; justify-content: space-between; padding: var(--ui-space-2) var(--ui-space-3); cursor: pointer; border-bottom: var(--ui-border-width) solid var(--ui-border); transition: background var(--ui-transition-fast); }
.ui-drilldown-row:hover { background: var(--ui-bg-muted); }
.ui-stepper-done { color: var(--ui-success); }
.ui-wizard-body { padding: var(--ui-space-4); min-height: 8rem; }
.ui-wizard-footer { display: flex; justify-content: space-between; padding: var(--ui-space-3); border-top: var(--ui-border-width) solid var(--ui-border); }
.ui-action { display: inline-flex; gap: var(--ui-space-1); }
/* Hyper-parameter live-edit panel (gallery) */
.ui-hp-controls { display: flex; flex-direction: column; gap: var(--ui-space-3); padding: var(--ui-space-3) var(--ui-space-4); margin-bottom: var(--ui-space-3); background: var(--ui-bg-elev); border: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-lg); }
.ui-hp-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-space-3); }
.ui-hp-row-label { font-size: var(--ui-text-xs); font-weight: var(--ui-font-semibold); color: var(--ui-fg-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.ui-hp-chips { display: flex; flex-wrap: wrap; gap: var(--ui-space-1); }
.ui-hp-chips .ui-chip { font-size: var(--ui-text-xs); padding: var(--ui-space-1) var(--ui-space-2); }
.ui-hp-font   { width: auto; min-width: 9rem; padding: var(--ui-space-1) var(--ui-space-2); font-size: var(--ui-text-sm); }
.ui-hp-weight { width: auto; min-width: 7rem; padding: var(--ui-space-1) var(--ui-space-2); font-size: var(--ui-text-sm); }
.ui-hp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--ui-space-3); padding-top: var(--ui-space-2); border-top: var(--ui-border-width) solid var(--ui-border); }
.ui-hp-cell { display: flex; flex-direction: column; gap: var(--ui-space-1); font-size: var(--ui-text-xs); color: var(--ui-fg-muted); }
.ui-hp-cell label { display: flex; align-items: center; justify-content: space-between; gap: var(--ui-space-2); font-weight: var(--ui-font-medium); color: var(--ui-fg); text-transform: uppercase; letter-spacing: 0.04em; font-size: var(--ui-text-2xs); }
.ui-hp-readout { font-family: var(--ui-font-mono); font-size: var(--ui-text-xs); color: var(--ui-fg-muted); padding: 0 var(--ui-space-1); background: var(--ui-bg-muted); border-radius: var(--ui-radius-sm); }
.ui-hp-slider { width: 100%; appearance: none; height: 4px; background: var(--ui-bg-muted); border-radius: var(--ui-radius-full); outline: none; }
.ui-hp-slider::-webkit-slider-thumb { appearance: none; width: 14px; height: 14px; border-radius: 50%; background: var(--ui-primary); cursor: pointer; box-shadow: var(--ui-shadow-sm); transition: transform var(--ui-transition-fast); }
.ui-hp-slider::-webkit-slider-thumb:hover { transform: scale(1.15); }
.ui-hp-slider::-moz-range-thumb { width: 14px; height: 14px; border: 0; border-radius: 50%; background: var(--ui-primary); cursor: pointer; box-shadow: var(--ui-shadow-sm); }
.ui-hp-cell-color { gap: 4px; }
.ui-hp-color-row { display: flex; align-items: center; gap: var(--ui-space-2); }
.ui-hp-color-row > span { font-family: var(--ui-font-mono); font-size: var(--ui-text-2xs); color: var(--ui-fg-muted); width: 0.9rem; flex-shrink: 0; }
.ui-hp-color-row .ui-hp-slider { flex: 1; }
.ui-hp-swatch { display: inline-block; width: 1.1rem; height: 1.1rem; border-radius: var(--ui-radius-sm); border: var(--ui-border-width) solid var(--ui-border); }
.ui-hp-snippet { display: flex; flex-direction: column; gap: var(--ui-space-2); padding-top: var(--ui-space-2); border-top: var(--ui-border-width) solid var(--ui-border); }
.ui-hp-snippet-bar { display: flex; align-items: center; gap: var(--ui-space-2); }
.ui-hp-snippet-name { flex: 0 1 12rem; font-family: var(--ui-font-mono); font-size: var(--ui-text-sm); }
.ui-hp-snippet-text { width: 100%; font-family: var(--ui-font-mono); font-size: var(--ui-text-xs); line-height: 1.5; resize: vertical; background: var(--ui-bg-sunken); }

/* ---- 7a. Bundled theme variants ---- comprehensive (font/weight/spacing/
 * corner/border/shadow/motion/hue) at the top, scoped tweaks at the bottom. */
.ui-theme-apple {
    --hp-font-heading: var(--hp-font-stack-display); --hp-font-body: var(--hp-font-stack-system); --hp-font-mono: var(--hp-font-stack-mono);
    --hp-weight-heading: 700; --hp-weight-body: 400; --hp-density: 1.15; --hp-pad-base: 0.55rem;
    --hp-text-ratio: 1.22; --hp-line-height: 1.5; --hp-corner-scale: 1.5; --hp-border-scale: 0.5;
    --hp-shadow-intensity: 0.08; --hp-transition-speed: 0.9;
    --hp-hue-1: 211; --hp-sat-1: 100%; --hp-hue-2: 268; --hp-sat-2: 70%; --hp-hue-accent: 32; --hp-sat-accent: 95%;
}
.ui-theme-metro {
    --hp-font-heading: var(--hp-font-stack-system); --hp-font-body: var(--hp-font-stack-system); --hp-font-mono: var(--hp-font-stack-mono);
    --hp-weight-heading: 300; --hp-weight-body: 400; --hp-density: 0.95; --hp-pad-base: 0.45rem;
    --hp-line-height: 1.4; --hp-corner-scale: 0; --hp-border-scale: 0; --hp-shadow-intensity: 0; --hp-transition-speed: 1.2;
    --hp-hue-1: 200; --hp-sat-1: 100%; --hp-hue-2: 14; --hp-sat-2: 92%; --hp-hue-accent: 130; --hp-sat-accent: 90%;
}
.ui-theme-material {
    --hp-font-heading: var(--hp-font-stack-roboto); --hp-font-body: var(--hp-font-stack-roboto); --hp-font-mono: var(--hp-font-stack-roboto-mono);
    --hp-weight-heading: 500; --hp-weight-body: 400; --hp-corner-scale: 0.5; --hp-border-scale: 0.7;
    --hp-shadow-intensity: 0.22;
    --hp-hue-1: 207; --hp-sat-1: 90%; --hp-hue-2: 174; --hp-sat-2: 70%; --hp-hue-accent: 36; --hp-sat-accent: 100%;
}
.ui-theme-brutalist {
    --hp-font-heading: var(--hp-font-stack-bebas); --hp-font-body: var(--hp-font-stack-mono); --hp-font-mono: var(--hp-font-stack-mono);
    --hp-weight-heading: 400; --hp-weight-body: 500; --hp-density: 1.05; --hp-text-scale: 1.05; --hp-text-ratio: 1.4;
    --hp-line-height: 1.4; --hp-corner-scale: 0; --hp-border-scale: 2.5; --hp-shadow-intensity: 0; --hp-transition-speed: 0.5;
    --hp-hue-1: 0; --hp-sat-1: 0%; --hp-hue-2: 0; --hp-sat-2: 0%; --hp-hue-accent: 50; --hp-sat-accent: 100%;
}
.ui-theme-newspaper {
    --hp-font-heading: var(--hp-font-stack-playfair); --hp-font-body: var(--hp-font-stack-source); --hp-font-mono: var(--hp-font-stack-source-code);
    --hp-weight-heading: 700; --hp-density: 0.95; --hp-text-ratio: 1.3; --hp-line-height: 1.65;
    --hp-corner-scale: 0.2; --hp-border-scale: 0.5; --hp-shadow-intensity: 0.04;
    --hp-hue-1: 24; --hp-sat-1: 60%; --hp-hue-2: 15; --hp-sat-2: 35%; --hp-hue-accent: 0; --hp-sat-accent: 70%;
}
.ui-theme-mono {
    --hp-font-heading: var(--hp-font-stack-jetbrains); --hp-font-body: var(--hp-font-stack-jetbrains); --hp-font-mono: var(--hp-font-stack-jetbrains);
    --hp-weight-heading: 600; --hp-text-scale: 0.94; --hp-line-height: 1.55;
    --hp-corner-scale: 0.4; --hp-border-scale: 0.8; --hp-shadow-intensity: 0.06;
    --hp-hue-1: 152; --hp-sat-1: 55%; --hp-hue-2: 25; --hp-sat-2: 70%; --hp-hue-accent: 268; --hp-sat-accent: 70%;
}
.ui-theme-serif {
    --hp-font-heading: var(--hp-font-stack-garamond); --hp-font-body: var(--hp-font-stack-garamond); --hp-font-mono: var(--hp-font-stack-mono);
    --hp-weight-heading: 600; --hp-density: 1.05; --hp-text-scale: 1.05; --hp-text-ratio: 1.28; --hp-line-height: 1.6;
    --hp-corner-scale: 0.4; --hp-border-scale: 0.5; --hp-shadow-intensity: 0.06;
    --hp-hue-1: 212; --hp-sat-1: 45%; --hp-hue-2: 14; --hp-sat-2: 50%; --hp-hue-accent: 33; --hp-sat-accent: 70%;
}
/* Scoped tweaks — colour-only or density-only. */
.ui-theme-emerald  { --hp-hue-1: 158; --hp-sat-1: 70%; --hp-hue-2: 195; }
.ui-theme-amber    { --hp-hue-1: 32;  --hp-sat-1: 92%; --hp-hue-2: 14;  --hp-sat-2: 80%; }
.ui-theme-violet   { --hp-hue-1: 268; --hp-sat-1: 70%; --hp-hue-2: 320; --hp-sat-2: 65%; }
.ui-theme-slate    { --hp-hue-1: 215; --hp-sat-1: 18%; --hp-hue-2: 215; --hp-sat-2: 18%; --hp-shadow-intensity: 0.05; }
.ui-theme-compact  { --hp-density: 0.8; --hp-text-scale: 0.92; --hp-corner-scale: 0.8; }
.ui-theme-spacious { --hp-density: 1.2; --hp-text-scale: 1.06; --hp-corner-scale: 1.2; }
.ui-theme-rounded  { --hp-corner-scale: 1.6; }
.ui-theme-sharp    { --hp-corner-scale: 0; --hp-border-scale: 1.2; }

/* ---- 7b. Animations ---- */
@keyframes ui-fade-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes ui-fade-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
.ui-pageframe-stage > *, .ui-pageframe-body > * { animation: ui-fade-in var(--ui-transition-base) var(--ui-ease) both; }
.ui-modal { animation: ui-fade-up var(--ui-transition-base) var(--ui-ease) both; }

/* ---- 8. Responsive ---- */
@media (max-width: 640px) {
    :root { --hp-density: 0.9; --hp-text-scale: 0.94; }
    .ui-pageframe-header { flex-wrap: wrap; }
    .ui-pageframe-actions { margin-left: 0; }
    .ui-data-cards-grid > *, .ui-bind-cards > * { flex-basis: 100%; max-width: 100%; }
    .ui-modal { padding: var(--ui-space-4); border-radius: var(--ui-radius-lg); }
    /* Fixed multi-column grids stack on phones (cols-N opt out of mobile otherwise). */
    .ui-grid-cols-2, .ui-grid-cols-3, .ui-grid-cols-2-1, .ui-grid-cols-1-2 { grid-template-columns: 1fr; }
    /* Page-frame nav collapses into the hamburger: hidden until opened, then a
       full-width vertical drop-down below the header row. */
    .ui-pageframe-burger { display: inline-flex; }
    .ui-pageframe-tabs { display: none; order: 5; flex-basis: 100%; }
    .ui-pageframe-nav-open .ui-pageframe-tabs { display: flex; flex-direction: column; align-items: stretch; gap: var(--ui-space-1); }
    .ui-pageframe-nav-open .ui-tabs-bar, .ui-pageframe-nav-open .ui-navbar { flex-direction: column; }
    /* Standalone nav/tabs (not in a page-frame) wrap rather than clip. */
    .ui-navbar:not(.ui-pageframe-tabs *), .ui-tabs-bar:not(.ui-pageframe-tabs *) { flex-wrap: wrap; }
}
@media (min-width: 1600px) { :root { --hp-text-scale: 1.05; } }

/* Heatmap — service-agnostic band-coloured grid (used by Accreditation matrix). */
.ui-heatmap { border-collapse: collapse; width: 100%; font-size: var(--ui-text-sm); }
.ui-heatmap th, .ui-heatmap td { border: 1px solid var(--ui-gray-200); padding: var(--ui-space-2); text-align: center; min-width: 64px; }
.ui-heatmap td:first-child, .ui-heatmap th:first-child { text-align: left; font-weight: var(--ui-font-semibold); }
.ui-band { cursor: pointer; transition: opacity var(--ui-transition-fast); }
.ui-band:hover { opacity: 0.85; outline: 2px solid var(--ui-primary); outline-offset: -2px; }
.ui-band-emerging   { background: var(--ui-danger-100);  color: var(--ui-danger); }
.ui-band-developed  { background: var(--ui-warn-100);    color: var(--ui-warn); }
.ui-band-exit-level { background: var(--ui-success-100); color: var(--ui-success); }
.ui-band-exceeds    { background: var(--ui-primary-100); color: var(--ui-primary); }
.ui-band-not-met    { background: var(--ui-gray-100);    color: var(--ui-text-muted); }
.ui-band-empty      { color: var(--ui-text-muted); }

/* Trend bar — cross-scan metric strip (PerformanceScanner.renderMetricTrend). */
.ui-trend-bar { flex: 1; height: 12px; background: var(--ui-gray-100); border-radius: var(--ui-radius-sm); position: relative; overflow: hidden; }
.ui-trend-bar::after { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: var(--w, 0%); background: var(--ui-primary); }
/* Row label beside a trend bar — fixed gutter so the bars share one baseline.
   Was an inline style="min-width:9rem" at the call site (design §35 / #8). */
.ui-trend-label { min-width: 9rem; }

/* ProcessFlow step-map legend — was four inline style="color:#hex" swatches in
   the service, invisible to every theme. Tokens only (design §35 / #8). */
.pf-legend-done    { color: var(--ui-success); }
.pf-legend-current { color: var(--ui-warning); }
.pf-legend-pending { color: var(--ui-fg-muted); }
.pf-legend-split   { color: var(--ui-accent, var(--ui-primary)); }

/* TestRig console layout — was inline style= on the column wrappers, the
   field labels and the service cards (design §35 / #8). */
.trig-cols        { gap: var(--ui-space-3); align-items: flex-start; }
.trig-col         { flex: 1; min-width: 240px; }
.trig-col-wide    { flex: 2; min-width: 380px; }
.trig-field-label { min-width: 180px; }
.trig-svc-card    { flex: 1 1 250px; min-width: 250px; max-width: 320px; }
.trig-svc-list    { display: flex; flex-wrap: wrap; gap: var(--ui-space-3); align-items: flex-start; }

/* Map offline-basemap note — the remote style could not be fetched, so the map
   fell back to a network-free background. Stated, never absorbed. */
.ui-map-offline-note { position: absolute; left: 8px; bottom: 8px; z-index: 2;
  background: var(--ui-bg-elev); color: var(--ui-fg-muted); border: 1px solid var(--ui-border);
  border-radius: var(--ui-radius-sm); padding: 4px 8px; font-size: var(--ui-text-xs); }

/* uiMcq — multiple-choice question primitive (class.ui.js). One ruleset
   per concept; lean. Hyper-tokens used directly so themes can re-skin. */
.ui-mcq { background: var(--ui-bg-elevated); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg); padding: var(--ui-space-4) var(--ui-space-5); }
.ui-mcq-tag { text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--ui-text-xs); font-weight: 600; color: var(--ui-text-muted); margin-bottom: var(--ui-space-2); }
.ui-mcq-statement { font-size: var(--ui-text-base); margin-bottom: var(--ui-space-3); }
.ui-mcq-options { display: flex; flex-direction: column; gap: var(--ui-space-2); margin-bottom: var(--ui-space-2); }
.ui-mcq-option { display: flex; align-items: flex-start; gap: var(--ui-space-3); padding: var(--ui-space-2) var(--ui-space-3); background: var(--ui-bg); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); cursor: pointer; text-align: left; font-size: var(--ui-text-sm); transition: border-color 100ms, background 100ms; }
.ui-mcq-option:hover:not(:disabled) { border-color: var(--ui-primary); }
.ui-mcq-option:disabled { cursor: default; }
.ui-mcq-letter { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--ui-radius-full, 999px); background: var(--ui-bg-muted); color: var(--ui-text-muted); font-weight: 700; font-size: var(--ui-text-xs); flex-shrink: 0; }
.ui-mcq-label { flex: 1; }
.ui-mcq-label > p:first-child { margin-top: 0; }
.ui-mcq-correct { background: var(--ui-success-100); border-color: var(--ui-success); }
.ui-mcq-correct .ui-mcq-letter { background: var(--ui-success); color: white; }
.ui-mcq-wrong { background: var(--ui-danger-100); border-color: var(--ui-danger); }
.ui-mcq-wrong .ui-mcq-letter { background: var(--ui-danger); color: white; }
.ui-mcq-result { font-weight: 600; margin-top: var(--ui-space-2); }
.ui-mcq-result-correct { color: var(--ui-success); }
.ui-mcq-result-wrong { color: var(--ui-danger); }
.ui-mcq-solution { background: var(--ui-bg); padding: var(--ui-space-3); border-radius: var(--ui-radius-md); border-left: 3px solid var(--ui-primary); margin-top: var(--ui-space-2); }

/* Casework Workbench — two-region layout. A fixed-width side panel (selected-
   case detail stacked above the category rail) and a wide kanban board that
   takes all remaining width so lanes aren't forced thin. Wraps on narrow. */
.ui-cw-workbench { flex-wrap: wrap; }
.ui-cw-pane { display: flex; flex-direction: column; min-width: 0; gap: var(--ui-space-3); }
.ui-cw-pane-side  { flex: 0 0 22rem; }
.ui-cw-pane-board { flex: 1 1 0; min-width: 0; }
/* 3-column workbench panes — used by NINE services (Analytics, Allocation,
   Approval, CareerHub, ProcessFlow, Scheduling, Screening, Survey, Workload)
   but never styled: every such workbench silently stacked into one column
   with a blank right region (/fullservice 2026-07-03 AN-1, systemic). */
.ui-cw-pane-left   { flex: 0 0 22rem; }
.ui-cw-pane-center { flex: 1 1 0; min-width: 0; }
.ui-cw-pane-right  { flex: 0 0 24rem; }
.ui-cw-detail { display: flex; flex-direction: column; gap: var(--ui-space-3); }
@media (max-width: 60rem) {
    .ui-cw-pane-side, .ui-cw-pane-board,
    .ui-cw-pane-left, .ui-cw-pane-center, .ui-cw-pane-right { flex: 1 1 100%; }
}

/* Chart canvas — responsive container for embedded Plotly plots
   (used by Runbook, Ordering, Specimen, etc.). clamp() scales with viewport
   while keeping a usable minimum + maximum so Plotly always has size. */
.ui-rb-chart { width: 100%; height: clamp(16rem, 40vh, 28rem); }

/* Workload utilization bars — width driven by --util-pct CSS variable set
   inline (data-bound, not layout). Colour band via modifier class. */
.ui-wl-util-row   { display: flex; align-items: center; gap: var(--ui-space-2); margin: var(--ui-space-1) 0; }
.ui-wl-util-label { flex: 0 0 9rem; font-size: var(--ui-text-sm); color: var(--ui-fg-muted); }
.ui-wl-util-bar   { flex: 1 1 auto; height: 10px; background: var(--ui-bg-muted); border-radius: var(--ui-radius-sm); overflow: hidden; min-width: 4rem; }
.ui-wl-util-fill  { display: block; height: 100%; background: var(--ui-success); transition: width 0.3s; width: var(--util-pct, 0%); }
.ui-wl-util-fill.ui-wl-util-amber { background: var(--ui-warning); }
.ui-wl-util-fill.ui-wl-util-red   { background: var(--ui-danger); }
.ui-wl-util-fill.ui-wl-util-dark  { background: var(--ui-danger-700); }
.ui-wl-util-pct   { flex: 0 0 3.5rem; text-align: right; font-variant-numeric: tabular-nums; font-size: var(--ui-text-sm); }

/* Analytics KPI cards — flex grid of selectable cards, each with a large
   value number, target context, status badge, and trend arrow. Card colour
   accent and trend glyph are data-driven via the AN_STATUS_* maps in code. */
.ui-an-kpi-grid   { display: flex; flex-wrap: wrap; gap: var(--ui-space-3); }
.ui-an-kpi-card   { flex: 1 1 14rem; min-width: 12rem; padding: var(--ui-space-3); border-radius: var(--ui-radius-lg); background: var(--ui-bg-elev); border: 1px solid var(--ui-border); cursor: pointer; transition: box-shadow var(--ui-transition-fast); display: flex; flex-direction: column; gap: var(--ui-space-1); }
.ui-an-kpi-card:hover { box-shadow: var(--ui-shadow-md); }
.ui-an-kpi-card.ui-active { border-color: var(--ui-primary); box-shadow: 0 0 0 2px var(--ui-primary-100); }
.ui-an-kpi-name   { font-size: var(--ui-text-sm); color: var(--ui-fg-muted); font-weight: var(--ui-font-semibold); }
.ui-an-kpi-value  { font-size: 2rem; font-weight: var(--ui-font-bold); font-variant-numeric: tabular-nums; line-height: 1.1; }
.ui-an-kpi-unit   { font-size: var(--ui-text-sm); color: var(--ui-fg-muted); margin-left: var(--ui-space-1); }
.ui-an-kpi-meta   { display: flex; align-items: center; justify-content: space-between; gap: var(--ui-space-2); font-size: var(--ui-text-xs); color: var(--ui-fg-muted); }
.ui-an-kpi-trend  { font-variant-numeric: tabular-nums; }
.ui-an-kpi-trend.ui-an-up    { color: var(--ui-success); }
.ui-an-kpi-trend.ui-an-down  { color: var(--ui-danger); }
.ui-an-kpi-trend.ui-an-flat  { color: var(--ui-fg-muted); }

/* Specimen / Chain-of-Custody — bespoke visuals for the LIMS service. */
.ui-spc-tree { display: flex; flex-direction: column; gap: var(--ui-space-1); }
.ui-spc-tree-node { padding: var(--ui-space-2) var(--ui-space-3); border-left: 3px solid var(--ui-border); border-radius: var(--ui-radius-md); background: var(--ui-bg); margin-left: calc(var(--tree-depth, 0) * var(--ui-space-4)); }
.ui-spc-tree-selected { border-left-color: var(--ui-primary); background: var(--ui-primary-50); }
.ui-spc-tree-head { display: flex; align-items: center; gap: var(--ui-space-2); flex-wrap: wrap; }
.ui-spc-tree-bullet { color: var(--ui-fg-muted); }

.ui-spc-timeline { display: flex; flex-direction: column; gap: var(--ui-space-2); }
.ui-spc-timeline-row { display: flex; gap: var(--ui-space-3); padding: var(--ui-space-2) var(--ui-space-3); border-left: 3px solid var(--ui-primary); background: var(--ui-bg); border-radius: var(--ui-radius-md); }
.ui-spc-timeline-icon { font-size: 1.25rem; flex: 0 0 1.5rem; }
.ui-spc-timeline-body { flex: 1 1 auto; min-width: 0; }

.ui-spc-fill { background: var(--ui-bg-muted); border-radius: var(--ui-radius-full); overflow: hidden; height: 0.5rem; margin-top: var(--ui-space-2); }
.ui-spc-fill-bar { height: 100%; transition: width 200ms ease; width: var(--fill-pct, 0%); }
.ui-spc-fill-success { background: var(--ui-success); }
.ui-spc-fill-warn    { background: var(--ui-warn); }
.ui-spc-fill-danger  { background: var(--ui-danger); }

.ui-spc-chips { display: flex; flex-wrap: wrap; gap: var(--ui-space-1); margin-top: var(--ui-space-2); }
.ui-spc-chip { display: inline-flex; align-items: center; padding: 1px var(--ui-space-2); border-radius: var(--ui-radius-sm); font-size: var(--ui-text-xs); background: var(--ui-bg-muted); color: var(--ui-fg); border: 1px solid var(--ui-border); }
.ui-spc-chip-more { background: transparent; font-style: italic; color: var(--ui-fg-muted); }

.ui-spc-due { padding: var(--ui-space-2) var(--ui-space-3); border-radius: var(--ui-radius-md); border-left: 3px solid var(--ui-warn); background: var(--ui-bg); font-size: var(--ui-text-sm); }
.ui-spc-due-past { border-left-color: var(--ui-danger); background: var(--ui-danger-50); }

.ui-btn-tiny { padding: 0 var(--ui-space-2); font-size: var(--ui-text-xs); height: auto; line-height: 1.4; }
.ui-btn-success { background: var(--ui-success); color: #fff; border-color: var(--ui-success); }
.ui-btn-success:hover { filter: brightness(0.95); }

/* Procedure — scope-of-accreditation matrix (analytes × matrices). */
.ui-proc-scope { display: flex; flex-direction: column; gap: 2px; }
.ui-proc-scope-row { display: flex; gap: 2px; }
.ui-proc-scope-cell { flex: 1 1 0; min-width: 6rem; padding: var(--ui-space-2); border-radius: var(--ui-radius-sm); background: var(--ui-bg); border: 1px solid var(--ui-border); font-size: var(--ui-text-sm); display: flex; flex-wrap: wrap; gap: var(--ui-space-1); align-items: center; }
.ui-proc-scope-corner { background: var(--ui-bg-muted); font-style: italic; color: var(--ui-fg-muted); justify-content: center; }
.ui-proc-scope-mhead { background: var(--ui-bg-muted); font-weight: var(--ui-font-semibold); justify-content: center; }
.ui-proc-scope-ahead { background: var(--ui-bg-muted); font-weight: var(--ui-font-semibold); }
.ui-proc-scope-success { background: var(--ui-success-50); border-color: var(--ui-success); }
.ui-proc-scope-warn    { background: var(--ui-warn-50); border-color: var(--ui-warn); }
.ui-proc-scope-danger  { background: var(--ui-danger-50); border-color: var(--ui-danger); }
.ui-proc-scope-muted   { background: var(--ui-bg-muted); }
.ui-proc-scope-pill { display: inline-flex; padding: 1px var(--ui-space-2); border-radius: var(--ui-radius-sm); font-size: var(--ui-text-xs); background: var(--ui-bg); border: 1px solid var(--ui-border); cursor: help; }
.ui-proc-scope-pill.ui-proc-scope-success { background: var(--ui-success); color: #fff; border-color: var(--ui-success); }
.ui-proc-scope-pill.ui-proc-scope-warn    { background: var(--ui-warn); color: #fff; border-color: var(--ui-warn); }
.ui-proc-scope-pill.ui-proc-scope-danger  { background: var(--ui-danger); color: #fff; border-color: var(--ui-danger); }

.ui-proc-req-list { display: flex; flex-direction: column; gap: var(--ui-space-1); }
.ui-proc-req-item { padding: var(--ui-space-1) var(--ui-space-2); border-left: 3px solid var(--ui-primary); background: var(--ui-bg-muted); border-radius: var(--ui-radius-sm); font-size: var(--ui-text-sm); }
.ui-proc-rev-current { border-left-color: var(--ui-success); background: var(--ui-success-50); }

/* Risk — 5×5 likelihood × impact heatmap. */
/* Score-engine columns must be shrinkable grid children (min-width:auto is
   the overflow trap the chart containment above closes from the other side). */
.ui-rsk-engine-cols, .ui-rsk-engine-col { min-width: 0; max-width: 100%; }
.ui-rsk-heatmap { display: flex; flex-direction: column; gap: 2px; }
.ui-rsk-heat-row { display: flex; gap: 2px; }
.ui-rsk-heat-cell { flex: 1 1 0; min-width: 6rem; min-height: 5rem; padding: var(--ui-space-2); border-radius: var(--ui-radius-sm); border: 1px solid var(--ui-border); font-size: var(--ui-text-xs); display: flex; flex-direction: column; gap: var(--ui-space-1); }
.ui-rsk-heat-head    { background: var(--ui-bg-muted); font-weight: var(--ui-font-semibold); justify-content: center; align-items: center; min-height: 2rem; flex-direction: row; }
.ui-rsk-heat-corner  { background: var(--ui-bg-muted); font-style: italic; color: var(--ui-fg-muted); justify-content: center; align-items: center; min-height: 2rem; flex-direction: row; }
.ui-rsk-heat-low        { background: #d1fae5; border-color: #10b981; }
.ui-rsk-heat-mediumLow  { background: #d9f99d; border-color: #84cc16; }
.ui-rsk-heat-mediumHigh { background: #fef3c7; border-color: #f59e0b; }
.ui-rsk-heat-high       { background: #fecaca; border-color: #ef4444; }
.ui-rsk-heat-score { font-weight: var(--ui-font-semibold); font-size: var(--ui-text-sm); }
.ui-rsk-heat-chips { display: flex; flex-wrap: wrap; gap: 2px; }

.ui-rsk-card-success { border-left-color: var(--ui-success) !important; }
.ui-rsk-card-warn    { border-left-color: var(--ui-warn) !important; }
.ui-rsk-card-danger  { border-left-color: var(--ui-danger) !important; }

/* QR — printable label preview (mimics a real sticker, responsive). */
.ui-qr-label { display: flex; flex-direction: column; border: 2px dashed var(--ui-border); padding: var(--ui-space-3); border-radius: var(--ui-radius-md); background: var(--ui-bg-elev); width: 100%; max-width: 28rem; box-shadow: var(--ui-shadow-sm); }
.ui-qr-label-head { font-size: var(--ui-text-xs); font-weight: var(--ui-font-semibold); color: var(--ui-fg-muted); border-bottom: 1px solid var(--ui-border); padding-bottom: var(--ui-space-1); margin-bottom: var(--ui-space-2); text-transform: uppercase; letter-spacing: 0.06em; }
.ui-qr-label-body { display: flex; flex-direction: row; flex-wrap: wrap; gap: var(--ui-space-3); align-items: center; }
.ui-qr-canvas { flex: 0 0 auto; aspect-ratio: 1; width: clamp(8rem, 28vw, 12rem); display: flex; align-items: center; justify-content: center; background: var(--ui-bg-sunken); border: 1px solid var(--ui-border); }
.ui-qr-canvas canvas, .ui-qr-canvas img { display: block; max-width: 100%; max-height: 100%; }
.ui-qr-label-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--ui-space-1); }
.ui-qr-label-code { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: var(--ui-text-base); font-weight: var(--ui-font-semibold); word-break: break-all; }
.ui-qr-label-url  { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: var(--ui-text-xs); color: var(--ui-fg-muted); word-break: break-all; }
.ui-qr-fallback   { font-family: ui-monospace, monospace; text-align: center; color: var(--ui-fg-muted); font-size: var(--ui-text-xs); line-height: 1.4; }

/* Chat bubbles — used by MessagesService.renderConversation. Bubbles are
   left-aligned by default; .ui-msg-mine rows align right + use the primary
   colour so the user can tell their own messages apart at a glance. */
.ui-msg-list { display: flex; flex-direction: column; gap: var(--ui-space-2); padding: var(--ui-space-2); background: var(--ui-bg-sunken, var(--ui-gray-50)); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); }
.ui-msg-row { display: flex; flex-direction: column; align-items: flex-start; max-width: 78%; }
.ui-msg-row.ui-msg-mine { align-self: flex-end; align-items: flex-end; }
.ui-msg-meta { font-size: var(--ui-text-xs); color: var(--ui-fg-muted); margin-bottom: 2px; }
.ui-msg-bubble { background: var(--ui-bg-elev); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg); padding: var(--ui-space-2) var(--ui-space-3); white-space: pre-wrap; word-wrap: break-word; line-height: 1.4; }
.ui-msg-bubble.ui-msg-bubble-mine { background: var(--ui-primary-100); border-color: var(--ui-primary-200); }
.ui-msg-compose { align-items: stretch; }
.ui-msg-input { flex: 1 1 auto; resize: vertical; min-height: 2.4rem; }

/* NotificationService — bell chip + inbox card states. The unread card uses
   a primary-tinted left border so it reads as "needs attention" without
   shouting; read cards drop to the muted border colour. */
.ui-ntf-bell { font-weight: var(--ui-font-semibold); padding: var(--ui-space-1) var(--ui-space-2); }
.ui-ntf-unread { border-left: 3px solid var(--ui-primary) !important; }
.ui-ntf-read   { opacity: 0.85; }

/* ─── Pane work surface — desktop-instrument shell (see design/) ───────────
   Layout regions are PANES: full-bleed, hairline-divided, no shadow/rounding.
   Cards represent objects only. Host: .ui-panes-host wraps panes + statusbar
   and stretches to the viewport remainder via --ui-panes-minh. */
.ui-panes-host { display: flex; flex-direction: column; flex: 1 1 auto; min-height: var(--ui-panes-minh, calc(100dvh - 130px)); }
.ui-pageframe-stage:has(> .ui-panes-host) { padding: 0; }
.ui-pageframe-stage > .ui-panes-host { animation: none; }
/* Grouped tab — a segmented sub-nav above a filling body. Lets one top-level
   tab host several related views so the main nav strip doesn't overcrowd.
   The nested .ui-panes-host fills the body instead of the viewport calc. */
.ui-subnav-host { display: flex; flex-direction: column; flex: 1 1 auto; min-height: var(--ui-panes-minh, calc(100dvh - 130px)); }
.ui-subnav-bar  { flex: 0 0 auto; padding: 0 var(--ui-space-3); border-bottom: var(--ui-border-width) solid var(--ui-border); }
/* Subnav is a tabset, not a pill toggle — small, tight, flat icon+text tabs; a
   quiet primary underline marks the active one (no heavy filled pill). §0 tabs. */
.ui-subnav-bar .ui-segmented { background: transparent; padding: 0; border-radius: 0; gap: 0; }
.ui-subnav-bar .ui-segmented-item { border-radius: 0; padding: var(--ui-space-2) var(--ui-space-2); }
.ui-subnav-bar .ui-segmented-item.ui-active { background: transparent; color: var(--ui-primary); box-shadow: inset 0 -2px 0 var(--ui-primary); font-weight: var(--ui-font-semibold); }
.ui-subnav-bar .ui-segmented-item .fa { font-size: 0.9em; opacity: 0.75; }
.ui-subnav-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ui-subnav-body > * { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ui-subnav-body .ui-panes-host { min-height: 0; }
/* Record split — a wide central work surface beside a narrower reference rail.
   The high-dwell facet (a live conversation, the main document) takes the main
   column; low-change facets fold into the rail (an accordion). Design 2c. */
.ui-record-split { display: flex; flex: 1 1 auto; min-height: 0; }
.ui-record-main  { flex: 2 1 0; min-width: 0; display: flex; flex-direction: column; }
.ui-record-rail  { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; overflow: auto; border-left: var(--ui-border-width) solid var(--ui-border); }
.ui-pageframe-stage:has(> .ui-subnav-host) { padding: 0; }
.ui-pageframe-stage > .ui-subnav-host { animation: none; }
.ui-panes { display: flex; flex: 1 1 auto; min-height: 0; background: var(--ui-bg-elev); border-bottom: 0; }
.ui-pane  { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.ui-pane + .ui-pane { border-left: var(--ui-border-width) solid var(--ui-border); }
.ui-pane-fixed { flex: 0 0 300px; }
.ui-pane-fill  { flex: 1 1 auto; }
.ui-pane-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; }
/* Responsive (D3): below 640px the side-by-side panes would cramp (titles wrap,
   the rail squeezes), so stack them vertically — the divider moves to the top
   edge and fixed-width panes go full-width. Desktop/tablet (≥640px) unchanged. */
@media (max-width: 640px) {
    .ui-panes { flex-direction: column; }
    .ui-pane-fixed { flex: 0 0 auto; }
    .ui-pane + .ui-pane { border-left: 0; border-top: var(--ui-border-width) solid var(--ui-border); }
    .ui-panes-host { min-height: var(--ui-panes-minh, calc(100dvh - 110px)); }
}
.ui-pane-head { display: flex; align-items: center; gap: var(--ui-space-2); padding: var(--ui-space-2) var(--ui-space-3); border-bottom: var(--ui-border-width) solid var(--ui-border); flex: 0 0 auto; min-height: 2.25rem; }
/* Title owns the head's free width (flex-1); meta yields by truncating —
   otherwise a long meta crushes the title to "B…" and the pane loses its identity. */
.ui-pane-head-title { font-weight: var(--ui-font-semibold); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0; }
.ui-pane-head-meta  { color: var(--ui-fg-muted); font-size: var(--ui-text-sm); white-space: nowrap; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ui-toolbar { display: flex; align-items: center; gap: var(--ui-space-2); padding: var(--ui-space-1) var(--ui-space-2); border-bottom: var(--ui-border-width) solid var(--ui-border); flex: 0 0 auto; min-height: 2rem; }
.ui-statusbar { display: flex; align-items: center; gap: var(--ui-space-4); padding: 0 var(--ui-space-3); height: 1.5rem; border-top: var(--ui-border-width) solid var(--ui-border); background: var(--ui-bg-muted); font-size: var(--ui-text-xs); color: var(--ui-fg-muted); flex: 0 0 auto; }
.ui-statusbar strong { color: var(--ui-fg); font-weight: var(--ui-font-semibold); }
.ui-statusbar-inner { display: flex; align-items: center; gap: var(--ui-space-4); min-width: 0; }
/* Record surface — facets in parallel (design/ principle 2b). */
.ui-facet-grid { display: flex; flex: 1 1 auto; min-height: 0; align-items: stretch; }
.ui-facet-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
.ui-facet-col + .ui-facet-col { border-left: var(--ui-border-width) solid var(--ui-border); }
.ui-facet { display: flex; flex-direction: column; min-height: 0; border-bottom: var(--ui-border-width) solid var(--ui-border); }
.ui-facet-head { display: flex; align-items: center; gap: var(--ui-space-2); padding: var(--ui-space-1) var(--ui-space-2); border-bottom: var(--ui-border-width) solid var(--ui-border); background: var(--ui-bg-muted); flex: 0 0 auto; }
.ui-facet-title { font-size: var(--ui-text-sm); font-weight: var(--ui-font-semibold); }
.ui-facet-count { font-size: var(--ui-text-xs); color: var(--ui-fg-muted); }
.ui-facet-body { min-height: 0; max-height: 24rem; overflow: auto; }
.ui-facet-tall { max-height: none; flex: 1 1 auto; }
.ui-pane-info { border-top: var(--ui-border-width) solid var(--ui-border); padding: var(--ui-space-2) var(--ui-space-3); flex: 0 0 auto; }
.ui-panes .ui-bind-layout { border: 0; box-shadow: none; border-radius: 0; }
/* Chart frames flatten inside panes too — the card chrome (border/radius/
   padding) is for charts standing alone on a page, not pane central elements. */
.ui-panes .ui-chart, .ui-panes .ui-chart-frame { border: 0; box-shadow: none; border-radius: 0; padding: 0; background: transparent; }
/* Avatars ARE shown in instrument lists (mono — see .ui-avatar-c* default). The
   old `.ui-panes .ui-avatar{display:none}` text-first suppression was removed. */
.ui-feed { display: flex; flex-direction: column; }
.ui-feed-row { display: flex; flex-direction: column; gap: var(--ui-space-1); padding: var(--ui-space-2) var(--ui-space-3); border-bottom: var(--ui-border-width) solid var(--ui-border); }
.ui-feed-row-click { cursor: pointer; }
.ui-feed-row-click:hover { background: var(--ui-bg-muted); }
/* Native colour swatch in a toolbar — without a size it collapses to a sliver. */
.ui-input-color { inline-size: 2.5rem; block-size: 1.9rem; padding: 2px; flex: 0 0 auto; }
.ui-form-modal { min-width: min(420px, 80vw); }
.ui-input-invalid { border-color: var(--ui-danger) !important; }
.ui-pill-toggle { cursor: pointer; }
.ui-pill.ui-pill-off { background: var(--ui-bg-muted); color: var(--ui-fg-muted); }
.ui-dot { width: 14px; height: 14px; border-radius: 50%; display: inline-block; flex: 0 0 auto; background: var(--ui-dot-color, var(--ui-fg-muted)); }
.ui-input-compact { width: auto; flex: 0 0 auto; max-width: 8rem; }
.ui-ul-tight { margin: 0; padding-left: 1.2em; }
/* Inside panes the binding's chrome row becomes a flush toolbar strip. */
.ui-panes .ui-bind-list-bar { margin: 0; padding: var(--ui-space-1) var(--ui-space-2); border-bottom: var(--ui-border-width) solid var(--ui-border); }
.ui-panes .ui-bind-list-rows > * { padding-left: var(--ui-space-3); padding-right: var(--ui-space-3); }

/* Opt-in list section header (bindSelector groupBy) — Moodle-style topic band. */
.ui-bind-list-group { padding: 6px 10px 4px; font-size: var(--ui-text-xs, 0.72rem); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.04em; color: var(--ui-text-muted, #6b7280);
    background: var(--ui-bg-muted, #f4f5f7); border-bottom: 1px solid var(--ui-border, #e5e7eb); }

/* Gradebook grader-report cells (Service.Markbook.renderGradebook) — colour by
   attainment band; no inline styles at the call site (design §35). */
.ui-gradecell        { text-align: center; }
.ui-gradecell-hi     { background: var(--ui-success-100); }
.ui-gradecell-mid    { background: var(--ui-warn-100); }
.ui-gradecell-lo     { background: var(--ui-danger-100); }
.ui-gradecell-empty  { background: var(--ui-gray-100); color: var(--ui-text-muted); }
.ui-gradecell-total  { font-weight: 700; border-left: 2px solid var(--ui-gray-200); }
.ui-grade-name       { font-weight: 600; }
.ui-gradecell-edit   { padding: 0; }
.ui-gradecell-input  { width: 100%; min-width: 3rem; box-sizing: border-box; border: 0; background: transparent; text-align: center; font: inherit; color: inherit; padding: var(--ui-space-2) var(--ui-space-1); cursor: text; }
.ui-gradecell-input:focus { outline: 2px solid var(--ui-primary); outline-offset: -2px; background: var(--ui-bg); }
.ui-gradecell-input:hover { background: rgba(0,0,0,0.03); }
.ui-gradecell-input.ui-gradecell-bad { outline: 2px solid var(--ui-danger); outline-offset: -2px; }
.ui-grade-scroll     { overflow-x: auto; }

/* Metadata export preview (Service.Webbook.renderPublishing) — wrapped, scrollable,
   monospace XML; call site stays inline-style-free (design §35). */
.ui-export-pre { max-height: 320px; overflow: auto; background: var(--ui-gray-50, #f8fafc);
  padding: 12px; border-radius: var(--ui-radius-md, 6px); font-size: var(--ui-text-xs, 0.72rem);
  white-space: pre-wrap; word-break: break-word; }

/* Facet fill + dominance (deep-review pass 2) — a growing facet fills its
   column (its body scrolls), killing the white gulf; main/rail column widths
   make one facet the dominant central element with a subordinate side rail. */
.ui-facet-grow { flex: 1 1 auto; min-height: 0; }
.ui-facet-grow > .ui-facet-body { flex: 1 1 auto; min-height: 0; overflow: auto; }
.ui-facet-col-main { flex: 1.7 1 0; }
.ui-facet-col-rail { flex: 1 1 0; }

/* ── Plan-driven multistream home (shared: learning.views renderStudentHome;
 * used by ScholarCloud + NeuroCraft). Accent: a host accentClass sets --sc-accent
 * (palette below) or --nc-hue (NeuroCraft subject colours); else system primary. */
.sc-home-min0 { min-width: 0; }
.sc-home-ic { width: 42px; height: 42px; border-radius: var(--ui-radius-lg); display: grid; place-items: center;
    font-size: 18px; background: var(--sc-accent-soft, var(--nc-hue-soft, var(--ui-bg-sunken))); color: var(--sc-accent, var(--nc-hue, var(--ui-primary))); flex: none; }
.sc-home-next { color: var(--sc-accent, var(--nc-hue, var(--ui-primary))); }
.sc-home-stream .ui-progress-bar { background: var(--sc-accent, var(--nc-hue, var(--ui-primary))); }
.sc-home-stream.ui-clickable, .sc-home-task.ui-clickable { transition: var(--ui-transition-fast); }
.sc-home-stream.ui-clickable:hover, .sc-home-task.ui-clickable:hover { border-color: var(--ui-primary); box-shadow: var(--ui-shadow-sm); transform: translateY(-1px); }
.sc-home-task { background: var(--ui-bg); border: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-md); padding: 10px 12px; }
.sc-home-task-tag { font-size: var(--ui-text-2xs); font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
    color: var(--sc-accent, var(--nc-hue, var(--ui-primary))); background: var(--sc-accent-soft, var(--nc-hue-soft, var(--ui-bg-sunken))); padding: 3px 8px; border-radius: 999px; flex: none; }
/* Per-stream colour palette (host assigns .sc-accent-N to a card to differentiate streams). */
.sc-accent-1 { --sc-accent: #2563eb; --sc-accent-soft: #dbeafe; }
.sc-accent-2 { --sc-accent: #059669; --sc-accent-soft: #d1fae5; }
.sc-accent-3 { --sc-accent: #7c3aed; --sc-accent-soft: #ede9fe; }
.sc-accent-4 { --sc-accent: #d97706; --sc-accent-soft: #fef3c7; }
.sc-accent-5 { --sc-accent: #e11d48; --sc-accent-soft: #ffe4e6; }
.sc-accent-6 { --sc-accent: #0891b2; --sc-accent-soft: #cffafe; }

/* Editable parallel plan (shared: learning.views renderPlanEditor). Days are
 * lanes; colour pills sit in parallel and drag between lanes. */
.sc-plan-day { padding: 5px 0; border-top: var(--ui-border-width) solid var(--ui-border); }
.sc-plan-day:first-of-type { border-top: 0; }
.sc-plan-daylbl { width: 42px; flex: none; font-weight: 800; font-size: var(--ui-text-2xs); text-transform: uppercase; color: var(--ui-fg-muted); }
.sc-plan-lane { min-height: 34px; padding: 4px; border-radius: var(--ui-radius-md); }
.sc-plan-empty { align-self: center; }
.sc-plan-pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px; border-radius: 999px; cursor: grab;
    background: var(--sc-accent-soft, var(--nc-hue-soft, var(--ui-bg-sunken)));
    border: 1px solid color-mix(in srgb, var(--sc-accent, var(--nc-hue, var(--ui-primary))) 35%, transparent); }
.sc-plan-pill-practice { border-style: dashed; opacity: .9; }
.sc-plan-pill-review { border-style: dotted; border-width: 2px; }
.sc-plan-pill-tag { font-weight: 800; text-transform: uppercase; font-size: var(--ui-text-2xs); color: var(--sc-accent, var(--nc-hue, var(--ui-primary))); }
.sc-plan-pill-label { font-weight: 600; font-size: var(--ui-text-xs); color: var(--ui-fg); }
.sc-plan-pill-launch { cursor: pointer; }
.sc-plan-pill-launch:hover .sc-plan-pill-label { text-decoration: underline; }
.sc-plan-act { cursor: pointer; opacity: .55; font-weight: 800; padding: 0 2px; }
.sc-plan-act:hover { opacity: 1; }

/* ── Study-activity tracker (shared component: learning.views renderStudyTracker;
 * used by ScholarCloud + NeuroCraft) — all from --ui-* tokens (CLAUDE.md §35b). */
.sc-heat { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; max-width: 320px; }
.sc-heatcell { width: 14px; height: 14px; border-radius: var(--ui-radius-sm, 3px); background: var(--ui-gray-200, #e5e7eb); }
.sc-heat-pad { background: transparent; }
.sc-heat-0 { background: var(--ui-gray-200, #e5e7eb); }
.sc-heat-1 { background: color-mix(in srgb, var(--ui-primary) 30%, var(--ui-gray-100, #f3f4f6)); }
.sc-heat-2 { background: color-mix(in srgb, var(--ui-primary) 60%, white); }
.sc-heat-3 { background: var(--ui-primary); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-primary) 35%, white); }
.sc-ring { width: 56px; height: 56px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: conic-gradient(var(--ui-primary) calc(var(--sc-ring-pct, 0) * 1%), var(--ui-gray-200, #e5e7eb) 0); flex: 0 0 auto; }
.sc-ring-label { width: 42px; height: 42px; border-radius: 50%; background: var(--ui-bg, #fff); display: flex; align-items: center; justify-content: center; font-size: var(--ui-text-xs, 0.72rem); font-weight: 600; color: var(--ui-text, #111); }
.sc-ring-met { --sc-ring-pct: 100; }
.sc-bar { height: 8px; background: var(--ui-gray-200, #e5e7eb); border-radius: var(--ui-radius-sm, 3px); overflow: hidden; }
.sc-bar-fill { height: 100%; width: calc(var(--sc-bar-pct, 0) * 1%); background: var(--ui-primary); border-radius: var(--ui-radius-sm, 3px); }

/* ── uiRatingChip — compact per-concept rating control (UI/class.ui.rating.js).
   Hover reveals the tiny drag-slider; a comment icon opens the comment+anonymity
   popover. All colour from tokens; --rc-accent is the (data-driven) tag colour. */
.ui-ratingchip { display: inline-flex; align-items: center; gap: var(--ui-space-1);
    padding: 1px var(--ui-space-2); border: var(--ui-border-width) solid var(--ui-border);
    border-left: 3px solid var(--rc-accent, var(--ui-primary)); border-radius: var(--ui-radius-full, 999px);
    background: var(--ui-bg); font-size: var(--ui-text-xs); line-height: 1.5; max-width: 100%; }
.ui-ratingchip-label { font-weight: var(--ui-font-semibold); color: var(--ui-fg); white-space: nowrap; }
.ui-ratingchip-agg { display: inline-flex; align-items: center; justify-content: center; min-width: 1.6em;
    padding: 0 4px; border-radius: var(--ui-radius-full, 999px); background: var(--ui-primary-100);
    color: var(--ui-primary-700); font-weight: var(--ui-font-semibold); font-size: var(--ui-text-2xs); }
.ui-ratingchip-slider { display: none; align-items: center; gap: 4px; }
.ui-ratingchip:hover .ui-ratingchip-slider,
.ui-ratingchip-rated .ui-ratingchip-slider { display: inline-flex; }
.ui-ratingchip-range { width: 64px; height: 14px; accent-color: var(--rc-accent, var(--ui-primary)); cursor: pointer; }
.ui-ratingchip-val { min-width: 1.9em; text-align: right; color: var(--ui-fg-muted); font-variant-numeric: tabular-nums; }
.ui-ratingchip-comment { border: 0; background: transparent; color: var(--ui-fg-muted); cursor: pointer; padding: 0 2px; font-size: var(--ui-text-xs); }
.ui-ratingchip-comment:hover { color: var(--ui-primary); }
.ui-ratingchip-row { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); align-items: center; }

/* ── Publon bulletin board — comments + reactions (UIBind.bindPublonComments) */
.ui-publon-comments { display: flex; flex-direction: column; gap: var(--ui-space-2); }
.ui-comment { padding: var(--ui-space-2); border: var(--ui-border-width) solid var(--ui-border);
    border-radius: var(--ui-radius-lg, 6px); background: var(--ui-bg); }
.ui-comment-author { font-weight: var(--ui-font-semibold); color: var(--ui-fg); font-size: var(--ui-text-sm); }
.ui-comment-body { color: var(--ui-fg); margin: 2px 0 var(--ui-space-1); white-space: pre-wrap; }
.ui-comment-reactions { margin-top: 2px; }
.ui-reaction { border: var(--ui-border-width) solid var(--ui-border); background: var(--ui-bg);
    border-radius: var(--ui-radius-full, 999px); padding: 0 var(--ui-space-2); font-size: var(--ui-text-xs);
    line-height: 1.6; cursor: pointer; color: var(--ui-fg-muted); }
.ui-reaction:hover { border-color: var(--ui-primary); color: var(--ui-primary); }
.ui-reaction-on { background: var(--ui-primary-100); color: var(--ui-primary-700); border-color: var(--ui-primary); }

/* ── Open tagging surface (UIBind.bindPublonTags) — taxonomy + folksonomy */
.ui-publon-tags { display: flex; flex-direction: column; }
.ui-tagpill { display: inline-flex; align-items: center; gap: 4px; padding: 1px var(--ui-space-2);
    border: var(--ui-border-width) solid var(--ui-border); border-left: 3px solid var(--tp-accent, var(--ui-primary));
    border-radius: var(--ui-radius-full, 999px); background: var(--ui-bg); font-size: var(--ui-text-xs); line-height: 1.6; }
.ui-tagpill-label { color: var(--ui-fg); white-space: nowrap; }
.ui-tagpill-x { border: 0; background: transparent; color: var(--ui-fg-muted); cursor: pointer; padding: 0 0 0 2px; font-size: var(--ui-text-sm); line-height: 1; }
.ui-tagpill-x:hover { color: var(--ui-danger); }
.ui-tagpill-suggest { cursor: pointer; }
.ui-tagpill-suggest:hover { border-color: var(--ui-primary); }
.ui-tagpill-count { color: var(--ui-fg-muted); font-size: var(--ui-text-2xs); font-variant-numeric: tabular-nums; }
.ui-tagpill-new { cursor: pointer; border-style: dashed; border-left-style: dashed; color: var(--ui-primary); background: transparent; }
.ui-tagpill-new:hover { background: var(--ui-primary-100); }

/* ── Interactive publon (uiInteractive, class.interactive.js) */
.ui-interactive { display: flex; flex-direction: column; gap: var(--ui-space-3); }
.ui-interactive-stage svg { display: block; max-width: 100%; height: auto; margin: 0 auto; }
.ui-interactive-controls { display: flex; flex-direction: column; gap: var(--ui-space-2);
    padding: var(--ui-space-2) var(--ui-space-3); background: var(--ui-gray-50); border-radius: var(--ui-radius-lg); }
.ui-interactive-row { display: grid; grid-template-columns: minmax(90px, max-content) 1fr minmax(48px, max-content);
    align-items: center; gap: var(--ui-space-3); font-size: var(--ui-text-sm); }
.ui-interactive-row input[type=range] { width: 100%; }
.ui-interactive-label { color: var(--ui-fg); font-weight: var(--ui-font-medium); }
.ui-interactive-value { font-family: ui-monospace, monospace; text-align: right; color: var(--ui-primary); font-variant-numeric: tabular-nums; }
.ui-interactive-feedback { padding: var(--ui-space-2) var(--ui-space-3); border-radius: var(--ui-radius-lg); font-size: var(--ui-text-sm); }
.ui-interactive-feedback ul { margin: var(--ui-space-1) 0 0; padding-left: var(--ui-space-4); }
.ui-interactive-correct { background: var(--ui-success-100); color: var(--ui-success); }
.ui-interactive-wrong { background: var(--ui-danger-100); color: var(--ui-danger-600); }
.ui-step-now { border-color: var(--ui-primary) !important; box-shadow: 0 0 0 2px var(--ui-primary-100, #dbeafe); }
.ui-step-pulse { animation: ui-step-pulse 0.45s ease 2; }
@keyframes ui-step-pulse { 50% { box-shadow: 0 0 0 6px var(--ui-primary-100, #dbeafe); transform: scale(1.14); } }
/* verdict colours must beat the target highlight — keep these LAST */
.ui-step-ok  { border-color: var(--ui-success-500, #16a34a) !important; background: var(--ui-success-100) !important; }
.ui-step-bad { border-color: var(--ui-danger-500, #dc2626) !important; background: var(--ui-danger-100) !important; box-shadow: 0 0 0 2px var(--ui-danger-100) !important; }
/* Completion problems — steps GIVEN as worked scaffolding (faded worked example). */
.ui-step-given { background: var(--ui-bg-sunken, #f3f4f6) !important; color: var(--ui-fg-muted) !important; border-style: dashed !important; }

/* CW4: fixed-width utilities for inline inputs in flex rows (a route-sheet
   value box must not sprawl). Declared LAST so they beat .ui-input's
   width:100% at equal specificity; flex:none so ui-flex parents don't
   stretch them. */
.ui-w-24 { width: 6rem; flex: none; }
.ui-w-40 { width: 10rem; flex: none; }

/* ── Learner day-story + progress room (learning.views renderDayStory /
   renderSkillRecord · gamification shelf · 2026-07-24 v2 design).
   Token-driven throughout; hosts theme via --sc-day-accent per stream. ── */
.sc-day-sum  { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); margin-bottom: var(--ui-space-4); }
.sc-day-tl   { display: flex; flex-direction: column; }
.sc-day-ev   { display: flex; gap: var(--ui-space-3); }
.sc-day-when { flex: 0 0 3.2em; text-align: right; font-size: var(--ui-text-xs); font-weight: var(--ui-font-semibold); color: var(--ui-fg-muted); padding-top: 14px; }
.sc-day-rail { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; }
.sc-day-node { width: 12px; height: 12px; border-radius: var(--ui-radius-full); border: 3px solid var(--sc-accent, var(--ui-primary)); background: var(--ui-bg); margin-top: 14px; }
.sc-day-line { flex: 1; width: 2px; background: var(--ui-border); }
.sc-day-card { flex: 1; min-width: 0; background: var(--ui-bg-elev); border: var(--ui-border-width) solid var(--ui-border); border-left: 4px solid var(--sc-accent, var(--ui-primary)); border-radius: var(--ui-radius-lg); padding: var(--ui-space-3); margin-bottom: var(--ui-space-3); box-shadow: var(--ui-shadow-sm); }
.sc-day-ev-now  .sc-day-node { background: var(--ui-success); border-color: var(--ui-success); animation: sc-day-pulse 2s infinite; }
.sc-day-ev-now  .sc-day-card { border-color: var(--ui-success); border-left-color: var(--ui-success); box-shadow: var(--ui-shadow-md); }
.sc-day-ev-next .sc-day-card { background: transparent; border-style: dashed; box-shadow: none; }
.sc-day-ev-next .sc-day-node { border-style: dashed; border-width: 2px; }
.sc-day-gap     { font-size: var(--ui-text-xs); font-style: italic; color: var(--ui-fg-muted); padding: var(--ui-space-1) 0; }
.sc-day-whyline { display: flex; align-items: center; flex-wrap: wrap; gap: var(--ui-space-2); margin-top: var(--ui-space-2); padding-top: var(--ui-space-2); border-top: 1px dashed var(--ui-border); }
@keyframes sc-day-pulse { 0%, 100% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--ui-success) 18%, transparent); }
                          50%      { box-shadow: 0 0 0 9px color-mix(in srgb, var(--ui-success) 6%,  transparent); } }

.sc-why-chip   { display: inline-flex; align-items: center; gap: 4px; font-size: var(--ui-text-2xs); font-weight: var(--ui-font-semibold); padding: 2px 10px; border-radius: var(--ui-radius-full); white-space: nowrap; }
.sc-why-plan   { background: color-mix(in srgb, var(--ui-accent) 14%, transparent);        color: var(--ui-accent); }
.sc-why-review { background: color-mix(in srgb, var(--ui-warn) 15%, transparent);          color: var(--ui-warn); }
.sc-why-path   { background: color-mix(in srgb, var(--ui-info) 14%, transparent);          color: var(--ui-info); }
.sc-why-ready  { background: color-mix(in srgb, var(--ui-secondary-700) 14%, transparent); color: var(--ui-secondary-700); }
.sc-why-pick   { background: color-mix(in srgb, var(--ui-success) 14%, transparent);       color: var(--ui-success); }

.sc-acc-ring   { --p: 0; width: 32px; height: 32px; border-radius: var(--ui-radius-full); flex: 0 0 auto; display: flex; align-items: center; justify-content: center; font-size: var(--ui-text-2xs); font-weight: var(--ui-font-bold); background: conic-gradient(var(--sc-accent, var(--ui-primary)) calc(var(--p) * 1%), var(--ui-bg-sunken) 0); }
.sc-acc-ring i { width: 24px; height: 24px; border-radius: var(--ui-radius-full); background: var(--ui-bg-elev); display: flex; align-items: center; justify-content: center; font-style: normal; }

.sc-live-strip { display: flex; align-items: center; flex-wrap: wrap; gap: var(--ui-space-3); border: var(--ui-border-width) solid var(--ui-success); border-radius: var(--ui-radius-full); background: var(--ui-bg-elev); padding: var(--ui-space-2) var(--ui-space-3); }
.sc-live-dot   { width: 8px; height: 8px; border-radius: var(--ui-radius-full); background: var(--ui-success); animation: sc-live-blink 1.2s infinite; }
@keyframes sc-live-blink { 50% { opacity: .35; } }

.sc-skill-dots   { display: flex; gap: 4px; flex: 0 0 auto; }
.sc-skill-dots i { width: 9px; height: 9px; border-radius: var(--ui-radius-full); background: var(--ui-bg-sunken); }
.sc-skill-dots i.y { background: var(--ui-success); }
.sc-skill-dots i.n { background: var(--ui-danger); }

.gam-shelf-row  { display: flex; justify-content: space-around; align-items: flex-end; gap: var(--ui-space-2); padding: 0 var(--ui-space-1) var(--ui-space-2); border-bottom: 5px solid color-mix(in srgb, var(--ui-warn) 45%, var(--ui-border)); margin-bottom: var(--ui-space-4); }
.gam-trophy     { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1 1 0; min-width: 0; text-align: center; }
.gam-trophy-ico { width: 48px; height: 48px; border-radius: var(--ui-radius-full); display: flex; align-items: center; justify-content: center; font-size: var(--ui-text-2xl); background: color-mix(in srgb, var(--ui-warn) 18%, var(--ui-bg-elev)); box-shadow: var(--ui-shadow-sm); }
.gam-trophy-name { font-size: var(--ui-text-2xs); font-weight: var(--ui-font-semibold); line-height: 1.25; }
.gam-trophy-date { font-size: var(--ui-text-2xs); color: var(--ui-fg-muted); }
.gam-trophy-locked .gam-trophy-ico { background: transparent; border: 2px dashed var(--ui-border); box-shadow: none; filter: grayscale(1); opacity: .5; }

.sc-days-row    { display: flex; gap: var(--ui-space-3); overflow-x: auto; padding-bottom: var(--ui-space-1); }
.sc-day-mini    { flex: 0 0 auto; width: 100px; }
.sc-day-mini-cv { height: 64px; border-radius: var(--ui-radius-md); background: var(--ui-bg-sunken); border: var(--ui-border-width) solid var(--ui-border); display: flex; align-items: flex-end; gap: 6%; padding: 12%; }
.sc-day-mini-cv i { flex: 1; border-radius: 2px; background: var(--sc-accent, var(--ui-primary)); height: calc(var(--sc-bar-pct, 10) * 1%); }
.sc-day-mini-today .sc-day-mini-cv { outline: 2px solid var(--ui-success); outline-offset: 2px; }

/* Day-story doors — compact subject entry pills under the day head (v2 shell). */
.sc-day-doors { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); margin-bottom: var(--ui-space-4); }
.sc-day-door  { display: flex; align-items: center; gap: var(--ui-space-2); background: var(--ui-bg-elev); border: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-full); padding: var(--ui-space-1) var(--ui-space-3) var(--ui-space-1) var(--ui-space-2); max-width: 100%; }
.sc-day-door.ui-clickable:hover { border-color: var(--sc-accent, var(--ui-primary)); box-shadow: var(--ui-shadow-sm); transform: translateY(-1px); }
.sc-day-door-ic { width: 28px; height: 28px; border-radius: var(--ui-radius-full); display: flex; align-items: center; justify-content: center; font-size: var(--ui-text-base); background: var(--sc-accent-soft, var(--ui-bg-sunken)); flex: none; }

/* Plan editor — quiet day/week clear actions (2026-07-25). */
.sc-plan-clear { flex: none; font-size: var(--ui-text-2xs); font-weight: var(--ui-font-semibold); color: var(--ui-fg-muted); cursor: pointer; padding: 2px 8px; border-radius: var(--ui-radius-full); user-select: none; }
.sc-plan-clear:hover { color: var(--ui-danger); background: var(--ui-bg-sunken); }

/* Plan editor — the add-a-lesson picker list (2026-07-25). */
.sc-plan-addlist { max-height: 55vh; overflow-y: auto; }
.sc-plan-addrow { border-bottom: var(--ui-border-width) solid var(--ui-border); border-radius: var(--ui-radius-sm); padding-inline: var(--ui-space-2); }
.sc-plan-addrow:hover { background: var(--ui-bg-sunken); }

/* Probe element for ui.tokenColor() — painted, never seen. It must stay in the
   document (a detached element has no computed style) and must not affect
   layout or hit-testing. */
.ui-token-probe {
    position: absolute;
    width: 0; height: 0;
    visibility: hidden;
    pointer-events: none;
}

/* ── Workbench — the default integrated-view shape ───────────────────────────
   Owner ruling 2026-08-10: a 1/3 control rail (accordion / tabset), the STRONG
   central element in the 2/3 beside it, and two columns underneath for the
   supporting detail and interactions. Three equal tracks: the rail takes one,
   `main` spans the other two, and colA/colB take one each — so the rail is a
   true third and the central element a true two-thirds. The rail spans both
   rows, keeping the controls reachable while reading the lower columns. */
.ui-workbench {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    /* The central element sizes to its CONTENT and the columns take the rest:
       a fixed 3fr/2fr split left a chart of its natural height stranded in the
       middle of an empty band. */
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas:
        "control main main"
        "control colA colB";
    gap: var(--ui-space-4);
    height: 100%; min-height: 0;
}
.ui-workbench-control { grid-area: control; }
.ui-workbench-main    { grid-area: main; }
.ui-workbench-colA    { grid-area: colA; }
.ui-workbench-colB    { grid-area: colB; }
.ui-workbench-control,
.ui-workbench-main,
.ui-workbench-colA,
.ui-workbench-colB {
    display: flex; flex-direction: column; gap: var(--ui-space-3);
    min-width: 0; min-height: 0; overflow: auto;
}
/* Narrow: one column, the reading order the layout implies. */
@media (max-width: 800px) {
    .ui-workbench {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto auto auto;
        grid-template-areas: "control" "main" "colA" "colB";
    }
}

/* ── uiDrawing / uiDrawingSubmission — the drawing-submission input ─────────
   Axes + student strokes as an Entry-Contract component; figure = JSON value.
   Tokens only; stroke colours are DATA (svg attrs), not styling. */
.ui-draw { position: relative; max-width: 100%; }
.ui-draw-svg { display: block; width: 100%; height: auto; background: var(--ui-gray-50);
  border: 1px solid var(--ui-gray-200); border-radius: var(--ui-radius-lg); cursor: crosshair; }
.ui-draw-readonly .ui-draw-svg { cursor: default; }
.ui-draw-axis { stroke: var(--ui-gray-700); stroke-width: 1.4; }
.ui-draw-tick { stroke: var(--ui-gray-500); stroke-width: 1; }
.ui-draw-ticklabel { font-size: 10px; fill: var(--ui-gray-500); }
.ui-draw-base { opacity: .75; }
.ui-draw-sel { filter: drop-shadow(0 0 2px var(--ui-primary)); }
.ui-draw-vertex { fill: var(--ui-white, #fff); stroke: var(--ui-primary); stroke-width: 1.2; }
.ui-draw-axlabel { position: absolute; font-size: var(--ui-text-sm); color: var(--ui-gray-600); pointer-events: none; }
.ui-draw-axlabel-x { bottom: 0; left: 50%; transform: translateX(-50%); }
.ui-draw-axlabel-y { top: 50%; left: 0; transform: translate(-40%, -50%) rotate(-90deg); }
.ui-draw-tool { display: flex; flex-direction: column; gap: 8px; }
.ui-draw-toolbar { flex-wrap: wrap; }
.ui-draw-btn { font-size: var(--ui-text-xs); }
.ui-draw-swatch { width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--ui-gray-200);
  background: var(--sw); cursor: pointer; padding: 0; }
.ui-draw-swatch-on { border-color: var(--ui-gray-700); box-shadow: var(--ui-shadow-sm); }
.ui-sketch-tabs { margin-bottom: 8px; }
.ui-sketch-pane.ui-hidden { display: none; }
.ui-sketch-addbar { margin-bottom: 8px; }
.ui-sketch-block { border: 1px solid var(--ui-gray-200); border-radius: var(--ui-radius-lg);
  padding: 8px; margin-bottom: 10px; background: var(--ui-white, #fff); }
.ui-sketch-blockhead { justify-content: flex-end; margin-bottom: 4px; }
.ui-sketch-blockhead span { margin-right: auto; }
.ui-sketch-text, .ui-sketch-tex, .ui-sketch-json { width: 100%; box-sizing: border-box;
  border: 1px solid var(--ui-gray-200); border-radius: var(--ui-radius-sm);
  padding: 6px 8px; font-size: var(--ui-text-sm); }
.ui-sketch-json { font-family: ui-monospace, monospace; font-size: var(--ui-text-xs); }
.ui-sketch-texprev { padding: 6px 0 2px; min-height: 10px; }
.ui-draw-vertex { cursor: grab; }
.ui-sketch-viewblock { position: relative; padding: 4px 34px 4px 2px; border-radius: var(--ui-radius-sm); }
.ui-sketch-viewblock:hover { background: var(--ui-gray-50, #f9fafb); }
.ui-sketch-editbtn { position: absolute; top: 2px; right: 2px; opacity: 0; transition: opacity 0.15s; }
.ui-sketch-viewblock:hover .ui-sketch-editbtn, .ui-sketch-editbtn:focus { opacity: 1; }
.ui-sketch-view-text { white-space: pre-wrap; font-size: var(--ui-text-sm); line-height: 1.55; }
.ui-sketch-view-eq { padding: 2px 0; }

/* ── uiDocument — a self-contained artefact hosted in a surface ───────────
   The frame is a deliberate style boundary: the document inside keeps its own
   design system, and none of it leaks into --ui-* (or the reverse). Only the
   CHROME around it is themed here. */
.ui-doc { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.ui-doc-bar { display: flex; align-items: center; justify-content: space-between;
    gap: var(--ui-space-2); padding: var(--ui-space-1) var(--ui-space-2);
    border-bottom: 1px solid var(--ui-border); background: var(--ui-bg-elev); }
.ui-doc-title { font-size: var(--ui-text-sm); font-weight: 600; color: var(--ui-fg); }
.ui-doc-open { font-size: var(--ui-text-xs); color: var(--ui-primary); text-decoration: none; white-space: nowrap; }
.ui-doc-open:hover { text-decoration: underline; }
.ui-doc-frame { flex: 1 1 auto; width: 100%; min-height: var(--ui-doc-h, 60vh);
    height: var(--ui-doc-h, auto); border: 0; display: block; background: var(--ui-bg); }
.ui-doc-broken { flex: 0 0 auto; }
/* ── ui.mdToHtml default figure — callers that already style their own figure
   pass `figureClass`; everything else lands here rather than unstyled. */
.ui-md-figure { margin: var(--ui-space-4) 0; }
.ui-md-figure img { display: block; width: 100%; height: auto;
    border-radius: var(--ui-radius-sm); }
.ui-md-figure figcaption { margin-top: var(--ui-space-1);
    font-size: var(--ui-text-xs); color: var(--ui-fg-muted); }

.ui-sketch-axisrow { flex-wrap: wrap; margin-bottom: 6px; }
.ui-sketch-axnum { width: 74px; }
.ui-sketch-axlab { width: 132px; }
.ui-sketch-axnum, .ui-sketch-axlab { border: 1px solid var(--ui-gray-200);
  border-radius: var(--ui-radius-sm); padding: 3px 6px; font-size: var(--ui-text-xs); }
