/* ── Fonts (self-hosted, all SIL OFL — see docs/brand/README.md) ────
   Inter carries the product UI, JetBrains Mono carries IDs/amounts/
   config, Fraunces 600 appears ONLY at brand moments (rail tile, login
   headline) — never inside dense app screens. */

@font-face {
    font-family: "Inter";
    src: url("../fonts/InterVariable.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Inter";
    src: url("../fonts/InterVariable-Italic.woff2") format("woff2");
    font-weight: 100 900;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: "JetBrains Mono";
    src: url("../fonts/jetbrains-mono-v24-latin-regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "JetBrains Mono";
    src: url("../fonts/jetbrains-mono-v24-latin-600.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Fraunces";
    src: url("../fonts/fraunces-v38-latin-600.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}

/* ── Reset ──────────────────────────────────────────────────────── */

html,
body {
    margin: 0;
    padding: 0;
    min-height: 100%;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body {
    background: var(--mud-palette-background);
    color: var(--mud-palette-text-primary);
}

/* ── Design tokens & budget (Phase 12.1) ────────────────────────────
   The semantic layer above MudBlazor's palette. The written budget
   lives in UI_STYLE_GUIDE.md ("Design language & budget"); short form:
   - Three card tiers: hero (at most ONE per page, accent-tinted border,
     "look here first") / standard (surface-card) / quiet (.summary-item
     tiles — numbers first, no chrome).
   - Status colors ONLY on true status, never decoration. One red thing
     per healthy screen; red multiplies only when things are genuinely
     on fire.
   - Type scale: big numbers (--cw-num-*) over whispering uppercase
     labels (--cw-label-*).
   - An empty queue collapses to a one-line .cw-allclear strip — it
     never spends a full card proving nothing is wrong. */

:root {
    --cw-radius-card: 14px; /* hero + standard surfaces */
    --cw-radius-tile: 12px; /* quiet tiles */

    /* Type scale: numbers read first, labels whisper. Weight caps at
       600 — the heaviest JetBrains Mono we ship. */
    --cw-num-size: 1.55rem;
    --cw-num-weight: 600;
    --cw-label-size: 0.6875rem;

    /* Status inks — true status only, never decoration. */
    --cw-status-critical: #DC2626;
    --cw-status-warning: #CA8A04;
    --cw-status-ok: #1E8E3E;

    /* Brand faces (Calmwright materials, docs/brand). Display serif is
       for brand moments only; mono is for IDs, durations, amounts. */
    --cw-font-display: "Fraunces", Georgia, serif;
    --cw-font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;

    /* Chalk — the light ink the iron rail and login panel write with. */
    --cw-chalk: #EDE5D6;
    --cw-chalk-bright: #F5EFE2;

    /* The warm-iron rail (login panel family) — fixed in both themes,
       exactly like the login brand panel. Chalk at graded opacities. */
    --cw-rail-base: #201A14;
    --cw-rail-text: rgba(237, 229, 214, 0.85);
    --cw-rail-text-dim: rgba(237, 229, 214, 0.62);
    --cw-rail-icon: rgba(237, 229, 214, 0.68);
    --cw-rail-hover: rgba(237, 229, 214, 0.07);
    --cw-rail-active: rgba(237, 229, 214, 0.14);
    --cw-rail-hairline: rgba(237, 229, 214, 0.14);
}

/* ── Global control polish ──────────────────────────────────────── */
/* Buttons and tabs read sentence-case app-wide (admin-center look).
   Previously this was scoped to the tickets pages only, which made the
   rest of the app shout in ALL CAPS. */

.mud-button-root {
    text-transform: none !important;
    letter-spacing: 0 !important;
}

.mud-tabs .mud-tab {
    text-transform: none !important;
    letter-spacing: 0 !important;
}

/* ── Machine values ─────────────────────────────────────────────── */
/* Ticket IDs, durations, and currency amounts in DATA positions
   (table cells, stat values, totals) wear JetBrains Mono; the same
   value inside a sentence stays Inter — prose is prose. Only weights
   400/600 are self-hosted, so font-synthesis is off: a <b> inside a
   mono cell renders at 600, never a faux 700. */

.cw-mono {
    font-family: var(--cw-font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: normal;
    font-synthesis-weight: none;
}

/* Numeric table cells: right-aligned, tabular digits (the type-scale
   rule — numbers read as columns, labels stay left). Global because
   the reports pages share the class; the mono face applies to data
   cells only, never the <th> label. */
.cw-num-cell {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

td.cw-num-cell {
    font-family: var(--cw-font-mono);
    letter-spacing: normal;
    font-synthesis-weight: none;
}

/* MudBlazor's `.mud-table-root .mud-table-body .mud-table-cell` family
   rule out-ranks the single-class utilities above, so a `cw-mono` or
   `cw-num-cell` placed on a MudTd silently rendered Inter. Restate the
   mono face at the same cell level for every Mud table app-wide. */
.mud-table-root .mud-table-body .mud-table-cell.cw-mono,
.mud-table-root .mud-table-body td.mud-table-cell.cw-num-cell {
    font-family: var(--cw-font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: normal;
    font-synthesis-weight: none;
}

/* MudSimpleTable's `table * tr>td/th` rule (0,1,3) out-ranks the
   utility's alignment the same way — restate it. */
.mud-simple-table table tr > th.cw-num-cell,
.mud-simple-table table tr > td.cw-num-cell {
    text-align: right;
}

/* ── Wide tables on narrow screens: scroll sideways, never clip. ──────
   Add `report-table` beside data-grid-container and set --cw-table-min
   in markup: the table gets a width floor so MudBlazor's own
   .mud-table-container scrolls it instead of crushing columns, and a
   right-edge fade signals that more columns are off-canvas. In engines
   with scroll-driven animations the fade lifts at scroll end and never
   shows when the table fits; elsewhere the scrollbar is the affordance. */
.report-table {
    position: relative;
    timeline-scope: --cw-report-x;
}

.report-table .mud-table-container {
    scroll-timeline: --cw-report-x inline;
}

.report-table table {
    /* Opt-in floor: pages set --cw-table-min where columns would crush;
       without it the class still provides scroll + fade affordance. */
    min-width: var(--cw-table-min, 0px);
}

.report-table::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 32px;
    pointer-events: none;
    border-radius: 0 14px 14px 0;
    background: linear-gradient(to left, var(--mud-palette-surface), transparent);
    opacity: 0;
}

@supports (animation-timeline: scroll()) {
    .report-table::after {
        animation: cw-report-scroll-fade linear both;
        animation-timeline: --cw-report-x;
    }
}

@keyframes cw-report-scroll-fade {
    0%, 96% { opacity: 1; }
    100% { opacity: 0; }
}

/* report-table composes with stacked MudTables: below the stacking
   breakpoint there is no "table" left to floor, so the min-width must
   not reintroduce sideways overflow. */
@media (max-width: 600px) {
    .report-table .mud-xs-table .mud-table-root {
        min-width: 0;
    }
}

/* Stacked-mode data labels (MudBlazor's ::before content:attr(data-label))
   read as the same uppercase whisper everywhere — half the app's stacked
   tables were title-case, half uppercase. */
@media (max-width: 600px) {
    .mud-xs-table .mud-table-body .mud-table-cell::before {
        font-size: 0.6875rem;
        font-weight: 700;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--mud-palette-text-secondary);
        font-family: var(--mud-typography-default-family);
    }
}

/* ── Page Shell ─────────────────────────────────────────────────── */
/* Every main page shares the same centered column (matches the
   Service Desk baseline width). */

.page-shell {
    width: 100%;
    max-width: 1320px;
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    gap: 24px;
}

/* ── Surface Card (shared "Calmwright look" panel) ────────────────── */
/* Use Class="surface-card" on MudCard/MudPaper instead of inline
   border styles: white surface, hairline border, 14px radius. */

.surface-card {
    background: var(--mud-palette-surface);
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%) !important;
    border-radius: 14px !important;
    box-shadow: none !important;
}

/* UX-01: clickable cards (e.g. Settings index) are keyboard buttons —
   show where focus is. */
.surface-card[role="button"]:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* Nine call sites already relied on this Bootstrap-ism that neither
   MudBlazor nor we ever defined — every "centered" empty state was
   silently left-aligned (2026-07-14 polish sweep). */
.text-center {
    text-align: center;
}

.contract-template-card:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* ── Mud Dialog (global polish) ─────────────────────────────────── */

.mud-dialog {
    border-radius: 14px !important;
}

.mud-dialog .mud-dialog-content {
    padding: 18px 20px !important;
}

.mud-dialog .mud-dialog-actions {
    padding: 12px 16px 16px !important;
}

/* ── Page Header ────────────────────────────────────────────────── */

.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 4px;
}

.page-header-text {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.page-title {
    margin: 0;
    font-size: 1.75rem;
    line-height: 1.1;
    font-weight: 700;
    letter-spacing: -0.025em;
}

/* Blazor's FocusOnNavigate focuses the first <h1> for accessibility. Don't show a loud focus outline on headers. */
.page-title:focus {
    outline: none !important;
}

.page-title:focus-visible {
    outline: none !important;
}

/* FocusOnNavigate target for route changes (see Routes.razor) */
#cw-main:focus {
    outline: none !important;
}

#cw-main:focus-visible {
    outline: none !important;
}

/* The one Fraunces moment inside the app: the dashboard greets you by
   name, in the brand's own hand. Every other page keeps its plain Inter
   title — brand moments stay rare on purpose (see UI_STYLE_GUIDE.md). */
.page-title--greeting {
    font-family: var(--cw-font-display);
    font-weight: 600;
    letter-spacing: -0.01em;
}

.page-subtitle {
    margin: 0;
    font-size: 0.875rem;
    color: var(--mud-palette-text-secondary);
}

.page-header-actions {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* ── Dashboard Summary (quiet tier — see the token block up top) ── */

.dashboard-summary {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr));
    gap: 16px;
}

/* Mid-desktop: auto-fill lands on 5–7 columns and strands orphan tiles
   with the 8-tile admin set (7+1 at 1440, 6+2 at 1366). Pin the 8-tile
   grid to balanced 4+4 in that band; smaller tile sets (tech dashboards)
   keep the flexible fill. ≥1560px reaches 8-across again. */
@media (min-width: 961px) and (max-width: 1559px) {
    .dashboard-summary:has(> .summary-item:nth-child(8)) {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.dashboard-summary-compact {
    grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr));
}

.dashboard-summary-compact .summary-item {
    padding: 12px 16px;
}

.summary-item {
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--cw-radius-tile);
    padding: 14px 18px;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 5px;
    transition: border-color 0.15s ease;
}

.summary-item:hover {
    border-color: var(--mud-palette-action-default);
}

a.summary-item {
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

a.summary-item:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.summary-label {
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--mud-palette-text-secondary);
}

.summary-value {
    font-family: var(--cw-font-mono);
    font-size: var(--cw-num-size);
    font-weight: var(--cw-num-weight);
    letter-spacing: normal;
    color: var(--mud-palette-text-primary);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

/* Status inks for summary numbers — apply ONLY while the underlying
   condition is true (the class is conditional in markup). The budget:
   one red thing per healthy screen. */
.summary-crit {
    color: var(--cw-status-critical);
}

.summary-warn {
    color: var(--cw-status-warning);
}

/* Legacy value tints (still used by the billing workspace strip). */
.active-value {
    color: #16A34A;
}

.onboarding-value {
    color: #CA8A04;
}

.churned-value {
    color: #DC2626;
}

/* ── Stat cards (quiet tier, shared StatCard.razor) ────────────────
   Same tile language as .summary-item. These classes were referenced
   by StatCard + Books for a full phase while being defined NOWHERE —
   the Books overview rendered as unstyled stacked strips (the 12.1
   "stat-grid" bug). If you add a stat surface, wrap the cards in
   .stat-grid. */

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 16px;
}

/* Tablet band: auto-fit lands on 3 columns, wrapping 4-tile rows 3+1
   with a hole. Pin to a balanced 2×2 until 4-across genuinely fits. */
@media (min-width: 601px) and (max-width: 1099px) {
    .stat-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.stat-card {
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--cw-radius-tile);
    padding: 14px 18px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
}

.stat-card-header {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.stat-icon {
    width: 26px;
    height: 26px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}

/* Labels whisper (and wrap — they never truncate). */
.stat-title {
    font-size: var(--cw-label-size);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    line-height: 1.3;
    color: var(--mud-palette-text-secondary);
}

.stat-card-body {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.stat-value {
    font-family: var(--cw-font-mono);
    font-size: var(--cw-num-size);
    font-weight: var(--cw-num-weight);
    letter-spacing: normal;
    line-height: 1.1;
    color: var(--mud-palette-text-primary);
    font-variant-numeric: tabular-nums;
}

.stat-change {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.75rem;
    font-weight: 650;
}

.stat-change-up {
    color: var(--cw-status-ok);
}

.stat-change-down {
    color: var(--cw-status-critical);
}

/* auto-fit keeps the row balanced for any queue count (a fixed 4-col
   grid left a lone orphan card whenever there were 5 queues). */
.action-dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 16px;
}

.action-dashboard-grid-billing {
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.action-card {
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    min-width: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.action-card-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 14px 12px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.action-card-icon {
    width: 30px;
    height: 30px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--mud-palette-text-primary);
    background: color-mix(in oklab, var(--mud-palette-surface), #000 5%);
    border: 1px solid var(--mud-palette-lines-default);
    flex: 0 0 auto;
}

.action-card-title-block {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.action-card-title {
    font-size: .9rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--mud-palette-text-primary);
}

.action-card-sub {
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
}

.action-list {
    display: flex;
    flex-direction: column;
}

.action-row {
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 14px;
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.action-row:last-child {
    border-bottom: none;
}

.action-row:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-surface) 94%);
}

/* Email-triage rows carry a trailing AI button beside the link (Phase 10.3). */
.action-row-flex {
    display: flex;
    align-items: center;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.action-row-flex:last-child {
    border-bottom: none;
}

.action-row-flex > .action-row {
    flex: 1;
    min-width: 0;
    border-bottom: none;
}

.action-row-main,
.action-row-side {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.action-row-main {
    flex: 1 1 auto;
}

.action-row-side {
    flex: 0 0 auto;
    align-items: flex-end;
    max-width: 150px;
}

/* Titles wrap to two lines rather than truncating — a row you can't
   read isn't information (12.1: no truncated rows on the dashboard). */
.action-row-title {
    font-size: .86rem;
    font-weight: 650;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.action-row-meta,
.action-row-detail {
    font-size: .73rem;
    line-height: 1.2;
    color: var(--mud-palette-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

/* Side details wrap to two lines instead of truncating ("Unknown
   sender — assign a client" must stay readable). */
.action-row-detail {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    text-align: right;
}

.action-priority {
    font-size: .65rem;
    line-height: 1;
    font-weight: 800;
    letter-spacing: .03em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.action-priority-medium {
    color: #4E6577;
}

.action-priority-high {
    color: #CA8A04;
}

.action-priority-critical {
    color: #DC2626;
}

/* flex:1 keeps the "all clear" state centered in the full card body
   when siblings in the row are taller. */
.action-empty {
    flex: 1;
    min-height: 84px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 14px;
    color: color-mix(in oklab, #1E8E3E, var(--mud-palette-text-secondary) 45%);
    font-weight: 650;
    font-size: .82rem;
}

.action-card-footer {
    display: block;
    margin-top: auto;
    padding: 9px 14px;
    font-size: .75rem;
    font-weight: 650;
    color: var(--mud-palette-primary);
    text-decoration: none;
    border-top: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 35%);
}

.action-card-footer:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-surface) 94%);
}

/* ── Hero card (12.1 hero tier) ─────────────────────────────────────
   At most ONE per page: the thing to look at first. On the dashboard
   this is "Needs attention" — the merged list of genuinely-on-fire
   queues (Critical severity comes from the data, not the styling). */

.cw-card-hero {
    background: var(--mud-palette-surface);
    border: 1px solid color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-lines-default) 45%);
    border-radius: var(--cw-radius-card);
    box-shadow: 0 1px 6px color-mix(in oklab, var(--mud-palette-primary), transparent 90%);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.cw-hero-header {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 15px 18px 12px;
}

.cw-hero-icon {
    width: 32px;
    height: 32px;
    border-radius: 9px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--mud-palette-primary);
    background: var(--mud-palette-primary-lighten);
    flex: 0 0 auto;
}

.cw-hero-title {
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.01em;
    color: var(--mud-palette-text-primary);
}

.cw-hero-sub {
    font-size: .78rem;
    color: var(--mud-palette-text-secondary);
}

.cw-hero-row {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 12px 18px;
    color: inherit;
    text-decoration: none;
    border-top: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.cw-hero-row:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-surface) 96%);
}

.cw-hero-prio {
    flex: 0 0 84px;
}

.cw-hero-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Hero rows never truncate — this is the list that must be readable. */
.cw-hero-row-title {
    font-size: .92rem;
    font-weight: 650;
    line-height: 1.3;
}

.cw-hero-row-meta {
    font-size: .78rem;
    color: var(--mud-palette-text-secondary);
}

.cw-hero-side {
    /* Fixed column — content-sizing made the title column jitter row-to-row. */
    flex: 0 0 200px;
    font-size: .8rem;
    color: var(--mud-palette-text-secondary);
    text-align: right;
}

/* Phones: title takes the full width first, then the priority pill and
   SLA/detail share the line below — the three-column row collapsed the
   title to ~100px and collided glyphs with the SLA label at 390px. */
@media (max-width: 600px) {
    .cw-hero-row {
        flex-wrap: wrap;
        gap: 6px 12px;
    }

    .cw-hero-main {
        order: -1;
        flex: 1 1 100%;
    }

    .cw-hero-prio {
        flex: 0 0 auto;
    }

    .cw-hero-side {
        flex: 1 1 auto;
        text-align: right;
    }
}

/* ── All-clear strip (12.1 empty-state rule) ────────────────────────
   Empty queues collapse into ONE line — they never spend a full card
   proving nothing is wrong. */

.cw-allclear {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 18px;
    border: 1px dashed color-mix(in oklab, var(--mud-palette-lines-default), var(--mud-palette-text-secondary) 18%);
    border-radius: 999px;
    color: var(--mud-palette-text-secondary);
    font-size: .84rem;
    font-weight: 500;
    min-width: 0;
}

.cw-allclear .mud-icon-root {
    color: var(--cw-status-ok);
    flex: 0 0 auto;
}

.cw-allclear-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* On phones the strip's message beats single-line purity: wrap up to
   two lines rather than ellipsing away the actual reassurance. */
@media (max-width: 599.98px) {
    .cw-allclear-text {
        white-space: normal;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
    }
}

/* Phone table pagers: MudBlazor's fixed-height toolbar shears
   "Rows per page" and "1-10 of N" into ragged multi-line fragments at
   390px. Let the toolbar wrap as whole units instead. */
@media (max-width: 599.98px) {
    .mud-table-pagination-toolbar {
        height: auto;
        min-height: 48px;
        flex-wrap: wrap;
        justify-content: flex-end;
        padding-inline: 8px;
        row-gap: 2px;
    }

    .mud-table-pagination-toolbar .mud-table-pagination-caption,
    .mud-table-pagination-toolbar .mud-table-pagination-display {
        white-space: nowrap;
    }
}

/* ── Command Bar ────────────────────────────────────────────────── */

.command-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
}

.command-bar-items {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Vault list: reuse the shared list-page command bar, with enough wrapping
   room for the secret-management actions and filters. */
.vault-page .vault-toolbar {
    align-items: flex-start;
}

.vault-page .vault-filter-items,
.vault-page .vault-action-items {
    flex-wrap: wrap;
}

.vault-page .vault-filter-items {
    flex: 1 1 auto;
    min-width: 0;
}

.vault-page .vault-search-control {
    flex: 1 1 260px;
}

.vault-page .vault-filter-control {
    flex: 0 1 220px;
}

.vault-page .vault-action-items {
    flex: 0 0 auto;
    margin-left: auto;
}

.vault-embedded-grid {
    overflow-x: auto;
}

/* ── Data Grid Container ────────────────────────────────────────── */

.data-grid-container {
    background-color: var(--mud-palette-surface);
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    border-radius: 14px;
    overflow: hidden;
}

/* Let MudDataGrid's internal borders flow without interference */
.data-grid-container .mud-table {
    border: none !important;
    box-shadow: none !important;
}

/* Match the Service Desk table-head treatment: tinted surface,
   small uppercase labels, hairline divider. (!important needed to win
   over MudBlazor's .mud-table-cell typography rules.) */
.data-grid-container .mud-table-head th.mud-table-cell {
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 35%);
    font-size: 0.6875rem !important;
    font-weight: 700 !important;
    letter-spacing: 0.06em !important;
    text-transform: uppercase !important;
    color: var(--mud-palette-text-secondary) !important;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.data-grid-container .mud-table-body td {
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.data-grid-container .mud-table-hover .mud-table-body .mud-table-row:hover {
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 18%);
}

/* MudSimpleTable renders plain <thead>/<tbody> without the .mud-table-cell
   classes, so it needs its own selectors to pick up the same Service Desk head.
   :not(.mud-table-cell) keeps these from touching MudTable/MudDataGrid cells. */
.data-grid-container thead th:not(.mud-table-cell) {
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 35%);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
    text-align: left;
    padding: 10px 16px;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.data-grid-container tbody td:not(.mud-table-cell) {
    padding: 10px 16px;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.data-grid-container .mud-table-hover tbody tr:hover td:not(.mud-table-cell) {
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 18%);
}

.clickable-row {
    cursor: pointer;
}

/* UX-01: the primary cell of a clickable grid row is a real link — it reads
   like plain text but gives keyboard users a tab stop and everyone
   middle-click/copy-link, while RowClick keeps the whole row mouse-clickable. */
.grid-row-link {
    color: inherit;
    text-decoration: none;
}

.grid-row-link:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
    border-radius: 4px;
}

.grid-cell-primary {
    display: flex;
    align-items: center;
    gap: 12px;
}

.grid-text-primary {
    font-weight: 600;
}

.status-indicator {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.status-text {
    color: var(--mud-palette-text-secondary);
}

/* ── Details Page ───────────────────────────────────────────────── */

.details-shell {
    width: 100%;
    max-width: 1320px;
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.info-grid {
    display: grid;
    /* minmax(0, …) prevents long values (URLs, IDs) from forcing horizontal overflow */
    grid-template-columns: minmax(0, 180px) minmax(0, 1fr);
    gap: 10px 20px;
}

.info-label {
    color: var(--mud-palette-text-secondary);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding-top: 4px;
}

.info-value {
    font-size: 0.875rem;
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* ── Auth (login / setup) ───────────────────────────────────────────
   Split shell: brand panel on the left (desktop only), form column on
   a surface-colored panel to the right. The brand panel wears fixed
   warm iron in both themes (the rail's family — sign-in and app read
   as one product); the form side follows the theme. */

.auth-layout {
    min-height: 100dvh;
    display: flex;
    background: var(--mud-palette-background);
}

.auth-brand {
    display: none;
    position: relative;
    overflow: hidden;
    flex-direction: column;
    justify-content: space-between;
    gap: 48px;
    width: clamp(400px, 44vw, 640px);
    padding: 44px 52px;
    color: var(--cw-chalk);
    background:
        radial-gradient(1000px 640px at -10% 112%, rgba(222, 139, 74, 0.16), transparent 62%),
        radial-gradient(820px 540px at 112% -12%, rgba(237, 229, 214, 0.05), transparent 55%),
        linear-gradient(160deg, #26201A 0%, #201A14 58%, #2B2115 100%);
}

/* Faint shop-drawing grid, faded toward the edges. */
.auth-brand::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(237, 229, 214, 0.05) 1px, transparent 1px),
        linear-gradient(90deg, rgba(237, 229, 214, 0.05) 1px, transparent 1px);
    background-size: 44px 44px;
    -webkit-mask-image: radial-gradient(85% 85% at 30% 45%, #000 25%, transparent 100%);
    mask-image: radial-gradient(85% 85% at 30% 45%, #000 25%, transparent 100%);
    pointer-events: none;
}

.auth-brand > * {
    position: relative;
    z-index: 1;
}

.auth-brand-mark {
    display: flex;
    align-items: center;
    width: fit-content;
}

/* The reverse lockup (chalk + bright copper) sits directly on the iron. */
.auth-brand-logo {
    display: block;
    height: 40px;
    width: auto;
}

.auth-brand-headline {
    margin: 0 0 30px;
    font-family: var(--cw-font-display);
    font-size: clamp(1.6rem, 2.4vw, 2.15rem);
    font-weight: 600;
    line-height: 1.18;
    letter-spacing: -0.01em;
    max-width: 24ch;
    color: var(--cw-chalk-bright);
}

.auth-brand-points {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.auth-brand-points li {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 0.95rem;
    font-weight: 550;
    color: rgba(237, 229, 214, 0.92);
}

.auth-point-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 9px;
    background: rgba(237, 229, 214, 0.1);
    border: 1px solid rgba(237, 229, 214, 0.2);
    color: #DE8B4A;
    flex-shrink: 0;
}

.auth-brand-foot {
    font-size: 0.8rem;
    color: rgba(237, 229, 214, 0.6);
}

.auth-panel {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px 24px;
    background: var(--mud-palette-surface);
}

.auth-panel-inner {
    width: min(400px, 100%);
}

/* Compact brand row — only when the brand panel is hidden. The app
   icon (iron square, chalk + copper) reads in both themes; the name is
   set live in Fraunces, lowercase like the wordmark. */
.auth-panel-brand {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 36px;
}

.auth-panel-appicon {
    display: block;
    width: 34px;
    height: 34px;
    border-radius: 9px;
    flex-shrink: 0;
}

.auth-panel-brand-name {
    font-family: var(--cw-font-display);
    font-weight: 600;
    font-size: 1.2rem;
    letter-spacing: -0.01em;
    color: var(--mud-palette-text-primary);
}

.auth-head {
    margin-bottom: 26px;
}

.auth-eyebrow {
    display: block;
    margin-bottom: 4px;
    color: var(--mud-palette-text-secondary);
    font-size: var(--cw-label-size);
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.auth-title {
    margin: 0;
    font-size: 1.6rem;
    font-weight: 750;
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--mud-palette-text-primary);
}

.auth-title:focus,
.auth-title:focus-visible {
    outline: none !important;
}

.auth-subtitle {
    margin: 7px 0 0;
    font-size: 0.9rem;
    color: var(--mud-palette-text-secondary);
}

.auth-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.auth-form .auth-submit {
    margin-top: 6px;
    height: 44px;
    font-weight: 650;
}

/* Staff MFA (A9). The QR box stays white in both themes — scanner contrast
   beats palette consistency. */
.auth-mfa-qr {
    align-self: center;
    padding: 10px;
    background: #fff;
    border-radius: 10px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    line-height: 0;
}

.auth-mfa-qr svg {
    width: 190px;
    height: 190px;
    display: block;
}

.auth-mfa-secret {
    align-self: center;
    padding: 6px 10px;
    border-radius: 8px;
    background: color-mix(in oklab, var(--mud-palette-lines-default), transparent 82%);
    font-size: 0.85rem;
    letter-spacing: 0.08em;
    word-break: break-all;
    user-select: all;
}

.auth-mfa-links {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    font-size: 0.85rem;
}

.auth-mfa-codes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 16px;
    padding: 14px 16px;
    border-radius: 10px;
    background: color-mix(in oklab, var(--mud-palette-lines-default), transparent 82%);
}

.auth-mfa-codes code {
    font-size: 0.9rem;
    letter-spacing: 0.04em;
    user-select: all;
}

/* Circuit reconnect modal (A10). The .NET 11 blazor.web.js contract (read
   from the shipped source): it finds the element by id, toggles exactly one
   state class via classList — show → (+retrying) → hide on success, paused on
   a graceful server pause, failed/resume-failed when it gives up, rejected
   when the server disowns the circuit — and fills the attempt-counter ids.
   Everything below hangs off the id + those classes; no classes of our own on
   the root, so a future framework that rewrites className can't strand us.
   Mud palette vars are live by the time a circuit can drop (the page already
   rendered), so the overlay follows the active theme. */
#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 3000;
    align-items: center;
    justify-content: center;
    background: color-mix(in oklab, var(--mud-palette-overlay-dark, #0009), transparent 25%);
    backdrop-filter: blur(3px);
}

/* Any active state reveals the modal. .NET 11 has five: show/retrying
   (client reconnect), paused (server-initiated graceful pause), failed/
   resume-failed (gave up), rejected (circuit gone). Only -hide keeps it down. */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-resume-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}

.cw-reconnect-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    max-width: 380px;
    margin: 16px;
    padding: 28px 30px 24px;
    border-radius: 14px;
    text-align: center;
    background: var(--mud-palette-surface, #fff);
    color: var(--mud-palette-text-primary, #1a1a1a);
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default, #888), transparent 45%);
    box-shadow: 0 18px 48px rgb(0 0 0 / 0.35);
}

.cw-reconnect-card h2 {
    margin: 0 0 4px;
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.cw-reconnect-card p {
    margin: 0 0 4px;
    font-size: 0.9rem;
}

.cw-reconnect-note {
    color: var(--mud-palette-text-secondary, #666);
    font-size: 0.8rem !important;
}

/* One panel per situation: show/retrying = the client retry loop is running,
   paused = a graceful server restart parked the circuit (no loop — resuming
   is the button), failed/resume-failed = gave up, rejected = circuit gone. */
.cw-reconnect-state {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .cw-reconnect-state--reconnecting,
#components-reconnect-modal.components-reconnect-retrying .cw-reconnect-state--reconnecting,
#components-reconnect-modal.components-reconnect-paused .cw-reconnect-state--paused,
#components-reconnect-modal.components-reconnect-failed .cw-reconnect-state--failed,
#components-reconnect-modal.components-reconnect-resume-failed .cw-reconnect-state--failed,
#components-reconnect-modal.components-reconnect-rejected .cw-reconnect-state--rejected {
    display: block;
}

.cw-reconnect-spinner {
    width: 34px;
    height: 34px;
    margin-bottom: 8px;
    border-radius: 50%;
    border: 3px solid color-mix(in oklab, var(--mud-palette-primary, #6b6bd6), transparent 75%);
    border-top-color: var(--mud-palette-primary, #6b6bd6);
    animation: cw-reconnect-spin 0.9s linear infinite;
}

/* Spinner keeps turning while there's still hope (show/retrying/paused);
   it stops once we've given up (failed/resume-failed/rejected). */
#components-reconnect-modal.components-reconnect-failed .cw-reconnect-spinner,
#components-reconnect-modal.components-reconnect-resume-failed .cw-reconnect-spinner,
#components-reconnect-modal.components-reconnect-rejected .cw-reconnect-spinner {
    animation-play-state: paused;
    opacity: 0.35;
}

@keyframes cw-reconnect-spin {
    to { transform: rotate(360deg); }
}

.cw-reconnect-actions {
    display: flex;
    gap: 10px;
    margin-top: 12px;
}

.cw-reconnect-btn {
    padding: 8px 18px;
    border-radius: 8px;
    border: none;
    font: inherit;
    font-size: 0.85rem;
    font-weight: 650;
    cursor: pointer;
    color: var(--mud-palette-primary-text, #fff);
    background: var(--mud-palette-primary, #6b6bd6);
}

.cw-reconnect-btn:hover {
    filter: brightness(1.08);
}

.cw-reconnect-btn--ghost {
    display: none;
    background: transparent;
    color: var(--mud-palette-primary, #6b6bd6);
    border: 1px solid color-mix(in oklab, var(--mud-palette-primary, #6b6bd6), transparent 55%);
}

/* The retry/resume button shows wherever a click is the way forward: paused
   (nothing retries until the user acts) and the two gave-up states. While the
   automatic loop is running (show/retrying) it stays hidden — a second
   in-flight attempt would just race the loop. */
#components-reconnect-modal.components-reconnect-paused .cw-reconnect-btn--ghost,
#components-reconnect-modal.components-reconnect-failed .cw-reconnect-btn--ghost,
#components-reconnect-modal.components-reconnect-resume-failed .cw-reconnect-btn--ghost {
    display: inline-block;
}

/* No loop runs while paused — a frozen "spinner" would overpromise. */
#components-reconnect-modal.components-reconnect-paused .cw-reconnect-spinner {
    animation-play-state: paused;
    opacity: 0.35;
}

@media (min-width: 900px) {
    .auth-brand {
        display: flex;
    }

    .auth-panel {
        border-left: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    }

    .auth-panel-brand {
        display: none;
    }
}

/* ── Integrations catalog & wizard ────────────────────────────────── */

.integrations-grid {
    width: 100%;
}

.integrations-grid .mud-grid-item {
    display: flex;
}

.integration-card {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 14px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    width: 100%;
    min-height: 320px;
    height: 100%;
    overflow: hidden;
    transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}

.integration-card-clickable {
    cursor: pointer;
    position: relative;
}

.integration-card-clickable:hover {
    border-color: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-lines-default) 40%);
    box-shadow: 0 18px 32px rgba(15, 23, 42, 0.16);
    transform: translateY(-1px);
}

.integration-card-clickable:focus-visible {
    outline: 2px solid color-mix(in oklab, var(--mud-palette-primary), #ffffff 70%);
    outline-offset: 4px;
}

.integration-card-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.integration-card-header {
    display: flex;
    gap: 14px;
    align-items: center;
}

.integration-logo {
    width: 52px;
    height: 52px;
    border-radius: 14px;
    object-fit: cover;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), #000 8%);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
}

.integration-card-footer {
    padding: 14px 16px 16px;
    margin-top: auto;
    border-top: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), #000 8%);
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 58px;
}

.integration-card-footer-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--mud-palette-primary);
    font-weight: 700;
    letter-spacing: 0.01em;
    text-transform: none;
}

.integration-card-clickable:hover .integration-card-footer-link {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.integration-card-footer-muted {
    color: var(--mud-palette-text-secondary);
    font-weight: 600;
    letter-spacing: 0.01em;
    text-transform: none;
}

/* Live tile state (Phase 12.2): the dot + text pair replaces the old
   hardcoded status strings. Kinds: ok / warn / off / none. Colors come
   from the Mud palette so both themes stay legible. */
.integration-status {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
}

.integration-status-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: none;
    background: var(--mud-palette-text-disabled);
}

.integration-status--ok {
    color: var(--mud-palette-success);
    font-weight: 600;
}

.integration-status--ok .integration-status-dot {
    background: var(--mud-palette-success);
}

.integration-status--warn {
    color: var(--mud-palette-warning);
    font-weight: 600;
}

.integration-status--warn .integration-status-dot {
    background: var(--mud-palette-warning);
}

.integration-status--none .integration-status-dot {
    display: none;
}

/* The feature strip on the Integrations home (12.3): sign-in, mail,
   calendars, and AI at a glance above the vendor tiles. */
.integration-feature-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 20px;
}

.integration-feature-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    flex: 1 1 200px;
    min-width: 200px;
    cursor: pointer;
    border-radius: 10px;
    transition: border-color 120ms ease;
}

.integration-feature-item:hover,
.integration-feature-item:focus-visible {
    border-color: var(--mud-palette-primary);
}

.integration-feature-icon {
    color: var(--mud-palette-text-secondary);
    flex: none;
}

.integration-feature-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.integration-provider-cell {
    display: flex;
    align-items: center;
    gap: 10px;
}

.integration-provider-logo {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    object-fit: cover;
    border: 1px solid var(--mud-palette-lines-default);
}

.integration-provider-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.integration-provider-name {
    font-weight: 600;
    line-height: 1.2;
}

.integration-provider-category {
    font-size: 0.73rem;
    color: var(--mud-palette-text-secondary);
    line-height: 1.2;
}

.level-brand-row {
    margin-bottom: 4px;
}

.level-workspace-logo {
    width: 44px;
    height: 44px;
    border-radius: 8px;
}

.level-status-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 12px;
}

.level-status-item {
    border-radius: 8px;
    min-height: 94px;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
}

.level-tabs {
    margin-top: 4px;
}

.level-metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
}

.level-metric-item {
    min-height: 76px;
    padding: 12px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    border-radius: 8px;
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 42%);
}

.level-inline-section {
    padding: 12px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 40%);
    border-radius: 8px;
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 35%);
}

