﻿/* ============================================================================
   SAMS Voice AI â€” Design system
   Ported from preview.html so Blazor pages + static preview render identically.
   ============================================================================ */

:root {
    --mud-palette-primary:#1E88E5; --mud-palette-primary-darken:#1976D2; --mud-palette-primary-lighten:#E3F2FD;
    --mud-palette-success:#2E7D32; --mud-palette-success-lighten:#E8F5E9;
    --mud-palette-warning:#ED6C02; --mud-palette-warning-lighten:#FFF4E5;
    --mud-palette-error:#D32F2F;   --mud-palette-error-lighten:#FDECEA;
    --mud-palette-info:#0288D1;    --mud-palette-info-lighten:#E1F5FE;
    --mud-palette-background:#F7F8FB; --mud-palette-surface:#FFFFFF; --mud-palette-background-grey:#FAFBFD;
    --mud-palette-divider:#EEF0F5; --mud-palette-lines-default:#E6E8EF;
    --mud-palette-text-primary:#1E2432; --mud-palette-text-secondary:#596174; --mud-palette-text-light:white;  --mud-palette-text-disabled:#9AA0B1;
    --mud-typography-default-family: Inter, Roboto, -apple-system, "Segoe UI", sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }
html, body {
    background: var(--mud-palette-background);
    font-family: var(--mud-typography-default-family);
    -webkit-font-smoothing: antialiased;
    /* THE missing reset. MudBlazor's own body{} rule sets typography and colour but never margin,
       and it ships no *{margin:0}, so <body> keeps the browser default 8px on every side. That is
       8px top + 8px bottom OUTSIDE a body sized to the viewport, i.e. the document is 16px taller
       than the screen and grows a second scrollbar beside the grid's own. Harmless-looking while
       body height was auto; guaranteed the moment it became height:100% further down. */
    margin: 0;
}
.mud-main-content { background: var(--mud-palette-background); }

/* ============================================================================
   Shell â€” app bar + permanent drawer
   ============================================================================ */

.svai-brand { display:flex; align-items:center; gap:10px; font-weight:600; font-size:15px; white-space:nowrap; }
/* The bar is 56px and this is a flex row that will happily wrap "SAMS Voice AI" onto three
   clipped lines when the toolbar runs out of room — nowrap above keeps it on one line, and
   below 600px the wordmark gives up its space entirely so the sign-out button stays on screen.
   The .mark ("SV") is kept: it is the only brand element that costs a fixed 28px.
   Companion rules for the facility picker are in MainLayout.razor's own <style> block. */
@media (max-width: 600px) {
    .svai-brand > span   { display:none; }
    .svai-tenant-chip    { display:none; }
}
.svai-brand .mark {
    width:28px; height:28px; border:1px solid rgba(255,255,255,.5); border-radius:50%;
    display:grid; place-items:center; font-size:12px; font-weight:700;
}
.svai-tenant-chip {
    font-size:12px; background:rgba(255,255,255,.14); padding:5px 10px;
    border-radius:14px; letter-spacing:.2px; margin-left:4px; color:#fff;
}
.svai-appbar-avatar {
    width:32px; height:32px; border-radius:50%; background:rgba(255,255,255,.22);
    display:grid; place-items:center; font-weight:600; font-size:12px;
    margin-left:6px; border:1px solid rgba(255,255,255,.4); color:#fff;
}
.svai-nav-section {
    font-size:10.5px; font-weight:700; letter-spacing:.8px;
    color:var(--mud-palette-text-disabled); text-transform:uppercase;
    padding:8px 12px 3px;
}
.mud-nav-link.active-link {
    background:var(--mud-palette-primary-lighten) !important;
    color:var(--mud-palette-primary-darken) !important;
    font-weight:600 !important;
}
/* Default MudNavMenu recolors active text to primary but leaves the icon grey;
   recolor the active icon to match (Mud only ships this for colored navmenus).
   Second selector: MudNavLink puts a user-supplied Class on the OUTER
   .mud-nav-item wrapper, not the .mud-nav-link anchor. The Approval nav link is
   flagged active manually (Class="active") for its deep-linked tab routes
   (/approvals/{approved,rejected,escalated,expired}), so also match the wrapper
   case here â€” otherwise those routes leave the icon grey. */
.mud-nav-link.active .mud-nav-link-icon,
.mud-nav-item.active .mud-nav-link-icon {
    color:var(--mud-palette-primary) !important;
}
/* Active + hover background for sidebar menu items (primary @ 22%). The middle
   selector covers the manual wrapper-level active class (see note above) so the
   Approval item highlights on every tab route, matching genuinely-active links. */
.mud-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled),
.mud-navmenu .mud-nav-item.active .mud-nav-link:not(.mud-nav-link-disabled),
.mud-navmenu .mud-nav-link:hover:not(.mud-nav-link-disabled) {
    background-color:rgb(30 136 229 / 22%) !important;
}
/* MudBlazor's bundled CSS colors active link TEXT primary via .mud-nav-link.active,
   which only hits the anchor â€” so the manually-flagged Approval item kept default
   text. Mirror the text color for the wrapper-level active class so it matches. */
.mud-nav-item.active .mud-nav-link:not(.mud-nav-link-disabled) {
    color:var(--mud-palette-primary) !important;
}

/* Soft shadow on the right edge of the left navigation sidebar to lift it
   off the content area (Mud renders the drawer flat at Elevation=0). */
.mud-drawer.mud-drawer-pos-left {
    box-shadow:4px 0 16px rgba(20,27,45,.10), 1px 0 4px rgba(20,27,45,.06) !important;
}

/* Compact the left nav so every menu item fits on short screens WITHOUT a
   scrollbar (the drawer content is overflow:hidden). Tighter row height; scoped
   to the left navigation drawer only so the right Conversations drawer is
   unaffected. */
.mud-drawer-pos-left .mud-navmenu .mud-nav-link {
    min-height:38px; padding-top:6px; padding-bottom:6px;
}
.mud-drawer-pos-left .mud-navmenu .mud-nav-link .mud-nav-link-icon {
    min-width:32px;
}

/* â”€â”€ Large-screen collapsed "mini rail" â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   On large screens the menu button collapses the left drawer to the Mini variant
   instead of hiding it. In that collapsed state (.mud-drawer-mini.mud-drawer--closed)
   each nav item becomes a centered icon with its label stacked underneath in a
   small font, so the rail stays fully clickable/navigable. Scoped to the mini +
   closed + left drawer only, so the expanded drawer and the small-screen overlay
   are untouched. */
.mud-drawer-pos-left.mud-drawer-mini.mud-drawer--closed .mud-navmenu {
    padding-left:4px !important;
    padding-right:4px !important;
}
.mud-drawer-pos-left.mud-drawer-mini.mud-drawer--closed .mud-navmenu .mud-nav-link {
    flex-direction:column;
    align-items:center;
    justify-content:center;
    text-align:center;
    gap:1px;
    padding:6px 2px;
    min-height:0;
    border-radius:8px;
    margin-bottom:1px !important;
}
.mud-drawer-pos-left.mud-drawer-mini.mud-drawer--closed .mud-navmenu .mud-nav-link .mud-nav-link-icon {
    min-width:0;
    margin:0;
}
/* MudBlazor clips the label in mini mode by shrinking the rail; reinstate it as a
   wrapped caption under the icon. */
.mud-drawer-pos-left.mud-drawer-mini.mud-drawer--closed .mud-navmenu .mud-nav-link .mud-nav-link-text {
    display:block !important;
    opacity:1 !important;
    width:100%;
    margin:0 !important;
    padding:0 !important;
    font-size:9.5px;
    line-height:1.15;
    white-space:normal !important;
    overflow:visible;
    text-overflow:clip;
    letter-spacing:.1px;
    text-align:center !important;
}
/* Section captions ("Workspace", "CareSense", "Admin") are just noise in the narrow
   rail â€” hide them while keeping the items themselves. CareSense is no exception, even
   though it is a product name rather than a grouping word: in the rail the ITEM itself
   is labelled "CareSense" (MainLayout, _miniRail), so showing the caption too would
   print the same word twice in a row. The caption is how the expanded drawer carries
   the name; the item label is how the rail does. */
.mud-drawer-pos-left.mud-drawer-mini.mud-drawer--closed .mud-navmenu .svai-nav-section {
    display:none;
}

/* Let the left nav scroll when items exceed the viewport height â€” overrides the
   global `.mud-drawer-content { overflow:hidden }` (line ~552) for the LEFT
   drawer only, so no menu item is ever hidden. Thin scrollbar, shown only when
   content actually overflows. Scoped so the right Conversations drawer keeps its
   own overflow handling. */
.mud-drawer-pos-left .mud-drawer-content {
    overflow-y:auto;
    overflow-x:hidden;
    scrollbar-width:thin;                                   /* Firefox */
    scrollbar-color:var(--mud-palette-lines-default) transparent;
}
.mud-drawer-pos-left .mud-drawer-content::-webkit-scrollbar {            /* Chromium/Edge/WebKit */
    width:6px;
}
.mud-drawer-pos-left .mud-drawer-content::-webkit-scrollbar-thumb {
    background:var(--mud-palette-lines-default);
    border-radius:3px;
}
.mud-drawer-pos-left .mud-drawer-content::-webkit-scrollbar-thumb:hover {
    background:var(--mud-palette-text-secondary);
}
.mud-drawer-pos-left .mud-drawer-content::-webkit-scrollbar-track {
    background:transparent;
}

/* <FocusOnNavigate> focuses the page <h1> after each navigation, drawing a
   focus outline around the header until the user clicks. Suppress that ring on
   the (non-interactive) heading; real controls keep their focus rings. */
h1:focus, h1:focus-visible { outline:none; }

/* ============================================================================
   Layout helpers
   ============================================================================ */

.scroll { overflow-y:auto; flex:1; min-height:0; }

/* Locks a page's scroll region while a drawer is open over it. Declared HERE, globally, because a
   page style block only exists while that page's component is rendered - this rule lived in
   PatientReviewPanel.razor, so on any page that applies the class WITHOUT rendering that panel it
   was a class with no rule behind it and the lock silently did nothing. The onboarding wizard's
   facility drawer is one such page: its scroller stayed live, which is the second scrollbar behind
   an open drawer, and a scrolled page also moves the origin MudBlazor computes its popovers from
   (they are positioned absolutely from the trigger rect PLUS the scroll offset), so an anchored
   tooltip lands off-screen by however far the page has moved.

   The Approval pages have the same latent hole: they only get the rule while PatientReviewPanel
   happens to be rendered inside their drawer. */
.no-pagescroll { overflow:hidden !important; }

/* The DOCUMENT lock, which is a different scroller from the one above.
   .no-pagescroll locks a page's own .scroll region; it does nothing about html/body. If the
   document itself has grown - any horizontal overflow will do it - a second scrollbar appears
   there, and that one breaks popovers anchored inside the drawer: MudBlazor positions a popover
   ABSOLUTELY at the trigger's viewport rect PLUS document.scrollTop/scrollLeft, which assumes the
   trigger scrolls with the document. A trigger inside a position:fixed drawer does not, so every
   pixel the document is scrolled is a pixel the panel is placed wrong - and it lands off-screen.

   Locking the document while a temporary right drawer is open keeps that offset at zero, so the
   ordinary anchoring is correct. :has() rather than a body class because nothing here can write to
   <body> without JS; where :has is unsupported nothing matches and behaviour is unchanged. */
html:has(.mud-drawer-temporary.mud-drawer--open),
body:has(.mud-drawer-temporary.mud-drawer--open) { overflow:hidden !important; }
.content-padded { padding:20px 28px 32px; }

/* ============================================================================
   Page headers
   ============================================================================ */

.page-head {
    padding:20px 28px 12px;
    display:flex; align-items:flex-end; justify-content:space-between; gap:24px;
}
.page-head h1 { font-size:22px; font-weight:600; margin:0 0 4px; letter-spacing:-.2px; color:var(--mud-palette-text-primary); }
.page-head .sub { font-size:13px; color:var(--mud-palette-text-secondary); }

.page-title { font-size:22px; font-weight:600; margin:0 0 4px; letter-spacing:-.2px; color:var(--mud-palette-text-primary); }
/* New Campaign authored margin:0 instead of the usual 0 0 4px. NOT because it lacks a
   strapline - it has one ("Step N of 5") exactly like the other seventeen. Its title sits
   in a back-button flex row whose wrapper already carries margin-bottom:4px, so the
   title's own margin would double it. A modifier keeps that difference in CSS rather than
   sending one page back to an inline style. */
.page-title--flush { margin-bottom: 0; }
.page-sub   { font-size:13px; color:var(--mud-palette-text-secondary); }

/* ============================================================================
   KPI strip (compact pill) + KPI card (grid tile)
   ============================================================================ */

.kpis { display:flex; gap:10px; }
.kpi {
    background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:10px; padding:10px 14px; min-width:112px;
    display:flex; flex-direction:column;
}
.kpi .kv { font-size:18px; font-weight:600; color:var(--mud-palette-text-primary); font-variant-numeric:tabular-nums; }
.kpi .kl { font-size:11px; color:var(--mud-palette-text-secondary); text-transform:uppercase; letter-spacing:.5px; margin-top:2px; }
.kpi.accent .kv { color:var(--mud-palette-primary); }

.kpigrid {
    display: grid;
    grid-template-columns: repeat(5,1fr);
    gap: 14px;
    margin-bottom: 20px;
    height: stretch;
}
@media (max-width:1100px) { .kpigrid { grid-template-columns:repeat(3,1fr); } }
@media (max-width:680px)  { .kpigrid { grid-template-columns:repeat(2,1fr); } }
.kpi-card {
    background:var(--mud-palette-surface); border:0;
    border-radius:12px; padding:16px 18px;
    box-shadow:0 1px 3px rgba(20,27,45,.06), 0 8px 20px rgba(20,27,45,.05);
    display:flex; flex-direction:column;
    position:relative; overflow:hidden;
    transition:box-shadow .18s ease, transform .18s ease;
}
/* Light colored accent bar so the cards read as cards, not blank white. */
.kpi-card::before {
    content:""; position:absolute; left:0; top:0; bottom:0; width:3px;
    background:var(--mud-palette-primary); opacity:.65;
}
.kpi-card:hover { box-shadow:0 2px 6px rgba(20,27,45,.08), 0 12px 28px rgba(20,27,45,.09); transform:translateY(-1px); }
/* Clickable KPI card (e.g. Pending Approval â†’ /approvals) */
.kpi-card--link { text-decoration:none; color:inherit; cursor:pointer; }
.kpi-card--link .kpi-go {
    position:absolute; top:12px; right:12px; font-size:18px;
    color:var(--mud-palette-text-secondary); opacity:.45;
    transition:opacity .18s ease, transform .18s ease, color .18s ease;
}
.kpi-card--link:hover .kpi-go { opacity:1; transform:translateX(2px); color:var(--mud-palette-primary); }
.kpi-card .ol { font-size:10.5px; font-weight:700; color:var(--mud-palette-primary); letter-spacing:.6px; text-transform:uppercase; }
.kpi-card .vl { font-size:36px; font-weight:600; margin-top:4px; letter-spacing:-.5px; color:var(--mud-palette-text-primary); font-variant-numeric:tabular-nums; line-height:1.1; }
.kpi-card .cp { font-size:12px; color:var(--mud-palette-text-secondary); margin-top:6px; display:flex; align-items:center; gap:4px; }
/* Up/down delta as a small, friendly tinted pill with a compact arrow. */
.kpi-card .cp.up, .kpi-card .cp.down {
    align-self:flex-start; font-weight:600; font-size:11px;
    padding:2px 9px 2px 6px; border-radius:999px; gap:2px; margin-top:8px;
}
.kpi-card .cp.up   { color:var(--mud-palette-success); background:rgba(46,125,50,.12); }
.kpi-card .cp.down { color:var(--mud-palette-error);   background:rgba(211,47,47,.12); }
.kpi-card .cp .mud-icon-root, .kpi-card .cp .material-icons { font-size:13px; }
.kpi-card .cp.up .mud-icon-root, .kpi-card .cp.down .mud-icon-root { font-size:13px; }

/* ============================================================================
   Dashboard overview â€” 4 KPI cards (2Ã—2) on the left, Approval Workflow panel
   on the right. Structure ported from the V2 mockup, restyled to the light
   SAMS theme. Collapses to a single column on narrow screens.
   ============================================================================ */

.dash-overview { display:grid; grid-template-columns:5fr 7fr; gap:16px; margin-bottom:20px; align-items:start; }
@media (max-width:900px) { .dash-overview { grid-template-columns:1fr; } }

/* Left: the 4 KPI cards become a fixed 2Ã—2 grid (overrides the 5-col base). */
.kpigrid--quad { grid-template-columns:repeat(2,1fr); margin-bottom:0; }
@media (max-width:480px) { .kpigrid--quad { grid-template-columns:1fr; } }

/* Each left card carries a blue rounded-badge icon in the top-right corner. The
   value (.vl) is vertically centered in the card and the caption (.cp) is pinned
   to the bottom, so both line up across the row regardless of content height. */
.kpigrid--quad .kpi-card { min-height:148px; }
.kpigrid--quad .kpi-card .vl  { margin-top:auto; }
.kpigrid--quad .kpi-card .cp,
.kpigrid--quad .kpi-card .cp.up,
.kpigrid--quad .kpi-card .cp.down { margin-top:auto; }
.kpi-ic {
    position:absolute; top:14px; right:14px;
    box-sizing:content-box;
    font-size:24px !important;                       /* glyph size */
    padding:9px; border-radius:12px;
    background:var(--mud-palette-primary-lighten);   /* light-blue tint */
    color:var(--mud-palette-primary) !important;     /* solid blue glyph */
}

/* â”€â”€ Right: Approval Workflow â€” "progress bar per row" â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.aw-panel { margin-bottom:0; height:100%; }
.aw-list  { display:flex; flex-direction:column; }

.aw-row {
    display:block; text-decoration:none; color:inherit;
    padding:7px 4px 7px; /*border-bottom:1px solid var(--mud-palette-divider);*/
    transition:background .14s ease;
}
.aw-row:last-child { border-bottom:0; }
.aw-row:hover { background:var(--mud-palette-background-grey); }

.aw-head  { display:flex; align-items:center; gap:9px; margin-bottom:9px; }
.aw-ic    { font-size:18px !important; flex:0 0 auto; color:var(--mud-palette-text-secondary); }
.aw-label { font-size:13.5px; font-weight:500; color:var(--mud-palette-text-primary); }
.aw-count {
    margin-left:auto; font-size:17px; font-weight:700; font-variant-numeric:tabular-nums;
    color:var(--mud-palette-text-primary); line-height:1;
}

/* Progress-bar track + proportional fill. Width comes from an inline --aw-pct var. */
.aw-bar  { height:5px; border-radius:3px; background:var(--mud-palette-divider); overflow:hidden; }
.aw-fill {
    height:100%; border-radius:3px; width:var(--aw-pct,0%); min-width:6px;
    background:var(--mud-palette-primary); transition:width .5s cubic-bezier(.33,0,.2,1);
}

