/* One type system for the whole app — matches MudTheme.Typography so Mud
   components and raw HTML (login, error pages) render in the same font.
   Inter when it loads, Segoe UI / system as the native Windows fallback. */
html, body {
    font-family: "Inter", "Segoe UI", system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    font-feature-settings: "cv02", "cv03", "cv04", "ss01";  /* Inter's cleaner letterforms (no-op on fallback) */
}

/* Motion tokens — strong custom curves (the built-in CSS easings are too weak). */
:root {
    --ease-out: cubic-bezier(0.23, 1, 0.32, 1);       /* crisp UI entrances & hover lifts */
    --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);   /* on-screen movement */
}

/* Tactile press feedback on every action button (subtle, fast). */
.mud-button-root {
    transition: transform 120ms var(--ease-out);
}
.mud-button-root:active {
    transform: scale(0.98);
}
@media (prefers-reduced-motion: reduce) {
    .mud-button-root { transition: none; }
    .mud-button-root:active { transform: none; }
}

a, .btn-link {
    color: var(--mud-palette-primary-darken, #0A5560);   /* 09-09: text-grade teal, see the colour-review block */
}

/* Blazor form validation (harmless if unused) */
.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."
    }

/* ---- Shared layout / typography tokens ---- */

/* Single width source for the attendee detail surfaces (was a magic number
   duplicated inline on every card/panel). */
.detail-shell {
    max-width: 1600px;
}