.level-webhook-url-block {
    border: 1px dashed color-mix(in oklab, var(--mud-palette-lines-default), #000 12%);
    border-radius: 8px;
    background: color-mix(in oklab, var(--mud-palette-surface), #000 3%);
}

.level-client-map-card {
    border-radius: 8px;
}

.level-webhook-url {
    font-family: var(--cw-font-mono);
    overflow-wrap: anywhere;
    word-break: break-word;
}

.level-webhook-event-option {
    margin-bottom: 0;
}

.level-recommended-events {
    margin-top: 8px;
    margin-bottom: 0;
    padding-left: 18px;
}

/* ── Responsive ─────────────────────────────────────────────────── */

@media (max-width: 900px) {
    .page-title {
        font-size: 1.5rem;
    }

    .command-bar {
        flex-direction: column;
        align-items: stretch;
    }

    .command-bar-items {
        flex-wrap: wrap;
        width: 100%;
        min-width: 0;
    }

    .command-bar-items > * {
        max-width: 100%;
    }

    .vault-page .vault-filter-items,
    .vault-page .vault-action-items {
        width: 100%;
    }

    .vault-page .vault-search-control,
    .vault-page .vault-filter-control {
        max-width: none !important;
        flex: 1 1 190px;
    }

    .vault-page .vault-action-items {
        justify-content: flex-end;
        margin-left: 0;
    }

    .info-grid {
        grid-template-columns: 1fr;
    }

    .dashboard-summary {
        flex-wrap: wrap;
        gap: 12px;
    }

    /* Denser stat cards — at narrow widths the airy desktop padding
       turned five numbers into a full screen of scrolling. */
    .summary-item {
        flex: 1 1 calc(50% - 12px);
        min-width: 140px;
        padding: 14px 16px;
        gap: 4px;
    }

    .summary-value {
        font-size: 1.45rem;
    }

    .action-dashboard-grid,
    .action-dashboard-grid-billing {
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    }

    .level-status-grid {
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    }
}

@media (max-width: 700px) {
    .page-header {
        flex-direction: column;
        align-items: stretch;
    }

    .page-header-actions {
        flex-wrap: wrap;
    }

    /* Report headers commonly place two fixed-width date pickers and Apply
       in one flex row. At 390px that row was 14px wider than the viewport.
       Let header action groups wrap as a unit on phones. */
    .page-header-actions > .d-flex {
        width: 100%;
        flex-wrap: wrap;
    }

    /* MudTabs sizes the inner tab strip for every label. Constrain that strip
       to the page column so its own scroll buttons handle overflow instead of
       creating a document-wide horizontal scrollbar. */
    .page-shell > .mud-tabs,
    .page-shell > .mud-tabs .mud-tabs-tabbar-wrapper {
        min-width: 0;
        max-width: 100%;
    }

    .page-shell > .mud-tabs {
        overflow: hidden;
    }

    .vault-page .vault-action-items .mud-button-root {
        flex: 1 1 auto;
    }

    .level-status-grid {
        grid-template-columns: 1fr;
    }

    .action-dashboard-grid,
    .action-dashboard-grid-billing {
        grid-template-columns: 1fr;
    }

    .action-row-side {
        max-width: 96px;
    }
}

@media (max-width: 360px) {
    .cw-appbar .cw-timer-chip {
        padding-inline: 6px;
    }

    .cw-appbar .cw-timer-chip .mud-icon-root {
        display: none;
    }
}

/* ── Sidebar & app frame ────────────────────────────────────────────
   ALL rules that start at a MudBlazor component root (.cw-drawer,
   .cw-nav, .cw-appbar, .cw-content) MUST live here: Blazor CSS
   isolation never tags those elements, so scoped ::deep selectors
   silently match nothing (this bit us — the sidebar ran on MudBlazor
   defaults for months). See the note in MainLayout.razor.css. */

/* App bar (mobile only — hidden on desktop further below) */
.cw-appbar {
    background: var(--mud-palette-appbar-background) !important;
    border-bottom: 1px solid var(--mud-palette-lines-default) !important;
    box-shadow: none !important;
    color: var(--mud-palette-appbar-text) !important;
}

.cw-appbar .mud-toolbar {
    width: 100%;
    padding-left: 12px;
    padding-right: 12px;
}

.cw-menu-toggle,
.cw-theme-toggle {
    color: var(--mud-palette-appbar-text) !important;
}

.cw-appbar-actions {
    margin-right: 4px;
}

/* Drawer frame — the warm-iron rail (Calmwright workshop): the login
   panel's iron family carries into the app shell in BOTH themes, so
   sign-in and app read as one product. The base color comes from the
   palette (--mud-palette-drawer-background: #201A14 light / #120E09
   dark) so the rail deepens with the theme for free. A faint copper
   ember low in the rail echoes the login panel; no glow, no
   saturation creep. */
.cw-drawer {
    border-right: none !important;
    box-shadow: none !important;
    background:
        radial-gradient(420px 300px at -20% 110%, rgba(222, 139, 74, 0.12), transparent 60%),
        linear-gradient(178deg,
            var(--mud-palette-drawer-background) 0%,
            color-mix(in oklab, var(--mud-palette-drawer-background), #6B4A2E 10%) 70%,
            color-mix(in oklab, var(--mud-palette-drawer-background), #6B4A2E 16%) 100%) !important;
}

/* Faint shop-drawing grid, faded toward the bottom — same trick as the
   login brand panel (.auth-brand::after). pointer-events:none + the
   z-index on .mud-drawer-content keep it purely decorative. */
.cw-drawer::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(237, 229, 214, 0.045) 1px, transparent 1px),
        linear-gradient(90deg, rgba(237, 229, 214, 0.045) 1px, transparent 1px);
    background-size: 38px 38px;
    -webkit-mask-image: radial-gradient(130% 75% at 20% 10%, #000 20%, transparent 100%);
    mask-image: radial-gradient(130% 75% at 20% 10%, #000 20%, transparent 100%);
    pointer-events: none;
}

/* Full-height flex column so .cw-nav-bottom can push itself and the
   footer to the bottom edge. (position/z-index lift the content above
   the blueprint-grid overlay.) */
.cw-drawer .mud-drawer-content {
    display: flex;
    flex-direction: column;
    height: 100%;
    position: relative;
    z-index: 1;
}

/* Nav links */
.cw-nav {
    padding: 0 12px !important;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Short laptops (e.g. 1366×768): the daily-work nav yields and scrolls
   internally so the bottom cluster (Your day, notifications, settings,
   account) is never clipped at the viewport fold. */
.cw-drawer .mud-drawer-content > .cw-nav:not(.cw-nav-bottom) {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
}

/* Secondary nav (Settings) pinned to the bottom with the account,
   set off from the daily-work links by a hairline. */
.cw-nav-bottom {
    margin-top: auto;
    padding-top: 10px !important;
    border-top: 1px solid var(--cw-rail-hairline);
}

/* Nav on iron: chalk at graded opacities (login-panel idiom); the
   active state is a chalk-on-iron pill, not the accent — the accent
   belongs to the content, the rail belongs to the brand. */
.cw-nav .mud-nav-link {
    border-radius: 8px !important;
    padding: 9px 12px !important;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--cw-rail-text) !important;
    transition: background 0.12s ease, color 0.12s ease;
}

.cw-nav .mud-nav-link .mud-nav-link-icon {
    color: var(--cw-rail-icon) !important;
    font-size: 1.2rem !important;
    transition: color 0.12s ease;
}

.cw-nav .mud-nav-link:hover {
    background: var(--cw-rail-hover) !important;
    color: var(--cw-chalk-bright) !important;
}

.cw-nav .mud-nav-link:hover .mud-nav-link-icon {
    color: var(--cw-chalk-bright) !important;
}

.cw-nav .mud-nav-link.active {
    background: var(--cw-rail-active) !important;
    color: var(--cw-chalk-bright) !important;
    font-weight: 600;
}

.cw-nav .mud-nav-link.active .mud-nav-link-icon {
    color: var(--cw-chalk-bright) !important;
}

.cw-nav .mud-nav-link:focus-visible {
    outline: 2px solid rgba(237, 229, 214, 0.7);
    outline-offset: -2px;
}

.cw-notif-menu .mud-menu-activator {
    display: block !important;
}

/* MudBadge's default line box is 30px tall, so its 24px bell sits 3px above
   neighboring MudIconButton glyphs. Give the app-bar badge a true icon box
   and keep a wide `99+` pill inside the notification target instead of
   overlapping the theme button. */
.cw-notif-button .cw-notif-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    line-height: 1;
}

.cw-notif-button .cw-notif-badge > .mud-icon-root {
    display: block;
}

.cw-notif-button .mud-badge {
    left: auto !important;
    right: -12px !important;
}

/* Account menu popover (rendered at the popover provider root, so it
   can only be styled globally). */
.cw-user-menu-list {
    min-width: 220px;
    padding: 6px !important;
}

.cw-user-menu-list .mud-menu-item {
    border-radius: 8px;
    padding: 8px 12px;
}

.cw-user-menu-list .mud-menu-item .mud-menu-item-text {
    font-size: 0.8438rem;
    font-weight: 550;
}

.cw-user-menu-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 16px 6px;
}

/* Same identity mark as the rail trigger, popover-sized. */
.cw-user-menu-avatar {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--mud-palette-primary-lighten);
    color: var(--mud-palette-primary);
    font-size: 0.8rem;
    font-weight: 700;
}

.cw-user-menu-copy {
    min-width: 0;
}

.cw-user-menu-name {
    font-size: 0.85rem;
    font-weight: 650;
    letter-spacing: -0.01em;
    color: var(--mud-palette-text-primary);
}

.cw-user-menu-role {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

/* First-load layout stability: MudLayout only applies breakpoint
   classes after JS interop reports the viewport, which briefly renders
   content under the drawer. Apply the desktop offsets immediately. */
@media (min-width: 960px) {
    .cw-drawer.mud-drawer-mini.mud-drawer--open ~ .mud-main-content {
        margin-left: var(--mud-drawer-width-left) !important;
    }

    .cw-drawer.mud-drawer-mini.mud-drawer--closed ~ .mud-main-content {
        margin-left: var(--mud-drawer-width-mini-left, 72px) !important;
    }
}

/* ── Collapsed rail (mini variant) ─────────────────────────────── */

/* Prevent horizontal scrollbar on the collapsed drawer */
.cw-drawer.mud-drawer--closed {
    overflow: hidden !important;
}

.cw-drawer.mud-drawer--closed .mud-drawer-content {
    overflow-x: hidden !important;
}

/* Brand: centre icon, hide text */
.cw-drawer.mud-drawer--closed .cw-brand {
    padding: 14px 0 18px;
    justify-content: center;
}

.cw-drawer.mud-drawer--closed .cw-brand-name {
    display: none !important;
}

/* Nav: visually hide text labels, centre icons. Keeping the text in the
   accessibility tree gives each icon-only link its real name while the
   rail is collapsed; display:none made every focusable nav link unnamed. */
.cw-drawer.mud-drawer--closed .cw-nav {
    padding: 0 8px !important;
}

.cw-drawer.mud-drawer--closed .cw-nav .mud-nav-link {
    justify-content: center;
    padding: 10px 0 !important;
}

.cw-drawer.mud-drawer--closed .mud-nav-link-text {
    display: block !important;
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

.cw-drawer.mud-drawer--closed .mud-nav-link .mud-nav-link-icon {
    margin: 0 !important;
}

/* Footer: icon-only avatar (the MudMenu popover anchors itself, so no
   positioning hacks are needed in either state) */
.cw-drawer.mud-drawer--closed .cw-drawer-footer {
    padding: 12px 8px 14px;
    justify-content: center;
}

.cw-drawer.mud-drawer--closed .cw-account-trigger {
    display: grid;
    grid-template-columns: 34px;
    width: 48px;
    min-height: 48px;
    justify-content: center;
    padding: 7px;
    border: none;
    background: transparent;
}

.cw-drawer.mud-drawer--closed .cw-account-copy,
.cw-drawer.mud-drawer--closed .cw-account-caret {
    display: none;
}

/* ── Collapsed rail → reclaim content width ─────────────────────────
   When the nav is expanded, pages use a comfortable 1320px reading
   column. Collapsing the rail is the user opting into "more room", so
   the column widens to put the reclaimed space to work instead of
   leaving it as empty gutter. These overrides win over the scoped
   per-page 1320px caps on specificity (4 classes vs. the scoped
   single-class+attribute selectors), so no per-page CSS changes.
   Desktop only — below the Md breakpoint the drawer overlays the page
   and content is already full-width. */

/* Always on, so the width animates in step with the drawer slide in
   BOTH directions (collapse and expand). */
.cw-content-inner,
.page-shell,
.details-shell,
.service-desk,
.ticket-detail {
    transition: max-width 0.2s ease;
}

@media (min-width: 960px) {
    /* Outer gutter box must grow first, or it would clip the wider column. */
    .cw-drawer.mud-drawer--closed ~ .mud-main-content .cw-content-inner {
        max-width: 1760px;
    }

    .cw-drawer.mud-drawer--closed ~ .mud-main-content .page-shell,
    .cw-drawer.mud-drawer--closed ~ .mud-main-content .details-shell,
    .cw-drawer.mud-drawer--closed ~ .mud-main-content .service-desk,
    .cw-drawer.mud-drawer--closed ~ .mud-main-content .ticket-detail {
        max-width: 1600px;
    }
}

/* ── Desktop: full-height sidebar, no top bar ───────────────────────
   The collapse control now lives on the sidebar seam (.cw-seam-toggle),
   so the desktop app bar holds nothing useful — hide it and reclaim the
   ~48px of vertical space on every page. The app bar (with its
   hamburger) stays on mobile, where the drawer overlays the page and
   still needs a visible trigger. The drawer uses ClipMode.Never, so it
   already spans full height once the bar is gone. */
@media (min-width: 960px) {
    .cw-appbar {
        display: none !important;
    }

    /* No app bar to clear → drop MudMainContent's reserved top padding. */
    .cw-content {
        padding-top: 0 !important;
    }
}

/* ── Sidebar seam toggle (collapse / expand) ────────────────────────
   A round chevron straddling the drawer's right edge. It sits OUTSIDE
   the drawer (a fixed-positioned sibling) so the collapsed drawer's
   overflow:hidden can't clip it; its `left` tracks the drawer width and
   animates in step with the drawer (225ms, matching MudBlazor). */
.cw-seam-toggle {
    position: fixed;
    top: 21px;
    left: calc(var(--mud-drawer-width-left, 240px) - 14px);
    z-index: 1150; /* above drawer (1100); below popovers/menus/dialogs */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-secondary);
    cursor: pointer;
    box-shadow: 0 1px 4px color-mix(in oklab, #000, transparent 88%);
    transition: left 225ms cubic-bezier(0, 0, 0.2, 1),
                color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Collapsed: re-centre on the mini rail's edge. */
.cw-seam-toggle.is-collapsed {
    left: calc(var(--mud-drawer-width-mini-left, 72px) - 14px);
}

.cw-seam-toggle:hover {
    color: var(--mud-palette-primary);
    border-color: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-lines-default) 35%);
    box-shadow: 0 2px 8px color-mix(in oklab, #000, transparent 80%);
}

.cw-seam-toggle:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.cw-seam-toggle .cw-seam-icon {
    font-size: 18px;
}

/* Desktop only — on mobile the app bar's hamburger is the trigger. */
@media (max-width: 959.98px) {
    .cw-seam-toggle {
        display: none;
    }

    /* MudAppBar tracks a Mini drawer's desktop offset in its generated
       classes. When a collapsed 72px rail crosses into the mobile layout,
       that stale offset otherwise leaves the whole bar — including the
       hamburger — shifted 72px to the right. Mobile chrome always spans the
       viewport because the drawer is an overlay here. */
    .cw-appbar {
        left: 0 !important;
        right: 0 !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
        width: 100% !important;
    }

    /* Mini drawers remain docked below their breakpoint: "closed" still
       reserves the 72px rail and "open" pushes the page by 240px. On phones
       that leaves too little room for the application and makes the expanded
       menu create horizontal page scroll. Treat the same drawer as a mobile
       sheet instead: keep the app bar available as its close control, overlay
       the content while open, and move it completely off-canvas when closed. */
    .cw-drawer {
        top: var(--mud-appbar-min-height, 48px) !important;
        height: calc(100dvh - var(--mud-appbar-min-height, 48px)) !important;
        /* The MudBlazor width variable can still be the 72px mini-rail value
           for one breakpoint cycle after resizing from desktop. This sheet is
           always the drawer's explicit expanded Width from MainLayout. */
        width: 240px !important;
    }

    /* Short phones cannot fit the full primary nav plus account footer.
       Keep that overflow inside the fixed drawer so every destination and
       the user menu remain reachable without moving the page beneath it. */
    .cw-drawer .mud-drawer-content {
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    .cw-drawer.mud-drawer--closed {
        transform: translateX(-100%) !important;
        visibility: hidden;
    }

    .cw-drawer.mud-drawer--open {
        transform: translateX(0) !important;
        visibility: visible;
    }

    .cw-drawer ~ .mud-main-content {
        margin-left: 0 !important;
    }

    /* MudBlazor also keys the mini-drawer offset from a class on the layout
       root. When a desktop-open drawer crosses the breakpoint, that selector
       can survive the resize even though the drawer has become an overlay.
       Reset the logical and physical offset on the content itself. */
    body .mud-layout .mud-main-content.cw-content {
        margin-left: 0 !important;
        margin-inline-start: 0 !important;
        width: 100% !important;
    }
}

/* ── Sidebar nav badges ─────────────────────────────────────────────
   Counts (open tickets, draft invoices) are supplied by MainLayout via
   a data-count attribute. One attribute drives both states: a trailing
   pill when expanded, a small chip on the icon when collapsed.
   (Collapsed page-name tooltips use the native title attribute — a CSS
   tooltip would be clipped by the collapsed drawer's overflow:hidden.) */
.cw-nav .mud-nav-link {
    position: relative;
}

/* Expanded: count pill pushed to the trailing edge of the link.
   Chalk-on-iron chip — the rail never wears the accent. */
.cw-nav .mud-nav-link[data-count]:not([data-count=""])::after {
    content: attr(data-count);
    margin-left: auto;
    min-width: 20px;
    height: 18px;
    padding: 0 6px;
    border-radius: 9px;
    background: rgba(237, 229, 214, 0.18);
    color: var(--cw-chalk-bright);
    font-size: 0.6875rem;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    box-sizing: border-box;
}

/* ── Sidebar notification bell ──────────────────────────────────────
   Two homes, one component: below 960px the app bar is visible and owns
   the bell (top-right, the conventional spot), so the sidebar copy is
   hidden to avoid showing two bells. At >=960px the app bar is gone and
   this is the only always-visible bell. */
@media (max-width: 959.98px) {
    .cw-nav .cw-notif-menu {
        display: none !important;
    }
}

/* The bell activator is a <button>, not MudNavLink's <a>. Make the menu
   wrapper and button stretch full-width so the unread pill (drawn by the
   [data-count] ::after rule above) trails to the edge like other nav
   counts instead of butting against the word. */
.cw-nav .cw-notif-menu {
    display: block;
    width: 100%;
}

.cw-notif-nav {
    width: 100%;
    display: flex;
    align-items: center;
    background: none;
    border: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
    box-sizing: border-box;
}

/* Collapsed: same count, shrunk to a chip on the icon's top-right, with
   a ring in the drawer colour so it reads as separate from the icon. */
.cw-drawer.mud-drawer--closed .cw-nav .mud-nav-link[data-count]:not([data-count=""])::after {
    position: absolute;
    top: 3px;
    right: 9px;
    margin: 0;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    font-size: 0.625rem;
    line-height: 16px;
    box-shadow: 0 0 0 2px var(--mud-palette-drawer-background);
}

/* ── Billing Page ────────────────────────────────────────────────── */
/* Global styles for the billing page layout components.              */

/* Blocker banner */
.billing-page .billing-blockers {
    border-color: color-mix(in oklab, var(--mud-palette-warning), var(--mud-palette-lines-default) 30%) !important;
    background: color-mix(in oklab, var(--mud-palette-warning), var(--mud-palette-surface) 92%);
    padding: 20px 24px;
}

.billing-page .billing-blockers-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
}

.billing-page .billing-blockers-title {
    font-weight: 600 !important;
}

.billing-page .billing-blockers-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 12px;
}

.billing-page .billing-blocker-card {
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    padding: 16px 18px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.billing-page .billing-blocker-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
}

.billing-page .billing-blocker-card-title {
    font-weight: 600;
    font-size: 0.875rem;
    flex: 1;
}

/* Client links inside a blocker card read as separate wrapping pills —
   inline links with margins ran client names together. */
.billing-page .billing-blocker-clients {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.billing-page .billing-blocker-client {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    padding: 3px 10px;
    border-radius: 999px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-lines-default) 55%);
    background: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-surface) 93%);
    color: var(--mud-palette-primary);
    font-size: 0.78rem;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.billing-page .billing-blocker-client:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-surface) 86%);
    text-decoration: none;
}