/* Per-status accent â€” icon, alert-count tint, and bar fill color. */
.aw--pending   .aw-ic   { color:var(--mud-palette-primary); }
.aw--pending   .aw-count{ color:var(--mud-palette-primary); }
.aw--pending   .aw-fill { background:var(--mud-palette-primary); }
.aw--auto      .aw-ic   { color:var(--mud-palette-success); }
.aw--auto      .aw-count{ color:var(--mud-palette-success); }
.aw--auto      .aw-fill { background:var(--mud-palette-success); }
.aw--escalated .aw-ic   { color:var(--mud-palette-error); }
.aw--escalated .aw-count{ color:var(--mud-palette-error); }
.aw--escalated .aw-fill { background:var(--mud-palette-error); }
.aw--rejected  .aw-ic   { color:#FB8C00; }
.aw--rejected  .aw-count{ color:#FB8C00; }
.aw--rejected  .aw-fill { background:#FB8C00; }
.aw--expired   .aw-ic   { color:#ffc107; }
.aw--expired   .aw-count{ color:#ffc107; }
.aw--expired   .aw-fill { background:#ffc107; }

/* ============================================================================
   Filter bar
   ============================================================================ */

.filters { display:flex; gap:10px; align-items:center; flex-wrap:wrap; padding:8px 28px 14px; }

.mud-input {
    display:flex; align-items:center; background:var(--mud-palette-surface);
    border:1px solid var(--mud-palette-lines-default); border-radius:6px;
    padding:0 10px; height:36px; gap:6px;
}
.mud-input:focus-within { border-color:var(--mud-palette-primary); box-shadow:0 0 0 3px rgba(30,136,229,.12); }
.mud-input input, .mud-input select {
    border:0; outline:none; background:transparent;
    font:inherit; color:var(--mud-palette-text-primary); width:100%;
}
.mud-input.search { flex:1; min-width:240px; max-width:380px; }
/* .filters wraps, so the search box drops to its own line on a narrow screen — but a 240px floor
   inside a 28px-padded row leaves 296px, which overflows a 320px handset. Let it be fluid there
   and take the full line it already has, rather than forcing a horizontal scroll on the toolbar. */
@media (max-width:480px) {
    .filters { padding:8px 14px 12px; }
    .mud-input.search { min-width:0; max-width:none; flex:1 1 100%; }
}
.mud-input .label { font-size:11px; color:var(--mud-palette-text-secondary); font-weight:500; text-transform:uppercase; letter-spacing:.4px; white-space:nowrap; }
.mud-input .material-icons { color:var(--mud-palette-text-secondary); font-size:18px; }

/* Filter box that HOSTS a MudBlazor input (e.g. the searchable Provider dropdown).
   The app's custom `.mud-input` class name collides with MudBlazor's own internal
   `.mud-input` class, so a MudAutocomplete placed in a `.mud-input` wrapper renders a
   box-inside-a-box (the stacked look). We use a separate `.flt` wrapper and reset
   MudBlazor's input chrome inside it so the control sits inline like a native input. */
.flt {
    /* horizontal row: icon, label, autocomplete on one line, vertically centered â€”
       matching the sibling `.mud-input` filters. flex-flow row nowrap guarantees the
       autocomplete never drops onto a second line below the label. */
    display:flex; flex-flow:row nowrap; align-items:center; gap:6px;
    background:var(--mud-palette-surface);
    border:1px solid var(--mud-palette-lines-default); border-radius:6px;
    padding:0 10px; height:36px;
}
.flt:focus-within { border-color:var(--mud-palette-primary); box-shadow:0 0 0 3px rgba(30,136,229,.12); }
/* icon + label hold their size; the autocomplete takes the remaining width of the row */
.flt > .mud-icon-root, .flt > .flt-label { flex:0 0 auto; }
.flt .flt-label { font-size:11px; color:var(--mud-palette-text-secondary); font-weight:500; text-transform:uppercase; letter-spacing:.4px; white-space:nowrap; }
.flt .material-icons, .flt .mud-icon-root { color:var(--mud-palette-text-secondary); font-size:18px; }

/* the hosted MudAutocomplete: fill the rest of the row, stay vertically centered, and
   shed MudBlazor's stacked input chrome so it reads like the native inputs beside it. */
.flt-ac { flex:1 1 auto; min-width:0; margin:0; }
.flt-ac.mud-input-control { display:flex; flex-direction:column; justify-content:center; min-height:0; margin:0; }
.flt-ac .mud-input-control-input-container { min-height:0; }
.flt-ac .mud-input { margin:0; padding:0; border:0; border-radius:0; height:auto; min-height:0; background:transparent; box-shadow:none; }
.flt-ac .mud-input:focus-within { border:0; box-shadow:none; }
.flt-ac .mud-input.mud-input-underline::before,
.flt-ac .mud-input.mud-input-underline::after { display:none; }
.flt-ac .mud-input-slot { padding:0; height:auto; }
.flt-ac .mud-input-root { padding:0; font:inherit; color:var(--mud-palette-text-primary); }

/* Tenant-formatted date filter â€” hosts a MudDatePicker inline inside a `.mud-input`
   filter box. Native <input type="date"> renders in the browser's locale and ignores
   the tenant's configured date format; the picker honours it via DateFormat="@Dtf.DatePattern".
   These rules shed MudBlazor's stacked input chrome so the picker reads like the native
   filter inputs beside it (same reset approach as `.flt-ac` above). */
.svai-date { cursor:pointer; }
.svai-date .svai-date-pick { flex:1 1 auto; min-width:0; margin:0; }
.svai-date .mud-input-control { margin:0; }
.svai-date .mud-input { margin:0; padding:0; border:0; border-radius:0; height:auto; min-height:0; background:transparent; box-shadow:none; }
.svai-date .mud-input:focus-within { border:0; box-shadow:none; }
.svai-date .mud-input.mud-input-underline::before,
.svai-date .mud-input.mud-input-underline::after { display:none; }
.svai-date .mud-input-slot { padding:0; height:auto; }
.svai-date .mud-input-root { padding:0; font:inherit; font-size:13px; color:var(--mud-palette-text-primary); cursor:pointer; }
.svai-date .mud-input-root::placeholder { color:var(--mud-palette-text-secondary); opacity:1; }

.mud-chipset { display:flex;  flex-wrap:wrap; }
.mud-chip {
    display:inline-flex; align-items:center; gap:6px; height:30px; padding:0 10px 0 8px;
    background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:16px; font-size:12.5px; cursor:pointer; user-select:none;
    font-family:inherit; color:var(--mud-palette-text-primary);
}
.mud-chip:hover { background:var(--mud-palette-background-grey); }
.mud-chip.on { background:var(--mud-palette-primary-lighten); border-color:var(--mud-palette-primary); color:var(--mud-palette-primary-darken); }
.mud-chip .material-icons { font-size:14px; }
.mud-chip .dot { width:6px; height:6px; border-radius:50%; }

/* Escalation status-filter chips â€” badge-tinted background (no leading dot).
   The active filter keeps its tone and gains an inset ring in the same color. */
.mud-chip.esc-filter         { padding:0 10px; height:26px; font-weight:500; }
.mud-chip.esc-filter.default { background:var(--mud-palette-surface); border-color:var(--mud-palette-lines-default); color:var(--mud-palette-text-secondary); }
.mud-chip.esc-filter.warning { background:#FFF4E5; border-color:#FFD9A8; color:#ED6C02; }
.mud-chip.esc-filter.info    { background:#E1F5FE; border-color:#B3E5FC; color:#0288D1; }
.mud-chip.esc-filter.success { background:#E8F5E9; border-color:#C8E6C9; color:#2E7D32; }
.mud-chip.esc-filter:hover   { filter:brightness(.96); }
.mud-chip.esc-filter.on      { box-shadow:inset 0 0 0 2px currentColor; font-weight:700; }

.divider-v { width:1px; height:22px; background:var(--mud-palette-lines-default); flex-shrink:0; }

/* ============================================================================
   Grid card (data table wrapper)
   ============================================================================ */

.grid-card {
    margin:0 28px 24px;
    background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:10px; box-shadow:0 1px 2px rgba(20,27,45,.06);
    overflow:hidden; display:flex; flex-direction:column; min-height:0;
}
.grid-toolbar {
    display:flex; align-items:center; justify-content:space-between;
    padding:12px 16px; border-bottom:1px solid var(--mud-palette-divider); gap:12px; flex-shrink:0;
}
.grid-toolbar .count { font-size:13px; color:var(--mud-palette-text-secondary); }
.grid-toolbar .count b { color:var(--mud-palette-text-primary); }
.grid-actions { display:flex; gap:4px; }

.icon-btn {
    width:32px; height:32px; border-radius:50%; display:grid; place-items:center;
    cursor:pointer; color:var(--mud-palette-text-secondary);
    background:transparent; border:0; font-family:inherit; transition:background .12s, color .12s;
}
.icon-btn:hover { background:var(--mud-palette-background-grey); color:var(--mud-palette-primary); }
.icon-btn .material-icons { font-size:18px; }

.framed-note {
    margin:0 28px 18px; background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:8px; padding:8px 12px; font-size:11.5px;
    color:var(--mud-palette-text-secondary); display:flex; align-items:center; gap:8px;
}
.framed-note .material-icons { font-size:14px; color:var(--mud-palette-primary); }

/* ============================================================================
   Panels (Dashboard / Settings / misc.)
   ============================================================================ */

.panel {
    background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:10px; padding:16px; /*box-shadow:0 1px 2px rgba(20,27,45,.06);*/
}
.panel h3 { margin:0 0 10px; font-size:15px; font-weight:600; color:var(--mud-palette-text-primary); display:flex; align-items:center; gap:8px; }
.panel h3 .mud-icon-root { font-size:19px; color:var(--mud-palette-primary); flex:0 0 auto; }

.dash-grid   { display:grid; grid-template-columns:5fr 7fr; gap:16px; margin-bottom:16px; }
.dash-grid-2 { display:grid; grid-template-columns:7fr 5fr; gap:16px; }

/* â”€â”€ Operation Outcome â€” modern gradient gauge KPI card â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
.oc-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
/* year / month filter for the Operation Outcome breakdown */
.oc-filter { display:flex; align-items:center; gap:6px; flex:0 0 auto; }
.oc-select {
    font-size:12px; font-weight:600; color:var(--mud-palette-text-primary);
    background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:8px; padding:4px 8px; cursor:pointer; outline:none;
    transition:border-color .15s ease;
}
.oc-select:hover:not(:disabled) { border-color:var(--mud-palette-primary); }
.oc-select:focus-visible { border-color:var(--mud-palette-primary); }
.oc-select:disabled { opacity:.5; cursor:not-allowed; }
.oc-total-chip {
    font-size:11px; font-weight:600; color:var(--mud-palette-primary);
    background:var(--mud-palette-primary-lighten); padding:3px 10px; border-radius:11px; white-space:nowrap;
}
/* headline trend pill */
.og-trend { display:inline-flex; align-items:center; gap:3px; font-size:11.5px; font-weight:700;
    padding:3px 9px 3px 6px; border-radius:11px; white-space:nowrap; }
.og-trend .mud-icon-root { font-size:14px; }
.og-trend.up   { color:var(--mud-palette-success); background:rgba(46,125,50,.12); }
.og-trend.down { color:var(--mud-palette-error);   background:rgba(211,47,47,.12); }

.og-wrap { display:flex; align-items:center; gap:6px; margin-top:2px; }
.og-gauge-box { position:relative; flex:0 0 auto; width:206px; }
.og-gauge { width:100%; height:auto; aspect-ratio:200 / 124; display:block; overflow:visible; }
.og-arc { transition:stroke-width .2s ease; }
.oc-animate .og-arc { animation:ogDraw 1.1s cubic-bezier(.33,0,.2,1) both; }
@keyframes ogDraw { from { stroke-dasharray:0 100; } to { stroke-dasharray:var(--p) 100; } }

/* big % + caption, sitting in the open bottom of the semicircle */
.og-gauge-center { position:absolute; left:0; right:0; bottom:8px;
    display:flex; flex-direction:column; align-items:center; pointer-events:none; }
.og-pct { font-size:34px; font-weight:700; letter-spacing:-1.5px; line-height:1;
    color:var(--mud-palette-text-primary); font-variant-numeric:tabular-nums; }
.oc-animate .og-pct { animation:ocValueIn .6s ease both .15s; }
@keyframes ocValueIn { from { opacity:0; transform:scale(.82); } to { opacity:1; transform:scale(1); } }
.og-cap { font-size:10.5px; font-weight:600; letter-spacing:.3px; color:var(--mud-palette-text-secondary); margin-top:3px; }

/* sparkline column */
.og-side { flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; padding-top:6px; }
.og-side-cap { font-size:9.5px; font-weight:700; letter-spacing:1px; text-transform:uppercase;
    color:var(--mud-palette-text-secondary); }
.og-side-pct { letter-spacing:0; color:#10B981; font-variant-numeric:tabular-nums; }
.og-side-pct .pk { color:var(--mud-palette-text-secondary); font-weight:600; }
.og-spark { width:100%; height:48px; display:block; }
.og-spark-line { fill:none; stroke:#10B981; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.og-spark-area { stroke:none; }
.oc-animate .og-spark-line { stroke-dasharray:340; stroke-dashoffset:340; animation:ogSpark 1.2s ease both .2s; }
.oc-animate .og-spark-area { animation:fadeIn .8s ease both .5s; }
@keyframes ogSpark { to { stroke-dashoffset:0; } }
@keyframes fadeIn { from { opacity:0; } to { opacity:1; } }

/* outcome stat grid */
.og-stats { display:grid; grid-template-columns:1fr 1fr; gap:4px 16px; margin-top:14px;
    padding-top:12px; border-top:1px solid var(--mud-palette-divider); }
.oc-animate .og-stats { animation:fadeUp .5s ease both .35s; }
@keyframes fadeUp { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
.og-stat { display:flex; align-items:center; gap:8px; font-size:12.5px; padding:2px 0; }
.og-stat-dot { width:9px; height:9px; border-radius:3px; flex:0 0 auto; box-shadow:0 1px 2px rgba(0,0,0,.2); }
.og-stat-name { color:var(--mud-palette-text-secondary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.og-stat-count { margin-left:auto; flex:0 0 auto; font-weight:700; font-variant-numeric:tabular-nums; color:var(--mud-palette-text-primary); }
.og-stat-pct { flex:0 0 auto; width:34px; text-align:right; font-size:11px; color:var(--mud-palette-text-secondary); font-variant-numeric:tabular-nums; }

.oc-empty {
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px;
    height:200px; color:var(--mud-palette-text-secondary); font-size:13px;
}
.oc-empty .mud-icon-root { font-size:40px; opacity:.4; }

/* Responsive: collapse the dashboard grid, then stack gauge over sparkline */
@media (max-width:900px) { .dash-grid, .dash-grid-2 { grid-template-columns:1fr; } }
@media (max-width:560px) {
    .og-wrap { flex-direction:column; align-items:stretch; }
    .og-gauge-box { align-self:center; }
    .og-stats { grid-template-columns:1fr; }
}

.bar-chart {
    display:flex; align-items:flex-end; gap:4px; height:230px;
    padding:8px 4px 28px; position:relative;
    border-left:1px solid var(--mud-palette-lines-default);
    border-bottom:1px solid var(--mud-palette-lines-default);
}
.bar-chart .bar {
    flex:1;
    background:linear-gradient(180deg,var(--mud-palette-primary) 0%,var(--mud-palette-primary-darken) 100%);
    border-radius:3px 3px 0 0; position:relative; min-height:2px;
}
.bar-chart .bar:hover { filter:brightness(1.08); }
.bar-chart .lbl {
    position:absolute; bottom:-22px; left:50%; transform:translateX(-50%);
    font-size:10.5px; color:var(--mud-palette-text-secondary); font-variant-numeric:tabular-nums;
}
.bar-chart .vtop {
    position:absolute; top:-16px; left:50%; transform:translateX(-50%);
    font-size:10px; color:var(--mud-palette-text-primary); font-weight:600;
}

/* Axis titles for the Calls-by-Hour chart â€” the bars already carry per-hour tick
   labels, these name what each axis MEANS (y = number of calls, x = hour of day). */
.bar-chart-wrap { display:flex; align-items:stretch; gap:6px; }
.bar-chart-wrap .bar-chart { flex:1; min-width:0; }
.bar-chart-wrap .axis-y {
    flex:0 0 auto; align-self:center;
    writing-mode:vertical-rl; transform:rotate(180deg);
    font-size:10px; font-weight:600; letter-spacing:.5px; text-transform:uppercase;
    color:var(--mud-palette-text-secondary);
    padding-bottom:28px;   /* offset the chart's x-tick gutter so it centers on the bars */
}
.axis-x {
    text-align:center; margin-top:8px;
    font-size:10px; font-weight:600; letter-spacing:.5px; text-transform:uppercase;
    color:var(--mud-palette-text-secondary);
}

.mini-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12.5px;
    border-radius: 0px 0px 15px 15px;
    border: 1px solid #cee5f9;
}

    .mini-table th {
        background-color: #cee5f9;
    }

.mini-table th, .mini-table td { padding:8px 10px; text-align:left; border-bottom:1px solid var(--mud-palette-divider); }
.mini-table th { font-size:10.5px; text-transform:uppercase; color:var(--mud-palette-text-secondary); letter-spacing:.4px; font-weight:600; }
.mini-table tr:last-child td { border-bottom:0; }

.health-row { display:flex; align-items:center; gap:10px; padding:9px 0; border-bottom:1px solid var(--mud-palette-divider); }
.health-row:last-child { border-bottom:0; }
.health-row .hn { flex:1; font-size:13px; color:var(--mud-palette-text-primary); }
.health-row .hv { font-size:12px; color:var(--mud-palette-text-secondary); font-variant-numeric:tabular-nums; }
.health-row .mud-icon-root { font-size:16px; }

/* ============================================================================
   Pills â€” tone-coded status / outcome / sentiment indicators
   ============================================================================ */

.pill {
    display:inline-flex; align-items:center; gap:6px; height:22px; padding:0 9px;
    border-radius:11px; font-size:11.5px; font-weight:500; white-space:nowrap;
}
.pill .dot { width:6px; height:6px; border-radius:50%; background:currentColor; }
.pill.success { background:#E8F5E9 !important; color:#2E7D32 !important; }
.pill.warning { background:#FFF4E5 !important; color:#ED6C02 !important; }
.pill.error   { background:#FDECEA !important; color:#D32F2F !important; }
.pill.info    { background:#E1F5FE !important; color:#0288D1 !important; }
.pill.default { background:rgba(0, 0, 0, .08) !important; color:#596174 !important; border:1px solid #E6E8EF !important; }
/* Theme-blue tone â€” theme primary text on a light tint of the same blue.
   Used for Cancelled campaign-queue tasks so they read distinct from the
   gray Pending default. */
.pill.primary { background:rgba(30, 136, 229, .12) !important; color:#1E88E5 !important; }

/* â”€â”€ Active / inactive count pair (Tenants grid: Facilities, Users) â”€â”€
   Two labelled pills side by side, green for live and gray for switched off. Each
   carries its own "Active"/"Inactive" text â€” the count alone made the reader hover to
   learn which was which. Either pill is dropped entirely at 0, so min-width is what keeps
   the pair on one left edge across rows whether a count is 1 or 2 digits. */
.count-pair { display:flex; align-items:center; gap:5px; }
.count-pair .pill { min-width:72px; gap:5px; padding:0 8px; }

/* ============================================================================
   DataGrid cell helpers
   ============================================================================ */

.cell-date .abs { color:var(--mud-palette-text-primary); font-size:13px; }
.cell-date .rel { color:var(--mud-palette-text-secondary); font-size:11.5px; font-variant-numeric:tabular-nums; }

.cell-patient { display:flex; align-items:center; gap:10px; }
.cell-patient .avatar-sm {
    width:30px; height:30px; border-radius:50%;
    background:var(--mud-palette-primary-lighten); color:var(--mud-palette-primary-darken);
    display:grid; place-items:center; font-weight:600; font-size:11.5px; flex-shrink:0;
}
.cell-patient .name  { font-weight:500; line-height:1.2; }
.cell-patient .phone { font-size:11.5px; color:var(--mud-palette-text-secondary); font-variant-numeric:tabular-nums; }

.cell-dir { display:inline-flex; align-items:center; gap:6px; color:var(--mud-palette-text-secondary); }
.cell-dir .mud-icon-root, .cell-dir .material-icons { font-size:15px; }
.cell-dir.in  .mud-icon-root, .cell-dir.in  .material-icons { color:var(--mud-palette-success); }
.cell-dir.out .mud-icon-root, .cell-dir.out .material-icons { color:var(--mud-palette-primary); }
.cell-dur { font-variant-numeric:tabular-nums; color:var(--mud-palette-text-secondary); text-align:right; }

/* MudDataGrid row selection */
.mud-table-row.mud-selected-item,
.mud-data-grid-row.mud-selected-item {
    background:var(--mud-palette-primary-lighten) !important;
}
.mud-table-row.mud-selected-item td:first-child,
.mud-data-grid-row.mud-selected-item td:first-child {
    box-shadow:inset 3px 0 0 var(--mud-palette-primary);
}

/* ============================================================================
   Conversations drawer
   ============================================================================ */

/* Drawer header row */
.dh {
    padding:14px 18px; border-bottom:1px solid var(--mud-palette-divider);
    display:flex; align-items:center; gap:10px; flex-shrink:0;
}
.dh .mud-icon-root, .dh .material-icons { color:var(--mud-palette-primary); }
.dh .title { font-size:14px; font-weight:600; flex:0 0 auto; }
.dh .cid {
    font-size:11px; color:var(--mud-palette-text-secondary);
    font-family:'JetBrains Mono',monospace;
    background:var(--mud-palette-background-grey); padding:2px 6px; border-radius:4px;
}

/* Patient header strip */
.patient-hdr {
    display:flex; align-items:center; gap:12px; padding:16px 18px;
    background:var(--mud-palette-background-grey);
    border-bottom:1px solid var(--mud-palette-divider); flex-shrink:0;
}
.patient-hdr .av {
    width:44px; height:44px; border-radius:50%;
    background:var(--mud-palette-primary-lighten); color:var(--mud-palette-primary-darken);
    display:grid; place-items:center; font-weight:600; font-size:15px; flex-shrink:0;
}
.patient-hdr .name { font-weight:600; font-size:15px; }
.patient-hdr .meta { font-size:12px; color:var(--mud-palette-text-secondary); margin-top:2px; display:flex; gap:10px; flex-wrap:wrap; }
.patient-hdr .meta .mud-icon-root,
.patient-hdr .meta .material-icons { font-size:12px; vertical-align:middle; }

.dbody { overflow-y:auto; flex:1; min-height:0; }

/* Sections */
.sec { padding:16px 18px; border-bottom:1px solid var(--mud-palette-divider); }
.sec:last-child { border-bottom:0; }
.sec-title {
    font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.7px;
    color:var(--mud-palette-text-secondary); margin:0 0 10px;
    display:flex; align-items:center; gap:8px;
}
.sec-title .tag {
    font-size:10px; font-weight:600; padding:2px 6px; border-radius:4px;
    background:var(--mud-palette-primary-lighten); color:var(--mud-palette-primary-darken); letter-spacing:.3px;
    text-transform:none;
}
.sec-title .right {
    margin-left:auto; font-size:11px; font-weight:500;
    color:var(--mud-palette-text-secondary); text-transform:none; letter-spacing:0;
}

/* AI Summary */
.paper-ai {
    background:linear-gradient(180deg,var(--mud-palette-primary-lighten) 0%,var(--mud-palette-background-grey) 100%);
    border:1px solid var(--mud-palette-lines-default); border-radius:10px;
    padding:14px; box-shadow:0 1px 2px rgba(20,27,45,.06);
}
.paper-ai .row { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
.paper-ai .mk {
    width:22px; height:22px; border-radius:6px;
    background:var(--mud-palette-primary); color:#fff;
    display:grid; place-items:center; font-size:11px; font-weight:700;
}
.paper-ai .lb { font-size:11px; color:var(--mud-palette-primary-darken); font-weight:600; letter-spacing:.4px; text-transform:uppercase; }
.paper-ai p { margin:0; font-size:13.5px; line-height:1.55; color:var(--mud-palette-text-primary); }

/* Metadata grid */
.meta-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px 18px; }
.meta-grid .k { font-size:11px; text-transform:uppercase; letter-spacing:.4px; color:var(--mud-palette-text-secondary); margin-bottom:2px; }
.meta-grid .v { font-size:13px; font-variant-numeric:tabular-nums; display:flex; align-items:center; gap:6px; color:var(--mud-palette-text-primary); }
.meta-grid .v .mud-icon-root, .meta-grid .v .material-icons { font-size:14px; color:var(--mud-palette-text-secondary); }
/* Two label/value columns stop being readable once each is under ~130px: the value wraps under
   its own label and the pairs stop reading as pairs. Compact content, so it holds until 560. */
@media (max-width:560px) { .meta-grid { grid-template-columns:1fr; } }

/* Chat bubbles */
.bubble-row { display:flex; margin-bottom:10px; gap:8px; }
.bubble-row.ai { justify-content:flex-start; }
.bubble-row.patient { justify-content:flex-end; }
.bubble { max-width:78%; padding:9px 12px; border-radius:12px; font-size:13px; line-height:1.5; }
.bubble-row.ai .bubble {
    background:var(--mud-palette-background-grey); color:var(--mud-palette-text-primary);
    border:1px solid var(--mud-palette-lines-default); border-top-left-radius:3px;
}
.bubble-row.patient .bubble { background:var(--mud-palette-primary); color:#fff; border-top-right-radius:3px; }
.bubble .ts { display:block; font-size:10px; opacity:.7; margin-bottom:2px; font-variant-numeric:tabular-nums; }
.sp-mk {
    width:24px; height:24px; border-radius:50%;
    background:var(--mud-palette-primary); color:#fff;
    display:grid; place-items:center; flex-shrink:0; align-self:flex-end;
}
.sp-mk .mud-icon-root, .sp-mk .material-icons { font-size:14px; }

/* Related calls */
.rel-list { display:flex; flex-direction:column; gap:6px; }
.rel-item {
    display:flex; align-items:center; gap:10px;
    padding:8px 10px; border:1px solid var(--mud-palette-lines-default); border-radius:6px;
    background:var(--mud-palette-background-grey); cursor:pointer;
    transition:background .12s, border-color .12s;
}
.rel-item:hover { background:var(--mud-palette-primary-lighten); border-color:var(--mud-palette-primary) !important; }
.rel-item .when { font-size:12px; color:var(--mud-palette-text-secondary); min-width:92px; font-variant-numeric:tabular-nums; }
.rel-item .mud-icon-root, .rel-item .material-icons { font-size:14px; }
.rel-item .arrow { margin-left:auto; color:var(--mud-palette-text-disabled); }

/* Action buttons */
.btnrow { display:flex; gap:8px; flex-wrap:wrap; }
.btn {
    display:inline-flex; align-items:center; gap:6px; height:32px; padding:0 12px;
    border-radius:6px; border:1px solid var(--mud-palette-lines-default);
    background:var(--mud-palette-surface); color:var(--mud-palette-text-primary);
    font:inherit; font-size:12.5px; font-weight:500; cursor:pointer;
}
.btn:hover { background:var(--mud-palette-background-grey); }
.btn .mud-icon-root, .btn .material-icons { font-size:14px; }
.btn.primary { background:var(--mud-palette-primary); color:#fff; border-color:var(--mud-palette-primary); }
.btn.primary:hover { background:var(--mud-palette-primary-darken); }

/* Disabled buttons MUST look disabled. Without this a disabled .btn rendered identically to an
   enabled one — same fill, same cursor, same hover — so the only feedback was that clicking it
   did nothing. That is exactly how "Add plan doesn't open the popup" was reported on the
   Insurance page: the button is disabled until a SuperAdmin picks a tenant, and nothing said so.
   35 buttons across the app carry a disabled attribute and every one of them had this problem.
   :hover is neutralised too — a disabled button that still lights up on hover reads as clickable. */
.btn:disabled,
.btn[disabled] {
    opacity: .5;
    cursor: not-allowed;
}
.btn:disabled:hover,
.btn[disabled]:hover { background: var(--mud-palette-surface); }
.btn.primary:disabled:hover,
.btn.primary[disabled]:hover { background: var(--mud-palette-primary); }

/* Audio card */
.audio-card { background:var(--mud-palette-background-grey); border:1px solid var(--mud-palette-lines-default); border-radius:10px; padding:12px; }
.audio-card audio { width:100%; height:36px; outline:none; }
.audio-meta { display:flex; justify-content:space-between; align-items:center; margin-top:8px; font-size:11.5px; color:var(--mud-palette-text-secondary); }
.audio-meta a { color:var(--mud-palette-primary); text-decoration:none; font-weight:500; }
.audio-meta a:hover { text-decoration:underline; }

/* Sentiment sparkline */
.senti { background:var(--mud-palette-background-grey); border:1px solid var(--mud-palette-lines-default); border-radius:6px; padding:10px 12px; }
.senti .lg { display:flex; justify-content:space-between; font-size:10.5px; color:var(--mud-palette-text-secondary); margin-top:4px; font-variant-numeric:tabular-nums; }

/* MudDrawer override â€” match preview drawer position/shadow */
.mud-drawer--right.mud-drawer-open { box-shadow:0 10px 28px rgba(20,27,45,.18),0 4px 12px rgba(20,27,45,.1) !important; }
.mud-drawer-content { display:flex; flex-direction:column; overflow:hidden; }

/* ============================================================================
   SearchableMultiSelect â€” searchable + multi-select filter control.
   Hosted inside a `.mud-input.ms-host` box so the trigger matches the sibling
   Specialty / Priority filter boxes exactly (same height, border, alignment).
   ============================================================================ */
/* Fixed-width box: never grows/shrinks as the selected-text summary changes â€” the
   summary truncates with an ellipsis instead of stretching the box. (No overflow:hidden
   here â€” the dropdown menu is absolutely positioned and must escape the box.) */
.ms-host { position:relative; flex:0 0 auto; }
.ms-root { display:flex; align-items:center; flex:1 1 auto; min-width:0; }

/* Trigger â€” renders like the native <input>/<select> inside .mud-input. */
.ms-trigger {
    display:flex; align-items:center; gap:6px; width:100%; min-width:0; height:100%;
    border:0; background:transparent; padding:0; margin:0; cursor:pointer; font:inherit;
    color:var(--mud-palette-text-primary);
}
.ms-value { flex:1 1 auto; min-width:0; text-align:left; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; }
.ms-value.ph { color:var(--mud-palette-text-secondary); }
.ms-arrow { flex:0 0 auto; font-size:18px !important; color:var(--mud-palette-text-secondary); transition:transform .15s; }
.ms-root.open .ms-arrow { transform:rotate(180deg); }

/* Click-away backdrop + floating menu. */
.ms-backdrop { position:fixed; inset:0; z-index:1400; background:transparent; }
.ms-menu {
    position:absolute; z-index:1401; top:calc(100% + 5px); left:0; width:264px; padding:8px;
    background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:10px; box-shadow:0 10px 28px rgba(20,27,45,.18), 0 4px 12px rgba(20,27,45,.10);
}

/* Search */
.ms-search {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 42px !important;
    padding: 0 0 0 10px;
    border-right:none;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: var(--mud-palette-surface);
    transition: border-color .15s, box-shadow .15s;
}
.ms-search:focus-within { border-color:var(--mud-palette-primary); box-shadow:0 0 0 3px rgba(30,136,229,.12); }
.ms-search input { border:0; outline:none; background:transparent; font:inherit; font-size:13.5px; width:100%; color:var(--mud-palette-text-primary); }
.ms-search-ic { font-size:18px !important; color:var(--mud-palette-text-secondary); flex:0 0 auto; }
.ms-search-x { font-size:16px !important; color:var(--mud-palette-text-secondary); cursor:pointer; border-radius:50%; flex:0 0 auto; }
.ms-search-x:hover { background:var(--mud-palette-background-grey); color:var(--mud-palette-text-primary); }

/* Toolbar */
.ms-tools { display:flex; align-items:center; justify-content:space-between; padding:8px 2px 6px; }
.ms-count { font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.5px; color:var(--mud-palette-text-secondary); }
.ms-tools-actions { display:flex; align-items:center; gap:12px; }
.ms-link { border:0; background:transparent; cursor:pointer; font:inherit; font-size:12px; font-weight:600; color:var(--mud-palette-primary); padding:0; }
.ms-link:hover { text-decoration:underline; }

/* Options */
.ms-list { max-height:240px; overflow-y:auto; margin:0 -4px; padding:0 4px; }
.ms-list::-webkit-scrollbar { width:8px; }
.ms-list::-webkit-scrollbar-thumb { background:var(--mud-palette-lines-default); border-radius:8px; border:2px solid var(--mud-palette-surface); }
.ms-opt {
    display:flex; align-items:center; gap:10px; height:36px; padding:0 8px; border-radius:8px; cursor:pointer;
    font-size:13.5px; color:var(--mud-palette-text-primary); margin:0;
}
.ms-opt:hover { background:var(--mud-palette-background-grey); }
.ms-opt.sel { background:rgba(30,136,229,.08); }
.ms-opt input[type=checkbox] { width:16px; height:16px; flex:0 0 auto; accent-color:var(--mud-palette-primary); cursor:pointer; margin:0; }
.ms-opt-text { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Per-option explanation rendered inline (SearchableMultiSelect's ItemTooltip). Hidden by DEFAULT:
   where there is a pointer the tooltip carries this text, and a second copy in the list would be
   noise. The <=600 block turns it on, because a touch device has no hover and the tooltip can
   never fire there. The two are mutually exclusive by breakpoint, not two mechanisms at once. */
.ms-opt-sub { display:none; }

/* MudTooltip wraps its trigger in .mud-tooltip-root, which is width:fit-content - that would
   shrink an option row to its label and break the list's full-width rows and hover target.
   Shared, not page-scoped: any field passing ItemTooltip to SearchableMultiSelect needs it, and
   the Facilities drawer's Service tiers field is the second one to do so. */
.ms-opt-tiproot { display:block; width:100%; }
.ms-opt.sel .ms-opt-text { font-weight:600; }
.ms-empty { padding:18px; text-align:center; color:var(--mud-palette-text-secondary); font-size:12.5px; }

/* Fixed menu (FixedMenu="true") â€” for a control inside a MudDialog, where an absolutely-positioned
   menu is clipped. js/msanchor.js writes top/left in viewport coordinates and may clamp max-height
   to the space available, so the option list has to be the part that gives, hence the column flex.
   Sits above .mud-dialog (--mud-zindex-dialog + 2 = 1402) and below the snackbar, and above the
   .ms-backdrop below it â€” otherwise the backdrop covers the menu and eats every click.
   `bottom:auto` defeats the wizard's open-upward override; direction is JS's call here.

   Starts visibility:hidden and is revealed by place(). This app runs InteractiveServer, so the
   menu is inserted by a DOM patch and painted at these top:0/left:0 defaults a full SignalR
   round-trip BEFORE OnAfterRenderAsync can call place() â€” without this the menu visibly appears
   in the viewport corner and then snaps to its field on every open. visibility (not display:none)
   because place() has to measure offsetHeight/offsetWidth, and (not opacity:0) because a
   transparent menu would still swallow clicks. */
.ms-menu.fixed { position:fixed; z-index:1501; top:0; left:0; bottom:auto; display:flex; flex-direction:column; visibility:hidden; }
.ms-menu.fixed .ms-search, .ms-menu.fixed .ms-tools { flex:0 0 auto; }
.ms-menu.fixed .ms-list { flex:1 1 auto; min-height:0; }
.ms-root.ms-fixed .ms-backdrop { z-index:1500; }

/* MudBlazor's mud-open-dialog-center animation uses animation-fill-mode:both and ends on
   transform:scale(1), so .mud-dialog keeps a transform permanently. A transformed ancestor becomes
   the containing block for position:fixed children and clips them with its own overflow-y:auto â€”
   which traps any floating menu inside the dialog. A dialog that hosts one adds .svai-dlg-pop to
   swap in a fade that leaves no transform behind; internal scrolling is untouched. */
@keyframes svai-dlg-pop-in { from { opacity:0; } to { opacity:1; } }
.mud-dialog.svai-dlg-pop { animation-name:svai-dlg-pop-in; }

/* ============================================================================
   Shared filter dropdown (tenant-area tabs)

   The Approval pages have their own .appr-filterpop, sized and placed for a 720px five-control
   panel inside a sticky header they own. This is the same IDEA at the scale the tenant-area tabs
   need - one or two controls - and it is here rather than in each page block because four tabs
   use it and four copies drift.

   Anchored off the Filter button through the two custom properties stickyshadow.js publishes from
   it, which is why that button carries .js-filter-anchor.
   ============================================================================ */

.svai-headtools { display:flex; align-items:center; gap:8px; }

.svai-filter-toggle { display:inline-flex; align-items:center; }
.svai-filter-toggle.on { border-color:var(--mud-palette-primary); color:var(--mud-palette-primary); }
.svai-filter-badge {
    display:inline-grid; place-items:center; min-width:16px; height:16px; padding:0 4px;
    margin-left:6px; border-radius:8px; font-size:10.5px; font-weight:700;
    background:var(--mud-palette-primary); color:#fff;
}

/* height:0, NOT a full-screen scrim. Only a container so the fixed panel contributes no layout;
   it has no click handler, so an inset:0 version would cover the viewport and swallow every click
   on the page while the panel is open. .appr-filterpop-wrap is height:0 for the same reason. */
/* ── Filter bar: an inline CARD on a big screen, a DROPDOWN on a phone ─────────────────
   The spec, stated per width:

     >= 601   header row is [search] [Add]; NO Filter button. The filters are a card inside the
              tab that applies ON CHANGE - no Apply, no Clear, no "Filters" heading, because the
              controls are right there and a button that repeats what already happened does
              nothing.
     <= 600   header wraps to two lines - [title][Add] then [search][Filter] - the tabs follow,
              then the pinned table header. The same controls become a dropdown behind the Filter
              button and stage until Apply.

   ONE set of markup, switched here, so the two modes cannot drift; the pages test _filtersOpen
   (never the viewport) to decide whether a change applies now or waits for Apply, which works
   because below 600 the card is display:none until the button opens it. */
.svai-filterbar {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    padding: 12px 16px;
    margin-bottom: 12px;
}
/* display:contents dissolves the grid wrapper so the controls become direct children of the card
   and lay out as one row rather than a nested block. */
.svai-filterbar .svai-filterpop-grid { display: contents; }
.svai-filterbar .mud-input { min-width: 200px; }

/* The head, the foot and the Filter button belong to the dropdown only. Compound selectors, not
   bare class names: .svai-filterpop-head / -foot set display:flex further down this file, so an
   equal-specificity rule up here loses the tie on source order - which is exactly how a "Filters"
   heading and an Apply/Clear pair appeared on the desktop card once already. */
.svai-filterbar .svai-filterbar-smonly { display: none; }
.svai-filter-toggle { display: none; }
.svai-filterbar-count { margin-left: auto; font-size: 12px; color: var(--mud-palette-text-secondary); }

@media (max-width: 600px) {
    .svai-filter-toggle { display: inline-flex; align-items: center; }
    .svai-filterbar .svai-filterbar-smonly { display: flex; }

    /* Closed until the button opens it. Placement is IN FLOW - see the note on the rule itself
       below.

       That works only where the bar sits directly under the header in the MARKUP, which is the
       case on the tenant-area tabs. A page whose bar sits after a tab strip gets the strip's
       height as a gap between the button and the panel (measured: 60px on /settings at 393), and
       must override this - see .svai-stk--settings in Settings.razor. */
    .svai-filterbar { display: none; }
    /* IN FLOW, not positioned. Two placements were tried and both were wrong in opposite ways:
       anchored under the Filter button it covered the tab strip (a covered navigation reads as a
       removed one), and pushed below the whole sticky header it opened a visible gap the height of
       the tabs. In flow it simply appears where it sits in the markup - directly under the header,
       pushing the tabs and the table down - so nothing is hidden and there is no gap to get wrong.

       It also drops the coordinate problem entirely: no --svai-filterbtn-* to republish on scroll,
    /* COPIED FROM .conv-filterpop.open, which is the page this area was told to look like.
       Three of my own departures are undone here: a `left` (Conversations sets only `right`, so
       the panel keeps its own width instead of stretching edge to edge), a viewport-derived `top`
       (it hangs off the BUTTON, via the two custom properties stickyshadow.js publishes), and an
       in-flow experiment that pushed the page down instead of floating over it.

       width uses 100%, NOT 100vw: for a fixed element a percentage resolves against the viewport
       EXCLUDING scrollbars, while 100vw includes them - so calc(100vw - 30px) comes out ~15px too
       wide on a scrolling page and pushes a horizontal scrollbar onto it. Conversations' own
       comment records the same thing.

       It DOES float over the tab strip, and that is the house behaviour: .appr-filterpop and
       .conv-filterpop both cover whatever sits beneath their button - on the Approval pages that
       is their tab strip too. Anchoring below the tabs instead was tried twice and traded the
       overlap for a gap or for the panel not showing at all; this is the version that matches the
       rest of the portal. */
    .svai-filterbar.open {
        /* flex column, not block: the head and the foot stay put and only the field grid scrolls -
           the same shape as the drawers. See the max-height below for why that matters. */
        display: flex;
        flex-direction: column;
        /* RESET THE ROW MODE. .svai-filterbar's base rule is itself a flex container - a wrapping,
           centered, 12px-gapped ROW - and those three declarations were inert only while this rule
           said display:block. Turning it into a flex column activated them against the head, the
           grid and the foot: align-items:center stopped them stretching, so the head and the foot
           collapsed to the width of their own contents instead of spanning the panel, and the gap
           opened seams between the three. Restated here rather than removed from the base rule,
           which is still the desktop inline bar. */
        align-items: stretch;
        flex-wrap: nowrap;
        gap: 0;
        position: fixed;
        top: var(--svai-filterbtn-bottom, 120px);
        right: var(--svai-filterbtn-right, 15px);
        z-index: 900;                       /* over our bar (30), under MudBlazor's app bar */
        width: min(420px, calc(100% - 30px));

        /* THE LEFT-EDGE CAP. The comment on .svai-filterpop says "same left-edge cap, same reason,
           as .svai-filterbar above" - it was never actually here, and this is the bug that made the
           panel run off the left of a phone the moment a scrollbar appeared.

           The panel is pinned by its RIGHT edge, so its left edge lands at
           (button's right edge) - (panel width). Nothing in `width` knows where the button is, so
           on a narrow screen the two collide: at 393px width resolves to 363px while the button's
           right edge sits at ~377px, leaving 14px of margin - and a scroll region growing a ~15px
           scrollbar pulls the button 15px left, which spends all of it and then some. The panel
           then overhangs the left edge and part of it is unreachable.

           Capping the WIDTH against the same published offset the `right` uses keeps the left edge
           at >= 12px whatever the button does, instead of trying to predict it. It is the same cap
           .svai-filterpop already carries a few rules down.

           Settings.razor hit this first and fixed it for its own tabs a different way - pinning
           BOTH left and right with width:auto (.svai-stk--settings .svai-filterbar.open, whose
           comment describes this failure exactly). That rule overrides on specificity and is left
           alone: it also has its own `top` to correct for the tab strip. This one is the cap for
           every other page using the bar, and holds the panel to its natural width rather than
           stretching it edge to edge. */
        max-width: calc(100% - var(--svai-filterbtn-right, 15px) - 12px);

        /* THE BOTTOM CAP, the same failure on the other axis. `top` hangs off the button, which on
           these tabs sits in the SCROLLING content - so the lower the button, the less room the
           panel has, and with overflow:hidden and no cap the Apply/Clear footer is simply cut off
           below the fold. A fixed element cannot be scrolled to, so there is no way to reach it.

           dvh after vh, not instead of it: on mobile the browser chrome makes 100vh taller than
           what is actually visible. A browser without dvh keeps the vh line. */
        max-height: calc(100vh - var(--svai-filterbtn-bottom, 120px) - 12px);
        max-height: calc(100dvh - var(--svai-filterbtn-bottom, 120px) - 12px);

        padding: 0;
        margin-bottom: 0;
        border-radius: 10px;
        box-shadow: 0 14px 32px rgba(20, 27, 45, .20);
        overflow: hidden;
    }
    /* Head and foot hold their size; only the fields scroll. min-height:0 is required - a flex item
       defaults to min-height:auto and would refuse to shrink below its content, which puts the
       overflow back on the panel where nothing can scroll it. */
    .svai-filterbar.open .svai-filterpop-head,
    .svai-filterbar.open .svai-filterpop-foot { flex: 0 0 auto; }
    .svai-filterbar.open .svai-filterpop-grid {
        display: grid; grid-template-columns: minmax(0,1fr); gap: 12px; padding: 14px;
        flex: 1 1 auto; min-height: 0; overflow-y: auto;
    }
    .svai-filterbar.open .mud-input { min-width: 0; }
    .svai-filterbar.open .svai-filterpop-foot .btn { flex: 1 1 0; justify-content: center; }
    .svai-filterbar-count { display: none; }
}

/* ── Search box (all columns) ──────────────────────────────────────────────────────────
   UNCONTROLLED input on the caller's side: in Blazor Server a bound value is re-asserted by the
   server on every keystroke, and that echo is what makes the box flicker and drop characters. */
.svai-searchbox {
    display: flex; align-items: center; gap: 6px;
    height: 36px; padding: 0 10px; min-width: 0; width: 260px;
    border: 1px solid var(--mud-palette-lines-default); border-radius: 8px;
    background: var(--mud-palette-surface);
}
.svai-searchbox:focus-within { border-color: var(--mud-palette-primary); box-shadow: 0 0 0 3px rgba(30,136,229,.12); }
.svai-searchbox input {
    border: 0; outline: none; background: transparent; font: inherit; font-size: 13px;
    width: 100%; min-width: 0; color: var(--mud-palette-text-primary);
}
.svai-searchbox .mud-icon-root { color: var(--mud-palette-text-secondary); flex: 0 0 auto; }
.svai-searchbox-clear {
    appearance: none; border: 0; background: transparent; cursor: pointer; padding: 0;
    color: var(--mud-palette-text-secondary); font-size: 14px; line-height: 1; flex: 0 0 auto;
}
@media (max-width: 600px) { .svai-searchbox { width: auto; } }

.svai-filterpop-wrap { height:0; }

/* position:fixed so it paints OVER the rows instead of pushing them down. Measuring from the RIGHT
   lines its edge up with the button's and absorbs the scroll region's scrollbar width, which a
   hardcoded offset would have to guess at.

   z-index 900: above our sticky header (30) and the grid, below MudBlazor's app bar (1100) and its
   popover layer, so a control opened INSIDE this panel still renders on top of it. */
.svai-filterpop {
    position:fixed;
    top:var(--svai-filterbtn-bottom, 140px);
    right:var(--svai-filterbtn-right, 44px);
    z-index:900;
    width:min(520px, calc(100% - 24px));
    /* Same left-edge cap, same reason, as .svai-filterbar above - this panel is pinned by its
       right edge off the same published offset, so it overhangs the left in the same way once a
       scroll region grows a scrollbar. */
    max-width:calc(100% - var(--svai-filterbtn-right, 44px) - 12px);
    background:var(--mud-palette-surface);
    border:1px solid var(--mud-palette-lines-default);
    border-radius:12px;
    box-shadow:0 18px 44px rgba(20,27,45,.20), 0 6px 16px rgba(20,27,45,.12);
    overflow:hidden;
}
.svai-filterpop-head {
    display:flex; align-items:center; gap:8px; padding:12px 14px;
    border-bottom:1px solid var(--mud-palette-lines-default);
    color:var(--mud-palette-text-secondary);
}
.svai-filterpop-head h3 {
    margin:0; font-size:13px; font-weight:600; color:var(--mud-palette-text-primary);
    margin-right:auto;
}
.svai-filterpop-close {
    appearance:none; background:transparent; border:0; cursor:pointer; padding:0;
    display:grid; place-items:center; width:28px; height:28px; border-radius:8px;
    color:var(--mud-palette-text-secondary);
}
.svai-filterpop-close:hover { background:var(--mud-palette-background-grey); }

.svai-filterpop-grid { display:grid; grid-template-columns:minmax(0,1fr); gap:12px; padding:14px; }
.svai-filterpop-foot {
    display:flex; align-items:center; gap:8px; padding:12px 14px;
    border-top:1px solid var(--mud-palette-lines-default);
}
.svai-filterpop-count { margin-left:auto; font-size:12px; color:var(--mud-palette-text-secondary); }

@media (max-width: 600px) {
    .svai-filterpop { width:min(420px, calc(100vw - 24px)); }
    .svai-filterpop-foot .btn { flex:1 1 0; justify-content:center; }
    .svai-filterpop-count { display:none; }
}

/* ============================================================================
   Settings page
   ============================================================================ */

.tabs {
    display: flex;
    gap: 0;
    /*    background: var(--mud-palette-surface);*/
    border: 0;
    background: transparent;
    border-bottom: 1px solid #c0c5cc;
    border-top-left-radius: 8px;
    border-top-right-radius: 8px;
    margin: 0 28px 0;
}

.tab {
    padding:12px 20px; font-size:13px; font-weight:500;
    color:var(--mud-palette-text-secondary); cursor:pointer;
    border-bottom:2px solid transparent;
    display:flex; align-items:center; gap:6px;
    background:transparent; 
    border-left:0; border-right:0; border-top:0;
    font-family:inherit;
}
.tab .mud-icon-root, .tab .material-icons { font-size:16px; }
.tab:hover { color:var(--mud-palette-text-primary); }
    .tab.on {
        color: var(--mud-palette-primary);
        border-bottom-color: var(--mud-palette-primary);
        background: var(--mud-palette-primary-lighten);
        border-radius: 10px 10px 0 0;
    }

.tab-body { margin:0 28px 28px; border:1px solid var(--mud-palette-lines-default); border-top:0; border-bottom-left-radius:8px; border-bottom-right-radius:8px; background:var(--mud-palette-surface); }
.tab-body-inner { padding:16px; }

/* Dialing compliance table */
.comp-card { background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default); border-radius:10px; overflow:hidden; /*margin:0 28px 28px;*/ }
.comp-card table { width:100%; border-collapse:separate; border-spacing:0; font-size:13px; }
.comp-card th {
    text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.5px;
    color:var(--mud-palette-text-secondary); padding:12px 16px;
    background:var(--mud-palette-background-grey);
    border-bottom:1px solid var(--mud-palette-lines-default); font-weight:600;
}
.comp-card td {
    padding:12px 16px; border-bottom:1px solid var(--mud-palette-divider);
    font-variant-numeric:tabular-nums; color:var(--mud-palette-text-primary); vertical-align:middle;
}
.comp-card tr:last-child td { border-bottom:0; }
.comp-card td input[type=number] {
    width:70px; border:1px solid var(--mud-palette-lines-default);
    border-radius:4px; padding:4px 8px; font:inherit; font-size:13px;
    text-align:center; background:var(--mud-palette-surface); color:var(--mud-palette-text-primary);
}
.comp-card td input[type=number]:focus { outline:none; border-color:var(--mud-palette-primary); box-shadow:0 0 0 2px rgba(30,136,229,.12); }

.comp-window { position:relative; height:20px; background:var(--mud-palette-background-grey); border-radius:10px; border:1px solid var(--mud-palette-lines-default); }
.comp-window .bar-bg { position:absolute; top:0; bottom:0; background:var(--mud-palette-primary-lighten); border-radius:10px; }
.comp-window .bar-fg { position:absolute; top:0; bottom:0; background:linear-gradient(90deg,var(--mud-palette-primary),var(--mud-palette-primary-darken)); opacity:.55; border-radius:10px; }
.comp-scale { display:flex; justify-content:space-between; font-size:10px; color:var(--mud-palette-text-secondary); margin-top:2px; font-variant-numeric:tabular-nums; }

/* Template cards */
.set-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; margin:0 28px 28px; }
/* Template cards hold textareas, so they need real width — collapse at the same 900 as .dash-grid.
   The 28px side gutter goes too: at 320px it alone spends 56px, and the card is what matters. */
@media (max-width:900px) { .set-grid { grid-template-columns:1fr; } }
@media (max-width:560px) { .set-grid { margin:0 14px 20px; } }
.tpl-card { background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default); border-radius:10px; padding:16px; box-shadow:0 1px 2px rgba(20,27,45,.06); }
.tpl-head { display:flex; align-items:center; margin-bottom:14px; }
.tpl-head h4 { margin:0; font-size:15px; font-weight:600; color:var(--mud-palette-text-primary); }

/* A tab body inside Tenant.razor (Facilities / Operating Hours / Holidays). These render content
   only â€” the host page owns the scroller, the .content-padded gutter and the <h1>. Marker class:
   it keeps the panes identifiable and gives them a consistent bottom gap above the fold. */
.svai-tab-pane { padding-bottom:16px; }

.switch { display:inline-flex; align-items:center; gap:8px; margin-left:auto; cursor:pointer; }
.switch .track { width:34px; height:18px; background:var(--mud-palette-primary); border-radius:9px; position:relative; }
.switch.off .track { background:var(--mud-palette-lines-default); }
.switch .thumb { position:absolute; top:2px; left:18px; width:14px; height:14px; background:#fff; border-radius:50%; box-shadow:0 1px 2px rgba(0,0,0,.2); transition:left .15s; }
.switch.off .thumb { left:2px; }
.switch .lbl { font-size:12px; color:var(--mud-palette-text-secondary); font-weight:500; }

.field { margin-bottom:12px; }
.field .lb { font-size:10.5px; color:var(--mud-palette-text-secondary); text-transform:uppercase; letter-spacing:.5px; font-weight:600; margin-bottom:4px; }
/* Every text-like input, not just [type=text]. Password / date / time / number / email inputs were
   falling through this rule entirely and rendering as raw browser controls next to styled ones â€”
   which is why the password fields on the Users dialogs and the date/time fields on the Holiday
   dialog looked nothing like their neighbours. :not() keeps checkboxes and radios out. */
.field textarea,
.field input:not([type=checkbox]):not([type=radio]) {
    width:100%; border:1px solid var(--mud-palette-lines-default);
    border-radius:6px; padding:10px 12px; font:inherit; font-size:13px;
    background:var(--mud-palette-surface); color:var(--mud-palette-text-primary); resize:vertical;
}
.field textarea:focus,
.field input:not([type=checkbox]):not([type=radio]):focus {
    outline:none; border-color:var(--mud-palette-primary); box-shadow:0 0 0 3px rgba(30,136,229,.12);
}
/* Read-only fields (e.g. the user name / email shown for context) read as non-editable. */
.field input[readonly] { background:var(--mud-palette-background-grey); color:var(--mud-palette-text-secondary); }
.field .help { font-size:11px; color:var(--mud-palette-text-secondary); margin-top:4px; }

/* Wrapper that gives a SearchableMultiSelect the same input chrome as the native selects.
   Promoted here from Onboarding.razor's page <style> so the Facility dialog can use the very
   same control â€” page-scoped, it silently did nothing outside the wizard. */
.svai-ob-msbox {
    position:relative; display:flex; align-items:center; width:100%; min-height:40px; padding:0 12px;
    border:1px solid var(--mud-palette-lines-default); border-radius:6px; background:var(--mud-palette-surface);
}
.svai-ob-msbox:focus-within { border-color:var(--mud-palette-primary); box-shadow:0 0 0 3px rgba(30,136,229,.12); }
.svai-ob-msbox.svai-ob-invalid { border-color:var(--mud-palette-error) !important; box-shadow:0 0 0 3px rgba(244,67,54,.12) !important; }
.svai-ob-msbox .ms-trigger { height:auto; }
.svai-ob-msbox .ms-list { max-height:200px; }
.tpl-foot { font-size:11px; color:var(--mud-palette-text-secondary); }

/* Tenant identity */
.tenant-identity { padding:24px; }
.tenant-identity h3 { margin:0 0 16px; font-size:15px; font-weight:600; color:var(--mud-palette-text-primary); }
.tenant-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; }
/* Monospace text inputs — two of them side by side truncate the value being edited well before
   the layout looks broken, which is the failure worth avoiding on a form. Collapse earlier (700)
   than the card grids for that reason. .tenant-identity's 24px padding tightens with it. */
@media (max-width:700px) { .tenant-grid { grid-template-columns:1fr; } }
@media (max-width:560px) { .tenant-identity { padding:16px; } }
.tenant-field .lb { font-size:10.5px; color:var(--mud-palette-text-secondary); text-transform:uppercase; letter-spacing:.5px; font-weight:600; margin-bottom:4px; }
.tenant-field input[type=text] {
    width:100%; border:1px solid var(--mud-palette-lines-default);
    border-radius:6px; padding:10px 12px; font-family:'JetBrains Mono',monospace; font-size:13px;
    background:var(--mud-palette-background-grey); color:var(--mud-palette-text-primary);
}
/* Editable inputs get the same surface background as the select boxes; the grey
   fill stays only on the read-only fields to signal they can't be edited. */
.tenant-field input[type=text]:not([readonly]) {
    background-color:var(--mud-palette-surface);
}
.tenant-field input[type=text]:not([readonly]):focus {
    outline:none; border-color:var(--mud-palette-primary);
    box-shadow:0 0 0 3px rgba(30,136,229,.20);
}
/* Bootstrap-style form-select â€” the app's native dropdown look. Shared by .tenant-field (Tenant
   Identity) and .field (onboarding wizard, Facility dialog) so every form dropdown in the product
   is the same control. Note MudSelect is deliberately NOT used for these: MudBlazor's internal
   `.mud-input` class collides with this app's own `.mud-input` (see the .flt note above), so a
   MudSelect renders with different chrome than the native selects beside it. */
.tenant-field select,
.field select {
    width:100%; border:1px solid var(--mud-palette-lines-default);
    border-radius:6px; padding:10px 36px 10px 12px; font:inherit; font-size:13px;
    color:var(--mud-palette-text-primary); background-color:var(--mud-palette-surface);
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23596174' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat:no-repeat; background-position:right 12px center; background-size:14px 11px;
    -webkit-appearance:none; -moz-appearance:none; appearance:none; cursor:pointer;
    transition:border-color .15s ease, box-shadow .15s ease;
}
.tenant-field select:hover  { border-color:var(--mud-palette-text-secondary); }
.tenant-field select:focus  {
    outline:none; border-color:var(--mud-palette-primary);
    box-shadow:0 0 0 3px rgba(30,136,229,.20);
}
.tenant-note { margin-top:16px; font-size:11.5px; color:var(--mud-palette-text-secondary); line-height:1.5; }

/* â”€â”€ Provider Directory â”€â”€ */
.pd-toolbar { display:flex; align-items:center; gap:12px; /*margin:0 28px 12px;*/ }
.pd-search { display:flex; align-items:center; gap:6px; flex:0 1 360px;
    border:1px solid var(--mud-palette-lines-default); border-radius:8px; padding:6px 12px;
    background:var(--mud-palette-surface); color:var(--mud-palette-text-secondary); }
.pd-search input { border:0; outline:0; flex:1; font:inherit; font-size:13px; background:transparent; color:var(--mud-palette-text-primary); }

/* Clearable search boxes. js/inputfilter.js toggles `.has-text` on the .js-search wrapper as the
   input gains/loses text; the âœ• button clears it. Works inside both .pd-search and .mud-input. */
.js-search .js-clear {
    display:none; align-items:center; justify-content:center; flex:0 0 auto;
    border:0; background:transparent; cursor:pointer; padding:2px; line-height:0;
    border-radius:50%; color:var(--mud-palette-text-secondary);
}
.js-search.has-text .js-clear { display:inline-flex; }
.js-search .js-clear:hover { background:rgba(0,0,0,.06); color:var(--mud-palette-text-primary); }
.js-search .js-clear .material-icons { font-size:16px; }

/* â”€â”€ Full-page loader â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Fixed overlay with a light frosted-glass backdrop and a centred three-dot bounce
   loader. Used on the login POST (toggled by JS) and the dashboard's initial data
   load (Blazor). */
.svai-page-loader {
    position: fixed; inset: 0; z-index: 20000;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255, 255, 255, .55);
    -webkit-backdrop-filter: blur(8px) saturate(115%); backdrop-filter: blur(8px) saturate(115%);
}
.svai-page-loader[hidden] { display: none !important; }

/* Startup splash variant (host-level, App.razor): solid white so it fully hides the white/boot
   screen, and a fade-out when dismissed. z-index above everything else during boot. */
.svai-page-loader.svai-splash {
    background: #fff; -webkit-backdrop-filter: none; backdrop-filter: none;
    z-index: 30000; transition: opacity .3s ease;
}
.svai-page-loader.svai-splash.hide { opacity: 0; pointer-events: none; }

/* Medical ECG / heartbeat loader: a faint baseline waveform with a bright pulse that
   draws leftâ†’right and sweeps off, on loop. Fits the healthcare context. */
/* Circular badge holding the waveform, wrapped by a rotating spinner ring on its outline. */
.svai-page-loader .svai-ecg {
    position: relative;
    width: 92px; height: 92px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: #fff;
    box-shadow: 0 8px 24px rgba(15, 23, 42, .14);
}
/* Rotating conic ring hugging the circle's edge â€” the "outline" loader. */
.svai-page-loader .svai-ecg::before {
    content: ""; position: absolute; inset: -5px; border-radius: 50%;
    background: conic-gradient(from 0deg, rgba(30, 136, 229, 0) 0deg, rgba(30, 136, 229, .25) 150deg, #1E88E5 360deg);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
            mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
    animation: svai-spin 1.1s linear infinite;
}
/* Bigger ECG line inside the circle, above the ring. */
.svai-page-loader .svai-ecg svg { position: relative; width: 86px; height: auto; overflow: visible; display: block; }
@keyframes svai-spin { to { transform: rotate(360deg); } }
.svai-page-loader .svai-ecg .track {
    fill: none; stroke: rgba(30, 136, 229, .16); stroke-width: 2.5;
    stroke-linecap: round; stroke-linejoin: round;
}
.svai-page-loader .svai-ecg .pulse {
    fill: none; stroke: #1E88E5; stroke-width: 3;
    stroke-linecap: round; stroke-linejoin: round;
    stroke-dasharray: 100;                    /* path normalised to 100 via pathLength */
    filter: drop-shadow(0 0 4px rgba(30, 136, 229, .5));
    animation: svai-ecg 1.6s ease-in-out infinite;
}
@keyframes svai-ecg {
    0%   { stroke-dashoffset: 100; }   /* nothing drawn */
    55%  { stroke-dashoffset: 0;   }   /* fully drawn */
    100% { stroke-dashoffset: -100; }  /* swept off the right */
}

.pd-badge { display:inline-block; padding:3px 10px; border-radius:12px; font-size:11.5px; font-weight:600; }
.pd-badge.on  { background:rgba(46,160,67,.14);  color:#2EA043; }
.pd-badge.off { background:var(--mud-palette-background-grey); color:var(--mud-palette-text-secondary); }

/* Drawer form */
.pd-form { padding:18px 20px 28px; overflow-y:auto; }
.pd-section { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.6px;
    color:var(--mud-palette-primary); margin:18px 0 10px; padding-bottom:6px;
    border-bottom:1px solid var(--mud-palette-divider); }
.pd-form .pd-section:first-child { margin-top:0; }
.pd-row { display:flex; gap:12px; }
.pd-toggle { display:flex; align-items:center; gap:6px; font-size:13px; color:var(--mud-palette-text-primary); margin-top:4px; }

.pd-chips { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:6px; }
.pd-chip { display:inline-flex; align-items:center; gap:4px; padding:3px 6px 3px 10px;
    background:var(--mud-palette-primary-lighten); color:var(--mud-palette-primary);
    border-radius:12px; font-size:12px; font-weight:500; }
.pd-chip button { border:0; background:transparent; cursor:pointer; color:inherit;
    font-size:15px; line-height:1; padding:0 2px; }
.pd-chip button:hover { color:var(--mud-palette-error); }

/* â”€â”€ Consent Registry â”€â”€ */
.cn-badge { display:inline-block; padding:3px 10px; border-radius:12px; font-size:11.5px; font-weight:600; }
.cn-badge.ok    { background:rgba(46,160,67,.14); color:#2EA043; }
.cn-badge.muted { background:var(--mud-palette-background-grey); color:var(--mud-palette-text-secondary); }
.cn-flag { display:inline-block; margin-right:4px; padding:2px 8px; border-radius:10px; font-size:11px; font-weight:600;
    background:rgba(211,47,47,.12); color:var(--mud-palette-error); }

.cn-warn { display:flex; align-items:flex-start; gap:8px; margin-bottom:14px; padding:10px 12px;
    border-radius:8px; font-size:12.5px; line-height:1.45;
    background:rgba(237,108,2,.10); border:1px solid rgba(237,108,2,.35); color:var(--mud-palette-warning); }
.cn-warn .mud-icon-root { flex:0 0 auto; margin-top:1px; }

.cn-toggle { display:flex; align-items:center; gap:12px; padding:8px 0; }
.cn-toggle .cn-toggle-text { flex:1; }
.cn-toggle-text .t { font-size:13px; font-weight:600; color:var(--mud-palette-text-primary); }
.cn-toggle-text .s { font-size:11.5px; color:var(--mud-palette-text-secondary); margin-top:1px; }
.cn-divider { height:1px; background:var(--mud-palette-divider); margin:6px 0; }

.cn-history { display:flex; flex-direction:column; gap:8px; margin-top:4px; }
.cn-hrow { display:flex; gap:10px; padding:8px 10px; border:1px solid var(--mud-palette-lines-default);
    border-radius:8px; background:var(--mud-palette-background-grey); }
.cn-hwhen { flex:0 0 92px; font-size:11px; color:var(--mud-palette-text-secondary); font-variant-numeric:tabular-nums; }
.cn-hbody { flex:1; display:flex; flex-direction:column; gap:2px; }
.cn-haction { font-size:11px; font-weight:700; letter-spacing:.4px; color:var(--mud-palette-primary); }
.cn-hflags { font-size:12px; color:var(--mud-palette-text-primary); }
.cn-hby { font-size:11px; color:var(--mud-palette-text-secondary); }

/* ============================================================================
   MudDataGrid overrides â€” match preview table styling exactly
   MudBlazor renders thead th as .mud-table-cell inside .mud-table-head;
   the header content is wrapped in a .column-header div > .header-label span.
   ============================================================================ */

/* Center a column header's content (e.g. the Actions column) â€” used via HeaderClass="svai-col-center".
   The header content is a flex .column-header, so justify-content centers it (text-align won't). */
.mud-data-grid .mud-table-cell.svai-col-center .column-header { justify-content: center !important; }

/* â”€â”€ Header cells â”€â”€ */
.mud-data-grid .mud-table-head .mud-table-cell {
    padding: 10px 14px !important;
    font-size: 11.5px !important;
    font-weight: 600 !important;
    letter-spacing: .5px !important;
    text-transform: uppercase !important;
    color: #596174 !important;
    background: #F7F8FB !important;
    border-bottom: 1px solid #E6E8EF !important;
    white-space: nowrap !important;
    height: auto !important;
    line-height: 1.4 !important;
    vertical-align: middle !important;
}

/* Reset the inner column-header wrapper so it inherits all typography above */
.mud-data-grid .column-header {
    background: transparent !important;
    border: none !important;
    padding: 0 !important;
    margin: 0 !important;
    font-size: inherit !important;
    font-weight: inherit !important;
    color: inherit !important;
    letter-spacing: inherit !important;
    text-transform: inherit !important;
    display: flex !important;
    align-items: center !important;
    width: 100% !important;
    cursor: default !important;
    gap: 4px !important;
}
.mud-data-grid .header-label {
    font-size: inherit !important;
    font-weight: inherit !important;
    color: inherit !important;
    letter-spacing: inherit !important;
    text-transform: inherit !important;
    line-height: inherit !important;
    overflow: visible !important;
    white-space: nowrap !important;
}
/* Hide sort icon in non-sortable / hide excess space from sort button */
.mud-data-grid .column-options { display: none !important; }

/* â”€â”€ Conversations grid: stable column widths on sort â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Without this the grid runs in the browser-default table-layout:auto, so
   column widths are content-driven.

   CORRECTION to an earlier reading of this: it is NOT the sort arrow. MudBlazor
   renders .sort-direction-icon into EVERY sortable header and reveals it with
   opacity (MudBlazor.min.css: .sort-direction-icon{opacity:0}), so it occupies
   its width whether that column is sorted or not. What actually changes is WHICH
   ROWS are rendered: the grid paints 25 at a time, so sorting swaps which 25 are
   page 1 and searching swaps them outright. Auto layout re-measures the widest
   cell in view and every other column takes up the slack.
   Pinning table-layout:fixed with explicit per-column widths makes widths
   independent of content, which is why the fix works either way.
   Scoped to .svai-conv-grid (Conversations only) because the 8 widths below
   match its specific columns; other grids keep the auto layout. */
.svai-conv-grid table {
    table-layout: fixed !important;
    width: 100% !important;
}
.svai-conv-grid .mud-table-cell:nth-child(1) { width: 15%; }  /* Date / Time */
.svai-conv-grid .mud-table-cell:nth-child(2) { width: 20%; }  /* Patient     */
.svai-conv-grid .mud-table-cell:nth-child(3) { width: 10%; }  /* Direction   */
.svai-conv-grid .mud-table-cell:nth-child(4) { width: 12%; }  /* Outcome     */
.svai-conv-grid .mud-table-cell:nth-child(5) { width: 11%; }  /* Sentiment   */
.svai-conv-grid .mud-table-cell:nth-child(6) { width:  9%; }  /* Language    */
.svai-conv-grid .mud-table-cell:nth-child(7) { width: 15%; }  /* Campaign    */
.svai-conv-grid .mud-table-cell:nth-child(8) { width:  8%; }  /* Duration    */

/* ── Approval tabs: stable column widths on sort / page / search ──────────────
   Approved, Rejected, Escalated and Expired, all four at once - they share a column shape and
   there is nothing page-specific to say. Same fault and same fix as the Conversations block
   above: auto layout measures the widest cell AMONG THE ROWS IN VIEW, and these grids paint 25
   at a time, so every sort, page turn and search re-measured every column and the row shifted.

   Unlike Conversations the widths are NOT here - each column carries its own px width in the
   markup, next to the cell it sizes, because these four grids have different columns from one
   another. Prose columns are deliberately left unwidthed and share the remainder.

   Approval Queue (the fifth tab) keeps its own block in ApprovalQueue.razor: it is the grouped
   one, and grouping needs a rule these four do not. */
.appr-tab-grid .mud-table-root { table-layout: fixed; width: 100%; }
/* Long text wraps inside its own cell instead of widening the table - the same pairing
   ApprovalQueue.razor uses, and what makes pinning the other columns safe. */
.appr-tab-grid .mud-table-root tbody td {
    white-space: normal;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* â”€â”€ Body cells â”€â”€ */
.mud-data-grid .mud-table-body .mud-table-cell {
    padding: 11px 14px;
    border-bottom: 1px solid #EEF0F5 !important;
    vertical-align: middle !important;
    font-size: 13px !important;
    color: #1E2432 !important;
    line-height: 1.4 !important;
    height: auto !important;
}

/* Remove border on last row */
.mud-data-grid .mud-table-body .mud-table-row:last-child .mud-table-cell {
    border-bottom: 0 !important;
}

/* Hover row background â€” scoped to the BODY so hovering the header row
   (thead is also a .mud-table-row) doesn't repaint the header cells. */
.mud-data-grid.mud-table-hover .mud-table-body .mud-table-row:hover .mud-table-cell {
    background: #F7F8FB !important;
}

/* Pager row â€” keep compact */
.mud-data-grid .mud-table-pagination-toolbar {
    background-color: white !important;
    padding: 8px 14px !important;
    font-size: 12px !important;
    border-top: 1px solid #E6E8EF !important;
    min-height: unset !important;
}


/* THE MISSING LINK. MudBlazor already ships a complete height chain — .mud-layout is height:100%
   and MainLayout gives MudMainContent height:100% + overflow:hidden + a flex column — but it all
   resolves against <body>, and html/body set no height (see the rule near the top of this file).
   A percentage height against an auto-height parent is ignored, so .mud-layout collapsed to content
   height, .mud-main-content never clipped anything, and the DOCUMENT grew instead. That is the
   single root cause of the phantom second scrollbar, and the reason .svai-sticky-scroll used to
   guess its own height with calc(100vh - <some constant>): the constant was always a stand-in for
   "the space this region actually has", which the flex chain gives us exactly, for free.

   Gated on :has() so it only applies to pages that own a full-height scroll region. Pages whose
   content is NOT inside one (Users, SuperAdminDashboard) must keep growing the document — with the
   chain resolved, .mud-main-content's overflow:hidden would clip them unscrollably. */
html:has(.svai-sticky-scroll),
html:has(.svai-sticky-scroll) body { height: 100%; }

.svai-sticky-scroll {
    /* Fill the space MudMainContent's flex column has left, whatever that is. No viewport
       arithmetic, no app-bar constant, no JS measurement — so nothing here can drift out of sync
       with the shell again. min-height:0 is what lets a flex item actually shrink and scroll. */
    flex: 1 1 auto !important;
    height: auto;
    min-height: 0;

    /* Air under the sticky pager, on every list page. It is a MARGIN, i.e. the region genuinely
       ends above the viewport bottom — NOT a lifted pager (`bottom: 16px`). Sticky offsets are
       measured against the scrollport, so lifting the pager would leave a 16px band still INSIDE
       the scroller and rows would scroll visibly through it: the same trap as the header seam and
       the .is-stuck ledge band. Override per page with --svai-region-bottom. */
    margin-bottom: var(--svai-region-bottom, 16px);

    overflow-y: auto;
    background: var(--mud-palette-background, #F7F8FB);
    --svai-thead-top: 0px;
}

.svai-sticky-grid,
.svai-sticky-grid .mud-data-grid,
.svai-sticky-grid .mud-table-container { overflow: visible !important; }

/*.svai-sticky-grid {
    border-radius: 0 !important;
}*/
.svai-sticky-grid .mud-data-grid .mud-table-head .mud-table-cell {
    color: #ffffff !important;
    background: #1E88E5 !important;
    position: sticky !important;
    top: var(--svai-thead-top, 0px) !important;
    z-index: 20;
}

.svai-sticky-grid.is-stuck::before {
    content: "";
    position: sticky;
    top: 0;
    /* 21, and it must stay above .mud-table-container's z-index 20: that container is a
       positioned ancestor of BOTH the rows and the thead, so they share one stacking context and
       no z-index can slip between them. Lowering this to 19 to stop it covering the header put it
       under the whole table instead, and rows scrolled through the strip in plain sight.
       What actually stops it covering the header is stickyshadow.js only adding .is-stuck once
       the ledge is genuinely pinned - see the comment there. */
    z-index: 21;
    display: block;
    height: var(--svai-thead-top, 0px);
    margin: 0 -1px calc(-1 * var(--svai-thead-top, 0px));
    background: var(--mud-palette-background, #F7F8FB);
    box-shadow: inset 0 -6px 6px -6px rgba(20, 27, 45, .28);
    pointer-events: none;
}
/* Pager pinned to the bottom of the visible scroll region. Opaque background +
   thin top border so rows scrolling underneath don't bleed through. */
.svai-sticky-grid .mud-data-grid .mud-table-pagination,
.svai-sticky-grid .mud-data-grid .mud-table-pager {
    position: sticky !important;
    /* Nudged 6px lower so the footer sits closer to the very bottom edge of the
       scroll region. The opaque background still covers rows scrolling under it. */
    bottom: -1px !important;
    z-index: 25;
    background: var(--mud-palette-surface, #fff);
    border-top: 1px solid #E6E8EF;
}


.svai-sticky-toolbar {
    position: sticky !important;
    top: 0 !important;
    z-index: 30;
    background: var(--mud-palette-background, #F7F8FB);
    padding-top: 8px !important;
    padding-bottom: 12px !important;
    margin-top: 0 !important;
}


/* Conversations Hub (/conversations) â€” no sticky toolbar above the grid. */
.svai-stk--conv         { --svai-thead-top: 20px; }
/* Campaign Queue (/campaigns) â€” filter box scrolls away; thead pins below the
   common toolbar with a 20px gap (see per-page <style> in Campaigns.razor). */
.svai-stk--camp         { --svai-thead-top: 20px; }
/* Approval History (/approvals/history) â€” no sticky toolbar above the grid. */
.svai-stk--apphist      { --svai-thead-top: 20px; }
/* Learned Templates (/approvals/templates) â€” no sticky toolbar above the grid. */
.svai-stk--apptmpl      { --svai-thead-top: 20px; }
/* Provider Directory (/provider-directory) â€” sticky .pd-toolbar above. */
.svai-stk--provider     { --svai-thead-top: 64px; }
/* Consent Registry (/consent-registry) â€” sticky .pd-toolbar above. */
.svai-stk--consent      { --svai-thead-top: 64px; }
/* Facilities (/facilities) â€” no sticky toolbar above the grid. */
.svai-stk--facilities   { --svai-thead-top: 20px; }
/* Reminder Rules (/reminder-rules) â€” no sticky toolbar above the grid. */
.svai-stk--reminders    { --svai-thead-top: 20px; }
/* Facility Holidays (/facility-holidays) â€” facility-picker panel above. */
.svai-stk--holidays     { --svai-thead-top: 20px; }
/* Users (/users) â€” filter panel scrolls away; no sticky toolbar above the grid. */
.svai-stk--users        { --svai-thead-top: 20px; }
/* Approval (/approvals) â€” the HOST page owns the one scroller for all five tabs and pins a
   shared header above them, so each tab's grid thead has to clear it. The bar is 115px:
   .appr-headrow (70) + .appr-tabs (45), both fixed below so that total stays exact. Before this,
   every tab rendered a 100vh scroller of its OWN inside the host's, and a thead sticking to that
   inner box scrolled away with it â€” which is why sticky headers only misbehaved once the tabs
   were embedded here.

   113px, NOT 115px, and the 2px is load-bearing. Two independently-positioned sticky layers meet
   here: .appr-headbar painting 0â†’115 (z-index 30) and the thead pinning below it (z-index 20).
   Pinned at exactly 115 they ABUT, and the browser rounds each layer's paint edge on its own â€” at
   the fractional device-pixel ratios Windows display scaling produces (125% => 143.75dp, 150% =>
   172.5dp) the two edges can land a device pixel apart. That hairline is a window onto the rows
   behind, so every row flashes through the join as it scrolls past it. Pinning 2px HIGHER makes
   the header tuck under the bar instead: the layers overlap, so there is no line to show through
   at any rounding. The cost is the header's top 2px hiding behind the bar, which is invisible
   against a solid colour block.
   Keep this a plain px value: stickyshadow.js reads it with parseFloat(getPropertyValue(...)),
   and an unregistered custom property hands back the literal token stream â€” a calc() here would
   come back "calc(113px - 2px)", parse to NaN, and silently disable the .is-stuck ledge.
   MEASURED, not estimated: on a 1916px-wide capture the bar's bottom border sits at y=197 and the
   thead pins at y=198, putting the real bar height at 113px â€” 2px under the 115 the heights below
   suggest.
   123 = that measured 113, + 12px of breathing room below the tab line (.appr-headbar's
   padding-bottom), - a 2px tuck. The gap is deliberately part of the BAR rather than empty space
   between the bar and the table: the bar's background is opaque and sits at z-index 30, so rows
   scroll behind the gap instead of showing through it. Leave the gap outside the bar and it
   becomes a 12px window onto the scrolling rows â€” the original bug, reintroduced. */
.svai-stk--approvals    {
    /* THE number for this page, set here in CSS and nowhere else. The grid's thead pins at exactly
       this offset, and .appr-headbar takes it as a min-height, so the bar can never be shorter than
       the offset and no gap can open between them for rows to show through. That coupling is what
       makes a hand-picked value safe here: the two things that have to agree both read this one
       declaration, instead of one being typed and the other being derived by adding up part
       heights (70 + 45 + 12) that render ~2px short of their nominal total.
       Change it and the bar follows. The 12px of air under the tab line is .appr-headbar's
       padding-bottom; anything left over after the parts is absorbed by the min-height. */
    --svai-thead-top: 127px;
    /* (The pager gap is no longer set here — .svai-sticky-scroll now defaults --svai-region-bottom
       to 16px for every list page. Override it here only if this page wants a different gap.) */
}

/* Backstop for the tooltip double scrollbar on the Approval page. .svai-sticky-scroll is this
   page's ONLY scroll region and MudMainContent clips everything else, so nothing in the layout
   needs the document to scroll - the one thing that can grow it is an absolutely-positioned
   popover (a truncated-cell tooltip) left below the viewport bottom, which gave the page a second
   scrollbar beside the grid's. ApprovalQueue.razor closes the tooltip on scroll (the actual fix);
   this keeps a popover that still lands off-screen from ever adding that second scrollbar. Scoped
   to this page: pages without a full-height region must keep growing the document. */
html:has(.svai-stk--approvals),
html:has(.svai-stk--approvals) body { overflow: hidden; }

/* The shared .is-stuck ledge band is DISABLED on this page, because here it EATS THE HEADER.
   .svai-sticky-grid.is-stuck::before is a sticky (top:0) block as tall as --svai-thead-top that
   covers the strip between the scroll-region top and the pinned thead. It assumes that strip is
   the ~20px every other list page uses. Here it is 111px, and â€” this is the part that bites â€”
   the band is a child of the GRID PANEL, so it only pins once the panel's own top scrolls above
   the pin line. The thead pins far earlier, so through that whole window the band sits at its
   natural position OVERLAPPING the already-pinned header, and at z-index 21 vs the thead's 20 it
   paints straight over it. Measured off the bug capture: band 122â†’235, header pinned 198â†’240,
   so 37px of a 42px header was covered and only y=236-239 stayed blue â€” the header looked like it
   collapsed to a sliver with the rows showing through, for 111px of scrolling.
   Nothing is lost by removing it: the strip it exists to cover is already covered opaquely by
   .appr-headbar at z-index 30. */
.svai-stk--approvals .svai-sticky-grid.is-stuck::before { content: none; }


/* No ledge shadow under the tab line: the band's inset shadow is gone with the band, and the
   bar deliberately does NOT draw a replacement â€” the tab strip's own 1px bottom border is the
   separator. (An earlier revision put a :has()-driven drop shadow here; removed by request.) */

/* ============================================================================
   Approval page header (Approval.razor)
   One header for all five tabs: title, search-all-columns box, the ACTIVE tab's
   primary action, and the filter toggle. The tabs render content only â€” no
   scroller, no gutter, no <h1> of their own (same split as Tenant.razor's tabs).
   ============================================================================ */
.appr-headbar {
    position: sticky; top: 0; z-index: 30;
    background: var(--mud-palette-background, #F7F8FB);
    /* Breathing room between the tab line and the table's blue header. It lives HERE, inside the
       sticky bar, so the gap keeps the bar's opaque background and rows scroll behind it. The same
       12px sitting outside the bar would be a window onto the rows. */
    padding-bottom: 12px;
    /* Guarantees the bar is never shorter than the offset the thead pins at. This page is the one
       place where .svai-sticky-grid.is-stuck::before is disabled (it was painting over the header),
       so nothing else covers the strip above the thead — if the bar came up even 1px short, that
       strip would be a window onto the scrolling rows. Reading the same custom property the thead
       reads makes the two impossible to get out of step. Any slack lands below the tab line, as
       more of the same background, so it is invisible. */
    min-height: var(--svai-thead-top, 127px);
}
/* Fixed height â€” .svai-stk--approvals' --svai-thead-top is derived from it. */
.appr-headrow {
    height: 70px; box-sizing: border-box;
    padding: 8px 28px 4px;
    display: flex; align-items: center; gap: 16px;
}
.appr-headrow h1 { font-size: 22px; font-weight: 600; margin: 0 0 2px; letter-spacing: -.2px; }
.appr-headrow .appr-sub { font-size: 13px; color: var(--mud-palette-text-secondary); }
.appr-headtools { margin-left: auto; display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* Search box â€” same chrome as .mud-input, sized to sit inside the 70px header row. */
.appr-search {
    display: flex; align-items: center; gap: 6px;
    height: 34px; width: 260px; padding: 0 10px;
    border: 1px solid var(--mud-palette-lines-default); border-radius: 6px;
    background: var(--mud-palette-surface);
}
.appr-search:focus-within { border-color: var(--mud-palette-primary); box-shadow: 0 0 0 3px rgba(30,136,229,.12); }
.appr-search .mud-icon-root { font-size: 18px; color: var(--mud-palette-text-secondary); flex: 0 0 auto; }
.appr-search input {
    flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent;
    font: inherit; font-size: 13px; color: var(--mud-palette-text-primary);
}
/* The native clear affordance renders differently per browser (and not at all in Firefox),
   so it's suppressed and we draw our own cross that also resets the tab's filtered view. */
.appr-search input::-webkit-search-cancel-button { display: none; }
.appr-search .appr-search-clear {
    border: 0; background: transparent; cursor: pointer; padding: 0 2px; line-height: 1;
    color: var(--mud-palette-text-secondary); font-size: 15px; flex: 0 0 auto;
}
.appr-search .appr-search-clear:hover { color: var(--mud-palette-text-primary); }

/* Filter toggle reads as pressed while the filter card is open. */
/* Active-filter count on the Filter button. Below 900px the filter controls are collapsed
   behind that button, so this is the only on-screen evidence a filter is applied. Solid
   primary with white text in BOTH states: .appr-filter-toggle.on paints the button
   primary-lighten, and a primary-on-primary-lighten badge would be present but invisible.
   (Conversations carries an identical .conv-filter-badge in its own <style> block; the two
   are candidates to merge into one shared class when the rollout is finished.) */
.appr-filter-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 16px; height: 16px; padding: 0 4px; margin-left: 2px;
    border-radius: 8px;
    background: var(--mud-palette-primary); color: #fff;
    font-size: 10px; font-weight: 700; line-height: 1;
}

.btn.appr-filter-toggle.on {
    background: var(--mud-palette-primary-lighten);
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary);
}

/* Tab bar inside the sticky header. Fixed height keeps --svai-thead-top exact. */
.appr-tabs.tabs { height: 45px; box-sizing: border-box; margin-bottom: 0; }
.appr-tabs .tab { height: 44px; padding-top: 0; padding-bottom: 0; }

/* Gutter for the tab bodies (the tabs no longer carry .content-padded themselves).
   Later in the file than .content-padded, so this top padding wins.
   Zero on purpose: the gap above the table is supplied by .appr-headbar's padding-bottom, which
   stays put when the bar pins. Padding here would stack on top of it unscrolled and then vanish
   on scroll, so the spacing would visibly jump the moment the header pinned. */
.appr-tabbody { padding-top: 0; }

/* ── Filter dropdown ──────────────────────────────────────────────────────────
   Hangs off the header's Filter button and behaves like a real dropdown: it stays put while the
   rows scroll behind it, and it paints OVER the tab strip rather than sliding under it.
   That needs position:FIXED, not absolute. Absolute would tie the panel to the scrolling tab body,
   so it scrolled away with the rows; and any z-index low enough to sit under the header (30) also
   put it under the tabs. Fixed lifts it out of the scroller entirely, and the coordinates come
   from stickyshadow.js, which publishes the Filter button's viewport position on :root — the panel
   is anchored to the button itself rather than to a guessed offset.
   The wrapper stays height:0 so the panel never takes layout space or pushes the grid down; the
   rows behind must stay visible, since the point of leaving this open after Apply is to read the
   filters and their results together. */
.appr-filterpop-wrap { height: 0; }
.appr-filterpop {
    position: fixed;
    top: var(--svai-filterbtn-bottom, 140px);
    right: var(--svai-filterbtn-right, 44px);
    /* Above our sticky header (30) and everything in the grid, but below MudBlazor's app bar
       (1100) and its popover/dialog layers — so a date picker opened inside this panel still
       renders on top of it. */
    z-index: 900;
    width: min(720px, 100%);
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    box-shadow: 0 14px 32px rgba(20, 27, 45, .20);
    padding: 14px 16px;
}
.appr-filterpop-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.appr-filterpop-head h3 {
    margin: 0; font-size: 13px; font-weight: 600; letter-spacing: .3px;
    text-transform: uppercase; color: var(--mud-palette-text-secondary);
}
.appr-filterpop-head .mud-icon-root { color: var(--mud-palette-primary); }
.appr-filterpop-head .appr-filtercard-close { margin-left: auto; }
/* Two per row, each free to shrink (minmax 0) so long specialty names can't widen the panel. */
.appr-filterpop-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.appr-filterpop-foot {
    display: flex; align-items: center; gap: 8px;
    margin-top: 14px; padding-top: 12px;
    border-top: 1px solid var(--mud-palette-divider);
}
/* Live result count, so applying a filter reports back inside the panel that produced it. */
.appr-filterpop-count { margin-left: auto; font-size: 12px; color: var(--mud-palette-text-secondary); }
.appr-filterpop-count b { color: var(--mud-palette-text-primary); }

@media (max-width: 900px) {
    .appr-filterpop { width: min(420px, calc(100vw - 40px)); }
    .appr-filterpop-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Close (X) for the filter panel, so the header button isn't the only way back. */
.appr-filtercard-close {
    border: 0; background: transparent; cursor: pointer; line-height: 1;
    color: var(--mud-palette-text-secondary); padding: 4px 6px; border-radius: 4px; flex: 0 0 auto;
    display: inline-flex; align-items: center;
}
.appr-filtercard-close:hover { color: var(--mud-palette-text-primary); background: var(--mud-palette-background-grey); }

/* ============================================================================
   Campaign Wizard
   ============================================================================ */

.wiz-steps {
    display:flex; align-items:center; padding:16px 28px 0; gap:0; flex-shrink:0;
}
.wiz-step { display:flex; align-items:center; gap:8px; flex-shrink:0; }
.wiz-step .num {
    width:24px; height:24px; border-radius:50%; border:2px solid;
    display:grid; place-items:center; font-size:11px; font-weight:700; flex-shrink:0;
}
.wiz-step .lbl { font-size:12.5px; font-weight:500; white-space:nowrap; }
.wiz-step.future .num  { border-color:var(--mud-palette-lines-default); color:var(--mud-palette-text-disabled); background:transparent; }
.wiz-step.future .lbl  { color:var(--mud-palette-text-disabled); }
.wiz-step.active .num  { border-color:var(--mud-palette-primary); background:var(--mud-palette-primary); color:#fff; }
.wiz-step.active .lbl  { color:var(--mud-palette-primary); font-weight:600; }
.wiz-step.done .num    { border-color:#2E7D32; background:#2E7D32; color:#fff; }
.wiz-step.done .lbl    { color:var(--mud-palette-text-secondary); }
.wiz-connector         { flex:1; height:2px; background:var(--mud-palette-lines-default); margin:0 8px; min-width:20px; }
.wiz-connector.done    { background:#2E7D32; }

.wiz-body { flex:1; overflow-y:auto; padding:20px 28px 8px; min-height:0; }
.wiz-section { margin-bottom:24px; }
.wiz-section h3 {
    font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.6px;
    color:var(--mud-palette-primary); margin:0 0 14px; border-bottom:1px solid var(--mud-palette-lines-default);
    padding-bottom:8px;
}
.wiz-foot {
    padding:12px 28px 18px; border-top:1px solid var(--mud-palette-lines-default);
    display:flex; align-items:center; gap:10px; flex-shrink:0;
}
/* The gap that pushes the primary action right. A class rather than an inline style= on the div,
   so the phone rule below can drop it - inline beats every selector including !important. */
.wiz-footspacer { flex:1; }

/* ── Wizard chrome on a phone ────────────────────────────────────────────────
   The .wiz-* family has exactly TWO consumers, and both were read before this was written:
   /onboarding/new (Onboarding.razor) and /campaigns/new (NewCampaign.razor), five steps each.
   That is why these rules are global rather than scoped to one page - an earlier revision
   scoped them to the onboarding page precisely because the campaign wizard had not been looked
   at yet, and that reason has now expired.

   Both pages override .wiz-steps / .wiz-foot PADDING inline (they sit inside .content-padded and
   rebase it), and inline wins - so the padding below reaches NewCampaign only, while the layout
   declarations reach both. That is intended, not an oversight. */
@media (max-width: 600px) {
    .wiz-steps { padding:12px 15px 0; }
    .wiz-body  { padding:16px 15px 8px; }

    /* Keep every circle, show only the ACTIVE label. Five labels are far more nowrap text than a
       393px screen holds - Onboarding's are ~470px on their own - while the circles ARE the
       progress indicator (which of five, and which are done) and cost almost nothing.

       The arithmetic, so a sixth step is not added blind: 5 x 24 circle + 4 x (20 connector + 12
       margins) = 248, leaving ~115 for the active label in a 363px content box. Onboarding's
       longest is "EHR integration" at ~100; NewCampaign's is "Schedule" at ~58.

       Neither page loses the step name: both print "Step N of 5 - <label>" in their own header,
       which is what makes hiding the inactive words safe here. */
    .wiz-step .lbl        { display:none; }
    .wiz-step.active .lbl { display:block; }
    .wiz-step             { gap:6px; }
    .wiz-connector        { margin:0 6px; min-width:14px; }

    /* Buttons share the row. With the spacer left in, flex:1 pushes the primary action against
       the right edge and squeezes both to their text; min-width plus wrap is what keeps THREE
       buttons legible (Onboarding edit mode: Back + Next + Save; NewCampaign step 5: Back +
       Save as Draft + Launch Campaign) instead of three slivers. */
    .wiz-foot          { flex-wrap:wrap; gap:8px; padding-left:15px; padding-right:15px; }
    .wiz-footspacer    { display:none; }
    .wiz-foot .btn     { flex:1 1 auto; min-width:120px; justify-content:center; }
}

.channel-btns { display:flex; gap:8px; }
.channel-btn {
    display:inline-flex; align-items:center; gap:6px; height:36px; padding:0 18px;
    border-radius:8px; border:1.5px solid var(--mud-palette-lines-default);
    background:var(--mud-palette-surface); color:var(--mud-palette-text-secondary);
    font:inherit; font-size:13px; font-weight:500; cursor:pointer; transition:all .12s;
}
.channel-btn:hover { border-color:var(--mud-palette-primary); color:var(--mud-palette-primary); }
.channel-btn.on {
    border-color:var(--mud-palette-primary); background:var(--mud-palette-primary-lighten);
    color:var(--mud-palette-primary-darken);
}
.channel-btn .mud-icon-root { font-size:16px; }

.state-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(78px,1fr)); gap:6px; }
.state-chip {
    display:flex; align-items:center; gap:5px; padding:6px 8px;
    border:1px solid var(--mud-palette-lines-default); border-radius:6px;
    cursor:pointer; font-size:12.5px; background:var(--mud-palette-surface);
    color:var(--mud-palette-text-secondary); user-select:none; transition:all .1s;
}
.state-chip input[type=checkbox] { accent-color:var(--mud-palette-primary); margin:0; pointer-events:none; }
.state-chip.checked {
    border-color:var(--mud-palette-primary); background:var(--mud-palette-primary-lighten);
    color:var(--mud-palette-primary-darken);
}

.reach-pill {
    display:inline-flex; align-items:center; gap:10px; padding:12px 18px;
    background:var(--mud-palette-primary-lighten); border:1px solid var(--mud-palette-primary);
    border-radius:10px; font-size:13px; color:var(--mud-palette-primary-darken); font-weight:500;
}
.reach-pill .num { font-size:22px; font-weight:700; font-variant-numeric:tabular-nums; }
.reach-pill .mud-icon-root { font-size:20px; }

.tpl-preview {
    background:var(--mud-palette-background-grey); border:1px solid var(--mud-palette-lines-default);
    border-radius:8px; padding:14px;
}
.tpl-preview .body { font-size:13px; line-height:1.6; color:var(--mud-palette-text-primary); white-space:pre-wrap; }

.review-table { width:100%; border-collapse:separate; border-spacing:0; font-size:13px; }
.review-table tr:not(:last-child) td { border-bottom:1px solid var(--mud-palette-divider); }
.review-table td { padding:11px 14px; vertical-align:middle; }
.review-table td:first-child {
    width:150px; font-size:11px; font-weight:700; text-transform:uppercase;
    letter-spacing:.5px; color:var(--mud-palette-text-secondary); white-space:nowrap;
}

/* ============================================================================
   Escalations page
   ============================================================================ */

.esc-layout { display:flex; height:100%; overflow:hidden; }

.esc-sidebar {
    width:360px; flex-shrink:0; border-right:1px solid var(--mud-palette-lines-default);
    display:flex; flex-direction:column; overflow:hidden; background:var(--mud-palette-surface);
    /* Same story as .svai-sticky-scroll: this subtracted a hardcoded 43px while its own comment
       claimed 56px, and neither matched the real offset (app bar + .mud-main-content padding-top),
       so this sidebar overhung the viewport and the document scrolled behind it.
       --svai-content-top is the measured top of the content area, published on :root by
       stickyshadow.js. */
    height:calc(100vh - var(--svai-content-top, 56px));
}
.esc-sidebar-head { padding:16px 16px 12px; border-bottom:1px solid var(--mud-palette-lines-default); flex-shrink:0; }
.esc-sidebar-head h2 { font-size:15px; font-weight:600; margin:0 0 10px; color:var(--mud-palette-text-primary); }

.esc-list { overflow-y:auto; flex:1; min-height:0; }

.esc-card {
    padding:12px 16px; border-bottom:1px solid var(--mud-palette-divider);
    cursor:pointer; display:flex; flex-direction:column; gap:5px; border-left:3px solid transparent;
    transition:background .1s;
}
.esc-card:hover { background:var(--mud-palette-background-grey); }
.esc-card.selected {
    background:var(--mud-palette-primary-lighten);
    border-left-color:var(--mud-palette-primary);
}
.esc-card .esc-name {
    font-weight:600; font-size:13px; display:flex;
    align-items:center; justify-content:space-between; gap:8px;
}
.esc-card .esc-q {
    font-size:12.5px; color:var(--mud-palette-text-secondary); line-height:1.4;
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.esc-card .esc-meta {
    font-size:11px; color:var(--mud-palette-text-disabled);
    display:flex; align-items:center; gap:6px;
}
.esc-card .esc-meta .mud-icon-root { font-size:12px; }

.esc-main { flex:1; display:flex; flex-direction:column; overflow:hidden; background:var(--mud-palette-background); }
.esc-empty {
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    flex:1; color:var(--mud-palette-text-disabled); font-size:13px; text-align:center; gap:12px;
}
.esc-empty .mud-icon-root { font-size:52px; opacity:.4; }

.esc-detail { flex:1; overflow:hidden; display:flex; flex-direction:column; }
/* Phone-only control — the <=600 block turns it into a real button. On desktop the list
   pane is still on screen beside the detail, so there is nowhere to go back TO. */
.esc-back { display:none; }
.esc-detail-head {
    padding:14px 20px; border-bottom:1px solid var(--mud-palette-divider);
    display:flex; align-items:center; justify-content:space-between;
    flex-shrink:0; background:var(--mud-palette-surface);
}

.esc-reply-thread {
    margin:10px 20px 20px;
    border:1px solid var(--mud-palette-lines-default);
    border-left:3px solid var(--mud-palette-primary);
    border-radius:8px; padding:14px 16px 16px;
    background:var(--mud-palette-surface);
}
.esc-reply-label {
    display:flex; align-items:center; gap:8px; margin-bottom:10px;
    font-size:11px; font-weight:700; text-transform:uppercase;
    letter-spacing:.5px; color:var(--mud-palette-text-secondary);
}
.esc-reply-label .mk-reply {
    width:22px; height:22px; border-radius:50%; flex-shrink:0;
    background:var(--mud-palette-primary-lighten); color:var(--mud-palette-primary-darken);
    display:grid; place-items:center; font-size:9px; font-weight:700;
}
.esc-reply-textarea {
    width:100%; border:1px solid var(--mud-palette-lines-default); border-radius:6px;
    padding:10px 12px; font:inherit; font-size:13px; resize:vertical;
    background:var(--mud-palette-background); color:var(--mud-palette-text-primary);
    min-height:100px; box-sizing:border-box;
}
.esc-reply-textarea:focus {
    outline:none; border-color:var(--mud-palette-primary);
    box-shadow:0 0 0 3px rgba(30,136,229,.12);
}

/* â”€â”€ Login / auth screen â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */
/*.svai-auth-shell {
    min-height:100vh; display:flex; align-items:center; justify-content:center;
    background:linear-gradient(135deg,#1E88E5 0%,#1565C0 100%); padding:24px;
}*/

.svai-auth-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    /* Scrim deliberately zero at both ends — it is there only to hold the white
       brand text over the brighter middle of the artwork, not to dim the image. */
    background-image:
        linear-gradient(180deg,
            rgb(10 26 56 / 0%) 0%,
            rgba(10, 26, 56, .10) 34%,
            rgba(10, 26, 56, .14) 54%,
            rgb(10 26 56 / 0%) 100%),
        url(/img/logiin-flip.png);
    background-size: cover, cover;
    background-position: center, center;
    background-repeat: no-repeat, no-repeat;
    background-color: #12509C;   /* fallback if the artwork is missing */
}

/* ── Angled split sign-in ─────────────────────────────────────────────────
   Full-page artwork with one full-height white panel over its right side. The
   panel's left edge runs on a diagonal — 52% of the viewport width at the top,
   42.6% at the bottom. Brand and statement sit on the artwork at the left in
   white; the form sits on the white. Doing the white as a single overlay rather
   than as a clipped column means the image underneath is never cut up. */
.svai-login-panel {
    position: relative;
    width: 100%; min-height: 100vh; display: flex; color: #101830;
}
.svai-login-panel::before {
    content: ""; position: absolute; inset: 0; z-index: 0;
    background: #fff;
    clip-path: polygon(52% 0, 100% 0, 100% 100%, 42.6% 100%);
}

/* Left — brand and statement on the artwork. Narrower than the diagonal's
   leftmost point (42.6%) so the white never runs over the text. */
.svai-lp-wedge {
    position: relative; z-index: 1;
    width: 40%; flex: none; min-width: 0;
    padding: 56px 60px;
    display: flex; flex-direction: column;
    color: #fff;
}
/* These carry the extra .svai-login-brand on purpose. The base brand rules live
   further down this file with the same specificity (0-2-0), so a shorter selector
   here would lose on source order and the text would render in the themed dark
   colour over the artwork. 0-3-0 wins regardless of position. */
.svai-lp-wedge .svai-login-brand { margin-bottom: 0; }
.svai-lp-wedge .svai-login-brand .mark {
    background: rgba(255,255,255,.18); color: #fff;
    box-shadow: inset 0 0 0 1px rgba(255,255,255,.35);
}
.svai-lp-wedge .svai-login-brand .title { color: #fff; }
.svai-lp-wedge .svai-login-brand .subtitle { color: rgba(255,255,255,.78); }
.svai-lp-stmt {
    margin: auto 0 0; max-width: 14ch;
    font-size: clamp(26px, 2.6vw, 40px); line-height: 1.2;
    font-weight: 650; letter-spacing: -.022em;
}

/* Right — the form on the white. The asymmetric padding centres it inside the
   white shape rather than inside its own column, which begins further left. */
.svai-lp-form {
    position: relative; z-index: 1;
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; justify-content: center;
    padding: 48px 6% 48px 16%;
}
.svai-lp-inner { width: 100%; max-width: 360px; margin: 0 auto; }
.svai-lp-compact { display: none; }
.svai-lp-h { margin: 0 0 22px; font-size: 24px; font-weight: 650; letter-spacing: -.015em; }
.svai-lp-inner .svai-login-field span { color: #55637C; font-weight: 500; }
.svai-lp-inner .svai-login-field input {
    background: #F7F9FC; border-color: #DCE3EE; color: #101830;
}
.svai-lp-inner .svai-login-field input:focus { background: #fff; border-color: #1565C0; }
.svai-lp-inner .svai-login-foot { color: #97A2B6; }

@media (max-width: 1100px) {
    .svai-login-panel::before { clip-path: polygon(48% 0, 100% 0, 100% 100%, 39% 100%); }
    .svai-lp-wedge { width: 36%; padding: 44px 40px; }
    .svai-lp-form { padding: 40px 5% 40px 14%; }
}
@media (max-width: 820px) {
    .svai-login-panel::before { clip-path: none; }
    .svai-lp-wedge { display: none; }
    .svai-lp-compact { display: flex; margin-bottom: 24px; }
    .svai-lp-form { padding: 40px 28px; }
    .svai-lp-inner { max-width: 380px; }
}

.svai-login-card {
    width:100%; max-width:380px; background:var(--mud-palette-surface);
    border-radius:12px; padding:32px; box-shadow:0 18px 50px rgba(16,24,48,.28);
}
.svai-login-brand { display:flex; align-items:center; gap:12px; margin-bottom:24px; }
.svai-login-brand .mark {
    width:40px; height:40px; border-radius:9px; flex:none;
    background:linear-gradient(135deg,#1E88E5,#1565C0); color:#fff;
    display:flex; align-items:center; justify-content:center; font-weight:700; font-size:15px;
}
.svai-login-brand .title    { font-size:17px; font-weight:600; color:var(--mud-palette-text-primary); }
.svai-login-brand .subtitle { font-size:12px; color:var(--mud-palette-text-secondary); }
.svai-login-field { display:block; margin-bottom:16px; }
.svai-login-field span {
    display:block; font-size:12px; font-weight:500; margin-bottom:6px;
    color:var(--mud-palette-text-secondary);
}
.svai-login-field input {
    width:100%; box-sizing:border-box; padding:10px 12px; font-size:14px;
    border:1px solid var(--mud-palette-lines-default); border-radius:7px;
    background:var(--mud-palette-background-grey); color:var(--mud-palette-text-primary);
}
.svai-login-field input:focus {
    outline:none; border-color:var(--mud-palette-primary);
    box-shadow:0 0 0 3px rgba(30,136,229,.12); background:#fff;
}
/* Password field with show/hide toggle */
.svai-pw-wrap { position:relative; }
.svai-pw-wrap input { padding-right:42px; }
.svai-pw-toggle {
    position:absolute; top:0; right:0; height:100%; width:40px;
    display:flex; align-items:center; justify-content:center;
    background:none; border:none; cursor:pointer; padding:0;
    color:var(--mud-palette-text-secondary);
}
.svai-pw-toggle:hover  { color:var(--mud-palette-primary); }
.svai-pw-toggle:focus-visible {
    outline:none; color:var(--mud-palette-primary);
    box-shadow:0 0 0 3px rgba(30,136,229,.12); border-radius:7px;
}
/* Default: password hidden â†’ show the "eye"; hide the "eye-off". Swap when visible. */
.svai-pw-toggle .svai-eye-off { display:none; }
.svai-pw-toggle.is-visible .svai-eye     { display:none; }
.svai-pw-toggle.is-visible .svai-eye-off { display:block; }
.svai-login-btn {
    width:100%; padding:11px; margin-top:4px; font-size:14px; font-weight:600; color:#fff;
    background:linear-gradient(135deg,#1E88E5,#1565C0); border:none; border-radius:7px; cursor:pointer;
}
    .svai-login-btn:hover {
        background: linear-gradient(135deg,#1567c2,#1565C0);
    }
.svai-login-error {
    background: var(--mud-palette-error-lighten);
    color: var(--mud-palette-text-light);
    border: 1px solid rgba(211,47,47,.25);
    border-radius: 7px;
    padding: 9px 12px;
    font-size: 13px;
    margin-bottom: 16px;
}
.svai-login-foot {
    margin-top:20px; text-align:center; font-size:11px; color:var(--mud-palette-text-disabled);
}

/* ============================================================================
   Health Query â€” NL-to-SQL page styles (matches HealthQuery.Web design)
   ============================================================================ */

/* Card with header bar */
.hq-card {
    background:var(--mud-palette-surface); border:1px solid var(--mud-palette-lines-default);
    border-radius:10px; box-shadow:0 1px 3px rgba(20,27,45,.06); overflow:hidden;
}
.hq-card-header {
    display:flex; align-items:center; justify-content:space-between;
    padding:14px 18px; border-bottom:1px solid var(--mud-palette-lines-default);
    font-size:14px; font-weight:600; color:var(--mud-palette-text-primary);
    background:var(--mud-palette-background-grey);
}

/* "AI-Powered" pill */
.hq-pill-ai {
    display:inline-flex; align-items:center; gap:4px; padding:3px 10px;
    border:1.5px solid var(--mud-palette-primary); border-radius:14px;
    font-size:11px; font-weight:600; color:var(--mud-palette-primary);
    background:var(--mud-palette-primary-lighten); letter-spacing:.3px;
}

/* Query textarea */
.hq-textarea {
    width:100%; border:1px solid var(--mud-palette-lines-default);
    border-radius:8px; padding:14px 16px; font:inherit; font-size:14px; line-height:1.5;
    background:var(--mud-palette-surface); color:var(--mud-palette-text-primary);
    resize:vertical; min-height:80px; transition:border-color .15s, box-shadow .15s;
}
.hq-textarea:focus {
    outline:none; border-color:var(--mud-palette-primary);
    box-shadow:0 0 0 3px rgba(30,136,229,.12);
}
.hq-textarea::placeholder { color:var(--mud-palette-text-disabled); }

/* Run / action buttons */
.hq-btn-run {
    display:inline-flex; align-items:center; gap:6px; height:38px; padding:0 20px;
    border-radius:8px; border:none; cursor:pointer;
    font:inherit; font-size:13px; font-weight:600; transition:background .15s;
    background:#2563eb; color:#fff;
}
.hq-btn-run:hover { background:#1d4ed8; }
.hq-btn-run:disabled { opacity:.55; cursor:not-allowed; }

.hq-btn-clear {
    display:inline-flex; align-items:center; gap:4px; height:38px; padding:0 14px;
    border:none; background:transparent; cursor:pointer;
    font:inherit; font-size:13px; font-weight:500; color:var(--mud-palette-text-secondary);
}
.hq-btn-clear:hover { color:var(--mud-palette-text-primary); }

.hq-btn-export {
    display:inline-flex; align-items:center; gap:6px; height:34px; padding:0 14px;
    border:1.5px solid #10b981; border-radius:8px; background:transparent;
    font:inherit; font-size:12px; font-weight:600; color:#10b981; cursor:pointer;
    transition:background .15s, color .15s;
}
.hq-btn-export:hover { background:rgba(16,185,129,.08); }

/* Results metadata line */
.hq-results-meta {
    font-size:12px; font-weight:500; color:var(--mud-palette-text-secondary);
    font-variant-numeric:tabular-nums; letter-spacing:.2px;
}

/* Tabs â€” Data Table / Chart */
.hq-tabs {
    display:flex; align-items:center; border-bottom:1px solid var(--mud-palette-lines-default);
    padding:0 18px; gap:0;
}
.hq-tab {
    display:inline-flex; align-items:center; gap:5px;
    padding:11px 18px; font-size:13px; font-weight:600;
    color:var(--mud-palette-text-secondary); cursor:pointer;
    border:none; background:none;
    border-bottom:2.5px solid transparent;
    transition:color .15s, border-color .15s;
    font-family:inherit;
}
.hq-tab:hover { color:var(--mud-palette-text-primary); }
.hq-tab.active {
    color:#2563eb;
    border-bottom-color:#2563eb;
}
.hq-tab .mud-icon-root { font-size:16px; }

/* "Auto" pill next to Chart tab */
.hq-auto-pill {
    display:inline-flex; align-items:center; padding:2px 7px;
    border-radius:10px; font-size:10px; font-weight:600; letter-spacing:.3px;
    background:#E8F5E9; color:#2E7D32; margin-left:4px;
}

/* Chart controls bar */
.hq-chart-controls {
    display:flex; align-items:center; gap:14px; padding:12px 18px; flex-wrap:wrap;
    border-bottom:1px solid var(--mud-palette-lines-default);
    background:var(--mud-palette-background-grey);
}
.hq-chart-control {
    display:flex; align-items:center; gap:6px;
}
.hq-chart-control label {
    font-size:12px; font-weight:600; color:var(--mud-palette-text-secondary);
    white-space:nowrap;
}

/* Chart canvas container */
.hq-chart-wrap {
    position:relative; height:400px; padding:18px;
}

/* Chart not-applicable placeholder */
.hq-chart-na {
    padding:60px 24px; text-align:center; color:var(--mud-palette-text-secondary);
}
.hq-chart-na .icon {
    font-size:42px; opacity:.25; display:block; margin:0 auto 14px;
}
.hq-chart-na .title { font-size:14px; font-weight:600; margin-bottom:4px; }
.hq-chart-na .hint  { font-size:12px; opacity:.7; }

/* SQL block */
.hq-sql-block {
    padding:14px 18px; margin:0; font-size:12px;
    font-family:'JetBrains Mono', Consolas, monospace;
    background:#0f172a; color:#e2e8f0; border-radius:0 0 10px 10px;
    overflow-x:auto; line-height:1.6; white-space:pre-wrap; word-break:break-word;
}

/* Skeleton loader */
.hq-skeleton {
    height:14px; border-radius:6px; margin-bottom:10px;
    background:linear-gradient(90deg, var(--mud-palette-lines-default) 25%, var(--mud-palette-background-grey) 50%, var(--mud-palette-lines-default) 75%);
    background-size:200% 100%;
    animation:hqShimmer 1.5s infinite;
}
@keyframes hqShimmer {
    0%   { background-position:200% 0; }
    100% { background-position:-200% 0; }
}

/* KPI value styling inside HQ */
.hq-kpi-value {
    font-size:24px; font-weight:700; color:var(--mud-palette-text-primary);
    font-variant-numeric:tabular-nums; letter-spacing:-.5px; line-height:1.1;
}
.hq-kpi-label {
    font-size:11px; font-weight:600; color:var(--mud-palette-text-secondary);
    text-transform:uppercase; letter-spacing:.5px; margin-top:4px;
    display:flex; align-items:center; gap:4px;
}

/* ============================================================================
   SAMS Assistant â€” floating action button (all pages)
   ============================================================================ */

.sams-fab {
    position: fixed;
    bottom: 28px;
    right: 28px;
    z-index: 1200;
    display: flex;
    align-items: center;
    gap: 0;
    height: 52px;
    border-radius: 26px;
    background : #1e88e5 !important;
    /*background:linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);*/
    color: #fff;
    text-decoration: none;
    box-shadow: 0 4px 14px rgba(30,136,229,.4), 0 2px 6px rgba(0,0,0,.12);
    cursor: grab;
    overflow: hidden;
    padding: 0 16px;
    transition: all .3s cubic-bezier(.4,0,.2,1);
    touch-action: none;
    user-select: none;
}
/* While dragging: pin movement to pointer (no transition/animation/hover lift) */
.sams-fab--dragging {
    transition:none !important;
    animation:none !important;
    transform:none !important;
    cursor:grabbing;
}
.sams-fab:hover {
    box-shadow:0 6px 22px rgba(30,136,229,.5), 0 3px 10px rgba(0,0,0,.15);
    transform:translateY(-2px);
    padding:0 20px 0 16px;
}
.sams-fab:active {
    transform:translateY(0);
    box-shadow:0 2px 8px rgba(30,136,229,.35);
}

.sams-fab-icon {
    display:flex; align-items:center; justify-content:center;
    width:24px; height:24px; flex-shrink:0;
}

.sams-fab-label {
    max-width:0; opacity:0; overflow:hidden; white-space:nowrap;
    font-size:13px; font-weight:600; letter-spacing:.2px;
    transition:max-width .3s cubic-bezier(.4,0,.2,1), opacity .25s ease, margin .3s ease;
    margin-left:0;
}
.sams-fab:hover .sams-fab-label {
    max-width:160px; opacity:1; margin-left:10px;
}

/* Subtle breathing animation when idle */
@keyframes samsFabPulse {
    0%, 100% { box-shadow:0 4px 14px rgba(30,136,229,.4), 0 2px 6px rgba(0,0,0,.12); }
    50%      { box-shadow:0 4px 20px rgba(30,136,229,.55), 0 2px 8px rgba(0,0,0,.12); }
}
.sams-fab { animation:samsFabPulse 3s ease-in-out infinite; }
.sams-fab:hover { animation:none; }

/* Voice recording button */
.hq-voice-btn {
    position:absolute; right:10px; top:10px; width:36px; height:36px;
    border-radius:50%; border:1.5px solid var(--mud-palette-lines-default);
    background:var(--mud-palette-surface); display:flex; align-items:center;
    justify-content:center; cursor:pointer; padding:0;
    color:var(--mud-palette-text-secondary);
    transition:all .2s ease;
}
.hq-voice-btn:hover {
    border-color:var(--mud-palette-primary); color:var(--mud-palette-primary);
    background:var(--mud-palette-primary-lighten);
}
.hq-voice-btn:disabled { opacity:.4; cursor:not-allowed; }
.hq-voice-btn.recording {
    border-color:#ef4444; background:#fef2f2; color:#ef4444;
    animation:hqVoiceBtnPulse 1.5s ease-in-out infinite;
}
@keyframes hqVoiceBtnPulse {
    0%, 100% { box-shadow:0 0 0 0 rgba(239,68,68,.3); }
    50%      { box-shadow:0 0 0 8px rgba(239,68,68,0); }
}

/* Voice status indicator */
.hq-voice-status {
    display:inline-flex; align-items:center; gap:5px;
    font-size:11px; font-weight:600; color:#ef4444;
}
.hq-voice-pulse {
    display:inline-block; width:8px; height:8px;
    background:#ef4444; border-radius:50%;
    animation:hqPulseDot 1s infinite;
}
@keyframes hqPulseDot {
    0%, 100% { opacity:1; }
    50%      { opacity:.3; }
}

/* ============================================================================
   Toast / Snackbar â€” polished look (enhances MudBlazor's flat Filled default).
   Keeps the solid severity color + white text (readable), but adds rounded
   corners, a richer shadow, a subtle gradient sheen, tighter typography and a
   cleaner close button. Scoped to .mud-snackbar so nothing else is affected.
   ============================================================================ */
.mud-snackbar {
    border-radius:12px !important;
    box-shadow:0 12px 32px rgba(20,27,45,.26), 0 4px 12px rgba(20,27,45,.16) !important;
    padding:12px 14px !important;
    min-width:300px; max-width:440px;
    background-image:linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,0)) !important;
    border:1px solid rgba(255,255,255,.16) !important;
}
/* A 300px floor plus MudBlazor's own 24px positioning offsets does not fit a 320px handset, and a
   toast that overflows is one whose dismiss button can sit off-screen. Fluid below 400px. */
@media (max-width:400px) {
    .mud-snackbar { min-width:0 !important; max-width:calc(100vw - 24px) !important; }
}
.mud-snackbar .mud-snackbar-content         { font-size:13.5px; line-height:1.45; font-weight:500; letter-spacing:.1px; }
.mud-snackbar .mud-snackbar-content-message { padding:0 4px; }
.mud-snackbar .mud-icon-root                { font-size:20px; }

/* Close button: subtle on the colored surface, solid on hover. */
.mud-snackbar .mud-button-root.mud-icon-button {
    color:rgba(255,255,255,.85) !important;
    opacity:.85; border-radius:8px;
}
.mud-snackbar .mud-button-root.mud-icon-button:hover {
    opacity:1; background:rgba(255,255,255,.18) !important;
}

/* ============================================================================
   DARK MODE
   ----------------------------------------------------------------------------
   app.css mirrors the MudBlazor palette on :root with LIGHT values and uses a
   lot of literal hex (panels, tables, pills) that the dark palette can't reach,
   so dark mode left text/background unreadable. The `.svai-dark` class is added
   to <html> by MainLayout (js/theme.js) in lock-step with MudThemeProvider.

   1) Re-point every palette variable to dark â€” this alone fixes everything that
      uses var(--mud-palette-*): .panel, .btn, .mud-input, .oc-select, selected
      rows, drawers, and all native MudBlazor components/dialogs/popovers.
   2) Override the remaining literal-hex rules (data-grid header/body/HOVER/pager
      and the tinted pills/chips) that ignore the palette.
   html.svai-dark (0,1,1) out-specifies :root (0,1,0), so the remap always wins.
   ============================================================================ */
html.svai-dark {
    --mud-palette-primary:#42A5F5; --mud-palette-primary-darken:#1E88E5; --mud-palette-primary-lighten:rgba(66,165,245,.16);
    --mud-palette-secondary:#26A69A;
    --mud-palette-success:#66BB6A; --mud-palette-success-lighten:rgba(102,187,106,.16);
    --mud-palette-warning:#FFA726; --mud-palette-warning-lighten:rgba(255,167,38,.16);
    --mud-palette-error:#EF5350;   --mud-palette-error-lighten:rgba(239,83,80,.16);
    --mud-palette-info:#29B6F6;     --mud-palette-info-lighten:rgba(41,182,246,.16);
    --mud-palette-background:#0F1320; --mud-palette-surface:#171C2C; --mud-palette-background-grey:#1E2536;
    --mud-palette-divider:#262C3D; --mud-palette-lines-default:#2A3142;
    --mud-palette-text-primary:#E7EAF3; --mud-palette-text-secondary:#A4ACC2; --mud-palette-text-disabled:#6B7280;
}

/* â”€â”€ Data grid: header / body / hover / pager were hardcoded light â”€â”€ */
html.svai-dark .mud-data-grid .mud-table-head .mud-table-cell {
    color: var(--mud-palette-text-secondary) !important;
    background: #1E2536 !important;
    border-bottom-color: var(--mud-palette-lines-default) !important;
}
/* Sticky-grid tables keep the same blue header row in dark mode as in light mode
   (the rule above would otherwise repaint every grid header gray). Higher specificity
   than both the light .svai-sticky-grid rule and the dark grey rule above, so it wins;
   sticky/z-index positioning is still inherited from the light .svai-sticky-grid rule. */
html.svai-dark .svai-sticky-grid .mud-data-grid .mud-table-head .mud-table-cell {
    color: #ffffff !important;
    background: #1E88E5 !important;
    border-bottom-color: #1976D2 !important;
}
html.svai-dark .mud-data-grid .mud-table-body .mud-table-cell {
    color: var(--mud-palette-text-primary) !important;
    border-bottom-color: var(--mud-palette-divider) !important;
}
/* The reported hover bug: rows turned near-white (#F7F8FB) on hover in dark mode. */
html.svai-dark .mud-data-grid.mud-table-hover .mud-table-body .mud-table-row:hover .mud-table-cell {
    background: rgba(255,255,255,.05) !important;
}
html.svai-dark .mud-data-grid .mud-table-pagination-toolbar {
    background-color: var(--mud-palette-surface) !important;
    border-top-color: var(--mud-palette-lines-default) !important;
}
html.svai-dark .svai-sticky-grid .mud-data-grid .mud-table-pagination,
html.svai-dark .svai-sticky-grid .mud-data-grid .mud-table-pager {
    border-top-color: var(--mud-palette-lines-default) !important;
}
/* Focused input glow used a light-mode blue tint at very low alpha â€” bump it. */
html.svai-dark .mud-input:focus-within { box-shadow:0 0 0 3px rgba(66,165,245,.25); }
/* Native <select> in a .mud-input (the Approval filter dropdown's Specialty / Priority). The base
   rule makes the control transparent so it blends into the field chrome, but the browser paints
   the OPEN option list from the control's own colours — transparent + light text renders as white
   on white in dark mode, i.e. an unreadable list. Give both the control and its options a real
   surface colour here; it matches .mud-input's background, so the closed control looks unchanged. */
html.svai-dark .mud-input select,
html.svai-dark .mud-input select option {
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
}

/* â”€â”€ Native form controls (select boxes) in dark mode â”€â”€
   The <select> BOX styles use theme vars and adapt, but the browser-drawn parts â€” the
   open <option> popup, dropdown spinners, and scrollbars â€” render LIGHT unless the page
   declares it's dark. `color-scheme: dark` fixes the browser-drawn chrome; the explicit
   option rule covers browsers that ignore it (so the popup list matches the theme). */
html.svai-dark { color-scheme: dark; }

/* (the app bar's facility picker opts out of both this scheme and the catch-all below - see the
   .svai-facility-select block after them) */

/* The form-select chevron is a data-URI SVG, so its stroke can't inherit a CSS var â€” it is baked
   to the LIGHT text-secondary (#596174), which nearly disappears on the dark surface. Re-bake it
   with the dark text-secondary (#A4ACC2). Box colours already adapt via the theme vars. */
html.svai-dark .tenant-field select,
html.svai-dark .field select {
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23A4ACC2' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3E%3C/svg%3E");
}
html.svai-dark select,
html.svai-dark select option {
    background-color: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
}

/* ── EXCEPT the app bar's facility picker ──
   Every select above is on the page surface, where the dark surface colour is right. This one is
   on the primary-coloured HEADER, and the catch-all above was repainting it near-black: a black
   box on a coloured bar, with a black list dropping out of it. It outranks the component's own
   rule in MainLayout.razor (html.svai-dark select beats .svai-facility-select select), which is
   why styling it there had no effect - the override has to live here, after it, to win.

   The CONTROL goes back to transparent so the translucent-white pill around it shows, exactly as
   in light mode; the OPEN LIST stays WHITE with dark text in both themes, as a menu should be -
   the light palette's own Surface / TextPrimary, written out because the theme vars follow the
   mode and would go near-black again here. color-scheme:light is the other half: while the page
   declares itself dark the browser paints the popup in its own near-black chrome and ignores
   these colours entirely. */
html.svai-dark .svai-facility-select select {
    background-color: transparent;
    color: #fff;
    color-scheme: light;
}
html.svai-dark .svai-facility-select select option {
    background-color: #FFFFFF;
    color: #1E2432;
}

/* â”€â”€ Pills: tinted backgrounds + dark text were baked in as light hex â”€â”€ */
html.svai-dark .pill.success { background:rgba(102,187,106,.16) !important; color:#81C784 !important; }
html.svai-dark .pill.warning { background:rgba(255,167,38,.16)  !important; color:#FFB74D !important; }
html.svai-dark .pill.error   { background:rgba(239,83,80,.16)   !important; color:#E57373 !important; }
html.svai-dark .pill.info    { background:rgba(41,182,246,.16)  !important; color:#4FC3F7 !important; }
html.svai-dark .pill.primary { background:rgba(66,165,245,.16)  !important; color:#64B5F6 !important; }
html.svai-dark .pill.default {
    background:rgba(255,255,255,.08) !important;
    color:var(--mud-palette-text-secondary) !important;
    border-color:var(--mud-palette-lines-default) !important;
}

/* â”€â”€ Escalation filter chips (same baked-in light tints) â”€â”€ */
html.svai-dark .mud-chip.esc-filter.warning { background:rgba(255,167,38,.16) !important;  border-color:rgba(255,167,38,.4) !important;  color:#FFB74D !important; }
html.svai-dark .mud-chip.esc-filter.info    { background:rgba(41,182,246,.16) !important;  border-color:rgba(41,182,246,.4) !important;  color:#4FC3F7 !important; }
html.svai-dark .mud-chip.esc-filter.success { background:rgba(102,187,106,.16) !important; border-color:rgba(102,187,106,.4) !important; color:#81C784 !important; }

/* â”€â”€ Left nav drawer â”€â”€
   MudBlazor gives the drawer no border, so the elevated drawer surface (#171C2C)
   meeting the darker content (#0F1320) read as a fuzzy light seam, and the
   light-mode drop-shadow is invisible on a dark background. Define the edge with
   a crisp dark border + dark shadow so it looks deliberate. */
html.svai-dark .mud-drawer.mud-drawer-pos-left {
    border-right:1px solid var(--mud-palette-lines-default);
    box-shadow:4px 0 18px rgba(0,0,0,.45), 1px 0 0 rgba(0,0,0,.6) !important;
}

/* â”€â”€ Unverified caller badge + dashed avatar â”€â”€
   The tag's background fell back to a black tint (invisible on dark) and the
   dashed avatar ring vanished. Restore a subtle, readable neutral treatment. */
html.svai-dark .unverified-tag {
    color:var(--mud-palette-text-secondary) !important;
    background:rgba(255,255,255,.07) !important;
    border-color:var(--mud-palette-lines-default) !important;
}
html.svai-dark .avatar-sm.unverified,
html.svai-dark .av.unverified {
    color:var(--mud-palette-text-secondary) !important;
    border-color:var(--mud-palette-text-disabled) !important;
}

/* â”€â”€ Switch track â”€â”€
   MudBlazor paints EVERY switch track from --mud-palette-black (opacity .48 off,
   .5 checked, .12 disabled) â€” the color carrier for on/off is the thumb, not the
   track. This app's dark remap never re-points --mud-palette-black, so it keeps its
   light-theme near-black value: an off (or disabled) toggle's track is a near-black
   bar on the ~#171C2C table surface and reads as invisible â€” the switch looks like
   it has no background at all. Repaint the track from white so it stays visible in
   every state; MudBlazor's per-state opacities keep off/checked/disabled distinct. */
html.svai-dark .mud-switch .mud-switch-track {
    background-color: #FFFFFF !important;
}
/* Disabled: MudBlazor drops the track to .12 opacity (too faint even on white) and
   the thumb to --mud-palette-gray-default (a light-theme grey). Lift both so a
   disabled switch stays visible while still looking clearly muted. */
html.svai-dark .mud-switch .mud-switch-base.mud-switch-disabled + .mud-switch-track {
    opacity: .28 !important;
}
html.svai-dark .mud-switch .mud-switch-base.mud-switch-disabled {
    color: #8A92A6 !important;
}
/* ============================================================================
   MudDataGrid on phone AND tablet — label/value cards instead of a 20-column table
   ----------------------------------------------------------------------------
   Every admin grid is a MudDataGrid (directly, or via PagedDataGrid which
   subclasses it). MudDataGrid has no responsive mode of its own — that belongs
   to MudTable, whose MudTd emits the data-label this technique needs — so
   js/gridcards.js copies each column header onto its cells as data-label and
   marks the grid .svai-grid-cards. Everything below keys off those two things.
   Change the breakpoint here and in gridcards.js together.

   900px, RAISED FROM 600. This block used to stop at 600 on the argument that a
   tablet has room for the real table plus horizontal scroll, and that the header
   row is how you sort. Measured at 820x1180 on /conversations, /campaigns,
   /approvals, /consent-registry and /provider-directory, that did not hold:

     - .svai-grid-wide gave an 11-column table min-width: 11 * 110px = 1210px;
     - .svai-sticky-grid forces .mud-table-container to overflow:visible so the
       sticky thead can pin against the page region, so the table had nowhere to
       overflow to except .svai-sticky-scroll;
     - that region is overflow-y:auto, and CSS computes overflow-x to auto when
       the other axis is not visible — so the WHOLE REGION became the horizontal
       scroller. The blue header ran to x=1239 inside an 820px viewport and the
       sticky pager stayed 762px wide against a 1210px table.

   A header you must scroll sideways to reach is not a header you can sort by, so
   the thing the 600px breakpoint was protecting was already gone. Cards fit, and
   sorting is the .filters bar (see SORTING MOVES TO THE FILTER BAR below).

   THIS BLOCK IS NOW DELIBERATELY OUT OF STEP WITH THE PAGE-CHROME BLOCK BELOW,
   WHICH STAYS AT 600. That block does things that are right for a phone and wrong
   for a tablet — full-viewport right drawers, a static .appr-headbar, a stacked
   .esc-layout. Only the grid treatment moves to 900. Do not "make them consistent".

   WITHOUT THIS the grids do not break, they scroll: MudBlazor's
   .mud-table-container is overflow:auto, so an 11-column Conversations or
   20-column ApprovalQueue becomes a horizontally-scrolled strip where a phone
   shows about two columns at a time and the row you are reading runs off the
   edge. Reachable, but not usable.

   ONLY GRIDS THE SCRIPT MARKED
     gridcards.js applies .svai-grid-cards only to grids with >= 6 columns.
     Appointments (4) and the other narrow grids keep their table, which at these
     widths is both shorter and easier to scan than a stack of cards. Measured at
     820px, every grid under 6 columns already fits with room to spare.

   THE SELECTORS CARRY AN EXTRA CLASS ON PURPOSE
     The base body-cell rule (~line 1374) is all !important, and the dark theme
     re-overrides it as `html.svai-dark .mud-data-grid .mud-table-body
     .mud-table-cell` = (0,4,1). Matching that on class count alone loses, so
     these rules name .mud-table-row as well to reach (0,5,0) and win in BOTH
     themes from one declaration. Colours are theme variables throughout, so
     there is no second dark-mode copy of this block to keep in sync.

   SORTING MOVES TO THE FILTER BAR
     The header row is what carries MudDataGrid's sort and column menus, and it
     is hidden here — there is nowhere to put 20 header menus on a 375px screen,
     and at 820px they sat past the right edge of a table you had to scroll to.
     Every page that owns one of these grids has its own .filters bar (search +
     selects) which is unaffected and remains the way to narrow data here.
   ============================================================================ */
@media (max-width: 900px) {

    /* The container is the horizontal scroller. Cards must flow in the page instead,
       or they would sit inside a scroll region that no longer has anything to scroll. */
    .mud-data-grid.svai-grid-cards .mud-table-container { overflow: visible !important; }

    .mud-data-grid.svai-grid-cards .mud-table-root,
    .mud-data-grid.svai-grid-cards .mud-table-body { display: block; width: 100%; }

    /* Labels live on the cells now. */
    .mud-data-grid.svai-grid-cards .mud-table-head { display: none; }

    /* Row -> card. */
    .mud-data-grid.svai-grid-cards .mud-table-body .mud-table-row {
        display: block;
        margin: 0 0 10px;
        border: 1px solid var(--mud-palette-lines-default);
        border-radius: 10px;
        background: var(--mud-palette-surface);
        overflow: hidden;
    }

    /* Cell -> one label/value line. width:auto defeats the per-column percentage
       widths set for the wide grids (e.g. .svai-conv-grid nth-child rules). */
    .mud-data-grid.svai-grid-cards .mud-table-body .mud-table-row .mud-table-cell {
        display: flex !important;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 4px 12px;
        width: auto !important;
        max-width: none !important;
        height: auto !important;
        padding: 8px 12px !important;
        text-align: left !important;
        white-space: normal !important;
        border-bottom: 1px solid var(--mud-palette-lines-default) !important;
    }
    .mud-data-grid.svai-grid-cards .mud-table-body .mud-table-row .mud-table-cell:last-child {
        border-bottom: 0 !important;
    }

    /* The label. An empty data-label (checkbox / chevron / icon-only action columns)
       is deliberately excluded, so those cells use the full width instead of
       reserving an empty label gutter down the side of every card. */
    .mud-data-grid.svai-grid-cards .mud-table-body .mud-table-row .mud-table-cell[data-label]:not([data-label=""])::before {
        content: attr(data-label);
        flex: 0 0 38%;
        max-width: 38%;
        font-size: 10.5px;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: .4px;
        line-height: 1.6;
        color: var(--mud-palette-text-secondary);
    }

    /* Value side takes the rest and is allowed to wrap rather than overflow the card. */
    .mud-data-grid.svai-grid-cards .mud-table-body .mud-table-row .mud-table-cell > * {
        min-width: 0;
        flex: 1 1 auto;
        overflow-wrap: anywhere;
    }

    /* A cell with no content would collapse to a bare label; keep the line height honest. */
    .mud-data-grid.svai-grid-cards .mud-table-body .mud-table-row .mud-table-cell::after {
        content: "";
        display: block;
        min-height: 1px;
    }

    /* The empty-state / loading row spans the table and gets no label from the script
       (it has a single cell); let it fill the card rather than sit in a 38% column. */
    .mud-data-grid.svai-grid-cards .mud-table-body .mud-table-row .mud-table-cell[colspan] {
        display: block !important;
        text-align: center !important;
    }

    /* Hovering a card shouldn't tint one cell at a time. */
    .mud-data-grid.svai-grid-cards.mud-table-hover .mud-table-body .mud-table-row:hover .mud-table-cell {
        background: transparent !important;
    }
    .mud-data-grid.svai-grid-cards.mud-table-hover .mud-table-body .mud-table-row:hover {
        border-color: var(--mud-palette-primary);
    }

    /* Pager: let the row wrap instead of forcing the toolbar wider than the screen. */
    .mud-data-grid.svai-grid-cards .mud-table-pagination-toolbar {
        flex-wrap: wrap;
        gap: 4px 10px;
        padding: 8px 10px !important;
        height: auto !important;
    }

    /* The pager STAYS PINNED in card mode. It used to revert to position:static here,
       on the reasoning that a sticky pager "would float over the cards" — but floating
       above the content is exactly what pinning means, and static put it at the very
       end of the stack instead: measured on /campaigns at 393px the pager sat at
       top: 11592px, i.e. below every card on the page, so changing page or rows-per-page
       meant scrolling the entire result set to reach the control that pages it.

       Only `top` is stood down. The thead is display:none here so there is nothing left
       to pin beneath, and leaving --svai-thead-top applied would offset the pager for a
       header that is not on screen. */
    .svai-sticky-grid .mud-data-grid.svai-grid-cards .mud-table-pagination,
    .svai-sticky-grid .mud-data-grid.svai-grid-cards .mud-table-pager {
        position: sticky !important;
        top: auto !important;
        bottom: -1px;
        z-index: 20;
        background: var(--mud-palette-surface);
        border-top: 1px solid var(--mud-palette-lines-default);
    }
}
/* ============================================================================
   Page chrome below 600px — the rows that could not wrap, and the tables that
   could not scroll                                              svai-mobile-chrome
   ----------------------------------------------------------------------------
   Measured in a real browser at 393x852 (iPhone 15 Pro) against the running app.
   None of these made the DOCUMENT scroll sideways — scrollWidth stayed at 393 on
   every page — which is exactly why they were easy to miss: the content was
   CLIPPED at the viewport edge instead, so a KPI tile, a tab, or the right-hand
   half of a header toolbar simply was not there, with no scrollbar to hint that
   anything had been cut.

   Each rule below fixes one measured offender, named with what it measured at:
     .kpis            356px wide, right edge at 560   (Conversations)
     .appr-headtools  up to 525 wide, right edge at 663 (every /approvals tab)
     .tabs / .tab     tab strip running to 611        (approvals, settings, tenant)
     mini-table       445 wide inside a 393 screen    (dashboard)

   The tab strip SCROLLS rather than wraps: a wrapped tab strip reflows as the
   selected tab changes and moves the thing under the user's finger, and it stops
   reading as one row of peers. Horizontal scroll is the standard mobile idiom
   and keeps the order stable. The scrollbar itself is hidden because the strip
   is short and the bar would sit on top of the active-tab underline.

   The table rule deliberately excludes .mud-table-root: MudDataGrid and MudTable
   already own .mud-table-container for scrolling, and on a phone the wide ones
   are stacked into cards by gridcards.js instead (see the card block above).
   Only the hand-rolled tables (.mini-table, the Settings compliance table) need
   this, and display:block is what allows overflow to apply to a <table> at all.
   ============================================================================ */
@media (max-width: 600px) {

    /* KPI tiles: wrap onto as many rows as they need instead of running off. */
    .kpis     { flex-wrap: wrap; }
    .kpis > * { flex: 1 1 140px; min-width: 0; }

    /* Approvals header: the title row could wrap but the tool cluster was pinned
       flex:0 0 auto with margin-left:auto, so it stayed on the title's line and
       overhung the edge. Give it its own full-width row. */
    .appr-headrow   { flex-wrap: wrap; }
    .appr-headtools { margin-left: 0; width: 100%; flex-wrap: wrap; justify-content: flex-start; }
    .appr-headtools > * { min-width: 0; }

    /* .appr-search is a fixed 260px, and on Campaigns it sits in a page header row
       that has no flex-wrap. .content-padded keeps its 28px side padding here, so at
       393px that row has a 327px content box while its contents want 367 — measured
       with the box at L178/R395, i.e. 40px past the row and 2px past the viewport.
       Two pixels was enough to make .svai-sticky-scroll a horizontal scroller
       (clientW 383 -> scrollW 395) on a page that otherwise fits.
       Shrinking beats wrapping here: the row is an inline-styled div with no class
       of its own, so there is nothing to hang a flex-wrap on without editing every
       page's markup. The inner input is already min-width:0, so this shrinks cleanly. */
    .appr-search { width: auto; flex: 1 1 120px; min-width: 0; }

    /* Tab strips scroll; the 28px page gutter tightens with them. */
    .tabs {
        margin: 0 14px;
        overflow-x: auto;
        overflow-y: hidden;
        scrollbar-width: none;              /* Firefox */
        -webkit-overflow-scrolling: touch;
    }
    .tabs::-webkit-scrollbar { display: none; }   /* Chromium/WebKit */
    .tab      { padding: 12px 14px; flex: 0 0 auto; white-space: nowrap; }
    .tab-body { margin: 0 14px 20px; }

    /* The right-hand detail drawers (Patient Review, the Conversations transcript,
       Consent) are declared per page as Width="420px"/"480px". A fixed 480 on a
       393px screen does not overflow to the RIGHT — MudDrawer pins right:0, so the
       drawer hangs off the LEFT instead: measured left=-87, which silently ate the
       first ~87px of every line. The patient's name, the "Patient Review" title and
       the leading word of every field label were simply not on screen, and nothing
       scrolled to reveal them.

       Overriding the variable MudBlazor itself reads (.mud-drawer-pos-right is
       width: var(--mud-drawer-width, var(--mud-drawer-width-right))) fixes all
       eight declarations at once, so no page can be missed or drift later.
       !important is required and is not lazy here: MudDrawer writes the Width
       parameter as an INLINE custom property, which an author stylesheet cannot
       otherwise outrank.

       100% and NOT 100vw: 100vw INCLUDES the scrollbar gutter and the viewport does not, so on a
       page that scrolls it resolves ~15px too wide and pushes a horizontal scrollbar onto the
       page - and the drawer then hangs that far off the left, which is the very thing this rule
       exists to prevent. For a position:fixed box a percentage resolves against the initial
       containing block, which EXCLUDES the scrollbar. Same trap as 5.9 on the filter dropdown. */
    .mud-drawer.mud-drawer-pos-right { --mud-drawer-width: 100% !important; }

    /* The Approvals header is a HEIGHT CONTRACT, and wrapping breaks it.
       .appr-headrow is a fixed 70px, the tab line is 45, .appr-headbar adds 12px
       of air, and --svai-thead-top: 127px is that total — the bar takes it as a
       min-height and the grid's thead pins at exactly it. At 393px the subtitle
       wraps to two lines and the tool cluster needs a row of its own, so both
       overflowed their fixed-height parents and painted over the tab strip and
       the search box (measured: .appr-headtools ran to bottom=211 inside a parent
       ending at 112).

       The contract is stood down here rather than recomputed, because on a phone
       there is nothing left for it to protect: a pinned bar this tall would eat a
       quarter of an 852px viewport, and in card mode the grid has no thead to pin
       beneath it at all (.mud-table-head is display:none). --svai-thead-top drops
       to 0 for the narrow grids that DO keep their thead here (/approvals/history
       is 5 columns and stays a table) — with the bar no longer sticky, pinning
       them 127px down would suspend the header in mid-air.
       Keep it a plain px value: stickyshadow.js parseFloat()s it. */
    .appr-headbar {
        /* STICKY here too, restored. The paragraph above stood this down for two reasons
           and only one still holds. "In card mode the grid has no thead to pin beneath it"
           was true when every grid here stacked into cards below 900 - Approval Queue is
           now .rt-tiered and keeps a real table with a real header at every width, so
           there IS something to pin. The other reason (the bar is tall) is a cost, not a
           blocker: the search row and the tab strip are the two controls you reach for
           while scrolling a long queue, which is exactly why they were asked to stay put.

           min-height reads the same custom property the thead pins at, so the two cannot
           drift - the same coupling the desktop rule relies on. Erring HIGH is safe (the
           slack is more of the bar's own opaque background); erring LOW pins the thead
           behind the bar at z-index 30 and the table header disappears. */
        position: sticky; top: 0; z-index: 30;
        min-height: var(--svai-thead-top, 0);
        padding-bottom: 8px;
    }
    /* gap is 16px on desktop, where this row never wraps. Here it DOES wrap, so that 16
       became a row gap as well and opened a visible band between the title and the search
       box. 4px vertical keeps the two lines together; 12px horizontal still separates the
       search box from the buttons beside it. */
    .appr-headrow { height: auto; padding: 8px 14px 4px; gap: 4px 12px; }

    /* Page gutters follow the phone gutter used everywhere else at this width (.grid-card
       is 15px here too). 28px each side costs 56px of a 393px screen. */
    .content-padded { padding: 0px 15px 32px; }

    /* MEASURED at 393px, not derived: 78px header row (title line + tools line) + 45px tab
       strip + 8px padding = 131, plus 3px of slack. Slack is the safe direction - it shows
       as more of the bar's own opaque background, while being short pins the thead BEHIND
       the bar and the blue header disappears. RE-MEASURE whenever the header changes: this
       has already moved TWICE: 143 -> 131 when the wrapped row gap was tightened from 16px to
       4px, then 131 -> 139 when the header was re-laid out as title+Filter / search+Approve.
       Measured natural height is 139; 142 leaves 3px of slack. */
    .svai-stk--approvals { --svai-thead-top: 142px; }

    /* The strapline goes too. It is the line the comment above says wraps to two at
       393px, so it costs ~34px of the tallest header on the site to repeat what the
       tab strip immediately below already tells you. Same call as Conversations'
       .page-head .sub. The title itself steps down to 18px through the shared rule
       further down this file. */
    .appr-headrow .appr-sub { display: none; }

    /* ── Two deliberate lines on a phone ────────────────────────────────────────
         line 1:  Approval .................... [Filter]
         line 2:  [ Search all columns ] [Approve (n)]
       then the tab strip, then the pinned table header.

       display:contents dissolves .appr-headtools so its three children become direct flex
       items of .appr-headrow and can be ordered independently - the markup keeps them in one
       wrapper, which is what the wider layouts need. The ::after is the standard flexbox
       line-break: a zero-height, 100%-basis item ordered BETWEEN the filter button and the
       search box, so the wrap lands exactly there instead of wherever the widths happen to
       run out. order must be an integer, hence 1/2/3/4/5 rather than a half step. */
    .appr-headtools { display: contents; }

    .appr-headrow > div:first-child      { order: 1; flex: 1 1 auto; min-width: 0; }
    .appr-headrow .appr-filter-toggle    { order: 2; flex: 0 0 auto; margin-left: auto; }
    .appr-headrow::after                 { content: ""; order: 3; flex-basis: 100%; height: 0; }
    /* flex-basis 0, NOT auto. Flex decides line BREAKING from each item's hypothetical main
       size before it shrinks anything, so with basis:auto the search box was measured at its
       ~260px content width, the pair no longer fit, and the button wrapped to a third line -
       after which the search grew to fill line 2 alone. Measured at 360px: search 322 wide,
       button on its own line. Basis 0 makes the search claim nothing up front, so the two
       always share the line and the search then grows into whatever the button leaves. */
    .appr-headrow .appr-search           { order: 4; flex: 1 1 0; min-width: 0; }
    .appr-headrow .btn.primary           { order: 5; flex: 0 0 auto; }

    /* "Approve Selected (3)" -> "Approve (3)". The qualifier is the first thing worth
       dropping when the header already wraps to two rows: the count carries the meaning
       and the button sits beside the tab telling you which queue you are in. */
    .appr-action-tail { display: none; }

    /* ── Multi-select option rows: explanation inline, because there is no hover here ──
       A touch device fires no hover, so SearchableMultiSelect's per-option tooltip is unreachable
       on a phone - the text would simply never be seen. Rendering the same string inline is the
       only form that works without a pointer. The row stops being a single nowrap line to hold it,
       and its fixed 36px height goes with that. */
    .ms-opt--sub { height:auto; align-items:flex-start; padding-top:7px; padding-bottom:7px; }
    .ms-opt--sub .ms-opt-text { white-space:normal; overflow:visible; text-overflow:clip; }
    .ms-opt-sub {
        display:block; margin-top:2px; font-size:11.5px; line-height:1.35;
        color:var(--mud-palette-text-secondary); white-space:normal;
    }
    .ms-opt--sub input[type=checkbox] { margin-top:2px; }

    /* ── Escalations: list full-height, detail as a sliding drawer ────────────
       A two-pane desktop layout (360px fixed sidebar beside a flex:1 main pane). At 393px the
       sidebar alone claims 360 of the screen and "main" is left with about 33px — the detail
       pane was still THERE, which is why it never registered as overflow, it was just squeezed
       to nothing.

       The first fix stacked the two panes and capped the list at 45vh. That traded one problem
       for another: the list is the page's primary surface — a queue you scan, sorted urgent
       first — and 45vh showed about two cards, with a permanent "Select an escalation" placeholder
       eating the other half of a phone screen to say nothing.

       So: the list gets the WHOLE region, and the detail becomes a drawer over it. It is the same
       element as the desktop pane, taken out of flow and translated off-screen, slid in by
       .esc-open (which the page sets from _selected — the identical flag the desktop pane keys
       off, so there is no second state and no viewport test in C#). */
    /* position:relative makes THIS the containing block for the detail pane below. Without it the
       pane resolved against .mud-main-content, and an absolutely positioned box is placed against
       its containing block's PADDING BOX - which on .mud-main-content starts ABOVE its padding-top,
       i.e. underneath the fixed app bar. inset:0 therefore put the drawer's own header behind the
       nav bar. .esc-layout has no padding and already fills the region exactly, so the same inset:0
       now lands on the content area and nothing else has to be measured or subtracted. */
    .esc-layout  { flex-direction: column; overflow: hidden; position: relative; }
    .esc-sidebar {
        width: 100%; max-height: none; flex: 0 0 auto;
        /* Both borders go: it is no longer beside or above anything. */
        border-right: 0; border-bottom: 0;
        /* HEIGHT IS DELIBERATELY NOT SET HERE. The base rule's
           calc(100vh - var(--svai-content-top)) is a DEFINITE height that depends on nothing
           above it, and a definite height is what makes .esc-list the only scroller - which is
           in turn what pins .esc-sidebar-head, exactly as it is pinned on a desktop.

           An earlier revision overrode it with height:100%, which resolves against .esc-layout
           and collapsed to auto: the sidebar then grew to its content height, .esc-list's
           overflow-y:auto never engaged, and the head scrolled away with the cards. Reported as
           "Patient Escalations needs to stick with the status badges like the big screen". */
    }

    /* Belt and braces on the pin. flex-shrink:0 inside a definite-height column already keeps the
       head out of the scroller, but this costs nothing and means a future change to the flex chain
       degrades to a sticky header rather than to a header that scrolls away. Opaque, or cards show
       through it. */
    .esc-sidebar-head {
        position: sticky; top: 0; z-index: 5;
        background: var(--mud-palette-surface);
    }

    .esc-main {
        /* ABSOLUTE against .esc-layout (which position:relative above makes the containing
           block), inset:0 rather than any computed top. .esc-layout has no padding and fills the
           content region exactly, so this lands on the region with no arithmetic at all.

           TWO earlier revisions got this wrong, in opposite directions, and both are worth keeping:

           1. position:fixed + top:var(--svai-content-top) stacks two assumptions - that the
              variable is published (stickyshadow.js sets it, so a cached or slow script leaves the
              56px fallback), and that "fixed" means the viewport (any transform / filter / contain
              on an ancestor silently re-parents a fixed box). Either failing moves the whole pane.

           2. absolute + inset:0 while the containing block was .mud-main-content. An absolutely
              positioned box is placed against its containing block's PADDING BOX, and
              .mud-main-content's padding box starts ABOVE its own padding-top - which is exactly
              the strip the fixed app bar occupies. So top:0 was behind the nav bar, and the
              drawer's header with it. "The content area" and "the element whose padding pushes
              content below the app bar" are not the same box; positioning against the inner
              element is what removes the distinction. */
        position: absolute;
        inset: 0;
        /* 1000, deliberately BELOW MudBlazor's --mud-zindex-drawer (1100) and app bar (1300).
           This pane only ever covers the content area under the app bar, so it never needs to
           outrank either — and at 1250 it would have painted over the mobile nav drawer, which
           slides in from the left across the same region. Everything it does have to beat lives
           in the 20-31 band (sticky grid headers and bars), so there is a wide margin. */
        z-index: 1000;
        transform: translateX(100%);
        /* Nothing to hit while it is parked off-screen. Without this the invisible pane still
           swallows taps on the list underneath it. */
        visibility: hidden;
        transition: transform .22s ease, visibility 0s linear .22s;
    }
    .esc-main.esc-open {
        transform: translateX(0);
        visibility: visible;
        /* Visibility flips immediately on the way IN and only after the slide on the way out —
           hence the delay above and its absence here. */
        transition: transform .22s ease, visibility 0s;
    }

    /* The word "Priority" goes from the drawer header. That row has to carry a back button, the
       patient name and phone, this select and the status pill across a 393px screen, and the
       select's own options (Urgent / Normal / Low) already say what it is. The select keeps its
       aria-label, so nothing is lost for a screen reader - only the redundant visible text.
       The gap:6px on the wrapping label collapses on its own once the span is display:none. */
    .esc-prio-lbl { display: none; }

    /* The way back to the list. inline-flex only here: on desktop the list is still on screen
       beside the pane, so a back button would point at nothing. */
    .esc-back {
        display: inline-flex; align-items: center; justify-content: center;
        width: 32px; height: 32px; flex-shrink: 0;
        appearance: none; background: transparent; border: 0; cursor: pointer;
        margin-left: -6px; border-radius: 8px;
        color: var(--mud-palette-text-secondary);
    }
    .esc-back:hover { background: var(--mud-palette-background-grey); }

    /* The detail head is the drawer's own title bar now, so it has to stay put while the body
       scrolls under it. It is already flex-shrink:0 inside a column flex box, which is enough —
       recorded here so nobody "simplifies" that away. */

    /* Hand-rolled tables become their own horizontal scroller. */
    .mud-main-content table:not(.mud-table-root) {
        display: block;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        max-width: 100%;
    }
}

/* ── Wide grids between phone and desktop (601–900px) — REMOVED ──────────────
   There was a rule here giving .svai-grid-wide a min-width of --svai-cols * 110px,
   so an 11-column table would scroll sideways at tablet widths rather than compress
   every cell to "Ra..." / "Patien". It could not work: .svai-sticky-grid forces
   .mud-table-container to overflow:visible (the sticky thead has to pin against the
   page region, not the container), so the 1210px table it produced had no scroller
   of its own and pushed the overflow out to .svai-sticky-scroll — which then scrolled
   the whole region sideways, header and all, with the pager left behind at its own
   narrower width. It made the tablet worse than the compression it was fixing.

   The card block above now covers 601–900 instead, and its membership test is the
   same >= 6 columns that .svai-grid-wide used, so this rule had no case left to
   serve. gridcards.js no longer publishes --svai-cols or .svai-grid-wide either;
   if a future change reinstates a real table at these widths, the container has to
   become the scroller and the thead has to stop pinning to the page region — those
   two go together. ─────────────────────────────────────────────────────────── */

/* ============================================================================
   Conversations Hub — compact header + filter dropdown below 900px
   ----------------------------------------------------------------------------
   Desktop is untouched. Below 900px the page sheds the things that do not earn
   their vertical space on a small screen, and the filter controls collapse behind
   one button so the bar can be a single sticky line.

   The dropdown deliberately reuses the Approval page's mechanism rather than a new
   one: the toggle carries .appr-filter-toggle, which is the selector stickyshadow.js
   looks for when it publishes --svai-filterbtn-bottom / --svai-filterbtn-right on
   :root. So the panel anchors itself to the button with no extra script, and stays
   anchored on resize.
   ============================================================================ */

/* Grid card gutters, matched to the page gutters at each size. Desktop keeps the
   authored 28px; nothing above 900px changes. */
@media (max-width: 600px) { .grid-card { margin: 0 15px 24px; } }
@media (min-width: 601px) and (max-width: 900px) { .grid-card { margin: 0 20px 24px; } }

/* Above the breakpoint the wrapper must not exist as a box: display:contents
   dissolves it so Range / Direction / Sentiment sit directly in .filters, exactly
   as they did before this was introduced. */
/* Above 600px the inline controls are the filter UI and must sit in .filters as
   plain children — display:contents dissolves the wrapper so nothing about the
   existing tablet/desktop layout changes. */
.conv-inline-filters { display: contents; }
.conv-filter-toggle  { display: none; }

/* KPI tiles are hidden on small AND medium — they are a scorecard, not a control,
   and on either size they pushed the grid most of a screen down. Desktop keeps them.
   This is the ONLY part of the compact treatment that reaches tablet: the filter bar,
   the dropdown and the sticky header below are small-screen only, because at 820px
   the inline controls still fit and the existing behaviour was asked to stand. */
@media (max-width: 900px) {
    .svai-stk--conv .page-head .kpis { display: none; }
}

@media (max-width: 600px) {

    /* The three KPI tiles go. They are a scorecard, not a control, and on a phone
       they pushed the grid a full screen down. Still there on desktop. */
    /* The title row STICKS too, above the search bar, so scrolling a long list keeps
       "Conversations Hub" / search / table header in that order rather than letting the
       title scroll away. Two stacked sticky elements, not one bar: they are separate
       elements in the markup, so .page-head pins at 0 and .conv-filters pins at exactly
       .page-head's height (set just below). Both MUST be opaque - a transparent sticky
       element lets the rows scroll visibly through it, which reads as flicker.
       Padding tightened from 12/4 to 8/2: the gap between the title and the search row was
       most of what the title cost. */
    .svai-stk--conv .page-head {
        padding: 8px 15px 3px;
        position: sticky; top: 0; z-index: 31;
        background: var(--mud-palette-background, #F7F8FB);
    }

    /* 22px is a desktop title. On a 393px screen it is most of the line and buys
       nothing, so it steps down to 18. The margin-bottom goes with it: those 4px
       exist to separate the title from .sub, and .sub is display:none here (see
       below) — so the gap was padding the title away from the search bar for no
       reason. Between them that is 12px of dead space recovered above the fold,
       which on a phone is the difference between seeing two rows and three.

       This does NOT touch the sticky contract. .conv-filters keeps height:56px and
       --svai-thead-top stays 56 (Conversations.razor's own <style> block): the bar
       is sticky at top:0 of the scroll region and the thead pins beneath IT, so the
       page header above simply scrolls away and its height is not part of the sum.
       Only a change to the BAR's height would require the variable to move. */
    /* ── Page title, EVERY page ─────────────────────────────────────────────
       22px is a desktop title; on a 393px screen it is most of the line. THREE header
       patterns need covering, and missing one is invisible - that page simply keeps a
       22px title while every other page shrinks:

         .page-head h1     Conversations, Appointments
         .page-title       the eighteen pages converted off inline styles
         .appr-headrow h1  Approval, which has its own header bar and was never
                           converted. Safe to restyle here even though that bar is a
                           height contract on desktop: the same 600px block stands the
                           contract down (position:static, --svai-thead-top:0) above.

       An earlier revision matched `.content-padded h1` instead of `.page-title`. That
       was a workaround for the un-converted markup; the class is the contract now.

       No !important. Every page now carries the CLASS: the eighteen that used to
       hard-code this inline were converted, because an inline declaration beats any
       selector and made the plain rule a silent no-op on all of them. `.page-title`
       is defined earlier in this file, so this later rule wins on source order with
       no specificity games. If a new page reintroduces an inline title style, THAT
       is the bug - do not reach for !important to paper over it.

       margin-bottom stays POSITIVE here on purpose. All eighteen show a strapline
       directly beneath the title, so pulling the title down onto it would cost more
       than the space it saves. Conversations can afford a negative one only because
       it hides its .sub (see below) - which is the condition any other page must meet
       before copying that value. */
    .page-head h1,
    .appr-headrow h1,
    .page-title { font-size: 18px; margin-bottom: 2px; }

    /* Conversations hides its strapline, so nothing sits under the title and it can
       pull the filter bar up. More specific than the rule above, so it simply wins. */
    .svai-stk--conv .page-head h1 { font-size: 18px; margin-bottom: -10px; }

    /* The grid toolbar ("N of M conversations" + the icon buttons) loses two thirds
       of its vertical padding. Deliberately UNSCOPED - it is the same strip on every
       grid page, so this one rule is already the all-pages treatment. Desktop keeps
       the authored 12px. */
    .grid-toolbar {
        padding: 4px 16px;
    }

    /* One sticky line: search grows, Filter button pinned right.
       FIXED HEIGHT IS A CONTRACT — --svai-thead-top below must equal it, or a gap
       opens between the bar and the pinned table header and rows scroll through it.
       Same coupling as .appr-headbar / .svai-stk--approvals. */
    .conv-filters {
        /* top is .page-head's height, so this bar pins directly beneath the title instead
           of over it. Kept in one place: --svai-convhead is the single source both this
           offset and the thead offset are derived from. */
        position: sticky; top: var(--svai-convhead, 26px); z-index: 30;
        background: var(--mud-palette-background, #F7F8FB);
        display: flex; flex-wrap: nowrap; align-items: center; gap: 8px;
        height: 56px; box-sizing: border-box;
        padding: 8px 15px; margin: 0;
    }
    .conv-filters .search { flex: 1 1 auto; min-width: 0; }
    .conv-filter-toggle { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; }

    /* The table header pins directly beneath the bar (56 = .conv-filters height).
       That override lives in Conversations.razor's own <style> block, not here: the
       page block is in the body and therefore parsed after this file, so its
       `.svai-stk--conv { --svai-thead-top: 20px }` beat anything written here. */

    /* The shared .is-stuck ledge is a sticky band as tall as --svai-thead-top at
       z-index 21, which would paint straight over the header pinned at 20 — the same
       trap .svai-stk--approvals disables. .conv-filters is opaque at z-index 30 and
       already covers that strip, so nothing is lost. */
    .svai-stk--conv .svai-sticky-grid.is-stuck::before { content: none; }

    /* Inline controls step aside; the staged dropdown takes over. */
    .conv-inline-filters { display: none; }

    /* The dropdown hangs off the Filter button. */
    .conv-filterpop.open {
        display: block;
        position: fixed;
        top: var(--svai-filterbtn-bottom, 120px);
        right: var(--svai-filterbtn-right, 15px);
        z-index: 900;                       /* over our bar (30), under MudBlazor's app bar */
        /* 100%, NOT 100vw. For a fixed element a percentage resolves against the
           viewport EXCLUDING scrollbars, while 100vw INCLUDES them — so on a page
           with a vertical scrollbar calc(100vw - 30px) came out ~15px wider than the
           space actually available and pushed a horizontal scrollbar onto the page. */
        width: min(420px, calc(100% - 30px));
        background: var(--mud-palette-surface);
        border: 1px solid var(--mud-palette-lines-default);
        border-radius: 10px;
        box-shadow: 0 14px 32px rgba(20, 27, 45, .20);
        padding: 14px 16px;
    }
    .conv-filterpop.open > label.mud-input { width: 100%; margin-bottom: 12px; }
    .conv-filterpop.open > .mud-chipset { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
    .conv-filterpop.open > .divider-v { display: none; }   /* a vertical rule in a stacked panel reads as a stray line */
    .conv-filterpop.open > :last-child { margin-bottom: 0; }

    .conv-filterpop-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
    .conv-filterpop-head h3 {
        margin: 0; font-size: 13px; font-weight: 600; letter-spacing: .3px;
        text-transform: uppercase; color: var(--mud-palette-text-secondary);
    }
    .conv-filterpop-head .mud-icon-root { color: var(--mud-palette-primary); }
    .conv-filterpop-head .appr-filtercard-close { margin-left: auto; }

    /* Apply / Clear. Nothing in this panel filters on change — see the comment in
       Conversations.razor — so these two are the only way out of it. */
    .conv-filterpop-foot {
        display: flex; align-items: center; gap: 8px;
        margin-top: 14px; padding-top: 12px;
        border-top: 1px solid var(--mud-palette-divider);
    }
    .conv-filterpop-count { margin-left: auto; font-size: 12px; color: var(--mud-palette-text-secondary); }
    .conv-filterpop-count b { color: var(--mud-palette-text-primary); }
}

/* Small only: the strapline goes too. It explains the page once; on a 393px screen
   it costs two lines above the data every single visit. The title stays. */
@media (max-width: 600px) {
    .svai-stk--conv .page-head .sub { display: none; }
}

/* ── Edit drawer, shared by the tenant-area tabs ────────────────────────────────────────────
   This started in FacilityHours.razor's own style block, which is exactly where it could not
   stay: a component style block only reaches the DOM while that component is rendered, so the
   Holidays tab - which uses the same drawer - got the markup and none of the layout. Named for
   what it IS rather than for the first tab that needed it; a shared rule called .fh-* is one
   that gets duplicated the next time somebody reads the name as "facility hours only".

   A .svai-tabtools row lived here too, holding a search box and an "N of M" count above each
   grid. Both are gone: the search moved to the page header (Tenant.razor), and the count was
   dropped by request - it restated what the table already shows and cost a line above it. */

/* The house editing surface: fixed head, fixed foot, one scrolling body between them.

   The column flex goes on .mud-drawer-content, NOT on the drawer element: MudDrawer wraps
   ChildContent in that div, so a flex container on the outer aside leaves head / body / foot as
   ordinary blocks inside a plain child - the body never gets flex:1, never scrolls, and the Save
   button slides off the bottom. Its overflow:hidden is also what stops the drawer itself
   scrolling, in either direction. Same shape and same reasoning as .rr-drawer in
   ReminderRules.razor, which is where this was worked out.

   max-width:100% and not 100vw - vw includes the scrollbar gutter. */
.svai-editdrawer { max-width: 100%; }
.svai-editdrawer .mud-drawer-content {
    height: 100%; overflow: hidden !important;
    display: flex; flex-direction: column;
}

/* flex-shrink:0 on both, or a long body squeezes the head and foot instead of scrolling. */
.svai-editdrawer .svai-editdrawer-head { flex-shrink: 0; }

/* The ONE scroller. .pd-form already sets overflow-y:auto, but flex:1 and min-height:0 are what
   make it resolve: flex:1 against an auto min-height floors the item at its content size, which
   is the usual reason an "overflow:auto" appears to do nothing. overflow-wrap so an unbroken
   facility id wraps rather than widening the drawer and taking a scrollbar with it. */
.svai-editdrawer .svai-editdrawer-body {
    flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; overflow-wrap: anywhere;
    padding-bottom: 18px;
}

.svai-editdrawer .svai-editdrawer-foot {
    display: flex; align-items: center; justify-content: flex-end; gap: 10px; flex-shrink: 0;
    padding: 12px 20px; border-top: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
}

/* Full-width buttons once the drawer is effectively the viewport. */
@media (max-width: 560px) {
    .svai-editdrawer .svai-editdrawer-foot .btn { flex: 1 1 0; justify-content: center; }
}

/* ── Service-tier field: the info button and its tooltip ────────────────────────────────────
   MOVED HERE FROM Onboarding.razor's style block, unchanged apart from dropping the `.svai-ob`
   scope that pinned them to the wizard. The Facilities drawer's Service tiers field now uses the
   same control, and a component style block only reaches the DOM while that component renders -
   so left in place, the drawer would have got the markup and none of the layout. The `ob-` names
   are kept so the wizard's markup did not have to be touched at all; they mean "onboarding" only
   as an accident of where the control was built first.

   Everything below was worked out against the wizard at 393px and is deliberately verbatim. */

/* THE ICON COLUMN IS FIXED BY CONSTRUCTION. A grid does not negotiate: `auto` is sized to its
   content and never reduced below it, and minmax(0,1fr) lets the field take whatever is left and
   no more. So the icon keeps its column at ANY selection length. As a flex row the icon was
   squeezed away once the selection summary got long enough to claim the slack ("more than 4
   selected"). */
.ob-tierrow { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:6px; }
.ob-tierrow .svai-ob-msbox { min-width:0; }

/* THE FLEX ITEM IS THE TOOLTIP ROOT, NOT THE BUTTON. MudTooltip wraps its trigger in
   .mud-tooltip-root (width: fit-content), so that wrapper - not .ob-tierinfo - is what sits in
   this row, and a layout rule aimed at the trigger protects nothing. */
.ob-tierinfo-root { display:flex; align-items:center; }
.ob-tierinfo { flex:0 0 auto; color:var(--mud-palette-primary); }
.ob-tierinfo:hover { color:var(--mud-palette-primary); }

/* The tooltip skin, shared by the info button AND the per-option row tooltips. MudBlazor's
   default is white-on-gray-darker, which reads as a system tooltip rather than as part of the
   form. !important throughout: .mud-tooltip.mud-tooltip-default sets colour and background at
   (0,2,0) from the linked stylesheet, and MudBlazor also ships theme variables inline on some
   surfaces, so source order alone is not enough. */
.mud-tooltip.ob-tip {
    background:var(--mud-palette-surface) !important;
    color:var(--mud-palette-text-primary) !important;
    border:1px solid var(--mud-palette-lines-default);
    box-shadow:0 10px 28px rgba(20,27,45,.18), 0 4px 12px rgba(20,27,45,.10);
    padding:10px 12px;
    /* Never wider than the screen it sits inside. min() rather than a flat 320: on a 393px phone
       a 320px tooltip plus MudBlazor's own offset is what put the document into horizontal
       scroll. calc leaves a gutter on both sides. */
    max-width:min(320px, calc(100vw - 32px));
    white-space:normal;
    overflow-wrap:anywhere;
}
/* The arrow is a BORDER TRIANGLE on the tooltip element itself, whose base rule is
   `border-color: transparent; border-top-color: inherit`. So border-top-color is what has to
   change, not a background - left alone it is a dark spike hanging off a white box. */
.mud-tooltip.ob-tip.mud-tooltip-arrow::after { border-top-color:var(--mud-palette-surface) !important; }

.ob-tierinfo-body { text-align:left; display:flex; flex-direction:column; gap:8px; }
.ob-tierinfo-row { display:flex; flex-direction:column; gap:2px; }
.ob-tierinfo-name { font-weight:700; font-size:12.5px; color:var(--mud-palette-text-primary); }
.ob-tierinfo-desc { font-size:12px; line-height:1.45; color:var(--mud-palette-text-secondary); }

@media (max-width:600px) {
    /* The INFO BUTTON STAYS on small screens - it is the only way to read what the selected tiers
       grant once the menu is closed, and a phone has no hover to fall back on. Comfortable tap
       target; its column is guaranteed by the grid above. */
    .ob-tierinfo { width:36px; height:36px; }

    /* The per-option tooltip is off here, and only that one: each row already carries its
       description inline (.ms-opt-sub), so the tooltip would be a second copy of text two
       millimetres away - and there is no hover on a phone to trigger it. Its own class, so
       switching it off leaves the info tooltip alone. */
    .mud-tooltip.ob-opt-tip { display:none !important; }

    /* 320 plus MudBlazor's offset leaves almost no margin at 393px, so the cap comes down. */
    .mud-tooltip.ob-tip { max-width:min(300px, calc(100vw - 24px)); padding:9px 11px; }

    /* HORIZONTAL CLAMP ONLY - measured, not guessed. Placement.Left subtracts the full panel
       width from the anchor, and on a 393px screen there is not that much room to the left of a
       control at the right edge: measured left=-180. Only `left` is overridden; the vertical
       placement was never wrong, and an earlier revision that also forced position/top/bottom/
       transform stopped the panel appearing at all. right:auto matters - left+right together
       would stretch a box whose width is already correct. */
    .mud-popover:has(.ob-tierinfo-tip) {
        left:12px !important;
        right:auto !important;
    }

    .ob-tierinfo-name { font-size:12px; }
    .ob-tierinfo-desc { font-size:11.5px; line-height:1.4; }
}

/* ==========================================================================================
   BROWSER AUTOFILL vs MudBlazor's FLOATING LABEL
   ==========================================================================================
   MudBlazor floats a label by putting .mud-shrink on the input wrapper, and it decides that from
   the BOUND value in C#. A browser autofill writes straight to the DOM and raises nothing Blazor
   binds to, so the component still believes the field is empty: .mud-shrink never appears, and the
   label stays sitting inside the box, directly on top of the text the browser has just painted
   there. The field reads as empty with its label acting as placeholder text.

   The real fix is upstream - the payments credential and bank forms carry autocomplete="off" /
   "new-password" so the fill does not happen at all, and each explains why at the field. This is
   the backstop for the password managers that ignore those attributes, and for any form that
   acquires an autofillable field later without thinking about it.

   :-webkit-autofill is set by the browser itself whenever it fills a field, which makes it the one
   signal available in CSS that the DOM holds a value the component does not know about. :has() is
   required because MudBlazor's label is a SIBLING of the input wrapper, not an ancestor of the
   input - supported in Chrome/Edge/Safari, and a browser without it simply keeps today's behaviour
   rather than breaking.

   The three transforms are copied from MudBlazor.min.css and must stay in step with it: default
   (text) variant, filled, and outlined. Getting one wrong moves the label somewhere worse than
   where it started, so they are listed separately rather than collapsed.
   ========================================================================================== */
.mud-input-control:has(input:-webkit-autofill) > label.mud-input-label.mud-input-label-inputcontrol {
    transform:translate(0, 1.5px) scale(0.75);
    transform-origin:top left;
    color:var(--mud-palette-text-primary);
}
.mud-input-control:has(input:-webkit-autofill) > label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-filled {
    transform:translate(12px, 10px) scale(0.75);
    max-width:calc(100% - 12px);
}
.mud-input-control:has(input:-webkit-autofill) > label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-outlined {
    transform:translate(14px, -6px) scale(0.75);
    max-width:calc(100% - 14px);
}
