h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ── Contextual page help (PageHelpDialog) ─────────────────────────────────── */
/* .nx-page-help is gone with the inline card — the help now opens in a dialog from the breadcrumb
   toolbar, and MudDialog brings its own surface. The block and step styles below are still used by
   the dialog body. */
.nx-help-block {
    min-width: 0;
}

ol.nx-help-steps {
    margin: 0;
    padding-left: 1.25rem;
}

ol.nx-help-steps > li {
    margin-bottom: 0.25rem;
}
/* A count badge on a full-size (48px) icon button. MudBadge anchors 8px outside the BUTTON's corner,
   and a default icon button wraps its 24px glyph in 12px of padding — so the badge lands ~10px clear of
   the icon and reads as floating next to it rather than belonging to it. The small buttons elsewhere
   (the thread header's notes badge) have only 3px of padding, which is why those look right untouched.
   MudBadge positions an overlapping badge with bottom/left — `calc(100% - 12px)` against the button box,
   with top/right forced to auto — so those are the properties to move; setting top/right does nothing.
   12px → 21px pulls it in by 9, landing it on the glyph's corner the way the notes badge sits. The
   number assumes the default 48px icon button holding a 24px glyph, which is what this class is for.
   The full selector is needed to outrank MudBlazor's own four-class rule. */
.nx-badge-on-icon .mud-badge.mud-badge-top.right.mud-badge-overlap {
    bottom: calc(100% - 21px);
    left: calc(100% - 21px);
}

/* Inbox: a quoted reply is a jump link to the message it answers (nxScrollToMessage). The flash is
   what makes the landing obvious — without it a smooth scroll leaves you guessing which bubble. */
.nx-quote:hover {
    background: rgba(0, 0, 0, .08) !important;
}

@keyframes nx-quote-flash {
    0%   { background-color: rgba(7, 94, 84, .28); }
    100% { background-color: transparent; }
}

.nx-quote-flash {
    animation: nx-quote-flash 1.4s ease-out 1;
    border-radius: 8px;
}

/* A count badge on a MudTabs tab (the platform WhatsApp "Conversations" tab, and anything else that grows
   one). MudBadge anchors the badge 16px right of the badge-root, which sits immediately after the tab's
   label — so on a tab with 12px of right padding the 20px badge ends up 4px past the tab's own box, and
   `.mud-tab` is `overflow: hidden` for its ripple, which slices the badge's right edge off.
   Widening the padding rather than unhiding the overflow is deliberate: the hidden overflow is what keeps
   the ripple inside the tab, and letting it out to fix a badge would trade one visual bug for another.
   28px = the default 12px plus 16px: the measured 4px shortfall plus air, so the badge reads as clear
   of the label instead of touching it. Measured after: 12px clear on the right, 8px top, 20px bottom. Scoped with :has() so tabs WITHOUT a badge keep their normal width and the tab row does
   not shift the day a badge appears or disappears. */
.mud-tab:has(.mud-tab-badge) {
    padding-right: 28px;
}

/* Dialog forms on a narrow screen.

   Every form dialog in Sales and Tech Support pinned its content to a fixed min-width (460–560px) and
   laid paired fields out in `d-flex` rows that never wrap. On a 375px phone that meant the dialog fitted
   the viewport but its content did not: measured 568px of content inside a 375px dialog, so "Company
   name", "Email", "Country" and "Estimated MRR" all sat off the right edge and the form had to be
   scrolled sideways to be filled in.

   Two rules, because there were two causes. The width floor becomes a ceiling as well — the dialog keeps
   its comfortable desktop width and gives it up when there is no room. Each dialog passes its own width
   through --nx-form-w so widening one does not mean editing this file. `100vw - 48px` leaves the dialog
   its margins; a percentage cannot be used here because the dialog is sized *by* this content, so a
   percentage min-width would resolve against a containing block that does not have a width yet.

   Then the rows wrap. `flex: 1 1 200px` lets two fields share a line while there is room for both and
   drops the second onto its own line when there is not, instead of squeezing both into something too
   narrow to read. `min-width: 0` is what actually permits the shrink — flex items default to
   `min-width: auto`, which refuses to go below the input's intrinsic width and is the usual reason a
   wrap rule looks like it did nothing. */