.billing-page .billing-blocker-confirm {
    min-width: 28px;
    min-height: 28px;
}

.billing-page .billing-blocker-more {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    font-size: 0.78rem;
    color: var(--mud-palette-text-secondary);
    font-weight: 600;
}

/* Cycle card — "this month's billing" state + prompts */
.billing-page .billing-cycle {
    border-color: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-lines-default) 55%) !important;
}

.billing-page .billing-cycle-main {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.billing-page .billing-cycle-prompt {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 12px;
    padding: 10px 14px;
    border: 1px dashed var(--mud-palette-lines-default);
    border-radius: 10px;
    font-size: 0.875rem;
}

.billing-page .billing-cycle-prompt > span:first-of-type {
    flex: 1;
    min-width: 220px;
}

/* Money row additions */
.billing-page .billing-money-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

.billing-page .billing-money-grid .summary-item {
    min-width: 0;
    min-height: 124px;
}

.billing-page .summary-sub {
    display: block;
    font-size: 0.8rem;
    font-weight: 500;
    line-height: 1.45;
    color: var(--mud-palette-text-secondary);
}

/* True status only (e.g. "$36,453 overdue") — a muted warning note under a
   neutral-ink number, per the color budget. */
.billing-page .summary-sub-warn {
    color: var(--cw-status-warning);
}

.billing-page .summary-clickable {
    cursor: pointer;
}

.billing-page .summary-clickable:hover {
    border-color: var(--mud-palette-primary) !important;
}

.billing-page .summary-clickable:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* Section wrappers */
.billing-page .billing-section {
    padding: 24px;
}

.billing-page .billing-section-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 4px;
}

