@import url("https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700;800&family=Ubuntu:wght@400;500;700;800&display=swap");

/* Palette and type follow the shared Blacklight internal-app design system
   (globals.css): Raleway headings over Ubuntu body, brand magenta accent,
   pure-black dark surfaces, soft plum-tinted light surfaces. */
/* ============================================================================
   DESIGN TOKENS — two layers, so re-tuning a theme means editing ONE block.
     1. Structural tokens (:root)     — type, radius, spacing, layout. Theme-agnostic.
     2a. Dark palette / 2b. Light palette — every colour token, in matching order so
         the two blocks read as parallel columns. Edit the pair to retune light/dark.
   Dark is the default AND paints the body behind the shell, so it lives on :root;
   light overrides on the shell. Contextual re-scopes that layer on top of whichever
   palette is active (sidebar stays dark in light theme, high-contrast a11y, the
   bug-report overlay) live further down — search "re-scope".
   ============================================================================ */

/* --- 1. Structural tokens (shared by every theme) --- */
:root {
    --bls-font-heading: "Raleway", "Segoe UI", system-ui, sans-serif;
    --bls-font-body: "Ubuntu", "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
    --bls-radius-sm: 0.65rem;
    --bls-radius-md: 0.9rem;
    --bls-radius-lg: 1.2rem;
    --bls-space-2: 0.5rem;
    --bls-space-3: 0.75rem;
    --bls-space-4: 1rem;
    --bls-space-6: 1.5rem;
    --portal-viewport-height: 100vh;
    --portal-safe-top: env(safe-area-inset-top, 0px);
    --portal-safe-right: env(safe-area-inset-right, 0px);
    --portal-safe-bottom: env(safe-area-inset-bottom, 0px);
    --portal-safe-left: env(safe-area-inset-left, 0px);
    --portal-shell-inline-padding: clamp(0.5rem, 1.25vw, 12px);
    --portal-shell-inline-extra: 0px;
    --portal-shell-gap: clamp(0.9rem, 2vw, 1.3rem);
    --portal-shell-padding-block-start-base: 1.1rem;
    --portal-shell-padding-block-end-base: 2rem;
    --portal-shell-padding-block-start: calc(var(--portal-shell-padding-block-start-base) + var(--portal-safe-top));
    --portal-shell-padding-block-end: calc(var(--portal-shell-padding-block-end-base) + var(--portal-safe-bottom));
    --portal-sidebar-width: 13rem;
    --portal-sidebar-collapsed-width: 4.85rem;
    --portal-current-sidebar-width: var(--portal-sidebar-width);
    /* Slim account strip - just tall enough for the account pill (2.3rem avatar +
       pill padding/borders). The height freed up flows straight into
       --portal-page-min-height below. */
    /* The topbar is now a zero-height overlay: the account chrome floats into the
       top-right corner of the page's first header card instead of owning a strip, so
       the content rises to fill what used to be the 3rem band. Kept at 0 so the page
       min-height math below reclaims the full height. */
    --portal-topbar-height: 0rem;
    /* Corner chrome geometry: how far the floating icons sit in from the content
       corner, and how much right-hand room a page header must leave so its own
       controls never slide under them. The cluster is a vertical stack now
       (avatar over customer-context over raise-case), so its footprint is only
       as WIDE as a single circle — reserve ≈ the stacked corner cluster's width
       (one 2.3rem circle + inset margin). */
    --portal-account-corner-inset: 0.55rem;
    --portal-account-corner-reserve: 3.4rem;
    --portal-content-padding-block-end: clamp(1rem, 2vw, 2rem);
    --portal-page-gap: clamp(0.75rem, 1.5vw, 0.95rem);
    --portal-page-min-height: max(0px, calc(var(--portal-viewport-height) - var(--portal-shell-padding-block-start) - var(--portal-shell-padding-block-end) - var(--portal-topbar-height) - var(--portal-shell-gap) - var(--portal-content-padding-block-end)));
    --portal-overlay-padding: clamp(0.75rem, 2.5vw, 2rem);
    /* Height of the phone bottom navigation bar. 0 everywhere for now; the P1
       mobile nav sets it (≈56px) at phone widths so fixed bottom-anchored
       widgets (admin assistant) automatically clear the bar. */
    --portal-mobile-bar-height: 0px;
    --portal-overlay-height: var(--portal-page-min-height);
    --portal-popover-max-height: max(12rem, calc(var(--portal-overlay-height) - (var(--portal-overlay-padding) * 2)));
}

/* The hidden attribute must ALWAYS hide, even when a class on the same element sets
   its own display (author rules beat the UA's [hidden] default, which is how several
   tabbed sections used to leak). Replaces the per-class *[hidden] patches. */
[hidden] {
    display: none !important;
}

/* --- 2a. DARK palette (default: "Cinema") ------------------------------------------
   Near-black stage rather than pure black - surfaces climb a real elevation ladder
   (bg -> surface -> floating layers) and structure comes from shadows, not heavy
   magenta borders. The old stark black-on-black look is pinned by the high-contrast
   a11y mode only (see .portal-a11y-contrast). On :root (not .theme-dark) so it also
   paints the body behind the shell. Alternative dark presets (.theme-preset-*)
   override this block; keep the token order in sync everywhere. */
:root {
    color-scheme: dark;
    --bls-bg: #0d0b0f;
    --bls-surface: #171219;
    --bls-surface-strong: rgba(196, 78, 175, 0.08);
    --bls-text: #f1ebf2;
    --bls-ink: #ffffff;
    --bls-muted: #9d93a1;
    --bls-border: rgba(226, 204, 230, 0.09);
    /* Stronger hairline for controls (inputs etc.) that would vanish at 9% alpha
       now the border token no longer doubles as visible structure. */
    --bls-border-strong: rgba(226, 204, 230, 0.16);
    --bls-primary: #bd42a6;
    --bls-primary-dark: #e36ccc;
    --bls-purple: #a784e0;
    --bls-success: #34d399;
    --bls-warning: #fbbf24;
    --bls-danger: #fb7185;
    /* Elevation ladder: soft = resting cards, full = floating layers. */
    --bls-shadow: 0 2px 10px rgba(0, 0, 0, 0.5), 0 14px 30px rgba(0, 0, 0, 0.4);
    --bls-shadow-soft: 0 1px 4px rgba(0, 0, 0, 0.45);
    /* Materials layer (see MOTION + MATERIALS at the end of this file): floating
       popovers/menus paint popover-glass + glass-blur where backdrop-filter is
       supported, falling back to popover-solid. Presets retint all three. */
    --bls-popover-solid: #171219;
    --bls-popover-glass: rgba(23, 18, 25, 0.78);
    --bls-glass-blur: blur(12px) saturate(1.3);
    /* Hover reward for primary actions - a plain shadow lift here; the aubergine
       preset swaps in a magenta glow. */
    --bls-hover-glow: 0 10px 24px rgba(0, 0, 0, 0.5);
    /* Sidebar keeps its identity across themes AND presets - always dark, pure black
       with a hot-magenta edge. (Light theme re-scopes it in .theme-light .portal-sidebar.) */
    --portal-sidebar-bg: #000;
    --portal-sidebar-bg-2: #000;
    --portal-sidebar-border: rgb(234 17 195 / 50%);
}

/* --- 2b. LIGHT palette ------------------------------------------------------------
   Tuned for real screens, not just high-contrast panels: a lilac-grey page so white
   cards clearly separate, visible plum-grey borders, darker muted/ink text, and
   pronounced shadows — the subtle white-on-white highlights of the old values vanished
   on lower-contrast displays. Mirror the dark palette's token order above. The sidebar
   stays dark here too — see .theme-light .portal-sidebar below. */
.portal-shell.theme-light {
    /* The five tokens the always-dark sidebar re-scopes to white-on-dark
       (.theme-light .portal-sidebar) are declared once here as --light-* constants.
       The bug-report overlay nested inside that sidebar restores them from the SAME
       source, so these hexes live in exactly one place — edit here, both update. */
    --light-surface-strong: #f1e8f2;
    --light-text: #211b1f;
    --light-ink: #1b161a;
    --light-muted: #574651;
    --light-border: #cfbacb;

    color-scheme: light;
    --bls-bg: #eae3ea;
    --bls-surface: #ffffff;
    --bls-surface-strong: var(--light-surface-strong);
    --bls-text: var(--light-text);
    --bls-ink: var(--light-ink);
    --bls-muted: var(--light-muted);
    --bls-border: var(--light-border);
    --bls-primary: #a5318a;
    --bls-primary-dark: #6c2257;
    --bls-purple: #6a39b5;
    --bls-success: #1b7b64;
    --bls-warning: #9c5f0c;
    --bls-danger: #b83a30;
    --bls-shadow: 0 1px 2px rgba(38, 24, 36, 0.09), 0 8px 22px rgba(38, 24, 36, 0.1);
    --bls-shadow-soft: 0 1px 3px rgba(38, 24, 36, 0.07);
    /* Materials layer tokens - light glass over a white solid fallback. */
    --bls-border-strong: var(--light-border);
    --bls-popover-solid: #ffffff;
    --bls-popover-glass: rgba(255, 255, 255, 0.8);
    --bls-glass-blur: blur(12px) saturate(1.15);
    --bls-hover-glow: 0 10px 24px rgba(38, 24, 36, 0.16);
    background: var(--bls-bg);
}

/* --- 2c. DARK PRESETS ("Appearance" in Settings > Accessibility) -------------------
   Each preset is a full token override applied as a class on .portal-shell, exactly
   like theme-light. Presets are DARK variants only - PortalLayout never emits a
   preset class while theme-light is active. Cinema is the :root default (no class).
   Single-class selectors keep them on the same specificity plane as the a11y
   overrides further down (.portal-a11y-contrast), which are later in the file and
   therefore win. */

/* Graphite plum - cooler, slightly lifted greys, quieter shadows. */
.theme-preset-graphite {
    --bls-bg: #131016;
    --bls-surface: #1b1721;
    --bls-surface-strong: rgba(196, 78, 175, 0.07);
    --bls-text: #f0eaf2;
    --bls-muted: #a89fad;
    --bls-border: rgba(236, 220, 240, 0.09);
    --bls-border-strong: rgba(236, 220, 240, 0.16);
    --bls-shadow: 0 2px 8px rgba(0, 0, 0, 0.42), 0 12px 26px rgba(0, 0, 0, 0.34);
    --bls-shadow-soft: 0 1px 3px rgba(0, 0, 0, 0.38);
    --bls-popover-solid: #1b1721;
    --bls-popover-glass: rgba(27, 22, 33, 0.72);
    --bls-glass-blur: blur(10px) saturate(1.4);
    --bls-hover-glow: 0 10px 24px rgba(0, 0, 0, 0.45);
}

/* Aubergine glow - warm plum surfaces, magenta-tinted borders, hover glow. */
.theme-preset-aubergine {
    --bls-bg: #150e17;
    --bls-surface: #1e1425;
    --bls-surface-strong: rgba(196, 78, 175, 0.12);
    --bls-text: #f3ecf5;
    --bls-muted: #b09ab3;
    --bls-border: rgba(196, 78, 175, 0.24);
    --bls-border-strong: rgba(196, 78, 175, 0.34);
    --bls-primary-dark: #e36ccc;
    --bls-popover-solid: #231729;
    --bls-popover-glass: rgba(35, 23, 41, 0.8);
    --bls-hover-glow: 0 0 18px rgba(196, 78, 175, 0.25);
}

/* Soft studio - neutral warm greys, least saturated of the set. */
.theme-preset-studio {
    --bls-bg: #1a181b;
    --bls-surface: #232024;
    --bls-surface-strong: rgba(196, 78, 175, 0.09);
    --bls-text: #eeebee;
    --bls-muted: #a3a0a5;
    --bls-border: rgba(255, 255, 255, 0.075);
    --bls-border-strong: rgba(255, 255, 255, 0.14);
    --bls-primary: #b8479f;
    --bls-popover-solid: #232024;
    --bls-popover-glass: rgba(35, 32, 37, 0.85);
    --bls-glass-blur: blur(14px) saturate(1.3);
    --bls-hover-glow: 0 10px 24px rgba(0, 0, 0, 0.5);
}

/* --- 2d. ACCENT COLOURS ("Appearance" in Settings > Accessibility) -----------------
   Five curated accents swap --bls-primary/--bls-primary-dark. Magenta is the default
   (no class). Each accent has a dark pair and a darker light-theme pair so text on
   the accent (and the accent as text) keeps AA contrast on both shells. Placed after
   the theme-light + preset blocks so the .theme-light.accent-* pairs win on order. */

.accent-violet {
    --bls-primary: #7f77dd;
    --bls-primary-dark: #a99ff0;
}

.portal-shell.theme-light.accent-violet {
    --bls-primary: #534ab7;
    --bls-primary-dark: #3c3489;
}

.accent-ocean {
    --bls-primary: #378add;
    --bls-primary-dark: #85b7eb;
}

.portal-shell.theme-light.accent-ocean {
    --bls-primary: #185fa5;
    --bls-primary-dark: #0c447c;
}

.accent-teal {
    --bls-primary: #1d9e75;
    --bls-primary-dark: #5dcaa5;
}

.portal-shell.theme-light.accent-teal {
    --bls-primary: #0f6e56;
    --bls-primary-dark: #085041;
}

.accent-ember {
    --bls-primary: #d85a30;
    --bls-primary-dark: #f0997b;
}

.portal-shell.theme-light.accent-ember {
    --bls-primary: #993c1d;
    --bls-primary-dark: #712b13;
}

@supports (height: 100dvh) {
    :root {
        --portal-viewport-height: 100dvh;
    }
}

/* Tighter overlay gutters on medium/small screens so popovers fill the viewport instead
   of floating in a wide dimmed frame. Driving it through the shared token means every
   overlay (settings, case-detail, bug-report) and the derived --portal-popover-max-height
   inherit it automatically — popovers also get taller as the padding shrinks. */
@media (max-width: 1100px) {
    :root {
        --portal-overlay-padding: clamp(0.6rem, 2vw, 1.25rem);
    }
}

/* Breakpoint convention: 1100px = stacked/tablet switch, 600px = THE phone switch.
   Legacy 560px and 640px phone blocks were consolidated onto 600px (2026-07-28)
   so every phone rule flips at the same width. */
@media (max-width: 600px) {
    :root {
        --portal-overlay-padding: 0.6rem;
        /* R5 composer growth cap. Deliberately declared ONLY here: the consumer reads
           var(--bls-composer-max-height, 9rem), so the desktop default IS the fallback
           and a :root duplicate would only be a second place to change it (same idiom
           as --bls-conversation-scale). 5.5rem ≈ 3-4 lines, which is as much of a phone
           viewport as the composer may take with the soft keyboard up; past that the
           textarea scrolls internally. */
        --bls-composer-max-height: 5.5rem;
    }
}

/* The composer cap is a HEIGHT problem, so it needs the height arm too. A landscape phone
   is 844 x 390 and no max-width breakpoint reaches it, yet 9rem is 144px of those 390 -
   more than a third of the screen for one textarea. Same two-arm shape (and the same
   <= 1100px bound) as the case-detail short-viewport rules at the end of this file: a
   short desktop WINDOW has room a landscape phone does not. */
@media (max-width: 1100px) and (max-height: 600px) {
    :root {
        --bls-composer-max-height: 5.5rem;
    }
}

* {
    box-sizing: border-box;
}

html,
body {
    background: var(--bls-bg);
    color: var(--bls-text);
    font-family: var(--bls-font-body);
    margin: 0;
    min-height: 100%;
    /* clip (not hidden) keeps horizontal overflow contained WITHOUT turning the
       body into a scroll container — hidden would promote overflow-y to auto and
       break the sticky sidebar (it would ride up with the page scroll). */
    overflow-x: clip;
}

h1,
h2,
h3,
h4,
h5,
h6 {
    font-family: var(--bls-font-heading);
}

.portal-shell,
.portal-public-shell,
.portal-shell *,
.portal-public-shell * {
    -ms-overflow-style: none;
    scrollbar-width: none;
}

.portal-shell::-webkit-scrollbar,
.portal-public-shell::-webkit-scrollbar,
.portal-shell *::-webkit-scrollbar,
.portal-public-shell *::-webkit-scrollbar {
    display: none;
}

@supports selector(body:has(.portal-shell)) {
    html:has(.portal-shell),
    html:has(.portal-public-shell),
    body:has(.portal-shell),
    body:has(.portal-public-shell) {
        -ms-overflow-style: none;
        scrollbar-width: none;
    }

    html:has(.portal-shell)::-webkit-scrollbar,
    html:has(.portal-public-shell)::-webkit-scrollbar,
    body:has(.portal-shell)::-webkit-scrollbar,
    body:has(.portal-public-shell)::-webkit-scrollbar {
        display: none;
    }
}

a {
    color: inherit;
}

.min-h-screen {
    min-height: var(--portal-viewport-height);
}

.text-ink {
    color: var(--bls-ink);
}

button,
input,
select,
textarea {
    font: inherit;
}

/* Native <select> popup lists. The popup is OS/browser-drawn: it does not inherit the
   app's surfaces, only whatever is set explicitly on the option elements - and with
   nothing set, staff on Windows got a WHITE popup whose options inherited the app's
   near-white text: white-on-white, readable only via the hover highlight (reported from
   the tray time logger 2026-08-17, but it affected every native select app-wide).
   Explicit tokens on the options make the popup readable in every theme: both palettes
   define --bls-surface as a SOLID colour (popups cannot composite alpha), and theme-light
   re-scopes both tokens so this needs stating exactly once. */
select option,
select optgroup {
    background: var(--bls-surface);
    color: var(--bls-text);
}

.sr-only {
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    width: 1px;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
}

.portal-sidebar-toggle-state {
    clip: rect(0, 0, 0, 0);
    height: 1px;
    margin: -1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}

.portal-shell {
    background:
        radial-gradient(ellipse 70rem 34rem at 92% -12%, rgba(196, 78, 175, 0.045), transparent 62%),
        var(--bls-bg);
    display: grid;
    /* Column gap keeps the sidebar off the content; the topbar row carries NO gap —
       it's a zero-height overlay for the floating account chrome, so the content rises
       flush to the top instead of sitting below an empty strip. */
    column-gap: var(--portal-shell-gap);
    row-gap: 0;
    /* The sidebar fills the full height down the left; the topbar is a 0-height overlay
       pinned to the top of the content column, with the content directly beneath it. */
    grid-template-areas:
        "sidebar topbar"
        "sidebar main";
    grid-template-columns: minmax(0, var(--portal-current-sidebar-width)) minmax(0, 1fr);
    grid-template-rows: 0 minmax(0, 1fr);
    /* App-shell: the window itself is fixed to the viewport and never scrolls;
       regions inside (the nav, the content pane) scroll independently. */
    height: var(--portal-viewport-height);
    overflow: hidden;
    padding-block: var(--portal-shell-padding-block-start) var(--portal-shell-padding-block-end);
    padding-left: max(calc(var(--portal-shell-inline-padding) + var(--portal-shell-inline-extra)), var(--portal-safe-left));
    padding-right: max(calc(var(--portal-shell-inline-padding) + var(--portal-shell-inline-extra)), var(--portal-safe-right));
    transition: grid-template-columns 180ms ease, gap 180ms ease, padding 180ms ease;
}

.portal-sidebar-toggle-state:checked + .portal-shell {
    --portal-current-sidebar-width: var(--portal-sidebar-collapsed-width);
}

.portal-sidebar {
    background: linear-gradient(180deg, var(--portal-sidebar-bg) 0%, var(--portal-sidebar-bg-2) 100%);
    border: 1px solid var(--portal-sidebar-border);
    border-radius: 1.4rem;
    box-shadow: var(--bls-shadow);
    color: var(--bls-text);
    display: flex;
    flex-direction: column;
    gap: 1rem;
    grid-area: sidebar;
    /* Fills its grid row (the fixed shell height); the nav scrolls within it. */
    height: 100%;
    min-height: 0;
    overflow: hidden;
    padding: 0.85rem;
}

/* The sidebar stays dark in the light theme (shared design-system rule), so
   re-scope the text/border vars for everything inside it — nav links, section
   labels, footer tools — to light-on-dark values. */
.portal-shell.theme-light .portal-sidebar {
    --bls-text: #ffffff;
    --bls-ink: #ffffff;
    --bls-muted: rgba(255, 255, 255, 0.5);
    --bls-border: rgba(255, 255, 255, 0.08);
    --bls-surface-strong: rgba(255, 255, 255, 0.08);
    --portal-sidebar-bg: #2b2b2b;
    --portal-sidebar-bg-2: #1a1419;
    --portal-sidebar-border: rgba(255, 255, 255, 0.08);
    color-scheme: dark;
}

.portal-sidebar-header,
.portal-brand,
.portal-sidebar-toggle,
.portal-sidebar-profile,
.portal-sidebar-footer-action,
.portal-nav a,
.portal-nav .portal-nav-action {
    align-items: center;
    display: flex;
}

.portal-sidebar-header {
    gap: 0.7rem;
    /* The collapse toggle always sits right next to the logo (not pushed to the
       far edge) so the control reads the same in every layout. */
    justify-content: flex-start;
}

.portal-brand {
    gap: 0.85rem;
    min-width: 0;
    padding: 0.3rem 0.25rem;
    text-decoration: none;
}

/* The Blacklight wordmark at the top of the menu - moved here from the old topbar
   and grown slightly (3.3rem there, 3.5rem here); it must never render smaller.
   Height-driven: width follows the image's intrinsic ratio. */
.portal-sidebar-logo {
    display: block;
    height: 3.5rem;
    width: auto;
}

/* The square favicon mark: the brand's collapsed-rail form, so the logo is ALWAYS
   visible on desktop (full wordmark expanded, mark collapsed; only mobile may truly
   hide or shrink the brand). Hidden until the rail collapses. */
.portal-sidebar-mark {
    border-radius: 0.55rem;
    display: none;
    height: 2.4rem;
    width: 2.4rem;
}

.portal-brand-lockup {
    align-items: center;
    display: inline-flex;
    gap: 0.7rem;
    min-width: 0;
}

.portal-brand-logo {
    display: block;
    height: 100%;
    object-fit: contain;
    object-position: center;
    width: 100%;
}

.portal-brand-mark,
.portal-logo-slot {
    align-items: center;
    background: #a53a8d;
    border-radius: 0.08rem;
    box-shadow: none;
    color: #ffffff;
    flex: 0 0 auto;
    display: inline-flex;
    font-weight: 800;
    height: 2.65rem;
    justify-content: center;
    width: 2.65rem;
}

.portal-logo-slot-image {
    background: transparent;
    overflow: visible;
    padding: 0.18rem;
}

.portal-brand-copy {
    display: flex;
    flex-direction: column;
    gap: 0.12rem;
    justify-content: center;
}

.portal-brand-copy strong {
    color: #ffffff;
    font-size: 0.95rem;
    font-weight: 900;
    letter-spacing: -0.03em;
    line-height: 1;
}

.portal-brand-copy small {
    color: #e36ccc;
    font-size: 0.58rem;
    font-weight: 900;
    letter-spacing: 1.5px;
    line-height: 1;
    text-transform: uppercase;
}

.portal-brand-copy,
.portal-profile-copy,
.portal-nav-label {
    min-width: 0;
    overflow: hidden;
    transition: opacity 140ms ease, width 180ms ease;
    white-space: nowrap;
}

.portal-sidebar-toggle {
    background: #1d1324;
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-muted);
    cursor: pointer;
    flex: 0 0 auto;
    height: 2.35rem;
    justify-content: center;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 2.35rem;
}

.portal-sidebar-toggle:hover,
.portal-sidebar-toggle-state:focus-visible + .portal-shell .portal-sidebar-toggle,
.library-sidebar-toggle:hover,
.library-sidebar-toggle:focus-visible {
    background: rgba(196, 78, 175, 0.14);
    color: var(--bls-primary-dark);
}

.portal-toggle-icon,
.portal-nav-icon {
    align-items: center;
    display: inline-flex;
    flex: 0 0 auto;
    height: 1.25rem;
    justify-content: center;
    width: 1.25rem;
}

.portal-toggle-icon {
    text-shadow: 0 0 14px rgba(196, 78, 175, 0.28);
    transition: transform 140ms ease;
}

.portal-toggle-icon svg[blazicon] {
    height: 1em;
    width: 1em;
}

.portal-toggle-icon-open,
.portal-toggle-icon-collapsed {
    align-items: center;
    display: inline-flex;
    font-size: 1.15rem;
    justify-content: center;
}

.portal-toggle-icon-collapsed {
    display: none;
}

.portal-sidebar-toggle:hover .portal-toggle-icon,
.library-sidebar-toggle:hover .portal-toggle-icon {
    transform: translateX(-0.06rem);
}

.portal-nav-icon svg[blazicon],
.portal-theme-bulb svg[blazicon] {
    height: 1em;
    width: 1em;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-toggle-icon-open {
    display: none;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-toggle-icon-collapsed {
    display: inline-flex;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-toggle:hover .portal-toggle-icon,
.library-sidebar-collapsed .library-sidebar-toggle:hover .portal-toggle-icon,
.library-books-collapsed .library-sidebar-toggle:hover .portal-toggle-icon {
    transform: translateX(0.06rem);
}

.portal-brand small,
.portal-eyebrow,
.portal-breadcrumbs,
.portal-card-header p,
.list-card span,
.case-card p,
.case-card-meta,
.metric-card small {
    color: var(--bls-muted);
}

.portal-brand small {
    color: var(--bls-muted);
    display: block;
}

.portal-brand strong,
.portal-profile-copy strong,
.login-copy h1,
.login-card h2 {
    color: var(--bls-ink);
}

.portal-brand .portal-brand-copy strong {
    color: #ffffff;
}

.portal-brand .portal-brand-copy small {
    color: #e36ccc;
}

.portal-nav {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.3rem;
    min-height: 0;
    overflow-y: auto;
    padding-right: 0.1rem;
}

.portal-nav a,
.portal-nav .portal-nav-action {
    border-radius: 0.95rem;
    color: rgba(255, 255, 255, 0.72);
    gap: 0.8rem;
    min-height: 2.6rem;
    padding: 0.66rem 0.78rem;
    text-decoration: none;
}

.portal-nav .portal-nav-action {
    background: transparent;
    border: none;
    cursor: pointer;
    font: inherit;
    width: 100%;
}

/* Flat main menu: no hover fill, no filled/active background, no recoloured icon. The
   selected item is marked by a thin pink line — a left edge in the vertical sidebar
   (base, below) and an underline in the horizontal top bar (the ≤1100px override). */
.portal-nav a {
    position: relative;
}

.portal-nav a.active {
    font-weight: 700;
}

.portal-nav a.active::after {
    background: var(--bls-primary, #c44eaf);
    border-radius: 999px;
    bottom: 0.55rem;
    content: "";
    left: 0;
    position: absolute;
    top: 0.55rem;
    width: 2px;
}

.portal-nav-section {
    color: rgba(255, 255, 255, 0.45);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    margin: 1rem 0 0.2rem;
    text-transform: uppercase;
    transition: color 140ms ease;
}

.portal-nav-loading {
    color: var(--bls-muted);
    font-size: 0.9rem;
    padding: 0.75rem 0.85rem;
}

.portal-sidebar-footer {
    align-items: center;
    background: var(--portal-sidebar-bg-2);
    border-top: 1px solid var(--bls-border);
    bottom: 0;
    display: flex;
    gap: 0.3rem;
    justify-content: space-between;
    margin-top: auto;
    padding-top: 0.6rem;
    position: sticky;
    z-index: 2;
}

.portal-sidebar-footer-action {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.95rem;
    color: rgba(255, 255, 255, 0.72);
    cursor: pointer;
    /* Share the expanded footer width evenly so four controls (settings,
       perspective, view scope, theme) fit without crowding the theme pill. */
    flex: 1 1 0;
    min-width: 0;
    gap: 0;
    height: 2.45rem;
    justify-content: center;
    min-height: 2.45rem;
    padding: 0;
    text-align: left;
    text-decoration: none;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 2.45rem;
}

/* The desktop footer is an ICON RAIL: the four controls share the footer width as
   2.45rem squares and must stay that way. FooterToolsFragment now emits a
   .portal-nav-label next to each icon purely so the phone Menu flyout can show it
   (.portal-flyout-utility reveals it); hide it everywhere else so this rail renders
   exactly as it did before. */
.portal-sidebar-footer-action .portal-nav-label {
    display: none;
}

/* Flat: no hover/active fill — the active control is just pink-accented. */
.portal-sidebar-footer-action.active {
    color: var(--bls-primary-dark, #e36ccc);
}

.portal-sidebar-footer-action:focus-visible {
    border-color: rgba(196, 78, 175, 0.45);
    color: var(--bls-primary-dark, #e36ccc);
    outline: none;
}

.portal-sidebar-footer-label {
    font-size: 0.82rem;
    font-weight: 800;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.portal-theme-control {
    padding-inline: 0.48rem;
    justify-content: center;
    flex: 0 0 auto;
    width: auto;
}

.portal-theme-control .portal-theme-bulb {
    align-self: center;
    cursor: inherit;
    margin: 0 auto;
    pointer-events: none;
}

.portal-sidebar-profile {
    background: #1d1726;
    border: 1px solid var(--bls-border);
    border-radius: 1.1rem;
    display: grid;
    gap: 0.7rem;
    margin-top: auto;
    padding: 0.75rem;
    position: relative;
}

.portal-sidebar-profile .portal-profile-copy {
    display: grid;
    flex: 1 1 auto;
    gap: 0.12rem;
    line-height: 1.18;
    min-width: 0;
    overflow: visible;
    white-space: normal;
}

.portal-sidebar-profile .portal-profile-copy strong,
.portal-sidebar-profile .portal-profile-copy small {
    overflow-wrap: anywhere;
    word-break: normal;
}

.portal-sidebar-profile .portal-profile-copy strong {
    font-size: clamp(0.72rem, 0.66rem + 0.35vw, 0.9rem);
}

.portal-sidebar-profile .portal-profile-copy small {
    font-size: clamp(0.62rem, 0.58rem + 0.25vw, 0.74rem);
}

.portal-profile-main,
.portal-profile-actions {
    align-items: center;
    display: flex;
}

.portal-profile-main {
    gap: 0.7rem;
}

.portal-profile-actions {
    justify-content: space-between;
}

.portal-sidebar-profile small {
    color: var(--bls-muted);
    display: block;
}

.portal-profile-avatar {
    align-items: center;
    background: linear-gradient(135deg, var(--bls-primary), var(--bls-purple));
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 999px;
    color: #ffffff;
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 0.78rem;
    font-weight: 900;
    height: 2.3rem;
    justify-content: center;
    letter-spacing: 0.03em;
    overflow: hidden;
    width: 2.3rem;
}

.portal-profile-avatar-img {
    border-radius: inherit;
    height: 100%;
    object-fit: cover;
    width: 100%;
}

.portal-resume-banner {
    align-items: center;
    display: flex;
    gap: 0.75rem;
    margin-bottom: 1rem;
    padding: 0.75rem 1rem;
}

.portal-resume-banner span {
    flex: 1 1 auto;
    font-weight: 700;
}

.profile-editor {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}

.profile-editor-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 0.85rem;
    font-weight: 700;
}

.profile-editor-field input {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 0.5rem;
    color: inherit;
    font: inherit;
    padding: 0.5rem 0.75rem;
}

.profile-editor-error {
    color: var(--bls-danger, #ff7a90);
    font-size: 0.8rem;
    margin: 0;
}

.profile-editor-hint {
    font-size: 0.8rem;
    margin: 0;
    opacity: 0.75;
}

.profile-editor-actions {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
}

.portal-member-badge {
    align-self: center;
    background: rgba(196, 78, 175, 0.16);
    border: 1px solid rgba(196, 78, 175, 0.25);
    border-radius: 999px;
    color: #ffbdf5;
    display: inline-flex;
    font-size: 0.66rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    padding: 0.16rem 0.48rem;
    text-transform: uppercase;
}

.portal-theme-bulb {
    align-items: center;
    align-self: center;
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 1.18rem;
    justify-content: center;
    line-height: 1;
    margin-left: auto;
    transition: color 160ms ease, filter 160ms ease, transform 160ms ease;
}

/* Light mode: the bulb is lit — warm amber glow. */
.portal-theme-bulb-on {
    color: #ffd447;
    filter: drop-shadow(0 0 6px rgba(255, 212, 71, 0.7)) drop-shadow(0 0 12px rgba(245, 158, 11, 0.35));
}

/* Dark mode: the bulb is dim — muted, no glow. */
.portal-theme-bulb-off {
    color: rgba(255, 255, 255, 0.4);
    filter: none;
}

.portal-theme-control:hover .portal-theme-bulb-on {
    color: #ffde6b;
    filter: drop-shadow(0 0 7px rgba(255, 212, 71, 0.85)) drop-shadow(0 0 14px rgba(245, 158, 11, 0.45));
}

.portal-theme-control:hover .portal-theme-bulb-off {
    color: rgba(255, 255, 255, 0.62);
}

.portal-theme-control:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.28);
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar {
    align-items: stretch;
    padding-inline: 0.75rem;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-footer {
    flex-direction: column;
    justify-content: center;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-footer-action {
    /* Stacked (collapsed) footer: natural heights, not evenly stretched. */
    flex: 0 0 auto;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-header {
    align-items: center;
    flex-direction: column;
    gap: 0.55rem;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-brand-copy,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar .portal-profile-copy,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-footer-label,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav-label {
    opacity: 0;
    width: 0;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar .portal-profile-actions {
    display: none;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar .portal-profile-main {
    gap: 0;
    justify-content: center;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-header,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-brand,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-footer-action,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav a,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav .portal-nav-action,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar .portal-sidebar-profile {
    justify-content: center;
}

/* Collapsed rail: the rail is icon-width, far narrower than the wordmark - and the
   wordmark must never shrink - so the brand swaps to the square favicon mark and the
   header stacks it above the collapse toggle. Desktop always shows the brand in one
   of its two forms. (The stacked <=1100px header restores the wordmark.) */
.portal-sidebar-toggle-state:checked + .portal-shell .portal-brand {
    padding-inline: 0;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-logo {
    display: none;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-mark {
    display: block;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-header {
    flex-direction: column;
    gap: 0.55rem;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav a,
.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav .portal-nav-action {
    gap: 0;
    padding-inline: 0;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-footer-action {
    gap: 0;
}

.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav-section {
    background: var(--bls-border);
    color: transparent;
    height: 1px;
    margin: 0.85rem auto 0.3rem;
    overflow: hidden;
    width: 2.1rem;
}

@media (min-width: 1101px) {
    .portal-shell {
        --portal-shell-inline-extra: 0px;
    }

    .portal-sidebar {
        width: 100%;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar {
        width: 100%;
    }
}

.portal-main {
    display: flex;
    flex-direction: column;
    grid-area: main;
    /* Fills the fixed shell row; .portal-content scrolls within it. */
    height: 100%;
    min-height: 0;
    min-width: 0;
}

/* Chrome-less strip: no background, border or shadow of its own - it only carries
   the right-aligned account cluster (and the customer raise-a-case action). The
   brand moved to the top of the sidebar. */
.portal-topbar {
    background: transparent;
    grid-area: topbar;
    /* Zero-height overlay: it owns no strip. Its only child (the account cluster) is
       absolutely positioned into the content's top-right corner and overflows down over
       the first header card. */
    height: 0;
    min-height: 0;
    overflow: visible;
    padding: 0;
    position: relative;
    /* The transparent bar must not swallow clicks meant for the header beneath it —
       only the actual chrome (which sets pointer-events:auto) is interactive. */
    pointer-events: none;
    /* Above the sidebar (z-index 20 in stacked mode) so the customer-context
       popover drops over the nav instead of under it. */
    z-index: 30;
}

/* The notifications panel lives inside the topbar's stacking context, so at rest it
   sits under later-DOM fullscreen overlays (case detail 30, settings 60/90, report
   viewer 80). While the panel is open the whole topbar rises above all of them —
   but stays below toasts and the app loader (200+). */
.portal-topbar.portal-topbar-notifications-open {
    z-index: 120;
}

/* The customer's primary action, pinned to the centre of the topbar regardless of
   the headline/account widths on either side. */
.portal-topbar-add-case {
    align-items: center;
    display: inline-flex;
    gap: 0.5rem;
    left: 50%;
    line-height: 1;
    padding: 0.5rem 1.1rem;
    position: absolute;
    transform: translateX(-50%);
    white-space: nowrap;
    z-index: 1;
}

.portal-topbar-add-case .portal-nav-icon {
    align-items: center;
    display: inline-flex;
}

.portal-topbar-add-case svg[blazicon] {
    height: 1.05rem;
    width: 1.05rem;
}

/* On narrow screens the centred label would collide with the side clusters, so drop
   to an icon-only pill. */
@media (max-width: 600px) {
    .portal-topbar-add-case span:not(.portal-nav-icon) {
        display: none;
    }

    .portal-topbar-add-case {
        padding: 0.5rem;
    }
}

/* Personal greeting under the dashboard header. */
.dashboard-greeting {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0 0.25rem;
}

.dashboard-greeting strong {
    color: var(--bls-ink);
    font-family: var(--bls-font-heading);
    font-size: 1.15rem;
}

.dashboard-greeting span {
    color: var(--bls-muted);
    font-size: 0.9rem;
}

/* Floating corner chrome: at rest just the icons (raise-case, customer-context,
   avatar), pinned into the top-right corner of the content column so they land on the
   corner of the page's first header card. The details live in a popover (below). */
.portal-topbar-account {
    align-items: flex-end;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    /* Pinned to the VIEWPORT corner (not the content column) so the cluster can
       never sit on top of a page or overlay header's own controls. */
    position: fixed;
    top: max(var(--portal-account-corner-inset), var(--portal-safe-top));
    right: max(var(--portal-account-corner-inset), var(--portal-safe-right));
    /* Opt back in: the parent topbar is pointer-events:none so clicks fall through to
       the header underneath everywhere except on this cluster. */
    pointer-events: auto;
    z-index: 2;
}

/* The details popover: name / email / role, notifications and sign-out. Absolutely
   positioned under the avatar so it OVERLAYS the page (no layout shift on the chrome).
   Hidden at rest; it PEEKS on hover/focus of the cluster and stays put while PINNED
   (the avatar was clicked — .portal-account-pinned). */
.portal-account-popover {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
    max-width: min(24rem, 80vw);
    opacity: 0;
    padding: 0.85rem;
    pointer-events: none;
    position: absolute;
    /* Opens to the LEFT of the corner cluster, not beneath it. Since the cluster became a
       vertical stack, dropping this card below both circles left it floating oddly in the
       middle of the page; alongside reads as attached to the avatar. Anchoring to the
       cluster's inline-start also means the stack's height no longer pushes the card down
       the viewport, so it needs no height gymnastics. Logical inset so it mirrors in RTL,
       where the cluster sits on the left and the card must open rightward. */
    inset-inline-end: calc(100% + 0.5rem);
    top: 0;
    transform: translateX(0.35rem);
    transition: opacity 160ms ease, transform 160ms ease, visibility 0s linear 160ms;
    visibility: hidden;
    width: max-content;
    /* Below the notifications / customer-context panels (z 30) so opening the bell or
       the chooser lays their opaque card cleanly over this peek popover. */
    z-index: 20;
}

/* Peek ONLY off the avatar (or the popover itself, so the pointer can travel down into
   it) — NOT the whole cluster, so hovering/focusing the customer-context or raise-case
   controls no longer opens the account details. Still pinned open by the avatar click. */
.portal-profile-toggle:hover ~ .portal-account-popover,
.portal-profile-toggle:focus ~ .portal-account-popover,
.portal-account-popover:hover,
.portal-account-popover:focus-within,
.portal-topbar-account.portal-account-pinned .portal-account-popover {
    opacity: 1;
    pointer-events: auto;
    transform: none;
    transition: opacity 160ms ease, transform 160ms ease;
    visibility: visible;
}

.portal-profile-details {
    align-items: center;
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
    white-space: nowrap;
}

.portal-account-popover .portal-profile-copy {
    display: grid;
    gap: 0.1rem;
    line-height: 1.25;
    min-width: 0;
    text-align: left;
}

.portal-account-popover-actions {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
}

/* Pinned-only click-catcher: a full-viewport transparent layer so a click anywhere off
   the popover collapses it back to icons. Sits above the page but below the cluster +
   its panels (which live in the topbar's z-30 context). Peek-on-hover renders no
   backdrop — it closes itself when the pointer leaves. */
.portal-account-backdrop {
    background: transparent;
    border: 0;
    cursor: default;
    inset: 0;
    padding: 0;
    position: fixed;
    /* Re-enable events on this child of the pointer-events:none topbar. */
    pointer-events: auto;
    /* Below the cluster (z 2) so the avatar / popover stay clickable, but the whole
       topbar sits in its own z-30 context above the page, so this still catches every
       click on the content beneath. */
    z-index: 1;
}

/* Reserve room in the top-right of the page's first header so its own controls (the
   board's filter buttons, a page header's actions) never slide under the floating
   corner chrome. Logical inline-end so it flips with RTL, where the chrome sits left.
   The library renders no .portal-page-header at all (the workspace skips it there), so
   its own card header IS the page's first header and needs the same reserve for its
   search bar. The admin tools card and the case-list workspace cards likewise render
   their .portal-card-header as the page's first header, so they carry the reserve too.
   NOTE: P1 (mobile nav) replaces the floating corner cluster on phones; when it does,
   this reserve can be zeroed at the phone breakpoint rather than per-selector. */
.portal-page > .portal-page-header,
.kanban-card > .portal-card-header,
.portal-page > .library-shell > .portal-card-header,
.portal-page > .portal-card-header,
.portal-page > .admin-tools-card > .portal-card-header,
.case-list-workspace > .portal-card > .portal-card-header {
    padding-inline-end: var(--portal-account-corner-reserve);
}

.portal-profile-toggle {
    background: transparent;
    border: 0;
    border-radius: 999px;
    cursor: pointer;
    flex: 0 0 auto;
    padding: 0;
}

.portal-profile-toggle:focus-visible {
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.3);
    outline: none;
}

/* Sign out sits in the account popover (no dropdown, no Settings — that's already on
   the main menu). */
.portal-account-signout {
    align-items: center;
    background: rgba(248, 113, 113, 0.12);
    border: 1px solid rgba(248, 113, 113, 0.4);
    border-radius: 999px;
    color: #f87171;
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 0.82rem;
    font-weight: 700;
    gap: 0.4rem;
    padding: 0.32rem 0.7rem;
    text-decoration: none;
    white-space: nowrap;
}

.portal-account-signout:hover,
.portal-account-signout:focus-visible {
    background: rgba(248, 113, 113, 0.2);
    color: #fecaca;
    outline: none;
}

.portal-shell.theme-light .portal-topbar .portal-member-badge {
    background: rgba(196, 78, 175, 0.1);
    color: var(--bls-primary-dark);
}

/* Clean circle that matches the avatar beside it — same 2.3rem, no extra ring. */
/* Client-facing "Raise a case" action: a round, icon-only (+) button that now rides the
   floating corner cluster on customer pages (it used to be centred in the topbar strip).
   Transparent with just a border + accent glyph — the reserved, business-like style the
   admin AI launcher mirrors. It stays FIRST in markup (so its optional @if doesn't have
   to jump over the avatar/context blocks) but visually anchors the BOTTOM of the stacked
   cluster, below the two circles — order:0 (avatar) / order:0 (context, follows in DOM)
   put it last without touching source order for the other two. */
.portal-topbar-action {
    align-items: center;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-primary-dark);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    height: 2.3rem;
    justify-content: center;
    order: 3;
    padding: 0;
    text-decoration: none;
    transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 2.3rem;
}

.portal-topbar-action:hover,
.portal-topbar-action:focus-visible {
    background: rgba(196, 78, 175, 0.14);
    border-color: rgba(196, 78, 175, 0.55);
    color: var(--bls-primary-dark);
    outline: none;
}

.portal-topbar-action svg[blazicon] {
    height: 1.15rem;
    width: 1.15rem;
}

/* Skip-to-content link: first focusable element, hidden off-screen until keyboard-focused,
   then slides into view. Jumps to the <main> (which is tabindex=-1 so it can receive focus).
   inset-inline-start keeps it correct under RTL. Invisible to mouse users — no default-look change. */
.portal-skip-link {
    position: fixed;
    top: -3rem;
    inset-inline-start: 0.5rem;
    z-index: 200;
    padding: 0.5rem 0.9rem;
    background: var(--bls-primary, #bd42a6);
    color: #fff;
    border-radius: 0.5rem;
    font-weight: 600;
    transition: top 0.15s ease;
}

.portal-skip-link:focus,
.portal-skip-link:focus-visible {
    top: 0.5rem;
}

/* The focus target shouldn't show a persistent outline when jumped to via the skip link. */
#portal-main-content:focus {
    outline: none;
}

.portal-customer-context-button {
    align-items: center;
    background: rgba(255, 255, 255, 0.06);
    border: 0;
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    height: 2.3rem;
    justify-content: center;
    overflow: hidden;
    padding: 0;
    width: 2.3rem;
}

.portal-customer-context-button:hover,
.portal-customer-context-button:focus-visible {
    background: rgba(196, 78, 175, 0.2);
    color: #ffffff;
    outline: none;
}

/* A client context is set: the logo fills the circle cleanly, no ring. */
.portal-customer-context-active {
    background: transparent;
}

.portal-customer-context-button svg[blazicon] {
    height: 1.05rem;
    width: 1.05rem;
}

.portal-search,
.portal-actions,
.case-card-top,
.case-card-meta,
.portal-card-header,
.list-card {
    align-items: center;
    display: flex;
}

.portal-search {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    justify-content: space-between;
    padding: 0.65rem 0.95rem;
}

.portal-search kbd {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 0.45rem;
    color: var(--bls-primary-dark);
    padding: 0.12rem 0.4rem;
}

.portal-context-status {
    align-items: center;
    color: var(--bls-muted);
    display: inline-flex;
    flex-wrap: wrap;
    font-size: 0.84rem;
    font-weight: 800;
    gap: 0.45rem;
}

.portal-status-dot,
.portal-presence {
    background: var(--bls-warning);
    border-radius: 999px;
    box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.14);
    height: 0.58rem;
    width: 0.58rem;
}

.portal-content {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    /* The page-level scroll region: the window is fixed, this scrolls. */
    overflow-y: auto;
    padding: 0 0 var(--portal-content-padding-block-end);
}

.portal-content-has-context-popover > .portal-page {
    isolation: isolate;
}

.portal-content-has-context-popover > .portal-page::before {
    background: rgba(11, 7, 15, 0.66);
    content: "";
    inset: 0;
    pointer-events: none;
    position: absolute;
    z-index: 12;
}

@supports selector(main:has(.kanban-card)) {
    main.portal-content:has(.kanban-card) {
        /* No inner bottom padding on the board page: the shell already provides the
           32px outer gap (the sidebar/menu-bar line), so the card sits flush with it.
           NOTE: this :has() rule outranks the ≤720px .portal-content reset (which also
           sets this var to 0), and it carries no media query, so it applies at every
           width. Keep it at 0 — a non-zero value here would win over the mobile reset
           and silently leave a gap on the narrow board view. */
        --portal-content-padding-block-end: 0px;
    }

    /* Scoped to the board page only. The global .portal-page keeps flex:1 0 auto +
       min-height so tall, content-heavy pages still grow and scroll (see the note on
       .portal-page). On the board the content scrolls inside the columns, so here the
       wrapper is freed to fill its box — letting the card reach the menu-bar line
       instead of being pinned to the stale page-min-height. */
    .portal-page:has(.kanban-card) {
        flex: 1 1 auto;
        min-height: 0;
    }
}

.portal-public-shell {
    align-items: center;
    background:
        radial-gradient(circle at 22% 8%, rgba(196, 78, 175, 0.18), transparent 24rem),
        radial-gradient(circle at 88% 18%, rgba(245, 158, 11, 0.1), transparent 22rem),
        linear-gradient(180deg, #0d0712 0%, var(--bls-bg) 58%);
    display: flex;
    justify-content: center;
    min-height: var(--portal-viewport-height);
    padding-block: max(clamp(1.25rem, 5vw, 4rem), var(--portal-safe-top)) max(clamp(1.25rem, 5vw, 4rem), var(--portal-safe-bottom));
    padding-left: max(clamp(1.25rem, 5vw, 4rem), var(--portal-safe-left));
    padding-right: max(clamp(1.25rem, 5vw, 4rem), var(--portal-safe-right));
}

.portal-public-content {
    max-width: 980px;
    width: 100%;
}

.login-hero {
    display: grid;
    gap: 1.2rem;
    grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr);
    min-height: min(640px, calc(var(--portal-viewport-height) - 8rem));
}

.login-hero-compact {
    align-items: center;
    grid-template-columns: minmax(0, 720px);
    justify-content: center;
}

.login-copy,
.login-card,
.portal-auth-status {
    background: rgba(23, 16, 29, 0.82);
    border: 1px solid var(--bls-border);
    border-radius: 1.55rem;
    box-shadow: var(--bls-shadow);
}

.login-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1.05rem;
    padding: clamp(1.4rem, 4vw, 3rem);
}

.login-brand-lockup {
    margin-bottom: 1rem;
}

.login-copy h1 {
    font-size: clamp(2.4rem, 5vw, 4.4rem);
    letter-spacing: -0.06em;
    line-height: 0.96;
    margin: 0;
    max-width: 720px;
}

.login-copy p,
.login-card p {
    color: var(--bls-muted);
    line-height: 1.6;
    margin: 0;
}

.portal-perspective-actions {
    flex-wrap: wrap;
}

.portal-perspective-actions-area {
    position: relative;
}

.portal-signed-in-note {
    font-size: 0.9rem;
}

.portal-signed-in-signout {
    color: var(--bls-primary-dark);
    font-weight: 700;
    margin-left: 0.4rem;
    text-decoration: none;
}

.portal-signed-in-signout:hover,
.portal-signed-in-signout:focus-visible {
    text-decoration: underline;
}

.customer-chooser-popover {
    /* Floats over page content - solid popover surface, not the translucent inline one. */
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-radius: 1.15rem;
    box-shadow: var(--bls-shadow);
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1rem;
}

.portal-perspective-actions-area > .customer-chooser-popover {
    bottom: calc(100% + 0.75rem);
    left: 0;
    position: absolute;
    right: 0;
    z-index: 5;
}

.customer-chooser-header {
    align-items: flex-start;
    display: flex;
    gap: 1rem;
    justify-content: space-between;
}

.customer-chooser-header h2 {
    margin: 0.1rem 0 0.25rem;
}

.customer-chooser-header-actions {
    align-items: stretch;
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    gap: 0.4rem;
}

.customer-chooser-header-actions-row {
    display: flex;
    gap: 0.4rem;
    justify-content: flex-end;
}

.customer-chooser-clear-button[disabled] {
    cursor: not-allowed;
    opacity: 0.5;
}

.customer-chooser-sort-button {
    align-self: stretch;
    font-size: 0.78rem;
    padding: 0.4rem 0.7rem;
    text-align: center;
}

.customer-chooser-list {
    display: grid;
    gap: 0.7rem;
    /* The panel keeps its height; the account list is the scroll region so the
       header, search, and sort controls stay in view (800+ live accounts). */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-right: 0.25rem;
}

/* Inline text filter for long live lists (customer chooser, accounts panel). */
.list-filter-input {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    flex: 0 0 auto;
    min-height: 2.45rem;
    min-width: 0;
    padding: 0.6rem 0.9rem;
    width: 100%;
}

.customer-chooser-card {
    align-items: center;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--bls-border);
    border-radius: 0.95rem;
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    padding: 0.9rem;
    text-align: left;
    width: 100%;
}

.customer-chooser-card:hover,
.customer-chooser-card:focus-visible,
.customer-chooser-card[aria-current="true"] {
    background: rgba(196, 78, 175, 0.12);
    border-color: rgba(196, 78, 175, 0.36);
    outline: none;
}

.customer-chooser-card-main,
.customer-chooser-card-meta {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    /* Let the text column shrink inside the space-between row so a long account name
       wraps within the card instead of colliding with the meta block. */
    min-width: 0;
}

.customer-chooser-card-main strong,
.customer-chooser-card-main span,
.customer-chooser-card-main small,
.customer-chooser-card-meta small {
    overflow-wrap: anywhere;
}

.customer-chooser-card-main span,
.customer-chooser-card-main small,
.customer-chooser-card-meta small {
    color: var(--bls-muted);
}

.customer-chooser-card-meta {
    align-items: flex-end;
    flex-shrink: 0;
}

/* --- Shared chooser component (CustomerChooserPanel, D5) --------------------------
   The wrapper has to relay the host panel's flex sizing: inside the shell's context
   panel/flyout it is the flexible child, so the card list below keeps its bounded,
   scrollable height exactly as before extraction. */
.customer-chooser {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 1rem;
    min-height: 0;
    min-width: 0;
}

/* Search + sort cycle + Active filter on one wrapping row. The filter input's
   full-row width assumption must relax so the buttons share the row. */
.customer-chooser-toolbar {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.customer-chooser-toolbar .list-filter-input {
    flex: 1 1 12rem;
    width: auto;
}

/* Embedded presentation (Accounts & access, Who-what-where): a responsive card GRID
   in normal page flow - the page scrolls it, so the list must not cap or scroll. */
.customer-chooser-embedded .customer-chooser-list {
    align-items: stretch;
    display: grid;
    flex: 0 0 auto;
    /* min(16rem, 100%) is the house guard (see .account-tile-grid and friends): a
       bare 16rem floor sets a min-content width the track cannot go below, so in a
       host narrower than 256px - the settings popover at 320px, or at 390px once the
       interface text slider is at 125% - the cards are clipped by the popover's
       overflow: hidden with no horizontal scroll to recover them. */
    grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
    overflow-y: visible;
    padding-right: 0;
}

.portal-customer-context-panel {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    margin: 0;
    /* `- 100%` is the CLUSTER's own height: this panel is absolutely positioned, so its
       containing block is .portal-topbar-account's padding box and a percentage resolves
       against that box's height — the same basis `top: calc(100% + 0.75rem)` below already
       uses. The corner chrome is a VERTICAL stack whose height varies with the icon count
       (2 or 3 circles), so a hard-coded term would be wrong for one of them; this
       self-adjusts. Plus the inset the fixed cluster is held off the viewport corner by. */
    max-height: calc(var(--portal-viewport-height) - var(--portal-shell-padding-block-start) - var(--portal-topbar-height) - 100% - var(--portal-account-corner-inset));
    /* The list inside is the scroll region; the panel itself must not scroll or
       the header and controls drift out of view. */
    overflow: hidden;
    padding: 1rem;
    position: absolute;
    right: 0;
    top: calc(100% + 0.75rem);
    width: min(42rem, calc(100vw - 2rem));
    z-index: 30;
}

/* --- Notification centre (avatar badge + bell dropdown) --- */

/* Wavebox-style unread bubble. Shared by the bell (in-pill) and the avatar overlay. */
.portal-notification-badge {
    align-items: center;
    background: var(--bls-primary);
    border-radius: 999px;
    color: #fff;
    display: inline-flex;
    font-size: 0.62rem;
    font-weight: 700;
    justify-content: center;
    line-height: 1;
    min-width: 1.1rem;
    padding: 0.18rem 0.3rem;
}

/* Riding the avatar: visible even when the account pill is collapsed. */
.portal-profile-toggle {
    position: relative;
}

.portal-avatar-badge {
    border: 2px solid #1d1726;
    position: absolute;
    right: -0.2rem;
    top: -0.2rem;
    z-index: 2;
}

.portal-notification-bell {
    align-items: center;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: inherit;
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    gap: 0.3rem;
    padding: 0.32rem 0.55rem;
}

.portal-notification-bell:hover,
.portal-notification-bell:focus-visible {
    border-color: var(--bls-primary);
}

.portal-notifications-panel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin: 0;
    /* Same self-adjusting basis as .portal-customer-context-panel above: `- 100%` is the
       vertical corner cluster's own height (this panel's containing block), so the bell
       list stays inside the fold whether the cluster carries 2 icons or 3. */
    max-height: calc(var(--portal-viewport-height) - var(--portal-shell-padding-block-start) - var(--portal-topbar-height) - 100% - var(--portal-account-corner-inset));
    overflow: hidden;
    padding: 1rem;
    position: absolute;
    right: 0;
    top: calc(100% + 0.75rem);
    width: min(28rem, calc(100vw - 2rem));
    z-index: 30;
}

.portal-notifications-header {
    align-items: center;
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
}

.portal-notifications-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    overflow-y: auto;
}

.portal-notification-row {
    align-items: flex-start;
    display: flex;
    gap: 0.35rem;
}

.portal-notification-item {
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: inherit;
    cursor: pointer;
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
    padding: 0.6rem 0.75rem;
    text-align: left;
}

.portal-notification-item:hover,
.portal-notification-item:focus-visible {
    border-color: var(--bls-primary);
}

.portal-notification-item span {
    color: var(--bls-muted);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    font-size: 0.85rem;
    overflow: hidden;
}

.portal-notification-item small {
    align-items: center;
    color: var(--bls-muted);
    display: flex;
    gap: 0.4rem;
}

.portal-notification-dismiss {
    flex: 0 0 auto;
}

/* "Yours" chip on bell rows: the viewer owns this case (accent, not another grey
   pill). --bls-accent doesn't exist in this theme - --bls-primary is the accent
   token (see .portal-notification-badge and the accent-swatch selection ring).

   The selector is .status-pill-qualified on purpose. The chip is emitted as
   `class="status-pill portal-notification-yours"`, and .status-pill is defined LATER in
   this file (~3837) with its own background + color; both bare classes are (0,1,0), so
   SOURCE ORDER won and this chip rendered identical to the case-number pill next to it.
   (0,2,0) beats (0,1,0) regardless of order, so it now wins where the block already sits
   rather than having to move below .status-pill.

   The differentiator is the ring, not "more accent": .status-pill's background is
   rgba(196,78,175,.14) — the SAME magenta as --bls-primary — so an 18%-vs-14% tint of one
   hue is not perceptibly distinct. .status-pill sets no border, hence the ring reads as
   ownership on its own. It is an INSET SHADOW rather than a border so the chip keeps
   .status-pill's exact box size and stays flush with the case pill beside it (the old
   `border-color` here was inert anyway — nothing sets a border-style on .status-pill).

   Text stays on --bls-primary-dark (the on-surface accent token .status-pill itself uses,
   light in the dark themes / dark in the light ones). --bls-primary as the text colour is
   only ~3.1:1 on this popover, an AA failure at 0.76rem/800; --bls-primary-dark on the
   24% tint is ~5:1. */
.status-pill.portal-notification-yours {
    background: color-mix(in srgb, var(--bls-primary) 24%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bls-primary) 60%, transparent);
    color: var(--bls-primary-dark);
}

.portal-customer-context-panel .customer-chooser-header p {
    color: var(--bls-muted);
    line-height: 1.55;
    margin: 0;
}

.login-card {
    align-self: start;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 1rem;
}

.login-card h2 {
    margin: 0.25rem 0 0;
}

.portal-auth-status {
    margin: 3rem auto;
    max-width: 720px;
    padding: 2rem;
}

.portal-page {
    display: flex;
    flex-direction: column;
    /* flex-shrink must stay 0: with an explicit min-height the page loses its
       min-content floor, so shrink:1 would let flexbox compress it to the
       viewport and the overflow:hidden cards would swallow the rest — taller
       pages then have nothing to scroll. shrink:0 lets the page grow with its
       content and .portal-content scrolls it. */
    flex: 1 0 auto;
    gap: var(--portal-page-gap);
    min-height: var(--portal-page-min-height);
    min-width: 0;
    position: relative;
    width: 100%;
}

/* App-shell workspaces scroll internally (board columns, table body, library
   panes) rather than growing the page. Now that .portal-page grows with its
   content, these need an explicit viewport cap — they previously got it from
   the page shrinking. Released at stacked (narrow) widths below. */
.kanban-workspace,
.case-list-workspace,
.library-shell,
.portal-page-admin-tools .admin-tools-shell {
    max-height: var(--portal-page-min-height);
}

.portal-page-has-overlay {
    --portal-settings-overlay-padding: var(--portal-overlay-padding);
    --portal-overlay-height: var(--portal-page-min-height);
    height: var(--portal-overlay-height);
    max-height: var(--portal-overlay-height);
    overflow: hidden;
}

/* The new-case intake page is SELF-CONTAINED: the card fills the viewport and its two panes
   (the form and the assistant) scroll internally. It must therefore never scroll as a whole -
   and it was, by over 1100px on a 720px viewport, into pure empty space below the card.

   Measured cause: the intake form is ~1780px tall inside .intake-form-pane, which clips it
   correctly (clientHeight 510, scrollHeight 1784) - and .intake-card clips too. Despite that,
   .portal-page still reported a 1773px scroll height with a single 624px child and no
   positioned descendants, and .portal-content faithfully scrolled the difference. The
   scrollable overflow of the clipped form was reaching the shell scroller regardless of the
   two clip boundaries between them.

   Clipping HERE stops it propagating. Scoped to this one route on purpose: .portal-page is
   deliberately allowed to grow (see flex: 1 0 auto above) so genuinely tall pages have
   something for .portal-content to scroll - clipping it globally would leave those pages
   unable to scroll at all, which is the exact regression the shrink:0 comment records.

   Verified in the browser rather than reasoned about: phantom scroll 1102px -> 0, with the
   form pane still scrolling its full 1274px and the assistant pane untouched. */
.portal-page-intake {
    overflow: hidden;
}

.portal-page-has-overlay > :not(.portal-settings-overlay):not(.case-detail-overlay) {
    filter: saturate(0.78);
}

.portal-page > :not(.portal-settings-overlay):not(.case-detail-overlay) {
    min-width: 0;
}

@supports selector(body:has(.portal-page-has-overlay)) {
    html:has(.portal-page-has-overlay),
    body:has(.portal-page-has-overlay) {
        overflow: hidden;
    }
}

/* The shared case popout chrome (CaseDetailPopout.razor) — mounted by the kanban
   board AND the case lists, always as a direct child of .portal-page. Padding
   reads the :root token directly (not the .portal-page-has-overlay alias) because
   the kanban page opens this popout without setting the page-overlay class. */
.case-detail-overlay {
    align-items: start;
    display: grid;
    inset: 0;
    justify-items: center;
    min-height: 100%;
    overscroll-behavior: contain;
    padding: var(--portal-overlay-padding);
    position: absolute;
    z-index: 15;
}

/* Settings = a best-practice responsive dialog, centred over the WHOLE viewport (fixed) so
   the popover can use the full SCREEN height, floating over the nav — not just the content
   area below it (which capped it at ~60%). Widescreen (> 14/9): a compact centred modal sized
   to its content. Squarer / narrower / portrait (<= 14/9): a large ~80%-of-screen popover
   (aspect media block below). Either way it stays a popover — rounded, with a margin around
   it — never an edge-to-edge sheet. */
/* Settings is a full-viewport GRID so the popover can centre itself, which means the
   element spans the screen even where it only shows scrim - and, being a plain fixed box,
   it swallowed every click across the whole shell. The ::before scrim was already
   pointer-events:none, so the dimming was never the blocker; the container was.

   That made the sidebar look available and behave as if it were not: the my-work/
   all-activity switch is visible while settings is open (deliberately - Accounts & access
   is scoped by it) but could not be clicked, which is worse than hiding it.

   Clicks now pass through everywhere except the popover itself. Nothing is lost: the
   overlay carries role="presentation" and no click-to-close handler, so it never wanted
   the events. Escape and the close button remain the ways out. */
.portal-settings-overlay {
    align-items: center;
    display: grid;
    inset: 0;
    justify-items: center;
    overscroll-behavior: contain;
    padding: var(--portal-settings-overlay-padding);
    pointer-events: none;
    position: fixed;
    z-index: 70;
}

.case-detail-overlay {
    z-index: 30;
}

/* Modal dim for the SETTINGS dialog only. The case-detail popout deliberately has NO scrim:
   in both layouts .case-detail-popover fills the entire overlay (align-items: stretch at
   <= 1100px, height/width 100% above it), so an inset:0 wash could only ever show through
   the overlay's own padding - a dark FRAME around the panel, never a backdrop behind it.
   That frame is what it looked like, because that is all it ever was. Settings is a genuinely
   centred, content-sized modal, so there the dim does real work and stays.
   DO NOT re-add .case-detail-overlay::before to this selector. */
.portal-settings-overlay::before {
    background: rgba(11, 7, 15, 0.66);
    content: "";
    inset: 0;
    pointer-events: none;
    position: absolute;
}

/* Fixed-chrome dialog: header + footer stay put and only the body scrolls (flex column +
   overflow:hidden; the inner pages own the scroll). Widescreen (> 14/9) sizes to content,
   capped well below full height so it isn't oversized; squarer screens go to 80% (media
   block below). One constant width so the popover never resizes as you move between pages. */
.portal-settings-popover,
.portal-settings-popover.portal-card {
    display: flex;
    flex-direction: column;
    max-height: min(72dvh, calc(100dvh - (var(--portal-settings-overlay-padding) * 2)));
    overscroll-behavior: contain;
    overflow: hidden;
    /* Restores what the container gave up above - the dialog itself is interactive. */
    pointer-events: auto;
    position: relative;
    width: min(1280px, 82vw);
    z-index: 1;
}

/* Squarer / narrower / portrait frames (aspect closer to 4:3 than 16:9 — threshold at the
   14/9 midpoint ≈ 1.556): the dialog grows into a LARGE popover that uses most of the screen,
   but it stays a popover — rounded, with a margin around it — never an edge-to-edge full-screen
   sheet. Widescreen (> 14/9) keeps the centred, content-sized modal defined above. */
@media (max-aspect-ratio: 14 / 9), (max-width: 720px) {
    .portal-settings-popover,
    .portal-settings-popover.portal-card {
        height: 80dvh;
        max-height: 80dvh;
        width: min(1280px, 82vw);
    }
}

.case-detail-popover {
    max-height: var(--portal-popover-max-height);
}

.portal-settings-panel {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

.portal-settings-header {
    align-items: center;
    border-bottom: 1px solid var(--bls-border);
    display: flex;
    flex: 0 0 auto;
    gap: 1rem;
    justify-content: space-between;
    padding: 0.85rem 1.15rem;
}

.portal-settings-header h2,
.portal-settings-header p,
.portal-settings-section h3,
.portal-settings-list {
    margin: 0;
}

.portal-settings-header p:not(.portal-eyebrow),
.portal-settings-muted {
    color: var(--bls-muted);
}

.portal-settings-close {
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    flex: 0 0 auto;
    font-weight: 800;
    padding: 0.55rem 0.8rem;
    text-decoration: none;
}

.portal-settings-close:hover {
    background: rgba(255, 255, 255, 0.12);
}

.portal-settings-identity {
    align-items: center;
    background: var(--bls-surface-strong);
    border-bottom: 1px solid var(--bls-border);
    display: grid;
    flex: 0 0 auto;
    gap: 0.85rem;
    grid-template-columns: auto minmax(0, 1fr) auto;
    padding: 1rem 1.15rem;
}

.portal-settings-identity > div:not(.portal-settings-pills) strong,
.portal-settings-identity > div:not(.portal-settings-pills) span {
    display: block;
}

.portal-settings-avatar {
    height: 3rem;
    width: 3rem;
}

.portal-settings-pills {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    justify-content: flex-end;
}

.portal-settings-sections {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 1rem;
    min-height: 0;
    overflow: hidden;
    padding: clamp(0.6rem, 2vw, 1rem);
}

/* ---- Report viewer ---- */
.report-viewer { display: flex; flex-direction: column; gap: 1rem; }
.report-viewer-empty { color: var(--bls-muted, #888); font-size: 0.85rem; }
.report-viewer-header { display: flex; align-items: baseline; gap: 0.75rem; margin-bottom: 0.75rem; }
.report-kpi-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1rem; }
.report-kpi { background: var(--bls-surface, #141417); border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 8px; padding: 0.6rem 1rem; min-width: 8rem; }
.report-kpi > span { display: block; font-size: 0.75rem; color: var(--bls-muted, #aaa7ad); }
.report-kpi > strong { font-size: 1.6rem; }
.report-panel-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; }
.report-panel { background: var(--bls-surface, #141417); border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 8px; padding: 0.9rem 1rem; }
.report-bars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.report-bars li { display: grid; grid-template-columns: 8rem 1fr 2.5rem; align-items: center; gap: 0.5rem; font-size: 0.8rem; }
.report-bar-track { background: var(--bls-border, #2a2a31); border-radius: 4px; height: 0.7rem; overflow: hidden; }
.report-bar-fill { display: block; height: 100%; background: var(--bls-primary, #c44eaf); }
.report-bar-value { text-align: right; font-variant-numeric: tabular-nums; }

/* Top tab-bar: all sections share one row; the collapse toggle stays a compact square. */
.portal-settings-menu {
    align-items: stretch;
    display: flex;
    flex: 0 0 auto;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 0.5rem;
}

.portal-settings-menu .case-command-collapse {
    align-self: stretch;
    flex: 0 0 auto;
}

.portal-settings-menu-item {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    flex: 1 1 0;
    gap: 0.6rem;
    min-width: 0;
    padding: 0.75rem;
    text-align: left;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease, color 140ms ease;
}

/* Flat: no hover fill, no filled active state — selected is just pink-accented. */
.portal-settings-menu-item:focus-visible,
.portal-settings-menu-item.active {
    border-color: rgba(196, 78, 175, 0.55);
    color: var(--bls-primary-dark, #e36ccc);
    outline: none;
}

.portal-settings-menu-item.active span {
    color: var(--bls-primary-dark, #e36ccc);
}

.portal-settings-menu-item span,
.portal-settings-menu-item small {
    display: block;
}

.portal-settings-menu-item span {
    font-weight: 800;
}

.portal-settings-menu-item small {
    color: var(--bls-muted);
}

.portal-settings-menu-item > svg[blazicon] {
    flex: 0 0 auto;
    height: 1.1rem;
    width: 1.1rem;
}

/* Collapsed settings rail — icon-only, matching the other section menus. */
.portal-settings-sections-collapsed {
    grid-template-columns: auto minmax(0, 1fr);
}

.portal-settings-sections-collapsed .case-command-text {
    display: none;
}

.portal-settings-sections-collapsed .portal-settings-menu-item {
    justify-content: center;
    padding: 0.7rem;
}

.portal-settings-pages {
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    overflow-y: auto;
}

.portal-settings-section {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
    padding: 0.95rem;
}

.portal-settings-page {
    min-height: 100%;
}

.portal-settings-section-wide {
    grid-column: 1 / -1;
}

.portal-settings-list {
    display: grid;
    gap: 0.75rem;
}

.portal-settings-list div {
    display: grid;
    gap: 0.2rem;
}

.portal-settings-list dt,
.portal-settings-options label {
    color: var(--bls-muted);
    font-size: 0.82rem;
    font-weight: 800;
}

.portal-settings-list dd {
    margin: 0;
}

.portal-settings-options,
.portal-settings-account-list {
    display: grid;
    gap: 0.65rem;
}

.portal-settings-options label {
    align-items: center;
    background: rgba(255, 255, 255, 0.045);
    border: 1px solid var(--bls-border);
    border-radius: 0.8rem;
    display: flex;
    gap: 0.55rem;
    padding: 0.7rem;
}

.portal-settings-options input {
    accent-color: var(--bls-primary);
}

.portal-settings-section-heading,
.portal-settings-actions {
    align-items: flex-start;
    display: flex;
    gap: 0.8rem;
    justify-content: space-between;
}

.portal-settings-section-heading h3,
.portal-settings-section-heading p {
    margin: 0;
}

.portal-integration-form {
    display: grid;
    gap: 0.8rem;
}

.portal-settings-toggle,
.portal-settings-field,
.portal-settings-callout {
    background: rgba(255, 255, 255, 0.045);
    border: 1px solid var(--bls-border);
    border-radius: 0.8rem;
}

.portal-settings-toggle {
    align-items: flex-start;
    color: var(--bls-text);
    display: flex;
    gap: 0.65rem;
    padding: 0.75rem;
}

.portal-settings-toggle input {
    accent-color: var(--bls-primary);
    margin-top: 0.2rem;
}

.portal-settings-toggle strong,
.portal-settings-toggle small,
.portal-settings-callout strong,
.portal-settings-callout span {
    display: block;
}

.portal-settings-toggle small,
.portal-settings-field label,
.portal-settings-fieldset legend,
.portal-settings-callout span {
    color: var(--bls-muted);
    font-size: 0.82rem;
    font-weight: 800;
}

.portal-settings-field {
    display: grid;
    gap: 0.4rem;
    padding: 0.75rem;
}

.portal-settings-field input {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.7rem;
    color: var(--bls-text);
    min-width: 0;
    padding: 0.65rem 0.75rem;
}

.portal-settings-field input::placeholder {
    color: var(--bls-muted);
}

/* --- Range sliders (Settings > Accessibility > Text size) --------------------------
   The OS-default control is a bright-blue engine-drawn range that ignores the theme
   and doesn't even render the same across engines. Restyle the CHROME only - slim
   themed track, magenta thumb - keeping the default control's footprint so nothing
   moves at 100%. accent-color is the fallback for engines that skip the
   pseudo-element styling; both tokens re-scope under .portal-shell.theme-light.
   The higher attribute specificity also lifts ranges out of the boxy
   .portal-settings-field input chrome (background/border/padding) above. */
.portal-shell input[type="range"] {
    accent-color: var(--bls-primary);
    appearance: none;
    background: none;
    border: 0;
    cursor: pointer;
    height: 1.15rem;
    margin: 0;
    padding: 0;
}

.portal-shell input[type="range"]::-webkit-slider-runnable-track {
    background: var(--bls-border-strong);
    border-radius: 999px;
    height: 0.3rem;
}

.portal-shell input[type="range"]::-webkit-slider-thumb {
    appearance: none;
    background: var(--bls-primary);
    border: 0;
    border-radius: 50%;
    box-shadow: var(--bls-shadow-soft);
    height: 1.05rem;
    /* Centre the thumb on the slim track: (track - thumb) / 2. */
    margin-top: calc((0.3rem - 1.05rem) / 2);
    width: 1.05rem;
}

.portal-shell input[type="range"]::-moz-range-track {
    background: var(--bls-border-strong);
    border-radius: 999px;
    height: 0.3rem;
}

/* Firefox draws the filled (left) half itself - match it to the accent. */
.portal-shell input[type="range"]::-moz-range-progress {
    background: var(--bls-primary);
    border-radius: 999px;
    height: 0.3rem;
}

.portal-shell input[type="range"]::-moz-range-thumb {
    background: var(--bls-primary);
    border: 0;
    border-radius: 50%;
    box-shadow: var(--bls-shadow-soft);
    height: 1.05rem;
    width: 1.05rem;
}

/* House focus policy: nothing on mouse, magenta ring on keyboard focus (same idiom
   as .portal-toggle-control:focus-visible). */
.portal-shell input[type="range"]:focus-visible {
    outline: 2px solid var(--bls-primary);
    outline-offset: 4px;
}

/* Input + trailing action (e.g. the board-background URL's Clear button) on one row. */
.portal-settings-inline-field {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.portal-settings-inline-field input {
    flex: 1 1 auto;
}

/* Small colour swatch beside the board-background URL input — acts as both the
   picker and the live preview of the chosen colour, so keep it compact. */
.portal-settings-inline-field input.kanban-background-color-picker {
    background: none;
    border: 1px solid var(--bls-border);
    border-radius: 0.5rem;
    cursor: pointer;
    flex: 0 0 auto;
    height: 2.1rem;
    padding: 0.15rem;
    width: 2.6rem;
}

.portal-settings-field-error {
    color: #f0a5a5;
    font-size: 0.85rem;
    margin: 0;
}

.portal-shell.theme-light .portal-settings-field-error {
    color: #a03030;
}

.portal-settings-fieldset {
    border: 0;
    margin: 0;
    padding: 0;
}

.portal-settings-fieldset legend {
    margin-bottom: 0.5rem;
}

.portal-settings-callout {
    border-left: 4px solid var(--bls-primary);
    display: grid;
    gap: 0.25rem;
    padding: 0.75rem;
}

.portal-settings-actions {
    align-items: center;
    flex-wrap: wrap;
}

.portal-settings-actions .btn {
    font-size: 0.86rem;
    padding: 0.55rem 0.8rem;
}

.portal-settings-actions .btn:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.portal-toggle-list {
    display: grid;
    gap: 0.55rem;
}

.portal-toggle {
    align-items: center;
    background: rgba(255, 255, 255, 0.045);
    border: 1px solid var(--bls-border);
    border-radius: 0.8rem;
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    gap: 0.85rem;
    justify-content: space-between;
    padding: 0.8rem 0.9rem;
    transition: border-color 120ms ease, background 120ms ease;
}

.portal-toggle:hover {
    border-color: rgba(196, 78, 175, 0.45);
}

.portal-toggle-on {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.12), rgba(123, 76, 193, 0.08));
    border-color: rgba(196, 78, 175, 0.55);
}

.portal-toggle-disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.portal-toggle-copy {
    display: grid;
    gap: 0.15rem;
    min-width: 0;
}

.portal-toggle-copy strong {
    color: var(--bls-ink);
    font-size: 0.95rem;
    font-weight: 700;
}

.portal-toggle-copy small {
    color: var(--bls-muted);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.35;
}

.portal-toggle-control {
    background: transparent;
    border: 0;
    cursor: pointer;
    flex: 0 0 auto;
    padding: 0;
}

.portal-toggle-control:disabled {
    cursor: not-allowed;
}

.portal-toggle-control:focus-visible {
    outline: 2px solid var(--bls-primary);
    outline-offset: 4px;
    border-radius: 999px;
}

.portal-toggle-track {
    background: rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
    display: inline-flex;
    height: 1.4rem;
    position: relative;
    transition: background 160ms ease, box-shadow 160ms ease;
    width: 2.55rem;
}

.portal-toggle-on .portal-toggle-track {
    background: linear-gradient(135deg, var(--bls-primary), var(--bls-purple));
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15), 0 6px 14px rgba(196, 78, 175, 0.35);
}

.portal-toggle-thumb {
    background: #ffffff;
    border-radius: 999px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
    height: 1.1rem;
    left: 0.15rem;
    position: absolute;
    top: 0.15rem;
    transition: left 160ms ease;
    width: 1.1rem;
}

.portal-toggle-on .portal-toggle-thumb {
    left: calc(100% - 1.25rem);
}

.portal-permission-card {
    align-items: center;
    border: 1px solid var(--bls-border);
    border-radius: 0.85rem;
    display: flex;
    gap: 0.85rem;
    justify-content: space-between;
    padding: 0.85rem 0.95rem;
}

.portal-permission-card-copy {
    display: grid;
    gap: 0.2rem;
    min-width: 0;
}

.portal-permission-card-copy strong {
    color: var(--bls-ink);
    font-weight: 700;
    overflow-wrap: anywhere;
}

.portal-permission-card-copy span {
    color: var(--bls-muted);
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

.portal-permission-card .btn {
    flex: 0 0 auto;
    font-size: 0.86rem;
    padding: 0.55rem 0.85rem;
    white-space: nowrap;
}

.portal-permission-card-info {
    background: linear-gradient(135deg, rgba(123, 76, 193, 0.12), rgba(196, 78, 175, 0.08));
    border-color: rgba(123, 76, 193, 0.45);
}

.portal-permission-card-success {
    background: linear-gradient(135deg, rgba(52, 211, 153, 0.12), rgba(52, 211, 153, 0.04));
    border-color: rgba(52, 211, 153, 0.45);
}

.portal-permission-card-danger {
    background: linear-gradient(135deg, rgba(251, 113, 133, 0.14), rgba(251, 113, 133, 0.04));
    border-color: rgba(251, 113, 133, 0.45);
}

.portal-permission-card-muted {
    background: rgba(255, 255, 255, 0.04);
}

@media (max-width: 600px) {
    .portal-permission-card {
        align-items: flex-start;
        flex-direction: column;
    }

    .portal-permission-card .btn {
        width: 100%;
    }
}

.welcome-hero {
    align-items: stretch;
    background:
        radial-gradient(circle at 88% 12%, rgba(196, 78, 175, 0.24), transparent 32%),
        radial-gradient(circle at 10% 10%, rgba(123, 76, 193, 0.18), transparent 30%),
        linear-gradient(135deg, rgba(23, 16, 29, 0.95) 0%, rgba(16, 8, 22, 0.95) 100%);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-lg);
    box-shadow: var(--bls-shadow-soft);
    color: var(--bls-text);
    display: grid;
    gap: 1.5rem;
    grid-template-columns: minmax(0, 1.5fr) minmax(280px, 0.75fr);
    overflow: hidden;
    padding: clamp(1.5rem, 4vw, 3rem);
}

.welcome-copy {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    justify-content: center;
}

.welcome-copy h1 {
    font-size: clamp(2.3rem, 5vw, 4.5rem);
    line-height: 1;
    margin: 0;
    max-width: 780px;
}

.welcome-copy p {
    color: var(--bls-muted);
    font-size: 1.08rem;
    line-height: 1.65;
    margin: 0;
    max-width: 720px;
}

.welcome-hero .portal-eyebrow {
    color: var(--bls-primary-dark);
}

.welcome-operations-strip {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.55rem;
    margin-top: 0.2rem;
}

.welcome-operations-strip span {
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: #dfcde8;
    font-size: 0.78rem;
    font-weight: 800;
    padding: 0.36rem 0.68rem;
}

.welcome-operations-strip span:first-child {
    color: var(--bls-primary-dark);
    letter-spacing: 0.08em;
}

.welcome-login-card {
    background: rgba(30, 20, 38, 0.86);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-lg);
    box-shadow: var(--bls-shadow);
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    padding: 1.2rem;
}

.welcome-login-card h2,
.welcome-login-card p {
    margin: 0;
}

.welcome-login-card p,
.login-option span {
    color: var(--bls-muted);
}

.login-option {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: grid;
    gap: 0.25rem;
    padding: 0.95rem;
}

.welcome-brand-lockup {
    margin-bottom: 0.2rem;
}

.portal-context-status-card {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    justify-content: space-between;
    padding: 0.4rem 0.45rem 0.4rem 0.7rem;
}

.portal-breadcrumbs,
.portal-eyebrow {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* The case number is the headline id in the case-detail header — bigger + accent-tinted.
   Scoped to .case-detail-heading so other eyebrows are unchanged. */
.case-detail-heading .portal-eyebrow {
    font-size: clamp(1rem, 1.6vw, 1.2rem);
    letter-spacing: 0.05em;
    color: var(--bls-primary-dark, #e36ccc);
}

.portal-page-header {
    align-items: flex-end;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-lg);
    box-shadow: none;
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    padding: 1.05rem 1.15rem;
}

.portal-page-header h1 {
    font-size: clamp(1.8rem, 3vw, 2.8rem);
    margin: 0.15rem 0;
}

.portal-page-header p {
    color: var(--bls-muted);
    margin: 0;
    max-width: 760px;
}

.portal-actions {
    gap: 0.6rem;
}

.btn {
    border: 1px solid transparent;
    border-radius: 999px;
    color: inherit;
    cursor: pointer;
    font-weight: 700;
    padding: 0.7rem 1rem;
    text-decoration: none;
}

.btn:focus-visible {
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    outline: none;
}

.btn-primary {
    background: linear-gradient(135deg, var(--bls-primary), #f59e0b);
    box-shadow: 0 12px 28px rgba(196, 78, 175, 0.26);
    color: #ffffff;
}

.btn-secondary {
    background: rgba(255, 255, 255, 0.07);
    border-color: var(--bls-border);
    color: #f0e4f7;
}

.portal-shell.theme-light .btn-secondary {
    background: #ffffff;
    border-color: #e2d3de;
    box-shadow: 0 1px 2px rgba(43, 43, 43, 0.06);
    color: var(--bls-ink);
}

.btn-primary:hover {
    filter: brightness(1.06);
}

.btn-secondary:hover,
.btn-ghost:hover {
    background: rgba(255, 255, 255, 0.11);
}

.btn-secondary:focus-visible,
.btn-secondary.active,
.btn-ghost:focus-visible {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    color: var(--bls-ink);
    outline: none;
}

.portal-shell.theme-light .btn-secondary:hover {
    background: #faf4f8;
    border-color: #d6bdd0;
}

.portal-shell.theme-light .btn-secondary:focus-visible,
.portal-shell.theme-light .btn-secondary.active {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.16), rgba(123, 76, 193, 0.1));
}

/* ── Light-theme contrast pass ────────────────────────────────────────────────
   A few controls were styled for the dark theme (cream text on translucent amber /
   white fills) and washed out on the light surfaces. These give them solid, on-brand
   contrast so the light theme reads as a finished, professional product. */

/* Small action buttons → a legible magenta chip instead of cream-on-amber
   (e.g. the kanban card's open-case icon button). */
.portal-shell.theme-light .btn-small {
    background: rgba(165, 58, 141, 0.1);
    color: var(--bls-primary-dark);
}

.portal-shell.theme-light .btn-small:hover {
    background: rgba(165, 58, 141, 0.16);
}

/* Keep primary buttons on-brand in light mode even when also .btn-small (the .btn-small
   light override above would otherwise wash them out to the same pale lavender). */
.portal-shell.theme-light .btn-primary {
    background: linear-gradient(135deg, var(--bls-primary), #f59e0b);
    color: #ffffff;
}

/* Ghost buttons → a defined white chip rather than near-invisible text (the card
   "Dynamics" / log-time actions, sort controls). Declared after .btn-small so the
   combined btn-small.btn-ghost reads as a ghost, matching the dark theme. */
.portal-shell.theme-light .btn-ghost {
    background: #ffffff;
    border-color: #e2d3de;
    color: var(--bls-primary-dark);
}

.portal-shell.theme-light .btn-ghost:hover,
.portal-shell.theme-light .btn-ghost:focus-visible {
    background: rgba(165, 58, 141, 0.08);
    border-color: rgba(165, 58, 141, 0.4);
    color: var(--bls-primary-dark);
}

/* "Normal" priority badge: the dark-theme cream text vanishes on light — use a
   readable ochre with enough fill. */
.portal-shell.theme-light .priority-normal {
    background: rgba(179, 116, 27, 0.16);
    color: #8a5512;
}

/* Card type tag picks up a faint fill so it reads as a chip on the white card. */
.portal-shell.theme-light .case-card-tag {
    background: rgba(43, 43, 43, 0.05);
}

.btn-small {
    background: rgba(245, 158, 11, 0.16);
    color: #ffd89a;
    font-size: 0.82rem;
    padding: 0.45rem 0.65rem;
}

/* Square icon-only button for actions that previously carried short labels
   (Open case, Log time, Copy context, Close). */
.btn-icon {
    align-items: center;
    aspect-ratio: 1;
    display: inline-flex;
    justify-content: center;
    padding: 0.55rem;
}

.btn-icon svg[blazicon] {
    height: 1.05rem;
    width: 1.05rem;
}

.btn-icon .dynamics-365-logo {
    height: 1.15rem;
    width: 1.15rem;
}

/* Icon-only send button — an SMS-style send affordance for the conversation
   compose boxes instead of a worded "Send reply" button. */
.btn-send {
    align-items: center;
    aspect-ratio: 1;
    display: inline-flex;
    justify-content: center;
    padding: 0.6rem;
}

.btn-send svg {
    height: 1.15rem;
    width: 1.15rem;
}

/* Dim the send button while there's nothing to send; it only lights up to the full
   gradient once the composer has text or an attachment. */
.btn-send:disabled {
    box-shadow: none;
    cursor: not-allowed;
    filter: saturate(0.3);
    opacity: 0.4;
}

.btn-ghost {
    background: transparent;
    border-color: var(--bls-border);
    color: var(--bls-muted);
}

.metric-grid,
.portal-grid {
    display: grid;
    gap: var(--portal-page-gap);
    min-width: 0;
}

.metric-grid {
    grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr));
}

.portal-grid {
    align-items: start;
}

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

.case-list-workspace {
    align-items: stretch;
    flex: 1 1 auto;
    grid-template-columns: minmax(0, 1fr);
    min-height: 0;
}

.case-list-workspace-has-detail {
    align-items: stretch;
    grid-template-columns: minmax(0, 1fr) minmax(min(28rem, 100%), 0.72fr);
}

.case-list-workspace > .portal-card {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.case-list-workspace > .case-detail-panel {
    max-height: var(--portal-page-min-height);
}

.case-list-workspace > .portal-card > .table-wrap {
    flex: 1 1 auto;
}

.portal-card,
.metric-card {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: 1.2rem;
    /* Flow-style separation: a soft glowing hairline along the top edge plus depth, so
       every box reads as distinctly lifted off the near-black background. */
    box-shadow:
        inset 0 1px 0 color-mix(in srgb, var(--bls-primary) 32%, transparent),
        var(--bls-shadow);
}

/* Metric cards add a soft tone-tinted corner glow on top of the shared base. */
.metric-card {
    --metric-accent: var(--bls-primary);
    background:
        radial-gradient(135% 90% at 100% 0%, color-mix(in srgb, var(--metric-accent) 16%, transparent), transparent 60%),
        var(--bls-surface);
}

/* Metric tiles that deep-link (e.g. Triaging -> filtered case list). */
a.metric-card-link {
    color: inherit;
    cursor: pointer;
    text-decoration: none;
    transition: border-color 0.15s ease, transform 0.15s ease;
}

a.metric-card-link:hover,
a.metric-card-link:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    transform: translateY(-1px);
}

.not-found-card {
    padding: 1.25rem;
}

/* Flow-style metric card: no coloured top bar — the tone reads as a soft corner glow
   (--metric-accent) instead, matching Blacklight Flow's dashboard cards. */
.metric-card {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 1.1rem;
    position: relative;
}

.metric-card span {
    color: var(--bls-muted);
    font-size: 0.74rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.metric-card strong {
    font-size: 2rem;
}

.metric-warning {
    --metric-accent: var(--bls-warning);
}

.metric-success {
    --metric-accent: var(--bls-success);
}

.metric-danger {
    --metric-accent: var(--bls-danger);
}

.metric-neutral {
    --metric-accent: var(--bls-purple);
}

.portal-card {
    min-width: 0;
    overflow: hidden;
}

.portal-card-header {
    border-bottom: 1px solid var(--bls-border);
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.1rem;
}

.portal-card-header h2,
.portal-card-header p,
.section-title {
    margin: 0;
}

.kanban-bucket-summary {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1rem;
}

.kanban-bucket-total {
    align-items: center;
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.16), rgba(123, 76, 193, 0.12));
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: flex;
    justify-content: space-between;
    padding: 1rem;
}

.kanban-bucket-total span {
    color: var(--bls-muted);
}

.kanban-bucket-total strong {
    font-size: 2rem;
}

.report-runs-summary {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 1rem;
}

.report-runs-hero,
.report-status-grid article,
.report-run-row {
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
}

.report-runs-hero,
.report-run-row {
    align-items: center;
    display: flex;
    justify-content: space-between;
}

.report-runs-hero {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.16), rgba(123, 76, 193, 0.12));
    padding: 1rem;
}

.report-runs-hero span,
.report-runs-hero small,
.report-status-grid span,
.report-run-row span {
    color: var(--bls-muted);
}

.report-runs-hero strong {
    display: block;
    font-size: 2rem;
}

.report-status-grid {
    display: grid;
    gap: 0.75rem;
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.report-status-grid article {
    background: var(--bls-surface-strong);
    padding: 0.85rem;
}

.report-status-grid strong,
.report-status-grid span,
.report-run-row strong,
.report-run-row span {
    display: block;
}

.report-status-grid strong {
    font-size: 1.35rem;
    margin-top: 0.2rem;
}

.report-run-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.report-run-row {
    background: var(--bls-surface-strong);
    gap: 1rem;
    padding: 0.85rem;
}

/* Text column shrinks so a long report name / requester email wraps inside the row
   instead of colliding with the status pill, which keeps its size. */
.report-run-row > div {
    min-width: 0;
}

.report-run-row strong,
.report-run-row > div span {
    overflow-wrap: anywhere;
}

.report-run-row > .status-pill {
    flex-shrink: 0;
}

/* Dashboard report runs are buttons that open the saved report in the viewer overlay. */
button.report-run-row {
    color: inherit;
    cursor: pointer;
    font: inherit;
    text-align: start;
    transition: background 0.15s ease, border-color 0.15s ease;
    width: 100%;
}

button.report-run-row:hover:not(:disabled),
button.report-run-row:focus-visible {
    border-color: rgba(196, 78, 175, 0.36);
}

.report-status-running {
    background: rgba(245, 158, 11, 0.15);
    color: var(--bls-warning);
}

.report-status-queued {
    background: rgba(255, 255, 255, 0.08);
    color: var(--bls-muted);
}

.report-status-ready {
    background: rgba(52, 211, 153, 0.13);
    color: var(--bls-success);
}

/* ---- reports-by-account (Admin tools > Reports) ---- */

/* Grid of account cards reuses the accounts-page list-card look (logo/monogram + body +
   pill) laid out in the shared admin card grid, so the reports view reads as native. The
   card is a button, so it needs the usual control resets. */
.reports-account-card {
    color: inherit;
    cursor: pointer;
    font: inherit;
    text-align: start;
    width: 100%;
}

.reports-card-breakdown {
    color: var(--bls-muted);
    font-size: 0.78rem;
}

/* Drill-in header: back button beside the selected account name. */
.reports-drill-header {
    align-items: center;
    display: flex;
    gap: 0.75rem;
    margin: 0.25rem 0 0.85rem;
}

.reports-drill-header h4 {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}

.reports-drill-header .btn svg[blazicon] {
    height: 0.9rem;
    width: 0.9rem;
}

/* A drill-in report row: a clickable text region (opens the viewer) plus an actions
   group (status pill + Clear confirm). The whole row can't be one button because it holds
   its own buttons, so the text region is the button instead. */
.reports-run-row {
    gap: 1rem;
}

.reports-run-open {
    background: none;
    border: none;
    color: inherit;
    cursor: pointer;
    flex: 1 1 auto;
    font: inherit;
    min-width: 0;
    padding: 0;
    text-align: start;
}

.reports-run-open strong,
.reports-run-open span {
    display: block;
    overflow-wrap: anywhere;
}

.reports-run-open span {
    color: var(--bls-muted);
    margin-top: 0.15rem;
}

.reports-run-open:hover:not(:disabled) strong,
.reports-run-open:focus-visible:not(:disabled) strong {
    color: var(--bls-primary-dark);
}

.reports-run-row .list-card-actions {
    flex-shrink: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
}

.table-wrap {
    min-height: 0;
    overflow: auto;
}

/* Case table filters reuse the shared .kanban-filter-menu / .kanban-filter-options
   dropdowns so the customer view matches the rest of the app. */

.customer-support-snapshot {
    overflow: hidden;
}

.customer-scope-switch {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 1rem 1rem 0;
}

.customer-scope-tab {
    align-items: center;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    font-weight: 800;
    gap: 0.5rem;
    padding: 0.45rem 0.75rem;
}

.customer-scope-tab span {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    color: var(--bls-text);
    min-width: 1.75rem;
    padding: 0.2rem 0.45rem;
    text-align: center;
}

.customer-scope-tab:hover,
.customer-scope-tab:focus-visible,
.customer-scope-tab-active {
    background: rgba(196, 78, 175, 0.18);
    border-color: rgba(196, 78, 175, 0.45);
    color: var(--bls-text);
    outline: none;
}

.customer-scope-summary {
    color: var(--bls-muted);
    margin: 0;
    padding: 0.65rem 1rem 0;
}

.customer-snapshot-grid,
.customer-snapshot-sections {
    display: grid;
    gap: 0.85rem;
    padding: 1rem;
}

.customer-snapshot-grid {
    grid-template-columns: 1.25fr repeat(2, minmax(0, 1fr));
    padding-bottom: 0;
}

.customer-snapshot-sections {
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
}

.customer-snapshot-hero,
.customer-snapshot-tile,
.customer-focus-item,
.customer-status-list article {
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
}

.customer-snapshot-hero,
.customer-snapshot-tile {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    justify-content: space-between;
    min-height: 8rem;
    padding: 1rem;
}

.customer-snapshot-hero {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.16), rgba(123, 76, 193, 0.12));
}

.customer-snapshot-tile,
.customer-focus-item,
.customer-status-list article {
    background: var(--bls-surface-strong);
}

.customer-snapshot-hero span,
.customer-snapshot-hero small,
.customer-snapshot-tile span,
.customer-snapshot-tile small,
.customer-focus-item span,
.customer-status-list span {
    color: var(--bls-muted);
}

.customer-snapshot-hero strong {
    font-size: 2.4rem;
}

.customer-snapshot-tile strong {
    font-size: 1.1rem;
}

.customer-snapshot-sections h3 {
    margin: 0 0 0.75rem;
}

.customer-focus-list,
.customer-status-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.customer-focus-item {
    align-items: center;
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    padding: 0.85rem;
}

.customer-focus-item strong,
.customer-focus-item span,
.customer-status-list span,
.customer-status-list strong {
    display: block;
}

/* Title/guidance column shrinks and wraps; the priority pill keeps its size. */
.customer-focus-item > div {
    min-width: 0;
}

.customer-focus-item > div strong,
.customer-focus-item > div span {
    overflow-wrap: anywhere;
}

.customer-focus-item > .priority-pill {
    flex-shrink: 0;
}

.customer-status-list article {
    align-items: center;
    display: flex;
    justify-content: space-between;
    padding: 0.85rem;
}

.customer-status-list strong {
    background: rgba(196, 78, 175, 0.14);
    border-radius: 999px;
    color: var(--bls-primary-dark);
    min-width: 2.35rem;
    padding: 0.32rem 0.68rem;
    text-align: center;
}

.customer-service-plan {
    overflow: hidden;
}

.customer-service-balances,
.customer-service-detail-grid {
    display: grid;
    gap: 0.85rem;
    padding: 1rem;
}

.customer-service-balances {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.customer-service-detail-grid {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
    padding-top: 0;
}

.customer-service-detail-grid h3 {
    margin: 0 0 0.75rem;
}

.customer-service-balance,
.customer-service-detail-card,
.customer-service-request-card,
.customer-service-confirmation {
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
}

.customer-service-balance,
.customer-service-detail-card,
.customer-service-request-card {
    background: var(--bls-surface-strong);
}

.customer-service-balance {
    align-items: flex-start;
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
    min-height: 8rem;
    padding: 1rem;
}

.customer-service-balance span,
.customer-service-balance small,
.customer-service-detail-card p,
.customer-service-detail-card small,
.customer-service-request-card span,
.customer-service-request-card p,
.customer-service-request-card small,
.customer-service-confirmation span {
    color: var(--bls-muted);
}

.customer-service-balance strong {
    display: block;
    font-size: 1.55rem;
    margin: 0.2rem 0;
    overflow-wrap: anywhere;
}

/* Left text column shrinks/wraps; the status pill keeps its size. */
.customer-service-balance > div {
    min-width: 0;
}

.customer-service-balance > div span,
.customer-service-balance > div small {
    overflow-wrap: anywhere;
}

.customer-service-balance > .service-status {
    flex-shrink: 0;
}

.customer-service-detail-list,
.customer-service-request-list {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}

.customer-service-detail-card,
.customer-service-request-card {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
    padding: 1rem;
}

.customer-service-detail-heading {
    align-items: flex-start;
    display: flex;
    gap: 1rem;
    justify-content: space-between;
}

.customer-service-detail-heading span,
.customer-service-detail-heading strong,
.customer-service-confirmation strong {
    display: block;
}

/* Heading text column shrinks/wraps; the status pill keeps its size. */
.customer-service-detail-heading > div {
    min-width: 0;
}

.customer-service-detail-heading > div span,
.customer-service-detail-heading > div strong {
    overflow-wrap: anywhere;
}

.customer-service-detail-heading > .service-status {
    flex-shrink: 0;
}

/* Request-card entitlement name/description wrap within the vertical card. */
.customer-service-request-card strong,
.customer-service-request-card span,
.customer-service-request-card p {
    overflow-wrap: anywhere;
}

.customer-service-progress {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    height: 0.55rem;
    overflow: hidden;
}

.customer-service-progress span {
    background: linear-gradient(90deg, var(--bls-primary), var(--bls-warning));
    display: block;
    height: 100%;
}

.customer-service-request-card .btn {
    align-self: flex-start;
}

.customer-service-confirmation {
    background: rgba(52, 211, 153, 0.1);
    border-color: rgba(52, 211, 153, 0.25);
    margin: 0 1rem 1rem;
    padding: 1rem;
}

.service-status {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    color: var(--bls-muted);
    flex: 0 0 auto;
    font-size: 0.78rem;
    font-weight: 800;
    padding: 0.28rem 0.55rem;
}

.service-status-active,
.service-status-available {
    background: rgba(52, 211, 153, 0.13);
    color: var(--bls-success);
}

.service-status-low-balance {
    background: rgba(245, 158, 11, 0.15);
    color: var(--bls-warning);
}

.portal-table {
    border-collapse: collapse;
    min-width: 860px;
    width: 100%;
}

.portal-table th,
.portal-table td {
    border-bottom: 1px solid var(--bls-border);
    padding: 0.85rem 1rem;
    text-align: left;
    vertical-align: top;
}

.portal-table th {
    background: var(--bls-surface-strong);
    color: var(--bls-muted);
    font-size: 0.76rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.portal-table td span {
    color: var(--bls-muted);
    display: block;
    margin-top: 0.15rem;
}

/* Case list column budget: the case (number + title) and customer columns take ALL the
   spare width; the pill/metadata columns shrink to their content (width:1% + nowrap).
   Without this, table auto-layout spreads spare width evenly, so on a narrower viewport
   the case title wraps into a sliver while every pill column floats in whitespace. */
.case-table {
    /* Content (nowrap pills, case number) sets the real floor; overflow-x scrolls only
       when genuinely needed instead of forcing an 860px table into a smaller card. */
    min-width: 0;
}

.case-table th,
.case-table td {
    padding: 0.7rem 0.65rem;
}

.case-table .case-col-tight {
    white-space: nowrap;
    width: 1%;
}

.case-table .case-col-main {
    min-width: 13rem;
}

/* The case number never breaks mid-code; the title wraps under it with the full width. */
.case-table .case-col-main strong {
    white-space: nowrap;
}

.case-table .case-col-customer {
    min-width: 8rem;
}

.case-table .case-col-customer .case-row-customer span {
    overflow-wrap: anywhere;
}

/* Contact stays one compact line; long names ellipsise (full name in the tooltip). */
.case-table .case-col-contact {
    max-width: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    width: 1%;
}

/* Sortable column headers. The <th> keeps its own uppercase/letter-spaced label styling
   (see .portal-table th) and the button inherits ALL of it - font, tracking, case, colour -
   so a sortable header is visually identical to a plain one until you hover it. Only the
   direction icon and the brighter text on the active column mark the sort, never colour
   alone: the icon is a SHAPE (up / down / two-way) and the <th> carries aria-sort. */
.case-table th .case-sort-btn {
    align-items: center;
    background: none;
    border: 0;
    border-radius: 0.4rem;
    color: inherit;
    cursor: pointer;
    display: inline-flex;
    font: inherit;
    gap: 0.3rem;
    letter-spacing: inherit;
    margin: -0.15rem -0.3rem;
    padding: 0.15rem 0.3rem;
    text-align: left;
    text-transform: inherit;
    transition: color 120ms ease, background-color 120ms ease;
}

.case-table th .case-sort-btn:hover,
.case-table th .case-sort-btn:focus-visible {
    background: var(--bls-surface-strong);
    color: var(--bls-text);
}

.case-table th .case-sort-btn:focus-visible {
    box-shadow: 0 0 0 2px var(--bls-primary);
    outline: none;
}

/* Active column: the label steps up to full text colour and bold, so the sorted column is
   obvious in every theme/preset (the tokens are redefined per preset) and in high contrast,
   where a tint alone would be flattened out. */
.case-table th .case-sort-btn.is-sorted {
    color: var(--bls-text);
    font-weight: 800;
}

.case-table th .case-sort-btn svg[blazicon] {
    flex: 0 0 auto;
    height: 0.85rem;
    /* Idle columns keep the affordance visible but quiet; it comes up to full strength on
       hover/focus and on the column actually in force. */
    opacity: 0.4;
    width: 0.85rem;
}

.case-table th .case-sort-btn.is-sorted svg[blazicon],
.case-table th .case-sort-btn:hover svg[blazicon],
.case-table th .case-sort-btn:focus-visible svg[blazicon] {
    opacity: 1;
}

/* The case cell sorts by number OR title, so it carries two buttons; they share the row and
   the divider keeps them readable as two separate controls rather than one long label. */
.case-sort-pair {
    align-items: center;
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.5rem;
}

.case-sort-pair .case-sort-btn + .case-sort-btn {
    border-left: 1px solid var(--bls-border-strong);
    border-radius: 0 0.4rem 0.4rem 0;
    margin-left: 0;
    padding-left: 0.5rem;
}

/* 601-1100px (the stacked/tablet band): the case table gets a modest 640px floor
   instead of .portal-table's 860px (which the min-width: 0 above already released)
   so .table-wrap scrolls far less violently on tablets - the column budget (width:1%
   pill columns + the two min-width'd text columns) is what actually compresses the
   row first. Other .portal-table instances keep the 860px floor. Below 600px the
   purpose-built card layout (end of file) replaces the table shape entirely, so no
   floor applies there. */
@media (min-width: 601px) and (max-width: 1100px) {
    .case-table {
        min-width: 640px;
    }
}

.portal-table-row-selectable {
    cursor: pointer;
}

.portal-table-row-selectable:hover,
.portal-table-row-selectable:focus-visible {
    background: rgba(196, 78, 175, 0.08);
    outline: none;
}

.portal-table-row-selectable[aria-selected="true"] {
    background: rgba(196, 78, 175, 0.13);
}

.status-pill,
.priority-pill,
.health {
    border-radius: 999px;
    display: inline-flex;
    font-size: 0.76rem;
    font-weight: 800;
    padding: 0.28rem 0.55rem;
}

button.status-pill,
button.priority-pill,
button.case-card-tag {
    cursor: pointer;
    font: inherit;
    line-height: 1;
    transition: filter 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

button.status-pill,
button.priority-pill {
    border: 1px solid transparent;
}

button.status-pill:hover,
button.priority-pill:hover,
button.case-card-tag:hover {
    filter: brightness(1.15);
}

button.status-pill:focus-visible,
button.priority-pill:focus-visible,
button.case-card-tag:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.18);
    outline: none;
}

button.status-pill[aria-expanded="true"],
button.priority-pill[aria-expanded="true"],
button.case-card-tag[aria-expanded="true"] {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.18);
}

/* Case-type chip in the detail header: same tag as the kanban card, sized to sit
   flush with the status/priority pills either side of it. */
.case-detail-badges .case-card-tag {
    align-items: center;
    display: inline-flex;
    font-size: 0.76rem;
    padding: 0.28rem 0.55rem;
}

.case-pill-menu {
    position: relative;
}

.case-pill-menu-options {
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow-soft);
    display: grid;
    gap: 0.2rem;
    min-width: 12rem;
    padding: 0.35rem;
    position: absolute;
    right: 0;
    top: calc(100% + 0.4rem);
    z-index: 12;
}

.case-pill-menu-option {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--bls-radius-sm);
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.5rem;
    justify-content: flex-start;
    padding: 0.35rem 0.5rem;
    text-align: left;
    transition: background-color 120ms ease, border-color 120ms ease;
    width: 100%;
}

.case-pill-menu-option:hover {
    background: rgba(196, 78, 175, 0.08);
}

.case-pill-menu-option:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    outline: none;
}

.case-pill-menu-option.active {
    background: rgba(196, 78, 175, 0.16);
    border-color: rgba(196, 78, 175, 0.42);
}

.case-pill-menu-option > .status-pill,
.case-pill-menu-option > .priority-pill,
.case-pill-menu-option > .case-card-tag {
    pointer-events: none;
}

.status-pill {
    background: rgba(196, 78, 175, 0.14);
    color: var(--bls-primary-dark);
}

.priority-normal {
    background: rgba(245, 158, 11, 0.15);
    color: #ffd89a;
}

.priority-high,
.priority-critical,
.health-failed {
    background: rgba(251, 113, 133, 0.15);
    color: var(--bls-danger);
}

/* Severity pills (incident.severitycode, P1-P5). Deliberately smaller and lighter than
   the priority pill so it reads as the secondary dimension — a compact chip, not a button. */
.severity-pill {
    align-items: center;
    border-radius: 999px;
    display: inline-flex;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1.1;
    padding: 0.1rem 0.42rem;
}

button.severity-pill {
    border: 1px solid transparent;
    cursor: pointer;
    font: inherit;
    line-height: 1;
    transition: filter 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

button.severity-pill:hover {
    filter: brightness(1.15);
}

button.severity-pill:focus-visible,
button.severity-pill[aria-expanded="true"] {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.18);
    outline: none;
}

.case-pill-menu-option > .severity-pill {
    pointer-events: none;
}

.severity-p1 { background: rgba(251, 113, 133, 0.16); color: var(--bls-danger); }
.severity-p2 { background: rgba(245, 158, 11, 0.18); color: #ffcf8a; }
.severity-p3 { background: rgba(245, 158, 11, 0.13); color: #ffd89a; }
.severity-p4 { background: rgba(125, 211, 252, 0.12); color: #bfe6ff; }
.severity-p5 { background: rgba(255, 255, 255, 0.08); color: var(--bls-muted); }

/* SLA state badge — shared across kanban cards, case table, and case detail header.
   AtRisk is the standout state (amber, slightly enlarged font-weight) because it is
   the proactive "going to fail" warning that demands attention. */
.sla-badge {
    border-radius: 999px;
    display: inline-flex;
    font-size: 0.72rem;
    font-weight: 700;
    padding: 0.22rem 0.5rem;
}
.sla-badge-ontrack {
    background: rgba(52, 211, 153, 0.12);
    color: var(--bls-success, #34d399);
}
.sla-badge-atrisk {
    background: rgba(251, 191, 36, 0.2);
    color: var(--bls-warning, #fbbf24);
    font-weight: 800;
    letter-spacing: 0.01em;
    outline: 1px solid rgba(251, 191, 36, 0.35);
    outline-offset: -1px;
}
.sla-badge-met {
    background: rgba(52, 211, 153, 0.12);
    color: var(--bls-success, #34d399);
}
.sla-badge-breached {
    background: rgba(251, 113, 133, 0.15);
    color: var(--bls-danger, #fb7185);
}
/* Light-theme overrides for the SLA badge */
.portal-shell.theme-light .sla-badge-ontrack,
.portal-shell.theme-light .sla-badge-met {
    background: rgba(27, 123, 100, 0.12);
    color: var(--bls-success, #1b7b64);
}
.portal-shell.theme-light .sla-badge-atrisk {
    background: rgba(156, 95, 12, 0.12);
    color: var(--bls-warning, #9c5f0c);
    outline-color: rgba(156, 95, 12, 0.3);
}
.portal-shell.theme-light .sla-badge-breached {
    background: rgba(184, 58, 48, 0.12);
    color: var(--bls-danger, #b83a30);
}

/* Intake severity picker: P1-P5 pills as a single-select row. */
.severity-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.severity-picker-option {
    border: 1px solid transparent;
    cursor: pointer;
    font: inherit;
    line-height: 1;
    opacity: 0.55;
    transition: opacity 120ms ease, border-color 120ms ease;
}

.severity-picker-option:hover {
    opacity: 0.85;
}

.severity-picker-option-on {
    opacity: 1;
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 2px rgba(196, 78, 175, 0.18);
}

.health-healthy {
    background: rgba(52, 211, 153, 0.13);
    color: var(--bls-success);
}

.health-warning {
    background: rgba(245, 158, 11, 0.15);
    color: var(--bls-warning);
}

.health-paused {
    background: rgba(255, 255, 255, 0.08);
    color: var(--bls-muted);
}

.kanban-hidden-buckets {
    align-items: center;
    align-self: start;
    border-bottom: 1px solid var(--bls-border);
    display: flex;
    flex: 0 0 auto;
    gap: 0.55rem;
    min-height: 0;
    overflow-x: auto;
    padding: 0.42rem 1rem;
}

/* Free-text search — shared pill styling for the kanban filter row, the customer
   case table action row and the library header. Icon + borderless input inside a
   rounded chip. */
.kanban-search,
.case-table-search,
.library-search {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    display: inline-flex;
    flex: 0 0 auto;
    gap: 0.45rem;
    min-height: 2.45rem;
    padding: 0.35rem 0.85rem;
}

.kanban-search:focus-within,
.case-table-search:focus-within,
.library-search:focus-within {
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
}

.kanban-search svg,
.case-table-search svg,
.library-search svg {
    flex: 0 0 auto;
    height: 1rem;
    width: 1rem;
}

.kanban-search input,
.case-table-search input,
.library-search input {
    background: transparent;
    border: 0;
    color: var(--bls-text);
    min-width: 9rem;
    outline: none;
    padding: 0;
}

.case-table-actions {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

/* Surfaced when a board action (e.g. drag-drop status change) fails to persist to
   Dynamics, so the optimistic move that was rolled back doesn't fail silently. */
.kanban-board-error {
    align-items: center;
    background: rgba(196, 69, 58, 0.12);
    border: 1px solid var(--bls-danger);
    border-radius: 0.75rem;
    color: var(--bls-text);
    display: flex;
    gap: 0.6rem;
    margin: 0 1rem 0.6rem;
    padding: 0.6rem 0.9rem;
}

.kanban-board-error svg {
    color: var(--bls-danger);
    flex: 0 0 auto;
    height: 1.1rem;
    width: 1.1rem;
}

.kanban-board-error span {
    flex: 1 1 auto;
}

/* Surfaced when a priority-list action (add/remove/reorder/submit/reopen) fails to
   persist to Dataverse, so the optimistic list state that was rolled back doesn't fail
   silently. Mirrors .kanban-board-error. */
.priority-list-error {
    align-items: center;
    background: rgba(196, 69, 58, 0.12);
    border: 1px solid var(--bls-danger);
    border-radius: 0.75rem;
    color: var(--bls-text);
    display: flex;
    gap: 0.6rem;
    margin: 0 0 0.6rem;
    padding: 0.6rem 0.9rem;
}

.priority-list-error svg {
    color: var(--bls-danger);
    flex: 0 0 auto;
    height: 1.1rem;
    width: 1.1rem;
}

.priority-list-error span {
    flex: 1 1 auto;
}

.kanban-filter-menu {
    position: relative;
}

.kanban-filter-menu > .btn.active {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    color: var(--bls-ink);
    outline: none;
}

.kanban-filter-options {
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow-soft);
    display: grid;
    gap: 0.35rem;
    min-width: 16rem;
    padding: 0.45rem;
    position: absolute;
    right: 0;
    top: calc(100% + 0.45rem);
    z-index: 8;
}

.kanban-filter-option {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.8rem;
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    font-weight: 800;
    gap: 0.75rem;
    justify-content: space-between;
    padding: 0.62rem 0.7rem;
    text-align: left;
}

.kanban-filter-option:hover,
.kanban-filter-option:focus-visible,
.kanban-filter-option.active {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    color: var(--bls-ink);
    outline: none;
}

.kanban-filter-option strong {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    flex: 0 0 auto;
    padding: 0.08rem 0.42rem;
}

/* In-popup search + scroll for long filter lists (the customer filter spans the whole
   client base, well over a hundred accounts). */
.kanban-filter-search-input {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    box-sizing: border-box;
    color: var(--bls-text);
    min-height: 2.3rem;
    padding: 0.45rem 0.65rem;
    width: 100%;
}

.kanban-filter-search-input:focus-visible {
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    outline: none;
}

.kanban-filter-options-customer {
    max-width: min(20rem, 80vw);
}

.case-filter-static {
    cursor: default;
    opacity: 0.85;
    pointer-events: none;
}

.kanban-filter-scroll {
    display: grid;
    gap: 0.35rem;
    max-height: min(50vh, 20rem);
    overflow-y: auto;
}

.kanban-filter-empty {
    color: var(--bls-muted);
    font-size: 0.82rem;
    padding: 0.5rem 0.7rem;
}

/* ---- Weekly priority list ---- */
/* Fills the page height like the kanban so the add-case menu has room and the list
   scrolls inside the card rather than the page floating short. */
.priority-list-card {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    height: var(--portal-page-min-height);
    max-height: var(--portal-page-min-height);
    min-height: 0;
}

.priority-list-card .empty-state {
    flex: 1 1 auto;
    margin: 1rem;
}

.priority-header-actions {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    gap: 0.5rem;
}

.priority-locked-badge {
    align-items: center;
    background: var(--bls-surface-muted, rgba(148, 163, 184, 0.16));
    border-radius: 999px;
    color: var(--bls-text-muted, #64748b);
    display: inline-flex;
    font-size: 0.78rem;
    font-weight: 600;
    gap: 0.35rem;
    padding: 0.3rem 0.7rem;
    white-space: nowrap;
}

.priority-locked-badge svg[blazicon] {
    height: 0.9rem;
    width: 0.9rem;
}

/* User access (delegated seat admin) */
.user-access-seats {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.user-access-seats .user-access-seat-full {
    background: rgba(220, 38, 38, 0.16);
    color: #fca5a5;
}

.user-access-status {
    color: #fca5a5;
    font-size: 0.85rem;
    margin: 0 0 0.75rem;
}

.user-access-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* Compact person cards — click one to open the access popover. */
.user-access-people {
    display: grid;
    gap: 0.5rem;
    /* Lower the card floor (208px) so more contacts fit per row in the wider detail pane;
       min(100%, 13rem) stops a single card forcing horizontal overflow on narrow panes. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
}

/* Compact vertical card (~half the height of a team card): name + email stacked at the
   top, status + tools summary on their own row at the foot. Stacking (rather than a
   space-between row) keeps a long email from colliding with the status/summary, and the
   card grows to fit its content rather than letting anything spill outside it. */
.user-access-person-card {
    align-items: stretch;
    background: var(--bls-surface-muted, rgba(148, 163, 184, 0.08));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    border-radius: 0.75rem;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    font: inherit;
    gap: 0.5rem;
    justify-content: space-between;
    min-height: 5.5rem;
    padding: 0.7rem 0.85rem;
    text-align: left;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.user-access-person-card:hover,
.user-access-person-card:focus-visible {
    border-color: var(--bls-primary, #c44eaf);
    outline: none;
}

.user-access-person-meta {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.5rem;
}

.user-access-person-summary {
    color: var(--bls-text-muted, #94a3b8);
    font-size: 0.72rem;
    overflow-wrap: anywhere;
}

/* Per-person access popover. */
.user-access-popover-overlay {
    align-items: center;
    background: rgba(2, 6, 23, 0.55);
    display: flex;
    inset: 0;
    justify-content: center;
    padding: var(--portal-overlay-padding);
    position: fixed;
    z-index: 60;
}

.user-access-popover {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    max-width: 30rem;
    padding: 1.1rem 1.25rem;
    width: 100%;
}

.user-access-popover-header {
    align-items: flex-start;
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
}

.user-access-popover-row {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

.user-access-popover-features {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.user-access-popover-features p {
    margin: 0;
}

.user-access-row {
    align-items: center;
    background: var(--bls-surface-muted, rgba(148, 163, 184, 0.08));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    border-radius: 0.75rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
    justify-content: space-between;
    padding: 0.7rem 0.9rem;
}

.user-access-person {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    /* min-width: 0 lets the block shrink inside the grid track so a long email wraps
       within the card instead of forcing the name/email past its edge. */
    min-width: 0;
}

.user-access-person strong {
    font-size: 0.9rem;
    overflow-wrap: anywhere;
}

.user-access-person span {
    color: var(--bls-text-muted, #94a3b8);
    font-size: 0.78rem;
    overflow-wrap: anywhere;
}

.user-access-enable {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.user-access-status-on {
    background: rgba(34, 197, 94, 0.16);
    color: #86efac;
}

.user-access-tier {
    align-items: center;
    color: var(--bls-text-muted, #94a3b8);
    display: flex;
    font-size: 0.76rem;
    font-weight: 600;
    gap: 0.35rem;
}

.user-access-tier select {
    background: var(--bls-surface, rgba(15, 23, 42, 0.6));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.35));
    border-radius: 0.5rem;
    color: var(--bls-text, #e2e8f0);
    font-size: 0.76rem;
    padding: 0.2rem 0.4rem;
}

.user-access-features {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.user-access-chip {
    background: color-mix(in srgb, var(--bls-text) 6%, transparent);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    font-size: 0.76rem;
    font-weight: 600;
    padding: 0.28rem 0.7rem;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.user-access-chip:hover:not(:disabled):not([aria-disabled="true"]) {
    border-color: var(--bls-primary, #c44eaf);
    color: var(--bls-text, #e2e8f0);
}

.user-access-chip-on {
    background: rgba(196, 78, 175, 0.18);
    border-color: rgba(196, 78, 175, 0.6);
    color: #ffbdf5;
}

.user-access-chip:disabled {
    cursor: not-allowed;
    opacity: 0.45;
}

.user-access-remove {
    color: var(--bls-danger);
}

/* Non-blocking acknowledgement shown while a pinned customer context loads. */
.portal-context-switching {
    background: var(--bls-surface, rgba(15, 23, 42, 0.92));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.25));
    border-radius: 999px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
    left: 50%;
    padding: 0.35rem 0.9rem;
    position: fixed;
    top: 4.5rem;
    transform: translateX(-50%);
    z-index: 50;
}

/* Create-account / invite-contact forms (Settings → Account management). */
.portal-create-form {
    background: var(--bls-surface-muted, rgba(148, 163, 184, 0.08));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    border-radius: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 0.9rem;
    padding: 0.9rem 1rem;
}

.portal-create-form h3 {
    margin: 0;
}

.portal-create-form h4 {
    color: var(--bls-text-muted, #94a3b8);
    margin: 0.4rem 0 0;
}

.portal-create-form .portal-settings-field input,
.portal-create-form .portal-settings-field select {
    background: var(--bls-surface, rgba(15, 23, 42, 0.6));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.35));
    border-radius: 0.5rem;
    color: var(--bls-text, #e2e8f0);
    padding: 0.4rem 0.55rem;
    width: 100%;
}

/* Profile → password reset row. */
.portal-settings-security {
    align-items: center;
    border-top: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    justify-content: space-between;
    margin-top: 0.75rem;
    padding-top: 0.85rem;
}

.portal-settings-security p {
    margin: 0.15rem 0 0;
}

/* Unified Accounts & access tab (master-detail). */
.account-access-panel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.account-access-layout {
    display: grid;
    gap: 1rem;
    /* Proportional master that floors at 11rem and caps at 18rem: the account list scales
       DOWN with the popover instead of always claiming 18rem, so the access/contacts pane
       (minmax(0,1fr)) keeps the majority of width at medium sizes and can shrink its grid. */
    grid-template-columns: minmax(11rem, min(26%, 18rem)) minmax(0, 1fr);
}

/* Stack the master-detail earlier (960px not 720px): below this there isn't enough width
   for two comfortable columns, so the contacts pane gets the full width. */
@media (max-width: 960px) {
    .account-access-layout {
        grid-template-columns: 1fr;
    }

    /* Higher specificity than the base list rule below so it wins regardless of source
       order — a shorter, viewport-relative list when stacked so contacts aren't pushed far. */
    .account-access-layout .account-access-account-list {
        max-height: clamp(10rem, 28vh, 18rem);
    }
}

.account-access-master {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}

.account-access-account-list {
    /* Viewport-relative instead of a fixed 22rem cap: more accounts on tall screens, yields
       room to the contact pane on short ones (the 960px stacked rule shortens it further). */
    max-height: clamp(16rem, 45vh, 30rem);
}

.account-access-master .list-card-clickable {
    cursor: pointer;
    font: inherit;
    text-align: left;
    width: 100%;
}

.account-access-detail {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    min-width: 0;
}

.account-access-detail-header {
    align-items: center;
    border-bottom: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    padding-bottom: 0.75rem;
}

.account-access-detail-title {
    display: flex;
    flex-direction: column;
    margin-right: auto;
}

.account-access-detail-title strong {
    font-size: 1rem;
}

.account-access-detail-title span {
    color: var(--bls-text-muted, #94a3b8);
    font-size: 0.8rem;
}

.account-access-readonly-contacts {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.account-access-directory {
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    border-radius: 0.75rem;
    padding: 0.5rem 0.9rem;
}

.account-access-directory > summary {
    cursor: pointer;
    font-weight: 600;
    padding: 0.35rem 0;
}

/* Customer rights (staff-set seat entitlements per customer tool). */
.customer-rights-panel {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.customer-rights-grid {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.customer-rights-row {
    align-items: center;
    background: var(--bls-surface-muted, rgba(148, 163, 184, 0.08));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    border-radius: 0.6rem;
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
    padding: 0.5rem 0.75rem;
}

.customer-rights-role {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.customer-rights-edit {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.customer-rights-edit input {
    background: var(--bls-surface, rgba(15, 23, 42, 0.6));
    border: 1px solid var(--bls-border, rgba(148, 163, 184, 0.35));
    border-radius: 0.45rem;
    color: inherit;
    padding: 0.35rem 0.5rem;
    width: 5.5rem;
}

/* Team-based access (internal account: teams carry the role bundle, members hold it). */
.team-access-panel {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}

.team-access-roles,
.team-access-members {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.team-access-member {
    align-items: center;
    display: flex;
    gap: 0.6rem;
    justify-content: space-between;
}

/* Optimistically-added member: the card shows instantly, slightly dimmed until the
   backend confirms. */
.team-access-member-syncing {
    opacity: 0.65;
}

/* Leaver hygiene + access log panels reuse the team-access list styling. */
.access-hygiene-panel,
.access-audit-panel {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.9rem;
}

/* Legacy-to-teams migration (RBAC Phase 6): proposal cards + reports reuse the
   team-access list styling. */
.team-migration-panel {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.9rem;
}

.team-migration-include {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.team-migration-include input[type="checkbox"] {
    flex: 0 0 auto;
}

.team-migration-roles {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin: 0.4rem 0;
}

.team-migration-members {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.team-migration-report {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.access-audit-list {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

/* Sub-tabs that chunk the access settings into one section at a time (small screens):
   reuses the customer-scope pill tabs but sits flush inside the settings card. */
.access-subtabs {
    margin: 0.9rem 0 0.6rem;
    padding: 0;
}

/* Coalesced role-change emails waiting to send (with a manual "Send now"). */
.pending-access-updates {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin: 0.6rem 0;
    padding: 0.5rem 0.75rem;
}

/* Access-request queue (approver) + request card (requester). */
.access-request-queue,
.access-request-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0.6rem 0;
}

.access-request-row {
    align-items: center;
    display: flex;
    gap: 0.6rem;
    justify-content: space-between;
}

/* Directory-wide access management (Settings → Access management). */
.directory-access-search {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    margin-bottom: 0.85rem;
}

.directory-access-search .list-filter-input {
    flex: 1 1 auto;
}

/* The result rows are buttons but should read like the user-access rows. */
.directory-access-result {
    cursor: pointer;
    text-align: left;
    width: 100%;
}

.directory-access-selected {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    margin-bottom: 0.85rem;
}

.directory-access-group {
    border-top: 1px solid var(--bls-border, rgba(148, 163, 184, 0.18));
    padding-top: 0.75rem;
    margin-top: 0.75rem;
}

.directory-access-group .portal-eyebrow {
    margin-bottom: 0.5rem;
}

.user-access-roles-count {
    color: var(--bls-muted);
    font-size: 0.78rem;
    margin: 0 0 0.25rem;
}

.user-access-roles-count strong {
    color: var(--bls-primary-dark, #e36ccc);
    font-variant-numeric: tabular-nums;
}

/* A chip mid-toggle: reads as "working", not "disabled". Uses aria-disabled (not native
   disabled) so the button keeps keyboard focus while the toggle is in flight; re-clicks are
   guarded in code. Placed after .user-access-chip:disabled so it wins the opacity/cursor. */
.user-access-chip-pending {
    cursor: progress;
    opacity: 0.85;
}

.user-access-chip[aria-disabled="true"] {
    cursor: progress;
}

.user-access-chip-spinner {
    animation: user-access-chip-spin 0.6s linear infinite;
    border: 2px solid currentColor;
    border-radius: 50%;
    border-right-color: transparent;
    display: inline-block;
    height: 0.7em;
    margin-left: 0.4em;
    vertical-align: -0.1em;
    width: 0.7em;
}

@keyframes user-access-chip-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .user-access-chip-spinner { animation-duration: 1.6s; }
}

/* A chip armed for removal (first click of the two-click remove guard). Placed after
   .user-access-chip-on so the danger colours win over the "on" magenta. */
.user-access-chip-confirm {
    background: rgba(220, 38, 38, 0.16);
    border-color: var(--bls-danger, #fb7185);
    color: var(--bls-danger, #fb7185);
}

/* The "Remove from portal" second-step confirmation block. */
.user-access-remove-confirm {
    background: rgba(220, 38, 38, 0.08);
    border: 1px solid var(--bls-danger, #fb7185);
    border-radius: 0.6rem;
    margin-top: 0.5rem;
    padding: 0.6rem 0.75rem;
}

.user-access-remove-confirm p {
    color: var(--bls-muted);
    font-size: 0.8rem;
    margin: 0 0 0.5rem;
}

.priority-add-menu {
    position: relative;
}

.priority-add-menu .btn {
    align-items: center;
    display: inline-flex;
    gap: 0.4rem;
}

.priority-add-menu .btn svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.priority-add-options {
    min-width: 18rem;
}

.priority-list {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.6rem;
    list-style: none;
    margin: 0;
    min-height: 0;
    overflow-y: auto;
    padding: 1rem;
}

.priority-item {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    cursor: grab;
    display: flex;
    gap: 0.75rem;
    padding: 0.7rem 0.85rem;
}

.priority-item-dragging {
    border-color: rgba(196, 78, 175, 0.5);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.14);
    opacity: 0.85;
}

.priority-rank {
    align-items: center;
    background: linear-gradient(135deg, var(--bls-primary), var(--bls-purple));
    border-radius: 999px;
    color: #ffffff;
    display: inline-flex;
    flex: 0 0 auto;
    font-weight: 900;
    height: 1.9rem;
    justify-content: center;
    width: 1.9rem;
}

.priority-grip {
    color: var(--bls-muted);
    cursor: grab;
    flex: 0 0 auto;
}

.priority-grip svg[blazicon] {
    height: 1.1rem;
    width: 1.1rem;
}

.priority-item-body {
    display: grid;
    flex: 1 1 auto;
    gap: 0.15rem;
    min-width: 0;
}

.priority-item-top {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.priority-case-number {
    color: var(--bls-muted);
    font-size: 0.78rem;
    font-weight: 800;
}

.priority-item-body strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.priority-item-meta {
    color: var(--bls-muted);
    font-size: 0.8rem;
    overflow-wrap: anywhere;
}

.priority-item-actions {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    gap: 0.2rem;
}

.kanban-order-control {
    align-items: center;
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    display: inline-flex;
    font-size: 0.82rem;
    font-weight: 800;
    gap: 0.45rem;
    min-height: 2.75rem;
    padding: 0.35rem 0.55rem 0.35rem 0.85rem;
}

.kanban-order-control select {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--bls-ink);
    cursor: pointer;
    font-weight: 800;
    outline: none;
    padding: 0.2rem 1.15rem 0.2rem 0;
}

.kanban-order-control option {
    background: var(--bls-surface-strong);
    color: var(--bls-text);
}

.kanban-order-control:focus-within {
    border-color: rgba(196, 78, 175, 0.58);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
}

.kanban-hidden-buckets > span {
    color: var(--bls-muted);
    flex: 0 0 auto;
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.kanban-hidden-bucket {
    align-items: center;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 0.82rem;
    font-weight: 800;
    gap: 0.38rem;
    min-height: 2rem;
    padding: 0.38rem 0.65rem;
}

.kanban-hidden-bucket:hover,
.kanban-hidden-bucket:focus-visible,
.kanban-hide-column:hover,
.kanban-hide-column:focus-visible {
    background: rgba(196, 78, 175, 0.14);
    border-color: rgba(196, 78, 175, 0.5);
    color: var(--bls-ink);
    outline: none;
}

.kanban-hidden-bucket strong {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    padding: 0.08rem 0.38rem;
}

.kanban-hidden-bucket svg,
.kanban-hide-column svg {
    height: 0.95rem;
    width: 0.95rem;
}

.kanban-board-shell {
    display: grid;
    flex: 1 1 auto;
    min-height: 0;
    position: relative;
}

/* Trello-style custom board background — a per-user image URL (Settings >
   Personalisation) exposed by AdminKanban as --kanban-custom-bg. The image is
   painted under a soft scrim so cards, pills and text stay readable over busy
   photos; the columns turn translucent/frosted (rules further down). */
.kanban-board-shell-custom-bg {
    /* 105% / 105% (not cover): sized 5% larger than the shell in both axes so every
       image always fills the box — including SVGs with no intrinsic size, where cover
       falls back to contain-like gaps. The overhang is cropped by the clip below. */
    background:
        linear-gradient(rgba(10, 8, 16, 0.32), rgba(10, 8, 16, 0.48)),
        var(--kanban-custom-bg) center / 105% 105% no-repeat;
    border-radius: 1rem;
    /* Hard rounded clip: overflow+radius alone lets the sticky column headers and
       backdrop-filter columns paint square corners in Chromium, so the wallpaper
       showed square top corners under the card header. clip-path always wins. */
    clip-path: inset(0 round 1rem);
    overflow: hidden;
}

.portal-shell.theme-light .kanban-board-shell-custom-bg {
    background:
        linear-gradient(rgba(20, 12, 28, 0.12), rgba(20, 12, 28, 0.28)),
        var(--kanban-custom-bg) center / 105% 105% no-repeat;
}

.kanban-board {
    display: flex;
    gap: 0.5rem;
    cursor: grab;
    height: 100%;
    min-height: 0;
    overscroll-behavior-inline: contain;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 1rem;
    scroll-behavior: smooth;
    scrollbar-width: none;
}

.kanban-board.kanban-compact {
    display: grid;
    grid-template-columns: repeat(3, minmax(220px, 1fr));
}

.kanban-board::-webkit-scrollbar {
    display: none;
}

.kanban-board:focus-visible {
    box-shadow: inset 0 0 0 3px rgba(196, 78, 175, 0.18);
    outline: none;
}

.kanban-board-panning {
    cursor: grabbing;
    user-select: none;
}

.kanban-empty-board {
    align-self: center;
    grid-column: 1 / -1;
    justify-self: center;
    max-width: 28rem;
    text-align: center;
}

.kanban-pan-button {
    align-items: center;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    box-shadow: none;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    height: 2.35rem;
    justify-content: center;
    opacity: 0.85;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    transition: background 140ms ease, border-color 140ms ease, opacity 140ms ease;
    width: 2.35rem;
    z-index: 4;
}

.kanban-board-shell:not(.kanban-board-shell-overflowing) .kanban-pan-button {
    display: none;
}

.kanban-pan-button:hover,
.kanban-pan-button:focus-visible {
    background: rgba(196, 78, 175, 0.2);
    border-color: rgba(196, 78, 175, 0.58);
    opacity: 1;
    outline: none;
}

.kanban-pan-left {
    left: 0.55rem;
}

.kanban-pan-right {
    right: 0.55rem;
}

.kanban-pan-glyph {
    display: inline-block;
    position: relative;
}

.kanban-pan-glyph {
    border-color: currentColor;
    border-style: solid;
    height: 0.72rem;
    width: 0.72rem;
}

.kanban-pan-glyph-left {
    border-width: 0 0 2px 2px;
    transform: translateX(0.12rem) rotate(45deg);
}

.kanban-pan-glyph-right {
    border-width: 2px 2px 0 0;
    transform: translateX(-0.12rem) rotate(45deg);
}

.kanban-compact {
    grid-template-columns: repeat(3, minmax(220px, 1fr));
}

.kanban-column {
    background: var(--bls-surface-strong);
    border: 1px solid transparent;
    border-radius: 0.9rem;
    display: flex;
    flex: 1 0 230px;
    flex-direction: column;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* No top padding: the sticky header sits flush at the top so it pins cleanly at top:0
       (a negative top margin on the header would break sticky pinning). */
    padding: 0 0.75rem 0.75rem;
    scrollbar-width: none;
    transition: border-color 140ms ease, box-shadow 140ms ease, background 140ms ease;
}

.kanban-compact > .kanban-column {
    flex-basis: auto;
}

.kanban-column::-webkit-scrollbar {
    display: none;
}

.kanban-column header {
    background: var(--bls-surface-strong);
    display: flex;
    flex: 0 0 auto;
    font-weight: 800;
    justify-content: space-between;
    /* Negative side margins + matching padding stretch the opaque background across the
       column's full width (over the side padding) so cards are fully masked as they scroll
       under the pinned title. top:0 with no negative top margin keeps sticky pinning intact. */
    margin: 0 -0.75rem 0.2rem;
    padding: 0.75rem 0.75rem 0.7rem;
    position: sticky;
    top: 0;
    z-index: 3;
}

.kanban-column-title {
    align-items: center;
    display: flex;
    gap: 0.42rem;
    min-width: 0;
}

.kanban-column-title > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.kanban-hide-column {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    height: 1.6rem;
    justify-content: center;
    padding: 0;
    width: 1.6rem;
}

.kanban-column-drop-target {
    background: rgba(196, 78, 175, 0.08);
    border-color: rgba(196, 78, 175, 0.7);
    box-shadow: inset 0 0 0 2px rgba(196, 78, 175, 0.18);
}

/* Rounded columns: match the sticky header's top corners to the column radius so it
   doesn't poke square corners over the rounding as cards scroll beneath it. */
.kanban-column > header {
    border-radius: 0.9rem 0.9rem 0 0;
}

/* Bucket count as a quiet pill instead of a bare number. */
.kanban-column > header > strong {
    background: rgba(196, 78, 175, 0.14);
    border-radius: 999px;
    font-size: 0.8rem;
    line-height: 1.4;
    min-width: 1.6rem;
    padding: 0.1rem 0.55rem;
    text-align: center;
}

/* Frosted, translucent buckets when a custom background image is set — the Trello
   look. The sticky header gets a stronger alpha of the same tint (nested
   backdrop-filters don't compose, so it can't blur again) to keep masking cards
   that scroll underneath it. */
.kanban-board-shell-custom-bg .kanban-column {
    backdrop-filter: blur(12px) saturate(1.15);
    -webkit-backdrop-filter: blur(12px) saturate(1.15);
    background: rgba(22, 16, 30, 0.66);
    border-color: rgba(255, 255, 255, 0.09);
}

.kanban-board-shell-custom-bg .kanban-column > header {
    background: rgba(22, 16, 30, 0.85);
}

.portal-shell.theme-light .kanban-board-shell-custom-bg .kanban-column {
    background: rgba(255, 255, 255, 0.72);
    border-color: rgba(33, 27, 31, 0.12);
}

.portal-shell.theme-light .kanban-board-shell-custom-bg .kanban-column > header {
    background: rgba(255, 255, 255, 0.88);
}

/* High contrast: buckets get an explicit strong edge (they otherwise rely on a
   subtle surface tint), and the frosted translucency is dropped so text never
   sits over a low-contrast blurred photo. */
.portal-a11y-contrast .kanban-column {
    border-color: var(--bls-border);
    border-width: 2px;
}

.portal-a11y-contrast .kanban-board-shell-custom-bg .kanban-column {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--bls-bg, #0d0a12);
}

.portal-a11y-contrast .kanban-board-shell-custom-bg .kanban-column > header {
    background: var(--bls-bg, #0d0a12);
}

/* Reduce transparency: the frosted buckets go solid (wallpaper stays, blur goes) -
   the toggle's contract is "no translucent surfaces", and this pair was the one
   custom-bg surface without a fallback (caught by the P3 phone verify). The
   .portal-shell prefix isn't decorative: it lifts specificity to (0,4,0) so these
   beat the theme-light frosted rules above on the source-order tiebreak. A11y stays
   opt-in - the default frosted look is untouched. */
.portal-shell.portal-a11y-notransparency .kanban-board-shell-custom-bg .kanban-column {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--bls-bg, #0d0a12);
}

.portal-shell.portal-a11y-notransparency .kanban-board-shell-custom-bg .kanban-column > header {
    background: var(--bls-bg, #0d0a12);
}

.case-card {
    /* Solid (not a gradient) so every card is the exact same shade regardless of height;
       depth comes from the border + shadow. */
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: 0.95rem;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.04);
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-bottom: 0.8rem;
    padding: 0.95rem;
}

/* Light mode: match the dark card's depth (the flat pale version read too basic) —
   a soft white→lavender gradient, a tinted border, and a gentle shadow + top highlight. */
.portal-shell.theme-light .case-card {
    background: #ffffff;
    border-color: rgba(123, 76, 120, 0.2);
    box-shadow: 0 2px 12px rgba(86, 54, 98, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.85);
}

/* Green "Time" button on each case card. */
.case-card-time-btn {
    background: rgba(34, 197, 94, 0.16);
    border: 1px solid rgba(34, 197, 94, 0.5);
    color: #86efac;
}

.case-card-time-btn:hover,
.case-card-time-btn:focus-visible {
    background: rgba(34, 197, 94, 0.28);
    border-color: rgba(34, 197, 94, 0.75);
    color: #dcfce7;
    outline: none;
}

/* Light mode: the dark-card green tint + light text washes out on white. Give the Time
   button a filled tint + dark readable text + a defined border so it pops like the dark card. */
.portal-shell.theme-light .case-card-time-btn {
    background: rgba(34, 197, 94, 0.16);
    border-color: rgba(22, 163, 74, 0.6);
    color: #15803d;
}

.portal-shell.theme-light .case-card-time-btn:hover,
.portal-shell.theme-light .case-card-time-btn:focus-visible {
    background: rgba(34, 197, 94, 0.26);
    color: #166534;
}

/* Icon-only card buttons (Open case, Log time) go grey in light theme — except the
   Time button, which keeps its green .case-card-time-btn treatment. */
.portal-shell.theme-light .case-card-actions .btn-icon:not(.case-card-time-btn) {
    background: rgba(33, 27, 31, 0.05);
    border: 1px solid rgba(33, 27, 31, 0.18);
    color: #211b1f;
}

.case-card-selectable {
    cursor: pointer;
}

.case-card-selectable[draggable="true"] {
    cursor: grab;
}

.case-card-dragging {
    cursor: grabbing;
    opacity: 0.58;
}

.case-card-selectable:hover,
.case-card-selectable:focus-visible {
    border-color: rgba(196, 78, 175, 0.7);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28), 0 0 0 3px rgba(196, 78, 175, 0.14);
    outline: none;
    transform: translateY(-1px);
}

.case-card-selectable {
    transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
}

/* Cards over a custom background: near-opaque so photo detail never fights the text. */
.kanban-board-shell-custom-bg .case-card {
    background: rgba(14, 10, 18, 0.92);
}

.portal-shell.theme-light .kanban-board-shell-custom-bg .case-card {
    background: rgba(255, 255, 255, 0.96);
}

/* Pan buttons need their own scrim to stay visible over an arbitrary photo. */
.kanban-board-shell-custom-bg .kanban-pan-button {
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    background: rgba(14, 10, 18, 0.55);
}

.case-card h3 {
    font-size: 1rem;
    line-height: 1.3;
    margin: 0;
}

.case-card p {
    margin: 0;
}

/* Card masthead: the customer logo sits in the top-left corner, badges (priority,
   severity, SLA) cluster at the right. The customer name is intentionally dropped —
   the logo identifies the account and frees the card for the title. */
.case-card-top {
    align-items: center;
    gap: 0.5rem;
    justify-content: space-between;
}

.case-card-badges {
    align-items: center;
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    justify-content: flex-end;
}

/* Case ref + title stacked together on their own lines beneath the masthead, so the
   code reads as a clear eyebrow on the line above the title rather than crowding pills. */
.case-card-headline {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

.case-card-number {
    color: var(--bls-muted);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    /* Long case numbers wrap rather than overflow the card. */
    overflow-wrap: anywhere;
    text-transform: uppercase;
}

.case-card-top .priority-pill,
.case-card-top .severity-pill {
    flex: 0 0 auto;
}

/* Wrap-friendly meta row: a Type tag plus icon facts (age, files). Left-aligned with
   real gaps so values can't collide the way the old space-between spans did. */
.case-card-meta {
    color: var(--bls-muted);
    flex-wrap: wrap;
    font-size: 0.78rem;
    gap: 0.35rem 0.5rem;
    justify-content: flex-start;
}

.case-card-tag {
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    font-weight: 700;
    padding: 0.12rem 0.55rem;
}

.case-card-fact {
    align-items: center;
    display: inline-flex;
    gap: 0.28rem;
    white-space: nowrap;
}

.case-card-fact svg[blazicon] {
    height: 0.85rem;
    width: 0.85rem;
}

.intake-card {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    /* Viewport-fit card: the form pane and assistant thread scroll inside it. */
    max-height: var(--portal-page-min-height);
    min-height: 0;
}

.intake-layout {
    display: grid;
    flex: 1 1 auto;
    gap: 1rem;
    grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
    min-height: 0;
    padding: 1rem;
}

.intake-compact {
    grid-template-columns: 1fr;
}

.intake-form-pane {
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The intake form stacks in a single column beside the assistant pane. */
.intake-form-pane .case-form {
    grid-template-columns: minmax(0, 1fr);
}

.intake-assistant {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 1rem;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

.intake-assistant-header {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.22), rgba(255, 255, 255, 0.04));
    border-bottom: 1px solid var(--bls-border);
    padding: 0.75rem 0.9rem;
}

.intake-assistant-header h3 {
    margin: 0.05rem 0 0;
}

/* The docked panel is narrower than a full card, so the chat runs tighter
   than the floating popover's bubbles. */
.intake-assistant .assistant-thread {
    font-size: 0.85rem;
    gap: 0.14rem;
    padding: 0.75rem;
}

.intake-assistant .chat-bubble {
    max-width: 88%;
    padding: 0.5rem 0.75rem;
}

.intake-assistant .assistant-composer {
    margin: 0 0.75rem 0.75rem;
}

.intake-assistant .assistant-composer-input {
    font-size: 0.85rem;
}

.intake-assistant .assistant-composer-files {
    font-size: 0.8rem;
    margin: -0.45rem 0.75rem 0.75rem;
}

.stack-list,
.case-form {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}

.case-form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.case-form label {
    color: var(--bls-muted);
    display: flex;
    flex-direction: column;
    font-size: 0.82rem;
    font-weight: 700;
    gap: 0.35rem;
}

.case-form input,
.case-form select,
.case-form textarea,
.case-form-value {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-text);
    display: block;
    min-height: 2.75rem;
    padding: 0.65rem 0.75rem;
}

.case-form textarea {
    min-height: 7rem;
    resize: vertical;
}

.case-form-value-text {
    min-height: 7rem;
    white-space: pre-wrap;
}

.span-2 {
    grid-column: span 2;
}

.attachment-drop {
    background: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.04) 12px, rgba(196, 78, 175, 0.07) 12px, rgba(196, 78, 175, 0.07) 24px);
    border: 1px dashed var(--bls-primary);
    border-radius: 0.9rem;
    color: var(--bls-muted);
    padding: 1rem;
}

.attachment-drop strong {
    color: var(--bls-text);
    display: block;
}

.stack-list {
    /* As a grid/flex item the default min-width:auto refuses to shrink below the
       content's min-content width, so wide rows (logo + name + status pill)
       overflow narrow columns on a phone. Allow it to shrink to its container. */
    min-width: 0;
    padding: 1rem;
}

.section-title {
    padding: 1rem 1rem 0;
}

/* Long live lists (800+ accounts, 1500+ contacts) scroll within their own column
   instead of stretching the page. */
.stack-list-scroll {
    max-height: min(36rem, 65vh);
    overflow-y: auto;
}

.list-card {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.9rem;
    justify-content: space-between;
    padding: 0.85rem;
}

.list-card span {
    display: block;
    margin-top: 0.15rem;
}

/* Account cards select a client to filter the contacts column. */
.list-card-clickable {
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.list-card-clickable:hover {
    border-color: rgba(196, 78, 175, 0.36);
}

.list-card-selected {
    background: rgba(196, 78, 175, 0.12);
    border-color: rgba(196, 78, 175, 0.36);
}

.kanban-filters,
.case-card-actions,
.case-detail-badges,
.attachment-panel-header,
.attachment-meta,
.list-card-actions {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.kanban-workspace {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

.kanban-workspace-compact {
    grid-template-columns: 1fr;
}

.case-card-actions {
    justify-content: space-between;
}

.kanban-card {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    /* Fill the page via the flex chain rather than a fixed page-min-height (which
       settled ~14px short of the content box, leaving a dead strip below the board).
       height:0 makes flex-grow drive the size from the available box instead of the
       card's content height (content-height would overshoot and scroll). min-height:0
       keeps the board scrolling internally instead of growing the page. Paired with the
       .portal-page:has(.kanban-card) override below, which frees the wrapper to fill. */
    height: 0;
    min-height: 0;
    position: relative;
}

.case-detail-popover {
    border-radius: var(--bls-radius-lg);
    display: flex;
    overscroll-behavior: contain;
    overflow: hidden;
    position: relative;
    width: min(980px, 100%);
    z-index: 1;
}

/* P4 (a) — HEIGHT FIRST, at stacked/phone widths. The internal scroll chain below
   (body overflow:hidden -> per-page scrollport -> per-pane scrollport -> pinned
   composer) needs a DEFINITE height to divide up; without one, "only the messages
   scroll" degrades into "nothing scrolls".
   The >= 1101px block further down already solves this by making the overlay
   viewport-fixed and the popover height:100% of it. The SAME two moves are made
   here for the whole <= 1100px range, and the query is deliberately the exact
   complement of (min-width: 1101px) so the two geometries can never both apply
   (a short-but-wide desktop window keeps the desktop insets; a landscape phone,
   844x390, is <= 1100px and lands here).
   WHY NOT the sibling-overlay idiom's literal shape - height: calc(100dvh - 2 *
   var(--portal-overlay-padding)) (.template-editor-popover / .report-viewer-popover
   / .my-time-popover): that arithmetic silently disagrees with the overlay's own
   padding the moment the padding is max()'d against a safe-area inset (below), and
   it hard-codes a viewport unit the shell already tokenises. height:100% of a
   fixed inset:0 overlay is the same number, is padding-proof, and mirrors the
   desktop chain declaration-for-declaration.
   WHY NOT --portal-page-min-height / --portal-overlay-height /
   --portal-popover-max-height: --portal-page-min-height subtracts only the FIXED
   desktop chrome (--portal-topbar-height is 0rem) and never the extra auto-height
   row the shell grows when it stacks, so .portal-page ends up TALLER than the
   visible content box and a composer pinned to the bottom of that box lands below
   the fold.
   WHY the overlay must go fixed rather than stay position:absolute: the base rule
   anchors it to .portal-page, and .portal-page is NOT the viewport on either mount
   route. On /admin/cases .portal-page-has-overlay clamps the page to
   --portal-page-min-height (over-measured, see above) so a viewport-height popover
   would hang below the fold; on /admin/kanban there is no page-overlay class at all
   (AdminKanban mounts the popout itself) and the page is sized by the board's flex
   chain, so the popover would overflow a box unrelated to the screen. Fixed +
   inset:0 makes the popout a full-screen dialog on both routes - which is also what
   R4 asks for - and it is exactly what .portal-settings-overlay already does.
   z-index 30 (set above) intentionally covers the z-14 phone bottom bar: an open
   overlay must cover the bar (see the .portal-mobile-bar comment). */
@media (max-width: 1100px) {
    .case-detail-overlay {
        /* stretch, not start: the popover takes the whole grid area. */
        align-items: stretch;
        inset: 0;
        min-height: 0;
        overflow: hidden;
        /* Notch / home-indicator clearance. viewport-fit=cover means inset:0 runs
           under the system chrome, and the 0.6rem phone gutter is smaller than a
           notch, so each side takes whichever is larger. */
        padding: max(var(--portal-overlay-padding), var(--portal-safe-top))
                 max(var(--portal-overlay-padding), var(--portal-safe-right))
                 max(var(--portal-overlay-padding), var(--portal-safe-bottom))
                 max(var(--portal-overlay-padding), var(--portal-safe-left));
        position: fixed;
    }

    /* Beats the base max-height: var(--portal-popover-max-height) on source order
       at equal specificity (that rule sits ~3700 lines earlier). */
    .case-detail-popover {
        height: 100%;
        max-height: 100%;
    }
}

.case-detail-popover > .case-detail-panel {
    max-height: inherit;
    width: 100%;
}

.case-detail-popover .case-detail-panel > .portal-card-header {
    align-items: start;
    display: grid;
    gap: 0.55rem 1rem;
    grid-template-areas: "heading right";
    grid-template-columns: minmax(0, 1fr) auto;
    /* Symmetric padding on purpose: the account cluster is a narrow vertical
       stack in the topbar and the popout sits BELOW the topbar, so the header
       needs no corner clearance — a reserve here just left a dead right gutter. */
    padding: 1rem 1.1rem;
    position: relative;
}

.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-heading {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    grid-area: heading;
    min-width: 0;
}

.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-heading .portal-eyebrow,
.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-heading h2,
.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-heading h3 {
    max-width: 100%;
    overflow-wrap: anywhere;
}

.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-heading p {
    max-width: 100%;
}

.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-header-right {
    grid-area: right;
    justify-self: end;
}

.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-header-right .case-detail-header-badges {
    align-self: flex-end;
    position: static;
}

.case-detail-popover .case-detail-panel > .portal-card-header .case-detail-header-right .portal-actions {
    justify-content: flex-end;
    position: static;
}

@media (min-width: 1101px) {
    .portal-shell {
        --kanban-detail-edge-gutter: calc(0.35rem + 5px);
        --kanban-detail-shell-inline-start-padding: max(calc(var(--portal-shell-inline-padding) + var(--portal-shell-inline-extra)), var(--portal-safe-left));
        --kanban-detail-shell-inline-end-padding: max(calc(var(--portal-shell-inline-padding) + var(--portal-shell-inline-extra)), var(--portal-safe-right));
        --kanban-detail-sidebar-width: var(--portal-current-sidebar-width);
        --kanban-detail-shell-gap: var(--portal-shell-gap);
    }

    .case-detail-overlay {
        /* Bottom edge runs to just a gutter above the WINDOW bottom (not the
           content box's bottom shell padding) so the popout uses the full
           height; the top and sides keep tracking the content area. */
        bottom: var(--kanban-detail-edge-gutter);
        left: calc(var(--kanban-detail-shell-inline-start-padding) + var(--kanban-detail-sidebar-width) + var(--kanban-detail-shell-gap) + var(--kanban-detail-edge-gutter));
        min-height: 0;
        overflow: hidden;
        padding: clamp(1.1rem, 2vw, 1.6rem);
        padding-bottom: var(--kanban-detail-edge-gutter);
        position: fixed;
        right: calc(var(--kanban-detail-shell-inline-end-padding) + var(--kanban-detail-edge-gutter));
        top: calc(var(--portal-shell-padding-block-start) + var(--portal-topbar-height) + var(--kanban-detail-edge-gutter));
    }

    /* There is deliberately no .case-detail-overlay::before rule here any more. It used to
       re-assert position:absolute so the scrim stayed inside this fixed overlay instead of
       covering the viewport - but the scrim itself is gone (see the settings-only dim rule
       near the base .case-detail-overlay), so a pseudo-element with no content generates no
       box and the override had nothing left to position. */

    .case-detail-popover {
        height: 100%;
        max-height: 100%;
        max-width: none;
        width: 100%;
    }

    /* DO NOT re-add `.case-detail-body { overflow: hidden }` here. It lived in this
       block from 2026-06-30 to 2026-07-28 and was INERT the whole time: the base
       .case-detail-body rule (search "CASE TIME ENTRY" — it is a few hundred lines
       below this block) declares overflow at the SAME (0,1,0) specificity and LATER
       in source, so it won every media match. The base rule now carries
       overflow:hidden for all widths, which is what P4 (b) wanted anyway. */

    /* The two-column form. The stacked <= 1100px form is `auto minmax(0, 1fr)`
       (menu row + pages row); here menu and pages share ONE row, so a second
       track would leave the pages in an implicit auto row. */
    .case-command-sections {
        grid-template-rows: minmax(0, 1fr);
    }

    /* Side-by-side panes share ONE row. The stacked form uses grid-auto-rows so it
       works with either one pane (phone, pane switch) or two (tablet). */
    .case-command-split {
        grid-template-rows: minmax(0, 1fr);
    }
}

.case-detail-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.case-detail-panel > .portal-card-header {
    flex: 0 0 auto;
}

.case-detail-heading {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}

.case-detail-heading h2,
.case-detail-heading h3,
.case-detail-heading p {
    margin: 0;
}

.case-detail-heading h3 {
    color: var(--bls-ink);
    font-size: clamp(1.15rem, 2vw, 1.35rem);
    line-height: 1.2;
}

.case-detail-description {
    line-height: 1.45;
}

.case-detail-header-badges {
    align-self: flex-end;
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.1), rgba(123, 76, 193, 0.08));
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    flex-wrap: wrap;
    justify-content: flex-end;
    padding: 0.35rem;
}

/* Right-hand header column: action-button row on top, status/priority/severity pills + SLA
   badge row directly beneath, the whole stack right-aligned. Works for the admin
   (interactive pill menus + SlaBadge) and customer (static spans) variants alike. */
.case-detail-header-right {
    align-items: flex-end;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}

.case-detail-compact > .portal-card-header {
    align-items: start;
    display: grid;
    gap: 0.55rem 1rem;
    grid-template-areas: "heading right";
    grid-template-columns: minmax(0, 1fr) auto;
}

.case-detail-compact > .portal-card-header .case-detail-heading {
    grid-area: heading;
}

.case-detail-compact > .portal-card-header .case-detail-header-right {
    grid-area: right;
    justify-self: end;
}

/* Copy-context button feedback. The bundle is assembled server-side and has taken 2.7s on
   prod, so the four states (Idle / .busy / .copied / .failed set by CaseDetailPanel) each
   need to be visible at a glance - a 16px glyph swap on its own is not feedback, and an
   inert-looking button gets clicked twice, firing two round-trips. Every rule is scoped to
   .case-copy-context so the generic .copied / .failed words can't tint any other button;
   .btn is carried in the selector purely to out-specify .portal-shell.theme-light
   .btn-secondary (and its :hover / :focus-visible variants), which would otherwise repaint
   the tint away. */
.btn.case-copy-context.busy {
    cursor: progress;
    opacity: 0.65;
}

.btn.case-copy-context.busy svg[blazicon] {
    animation: case-copy-context-spin 0.9s linear infinite;
}

@keyframes case-copy-context-spin {
    to { transform: rotate(360deg); }
}

/* Success: a real green wash, not just the tick, so the confirmation registers in
   peripheral vision. --bls-success is retuned per theme (dark / light / high contrast). */
.btn.case-copy-context.copied {
    background: color-mix(in srgb, var(--bls-success) 20%, transparent);
    border-color: color-mix(in srgb, var(--bls-success) 45%, transparent);
    color: var(--bls-success);
}

/* Failure: empty bundle, refused clipboard write or a thrown export. Held longer than the
   success state by the handler, and tinted with the shared danger token. */
.btn.case-copy-context.failed {
    background: color-mix(in srgb, var(--bls-danger) 20%, transparent);
    border-color: color-mix(in srgb, var(--bls-danger) 48%, transparent);
    color: var(--bls-danger);
}

/* Hover and keyboard focus must not repaint the state tint back to the neutral
   secondary-button grey or the focus-state magenta gradient. */
.btn.case-copy-context.copied:hover,
.btn.case-copy-context.copied:focus-visible {
    background: color-mix(in srgb, var(--bls-success) 28%, transparent);
    color: var(--bls-success);
}

.btn.case-copy-context.failed:hover,
.btn.case-copy-context.failed:focus-visible {
    background: color-mix(in srgb, var(--bls-danger) 28%, transparent);
    color: var(--bls-danger);
}

/* Motion gate: the busy spin is decoration, the dimming carries the meaning - drop the
   rotation under both the OS preference and the app Reduce-motion toggle. */
@media (prefers-reduced-motion: reduce) {
    .btn.case-copy-context.busy svg[blazicon] {
        animation: none;
    }
}

.portal-shell.portal-a11y-reduce-motion .btn.case-copy-context.busy svg[blazicon] {
    animation: none;
}

/* Send-to-FLARE button feedback (case-detail header and kanban cards). The send is a
   Web -> API -> LAN round-trip that fast-fails at 15s when FLARE is unreachable - long
   enough that a disabled-but-static flame reads as the click having done nothing (the
   2026-08 prod incident). Same idiom as .case-copy-context: cursor + dim carry the
   meaning, the spinning LoaderCircle glyph is decoration. */
.btn.case-flare-send.busy {
    cursor: progress;
    opacity: 0.65;
}

.btn.case-flare-send.busy svg[blazicon] {
    animation: case-flare-send-spin 0.9s linear infinite;
}

@keyframes case-flare-send-spin {
    to { transform: rotate(360deg); }
}

/* Motion gate: drop the rotation under both the OS preference and the app toggle - the
   dimming above still says busy. */
@media (prefers-reduced-motion: reduce) {
    .btn.case-flare-send.busy svg[blazicon] {
        animation: none;
    }
}

.portal-shell.portal-a11y-reduce-motion .btn.case-flare-send.busy svg[blazicon] {
    animation: none;
}

.case-detail-compact > .portal-card-header .case-detail-header-right .portal-actions {
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* P4 (b) — the body is a FRAME, not a scroller, at every width (D9: "the same scroll
   effect of the messages and notes should happen on all sizes of the app"). It was
   overflow:auto here, and a duplicate overflow:hidden inside @media (min-width:1101px)
   was inert against it (same specificity, earlier in source) — so the popout has in
   fact been scrolling as one page at ALL widths, desktop included, and only looked
   right on desktop because the panes were ALSO scrollports. Flipping it here is what
   makes the per-page and per-pane scrollports below the only scrollports.
   Ships together with the per-page scrollport (.case-command-pages >
   .case-command-page) — on its own it would make Details / Attachments / Time
   unscrollable, because none of those pages owns a scrollport of its own. */
.case-detail-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
    overscroll-behavior: contain;
    overflow: hidden;
}

/*==== CASE TIME ENTRY ====*/

.case-time-entry-panel {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* Time tab layout: the logging widget is a narrower fixed-width column on the left;
   "Time entries" fills the rest and stretches to full height so its list uses the vertical
   space instead of leaving a gap below the logger. */
.case-time-layout {
    align-items: stretch;
    display: flex;
    flex: 1 1 auto;
    gap: 1rem;
    min-height: 0;
}

.case-time-layout-logger {
    flex: 0 0 min(340px, 40%);
    min-width: 0;
}

.case-time-layout-entries {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.case-time-layout-entries > .case-time-entry-panel {
    flex: 1 1 auto;
    min-height: 0;
}

.case-time-layout-entries .case-time-entry-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

@media (max-width: 1100px) {
    /* P4 / T6 — the Time tab is the worst stacked case: logger over entries inside a
       page that is now height-bounded, with .case-time-entry-list already an
       overflow-y:auto scrollport of its own. Left as flex:1 1 auto + min-height:0 the
       entries list gets whatever is left after the logger (which is most of a phone
       screen) and collapses to a few pixels.
       Stacked therefore sizes to CONTENT and lets the page be the single scrollport —
       so 20+ entries scroll as one list with the logger above them, rather than in a
       sliver. The min-height keeps the panel from reading as a sliver in the other
       direction too (0-1 entries). */
    .case-time-layout {
        flex: 0 0 auto;
        flex-direction: column;
        min-height: auto;
    }

    .case-time-layout-logger {
        flex: 0 0 auto;
    }

    .case-time-layout-entries {
        flex: 0 0 auto;
        min-height: 8rem;
    }

    .case-time-layout-entries .case-time-entry-list {
        min-height: 0;
        overflow-y: visible;
    }
}

.case-time-entry-panel-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.case-time-entry-panel-header h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
    color: var(--bls-ink);
}

.case-time-entry-panel-header p {
    margin: 0;
    font-size: 0.85rem;
    color: var(--bls-muted);
}

.case-time-entry-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.case-time-entry-item {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.875rem;
    border: 1px solid var(--bls-border);
    border-radius: 12px;
    background: var(--bls-surface-strong);
}

.case-time-entry-avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-weight: 700;
    background: var(--bls-surface);
    color: var(--bls-ink);
    flex-shrink: 0;
}

.case-time-entry-details {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.case-time-entry-details strong {
    font-size: 0.95rem;
}

.case-time-entry-details span {
    font-size: 0.85rem;
    color: var(--bls-muted);
}

.case-time-entry-meta {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.case-time-entry-action {
    padding: 1rem;
    border: 2px dashed var(--bls-border);
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.quick-time-log {
    display: flex;
    flex-direction: column;
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    overflow: hidden;
    width: 100%;
}

.quick-time-log-inline {
    margin-bottom: 1rem;
}

.quick-time-log-case {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 0.9rem;
    background: var(--bls-surface-strong);
    border-bottom: 1px solid var(--bls-border);
}

.quick-time-log-case-name {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    min-width: 0;
}

.quick-time-log-case-name span {
    font-size: 0.75rem;
    color: var(--bls-muted);
    white-space: nowrap;
}

.quick-time-log-case-name strong {
    font-size: 0.85rem;
    color: var(--bls-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.quick-time-log-account {
    font-size: 0.72rem;
    color: var(--bls-muted);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    padding: 0.15rem 0.6rem;
    white-space: nowrap;
}

.quick-time-log-body {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
    padding: 0.9rem;
}

.quick-time-log-label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--bls-primary-dark);
}

.quick-time-log-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.quick-time-log-durations {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
}

.quick-time-log-chip {
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    background: var(--bls-surface-strong);
    color: var(--bls-text);
    font-size: 0.78rem;
    padding: 0.35rem 0.75rem;
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}

.quick-time-log-durations .quick-time-log-chip {
    border-radius: var(--bls-radius-sm);
    padding: 0.35rem 0.2rem;
    text-align: center;
}

.quick-time-log-chip:hover {
    border-color: var(--bls-primary);
}

.quick-time-log-chip.active {
    border-color: var(--bls-primary);
    background: color-mix(in srgb, var(--bls-primary) 18%, transparent);
    color: var(--bls-ink);
}

.quick-time-log-note {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    padding: 0.5rem 0.65rem;
    font-size: 0.82rem;
}

.quick-time-log-note:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.15);
    outline: none;
}

/* Activity mode's Activity/Sub-type dropdowns - same box as the note input above. */
.quick-time-log-select {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    padding: 0.5rem 0.65rem;
    font-size: 0.82rem;
}

.quick-time-log-select:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.15);
    outline: none;
}

.quick-time-log-footer {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.quick-time-log-footer .btn {
    margin-left: auto;
}

.quick-time-log-billable {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.8rem;
    color: var(--bls-text);
}

/* "Post-date?" — a ghost button beside Billable that swaps itself for a date input
   (capped at today) when the entry belongs to an earlier day. Matches the time inputs
   in the Start – end row so the footer reads as one control strip. */
/* The footer pushes its .btn to the right; the post-date affordance is not that button -
   it stays beside Billable so only "Log time" is right-anchored. */
.quick-time-log-footer .quick-time-log-postdate {
    margin-left: 0;
}

.quick-time-log-date {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    padding: 0.35rem 0.5rem;
    font-size: 0.82rem;
}

.quick-time-log-date:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.15);
    outline: none;
}

/* Quiet consequence line: a post-dated entry still lands on the case and in My Time,
   it just never reaches the Flow timesheet (Flow takes same-working-day time only). */
.quick-time-log-postdate-hint {
    font-size: 0.78rem;
    color: var(--bls-muted);
}

/* Start – end row: two time inputs tied to the duration tiles (tiles move End,
   editing End recalculates the duration) plus the effective-duration summary. */
.quick-time-log-when {
    display: flex;
    align-items: center;
    gap: 0.45rem;
}

.quick-time-log-when input[type="time"] {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    padding: 0.35rem 0.5rem;
    font-size: 0.82rem;
}

.quick-time-log-when input[type="time"]:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.15);
    outline: none;
}

.quick-time-log-when-sep {
    color: var(--bls-muted);
}

.quick-time-log-when-summary {
    color: var(--bls-muted);
    font-size: 0.78rem;
}

.quick-time-log-error {
    font-size: 0.78rem;
    color: var(--bls-danger);
}

/* Soft warning: the case entry saved but the Flow timesheet copy was rejected
   (overlap, lock window, ...) — informational, not a failure of the log itself. */
.quick-time-log-flow-notice {
    font-size: 0.78rem;
    color: var(--bls-warning, #b45309);
}

/* Popover hosting: anchor wraps the trigger button; panel floats below-right. */
.quick-time-log-anchor {
    position: relative;
}

.quick-time-log-popover {
    position: absolute;
    top: calc(100% + 0.4rem);
    right: 0;
    z-index: 40;
    width: min(26rem, 90vw);
    box-shadow: var(--bls-shadow);
    border-radius: var(--bls-radius-md);
}

/* Kanban board host: the columns scroll-clip absolute popovers, so the board
   logs time in a centred fixed overlay instead. */
.quick-time-log-overlay {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-items: center;
    padding: 1rem;
    background: rgba(7, 5, 11, 0.55);
}

.quick-time-log-overlay-panel {
    width: min(26rem, 94vw);
    box-shadow: var(--bls-shadow);
    border-radius: var(--bls-radius-md);
}

/* Inside the overlay panel the header hosts dropdown pickers, so the section
   can't clip; round the header's own top corners instead. */
.quick-time-log-overlay-panel .quick-time-log {
    overflow: visible;
}

.quick-time-log-overlay-panel .quick-time-log-case {
    border-top-left-radius: calc(var(--bls-radius-md) - 1px);
    border-top-right-radius: calc(var(--bls-radius-md) - 1px);
}

/* Helper guide — one-time feature-discovery dialog (HelperGuide.razor). Fixed and
   centred like the quick-time-log overlay, but with a lighter backdrop: it's an
   invitation, not a task, so the page stays readable behind it. */
.helper-guide-backdrop {
    background: rgba(7, 5, 11, 0.45);
    inset: 0;
    position: fixed;
    /* Above every feature overlay (report popover is 80) - a guide can introduce
       the overlay it sits on. Still below toasts/loader (200+). */
    z-index: 90;
}

.helper-guide {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow);
    display: grid;
    gap: 0.65rem;
    left: 50%;
    max-width: min(26rem, 92vw);
    padding: 1.4rem 1.5rem 1.1rem;
    position: fixed;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 100%;
    z-index: 91;
}

.helper-guide-close {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    font-size: 1.15rem;
    height: 1.9rem;
    line-height: 1;
    position: absolute;
    right: 0.75rem;
    top: 0.75rem;
    width: 1.9rem;
}

.helper-guide-close:hover,
.helper-guide-close:focus-visible {
    border-color: rgba(196, 78, 175, 0.58);
    color: var(--bls-text);
    outline: none;
}

.helper-guide-progress {
    color: var(--bls-muted);
    font-size: 0.82rem;
    margin-inline-end: auto;
}

.helper-guide-badge {
    align-items: center;
    background: rgba(196, 78, 175, 0.16);
    border: 1px solid rgba(196, 78, 175, 0.45);
    border-radius: 999px;
    color: #e8a6dd;
    display: inline-flex;
    height: 2.4rem;
    justify-content: center;
    width: 2.4rem;
}

.portal-shell.theme-light .helper-guide-badge {
    color: #96308a;
}

.helper-guide h3 {
    font-size: 1.15rem;
    margin: 0;
}

.helper-guide-body {
    display: grid;
    gap: 0.6rem;
}

.helper-guide-body p {
    color: var(--bls-muted);
    margin: 0;
}

.helper-guide-inline-field input {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.7rem;
    color: var(--bls-text);
    padding: 0.6rem 0.75rem;
    width: 100%;
}

.helper-guide-inline-field input::placeholder {
    color: var(--bls-muted);
}

.helper-guide-error {
    color: #f0a5a5;
    font-size: 0.85rem;
    margin: 0;
}

.portal-shell.theme-light .helper-guide-error {
    color: #a03030;
}

.helper-guide-hint {
    font-size: 0.88rem;
}

.helper-guide-actions {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
    margin-top: 0.25rem;
}

.helper-guide-footnote {
    color: var(--bls-muted);
    font-size: 0.78rem;
    margin: 0;
    text-align: end;
}

/* Eyebrow label marking audience-specific guides (e.g. the customer welcome lesson). */
.helper-guide-eyebrow {
    color: #e8a6dd;
    font-size: 0.75rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.portal-shell.theme-light .helper-guide-eyebrow {
    color: #96308a;
}

/* Settings > Feature tips: one row per topic the user's roles let them see. */
.helper-guide-topics {
    display: grid;
    gap: 0.35rem;
    list-style: none;
    margin: 0;
    padding: 0 0.75rem 0.75rem;
}

.helper-guide-topics li {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.7rem;
    display: flex;
    gap: 0.6rem;
    padding: 0.5rem 0.75rem;
}

.helper-guide-topic-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.helper-guide-topic-status {
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    font-size: 0.75rem;
    padding: 0.08rem 0.55rem;
    white-space: nowrap;
}

.helper-guide-topic-done {
    background: rgba(34, 197, 94, 0.14);
    border-color: rgba(34, 197, 94, 0.5);
    color: #86efac;
}

.portal-shell.theme-light .helper-guide-topic-done {
    background: rgba(34, 197, 94, 0.12);
    color: #15803d;
}

/* Global Log time: manual / stopwatch mode toggle sits above the chosen surface. */
.global-time-log-stack {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.global-time-log-mode {
    display: flex;
    gap: 0.25rem;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow-soft);
    padding: 0.25rem;
}

.global-time-log-mode-btn {
    flex: 1 1 0;
    background: transparent;
    border: none;
    border-radius: var(--bls-radius-sm);
    color: var(--bls-muted);
    cursor: pointer;
    font: inherit;
    font-size: 0.82rem;
    font-weight: 700;
    padding: 0.5rem 0.6rem;
    transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
}

.global-time-log-mode-btn:hover {
    color: var(--bls-text);
}

.global-time-log-mode-btn.is-on {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.22), rgba(123, 76, 193, 0.12));
    box-shadow: inset 0 0 0 1px rgba(196, 78, 175, 0.4);
    color: var(--bls-ink);
}

/* When the count-up timer hosts the picker header (global Log time overlay), let the
   header sit flush to the card's top edge, matching the manual form's header. */
.case-timer-embedded-header {
    margin: -1rem -1.1rem 0.2rem;
}

.case-timer-embedded-header > .quick-time-log-case {
    border-top-left-radius: calc(var(--bls-radius-lg) - 1px);
    border-top-right-radius: calc(var(--bls-radius-lg) - 1px);
}

/* The timer is the last child in the overlay stack — drop its own bottom margin so
   both modes leave the same gap to the overlay edge. */
.global-time-log-stack > .case-timer {
    margin-bottom: 0;
}

/* ── Case timer (count-up stopwatch — analogue / digital) ─────────────────── */
.case-timer {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-lg);
    padding: 1rem 1.1rem;
    box-shadow: var(--bls-shadow-soft);
    margin-bottom: 0.9rem;
}

.case-timer-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }

.case-timer-eyebrow {
    font-size: 0.66rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--bls-primary-dark);
    display: flex;
    align-items: center;
    gap: 0.45rem;
}
.case-timer-eyebrow-sub { color: var(--bls-muted); }

.case-timer-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--bls-success);
    box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.18);
}
.case-timer-dot.is-paused { background: var(--bls-warning); box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.18); }

.case-timer-display { display: flex; background: var(--bls-bg); border: 1px solid var(--bls-border); border-radius: 9px; padding: 2px; }
.case-timer-toggle {
    border: 0; background: transparent; color: var(--bls-muted);
    font: inherit; font-size: 0.72rem; font-weight: 600;
    padding: 4px 10px; border-radius: 7px; cursor: pointer;
}
.case-timer-toggle.is-on { background: var(--bls-primary); color: #fff; }

.case-timer-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.case-timer-chip {
    font-size: 0.74rem; padding: 5px 10px; border-radius: 999px;
    border: 1px solid var(--bls-border); color: var(--bls-muted);
    background: var(--bls-bg); cursor: pointer;
}
.case-timer-chip.is-on { border-color: var(--bls-primary); color: var(--bls-ink); background: rgba(196, 78, 175, 0.16); }

.case-timer-field { display: flex; align-items: center; gap: 0.6rem; }
.case-timer-note {
    flex: 1; background: var(--bls-bg); border: 1px solid var(--bls-border);
    border-radius: 9px; color: var(--bls-text); padding: 7px 10px; font: inherit; font-size: 0.82rem;
}
.case-timer-bill { display: flex; align-items: center; gap: 0.4rem; font-size: 0.78rem; color: var(--bls-muted); white-space: nowrap; }
.case-timer-bill input { accent-color: var(--bls-primary); }

.case-timer-start {
    border: 0; background: var(--bls-primary); color: #fff;
    font: inherit; font-weight: 700; font-size: 0.92rem;
    padding: 0.7rem; border-radius: var(--bls-radius-md); cursor: pointer;
    box-shadow: 0 2px 10px rgba(196, 78, 175, 0.4);
}
.case-timer-start:hover:not(:disabled) { background: var(--bls-primary-dark); }
.case-timer-start:disabled { opacity: 0.55; cursor: default; box-shadow: none; }

.case-timer-stage { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; padding: 0.2rem 0; }
.case-timer-dial { width: 184px; height: 184px; }
.case-timer-hand { transform-box: view-box; transform-origin: 80px 80px; transition: transform 1s linear; }
.case-timer-subtime { font-variant-numeric: tabular-nums; color: var(--bls-muted); font-size: 0.9rem; }
.case-timer-digital {
    font-variant-numeric: tabular-nums; font-weight: 700; color: var(--bls-ink);
    font-size: 2.6rem; letter-spacing: 0.02em; line-height: 1; padding: 0.6rem 0;
}
.case-timer-meta { text-align: center; font-size: 0.74rem; color: var(--bls-muted); }

.case-timer-actions { display: flex; gap: 0.5rem; }
.case-timer-btn {
    flex: 1; border: 1px solid var(--bls-border); background: var(--bls-surface-strong);
    color: var(--bls-text); font: inherit; font-weight: 600; font-size: 0.82rem;
    padding: 0.55rem; border-radius: var(--bls-radius-sm); cursor: pointer;
}
.case-timer-btn:hover:not(:disabled) { border-color: var(--bls-primary); }
.case-timer-btn.is-primary { border-color: transparent; background: var(--bls-primary); color: #fff; }
.case-timer-btn.is-primary:hover:not(:disabled) { background: var(--bls-primary-dark); }
.case-timer-btn:disabled { opacity: 0.55; cursor: default; }

.case-timer-error { color: var(--bls-danger); font-size: 0.85rem; margin: 0; }

@media (prefers-reduced-motion: reduce) { .case-timer-hand { transition: none; } }

/* Inline edit/delete on logged time entries */
.case-time-entry-action {
    border: 1px solid var(--bls-border);
    background: transparent;
    color: var(--bls-muted);
    font: inherit;
    font-size: 0.72rem;
    padding: 3px 9px;
    border-radius: 999px;
    cursor: pointer;
}
.case-time-entry-action:hover:not(:disabled) { border-color: var(--bls-primary); color: var(--bls-text); }
.case-time-entry-action.is-danger:hover:not(:disabled) { border-color: var(--bls-danger); color: var(--bls-danger); }
.case-time-entry-action:disabled { opacity: 0.5; cursor: default; }

.case-time-edit { display: flex; flex-direction: column; gap: 0.6rem; width: 100%; }
.case-time-edit-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.7rem; }
.case-time-edit-field { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.72rem; color: var(--bls-muted); }
.case-time-edit-field .case-timer-note { width: 9rem; }

/* App-wide ErrorBoundary fallback panel (PortalLayout) — a thrown component keeps the
   circuit alive and shows this instead of bricking the whole page. */
.portal-screen-error {
    margin: 2rem auto;
    max-width: 36rem;
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-left: 3px solid var(--bls-danger);
    border-radius: var(--bls-radius-md);
    padding: 1.25rem 1.4rem;
    color: var(--bls-text);
}
.portal-screen-error h2 { margin: 0 0 0.4rem; font-size: 1.05rem; }
.portal-screen-error p { margin: 0 0 0.9rem; color: var(--bls-muted); font-size: 0.9rem; }

/* Pre-submission preliminary-checks dialog (accounts with PreliminaryChecksRequired). */
.prelim-overlay {
    position: fixed;
    inset: 0;
    z-index: 70;
    display: grid;
    place-items: center;
    padding: 1rem;
    background: rgba(7, 5, 11, 0.55);
}

.prelim-panel {
    width: min(34rem, 94vw);
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: var(--bls-shadow);
    border-radius: var(--bls-radius-md);
}

.prelim-body {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 0.25rem 0 0.5rem;
}

.prelim-question {
    border: none;
    margin: 0;
    padding: 0;
}

.prelim-question legend {
    font-weight: 600;
    color: var(--bls-text);
    padding: 0;
    margin-bottom: 0.6rem;
}

.prelim-choices {
    display: flex;
    gap: 0.6rem;
}

.prelim-choice {
    flex: 1;
    padding: 0.6rem 1rem;
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    background: var(--bls-surface-strong);
    color: var(--bls-text);
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.prelim-choice:hover {
    border-color: var(--bls-primary);
}

.prelim-choice-on {
    border-color: var(--bls-primary);
    background: var(--bls-primary);
    color: #fff;
}

.prelim-steps {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    font-weight: 600;
    color: var(--bls-text);
}

.prelim-steps textarea {
    width: 100%;
    resize: vertical;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    background: var(--bls-surface-strong);
    color: var(--bls-text);
    font: inherit;
    font-weight: 400;
}

.prelim-steps textarea:focus {
    outline: none;
    border-color: var(--bls-primary);
}

.prelim-hint {
    margin: 0;
    color: var(--bls-muted);
    font-size: 0.9rem;
}

.prelim-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.6rem;
    border-top: 1px solid var(--bls-border);
    padding-top: 0.9rem;
}

/* Omission-confirm dialog: the still-blank intake fields listed before submission.
   Kept as a real bulleted list (no flex/grid - they'd blockify the items and drop the
   markers) so screen readers and eyes both get "a list of what's missing". */
.omission-list {
    color: var(--bls-text);
    margin: 0.25rem 0 0.9rem;
    padding-left: 1.25rem;
}

.omission-list li {
    margin: 0.2rem 0;
}

/* Per-account settings row hosting the preliminary-checks toggle. */
.account-settings-row {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.75rem 0;
    border-top: 1px solid var(--bls-border);
    margin-bottom: 0.5rem;
}

/* Global add-time picker header: clickable client/case pills in the header strip. */
.quick-time-log-pick {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    font-size: 0.78rem;
    padding: 0.25rem 0.7rem;
    cursor: pointer;
    max-width: 100%;
}

.quick-time-log-pick:hover:not(:disabled) {
    border-color: var(--bls-primary);
}

/* Client -> case pill picker (planner todos + block editor): two pills side by side,
   menus anchored to their pill; the backdrop closes them on any outside click. */
.client-case-picker {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    min-width: 0;
}

.client-case-picker-step .quick-time-log-pick {
    max-width: 16rem;
}

.client-case-picker-step .quick-time-log-pick strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.client-case-picker-backdrop {
    position: fixed;
    inset: 0;
    z-index: 30;
    background: transparent;
}

.client-case-picker .case-pill-menu-options {
    position: absolute;
    top: calc(100% + 0.3rem);
    left: 0;
    z-index: 31;
    max-height: 16rem;
    overflow-y: auto;
}

.quick-time-log-pick:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.quick-time-log-pick strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.quick-time-log-picker {
    min-width: 0;
    flex: 1;
}

.quick-time-log-picker-options {
    left: 0;
    right: auto;
    max-height: 16rem;
    min-width: min(20rem, 80vw);
    overflow-y: auto;
}

.quick-time-log-picker-options-right {
    left: auto;
    right: 0;
}

.quick-time-log-picker-option-number {
    color: var(--bls-muted);
    font-size: 0.72rem;
    white-space: nowrap;
}

/* Two-step chooser (screen 1 of the global add-time overlay). */
.quick-time-log-choice-list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    max-height: 11rem;
    overflow-y: auto;
}

.quick-time-log-choice {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    width: 100%;
    text-align: left;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    font-size: 0.85rem;
    padding: 0.55rem 0.75rem;
    cursor: pointer;
}

.quick-time-log-choice:hover {
    border-color: var(--bls-primary);
}

.quick-time-log-choice.active {
    border-color: var(--bls-primary);
    background: color-mix(in srgb, var(--bls-primary) 18%, transparent);
    color: var(--bls-ink);
}

.quick-time-log-choice strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.quick-time-log-choice-hint {
    margin: 0;
    font-size: 0.82rem;
    color: var(--bls-muted);
}

/* ====================== */

.thread-panel h3,
.timeline-panel h3,
.attachment-panel h3,
.tool-card p {
    margin: 0;
}

.process-track {
    display: grid;
    gap: 0.75rem;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    padding: 1rem;
}

.process-stage {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: flex;
    gap: 0.6rem;
    padding: 0.75rem;
}

.process-stage span {
    align-items: center;
    background: rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    display: inline-flex;
    font-weight: 800;
    height: 1.75rem;
    justify-content: center;
    width: 1.75rem;
}

.process-active {
    border-color: var(--bls-primary);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.14);
}

.process-active span,
.process-complete span {
    background: var(--bls-primary);
    color: #ffffff;
}

.process-complete {
    border-color: rgba(21, 128, 61, 0.35);
}

.case-detail-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    padding: 1rem;
}

.detail-tile,
.tool-card,
.empty-state {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    padding: 0.85rem;
}

.detail-tile-wide {
    grid-column: span 2;
}

/* Compact Status-tab layout: small facts pack into as many columns as fit, prose rows
   (steps, description) span the full width below — so a long description no longer
   stretches every fact tile in its row into mostly-empty boxes. */
.case-context-grid {
    align-items: start;
    gap: 0.75rem;
    grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}

.case-context-grid .detail-tile {
    padding: 0.7rem 0.85rem;
}

.detail-tile-full {
    grid-column: 1 / -1;
}

/* Editable fact values: the value itself is the button, with a pencil affordance. */
.detail-tile-value-button {
    align-items: center;
    background: none;
    border: none;
    color: inherit;
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.4rem;
    padding: 0;
    text-align: start;
}

.detail-tile-value-button svg {
    color: var(--bls-muted);
    flex-shrink: 0;
    height: 0.85rem;
    width: 0.85rem;
}

.detail-tile-value-button:hover strong,
.detail-tile-value-button:focus-visible strong {
    text-decoration: underline;
}

.detail-tile-value-button:hover svg,
.detail-tile-value-button:focus-visible svg {
    color: var(--bls-primary);
}

/* Inline editor that unfolds inside the tile (contact/owner picker, environment text). */
.detail-tile-editor {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.55rem;
}

.detail-tile-editor input[type="text"] {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    font: inherit;
    padding: 0.45rem 0.6rem;
    width: 100%;
}

.detail-tile-editor-actions {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
}

.detail-tile-editor textarea,
.detail-tile-editor input[type="datetime-local"],
.detail-tile-editor input[type="number"] {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    font: inherit;
    padding: 0.45rem 0.6rem;
    width: 100%;
}

/* Prose section headings carry their edit pencil inline. */
.detail-tile-prose-heading {
    align-items: center;
    display: inline-flex;
    gap: 0.4rem;
}

.detail-tile-prose {
    align-content: start;
}

.detail-tile-prose p {
    color: var(--bls-text);
    line-height: 1.45;
    margin: 0.35rem 0 0;
    overflow-wrap: anywhere;
}

.detail-tile span,
.detail-tile small,
.attachment-panel p,
.attachment-item span,
.empty-state span,
.tool-card small,
.tool-form label,
.confirmation-panel span {
    color: var(--bls-muted);
}

.detail-tile {
    min-width: 0;
}

.detail-tile strong,
.detail-tile small {
    display: block;
    overflow-wrap: anywhere;
}

.case-detail-lanes {
    padding: 0 1rem 1rem;
}

.case-detail-tabs {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 0 1rem 1rem;
}

.case-detail-tab-list {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: flex;
    gap: 0.35rem;
    overflow-x: auto;
    padding: 0.35rem;
}

.case-detail-tab {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.75rem;
    color: var(--bls-muted);
    cursor: pointer;
    flex: 1 1 0;
    font-weight: 800;
    min-width: max-content;
    padding: 0.65rem 0.8rem;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease, color 140ms ease;
}

.case-detail-tab:hover,
.case-detail-tab:focus-visible,
.case-detail-tab.active {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    color: var(--bls-ink);
    outline: none;
}

.case-detail-tab-panel {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    padding: 1rem;
}

.case-detail-tab-panel > .attachment-panel {
    padding: 0;
}

/* P4 (c) — link 1 of the min-height ladder. Every box between the popover and the
   message list must be allowed to shrink below its content, or the "definite height"
   the popover was given never reaches the scrollports. min-height:0 is universal;
   only grid-template-rows differs by breakpoint (one row side-by-side, two rows
   stacked — see both media blocks). */
.case-command-sections {
    display: grid;
    flex: 1 1 auto;
    gap: 1rem;
    grid-template-columns: minmax(12rem, 15rem) minmax(0, 1fr);
    min-height: 0;
    padding: 1rem;
}

/* The rail is its own scrollport, at every width. P4 (b) turned .case-detail-body into a
   frame for ALL widths (it was overflow:auto at the base and the >= 1101px copy was
   inert), and .case-command-sections declares no overflow of its own, so a rail taller
   than its grid row now overflows into a clipped box with nothing to scroll: the last tab
   (Time for staff, Attachments for a customer) simply disappears. That is reachable
   today in a half-height desktop window or with devtools docked at the bottom - roughly
   below 570px of window height, where the >= 1101px row is minmax(0, 1fr) with a 0 floor
   and the rail is content-sized.
   Fixed AT THE RAIL rather than by releasing the scroll chain at short heights: the
   definite-height / per-pane-scroll / pinned-composer geometry is the whole point of P4
   and should survive a short window. Inert whenever the rail fits, which is every normal
   height - and scrollbars are suppressed app-wide, so nothing appears either way.
   The inline padding is not decoration: overflow-y computes overflow-x to auto as well,
   which would clip the 3px box-shadow ring that the ACTIVE tab always carries; the
   negative margin hands the space back so the column measures exactly as before. */
.case-command-menu {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    margin-inline: -3px;
    min-height: 0;
    min-width: 0;
    overflow-y: auto;
    padding-inline: 3px;
}

/* Matches the main sidebar / library collapse toggles: rounded-square button with the
   same circle-chevron icon, so every expand/minimise control reads identically. */
.case-command-collapse {
    align-items: center;
    align-self: flex-end;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    padding: 0.4rem 0.55rem;
}

/* Flat: no hover fill — focus only, for keyboard accessibility. */
.case-command-collapse:focus-visible {
    color: var(--bls-primary-dark);
    outline: none;
}

.case-command-collapse svg[blazicon] {
    height: 1.15rem;
    width: 1.15rem;
}

/* Collapsed: the menu column shrinks to icon width and the button labels hide,
   mirroring the main sidebar's icon-only mode. */
.case-command-sections-collapsed {
    grid-template-columns: auto minmax(0, 1fr);
}

.case-command-sections-collapsed .case-command-text {
    display: none;
}

.case-command-sections-collapsed .case-command-menu-item {
    justify-content: center;
    padding: 0.7rem;
}

.case-command-sections-collapsed .case-command-collapse {
    align-self: center;
}

.case-command-menu-item {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    gap: 0.6rem;
    padding: 0.7rem 0.75rem;
    position: relative;
    text-align: left;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease, color 140ms ease;
}

.case-command-menu-item > svg[blazicon] {
    flex: 0 0 auto;
    height: 1.1rem;
    width: 1.1rem;
}

.case-command-text {
    display: grid;
    gap: 0.18rem;
    min-width: 0;
}

.case-command-menu-item:hover,
.case-command-menu-item:focus-visible,
.case-command-menu-item.active {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    color: var(--bls-ink);
    outline: none;
}

.case-command-menu-item span,
.case-command-menu-item small {
    display: block;
}

.case-command-menu-item span {
    font-weight: 800;
}

.case-command-menu-item small {
    color: var(--bls-muted);
}

.case-command-menu-item .case-command-label {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    justify-content: space-between;
}

/* Unread indicators share the Blacklight pink, colour-on-transparent like the timer
   chips (rgba fill + themed text) rather than a solid block - and the same accent as
   the menu selection bar. --bls-primary-dark is the theme-aware text pair: bright
   pink on the dark theme, deep plum on light. */
.case-command-menu-item .unread-count-pill,
.unread-message-pill {
    align-items: center;
    background: rgba(196, 78, 175, 0.16);
    border: 1px solid rgba(196, 78, 175, 0.42);
    border-radius: 999px;
    color: var(--bls-primary-dark);
    display: inline-flex;
    font-size: 0.72rem;
    font-weight: 800;
    line-height: 1;
    padding: 0.22rem 0.45rem;
    white-space: nowrap;
}

/* WhatsApp-style numeric bubble. Shared by the case list, kanban cards and the
   collapsed command rail. Same pink-on-transparent idiom as the pills above. */
.unread-badge {
    align-items: center;
    background: rgba(196, 78, 175, 0.16);
    border: 1px solid rgba(196, 78, 175, 0.42);
    border-radius: 999px;
    color: var(--bls-primary-dark);
    display: inline-flex;
    font-size: 0.72rem;
    font-weight: 800;
    gap: 0.25rem;
    justify-content: center;
    line-height: 1;
    min-width: 1.35rem;
    padding: 0.24rem 0.45rem;
    white-space: nowrap;
}

/* The generic ".portal-table td span" rule display:blocks spans in cells (and mutes
   their colour), which stretched the badge into a full-width bar on the case list.
   Same specificity, later in source - this wins and keeps it a pill. */
.portal-table td .unread-badge {
    color: var(--bls-primary-dark);
    display: inline-flex;
    margin-top: 0.25rem;
}

.unread-badge svg[blazicon] {
    height: 0.85rem;
    width: 0.85rem;
}

/* Corner count on the Conversation tab icon — only when the rail is collapsed to
   icons (expanded mode already shows the "N unread" pill in the label). */
.case-command-unread-badge {
    display: none;
}

.case-command-sections-collapsed .case-command-unread-badge {
    align-items: center;
    background: rgba(196, 78, 175, 0.22);
    border: 1px solid rgba(196, 78, 175, 0.5);
    border-radius: 999px;
    color: var(--bls-primary-dark);
    display: inline-flex;
    font-size: 0.66rem;
    font-weight: 800;
    justify-content: center;
    line-height: 1;
    min-width: 1.1rem;
    padding: 0.18rem 0.3rem;
    position: absolute;
    right: 0.25rem;
    top: 0.25rem;
}

/* Staff-only "seen by customer" tick in the admin thread meta row. Scoped under
   .thread-message so it outranks the generic .thread-message span colour/size. */
.thread-message .thread-seen-tick {
    color: var(--bls-success);
    font-size: 0.72rem;
    font-weight: 700;
    white-space: nowrap;
}

/* Invisible bottom-of-thread marker for the read-receipt IntersectionObserver. */
.thread-end-sentinel {
    height: 1px;
}

/* CRM-style collapsed internal notes: clamp long bodies to a four-line preview. */
.timeline-detail-collapsed {
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    display: -webkit-box;
    overflow: hidden;
}

/* Staff-only per-entry read receipt derived from colleagues' case watermarks. Scoped
   under .timeline-item so the font-size outranks the generic .timeline-item span. */
.timeline-item .timeline-seen-by {
    color: var(--bls-muted);
    font-size: 0.72rem;
}

/* P4 (c) — link 2 of the ladder. */
.case-command-pages {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.case-command-page {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.9rem;
    min-height: 100%;
    padding: 0.95rem;
}

/* P4 (c) — link 3, and THE per-page scrollport. Reference pages (Details,
   Attachments, Time, the customer Progress tracker) own no scrollport of their own,
   so this is what keeps them reachable now that .case-detail-body is a frame.
   Universal since 2026-07-28 (was @media (min-width: 1101px) only).
   Selector keeps the .case-command-pages > .case-command-page (0,2,0) form it had in
   the desktop block: it must out-rank .case-command-page { min-height: 100% } on
   specificity, not merely on source order, so reordering these two blocks can never
   quietly re-inert the shrink-below-content guard. */
.case-command-pages > .case-command-page {
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Conversation pages never scroll themselves — the thread (or, for staff, each
   split pane) inside does, which keeps the composer docked to the bottom of the
   page. (0,2,0), same as the rule above, and later in source, so it wins. */
.case-command-page:has(> .thread-panel),
.case-command-page:has(> .case-command-split) {
    overflow: hidden;
}

/* Customer conversation reads like SMS: the thread fills the page and the message
   composer stays pinned to the bottom. Only matches the non-admin layout, where the
   thread and composer are direct children (admin composers sit inside split panes). */
.case-command-page > .thread-panel {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.case-command-page > .message-composer {
    margin-top: auto;
}

.case-command-page-header h3,
.case-command-page-header p {
    margin: 0;
}

.case-command-page-header p {
    color: var(--bls-muted);
    margin-top: 0.2rem;
}

.case-command-page > .process-track,
.case-command-page > .case-detail-grid,
.case-command-page .attachment-panel {
    padding: 0;
}

.case-detail-compact .case-command-page > .case-detail-grid {
    grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
}

/* Admin conversation view: customer chat on the left, internal timeline on
   the right, each with its own composer pinned to the bottom of the pane. */
.case-command-split {
    display: grid;
    flex: 1 1 auto;
    gap: 1rem;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* P4 (c) — link 4 of the ladder. */
    min-height: 0;
    transition: grid-template-columns 200ms ease;
}

/* Pane focus: one side expands to 2/3 while the other shrinks. The stacked-layout
   media block later in the file resets to a single column at the same specificity,
   so source order keeps mobile intact. */
.case-command-split-focus-left {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}

.case-command-split-focus-right {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
}

.thread-panel-header {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    justify-content: space-between;
}

/* P4 (d) / R1 + R4 — the STATIC pane head. The heading (and, for the internal pane,
   the timeline toolbar per D3) used to live INSIDE section.thread-panel /
   section.timeline-panel, i.e. inside the scrollport, and was held in place with
   position:sticky plus a frosted rgba(255,255,255,0.07) fill so scrolling content
   could not bleed through it. Daniel: "The titles here should NOT be visibly
   different from the rest of the container."
   Both the frost and the fill are gone, and the head is now a sibling of the
   scrolling section rather than its first child — so there is nothing to mask, and
   the head paints straight onto the pane's own surface. That is the ONLY exact
   colour match: copying the pane's own rgba(255,255,255,0.03) onto a DESCENDANT
   composites to ~0.059 over the pane and still reads as a band, and no opaque colour
   can match a wash over a frosted card at all. "No background" is also theme-proof,
   which matters because neither header ever had a .theme-light repaint (the old
   white-7% fill was invisible in the light theme — a bug that this deletes).
   Lifting it out is also what R4 asks for literally: "show the whole message pane and
   only allow the messages to scroll up and down on".
   Markup: CaseDetailPanel.razor renders .case-pane-head as the first child of each
   .case-command-split-pane. Deleted with the frost: the
   .portal-a11y-notransparency / .portal-a11y-contrast substitute pair, whose only
   job was to swap an opaque fill in for the dropped blur. */
.case-pane-head {
    display: grid;
    flex: 0 0 auto;
    gap: 0.5rem;
    /* Explicit shrinkable track. As a plain auto track the column would take a
       content-based minimum from .timeline-search (flex: 1 1 10rem; min-width: 8rem)
       and could push the head wider than the pane on a 390px screen. */
    grid-template-columns: minmax(0, 1fr);
}

/* Both pane headings now sit outside .thread-panel / .timeline-panel, so the
   ".thread-panel h3, .timeline-panel h3 { margin: 0 }" rule earlier in the file no
   longer reaches them. Re-pointed here. (That rule still serves the customer
   Progress tracker and the attachment panel, which keep their inline h3.) */
.thread-panel-header h3,
.timeline-panel-header h3 {
    margin: 0;
}

.case-command-split-pane {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    /* P4 (c) — link 5 of the ladder. */
    min-height: 0;
    min-width: 0;
    padding: 1rem;
}

.case-command-split-pane .case-command-form-grid {
    margin-top: auto;
}

/* P4 (c) — link 6, the innermost scrollport: one per pane, at every width (was
   @media (min-width: 1101px) only). overscroll-behavior stops a rubber-band at the
   end of a thread from scrolling the page behind the popout on touch. */
.case-command-split-pane .thread-panel,
.case-command-split-pane .timeline-panel {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Chat reads bottom-up like SMS: a short thread hugs the composer instead of
   floating at the top of the pane. This replaces the old
   ".thread-panel .thread-panel-header { margin-bottom: auto }" idiom, which stopped
   working the moment the header left the section — an element outside the pane's
   scrollport cannot absorb the pane's free space. An auto margin on the FIRST child
   inside the scrollport does the same job and, unlike justify-content: flex-end,
   degrades safely once the content overflows (negative free space resolves the auto
   margin to 0, so the top of a long thread stays reachable). */
.case-command-split-pane .thread-panel > *:first-child {
    margin-block-start: auto;
}

.case-command-form-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: 1fr;
}

.case-command-form-actions {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    justify-content: flex-end;
}

.case-command-form-actions .portal-settings-muted {
    flex: 1 1 auto;
    font-size: 0.82rem;
    min-width: 0;
}

.case-command-form-actions .btn {
    flex: 0 0 auto;
}

/* Reusable message composer (customer reply / internal note / customer message) with
   the circle-plus "add to message" menu. */
.message-composer {
    display: grid;
    gap: 0.55rem;
}

.case-command-split-pane .message-composer {
    margin-top: auto;
}

.message-composer-input {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    box-sizing: border-box;
    color: var(--bls-text);
    min-height: 5rem;
    padding: 0.65rem 0.75rem;
    resize: vertical;
    width: 100%;
}

.message-composer-input:focus-visible {
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    outline: none;
}

.message-composer-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.message-composer-chip {
    align-items: center;
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    display: inline-flex;
    gap: 0.4rem;
    max-width: 16rem;
    padding: 0.3rem 0.4rem 0.3rem 0.6rem;
}

.message-composer-chip > svg[blazicon] {
    flex: 0 0 auto;
    height: 0.85rem;
    width: 0.85rem;
}

.message-composer-chip-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.message-composer-chip-remove {
    align-items: center;
    background: transparent;
    border: 0;
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    padding: 0.15rem;
}

.message-composer-chip-remove:hover {
    color: var(--bls-text);
}

.message-composer-chip-remove svg[blazicon] {
    height: 0.85rem;
    width: 0.85rem;
}

.message-composer-link {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.message-composer-link input {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-text);
    flex: 1 1 12rem;
    min-height: 2.4rem;
    padding: 0.45rem 0.7rem;
}

.message-composer-add {
    flex: 0 0 auto;
    position: relative;
}

/* Click-catcher behind the add menu so clicking anywhere off it dismisses the popup
   (and prevents two composer menus showing at once). */
.message-composer-backdrop {
    background: transparent;
    inset: 0;
    position: fixed;
    z-index: 8;
}

/* Shared click-catcher for popover choice menus (filter dropdowns, status/priority
   pickers): clicking anywhere off the menu closes it without making a selection.
   Sits below the menus (filter z-index 8, pill menus z-index 12). */
.popover-backdrop {
    background: transparent;
    inset: 0;
    position: fixed;
    z-index: 7;
}

.portal-settings-close svg[blazicon] {
    height: 1.1rem;
    width: 1.1rem;
}

.message-composer-add-button {
    align-items: center;
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    height: 2.6rem;
    justify-content: center;
    width: 2.6rem;
}

.message-composer-add-button:hover,
.message-composer-add-button:focus-visible,
.message-composer-add-button.active {
    background: rgba(196, 78, 175, 0.18);
    border-color: rgba(196, 78, 175, 0.45);
    color: var(--bls-ink);
    outline: none;
}

.message-composer-add-button svg[blazicon] {
    height: 1.2rem;
    width: 1.2rem;
}

/* Opens upward — the composer sits at the bottom of the panel. */
.message-composer-menu {
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    bottom: calc(100% + 0.45rem);
    box-shadow: var(--bls-shadow-soft);
    display: grid;
    gap: 0.25rem;
    left: 0;
    min-width: 14rem;
    padding: 0.4rem;
    position: absolute;
    z-index: 9;
}

.message-composer-menu-item {
    align-items: center;
    background: transparent;
    border: 0;
    border-radius: 0.65rem;
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    font-weight: 700;
    gap: 0.6rem;
    padding: 0.55rem 0.65rem;
    text-align: left;
    width: 100%;
}

.message-composer-menu-item:hover,
.message-composer-menu-item:focus-visible {
    background: rgba(196, 78, 175, 0.16);
    outline: none;
}

.message-composer-menu-item svg[blazicon] {
    flex: 0 0 auto;
    height: 1rem;
    width: 1rem;
}

.message-composer-error {
    color: var(--bls-danger, #ff7a90);
}

.message-composer-status {
    flex: 1 1 auto;
    font-size: 0.82rem;
    min-width: 0;
}

.thread-panel,
.timeline-panel,
.attachment-panel,
.tool-form {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}

/* Timeline toolbar: search + kind chips + order toggle under the pane heading. */
.timeline-panel-header {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    justify-content: space-between;
}

.timeline-toolbar {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.timeline-search {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    display: inline-flex;
    flex: 1 1 10rem;
    gap: 0.4rem;
    min-width: 8rem;
    padding: 0.3rem 0.7rem;
}

.timeline-search svg[blazicon] {
    flex: 0 0 auto;
    height: 0.95rem;
    width: 0.95rem;
}

.timeline-search input {
    background: transparent;
    border: none;
    color: var(--bls-text);
    min-width: 0;
    outline: none;
    width: 100%;
}

.timeline-order-toggle {
    white-space: nowrap;
}

/* SMS-style rhythm: bubbles sit closer together than panel cards do. */
.thread-panel {
    gap: 0.5rem;
}

.thread-message,
.timeline-item {
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    padding: 0.85rem;
}

/* Optimistically-sent message/note: the bubble shows instantly, slightly dimmed until
   the Dataverse write confirms (same convention as .team-access-member-syncing). */
.case-message-pending {
    opacity: 0.65;
}

/* Follow-up actions under a FLARE triage note (draft reply / copy dev brief). Overrides
   the generic .timeline-item div flex-between so the buttons sit as a left-aligned row. */
.timeline-item .timeline-flare-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    justify-content: flex-start;
    margin-top: 0.6rem;
}

.timeline-flare-actions .btn {
    align-items: center;
    display: inline-flex;
    gap: 0.35rem;
}

.timeline-flare-actions svg {
    height: 0.85rem;
    width: 0.85rem;
}

.thread-message div,
.timeline-item div {
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
}

.thread-message-header {
    align-items: flex-start;
}

.thread-message-meta {
    align-items: center;
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    justify-content: flex-end;
}

.thread-message span,
.timeline-item span {
    color: var(--bls-muted);
    font-size: 0.82rem;
}

.thread-message .unread-message-pill {
    color: var(--bls-primary-dark);
    font-size: 0.72rem;
}

.thread-message p,
.timeline-item p {
    margin: 0.5rem 0 0;
}

/* Rendered message/note bodies (CaseMessageHtml): markdown-generated blocks need
   chat-scale rhythm - trim the generated <p> margins so bubbles stay tight. The
   explicit display:block overrides the generic ".thread-message div / .timeline-item
   div" flex rule earlier in the file, which would otherwise lay a multi-block body
   (text + list, or two paragraphs) out on one horizontal row. */
.thread-message .thread-body,
.timeline-item .timeline-detail {
    display: block;
    margin: 0.5rem 0 0;
}

/* WhatsApp-style attachment strip under a message: image thumbnails + file chips,
   wrapping. Sits below the body (or is the whole payload for an attachment-only
   comment). */
.thread-attachments {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.5rem;
}

.thread-attachment {
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    overflow: hidden;
    text-decoration: none;
    transition: border-color 140ms ease;
}

.thread-attachment:hover,
.thread-attachment:focus-visible {
    border-color: var(--bls-primary);
    outline: none;
}

/* Minified image batch: 2-column grid of uniform tiles; a single image gets one
   larger tile; the last tile past four carries the "+N" overlay. */
.thread-attachment-gallery {
    display: grid;
    flex: 1 1 100%;
    gap: 0.3rem;
    grid-template-columns: repeat(2, minmax(0, 9rem));
}

.thread-attachment-gallery-single {
    grid-template-columns: minmax(0, 16rem);
}

.thread-attachment-gallery .thread-attachment-image {
    position: relative;
}

.thread-attachment-image {
    display: block;
}

.thread-attachment-image img {
    aspect-ratio: 4 / 3;
    display: block;
    height: 100%;
    object-fit: cover;
    width: 100%;
}

.thread-attachment-gallery-single .thread-attachment-image img {
    aspect-ratio: auto;
    max-height: 14rem;
}

.thread-attachment-more {
    align-items: center;
    background: rgba(20, 14, 24, 0.6);
    color: #fff;
    display: flex;
    font-size: 1.1rem;
    font-weight: 700;
    inset: 0;
    justify-content: center;
    position: absolute;
}

.thread-attachment-file {
    align-items: center;
    background: rgba(196, 78, 175, 0.08);
    color: var(--bls-text);
    display: inline-flex;
    gap: 0.4rem;
    max-width: 100%;
    padding: 0.4rem 0.65rem;
}

.thread-attachment-file .thread-attachment-icon {
    color: var(--bls-primary-dark);
    display: inline-flex;
    flex: 0 0 auto;
}

.thread-attachment-name {
    font-size: 0.8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Attachments-tab tile becomes a whole-tile link when the host can serve downloads:
   thumbnail, name and row are all one click target. */
.attachment-item-link {
    color: inherit;
    cursor: pointer;
    text-decoration: none;
    transition: border-color 140ms ease;
}

.attachment-item-link:hover,
.attachment-item-link:focus-visible {
    border-color: var(--bls-primary);
    outline: none;
}

/* Re-win the collapse over the block override above (0,3,0 beats 0,2,0): a long
   internal note must still clamp to its four-line preview with the View more toggle. */
.timeline-item .timeline-detail.timeline-detail-collapsed {
    display: -webkit-box;
}

/* The View more/less toggle anchors bottom-right of the entry card; block + auto
   left margin because the article itself is not a flex container. */
.timeline-item .timeline-view-more {
    display: block;
    margin-left: auto;
    width: fit-content;
}

.thread-message .thread-body p,
.timeline-item .timeline-detail p {
    /* Markdig emits a literal "\n" after each <br/>; without this the inherited
       pre-wrap on ".thread-message p / .timeline-item p" would render that newline
       too, doubling every line break. overflow-wrap from that rule still applies. */
    margin: 0 0 0.45rem;
    white-space: normal;
}

.thread-message .thread-body p:last-child,
.timeline-item .timeline-detail p:last-child {
    margin-bottom: 0;
}

.thread-message .thread-body ul,
.thread-message .thread-body ol,
.timeline-item .timeline-detail ul,
.timeline-item .timeline-detail ol {
    margin: 0.35rem 0;
    padding-left: 1.2rem;
}

.thread-message .thread-body code,
.timeline-item .timeline-detail code {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 0.3rem;
    font-size: 0.85em;
    padding: 0.1rem 0.3rem;
}

/* Inside a code block the <pre> already carries the surface; the inline-code pill
   above would otherwise double up as a darker box per line. */
.thread-message .thread-body pre code,
.timeline-item .timeline-detail pre code {
    background: none;
    border-radius: 0;
    padding: 0;
}

.thread-message .thread-body pre,
.timeline-item .timeline-detail pre {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid var(--bls-border);
    border-radius: 0.5rem;
    overflow-x: auto;
    padding: 0.6rem;
}

/* Light theme flips the dark translucent code/pre fills to ink-tinted ones. */
.portal-shell.theme-light .thread-message .thread-body code,
.portal-shell.theme-light .timeline-item .timeline-detail code {
    background: color-mix(in srgb, var(--bls-text) 8%, transparent);
}

.portal-shell.theme-light .thread-message .thread-body pre,
.portal-shell.theme-light .timeline-item .timeline-detail pre {
    background: color-mix(in srgb, var(--bls-text) 5%, transparent);
}

/* Smart link chip: a long bare URL collapses to a host-labelled pill (full URL in
   the tooltip) instead of wrapping across four lines. */
.msg-link-chip {
    background: rgba(196, 78, 175, 0.14);
    border: 1px solid rgba(196, 78, 175, 0.35);
    border-radius: 999px;
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    padding: 0.08rem 0.55rem;
    text-overflow: ellipsis;
    vertical-align: bottom;
    white-space: nowrap;
}

/* The conversation reads as an SMS chat: the viewer's own messages sit on
   the right, everyone else's on the left (.thread-self / .thread-other are
   assigned relative to whoever is looking — customer or agent). Bubbles are
   borderless solid fills sized to their content, with small meta text. */
.thread-message {
    border-color: transparent;
    max-width: min(34rem, 84%);
    min-width: 0;
    padding: 0.6rem 0.9rem;
}

.thread-message .thread-message-header strong {
    color: var(--bls-muted);
    font-size: 0.74rem;
    font-weight: 700;
}

.thread-message span {
    font-size: 0.72rem;
}

.thread-message p {
    margin-top: 0.3rem;
}

.thread-message p,
.timeline-item p {
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

/* Clickable links inside chat bubbles / timeline entries (CaseMessageHtml). Long URLs
   must break rather than widen the bubble. */
.thread-message .thread-body a,
.timeline-item .timeline-detail a,
.thread-message p a,
.timeline-item p a {
    color: var(--bls-primary-dark, #e36ccc);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 0.12em;
    overflow-wrap: anywhere;
    word-break: break-word;
}

.thread-message .thread-body a:hover,
.timeline-item .timeline-detail a:hover,
.thread-message p a:hover,
.timeline-item p a:hover {
    text-decoration: none;
}

.thread-self {
    align-self: flex-end;
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.4), rgba(123, 76, 193, 0.32));
    border-radius: 1.15rem 1.15rem 0.3rem 1.15rem;
}

.thread-other {
    align-self: flex-start;
    background: rgba(255, 255, 255, 0.09);
    border-radius: 1.15rem 1.15rem 1.15rem 0.3rem;
}

.thread-unread {
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.08);
}

.thread-agent.thread-unread {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.12), rgba(255, 255, 255, 0.05));
}

.timeline-internal {
    border-style: dashed;
}

.admin-action-grid {
    border-top: 1px solid var(--bls-border);
    display: grid;
    gap: 1rem;
    grid-template-columns: 220px repeat(2, minmax(0, 1fr));
    padding: 1rem;
}

.admin-action-grid label,
.case-command-form-grid label,
.tool-form label {
    display: flex;
    flex-direction: column;
    font-size: 0.82rem;
    font-weight: 700;
    gap: 0.35rem;
}

.admin-action-grid select,
.admin-action-grid textarea,
.admin-action-grid input[type="text"],
.admin-action-grid input[type="datetime-local"],
.case-command-form-grid select,
.case-command-form-grid textarea,
.case-command-form-grid input[type="text"],
.case-command-form-grid input[type="datetime-local"],
.tool-form select,
.tool-form textarea,
.tool-form input[type="text"],
.tool-form input[type="datetime-local"],
.template-editor-field input,
.template-editor-field textarea {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-text);
    min-height: 2.75rem;
    padding: 0.65rem 0.75rem;
}

.admin-action-grid textarea,
.case-command-form-grid textarea,
.tool-form textarea {
    min-height: 5rem;
    resize: vertical;
}

.attachment-panel {
    padding: 1rem;
}

.attachment-panel-header {
    justify-content: space-between;
}

.attachment-list {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.attachment-item {
    align-items: center;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: grid;
    gap: 0.75rem;
    grid-template-columns: auto minmax(0, 1fr) auto;
    padding: 0.75rem;
}

.attachment-icon {
    align-items: center;
    background: rgba(196, 78, 175, 0.14);
    border-radius: 0.8rem;
    color: var(--bls-primary-dark);
    display: inline-flex;
    font-size: 0.72rem;
    font-weight: 800;
    height: 2.5rem;
    justify-content: center;
    width: 2.5rem;
}

.attachment-thumbnail {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--bls-border);
    border-radius: 0.8rem;
    height: 3.75rem;
    object-fit: cover;
    width: 4.75rem;
}

.attachment-drop-action {
    cursor: pointer;
    display: block;
}

.empty-state {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.empty-state-large {
    margin: 1rem;
    min-height: 12rem;
    justify-content: center;
}

.admin-tools-shell {
    display: grid;
    gap: 1rem;
    grid-template-columns: minmax(12rem, 15rem) minmax(0, 1fr);
    padding: 1rem;
}

.admin-tools-compact {
    grid-template-columns: 1fr;
}

.admin-tools-menu {
    align-self: start;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    min-width: 0;
}

.admin-tools-compact .admin-tools-menu {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(10.5rem, 100%), 1fr));
}

.admin-tools-menu-item {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    gap: 0.6rem;
    padding: 0.75rem;
    text-align: left;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease, color 140ms ease;
}

.admin-tools-menu-item > svg[blazicon] {
    flex: 0 0 auto;
    height: 1.1rem;
    width: 1.1rem;
}

/* Collapsed admin tools rail — icon-only, matching the case-detail menu. */
.admin-tools-shell-collapsed {
    grid-template-columns: auto minmax(0, 1fr);
}

.admin-tools-shell-collapsed .case-command-text {
    display: none;
}

.admin-tools-shell-collapsed .admin-tools-menu-item {
    justify-content: center;
    padding: 0.7rem;
}

.admin-tools-compact .case-command-collapse {
    display: none;
}

.admin-tools-menu-item:hover,
.admin-tools-menu-item:focus-visible,
.admin-tools-menu-item.active {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    color: var(--bls-ink);
    outline: none;
}

.admin-tools-menu-item span,
.admin-tools-menu-item small {
    display: block;
}

.admin-tools-menu-item span {
    font-weight: 800;
}

.admin-tools-menu-item small,
.admin-tools-page-header p,
.tool-card p {
    color: var(--bls-muted);
}

.admin-tools-pages {
    min-width: 0;
}

.admin-tools-page {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    min-height: 100%;
    padding: 0.95rem;
}

.admin-tools-page-header {
    align-items: flex-start;
    display: flex;
    gap: 0.8rem;
    justify-content: space-between;
}

.admin-tools-page-header h3,
.admin-tools-page-header p,
.tool-card p {
    margin: 0;
}

.admin-tools-page-header p {
    margin-top: 0.2rem;
}

.admin-tools-card-grid {
    display: grid;
    gap: 0.75rem;
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}

/* Shared panel-header treatment: a leading magenta icon chip beside the title +
   description, so every Admin-tools tab reads as one designed surface. The chip +
   text group sits left; any actions stay right via the header's space-between. */
.admin-tools-page-heading {
    align-items: flex-start;
    display: flex;
    gap: 0.7rem;
    min-width: 0;
}

.admin-tools-page-chip {
    align-items: center;
    background: rgba(196, 78, 175, 0.14);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-primary-dark);
    display: inline-flex;
    flex: 0 0 auto;
    height: 2.1rem;
    justify-content: center;
    width: 2.1rem;
}

.admin-tools-page-chip svg[blazicon] {
    height: 1.15rem;
    width: 1.15rem;
}

/* Quiet uppercase section labels within a panel (Run a report / Recent runs). */
.admin-tools-subhead {
    color: var(--bls-muted);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    margin: 0;
    text-transform: uppercase;
}

/* Who what where (Admin tools): the per-customer coverage directory. Reuses the
   reports-by-account card grid and list-card rows; these rules only space the two
   drill sections and keep the BA form inline. */
.www-section-header {
    align-items: flex-end;
    margin-top: 1.1rem;
}

/* Label + filter on one row. .list-filter-input is built for a full-width row of its
   own (width: 100% AND flex: 0 0 auto), so dropped beside a label it laid out at 100%
   of the header, refused to shrink and ran off the card's right edge - which in turn
   squeezed the label to its min-content width and broke it over three lines. Let the
   input flex into whatever the label leaves instead (min-width: 0 is already on the
   shared rule), and hold the label at its natural single-line width. */
.www-filter-header {
    align-items: center;
}

.www-filter-header .admin-tools-subhead {
    flex: 0 0 auto;
    white-space: nowrap;
}

.www-filter-header .list-filter-input {
    flex: 1 1 auto;
    max-width: 24rem;
    width: auto;
}

.www-section-header .portal-settings-muted {
    margin: 0.2rem 0 0;
}

.www-knowledge-row > div:first-child {
    min-width: 0;
}

.www-ba-form {
    align-items: flex-end;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-top: 0.8rem;
}

.www-ba-form label {
    flex: 1 1 16rem;
}

/* Report generation: tiles in a responsive grid replace the floating gradient
   pills — icon chip + name + period meta + a Generate affordance. */
.admin-report-grid {
    display: grid;
    gap: 0.7rem;
    grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
}

.admin-report-tile {
    align-items: center;
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.7rem;
    padding: 0.85rem 0.95rem;
    text-align: left;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
    width: 100%;
}

.admin-report-tile:hover:not(:disabled),
.admin-report-tile:focus-visible:not(:disabled) {
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.12), rgba(123, 76, 193, 0.06));
    border-color: rgba(196, 78, 175, 0.45);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    outline: none;
    transform: translateY(-1px);
}

.admin-report-tile:disabled {
    cursor: progress;
    opacity: 0.7;
}

.admin-report-tile-chip {
    align-items: center;
    background: rgba(167, 132, 224, 0.16);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-purple);
    display: inline-flex;
    flex: 0 0 auto;
    height: 2.3rem;
    justify-content: center;
    width: 2.3rem;
}

.admin-report-tile-chip svg[blazicon] {
    height: 1.2rem;
    width: 1.2rem;
}

.admin-report-tile-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.admin-report-tile-name {
    font-weight: 800;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-report-tile-meta {
    color: var(--bls-muted);
    font-size: 0.78rem;
}

.admin-report-tile-cta {
    align-items: center;
    color: var(--bls-primary-dark);
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 0.8rem;
    font-weight: 700;
    gap: 0.1rem;
}

.admin-report-tile-cta svg[blazicon] {
    height: 0.95rem;
    width: 0.95rem;
}

/* Tool-cards used as buttons (e.g. the API explorer section grid) get a gentle
   lift on hover so they read as actionable, matching the report tiles. */
button.tool-card {
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
}

button.tool-card:hover:not(:disabled),
button.tool-card:focus-visible:not(:disabled) {
    border-color: rgba(196, 78, 175, 0.4);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.1);
    outline: none;
    transform: translateY(-1px);
}

.admin-tools-form {
    padding: 0;
}

.admin-tools-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.8rem;
    align-items: start;
}

.admin-tools-form-wide {
    grid-column: 1 / -1;
}

.admin-tools-grid .btn,
.admin-tools-grid .health {
    grid-column: 1 / -1;
}

.admin-tools-check-inline {
    align-items: center;
    display: flex;
    flex-direction: row;
    gap: 0.5rem;
    font-weight: 600;
}

@media (max-width: 600px) {
    .admin-tools-grid {
        grid-template-columns: 1fr;
    }
}

/* Reply templates, part 1 - the tiles. A stack of open title+body editors made the
   templates impossible to compare, so each one is now a scannable tile built on the
   report-tile shell (chip + body + CTA) with a clamped preview of the message; editing
   moved into the popup in part 2. Only what differs from .admin-report-tile lives here. */
.template-tile-grid {
    grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
    margin: 0.6rem 0 1.4rem;
}

/* Taller than a report tile (three lines of preview), so pin the chip and the CTA level
   with the title instead of letting them float in the middle of the tile. */
.template-tile {
    align-items: flex-start;
}

.template-tile .admin-report-tile-cta {
    padding-top: 0.2rem;
}

/* The point of the grid: a readable slice of the body, clamped to three lines (same
   -webkit-box idiom as .timeline-detail-collapsed). */
.template-tile-preview {
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    color: var(--bls-muted);
    display: -webkit-box;
    font-size: 0.82rem;
    line-height: 1.35;
    overflow: hidden;
}

.template-tile-preview-empty {
    font-style: italic;
    opacity: 0.75;
}

/* Create tile: dashed and unfilled so it reads as "add", not as another template. */
.template-tile-new {
    align-items: center;
    background: transparent;
    border-style: dashed;
}

/* Reply templates, part 2 - the editor popup (chrome mirrors .report-viewer-overlay /
   .my-time-overlay, sized for a form rather than a dashboard): title field plus a tall
   message box in a scrolling body, with rank/delete and commit actions split across a
   fixed footer. Closed by the header X, a backdrop click, or Esc. */
.template-editor-overlay {
    align-items: center;
    display: grid;
    inset: 0;
    justify-items: center;
    overscroll-behavior: contain;
    padding: var(--portal-overlay-padding, 1.5rem);
    position: fixed;
    z-index: 80;
}

.template-editor-overlay::before {
    background: rgba(11, 7, 15, 0.66);
    content: "";
    inset: 0;
    pointer-events: none;
    position: absolute;
}

/* Lock background scroll while the editor is open. */
body:has(.template-editor-overlay) {
    overflow: hidden;
}

.template-editor-popover {
    background: var(--bls-bg, #0b070f);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 14px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    display: flex;
    flex-direction: column;
    max-height: calc(100dvh - (var(--portal-overlay-padding, 1.5rem) * 2));
    overflow: hidden;
    position: relative;
    width: min(740px, 92vw);
    z-index: 1;
}

.template-editor-popover-header {
    align-items: center;
    border-bottom: 1px solid var(--bls-border, #2a2a31);
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
    padding: 1rem 1.25rem;
}

.template-editor-popover-title {
    align-items: baseline;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    min-width: 0;
}

.template-editor-popover-title h3 {
    margin: 0;
}

.template-editor-popover-title small {
    color: var(--bls-muted, #aaa7ad);
}

/* Only Save persists - and a Move/Delete reloads the list, which drops the edit buffer -
   so flag unsaved text instead of letting it look committed. */
.template-editor-dirty {
    color: var(--bls-warning);
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
}

.template-editor-popover-close {
    background: transparent;
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 8px;
    color: var(--bls-text, #f3f0f5);
    cursor: pointer;
    display: inline-flex;
    padding: 0.4rem;
}

.template-editor-popover-close:hover {
    background: var(--bls-surface, #141417);
}

/* Only the body scrolls; the header and footer stay put. */
.template-editor-popover-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.85rem;
    min-height: 0;
    overflow-y: auto;
    padding: 1.25rem;
}

/* Field surface comes from the shared .tool-form input/textarea rule above. */
.template-editor-field {
    color: var(--bls-muted);
    display: flex;
    flex-direction: column;
    font-size: 0.82rem;
    font-weight: 700;
    gap: 0.35rem;
}

.template-editor-field input,
.template-editor-field textarea {
    color: var(--bls-text);
}

.template-editor-field input {
    font-weight: 700;
}

/* The "easier to read" fix: the message box gets real height and grows with the popover
   instead of sitting at three rows. */
.template-editor-field-body {
    flex: 1 1 auto;
    min-height: 0;
}

.template-editor-field-body textarea {
    min-height: 18rem;
    resize: vertical;
    width: 100%;
}

.template-editor-popover-footer {
    align-items: center;
    border-top: 1px solid var(--bls-border, #2a2a31);
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.85rem 1.25rem;
}

.template-editor-popover-rank,
.template-editor-popover-commit {
    align-items: center;
    display: flex;
    gap: 0.4rem;
}

/* Rank/delete sit left, commit right - as an auto margin rather than space-between, so
   create mode (which has no rank group) still keeps Cancel/Add on the right. */
.template-editor-popover-commit {
    margin-inline-start: auto;
}

/* Small screens / short viewports: fill the frame, like the other popovers. */
@media (max-width: 720px), (max-height: 600px) {
    .template-editor-popover {
        height: calc(100dvh - (var(--portal-overlay-padding, 0.75rem) * 2));
        width: calc(100vw - (var(--portal-overlay-padding, 0.75rem) * 2));
    }

    .template-editor-field-body textarea {
        min-height: 10rem;
    }
}

/* /admin/tools is a fixed app-shell pane like the kanban board: the page is pinned to
   the viewport, the card fills the remaining space exactly, and each tool panel scrolls
   its own overflow (API explorer, external call log). Scrollbars stay hidden like the
   rest of the shell. The compact dashboard embed is unaffected - these rules only apply
   under .portal-page-admin-tools. */
.portal-page-admin-tools {
    height: var(--portal-page-min-height);
    max-height: var(--portal-page-min-height);
    overflow: hidden;
}

.portal-page-admin-tools .admin-tools-card {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

.portal-page-admin-tools .admin-tools-shell {
    flex: 1 1 auto;
    grid-template-rows: minmax(0, 1fr);
    min-height: 0;
}

.portal-page-admin-tools .admin-tools-pages {
    min-height: 0;
}

.portal-page-admin-tools .admin-tools-page {
    max-height: 100%;
    overflow-y: auto;
    scrollbar-width: none;
}

.portal-page-admin-tools .admin-tools-page::-webkit-scrollbar {
    display: none;
}

/* API explorer response: always a substantial, self-scrolling block so the result is
   readable without hunting; long bodies scroll inside it. The copy icon button sits in
   the top-right corner and only appears while hovering (or keyboard-focused). */
.admin-tools-response {
    position: relative;
}

.admin-tools-response-copy {
    align-items: center;
    background: rgba(20, 12, 28, 0.88);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    font-size: 1rem;
    justify-content: center;
    opacity: 0;
    padding: 0.4rem;
    position: absolute;
    right: 0.5rem;
    top: 0.5rem;
    transition: opacity 120ms ease, border-color 120ms ease;
}

.admin-tools-response:hover .admin-tools-response-copy,
.admin-tools-response-copy:focus-visible,
.admin-tools-response-copy.copied {
    opacity: 1;
}

.admin-tools-response-copy:hover,
.admin-tools-response-copy:focus-visible {
    border-color: rgba(196, 78, 175, 0.42);
}

.admin-tools-response-body {
    background: rgba(8, 5, 12, 0.45);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    font-size: 0.8rem;
    margin: 0;
    min-height: 16rem;
    max-height: 60vh;
    overflow: auto;
    padding: 0.75rem;
    scrollbar-width: thin;
    white-space: pre-wrap;
    word-break: break-word;
}

.list-card-actions {
    justify-content: flex-end;
}

.tool-card {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.tool-form {
    padding: 1rem;
}

.library-shell {
    container-type: inline-size;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

.library-shell > .portal-card-header > div:first-child {
    min-width: 0;
}

/* The library search is a persistent bar in the card header (the shared pill above),
   sized here rather than in that shared block: the placeholder is long, and the bar
   shares the header row with the title block, so it takes a definite width and is
   allowed to give it back when the card gets narrow (the shared rule pins the case
   board's copy at flex: 0 0 auto). */
.library-search {
    flex: 0 1 auto;
}

.library-search input {
    min-width: 0;
    width: 16rem;
}

.library-layout {
    display: grid;
    grid-template-columns: minmax(14rem, 0.24fr) minmax(0, 1fr);
    min-height: clamp(20rem, var(--portal-page-min-height), 42rem);
    min-width: 0;
    transition: grid-template-columns 180ms ease;
}

.library-layout-sidebar-collapsed {
    grid-template-columns: 3.75rem minmax(0, 1fr);
}

.library-sidebar {
    background: var(--bls-surface-strong);
    border-right: 1px solid var(--bls-border);
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    min-width: 0;
    overflow: hidden;
    padding: 1rem;
    position: relative;
    /* Sit above the collapsed books rail (z-index 30) so the sidebar's own
       hover flyouts/tooltips overlay it instead of being painted under it. */
    z-index: 40;
}

.library-sidebar-collapsed {
    align-items: center;
    gap: 0.75rem;
    overflow: visible;
    padding: 0.75rem 0.55rem;
}

.library-books-collapsed .library-books-content {
    display: none;
}

.library-books-mini {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    margin-top: 0.35rem;
    width: 100%;
}

.library-books-mini-group {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    width: 100%;
}

.library-books-mini-group + .library-books-mini-group {
    border-top: 1px dashed rgba(255, 255, 255, 0.08);
    padding-top: 0.55rem;
}

.library-books-mini-book {
    align-items: center;
    background: rgba(196, 78, 175, 0.18);
    border: 1px solid rgba(196, 78, 175, 0.4);
    border-radius: 0.7rem;
    color: var(--bls-ink);
    cursor: pointer;
    display: inline-flex;
    height: 2.35rem;
    justify-content: center;
    padding: 0;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms ease;
    width: 2.35rem;
}

.library-books-mini-book svg[blazicon] {
    height: 1.15rem;
    width: 1.15rem;
}

.library-books-mini-book:hover,
.library-books-mini-book:focus-visible {
    outline: none;
    transform: translateY(-1px);
}

.library-books-mini-book.library-shelf-book-primary {
    background: rgba(196, 78, 175, 0.18);
    border-color: rgba(196, 78, 175, 0.4);
}

.library-books-mini-book.library-shelf-book-primary:hover,
.library-books-mini-book.library-shelf-book-primary:focus-visible,
.library-books-mini-book.library-shelf-book-primary.active {
    background: rgba(196, 78, 175, 0.32);
    border-color: rgba(196, 78, 175, 0.6);
}

.library-books-mini-book.library-shelf-book-warning {
    background: rgba(245, 158, 11, 0.18);
    border-color: rgba(245, 158, 11, 0.42);
}

.library-books-mini-book.library-shelf-book-warning:hover,
.library-books-mini-book.library-shelf-book-warning:focus-visible,
.library-books-mini-book.library-shelf-book-warning.active {
    background: rgba(245, 158, 11, 0.32);
    border-color: rgba(245, 158, 11, 0.62);
}

.library-books-mini-book.library-shelf-book-success {
    background: rgba(34, 197, 94, 0.18);
    border-color: rgba(34, 197, 94, 0.42);
}

.library-books-mini-book.library-shelf-book-success:hover,
.library-books-mini-book.library-shelf-book-success:focus-visible,
.library-books-mini-book.library-shelf-book-success.active {
    background: rgba(34, 197, 94, 0.32);
    border-color: rgba(34, 197, 94, 0.62);
}

.library-books-mini-page {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.55rem;
    color: var(--bls-ink);
    cursor: pointer;
    display: inline-flex;
    height: 2.15rem;
    justify-content: center;
    padding: 0;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 2.15rem;
}

.library-books-mini-page svg[blazicon] {
    height: 1.05rem;
    width: 1.05rem;
}

.library-books-mini-page:hover,
.library-books-mini-page:focus-visible {
    background: rgba(196, 78, 175, 0.14);
    border-color: rgba(196, 78, 175, 0.32);
    color: var(--bls-ink);
    outline: none;
}

.library-books-mini-page.active {
    background: rgba(196, 78, 175, 0.2);
    border-color: rgba(196, 78, 175, 0.45);
    color: var(--bls-ink);
}

.library-books-mini-book,
.library-books-mini-page {
    position: relative;
}

.library-mini-tooltip {
    display: none;
}

.library-books-mini-book:hover .library-mini-tooltip,
.library-books-mini-page:hover .library-mini-tooltip,
.library-add-mini:hover .library-mini-tooltip,
.library-add-mini:focus-visible .library-mini-tooltip {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.7rem;
    box-shadow: var(--bls-shadow-soft);
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    left: calc(100% + 0.55rem);
    padding: 0.55rem 0.75rem;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    white-space: nowrap;
    z-index: 50;
}

.library-mini-tooltip strong {
    color: var(--bls-text);
    font-size: 0.85rem;
}

.library-mini-tooltip span {
    color: var(--bls-muted);
    font-size: 0.78rem;
}

.library-mini-tooltip-pending {
    color: rgb(245, 184, 110);
    font-size: 0.72rem !important;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

/* Icon-only "+" buttons shown in the collapsed (mini) rails. */
.library-add-mini {
    align-items: center;
    background: rgba(255, 255, 255, 0.04);
    border: 1px dashed rgba(196, 78, 175, 0.5);
    border-radius: 0.7rem;
    color: var(--bls-ink);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    height: 2.35rem;
    justify-content: center;
    padding: 0;
    position: relative;
    transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
    width: 2.35rem;
}

.library-add-mini svg[blazicon] {
    height: 1.05rem;
    width: 1.05rem;
}

.library-add-mini:hover,
.library-add-mini:focus-visible {
    background: rgba(196, 78, 175, 0.2);
    border-color: rgba(196, 78, 175, 0.7);
    outline: none;
    transform: translateY(-1px);
}

.library-add-mini-page {
    border-radius: 0.55rem;
    height: 2.15rem;
    width: 2.15rem;
}

.library-add-mini-page svg[blazicon] {
    height: 0.95rem;
    width: 0.95rem;
}

/* Compact "has pending content" indicator used across every mini rail. */
.library-mini-pending-dot {
    background: rgb(245, 158, 11);
    border: 1px solid var(--bls-surface, #181221);
    border-radius: 999px;
    height: 0.55rem;
    pointer-events: none;
    position: absolute;
    right: 0.18rem;
    top: 0.18rem;
    width: 0.55rem;
}

/* The shelf icon needs to anchor its pending dot. */
.library-shelf-category-icon {
    position: relative;
}

/* The shelf-name line only adds context inside the collapsed flyout. */
.library-shelf-pending-title {
    display: none;
}

/* In the collapsed rail the full "N pending / Approve" block would overflow the
   narrow strip, so hide it inline and reveal it as a hover flyout instead. */
.library-sidebar-collapsed .library-shelf-pending {
    display: none;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-pending,
.library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-pending {
    align-items: flex-start;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.7rem;
    box-shadow: var(--bls-shadow-soft);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    left: calc(100% + 0.55rem);
    padding: 0.6rem 0.7rem;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    white-space: nowrap;
    z-index: 60;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-pending .library-shelf-pending-title,
.library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-pending .library-shelf-pending-title {
    color: var(--bls-text);
    display: block;
    font-size: 0.85rem;
    font-weight: 700;
}

/* When a shelf has the pending flyout, suppress the generic copy flyout so the
   two don't stack on top of each other. */
.library-sidebar-collapsed .library-nav-item-row:hover .library-nav-item:has(.library-shelf-pending) .library-shelf-copy,
.library-sidebar-collapsed .library-nav-item-row:focus-within .library-nav-item:has(.library-shelf-pending) .library-shelf-copy {
    display: none;
}

.library-sidebar-collapsed .library-sidebar-content {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    width: 100%;
}

.library-sidebar-collapsed .library-sidebar-content > section + section {
    display: none;
}

.library-sidebar-collapsed .library-nav-list {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    margin-top: 0;
    width: 100%;
}

.library-sidebar-collapsed .library-nav-item-row {
    position: relative;
    width: auto;
}

.library-sidebar-collapsed .library-nav-item {
    padding: 0;
}

.library-sidebar-collapsed .library-shelf-header {
    align-items: center;
    border-radius: 0.75rem;
    display: flex;
    justify-content: center;
    padding: 0.25rem;
    width: auto;
}

.library-sidebar-collapsed .library-shelf-heading {
    gap: 0;
    justify-content: center;
}

.library-sidebar-collapsed .library-shelf-copy {
    display: none;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-copy {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.7rem;
    box-shadow: var(--bls-shadow-soft);
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    left: calc(100% + 0.55rem);
    padding: 0.55rem 0.75rem;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    white-space: nowrap;
    z-index: 60;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-copy strong,
.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-copy span {
    overflow: visible;
    text-overflow: clip;
}

.library-sidebar-collapsed .library-shelf-books {
    display: none;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-books,
.library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-books {
    align-items: stretch;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow-soft);
    display: flex;
    flex-direction: column;
    gap: 0.32rem;
    left: calc(100% + 0.6rem);
    max-height: min(28rem, calc(var(--portal-viewport-height) - 2rem));
    min-height: auto;
    overflow-x: visible;
    overflow-y: auto;
    padding: 0.6rem;
    position: absolute;
    top: 0;
    width: auto;
    z-index: 14;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-books::after,
.library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-books::after {
    display: none;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-book,
.library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-book {
    flex: 0 0 auto;
    height: 5.5rem;
    width: 2.35rem;
}

.library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-empty,
.library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-empty {
    padding: 0.4rem 0.6rem;
    white-space: nowrap;
}

.library-sidebar-heading {
    align-items: center;
    display: flex;
    gap: 0.65rem;
    justify-content: space-between;
}

.library-sidebar-collapsed .library-sidebar-heading {
    justify-content: center;
    width: 100%;
}

.library-sidebar-toggle {
    align-items: center;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    height: 2.35rem;
    justify-content: center;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 2.35rem;
}

.library-sidebar-toggle .portal-toggle-icon {
    font-size: 1.15rem;
}

.library-section-heading {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    justify-content: space-between;
}

.library-section-heading h3 {
    margin: 0;
}

.library-section-toggle {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.6rem;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    height: 1.85rem;
    justify-content: center;
    padding: 0;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 1.85rem;
}

.library-section-toggle:hover,
.library-section-toggle:focus-visible {
    background: rgba(196, 78, 175, 0.14);
    border-color: rgba(196, 78, 175, 0.28);
    color: var(--bls-primary-dark);
}

.library-section-toggle .portal-toggle-icon {
    font-size: 1rem;
}

.library-sidebar h3,
.library-books h3,
.library-details h3,
.library-page-preview h3,
.library-page-preview h4 {
    margin: 0;
}

.library-section-title {
    align-items: center;
    display: inline-flex;
    gap: 0.55rem;
    min-width: 0;
}

.library-section-title-icon {
    align-items: center;
    color: var(--bls-ink);
    display: inline-flex;
    flex: 0 0 auto;
    font-size: 1.05rem;
    height: 1.15rem;
    justify-content: center;
    width: 1.15rem;
}

.library-section-title-icon svg[blazicon] {
    height: 1em;
    width: 1em;
}

.library-nav-list,
.library-sidebar-content,
.library-main,
.library-books,
.library-books-content,
.library-details,
.library-page-preview,
.library-page-body {
    display: flex;
    flex-direction: column;
}

.library-sidebar-content {
    gap: 1.25rem;
    min-width: 0;
}

.library-sidebar-content > section,
.library-books-content {
    min-width: 0;
}

.library-nav-list {
    gap: 0.55rem;
    margin-top: 0.7rem;
}

.library-nav-item,
.library-recent-item,
.library-book-card-main,
.library-page-link {
    background: transparent;
    border: 1px solid transparent;
    color: inherit;
    text-align: left;
}

.library-recent-item,
.library-book-card-main,
.library-page-link {
    cursor: pointer;
}

.library-nav-item-row {
    align-items: center;
    display: block;
    position: relative;
}

.library-nav-item-row .library-nav-item {
    min-width: 0;
    width: 100%;
}

.library-shelf-header {
    background: transparent;
    border: 0;
    color: inherit;
    cursor: pointer;
    display: block;
    font: inherit;
    padding: 0;
    text-align: left;
    width: 100%;
}

.library-nav-item,
.library-recent-item {
    border-radius: 0.85rem;
    display: flex;
    flex-direction: column;
    padding: 0.75rem;
}

.library-nav-item {
    gap: 0.75rem;
}

.library-recent-item {
    gap: 0.22rem;
}

.library-item-title {
    align-items: center;
    display: inline-flex;
    gap: 0.45rem;
    min-width: 0;
}

.library-item-title > span:last-child,
.library-page-link > span:last-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.library-item-icon {
    align-items: center;
    color: var(--bls-ink);
    display: inline-flex;
    flex: 0 0 auto;
    height: 1rem;
    justify-content: center;
    width: 1rem;
}

.library-item-icon svg[blazicon] {
    height: 1em;
    width: 1em;
}

.library-shelf-heading {
    align-items: flex-start;
    display: flex;
    gap: 0.55rem;
    min-width: 0;
}

.library-shelf-icon,
.library-shelf-initials {
    align-items: center;
    background: rgba(196, 78, 175, 0.14);
    border: 1px solid rgba(196, 78, 175, 0.28);
    border-radius: 0.75rem;
    color: var(--bls-primary-dark);
    display: inline-flex;
    flex: 0 0 auto;
    height: 2rem;
    justify-content: center;
    width: 2rem;
}

.library-shelf-initials {
    font-size: 0.72rem;
    font-weight: 900;
    letter-spacing: 0.04em;
    line-height: 1;
    text-transform: uppercase;
}

.library-shelf-icon svg[blazicon],
.library-shelf-book-icon svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.library-shelves-marker,
.library-books-marker {
    display: none;
}

.library-sidebar-collapsed .library-shelves-marker,
.library-books-collapsed .library-books-marker {
    align-items: center;
    color: var(--bls-muted);
    display: flex;
    font-size: 1.15rem;
    height: 1.85rem;
    justify-content: center;
    margin: 0 auto 0.25rem;
    width: 1.85rem;
}

.library-sidebar-collapsed .library-shelves-marker svg[blazicon],
.library-books-collapsed .library-books-marker svg[blazicon] {
    height: 1em;
    width: 1em;
}

.library-shelf-copy {
    display: flex;
    flex-direction: column;
    gap: 0.12rem;
    min-width: 0;
}

.library-shelf-copy strong,
.library-shelf-copy span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.library-shelf-copy span {
    color: var(--bls-muted);
    font-size: 0.78rem;
    font-weight: 800;
}

.library-shelf-books {
    align-items: flex-end;
    background:
        linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015)),
        rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0.9rem;
    display: flex;
    gap: 0.22rem;
    min-height: 7.25rem;
    overflow-x: auto;
    padding: 0.55rem 0.5rem 0.38rem;
    position: relative;
    scrollbar-width: thin;
}

.library-shelf-books::after {
    background: linear-gradient(90deg, rgba(196, 78, 175, 0.2), rgba(255, 255, 255, 0.14), rgba(196, 78, 175, 0.18));
    border-radius: 999px;
    bottom: 0.25rem;
    content: "";
    height: 0.22rem;
    left: 0.5rem;
    position: absolute;
    right: 0.5rem;
}

.library-shelf-book {
    align-items: center;
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 0.48rem 0.48rem 0.2rem 0.2rem;
    box-shadow: inset 0.45rem 0 0 rgba(255, 255, 255, 0.08), 0 0.45rem 1rem rgba(0, 0, 0, 0.2);
    color: #ffffff;
    cursor: pointer;
    display: flex;
    flex: 0 0 2.35rem;
    flex-direction: column;
    font: inherit;
    gap: 0.32rem;
    height: 6.35rem;
    justify-content: flex-start;
    overflow: hidden;
    padding: 0.42rem 0.22rem 0.55rem;
    position: relative;
    transition: transform 140ms ease, box-shadow 140ms ease;
    z-index: 1;
}

.library-shelf-book:hover {
    transform: translateY(-2px);
    box-shadow:
        inset 0.45rem 0 0 rgba(255, 255, 255, 0.16),
        0 0.6rem 1.25rem rgba(0, 0, 0, 0.28);
}

.library-shelf-book.active {
    border-color: rgba(255, 255, 255, 0.6);
    box-shadow:
        inset 0.45rem 0 0 rgba(255, 255, 255, 0.2),
        0 0.6rem 1.25rem rgba(0, 0, 0, 0.32),
        0 0 0 2px rgba(196, 78, 175, 0.55);
}

.library-shelf-book-primary {
    background: linear-gradient(165deg, var(--bls-primary), var(--bls-purple));
}

.library-shelf-book-warning {
    background: linear-gradient(165deg, #f59e0b, #c44eaf);
}

.library-shelf-book-success {
    background: linear-gradient(165deg, #38d996, #155e75);
}

.library-shelf-book-icon {
    align-items: center;
    color: rgba(255, 255, 255, 0.86);
    display: inline-flex;
    flex: 0 0 auto;
    justify-content: center;
}

.library-nav-item .library-shelf-book-title {
    color: #ffffff;
    font-size: 0.68rem;
    font-weight: 900;
    letter-spacing: 0.01em;
    line-height: 1;
    max-height: 4.65rem;
    overflow: hidden;
    text-overflow: ellipsis;
    transform: rotate(180deg);
    writing-mode: vertical-rl;
}

.library-shelf-empty {
    align-self: center;
    color: var(--bls-muted);
    font-size: 0.78rem;
    font-weight: 800;
    padding: 0.6rem 0;
    z-index: 1;
}

.library-customer-section {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    padding-bottom: 0.6rem;
    width: 100%;
}

.library-customer-section + .library-customer-section {
    border-top: 1px dashed rgba(255, 255, 255, 0.08);
    padding-top: 0.85rem;
}

.library-customer-section-header {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.7rem;
    color: inherit;
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.55rem;
    min-width: 0;
    padding: 0.35rem 0.45rem;
    text-align: left;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 100%;
}

.library-customer-section-header:hover,
.library-customer-section-header:focus-visible {
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(255, 255, 255, 0.1);
    outline: none;
}

.library-customer-section-toggle {
    align-items: center;
    color: var(--bls-muted);
    display: inline-flex;
    flex: 0 0 auto;
    height: 1.4rem;
    justify-content: center;
    margin-left: auto;
    width: 1.4rem;
}

.library-customer-section-toggle svg[blazicon] {
    height: 1em;
    width: 1em;
}

.library-customer-section-collapsed {
    gap: 0;
    padding-bottom: 0.25rem;
}

.library-customer-section-collapsed .library-customer-section-header {
    border-color: rgba(255, 255, 255, 0.06);
}

.library-customer-section-collapsed .library-customer-section-shelves {
    display: none;
}

.library-sidebar-collapsed .library-customer-section-collapsed {
    gap: 0.55rem;
    padding-bottom: 0.6rem;
}

.library-sidebar-collapsed .library-customer-section-collapsed .library-customer-section-shelves {
    display: flex;
}

.library-customer-section-copy {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.library-customer-section-copy strong {
    color: var(--bls-ink);
    font-size: 0.82rem;
    font-weight: 900;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.library-customer-section-copy span {
    color: var(--bls-muted);
    font-size: 0.72rem;
    font-weight: 700;
}

.library-customer-section-shelves {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.library-shelf-category-icon {
    align-items: center;
    background: rgba(196, 78, 175, 0.14);
    border: 1px solid rgba(196, 78, 175, 0.28);
    border-radius: 0.75rem;
    color: var(--bls-ink);
    display: inline-flex;
    flex: 0 0 auto;
    height: 2rem;
    justify-content: center;
    width: 2rem;
}

.library-shelf-category-icon svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.library-sidebar-collapsed .library-customer-section + .library-customer-section {
    border-top: 0;
    padding-top: 0;
}

.library-sidebar-collapsed .library-customer-section-header {
    background: transparent;
    border-color: transparent;
    cursor: default;
    justify-content: center;
    padding: 0;
    pointer-events: none;
}

.library-sidebar-collapsed .library-customer-section-copy,
.library-sidebar-collapsed .library-customer-section-toggle {
    display: none;
}

.library-nav-item:hover,
.library-nav-item.active,
.library-recent-item:hover,
.library-page-link:hover,
.library-page-link.active {
    background: rgba(196, 78, 175, 0.13);
    border-color: rgba(196, 78, 175, 0.3);
    color: var(--bls-ink);
}

.library-nav-item span,
.library-recent-item span,
.library-book-card small,
.library-page-preview p,
.library-file-preview span,
.library-details dt,
.library-toc-item {
    color: var(--bls-muted);
}

.library-nav-item .library-shelf-icon {
    color: var(--bls-primary-dark);
}

.library-nav-item .library-shelf-book-icon {
    color: rgba(255, 255, 255, 0.86);
}

.library-main {
    gap: 1rem;
    min-width: 0;
    padding: 1rem;
}

.library-breadcrumbs {
    align-items: center;
    color: var(--bls-muted);
    display: flex;
    flex-wrap: wrap;
    font-size: 0.82rem;
    gap: 0.45rem;
}

.library-breadcrumbs span::after {
    content: ">";
    margin-left: 0.45rem;
    opacity: 0.55;
}

.library-toolbar {
    align-items: center;
    display: flex;
    gap: 0.85rem;
    justify-content: flex-end;
}

.library-book-grid {
    display: grid;
    gap: 0.85rem;
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}

/* Featured variant: one header card for the book currently open. */
.library-book-grid-featured {
    grid-template-columns: 1fr;
}

.library-book-card {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    position: relative;
}

.library-book-card-main {
    align-items: flex-start;
    border-radius: inherit;
    display: flex;
    gap: 0.8rem;
    min-width: 0;
    padding: 0.9rem;
    width: 100%;
}

.library-book-card.active .library-book-card-main {
    padding-right: 3.15rem;
}

.library-book-card.active,
.library-book-card:hover {
    border-color: rgba(196, 78, 175, 0.46);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.1);
}

.library-book-card-main > span:last-child {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}

.library-info-button {
    align-items: center;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    height: 2rem;
    justify-content: center;
    transition: background 160ms ease, border-color 160ms ease, color 160ms ease, transform 160ms ease;
    width: 2rem;
}

.library-info-button svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.library-info-button:hover,
.library-info-button:focus-visible {
    background: rgba(196, 78, 175, 0.16);
    border-color: rgba(196, 78, 175, 0.4);
    color: var(--bls-ink);
    transform: translateY(-1px);
}

.library-book-info-button {
    position: absolute;
    right: 0.75rem;
    top: 0.75rem;
}

.library-book-cover,
.library-file-icon {
    align-items: center;
    border-radius: 0.9rem;
    color: #ffffff;
    display: inline-flex;
    flex: 0 0 auto;
    font-weight: 900;
    justify-content: center;
}

.library-book-cover {
    height: 3.4rem;
    width: 2.65rem;
}

.library-book-cover-primary {
    background: linear-gradient(160deg, var(--bls-primary), var(--bls-purple));
}

.library-book-cover-warning {
    background: linear-gradient(160deg, #f59e0b, #c44eaf);
}

.library-book-cover-success {
    background: linear-gradient(160deg, #38d996, #155e75);
}

.library-reader {
    display: grid;
    gap: 1rem;
    grid-template-columns: minmax(13rem, 0.24fr) minmax(0, 1fr);
    position: relative;
    transition: grid-template-columns 180ms ease;
}

.library-reader-books-collapsed {
    grid-template-columns: 3.75rem minmax(0, 1fr);
}

.library-books,
.library-details,
.library-page-preview {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    gap: 0.8rem;
    min-width: 0;
    overflow: hidden;
    padding: 1rem;
}

.library-books {
    position: relative;
}

/* Bound the library to the fixed app-shell so its panes scroll internally
   rather than scrolling the whole content pane. The shelves list and the books
   nav each scroll on their own; the three-pane reader (below) goes further. */
.library-shell {
    flex: 1 1 auto;
    min-height: 0;
}

.library-layout {
    flex: 1 1 auto;
    grid-auto-rows: minmax(0, 1fr);
    min-height: 0;
}

.library-sidebar {
    min-height: 0;
}

.library-sidebar-content {
    min-height: 0;
    overflow-y: auto;
}

.library-main {
    min-height: 0;
    overflow-y: auto;
}

/* Three-pane reader (same container breakpoint the reader uses to un-stack):
   the books nav and the page itself become independent scroll panes, so reading
   a long document never scrolls the navigation away. */
@container (min-width: 60.0625rem) {
    .library-main {
        overflow: hidden;
    }

    .library-reader {
        flex: 1 1 auto;
        grid-auto-rows: minmax(0, 1fr);
        min-height: 0;
    }

    .library-books,
    .library-page-preview {
        min-height: 0;
        overflow-y: auto;
    }
}

/* Phones: the shell can't be container-queried (it *is* the container), so opt
   out of the height-bounding by viewport here — let the library grow naturally
   and scroll with the content pane instead of squeezing into split panes. */
@media (max-width: 720px) {
    .library-shell {
        flex: 0 1 auto;
        min-height: auto;
    }

    .library-layout {
        grid-auto-rows: auto;
    }

    .library-main,
    .library-sidebar-content {
        overflow: visible;
    }
}

.library-books-collapsed {
    align-items: center;
    overflow: visible;
    padding: 0.75rem 0.55rem;
    /* Lift the collapsed rail into its own stacking context so its hover
       tooltips/flyouts paint above the solid-background preview pane beside it. */
    z-index: 30;
}

.library-books-heading {
    align-items: center;
    display: flex;
    gap: 0.65rem;
    justify-content: space-between;
}

.library-books-collapsed .library-books-heading {
    justify-content: center;
    width: 100%;
}

.library-chapter {
    border-left: 3px solid rgba(196, 78, 175, 0.48);
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    padding-left: 0.75rem;
}

.library-chapter > div {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.library-chapter span {
    color: var(--bls-muted);
    font-size: 0.8rem;
}

.library-book-link {
    font-weight: 800;
}

.library-book-chapter {
    border-left: 2px solid rgba(196, 78, 175, 0.28);
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-left: 1.4rem;
    padding-left: 0.6rem;
}

.library-book-chapter-heading {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.library-book-chapter-heading .library-item-title {
    flex: 1 1 auto;
    min-width: 0;
}

.library-book-chapter-heading strong {
    font-size: 0.85rem;
}

.library-chapter-meta {
    color: var(--bls-muted);
    font-size: 0.75rem;
    white-space: nowrap;
}

.library-chapter-rename {
    background: var(--bls-surface);
    border: 1px solid rgba(196, 78, 175, 0.4);
    border-radius: 0.5rem;
    color: var(--bls-ink);
    flex: 1 1 auto;
    font: inherit;
    font-size: 0.85rem;
    min-width: 0;
    padding: 0.2rem 0.45rem;
}

.library-page-nested {
    margin-left: 0;
}

/* Small icon actions on a chapter/page (reorder, rename, delete). Hidden until
   the row is hovered or active to keep the tree calm. */
.library-chapter-actions,
.library-page-actions {
    align-items: center;
    display: inline-flex;
    gap: 0.1rem;
    opacity: 0;
    transition: opacity 0.12s ease;
}

/* Page reorder arrows live in a slim gutter to the LEFT of the page, stacked
   vertically, so the page title keeps the full remaining width. The gutter is
   always reserved (opacity-only reveal) so the title never shifts on hover. */
.library-page-actions {
    flex-direction: column;
    flex: 0 0 auto;
    gap: 0;
}

.library-page-actions .library-icon-button {
    height: 0.95rem;
    padding: 0;
}

.library-page-actions .library-icon-button svg[blazicon] {
    height: 0.72rem;
    width: 0.72rem;
}

.library-book-chapter-heading:hover .library-chapter-actions,
.library-page-row:hover .library-page-actions,
.library-page-row.active .library-page-actions {
    opacity: 1;
}

.library-icon-button {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.4rem;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    padding: 0.15rem;
}

.library-icon-button svg {
    height: 0.9rem;
    width: 0.9rem;
}

.library-icon-button:hover:not(:disabled) {
    background: rgba(196, 78, 175, 0.13);
    color: var(--bls-ink);
}

.library-icon-button:disabled {
    cursor: default;
    opacity: 0.3;
}

.library-icon-button-danger:hover:not(:disabled) {
    background: rgba(220, 70, 70, 0.16);
    color: #ff8a8a;
}

/* A page row = the page link plus its reorder actions. */
.library-page-row {
    align-items: center;
    display: flex;
    gap: 0.25rem;
    margin-left: 0.4rem;
}

.library-page-row .library-page-link {
    flex: 1 1 auto;
    min-width: 0;
}

.library-page-row.active .library-page-link {
    background: rgba(196, 78, 175, 0.13);
    border-color: rgba(196, 78, 175, 0.3);
    color: var(--bls-ink);
}

.library-chapter-delete-confirm {
    background: rgba(220, 70, 70, 0.08);
    border: 1px solid rgba(220, 70, 70, 0.25);
    border-radius: 0.6rem;
    padding: 0.5rem 0.6rem;
}

/* New chapter / New page actions sit side by side under the chapter list. */
.library-add-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.library-add-actions .library-add-link {
    flex: 1 1 8rem;
}

.library-add-page-nested {
    margin-left: 0.4rem;
}

/* Contextual "+ add" rows at the end of each list (book list / page list),
   styled like a subtle dashed action so they read as "append here". */
.library-add-link {
    align-items: center;
    background: transparent;
    border: 1px dashed var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-muted);
    cursor: pointer;
    display: flex;
    font: inherit;
    font-weight: 700;
    gap: 0.45rem;
    padding: 0.5rem 0.65rem;
    text-align: left;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
    width: 100%;
}

.library-add-link:hover,
.library-add-link:focus-visible {
    background: rgba(196, 78, 175, 0.08);
    border-color: rgba(196, 78, 175, 0.45);
    color: var(--bls-ink);
    outline: none;
}

.library-add-icon {
    align-items: center;
    color: var(--bls-primary-dark);
    display: inline-flex;
    flex: 0 0 auto;
}

.library-add-icon svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.library-add-page {
    margin-left: 1.4rem;
    margin-top: 0.15rem;
}

.library-add-book {
    margin-top: 0.5rem;
}

.library-add-shelf {
    margin-top: 0.55rem;
}

.empty-state .library-add-link {
    margin-top: 0.75rem;
    width: auto;
}

.library-page-link {
    align-items: center;
    border-radius: 0.75rem;
    color: var(--bls-muted);
    display: inline-flex;
    gap: 0.45rem;
    font-weight: 800;
    padding: 0.55rem 0.65rem;
}

.library-page-header {
    align-items: flex-start;
    display: flex;
    gap: 0.85rem;
    justify-content: space-between;
}

.library-page-header > div {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}

.library-edit-hint {
    color: var(--bls-muted);
    font-size: 0.8rem;
    margin: 0.15rem 0 0;
}

.library-edit-hint code {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 4px;
    padding: 0.05rem 0.3rem;
}

/* Rendered reading view + editor share the same document typography. */
.library-doc {
    color: var(--bls-ink);
    display: flex;
    flex-direction: column;
    font-size: 0.95rem;
    line-height: 1.6;
    min-width: 0;
}

.library-doc-empty {
    color: var(--bls-muted);
    font-style: italic;
}

.library-doc h1 { font-size: 1.55rem; margin: 0.6rem 0 0.4rem; }
.library-doc h2 { font-size: 1.25rem; margin: 0.55rem 0 0.35rem; }
.library-doc h3 { font-size: 1.05rem; margin: 0.5rem 0 0.3rem; }
.library-doc p { margin: 0.35rem 0; }
.library-doc ul,
.library-doc ol { margin: 0.35rem 0; padding-left: 1.4rem; }
.library-doc li { margin: 0.15rem 0; }

.library-doc a {
    color: rgb(232, 121, 219);
    text-decoration: underline;
}

.library-doc code {
    background: rgba(255, 255, 255, 0.08);
    border-radius: 4px;
    font-size: 0.88em;
    padding: 0.05rem 0.35rem;
}

.library-doc pre {
    background: rgba(0, 0, 0, 0.28);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    overflow: auto;
    padding: 0.75rem 0.9rem;
}

.library-doc pre code {
    background: none;
    padding: 0;
}

.library-doc blockquote {
    border-left: 3px solid rgba(196, 78, 175, 0.55);
    color: var(--bls-muted);
    margin: 0.4rem 0;
    padding: 0.1rem 0 0.1rem 0.85rem;
}

.library-doc table {
    border-collapse: collapse;
    margin: 0.5rem 0;
    width: 100%;
}

.library-doc th,
.library-doc td {
    border: 1px solid var(--bls-border);
    padding: 0.35rem 0.6rem;
    text-align: left;
}

/* Block editor surface */
.library-doc-editor {
    counter-reset: lb-number;
    min-height: 8rem;
    position: relative;
}

.lb-block {
    border-radius: 6px;
    margin: 0.12rem 0;
    outline: none;
    padding: 0.12rem 0.25rem;
    white-space: pre-wrap;
    word-break: break-word;
}

.lb-block:focus {
    background: rgba(255, 255, 255, 0.04);
}

.lb-block[data-type="h1"] { font-size: 1.55rem; font-weight: 800; }
.lb-block[data-type="h2"] { font-size: 1.25rem; font-weight: 700; }
.lb-block[data-type="h3"] { font-size: 1.05rem; font-weight: 700; }

.lb-block[data-type="bullet"],
.lb-block[data-type="number"] {
    padding-left: 1.6rem;
    position: relative;
}

.lb-block[data-type="bullet"]::before {
    content: "•";
    left: 0.5rem;
    position: absolute;
}

.lb-block[data-type="number"] {
    counter-increment: lb-number;
}

.lb-block[data-type="number"]::before {
    content: counter(lb-number) ".";
    left: 0.25rem;
    position: absolute;
}

.lb-block[data-type="quote"] {
    border-left: 3px solid rgba(196, 78, 175, 0.55);
    color: var(--bls-muted);
    padding-left: 0.85rem;
}

.lb-block[data-type="code"] {
    background: rgba(0, 0, 0, 0.28);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    font-family: var(--bls-mono, ui-monospace, "Cascadia Code", Consolas, monospace);
    font-size: 0.85rem;
    padding: 0.6rem 0.8rem;
}

.lb-block[data-type="divider"] {
    cursor: default;
    padding: 0.35rem 0;
}

.lb-block[data-type="divider"] hr {
    border: none;
    border-top: 1px solid var(--bls-border);
    margin: 0;
}

.lb-block[data-type="p"]:empty::before,
.lb-block[data-type="h1"]:empty::before,
.lb-block[data-type="h2"]:empty::before,
.lb-block[data-type="h3"]:empty::before {
    color: var(--bls-muted);
    content: "Type '/' for blocks…";
    pointer-events: none;
}

.lb-slash-menu {
    background: var(--bls-surface, #181221);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4);
    display: none;
    max-height: 16rem;
    overflow: auto;
    padding: 0.3rem;
    position: absolute;
    width: 12rem;
    z-index: 20;
}

.lb-slash-item {
    background: none;
    border: none;
    border-radius: 6px;
    color: var(--bls-ink);
    cursor: pointer;
    display: block;
    font-size: 0.85rem;
    padding: 0.4rem 0.55rem;
    text-align: left;
    width: 100%;
}

.lb-slash-item:hover,
.lb-slash-item:focus-visible {
    background: rgba(196, 78, 175, 0.18);
}

/* Editable info panel */
.library-details-form {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
}

.library-details-readonly {
    border-top: 1px solid var(--bls-border);
    display: grid;
    gap: 0.25rem;
    margin: 0.25rem 0 0;
    padding-top: 0.6rem;
}

/* Delete affordances (admin only). */
.btn-danger {
    background: var(--bls-danger);
    border-color: transparent;
    color: #2a0810;
    font-weight: 700;
}

.btn-danger:hover,
.btn-danger:focus-visible {
    filter: brightness(1.06);
    outline: none;
}

.btn-danger-outline {
    background: rgba(251, 113, 133, 0.1);
    border-color: rgba(251, 113, 133, 0.45);
    color: var(--bls-danger);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.btn-danger-outline:hover,
.btn-danger-outline:focus-visible {
    background: rgba(251, 113, 133, 0.18);
    border-color: rgba(251, 113, 133, 0.7);
    outline: none;
}

.btn-danger-outline svg[blazicon],
.library-delete-shelf-link svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.library-details-danger {
    border-top: 1px solid rgba(251, 113, 133, 0.3);
    padding-top: 0.75rem;
}

.library-details-danger h3 {
    color: var(--bls-danger);
}

.library-delete-warning {
    color: var(--bls-muted);
    font-size: 0.85rem;
    margin: 0 0 0.1rem;
}

.library-delete-actions {
    display: flex;
    gap: 0.5rem;
}

.library-shelf-delete-confirm {
    background: rgba(251, 113, 133, 0.08);
    border: 1px solid rgba(251, 113, 133, 0.32);
    border-radius: var(--bls-radius-md);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.7rem;
}

.library-delete-shelf-link {
    color: var(--bls-danger);
}

.library-delete-shelf-link:hover,
.library-delete-shelf-link:focus-visible {
    background: rgba(251, 113, 133, 0.12);
    border-color: rgba(251, 113, 133, 0.4);
}

.library-details dl {
    display: grid;
    gap: 0.3rem;
    margin: 0;
}

.library-details {
    bottom: 0;
    box-shadow: -20px 0 45px rgba(0, 0, 0, 0.28);
    max-width: calc(100% - 1rem);
    opacity: 0;
    overscroll-behavior: contain;
    overflow: auto;
    pointer-events: none;
    position: absolute;
    right: 0;
    top: 0;
    transform: translateX(calc(100% + 1rem));
    transition: transform 200ms ease, opacity 160ms ease, visibility 160ms ease;
    visibility: hidden;
    width: min(24rem, 100%);
    z-index: 5;
}

.library-details-open {
    opacity: 1;
    pointer-events: auto;
    transform: translateX(0);
    visibility: visible;
}

.library-details-header {
    align-items: flex-start;
    border-bottom: 1px solid var(--bls-border);
    display: flex;
    gap: 0.85rem;
    justify-content: space-between;
    margin: -1rem -1rem 0;
    padding: 1rem;
}

.library-details-header p {
    margin: 0;
}

.library-details-close {
    align-items: center;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    font-weight: 900;
    height: 2rem;
    justify-content: center;
    line-height: 1;
    width: 2rem;
}

.library-details-close:hover,
.library-details-close:focus-visible {
    background: rgba(196, 78, 175, 0.16);
    border-color: rgba(196, 78, 175, 0.4);
    color: var(--bls-ink);
}

.library-details dd {
    margin: 0 0 0.45rem;
}

.library-details section {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.library-details .btn:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.library-toc-item {
    border-left: 2px solid rgba(255, 255, 255, 0.14);
    display: block;
    font-size: 0.85rem;
    padding-left: 0.55rem;
}

.library-toc-item strong,
.library-toc-item small {
    display: block;
}

/* Phone-width container fallback: the full single-column stack with the
   compact mobile shelf/book treatment. Tablet widths are handled by the
   max-width: 1100px viewport media query below. */
@container (max-width: 30rem) {
    .library-layout,
    .library-reader,
    .library-layout-sidebar-collapsed,
    .library-reader-books-collapsed {
        grid-template-columns: 1fr;
    }

    .library-sidebar,
    .library-sidebar-collapsed,
    .library-books,
    .library-books-collapsed {
        align-items: stretch;
        gap: 0.85rem;
        padding: 1rem;
    }

    .library-sidebar-collapsed .library-sidebar-heading,
    .library-books-collapsed .library-books-heading {
        justify-content: space-between;
    }

    .library-sidebar-collapsed .library-sidebar-heading::before {
        content: "Shelves";
        font-weight: 800;
    }

    .library-books-collapsed .library-books-heading::before {
        content: "Books";
        font-weight: 800;
    }

    .library-books-collapsed .library-books-mini {
        display: none;
    }

    .library-sidebar-collapsed .library-shelves-marker,
    .library-books-collapsed .library-books-marker {
        display: none;
    }

    .library-sidebar-collapsed .library-nav-list {
        align-items: stretch;
        display: flex;
        flex-direction: column;
        gap: 0.55rem;
    }

    .library-sidebar-collapsed .library-nav-item {
        padding: 0.75rem;
    }

    .library-sidebar-collapsed .library-shelf-header {
        display: block;
        justify-content: flex-start;
        padding: 0;
        width: 100%;
    }

    .library-sidebar-collapsed .library-shelf-heading {
        align-items: flex-start;
        gap: 0.55rem;
        justify-content: flex-start;
    }

    .library-sidebar-collapsed .library-customer-section-header {
        background: transparent;
        border: 1px solid transparent;
        cursor: pointer;
        justify-content: flex-start;
        padding: 0.35rem 0.45rem;
        pointer-events: auto;
    }

    .library-sidebar-collapsed .library-customer-section-header:hover,
    .library-sidebar-collapsed .library-customer-section-header:focus-visible {
        background: rgba(255, 255, 255, 0.05);
        border-color: rgba(255, 255, 255, 0.1);
        outline: none;
    }

    .library-sidebar-collapsed .library-customer-section-copy,
    .library-sidebar-collapsed .library-customer-section-toggle {
        display: flex;
    }

    .library-sidebar-collapsed .library-customer-section + .library-customer-section {
        border-top: 1px dashed rgba(255, 255, 255, 0.08);
        padding-top: 0.85rem;
    }

    .library-sidebar-collapsed .library-customer-section-collapsed .library-customer-section-shelves {
        display: none;
    }

    .library-sidebar-content,
    .library-sidebar-collapsed .library-sidebar-content {
        display: grid;
        gap: 0.85rem;
        grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
    }

    .library-sidebar-content .library-nav-list {
        margin-top: 0.55rem;
    }

    .library-books-content,
    .library-books-collapsed .library-books-content {
        display: grid;
        gap: 0.85rem;
        grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
    }

    .library-sidebar-collapsed .library-sidebar-content,
    .library-books-collapsed .library-books-content {
        background: transparent;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        left: auto;
        max-height: none;
        overflow: visible;
        padding: 0;
        position: static;
        top: auto;
        width: auto;
        z-index: auto;
    }

    .library-sidebar-collapsed .library-sidebar-content > section + section,
    .library-sidebar-collapsed .library-shelf-copy {
        display: revert;
    }

    .library-sidebar-collapsed .library-shelf-books,
    .library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-books,
    .library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-books {
        align-items: flex-end;
        background:
            linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015)),
            rgba(255, 255, 255, 0.03);
        border: 1px solid rgba(255, 255, 255, 0.08);
        border-radius: 0.9rem;
        box-shadow: none;
        display: flex;
        flex-direction: row;
        left: auto;
        max-height: none;
        min-height: 7.25rem;
        overflow-x: auto;
        overflow-y: visible;
        padding: 0.55rem 0.5rem 0.38rem;
        position: relative;
        top: auto;
        width: auto;
        z-index: auto;
    }

    .library-sidebar-collapsed .library-nav-item-row:hover .library-shelf-book,
    .library-sidebar-collapsed .library-nav-item-row:focus-within .library-shelf-book {
        height: 6.35rem;
    }

    .library-chapter {
        background: rgba(255, 255, 255, 0.04);
        border: 1px solid var(--bls-border);
        border-left: 3px solid rgba(196, 78, 175, 0.48);
        border-radius: var(--bls-radius-sm);
        padding: 0.75rem;
    }

    .library-page-link {
        width: 100%;
    }

    .library-page-preview {
        min-height: auto;
    }

    .library-page-preview .empty-state-large {
        min-height: 8rem;
    }
}

.confirmation-panel {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--bls-border);
    border-left: 4px solid var(--bls-primary);
    border-radius: var(--bls-radius-md);
    padding: 0.85rem;
}

.confirmation-panel strong,
.confirmation-panel span {
    display: block;
}

/* Result line under the intake actions, so Save draft / Review and confirm always
   show an outcome instead of appearing to do nothing. */
.intake-action-status {
    margin: 0.5rem 0 0;
    font-size: 0.875rem;
    font-weight: 600;
}

.intake-action-status-ok {
    color: var(--bls-success);
}

.intake-action-status-error {
    color: var(--bls-danger);
}

.assistant-popover-shell {
    bottom: max(1.5rem, var(--portal-safe-bottom));
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    max-width: calc(100vw - 2rem);
    position: fixed;
    right: max(1.5rem, var(--portal-safe-right));
    z-index: 60;
}

.assistant-popover {
    background: var(--bls-popover-solid);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 1.25rem;
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.42);
    display: flex;
    flex-direction: column;
    height: min(500px, max(16rem, calc(var(--portal-viewport-height) - 8rem - var(--portal-safe-top) - var(--portal-safe-bottom))));
    max-height: min(680px, max(16rem, calc(var(--portal-viewport-height) - 8rem - var(--portal-safe-top) - var(--portal-safe-bottom))));
    overscroll-behavior: contain;
    overflow: hidden;
    width: min(345px, calc(100vw - 2rem));
}

.assistant-popover-header {
    align-items: flex-start;
    background: linear-gradient(135deg, rgba(196, 78, 175, 0.22), rgba(255, 255, 255, 0.04));
    border-bottom: 1px solid var(--bls-border);
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    padding: 0.75rem 0.9rem;
}

.assistant-popover-header h3 {
    margin: 0.05rem 0 0;
}

.assistant-case-context {
    color: var(--bls-primary-dark);
    display: block;
    font-size: 0.78rem;
    font-weight: 800;
    margin-top: 0.1rem;
}

.assistant-thread {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 0.75rem;
    overscroll-behavior: contain;
    overflow: auto;
    padding: 1rem;
}

/* SMS-style chat thread (ChatThread component). Bubbles group into runs: facing
   corners flatten between same-sender neighbours and the tail only appears on the
   last bubble of a run, like a texting app. */
.chat-thread {
    gap: 0.16rem;
}

.chat-time-divider {
    align-self: center;
    color: var(--bls-muted);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    margin: 0.6rem 0 0.4rem;
    text-transform: uppercase;
}

.chat-bubble {
    animation: chat-bubble-in 0.18s ease-out;
    border-radius: 1.15rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    max-width: 82%;
    padding: 0.55rem 0.85rem;
    position: relative;
    width: fit-content;
}

.chat-bubble p {
    margin: 0;
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

.chat-bubble small {
    font-size: 0.74rem;
    font-weight: 700;
    opacity: 0.75;
}

.chat-bubble-assistant {
    align-self: flex-start;
    background: color-mix(in srgb, var(--bls-text) 11%, transparent);
    color: var(--bls-text);
}

.chat-bubble-user {
    align-self: flex-end;
    background: var(--bls-primary);
    color: #fff;
}

/* Grouping: flatten the corners that face a same-sender neighbour. */
.chat-bubble-assistant:not(.chat-run-start) {
    border-top-left-radius: 0.35rem;
}

.chat-bubble-assistant:not(.chat-run-end) {
    border-bottom-left-radius: 0.35rem;
}

.chat-bubble-user:not(.chat-run-start) {
    border-top-right-radius: 0.35rem;
}

.chat-bubble-user:not(.chat-run-end) {
    border-bottom-right-radius: 0.35rem;
}

/* Tail on the last bubble of a run: a small curved wedge butted against the bubble
   edge (no overlap, so translucent fills don't double up). */
.chat-bubble-assistant.chat-run-end {
    border-bottom-left-radius: 0.2rem;
}

.chat-bubble-user.chat-run-end {
    border-bottom-right-radius: 0.2rem;
}

.chat-bubble.chat-run-end::after {
    bottom: 0;
    content: "";
    height: 14px;
    position: absolute;
    width: 7px;
}

.chat-bubble-assistant.chat-run-end::after {
    background: color-mix(in srgb, var(--bls-text) 11%, transparent);
    clip-path: path("M7 0 L7 14 L0 14 C4.2 11.5 6.5 6.8 7 0 Z");
    left: -6px;
}

.chat-bubble-user.chat-run-end::after {
    background: var(--bls-primary);
    clip-path: path("M0 0 L0 14 L7 14 C2.8 11.5 0.5 6.8 0 0 Z");
    right: -6px;
}

.chat-typing {
    display: inline-flex;
    gap: 0.28rem;
    padding: 0.25rem 0.1rem;
}

.chat-typing span {
    animation: chat-typing-bounce 1.2s infinite ease-in-out;
    background: var(--bls-muted);
    border-radius: 50%;
    height: 0.42rem;
    width: 0.42rem;
}

.chat-typing span:nth-child(2) {
    animation-delay: 0.15s;
}

.chat-typing span:nth-child(3) {
    animation-delay: 0.3s;
}

.chat-caret {
    animation: chat-caret-blink 1s steps(1) infinite;
    background: currentColor;
    display: inline-block;
    height: 1em;
    margin-left: 0.15rem;
    vertical-align: text-bottom;
    width: 2px;
}

@keyframes chat-bubble-in {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.96);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes chat-typing-bounce {
    0%,
    60%,
    100% {
        opacity: 0.45;
        transform: translateY(0);
    }

    30% {
        opacity: 1;
        transform: translateY(-0.22rem);
    }
}

@keyframes chat-caret-blink {
    50% {
        opacity: 0;
    }
}

/* Respect the OS "reduce motion" setting for the customer-facing intake chat (ChatThread in
   GuidedCaseIntake): drop the bubble entrance and the infinite typing-dots bounce + streaming
   caret blink. The typing dots keep a static mid-opacity so they still read as "thinking". */
@media (prefers-reduced-motion: reduce) {
    .chat-bubble {
        animation: none;
    }

    .chat-typing span {
        animation: none;
        opacity: 0.7;
    }

    .chat-caret {
        animation: none;
    }
}

.assistant-steps {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    padding: 0.85rem 1rem 0;
}

.assistant-step {
    align-items: center;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    display: inline-flex;
    font-size: 0.72rem;
    font-weight: 700;
    gap: 0.3rem;
    padding: 0.2rem 0.6rem;
}

.assistant-step-done {
    border-color: rgba(196, 78, 175, 0.65);
    color: var(--bls-text);
}

.assistant-composer {
    align-items: center;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    display: flex;
    gap: 0.5rem;
    margin: 0 1rem 1rem;
    padding: 0.35rem;
}

.assistant-composer:focus-within {
    border-color: rgba(196, 78, 175, 0.65);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.14);
}

.assistant-composer-input {
    background: transparent;
    border: 0;
    color: var(--bls-text);
    flex: 1;
    min-width: 0;
    outline: 0;
    padding: 0.5rem 0.65rem;
}

.assistant-composer-field {
    display: flex;
    flex: 1;
    min-width: 0;
    position: relative;
}

.assistant-composer-field .assistant-composer-input {
    position: relative;
    width: 100%;
    z-index: 1;
}

.assistant-composer-placeholder {
    align-items: center;
    box-sizing: border-box;
    color: var(--bls-muted);
    container-type: inline-size;
    display: flex;
    inset: 0;
    overflow: hidden;
    padding: 0.5rem 0.65rem;
    pointer-events: none;
    position: absolute;
    z-index: 0;
}

.assistant-composer-input:not(:placeholder-shown) + .assistant-composer-placeholder {
    opacity: 0;
}

.assistant-composer-placeholder-text {
    animation: assistant-placeholder-rock 4.5s ease-in-out infinite alternate;
    display: inline-block;
    flex: 0 0 auto;
    white-space: nowrap;
    will-change: transform;
}

@keyframes assistant-placeholder-rock {
    0%,
    14% {
        transform: translateX(0);
    }

    86%,
    100% {
        transform: translateX(min(0px, calc(100cqw - 100%)));
    }
}

@media (prefers-reduced-motion: reduce) {
    .assistant-composer-placeholder-text {
        animation: none;
    }
}

.assistant-composer-input::placeholder {
    color: var(--bls-muted);
}

.assistant-composer-attach {
    align-items: center;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    font-weight: 900;
    height: 2rem;
    justify-content: center;
    width: 2rem;
}

.assistant-composer-attach:hover,
.assistant-composer-attach:focus-visible {
    background: rgba(196, 78, 175, 0.18);
    border-color: rgba(196, 78, 175, 0.45);
    outline: none;
}

.assistant-composer-send {
    align-items: center;
    background: var(--bls-primary);
    border: 0;
    border-radius: 999px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    font-weight: 800;
    height: 2rem;
    justify-content: center;
    width: 2rem;
}

.assistant-composer-send:disabled {
    cursor: not-allowed;
    opacity: 0.45;
}

.assistant-composer-send svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.assistant-composer-files {
    color: var(--bls-muted);
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: -0.55rem 1rem 1rem;
}

.assistant-composer-files span {
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    padding: 0.25rem 0.55rem;
}

.assistant-edit-button {
    align-self: flex-end;
    box-shadow: 0 14px 36px rgba(196, 78, 175, 0.32);
}

.health-clean {
    background: rgba(52, 211, 153, 0.13);
    color: var(--bls-success);
}

.health-pending {
    background: rgba(245, 158, 11, 0.15);
    color: var(--bls-warning);
}

.health-blocked {
    background: rgba(251, 113, 133, 0.15);
    color: var(--bls-danger);
}

@media (max-width: 1100px) {
    .portal-shell,
    .portal-sidebar-toggle-state:checked + .portal-shell {
        grid-template-areas:
            "topbar"
            "sidebar"
            "main";
        grid-template-columns: 1fr;
        grid-template-rows: auto auto minmax(0, 1fr);
    }

    /* Stacked top-bar mode: lay the sidebar out as a horizontal header grid so
       the footer tool cluster (settings / perspective / theme) sits at the
       top-right of the header instead of in its own full-width row. The nav
       drops onto a second row spanning the full width. */
    .portal-sidebar {
        align-items: center;
        align-self: stretch;
        column-gap: 0.75rem;
        display: grid;
        grid-template-areas:
            "header footer"
            "nav    nav";
        grid-template-columns: minmax(0, 1fr) auto;
        height: auto;
        max-height: none;
        overflow: visible;
        position: sticky;
        row-gap: 0.5rem;
        width: 100%;
        z-index: 20;
    }

    .portal-sidebar-header {
        grid-area: header;
    }

    .portal-nav {
        grid-area: nav;
    }

    /* Re-enable the collapse control here: it shrinks the header to icons only. */
    .portal-sidebar-toggle {
        display: inline-flex;
    }

    /* Footer tools move up beside the brand as a tight right-aligned icon
       cluster, dropping the sticky / bordered / full-width desktop footer. */
    .portal-sidebar-footer {
        background: transparent;
        border-top: 0;
        gap: 0.3rem;
        grid-area: footer;
        justify-content: flex-end;
        margin-top: 0;
        padding-top: 0;
        position: static;
        width: auto;
    }

    /* Icons-only header: collapse to a single row of brand + icon nav + tools. */
    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar {
        align-items: center;
        grid-template-areas: "header nav footer";
        /* nav takes the flexible space so the report-bug control (margin-left:auto) docks
           to the right next to the footer tools; footer is content-width on the far right. */
        grid-template-columns: auto minmax(0, 1fr) auto;
        padding-inline: 0.85rem;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-header {
        flex-direction: row;
        gap: 0.5rem;
    }

    /* The stacked header is a full-width row, so the full wordmark fits even in the
       icons-only (collapsed) state - undo the desktop rail's wordmark->mark swap and
       lay the header back out as a row. */
    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-logo {
        display: block;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-mark {
        display: none;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-header {
        flex-direction: row;
        gap: 0.7rem;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-footer {
        flex-direction: row;
        justify-content: flex-end;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-nav-section {
        display: none;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-nav a,
    .portal-sidebar-toggle-state:checked + .portal-shell .portal-nav .portal-nav-action {
        min-width: 2.2rem;
        padding-inline: 0.42rem;
    }

    /* Stacked top-bar mode: pack the nav into a single compact, content-sized
       pill row that wraps instead of a fixed 13rem grid (which forced tall
       multi-row blocks and a bloated menu). */
    .portal-nav {
        align-items: center;
        column-gap: 0.4rem;
        display: flex;
        flex: 0 0 auto;
        flex-direction: row;
        flex-wrap: wrap;
        overflow: visible;
        padding-right: 0;
        row-gap: 0.35rem;
    }

    /* Horizontal top bar: the active marker becomes a centred underline. */
    .portal-nav a.active::after {
        bottom: 0.15rem;
        height: 2px;
        left: 50%;
        right: auto;
        top: auto;
        transform: translateX(-50%);
        width: 1.1rem;
    }

    .portal-nav-section {
        flex-basis: 100%;
        margin: 0.1rem 0 0.05rem;
    }

    .portal-nav a,
    .portal-nav .portal-nav-action {
        flex: 0 0 auto;
        min-height: 2.2rem;
        padding: 0.42rem 0.78rem;
        width: auto;
    }

    /* Report bug separates from the nav pills in the horizontal menu: icon
       only, docked to the right end of the nav row — directly below the
       footer tool cluster (settings / perspective / theme switch). */
    .portal-nav-bug-wrap {
        border-top: 0;
        flex-direction: row;
        margin-left: auto;
        margin-top: 0;
        padding-top: 0;
    }

    .portal-nav .portal-nav-bug {
        gap: 0;
        justify-content: center;
        min-width: 2.2rem;
        padding-inline: 0.42rem;
        width: auto;
    }

    .portal-nav .portal-nav-bug .portal-nav-label {
        display: none;
    }

    .portal-topbar,
    .login-hero,
    .portal-grid-2,
    .portal-settings-sections,
    .welcome-hero,
    .case-list-workspace-has-detail,
    .kanban-workspace,
    .case-detail-grid,
    .case-command-sections,
    .case-command-split,
    .customer-service-detail-grid,
    .admin-action-grid,
    .admin-tools-shell,
    .intake-layout {
        grid-template-columns: 1fr;
    }

    /* P4 (c) — the STACKED form of the two grids the popout is built from. Both are
       re-columned to 1fr by the rule above, so their children become ROWS, and the
       desktop single-row template would put the first child in the 1fr and leave the
       rest in implicit auto rows (there is no grid-auto-rows on either).
       .case-command-sections: row 1 = the command rail (auto — it must keep its
       natural height, a 1fr floor of 0 would crush it), row 2 = the pages. */
    .case-command-sections {
        grid-template-rows: auto minmax(0, 1fr);
    }

    /* .case-command-split: grid-auto-rows, NOT grid-template-rows, because the number
       of panes in layout varies — two stacked halves at 601-1100px, and exactly one
       at <= 600px where the R3 pane switch display:none's the other. An explicit
       two-row template would give a lone pane half the height and leave the rest
       empty; grid-auto-rows sizes whatever rows actually exist equally. */
    .case-command-split {
        grid-auto-rows: minmax(0, 1fr);
    }

    /* Stacked panes: nothing to expand. */
    .case-split-expand {
        display: none;
    }

    /* Stacked mode: drop the viewport-derived page floor. --portal-page-min-height
       only subtracts the fixed desktop chrome — it cannot see the auto-height
       stacked header band above the content, so it over-measures here and every
       short page grew a phantom scroll. Pages size to content instead and
       .portal-content remains the scroller. (.portal-page-has-overlay is NOT
       released: it sizes with its own explicit height/max-height, which this
       min-height cannot loosen — its overflow:hidden already zeroes the auto
       minimum — and freezing the page behind an open overlay is still what that
       class is for. Note it no longer has anything to do with SIZING the popout:
       since P4 (a) BOTH overlays are viewport-fixed at these widths, so neither
       one measures itself against this page box any more.) */
    .portal-page {
        min-height: auto;
    }

    /* Stacked layouts: release the viewport caps so the page scrolls as one. */
    .kanban-workspace,
    .case-list-workspace,
    .library-shell,
    .portal-page-admin-tools .admin-tools-shell {
        max-height: none;
    }

    /* Stacked intake: release the viewport-fit card so the page scrolls as one,
       and give the embedded assistant its own fixed-height scroll region. */
    .intake-card {
        max-height: none;
    }

    .intake-form-pane {
        overflow-y: visible;
    }

    .intake-assistant {
        height: 26rem;
    }

    /* Stacked layout: the tools menu takes its own row above the panel. */
    .portal-page-admin-tools .admin-tools-shell {
        grid-template-rows: auto minmax(0, 1fr);
    }

    /* Tablet / narrow viewports (the portal has collapsed to one column, so the
       library card itself is wide): keep Shelves on the left and the book
       content on the right. The Book/Chapter/Page nav stays a docked, fixed-width
       aside exactly like the Shelves sidebar — it must never stretch to the full
       width of .library-main. Collapsing either rail falls back to the desktop
       icon-rail via the base styles, so the collapse toggles keep working. */
    .library-layout:not(.library-layout-sidebar-collapsed) {
        grid-template-columns: minmax(10rem, 0.32fr) minmax(0, 1fr);
    }

    .library-reader:not(.library-reader-books-collapsed) {
        grid-template-columns: minmax(11rem, 0.32fr) minmax(0, 1fr);
    }

    .library-page-preview {
        min-height: auto;
    }

    .library-chapter {
        background: rgba(255, 255, 255, 0.04);
        border: 1px solid var(--bls-border);
        border-left: 3px solid rgba(196, 78, 175, 0.48);
        border-radius: var(--bls-radius-sm);
        padding: 0.75rem;
    }

    .library-page-link {
        width: 100%;
    }

    .library-page-preview {
        min-height: auto;
    }

    .library-page-preview .empty-state-large {
        min-height: 8rem;
    }

    /* Keep the settings tabs on one row even on narrow screens (was a wrapping grid,
       which also stretched the collapse toggle to a full cell). */
    .portal-settings-menu {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
    }

    .case-command-menu,
    .admin-tools-menu {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
    }

    .detail-tile-wide {
        grid-column: auto;
    }
}

/*
 * Library narrow-mode layout.
 *
 * These container queries measure the library card itself (.library-shell is a
 * container) and are placed AFTER the viewport media query above so they win
 * for the library's own layout. The goal: on tablet-ish widths keep Shelves on
 * the left and the book content on the right (instead of one tall stack), and
 * only collapse to a single column on phones.
 */
@container (max-width: 60rem) {
    /* Shelves stay on the left, the books/preview area on the right. When the
       shelves rail is collapsed, leave the thin icon-only rail from the base
       .library-layout-sidebar-collapsed rule alone — don't force it back to a
       wide column, which left a large empty gutter in the stacked top-bar view. */
    .library-layout:not(.library-layout-sidebar-collapsed) {
        grid-template-columns: minmax(9.5rem, 0.34fr) minmax(0, 1fr);
    }

    /* Not enough room for the three-pane reader here, so stack books over the
       page — but when the books rail is collapsed, keep it as a thin icon rail
       beside the page instead of a full-width stacked block (which left the
       icons floating in a tall empty card). */
    .library-reader:not(.library-reader-books-collapsed) {
        grid-template-columns: 1fr;
    }

    .library-reader-books-collapsed {
        grid-template-columns: 3.75rem minmax(0, 1fr);
    }
}

@container (max-width: 30rem) {
    /* Phones: too narrow for two columns, fall back to a single stack — unless the
       rail is collapsed, in which case the thin icon strip + content reads fine. */
    .library-layout:not(.library-layout-sidebar-collapsed) {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 720px) {
    /* Phones: too narrow for two columns, so stack the library vertically.
       (Matches the specificity of the tablet rule above so it wins.) */
    .library-layout:not(.library-layout-sidebar-collapsed) {
        grid-template-columns: 1fr;
    }

    .library-sidebar {
        border-right: 0;
    }

    .portal-shell {
        --portal-shell-inline-padding: 0.85rem;
        --portal-shell-padding-block-start-base: 0.85rem;
        --portal-shell-padding-block-end-base: 0.85rem;
    }

    .portal-public-shell {
        padding-block: max(0.85rem, var(--portal-safe-top)) max(0.85rem, var(--portal-safe-bottom));
        padding-left: max(0.85rem, var(--portal-safe-left));
        padding-right: max(0.85rem, var(--portal-safe-right));
    }

    .portal-content {
        --portal-content-padding-block-end: 0px;
        padding: 0 0 var(--portal-content-padding-block-end);
    }

    .portal-topbar {
        /* With the brand gone the strip is a single right-aligned row even on
           phones - no more brand-over-profile stacking. Inside the fixed-height
           app-shell it must not shrink, or the account cluster overflows its box
           and overlaps the header below it. */
        flex-shrink: 0;
    }

    .portal-topbar .portal-sidebar-profile {
        max-width: none;
        width: 100%;
    }

    .portal-page-header,
    .portal-card-header,
    .customer-chooser-header {
        align-items: flex-start;
        flex-direction: column;
    }

    .case-detail-compact > .portal-card-header,
    .case-detail-popover .case-detail-panel > .portal-card-header {
        grid-template-areas:
            "heading"
            "right";
        grid-template-columns: minmax(0, 1fr);
    }

    .case-detail-compact > .portal-card-header .case-detail-header-right,
    .case-detail-popover .case-detail-panel > .portal-card-header .case-detail-header-right {
        align-items: flex-start;
        justify-self: start;
    }

    .customer-chooser-card {
        align-items: flex-start;
        flex-direction: column;
    }

    .customer-chooser-card-meta {
        align-items: flex-start;
    }

    .case-form,
    .process-track,
    .attachment-item,
    .report-status-grid,
    .customer-service-balances,
    .customer-service-detail-grid,
    .portal-settings-identity {
        grid-template-columns: 1fr;
    }

    .portal-settings-pills {
        justify-content: flex-start;
    }

    .span-2 {
        grid-column: span 1;
    }

    .customer-snapshot-grid,
    .customer-snapshot-sections,
    .library-toolbar {
        grid-template-columns: 1fr;
    }

    .assistant-popover-shell {
        bottom: max(1rem, var(--portal-safe-bottom));
        left: max(1rem, var(--portal-safe-left));
        right: max(1rem, var(--portal-safe-right));
    }

    .assistant-popover,
    .assistant-edit-button {
        width: 100%;
    }
}

/*
 * Phone header. The single-row icon header used on tablet can't fit a phone:
 * the nav icons wrap into the tool cluster and the brand wordmark clips. So at
 * phone widths the menu always uses the two-row layout — brand + collapse +
 * tools grouped on the top row, the nav wrapping on a full-width row below —
 * for BOTH the expanded and collapsed (icons-only) states.
 */
@media (max-width: 600px) {
    .portal-sidebar,
    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar {
        column-gap: 0.4rem;
        grid-template-areas:
            "header footer"
            "nav    nav";
        grid-template-columns: auto minmax(0, 1fr);
        row-gap: 0.45rem;
    }

    /* Just the logo mark — the "Blacklight Help Desk" wordmark has no room here. */
    .portal-brand-copy {
        display: none;
    }

    /* Brand + collapse toggle group on the left; tools stay a tight row on the right. */
    .portal-sidebar-header {
        gap: 0.45rem;
    }

    .portal-sidebar-footer,
    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar-footer {
        flex-direction: row;
        gap: 0.2rem;
        justify-content: flex-end;
    }

    /* Touch targets: 2.75rem = 44px, the WCAG/Apple minimum for fingers. The
       desktop sizes (2.2-2.6rem nav pills, 2.45rem searches/footer icons) stay
       untouched — these overrides only lift the floor at phone widths. The
       footer actions pin an explicit height on desktop (even width share), so
       height must be released here or the min-height would lose to it. */
    .portal-nav a,
    .portal-nav .portal-nav-action {
        min-height: 2.75rem;
    }

    .portal-sidebar-footer-action {
        height: auto;
        min-height: 2.75rem;
    }

    .kanban-search,
    .case-table-search,
    .library-search {
        min-height: 2.75rem;
    }

    .btn {
        min-height: 2.75rem;
    }

    /* min-height only bites on boxes that generate one: <button> (UA
       inline-block) takes the floor above, but <a class="btn"> and
       <label class="btn"> are non-replaced inline boxes where min-height is
       inert, so give exactly those a flex box. Deliberately NOT all .btn:
       flex layout drops whitespace-only text runs, and some <button class="btn">
       space their icon from the label with a literal space (e.g. the reports
       drill "All accounts" back buttons) — every a.btn/label.btn in the app is
       text-only (icon-bearing anchors all carry the already-flex btn-icon). */
    a.btn,
    label.btn {
        align-items: center;
        display: inline-flex;
        justify-content: center;
    }

    /* Data tables can't show 8 columns on a phone, so render each row as a
       labelled card: the column header becomes an inline label on each cell,
       and the first cell (case number + title) is the card's heading. */
    .portal-table {
        min-width: 0;
    }

    .portal-table thead {
        display: none;
    }

    .portal-table tbody,
    .portal-table tr,
    .portal-table td {
        display: block;
        width: 100%;
    }

    .portal-table tr {
        border: 1px solid var(--bls-border);
        border-radius: 0.85rem;
        margin-bottom: 0.7rem;
        padding: 0.35rem 0.85rem 0.55rem;
    }

    .portal-table td {
        border-bottom: 0;
        display: flex;
        gap: 1rem;
        justify-content: space-between;
        padding: 0.4rem 0;
        text-align: right;
    }

    .portal-table td::before {
        color: var(--bls-muted);
        content: attr(data-label);
        font-size: 0.74rem;
        font-weight: 700;
        letter-spacing: 0.04em;
        text-align: left;
        text-transform: uppercase;
    }

    .portal-table td span {
        display: inline;
        margin: 0;
        text-align: right;
    }

    /* First cell becomes the card heading: full width, no label, left aligned. */
    .portal-table td:first-child {
        border-bottom: 1px solid var(--bls-border);
        display: block;
        margin-bottom: 0.35rem;
        padding-bottom: 0.5rem;
        text-align: left;
    }

    .portal-table td:first-child::before {
        display: none;
    }

    .portal-table td:first-child span {
        display: block;
        text-align: left;
    }

    /* The desktop column-budget rules (width:1% + nowrap) don't apply to the stacked
       card layout — every cell is a full-width labelled row again. */
    .case-table .case-col-tight,
    .case-table .case-col-contact,
    .case-table .case-col-main,
    .case-table .case-col-customer {
        max-width: none;
        min-width: 0;
        overflow: visible;
        white-space: normal;
        width: 100%;
    }

    /* Filter / chip rows must wrap on a phone rather than overflow sideways. */
    .kanban-filters {
        flex-wrap: wrap;
    }
}

/* ---- Customer logo ---- */

/* Customer icons are always a clipped circle, consistent across the app — the image
   fills it (cover) so any logo shows as well as it can in the round frame. */
.customer-logo {
    align-items: center;
    aspect-ratio: 1;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    display: inline-flex;
    flex: 0 0 auto;
    justify-content: center;
    overflow: hidden;
    vertical-align: middle;
}

.customer-logo-xs {
    height: 1.1rem;
    width: 1.1rem;
}

.customer-logo-sm {
    height: 1.55rem;
    width: 1.55rem;
}

.customer-logo-md {
    height: 2.25rem;
    width: 2.25rem;
}

.customer-logo-lg {
    height: 2.85rem;
    width: 2.85rem;
}

.customer-logo-image {
    display: block;
    height: 100%;
    object-fit: cover;
    width: 100%;
}

.customer-logo-monogram {
    align-items: center;
    background: linear-gradient(135deg, color-mix(in srgb, var(--customer-logo-accent, var(--bls-primary)) 100%, transparent) 0%, color-mix(in srgb, var(--customer-logo-accent, var(--bls-primary)) 65%, #1a1024) 100%);
    color: #ffffff;
    display: inline-flex;
    font-weight: 900;
    height: 100%;
    justify-content: center;
    letter-spacing: 0.04em;
    line-height: 1;
    text-transform: uppercase;
    width: 100%;
}

.customer-logo-xs .customer-logo-monogram {
    font-size: 0.55rem;
}

.customer-logo-sm .customer-logo-monogram {
    font-size: 0.65rem;
}

.customer-logo-md .customer-logo-monogram {
    font-size: 0.85rem;
}

.customer-logo-lg .customer-logo-monogram {
    font-size: 1rem;
}

/* Place logo cleanly alongside text in shared list patterns */

.customer-chooser-card {
    align-items: center;
}

.list-card-with-logo {
    gap: 0.85rem;
}

.list-card-with-logo .list-card-body {
    flex: 1 1 auto;
    min-width: 0;
}

.list-card-with-logo .list-card-body span {
    display: block;
    margin-top: 0.15rem;
}

.case-row-customer {
    align-items: center;
    display: inline-flex;
    flex-wrap: nowrap;
    gap: 0.5rem;
    min-width: 0;
}

.case-row-customer > :first-child {
    flex: 0 0 auto;
}

.case-row-customer > span:last-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Logo + name sit side by side, vertically centred in the row. These outrank the
   generic ".portal-table td span" subtitle rule (display:block + top margin) which
   otherwise stacks the logo above the name. */
.portal-table td[data-label="Customer"] {
    vertical-align: middle;
}

.portal-table td span.case-row-customer {
    display: inline-flex;
    margin-top: 0;
}

.portal-table td .case-row-customer > span {
    margin-top: 0;
}

.library-recent-meta {
    align-items: center;
    display: inline-flex;
    gap: 0.4rem;
}

.kanban-filter-options [role="menuitemradio"] {
    align-items: center;
    display: flex;
    gap: 0.45rem;
}

.kanban-filter-options [role="menuitemradio"] > span {
    flex: 1 1 auto;
    min-width: 0;
    text-align: left;
}

.portal-customer-context-button .customer-logo {
    background: transparent;
    border: none;
}

/* Library authoring (admin) */
.library-header-actions {
    align-items: center;
    display: flex;
    flex: 0 1 auto;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: flex-end;
}

.library-author-button {
    align-items: center;
    display: inline-flex;
    gap: 0.4rem;
    white-space: nowrap;
}

.library-author-button svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.library-book-cover-neutral {
    background: linear-gradient(160deg, #5a6b7d, #2f3a47);
}

.library-shelf-book-neutral {
    background: linear-gradient(165deg, #5a6b7d, #2f3a47);
}

.library-form-overlay {
    align-items: center;
    background: rgba(11, 7, 15, 0.66);
    display: flex;
    inset: 0;
    justify-content: center;
    overscroll-behavior: contain;
    padding: 1.5rem;
    position: fixed;
    z-index: 60;
}

.library-form-card {
    display: flex;
    flex-direction: column;
    max-height: min(90vh, 48rem);
    overflow-y: auto;
    padding: 0 1.35rem;
    width: min(640px, 100%);
}

/* Header and actions stay pinned while the fields scroll between them, so the
   modal always "fits" and the buttons are never cut off on short screens. */
.library-form-header {
    align-items: flex-start;
    background: var(--bls-surface);
    border-bottom: 1px solid var(--bls-border);
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    padding-bottom: 0.85rem;
    padding-top: 1.35rem;
    position: sticky;
    top: 0;
    z-index: 3;
}

.library-form-header h3,
.library-form-header p {
    margin: 0;
}

.library-form-header p:not(.portal-eyebrow) {
    color: var(--bls-muted);
}

.library-form-grid {
    display: grid;
    gap: 0.85rem;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-top: 1rem;
}

.library-form-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}

.library-form-field-wide {
    grid-column: 1 / -1;
}

.library-form-field > span {
    color: var(--bls-muted);
    font-size: 0.82rem;
    font-weight: 600;
}

.library-form-field input,
.library-form-field select,
.library-form-field textarea {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    color: var(--bls-text);
    font: inherit;
    min-height: 2.45rem;
    min-width: 0;
    padding: 0.55rem 0.75rem;
    width: 100%;
}

.library-form-field textarea {
    min-height: 3.5rem;
    resize: vertical;
}

.library-form-field input:focus-visible,
.library-form-field select:focus-visible,
.library-form-field textarea:focus-visible {
    border-color: rgba(196, 78, 175, 0.5);
    outline: none;
}

.library-form-error {
    color: #fca5a5;
    margin: 0.85rem 0 0;
}

.library-form-actions {
    background: var(--bls-surface);
    border-top: 1px solid var(--bls-border);
    bottom: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: flex-end;
    margin-top: 1rem;
    padding-bottom: 1.35rem;
    padding-top: 0.85rem;
    position: sticky;
    z-index: 3;
}

/* ---- Phase 2c: customer snapshot panel (admin flow) ---- */
.customer-snapshot-panel {
    background: var(--bls-surface, #141417);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: var(--bls-radius-md, 0.9rem);
    margin-top: 0.5rem;
    padding: 1rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.snapshot-generate-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
}

.snapshot-generate-row .portal-select {
    flex: 0 1 320px;
    max-width: 320px;
    min-width: 200px;
}

.snapshot-step {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.snapshot-summary-label {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-weight: 600;
    font-size: 0.875rem;
}

.snapshot-summary-textarea {
    background: var(--bls-surface, #141417);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 6px;
    color: inherit;
    font-family: Ubuntu, sans-serif;
    font-size: 0.875rem;
    line-height: 1.5;
    min-height: 7rem;
    padding: 0.6rem 0.75rem;
    resize: vertical;
    width: 100%;
}

.snapshot-summary-textarea:focus {
    border-color: var(--bls-primary, #c44eaf);
    outline: none;
}

.snapshot-summary-textarea:disabled {
    opacity: 0.6;
}

.snapshot-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

.snapshot-lifecycle {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    border-top: 1px solid var(--bls-border, #2a2a31);
    padding-top: 0.75rem;
}

.snapshot-state-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.snapshot-meta {
    font-size: 0.8rem;
    color: var(--bls-muted, #aaa7ad);
}

.snapshot-error {
    color: var(--bls-danger, #e05252);
    font-size: 0.85rem;
    margin: 0;
}

.snapshot-shared-confirm {
    color: var(--bls-success, #3fb950);
    font-size: 0.85rem;
    margin: 0;
    width: 100%;
}

/* ---- Phase 2d: customer shared-reports panel ---- */
.shared-reports-panel {
    margin-top: 1.5rem;
}

.shared-report-detail {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.shared-report-summary {
    padding: 0.9rem 1rem;
}

.shared-report-summary-text {
    font-size: 0.9rem;
    line-height: 1.65;
    margin: 0.25rem 0 0;
    white-space: pre-wrap;
}

.shared-report-snippet {
    color: var(--bls-muted, #aaa7ad);
    font-size: 0.8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 600px) {
    .library-form-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* Styled choice control (shared) — matches the case board pill menus and
   replaces native <select> / <input list> in portal forms. */
.portal-select {
    min-width: 0;
    position: relative;
}

.portal-select-trigger {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.6rem;
    justify-content: space-between;
    min-height: 2.45rem;
    padding: 0.55rem 0.75rem;
    text-align: left;
    transition: border-color 120ms ease, box-shadow 120ms ease;
    width: 100%;
}

.portal-select-trigger:hover {
    border-color: rgba(196, 78, 175, 0.4);
}

.portal-select-trigger:focus-visible,
.portal-select-open .portal-select-trigger {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.18);
    outline: none;
}

.portal-select-disabled .portal-select-trigger {
    cursor: not-allowed;
    opacity: 0.55;
}

.portal-select-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.portal-select-placeholder {
    color: var(--bls-muted);
}

.portal-select-caret {
    color: var(--bls-muted);
    display: inline-flex;
    flex: 0 0 auto;
    transition: transform 160ms ease;
}

.portal-select-caret svg[blazicon] {
    height: 1rem;
    width: 1rem;
}

.portal-select-open .portal-select-caret {
    transform: rotate(180deg);
}

.portal-select-menu {
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow-soft);
    display: grid;
    gap: 0.2rem;
    margin-top: 0.4rem;
    max-height: min(18rem, 60vh);
    overflow-y: auto;
    padding: 0.35rem;
    z-index: 12;
}

.theme-light .portal-select-menu {
    background: var(--bls-surface);
}

.portal-select-search {
    padding: 0.35rem;
    position: sticky;
    top: 0;
    background: var(--bls-surface-strong);
}

.portal-select-search-input {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: 0.5rem;
    color: var(--bls-text);
    padding: 0.4rem 0.55rem;
    width: 100%;
}

.portal-select-custom {
    padding: 0.15rem 0.15rem 0.35rem;
}

.portal-select-custom-input {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    font: inherit;
    padding: 0.45rem 0.6rem;
    width: 100%;
}

.portal-select-custom-input:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    outline: none;
}

.portal-select-option {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--bls-radius-sm);
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.5rem;
    justify-content: space-between;
    padding: 0.45rem 0.55rem;
    text-align: left;
    transition: background-color 120ms ease, border-color 120ms ease;
    width: 100%;
}

.portal-select-option:hover {
    background: rgba(196, 78, 175, 0.08);
}

.portal-select-option:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    outline: none;
}

.portal-select-option.active {
    background: rgba(196, 78, 175, 0.16);
    border-color: rgba(196, 78, 175, 0.42);
}

.portal-select-option-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.portal-select-check {
    color: var(--bls-primary-dark);
    display: inline-flex;
    flex: 0 0 auto;
}

.portal-select-check svg[blazicon] {
    height: 0.95rem;
    width: 0.95rem;
}

/* Library vetting / approval */
.library-shelf-pending {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
    padding: 0 0.25rem 0.25rem;
}

.library-pending-badge {
    align-self: flex-start;
    background: rgba(245, 158, 11, 0.16);
    border: 1px solid rgba(245, 158, 11, 0.45);
    border-radius: 999px;
    color: #fbbf24;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    padding: 0.15rem 0.55rem;
    white-space: nowrap;
}

.library-approve-button {
    align-items: center;
    display: inline-flex;
    gap: 0.35rem;
    white-space: nowrap;
}

.library-pending-dot {
    background: #fbbf24;
    border-radius: 999px;
    box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.18);
    display: inline-block;
    flex: 0 0 auto;
    height: 0.5rem;
    margin-left: auto;
    width: 0.5rem;
}

/* Unredeemed B2B invitation marker on member/contact rosters. */
.invite-pending-pill {
    background: rgba(245, 158, 11, 0.16);
    color: #fbbf24;
}

/* Internal team tag pill ("Admin", "Devs") on team tiles and the team detail header. */
.team-tag-pill {
    background: rgba(196, 78, 175, 0.14);
    color: var(--bls-primary-dark);
    margin-left: 0.35rem;
}

/* Internal-reference-only tag: staff-facing marker on books customers never see. */
.library-internal-badge {
    align-self: flex-start;
    background: rgba(123, 76, 193, 0.16);
    border: 1px solid rgba(123, 76, 193, 0.5);
    border-radius: 999px;
    color: #b79aec;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    padding: 0.15rem 0.55rem;
    white-space: nowrap;
}

.library-internal-dot {
    background: #9a6ee0;
    border-radius: 999px;
    box-shadow: 0 0 0 3px rgba(123, 76, 193, 0.2);
    display: inline-block;
    flex: 0 0 auto;
    height: 0.5rem;
    margin-left: auto;
    width: 0.5rem;
}

/* Bug report launcher (admin diagnostic capture) — sits at the bottom of the
   primary nav, just above the sidebar footer. The trigger mirrors the nav-link
   shape so it lines up with the other items, with a red bug accent. */
.portal-nav-bug-wrap {
    border-top: 1px solid var(--bls-border);
    display: flex;
    flex-direction: column;
    margin-top: auto;
    padding-top: 0.5rem;
}

.portal-nav .portal-nav-bug {
    align-items: center;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.95rem;
    color: rgba(255, 255, 255, 0.72);
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.8rem;
    min-height: 2.6rem;
    padding: 0.66rem 0.78rem;
    text-align: left;
    text-decoration: none;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 100%;
}

.portal-nav .portal-nav-bug:hover,
.portal-nav .portal-nav-bug:focus-visible {
    background: linear-gradient(135deg, rgba(220, 38, 38, 0.92), rgba(196, 78, 175, 0.88));
    border-color: rgba(220, 38, 38, 0.45);
    color: #ffffff;
    outline: none;
}

.portal-nav .portal-nav-bug .portal-nav-icon {
    color: #f87171;
}

.portal-nav .portal-nav-bug:hover .portal-nav-icon,
.portal-nav .portal-nav-bug:focus-visible .portal-nav-icon {
    color: #ffffff;
}

/* Collapsed sidebar: match the nav-link collapsed treatment. */
.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav .portal-nav-bug {
    gap: 0;
    justify-content: center;
    padding-inline: 0;
}

@media (max-width: 1100px) {
    .portal-nav-bug-wrap {
        border-top: 0;
        margin-top: 0;
        padding-top: 0;
    }

    .portal-sidebar-toggle-state:checked + .portal-shell .portal-nav .portal-nav-bug {
        min-width: 2.2rem;
        padding-inline: 0.42rem;
    }

    .portal-nav .portal-nav-bug {
        flex: 0 0 auto;
        min-height: 2.2rem;
        padding: 0.42rem 0.78rem;
        width: auto;
    }
}

.bug-report-overlay {
    align-items: start;
    background: rgba(11, 7, 15, 0.66);
    display: grid;
    inset: 0;
    justify-items: center;
    overscroll-behavior: contain;
    padding: var(--portal-overlay-padding);
    position: fixed;
    z-index: 40;
}

.bug-report-popover {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    max-height: min(var(--portal-popover-max-height), calc(100dvh - (var(--portal-overlay-padding) * 2)));
    overflow: auto;
    padding: 1.15rem;
    position: relative;
    width: min(640px, 100%);
}

.bug-report-header {
    align-items: flex-start;
    display: flex;
    gap: 1rem;
    justify-content: space-between;
}

.bug-report-header h2,
.bug-report-header p {
    margin: 0;
}

.bug-report-header p:not(.portal-eyebrow) {
    color: var(--bls-muted);
    font-size: 0.88rem;
}

.bug-report-form {
    gap: 0.85rem;
}

.bug-report-input,
.bug-report-textarea {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-text);
    font: inherit;
    padding: 0.65rem 0.75rem;
    width: 100%;
}

.bug-report-input:focus,
.bug-report-textarea:focus {
    border-color: var(--bls-primary);
    outline: none;
}

.bug-report-textarea {
    min-height: 6rem;
    resize: vertical;
}

/* Re-scope: the bug-report launcher lives inside the always-dark sidebar, so its overlay
   inherits the sidebar's light-on-dark var overrides (white text/border) while the card
   still uses the white light-theme surface — white-on-white. Restore the page (light)
   theme tokens on the overlay so the modal reads as a normal light-theme dialog.
   The --light-* constants (declared in the 2b LIGHT palette) pass through the sidebar
   untouched, so this is a single source of truth — no hexes duplicated here. */
.portal-shell.theme-light .bug-report-overlay {
    --bls-text: var(--light-text);
    --bls-ink: var(--light-ink);
    --bls-muted: var(--light-muted);
    --bls-border: var(--light-border);
    --bls-surface-strong: var(--light-surface-strong);
    color: var(--bls-text);
    color-scheme: light;
}

/* Light mode: form fields would otherwise blend into the near-white card. Give them a
   white fill, a clearly visible border, and dark text so the form is usable. */
.portal-shell.theme-light .bug-report-input,
.portal-shell.theme-light .bug-report-textarea,
.portal-shell.theme-light .tool-form select,
.portal-shell.theme-light .tool-form textarea,
.portal-shell.theme-light .tool-form input[type="text"],
.portal-shell.theme-light .tool-form input[type="datetime-local"],
.portal-shell.theme-light .portal-create-form .portal-settings-field input,
.portal-shell.theme-light .portal-create-form .portal-settings-field select,
.portal-shell.theme-light .list-filter-input,
.portal-shell.theme-light .user-access-tier select {
    background: #ffffff;
    border-color: rgba(33, 27, 31, 0.28);
    color: #211b1f;
}

.bug-report-diagnostics {
    background: rgba(255, 255, 255, 0.04);
    border: 1px dashed var(--bls-border);
    border-radius: 0.85rem;
    color: var(--bls-muted);
    display: flex;
    flex-direction: column;
    font-size: 0.82rem;
    gap: 0.45rem;
    padding: 0.85rem 1rem;
}

.bug-report-diagnostics strong {
    color: var(--bls-text);
    font-size: 0.85rem;
}

.bug-report-diagnostics ul {
    list-style: disc;
    margin: 0;
    padding-left: 1.1rem;
}

.bug-report-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: flex-end;
    margin-top: 0.25rem;
}

.bug-report-error {
    background: rgba(220, 38, 38, 0.14);
    border: 1px solid rgba(220, 38, 38, 0.4);
    border-radius: 0.7rem;
    color: #fecaca;
    margin: 0;
    padding: 0.55rem 0.75rem;
}

.bug-report-success {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.bug-report-success > strong {
    color: var(--bls-success, #22c55e);
    font-size: 1rem;
}

.bug-report-attachment-list {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--bls-border);
    border-radius: 0.7rem;
    color: var(--bls-muted);
    font-size: 0.82rem;
    list-style: none;
    margin: 0;
    padding: 0.55rem 0.8rem;
}

.bug-report-attachment-list li {
    padding: 0.15rem 0;
}

.account-logo-editor {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.account-logo-upload {
    position: relative;
    overflow: hidden;
}

/* ── Loading handler ─────────────────────────────────────────────────────────
   Branded busy state for the FIRST app load (a fresh browser session / SignalR
   circuit). Driven by the --bls-* tokens so it tracks the active theme, and by the
   shared design-system magenta accent so it reads as the same family as Flow.

   Composition: the faux "editor window" (traffic-light dots + filename bar) whose
   syntax-coloured tokens type themselves in is the full-bleed BACKGROUND of the
   stage; the "support beacon" (brand tile under a sweeping arc, with soft signal
   rings) floats dead-centre OVER it; the wording sits UNDERNEATH. A radial scrim —
   a near-opaque ink core that fades to transparent before the stage edges, wrapped
   in a soft magenta wash — dims only the code directly behind the wordmark so the
   logo pops while the code stays vivid at the rim. The editor/scrim/tile are
   intentionally dark in both themes (the brand-surface rule); copy + token colours
   stay token-driven.

   Stacking is a single CSS grid cell (grid-area: 1/1) so all three layers share one
   auto-centred box with no magic offsets — robust down to 320px. The beacon is a
   SIBLING of the editor, so the editor's overflow:hidden clips its own typing tokens
   but never crops the ring or pulses.

   Variants: .portal-loader-page / -panel / -overlay / -inline (PortalLoader.razor). */
.portal-loader {
    align-items: center;
    color: var(--bls-text);
    display: flex;
    flex-direction: column;
    gap: 1.4rem;
    text-align: center;
}

/* First-load splash: covers the whole viewport (over the shell — sidebar, topbar and
   all) so the brand is front and centre while the workspace boots. */
.portal-loader-page {
    animation: portal-loader-fade-in 320ms ease both;
    background:
        radial-gradient(ellipse 70rem 34rem at 92% -12%, rgba(196, 78, 175, 0.045), transparent 62%),
        var(--bls-bg);
    gap: 2rem;
    inset: 0;
    justify-content: center;
    padding: 2rem 1rem;
    position: fixed;
    z-index: 900;
}

.portal-loader-panel {
    gap: 1.1rem;
    padding: 2.25rem 1rem;
}

/* Sits over already-rendered (now stale) content while it refreshes; the host
   needs position: relative for this to anchor. */
.portal-loader-overlay {
    animation: portal-loader-fade-in 180ms ease both;
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    background: color-mix(in srgb, var(--bls-bg) 60%, transparent);
    border-radius: inherit;
    gap: 1.1rem;
    inset: 0;
    justify-content: center;
    position: absolute;
    z-index: 14;
}

/* Stage: editor (full-bleed background) + scrim + floating beacon, all in one grid
   cell so they stack and centre with no magic offsets. align-content:stretch makes
   the single row fill the stage min-height so the editor/scrim are full-bleed. No
   overflow clip here — only the editor clips — so the ring + pulses are never cropped. */
.portal-loader-stage {
    align-content: stretch;
    border-radius: 1rem;
    display: grid;
    flex: 0 0 auto;
    isolation: isolate;
    place-items: center;
    position: relative;
    width: min(30rem, 92vw);
}

.portal-loader-stage > * {
    grid-area: 1 / 1;
}

.portal-loader-page .portal-loader-stage {
    min-height: clamp(20rem, 52vh, 32rem);
    width: min(44rem, 94vw);
}

/* Larger beacon + logo tile on the full-screen splash so the brand reads clearly. */
.portal-loader-page .portal-loader-beacon {
    height: 8.5rem;
    width: 8.5rem;
}

.portal-loader-page .portal-loader-ring {
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 7px), #000 calc(100% - 6px));
    mask: radial-gradient(farthest-side, transparent calc(100% - 7px), #000 calc(100% - 6px));
}

.portal-loader-page .portal-loader-mark {
    border-radius: 1.4rem;
    height: 5.2rem;
    padding: 0.72rem;
    width: 5.2rem;
}

.portal-loader-page .portal-loader-copy strong {
    font-size: 1.5rem;
}

.portal-loader-page .portal-loader-copy span {
    font-size: 1rem;
}

.portal-loader-panel .portal-loader-stage,
.portal-loader-overlay .portal-loader-stage {
    border-radius: 0.85rem;
    width: min(22rem, 88vw);
}

.portal-loader-panel .portal-loader-stage {
    min-height: 11rem;
}

.portal-loader-overlay .portal-loader-stage {
    min-height: 12rem;
}

/* "Writing your workspace" — the BACKGROUND editor, filling the whole stage. Kept
   dark in both themes (like the sidebar) so the token colours stay vivid and the
   dark logo reads over it. overflow:hidden keeps the typing tokens inside the
   rounded corners. */
.portal-loader-editor {
    background: linear-gradient(180deg, #140d1c, #0a0710);
    border: 1px solid rgba(196, 78, 175, 0.18);
    border-radius: inherit;
    box-shadow: var(--bls-shadow);
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    text-align: left;
    width: 100%;
    z-index: 1;
}

.portal-loader-editor-bar {
    align-items: center;
    background: rgba(255, 255, 255, 0.04);
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    display: flex;
    flex: 0 0 auto;
    gap: 0.4rem;
    padding: 0.5rem 0.75rem;
}

.portal-loader-editor-dot {
    border-radius: 999px;
    flex: 0 0 auto;
    height: 0.5rem;
    width: 0.5rem;
}

.portal-loader-editor-dot:nth-child(1) {
    background: #fb7185;
}

.portal-loader-editor-dot:nth-child(2) {
    background: #fbbf24;
}

.portal-loader-editor-dot:nth-child(3) {
    background: #34d399;
}

.portal-loader-editor-file {
    color: rgba(255, 255, 255, 0.42);
    font-family: ui-monospace, "Cascadia Code", "Consolas", "SFMono-Regular", monospace;
    font-size: 0.68rem;
    letter-spacing: 0.02em;
    margin-left: 0.35rem;
    white-space: nowrap;
}

.portal-loader-editor-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: clamp(0.5rem, 1.6vw, 0.85rem);
    justify-content: center;
    padding: clamp(0.85rem, 3vw, 1.5rem) clamp(0.85rem, 3.5vw, 1.7rem);
}

.portal-loader-panel .portal-loader-editor-body,
.portal-loader-overlay .portal-loader-editor-body {
    gap: 0.55rem;
    padding: 1rem 1.1rem;
}

.portal-loader-line {
    align-items: center;
    display: flex;
    gap: 0.3rem;
    height: 0.5rem;
}

.portal-loader-line-in1 {
    padding-left: 0.9rem;
}

.portal-loader-line-in2 {
    padding-left: 1.8rem;
}

.portal-loader-gutter {
    background: rgba(255, 255, 255, 0.12);
    border-radius: 999px;
    flex: 0 0 auto;
    height: 0.5rem;
    margin-right: 0.35rem;
    width: 0.5rem;
}

/* Each token is a syntax-coloured bar that "types" itself in (scaleX from the
   left), holds, then clears just before the loop so the editor keeps rewriting. */
.portal-loader-editor .tok {
    animation: portal-loader-type 9s infinite both;
    border-radius: 999px;
    flex: 0 0 auto;
    height: 0.5rem;
    transform: scaleX(0);
    transform-origin: left center;
}

.tok-kw {
    background: var(--bls-primary);
    width: 2.2rem;
}

.tok-fn {
    background: var(--bls-purple);
    width: 3.1rem;
}

.tok-var {
    background: rgba(255, 255, 255, 0.34);
    width: 2.6rem;
}

.tok-str {
    background: var(--bls-success);
    width: 3.4rem;
}

.tok-num {
    background: var(--bls-warning);
    width: 1.4rem;
}

.tok-op {
    background: rgba(255, 255, 255, 0.2);
    width: 0.8rem;
}

.tok-br {
    background: rgba(255, 255, 255, 0.16);
    width: 1.1rem;
}

.portal-loader-caret {
    animation: portal-loader-caret 1s steps(1) infinite;
    background: var(--bls-primary-dark);
    border-radius: 1px;
    flex: 0 0 auto;
    height: 0.72rem;
    width: 0.16rem;
}

/* Legibility scrim: an ink ellipse that fades out before the edges (a vignette, not
   a hole) + a soft magenta wash so the dark tile reads as a lit object floating over
   the code. Painted (never blurred) so the code stays crisp. Fills the whole stage. */
.portal-loader-scrim {
    background:
        radial-gradient(ellipse min(13rem, 46%) min(10rem, 60%) at 50% 50%, color-mix(in srgb, var(--bls-primary) 24%, transparent) 0%, color-mix(in srgb, var(--bls-primary) 8%, transparent) 38%, transparent 66%),
        radial-gradient(ellipse min(9rem, 34%) min(7rem, 46%) at 50% 50%, #05030a 0%, rgba(5, 3, 10, 0.92) 34%, rgba(5, 3, 10, 0.55) 54%, transparent 74%);
    border-radius: inherit;
    height: 100%;
    pointer-events: none;
    width: 100%;
    z-index: 2;
}

/* Beacon: brand tile + sweeping arc + signal rings. Floats dead-centre over the
   code, outside the editor's clip so it is never cropped. The drop-shadow halo +
   magenta border make it read as a lit object above the editor. */
.portal-loader-beacon {
    align-items: center;
    display: inline-flex;
    flex: 0 0 auto;
    height: 5.5rem;
    justify-content: center;
    position: relative;
    width: 5.5rem;
    z-index: 3;
}

.portal-loader-panel .portal-loader-beacon,
.portal-loader-overlay .portal-loader-beacon {
    height: 4.5rem;
    width: 4.5rem;
}

/* Sweeping conic arc — the actual indeterminate spinner, masked to a ring. */
.portal-loader-ring {
    animation: portal-loader-spin 1.1s linear infinite;
    background: conic-gradient(from 0deg, transparent 0deg 200deg, var(--bls-primary) 320deg, var(--bls-primary-dark) 360deg);
    border-radius: 50%;
    inset: 0;
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
    mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
    position: absolute;
}

/* Soft "we hear you" signal rings rippling outward. */
.portal-loader-pulse {
    animation: portal-loader-pulse 2.4s ease-out infinite;
    border: 1px solid var(--bls-primary);
    border-radius: 50%;
    inset: 0;
    opacity: 0;
    position: absolute;
}

.portal-loader-pulse:nth-of-type(2) {
    animation-delay: 1.2s;
}

/* Center brand tile holding the Blacklight wordmark — dark in both themes so the
   dark-variant logo (white "Black", magenta "light") always reads. */
/* Light mode: the splash goes white and uses the light Blacklight logo + a light tile,
   instead of the dark IDE look. */
/* Show exactly one logo. Scoped to .portal-loader-mark so these beat the base
   `.portal-loader-logo { display: block }` rule (which is defined later in the file). */
.portal-loader-mark .portal-loader-logo-light {
    display: none;
}

.portal-shell.theme-light .portal-loader-mark .portal-loader-logo-dark {
    display: none;
}

.portal-shell.theme-light .portal-loader-mark .portal-loader-logo-light {
    display: block;
}

.portal-shell.theme-light .portal-loader-page {
    background:
        radial-gradient(ellipse 70rem 34rem at 92% -12%, rgba(196, 78, 175, 0.06), transparent 62%),
        #ffffff;
}

.portal-shell.theme-light .portal-loader-editor {
    background: linear-gradient(180deg, #ffffff, #f4f1f7);
    border-color: rgba(196, 78, 175, 0.2);
}

.portal-shell.theme-light .portal-loader-scrim {
    background:
        radial-gradient(ellipse min(13rem, 46%) min(10rem, 60%) at 50% 50%, color-mix(in srgb, var(--bls-primary) 16%, transparent) 0%, color-mix(in srgb, var(--bls-primary) 6%, transparent) 38%, transparent 66%),
        radial-gradient(ellipse min(9rem, 34%) min(7rem, 46%) at 50% 50%, #ffffff 0%, rgba(255, 255, 255, 0.92) 34%, rgba(255, 255, 255, 0.55) 54%, transparent 74%);
}

.portal-shell.theme-light .portal-loader-mark {
    background: linear-gradient(150deg, #ffffff, #f3eef7);
    border-color: rgba(196, 78, 175, 0.25);
    box-shadow: 0 10px 30px rgba(196, 78, 175, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.6);
}

.portal-loader-mark {
    align-items: center;
    background: linear-gradient(150deg, #181020, #08060c);
    border: 1px solid rgba(196, 78, 175, 0.45);
    border-radius: 1.05rem;
    box-shadow: 0 10px 30px rgba(196, 78, 175, 0.4), 0 0 22px rgba(196, 78, 175, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.07);
    display: inline-flex;
    filter: drop-shadow(0 0 10px rgba(227, 108, 204, 0.35));
    height: 3.4rem;
    justify-content: center;
    padding: 0.5rem;
    position: relative;
    width: 3.4rem;
}

.portal-loader-panel .portal-loader-mark,
.portal-loader-overlay .portal-loader-mark {
    border-radius: 0.85rem;
    height: 2.85rem;
    padding: 0.42rem;
    width: 2.85rem;
}

.portal-loader-logo {
    display: block;
    height: 100%;
    object-fit: contain;
    width: 100%;
}

/* Wording, UNDERNEATH the stage — theme-aware via --bls-ink / --bls-muted. */
.portal-loader-copy {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    max-width: 26rem;
}

.portal-loader-copy strong {
    color: var(--bls-ink);
    font-family: var(--bls-font-heading);
    font-size: 1.12rem;
    font-weight: 800;
    letter-spacing: -0.01em;
}

.portal-loader-panel .portal-loader-copy strong,
.portal-loader-overlay .portal-loader-copy strong {
    font-size: 1rem;
}

.portal-loader-copy span {
    color: var(--bls-muted);
    font-size: 0.88rem;
    line-height: 1.5;
}

/* Inline: compact spinner + label only — no stage, editor, or tile. */
.portal-loader-inline {
    color: var(--bls-muted);
    flex-direction: row;
    gap: 0.6rem;
    justify-content: flex-start;
}

.portal-loader-inline .portal-loader-beacon {
    height: 1.2rem;
    width: 1.2rem;
    z-index: auto;
}

.portal-loader-inline .portal-loader-ring {
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2px));
    mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2px));
}

.portal-loader-inline-label {
    font-size: 0.9rem;
    font-weight: 600;
}

@keyframes portal-loader-spin {
    to {
        transform: rotate(360deg);
    }
}

@keyframes portal-loader-type {
    0% {
        opacity: 0.25;
        transform: scaleX(0);
    }

    5% {
        opacity: 1;
        transform: scaleX(1);
    }

    90% {
        opacity: 1;
        transform: scaleX(1);
    }

    97%,
    100% {
        opacity: 0.25;
        transform: scaleX(0);
    }
}

@keyframes portal-loader-pulse {
    0% {
        opacity: 0.5;
        transform: scale(0.55);
    }

    100% {
        opacity: 0;
        transform: scale(1.25);
    }
}

@keyframes portal-loader-caret {
    50% {
        opacity: 0;
    }
}

@keyframes portal-loader-fade-in {
    from {
        opacity: 0;
        transform: translateY(0.4rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .portal-loader-page,
    .portal-loader-overlay {
        animation: none;
    }

    .portal-loader-ring {
        animation-duration: 2.4s;
    }

    .portal-loader-pulse {
        display: none;
    }

    /* Settle the editor into a fully-written, static state. */
    .portal-loader-editor .tok {
        animation: none;
        opacity: 1;
        transform: scaleX(1);
    }

    .portal-loader-caret {
        animation: none;
    }
}

/* ============================================================================
   Light-mode theme-audit fixes. Each rule corrects a component that hardcoded a
   dark fill, a faint-white fill, or light text with no light-theme override, so
   it stays readable on light surfaces. Dark mode is untouched (all scoped under
   .portal-shell.theme-light).
   ============================================================================ */

/* Floating dark popovers / panels -> solid light surface. */
.portal-shell.theme-light .case-pill-menu-options,
.portal-shell.theme-light .kanban-filter-options,
.portal-shell.theme-light .message-composer-menu,
.portal-shell.theme-light .assistant-popover,
.portal-shell.theme-light .my-time-tray-panel,
.portal-shell.theme-light .my-time-tray-expand,
.portal-shell.theme-light .customer-chooser-popover {
    background: var(--bls-surface);
    border-color: var(--bls-border);
}

/* Admin-tools API response panel + its copy button. */
.portal-shell.theme-light .admin-tools-response-body {
    background: color-mix(in srgb, var(--bls-text) 6%, transparent);
}

.portal-shell.theme-light .admin-tools-response-copy {
    background: var(--bls-surface-strong);
    border-color: var(--bls-border);
}

/* Access chips / status text that used light-on-light colours. */
.portal-shell.theme-light .user-access-chip-on {
    color: var(--bls-primary-dark);
}

.portal-shell.theme-light .user-access-status,
.portal-shell.theme-light .user-access-seats .user-access-seat-full {
    color: var(--bls-danger);
}

.portal-shell.theme-light .user-access-status-on {
    color: var(--bls-success);
}

/* Profile editor input, toggle track, chat bubble, composer chips/buttons. */
.portal-shell.theme-light .profile-editor-field input {
    background: var(--bls-surface);
    border-color: var(--bls-border);
}

.portal-shell.theme-light .portal-toggle-track {
    background: rgba(43, 43, 43, 0.25);
    box-shadow: inset 0 0 0 1px rgba(43, 43, 43, 0.12);
}

.portal-shell.theme-light .thread-other {
    background: color-mix(in srgb, var(--bls-text) 8%, transparent);
}

.portal-shell.theme-light .process-stage span {
    background: color-mix(in srgb, var(--bls-text) 10%, transparent);
}

.portal-shell.theme-light .message-composer-chip,
.portal-shell.theme-light .message-composer-add-button {
    background: color-mix(in srgb, var(--bls-text) 7%, transparent);
}

/* Library content: shelf backing, code blocks, inline code, close button, links, errors. */
.portal-shell.theme-light .library-shelf-books {
    background: color-mix(in srgb, var(--bls-text) 4%, transparent);
    border-color: var(--bls-border);
}

.portal-shell.theme-light .library-doc pre,
.portal-shell.theme-light .lb-block[data-type="code"] {
    background: color-mix(in srgb, var(--bls-text) 7%, transparent);
}

.portal-shell.theme-light .library-doc code,
.portal-shell.theme-light .library-edit-hint code {
    background: color-mix(in srgb, var(--bls-text) 8%, transparent);
}

.portal-shell.theme-light .library-details-close {
    background: color-mix(in srgb, var(--bls-text) 8%, transparent);
}

.portal-shell.theme-light .library-doc a {
    color: var(--bls-primary-dark);
}

.portal-shell.theme-light .library-form-error {
    color: var(--bls-danger);
}

/* Danger button label stays readable on the dark-red light-theme danger fill. */
.portal-shell.theme-light .btn-danger {
    color: #ffffff;
}

/* Bug-report error banner + topbar sign-out hover. */
.portal-shell.theme-light .bug-report-error {
    color: var(--bls-danger);
}

.portal-shell.theme-light .portal-account-signout:hover,
.portal-shell.theme-light .portal-account-signout:focus-visible {
    color: var(--bls-danger);
}

/* Context chooser cards, topbar context button, search key cap. */
.portal-shell.theme-light .customer-chooser-card {
    background: var(--bls-surface);
}

.portal-shell.theme-light .portal-customer-context-button {
    background: rgba(43, 43, 43, 0.05);
}

.portal-shell.theme-light .portal-search kbd {
    background: color-mix(in srgb, var(--bls-text) 10%, transparent);
}

/* Settings popover faint-white field/card fills. */
.portal-shell.theme-light .portal-settings-toggle,
.portal-shell.theme-light .portal-settings-field,
.portal-shell.theme-light .portal-settings-callout,
.portal-shell.theme-light .portal-settings-options label,
.portal-shell.theme-light .portal-toggle,
.portal-shell.theme-light .portal-permission-card-muted {
    background: color-mix(in srgb, var(--bls-text) 4%, transparent);
}

/* Attachments, split pane, confirmation panel faint fills. */
.portal-shell.theme-light .attachment-item,
.portal-shell.theme-light .attachment-thumbnail,
.portal-shell.theme-light .confirmation-panel {
    background: color-mix(in srgb, var(--bls-text) 5%, transparent);
}

.portal-shell.theme-light .case-command-split-pane {
    background: color-mix(in srgb, var(--bls-text) 3%, transparent);
}

/* Library hover affordance, section dividers, TOC rail. */
.portal-shell.theme-light .library-customer-section-header:hover,
.portal-shell.theme-light .library-customer-section-header:focus-visible {
    background: rgba(196, 78, 175, 0.1);
    border-color: rgba(196, 78, 175, 0.22);
}

.portal-shell.theme-light .library-customer-section + .library-customer-section,
.portal-shell.theme-light .library-books-mini-group + .library-books-mini-group {
    border-top-color: var(--bls-border);
}

.portal-shell.theme-light .library-toc-item {
    border-left-color: var(--bls-border);
}

/* Loader code-editor inner bits (the editor flips white in light mode). */
.portal-shell.theme-light .portal-loader-editor-file {
    color: var(--bls-muted);
}

.portal-shell.theme-light .portal-loader-editor-bar {
    background: rgba(33, 27, 31, 0.04);
    border-bottom-color: rgba(33, 27, 31, 0.1);
}

.portal-shell.theme-light .portal-loader-gutter {
    background: rgba(33, 27, 31, 0.14);
}

.portal-shell.theme-light .tok-var {
    background: rgba(33, 27, 31, 0.34);
}

.portal-shell.theme-light .tok-op {
    background: rgba(33, 27, 31, 0.2);
}

.portal-shell.theme-light .tok-br {
    background: rgba(33, 27, 31, 0.16);
}

/* Light mode: the modal dim was a heavy ~66% near-black wash over the light app, which read
   as a messy dark backdrop behind the settings panel. Soften it. The case-detail popout is no
   longer listed here because it has no scrim at all now - softening its dark frame was only
   ever treating the symptom. */
.portal-shell.theme-light .portal-settings-overlay::before {
    background: rgba(38, 24, 36, 0.3);
}

/* ============================================================
   GLOBAL FOCUS POLICY — no blue halo anywhere.
   Mouse click = NO ring; keyboard (Tab) focus = a Blacklight-pink
   ring. Low-specificity safety net: the ~70 existing
   `.something:focus-visible` magenta rules above have higher
   specificity and keep winning unchanged. Uses outline (respects
   border-radius, no layout shift). We deliberately do NOT reset
   box-shadow globally so focusable elements with a resting shadow
   (e.g. .case-card) don't flatten on click — the only blue
   box-shadow lived in the base app.css and is fixed at source.
   ============================================================ */
:root {
    --bls-focus-ring: #c44eaf;
}

.portal-shell.theme-light {
    --bls-focus-ring: #a5318a;
}

/* The sidebar stays dark in BOTH themes and does not remap --bls-primary, so a
   light-theme sidebar would paint the darker ring on near-black. Brighten it there. */
.portal-shell .portal-sidebar {
    --bls-focus-ring: #e36ccc;
}

/* Mouse / programmatic (non-keyboard) focus: no outline ring. */
:focus:not(:focus-visible) {
    outline: none;
}

/* Keyboard focus: a pink ring. Single-pseudo selector keeps specificity low so every
   curated component :focus-visible rule above still overrides it. */
:focus-visible {
    outline: 2px solid var(--bls-focus-ring);
    outline-offset: 2px;
}

/* Popover footer: keep each settings section's save/actions pinned to the bottom of the
   scrolling body so the primary action stays visible without scrolling to find it. */
.portal-settings-pages .portal-settings-actions,
.portal-settings-pages .profile-editor-actions {
    background: var(--bls-surface-strong);
    bottom: 0;
    margin-top: 0.3rem;
    padding-top: 0.65rem;
    position: sticky;
}

/* Close controls: the X (and its border) turn red on hover / focus / active. The icon
   uses currentColor, so colouring the control reddens the X. */
a[aria-label^="Close"]:hover,
a[aria-label^="Close"]:focus-visible,
a[aria-label^="Close"]:active,
button[aria-label^="Close"]:hover,
button[aria-label^="Close"]:focus-visible,
button[aria-label^="Close"]:active {
    border-color: color-mix(in srgb, var(--bls-danger, #ef4444) 60%, transparent);
    color: var(--bls-danger, #ef4444);
}

/* Add case: a translucent pink-outlined button instead of the solid gradient. The compound
   selectors out-specify .btn-primary (and the theme-light gradient safeguard). */
.portal-topbar-add-case.btn-primary,
.portal-shell.theme-light .portal-topbar-add-case.btn-primary {
    background: color-mix(in srgb, var(--bls-primary) 14%, transparent);
    border: 1px solid var(--bls-primary);
    box-shadow: none;
    color: var(--bls-primary-dark);
}

.portal-topbar-add-case.btn-primary:hover,
.portal-topbar-add-case.btn-primary:focus-visible,
.portal-shell.theme-light .portal-topbar-add-case.btn-primary:hover,
.portal-shell.theme-light .portal-topbar-add-case.btn-primary:focus-visible {
    background: color-mix(in srgb, var(--bls-primary) 24%, transparent);
    border-color: var(--bls-primary);
    color: var(--bls-primary-dark);
}

/* ============================================================
   Accounts & access drill-down: Clients → Teams → Members.
   Square tiles for clients/teams, whole-width search, breadcrumb.
   ============================================================ */
.account-drill-breadcrumb {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.6rem;
}

.account-crumb {
    background: transparent;
    border: 0;
    color: var(--bls-muted);
    cursor: pointer;
    font: inherit;
    font-weight: 700;
    padding: 0.15rem 0.1rem;
}

.account-crumb:not(.account-crumb-current):hover {
    color: var(--bls-primary-dark);
    text-decoration: underline;
}

.account-crumb-current {
    color: var(--bls-text);
    cursor: default;
}

.account-crumb-sep {
    color: var(--bls-muted);
    opacity: 0.6;
}

.account-drill-search {
    margin-bottom: 0.85rem;
    width: 100%;
}

/* Square tiles for both clients and teams. */
.account-tile-grid {
    display: grid;
    gap: 0.75rem;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 9.5rem), 1fr));
}

.account-tile {
    align-items: center;
    aspect-ratio: 1 / 1;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    flex-direction: column;
    font: inherit;
    gap: 0.5rem;
    justify-content: center;
    padding: 0.9rem;
    text-align: center;
    transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
}

.account-tile:hover,
.account-tile:focus-visible {
    border-color: rgba(196, 78, 175, 0.55);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    outline: none;
    transform: translateY(-1px);
}

.account-tile strong {
    font-size: 0.92rem;
    line-height: 1.2;
    overflow-wrap: anywhere;
}

.account-tile > span {
    color: var(--bls-muted);
    font-size: 0.78rem;
}

.account-tile-team-icon {
    align-items: center;
    background: rgba(196, 78, 175, 0.14);
    border-radius: 50%;
    color: var(--bls-primary-dark);
    display: inline-flex;
    flex: 0 0 auto;
    height: 2.6rem;
    justify-content: center;
    width: 2.6rem;
}

/* Tiles rendered as links (dashboard status deep-links) keep the button-tile look. */
a.account-tile {
    text-decoration: none;
}

/* In-context dashboard: the tile roundel carries the bucket's case count. */
.kanban-status-count {
    font-size: 1.05rem;
    font-weight: 800;
}

/* Detail header (account/team page): title takes the slack so actions sit on the right. */
.account-access-detail-title {
    margin-right: auto;
}

.account-new-team {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px dashed var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-muted);
    display: flex;
    gap: 0.5rem;
    margin-top: 0.75rem;
    padding: 0.55rem 0.75rem;
}

.account-new-team input {
    background: transparent;
    border: 0;
    color: var(--bls-text);
    flex: 1 1 auto;
    font: inherit;
    min-width: 0;
}

.account-new-team input:focus {
    outline: none;
}

.account-add-to-team {
    display: grid;
    gap: 0.5rem;
}

/* ── Internal admin AI assistant ────────────────────────────────────────────
   App-wide floating launcher (bottom-right) + chat panel, rendered once in
   PortalLayout and self-gated on ai.use. The panel reuses the shared chat
   primitives (.assistant-thread, .chat-*, .assistant-composer); these rules add
   the floating frame and the write-action confirm card. The panel scrolls
   internally so the page body never has to. */
.admin-assistant-widget {
    /* Anchored above the phone bottom bar: the token is 0px until P1's mobile
       nav sets it at phone widths, so this is a no-op on desktop today and
       clears the 56px bar automatically once it exists. */
    bottom: calc(var(--portal-mobile-bar-height, 0px) + clamp(0.5rem, 1.2vw, 0.85rem));
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.75rem;
    position: fixed;
    right: clamp(0.5rem, 1.2vw, 0.85rem);
    z-index: 70;
}

/* Transparent, border-only, accent-glyph launcher — the same reserved, business-like
   style as the topbar "Raise a case" action, scaled up to a floating button. The border
   + soft shadow define it; the line-art robot face inside does the personality. */
.admin-assistant-launcher {
    align-items: center;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    box-shadow: var(--bls-shadow);
    color: var(--bls-primary-dark);
    cursor: pointer;
    display: inline-flex;
    height: 3.25rem;
    justify-content: center;
    transition: transform 140ms ease, box-shadow 140ms ease, background-color 140ms ease, border-color 140ms ease, color 140ms ease;
    width: 3.25rem;
}

.admin-assistant-launcher:hover,
.admin-assistant-launcher:focus-visible {
    background: rgba(196, 78, 175, 0.14);
    border-color: rgba(196, 78, 175, 0.55);
    color: var(--bls-primary-dark);
    outline: none;
    transform: translateY(-1px);
}

.admin-assistant-launcher svg[blazicon] {
    height: 1.45rem;
    width: 1.45rem;
}

.admin-assistant-launcher-glyph {
    align-items: center;
    display: inline-flex;
    justify-content: center;
}

/* When the panel is open the launcher becomes a solid "close" affordance. */
.admin-assistant-launcher-open {
    background: var(--bls-surface-strong);
    border-color: var(--bls-border);
    color: var(--bls-text);
}

/* ── Line-art robot mascot (closed launcher) ──────────────────────────────────
   currentColor inherits the launcher's accent, so the whole face is one tone. The
   eyes blink in unison and the antenna emits a quiet signal blip, giving the closed
   button a touch of life without losing the restrained, logo-like read. */
.admin-assistant-launcher .bls-bot {
    height: 1.7rem;
    width: 1.7rem;
}

/* Eyes blink and the antenna blip pulse share ONE 5s beat so the launcher
   repeats the same rhythm every loop: the blip brightens in time with the
   blink (both peak at 92%) instead of drifting on a separate cycle. */
.bls-bot-eye {
    transform-box: fill-box;
    transform-origin: center;
    animation: bls-bot-blink 5s ease-in-out infinite;
}

.bls-bot-blip {
    transform-box: fill-box;
    transform-origin: center;
    animation: bls-bot-signal 5s ease-in-out infinite;
}

/* A friendlier, quicker beat while hovering/focused — both cues keep the same
   period so they stay in phase. */
.admin-assistant-launcher:hover .bls-bot-eye,
.admin-assistant-launcher:focus-visible .bls-bot-eye,
.admin-assistant-launcher:hover .bls-bot-blip,
.admin-assistant-launcher:focus-visible .bls-bot-blip {
    animation-duration: 2.4s;
}

@keyframes bls-bot-blink {
    0%, 86%, 100% { transform: scaleY(1); }
    92% { transform: scaleY(0.12); }
}

@keyframes bls-bot-signal {
    0%, 80%, 100% { opacity: 0.4; }
    92% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .bls-bot-eye,
    .bls-bot-blip {
        animation: none;
    }
}

.admin-assistant-panel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    /* DO NOT drop --portal-mobile-bar-height from these two. The widget is
       position: fixed with only a `bottom`, so it grows UPWARD and any excess
       leaves the viewport through the TOP edge, where nothing can scroll it back
       into view - the header row (Previous conversations / New chat / Close) is
       what disappears first. The `bottom` at .admin-assistant-widget adds the bar
       token, so the height budget must subtract the same token or the two drift
       apart by 56-90px on a phone. The 7rem covers the launcher (3.25rem), the
       column gap (0.75rem) and the widget's own bottom clamp, with slack. */
    height: min(34rem, calc(100dvh - 7rem - var(--portal-mobile-bar-height, 0px)));
    max-height: calc(100dvh - 7rem - var(--portal-mobile-bar-height, 0px));
    overflow: hidden;
    padding: 1rem 1rem 0.85rem;
    width: min(24rem, calc(100vw - 2rem));
}

.admin-assistant-header {
    align-items: flex-start;
    display: flex;
    flex: 0 0 auto;
    gap: 0.75rem;
    justify-content: space-between;
}

.admin-assistant-header h2 {
    margin: 0;
}

.admin-assistant-header .portal-eyebrow {
    margin: 0;
}

.admin-assistant-header-actions {
    display: flex;
    flex: 0 0 auto;
    gap: 0.4rem;
}

/* Conversation history list: swaps in for the thread while the operator picks a
   previous conversation to resume. Scrolls like the thread does. */
.admin-assistant-history {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.35rem;
    min-height: 0;
    overflow-y: auto;
    padding: 0.5rem 0.25rem;
}

.admin-assistant-history-item {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.55rem 0.7rem;
    text-align: left;
}

.admin-assistant-history-item:hover,
.admin-assistant-history-item:focus-visible {
    border-color: var(--bls-primary);
}

/* The current-conversation highlight lives on the row wrapper; keep the visual
   treatment on the item card itself. */
.admin-assistant-history-row.admin-assistant-history-current .admin-assistant-history-item {
    border-color: var(--bls-primary);
}

.admin-assistant-history-item-title {
    font-size: 0.86rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-assistant-history-item-meta {
    color: var(--bls-text-muted);
    font-size: 0.75rem;
}

/* History management: search, group labels, row actions. Rows reveal their
   actions on hover/focus so the list stays quiet; the rename input and the
   customer picker swap in-place. */
.admin-assistant-history-search {
    width: 100%;
    margin-bottom: 0.5rem;
    padding: 0.4rem 0.65rem;
    border-radius: 0.6rem;
    border: 1px solid var(--bls-border);
    background: var(--bls-surface);
    color: var(--bls-text);
    font: inherit;
    font-size: 0.85rem;
}

.admin-assistant-history-group {
    margin: 0.65rem 0 0.15rem;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--bls-muted);
}

.admin-assistant-history-row {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.admin-assistant-history-row .admin-assistant-history-item {
    flex: 1 1 auto;
    min-width: 0;
}

.admin-assistant-history-actions {
    display: none;
    flex: 0 0 auto;
    gap: 0.1rem;
    align-items: center;
}

.admin-assistant-history-row:hover .admin-assistant-history-actions,
.admin-assistant-history-row:focus-within .admin-assistant-history-actions {
    display: inline-flex;
}

.admin-assistant-history-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.7rem;
    height: 1.7rem;
    padding: 0;
    border: none;
    border-radius: 0.5rem;
    background: transparent;
    color: var(--bls-muted);
    cursor: pointer;
}

.admin-assistant-history-action:hover {
    background: var(--bls-surface-strong);
    color: var(--bls-text);
}

.admin-assistant-history-action svg {
    width: 0.95rem;
    height: 0.95rem;
}

.admin-assistant-history-action-pinned {
    color: var(--bls-primary);
}

.admin-assistant-history-rename {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.35rem 0.55rem;
    border-radius: 0.5rem;
    border: 1px solid var(--bls-primary);
    background: var(--bls-surface);
    color: var(--bls-text);
    font: inherit;
    font-size: 0.85rem;
}

/* "File under customer": the searchable PortalSelect plus its cancel button. Top-aligned
   because the picker's menu renders in normal flow, so centring would drag the cancel
   button down to the middle of the open option list. */
.admin-assistant-history-move-row {
    display: flex;
    align-items: flex-start;
    gap: 0.25rem;
    flex: 1 1 auto;
    min-width: 0;
}

.admin-assistant-history-move-row .portal-select {
    flex: 1 1 auto;
}

.admin-assistant-history-error {
    margin: 0.25rem 0;
    font-size: 0.78rem;
    color: var(--bls-danger);
}

/* The thread is the only growing region; everything else is fixed-height, so the
   panel scrolls inside the thread and the page body stays put. */
.admin-assistant-panel .assistant-thread {
    flex: 1 1 auto;
    font-size: 0.86rem;
    gap: 0.16rem;
    min-height: 0;
    padding: 0.5rem 0.25rem;
}

.admin-assistant-panel .chat-bubble {
    max-width: 90%;
}

/* Quick-action chips under the admin assistant's context-aware greeting. */
.admin-assistant-quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0 1rem 0.5rem;
}

.admin-assistant-quick-actions .btn {
    font-size: 0.8rem;
    padding: 0.3rem 0.7rem;
}

.admin-assistant-composer {
    flex: 0 0 auto;
    margin: 0;
}

/* Write-action confirm card: a warning-tinted block that reads as "awaiting your
   go-ahead". Sits between the thread and the composer; the composer disables while
   it's showing so the only next move is approve/deny. */
.admin-assistant-confirm {
    background: color-mix(in srgb, var(--bls-warning) 12%, var(--bls-surface-strong));
    border: 1px solid color-mix(in srgb, var(--bls-warning) 45%, var(--bls-border));
    border-radius: var(--bls-radius-md);
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 0.6rem;
    max-height: 40%;
    overflow: auto;
    padding: 0.75rem;
}

.admin-assistant-confirm-head {
    align-items: flex-start;
    display: flex;
    gap: 0.6rem;
}

.admin-assistant-confirm-head .portal-eyebrow {
    color: var(--bls-warning);
    margin: 0 0 0.15rem;
}

.admin-assistant-confirm-head strong {
    color: var(--bls-ink);
    display: block;
    font-family: var(--bls-font-heading);
    line-height: 1.3;
}

.admin-assistant-confirm-badge {
    align-items: center;
    background: color-mix(in srgb, var(--bls-warning) 22%, transparent);
    border-radius: 999px;
    color: var(--bls-warning);
    display: inline-flex;
    flex: 0 0 auto;
    height: 1.9rem;
    justify-content: center;
    width: 1.9rem;
}

.admin-assistant-confirm-badge svg[blazicon] {
    height: 1.05rem;
    width: 1.05rem;
}

.admin-assistant-confirm-args {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin: 0;
}

.admin-assistant-confirm-arg {
    display: grid;
    gap: 0.1rem 0.6rem;
    grid-template-columns: minmax(6rem, 9rem) 1fr;
}

.admin-assistant-confirm-arg dt {
    color: var(--bls-muted);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.admin-assistant-confirm-arg dd {
    color: var(--bls-text);
    font-size: 0.84rem;
    margin: 0;
    overflow-wrap: anywhere;
}

.admin-assistant-confirm-reason {
    display: flex;
    flex-direction: column;
    font-size: 0.78rem;
    gap: 0.25rem;
}

.admin-assistant-confirm-reason span {
    color: var(--bls-muted);
    font-weight: 700;
}

.admin-assistant-confirm-reason textarea {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    color: var(--bls-text);
    font: inherit;
    padding: 0.5rem 0.6rem;
    resize: vertical;
    width: 100%;
}

.admin-assistant-confirm-reason textarea:focus {
    border-color: var(--bls-primary);
    outline: none;
}

.admin-assistant-confirm-actions {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
}

.admin-assistant-deny {
    color: var(--bls-danger);
}

@media (max-width: 600px) {
    .admin-assistant-panel {
        /* height and max-height MUST move together here: max-height always beats
           height, so a lone `height` in this block is silently vetoed by the base
           rule's max-height (this was inert until 2026-07-29). Both subtract the
           bar token for the reason written on the base rule. */
        height: calc(100dvh - 6rem - var(--portal-mobile-bar-height, 0px));
        max-height: calc(100dvh - 6rem - var(--portal-mobile-bar-height, 0px));
        width: calc(100vw - 2rem);
    }
}

/* The launcher is z-70, which is ABOVE the case popout (backdrop 15, panel 30) - and
   from <=1100px P4 makes that popout a full-frame `position: fixed; inset: 0` sheet,
   so the floating launcher lands inside it. At phone width its 3.25rem circle sits
   64-116px off the bottom edge, right edge 8-60px, which is squarely on the send end
   of the popout's pinned composer: the top ~11px of Send was covered and a tap there
   opened a full-screen AI chat over the case instead. Suppress it while a popout is
   up rather than re-rung it, so the launcher keeps one z-index everywhere.
   The two shared bottom sheets need no rule here - they were lifted to 121/122, so
   their scrim already covers and blocks the launcher. */
@supports selector(body:has(.case-detail-overlay)) {
    @media (max-width: 1100px) {
        body:has(.case-detail-overlay) .admin-assistant-widget {
            display: none;
        }
    }
}

/* ---- Report dashboard popup (ReportViewerOverlay) ----------------------------------
   A ~90% popover over the whole viewport (sidebar included), mirroring the settings
   overlay pattern: fixed position, dimmed scrim, centred rounded popover, fixed header
   + scrolling body. Closed via the header X, backdrop click, or Esc. */
.report-viewer-overlay {
    align-items: center;
    display: grid;
    inset: 0;
    justify-items: center;
    overscroll-behavior: contain;
    padding: var(--portal-overlay-padding, 1.5rem);
    position: fixed;
    z-index: 80;
}

.report-viewer-overlay::before {
    background: rgba(11, 7, 15, 0.66);
    content: "";
    inset: 0;
    pointer-events: none;
    position: absolute;
}

/* Lock background scroll while the dashboard is open (mirrors the
   body:has(.portal-page-has-overlay) rule used by the other overlays). */
body:has(.report-viewer-overlay) {
    overflow: hidden;
}

.report-viewer-popover {
    background: var(--bls-bg, #0b070f);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 14px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    display: flex;
    flex-direction: column;
    height: 90dvh;
    max-height: calc(100dvh - (var(--portal-overlay-padding, 1.5rem) * 2));
    overflow: hidden;
    position: relative;
    width: min(1600px, 92vw);
    z-index: 1;
}

.report-viewer-popover-header {
    align-items: center;
    border-bottom: 1px solid var(--bls-border, #2a2a31);
    display: flex;
    gap: 0.75rem;
    justify-content: space-between;
    padding: 1rem 1.25rem;
}

.report-viewer-popover-title { display: flex; align-items: baseline; gap: 0.75rem; }
.report-viewer-popover-title h3 { margin: 0; }
.report-viewer-popover-title small { color: var(--bls-muted, #aaa7ad); }

.report-viewer-popover-close {
    background: transparent;
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 8px;
    color: var(--bls-text, #f3f0f5);
    cursor: pointer;
    display: inline-flex;
    padding: 0.4rem;
}
.report-viewer-popover-close:hover { background: var(--bls-surface, #141417); }

/* Only the body scrolls; the header stays put. */
.report-viewer-popover-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 1.25rem;
}

.report-viewer-summary {
    background: var(--bls-surface, #141417);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 10px;
    margin-bottom: 1.25rem;
    padding: 1rem 1.25rem;
}
.report-viewer-summary .portal-eyebrow { color: var(--bls-muted, #aaa7ad); }

/* Two-column data table for Table-type panels. */
.report-table { border-collapse: collapse; width: 100%; font-size: 0.85rem; }
.report-table td { border-bottom: 1px solid var(--bls-border, #2a2a31); padding: 0.4rem 0.5rem; }
.report-table td:last-child { text-align: right; font-variant-numeric: tabular-nums; }

/* Detail-table panel (e.g. Stalled Open Cases): 8 columns can overflow narrow layouts. */
.report-detail-scroll { overflow-x: auto; }
.report-detail-table { width: 100%; }

/* Small-screen / short-viewport: let the popover use nearly the whole frame. */
@media (max-width: 720px), (max-height: 600px) {
    .report-viewer-popover {
        height: calc(100dvh - (var(--portal-overlay-padding, 0.75rem) * 2));
        width: calc(100vw - (var(--portal-overlay-padding, 0.75rem) * 2));
    }
}

/* ApexCharts renders its own SVG/HTML text, so it does not inherit the app theme. Drive the
   axis labels, legend, and gridlines from the design-system tokens so they stay legible on
   BOTH the dark and light themes (--bls-text/--bls-border flip with .theme-light). Scoped to
   axis + legend only - on-slice donut percentages keep their own (white) colour. */
.report-viewer .apexcharts-xaxis-label,
.report-viewer .apexcharts-yaxis-label {
    fill: var(--bls-text, #ece8ec) !important;
}
.report-viewer .apexcharts-legend-text {
    color: var(--bls-text, #ece8ec) !important;
}
.report-viewer .apexcharts-gridline {
    stroke: var(--bls-border, #2a2a31) !important;
}

/* Conversation text size (Settings > Accessibility). The thread + timeline panels zoom by
   a per-user multiplier driven from --bls-conversation-scale (set on .portal-shell by
   PortalLayout). zoom scales the whole region (text + spacing) uniformly; Chromium-only,
   which the portal targets. Default 1 leaves sizing untouched. */
.thread-panel,
.timeline-panel {
    zoom: var(--bls-conversation-scale, 1);
}



/* WhatsApp-style composer bar: + on the left, an auto-growing input, send on the right
   (rendered only when there is content). Replaces the old stacked label+textarea+toolbar. */
.message-composer-bar {
    align-items: flex-end;
    background: var(--bls-surface, #141417);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 1.4rem;
    display: flex;
    gap: 0.4rem;
    /* R5 — THE fix for "It should not keep going past the right hand side of the box".
       The bar is a grid item of .message-composer (display:grid with no
       grid-template-columns), so it lands in an IMPLICIT auto track and inherits a
       content-based automatic minimum size; the flex row inside then has no reason to
       shrink and shoves the send button out of the pane. Measured in a rig of the real
       ancestor chain: a 586px pill inside a 318px pane, i.e. 251px past the right
       border-box edge; with this one declaration, 221px inside 318px.
       min-width:0 on the textarea alone changes NOTHING — overflow-y:auto below
       already makes it a scroll container, so its own automatic minimum is already 0.
       The overflow was never the typed text (textareas soft-wrap by default and
       nothing here overrides white-space/wrap): it was always the BOX. */
    min-width: 0;
    padding: 0.35rem 0.4rem;
}

.message-composer-bar .message-composer-add {
    flex: 0 0 auto;
}

.message-composer-bar .message-composer-input {
    background: transparent;
    border: 0;
    /* Cancels the 0.75rem radius leaking from the base .message-composer-input rule
       ~7000 lines earlier (which this block otherwise overrides property by property).
       The pill owns the rounding. */
    border-radius: 0;
    color: var(--bls-text, #ece8ec);
    field-sizing: content;       /* auto-grow with content (Chromium) */
    flex: 1 1 auto;
    font: inherit;
    line-height: 1.4;
    /* ~6 lines, then scroll. Tokenised so phones can shorten it: 9rem is a large
       slice of a phone viewport with the soft keyboard up, and the cap measurably
       engages in an ordinary ~350px pane. */
    max-height: var(--bls-composer-max-height, 9rem);
    min-height: 1.6rem;
    /* Belt-and-braces with the bar's min-width (matches the .assistant-composer-input
       idiom), and overflow-wrap:anywhere so an unbreakable URL breaks INSIDE the pill
       instead of setting a min-content width the pill has to honour. */
    min-width: 0;
    overflow-wrap: anywhere;
    overflow-y: auto;
    padding: 0.35rem 0.25rem;
    resize: none;
}

/* R5 focus ring. The base .message-composer-input:focus-visible ring (0,2,0) drew a
   3px halo at the INPUT's 0.75rem radius — verified in-browser as a magenta rectangle
   traced inside the 1.4rem pill. Killed here at (0,3,0) and re-drawn on the pill
   itself below, so the indicator still exists (which .portal-a11y-contrast's
   :focus-visible expectations rely on) at the right geometry. */
.message-composer-bar .message-composer-input:focus,
.message-composer-bar .message-composer-input:focus-visible {
    box-shadow: none;
    outline: none;
}

/* :has(input:focus-visible), NOT :focus-within: the pill also contains the "add to
   message" menu button and the send button, each of which draws its own .btn focus ring -
   :focus-within would ring the whole pill on top of theirs and point at the wrong control.
   Chromium-only selector, which is what the portal targets (see the :has() page-overflow
   rules in the case-detail chain). */
.message-composer-bar:has(.message-composer-input:focus-visible) {
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
}

.message-composer-bar .message-composer-send,
.message-composer-bar .message-composer-send-text {
    flex: 0 0 auto;
    align-self: flex-end;
}

/* Status moves below the bar and only shows when set. */
.message-composer .message-composer-status {
    display: block;
    margin-top: 0.35rem;
    font-size: 0.75rem;
}

/* ==== ACCESSIBILITY (Settings > Accessibility) ====
   Each option is a class (or dir attribute) applied to .portal-shell by PortalLayout from
   the user's saved LastAccessState, so they apply app-wide and persist between sign-ins. */

/* Dyslexia-friendly font. OpenDyslexic (SIL OFL) is bundled in wwwroot/fonts/; the Comic Sans
   MS / Verdana fallback stack covers the brief window before the woff2 loads. */
@font-face {
    font-family: "OpenDyslexic";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("fonts/OpenDyslexic-Regular.woff2") format("woff2");
}

@font-face {
    font-family: "OpenDyslexic";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("fonts/OpenDyslexic-Bold.woff2") format("woff2");
}

.portal-a11y-dyslexic,
.portal-a11y-dyslexic * {
    font-family: "OpenDyslexic", "Comic Sans MS", Verdana, sans-serif !important;
}

/* High contrast: stronger text/border colours (both themes), underlined links, bold
   focus. In dark shells it also pins the ORIGINAL stark look the Cinema default
   replaced - pure-black stage, heavy visible borders, solid popovers, flat shadows,
   no glass (the materials layer's vibrancy gate excludes this class too). Later in
   the file than every .theme-preset-* block on the same specificity plane, so it
   wins over all presets. */
.portal-a11y-contrast {
    --bls-bg: #000;
    --bls-surface: #000;
    --bls-surface-strong: rgb(196 78 175 / 5%);
    --bls-text: #ffffff;
    --bls-muted: #e7e7f2;
    --bls-border: rgba(255, 255, 255, 0.55);
    --bls-border-strong: rgba(255, 255, 255, 0.55);
    --bls-shadow: 0 4px 12px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.4);
    --bls-shadow-soft: 0 1px 1px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.03);
    --bls-popover-solid: rgba(20, 14, 24, 0.98);
    --bls-popover-glass: rgba(20, 14, 24, 0.98);
    --bls-hover-glow: none;
}

.portal-shell.portal-a11y-contrast.theme-light {
    --bls-bg: #eae3ea;
    --bls-surface: #ffffff;
    --bls-surface-strong: var(--light-surface-strong);
    --bls-text: #000000;
    --bls-muted: #161616;
    --bls-border: rgba(0, 0, 0, 0.6);
    --bls-border-strong: rgba(0, 0, 0, 0.6);
    --bls-popover-solid: #ffffff;
    --bls-popover-glass: #ffffff;
}

.portal-a11y-contrast a {
    text-decoration: underline;
}

.portal-a11y-contrast :focus-visible {
    outline: 3px solid var(--bls-primary, #c44eaf);
    outline-offset: 2px;
}

/* Reduce motion: neutralise non-essential animation/transition for users who opt in
   (mirrors the prefers-reduced-motion reset, but user-controlled). */
.portal-a11y-reduce-motion *,
.portal-a11y-reduce-motion *::before,
.portal-a11y-reduce-motion *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
}

/* Increased text spacing. line-height / letter-spacing / word-spacing are inherited, so
   setting them on the shell lifts most text without !important-bombing every element
   (which would break pills/badges). Values are a readable nudge toward WCAG 1.4.12. */
.portal-a11y-spacing {
    line-height: 1.7;
    letter-spacing: 0.03em;
    word-spacing: 0.12em;
}

.portal-a11y-spacing p {
    margin-bottom: 1.6em;
}

/* Right-to-left is driven by dir="rtl" on the shell (set by PortalLayout), which flips text
   direction and default alignment. The block below mirrors the bespoke PHYSICAL-property CSS
   (left/right margins & padding, absolute left/right, translateX, asymmetric border-radius,
   left/right text-align) for the main customer-facing surfaces. Everything is scoped to
   .portal-shell[dir="rtl"] so it only applies when RTL is enabled. Flex/grid rules already
   using start/end/space-between auto-mirror and are left alone. */

/* --- App shell inline padding --- */
.portal-shell[dir="rtl"].portal-shell {
    padding-left: max(calc(var(--portal-shell-inline-padding) + var(--portal-shell-inline-extra)), var(--portal-safe-right));
    padding-right: max(calc(var(--portal-shell-inline-padding) + var(--portal-shell-inline-extra)), var(--portal-safe-left));
}

/* --- Sidebar / nav --- */
.portal-shell[dir="rtl"] .portal-nav a.active::after {
    left: auto;
    right: 0;
}

.portal-shell[dir="rtl"] .portal-sidebar-footer-action {
    text-align: right;
}

.portal-shell[dir="rtl"] .portal-theme-bulb {
    margin-left: 0;
    margin-right: auto;
}

/* --- Topbar / account cluster --- */
/* RTL: the floating corner chrome pins to the top-LEFT, and its popover opens from
   that edge. */
.portal-shell[dir="rtl"] .portal-topbar-account {
    left: var(--portal-account-corner-inset);
    right: auto;
}

/* No physical left/right override here on purpose: the popover positions itself with
   inset-inline-end, which already mirrors, so re-pinning left:0 would drop the card on
   top of the cluster in RTL. Only the closed-state slide needs flipping, because
   transforms are not direction-aware. */
.portal-shell[dir="rtl"] .portal-account-popover {
    transform: translateX(-0.35rem);
}

.portal-shell[dir="rtl"] .portal-topbar-account.portal-account-pinned .portal-account-popover,
.portal-shell[dir="rtl"] .portal-profile-toggle:hover ~ .portal-account-popover,
.portal-shell[dir="rtl"] .portal-profile-toggle:focus ~ .portal-account-popover,
.portal-shell[dir="rtl"] .portal-account-popover:hover,
.portal-shell[dir="rtl"] .portal-account-popover:focus-within {
    transform: none;
}

.portal-shell[dir="rtl"] .portal-account-popover .portal-profile-copy {
    text-align: right;
}

/* --- Settings dialog --- */
.portal-shell[dir="rtl"] .portal-settings-menu-item {
    text-align: right;
}

/* --- Pills: status / priority / severity choice menus --- */
.portal-shell[dir="rtl"] .case-pill-menu-options {
    left: 0;
    right: auto;
}

.portal-shell[dir="rtl"] .case-pill-menu-option {
    text-align: right;
}

/* --- Case-board filter menus --- */
.portal-shell[dir="rtl"] .kanban-filter-options {
    left: 0;
    right: auto;
}

.portal-shell[dir="rtl"] .kanban-filter-option {
    text-align: right;
}

/* --- Case-board horizontal pan buttons + chevron glyphs --- */
.portal-shell[dir="rtl"] .kanban-pan-left {
    left: auto;
    right: 0.55rem;
}

.portal-shell[dir="rtl"] .kanban-pan-right {
    right: auto;
    left: 0.55rem;
}

.portal-shell[dir="rtl"] .kanban-pan-glyph-left {
    border-width: 2px 2px 0 0;
    transform: translateX(-0.12rem) rotate(45deg);
}

.portal-shell[dir="rtl"] .kanban-pan-glyph-right {
    border-width: 0 0 2px 2px;
    transform: translateX(0.12rem) rotate(45deg);
}

/* --- Case detail: command menu items --- */
.portal-shell[dir="rtl"] .case-command-menu-item {
    text-align: right;
}

/* --- Message composer "add" menu --- */
.portal-shell[dir="rtl"] .message-composer-menu {
    left: auto;
    right: 0;
}

.portal-shell[dir="rtl"] .message-composer-menu-item {
    text-align: right;
}

/* --- Conversation thread (SMS-style bubbles): swap the asymmetric tail corner. --- */
.portal-shell[dir="rtl"] .thread-self {
    border-radius: 1.15rem 1.15rem 1.15rem 0.3rem;
}

.portal-shell[dir="rtl"] .thread-other {
    border-radius: 1.15rem 1.15rem 0.3rem 1.15rem;
}

/* --- Admin AI chat bubbles: grouped-corner flattening + run-end tail wedge --- */
.portal-shell[dir="rtl"] .chat-bubble-assistant:not(.chat-run-start) {
    border-top-left-radius: 1.15rem;
    border-top-right-radius: 0.35rem;
}

.portal-shell[dir="rtl"] .chat-bubble-assistant:not(.chat-run-end) {
    border-bottom-left-radius: 1.15rem;
    border-bottom-right-radius: 0.35rem;
}

.portal-shell[dir="rtl"] .chat-bubble-user:not(.chat-run-start) {
    border-top-right-radius: 1.15rem;
    border-top-left-radius: 0.35rem;
}

.portal-shell[dir="rtl"] .chat-bubble-user:not(.chat-run-end) {
    border-bottom-right-radius: 1.15rem;
    border-bottom-left-radius: 0.35rem;
}

.portal-shell[dir="rtl"] .chat-bubble-assistant.chat-run-end {
    border-bottom-left-radius: 1.15rem;
    border-bottom-right-radius: 0.2rem;
}

.portal-shell[dir="rtl"] .chat-bubble-user.chat-run-end {
    border-bottom-right-radius: 1.15rem;
    border-bottom-left-radius: 0.2rem;
}

.portal-shell[dir="rtl"] .chat-bubble-assistant.chat-run-end::after {
    left: auto;
    right: -6px;
    clip-path: path("M0 0 L0 14 L7 14 C2.8 11.5 0.5 6.8 0 0 Z");
}

.portal-shell[dir="rtl"] .chat-bubble-user.chat-run-end::after {
    right: auto;
    left: -6px;
    clip-path: path("M7 0 L7 14 L0 14 C4.2 11.5 6.5 6.8 7 0 Z");
}

.portal-shell[dir="rtl"] .chat-caret {
    margin-left: 0;
    margin-right: 0.15rem;
}

/* --- Stacked (<=1100px) top-bar mode --- */
.portal-shell[dir="rtl"] .portal-nav-bug-wrap {
    margin-left: 0;
    margin-right: auto;
}

/* ---- My Time (sidebar day tracker + overlay) ----------------------------------------
   The sidebar "Add time" button became a personal day tracker: logged-vs-target figure
   over a 3px progress track. Clicking it opens the ~90% MyTimeOverlay popover (same
   chrome idiom as .report-viewer-overlay): day navigation, target editor, entry cards
   with CRM/Flow sync badges, and an embedded quick-log. All colours come from the
   --bls-* tokens so both themes work; the sidebar itself is always dark. */

.portal-nav .portal-day-tracker .portal-nav-label {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 0;
}

.portal-day-tracker-figure {
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.portal-day-tracker-bar {
    background: rgba(255, 255, 255, 0.16);
    border-radius: 999px;
    display: block;
    height: 3px;
    overflow: hidden;
    width: 100%;
}

.portal-day-tracker-bar span {
    background: var(--bls-primary);
    border-radius: inherit;
    display: block;
    height: 100%;
    transition: width 0.3s ease;
}

/* A failed Flow sync today tints the clock amber as a nudge to open the day view. */
.portal-day-tracker-warn .portal-nav-icon {
    color: var(--bls-warning);
}

/* The collapsed sidebar hides labels with opacity/width 0 - but this label is
   flex: 1 1 auto, so it still GROWS invisibly and shoves the calendar icon
   off-centre. Kill its flex share in collapsed mode too (expanded is untouched). */
.portal-sidebar-toggle-state:checked + .portal-shell .portal-nav .portal-day-tracker .portal-nav-label {
    flex: 0 0 0px;
}

/* ---- My Time overlay (popover chrome mirrors .report-viewer-overlay) ---- */
.my-time-overlay {
    align-items: center;
    display: grid;
    inset: 0;
    justify-items: center;
    overscroll-behavior: contain;
    padding: var(--portal-overlay-padding, 1.5rem);
    position: fixed;
    z-index: 80;
}

.my-time-overlay::before {
    background: rgba(11, 7, 15, 0.66);
    content: "";
    inset: 0;
    pointer-events: none;
    position: absolute;
}

/* Lock background scroll while the day view is open. */
body:has(.my-time-overlay) {
    overflow: hidden;
}

.my-time-popover {
    background: var(--bls-bg, #0b070f);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 14px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    display: flex;
    flex-direction: column;
    height: 90dvh;
    max-height: calc(100dvh - (var(--portal-overlay-padding, 1.5rem) * 2));
    overflow: hidden;
    position: relative;
    width: min(880px, 92vw);
    z-index: 1;
}

.my-time-header {
    align-items: center;
    border-bottom: 1px solid var(--bls-border);
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    justify-content: space-between;
    padding: 1rem 1.25rem;
}

.my-time-day-nav {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.my-time-day-title {
    margin: 0;
    min-width: 13rem;
    text-align: center;
}

.my-time-nav-btn,
.my-time-close,
.my-time-icon-btn {
    align-items: center;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 8px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    padding: 0.35rem;
}

.my-time-nav-btn:hover,
.my-time-close:hover,
.my-time-icon-btn:hover {
    background: var(--bls-surface-strong);
}

.my-time-icon-btn.is-danger:hover {
    border-color: var(--bls-danger);
    color: var(--bls-danger);
}

.my-time-today-btn {
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    padding: 0.25rem 0.7rem;
    text-transform: uppercase;
}

.my-time-today-btn:hover {
    color: var(--bls-text);
}

.my-time-header-right {
    align-items: center;
    display: flex;
    gap: 0.75rem;
}

.my-time-total {
    align-items: center;
    color: var(--bls-muted);
    display: flex;
    font-variant-numeric: tabular-nums;
    gap: 0.35rem;
}

.my-time-total strong {
    color: var(--bls-text);
    font-size: 1.05rem;
}

.my-time-target-input {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: 8px;
    color: var(--bls-text);
    font: inherit;
    padding: 0.2rem 0.4rem;
    width: 5rem;
}

.my-time-progress {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    height: 8px;
    overflow: hidden;
    width: clamp(6rem, 18vw, 12rem);
}

.my-time-progress span {
    background: var(--bls-primary);
    border-radius: inherit;
    display: block;
    height: 100%;
    transition: width 0.3s ease;
}

/* Amber fill while the day is still under target; magenta once the target is met. */
.my-time-progress-under span {
    background: var(--bls-warning);
}

/* A failed Flow sync anywhere in the day tints the track edge as a warning. */
.my-time-progress-warn {
    border-color: var(--bls-danger);
}

/* Only the body scrolls; the header stays put. */
.my-time-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 1rem;
    min-height: 0;
    overflow-y: auto;
    padding: 1.25rem;
}

.my-time-empty {
    padding: 2rem 1rem;
}

.my-time-entry-list {
    display: grid;
    gap: 0.6rem;
}

.my-time-entry {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow-soft);
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    padding: 0.75rem 1rem;
}

.my-time-entry-main {
    display: grid;
    gap: 0.2rem;
    min-width: 0;
}

.my-time-entry-title {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.portal-case-chip {
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-primary-dark);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    padding: 0.1rem 0.55rem;
    text-decoration: none;
    white-space: nowrap;
}

.portal-case-chip:hover {
    border-color: var(--bls-primary);
}

.my-time-entry-work {
    color: var(--bls-text);
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.my-time-entry-when {
    color: var(--bls-muted);
    font-variant-numeric: tabular-nums;
}

.my-time-entry-meta {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    gap: 0.4rem;
    justify-content: flex-end;
}

.my-time-entry-duration {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* Small uppercase status chips: CRM posted, Flow sync outcome, billable. The
   billable / Flow-off / Flow-none chips keep the muted base look. */
.my-time-badge {
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    padding: 0.14rem 0.5rem;
    text-transform: uppercase;
    white-space: nowrap;
}

.my-time-badge-crm,
.my-time-badge-flow-synced {
    border-color: var(--bls-success);
    color: var(--bls-success);
}

.my-time-badge-flow-failed {
    border-color: var(--bls-danger);
    color: var(--bls-danger);
}

.my-time-badge-flow-pending {
    border-color: var(--bls-warning);
    color: var(--bls-warning);
}

.my-time-retry {
    background: transparent;
    border: 1px solid var(--bls-danger);
    border-radius: 999px;
    color: var(--bls-danger);
    cursor: pointer;
    font-size: 0.68rem;
    font-weight: 600;
    padding: 0.14rem 0.6rem;
}

.my-time-retry:hover:not(:disabled) {
    background: var(--bls-danger);
    color: var(--bls-bg);
}

.my-time-retry:disabled {
    cursor: default;
    opacity: 0.6;
}

/* The per-entry edit form reuses the case-time-edit / case-timer-* classes; it just
   needs to span the whole card. */
.my-time-entry .my-time-edit {
    flex: 1 1 auto;
}

/* ---- add-time section (embedded GlobalTimeLog) ---- */
.my-time-add {
    border-top: 1px solid var(--bls-border);
    display: grid;
    gap: 0.6rem;
    margin-top: auto;
    padding-top: 1rem;
}

.my-time-add-toggle {
    align-items: center;
    background: transparent;
    border: 1px dashed var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-muted);
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 0.5rem;
    justify-content: center;
    padding: 0.65rem;
}

.my-time-add-toggle:hover {
    border-color: var(--bls-primary);
    color: var(--bls-text);
}

/* Embedded quick-log: keep the picker header dropdowns visible (same overflow rule as
   the standalone overlay panel). */
.my-time-add .quick-time-log,
.my-time-add .case-timer {
    overflow: visible;
}

/* Small-screen / short-viewport: let the popover use nearly the whole frame. */
@media (max-width: 720px), (max-height: 600px) {
    .my-time-popover {
        height: calc(100dvh - (var(--portal-overlay-padding, 0.75rem) * 2));
        width: calc(100vw - (var(--portal-overlay-padding, 0.75rem) * 2));
    }

    .my-time-entry {
        flex-direction: column;
    }

    .my-time-entry-meta {
        justify-content: flex-start;
    }
}

/* ═══════════════════════════════ My-time tray ════════════════════════════════
   Fixed right-edge vertical clock pill that slides out a compact mini day card.
   Sits mid-right (top: 40%) with z-index 60, deliberately below AdminAssistantWidget
   (bottom-right, z-index 70) so the two fixed widgets never fight or overlap. */
.my-time-tray {
    position: fixed;
    right: 0;
    top: 40%;
    z-index: 60;
}

/* Phones: the fixed right-edge pill eats horizontal room the content needs and
   overlaps the scroll edge. The P1 Menu flyout carries My time on phones (the
   sidebar nav's day-tracker button already offers the same entry point), so
   the tray disappears entirely rather than shrinking. */
@media (max-width: 600px) {
    .my-time-tray {
        display: none;
    }
}

/* The rounded-left dark pill toggle — kanban-nav styling: quiet dark surface,
   magenta on hover/focus, flush against the viewport edge (until dragged).

   SIZE CONTRACT: every dimension is multiplied by --portal-tray-pill-scale (a
   unitless factor, default 1, emitted at the shell root by the Settings size
   slider) so the pill follows the slider with zero code coupling. At the
   default scale the computed footprint is EXACTLY the historical static
   button's (0.85/0.6/0.7rem padding around a 1.2rem glyph).

   touch-action: none — the pill is pointer-draggable (portal.js floatingTray);
   a touch drag must move the pill, not scroll the page under it. */
/* A full CIRCLE, not the old rounded-left half-pill: that shape existed to sit flush
   against the right viewport edge, and once the pill became draggable (2026-08-17) its
   flat, borderless right side looked bitten-off anywhere else on screen. Symmetric
   padding + full radius reads correctly at every position, edge included. */
.my-time-tray-toggle {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    box-shadow: var(--bls-shadow);
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    padding: calc(0.85rem * var(--portal-tray-pill-scale, 1));
    touch-action: none;
    transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease;
}

.my-time-tray-toggle svg[blazicon] {
    height: calc(1.2rem * var(--portal-tray-pill-scale, 1));
    width: calc(1.2rem * var(--portal-tray-pill-scale, 1));
}

/* ── Floating pill: live timer face + drag state ─────────────────────────────
   REST-SIZE CONTRACT: at rest the pill occupies exactly the static button's
   footprint — the mini analogue face fills the same 1.2rem box as the clock
   glyph (both × --portal-tray-pill-scale). Hover/focus may PRESENT the face
   larger, but only via transform (no layout growth), so the footprint never
   changes and the face always returns to rest size. */
.my-time-tray-face {
    display: block;
    height: calc(1.2rem * var(--portal-tray-pill-scale, 1));
    width: calc(1.2rem * var(--portal-tray-pill-scale, 1));
    transition: transform 140ms ease;
}

.my-time-tray-toggle:hover .my-time-tray-face,
.my-time-tray-toggle:focus-visible .my-time-tray-face {
    transform: scale(1.3);
}

/* The hands step once a second server-side; the 1s linear transition turns each
   step into a continuous sweep (same mechanism as .case-timer-hand). Movement
   only — no bounce, per the no-toy-motion rule. */
.my-time-tray-face-hand {
    transform-box: view-box;
    transform-origin: 20px 20px;
    transition: transform 1s linear;
}

.my-time-tray-dragging {
    user-select: none;
}

.my-time-tray-dragging .my-time-tray-toggle {
    cursor: grabbing;
}

@media (prefers-reduced-motion: reduce) {
    .my-time-tray-face,
    .my-time-tray-face-hand {
        transition: none;
    }
}

.portal-shell.portal-a11y-reduce-motion .my-time-tray-face,
.portal-shell.portal-a11y-reduce-motion .my-time-tray-face-hand {
    transition: none;
}

.my-time-tray-toggle:hover,
.my-time-tray.is-open .my-time-tray-toggle {
    background: rgba(196, 78, 175, 0.14);
    border-color: rgba(196, 78, 175, 0.55);
    color: var(--bls-primary-dark);
}

.my-time-tray-toggle:focus-visible {
    color: var(--bls-primary-dark);
    outline: 2px solid var(--bls-primary);
    outline-offset: 2px;
}

/* The quick-log widget slides out leftward from the pill; the kanban-style edge
   arrow expands the mini day view alongside it (a second column, so logged time and
   the logger are visible together). Anchored to the tray's top so it grows downward;
   capped and scrollable columns so a long day never runs off-screen. */
.my-time-tray-panel {
    align-items: stretch;
    animation: my-time-tray-in 160ms ease;
    /* Floats over the case list: needs the solid popover surface, not the translucent
       inline one (same treatment as .case-pill-menu-options / .message-composer-menu). */
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    box-shadow: var(--bls-shadow);
    display: flex;
    gap: 0.6rem;
    /* VIEWPORT-CLAMPED, not anchored: the panel used to hang from the tray pill
       (position:absolute; top:0 on a pill fixed at 40dvh) and grow DOWNWARD, so its
       85dvh max-height was measured from 40% down - anything taller than the space
       below the pill ran off the bottom of the screen, hiding the Log time button
       (reported from prod 2026-08-17, Activity mode's taller form). House rule now:
       a popup lives entirely inside the viewport; when its content expands, it grows
       around its centre ("bounces back up") instead of off-screen. Fixed + centred +
       capped below 100dvh makes off-screen geometrically impossible, and the two
       inner columns already scroll when the clamp bites.

       `translate` (not transform) for the centring: the pop-in animation animates
       `transform`, and the two properties compose instead of overwriting each other. */
    max-height: calc(100dvh - 1.5rem);
    padding: 0.85rem 0.85rem 0.85rem 1.5rem;
    position: fixed;
    right: 3.4rem;
    top: 50%;
    translate: 0 -50%;
    max-width: calc(100vw - 56px);
}

/* Kanban-pan-style arrow riding the panel's left edge: reveals/collapses the day
   column. Mirrors .kanban-pan-button (round, bordered, glyph inside). */
.my-time-tray-expand {
    align-items: center;
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    height: 2.1rem;
    justify-content: center;
    left: -1.05rem;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    transition: border-color 140ms ease, color 140ms ease;
    width: 2.1rem;
}

.my-time-tray-expand:hover,
.my-time-tray-expand:focus-visible {
    border-color: var(--bls-primary);
    color: var(--bls-primary-dark);
    outline: none;
}

/* The mini day column ("what's logged, where does the next task start"). */
.my-time-tray-day {
    border-right: 1px solid var(--bls-border);
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 0.6rem;
    overflow-y: auto;
    padding-right: 0.6rem;
    width: 290px;
}

.my-time-tray-log {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    overflow-y: auto;
    width: 340px;
}

.my-time-tray-when {
    color: var(--bls-muted);
    flex: 0 0 auto;
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
}

.my-time-tray-next {
    color: var(--bls-muted);
    font-size: 0.78rem;
    margin: 0;
}

@keyframes my-time-tray-in {
    from {
        opacity: 0;
        transform: translateX(0.5rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .my-time-tray-panel {
        animation: none;
    }

    .my-time-tray-toggle {
        transition: none;
    }
}

.my-time-tray-head {
    align-items: center;
    display: flex;
    gap: 0.5rem;
    justify-content: space-between;
}

.my-time-tray-head strong {
    font-size: 0.85rem;
}

.my-time-tray-nav {
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    flex: 0 0 auto;
    font-size: 0.95rem;
    height: 1.6rem;
    line-height: 1;
    width: 1.6rem;
}

.my-time-tray-nav:hover,
.my-time-tray-nav:focus-visible {
    border-color: var(--bls-primary);
    color: var(--bls-text);
}

.my-time-tray-total {
    align-items: baseline;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    font-variant-numeric: tabular-nums;
}

.my-time-tray-target {
    color: var(--bls-muted);
    font-size: 0.8rem;
}

.my-time-tray-short {
    color: var(--bls-warning);
    font-size: 0.75rem;
}

.my-time-tray-progress {
    background: var(--bls-border);
    border-radius: 999px;
    height: 0.3rem;
    overflow: hidden;
}

.my-time-tray-progress span {
    background: var(--bls-primary);
    border-radius: inherit;
    display: block;
    height: 100%;
}

.my-time-tray-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.my-time-tray-entry {
    align-items: center;
    border-top: 1px solid var(--bls-border);
    display: flex;
    gap: 0.45rem;
    font-size: 0.78rem;
    padding-top: 0.4rem;
}

.my-time-tray-case {
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    flex: 0 0 auto;
    font-size: 0.68rem;
    font-weight: 600;
    padding: 0.1rem 0.45rem;
    white-space: nowrap;
}

.my-time-tray-desc {
    color: var(--bls-text);
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.my-time-tray-duration {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* Uppercase mini status chips — same palette as the overlay's my-time-badge set:
   synced green, failed red, pending amber, the rest muted. */
.my-time-tray-chip {
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    flex: 0 0 auto;
    font-size: 0.58rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    padding: 0.1rem 0.4rem;
    text-transform: uppercase;
    white-space: nowrap;
}

.my-time-tray-chip.chip-synced {
    border-color: var(--bls-success);
    color: var(--bls-success);
}

.my-time-tray-chip.chip-failed {
    border-color: var(--bls-danger);
    color: var(--bls-danger);
}

.my-time-tray-chip.chip-pending {
    border-color: var(--bls-warning);
    color: var(--bls-warning);
}

/* Deliberately inert states: FLOW OFF (sync switched off when logged) and "—" (no
   sync recorded). Dashed border + dimmed text keeps them clearly quieter than the
   coloured outcome chips above. */
.my-time-tray-chip.chip-disabled,
.my-time-tray-chip.chip-none {
    border-style: dashed;
    color: var(--bls-muted);
    opacity: 0.75;
}

.my-time-tray-empty,
.my-time-tray-loading {
    color: var(--bls-muted);
    font-size: 0.8rem;
    margin: 0;
}

.my-time-tray-error {
    color: var(--bls-danger);
    font-size: 0.8rem;
    margin: 0;
}

.my-time-tray-add {
    align-items: center;
    background: transparent;
    border: 1px dashed var(--bls-border);
    border-radius: var(--bls-radius-md);
    color: var(--bls-muted);
    cursor: pointer;
    display: flex;
    font: inherit;
    font-size: 0.8rem;
    justify-content: center;
    padding: 0.45rem;
}

.my-time-tray-add:hover,
.my-time-tray-add:focus-visible {
    border-color: var(--bls-primary);
    color: var(--bls-text);
}


/* ══════════════════════════════════ Planner ══════════════════════════════════
   Personal work planner (/admin/planner): toolbar + day-column timeline grid with
   a capacity strip and one implicit row per planned block, a per-day "+" add row,
   and a ranked todo list aside. All colours come from the --bls-* tokens so both
   themes work; the block editor is a compact centred dialog over a backdrop
   (my-time-overlay chrome, scaled down). */
.planner {
    align-items: start;
    display: grid;
    gap: 1.25rem;
    grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
}

.planner-main {
    display: grid;
    gap: 0.75rem;
    min-width: 0;
}

/* ---- toolbar ---- */
.planner-toolbar {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    justify-content: space-between;
}

.planner-toolbar-nav {
    align-items: center;
    display: flex;
    gap: 0.5rem;
}

.planner-nav-btn,
.planner-icon-btn {
    align-items: center;
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 8px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    padding: 0.35rem;
}

.planner-nav-btn:hover,
.planner-icon-btn:hover:not(:disabled) {
    background: var(--bls-surface-strong);
}

.planner-icon-btn:disabled {
    cursor: default;
    opacity: 0.45;
}

.planner-icon-btn.is-danger:hover:not(:disabled) {
    border-color: var(--bls-danger);
    color: var(--bls-danger);
}

.planner-today-btn {
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    padding: 0.25rem 0.7rem;
    text-transform: uppercase;
}

.planner-today-btn:hover {
    color: var(--bls-text);
}

.planner-window-label {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Window-size chip group — same idiom as the case-timer work-type chips. */
.planner-toolbar-chips {
    display: flex;
    gap: 0.35rem;
}

.planner-chip {
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    cursor: pointer;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.25rem 0.7rem;
}

.planner-chip:hover {
    color: var(--bls-text);
}

.planner-chip.is-on {
    background: var(--bls-primary);
    border-color: var(--bls-primary);
    color: #fff;
}

/* ---- timeline grid ---- */
.planner-grid-scroll {
    overflow-x: auto;
}

.planner-grid {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: grid;
    gap: 2px;
    min-width: 100%;
    padding: 0.5rem;
    row-gap: 4px;
}

.planner-day-head {
    align-items: center;
    border-bottom: 1px solid var(--bls-border);
    color: var(--bls-muted);
    display: flex;
    flex-direction: column;
    font-size: 0.72rem;
    gap: 0.1rem;
    padding: 0.3rem 0.15rem 0.4rem;
    text-transform: uppercase;
}

.planner-day-head .planner-day-num {
    color: var(--bls-text);
    font-size: 0.95rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.planner-day-head.is-weekend,
.planner-capacity.is-weekend,
.planner-day-add.is-weekend {
    opacity: 0.55;
}

.planner-day-head.is-today {
    border-bottom-color: var(--bls-primary);
    color: var(--bls-primary-dark);
    opacity: 1;
}

.planner-day-head.is-today .planner-day-num {
    color: var(--bls-primary-dark);
}

/* Capacity strip: planned-vs-target hours per day; amber when over target. */
.planner-capacity {
    color: var(--bls-muted);
    font-size: 0.62rem;
    font-variant-numeric: tabular-nums;
    min-height: 1rem;
    text-align: center;
    white-space: nowrap;
}

.planner-capacity.is-over {
    color: var(--bls-warning);
    font-weight: 600;
}

/* Planned blocks: magenta fill, rounded, hover raise; one implicit row each. */
.planner-block {
    align-items: baseline;
    background: var(--bls-primary);
    border: none;
    border-radius: 8px;
    color: #fff;
    cursor: pointer;
    display: flex;
    flex-wrap: wrap;
    font: inherit;
    font-size: 0.78rem;
    gap: 0.35rem;
    min-width: 0;
    overflow: hidden;
    padding: 0.3rem 0.55rem;
    text-align: left;
    transition: transform 120ms ease, box-shadow 120ms ease;
}

.planner-block:hover,
.planner-block:focus-visible {
    box-shadow: var(--bls-shadow);
    transform: translateY(-1px);
}

/* Time off (Holiday kind): amber so unavailability never reads as booked work.
   Mixing the theme's own warning token keeps the label readable on both themes -
   same approach as the team planner's holiday tints. */
.planner-block.is-holiday {
    background: color-mix(in srgb, var(--bls-warning) 18%, var(--bls-surface));
    border: 1px solid color-mix(in srgb, var(--bls-warning) 50%, var(--bls-border));
    color: var(--bls-warning);
}

.planner-block-label {
    font-weight: 600;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The client a planner block belongs to, shown ahead of the detail text on the chip.
   Same muted treatment as the case chip beside it - the detail stays the loud part. */
.planner-block-client {
  font-weight: 600;
  opacity: 0.85;
  margin-right: 0.35rem;
  white-space: nowrap;
}

.planner-block-case {
    background: rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    padding: 0.05rem 0.45rem;
    white-space: nowrap;
}

.planner-block-hours {
    font-size: 0.68rem;
    font-variant-numeric: tabular-nums;
    opacity: 0.85;
    white-space: nowrap;
}

/* Per-day "+" click targets on the last grid row. */
.planner-day-add {
    background: transparent;
    border: 1px dashed var(--bls-border);
    border-radius: 8px;
    color: var(--bls-muted);
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    min-height: 1.7rem;
    padding: 0.1rem;
}

.planner-day-add:hover,
.planner-day-add:focus-visible {
    border-color: var(--bls-primary);
    color: var(--bls-primary-dark);
    opacity: 1;
}

.planner-empty {
    color: var(--bls-muted);
    font-size: 0.85rem;
    margin: 0;
}

.planner-error {
    color: var(--bls-danger);
    font-size: 0.8rem;
    margin: 0;
}

/* ---- shared inputs / buttons ---- */
.planner-input {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: 8px;
    color: var(--bls-text);
    font: inherit;
    font-size: 0.85rem;
    min-width: 0;
    padding: 0.4rem 0.55rem;
    width: 100%;
}

.planner-input:focus-visible {
    border-color: var(--bls-primary);
    outline: none;
}

.planner-btn {
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 8px;
    color: var(--bls-text);
    cursor: pointer;
    font: inherit;
    font-size: 0.8rem;
    font-weight: 600;
    padding: 0.35rem 0.8rem;
    white-space: nowrap;
}

.planner-btn:hover:not(:disabled) {
    background: var(--bls-surface-strong);
}

.planner-btn:disabled {
    cursor: default;
    opacity: 0.55;
}

.planner-btn.is-primary {
    background: var(--bls-primary);
    border-color: var(--bls-primary);
    color: #fff;
}

.planner-btn.is-primary:hover:not(:disabled) {
    background: var(--bls-primary-dark);
}

.planner-btn.is-danger {
    border-color: var(--bls-danger);
    color: var(--bls-danger);
}

.planner-btn.is-danger:hover:not(:disabled) {
    background: var(--bls-danger);
    color: var(--bls-bg);
}

/* ---- todos panel ---- */
.planner-todos {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: grid;
    gap: 0.75rem;
    padding: 1rem;
}

.planner-todos-head {
    align-items: center;
    display: flex;
    justify-content: space-between;
}

.planner-todo-add {
    display: grid;
    gap: 0.4rem;
}

.planner-todo-add-row {
    display: flex;
    gap: 0.4rem;
}

.planner-todo-case {
    flex: 1 1 auto;
}

.planner-todo-date {
    flex: 0 0 auto;
    width: 8.5rem;
}

.planner-todo-estimate {
    flex: 0 0 auto;
    width: 6.5rem;
}

.planner-todo-list {
    display: grid;
    gap: 0.45rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.planner-todo {
    align-items: flex-start;
    border: 1px solid var(--bls-border);
    border-radius: 8px;
    display: flex;
    gap: 0.5rem;
    padding: 0.5rem 0.6rem;
}

.planner-todo input[type="checkbox"] {
    accent-color: var(--bls-primary);
    margin-top: 0.15rem;
}

.planner-todo-main {
    display: grid;
    flex: 1 1 auto;
    gap: 0.15rem;
    min-width: 0;
}

.planner-todo-title {
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}

.planner-todo.is-done .planner-todo-title {
    color: var(--bls-muted);
    text-decoration: line-through;
}

.planner-todo-meta {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.planner-todo-due {
    color: var(--bls-muted);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
}

.planner-todo-due.is-overdue {
    color: var(--bls-warning);
    font-weight: 600;
}

.planner-todo-actions {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    gap: 0.25rem;
}

/* ---- block editor dialog ---- */
.planner-editor-overlay {
    align-items: center;
    background: rgba(4, 2, 8, 0.55);
    display: flex;
    inset: 0;
    justify-content: center;
    padding: var(--portal-overlay-padding, 1.5rem);
    position: fixed;
    z-index: 90;
}

.planner-editor {
    background: var(--bls-bg, #0b070f);
    border: 1px solid var(--bls-border, #2a2a31);
    border-radius: 14px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    display: grid;
    gap: 0.75rem;
    max-height: calc(100dvh - (var(--portal-overlay-padding, 1.5rem) * 2));
    overflow-y: auto;
    padding: 1.25rem;
    width: min(460px, 92vw);
}

.planner-editor-title {
    margin: 0;
}

.planner-field {
    color: var(--bls-muted);
    display: grid;
    font-size: 0.78rem;
    gap: 0.25rem;
}

.planner-field-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.planner-field-row .planner-field {
    flex: 1 1 8rem;
}

.planner-editor-actions {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.planner-editor-spacer {
    flex: 1 1 auto;
}

/* ---- responsive: todos below the timeline; grid scrolls sideways ---- */
@media (max-width: 1100px) {
    .planner {
        grid-template-columns: minmax(0, 1fr);
    }
}

@media (prefers-reduced-motion: reduce) {
    .planner-block {
        transition: none;
    }

    .planner-block:hover,
    .planner-block:focus-visible {
        transform: none;
    }
}

/* ---- team planner (Portal.Admin.PlannerTeam): all-staff week grid ---- */
.planner-view-toggle {
    display: flex;
    gap: 0.35rem;
    margin-bottom: 0.75rem;
}

.team-planner {
    display: grid;
    gap: 0.75rem;
    min-width: 0;
}

.team-planner-grid-scroll {
    overflow-x: auto;
}

/* overflow-x on the scroll wrap also clips vertically, which would cut off a cell's
   "+" pill menu - while one is open the wrap reserves room for it below the grid
   (padding), without pushing the rest of the page down (negative margin). 18rem
   covers the menu's 16rem height cap plus its drop offset. */
.team-planner-grid-scroll.is-menu-open {
    padding-bottom: 18rem;
    margin-bottom: -18rem;
}

/* Team planner pill menus pair with the fixed click-away backdrop (z-index 30), so
   they must sit above it — same layering as ClientCasePicker's menu (31). */
.team-planner .case-pill-menu-options {
    z-index: 31;
}

/* Columns: person | 7 day columns. Every (person, day) intersection is a cell that
   stacks the day's markers - out-of-office, time off, client chips, muted case chips
   from the person's own planner - with a "+" assign button at the bottom. */
.team-planner-grid {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: grid;
    gap: 2px;
    grid-template-columns: minmax(9rem, 12rem) repeat(7, minmax(7.5rem, 1fr));
    min-width: 100%;
    padding: 0.5rem;
    row-gap: 6px;
}

.team-planner-col-head {
    align-self: end;
    border-bottom: 1px solid var(--bls-border);
    color: var(--bls-muted);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    padding: 0.3rem 0.15rem 0.4rem;
    text-transform: uppercase;
}

.team-planner-person {
    align-items: center;
    align-self: start;
    display: flex;
    flex-wrap: wrap;
    font-size: 0.85rem;
    font-weight: 600;
    gap: 0.25rem;
    min-width: 0;
    padding: 0.25rem 0.35rem 0 0;
}

.team-planner-person-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.team-planner-person.is-me .team-planner-person-name::after {
    color: var(--bls-muted);
    content: " (you)";
    font-weight: 400;
}

/* Remove-person control: an icon that swaps inline for Confirm/Cancel buttons (same
   idiom as the todo delete on the personal planner). */
.team-planner-person-remove {
    display: inline-flex;
    flex: 0 0 auto;
    gap: 0.25rem;
}

button.team-planner-person-remove {
    font-size: 0.62rem;
    padding: 0.2rem;
}

/* A customer in this person's name for a day - removal is instant (no confirm). */
.team-planner-assignment-chip {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    display: inline-flex;
    font-size: 0.72rem;
    font-weight: 600;
    gap: 0.25rem;
    max-width: 100%;
    padding: 0.15rem 0.3rem 0.15rem 0.6rem;
}

/* Legacy whole-week marker on a chip that predates day-level planning. */
.team-planner-chip-marker {
    color: var(--bls-muted);
    flex: 0 0 auto;
    font-size: 0.6rem;
    font-weight: 400;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.team-planner-assignment-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.team-planner-assignment-remove {
    align-items: center;
    background: transparent;
    border: none;
    border-radius: 50%;
    color: var(--bls-muted);
    cursor: pointer;
    display: inline-flex;
    font-size: 0.7rem;
    justify-content: center;
    padding: 0.1rem;
}

.team-planner-assignment-remove:hover:not(:disabled) {
    color: var(--bls-danger);
}

.team-planner-assignment-remove:disabled {
    cursor: default;
    opacity: 0.45;
}

/* One (person, day) cell: the day's markers stack vertically, "+" assign at the
   bottom. */
.team-planner-cell {
    border: 1px dashed var(--bls-border);
    border-radius: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-height: 3.2rem;
    min-width: 0;
    padding: 2px;
}

.team-planner-cell.is-weekend {
    opacity: 0.55;
}

.team-planner-cell.is-today {
    border-color: var(--bls-primary);
}

/* Amber day tints mix the theme's own warning token (dark #fbbf24 / light #9c5f0c)
   so they read on both backgrounds - same approach as the SLA warning banner. */
.team-planner-cell.is-holiday {
    background: color-mix(in srgb, var(--bls-warning) 10%, transparent);
}

.team-planner-cell.is-oof {
    border: 1px dashed color-mix(in srgb, var(--bls-warning) 55%, var(--bls-border));
}

/* Booked time off (a Holiday planner block): the one removable amber chip - its X
   deletes the whole block, not just the day, so the title spells out the range. */
.team-planner-holiday-chip {
    align-items: center;
    background: color-mix(in srgb, var(--bls-warning) 22%, transparent);
    border-radius: 999px;
    color: var(--bls-warning);
    display: inline-flex;
    font-size: 0.72rem;
    font-weight: 600;
    gap: 0.25rem;
    max-width: 100%;
    padding: 0.15rem 0.3rem 0.15rem 0.5rem;
}

/* Outlook out-of-office marker: read-only (it mirrors the mailbox, the planner can't
   change it), so dashed and muted with no remove affordance. */
.team-planner-oof-chip {
    align-items: center;
    background: transparent;
    border: 1px dashed color-mix(in srgb, var(--bls-warning) 45%, var(--bls-border));
    border-radius: 999px;
    color: var(--bls-muted);
    display: inline-flex;
    font-size: 0.66rem;
    font-weight: 600;
    gap: 0.25rem;
    max-width: 100%;
    padding: 0.15rem 0.5rem;
}

/* A case the person planned on their own planner that day - context, visually
   secondary to the client chips, never editable here. */
.team-planner-case-chip {
    background: transparent;
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-muted);
    font-size: 0.66rem;
    max-width: 9rem;
    min-width: 0;
    overflow: hidden;
    padding: 0.1rem 0.5rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.team-planner-cell-add {
    background: transparent;
    border: 1px dashed var(--bls-border);
    border-radius: 6px;
    color: var(--bls-muted);
    cursor: pointer;
    font-size: 0.72rem;
    line-height: 1;
    margin-top: auto;
    padding: 0.2rem 0;
    width: 100%;
}

.team-planner-cell-add:hover:not(:disabled),
.team-planner-cell-add:focus-visible {
    border-color: var(--bls-primary);
    color: var(--bls-primary);
}

.team-planner-cell-add:disabled {
    cursor: default;
    opacity: 0.45;
}

/* The per-cell assign menu: capped and scrollable so the scroll wrap's 18rem
   is-menu-open reservation always covers it. */
.team-planner-cell-options {
    max-height: 16rem;
    min-width: 13rem;
    overflow-y: auto;
    padding: 0.4rem;
}

.team-planner-cell-options .planner-input {
    margin-bottom: 0.3rem;
}

.team-planner-menu-divider {
    border-top: 1px solid var(--bls-border);
    margin: 0.2rem 0;
}

/* Muted open-case count on a client option; logical margin so RTL menus keep it on
   the reading-end of the row. */
.team-planner-option-count {
    color: var(--bls-muted);
    font-size: 0.72rem;
    margin-inline-start: auto;
}

/* Staff filter inside the add-person pill menu. */
.team-planner-person-options {
    min-width: 16rem;
    padding: 0.4rem;
}

.team-planner-person-options .planner-input {
    margin-bottom: 0.3rem;
}

.team-planner-oof-hint {
    font-size: 0.78rem;
    margin: 0.25rem 0 0;
}

/* "Time off coming up" management list under the grid: one row per Holiday block in
   the coming weeks, deletable here so far-out time off needs no week paging. Same
   visual weight as the personal planner's todos aside. */
.team-planner-upcoming {
    background: var(--bls-surface);
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-md);
    display: grid;
    gap: 0.5rem;
    margin-top: 0.25rem;
    padding: 1rem;
}

.team-planner-upcoming-list {
    display: grid;
    gap: 0.35rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.team-planner-upcoming-row {
    align-items: baseline;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.team-planner-upcoming-name {
    font-size: 0.85rem;
    font-weight: 600;
}

.team-planner-upcoming-label {
    color: var(--bls-muted);
    font-size: 0.8rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.team-planner-upcoming-range {
    color: var(--bls-muted);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The X hugs the reading-end of the row (logical margin so RTL flips it) and
   centres itself off the baseline the text sits on. */
.team-planner-upcoming-row .planner-icon-btn {
    align-self: center;
    margin-inline-start: auto;
}

.team-planner-upcoming-more {
    color: var(--bls-muted);
    font-size: 0.78rem;
    margin: 0;
}

/* Copy popups (day -> days, week -> week): a tick/radio list of targets, each stating
   what it already holds so nothing is replaced blind. */
.team-planner-copy-list {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin: 0.5rem 0;
    padding: 0;
    list-style: none;
}

.team-planner-copy-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.5rem;
    border: 1px solid var(--bls-border);
    border-radius: var(--bls-radius-sm);
    cursor: pointer;
}

.team-planner-copy-row.has-content .team-planner-copy-option {
    border-color: var(--bls-primary);
}

.team-planner-copy-day {
    font-weight: 600;
}

.team-planner-copy-state {
    margin-left: auto;
    color: var(--bls-muted);
    font-size: 0.8125rem;
}

/* ---- P5: team planner at phone width (day picker + person list) -------------------
   The matrix is person + up to SEVEN day columns at minmax(7.5rem, 1fr) with
   min-width: 100% - a ~984px floor, i.e. a 2.5x horizontal scroll on a 390px phone, of
   a grid whose cells stack several chips each. Option A replaces it below 600px with a
   date strip that picks ONE day and a single-column list of person cards for that day.

   Both surfaces are always in the markup and CSS switches them (the same idiom as the
   case-detail pane switch, .case-pane-switch): no JS, no interop timing, and the phone
   view is correct on the very first paint. These two rules are the ONLY thing the new
   selectors do above 600px - nothing else here leaks to desktop, where the matrix must
   render byte-identically to before. */
.team-planner-days,
.team-planner-day-list {
    display: none;
}

@media (max-width: 600px) {
    /* The matrix goes; its scroll wrap is the single switch point, which takes the
       duplicate (person, day) assign menu inside it out of the a11y tree too. */
    .team-planner-grid-scroll {
        display: none;
    }

    /* The week nav is a single no-wrap row on desktop (2 arrows + Today + the date
       range + the "Out of hours" chip ≈ 330px). At 362px of content that is knife-edge,
       and it overflows outright once the range label grows across a month boundary, so
       let it wrap here. Scoped to .team-planner: the personal planner's toolbar is
       narrower (no Add person) and is not part of this piece. */
    .team-planner .planner-toolbar-nav {
        flex-wrap: wrap;
    }

    /* ---- the day picker ----
       One button per day in the CURRENTLY visible set, so "Out of hours" governs it
       exactly as it governs the matrix columns. flex: 1 1 0 shares the row evenly, and
       the 2.75rem min-width is what makes the overflow honest: 5 days fit 390px with
       ~68px each and 7 fit with ~47px each (no scroll either way), while a 320px screen
       showing 7 days genuinely cannot and scrolls. */
    .team-planner-days {
        display: flex;
        gap: 0.35rem;
        /* The buttons carry a focus ring; without the block padding this scroll
           container would clip it. The negative margin keeps the row's height as-is
           (same pair as the phone command rail). */
        margin-block: -3px;
        /* Only bites when the 2.75rem floors actually exceed the row. */
        overflow-x: auto;
        padding-block: 3px;
    }

    .team-planner-day-pick {
        align-items: center;
        background: transparent;
        border: 1px solid var(--bls-border);
        border-radius: var(--bls-radius-md);
        color: var(--bls-muted);
        cursor: pointer;
        display: flex;
        flex: 1 1 0;
        flex-direction: column;
        font: inherit;
        gap: 0.1rem;
        justify-content: center;
        /* 44px touch floor (P0), on both axes. */
        min-height: 2.75rem;
        min-width: 2.75rem;
        padding: 0.35rem 0.2rem;
    }

    .team-planner-day-pick-name {
        font-size: 0.66rem;
        letter-spacing: 0.04em;
        text-transform: uppercase;
    }

    .team-planner-day-pick-num {
        color: var(--bls-text);
        font-size: 0.95rem;
        font-variant-numeric: tabular-nums;
        font-weight: 600;
    }

    /* Today and the weekend read the same as they do on the matrix's column heads, so
       the phone strip and the desktop header row are recognisably the same thing. */
    .team-planner-day-pick.is-weekend {
        opacity: 0.55;
    }

    .team-planner-day-pick.is-today {
        border-color: var(--bls-primary);
    }

    /* Selected: the command rail's / pane switch's own selected treatment, so "which day
       am I looking at" reads identically to every other segmented control in the app.
       A selected weekend day must not stay dimmed - (0,2,0) beats the is-weekend rule. */
    .team-planner-day-pick.is-on {
        background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
        border-color: rgba(196, 78, 175, 0.42);
        color: var(--bls-ink);
        opacity: 1;
    }

    .team-planner-day-pick.is-on .team-planner-day-pick-num {
        color: var(--bls-ink);
    }

    /* Glow only - deliberately NO outline declaration of any kind. The global
       :focus-visible ring (and the stronger .portal-a11y-contrast one, which is declared
       EARLIER in this file) must keep applying: a same-specificity outline here would
       win on source order and quietly downgrade the high-contrast ring, which is exactly
       the trap the pane switch fell into and P4 had to patch back at the end of the
       file. */
    .team-planner-day-pick:focus-visible {
        box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    }

    /* ---- the person list ---- */
    .team-planner-day-list {
        display: grid;
        gap: 0.5rem;
        list-style: none;
        margin: 0;
        padding: 0;
    }

    /* One full-width card per person: name + day total, then the day's chips, then the
       assign control. Same surface/border/radius as the matrix so it is the same board,
       differently laid out. */
    .team-planner-day-card {
        background: var(--bls-surface);
        border: 1px solid var(--bls-border);
        border-radius: var(--bls-radius-md);
        display: grid;
        gap: 0.45rem;
        padding: 0.6rem;
    }

    /* The matrix's amber day tints, on the whole card (the cell they belonged to no
       longer exists at this width). */
    .team-planner-day-card.is-holiday {
        background: color-mix(in srgb, var(--bls-warning) 10%, transparent);
    }

    .team-planner-day-card.is-oof {
        border-color: color-mix(in srgb, var(--bls-warning) 55%, var(--bls-border));
    }

    .team-planner-day-card-head {
        align-items: center;
        display: flex;
        gap: 0.5rem;
    }

    .team-planner-day-card-name {
        flex: 1 1 auto;
        font-size: 0.9rem;
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    /* Same "(you)" marker the matrix puts on the person column. */
    .team-planner-day-card.is-me .team-planner-day-card-name::after {
        color: var(--bls-muted);
        content: " (you)";
        font-weight: 400;
    }

    /* The day's planned hours, hard against the head's reading-end. */
    .team-planner-day-card-hours {
        color: var(--bls-muted);
        flex: 0 0 auto;
        font-size: 0.78rem;
        font-variant-numeric: tabular-nums;
        font-weight: 600;
        white-space: nowrap;
    }

    .team-planner-day-card-body {
        display: flex;
        flex-wrap: wrap;
        gap: 0.3rem;
    }

    .team-planner-day-card-empty {
        color: var(--bls-muted);
        font-size: 0.75rem;
    }

    /* A case chip capped at 9rem is fine in a 7.5rem matrix cell but pointlessly narrow
       in a 362px card - let it use the row. */
    .team-planner-day-card .team-planner-case-chip {
        max-width: 100%;
    }

    /* Primary controls in the card take the 44px floor (P0). The chip-internal X
       removes deliberately do not: P0's floor covers navigation and primary actions, not
       secondary affordances inside a pill, and 44px squares inside wrapping chips would
       double the card's height. 2rem is still ~32px, up from the desktop chip's ~18px.
       .planner-btn is in here for the remove-person CONFIRM/CANCEL pair, which swaps
       inline for the X: at its desktop size (0.8rem text, 0.35rem block padding) it is
       ~26px tall, and at phone width the card is the only remove-person path there is -
       so the confirm step of a destructive action would sit well under the floor. */
    .team-planner-day-card .planner-icon-btn,
    .team-planner-day-card .planner-btn {
        flex: 0 0 auto;
        min-height: 2.75rem;
        min-width: 2.75rem;
    }

    .team-planner-day-card .team-planner-assignment-remove {
        min-height: 2rem;
        min-width: 2rem;
    }

    /* "+ Assign" is the card's own full-width action row, not the matrix's thin
       bottom-of-cell sliver, so it loses margin-top: auto (no flex column to push
       against here) and takes the touch floor. */
    .team-planner-day-card .team-planner-cell-add {
        margin-top: 0;
        min-height: 2.75rem;
    }

    /* The assign menu DOCKS to the bottom of the screen instead of dropping out of the
       card. Why it has to: the menu is up to 16rem tall and the click-away backdrop is
       position: fixed, so a menu opening off the bottom of the viewport cannot be
       scrolled into view - a touch-drag on a fixed element scrolls the document, and the
       app's scrollport is .portal-content, not the document. Tapping "+ Assign" on the
       fourth visible card would therefore open an unreachable menu, and this is the
       surface's primary action. The desktop matrix solves the same problem the other way
       (.team-planner-grid-scroll.is-menu-open reserves 18rem inside its clipping wrap),
       which has nothing to clip against here.
       Geometry is lifted verbatim from the shared .portal-sheet primitive - the same
       mobile-bar docking token (which already carries the home-indicator safe area) and
       the same 2.5rem reveal strip - so it reads as the app's bottom sheet rather than a
       second invention. Nothing else changes: same markup, same options, same handler.
       Specificity is (0,3,0) on purpose. The base drop is anchored `right: 0` and the RTL
       block re-anchors it with `.portal-shell[dir="rtl"] .case-pill-menu-options { left: 0;
       right: auto }`, which is also (0,3,0) - so a (0,2,0) rule here would lose to it and
       dock a 13rem menu to one edge under RTL. Matching (0,3,0) and being declared LATER
       wins both physical sides back. inset-inline (like .portal-sheet, house convention)
       then pins them direction-agnostically, so nothing physical is left to override.
       No animation is added: .case-pill-menu-options already carries portal-pop-in, which
       is already gated by both reduce-motion arms. Only its origin flips, because this
       one settles from below. */
    .team-planner .team-planner-day-card .team-planner-cell-options {
        border-bottom: 0;
        border-radius: 1rem 1rem 0 0;
        bottom: var(--portal-mobile-bar-height, 0px);
        inset-inline: 0;
        max-height: calc(100dvh - var(--portal-mobile-bar-height, 0px) - 2.5rem);
        max-width: none;
        min-width: 0;
        padding: 0.5rem 1rem 1rem;
        position: fixed;
        top: auto;
        transform-origin: bottom;
    }

    /* Docked options are full-bleed rows, so give them (and the client filter above
       them) the touch floor too. */
    .team-planner .team-planner-day-card .team-planner-cell-options .case-pill-menu-option,
    .team-planner .team-planner-day-card .team-planner-cell-options .planner-input {
        min-height: 2.75rem;
    }
}

/* ============================================================================
   MOTION + MATERIALS LAYER
   The "alive" macOS/fluent layer: micro-interactions on interactive surfaces,
   entrance animation + vibrancy (frosted glass) on floating layers. Everything
   here is decorative polish gated three ways:
     - reduce motion:   the media query (prefers-reduced-motion: reduce) AND the
                        app .portal-a11y-reduce-motion shell class kill ALL of it
                        (animations, transitions AND hover/active transforms).
     - transparency:    vibrancy only applies when the shell has NEITHER
                        .portal-a11y-notransparency NOR .portal-a11y-contrast,
                        and only inside @supports (backdrop-filter). Solid
                        var(--bls-popover-solid) fallbacks always paint first.
   Presets restyle the layer entirely through the --bls-popover-, --bls-glass- and
   --bls-hover-glow token families - no preset-specific rules below.

   DO NOT write a token glob as "--bls-popover-*" followed by a slash in this prose:
   that spells a comment terminator, which closed this banner early and made CSS error
   recovery swallow the :root token block below as a bad rule's body. The four duration
   and easing tokens were therefore UNDEFINED, so every declaration using them was
   invalid at computed-value time - the hover transition and the pop-in animation
   silently did nothing from 2026-07-22 until 2026-07-28.
   ============================================================================ */

:root {
    --bls-dur-fast: 140ms;
    --bls-dur-base: 200ms;
    --bls-ease-spring: cubic-bezier(0.2, 0.9, 0.3, 1.2);
    --bls-ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* --- Interactive lift: buttons, chips, tiles, cards, nav links. Subtle - this is
   a work tool: a 1px rise on hover, nothing more. There is deliberately NO press
   "bounce" on :active - a whole-surface scale on every click read as toy-like and
   undercut the professional feel, so clicks give crisp, immediate feedback via the
   colour/border change alone. --- */
.portal-shell .btn,
.portal-shell .planner-chip,
.portal-shell .planner-btn,
.portal-shell .quick-time-log-pick,
.portal-shell .admin-report-tile,
.portal-shell .kanban-card,
.portal-shell .list-card-clickable,
.portal-shell .portal-nav a,
.portal-shell .portal-toggle {
    transition:
        transform var(--bls-dur-fast) var(--bls-ease-out),
        box-shadow var(--bls-dur-fast) var(--bls-ease-out),
        border-color var(--bls-dur-fast) var(--bls-ease-out),
        background var(--bls-dur-fast) var(--bls-ease-out),
        filter var(--bls-dur-fast) var(--bls-ease-out);
}

.portal-shell .btn:hover:not(:disabled),
.portal-shell .planner-chip:hover,
.portal-shell .planner-btn:hover:not(:disabled),
.portal-shell .quick-time-log-pick:hover,
.portal-shell .admin-report-tile:hover,
.portal-shell .kanban-card:hover,
.portal-shell .list-card-clickable:hover {
    transform: translateY(-1px);
}

/* Primary actions earn the hover reward (a glow under aubergine, a shadow lift
   elsewhere - the token decides). */
.portal-shell .btn-primary:hover:not(:disabled) {
    box-shadow: var(--bls-hover-glow);
}

/* --- Entrance animation for floating layers: a short rise-and-settle pop. --- */
@keyframes portal-pop-in {
    from {
        opacity: 0;
        transform: translateY(4px) scale(0.985);
    }
}

.portal-shell .case-pill-menu-options,
.portal-shell .kanban-filter-options,
.portal-shell .message-composer-menu,
.portal-shell .assistant-popover,
.portal-shell .portal-select-menu,
.portal-shell .customer-chooser-popover,
.portal-shell .portal-notifications-panel,
.portal-shell .my-time-tray-panel {
    animation: portal-pop-in 160ms var(--bls-ease-out);
    transform-origin: top;
}

/* The composer menu opens UPWARD (anchored bottom), so it settles from below. */
.portal-shell .message-composer-menu {
    transform-origin: bottom;
}

/* --- Vibrancy: frosted-glass floating layers. The solid var(--bls-popover-solid)
   background on each class (declared with the component) is the always-on
   fallback; glass only layers on where backdrop-filter exists and the user has
   not opted out (reduce transparency / high contrast). The :not() guards also
   lift specificity above the grouped theme-light popover overrides, so light
   shells get light glass (tokens from .theme-light) for free. --- */
@supports (backdrop-filter: blur(1px)) {
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .case-pill-menu-options,
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .kanban-filter-options,
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .message-composer-menu,
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .assistant-popover,
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .portal-select-menu,
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .customer-chooser-popover,
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .portal-notifications-panel,
    .portal-shell:not(.portal-a11y-notransparency):not(.portal-a11y-contrast) .my-time-tray-panel {
        background: var(--bls-popover-glass);
        backdrop-filter: var(--bls-glass-blur);
        -webkit-backdrop-filter: var(--bls-glass-blur);
    }
}

/* --- Border sweep: inputs relied on the old heavy magenta border for their
   outline; at the new 9% hairline they vanish, so form controls step up to the
   stronger hairline token (presets + light + high contrast all retune it). --- */
.portal-shell input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
.portal-shell select,
.portal-shell textarea {
    border-color: var(--bls-border-strong);
}

/* --- Motion gates: kill every effect this layer adds (and re-pin transforms,
   which the duration-zeroing reduce-motion reset cannot catch) under both the
   OS preference and the app Reduce-motion toggle. --- */
@media (prefers-reduced-motion: reduce) {
    .portal-shell .btn,
    .portal-shell .planner-chip,
    .portal-shell .planner-btn,
    .portal-shell .quick-time-log-pick,
    .portal-shell .admin-report-tile,
    .portal-shell .kanban-card,
    .portal-shell .list-card-clickable,
    .portal-shell .portal-nav a,
    .portal-shell .portal-toggle {
        transition: none;
    }

    .portal-shell .btn:hover,
    .portal-shell .btn:active,
    .portal-shell .planner-chip:hover,
    .portal-shell .planner-chip:active,
    .portal-shell .planner-btn:hover,
    .portal-shell .planner-btn:active,
    .portal-shell .quick-time-log-pick:hover,
    .portal-shell .quick-time-log-pick:active,
    .portal-shell .admin-report-tile:hover,
    .portal-shell .kanban-card:hover,
    .portal-shell .kanban-card:active,
    .portal-shell .list-card-clickable:hover,
    .portal-shell .list-card-clickable:active {
        transform: none;
    }

    .portal-shell .case-pill-menu-options,
    .portal-shell .kanban-filter-options,
    .portal-shell .message-composer-menu,
    .portal-shell .assistant-popover,
    .portal-shell .portal-select-menu,
    .portal-shell .customer-chooser-popover,
    .portal-shell .portal-notifications-panel,
    .portal-shell .my-time-tray-panel {
        animation: none;
    }
}

.portal-shell.portal-a11y-reduce-motion .btn,
.portal-shell.portal-a11y-reduce-motion .planner-chip,
.portal-shell.portal-a11y-reduce-motion .planner-btn,
.portal-shell.portal-a11y-reduce-motion .quick-time-log-pick,
.portal-shell.portal-a11y-reduce-motion .admin-report-tile,
.portal-shell.portal-a11y-reduce-motion .kanban-card,
.portal-shell.portal-a11y-reduce-motion .list-card-clickable,
.portal-shell.portal-a11y-reduce-motion .portal-nav a,
.portal-shell.portal-a11y-reduce-motion .portal-toggle {
    transition: none;
}

.portal-shell.portal-a11y-reduce-motion .btn:hover,
.portal-shell.portal-a11y-reduce-motion .btn:active,
.portal-shell.portal-a11y-reduce-motion .planner-chip:hover,
.portal-shell.portal-a11y-reduce-motion .planner-chip:active,
.portal-shell.portal-a11y-reduce-motion .planner-btn:hover,
.portal-shell.portal-a11y-reduce-motion .planner-btn:active,
.portal-shell.portal-a11y-reduce-motion .quick-time-log-pick:hover,
.portal-shell.portal-a11y-reduce-motion .quick-time-log-pick:active,
.portal-shell.portal-a11y-reduce-motion .admin-report-tile:hover,
.portal-shell.portal-a11y-reduce-motion .kanban-card:hover,
.portal-shell.portal-a11y-reduce-motion .kanban-card:active,
.portal-shell.portal-a11y-reduce-motion .list-card-clickable:hover,
.portal-shell.portal-a11y-reduce-motion .list-card-clickable:active {
    transform: none;
}

.portal-shell.portal-a11y-reduce-motion .case-pill-menu-options,
.portal-shell.portal-a11y-reduce-motion .kanban-filter-options,
.portal-shell.portal-a11y-reduce-motion .message-composer-menu,
.portal-shell.portal-a11y-reduce-motion .assistant-popover,
.portal-shell.portal-a11y-reduce-motion .portal-select-menu,
.portal-shell.portal-a11y-reduce-motion .customer-chooser-popover,
.portal-shell.portal-a11y-reduce-motion .portal-notifications-panel,
.portal-shell.portal-a11y-reduce-motion .my-time-tray-panel {
    animation: none;
}

/* ============================================================================
   APPEARANCE SETTINGS UI (Settings > Accessibility > Appearance)
   Pure-CSS preset preview cards (bg / surface / accent strips) + round accent
   swatches. Chip colours are hardcoded per preset ON PURPOSE - each card shows
   what THAT preset looks like regardless of the active theme.
   ============================================================================ */
.portal-theme-preset-gallery {
    display: grid;
    gap: 0.55rem;
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
}

.portal-theme-preset-card {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.8rem;
    color: var(--bls-text);
    cursor: pointer;
    display: grid;
    gap: 0.45rem;
    padding: 0.6rem;
    text-align: left;
}

.portal-theme-preset-card:hover {
    border-color: rgba(196, 78, 175, 0.45);
}

.portal-theme-preset-card:focus-visible {
    outline: 2px solid var(--bls-primary);
    outline-offset: 2px;
}

.portal-theme-preset-card[aria-pressed="true"] {
    border-color: transparent;
    box-shadow: 0 0 0 2px var(--bls-primary);
}

.portal-theme-preset-card:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.portal-theme-preset-strip {
    border: 1px solid var(--bls-border);
    border-radius: 0.45rem;
    display: grid;
    grid-template-columns: 2fr 2fr 1fr;
    height: 1.6rem;
    overflow: hidden;
}

.portal-theme-preset-strip span {
    display: block;
    height: 100%;
}

.portal-theme-preset-name {
    font-size: 0.82rem;
    font-weight: 700;
}

.portal-accent-swatch-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    padding: 0.25rem 0;
}

.portal-accent-swatch {
    border: 2px solid transparent;
    border-radius: 999px;
    cursor: pointer;
    height: 2rem;
    padding: 0;
    width: 2rem;
}

.portal-accent-swatch:focus-visible {
    outline: 2px solid var(--bls-primary);
    outline-offset: 2px;
}

.portal-accent-swatch[aria-pressed="true"] {
    border-color: var(--bls-ink);
    box-shadow: 0 0 0 2px var(--bls-primary);
}

/* ============================================================================
   P1 MOBILE NAVIGATION (Option C, 2026-07-28)
   Phone navigation shell: a fixed bottom bar of icon+label slots (the first four
   derived from the role-filtered nav model, the fifth = Menu) plus three flyouts
   rendered by PortalLayout - left Menu (the full labelled nav + a utility row),
   right Account (the account-popover content full height), right Customer context
   (the shared CustomerChooserPanel). Flyout markup is @if-rendered only while
   open and only openable from phone-width triggers, so every unscoped selector
   here is inert by default; the bar is display:none outside the 600px block
   below. Desktop (>600px) rendering is untouched.
   ============================================================================ */

.portal-mobile-bar {
    display: none;
}

/* Full-viewport scrim that is also the tap-to-dismiss control (a real button).
   Above the topbar's raised notifications state (120), below toasts/loader (200+). */
.portal-flyout-backdrop {
    animation: portal-flyout-backdrop-in 180ms ease;
    background: rgba(11, 7, 15, 0.66);
    border: 0;
    cursor: default;
    inset: 0;
    padding: 0;
    position: fixed;
    z-index: 140;
}

/* OPAQUE panels on purpose: they cover content, so they never frost - which is also
   why they need no .portal-a11y-notransparency fallback (simplest is: do not frost).
   Safe-area block padding keeps content clear of notches / home indicators. */
.portal-flyout {
    animation: portal-flyout-in-from-left 180ms ease;
    background: var(--bls-popover-solid);
    bottom: 0;
    box-shadow: var(--bls-shadow);
    color: var(--bls-text);
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    overflow: hidden;
    padding: max(1rem, var(--portal-safe-top)) 1rem max(1rem, var(--portal-safe-bottom));
    position: fixed;
    top: 0;
    width: min(20rem, 86vw);
    z-index: 141;
}

/* Docking: Menu on the inline-start edge, account/context on inline-end. Logical
   insets put them on the correct sides under RTL for free; the slide animation is
   physical (translateX), so RTL swaps the two keyframe names to keep each panel
   sliding in from its own edge. */
.portal-flyout-left {
    border-inline-end: 1px solid var(--bls-border);
    inset-inline-start: 0;
}

.portal-flyout-right {
    animation-name: portal-flyout-in-from-right;
    border-inline-start: 1px solid var(--bls-border);
    inset-inline-end: 0;
}

.portal-shell[dir="rtl"] .portal-flyout-left {
    animation-name: portal-flyout-in-from-right;
}

.portal-shell[dir="rtl"] .portal-flyout-right {
    animation-name: portal-flyout-in-from-left;
}

/* Slide + fade only (house rule: no toy motion, no bounce). */
@keyframes portal-flyout-in-from-left {
    from {
        opacity: 0;
        transform: translateX(-1.25rem);
    }
}

@keyframes portal-flyout-in-from-right {
    from {
        opacity: 0;
        transform: translateX(1.25rem);
    }
}

@keyframes portal-flyout-backdrop-in {
    from {
        opacity: 0;
    }
}

/* Motion gates: the OS preference AND the app Reduce-motion toggle both kill the
   slide/fade (the blanket 0.001ms reset covers these too; the explicit animation:none
   pair follows the motion layer's worked pattern above). */
@media (prefers-reduced-motion: reduce) {
    .portal-flyout,
    .portal-flyout-backdrop {
        animation: none;
    }
}

.portal-shell.portal-a11y-reduce-motion .portal-flyout,
.portal-shell.portal-a11y-reduce-motion .portal-flyout-backdrop {
    animation: none;
}

/* The Menu flyout wears the sidebar's identity: always-dark surface in BOTH themes
   (shared design-system rule), same border treatment. */
.portal-flyout-menu {
    background: linear-gradient(180deg, var(--portal-sidebar-bg) 0%, var(--portal-sidebar-bg-2) 100%);
    border-inline-end-color: var(--portal-sidebar-border);
}

/* Mirror of the .theme-light .portal-sidebar re-scope: the light theme keeps the
   menu flyout dark, so everything inside flips to light-on-dark tokens. */
.portal-shell.theme-light .portal-flyout-menu {
    --bls-text: #ffffff;
    --bls-ink: #ffffff;
    --bls-muted: rgba(255, 255, 255, 0.5);
    --bls-border: rgba(255, 255, 255, 0.08);
    --bls-surface-strong: rgba(255, 255, 255, 0.08);
    --portal-sidebar-bg: #2b2b2b;
    --portal-sidebar-bg-2: #1a1419;
    --portal-sidebar-border: rgba(255, 255, 255, 0.08);
    color-scheme: dark;
}

.portal-flyout-header {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    gap: 0.75rem;
    justify-content: space-between;
}

.portal-flyout-header h2 {
    font-size: 1.05rem;
    margin: 0;
}

/* The body is the scroll region so the header (and its close button) never leave
   the screen. */
.portal-flyout-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.85rem;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Customer-context flyout: the chooser's card list is its own scroll region (search +
   sort stay put), so this body clips instead of double-scrolling. */
.portal-flyout-body-clip {
    overflow: hidden;
}

.portal-flyout-context-copy {
    color: var(--bls-muted);
    flex: 0 0 auto;
    line-height: 1.5;
    margin: 0;
}

.portal-flyout-context-clear {
    align-items: center;
    align-self: flex-start;
    display: inline-flex;
    flex: 0 0 auto;
    gap: 0.4rem;
}

/* Account flyout: the popover content relaxes its no-wrap desktop assumptions inside
   the narrow full-height panel. */
.portal-flyout .portal-profile-details {
    flex-wrap: wrap;
    white-space: normal;
}

.portal-flyout .portal-account-popover-actions {
    flex-wrap: wrap;
    justify-content: flex-start;
}

/* Menu flyout utility row: raise-case (customer perspective), the shared footer tool
   cluster, sign out. */
.portal-flyout-utility {
    align-items: center;
    border-top: 1px solid var(--bls-border);
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    gap: 0.4rem;
    justify-content: flex-end;
    padding-top: 0.75rem;
}

/* Raise-case docks to the start of the row; the icon tools group at the end. */
.portal-flyout-raise-case {
    align-items: center;
    display: inline-flex;
    gap: 0.4rem;
    margin-inline-end: auto;
}

/* The footer tools carry desktop sizing (flex 1 1 0, fixed 2.45rem height); in the
   utility row they become self-sized 44px touch targets instead - and LABELLED pills,
   like .portal-account-signout beside them. P1 restored labels for the flyout nav but
   left these four as bare icon squares, which made the only phone route to Settings an
   unlabelled cog among three other unlabelled squares (no hover tooltips on touch).
   min-width drops to 0 so a labelled pill sizes to its text; the row already wraps. */
.portal-flyout-utility .portal-sidebar-footer-action {
    flex: 0 0 auto;
    gap: 0.4rem;
    height: auto;
    min-height: 2.75rem;
    min-width: 0;
    padding-inline: 0.7rem;
    white-space: nowrap;
    width: auto;
}

.portal-flyout-utility .portal-sidebar-footer-action .portal-nav-label {
    display: inline;
    font-size: 0.82rem;
    font-weight: 600;
    /* The collapsed-rail rule hides labels with opacity/width, not display, so both
       have to be restored here (same pair as the flyout nav's label pin at ~17800). */
    opacity: 1;
    width: auto;
}

/* --- Flyout nav. The sidebar's nav classes are reused (one source of truth), so the
   stacked-band pill-row overrides (<=1100px) and the collapsed-rail label hiding
   (the sidebar checkbox is :checked by DEFAULT) both leak in. These compound
   selectors match or beat their (0,4,x) specificity and sit last in the file, so
   they pin the flyout back to the labelled vertical list the desktop rail renders. --- */
.portal-shell .portal-flyout .portal-flyout-nav.portal-nav {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 0.3rem;
    overflow: visible;
    padding-right: 0;
}

/* The collapsed-rail rule (the sidebar checkbox is :checked by DEFAULT) squashes
   sections into 1px hairlines with transparent text - every property it sets must be
   restored here or the flyout's section headings render invisible. */
.portal-shell .portal-flyout .portal-flyout-nav.portal-nav .portal-nav-section {
    background: none;
    color: rgba(255, 255, 255, 0.45);
    display: block;
    flex-basis: auto;
    height: auto;
    margin: 1rem 0 0.2rem;
    overflow: visible;
    width: auto;
}

.portal-shell .portal-flyout .portal-flyout-nav.portal-nav a,
.portal-shell .portal-flyout .portal-flyout-nav.portal-nav .portal-nav-action {
    flex: 0 0 auto;
    gap: 0.8rem;
    justify-content: flex-start;
    /* 2.75rem = the 44px phone touch floor. */
    min-height: 2.75rem;
    padding: 0.66rem 0.78rem;
    width: 100%;
}

/* Labels ARE the point of the Menu flyout: undo the collapsed-rail opacity/width
   hide (the rail checkbox defaults to checked). */
.portal-shell .portal-flyout .portal-flyout-nav.portal-nav .portal-nav-label {
    opacity: 1;
    width: auto;
}

/* Restore the vertical inline-start active marker (the <=1100px band swaps it for a
   centred underline). Logical insets so RTL mirrors it without a bespoke rule. */
.portal-shell .portal-flyout .portal-flyout-nav.portal-nav a.active::after {
    bottom: 0.55rem;
    height: auto;
    inset-inline: 0 auto;
    top: 0.55rem;
    transform: none;
    width: 2px;
}

/* The day tracker keeps its two-line figure+bar label (the collapsed rail zeroes its
   flex share). */
.portal-shell .portal-flyout .portal-flyout-nav.portal-nav .portal-day-tracker .portal-nav-label {
    flex: 1 1 auto;
}

/* The bug-report entry becomes a normal full-width labelled item again. */
.portal-shell .portal-flyout .portal-flyout-nav.portal-nav .portal-nav-bug-wrap {
    border-top: 0;
    flex-direction: column;
    margin-left: 0;
    margin-top: 0.35rem;
    padding-top: 0;
    width: 100%;
}

.portal-shell .portal-flyout .portal-flyout-nav.portal-nav .portal-nav-bug {
    gap: 0.8rem;
    justify-content: flex-start;
    min-height: 2.75rem;
    padding: 0.66rem 0.78rem;
    width: 100%;
}

.portal-shell .portal-flyout .portal-flyout-nav.portal-nav .portal-nav-bug .portal-nav-label {
    display: inline;
}

/* --- Phone (<=600px): the bar appears and replaces the header band's nav row and
   footer tools; the corner circles become the flyout triggers. --- */
@media (max-width: 600px) {
    :root {
        /* The bar's REAL height: 3.5rem of slots plus the home-indicator safe area
           beneath them. P0 wired fixed bottom widgets (admin assistant) to ride
           above this token. */
        --portal-mobile-bar-height: calc(3.5rem + var(--portal-safe-bottom));
    }

    /* Content must end above the fixed bar. The longhand is set directly and NOT via
       --portal-shell-padding-block-end-base: custom properties substitute var() at
       the scope that DECLARES them, so a .portal-shell-level base override can never
       reach the :root-computed derived token. 0.85rem matches the <=720px shell base. */
    .portal-shell {
        padding-block-end: calc(0.85rem + var(--portal-mobile-bar-height));
    }

    .portal-mobile-bar {
        background: linear-gradient(180deg, var(--portal-sidebar-bg) 0%, var(--portal-sidebar-bg-2) 100%);
        border-top: 1px solid var(--portal-sidebar-border);
        bottom: 0;
        display: grid;
        grid-auto-columns: 1fr;
        grid-auto-flow: column;
        height: var(--portal-mobile-bar-height);
        inset-inline: 0;
        padding-bottom: max(0px, var(--portal-safe-bottom));
        position: fixed;
        /* Above page content (page-internal layers stop at 12) and the account
           backdrop, BELOW every overlay (case detail 15/30, settings 60/90, reports
           80), the topbar chrome (30) and toasts/loader (200+): an open overlay
           must cover the bar. */
        z-index: 14;
    }

    /* The bar is chrome on the always-dark sidebar surface; the light theme re-tints
       it exactly like .theme-light .portal-sidebar. */
    .portal-shell.theme-light .portal-mobile-bar {
        --portal-sidebar-bg: #2b2b2b;
        --portal-sidebar-bg-2: #1a1419;
        --portal-sidebar-border: rgba(255, 255, 255, 0.08);
    }

    .portal-mobile-bar-link {
        align-items: center;
        background: transparent;
        border: 0;
        color: rgba(255, 255, 255, 0.72);
        cursor: pointer;
        display: flex;
        flex-direction: column;
        font: inherit;
        font-size: 0.6rem;
        font-weight: 700;
        gap: 0.15rem;
        justify-content: center;
        letter-spacing: 0.02em;
        /* 44px touch floor; the 3.5rem row exceeds it anyway. */
        min-height: 2.75rem;
        min-width: 0;
        padding: 0.3rem 0.25rem;
        position: relative;
        text-decoration: none;
    }

    /* Blazicon svgs are 1em, so the icon must not inherit the tiny label font-size. */
    .portal-mobile-bar-link .portal-nav-icon {
        font-size: 1.3rem;
        height: 1.3rem;
        width: 1.3rem;
    }

    .portal-mobile-bar-label {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .portal-mobile-bar-link.active {
        color: #ffffff;
    }

    .portal-mobile-bar-link.active .portal-nav-icon {
        color: var(--bls-primary-dark, #e36ccc);
    }

    /* The sidebar's thin pink active-edge language, rotated to a top tick. */
    .portal-mobile-bar-link.active::before {
        background: var(--bls-primary, #c44eaf);
        border-radius: 999px;
        content: "";
        height: 2px;
        inset-inline: 32%;
        position: absolute;
        top: 0;
    }

    /* The header band shrinks to ONE slim row (brand only): the nav pill row and the
       footer tool cluster are HIDDEN because the bar + Menu flyout replace them -
       duplicate navigation must never stay visible. The collapse toggle goes too
       (there is nothing left to collapse). */
    .portal-sidebar,
    .portal-sidebar-toggle-state:checked + .portal-shell .portal-sidebar {
        grid-template-areas: "header";
        grid-template-columns: minmax(0, 1fr);
        padding: 0.5rem 0.85rem;
    }

    .portal-sidebar .portal-nav,
    .portal-sidebar .portal-sidebar-footer,
    .portal-sidebar .portal-sidebar-toggle {
        display: none;
    }

    /* Slim row: the wordmark may shrink on MOBILE only (the never-shrink rule on
       .portal-sidebar-logo is a desktop rule). */
    .portal-sidebar-logo {
        height: 2.4rem;
    }

    /* The corner circles graduate to 44px touch targets - they are now the Account /
       Customer-context flyout triggers. */
    .portal-profile-avatar,
    .portal-customer-context-button {
        height: 2.75rem;
        width: 2.75rem;
    }

    /* Hover is meaningless on touch and the avatar is a plain flyout toggle here, so
       the peek/pinned popover never shows at phone width (the Account flyout carries
       the same content). display:none beats the peek rules, which only lift
       opacity/visibility. */
    .portal-topbar-account .portal-account-popover {
        display: none;
    }

    /* The raise-case (+) would make the corner stack three circles tall over the slim
       header, so it moves into the Menu flyout's utility row (see PortalLayout). */
    .portal-topbar-account .portal-raise-case {
        display: none;
    }
}

/* ============================================================================
   P2 MOBILE CASE LIST (2026-07-28)
   Two pieces. (1) At <=600px .case-table swaps the generic labelled-card
   transform (which keeps serving every OTHER .portal-table, e.g. Admin tools)
   for a purpose-built three-line card: case number + unread badge with the
   status pill right-aligned / two-line title / a muted customer-priority-
   severity-age meta line. The Type, SLA, Contact and Files cells are
   display:none - all four live in the case popout, the trade the design spec
   records. (2) A shared phone-only filter bottom sheet (PortalFilterSheet)
   replaces the wrapped dropdown toolbars on the case list AND the kanban
   board; its chips drive the HOST's existing filter state. Desktop (>600px)
   toolbars and tables render byte-identical: the trigger/count are
   display:none'd here and revealed only inside the 600px block below, and the
   sheet only renders while open - which only a phone-width trigger can do
   (the P1 flyout precedent for unscoped-but-inert rules).
   ============================================================================ */

/* Phone-only toolbar controls, inert on desktop. */
.portal-filter-trigger,
.portal-filter-result-count {
    display: none;
}

/* Full-viewport scrim that is also the tap-to-dismiss control (a real button).
   .portal-sheet-* is the SHARED bottom-sheet shell (PortalBottomSheet, extracted
   from this filter sheet in P3): the filter sheet and the kanban Move-to sheet
   both ride these three rules. */
.portal-sheet-backdrop {
    animation: portal-flyout-backdrop-in 180ms ease;
    background: rgba(11, 7, 15, 0.66);
    border: 0;
    cursor: default;
    inset: 0;
    padding: 0;
    position: fixed;
    /* MUST be above .portal-topbar (30) and above .portal-topbar-notifications-open
       (120). The topbar is a stacking context, and its child .portal-topbar-account
       is position: fixed with pointer-events: auto - the floating avatar and
       customer-context circles. At 20 the scrim neither dimmed nor blocked them, so
       a full-height sheet put its own header row (with "Clear all" at the right end)
       directly under a live 44px avatar: tapping there opened the Account flyout as a
       SECOND aria-modal dialog over the sheet, and the shared `opener` in
       portal.js's focus trap was consumed by whichever closed first. 121 also keeps
       the scrim over the bottom bar (14), so the bar stays un-tappable while a sheet
       is up. Below the flyouts (140/141) and toasts/loader (200+). The case popout
       (backdrop 15, panel 30) never coexists with the sheet - at phone width it
       covers the whole list, toolbar included. */
    z-index: 121;
}

/* OPAQUE surface on purpose (covers content, never frosts) - which is also why it
   needs no .portal-a11y-notransparency fallback. Bottom-docked: it rises off the
   mobile bar, whose height token already carries the home-indicator safe area. */
.portal-sheet {
    animation: portal-sheet-in 180ms ease;
    background: var(--bls-popover-solid);
    border: 1px solid var(--bls-border);
    border-bottom: 0;
    border-radius: 1rem 1rem 0 0;
    bottom: var(--portal-mobile-bar-height, 0px);
    color: var(--bls-text);
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
    inset-inline: 0;
    /* Never taller than the viewport minus the bar and a top reveal strip - the
       page must stay visibly behind ("live results" is the point of the sheet). */
    max-height: calc(100dvh - var(--portal-mobile-bar-height, 0px) - 2.5rem);
    padding: 0.45rem 1rem 1rem;
    position: fixed;
    /* One rung above its own scrim (121) - see the note there for why the pair had
       to clear the topbar's fixed corner cluster. */
    z-index: 122;
}

/* Slide + fade only (house rule: no toy motion, no bounce). */
@keyframes portal-sheet-in {
    from {
        opacity: 0;
        transform: translateY(1.25rem);
    }
}

/* Motion gates: the OS preference AND the app Reduce-motion toggle both kill the
   slide/fade (same worked pattern as the P1 flyouts above). */
@media (prefers-reduced-motion: reduce) {
    .portal-sheet,
    .portal-sheet-backdrop {
        animation: none;
    }
}

.portal-shell.portal-a11y-reduce-motion .portal-sheet,
.portal-shell.portal-a11y-reduce-motion .portal-sheet-backdrop {
    animation: none;
}

/* Purely decorative sheet affordance (dismissal is backdrop/Escape/Apply). */
.portal-sheet-grabber {
    background: var(--bls-border);
    border-radius: 999px;
    flex: 0 0 auto;
    height: 0.3rem;
    margin: 0 auto;
    width: 2.6rem;
}

.portal-filter-sheet-header {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    gap: 0.75rem;
    justify-content: space-between;
}

.portal-filter-sheet-header h2 {
    font-size: 1.05rem;
    margin: 0;
}

/* The body is the scroll region so the header and the Apply footer never leave
   the screen. */
.portal-filter-sheet-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.85rem;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.portal-filter-sheet-group h3 {
    color: var(--bls-muted);
    font-size: 0.74rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    margin: 0 0 0.45rem;
    text-transform: uppercase;
}

/* Narrowing search for oversized chip groups (the sheet adds it past the
   FilterSheetModel.SearchThreshold - the kanban board can span 100+ customers).
   Same idiom as .kanban-filter-search-input, but at the 44px touch floor because
   the sheet only ever opens at phone width. */
.portal-filter-sheet-search {
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.6rem;
    box-sizing: border-box;
    color: var(--bls-text);
    font: inherit;
    font-size: 0.9rem;
    margin-block-end: 0.45rem;
    min-height: 2.75rem;
    padding: 0.45rem 0.65rem;
    width: 100%;
}

.portal-filter-sheet-search:focus-visible {
    border-color: rgba(196, 78, 175, 0.42);
    box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
    outline: none;
}

/* Shown when a group's search narrows it to nothing. */
.portal-filter-sheet-empty {
    color: var(--bls-muted);
    font-size: 0.85rem;
    margin: 0;
}

.portal-filter-sheet-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
}

.portal-filter-chip {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 999px;
    color: var(--bls-text);
    cursor: pointer;
    display: inline-flex;
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    gap: 0.4rem;
    /* 2.75rem = the 44px touch floor; the sheet only ever opens at phone width. */
    min-height: 2.75rem;
    padding: 0.4rem 0.9rem;
}

/* The live "cases matching everything else" figure, muted next to the label. */
.portal-filter-chip strong {
    color: var(--bls-muted);
    font-size: 0.78rem;
}

.portal-filter-chip.active {
    background: rgba(196, 78, 175, 0.16);
    border-color: rgba(196, 78, 175, 0.55);
}

.portal-filter-chip.active strong {
    color: inherit;
}

.portal-filter-sheet-footer {
    display: flex;
    flex: 0 0 auto;
}

/* Full-width "Show N cases" - dismisses the sheet (chips already applied live). */
.portal-filter-sheet-apply {
    flex: 1 1 auto;
    justify-content: center;
    min-height: 2.75rem;
}

/* --- Phone (<=600px): collapse both filter toolbars and re-lay the case rows as
   three-line cards. Everything here overrides the generic .portal-table card
   transform (the earlier 600px block) by later source order at equal-or-higher
   specificity - that generic transform keeps serving every other table. --- */
@media (max-width: 600px) {
    /* Toolbar collapse: search pill + Filters trigger + live result count. The
       dropdown menus, their single-option static labels and the Unread toggle all
       fold into the sheet (Unread becomes the "Messages" chip group).

       SCOPED TO .portal-filter-sheet-host ON PURPOSE - do not widen this back to
       a bare .case-table-actions / .kanban-filters. .case-table-actions has a THIRD
       host with no sheet (AdminTools' external-call log, four Service/Operation/
       Time/Result dropdowns), and hiding on the toolbar class alone deleted those
       filters with nothing in their place, because .portal-filter-trigger and
       .portal-filter-result-count are display:none by default and AdminTools
       renders neither. The marker goes on the toolbar div at the same time as the
       <PortalFilterSheet/> mount, never separately. */
    .portal-filter-sheet-host .kanban-filter-menu,
    .portal-filter-sheet-host .case-filter-static,
    .portal-filter-sheet-host .case-table-unread-toggle {
        display: none;
    }

    .portal-filter-trigger {
        align-items: center;
        display: inline-flex;
        flex: 0 0 auto;
        gap: 0.4rem;
    }

    .portal-filter-result-count {
        color: var(--bls-muted);
        display: inline-block;
        flex: 0 0 auto;
        font-size: 0.82rem;
    }

    /* The search pill takes the remaining toolbar width; its input releases the
       9rem desktop floor so the pill, not the input, decides the width. */
    .case-table-actions .case-table-search,
    .kanban-filters .kanban-search {
        flex: 1 1 auto;
    }

    .case-table-search input,
    .kanban-search input {
        flex: 1 1 auto;
        min-width: 0;
    }

    /* --- Three-line case card --- */

    /* Cells whose data lives in the case popout anyway; hiding them is what keeps
       the card three lines (recorded trade, spec P2). */
    .case-table td[data-label="Type"],
    .case-table td[data-label="SLA"],
    .case-table td[data-label="Contact"],
    .case-table td[data-label="Files"] {
        display: none;
    }

    /* The purpose-built layout places everything by grid area - the generic
       transform's ::before data-labels would double-caption every cell. */
    .case-table td::before {
        content: none;
    }

    /* Row 1: case number + unread badge with the status pill right-aligned (the
       title row lives INSIDE the main cell - see its grid below). Row 2 of the tr
       grid is the meta line: customer / priority / severity / age. Column 1 takes
       all spare width (the customer name ellipsises in it); the pill/age columns
       hug their content. */
    .case-table tr {
        align-items: start;
        column-gap: 0.55rem;
        display: grid;
        grid-template-areas:
            "main main main status"
            "customer priority severity age";
        grid-template-columns: minmax(0, 1fr) max-content max-content max-content;
        /* 2.75rem = the 44px tap floor for the whole-card target; three lines of
           content exceed it in practice. */
        min-height: 2.75rem;
        padding: 0.6rem 0.85rem 0.65rem;
        row-gap: 0.35rem;
    }

    /* Customer-facing lists render no customer cell; without this the "customer"
       column would sit as an empty 1fr gap at the start of the meta line. :has()
       is already load-bearing elsewhere in this sheet (kanban page height, case
       command pages). The flexible track MUST sit under the spanning main cell:
       with max-content columns there instead, the tracks grow to the title's full
       unwrapped width (the 2-line clamp never engages) and the table's intrinsic
       sizing lets the whole card overflow the phone viewport - measured 568px at
       a 390px viewport. Items spanning a flexible track are excluded from
       intrinsic track sizing, so 1fr-under-main is what makes the clamp bite. */
    .case-table tr:not(:has(> [data-label="Customer"])) {
        grid-template-areas:
            "main main status"
            "priority severity age";
        grid-template-columns: minmax(0, 1fr) max-content max-content;
    }

    /* Undo the generic transform's labelled-flex-row cell shape for the five cells
       the card keeps: each becomes a plain block placed by its grid area. */
    .case-table td.case-col-main,
    .case-table td[data-label="Status"],
    .case-table td[data-label="Customer"],
    .case-table td[data-label="Priority"],
    .case-table td[data-label="Severity"],
    .case-table td[data-label="Age"] {
        border-bottom: 0;
        display: block;
        margin: 0;
        padding: 0;
        text-align: start;
        width: auto;
    }

    .case-table td[data-label="Status"] {
        grid-area: status;
        justify-self: end;
    }

    .case-table td[data-label="Customer"] {
        grid-area: customer;
        min-width: 0;
    }

    .case-table td[data-label="Priority"] {
        grid-area: priority;
    }

    .case-table td[data-label="Severity"] {
        grid-area: severity;
    }

    .case-table td[data-label="Age"] {
        color: var(--bls-muted);
        font-size: 0.78rem;
        grid-area: age;
        justify-self: end;
    }

    /* The meta line reads as one row: pills and text centre against each other. */
    .case-table td[data-label="Customer"],
    .case-table td[data-label="Priority"],
    .case-table td[data-label="Severity"],
    .case-table td[data-label="Age"] {
        align-self: center;
    }

    /* Main cell: its own two-row grid - number + badge on top, title beneath. */
    .case-table td.case-col-main {
        column-gap: 0.45rem;
        display: grid;
        grid-area: main;
        grid-template-columns: max-content minmax(0, 1fr);
        row-gap: 0.2rem;
    }

    .case-table .case-col-main strong {
        grid-column: 1;
        grid-row: 1;
    }

    /* Title: the full main-area width, clamped to two lines. :not() keeps the
       unread badge (also a direct span) out of this rule. */
    .case-table .case-col-main > span:not(.unread-badge) {
        -webkit-box-orient: vertical;
        display: -webkit-box;
        grid-column: 1 / -1;
        grid-row: 2;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        margin: 0;
        overflow: hidden;
    }

    .case-table .case-col-main > .unread-badge {
        /* The generic transform's ".portal-table td:first-child span" display:block
           (0,2,2) outguns the badge's own inline-flex pin (0,2,1) and would drop the
           icon/count flex gap; re-pin it here (blockifies to flex as a grid item). */
        display: inline-flex;
        grid-column: 2;
        grid-row: 1;
        justify-self: start;
        margin-top: 0;
    }

    /* The generic transform's ".portal-table td span { display: inline }" would
       unwrap the pills' flex box; pin them back. */
    .case-table td[data-label="Status"] .status-pill,
    .case-table td[data-label="Priority"] .priority-pill,
    .case-table td[data-label="Severity"] .severity-pill {
        display: inline-flex;
    }
}

/* ============================================================================
   P3 MOBILE KANBAN (2026-07-28)
   Trello-style phone board. Three pieces. (1) At <=600px the board becomes a
   snap carousel: one 90vw bucket centred per swipe with the neighbour peeking,
   pan buttons hidden (swipe replaces them) and a snap-position dot row (the app
   suppresses scrollbars everywhere, ~line 315, so the dots are the only "which
   bucket" signal - portal.js's kanban nav module marks the active one).
   (2) Capability-gated touch rules ((hover:none) and (pointer:coarse), NOT a
   width): the grab cursor stops lying and cards stop selecting text under the
   long-press. Drag-to-pan and card draggable are disabled with the SAME query
   in portal.js / AdminKanban, so desktop mice keep drag-and-drop at any width.
   (3) The long-press "Move to..." sheet rides the shared .portal-sheet shell
   (PortalBottomSheet - the P2 filter sheet's chrome, extracted so both sheets
   stay one piece of code); only its content styles live here. Unscoped-but-
   inert: the sheet only opens from a touch long-press (P1/P2 precedent).
   Desktop (>600px, mouse) renders byte-identical - every rule below is inside
   a media block or reachable only from touch interactions.
   ============================================================================ */

/* Desktop never shows the dots (pan buttons + overflow state serve that role);
   the <=600px block below reveals them. */
.kanban-snap-dots {
    display: none;
}

/* --- "Move to..." sheet content (shell chrome = .portal-sheet, shared) --- */

.kanban-move-sheet-header {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 0.15rem;
}

.kanban-move-sheet-header h2 {
    font-size: 1.05rem;
    margin: 0;
}

/* Which case is being moved: number + title, one muted single line. */
.kanban-move-case {
    color: var(--bls-muted);
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The bucket list is the sheet's scroll region (the shell caps overall height). */
.kanban-move-options {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.45rem;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.kanban-move-option {
    align-items: center;
    background: var(--bls-surface-strong);
    border: 1px solid var(--bls-border);
    border-radius: 0.75rem;
    color: var(--bls-text);
    cursor: pointer;
    display: flex;
    font: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    gap: 0.6rem;
    justify-content: space-between;
    /* 2.75rem = the 44px touch floor; the sheet only opens from a touch long-press. */
    min-height: 2.75rem;
    padding: 0.55rem 0.9rem;
    text-align: start;
}

/* The bucket the case is already in: marked (accent + "Current") and disabled. */
.kanban-move-option.current {
    background: rgba(196, 78, 175, 0.16);
    border-color: rgba(196, 78, 175, 0.55);
    cursor: default;
}

.kanban-move-option strong {
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* --- Touch capability gates (any width - see the portal.js twin checks) --- */
@media (hover: none) and (pointer: coarse) {
    /* grab/grabbing promise a pan that portal.js refuses on touch. */
    .kanban-board,
    .kanban-board.kanban-board-panning {
        cursor: auto;
    }

    /* A long-press must arm the Move-to sheet, not select the card's text or pop
       the iOS callout. Scoped to board cards only - case detail etc. keep normal
       text selection. */
    .kanban-board .case-card {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        user-select: none;
    }

    /* The column's base overscroll-behavior:contain is a BOTH-AXES shorthand, and
       setting overflow-y un-visibles overflow-x too, so the column computes to a
       horizontal scroll container with nothing to scroll. Engines differ on whether
       contain on a can't-scroll axis still cuts the scroll chain; on ones that do,
       every horizontal swipe starting over a column (i.e. nearly all of them) would
       die in the column instead of chaining to the snap board. Re-open the inline
       axis for touch; vertical card scrolling stays contained via the block axis. */
    .kanban-column {
        overscroll-behavior-inline: auto;
    }
}

/* --- Phone (<=600px): the snap carousel --- */
@media (max-width: 600px) {
    /* Each swipe lands ON a bucket, never between two. overflow-x, smooth scroll
       and hidden scrollbars all inherit from the base .kanban-board rule. */
    .kanban-board {
        cursor: auto;
        scroll-snap-type: x mandatory;
    }

    /* Current bucket centred with a card-edge of each neighbour peeking - the
       "there's more" cue. 84vw, NOT the spec's 90vw: the board's scrollport is
       ~92.5vw (shell inline padding 0.85rem x2 + card borders), so a 90vw column
       leaves (scrollport - column)/2 = ~5px a side and the 0.5rem column gap
       swallows it - zero visible neighbour, which defeats the peek the 90vw was
       chosen FOR. 84vw = ~9px of neighbour card + the 8px gap visible at 390px
       (scales up on wider phones). The base rule's overflow-y:auto +
       overscroll-behavior:contain keep vertical card scrolling inside the
       column, and the base sticky header (name + count) pins within it. */
    .kanban-column {
        flex: 0 0 84vw;
        scroll-snap-align: center;
    }

    /* The base header's --bls-surface-strong is 5-12% alpha in every theme, so the
       pinned title sat directly over card text as cards scrolled under it. Layer
       the same tint over the opaque per-theme popover surface: cards are fully
       masked, and the header keeps the column's tinted look instead of going flat.
       Phone-only (desktop renders untouched); the custom-bg frosted/contrast/
       notransparency header rules all outrank this on specificity and keep their
       stronger masks. */
    .kanban-column header {
        background: linear-gradient(var(--bls-surface-strong), var(--bls-surface-strong)), var(--bls-popover-solid);
    }

    /* Swipe replaces the pan buttons. An explicit rule is required: the base sheet
       only hides them while the shell is NOT overflowing, and a phone board
       overflows whenever two or more buckets are visible. */
    .kanban-pan-button {
        display: none;
    }

    /* Snap-position indicator row (rendered by AdminKanban above the board shell). */
    .kanban-snap-dots {
        display: flex;
        flex: 0 0 auto;
        gap: 0.4rem;
        justify-content: center;
        padding-block: 0.45rem 0.1rem;
    }

    .kanban-snap-dot {
        background: var(--bls-border);
        border-radius: 999px;
        height: 0.4rem;
        transition: background 140ms ease, width 140ms ease;
        width: 0.4rem;
    }

    /* Active bucket stretches to a segment: shape + colour, so the indicator
       never relies on hue alone (and survives high contrast). */
    .kanban-snap-dot-active {
        background: rgba(196, 78, 175, 0.9);
        width: 1.1rem;
    }

    /* Hidden-buckets strip: KEPT as the single horizontally-scrolling row it
       already is - one ~3rem non-wrapping row can't stack up phone height, and
       folding it into the filter sheet would put restores two taps away
       (documented decision). Padding trimmed to the narrow screen. */
    .kanban-hidden-buckets {
        padding: 0.35rem 0.75rem;
    }
}

/* Motion gates for the dot grow/tint transition: the OS preference AND the app
   Reduce-motion toggle (the blanket .portal-a11y-reduce-motion reset also
   covers it; this keeps the pair explicit, per the house rule that every new
   animation names both). */
@media (prefers-reduced-motion: reduce) {
    .kanban-snap-dot {
        transition: none;
    }

    /* .kanban-board { scroll-behavior: smooth } is pre-existing, but P3 made that
       board the phone's PRIMARY navigation (scroll-snap columns), so an animated
       scroll now fires on every bucket change. The app-toggle arm is covered by the
       blanket .portal-a11y-reduce-motion reset; this is the OS arm it was missing.
       There is no blanket OS-arm reset in this stylesheet - all of its
       prefers-reduced-motion blocks are per-selector - so it has to be named. */
    .kanban-board {
        scroll-behavior: auto;
    }
}

.portal-shell.portal-a11y-reduce-motion .kanban-snap-dot {
    transition: none;
}

/* ==== P4 — CASE DETAIL ON A PHONE (<= 600px) ============================================
   Appended last on purpose: every rule here is a phone-only override of a base or
   <= 1100px rule declared earlier, so source order does the work and none of them needs
   inflated specificity. Nothing in this section animates. */

/* This section is appended AFTER `.portal-a11y-contrast :focus-visible` (which is only
   (0,2,0)), so the two focus surfaces introduced by P4 have to restore the high-contrast
   outline themselves - source order would otherwise let their own outline:none win and
   silently remove it. The rest of the app's outline:none + box-shadow focus rules sit
   BEFORE that a11y rule and are unaffected. */
.portal-a11y-contrast .case-pane-switch-option:focus-visible,
.portal-a11y-contrast .message-composer-bar:has(.message-composer-input:focus-visible) {
    outline: 3px solid var(--bls-primary, #c44eaf);
    outline-offset: 2px;
}

/* Same trap, two more controls that phone work put on the critical path.
   .portal-filter-sheet-search is P2's narrowing field inside the filter sheet - its
   own `outline: none` at line ~18165 is (0,2,0) and later in source than the a11y
   rule, so in high contrast the caret had no ring at all, only a 12%-alpha glow
   that is invisible against the high-contrast surface. .planner-input is
   pre-existing, but P5's docked assign menu makes it the client filter a phone user
   types into. Any NEW `outline: none` focus rule written after line 15180 must be
   added here too. */
.portal-a11y-contrast .portal-filter-sheet-search:focus-visible,
.portal-a11y-contrast .planner-input:focus-visible {
    outline: 3px solid var(--bls-primary, #c44eaf);
    outline-offset: 2px;
}

/* R3 — the Messages | Internal pane switch. Rendered by CaseDetailPanel.razor as a
   SIBLING immediately above <div class="case-command-split">, never as a third child of
   that grid (a third grid item would displace the panes on desktop). Staff only:
   customers have no split at all, and already reach the two halves as separate command
   tabs (Conversation / Progress tracker).
   Default = hidden; the ONE-PANE block below turns it on. */
.case-pane-switch {
    display: none;
}

/* ---- ONE PANE AT A TIME ----------------------------------------------------------
   The query is the repo's own two-arm overlay shape (phone-narrow OR phone-short),
   BOTH arms bounded to <= 1100px. The height arm matters: a landscape phone is
   844 x 390 and no max-width breakpoint reaches it, but 390px of height cannot carry
   two stacked panes' worth of chrome. Bounding both arms to 1100px is not optional —
   an unbounded (max-height: 600px) arm would hide a pane on a short-but-WIDE desktop
   window, where the >= 1101px block still has the two panes side by side. */
@media (max-width: 600px), (max-width: 1100px) and (max-height: 600px) {
    .case-pane-switch {
        display: flex;
        flex: 0 0 auto;
        gap: 0.4rem;
    }

    .case-pane-switch-option {
        align-items: center;
        background: var(--bls-surface-strong);
        border: 1px solid var(--bls-border);
        border-radius: var(--bls-radius-md);
        color: var(--bls-muted);
        cursor: pointer;
        display: flex;
        flex: 1 1 0;
        font: inherit;
        font-size: 0.82rem;
        font-weight: 800;
        gap: 0.4rem;
        justify-content: center;
        /* 44px touch floor (P0). */
        min-height: 2.75rem;
        min-width: 0;
        padding: 0.45rem 0.6rem;
    }

    .case-pane-switch-option svg[blazicon] {
        flex: 0 0 auto;
        height: 1rem;
        width: 1rem;
    }

    /* Same selected treatment as the command rail's active tab, so "which half am I
       looking at" reads identically to "which tab am I on". */
    .case-pane-switch-option[aria-pressed="true"] {
        background: linear-gradient(135deg, rgba(196, 78, 175, 0.2), rgba(123, 76, 193, 0.12));
        border-color: rgba(196, 78, 175, 0.42);
        color: var(--bls-ink);
    }

    .case-pane-switch-option:focus-visible {
        box-shadow: 0 0 0 3px rgba(196, 78, 175, 0.12);
        outline: none;
    }

    /* R4 — "when you select the customer conversation, show the WHOLE message pane".
       Exactly one pane is in layout, so it takes the whole 1fr row that
       grid-auto-rows gives it, and only its message list scrolls.
       The unselected pane is display:none'd rather than unmounted: both composers stay
       in the DOM with their @key'd @ref intact, so an in-progress draft survives every
       switch (re-parenting or conditional markup would discard it).
       Written with :not() so the DEFAULT state (caseSplitFocus == null, "balanced")
       renders as Messages with ZERO state writes — opening a case on a phone needs no
       persisted preference, and desktop load behaviour is byte-identical. */
    .case-command-split:not(.case-command-split-focus-right) > .case-command-split-pane-internal {
        display: none;
    }

    .case-command-split-focus-right > .case-command-split-pane-conversation {
        display: none;
    }

    /* One pane on screen: the pane's own border + 1rem of padding a side duplicate the
       page card's frame and cost 2rem of a 390px screen — which is exactly the width
       the composer pill was measured fighting for. Drop the frame; the page card still
       provides one.
       The theme-light repaint of this class is (0,3,0), so it needs its own release —
       specificity beats source order and this block is last in the file. */
    .case-command-split-pane,
    .portal-shell.theme-light .case-command-split-pane {
        background: transparent;
        border: 0;
        padding: 0;
    }
}

/* ---- PHONE WIDTH (<= 600px) ------------------------------------------------------ */
@media (max-width: 600px) {
    /* D8 — the command rail becomes ONE horizontal labelled scrolling row. The
       <= 1100px repeat(auto-fit, minmax(10.5rem, 1fr)) grid stacked five tiles (each
       with a description line) into ~14rem of a ~600px-tall viewport before the case
       content even started. Labels stay ON (an icon-only mode already exists — it is
       .case-command-sections-collapsed — and D8 explicitly is not that); the
       descriptions go, which is what makes one row fit. */
    .case-command-menu {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        gap: 0.4rem;
        /* Same trick as the base rule's inline pair, on the other axis: this row is a
           scroll container on BOTH axes (overflow-x here, overflow-y from the base rule),
           so without the block padding it would clip the active tab's 3px ring top and
           bottom. The negative margin keeps the row's occupied height unchanged. */
        margin-block: -3px;
        overflow-x: auto;
        padding-block: 3px;
        /* Land on a tab, but proximity (not mandatory) so a deliberate half-scroll to
           peek at the last tab is allowed to stay put. */
        scroll-snap-type: x proximity;
    }

    .case-command-menu-item {
        flex: 0 0 auto;
        /* 44px touch floor (P0). */
        min-height: 2.75rem;
        padding: 0.5rem 0.7rem;
        scroll-snap-align: start;
        white-space: nowrap;
    }

    .case-command-menu-item small {
        display: none;
    }

    /* The collapse toggle has no meaning in a single row — and hiding it would strand
       anyone whose persisted state is "collapsed", so the row also force-expands the
       labels and drops the collapsed-mode corner badge (the "N unread" pill inside the
       label is already showing). (0,3,0) beats the (0,2,0) collapsed-mode rules. */
    .case-command-sections .case-command-collapse {
        display: none;
    }

    .case-command-sections.case-command-sections-collapsed .case-command-text {
        display: grid;
    }

    .case-command-sections.case-command-sections-collapsed .case-command-unread-badge {
        display: none;
    }

    .case-command-sections.case-command-sections-collapsed .case-command-menu-item {
        justify-content: flex-start;
    }

    /* Tighter chrome: the popout is the whole screen now, so the outer paddings are
       pure loss. */
    .case-command-sections {
        gap: 0.6rem;
        padding: 0.6rem;
    }

    .case-command-page {
        gap: 0.6rem;
        padding: 0.6rem;
    }
}

/* ---- SHORT VIEWPORT SAFETY ARM (<= 560px tall, <= 1100px wide) -------------------
   The whole point of (a)-(c) is that a DEFINITE popout height gets divided down to the
   message list. Below ~560px of height there is nothing left to divide: on a landscape
   phone (844 x 390) the popout is ~371px and the popout header, the command rail, the
   page padding, the pane head and the composer already account for ~314px of it — the
   thread would compute to 0 and the case would be unreadable, which is strictly worse
   than the scroll-as-one behaviour this piece replaced.
   So here the BODY becomes the scrollport again and everything above the message list
   sizes to content. That alone would hand R2/R4 straight back, though: with no ancestor
   holding a definite height, `flex: 1 1 auto` on the message list resolves to its own
   content, so it stops being a scrollport at all — and that also drags the composer off
   the bottom of the pane, because margin-top:auto has no free space left to absorb.
   A definite max-height on the message list is the one thing that still makes it a
   scrollport when nothing above it has a definite height. The pane then sizes to
   head + capped message window + composer, so the composer sits directly under the
   messages (R4), the messages scroll inside their own window (R2), and you scroll the
   BODY to bring the whole pane into view. A cap and not a fixed height, so a short
   thread still shrinks and a long wrapped heading or a three-row timeline toolbar can
   still grow the pane instead of being crushed inside it.
   Bounded to <= 1100px because a short-but-WIDE desktop window keeps the >= 1101px
   geometry intact: its overlay is inset by the shell gutters and its chain still has a
   definite height that reaches the panes. The one thing that DOES break there is a
   command rail taller than its row, and that is fixed on .case-command-menu itself.
   Ordered last in the file so every declaration here out-ranks the base and <= 1100px
   rules it releases, at equal specificity. */
@media (max-width: 1100px) and (max-height: 560px) {
    .case-detail-body {
        overflow-y: auto;
    }

    /* Rail row + pages row both size to content now that the body scrolls. */
    .case-command-sections {
        grid-template-rows: auto auto;
    }

    /* Release BOTH page-level overflow rules from (c) — the page is content-sized here,
       so a clip would only hide the bottom of the pane. */
    .case-command-pages > .case-command-page,
    .case-command-page:has(> .thread-panel),
    .case-command-page:has(> .case-command-split) {
        overflow: visible;
    }

    /* THE message window (see the header comment above for why a cap is the only thing
       that keeps it a scrollport here). Covers all three layouts in one rule: the two
       staff split panes — exactly one of which is in layout, because the pane switch's
       own height arm (<= 600px tall) reaches every viewport this arm does — and the
       customer conversation, whose thread is a direct child of the page.
       No .case-command-split override is needed alongside it: the <= 1100px
       grid-auto-rows: minmax(0, 1fr) already resolves its fr maximum to max-content in an
       indefinite-height container, i.e. it sizes the pane to exactly this content. */
    .case-command-split-pane .thread-panel,
    .case-command-split-pane .timeline-panel,
    .case-command-page > .thread-panel {
        max-height: 12rem;
    }
}

/* Submit lock for the guided case intake. The card is the positioning context, so the
   overlay covers the form and its header actions together - the point is that the whole
   screen is plainly BUSY, not that individual controls quietly grey out. Blocks pointer
   events (the double-submit this replaces) and uses the solid popover surface so the form
   behind stays visible but clearly out of reach. */
.intake-card-submitting {
    position: relative;
}

.intake-submitting-overlay {
    align-items: center;
    background: var(--bls-popover-solid);
    border-radius: inherit;
    display: grid;
    inset: 0;
    justify-items: center;
    opacity: 0.94;
    position: absolute;
    z-index: 5;
}