.nx-form-stack {
    min-width: min(var(--nx-form-w, 520px), calc(100vw - 48px));
}

.nx-form-stack .d-flex {
    flex-wrap: wrap;
}

.nx-form-stack .d-flex > * {
    flex: 1 1 200px;
    min-width: 0;
}

/* Page header row: title on the left, actions pushed right by a MudSpacer.

   These were plain `d-flex`, which cannot wrap, so on a narrow screen the actions ran off the right
   edge and took the page with them — /support/sessions measured 417px of document in a 375px viewport
   because its header carries a title, an "All agents" switch and a refresh button. Wrapping costs
   nothing on a wide screen, where the row never reaches its limit.

   The MudSpacer keeps working: on one line it still pushes the actions right, and once the row wraps it
   collapses to zero and the actions start the next line. */
.nx-page-header {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/* ── MudSelect with a leading icon keeps its dropdown arrow ────────────────── */
/* MudSelect has one adornment slot, and it spends it on the dropdown arrow. Passing
   `Adornment="Adornment.Start"` to put a leading icon on the field therefore does not *add* an
   adornment, it *moves* the only one — the arrow disappears and the select renders as something
   indistinguishable from a MudTextField. That is what /workspace/create, /{slug}/settings/company and
   the template library all looked like: five fields the user had no reason to think were selectable.

   The arrow is the affordance and the leading icon is the design, so this draws the arrow back rather
   than giving up either. `.mud-input` is an inline-flex row, so an ::after is a flex item after the
   input slot — it takes real space instead of overlapping the value, which matters for the long
   timezone labels that already ellipsize at the field edge.

   The geometry reproduces MudBlazor's own arrow so a select with a leading icon lines up with one
   without: a 24px glyph centred in the 48px box a default MudIconButton occupies (12px padding each
   side), plus the 14px `.mud-input-outlined.mud-input-adorned-end` would have added. Outlined is the
   only variant these selects use; a filled one would want 12px there.

   The guards keep it to the case that is actually broken: `.mud-input-adorned-end` means MudBlazor is
   still drawing its own arrow, and MudAutocomplete shares `.mud-select-input` but manages its end
   adornment itself (clear button, progress ring). */
.mud-select-input.mud-input-adorned-start:not(.mud-input-adorned-end, .mud-autocomplete *)::after {
    content: "";
    flex: 0 0 auto;
    width: 48px;
    height: 24px;
    margin-inline-end: 14px;
    background-color: var(--mud-palette-action-default);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E") no-repeat center / 24px 24px;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E") no-repeat center / 24px 24px;
    pointer-events: none;
}

.mud-input.mud-disabled.mud-select-input.mud-input-adorned-start:not(.mud-input-adorned-end, .mud-autocomplete *)::after {
    background-color: var(--mud-palette-text-disabled);
}

/* MCP-S10: copyable connect snippets on the API keys page. Wraps long commands instead of
   pushing the card sideways, and uses the theme surface so it reads in light and dark. */
.mcp-snippet {
    margin: 0;
    padding: 12px 14px;
    border-radius: var(--mud-default-borderradius);
    background-color: var(--mud-palette-background-gray);
    border: 1px solid var(--mud-palette-lines-default);
    font-size: 0.8125rem;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto;
}

/* ── ServerDataTable toolbar ───────────────────────────────────────────────── */
/* Search and actions share one row while they fit and wrap when they don't — never a horizontal
   scrollbar to reach a button. Button labels stay on one line (a squeezed "New template" broke in two). */
.nx-table-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.nx-table-toolbar__search {
    flex: 1 1 240px;
    max-width: 320px;
    min-width: 0;
}

.nx-table-toolbar__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.nx-table-toolbar__actions .mud-button-root {
    white-space: nowrap;
}