/* Accounting export dialog (Phase 8.3) */
.accounting-export-guidance-list {
    margin: 6px 0 0;
    padding-left: 18px;
    font-size: 0.82rem;
    line-height: 1.45;
}

.accounting-export-guidance-list li + li {
    margin-top: 4px;
}

/* Period quick-select bar */
.billing-page .billing-period-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 12px;
}

.billing-page .billing-period-nav {
    display: flex;
    align-items: center;
    gap: 2px;
}

.billing-page .billing-period-label {
    font-weight: 600;
    font-size: 0.9375rem;
    min-width: 180px;
    text-align: center;
    white-space: nowrap;
}

.billing-page .billing-period-presets {
    display: flex;
    gap: 4px;
}

@media (max-width: 700px) {
    .billing-page .billing-money-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Single column: the desktop equal-height floor just leaves ~40px of
       dead space under each card's caption. */
    .billing-page .billing-money-grid .summary-item {
        min-height: 0;
    }

    .billing-page .billing-blockers-grid {
        grid-template-columns: 1fr;
    }

    .billing-page .billing-section {
        padding: 16px;
    }
}

@media (min-width: 700.02px) and (max-width: 1100px) {
    .billing-page .billing-money-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* ── Billing Page – MudBlazor overrides ─────────────────────────── */
/* Global overrides scoped by .billing-page wrapper class.           */

.billing-page .billing-section .mud-table {
    background: transparent !important;
    box-shadow: none !important;
}

.billing-page .billing-section .mud-table-container {
    border-radius: 10px !important;
    border: 1px solid var(--mud-palette-lines-default) !important;
    overflow: hidden !important;
}

/* Service Desk table head: small uppercase labels on a soft tinted surface
   with a hairline divider. Values are kept identical to .data-grid-container
   so billing tables read the same as the Clients/Users grids. */
.billing-page .billing-section .mud-table-head .mud-table-cell {
    font-size: 0.6875rem !important;
    font-weight: 700 !important;
    letter-spacing: 0.06em !important;
    text-transform: uppercase !important;
    color: var(--mud-palette-text-secondary) !important;
    padding: 10px 16px !important;
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 35%) !important;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%) !important;
}