/* Label/value pair — small muted label above a normal-weight value. */
.tpni-field {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.tpni-field-label {
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.tpni-field-value {
    font-size: 0.95rem;
    color: var(--mud-palette-text-primary);
    word-break: break-word;
}

/* Attendee view — Registration Details. Section band separates the field groups; the Q&A block
   is for long demographic questions (full-width row, sentence-case label) so they read as a
   question instead of an uppercase label crammed into a narrow column. */
.tpni-detail-section {
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--mud-palette-primary);
    margin: 20px 0 8px;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}
.tpni-detail-section:first-child {
    margin-top: 4px;
}
.tpni-qa-label {
    font-size: 0.82rem;
    line-height: 1.35;
    color: var(--mud-palette-text-secondary);
    margin-bottom: 2px;
}
.tpni-qa-value {
    font-size: 0.95rem;
    color: var(--mud-palette-text-primary);
    white-space: pre-wrap;
    word-break: break-word;
}

/* Centered empty / initial state (Search + the shared EmptyState component). */
.search-empty,
.tpni-empty {
    text-align: center;
    padding: 56px 16px;
    color: var(--mud-palette-text-secondary);
}

/* Right-align a MudDataGrid numeric column HEADER over its right-aligned values.
   HeaderStyle="text-align:right" alone doesn't bite because the sortable header
   title sits in a .column-header / .sortable-column-header wrapper. Force that
   inner wrapper to a flex row justified to the end so the title + sort arrow sit
   over the right-aligned $$ values (text-align is the non-flex fallback). */
.tpni-th-right .column-header {
    justify-content: flex-end;   /* push the whole header block (title + sort arrow) to the right */
}
.tpni-th-right .sortable-column-header {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    text-align: right;
}
.tpni-th-right { text-align: right; }

/* Search results as compact tap cards on phone / narrow tablet.
   Replaces the MudDataGrid auto-stacked label:value rows, which run very tall. */
.search-cards {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.search-card {
    padding: 12px 14px;
    cursor: pointer;
    border-radius: 10px;
    transition: background-color 120ms ease;
}
.search-card:active {
    background-color: var(--mud-palette-action-default-hover);
}
.search-card-top {
    display: flex;
    align-items: baseline;
    gap: 10px;
}
.search-card-name {
    font-weight: 700;
    font-size: 1.05rem;
    line-height: 1.25;
    flex: 1 1 auto;
    min-width: 0;
    word-break: break-word;
}
.search-card-reg {
    flex: 0 0 auto;
    font-weight: 700;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
    color: var(--mud-palette-primary);
    background-color: var(--mud-palette-primary-hover);
    padding: 2px 8px;
    border-radius: 6px;
    white-space: nowrap;
}
.search-card-line {
    margin-top: 2px;
    color: var(--mud-palette-text-primary);
}
.search-card-sub {
    color: var(--mud-palette-text-secondary);
    font-size: 0.85rem;
    word-break: break-all;
}
.search-card-money {
    display: flex;
    align-items: baseline;
    gap: 14px;
    margin-top: 6px;
    font-size: 0.9rem;
    color: var(--mud-palette-text-primary);
}
.search-card-money-label {
    color: var(--mud-palette-text-secondary);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.search-card-id {
    color: var(--mud-palette-text-secondary);
    font-size: 0.8rem;
    white-space: nowrap;
}

/* Generic admin-list cards (ListCard) for phone / narrow tablet — the reusable version of the
   search cards, used by Reg Types / Products / Product Groups / Demographics / Source Codes, etc.
   Replaces the MudDataGrid's tall auto-stacked label:value rows with a compact tap-to-edit card. */
.tpni-cards {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.tpni-card {
    padding: 12px 14px;
    cursor: pointer;
    border-radius: 10px;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    transition: background-color 120ms ease;
}
.tpni-card:active {
    background-color: var(--mud-palette-action-default-hover);
}
.tpni-card-code {
    font-weight: 700;
    font-size: 1.02rem;
    line-height: 1.25;
    word-break: break-word;
}
.tpni-card-desc {
    margin-top: 2px;
    color: var(--mud-palette-text-primary);
    word-break: break-word;
}
.tpni-card-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
}
.tpni-card-chip {
    font-size: 0.75rem;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--mud-palette-action-default-hover);
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}
.tpni-card-chip b {
    font-weight: 700;
    color: var(--mud-palette-text-primary);
}
.tpni-card-chip.on {
    background: var(--mud-palette-primary-hover);
    color: var(--mud-palette-primary);
    font-weight: 600;
}
.tpni-card-chip.warn {
    background: var(--mud-palette-warning-hover);
    color: var(--mud-palette-warning);
    font-weight: 600;
}
.tpni-card-chip.ok {
    background: var(--mud-palette-success-hover);
    color: var(--mud-palette-success);
    font-weight: 600;
}
.tpni-card-chip.info {
    background: var(--mud-palette-info-hover);
    color: var(--mud-palette-info);
    font-weight: 600;
}
.tpni-card-chip.err {
    background: var(--mud-palette-error-hover);
    color: var(--mud-palette-error);
    font-weight: 600;
}
.tpni-card-chip.plum {
    background: rgba(110,79,142,.12);
    color: var(--mud-palette-tertiary);
    font-weight: 600;
}
.tpni-card-chip.slate {
    background: rgba(124,155,179,.18);
    color: var(--mud-palette-text-secondary);
    font-weight: 600;
}
.tpni-card-chip.teal {
    background: var(--mud-palette-primary-hover);
    color: var(--mud-palette-primary-darken);
    font-weight: 600;
}

/* Home module cards: a quiet hover lift to hint they're interactive.
   Gated so a touch tap doesn't trigger a sticky hover. */
.module-card {
    transition: transform 150ms var(--ease-out), box-shadow 150ms ease;
}

@media (hover: hover) and (pointer: fine) {
    .module-card:hover {
        transform: translateY(-2px);
        box-shadow: var(--mud-elevation-6);
    }
}

@media (prefers-reduced-motion: reduce) {
    .module-card {
        transition: box-shadow 150ms ease;
    }
    .module-card:hover {
        transform: none;
    }
}

/* Section header inside dialogs: label that never wraps + a rule filling the row. */
.section-label {
    display: flex;
    align-items: center;
    margin-top: 16px;
    margin-bottom: 8px;
}
.section-label-text {
    white-space: nowrap;
    flex-shrink: 0;       /* don't let the rule squeeze the label into a vertical stack */
}
.section-label-rule {
    flex: 1 1 auto;
    margin-left: 12px;
}

/* Editor dialogs: keep MudGrid gutters from producing a stray horizontal scrollbar. */
.tpni-editor-dialog .mud-dialog-content {
    overflow-x: hidden;
}

/* Email "Design" contenteditable + "Preview" surface. */
.tpni-rte,
.tpni-preview {
    min-height: 320px;
    max-height: 460px;
    overflow-y: auto;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
    padding: 12px 14px;
    background: var(--mud-palette-surface);
}
.tpni-rte:focus {
    outline: none;
    border-color: var(--mud-palette-primary);
}
.tpni-rte img, .tpni-preview img { max-width: 100%; }

/* Monospace input for code/HTML bodies. */
.tpni-mono .mud-input-slot,
.tpni-mono textarea {
    font-family: "Cascadia Code", "Consolas", "SFMono-Regular", monospace;
    font-size: 0.85rem;
}

/* MudBlazor 9.5 SYSTEMIC FIELD-HEIGHT BUG (Nick 2026-07-21, confirmed live via DevTools on Sender
   setup). A MudTextField's `Style` attribute is applied to the inner `.mud-input` element — which is
   a flex COLUMN child of the control — so an inline `flex:1 1 Npx` (written to size the field's WIDTH
   in a row) is interpreted as an N-px HEIGHT, ballooning single-line fields (SendGrid key, webhook
   URL, CNAME host/value, verification-to) into giant empty boxes. Neutralize that flex-basis on the
   inner element; width still comes from the field's own max-width and the row layout. Scoped to the
   exact broken pattern (`flex:1 1` in an inline style) so it can't touch a legitimately-tall field
   (those use Lines=, never an inline flex-basis). One rule fixes every affected field, this page or
   any other. */
.mud-input-control .mud-input[style*="flex:1 1"] { flex: initial !important; }

/* Reg Type editor: scrollable question / product assignment lists. */
.assign-list {
    max-height: 340px;
    overflow-y: auto;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 6px;
    padding: 4px 8px;
}
.assign-list .mud-input-control {
    margin-top: 0;
    margin-bottom: 0;
}

/* Standard page-header band — every non-Home route uses SetupPageHeader, which renders a manual
   breadcrumb (not MudBreadcrumbs — needed prominent styling we couldn't get from defaults).
   Background tint + left-border accent make the nav path read as a distinct "you are here"
   panel rather than getting lost as small grey text near the title. */
.tpni-page-breadcrumb {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 9px 14px;
    margin-bottom: 14px;
    background: var(--mud-palette-background-grey, #f5f7fa);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    font-size: 13.5px;
}
.tpni-breadcrumb-link {
    color: var(--mud-palette-primary, #1b6ec2);
    text-decoration: none;
    font-weight: 500;
    transition: color 100ms var(--ease-out);
}
.tpni-breadcrumb-link:hover {
    color: var(--mud-palette-primary-darken, #145a99);
    text-decoration: underline;
}
.tpni-breadcrumb-sep {
    color: var(--mud-palette-text-secondary, rgba(0,0,0,0.6));
    font-weight: 400;
    margin: 0 2px;
}
.tpni-breadcrumb-current {
    font-weight: 700;
    color: var(--mud-palette-text-primary, rgba(0,0,0,0.87));
}
/* Title row sits right under the breadcrumb. Keep it compact so the breadcrumb still anchors the
   eye at the top of the page. */
.tpni-page-titlebar {
    min-height: 40px;
    /* Title + action buttons share one flex row. On a phone the buttons (Refresh / Export …) would
       otherwise overflow past the title and push the whole page wider than the viewport (the
       horizontal "floating" / drift). Wrapping lets them drop to a second line instead. No effect on
       desktop, where everything fits on one line and wrap never triggers. */
    flex-wrap: wrap;
    row-gap: 6px;
}
/* SR-00520 (SetupPageHeader FitTitle): the title asks the row for only 12rem, so the buttons stay
   beside it whenever that much fits, then grows into whatever is left and ends in "..." only past
   that. On a phone 12rem + the buttons does not fit, so the buttons still drop to their own row. */
.tpni-page-titlebar > .tpni-title-fit {
    flex: 1 1 12rem;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* A phone already gives the title its own row - let a long name wrap there rather than cut it. */
@media (max-width: 599px) {
    .tpni-page-titlebar > .tpni-title-fit { white-space: normal; }
}

/* ---- Mobile horizontal "drift" guard (page rendering wider than the phone) ----
   On a phone the Onsite analytics page exceeded the viewport width and the whole page
   drifted sideways (left edge clipped off-screen). The page-titlebar wrap above wasn't
   enough — there were two more leaks, both fixed here:
   (1) Long company / source-code names in the Onsite tap-card rows: `text-truncate`
       only ellipsizes if its flex item is allowed to shrink, but a flex item's
       min-width defaults to `auto`, so the nowrap name forced the row — and the whole
       page — wider than the screen. min-width:0 lets it truncate instead.
   (2) Belt-and-suspenders: clip any residual horizontal overflow (e.g. MudGrid's
       negative gutters) on the page's scroll container so it can never scroll/drift
       sideways. `clip` (NOT `hidden`) so the sticky "Jump to" bar + vertical scroll
       keep working. Scoped to phone/tablet (<=960px); desktop is untouched. */
.tpni-onsite-card .text-truncate { min-width: 0; }
@media (max-width: 960px) {
    .mud-main-content { overflow-x: clip; }
}

/* ==== Tab bars ==========================================================================
   ⛔ THE BAR ELEMENT IS .mud-tabs-TABBAR. Everything here used to say .mud-tabs-toolbar — its
   MudBlazor v6 name — so every rule in this block had been dead since the v9 upgrade, and the
   white active "pill" the old comments described had never rendered anywhere in the Console.
   Repaired 2026-09-11 (Nick: "fix the .mud-tabs-toolbar rules everywhere too").

   Scope matters: the contrast treatment only makes sense where the bar has a COLOURED
   background (Color="Color.Primary" etc → .mud-tabs-tabbar-<colour>). On a plain bar a white
   active tab would be white-on-white, so plain bars keep MudBlazor's slider and nothing else. */

/* Both kinds: a bolder active label, and a slider thick enough to see when it is the indicator. */
.mud-tabs .mud-tab.mud-tab-active {
    font-weight: 700 !important;
    letter-spacing: 0.4px !important;
}
.mud-tabs .mud-tab-slider {
    height: 5px !important;
    border-radius: 3px 3px 0 0;
    box-shadow: 0 0 0 1px rgba(255,255,255,0.5);
}

/* ---- Coloured bars: separated items, and an active tab that is a TAB ----
   The band used to read as one undivided stripe with an active state you had to hunt for. A
   hairline separates the items; the active one is a white tab merged into the panel below it
   (square-bottomed, seam closed), which answers "where am I" by shape rather than by a 5px line. */
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) {
    box-shadow: inset 0 -1px 0 0 rgba(255,255,255,0.18);
    padding-inline: 0.25rem;
}
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab {
    position: relative;
    border-radius: 8px 8px 0 0;
    font-weight: 600;
    letter-spacing: 0.3px;
}

/* The separators. ⛔ TABS ARE NOT SIBLINGS OF EACH OTHER: MudBlazor wraps each one in a
   .mud-tooltip-root, so `.mud-tab + .mud-tab` matches nothing and drew no line at all (measured in
   the live User dialog on prod, 2026-09-11 — a static harness without the wrappers had looked fine,
   which is exactly how this was missed). Nor can a right-hand `:not(:last-child)` rule be used: the
   last element in the bar is the SLIDER, not a tab, so it hangs a hairline in empty space.
   ⇒ draw a LEFT hairline on every tab, then take it off the first one, the active one, and the one
   straight after the active one — each in both DOM shapes, wrapped and bare.
   ⛔ The first-tab rule MUST be `.mud-tabs-tabbar-wrapper > .mud-tab:first-child`, never a bare
   `.mud-tab:first-child`: in the wrapped shape EVERY tab is the first (and only) child of its own
   .mud-tooltip-root, so the bare form suppresses the line on all of them (measured on prod). */
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab::before {
    content: "";
    position: absolute;
    left: 0;
    top: 22%;
    bottom: 22%;
    width: 1px;
    background: rgba(255,255,255,0.28);
}
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tabs-tabbar-wrapper > :first-child .mud-tab::before,
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tabs-tabbar-wrapper > .mud-tab:first-child::before,
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab.mud-tab-active::before,
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab.mud-tab-active + .mud-tab::before,
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) :has(> .mud-tab.mud-tab-active) + * .mud-tab::before {
    display: none;
}

/* The active tab. Surface (not #fff) so it matches the panel it joins in either theme. */
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab.mud-tab-active {
    background-color: var(--mud-palette-surface, #fff) !important;
    color: var(--mud-palette-primary, #0c7c84) !important;
    border-radius: 10px 10px 0 0;
    margin-bottom: -1px;
    padding-bottom: 1px;
    box-shadow: 0 -6px 14px -8px rgba(0,0,0,0.35);
    z-index: 2;
}
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab.mud-tab-active :is(.mud-icon-root, .mud-tab-icon) {
    color: var(--mud-palette-primary, #0c7c84) !important;
}

/* SR-00206 E2: 0.55 on an inactive TAB is navigation the user cannot read. An inactive tab still has
   to be legible enough to choose — the active tab's shape and colour already carry "which one am I
   on", so the opacity only needs to say "not this one", not "don't read this". */
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab:not(.mud-tab-active) { opacity: 0.88; }
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab:not(.mud-tab-active):hover {
    opacity: 1;
    background-color: rgba(255,255,255,0.10);
    box-shadow: inset 0 -3px 0 0 rgba(255,255,255,0.6);
}

/* On a coloured bar the tab shape IS the indicator, so the slider would only draw a coloured bar
   underneath the white tab. Plain bars keep theirs (rule above). */
:is(.mud-tabs-tabbar-primary, .mud-tabs-tabbar-secondary, .mud-tabs-tabbar-tertiary,
    .mud-tabs-tabbar-info, .mud-tabs-tabbar-success, .mud-tabs-tabbar-warning,
    .mud-tabs-tabbar-error, .mud-tabs-tabbar-dark) .mud-tab-slider { display: none; }

@media (prefers-reduced-motion: reduce) {
    .mud-tabs .mud-tab { transition: none; transform: none; }
    .tpni-breadcrumb-link { transition: none; }
}

/* User dialog → Client/Event Access sub-tabs — each multi-select list lives in a fixed-height
   scroll container so the dialog doesn't grow indefinitely when there are 100+ clients/events. */
.access-scroll {
    max-height: 50vh;
    overflow-y: auto;
    border: 1px solid var(--mud-palette-lines-default, rgba(0,0,0,0.12));
    border-radius: 6px;
    padding: 4px;
}

/* HTML Pages folder tree — indented div rows so we get total control over rendering and don't
   depend on MudTreeView (which 9.5 rendered empty when bound to TreeItemData<string>). Each row is
   a single line: icon + name + file count chip; depth comes from inline padding-left. Hover gives
   a faint primary tint; selected row gets a stronger one + bolder text. */
.html-folder-tree {
    display: flex;
    flex-direction: column;
}
.html-folder-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border-radius: 4px;
    cursor: pointer;
    user-select: none;
    transition: background-color 100ms var(--ease-out);
}
.html-folder-row:hover {
    background-color: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.04));
}
.html-folder-row--selected {
    background-color: var(--mud-palette-primary-hover, rgba(25, 118, 210, 0.10));
    font-weight: 600;
}
.html-folder-name {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.html-folder-count {
    font-size: 11px;
    color: var(--mud-palette-text-secondary, rgba(0, 0, 0, 0.6));
    background: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.06));
    border-radius: 10px;
    padding: 1px 8px;
    min-width: 24px;
    text-align: center;
}
@media (prefers-reduced-motion: reduce) {
    .html-folder-row { transition: none; }
}

/* Master-detail pattern (Current Lists ↔ List details, future: HTML Pages list ↔ editor).
   Panels match height so the two cards read as one composed unit, and the names list inside
   the left panel is independently scrollable when it overflows. */
.customlist-panel {
    height: 100%;
    min-height: 480px;
}
.customlist-names {
    max-height: 60vh;
    overflow-y: auto;
}
/* Selected list-name row picks up a subtle primary tint so it's obvious which list the
   right panel is showing. (Mud's default selection style is a faint underline — too soft
   for this two-column reading.) */
.customlist-names .mud-selected-item {
    background-color: var(--mud-palette-primary-hover, rgba(25, 118, 210, 0.08));
}

/* Campaign Setup landing: each group is its own elevated card with breathing room
   below — turns the landing into a chunkable hierarchy instead of a wall of
   uniform tiles. The shadow stays subtle (Material elevation-1 equivalent) so
   the cards inside the group remain the visual focus, not the container. */
.setup-group {
    background: var(--mud-palette-surface, #fff);
    border-radius: 10px;
    padding: 20px 20px 8px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
    margin-bottom: 28px;
}
.setup-group:last-of-type {
    margin-bottom: 0;
}
.setup-group .section-label {
    margin-top: 0;     /* the group's own padding handles the top breathing room */
}

/* Campaign Setup section cards. Enabled ones lift on hover + dip on press; disabled
   stay flat with only their text muted (the "Coming soon" chip stays legible). */
.setup-card {
    height: 100%;
    transition: transform 150ms var(--ease-out), box-shadow 150ms ease;
}
.setup-card--off .setup-card-text .mud-typography {
    /* 09-09: opacity .6 on caption grey measured 1.8:1 on Campaign Setup; TextDisabled is 4.9:1 */
    color: var(--mud-palette-text-disabled) !important;
}
/* Chevron that nudges right on hover to signal "go". */
.setup-card-go {
    transition: transform 150ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
    .setup-card--on:hover {
        transform: translateY(-2px);
        box-shadow: var(--mud-elevation-6);
    }
    .setup-card--on:hover .setup-card-go {
        transform: translateX(4px);
    }
}
.setup-card--on:active {
    transform: scale(0.98);
}
@media (prefers-reduced-motion: reduce) {
    .setup-card,
    .setup-card-go {
        transition: box-shadow 150ms ease;
    }
    .setup-card--on:hover,
    .setup-card--on:active,
    .setup-card--on:hover .setup-card-go {
        transform: none;
    }
}

/* Register Application "Current Menus" tree — indented div tree (MudTreeView 9.5 renders empty when
   data-bound, so we hand-roll it). Groups expand to reveal their options + a "New Menu Option" row. */
.tpni-menu-tree { font-size: 0.9rem; }
.tpni-menu-tree .tpni-tree-root {
    display: flex; align-items: center; font-weight: 600;
    padding: 4px 4px 6px; color: var(--mud-palette-text-primary, rgba(0,0,0,0.87));
}
.tpni-menu-tree .tpni-tree-group,
.tpni-menu-tree .tpni-tree-option {
    display: flex; align-items: center; cursor: pointer; border-radius: 6px;
    padding: 4px 6px; transition: background-color 120ms ease;
}
.tpni-menu-tree .tpni-tree-group { padding-left: 18px; }
.tpni-menu-tree .tpni-tree-option { padding-left: 48px; }
.tpni-menu-tree .tpni-tree-group:hover,
.tpni-menu-tree .tpni-tree-option:hover { background-color: var(--mud-palette-action-default-hover, rgba(0,0,0,0.04)); }
.tpni-menu-tree .tpni-tree-group.selected,
.tpni-menu-tree .tpni-tree-option.selected { background-color: var(--mud-palette-primary-hover); font-weight: 600; }
.tpni-menu-tree .tpni-tree-toggle { margin-right: 4px; color: var(--mud-palette-text-secondary, rgba(0,0,0,0.54)); }
.tpni-menu-tree .tpni-tree-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tpni-menu-tree .tpni-tree-option .tpni-tree-label { color: var(--mud-palette-primary, #1976d2); }
.tpni-menu-tree .tpni-tree-add .tpni-tree-label { color: var(--mud-palette-success, #2e7d32); font-style: italic; }

/* ============================================================================
   TPNI Console visual system — brand v1 token pass (2026-07-18, locked)
   Charcoal neutrals + Pulse teal #0C7C84 primary, per-workspace accents
   (--tpni-accent-*), Urbanist headings, the three-blade mark as favicon/
   lockup/loader (assets.tpni.com/brand — never recreate a logo). Built ON
   TOP of the MudTheme palette (MainLayout.razor) so the var(--mud-palette-*)
   usages elsewhere in this file track the brand automatically.
   ========================================================================== */
:root {
    /* Layered, cool-tinted depth (toward the palette ink, never flat grey/black). */
    --tpni-shadow-sm: 0 1px 2px rgba(22,28,29,0.10), 0 1px 3px rgba(22,28,29,0.08);   /* 09-09: .06 read as flat once the page ground dropped to #EEF1F1 */
    --tpni-shadow-md: 0 2px 6px rgba(22,28,29,0.06), 0 8px 18px rgba(22,28,29,0.08);
    --tpni-shadow-lg: 0 12px 36px rgba(22,28,29,0.15), 0 4px 10px rgba(22,28,29,0.07);
    --tpni-ring: 0 0 0 3px rgba(12,124,132,0.30);
    --tpni-radius-card: 12px;
    /* Brand v1 workspace accents (assets.tpni.com/brand) — one accent blade per product. */
    --tpni-accent-pulse: #0C7C84;   /* CM / Pulse (the primary) */
    --tpni-accent-leads: #A23640;   /* Lead Retrieval */
    --tpni-accent-events: #7C9BB3;  /* organizer / Events */
    --tpni-accent-ams: #6E4F8E;     /* AMS / Membership */
    /* 09-09: ONE series palette for charts, legends and stage swatches (was two: this set and an
       indigo-era violet/mint/amber set 1.3-1.7:1 away from it). C# mirror: AnalyticsCharts.Palette. */
    --tpni-series-1: #0C7C84; --tpni-series-2: #6E4F8E; --tpni-series-3: #A23640; --tpni-series-4: #E08A0B;
    --tpni-series-5: #7C9BB3; --tpni-series-6: #2E80D6; --tpni-series-7: #11A06B; --tpni-series-8: #DC4A60;
}

/* ---- Depth: replace MudBlazor's flat grey elevations with the cool stack ---- */
.mud-paper.mud-elevation-1 { box-shadow: var(--tpni-shadow-sm); }
.mud-paper.mud-elevation-2,
.mud-paper.mud-elevation-3 { box-shadow: var(--tpni-shadow-md); }
.mud-paper.mud-elevation-4,
.mud-paper.mud-elevation-6,
.mud-paper.mud-elevation-8 { box-shadow: var(--tpni-shadow-lg); }
/* Outlined surfaces (most of the app's panels) get a crisper hairline + soft lift. */
.mud-paper.mud-paper-outlined {
    border-color: var(--mud-palette-lines-default);
    box-shadow: var(--tpni-shadow-sm);
}
/* Slightly softer card corners read more modern than the default 4px. */
.mud-card, .mud-paper.mud-paper-outlined, .mud-table-container { border-radius: var(--tpni-radius-card); }

/* ---- App bar: brand-ink charcoal with a deep-teal cast off the right edge (brand v1) ---- */
.mud-appbar.mud-appbar-fixed-top,
.mud-appbar {
    background-image: linear-gradient(100deg, #1b1d1f 0%, #26282b 52%, #0a5560 125%);
    box-shadow: 0 1px 0 rgba(22,28,29,0.10), 0 6px 18px -8px rgba(22,28,29,0.45);
}
.mud-appbar .mud-typography { letter-spacing: -0.01em; }

/* ---- Page content must clear the FIXED app bar ----------------------------------------------
   MudMainContent carries its own top offset for the fixed app bar, but MainLayout renders it as
   <MudMainContent Class="pa-6"> and the pa-6 utility sets padding on ALL FOUR sides with
   !important, clobbering that offset. Result: every page's title row (.tpni-page-titlebar) laid
   out at y=24 against a 64px-tall fixed bar — the page TITLE was hidden underneath it and only a
   few pixels poked out below, which reads as a stray artifact under the black bar.
   ⭐ Measured live on dev 2026-07-21 (DevTools, not guessed): --mud-appbar-height=64px,
   .mud-main-content padding-top=24px, titlebar top=24px → after this rule 88px and it clears.
   ⚠️ The !important is REQUIRED — a plain .mud-main-content.pa-6 rule is more specific but still
   loses to the utility's !important (verified: it silently did nothing). */
/* 2026-09-06: b5963ea changed the class to "pa-3 pa-sm-6" (phone padding) and this selector stopped
   matching, so every page title slid back under the app bar on PROD for ~15 hours. Match the element,
   not the utility class, and keep the phone/desktop split: 12px below sm, 24px from sm up. */
.mud-main-content.pa-3 { padding-top: calc(var(--mud-appbar-height, 64px) + 12px) !important; }
@media (min-width: 600px) {
    .mud-main-content.pa-sm-6, .mud-main-content.pa-6 { padding-top: calc(var(--mud-appbar-height, 64px) + 24px) !important; }
}

/* Brand v1 — the app-bar lockup (white corporate lockup SVG + the surface name in Urbanist). */
.tpni-appbar-brand { display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit; }
.tpni-appbar-brand img { height: 24px; display: block; }
.tpni-appbar-brand span {
    font-family: 'Urbanist', 'Inter', 'Segoe UI', system-ui, sans-serif;
    font-weight: 600; font-size: 1.02rem; letter-spacing: 0.012em; color: #fff; opacity: .92;
    border-left: 1px solid rgba(255,255,255,.28); padding-left: 10px; line-height: 1;
}

/* Brand v1 — kinetic-mark loader (BrandLoader.razor); the SVG carries its own reduced-motion guard. */
.tpni-brand-loader { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 26px 0; }
.tpni-brand-loader span { font-size: .8rem; color: var(--mud-palette-text-secondary); letter-spacing: .02em; }

/* ---- Drawer / nav: cool panel + an active-item pill (no side stripe) ---- */
.mud-drawer {
    border-right: 1px solid var(--mud-palette-lines-default);
}
.mud-drawer .mud-navmenu .mud-nav-link {
    border-radius: 8px;
    margin: 1px 8px;
    transition: background-color 120ms var(--ease-out), color 120ms var(--ease-out);
}
.mud-drawer .mud-navmenu .mud-nav-link:hover {
    background-color: var(--mud-palette-action-default-hover);
}
.mud-drawer .mud-navmenu .mud-nav-link.active,
.mud-drawer .mud-navmenu .mud-nav-link.mud-nav-link-active {
    background-color: var(--mud-palette-primary-hover);
    /* 09-09: primary on primary-hover was 4.3:1. !important because MudBlazor's own
       .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(...) is (0,5,0) and loads later. */
    color: var(--mud-palette-primary-darken) !important;
    font-weight: 600;
}
.mud-drawer .mud-navmenu .mud-nav-link.active .mud-icon-root {
    color: var(--mud-palette-primary);
}
.mud-drawer-header .mud-typography {
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--mud-palette-text-secondary);
}

/* ---- Branded focus ring: replace the default browser outline app-wide ---- */
.mud-button-root:focus-visible,
.mud-icon-button:focus-visible,
.mud-input-control .mud-input-slot:focus-visible,
.mud-nav-link:focus-visible,
.tpni-tree-group:focus-visible,
.tpni-tree-option:focus-visible,
a:focus-visible {
    outline: none;
    box-shadow: var(--tpni-ring);
    border-radius: 8px;
}
/* Outlined inputs draw their own focused border — the inner slot ring would show a second
   nested box (e.g. the Support "describe your problem" textarea). */
.mud-input.mud-input-outlined .mud-input-slot:focus-visible {
    box-shadow: none;
}

/* ---- Buttons: a touch more presence on filled primary ---- */
.mud-button-filled.mud-button-filled-primary {
    box-shadow: 0 1px 2px rgba(10,85,96,0.30), 0 2px 6px -1px rgba(10,85,96,0.28);
}
.mud-button-filled.mud-button-filled-primary:hover {
    box-shadow: 0 2px 4px rgba(10,85,96,0.34), 0 6px 14px -2px rgba(10,85,96,0.34);
}

/* ---- Data grids / tables: quieter header, clearer hover + selection ---- */
.mud-table-root .mud-table-head .mud-table-cell,
.mud-data-grid .mud-table-head .mud-table-cell {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.71rem;
    font-weight: 700;
    color: var(--mud-palette-text-secondary);
    background-color: var(--mud-palette-background-grey);
    border-bottom: 1px solid var(--mud-palette-lines-default);
}
.mud-table-row:hover > .mud-table-cell,
.mud-data-grid .mud-table-row:hover > .mud-table-cell {
    background-color: var(--mud-palette-table-hover);
}

/* ---- Row action icons sit side by side (Edit then Delete), never stacked ----
   The grid's auto table-layout was shrinking the Actions column to a single button's
   min-content width, which forced the second icon to wrap onto a new line. nowrap keeps
   both icons on one line and lets the column size to fit. Markup order is Edit then Delete,
   so horizontal layout puts Delete on the right. Scoped to cells that actually contain an
   icon button (the action cells) so text columns still wrap normally. */
.mud-table-cell:has(.mud-icon-button) { white-space: nowrap; }

/* ---- Chips / status: a hair more saturation + weight so state reads fast ---- */
.mud-chip { font-weight: 600; letter-spacing: 0.01em; }

/* ---- Dialogs: softer corners + the elevated cool shadow ---- */
.mud-dialog { border-radius: 14px; box-shadow: var(--tpni-shadow-lg); }
.mud-dialog-title { font-weight: 650; letter-spacing: -0.01em; }

/* ---- Setup-group cards: adopt the cool depth instead of the old grey shadow ---- */
.setup-group { box-shadow: var(--tpni-shadow-sm); }

/* ---- Page content gets a touch more horizontal breathing room on wide screens ---- */
@media (min-width: 1600px) {
    .mud-main-content > .mud-container,
    .mud-main-content { max-width: 1680px; }
}

/* ==== #151 Lane B — customer home (CmCustomerHome / OrganizerCustomerHome) ==== */
/* The CM home's scale line — replaced two hero-metric tiles (2026-08-10). Numbers are still
   tabular so they do not jitter on refresh; the sentence carries them instead of bordered boxes.
   .tpni-cust-stat* below is still used by the organizer home and the funnel. */
.tpni-cust-scale { margin-top: 10px; font-variant-numeric: tabular-nums; }
.tpni-cust-scale b { color: var(--mud-palette-text-primary); font-weight: 650; }

.tpni-cust-stats { display: flex; gap: 12px; margin-top: 18px; flex-wrap: wrap; }
.tpni-cust-stat { border-radius: var(--tpni-radius-card); padding: 12px 18px; min-width: 132px; }
.tpni-cust-stat .n { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.tpni-cust-stat .l { font-size: 11.5px; color: var(--mud-palette-text-secondary); margin-top: 2px; }
.tpni-cust-stat .d { font-size: 11px; color: var(--mud-palette-success); font-weight: 600; margin-top: 2px; }

.tpni-cust-sect { display: block; margin: 26px 0 2px; color: var(--mud-palette-text-secondary); font-weight: 700; letter-spacing: .09em; }
.tpni-cust-card { border-radius: var(--tpni-radius-card); }

/* Dashboard "Your Campaigns" rows (Nick 2026-07-21: campaigns are LISTED on the dashboard now,
   not just linked to). Same row rhythm as the Campaigns screen so the two read as one product. */
/* Audiences as rows (2026-08-10) — replaced a grid of identical cards. Four columns so the eye
   can run down one at a time: name+state, size, wash age, action. Collapses to a stacked block on
   a phone, where four columns would each be too narrow to read. */
.tpni-aud-list { border-radius: var(--tpni-radius-card); overflow: hidden; }
.tpni-aud-row { display:grid; grid-template-columns: minmax(0,1fr) 190px 260px auto;
                align-items:center; gap:16px; padding:12px 18px; cursor:pointer;
                border-bottom:1px solid var(--mud-palette-divider-light);
                transition: background .12s var(--ease-out, ease); }
.tpni-aud-row:last-child { border-bottom:0; }
.tpni-aud-row:hover { background: var(--mud-palette-table-hover); }
.tpni-aud-row .nm { display:flex; align-items:center; min-width:0; }
.tpni-aud-row .nm > .mud-typography { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tpni-aud-row .ct { font-size:13px; font-variant-numeric:tabular-nums;
                    color: var(--mud-palette-text-secondary); }
.tpni-aud-row .wa { font-size:12px; color: var(--mud-palette-text-secondary); }
.tpni-aud-row .wa .pend { color: var(--mud-palette-warning-darken); margin-left:4px; }
.tpni-aud-row .ac { justify-self:end; }
@media (max-width: 900px) {
    .tpni-aud-row { grid-template-columns: 1fr; gap:6px; padding:14px 16px; }
    .tpni-aud-row .ac { justify-self:start; }
}

.tpni-cust-camprow { display:flex; align-items:center; gap:12px; padding:14px 18px;
                     border-bottom:1px solid var(--mud-palette-lines-default); }
.tpni-cust-camprow:last-child { border-bottom:0; }

.tpni-cust-nums { display: flex; gap: 26px; margin-top: 14px; flex-wrap: wrap; }
.tpni-cust-nums .n { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.tpni-cust-nums .l { font-size: 11px; color: var(--mud-palette-text-secondary); }
.tpni-cust-nums .d { font-size: 11px; color: var(--mud-palette-success); font-weight: 600; }

.tpni-cust-live { animation: tpni-cust-pulse 1.6s infinite; }
@keyframes tpni-cust-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .tpni-cust-live { animation: none; } }

/* checklist rows */
.tpni-cust-step { display: flex; align-items: flex-start; gap: 14px; padding: 14px 20px; border-top: 1px solid var(--mud-palette-divider); }
.tpni-cust-step .mark { width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; display: grid; place-items: center; font-size: 13px; font-weight: 700; margin-top: 1px; }
.tpni-cust-step.done .mark { background: rgba(17,160,107,.14); color: var(--mud-palette-success); }
.tpni-cust-step.todo .mark { background: var(--mud-palette-background-gray); color: var(--mud-palette-text-secondary); border: 1.5px dashed var(--mud-palette-lines-inputs); }
.tpni-cust-step .st { flex: 1; min-width: 0; }
.tpni-cust-step .st b { font-size: 13.5px; display: block; }
.tpni-cust-step.done .st b { color: var(--mud-palette-text-secondary); }
.tpni-cust-step .st span { font-size: 12.5px; color: var(--mud-palette-text-secondary); display: block; margin-top: 2px; max-width: 62ch; }
.tpni-cust-step .act { flex-shrink: 0; align-self: center; }

/* organizer-new hero (brand v1: charcoal -> teal) */
.tpni-cust-hero { border-radius: var(--tpni-radius-card); background-image: linear-gradient(120deg, #232526 0%, #0A5560 62%, #0C7C84 100%); }

/* the visible-but-locked Campaign Launch teaser */
.tpni-cust-locked { border: 1px solid #D8DEE6; background: linear-gradient(135deg, #FBFCFC, #F1F5F6); }
.tpni-cust-locked .lockb { width: 26px; height: 26px; border-radius: 7px; background: var(--mud-palette-tertiary); color: #fff; display: grid; place-items: center; flex-shrink: 0; }

/* #151 LR dashboard — the "hot" live-leads KPI tile (rgba over surface = theme-safe) + the live spotlight */
.tpni-cust-stat.hot { background: rgba(232,89,12,.10); border: 1px solid rgba(232,89,12,.34); }
.tpni-cust-stat.hot .n { color: #E8590C; }
.tpni-cust-stat.hot .l .dot { color: #E8590C; font-size: 9px; margin-right: 3px; animation: tpni-cust-pulse 1.6s infinite; }
.tpni-spot { border: 1px solid var(--mud-palette-lines-default); }
.tpni-spot-nums { display: flex; gap: 44px; margin-top: 16px; flex-wrap: wrap; align-items: flex-end; }
.tpni-spot-nums .n { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.tpni-spot-nums .big .n { font-size: 40px; line-height: 1; letter-spacing: -0.02em; color: var(--mud-palette-primary); }
.tpni-spot-nums .l { font-size: 12px; color: var(--mud-palette-text-secondary); margin-top: 3px; }
.tpni-spot-nums .ago { font-size: 12px; color: var(--mud-palette-success); font-weight: 600; margin-top: 5px; }
@media (prefers-reduced-motion: reduce) { .tpni-cust-stat.hot .l .dot { animation: none; } }

/* ==== #151 — reusable wizard shell (WizardShell / WizardStep; docs/CM-WIZARD-SURFACES.md · S0) ==== */
.tpni-wiz-rail { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
.tpni-wiz-main { display: block; }
.tpni-wiz-main.has-preview { display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 380px); gap: 20px; align-items: start; }
.tpni-wiz-body { border-radius: var(--tpni-radius-card); }
.tpni-wiz-preview { position: sticky; top: 16px; }
.tpni-wiz-step { display: block; }
.tpni-wiz-foot { display: flex; align-items: center; gap: 8px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--mud-palette-lines-default); }
@media (max-width: 960px) {
    .tpni-wiz-main.has-preview { grid-template-columns: 1fr; }
    .tpni-wiz-preview { position: static; }
}

/* #151 · S2 — CM Setup hub cards + the booth-app qualifying-questions preview */
.tpni-setup-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: 14px; margin-top: 4px; }
.tpni-setup-card { transition: box-shadow .12s ease, transform .12s ease; }
.tpni-setup-card:hover { box-shadow: 0 6px 18px rgba(0,0,0,.10); transform: translateY(-1px); }
.tpni-setup-soon .mud-typography { color: var(--mud-palette-text-disabled) !important; }
.tpni-setup-soon .mud-avatar, .tpni-setup-soon .mud-icon-root { opacity: .55; }
.tpni-booth { display: flex; justify-content: center; }
.tpni-booth-screen { width: 100%; max-width: 300px; border: 1px solid var(--mud-palette-lines-default); border-radius: 18px; background: var(--mud-palette-surface); padding: 12px; box-shadow: 0 4px 16px rgba(0,0,0,.08); }
.tpni-booth-head { font-weight: 700; font-size: 13px; padding: 2px 2px 10px; border-bottom: 1px solid var(--mud-palette-divider); margin-bottom: 6px; }
.tpni-booth-q { padding: 7px 2px; }
.tpni-booth-q .ql { font-size: 12.5px; font-weight: 600; }
.tpni-booth-q .qf { margin-top: 4px; font-size: 11.5px; color: var(--mud-palette-text-secondary); border: 1px dashed var(--mud-palette-lines-inputs); border-radius: 6px; padding: 5px 8px; }
.tpni-booth-q .qo { margin-top: 5px; display: flex; flex-wrap: wrap; gap: 5px; }
.tpni-booth-q .qo span { padding: 2px 8px; font-size: 10.5px; line-height: 1.5; border: 1px solid var(--mud-palette-lines-inputs); border-radius: 11px; color: var(--mud-palette-text-secondary); }

/* #151 (Nick 2026-07-08, "carry the color inward") — each module's colored home tile carries its
   accent into its inner landing so the inside matches the colored home page. Wrap a landing's cards
   in .tpni-accent-cards and set --card-accent / --card-accent2; the enabled setup-cards then get the
   module's gradient icon square + a matching top border, chevron and section label. "Coming soon"
   cards stay muted (only .setup-card--on is accented). */
.tpni-accent-cards .setup-card--on {
    border-top: 3px solid var(--card-accent, var(--mud-palette-primary));
}
.tpni-accent-cards .setup-card--on .mud-avatar {
    background-image: linear-gradient(135deg, var(--card-accent) 0%, var(--card-accent2, var(--card-accent)) 100%) !important;
    border-radius: 12px !important;
}
.tpni-accent-cards .setup-card--on .setup-card-go { color: var(--card-accent) !important; }
.tpni-accent-cards .section-label { color: var(--card-accent, var(--mud-palette-primary)); }

/* #151 ground-0 (Nick 2026-07-08): ONE console — the accent "Work on this campaign" tiles are
   shared by the customer Campaign home AND the staff Home picker, so staff and client see the same
   colorful doors. Promoted here from CampaignHome so both pages render identical tiles. */
/* Bigger tiles (Nick 2026-07-08): wider min column, more padding, larger accent icon square. */
.tpni-camp-tiles { display:grid; grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr)); gap:16px; }
.tpni-camp-tile { cursor:pointer; padding:22px !important; min-height:104px;
                  transition:box-shadow .15s, transform .15s; border-top:3px solid var(--tile-accent, var(--mud-palette-primary)); }
.tpni-camp-tile:hover { box-shadow:0 2px 6px rgba(24,30,60,.06), 0 10px 22px rgba(24,30,60,.14); transform:translateY(-1px); }
.tpni-camp-tile.tpni-camp-tile--off { cursor:default; }
.tpni-camp-tile.tpni-camp-tile--off .mud-typography { color: var(--mud-palette-text-disabled) !important; }
.tpni-camp-tile.tpni-camp-tile--off .tpni-tile-ic { opacity:.45; }
.tpni-camp-tile.tpni-camp-tile--off:hover { box-shadow:none; transform:none; }
.tpni-tile-ic { width:54px; height:54px; border-radius:14px; flex-shrink:0; display:grid; place-items:center;
                box-shadow:0 3px 8px rgba(24,30,60,.18); }
.tpni-tile-ic .mud-icon-root { font-size:1.7rem; }
.tpni-camp-tile .mud-typography-subtitle1 { font-size:1.06rem; }

/* ---------- Webinar readiness rows (WebinarStepsList) ----------
   The WHOLE ROW is the link (Nick 2026-07-25 — it used to highlight on click and do nothing). The
   right-hand "Change / Set it up / Open" is a LABEL, not a nested button: an <a> inside an <a> is
   invalid and the parser hoists it out. These rules are what keep it READING as the control it is. */
.mud-list-item[href] .tpni-step-go { color:var(--mud-palette-primary); opacity:.85; transition:opacity .12s; }
.mud-list-item[href]:hover .tpni-step-go { opacity:1; text-decoration:underline; }
.mud-list-item[href] .tpni-step-go .mud-typography { font-size:.8125rem; letter-spacing:.02em; }

/* ---------- Journey spine (the registration-journey stepper on /event-setup/registration) ---------- */
.tpni-journey-spine { display:flex; flex-direction:column; }
.tpni-jstep { display:flex; gap:16px; align-items:stretch; }
.tpni-jstep__rail { display:flex; flex-direction:column; align-items:center; flex:0 0 auto; }
.tpni-jstep__node { width:40px; height:40px; border-radius:12px; display:grid; place-items:center; flex:0 0 auto;
                    border:1px solid color-mix(in srgb, var(--tpni-jtone,#6b7280) 35%, transparent);
                    background:color-mix(in srgb, var(--tpni-jtone,#6b7280) 14%, transparent); }
.tpni-jstep__node .mud-icon-root { color:var(--tpni-jtone,#6b7280); }
.tpni-jstep__line { width:2px; flex:1 1 auto; min-height:12px; background:var(--mud-palette-lines-default); margin:6px 0; }
.tpni-jstep:last-child .tpni-jstep__line { display:none; }
.tpni-jstep__card { flex:1 1 auto; margin-bottom:14px; }
.tpni-tone-editable  { --tpni-jtone:var(--mud-palette-success); }
.tpni-tone-managed   { --tpni-jtone:var(--mud-palette-info); }
.tpni-tone-attention { --tpni-jtone:var(--mud-palette-warning); }
.tpni-tone-bespoke   { --tpni-jtone:var(--mud-palette-text-secondary); }
.tpni-tone-muted     { --tpni-jtone:var(--mud-palette-text-secondary); }

/* Support board: fixed column widths so every stage-group table aligns */
.tpni-support-grid table { table-layout: fixed; width: 100%; }
.tpni-support-grid td, .tpni-support-grid th { overflow: hidden; }

/* Campaign-context strip in the customer drawer (Nick 2026-07-21: "you get lost whether you are in
   a campaign or not"). Two states, visually distinct at a glance: IN a campaign carries the primary
   accent bar and the campaign's name; OUT (the all-campaigns dashboard) stays flat and muted so the
   drawer never implies a scope the page doesn't have. */
.tpni-camp-ctx { margin:0 12px 6px; padding:9px 12px; border-radius:8px;
                 background:var(--mud-palette-background-gray); border-left:3px solid transparent; }   /* 09-10: gray-lighter is a fixed #e0e0e0 and stayed light in dark mode */
.tpni-camp-ctx.is-in { border-left-color:var(--mud-palette-primary);
                       background:color-mix(in srgb, var(--mud-palette-primary) 8%, transparent); }
/* SR-00206 E2: was a hard-coded #8B93A8 (~3.6:1) at 9.5px — small AND washed out, and it did not
   follow the theme into dark mode either. Use the token the rest of the app's quiet text uses. */
.tpni-camp-ctx-label { font-size:9.5px; letter-spacing:.09em; text-transform:uppercase;
                       color:var(--mud-palette-text-secondary); font-weight:700; }
.tpni-camp-ctx.is-in .tpni-camp-ctx-label { color:var(--mud-palette-primary); }
.tpni-camp-ctx-name { font-size:13px; font-weight:650; line-height:1.25; margin-top:2px;
                      color:var(--mud-palette-text-primary); overflow-wrap:anywhere; }
.tpni-camp-ctx-hint { font-size:11.5px; color:var(--mud-palette-text-secondary); margin-top:2px; }
.tpni-camp-ctx-link { display:inline-block; margin-top:5px; font-size:11.5px; font-weight:600;
                      color:var(--mud-palette-primary); text-decoration:none; }
.tpni-camp-ctx-link:hover { text-decoration:underline; }


/* ------------------------------------------------------------------
   SR-00316 - highlight the required fields on the data-entry forms.

   MudBlazor's Required only produced a small asterisk on the label, which
   staff were not seeing at a glance on a 20-plus field form. This tints
   the input itself so the mandatory set reads as a group.

   Required comes from AddressScreenDetails.Required, per reg type, so the
   highlight is per screen and needs no separate configuration. Enforcement
   is unchanged - MudForm already blocked a save on a missing Required
   field, and this adds nothing to that.
   ------------------------------------------------------------------ */
.tpni-required-field .mud-input-control,
.tpni-required-field .mud-input {
    background-color: var(--tpni-required-bg, rgba(255, 193, 7, .10));
    border-radius: 4px;
}

/* The accent bar is what makes a required field findable while scanning a
   column of inputs; the tint alone is too quiet in dark mode. */
.tpni-required-field .mud-input-control {
    box-shadow: inset 3px 0 0 0 var(--mud-palette-warning);
    padding-left: 6px;
}

/* Make Mud's asterisk actually legible rather than a grey speck. */
.tpni-required-field .mud-input-label .mud-input-required,
.tpni-required-field .mud-input-label::after {
    color: var(--mud-palette-warning);
    font-weight: 700;
}

.tpni-required-legend {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--mud-palette-text-secondary);
    margin-bottom: 6px;
}

.tpni-required-legend .swatch {
    width: 12px;
    height: 12px;
    border-radius: 3px;
    background-color: rgba(255, 193, 7, .18);
    box-shadow: inset 3px 0 0 0 var(--mud-palette-warning);
}

/* ==== 2026-09-09 colour review (memory: tpni_console_color_review; sheet on Nick's Desktop) ====
   1. MudText Color="Color.Secondary" renders .mud-secondary-text = the palette SECONDARY (Events slate
      #7C9BB3, 2.7:1 on every screen), NOT TextSecondary. 704 call sites use it for captions, and the
      three "darken the grey" passes edited TextSecondary, which they never read. Route the class to the
      caption grey. `body` prefix on purpose: app.css loads BEFORE MudBlazor.min.css (App.razor), so an
      equal-specificity !important loses.
   2. Semantic text painted the BASE colour on its own 6% tint (text-variant chips, .tpni-card-chip,
      MudText/MudIcon Color.Warning...) or on the surface (outlined chips, text/outlined buttons): same hue
      on same hue, 2.5-3.7:1. The -darken slot is text-grade in BOTH palettes (lighter in dark), so one
      rule per colour serves both modes. Buttons deliberately without !important so :disabled still wins. */
body .mud-secondary-text { color: var(--mud-palette-text-secondary) !important; }
body .mud-warning-text { color: var(--mud-palette-warning-darken) !important; }
body .mud-success-text { color: var(--mud-palette-success-darken) !important; }
body .mud-info-text    { color: var(--mud-palette-info-darken) !important; }
body .mud-error-text   { color: var(--mud-palette-error-darken) !important; }
body .mud-chip-text.mud-chip-color-warning, body .mud-chip-outlined.mud-chip-color-warning, .tpni-card-chip.warn,
body .mud-button-text.mud-button-text-warning, body .mud-button-outlined.mud-button-outlined-warning { color: var(--mud-palette-warning-darken); }
body .mud-chip-text.mud-chip-color-success, body .mud-chip-outlined.mud-chip-color-success, .tpni-card-chip.ok,
body .mud-button-text.mud-button-text-success, body .mud-button-outlined.mud-button-outlined-success { color: var(--mud-palette-success-darken); }
body .mud-chip-text.mud-chip-color-info, body .mud-chip-outlined.mud-chip-color-info, .tpni-card-chip.info,
body .mud-button-text.mud-button-text-info, body .mud-button-outlined.mud-button-outlined-info { color: var(--mud-palette-info-darken); }
body .mud-chip-text.mud-chip-color-error, body .mud-chip-outlined.mud-chip-color-error, .tpni-card-chip.err,
body .mud-button-text.mud-button-text-error, body .mud-button-outlined.mud-button-outlined-error { color: var(--mud-palette-error-darken); }
body .mud-chip-text.mud-chip-color-primary, .tpni-card-chip.on, .search-card-reg,
body .mud-button-text.mud-button-text-primary, body .mud-button-outlined.mud-button-outlined-primary { color: var(--mud-palette-primary-darken); }
/* Primary as TEXT (MudText Color.Primary, MudLink, plain <a>): teal on the page ground is 4.37:1 since the
   ground dropped to #EEF1F1; the deep teal is 7.5:1 there and 8.3:1 on a card. Filled buttons, icons and
   chips keep the brand teal. */
body .mud-primary-text { color: var(--mud-palette-primary-darken) !important; }
/* MudButtonGroup paints its children from its own colour class (.mud-button-group-outlined-primary .mud-button-root),
   so the per-button rules above never reach them. The active member is filled-DEFAULT (grey ground, group colour
   text) and needs the shade too; only a filled-<colour> member (white on the colour) is left alone. */
body .mud-button-group-outlined.mud-button-group-override-styles.mud-button-group-outlined-primary .mud-button-root:not(.mud-button-filled-primary), body .mud-button-group-text.mud-button-group-override-styles.mud-button-group-text-primary .mud-button-root { color: var(--mud-palette-primary-darken); }
body .mud-button-group-outlined.mud-button-group-override-styles.mud-button-group-outlined-warning .mud-button-root:not(.mud-button-filled-warning), body .mud-button-group-text.mud-button-group-override-styles.mud-button-group-text-warning .mud-button-root { color: var(--mud-palette-warning-darken); }
body .mud-button-group-outlined.mud-button-group-override-styles.mud-button-group-outlined-success .mud-button-root:not(.mud-button-filled-success), body .mud-button-group-text.mud-button-group-override-styles.mud-button-group-text-success .mud-button-root { color: var(--mud-palette-success-darken); }
body .mud-button-group-outlined.mud-button-group-override-styles.mud-button-group-outlined-info .mud-button-root:not(.mud-button-filled-info), body .mud-button-group-text.mud-button-group-override-styles.mud-button-group-text-info .mud-button-root { color: var(--mud-palette-info-darken); }
body .mud-button-group-outlined.mud-button-group-override-styles.mud-button-group-outlined-error .mud-button-root:not(.mud-button-filled-error), body .mud-button-group-text.mud-button-group-override-styles.mud-button-group-text-error .mud-button-root { color: var(--mud-palette-error-darken); }

/* Search results in group order (2026-09-10): a related record indents under its primary; the primary's
   name is bold. The 2nd cell is Last name (the 1st is the checkbox). */
.tpni-group-member td:nth-child(2) { padding-left: 1.6rem; }
.tpni-group-member td:nth-child(2)::before { content: "\21B3\00a0"; color: var(--mud-palette-text-secondary); }
.tpni-group-primary td:nth-child(2), .tpni-group-primary td:nth-child(3) { font-weight: 600; }

/* ---- Sticky form footer: the page's single Save, pinned to the bottom of the card ----
   Replaces the header-Actions Save on Campaign Maintenance (Nick 2026-09-11 — a button at the far
   right of a full-width header row, above a 1400px card, belongs to nothing visually). */
.tpni-form-actions {
    position: sticky;
    bottom: 0;
    z-index: 3;
    background: var(--mud-palette-surface, #fff);
    border-top: 1px solid var(--mud-palette-lines-default, rgba(0,0,0,0.12));
    border-radius: 0 0 var(--tpni-radius-card, 10px) var(--tpni-radius-card, 10px);
    box-shadow: 0 -6px 16px -10px rgba(24,30,60,0.35);
}

/* Phone helpers (Nick 2026-09-15, "work on the mobile screens"): swap a long label for a short one. */
.tpni-show-xs { display: none; }
@media (max-width: 599px) {
    .tpni-hide-xs { display: none !important; }
    .tpni-show-xs { display: inline !important; }
}

/* Sales CRM headline numbers (SR-00508, Chris Brogan 2026-09-17): five EQUAL tiles that fill the row, so
   the strip lines up with the full-width cards above it instead of wrapping ragged at each tile's own
   min-width. Steps 5 -> 3 -> 2 -> 1 column so a narrow window, a phone or a folded dual-screen still gets
   whole tiles rather than a half-cut fifth one. minmax(0,1fr) is what stops a long caption widening a cell. */
.crm-kpi-row {
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    align-items: stretch;
}
@media (max-width: 1279px) { .crm-kpi-row { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 959px)  { .crm-kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 599px)  { .crm-kpi-row { grid-template-columns: 1fr; } }

/* SR-00533: a report's KPI tiles fill the row for any count - five included - and wrap to whole tiles when narrow. */
.cm-report-kpis {
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    align-items: stretch;
}