@media (max-width: 599.98px) {
    .nx-table-toolbar__search {
        flex-basis: 100%;
        max-width: none;
    }

    /* One button per line on a phone would waste the screen; let them share rows and fill them. */
    .nx-table-toolbar__actions {
        width: 100%;
    }

    .nx-table-toolbar__actions > * {
        flex: 1 1 auto;
    }

    .nx-table-toolbar__actions .mud-button-root {
        width: 100%;
    }
}

/* ── Truncated table cell ──────────────────────────────────────────────────── */
/* For a long unbroken value (a template name is one snake_case word — it never wraps and widened the
   whole table past the screen). Put nx-cell-fill on the MudTd and wrap the value in a MudTooltip
   (RootClass="nx-cell-truncate-root") around <span class="nx-cell-truncate">. max-width:0 + width:100%
   is the table-layout idiom for "this column gets whatever the others leave, and clips to it" — it
   follows the table's real width instead of guessing from the viewport. */
.nx-cell-fill {
    max-width: 0;
    width: 100%;
}

.nx-cell-truncate-root {
    display: block;
    min-width: 0;
    max-width: 100%;
}

.nx-cell-truncate {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Card mode (MudTable's Breakpoint reached): each cell is a flex row of label + value, and the table
   still sizes itself to its widest content — so the full nowrap name would push every card off the
   screen. A zero flex-basis takes the value out of that measurement; it then grows into whatever the
   label leaves and clips there. max-width:0 is dropped, or it would collapse the whole cell. MudTable
   tags its root mud-{xs|sm|md}-table and switches to cards by media query, so match the same queries. */
@media (max-width: 599.98px) {
    .mud-xs-table .nx-cell-fill { max-width: none; }
    .mud-xs-table .nx-cell-fill .nx-cell-truncate-root { flex: 1 1 0; width: 0; margin-left: 12px; text-align: right; }
}

@media (max-width: 959.98px) {
    .mud-sm-table .nx-cell-fill { max-width: none; }
    .mud-sm-table .nx-cell-fill .nx-cell-truncate-root { flex: 1 1 0; width: 0; margin-left: 12px; text-align: right; }
}

@media (max-width: 1279.98px) {
    .mud-md-table .nx-cell-fill { max-width: none; }
    .mud-md-table .nx-cell-fill .nx-cell-truncate-root { flex: 1 1 0; width: 0; margin-left: 12px; text-align: right; }
}

/* ── Stacked tables (js/responsive-tables.js) ──────────────────────────────── */
/* A table wider than its container is flagged data-nx-stack by the script and laid out here as cards:
   one card per row, one "label: value" line per cell, labels taken from the column headers. Values
   wrap anywhere (IDs, URLs, snake_case names) so nothing can push the card wider than the screen.
   !important throughout: MudBlazor's stylesheet loads after this one and its table rules are more
   specific than anything short of it. */
table[data-nx-stack] {
    display: block !important;
    width: 100% !important;
    min-width: 0 !important;
}

table[data-nx-stack] > thead {
    display: none !important;
}

table[data-nx-stack] > tbody,
table[data-nx-stack] > tfoot {
    display: block !important;
    width: 100% !important;
}

table[data-nx-stack] > tbody > tr,
table[data-nx-stack] > tfoot > tr {
    display: block !important;
    width: 100% !important;
    padding: 4px 0;
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

table[data-nx-stack] > tbody > tr > td,
table[data-nx-stack] > tfoot > tr > td {
    display: flex !important;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 2px 12px;
    width: 100% !important;
    max-width: none !important;
    min-width: 0 !important;
    padding: 4px 12px !important;
    border: 0 !important;
    text-align: right !important;
    white-space: normal !important;
    overflow-wrap: anywhere;
    height: auto !important;
}

table[data-nx-stack] > tbody > tr > td[data-label]::before,
table[data-nx-stack] > tfoot > tr > td[data-label]::before {
    content: attr(data-label);
    margin-right: auto;
    padding-right: 8px;
    font-weight: 500;
    text-align: left;
    color: var(--mud-palette-text-secondary);
}

table[data-nx-stack] > tbody > tr > td > * {
    min-width: 0;
    max-width: 100%;
}

/* A clipped cell (nx-cell-fill) gets the leftover width of its line, as in MudTable's own card mode. */
table[data-nx-stack] .nx-cell-fill {
    max-width: none !important;
}

table[data-nx-stack] .nx-cell-fill .nx-cell-truncate-root {
    flex: 1 1 0;
    width: 0;
    text-align: right;
}

/* An empty "no records" / loading row spans every column; keep it centred rather than a labelled line. */
table[data-nx-stack] > tbody > tr > td[colspan] {
    justify-content: center;
    text-align: center !important;
}

/* MudTable's own card mode (below its Breakpoint): let values wrap instead of widening the card. */
.mud-table-cell[data-label] {
    overflow-wrap: anywhere;
}

/* ── Page spacing on small screens ─────────────────────────────────────────── */
/* On a phone the page sat inside four paddings — the layout's pa-4, the layout MudContainer's gutters,
   the page's own MudContainer gutters, then the card's pa-4 — 130px of a 375px screen. Below 600px the
   layout padding and outer gutter halve, a container nested in another container drops its duplicate
   gutter, and cards pad 12px. Below 960px (tablets) only the duplicate nested gutter goes. */
@media (max-width: 959.98px) {
    .mud-main-content .mud-container--gutters .mud-container--gutters {
        padding-left: 0 !important;
        padding-right: 0 !important;
    }
}

@media (max-width: 599.98px) {
    .mud-main-content {
        padding-left: 8px !important;
        padding-right: 8px !important;
    }

    .mud-main-content > .mud-container--gutters,
    .mud-main-content .mud-container--gutters {
        padding-left: 8px !important;
        padding-right: 8px !important;
    }

    .mud-main-content .mud-paper.pa-4,
    .mud-main-content .mud-card-content {
        padding: 12px !important;
    }

    .mud-main-content .mud-paper.pa-6,
    .mud-main-content .mud-paper.pa-8 {
        padding: 16px !important;
    }

    /* Page titles: h4 (34px) wraps a two-word title onto two lines on a phone. */
    .mud-main-content .mud-typography-h4 {
        font-size: 1.6rem;
    }
}

/* ── DesktopOnly (Components/Common/DesktopOnly.razor) ─────────────────────── */
/* display:contents keeps the wrapper out of the layout, so a feature sized against its parent (the flow
   canvas's height calc) renders exactly as it did unwrapped. */
.nx-desktop-only {
    display: contents;
}

.nx-mobile-only {
    display: none;
}

@media (max-width: 599.98px) {
    .nx-desktop-only {
        display: none !important;
    }

    .nx-mobile-only {
        display: block;
    }
}

/* ── App bar on a phone (MainLayout) ───────────────────────────────────────── */
/* Below 600px the app bar keeps ☰, the name, the bell and the avatar; the link menu, theme toggle, page
   help, knowledge base and Copilot buttons fold into one ⋮ overflow menu at the far right. The wrappers
   are display:contents so, on wider screens, the buttons lay out exactly as if unwrapped. */
span.nx-appbar-wide,
span.nx-appbar-overflow,
span.nx-appbar-help {
    display: contents;
}

span.nx-appbar-overflow {
    display: none;
}

/* Page help: the breadcrumb toolbar carries it from 960px up, the app bar from 600–959px. */
@media (min-width: 960px) {
    span.nx-appbar-help {
        display: none;
    }
}

@media (max-width: 599.98px) {
    .nx-appbar-wide,
    span.nx-appbar-help {
        display: none !important;
    }

    span.nx-appbar-overflow {
        display: contents;
    }
}

/* A staff console's name ("System Administration") shrinks and clips on a phone rather than pushing the
   bell and avatar off the bar. */
@media (max-width: 599.98px) {
    .nx-appbar-title {
        font-size: 1rem !important;
        min-width: 0;
        flex: 0 1 auto;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

/* Header-less key/value tables that overflow (responsive-tables.js): let the cells wrap. */
table[data-nx-wrap] td,
table[data-nx-wrap] th {
    white-space: normal !important;
    overflow-wrap: anywhere;
}


/* ── WhatsApp formatting ──────────────────────────────────────────────────────────────────────────────
   .wa-format wraps HTML from WhatsAppFormat.ToHtml (template previews, sent-template bubbles): *bold*,
   _italic_, ~strike~, ```monospace```, `code`, bullets, numbered lines and quotes, as the phone draws them.
   Line breaks are real 
, so the block keeps pre-wrap. */
.wa-format {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.wa-format strong { font-weight: 700; }
.wa-format em { font-style: italic; }
.wa-format del { text-decoration: line-through; }
.wa-format code.wa-mono,
.wa-format code.wa-code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .92em;
    color: inherit;
}
.wa-format code.wa-mono { background: none; padding: 0; }
.wa-format code.wa-code { background: rgba(0, 0, 0, .06); border-radius: 4px; padding: 0 3px; }
.wa-format .wa-bullet,
.wa-format .wa-number { display: inline-block; min-width: 1.1em; }
.wa-format .wa-quote {
    display: inline-block;
    border-left: 3px solid rgba(0, 0, 0, .22);
    padding-left: 8px;
    color: #54656f;
}

/* ── WaMessageEditor: an outlined field with WhatsApp's formatting toolbar ─────────────────────────────
   Drawn to sit with MudBlazor's outlined inputs (same border, radius, floating label and focus colour). */
.wa-editor {
    position: relative;
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: var(--mud-default-borderradius);
    background: var(--mud-palette-surface);
    transition: border-color .15s ease-in-out;
}
.wa-editor:hover { border-color: var(--mud-palette-action-default); }
.wa-editor:focus-within { border-color: var(--mud-palette-primary); box-shadow: inset 0 0 0 1px var(--mud-palette-primary); }
.wa-editor.wa-editor--over { border-color: var(--mud-palette-error); }
.wa-editor-label {
    position: absolute;
    top: -9px;
    left: 10px;
    padding: 0 4px;
    font-size: .75rem;
    line-height: 1rem;
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-secondary);
    pointer-events: none;
}
.wa-editor:focus-within .wa-editor-label { color: var(--mud-palette-primary); }
.wa-editor-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
    padding: 8px 6px 4px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}
.wa-editor-sep {
    width: 1px;
    height: 20px;
    margin: 0 4px;
    background: var(--mud-palette-lines-default);
}
.wa-editor-spacer { flex: 1 1 auto; }
.wa-editor textarea {
    display: block;
    width: 100%;
    box-sizing: border-box;
    border: 0;
    outline: 0;
    resize: none;
    overflow-y: hidden;
    padding: 10px 12px;
    background: transparent;
    color: var(--mud-palette-text-primary);
    font: inherit;
    font-size: 1rem;
    line-height: 1.5;
}
.wa-editor textarea::placeholder { color: var(--mud-palette-text-disabled); }
.wa-editor-foot {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
    padding: 0 12px 6px;
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
}
.wa-editor-count { margin-left: auto; white-space: nowrap; }
.wa-editor--over .wa-editor-count { color: var(--mud-palette-error); font-weight: 600; }

/* The editor's formatting help panel (MudPopover): "type this → shows as" pairs in two columns, then tips. */
.wa-editor-help { width: 480px; max-width: calc(100vw - 32px); }
.wa-editor-help code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .82em;
    white-space: pre;
    padding: 0 4px;
    border-radius: 3px;
    background: var(--mud-palette-background-gray);
}
.wa-editor-help-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; }
.wa-editor-help-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-width: 0;
    padding: 4px 6px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
    font-size: .85rem;
}
.wa-editor-help-item .wa-format { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wa-editor-help-tips { margin: 10px 0 6px 18px; padding: 0; font-size: .8rem; line-height: 1.45; }
.wa-editor-help-tips li { margin-bottom: 2px; }
@media (max-width: 599.98px) {
    .wa-editor-help-grid { grid-template-columns: 1fr; }
}

/* ── Contacts page: bulk action bar + selected rows (CTM-S05) ─────────────────────────────────── */
/* Sticks under the app bar while a long page of rows scrolls, so the actions stay in reach. Opaque on purpose:
   it scrolls over the table. app.css loads before MudBlazor, hence !important where a Mud rule competes. */
.nx-bulk-bar {
    position: sticky;
    top: calc(var(--mud-appbar-height, 64px) + 4px);
    z-index: 4;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-primary);
    border-left-width: 4px;
    border-radius: var(--mud-default-borderradius);
}
.mud-table-row.nx-row-selected > td {
    background-color: var(--mud-palette-action-default-hover) !important;
}