.billing-page .billing-section .mud-table-body .mud-table-cell {
    padding: 10px 16px !important;
    font-size: 0.875rem !important;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.billing-page .billing-section .mud-table-hover .mud-table-body .mud-table-row:hover {
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 18%);
}

/* The cell-level cw-mono/cw-num-cell restatement lives with the machine-value
   utilities near the top of this file (app-wide, not billing-only). Only the
   right-alignment nudge for Mud's cell rule stays here. */
.billing-page .mud-table .mud-table-cell.cw-num-cell {
    text-align: right;
}

/* Invoice numbers never break mid-ID; the ID column keeps room for them
   while low-value columns (Delivery/Due dashes) give up space first. */
.billing-page .billing-invoice-id {
    white-space: nowrap;
}

.billing-page th.billing-invoice-id-col {
    min-width: 9rem;
}

/* Row actions: each verb stays whole; the cluster wraps as units with the
   stack's own gap, and the column reserves a stable width so row heights
   stay even instead of re-wrapping per row. */
.billing-page .billing-invoice-actions {
    min-width: 232px;
    justify-content: flex-end;
}

.billing-page .billing-invoice-actions .mud-button-root {
    white-space: nowrap;
}

/* Nested draft line-item breakdown (a MudSimpleTable inside an expanded preview
   row) — plain <thead>/<tbody>, so it needs the head treatment by element. */
.billing-page .billing-section .mud-simple-table thead th {
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
    text-align: left;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.billing-page .billing-section .mud-simple-table tbody td {
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.billing-page .billing-section .mud-table-foot .mud-table-cell {
    border-top: 1px solid var(--mud-palette-lines-default) !important;
    padding: 6px 16px !important;
}

/* MudBlazor 9's pager wrapper (.mud-table-pagination) is display:inline with
   a block toolbar child — border/padding on the wrapper paint as orphan
   ~20px hairline stubs at the card's left edge (the leftover inline
   fragments). Keep the wrapper inert; the toolbar carries the divider. */
.billing-page .billing-section .mud-table-pagination {
    border-top: 0 !important;
    padding: 0 !important;
}

.billing-page .billing-section .mud-table-pagination-toolbar {
    border-top: 1px solid var(--mud-palette-lines-default) !important;
}

/* Tabs (Pending / Paid / Voided) */
.billing-page .billing-section .mud-tabs .mud-tabs-tabbar {
    background: transparent !important;
    border-bottom: 1px solid var(--mud-palette-lines-default) !important;
    min-height: 40px !important;
}

.billing-page .billing-section .mud-tabs .mud-tab {
    min-height: 40px !important;
    font-size: 0.8125rem !important;
    font-weight: 600 !important;
    text-transform: none !important;
    letter-spacing: 0 !important;
    padding: 0 16px !important;
}

.billing-page .billing-section .mud-tabs-panels {
    padding: 0 !important;
}

/* Expansion panel (Billing Run History) */
.billing-page .mud-expand-panel {
    border: 1px solid var(--mud-palette-lines-default) !important;
    border-radius: 10px !important;
    overflow: hidden !important;
    box-shadow: none !important;
}

.billing-page .mud-expand-panel::before {
    display: none !important;
}

.billing-page .mud-expand-panel .mud-expand-panel-header {
    padding: 12px 20px !important;
    min-height: 48px !important;
    font-weight: 600 !important;
}

.billing-page .mud-expand-panel .mud-expand-panel-content {
    padding: 0 20px 16px !important;
}

.billing-page .mud-expand-panel .mud-table-container {
    border-radius: 10px !important;
    border: 1px solid var(--mud-palette-lines-default) !important;
    overflow: hidden !important;
}

.billing-page .mud-expand-panel .mud-table-head .mud-table-cell {
    font-size: 0.6875rem !important;
    font-weight: 700 !important;
    letter-spacing: 0.06em !important;
    text-transform: uppercase !important;
    color: var(--mud-palette-text-secondary) !important;
    padding: 10px 16px !important;
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 35%) !important;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%) !important;
}

.billing-page .mud-expand-panel .mud-table-body .mud-table-cell {
    padding: 10px 16px !important;
    font-size: 0.875rem !important;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.billing-page .mud-expand-panel .mud-table-hover .mud-table-body .mud-table-row:hover {
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 18%);
}

/* Alert & Paper rounding */
.billing-page .billing-section .mud-alert {
    border-radius: 10px !important;
}

.billing-page .billing-blockers {
    border-radius: 10px !important;
}

/* Period chip cursor */
.billing-page .billing-period-presets .mud-chip {
    cursor: pointer !important;
}

@media (max-width: 700px) {

    .billing-page .billing-section .mud-table-body .mud-table-cell,
    .billing-page .billing-section .mud-table-head .mud-table-cell {
        padding: 8px 10px;
    }

    .billing-page .billing-invoice-actions {
        justify-content: flex-end;
        min-width: 0;
    }
}

/* == Client portal (Phase 8.5) ==================================== */
/* Portal page-level styles live globally (layout-scoped CSS cannot
   reach page markup). All classes are portal- prefixed. */

.portal-card-wrap {
    max-width: 420px;
    margin: max(6vh, 16px) auto 0;
}

.portal-card {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 16px;
    background: var(--mud-palette-surface);
}

.portal-auth-card {
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px -12px rgba(0, 0, 0, 0.12);
}

.portal-auth-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 16px;
    background: color-mix(in srgb, var(--mud-palette-primary) 12%, transparent);
    color: var(--mud-palette-primary);
}

.portal-auth-icon-ok {
    background: color-mix(in srgb, var(--mud-palette-success) 12%, transparent);
    color: var(--mud-palette-success);
}

.portal-continue {
    display: block;
    width: 100%;
    padding: 12px 16px;
    border: none;
    border-radius: 10px;
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font: inherit;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    transition: filter 0.15s ease;
}

.portal-continue:hover {
    filter: brightness(1.05);
}

.portal-continue:focus-visible {
    outline: 3px solid color-mix(in srgb, var(--mud-palette-primary) 40%, transparent);
    outline-offset: 2px;
}

/* Portal ticket list rows */
.portal-ticket-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 14px;
    background: var(--mud-palette-surface);
    text-decoration: none;
    color: var(--mud-palette-text-primary);
    min-width: 0;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.portal-ticket-row:hover {
    background: var(--mud-palette-action-default-hover);
    border-color: color-mix(in srgb, var(--mud-palette-primary) 35%, var(--mud-palette-lines-default));
}

.portal-ticket-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
}

.portal-ticket-title {
    font-weight: 600;
    overflow-wrap: anywhere;
}

.portal-ticket-snippet {
    color: var(--mud-palette-text-secondary);
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 60ch;
}

.portal-ticket-time {
    color: var(--mud-palette-text-disabled);
    font-size: 0.78rem;
}

.portal-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 40px 16px;
    text-align: center;
    color: var(--mud-palette-text-secondary);
}

.portal-empty .mud-icon-root {
    font-size: 2.6rem;
    opacity: 0.4;
}

.portal-narrow {
    max-width: 640px;
}

.portal-tabs .mud-tabs-panels {
    padding-top: 16px;
}

/* Profile page (12.9): plain definition rows, mobile-first. */
.portal-profile-rows {
    display: flex;
    flex-direction: column;
}

.portal-profile-row {
    display: grid;
    grid-template-columns: 130px 1fr;
    gap: 12px;
    padding: 9px 0;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    font-size: 0.9rem;
}

.portal-profile-row:last-child {
    border-bottom: none;
}

.portal-profile-key {
    color: var(--mud-palette-text-secondary);
}

.portal-profile-val {
    min-width: 0;
    overflow-wrap: anywhere;
}

@media (max-width: 480px) {
    /* Phone browsers normally use transient overlay scroll indicators. The
       desktop Chromium track used by responsive testing instead reserves a
       permanent gutter, making the fixed app bar look short and misaligned.
       Hide only the root track; scrolling still works by touch, wheel, keys,
       and assistive technology, while nested drawers/menus keep their own
       visible scrollbars. */
    html {
        scrollbar-width: none !important;
    }

    body {
        scrollbar-width: none !important;
    }

    html::-webkit-scrollbar,
    body::-webkit-scrollbar {
        width: 0;
        height: 0;
        display: none;
    }

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

    .dashboard-summary .summary-item {
        min-width: 0;
    }

    .portal-profile-row {
        grid-template-columns: 1fr;
        gap: 2px;
    }
}

.portal-cap-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 6px 0;
}

.portal-cap-row .mud-icon-root {
    margin-top: 2px;
}

.portal-contact-link {
    color: var(--mud-palette-primary);
    text-decoration: none;
}

.portal-contact-link:hover {
    text-decoration: underline;
}

/* Receipt strip (12.9): the request detail acknowledges "we've got it" until
   a human replies — quiet info tint, one line, never a dialog. */
.portal-ack-strip {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    margin: 8px 0 0;
    border: 1px solid color-mix(in srgb, var(--mud-palette-info) 35%, transparent);
    background: color-mix(in srgb, var(--mud-palette-info) 8%, var(--mud-palette-surface));
    border-radius: 10px;
    color: var(--mud-palette-text-secondary);
    font-size: 0.85rem;
}

.portal-ack-strip .mud-icon-root {
    color: var(--mud-palette-info);
    flex-shrink: 0;
}

/* Portal conversation — a chat screen, not a document. The :has() hooks
   turn the layout into a fixed-height column so the thread owns the
   scrolling and the composer never leaves the viewport. Browsers without
   :has() degrade to a normal scrolling page. */
.portal-shell:has(.portal-chat-page) {
    height: 100dvh;
}

.portal-shell:has(.portal-chat-page) .portal-foot {
    display: none;
}

.portal-main:has(.portal-chat-page) {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding-bottom: 12px;
}

.portal-chat-page {
    position: relative;
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.portal-chat-topbar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.portal-chat-heading {
    flex: 1;
    min-width: 0;
}

.portal-chat-title {
    font-weight: 600;
    font-size: 1.05rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.portal-chat-sub {
    font-size: 0.78rem;
    color: var(--mud-palette-text-secondary);
}

.portal-chat-status {
    flex-shrink: 0;
}

.portal-chat-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    scrollbar-gutter: stable;
    padding: 14px 2px 10px;
}

.portal-chat-thread {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-height: 100%;
}

/* A sparse conversation sits at the bottom, where the composer is — the
   chat idiom — instead of floating at the top of an empty column. */
.portal-chat-thread > :first-child {
    margin-top: auto;
}

.portal-chat-sys {
    text-align: center;
    color: var(--mud-palette-text-disabled);
    font-size: 0.75rem;
}

.portal-chat-day {
    display: flex;
    align-items: center;
    gap: 12px;
    color: var(--mud-palette-text-disabled);
    font-size: 0.75rem;
}

.portal-chat-day::before,
.portal-chat-day::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--mud-palette-lines-default);
}

/* One message row: optional avatar + (meta line above bubble) */
.portal-msg {
    display: flex;
    gap: 8px;
    max-width: min(85%, 620px);
}

.portal-msg-client {
    align-self: flex-end;
}

.portal-msg-msp {
    align-self: flex-start;
}

.portal-msg-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    flex-shrink: 0;
    align-self: flex-end;
    margin-bottom: 2px;
    background: color-mix(in srgb, var(--mud-palette-text-secondary) 14%, var(--mud-palette-surface));
    color: var(--mud-palette-text-secondary);
    font-size: 0.72rem;
    font-weight: 700;
}

.portal-msg-avatar-msp {
    background: color-mix(in srgb, var(--mud-palette-primary) 16%, var(--mud-palette-surface));
    color: var(--mud-palette-primary);
}

.portal-msg-main {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.portal-msg-client .portal-msg-main {
    align-items: flex-end;
}

.portal-msg-meta {
    display: flex;
    gap: 6px;
    padding: 0 4px;
    font-size: 0.72rem;
    color: var(--mud-palette-text-secondary);
}

.portal-msg-author {
    font-weight: 600;
}

.portal-bubble {
    width: fit-content;
    max-width: 100%;
    padding: 10px 14px;
    border-radius: 16px;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
    overflow-wrap: anywhere;
}

.portal-bubble-client {
    border-bottom-right-radius: 6px;
    background: color-mix(in srgb, var(--mud-palette-primary) 11%, var(--mud-palette-surface));
    border-color: color-mix(in srgb, var(--mud-palette-primary) 24%, transparent);
}

.portal-bubble-msp {
    border-bottom-left-radius: 6px;
}

.portal-bubble-body {
    white-space: pre-wrap;
}

.portal-bubble-images {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.portal-bubble-body + .portal-bubble-images,
.portal-bubble-body + .portal-bubble-files,
.portal-bubble-images + .portal-bubble-files {
    margin-top: 8px;
}

/* Fixed thumbnail boxes: no layout shift when the image loads, so the
   auto-scroll landing spot stays put. */
.portal-msg-image {
    display: block;
    width: 200px;
    height: 150px;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-background);
}

.portal-msg-image img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.portal-bubble-files {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.portal-file-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 999px;
    text-decoration: none;
    color: var(--mud-palette-text-primary);
    font-size: 0.85rem;
    background: var(--mud-palette-background);
}

.portal-file-chip:hover {
    background: var(--mud-palette-action-default-hover);
}

/* Composer — pinned below the thread */
.portal-chat-composer {
    flex-shrink: 0;
    padding-top: 10px;
    border-top: 1px solid var(--mud-palette-lines-default);
    padding-bottom: env(safe-area-inset-bottom, 0);
}

.portal-composer-box {
    display: flex;
    align-items: flex-end;
    gap: 4px;
    padding: 6px 8px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 18px;
    background: var(--mud-palette-surface);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.portal-composer-box:focus-within {
    border-color: var(--mud-palette-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--mud-palette-primary) 15%, transparent);
}

.portal-composer-box .mud-input-control {
    margin: 0;
}

.portal-composer-input {
    flex: 1;
    min-width: 0;
}

.portal-composer-input .mud-input {
    padding: 4px 4px 5px;
    font-size: 0.95rem;
}

.portal-composer-upload {
    align-self: flex-end;
    margin-bottom: 2px;
}

.portal-composer-send {
    align-self: flex-end;
}

.portal-composer-hint {
    padding: 6px 6px 0;
    font-size: 0.72rem;
    color: var(--mud-palette-text-disabled);
    min-height: 1.4em;
}

@media (hover: none) {
    /* Keyboard-shortcut and paste hints mean nothing on touch. */
    .portal-composer-hint {
        display: none;
    }
}

.portal-chat-closed {
    padding: 12px 4px 4px;
}

/* Drag-and-drop hint: overlay scoped to the chat page, click-through so the
   drop still lands on the page's own listeners. */
.portal-chat-page.portal-dropping::after {
    content: "Drop files to attach";
    position: absolute;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--mud-palette-primary);
    border: 2px dashed color-mix(in srgb, var(--mud-palette-primary) 55%, transparent);
    border-radius: 16px;
    background: color-mix(in srgb, var(--mud-palette-surface) 82%, transparent);
    backdrop-filter: blur(2px);
}

/* Portal invoices */
.portal-invoice-total {
    font-weight: 600;
    white-space: nowrap;
}

.portal-pay-card {
    border-color: color-mix(in srgb, var(--mud-palette-primary) 35%, transparent);
}

.portal-invoice-table-wrap {
    overflow-x: auto;
}

.portal-invoice-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.portal-invoice-table th,
.portal-invoice-table td {
    padding: 8px 10px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    text-align: left;
    overflow-wrap: anywhere;
}

.portal-invoice-table th {
    color: var(--mud-palette-text-secondary);
    font-weight: 500;
    font-size: 0.8rem;
}

.portal-invoice-table .portal-num {
    text-align: right;
    white-space: nowrap;
}

.portal-invoice-table tfoot td {
    border-bottom: none;
    padding-top: 6px;
    padding-bottom: 2px;
}

.portal-invoice-total-row td {
    font-weight: 700;
    font-size: 1rem;
    padding-top: 8px;
}

/* Settings → Email: outgoing-mail routing rows */
.routing-row {
    padding: 10px 0;
    border-top: 1px solid var(--mud-palette-lines-default);
}
.routing-row:first-of-type {
    border-top: none;
    padding-top: 0;
}

/* ===== Calendar workspace (Phase 11.2, docs/SCHEDULING-ARCHITECTURE.md D10) ===== */

.calendar-page .cal-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.calendar-page .cal-period-nav {
    display: flex;
    align-items: center;
    gap: 2px;
}

.calendar-page .cal-period-label {
    min-width: 172px;
    text-align: center;
    font-weight: 600;
    font-size: 0.95rem;
}

.calendar-page .cal-toolbar-spacer {
    flex: 1 1 auto;
}

.calendar-page .cal-tech-search {
    flex: 0 1 220px;
    min-width: 180px;
}

.calendar-page .cal-legend {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 1 1 360px;
    flex-wrap: nowrap;
    min-width: 220px;
    max-width: 100%;
    overflow-x: auto;
    padding-bottom: 2px;
    scrollbar-width: thin;
}

.calendar-page .cal-legend .mud-chip {
    flex: 0 0 auto;
}

.calendar-page .cal-canceled-switch {
    margin-left: 4px;
}

.cal-dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    margin-right: 6px;
    background: var(--cal-accent, var(--mud-palette-text-secondary));
    flex: 0 0 auto;
}