/* ── AI assistant → Knowledge: upload zone + preview (2026-10-07) ─────────────────────────────── */
.nx-upload-zone {
    border: 1px dashed var(--mud-palette-lines-inputs);
    border-radius: var(--mud-default-borderradius);
    background: var(--mud-palette-background-gray);
}
.nx-preview-text, .nx-preview-chunks {
    max-height: 60vh;
    overflow-y: auto;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    background: var(--mud-palette-background-gray);
}
.nx-preview-text {
    padding: 12px 14px;
    white-space: pre-wrap;
    word-break: break-word;
    font-size: .875rem;
    line-height: 1.55;
}
.nx-preview-chunk {
    padding: 10px 14px;
    font-size: .875rem;
    line-height: 1.5;
}
.nx-preview-chunk + .nx-preview-chunk {
    border-top: 1px solid var(--mud-palette-lines-default);
}

/* Two alternatives side by side with an "OR" between them (AI assistant → Knowledge: write an entry OR upload).
   A vertical rule with the word in the middle on wide screens; a horizontal one when the two stack. */
.nx-or-split {
    display: flex;
    align-items: stretch;
    gap: 24px;
}
.nx-or-split__pane {
    flex: 7 1 0;
    min-width: 0;
}
.nx-or-split__pane--narrow {
    flex: 5 1 0;
    display: flex;
    flex-direction: column;
}
.nx-or-split__pane--narrow .nx-upload-zone {
    flex: 1 1 auto;
}
.nx-or-split__or {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding-top: 28px; /* line up with the fields, below the two section headings */
    color: var(--mud-palette-text-secondary);
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .08em;
}
.nx-or-split__or::before,
.nx-or-split__or::after {
    content: "";
    flex: 1 1 auto;
    width: 1px;
    background: var(--mud-palette-lines-inputs);
}
.nx-or-split__or span {
    padding: 4px 10px;
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: 999px;
    background: var(--mud-palette-surface);
}
@media (max-width: 959.98px) {
    .nx-or-split { flex-direction: column; gap: 16px; }
    .nx-or-split__or { flex-direction: row; padding-top: 0; }
    .nx-or-split__or::before,
    .nx-or-split__or::after { width: auto; height: 1px; }
}