/* Per-tech accent hues — fills derive via color-mix so dark mode is free. */
.cal-accent-c0, .cal-dot-c0 { --cal-accent: #4f7cff; }
.cal-accent-c1, .cal-dot-c1 { --cal-accent: #2eaa6e; }
.cal-accent-c2, .cal-dot-c2 { --cal-accent: #d98324; }
.cal-accent-c3, .cal-dot-c3 { --cal-accent: #9d5cf0; }
.cal-accent-c4, .cal-dot-c4 { --cal-accent: #14a3b8; }
.cal-accent-c5, .cal-dot-c5 { --cal-accent: #e2547a; }
.cal-accent-c6, .cal-dot-c6 { --cal-accent: #7a8c2e; }
.cal-accent-c7, .cal-dot-c7 { --cal-accent: #b8562e; }
.cal-accent-c8, .cal-dot-c8 { --cal-accent: #8a93a6; }

.calendar-page .cal-head {
    display: grid;
    grid-template-columns: 56px repeat(var(--cal-days), minmax(var(--cal-lane-min), 1fr));
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.calendar-page .cal-head-day {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 6px;
    padding: 6px 0;
    font-size: 0.85rem;
    color: var(--mud-palette-text-secondary);
}

.calendar-page .cal-head-day .cal-head-num {
    font-weight: 600;
    color: var(--mud-palette-text-primary);
}

.calendar-page .cal-head-day.cal-today-head .cal-head-num {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-contrast-text);
    border-radius: 50%;
    min-width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.calendar-page .cal-allday-row {
    display: grid;
    grid-template-columns: 56px repeat(var(--cal-days), minmax(var(--cal-lane-min), 1fr));
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    min-height: 32px;
}

.calendar-page .cal-allday-gutter {
    font-size: 10px;
    color: var(--mud-palette-text-secondary);
    text-align: right;
    padding: 6px 6px 0 0;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.calendar-page .cal-allday-cell {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 3px;
    border-left: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
    cursor: pointer;
}

.calendar-page .cal-allday-cell:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 94%);
}

.calendar-page .cal-allday-zone {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-height: 22px;
}

.calendar-page .cal-scroll {
    max-height: 66vh;
    overflow-y: auto;
    overflow-x: hidden;
}

.calendar-page .cal-grid {
    display: grid;
    grid-template-columns: 56px repeat(var(--cal-days), minmax(var(--cal-lane-min), 1fr));
}

.calendar-page .cal-hour-label {
    height: var(--cal-hour);
    font-size: 11px;
    color: var(--mud-palette-text-secondary);
    text-align: right;
    padding-right: 6px;
    transform: translateY(-7px);
}

.calendar-page .cal-daycol {
    position: relative;
    border-left: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.calendar-page .cal-daycol.cal-today {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 97%);
}

.calendar-page .cal-slot-wrap {
    height: var(--cal-slot);
    box-sizing: border-box;
    cursor: pointer;
}

.calendar-page .cal-slot-wrap.cal-slot-hour {
    border-top: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.calendar-page .cal-slot-wrap:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 92%);
}

.calendar-page .cal-slot-zone {
    position: relative;
    height: 100%;
    width: 100%;
    min-height: 0;
    padding: 0;
    overflow: visible;
}

/* Idle zones are click-transparent: empty-slot clicks reach the wrapper's
   create handler, and entry blocks spilling over later slots stay clickable.
   cal-board.js stamps .cal-dragging on the page while a drag is live so
   zones can receive dragenter; MudBlazor then marks only the hovered zone
   with cal-zone-can-drop (ApplyDropClassesOnDragStarted is off). Items
   inside a zone re-enable themselves — except during a drag, when they'd
   otherwise sit on top of the slot under the cursor and swallow the drop. */
.calendar-page .cal-slot-zone,
.calendar-page .cal-allday-zone {
    pointer-events: none;
}

.calendar-page.cal-dragging .cal-slot-zone,
.calendar-page.cal-dragging .cal-allday-zone {
    pointer-events: auto;
}

.calendar-page .cal-slot-zone > *,
.calendar-page .cal-allday-zone > * {
    pointer-events: auto;
}

.calendar-page.cal-dragging .cal-slot-zone > *,
.calendar-page.cal-dragging .cal-allday-zone > * {
    pointer-events: none;
}

/* The one zone the drag is over: a clear target with its time as a chip. */
.calendar-page .cal-zone-can-drop {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 78%);
    box-shadow: inset 0 0 0 2px var(--mud-palette-primary);
    border-radius: 3px;
}

.calendar-page .cal-zone-can-drop::after {
    content: attr(data-time);
    position: absolute;
    top: -1px;
    left: 3px;
    z-index: 5;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--mud-palette-primary);
    background: var(--mud-palette-surface);
    border: 1px solid color-mix(in oklab, var(--mud-palette-primary), transparent 55%);
    border-radius: 3px;
    padding: 0 5px;
    pointer-events: none;
    white-space: nowrap;
}

.cal-block {
    position: absolute;
    display: flex;
    align-items: flex-start;
    gap: 4px;
    box-sizing: border-box;
    border-radius: 6px;
    padding: 2px 6px;
    font-size: 12px;
    line-height: 1.25;
    overflow: hidden;
    z-index: 3;
    cursor: pointer;
    border-left: 3px solid var(--cal-accent);
    background: color-mix(in oklab, var(--cal-accent), var(--mud-palette-surface) 82%);
    color: var(--mud-palette-text-primary);
}

.cal-block:hover {
    background: color-mix(in oklab, var(--cal-accent), var(--mud-palette-surface) 72%);
}

.cal-block-action {
    display: flex;
    align-items: flex-start;
    gap: 4px;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    line-height: inherit;
    text-align: left;
    cursor: pointer;
}

.cal-block .cal-block-time {
    font-weight: 600;
    font-size: 11px;
    white-space: nowrap;
}

.cal-block .cal-block-title {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

.cal-block .cal-block-icon {
    width: 13px;
    height: 13px;
    flex: 0 0 auto;
    opacity: 0.75;
}

.cal-block .cal-block-ticket {
    display: inline-flex;
    color: inherit;
}

.cal-block.cal-block-allday {
    position: static;
    border-radius: 5px;
    padding: 1px 6px;
}

.cal-block.cal-kind-maintenance {
    background-image: repeating-linear-gradient(135deg, transparent 0 6px,
        color-mix(in oklab, var(--cal-accent), transparent 88%) 6px 12px);
}

.cal-block.cal-kind-timeoff,
.cal-block.cal-kind-internal {
    border-left-style: dashed;
    background: color-mix(in oklab, var(--cal-accent), var(--mud-palette-surface) 90%);
}

.cal-block.cal-status-completed {
    opacity: 0.55;
}

.cal-block.cal-status-canceled {
    opacity: 0.5;
    border-left-style: dotted;
}

.cal-block.cal-status-canceled .cal-block-title {
    text-decoration: line-through;
}

.cal-now {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--mud-palette-error);
    z-index: 4;
    pointer-events: none;
}

.cal-now::before {
    content: "";
    position: absolute;
    left: -4px;
    top: -3px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--mud-palette-error);
}

/* Agenda list — the below-900px rendering (also how 784px stays scroll-free). */
.calendar-page .cal-agenda {
    display: none;
}

.calendar-page .cal-agenda-day {
    padding: 10px 2px;
    border-top: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.calendar-page .cal-agenda-day:first-of-type {
    border-top: none;
}

.calendar-page .cal-agenda-head {
    font-weight: 600;
    font-size: 0.85rem;
    margin-bottom: 6px;
}

.calendar-page .cal-agenda-today .cal-agenda-head {
    color: var(--mud-palette-primary);
}

.calendar-page .cal-agenda-empty {
    font-size: 0.8rem;
    color: var(--mud-palette-text-secondary);
    padding: 2px 0 4px;
}

.calendar-page .cal-agenda-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 4px;
    min-height: 44px;
    width: 100%;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    min-width: 0;
}

.calendar-page .cal-agenda-row:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 94%);
}

.calendar-page .cal-agenda-row:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.calendar-page .cal-agenda-row.cal-agenda-muted {
    opacity: 0.55;
}

.calendar-page .cal-agenda-time {
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    min-width: 84px;
}

.calendar-page .cal-agenda-title {
    font-size: 13px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

.calendar-page .cal-agenda-sub {
    font-size: 12px;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
    margin-left: auto;
}

/* Capacity strip (D9): information, not judgment — amber, never red alarms. */
.calendar-page .cal-capacity-row {
    display: grid;
    grid-template-columns: 56px repeat(var(--cal-days), minmax(var(--cal-lane-min), 1fr));
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.calendar-page .cal-capacity-gutter {
    font-size: 10px;
    color: var(--mud-palette-text-secondary);
    text-align: right;
    padding: 3px 6px 3px 0;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.calendar-page .cal-capacity-cell {
    font-size: 11px;
    text-align: center;
    padding: 3px 0;
    color: var(--mud-palette-text-secondary);
    border-left: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 55%);
}

.calendar-page .cal-capacity-cell.cal-capacity-over {
    color: color-mix(in oklab, var(--mud-palette-warning), black 22%);
    background: color-mix(in oklab, var(--mud-palette-warning), transparent 88%);
    font-weight: 600;
}

/* Non-working shading + external busy — render context, never blockers. */
.calendar-page .cal-nonwork {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 1;
    pointer-events: none;
    background: color-mix(in oklab, var(--mud-palette-text-secondary), transparent 92%);
}

.cal-block.cal-busy {
    --cal-accent: #8a93a6;
    z-index: 2;
    pointer-events: none;
    background: repeating-linear-gradient(135deg,
        color-mix(in oklab, var(--cal-accent), transparent 90%) 0 5px,
        color-mix(in oklab, var(--cal-accent), transparent 80%) 5px 10px);
    color: var(--mud-palette-text-secondary);
    border-left-style: solid;
}

/* 12.7 Books front door — the "what needs you" rows on the overview. */
.books-action-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 10px;
    min-height: 44px;
    width: 100%;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.books-action-row:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 94%);
}

.books-action-row:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.books-action-row .books-action-main {
    flex: 1 1 auto;
    min-width: 0;
}

.books-action-row > .mud-icon-root:first-child {
    margin-top: 2px;
}

/* 12.6 dispatch board — grid + unscheduled rail side by side. */
.calendar-page .cal-body {
    display: flex;
    gap: 14px;
    align-items: flex-start;
}

.calendar-page .cal-grid-area {
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    padding-bottom: 4px;
    scrollbar-width: thin;
}

.calendar-page .cal-head,
.calendar-page .cal-capacity-row,
.calendar-page .cal-allday-row,
.calendar-page .cal-scroll {
    min-width: var(--cal-board-min);
}

.calendar-page .cal-head-tech .cal-head-tech-name {
    font-weight: 600;
    color: var(--mud-palette-text-primary);
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

.calendar-page .cal-board-hint {
    margin-bottom: 10px;
    font-size: 12px;
    color: var(--mud-palette-text-secondary);
}

.calendar-page .cal-lane-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 10px;
    padding: 8px 10px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 45%);
    border-radius: 10px;
    background: color-mix(in oklab, var(--mud-palette-background), var(--mud-palette-surface) 58%);
}

.calendar-page .cal-lane-status,
.calendar-page .cal-lane-pager {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    color: var(--mud-palette-text-secondary);
    font-size: 12.5px;
}

.calendar-page .cal-lane-status span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.calendar-page .cal-lane-page {
    min-width: 42px;
    text-align: center;
    font-size: 12px;
    font-weight: 600;
    color: var(--mud-palette-text-primary);
}

.calendar-page .cal-whosoff {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-bottom: 12px;
    font-size: 12.5px;
    color: var(--mud-palette-text-secondary);
}

.calendar-page .cal-whosoff-label {
    font-weight: 600;
}

.calendar-page .cal-rail {
    flex: 0 0 250px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    border-radius: 10px;
    padding: 10px;
    max-height: calc(66vh + 150px);
}

.calendar-page .cal-rail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 600;
    font-size: 0.85rem;
}

.calendar-page .cal-rail-hint {
    font-size: 11px;
    color: var(--mud-palette-text-secondary);
}

.calendar-page .cal-rail-zone {
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 1 1 auto;
    min-height: 40px;
    overflow-y: auto;
}

.calendar-page .cal-rail-empty {
    font-size: 12px;
    color: var(--mud-palette-text-secondary);
    padding: 6px 2px;
}

.cal-rail-item {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
    border-radius: 8px;
    padding: 6px 8px;
    background: var(--mud-palette-surface);
    cursor: grab;
}

.cal-rail-item:hover {
    border-color: color-mix(in oklab, var(--mud-palette-primary), transparent 40%);
    background: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-surface) 96%);
}

.cal-rail-schedule {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.cal-rail-item .cal-rail-pri {
    flex: 0 0 4px;
    align-self: stretch;
    border-radius: 2px;
    background: var(--rail-pri, var(--mud-palette-lines-default));
}

.cal-rail-pri-critical { --rail-pri: var(--mud-palette-error); }
.cal-rail-pri-high { --rail-pri: var(--mud-palette-warning); }
.cal-rail-pri-medium { --rail-pri: var(--mud-palette-info); }
.cal-rail-pri-low { --rail-pri: var(--mud-palette-lines-default); }

.cal-rail-item .cal-rail-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.cal-rail-item .cal-rail-title {
    font-size: 12.5px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

.cal-rail-item .cal-rail-sub {
    font-size: 11px;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

.cal-rail-item .cal-rail-due {
    font-size: 11px;
    color: var(--mud-palette-text-secondary);
}

.cal-rail-item .cal-rail-due.cal-rail-overdue {
    color: color-mix(in oklab, var(--mud-palette-warning), black 22%);
    font-weight: 600;
}

.cal-rail-item .cal-rail-open {
    color: var(--mud-palette-text-secondary);
    display: inline-flex;
    margin-top: 2px;
}

/* Dragged into a slot zone the card floats above the grid instead of
   stretching the column — the reload right after snaps everything true. */
.calendar-page .cal-slot-zone .cal-rail-item,
.calendar-page .cal-allday-zone .cal-rail-item {
    position: absolute;
    z-index: 5;
    width: max(100%, 160px);
}

/* Pointer move/resize on entry blocks (cal-board.js). */
.cal-block.cal-live {
    cursor: grab;
    touch-action: none;
}

.cal-block.cal-live .cal-resize-handle {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 7px;
    cursor: ns-resize;
}

.cal-block.cal-live:hover .cal-resize-handle::after {
    content: "";
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: 2px;
    width: 26px;
    height: 3px;
    border-radius: 2px;
    background: color-mix(in oklab, var(--cal-accent), transparent 35%);
}

/* While a rail card is HTML5-dragged, blocks must not swallow the drop —
   they sit above the slot zones now that they render outside them. */
.calendar-page.cal-dragging .cal-block {
    pointer-events: none;
}

.cal-block.cal-drag-source {
    opacity: 0.35;
}

.cal-block.cal-drag-ghost {
    position: fixed;
    z-index: 1250;
    pointer-events: none;
    overflow: visible;
    opacity: 0.95;
    box-shadow: 0 8px 22px rgba(10, 14, 22, 0.35);
    background: color-mix(in oklab, var(--cal-accent), var(--mud-palette-surface) 70%);
}

.cal-block.cal-drag-ghost .cal-block-time {
    background: var(--mud-palette-surface);
    border: 1px solid color-mix(in oklab, var(--cal-accent), transparent 45%);
    border-radius: 3px;
    padding: 0 5px;
}

.cal-block.cal-block-allday.cal-drag-ghost {
    position: fixed;
    max-width: 240px;
}

.cal-resize-tip {
    position: fixed;
    z-index: 1250;
    pointer-events: none;
    font-size: 11px;
    font-weight: 600;
    color: var(--mud-palette-primary);
    background: var(--mud-palette-surface);
    border: 1px solid color-mix(in oklab, var(--mud-palette-primary), transparent 55%);
    border-radius: 3px;
    padding: 0 6px;
    line-height: 1.6;
    white-space: nowrap;
}

.calendar-page .cal-allday-cell.cal-allday-target {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 82%);
    box-shadow: inset 0 0 0 2px var(--mud-palette-primary);
    border-radius: 4px;
}

/* Block held over the rail: dropping returns the ticket to the queue. */
.calendar-page .cal-rail.cal-rail-drop-target {
    border-color: var(--mud-palette-primary);
    box-shadow: inset 0 0 0 2px var(--mud-palette-primary);
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 94%);
}

body.cw-gesturing {
    cursor: grabbing;
    user-select: none;
    -webkit-user-select: none;
}

.calendar-page .cal-block-action:focus-visible,
.calendar-page .cal-rail-schedule:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
    border-radius: 4px;
}

@media (max-width: 900px) {
    .calendar-page .cal-grid-area,
    .calendar-page .cal-rail,
    .calendar-page .cal-rail-toggle {
        display: none;
    }

    .calendar-page .cal-agenda {
        display: block;
    }
}

@media (max-width: 640px) {
    .calendar-page .cal-tech-search,
    .calendar-page .cal-legend {
        flex-basis: 100%;
    }

    .calendar-page .cal-lane-bar {
        align-items: flex-start;
        flex-direction: column;
    }
}

/* Portal upcoming-visits card (Phase 11.5 D7) — read-only, client-zone times. */
.portal-visits-card {
    border: 1px solid color-mix(in srgb, var(--mud-palette-primary) 24%, transparent);
    border-radius: 14px;
    padding: 14px 16px;
    background: color-mix(in srgb, var(--mud-palette-primary) 5%, var(--mud-palette-surface));
}

.portal-visits-title {
    display: flex;
    align-items: center;
    font-weight: 600;
    margin-bottom: 8px;
}

.portal-visit-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 0;
}

.portal-visit-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
}

.portal-visit-title {
    font-weight: 600;
    font-size: 0.95rem;
}

.portal-visit-when {
    font-size: 0.82rem;
    color: var(--mud-palette-text-secondary);
}

.portal-visit-note {
    font-size: 0.82rem;
    white-space: pre-wrap;
}

/* Settings → My calendar (Phase 11.6): connection rows + connect forms. */
.cal-conn-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 10px 0;
    border-top: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 45%);
}

.cal-conn-row:first-of-type {
    border-top: none;
    padding-top: 0;
}

.cal-conn-main {
    flex: 1;
    min-width: 0;
}

.cal-conn-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 600;
    font-size: 0.95rem;
}

.cal-conn-sub {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.82rem;
    color: var(--mud-palette-text-secondary);
    margin-top: 2px;
}

.cal-conn-form {
    margin-top: 14px;
    padding: 14px;
    border: 1px dashed color-mix(in oklab, var(--mud-palette-lines-default), transparent 25%);
    border-radius: 10px;
}

/* Dashboard per-tech load chips ("Alex 4.5h/8h") on Scheduled today (D9). */
.action-load-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 0 14px 8px;
}

.action-load-chip {
    font-size: 11px;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 45%);
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}

.action-load-chip--over {
    color: color-mix(in oklab, var(--mud-palette-warning), black 22%);
    border-color: color-mix(in oklab, var(--mud-palette-warning), transparent 70%);
    background: color-mix(in oklab, var(--mud-palette-warning), transparent 88%);
}

/* ── 12.4: notifications page ── */

/* Bell panel grouped rows: the clickable item plus a trailing dismiss. New
   structural classes live here (not the component's scoped CSS) because this
   block is 12.4's single append-only home. */
.cw-notif-row {
    display: flex;
    align-items: flex-start;
}

.cw-notif-row > .cw-notif-item {
    flex: 1 1 auto;
    min-width: 0;
}

.cw-notif-row .cw-notif-dismiss {
    flex: 0 0 auto;
    margin: 4px 6px 0 0;
    opacity: 0.55;
}

.cw-notif-row:hover .cw-notif-dismiss,
.cw-notif-row .cw-notif-dismiss:focus-visible {
    opacity: 1;
}

/* "×3" pill next to a grouped message — how many announcements one row holds. */
.cw-notif-count {
    display: inline-block;
    margin-left: 6px;
    padding: 0 6px;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 16px;
    background: color-mix(in oklab, var(--mud-palette-primary), var(--mud-palette-surface) 85%);
    color: var(--mud-palette-primary);
    white-space: nowrap;
    vertical-align: text-bottom;
}

.cw-notif-footer {
    display: flex;
    justify-content: center;
    padding: 4px 8px;
}

/* /notifications history list, bucketed by local day. */
.notif-day-head {
    padding: 12px 16px 4px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.notif-day-head:not(:first-child) {
    border-top: 1px solid var(--mud-palette-lines-default);
    margin-top: 4px;
}

.notif-row {
    display: flex;
    align-items: flex-start;
    gap: 4px;
    padding-right: 8px;
}

.notif-row:hover {
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 94%);
}

.notif-row-open {
    min-width: 0;
    min-height: 44px;
    flex: 1 1 auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 8px 10px 16px;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.notif-row-open:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

.notif-row-icon {
    margin-top: 2px;
    opacity: 0.7;
    flex-shrink: 0;
}

.notif-row-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1 1 auto;
}

.notif-row-message {
    font-size: 0.875rem;
    line-height: 1.35;
    overflow-wrap: anywhere;
}

.notif-row--unread .notif-row-message {
    font-weight: 600;
}

.notif-row-meta {
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
}

/* Dot and dismiss X both center on the row's FIRST text line (10px row
   padding + half the ~19px line box ≈ 19.5px) — they were ~9px apart. */
.notif-row-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--mud-palette-primary);
    margin-top: 5px;
    flex-shrink: 0;
}

.notif-row-dismiss {
    opacity: 0.55;
    margin-top: 7px;
}

.notif-row:hover .notif-row-dismiss,
.notif-row-dismiss:focus-visible {
    opacity: 1;
}

/* ── 12.4: service-desk stretched row links ── */
/* The ticket title is a real <a>; its ::after stretches over the whole row so
   middle-click/ctrl-click/copy-link work without nesting anchors. Interactive
   cells opt out by stacking above the overlay via .sd-row-interactive. */
.sd-row {
    position: relative;
}

.sd-row-link {
    color: inherit;
    text-decoration: none;
}

.sd-row-link::after {
    content: "";
    position: absolute;
    inset: 0;
}

.sd-row-link:focus-visible {
    outline: none;
}

.sd-row-link:focus-visible::after {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
    border-radius: inherit;
}

.sd-row-interactive {
    position: relative;
    z-index: 1;
}

/* ── 12.4: running timer chip + composer/shortcut helpers ── */
.ticket-timer-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--mud-palette-primary);
    background: var(--mud-palette-primary-hover);
}

.cw-timer-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: inherit;
    text-decoration: none;
    border: 1px solid currentColor;
}

/* Sidebar variant rides the nav-link skin; the two-line body needs its own layout. */
.cw-timer-nav .cw-timer-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.cw-timer-nav .cw-timer-elapsed {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

.cw-timer-nav .cw-timer-ticket {
    font-size: 0.72rem;
    opacity: 0.75;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.2;
}

/* Working-on belief chip (Phase 14.1) — the timer chip's quieter sibling:
   no border ring, softer weight, because a guess must not dress like a
   deliberate act. The ✕ stays subdued until pointed at. */
.cw-working-chip {
    border-style: dashed;
    font-weight: 500;
}

.cw-working-nav .cw-timer-elapsed {
    font-weight: 600;
}

.cw-working-nav-link,
.cw-working-chip-link {
    display: inline-flex;
    align-items: center;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.cw-working-nav-link {
    flex: 1;
}

.cw-working-chip-link {
    gap: 4px;
}

.cw-working-clear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 2px;
    padding: 1px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    opacity: 0.55;
    cursor: pointer;
    line-height: 0;
    min-width: 24px;
    min-height: 24px;
}

.cw-working-clear:hover {
    opacity: 1;
    background: var(--mud-palette-action-default-hover);
}

/* Time Truth consent surfaces (14.2a): the observed number always dresses as
   a quiet offer — dashed like the working-on chip, never like a record. */
.work-header-actions {
    display: flex;
    align-items: center;
    gap: 12px;
}

.work-unlogged,
.ticket-log-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    border: 1px dashed var(--mud-palette-lines-default);
    border-radius: 14px;
    background: transparent;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--mud-palette-text-secondary);
    cursor: pointer;
    line-height: 1.2;
    white-space: nowrap;
}

.work-unlogged:hover,
.ticket-log-chip:hover {
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary);
}

/* Armed = a made decision; the send button restates it in words. */
.ticket-log-chip--armed {
    border-style: solid;
    border-color: var(--mud-palette-primary);
    background: var(--mud-palette-primary-hover);
    color: var(--mud-palette-primary);
}

/* The composer chip splits in two: the body arms, the dial adjusts — because
   the number is an estimate and the person sending knows better. */
.ticket-log-chip-group {
    display: inline-flex;
    align-items: stretch;
}

.ticket-log-chip-group .ticket-log-chip--main {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    border-right-style: none;
}

.ticket-log-chip--edit {
    padding: 3px 6px;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    height: 100%;
}

.ticket-log-chip-group--armed .ticket-log-chip {
    border-style: solid;
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary);
}

.ticket-log-chip-group--armed .ticket-log-chip--main {
    background: var(--mud-palette-primary-hover);
    border-right-style: none;
}

/* The chip's inline editor (MudMenu panel). */
.ticket-log-editor {
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 280px;
    padding: 14px;
}

.ticket-log-editor-basis {
    font-size: 0.78rem;
    line-height: 1.35;
    color: var(--mud-palette-text-secondary);
}

.ticket-log-editor-fields {
    display: flex;
    gap: 10px;
}

.ticket-log-editor-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

.work-unlogged .mud-icon-root,
.ticket-log-chip .mud-icon-root,
.work-tracked-icon {
    opacity: 0.7;
}

.work-tracked-icon {
    color: var(--mud-palette-text-secondary);
    cursor: default;
}

/* Provenance caption under a pre-filled duration (14.2a). */
.time-prefill-hint {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.78rem;
    color: var(--mud-palette-text-secondary);
}

.time-prefill-hint .mud-icon-root {
    opacity: 0.7;
}

.ticket-composer-hint--draft {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin: 4px 2px 0;
}

.shortcut-help-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.shortcut-help-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.shortcut-help-key {
    min-width: 28px;
    padding: 2px 8px;
    border: 1px solid var(--mud-palette-lines-default);
    border-bottom-width: 2px;
    border-radius: 6px;
    text-align: center;
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    background: var(--mud-palette-background-gray);
}

/* ── In-app image lightbox (js/lightbox.js) ── */
/* Above dialogs and snackbars: whatever you were doing, the image is on top. */
.cw-lightbox {
    position: fixed;
    inset: 0;
    z-index: 2500;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 32px;
    background: rgba(12, 15, 22, 0.88);
    cursor: zoom-out;
}

.cw-lightbox img {
    max-width: 100%;
    max-height: calc(100% - 36px);
    border-radius: 6px;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.55);
}

.cw-lightbox-open {
    color: #cfd6e4;
    font-size: 0.8rem;
}

.cw-lightbox-open:hover {
    color: #ffffff;
}

/* ── Portal v2 (Phase 17.3): CSAT strip + knowledge ─────────────────── */

.portal-csat-strip {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 8px 16px 0;
    padding: 8px 12px;
    border-radius: 8px;
    background: color-mix(in oklab, var(--mud-palette-primary), transparent 92%);
    border: 1px solid color-mix(in oklab, var(--mud-palette-primary), transparent 78%);
    font-size: 0.875rem;
}

.portal-csat-actions {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.portal-csat-comment {
    flex: 1 1 220px;
}

.portal-csat-page {
    max-width: 560px;
    margin: 24px auto;
}

.portal-kb-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.portal-kb-title {
    font-weight: 600;
    flex: 1 1 auto;
}

/* ── Shared card grammar (Phase 15) ──────────────────────────────────
   The dash-card idiom began life scoped to Clients/Details.razor.css;
   the asset workspace and the 15.3 shared Related-items panel render the
   same cards from other components, so the generic rules live here now.
   (Clients' scoped copies are identical and harmless; they de-dupe when
   15.3 replaces that page's inline cards.) */

.dash-card {
    padding: 20px;
    border-radius: 14px;
    background: var(--mud-palette-surface);
}

.dash-card-header {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--mud-palette-text-secondary);
    padding-bottom: 12px;
    margin-bottom: 16px;
    border-bottom: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 35%);
}

.dash-card-title {
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.dash-kv-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.dash-kv {
    display: grid;
    grid-template-columns: 140px minmax(0, 1fr);
    gap: 10px;
    align-items: start;
}

.dash-k {
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
    line-height: 1.2;
    padding-top: 2px;
}

.dash-v {
    font-size: 0.875rem;
    color: var(--mud-palette-text-primary);
    min-width: 0;
    overflow-wrap: anywhere;
}

.dash-link-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.dash-link-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 0;
}

.dash-link-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 480px) {
    /* Long ticket/document names are the useful part of an asset relation.
       Give them the full card width and move secondary metadata below instead
       of spending half the row on a rigid status/date column. */
    .asset-detail-page .dash-link-row {
        flex-direction: column;
        align-items: flex-start;
        gap: 2px;
        padding: 8px 0;
    }

    .asset-detail-page .dash-link-row + .dash-link-row {
        border-top: 1px solid color-mix(in oklab, var(--mud-palette-lines-default), transparent 50%);
    }

    .asset-detail-page .dash-link-text {
        display: -webkit-box;
        width: 100%;
        overflow: hidden;
        white-space: normal;
        text-overflow: clip;
        overflow-wrap: anywhere;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        line-height: 1.35;
    }
}

@media (max-width: 360px) {
    /* On the smallest supported phones, protect the values from the fixed
       desktop label column. Labels may wrap; serials, models, and IPs remain
       the information a technician can actually scan. */
    .asset-detail-page .dash-kv {
        grid-template-columns: 105px minmax(0, 1fr);
    }
}

.portal-kb-meta {
    color: var(--mud-palette-text-secondary);
    font-size: 0.75rem;
    flex: 0 0 auto;
}

.portal-kb-content img {
    max-width: 100%;
    height: auto;
    border-radius: 6px;
}

.summary-sub {
    font-size: 0.75em;
    color: var(--mud-palette-text-secondary);
    font-weight: 400;
}

.dash-empty {
    font-size: 0.875rem;
    color: var(--mud-palette-text-secondary);
    padding: 12px 0 4px;
}

/* MudNumericField's default stacked steppers are 20×20. Give both controls
   the WCAG 2.2 minimum target without overlapping their hit areas. */
.mud-input-number-control .mud-input-control-input-container,
.mud-input-number-control .mud-input {
    min-height: 48px;
}

.mud-input-numeric-spin {
    width: 24px;
    height: 48px;
}

.mud-input-numeric-spin .mud-button-root {
    min-width: 24px !important;
    width: 24px;
    min-height: 24px !important;
    height: 24px;
    padding: 0 !important;
}

/* Assignee pickers stay usable for teams of 20, 50, or 75 people. Pinned
   shortcuts never scroll away; only the filtered team list does. */
.cw-assignee-picker {
    display: flex;
    flex-direction: column;
    width: min(320px, calc(100vw - 32px));
    max-height: min(440px, 70vh);
    overflow: hidden;
    background: var(--mud-palette-surface);
}

.cw-assignee-search {
    flex: 0 0 auto;
    padding: 8px 10px 6px;
    background: var(--mud-palette-surface);
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.cw-assignee-options {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.cw-assignee-pinned {
    flex: 0 0 auto;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.cw-assignee-results {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
}

.cw-assignee-empty {
    padding: 16px;
    color: var(--mud-palette-text-secondary);
    font-size: 0.8125rem;
    text-align: center;
}

@media (max-height: 640px) {
    .cw-assignee-picker {
        max-height: min(360px, 65vh);
    }
}

/* Compact desktop controls need a larger hit area once they become touch-first.
   Keep the 24px numeric steppers intact: each field already provides two
   adjacent 24px controls inside a 48px-tall input. */
@media (max-width: 700px) {
    button.mud-icon-button-size-small {
        min-width: 40px !important;
        width: 40px;
        min-height: 40px !important;
        height: 40px;
    }

    .mud-button-size-small:not(.mud-input-numeric-spin .mud-button-root),
    .mud-button-text-size-small,
    .mud-button-filled-size-small,
    .mud-button-outlined-size-small,
    .sd-assign-trigger {
        min-height: 36px;
    }

    .mud-chip.mud-clickable.mud-chip-size-small {
        min-width: 32px;
        min-height: 32px;
    }

    .cw-working-clear {
        min-width: 32px;
        width: 32px;
        min-height: 32px;
        height: 32px;
    }
}