/* ── Customer reports (RPT plan) ────────────────────────────────────────────────────────────────────────────────
   Chart colours: one validated set of three (teal, orange, blue — checked for colour-blind separation and contrast on
   both surfaces with the dataviz validator), stepped for the dark surface. Text and grid follow the theme. */
.rpt {
    --rpt-s1: #0d9488;
    --rpt-s2: #eb6834;
    --rpt-s3: #2a78d6;
    --rpt-text: var(--mud-palette-text-secondary);
    --rpt-grid: var(--mud-palette-lines-default);
    --rpt-axis: var(--mud-palette-text-disabled);
}
.rpt.rpt-dark {
    --rpt-s2: #d95926;
    --rpt-s3: #3987e5;
}
.rpt-chart svg { display: block; width: 100%; height: auto; }
/* Phones get a narrower drawing of the same chart, so its labels stay readable instead of shrinking to nothing. */
.rpt-narrow { display: none; }
@media (max-width: 599.98px) {
    .rpt-wide { display: none; }
    .rpt-narrow { display: block; }
}
.rpt-table .rpt-total td { font-weight: 600; }
.rpt-card-link { display: block; height: 100%; color: inherit; text-decoration: none; }
.rpt-card-link:hover .rpt-card, .rpt-card-link:focus-visible .rpt-card { border-color: var(--mud-palette-primary); }
.rpt-card { transition: border-color .15s; }
