/* ===== Self-hosted Inter (variable, SIL OFL — see fonts/inter-OFL.txt) ===== */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('fonts/inter-latin-wght-normal.woff2') format('woff2');
}

/* ===== Brand palette (from the POWERED UP! logo) ===== */
:root {
    /* 2026-09-18: darkened one step, #f56ec4 -> #f45bbc. The 2026-08-28 trial lightened #cf4a9b to
       #f56ec4, which measured 2.65:1 on white - every link in the app runs on this, and at that ratio
       small text is genuinely hard to read. This is a deliberate first step rather than a fix: 2.97:1,
       still short of the 4.5:1 AA wants for body text, and still recognisably the same pink.
       The two MUST move together - -rgb feeds the focus ring, and a mismatch shows as a ring in the
       old hue around a field in the new one. Roll back = #f56ec4 / 245, 110, 196. */
    --brand-pink: #f45bbc;
    --brand-pink-rgb: 244, 91, 188;
    --brand-pink-dark: #b23d86;
    --brand-pink-darker: #8f2f6b;
    --brand-pink-050: #fbeef6;
    --brand-charcoal: #4b4d52;
    --brand-charcoal-rgb: 75, 77, 82;
    --brand-ink: #33343a;

    /* ===== TRIAL 2026-09-17: the Purple palette =========================================================
       Values taken from the Purple admin template's own _variables.scss (primary #b66dff, success #1bcfb4,
       page #f2edf3, border #ebedf2), with the brand pink kept exactly as it was. The point is not to add a
       second loud colour but to give the pink somewhere to sit: violet is its neighbour, so the pink stops
       being the only saturated thing on a grey field and starts being the emphasis inside a palette.

       ROLLBACK is this block: restore the four values noted in the comments below and delete the violet
       and sidebar-ink tokens. Nothing outside :root and NavMenu.razor.css was changed for it. */
    --violet: #b66dff;
    --violet-deep: #7a3fb8;        /* carries text at AA on white; the link colour */
    --violet-050: #f3eaff;

    /* THE FRAME - two variants, one active. Everything else in the trial is identical between them, so
       this is a clean comparison of the frame alone.

       PLUM (active, the Purple route): the frame belongs to the violet family the links and the call
       actions now use, so the app reads as one palette rather than a pink accent on a grey box.

       GREY (commented, Corona's idea): a near-neutral dark panel. Pink is the only colour on it, which is
       what makes it pop hardest; the cost is that the frame says nothing about the rest of the palette.

       To switch, swap which pair is commented. The pre-trial light sidebar was #e2e2e2 -> #d8d8d8. */
    --sidebar-bg-top: #2f1f43;
    --sidebar-bg-bottom: #3f2a59;
    --sidebar-ink: #e9e2f2;
    --sidebar-ink-muted: #b6a8c9;

    /* --sidebar-bg-top: #1f2126;
       --sidebar-bg-bottom: #191b1f;
       --sidebar-ink: #e6e7ea;
       --sidebar-ink-muted: #9a9ca3; */
    --sidebar-hover-bg: rgba(255, 255, 255, 0.09);   /* TRIAL: new */
    --sidebar-hover-ink: #ffffff;                    /* TRIAL: new */

    --page-bg: #f2edf3;            /* TRIAL: was #f5f6f8 - Purple's own violet-biased grey */

    /* Bootstrap 5.3 variable overrides -> brand */
    --bs-primary: var(--brand-pink);
    --bs-primary-rgb: var(--brand-pink-rgb);
    /* TRIAL: was var(--brand-pink). Links are the most repeated coloured thing in the app, so pink-as-link
       is what made pink ordinary. Primary BUTTONS stay pink (--bs-primary above), which is the whole
       arrangement: violet for what you read, pink for what you press. */
    --bs-link-color: var(--violet-deep);
    --bs-link-color-rgb: 122, 63, 184;
    --bs-link-hover-color: var(--brand-charcoal);
    --bs-link-hover-color-rgb: 75, 77, 82;

    /* Calmer, easier-on-the-eyes typography */
    --bs-body-color: #474a52;
    --bs-body-line-height: 1.55;
    --bs-heading-color: var(--brand-ink);
    --bs-border-color: #ebedf2;   /* TRIAL: was #e7e8ec - Purple's border */

    /* TRIAL: Bootstrap's success green (#198754) surfaces where the act- tokens do not reach -
       .text-bg-success on the goal tracker's "Ahead" chip and on the "Just Created" / "saved" badges in
       the leads list. Pointing it at the turquoise retires the last of the green in one value rather than
       chasing each usage. */
    --bs-success: #0e8f7c;
    --bs-success-rgb: 14, 143, 124;
}

html, body {
    font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    background-color: var(--page-bg);
    color: #474a52;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    color: var(--brand-ink);
    font-weight: 600;
    letter-spacing: -0.01em;
}

h1, .h1 { font-size: 1.8rem; }
h2, .h2 { font-size: 1.5rem; }
h3, .h3 { font-size: 1.25rem; }

/* Calmer, roomier tables — the main fix for the "crowded" feel */
.table {
    --bs-table-color: #474a52;
    --bs-table-border-color: #ececf0;
    /* App-standard data-table type size. The list pages (deals / customers / leads / reconciliation)
       each used to copy `font-size: 0.85rem` into their own scoped CSS; making it the default here
       means every table — including new ones (Drops, the dashboard ledgers) — reads at the same size
       instead of falling back to Bootstrap's 1rem. A page can still override for a denser grid. */
    font-size: 0.85rem;
}
.table > :not(caption) > * > * {
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}
.table-sm > :not(caption) > * > * {
    padding-top: 0.4rem;
    padding-bottom: 0.4rem;
}
.table-light {
    --bs-table-bg: #f6f7f9;
    --bs-table-color: #6b7079;
}
thead th {
    font-weight: 600;
}

.text-muted {
    color: #767a83 !important;
}

/* Tabular (fixed-width) figures so amounts line up cleanly in columns */
.table td.text-end,
.table th.text-end,
.tabular-nums {
    font-variant-numeric: tabular-nums;
}

/* App-wide edge spacing: when a table sits flush in a card — directly, or in a
   no-padding body (card-body p-0) — nudge the outer columns in so text isn't jammed
   against the card border. Cards whose body already has padding are unaffected. */
.card > .table > thead > tr > th:first-child,
.card > .table > tbody > tr > td:first-child,
.card > .table > tfoot > tr > td:first-child,
.card-body.p-0 .table > thead > tr > th:first-child,
.card-body.p-0 .table > tbody > tr > td:first-child,
.card-body.p-0 .table > tfoot > tr > td:first-child {
    padding-left: 1rem;
}

.card > .table > thead > tr > th:last-child,
.card > .table > tbody > tr > td:last-child,
.card > .table > tfoot > tr > td:last-child,
.card-body.p-0 .table > thead > tr > th:last-child,
.card-body.p-0 .table > tbody > tr > td:last-child,
.card-body.p-0 .table > tfoot > tr > td:last-child {
    padding-right: 1rem;
}

/* Dashboard "page-flow" tables (Pending / Open Leads / Renewals): the section scrolls with the
   page and its column header pins just under the sticky top bar — no inner scrollbar / no scroll trap. */
.pageflow-sticky > thead > tr > th {
    position: sticky;
    top: var(--pf-hdr-top, 3.5rem);
    z-index: 2;
    background: var(--bs-table-bg, #f8f9fa);
}

/* Height of the fixed top chrome that page-level sticky bars must clear. Desktop = the single
   3.5rem top bar; mobile stacks the nav banner (3.5rem) + the New Lead / Search action bar (3.5rem).
   Everything that pins "under the top bar" reads this one value so the two layouts can't drift. */
:root { --app-chrome-top: 3.5rem; }

/* Dense data text: 11.9px, which is what a table cell carrying .small computes to (.table's 0.85rem
   x Bootstrap's 0.875em). The leads table on the customer page reads at exactly this, and it is the
   size to match when a block wants to sit beside one of these lists.

   It is written as one absolute value rather than left to compound, because compounding is what made
   the same content render at four different sizes: .small on a cell inside a 0.85rem table is 11.9px,
   but .small on a list-group item is 14px, and .small inside an already-dense card is 11.9px again by
   a different route. Anything that should match the lists takes this variable and stops there. */
:root { --data-sm: 0.745rem; }
@media (max-width: 640.98px) { :root { --app-chrome-top: 7rem; } }

/* Full-page list filter bar (/leads, /deals): stays pinned under the top bar while rows scroll.
   The table's header then pins just below it — set --pf-hdr-top on the table to the filter's height. */
.pf-filter {
    position: sticky;
    top: var(--app-chrome-top, 3.5rem);
    z-index: 3;
    background: var(--page-bg, #fff);
    padding-top: 0.25rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

/* On the dashboard the filter sits inside a white section card (not over the grey page), so drop the
   page-grey fill and match the card — the way the Upcoming Renewals toolbar reads. Still opaque for sticky. */
.dash-section .pf-filter {
    background: var(--bs-body-bg, #fff);
}

/* Mobile: compact 2-column grid — Filter + Mine on row 1, Salesperson + row-count on row 2.
   Applied to EVERY .pf-filter (leads, deals/contracts, customers, dashboard) so the filter bar reads
   the same on every list page. !important overrides the d-flex / gap-3 / align-items-end utilities. */
@media (max-width: 640.98px) {
    /* Tidy stacked layout: the Filter and each dropdown field (.pf-field — salesperson / status) get
       their own full-width row; the compact "Mine" toggle + row-count share the last row. */
    .pf-filter {
        display: grid !important;
        grid-template-columns: 1fr auto;
        gap: 0.5rem 0.75rem !important;
        align-items: end !important;
    }

    .pf-filter > .ws-search,
    .pf-filter > .pf-field {
        grid-column: 1 / -1;
    }

    .pf-filter > .ws-search { order: 1; }
    .pf-filter > .pf-field { order: 2; }
    .pf-filter > .align-self-center:not(.ms-auto) { order: 3; }   /* deals quick-filter badges */

    .pf-filter > .form-check {
        order: 3;
        justify-self: start;
        align-self: end !important;
        margin-left: 0 !important;
    }

    .pf-filter > .ms-auto {
        order: 4;
        justify-self: end;
        align-self: end !important;
        margin-left: 0 !important;
    }
}

/* Centered full-page shell (login) with a branded backdrop */
.centered-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    /* Login keeps the original deep charcoal backdrop (independent of the lightened sidebar tokens). */
    background: linear-gradient(135deg, #3d3f44 0%, #2c2d31 100%);
}

.login-card {
    width: 100%;
    max-width: 23rem;
    background: #fff;
    border-radius: 0.9rem;
    box-shadow: 0 20px 55px rgba(0, 0, 0, 0.35);
    padding: 2rem 2rem 1.75rem;
}

.login-logo {
    width: 210px;
    max-width: 82%;
    height: auto;
}

/* Inline-edit field (save-on-blur with a ✓/⚠ flash) */
.inline-edit {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.inline-edit > input,
.inline-edit > textarea,
.inline-edit > select {
    flex: 1 1 auto;
    min-width: 0;
}

.inline-edit-flash {
    flex: 0 0 0.9rem;
    text-align: center;
    font-size: 0.9rem;
    line-height: 1;
}

/* Dashboard metric tiles */
.metric-tile {
    background: #fff;
    border: 1px solid #e8e9ee;
    border-radius: 0.6rem;
    cursor: pointer;
    padding: 0.8rem 1rem;
    height: 100%;
    position: relative;   /* anchor for .metric-badge */
    transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}

.metric-tile:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(16, 17, 20, 0.10);
    border-color: var(--brand-charcoal, #4b4d52);
}

.metric-tile .metric-value {
    font-size: 1.7rem;
    font-weight: 700;
    line-height: 1.05;
    color: var(--brand-charcoal, #4b4d52);
    font-variant-numeric: tabular-nums;
}

/* Next Month tile: the commission figure reads green (just the number — border/label unchanged). */
.metric-tile .metric-value-green {
    color: var(--bs-success, #198754);
}

.metric-tile .metric-label {
    font-size: 0.8rem;
    color: #767a83;
    margin-top: 0.1rem;
}

/* Attention badge in a tile's top-right corner (e.g. due/overdue follow-ups on the Open leads tile) */
.metric-badge {
    position: absolute;
    top: 0.5rem;
    right: 0.55rem;
    font-size: 0.7rem;
    font-weight: 600;
    padding: 0.2em 0.5em;
}

a, .btn-link {
    color: var(--brand-pink);   /* default links = new brand pink app-wide (was the darker #b23d86) */
}

a:hover, .btn-link:hover {
    color: var(--brand-charcoal);   /* calm to charcoal on hover, like .cust-name / contact links */
}

/* A button that reads as inline text: it opens a modal rather than navigating, so it must not be an <a>,
   but it should not wear button chrome either. font: inherit is the point - these sit mid-sentence, so
   they take the size of the line they are in rather than .btn's own.

   It lives HERE, directly under the rule it has to match, because it did not: as .cx-linkbtn in a scoped
   sheet it still carried --brand-pink-dark (#b23d86, the pink --brand-pink replaced) and underlined on
   hover, so the two property-manager links on the customer page were the only links in the app wearing
   the old colour and a decoration nothing else has. */
.linkbtn {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--brand-pink);
    cursor: pointer;
}

.linkbtn:hover {
    color: var(--brand-charcoal);
    text-decoration: none;
}

/* Primary buttons + badges -> brand pink (Bootstrap 5.3 component vars) */
/* TRIAL 2026-09-18 - button radius from the theme ($btn-border-radius: .1875rem = 3px). Part of the same
   2-3-5px family as the inputs and cards; Bootstrap's default 6px is what made everything read soft. */
.btn {
    border-radius: 0.1875rem;
}

/* Fills with --brand-pink, as it always has. Thomas's call, 2026-09-18: white on #f56ec4 measures
   2.65:1, under AA, and he prefers the lighter pink anyway - it is the brand. Recorded here so the next
   person finds a decision rather than an oversight. Worth knowing the darker pink was never the fix for
   a regression: white on the pre-trial #cf4a9b was 4.11:1, so this button has never passed.
   If it is ever revisited, dark text on the same pink keeps the colour and fixes the reading. */
.btn-primary {
    --bs-btn-bg: var(--brand-pink);
    --bs-btn-border-color: var(--brand-pink);
    --bs-btn-hover-bg: var(--brand-pink-dark);
    --bs-btn-hover-border-color: var(--brand-pink-dark);
    --bs-btn-active-bg: var(--brand-pink-dark);
    --bs-btn-active-border-color: var(--brand-pink-dark);
    --bs-btn-disabled-bg: var(--brand-pink);
    --bs-btn-disabled-border-color: var(--brand-pink);
    --bs-btn-focus-shadow-rgb: var(--brand-pink-rgb);
}

.btn-outline-primary {
    --bs-btn-color: var(--brand-pink);
    --bs-btn-border-color: var(--brand-pink);
    --bs-btn-hover-bg: var(--brand-pink);
    --bs-btn-hover-border-color: var(--brand-pink);
    --bs-btn-active-bg: var(--brand-pink-dark);
    --bs-btn-active-border-color: var(--brand-pink-dark);
    --bs-btn-focus-shadow-rgb: var(--brand-pink-rgb);
}

/* Focus rings -> brand pink */
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem rgba(var(--brand-pink-rgb), 0.55);
}

.form-control:focus, .form-select:focus {
    border-color: var(--brand-pink);
    box-shadow: 0 0 0 0.2rem rgba(var(--brand-pink-rgb), 0.25);
}

/* TRIAL 2026-09-18 — inputs, from the theme (shared/components/_forms.scss + light/_variables.scss).
   $input-border-radius: 2px and $input-placeholder-color: #c9c8c8. The square corner is what makes their
   fields read as crisp rather than soft, and it is the same 2-3-5px family as their buttons and cards.

   NOT taken: the theme does `.form-control:focus { outline: none }` with nothing put back. That is fine
   on a showcase and an accessibility bug in an app somebody keyboards through all day, so our pink focus
   ring above stays exactly as it is. */
.form-control,
.form-select {
    border-radius: 2px;
}

.form-control::placeholder {
    color: #c9c8c8;
}

.form-check-input:checked {
    background-color: var(--brand-pink);
    border-color: var(--brand-pink);
}

.form-check-input:focus {
    border-color: var(--brand-pink);
    box-shadow: 0 0 0 0.2rem rgba(var(--brand-pink-rgb), 0.25);
}

/* TRIAL 2026-09-18 — cards, from the purchased theme (shared/components/_cards.scss).
   Theirs is: .card { border: 0 } with no shadow at all. A card is white, the page is #f2edf3, and that
   difference alone is the edge — which is most of why the demo reads as clean rather than as a stack of
   boxed-in panels. We had a border AND a shadow AND a tinted header doing the same separating job three
   times over.
   Radius 5px is $card-border-radius: .3125rem, theirs exactly.
   Roll back = restore border-color #e8e9ee, radius 0.55rem, the shadow and the #fbfbfc header. */
.card {
    border-color: #e8e9ee;
    border-radius: 0.3125rem;
    box-shadow: none;
}

/* The header keeps a rule, which the theme does not have. Their cards hold one thing; ours hold a heading
   with actions in it over a table, and with no border and no tint there was nothing at all telling the
   heading from the first row. This is the one place the extra edge earns its keep. */
.card-header {
    background-color: #fbfbfc;
    border-bottom-color: #edeef1;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

/* Validation outlines are intentionally gentle so a form with several required fields doesn't glare —
   the field the cursor is in still gets the strong brand-pink focus ring on top, so the active field
   stands out from the rest. */
.valid.modified:not([type=checkbox]) {
    outline: 1px solid rgba(38, 176, 80, 0.4);
}

.invalid {
    outline: 1px solid rgba(229, 0, 0, 0.4);
}

.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;
}

/* ============================================================================
   Global form field hierarchy (lifted from the deal screen, app-wide):
   quiet uppercase labels vs. plain-dark values in subtle-fill "data cells".
   ============================================================================ */
.form-label {
    text-transform: uppercase;
    font-size: 0.66rem;
    letter-spacing: 0.06em;
    font-weight: 600;
    color: var(--bs-secondary-color);
    opacity: 0.9;
    margin-bottom: 0.05rem;
}

.form-control,
.form-select {
    font-weight: 400;
    color: var(--bs-body-color);
    background-color: var(--bs-tertiary-bg);
    border-radius: 0.6rem;      /* bubblier corners */
    padding-left: 0.9rem;       /* more breathing room around the text */
}

/* form-controls get the extra room on both sides; selects keep their right-side caret space. */
.form-control {
    padding-right: 0.9rem;
}

/* Compact (-sm) fields used densely across the deal / lead screens get a proportional bump. */
.form-control-sm,
.form-select-sm {
    border-radius: 0.5rem;
    padding-left: 0.72rem;
}

.form-control-sm {
    padding-right: 0.72rem;
}

.form-control:focus,
.form-select:focus {
    background-color: var(--bs-body-bg);
}

.form-control::placeholder {
    font-weight: 400;
    color: var(--bs-secondary-color);
    opacity: 0.6;
}

/* TRIAL 2026-09-18 - badge base, from the theme (shared/components/_badges.scss):
       border-radius: .125rem;  font-size: 11px;  font-weight: initial;  line-height: 1;
   We had no base rule at all, so badges ran on Bootstrap defaults: 700 weight, 6px radius, and a size
   that drifted with whatever it sat in (.75em of a 0.85rem cell is not .75em of a 1rem heading). Bold is
   the big one - a contract list is mostly "Ended", and a column of bold grey pills shouted at you about
   the least interesting fact on the page.

   IT MUST STAY ABOVE THE COMPONENTS BELOW. It is a base, and every rule under it is a single class too,
   so cascade order is the only thing deciding who wins. Sitting after .contract-pill it silently undid
   that rule's font-weight:600 and font-size:0.78rem - both of which are deliberate, and one of which
   carries a comment saying exactly why.

   NOT taken: their padding (.375rem .5625rem, roughly 6px/9px). That is sized for a showcase card, and in
   our table cells it would add about 6px to every row. Ours stays. */
.badge {
    border-radius: 0.125rem;
    font-size: 11px;
    font-weight: 400;
    line-height: 1;
}

/* Outline badges sit on TRANSPARENT rather than #fff, which is the theme's badge-outline-variations mixin
   (color + border, no background). On a white card it looks identical; on a hovered row it takes the row
   shade instead of punching a white hole in it. */

/* Contract-ID pill — a clickable outline pill (fills pink on hover) used for the deal link on the
   /deals list (table + mobile card) and the dashboard Pending Contracts table. Pair with .badge.rounded-pill.
   Shows the Contract ID, or "View Deal" when the deal has none yet. */
.contract-pill {
    text-decoration: none;
    border: 1px solid var(--bs-border-color, #dee2e6);   /* subtle gray border; pink text (fills pink on hover) */
    background: var(--bs-body-bg, #fff);
    color: var(--brand-pink, #cf4a9b);
    font-weight: 600;
    /* Consistent size everywhere: the deals list bumps its badges to 0.78rem, but the dashboard Pending
       Contracts table (0.85rem font) would otherwise shrink the pill to ~0.64rem via .badge's 0.75em. */
    font-size: 0.78rem;
}

.contract-pill:hover {
    background: var(--brand-pink, #cf4a9b);
    border-color: var(--brand-pink, #cf4a9b);
    color: #fff;
}

/* PMVI flag under the Submitted date on the dashboard Pending Contracts + the Contracts list (replaces the
   old PMVI column / red row tint). "Late" = a priority move-in stalled at the supplier > 7 days; styled like
   the leads "EXPIRED" chip so it reads as the row's action cue without tinting the whole row. */
.pmvi-flag {
    display: inline-block;
    margin-top: 0.1rem;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
}
.pmvi-flag--info {
    color: var(--bs-secondary, #6c757d);
}
.pmvi-flag--late {
    color: #b02a37;
    background: var(--bs-danger-bg-subtle, #f8d7da);
    border-radius: 0.25rem;
    padding: 0 0.35rem;
}

/* Customer name / link — the bold anchor of each customer name app-wide (Leads, Deals, Pending Contracts,
   Customers, reconciliation, …; desktop tables + mobile cards). One shared definition so every surface stays
   consistent: bold, no underline at rest, underline on hover. Colour stays per-context: tables render it as
   the link colour (pink); cards recolour it charcoal via their own rules. */
.cust-name {
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
    /* TRIAL: was var(--brand-pink). A customer name is the most repeated link in the app - on the dashboard
       alone there are dozens - so this is where pink was being spent fastest. */
    color: var(--violet-deep, #7a3fb8);
}

.cust-name:hover {
    text-decoration: none;
    color: var(--brand-charcoal, #4b4d52);   /* calm to charcoal on hover, no underline */
}

/* When a customer-name link is the text of a heading (e.g. the deal page title), keep the heading's own
   size/weight and only borrow .cust-name's no-underline + hover-underline behaviour. */
:is(h1, h2, h3, h4, h5, h6) a.cust-name {
    font-size: inherit;
    font-weight: inherit;
}

/* ===== One hover, everywhere =====
   Every row a person can point at shades the same amount, whatever it is built from. Before this there
   were three different answers on one page: the contracts table carried Bootstrap .table-hover (whose
   default --bs-table-hover-bg is rgba(0,0,0,.075) - visibly darker), the leads table had only the rule
   below, and Activity, People, Attachments and Locations are list-groups, which had no hover at all.
   Seven tables across the app carry .table-hover, so this was never a customer-page problem. */

/* 1. Neutralise Bootstrap's own darker hover, so it no longer matters whether a table was given the
      .table-hover class. */
.table {
    --bs-table-hover-bg: var(--bs-tertiary-bg, #f8f9fa);
    --bs-table-hover-color: inherit;
}

/* 2. Every table row. Grouped tables (Deals / Pending / Renewals) additionally hover the whole group via
      their own scoped rules, which layer on top with this same colour. */
.table > tbody > tr:hover > td {
    background: var(--bs-tertiary-bg, #f8f9fa);
}

/* 3. List-group rows inside a card - Activity, People, Attachments, Locations. Excludes
      .list-group-item-action, which is a genuine button (the manager and merge pickers) and keeps
      Bootstrap's own stronger feedback. */
.card .list-group-item:not(.list-group-item-action):hover {
    background: var(--bs-tertiary-bg, #f8f9fa);
}

/* Contract status badges — the one palette behind ContractStages.BadgeClass(), used by every surface that
   shows a contract's status (deals list, contract page, customer page, dashboard).
   Not started = outlined (white fill, coloured border + text): Not Signed (primary blue), Submitted (grey),
   Confirmed (green). Live/terminal = solid: Flowing (green), Pending Drop (amber), Dropped (red),
   Ended (grey) — those four use Bootstrap's own text-bg-* utilities. The two greens are deliberate:
   outline = confirmed but not started, solid = flowing today. */
/* Not the brand pink, and no longer Bootstrap blue either: a status says what a contract IS, and a pink
   one would read as something to click. Not Signed is the quietest of the set because nothing has
   happened to it yet. */
/* TRIAL: the contract stages, re-derived into the palette. They are still a vocabulary of their own - each
   state has to stay tellable from the others at a glance - but they now run ALONG the lifecycle instead of
   being six unrelated hues: quiet grey while nothing has happened, violet once it is with the supplier,
   turquoise outline when the supplier says yes, solid turquoise once the power is flowing. Amber and red
   are untouched, because a warning has to look like one.

   Was: not-signed #0d6efd/#0a58ca (Bootstrap blue), submitted #8c9199/#5b6068, confirmed #4f9d84/#256b55,
   flowing solid #26775f. */
/* The two outline badges keep hand-picked border colours. The --act-*-border tokens are defined to sit
   AROUND A TINT, and on white they all but vanish: --act-neutral-border is 1.17:1 against the page, so
   the badge loses its shape and reads as loose coloured text. These are the values they had before the
   families were adopted, kept deliberately. The text colours ARE tokens - only the line is not. */
.badge-not-signed {
    background-color: transparent;
    border: 1px solid #b6aec4;
    color: var(--act-neutral);
}

.badge-submitted {
    background-color: transparent;
    border: 1px solid var(--violet, #b66dff);
    color: var(--violet-deep, #7a3fb8);
}

.badge-confirmed {
    background-color: transparent;
    border: 1px solid #7fd9c7;
    color: var(--act-positive-text);
}

/* Flowing is a TINT, not a solid. It was solid --act-positive-TEXT (#0b7a69) used as a fill - a colour
   that exists to be read ON the tint, pressed into service as the tint itself, which is why it looked
   like a green from a different palette. It could not simply become --act-positive (#0e8f7c): white on
   that is 4.01:1 and fails AA, and it failed before anyone measured it too, because bold text under
   18.66px is still "normal text" to WCAG. The darker green was the fix and the off-palette hue was the
   price.

   As a tint the problem does not arise: --act-positive-text ON --act-positive-bg is the pairing those
   two tokens were defined for, it measures 5.25:1, and it is the same green as every positive button in
   the app. Flowing is also the status most contracts sit in for years - a column of solid green shouted
   about the least surprising thing on the page. */
.badge-flowing {
    background-color: var(--act-positive-bg);
    border: 1px solid var(--act-positive-border);
    color: var(--act-positive-text);
}

/* Pending drop: a tint too. It is a warning, not an outcome - the drop has been notified, not confirmed,
   and the Drops page is where it gets settled. */
.badge-pendingdrop {
    background-color: var(--act-warning-bg);
    border: 1px solid var(--act-warning-border);
    color: var(--act-warning-text);
}

/* The ONE solid badge in the set, and it stays solid on purpose. Everything else here reports where a
   contract has got to; this one reports money that stopped arriving, and it is the only status anybody
   needs to see from across the room. Making everything solid made none of it urgent. */
.badge-dropped {
    background-color: var(--act-danger);
    border: 1px solid var(--act-danger);
    color: #fff;
}

.badge-ended {
    background-color: var(--act-neutral-bg);
    border: 1px solid var(--act-neutral-border);
    color: var(--act-neutral);
}

/* "Contacted today" cue — replaces the date wherever call history is shown (open leads, the customers
   list, both call-history modals, the customer activity feed, the contract call log). Rendered by
   Components/ContactDate.razor; the actual date moves to the tooltip.
   Soft yellow rather than a solid fill: it has to sit quietly inside dense tables and note lines, and
   green is already spoken for by the contract-status badges. */
.today-pill {
    display: inline-block;
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
    background-color: #fff3cd;
    border: 1px solid #ffe08a;
    color: #664d03;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1.4;
    white-space: nowrap;
    vertical-align: baseline;
}

/* ===== Action colours =====
   Chosen 2026-08-31, re-derived 2026-09-17 for the Purple trial: calling is violet (it was a warm orange,
   the hue that fought the pink hardest), positive is a darkened turquoise off Purple's #1bcfb4 (it was a
   muted teal-green), and danger stays the dusty coral-red — it is used sparingly and has to shout.
   Bootstrap's own hues are tuned for a white page with black text; this app's canvas is a violet-biased
   grey with a loud pink brand, so those defaults read harsh next to everything around them.

   Each role has a tint (resting fill), a border, a text colour, and a solid for the hover fill.
   Every pairing clears WCAG AA: text on tint 5.2-5.7:1, white on solid 4.8-5.4:1.
   Use these instead of hardcoding hues, so a future palette change is one edit here. */
:root {
    /* Orange #F3722C and green #43AA8B — the siblings of the Red Salsa red below, from the same
       palette. Both are too light to carry text or white-on-fill, so each keeps the hue and steps
       down in lightness: text 6.0:1 and 6.3:1 on white, white-on-solid 5.0:1 and 5.4:1. */
    /* TRIAL: the call family was orange (#b8500f / #fdf0e6 / #f7ddc6 / #a3480f / #fbe4cf / #eec49b). Orange
       is the hue that fought hardest with the pink - close enough to clash, far enough to look accidental.
       As violet it joins the family and the screen drops from six hues to four. */
    --act-call: #7a3fb8;
    --act-call-bg: #f3eaff;
    --act-call-border: #e2cdf8;
    --act-call-text: #6b34a5;
    --act-call-bg-hover: #ebddfd;
    --act-call-border-hover: #cfb0f2;

    /* TRIAL: was a forest green (#26775f / #ecf7f3 / #d2e9e1 / #256b55 / #dcefe8 / #b3d8ca). Purple's
       success is turquoise #1bcfb4, too light to carry text, so these are darkened from it. */
    --act-positive: #0e8f7c;
    --act-positive-bg: #e6faf6;
    --act-positive-border: #bdf0e6;
    --act-positive-text: #0b7a69;
    --act-positive-bg-hover: #d6f5ef;
    --act-positive-border-hover: #9ee7d8;

    /* Red Salsa #F94144 family — kept as the character, darkened where it has to carry text:
       #F94144 itself is only 3.6:1 on white, so text uses #b3272a (6.3:1) and the hover fill #d3363a (4.8:1). */
    --act-danger: #d3363a;
    --act-danger-bg: #fdecec;
    --act-danger-border: #f7d3d4;
    --act-danger-text: #b3272a;
    --act-danger-bg-hover: #fbdcdd;
    --act-danger-border-hover: #f0b6b8;

    /* Bright Sun #FAC846 family — the palette sibling used by warning actions; pairs with the
       "Today" pill, which is the same yellow. Dark text, never white. */
    --act-warning: #f4c14e;
    --act-warning-bg: #fff6e0;
    --act-warning-border: #f6dfa8;
    --act-warning-text: #7a5b00;
    --act-warning-bg-hover: #ffefc9;
    --act-warning-border-hover: #efd189;

    --act-neutral: #5b6068;
    --act-neutral-bg: #f4f5f7;
    --act-neutral-border: #dfe1e6;
    --act-neutral-bg-hover: #eaecef;
    --act-neutral-border-hover: #cbcfd6;
}

/* ===== Semantic action BUTTONS adopt the action tokens =====
   Bootstrap's own danger/success/warning are tuned for a white page with black text and read harsh on
   this canvas. Redefining the variants' CSS variables sweeps every action button in the app — deals,
   customers, dashboard, drops, reconciliation, the modals — with no component edits.

   Outline variants become tinted the way the lead actions are: soft wash at rest, one notch deeper on
   hover, text never inverts. Solid variants stay solid (a destructive primary action should look like
   one) but take the palette's hue and darken on hover instead of lightening.

   NOT touched: .btn-primary (brand pink), .btn-outline-secondary (already quiet), and every badge —
   contract status is a separate vocabulary that says what something IS, not what a click does. */
.btn-outline-danger {
    --bs-btn-color: var(--act-danger-text);
    --bs-btn-bg: var(--act-danger-bg);
    --bs-btn-border-color: var(--act-danger-border);
    --bs-btn-hover-color: var(--act-danger-text);
    --bs-btn-hover-bg: var(--act-danger-bg-hover);
    --bs-btn-hover-border-color: var(--act-danger-border-hover);
    --bs-btn-active-color: var(--act-danger-text);
    --bs-btn-active-bg: var(--act-danger-bg-hover);
    --bs-btn-active-border-color: var(--act-danger);
    --bs-btn-disabled-color: var(--act-danger-text);
    --bs-btn-disabled-border-color: var(--act-danger-border);
}

.btn-danger {
    --bs-btn-bg: var(--act-danger);
    --bs-btn-border-color: var(--act-danger);
    --bs-btn-hover-bg: var(--act-danger-text);
    --bs-btn-hover-border-color: var(--act-danger-text);
    --bs-btn-active-bg: var(--act-danger-text);
    --bs-btn-active-border-color: var(--act-danger-text);
    --bs-btn-disabled-bg: var(--act-danger);
    --bs-btn-disabled-border-color: var(--act-danger);
}

.btn-outline-success {
    --bs-btn-color: var(--act-positive-text);
    --bs-btn-bg: var(--act-positive-bg);
    --bs-btn-border-color: var(--act-positive-border);
    --bs-btn-hover-color: var(--act-positive-text);
    --bs-btn-hover-bg: var(--act-positive-bg-hover);
    --bs-btn-hover-border-color: var(--act-positive-border-hover);
    --bs-btn-active-color: var(--act-positive-text);
    --bs-btn-active-bg: var(--act-positive-bg-hover);
    --bs-btn-active-border-color: var(--act-positive);
}

.btn-success {
    --bs-btn-bg: var(--act-positive);
    --bs-btn-border-color: var(--act-positive);
    --bs-btn-hover-bg: var(--act-positive-text);
    --bs-btn-hover-border-color: var(--act-positive-text);
    --bs-btn-active-bg: var(--act-positive-text);
    --bs-btn-active-border-color: var(--act-positive-text);
    --bs-btn-disabled-bg: var(--act-positive);
    --bs-btn-disabled-border-color: var(--act-positive);
}

.btn-outline-warning {
    --bs-btn-color: var(--act-warning-text);
    --bs-btn-bg: var(--act-warning-bg);
    --bs-btn-border-color: var(--act-warning-border);
    --bs-btn-hover-color: var(--act-warning-text);
    --bs-btn-hover-bg: var(--act-warning-bg-hover);
    --bs-btn-hover-border-color: var(--act-warning-border-hover);
    --bs-btn-active-color: var(--act-warning-text);
    --bs-btn-active-bg: var(--act-warning-bg-hover);
    --bs-btn-active-border-color: var(--act-warning);
}

.btn-warning {
    --bs-btn-color: var(--act-warning-text);
    --bs-btn-bg: var(--act-warning);
    --bs-btn-border-color: var(--act-warning);
    --bs-btn-hover-color: var(--act-warning-text);
    --bs-btn-hover-bg: var(--act-warning-border-hover);
    --bs-btn-hover-border-color: var(--act-warning-border-hover);
    --bs-btn-active-color: var(--act-warning-text);
    --bs-btn-active-bg: var(--act-warning-border-hover);
}

/* The brand-pink outline button gets the same tinted treatment as the semantic ones — it was the last
   action variant still rendering transparent, which made it read as a different kind of control (most
   visibly the Regenerate button in the contract's Commission Schedule header). */
.btn-outline-primary {
    --bs-btn-color: var(--brand-pink-dark);
    --bs-btn-bg: var(--brand-pink-050);
    --bs-btn-border-color: #f2c2e0;
    --bs-btn-hover-color: var(--brand-pink-darker);
    --bs-btn-hover-bg: #f9e2f1;
    --bs-btn-hover-border-color: #eaa8d1;
    --bs-btn-active-color: var(--brand-pink-darker);
    --bs-btn-active-bg: #f9e2f1;
    --bs-btn-active-border-color: var(--brand-pink);
    --bs-btn-disabled-color: var(--brand-pink-dark);
    --bs-btn-disabled-bg: var(--brand-pink-050);
    --bs-btn-disabled-border-color: #f2c2e0;
}

/* Required-field marker on form labels. Uses the danger text colour rather than a raw red so it sits in
   the same palette as everything else, and carries a title so hovering says "Required" — an asterisk on
   its own is a convention people have to already know. */
.req {
    color: var(--act-danger-text, #b3272a);
    font-weight: 700;
    cursor: help;
}

/* The dashboard stacks three sticky layers: the tab strip, each panel filter bar, and that panel table
   header, each pinning below the sum of the ones above it. The heights are measured at runtime (see the
   script in App.razor) — the values here are only the fallback if that never runs, and were correct at
   one window size only. Each layer is pulled a pixel past the one above it (-1px, then -2px) so the
   layers overlap instead of leaving a hairline seam for rows to scroll through. */
:root { --dash-tabs-h: 6.25rem; }
@media (max-width: 640.98px) { :root { --dash-tabs-h: 4.4rem; } }   /* measured: the chip strip renders 67px */

.dash-section .pf-filter {
    top: calc(var(--app-chrome-top, 3.5rem) + var(--dash-tabs-h) - 1px);
}

/* Each panel pins its table header below whatever filter bar IT has — which is not the same for all
   three. Pending and Recent Confirms have none (0), the leads workspace has its pf-filter (4rem), and
   Upcoming Renewals has its own taller toolbar (73px). One shared 4rem left the first two floating a
   filter-height too low and the renewals header detached from its toolbar. */
.dash-section { --dash-filter-h: 0rem; }
.dash-section.sec-leads { --dash-filter-h: 4.4rem; }      /* measured: pf-filter renders 70px */
.dash-section.sec-renewals { --dash-filter-h: 4.6rem; }   /* measured: exp-toolbar renders 73px */

.dash-section .pageflow-sticky > thead > tr > th {
    top: calc(var(--app-chrome-top, 3.5rem) + var(--dash-tabs-h) + var(--dash-filter-h) - 2px);
}

/* ===== Shared row-action button =====
   The compact tinted action button used in list rows, off the --act-* tokens. Matches the open-leads
   actions (.ll-action), generalised so other lists can use the same control — the renewals rows do.
   Hover deepens the tint; nothing inverts. */
.act-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.28rem;
    /* A button is never narrower than its label. As a flex item in a card header it would otherwise
       shrink on a phone and clip its own text - "Log Call" arrived as "Ca", "Not Signed" as "Not Signe".
       Nothing overflowed the page; the boxes fit and the words inside them did not. */
    flex-shrink: 0;
    white-space: nowrap;
    padding: 0.15rem 0.65rem;
    border: 1px solid transparent;
    border-radius: 0.1875rem;   /* TRIAL 2026-09-18: theme $btn-border-radius (.1875rem). Was var(--bs-border-radius, 0.375rem). */
    font-size: 0.78rem;
    line-height: 1.2;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

/* NO OUTLINE ON ANY OF THEM. This used to be a rule about density: --action dropped its border because it
   repeats three-to-a-row down an eighty-three-row list, and the other four kept one because they appear
   once or twice a screen. The reasoning was fine and the result was not - "Change" (--action) and "Clear"
   (--danger) sit side by side on the customer card, so the rule put two buttons of the same size and
   shape next to each other with only one of them outlined, which reads as a mistake rather than a scope.

   The theme settles it. button-inverse-variant (shared/mixins/_buttons.scss) is:
       background-color: rgba($color, .2);  border-color: rgba($color, 0);  color: $color;
   rgba(_, 0) on EVERY variant, not just one. --action was following the theme; these four were not.

   The border-width stays so the box never changes size between rest and hover; only the colour goes.

   Log Call is defined here because the shared LeadActions component uses .act-btn: the leads list used to
   carry its own scoped .ll-action-call, so this variant was missing and Log Call rendered untinted. */
/* They INVERT on hover, all of them, the way --action does below: tint at rest, solid fill with white
   text under the cursor. That is the rest of button-inverse-variant -
       &:hover { color: $color-hover (#fff); background: $color; border-color: $color; }
   - and taking the border half of that mixin without the hover half is what left --danger deepening its
   tint by a shade nobody could see while the violet buttons beside it flipped to solid.

   --positive hovers to --act-positive-TEXT rather than --act-positive, and that is not a typo: white on
   #0e8f7c is 4.01:1 and fails AA, the same trap the Flowing badge was in. The darker green clears it and
   is close enough that nothing looks out of family. */
.act-btn--call { color: var(--act-call-text); background: var(--act-call-bg); border-color: transparent; }
/* Log Call hovers to --violet, the SAME fill as Change (.act-btn--action:hover) rather than its own
   --act-call (#7a3fb8). They are both violet and they sit in the same rows, so the darker one read as a
   heavier, more consequential action than the button beside it - which logging a call is not. */
.act-btn--call:hover { color: #fff; background: var(--violet); border-color: var(--violet); }
.act-btn--positive { color: var(--act-positive-text); background: var(--act-positive-bg); border-color: transparent; }
.act-btn--positive:hover { color: #fff; background: var(--act-positive-text); border-color: var(--act-positive-text); }
.act-btn--danger { color: var(--act-danger-text); background: var(--act-danger-bg); border-color: transparent; }
.act-btn--danger:hover { color: #fff; background: var(--act-danger); border-color: var(--act-danger); }
.act-btn--neutral { color: var(--act-neutral); background: var(--act-neutral-bg); border-color: transparent; }
.act-btn--neutral:hover { color: #fff; background: var(--act-neutral); border-color: var(--act-neutral); }

/* One house colour for "you can do this" - violet, not the brand pink.
   The pink version of this was right about one thing and wrong about the other: one colour for every row
   action IS better than four unrelated warning tints. But a leads list is 83 rows, so 83 pink chips made
   pink the texture of the page rather than its emphasis - the same complaint that started all of this,
   in a different hue.
   So the division is by SCOPE, not by role: pink is the primary action OF A PAGE (+ New Lead, Save -
   .btn-primary, once or twice a screen), violet is the action repeated on every row. Danger keeps its own
   red wherever something deletes. */
/* NO OUTLINE, unlike the other variants, and that is by scope rather than by taste. --action is the chip
   that repeats - 19 usages across the leads list, the customer page and the dashboard, three to a row and
   eighty-three rows deep. At that density the border was doing two jobs at once, separating the button
   from the row AND drawing its shape, so the eye read a grid of little boxes before it read any of the
   words. The fill alone carries "pressable" perfectly well.

   --call, --positive, --danger and --neutral appear once or twice per screen (a contract header, a report
   toolbar), where a defined edge costs nothing and helps. Same rule that keeps pink for the one primary
   action and violet for the repeated ones: treatment follows how often a thing appears. */
/* Now taken from the theme's own button-inverse-variant mixin (shared/mixins/_buttons.scss), which is
   the control this was always reaching for:
       background: rgba($color, 0.2);  border-color: rgba($color, 0);  color: $color;
       &:hover { color: #fff; background: $color; border-color: $color; }
   So the transparent border was the theme's answer too - and the hover INVERTS to the solid button you
   liked on the demo, rather than deepening the tint the way I had guessed. The solid violet still shows
   up; it just shows up under the pointer instead of eighty-three times at rest.

   One deliberate divergence: the theme sets the resting text to $primary itself (#b66dff), which on a 20%
   tint of itself is about 2.2:1 - fine on their showcase, unreadable at 0.78rem in a table. We keep
   --violet-deep, which clears AA. */
.act-btn--action {
    color: var(--violet-deep);
    background: rgba(182, 109, 255, 0.2);
    border-color: rgba(182, 109, 255, 0);
}

.act-btn--action:hover {
    color: #fff;
    background: var(--violet);
    border-color: var(--violet);
}

/* A latched action - pressed and STAYING pressed, like the BOX Confirm panel toggle on the contract
   page. It wears the hover fill, because "held down" and "about to be pressed" should not need two
   different colours to tell apart; what distinguishes them is that this one does not go away when the
   pointer does. Not the same thing as a toggle CHIP (.btn-group > .btn-primary), which is one choice
   out of several - this is a single switch with no alternatives beside it. */
.act-btn.is-on {
    color: #fff;
    background: var(--violet);
    border-color: var(--violet);
}

/* Icon-only actions: even padding so the glyph sits centred instead of inheriting the label's side
   padding. The label still ships as title + aria-label, so the action is never unnamed. */
.act-btn--icon { padding: 0.2rem 0.4rem; }

/* Segmented controls (Working / Parked, the 120-90-60 horizon, the Trends metric chips) mark a VIEW, not
   an action: nothing happens to a record when you press one. They used .btn-primary, which is the brand
   pink, so a filter looked exactly as loud as Save. Inside a .btn-group the selected segment takes the
   violet instead, leaving pink to mean press-me. Standalone .btn-primary is untouched. */
/* TRIAL 2026-09-18 - softened. Was solid --violet-deep with white text: correct in meaning but a heavy
   saturated block for something that only says which view you are on.

   Lighter SOLIDS are not available: white on the theme's own #b66dff measures 3.17:1 and on #9a55ff 4.1:1,
   both under AA. So the weight comes off the other way - a mid violet carrying dark text instead of a deep
   violet carrying white. #4a2570 on #d9bfff is 7.18:1, better than the 6.5:1 it replaces.

   It still cannot be confused with a row action: the action tint composites to about #f0e2ff with
   --violet-deep text, this is visibly deeper with near-black-violet text. Selected is still the heavier
   of the two, just no longer the loudest thing on the toolbar. */
/* Secondary buttons - Cancel, Close, Back, Clear, Reset: 65 of them, almost all in a modal footer beside
   a filled primary. Bootstrap outlines them in #6c757d, a hard mid-grey that is darker than most of the
   text on the page, so the button you are NOT meant to press had the strongest edge in the dialog.

   Softer border, and a neutral carrying a slight violet bias rather than a pure grey, so it reads as part
   of this palette instead of as Bootstrap showing through. The label stays dark enough to read easily -
   Cancel should be quiet, not hard to find.

   .btn-group > .btn-outline-secondary is more specific and still wins, so the segmented controls keep
   their own borderless treatment. */
.btn-outline-secondary {
    --bs-btn-color: #5f6470;
    --bs-btn-bg: transparent;
    --bs-btn-border-color: #dcdae2;
    --bs-btn-hover-color: #3f4350;
    --bs-btn-hover-bg: #f2f1f5;
    --bs-btn-hover-border-color: #cfccd8;
    --bs-btn-active-color: #3f4350;
    --bs-btn-active-bg: #eceaf0;
    --bs-btn-active-border-color: #cfccd8;
    --bs-btn-disabled-color: #9a9aa6;
    --bs-btn-disabled-border-color: #e6e4ea;
}

/* The control is framed as ONE thing, not as a row of boxes. The frame is the same violet as the filled
   chip, so the group reads as a single control at a glance and the unselected options stop looking like
   loose text. All eight .btn-group uses in the app are segmented toggles - lead bucket, activity scope,
   expiry horizon, contract stage, chart metric, candidates, reconciliation - so this is safe app-wide.

   No overflow:hidden, deliberately: it would clip the focus ring, which is a box-shadow.
   --seg-frame is a variable so the one control whose selected chip is NOT violet can say so. */
.btn-group {
    --seg-frame: #d9bfff;
    border: 1px solid var(--seg-frame);
    border-radius: 0.25rem;
}

/* The contract-stage control fills red when Dropped is the active stage; a violet frame around a red chip
   would read as two unrelated things. */
.btn-group:has(> .btn-danger) {
    --seg-frame: #f3c3c5;
}

/* The UNSELECTED segments carry no outline. A 1px box around each option drew a row of empty frames and
   competed with the one chip that is actually filled - the same double-duty border we took off the row
   actions. Selection is now the only thing with a fill, which is the whole job of a segmented control.
   They keep a hover tint so they still read as pressable rather than as labels. */
.btn-group > .btn-outline-secondary {
    --bs-btn-color: var(--bs-secondary-color, #6c757d);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: #4a2570;
    --bs-btn-hover-bg: rgba(182, 109, 255, 0.12);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: #4a2570;
    --bs-btn-active-bg: rgba(182, 109, 255, 0.12);
    --bs-btn-active-border-color: transparent;
}

.btn-group > .btn-primary {
    --bs-btn-color: #4a2570;
    --bs-btn-bg: #d9bfff;
    --bs-btn-border-color: #d9bfff;
    --bs-btn-hover-color: #3f1f63;
    --bs-btn-hover-bg: #cbb0f0;
    --bs-btn-hover-border-color: #cbb0f0;
    --bs-btn-active-color: #3f1f63;
    --bs-btn-active-bg: #cbb0f0;
    --bs-btn-active-border-color: #cbb0f0;
    --bs-btn-disabled-color: #4a2570;
    --bs-btn-disabled-bg: #d9bfff;
    --bs-btn-disabled-border-color: #d9bfff;
}

/* The two routes out of "set aside": stacked full-width targets rather than a button row, because each
   needs a line of explanation and they are not equal-weight choices you scan across. Same shape the
   renewal modal uses, so one decision looks like one decision wherever it is asked. */
.sa-choices { display: flex; flex-direction: column; gap: 0.5rem; }

.sa-choice {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    width: 100%;
    text-align: left;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--bs-border-radius, 0.375rem);
    background: #fff;
    color: inherit;
    cursor: pointer;
}

.sa-choice:hover { border-color: var(--act-positive-border); background: var(--act-positive-bg); }
.sa-choice--warn:hover { border-color: var(--act-danger-border); background: var(--act-danger-bg); }
.sa-choice-t { font-weight: 600; }
.sa-choice-d { font-size: 0.8125rem; color: var(--bs-secondary-color, #6c757d); line-height: 1.3; }

/* ===== Row actions as one control: the thing you do all day, plus a caret for the rest ===== */
.act-split { display: inline-flex; }
.act-split-main { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.act-split-caret {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    margin-left: -1px;            /* one shared edge, not two stacked borders */
    padding-left: 0.4rem;
    padding-right: 0.4rem;
}

/* The two halves used to be told apart by the outline they shared. With --action carrying no border they
   merged into one continuous lozenge with a caret floating in it, so the caret supplies its own divider -
   the only edge left in the control, and the only one it needed. */
.act-split > .act-btn--action.act-split-caret {
    border-left: 1px solid rgba(122, 63, 184, 0.25);
}

/* The menu opens IN FLOW, under the button, rather than floating over the row. A floating menu is
   clipped by the first scrolling ancestor, and on /leads the table sits in .table-responsive (overflow
   on both axes) - so it would be cut off exactly where the list is longest. In flow it cannot be
   clipped and needs no JS; the cost is that the row grows while the menu is open. */
/* The menu hangs in a zero-width slot, and this is the whole trick. A wrapping flex container's
   MAX-CONTENT width is measured as though every item sat on one line - the wrap that drops the menu
   under the button happens at layout time, not while the browser is deciding how wide things want to
   be. So the cell asked for button-row PLUS menu side by side, and an auto-layout table hands out
   width in proportion to what each column asks for: opening the menu took the Actions column from
   157px to 326px and squeezed every other column to pay for it (measured, 860px table).
   A slot of width 0 asks for nothing, so the column is unmoved (157 -> 165). The menu overflows it
   visibly, still in normal flow - so it still cannot be clipped by .table-responsive, and the row
   still grows in height to make room.

   It hangs from the LEFT, under the button that opened it. flex-end was tried and is wrong in both
   places it appears: on the customer page it pinned the menu to the far right of the cell, detached
   from its button, and in the leads list - where .ll-actions > .lead-actions is display:contents -
   the slot's right edge is the whole Customer column, so the menu landed under the customer name
   several inches from the caret. */
.act-menu-slot {
    flex: 1 0 100%;
    width: 0;
    min-width: 0;
    display: flex;
    justify-content: flex-start;
}

/* The customer page Leads table right-aligns its Actions column, so the button sits against the table
   edge rather than floating in the middle of a wide column. .lead-table is on that table only, so the
   leads list - where the actions sit under the customer name in a wide first column - is untouched. */
.lead-table th:last-child,
.lead-table td:last-child { text-align: right; }

/* The menu follows the button to that edge. Hanging leftward from the button IS correct once the button
   is itself on the right; it was wrong before only because the button was on the left and the menu flew
   off to meet a cell edge it had nothing to do with. */
.lead-table .act-menu-slot { justify-content: flex-end; }

/* And the flex LINES are right-aligned too, which is not the same thing. .lead-actions wraps, so the
   0.4rem column gap is counted between the button row and the zero-width slot: the container comes out
   one gap wider than the button row, and with the default flex-start the button lands 6px short of the
   menu edge below it. Right-aligning the lines makes the two right edges coincide exactly (measured). */
.lead-table .lead-actions { justify-content: flex-end; }

/* Where the menu floats there is nothing to protect: the slot dissolves so it does not add an empty
   flex line, and the absolute menu still resolves against .lead-actions--float. */
.lead-actions--float .act-menu-slot { display: contents; }

/* MUST live inside .act-menu-slot. The slot is the flex item that wraps onto its own line; on its own
   .act-menu is an ordinary flex item and opens BESIDE the button instead of under it. The dashboard
   renewals menu was written before the slot existed and broke exactly that way. */
.act-menu {
    flex: 0 0 auto;
    margin-top: 0.3rem;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    width: max-content;
    min-width: 8rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--bs-border-radius, 0.375rem);
    background: #fff;
    box-shadow: 0 4px 14px -8px rgba(0, 0, 0, 0.35);
    overflow: hidden;
}

.act-menu-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.7rem;
    border: 0;
    background: none;
    text-align: left;
    font-size: 0.8rem;
    color: var(--bs-body-color, #474a52);
    text-decoration: none;
    cursor: pointer;
}

.act-menu-item + .act-menu-item { border-top: 1px solid var(--bs-border-color, #eef0f3); }
/* Violet, matching the button that opened the menu and every other row action. This was pink, left
   over from when pink meant "interactive" everywhere - inside a menu of row actions it now reads as
   the page's primary action, which none of these are. */
.act-menu-item:hover { background: var(--violet-050); color: var(--violet-deep); }

/* Where nothing can clip it - a page header rather than a row inside a scrolling table - the menu floats
   instead. In flow it grows whatever box it sits in, which on a heading row means the title moves the
   moment you open the menu. Opt-in, because in a list the flow version is the one that survives. */
/* The company name on a saved lead: a link where a disabled input used to be, keeping the vertical
   rhythm of the fields around it. */
.lead-company {
    padding-top: 0.15rem;
    padding-bottom: 0.35rem;
    font-size: 1rem;
}

.lead-actions--float { position: relative; }

.act-menu--float {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 1060;
    margin-top: 0.25rem;
    flex: none;
}

/* ===== Standard reports =====
   Shared by every report page (they all render the same shapes: a subtitle naming the date basis, preset
   range buttons, a summary table and a sortable detail table), so these live here rather than being
   duplicated into each page's scoped sheet. */
.rpt-subtitle {
    font-size: 0.85rem;
    color: var(--bs-secondary-color, #6c757d);
}

.rpt-presets {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0.6rem 0 1rem;
}

.rpt-table {
    font-size: 0.85rem;
}

.rpt-table thead th {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--bs-secondary-color, #6c757d);
    font-weight: 600;
    white-space: nowrap;
    background: var(--bs-body-bg, #fff);   /* opaque: rows scroll UNDER a sticky header, not through it */
}

.rpt-table td {
    vertical-align: middle;
}

.rpt-sort {
    cursor: pointer;
    user-select: none;
}

.rpt-sort:hover {
    color: var(--brand-charcoal, #4b4d52);
}

.rpt-caret {
    font-size: 0.7rem;
    color: var(--brand-pink, #cf4a9b);
}

/* Totals read as a rule-off, the way a subtotal does on paper. */
.rpt-table tfoot .rpt-total td {
    font-weight: 700;
    border-top: 2px solid var(--bs-border-color, #dee2e6);
    background: var(--bs-tertiary-bg, #f8f9fa);
}

/* The count that means "there is still work to do today" — the only number on the summary worth a colour. */
.rpt-todo {
    font-weight: 700;
    color: var(--act-danger-text, #a4373a);
}

.rpt-contact {
    line-height: 1.35;
}

.rpt-contact a {
    text-decoration: none;
}

/* Renewal progress. Not the contract-status palette: this answers "has anyone worked it", which is a
   different axis from what the contract itself is doing. */
.rw-none { color: var(--act-danger-text, #a4373a); background: var(--act-danger-bg, #fdf0f0); border: 1px solid var(--act-danger-border, #f3c9c9); }
.rw-open { color: var(--act-call-text, #8a5a12); background: var(--act-call-bg, #fdf4e6); border: 1px solid var(--act-call-border, #f2ddb8); }
.rw-renewed { color: var(--act-positive-text, #256b55); background: var(--act-positive-bg, #ecf7f3); border: 1px solid var(--act-positive-border, #b7e0d0); }
.rw-closed { color: var(--act-neutral, #5c5f66); background: var(--act-neutral-bg, #f2f3f5); border: 1px solid var(--act-neutral-border, #dcdee3); }

/* Detail tables are NOT wrapped in .table-responsive on desktop: overflow:auto makes a scroll container,
   and a sticky header then pins to THAT instead of the page — the header floats mid-rows. Same reason
   /deals and /customers wrap their sticky tables in a plain div. */
.rpt-detail {
    overflow: visible;
}

/* Mobile flips the trade. A report table is far wider than a phone, and unconstrained it burst out of
   its card while the Summary above it scrolled politely inside one. So here the detail becomes a capped
   scroll pane — the same shape the dashboard uses for its lead list — which contains the sideways scroll
   AND gives the sticky header something to stick to: the pane, at top 0, rather than the page. */
@media (max-width: 767.98px) {
    .rpt-detail {
        overflow: auto;
        max-height: 70vh;
        -webkit-overflow-scrolling: touch;
    }

    .rpt-detail .pageflow-sticky > thead > tr > th {
        top: 0;
    }

    /* Match the Summary, which is a .table-responsive and already behaves. */
    .rpt-table {
        font-size: 0.8rem;
    }
}

/* ===== Lead action modals =====
   Lightweight modals (no Bootstrap JS). Global rather than scoped to LeadsWorkspace because the same
   three actions — log a call, convert to a contract, close — are offered wherever an open lead appears:
   the dashboard list, /leads, and the customer profile. One definition, one look. */
.lead-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 1050;
}

.lead-modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 1051;
    width: min(460px, 94vw);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
}

/* Log-call modal — wider, with a scrollable history list. */
.call-modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 1051;
    width: min(560px, 94vw);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
}

.call-history {
    max-height: 40vh;
    overflow-y: auto;
}

.call-item {
    padding: 0.4rem 0;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

.call-item:last-child {
    border-bottom: 0;
}

/* The lead action row, wherever it appears (leads list, customer profile). inline-flex so the box stops
   at the last button — a full-width wrapper swallows clicks in the empty space beside it. */
.lead-actions {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

/* Renewal-state extras for the expiring-contracts report. */
.rw-leadwon { color: var(--act-call-text, #a3480f); background: var(--act-call-bg, #fdf0e6); border: 1px solid var(--act-call-border, #f7ddc6); }


/* The end date and its "today" pill were crowding each other. */
.exp-when {
    margin-left: 0.4rem;
}

/* Inline "Edit lead" link — a quiet secondary link (grey → pink on hover) so it doesn't compete with the
   customer name beside it. Global rather than scoped to LeadsWorkspace: the customer profile offers the
   same link on an open lead, and it should be the same link, not a lookalike. */
.ll-edit-inline {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-size: 0.75rem;
    color: var(--bs-secondary-color, #6c757d);
    text-decoration: none;
}

.ll-edit-inline:hover {
    color: var(--brand-pink-dark, #b23d86);
    text-decoration: none;
}

/* The leads row nests one flex container inside another: LeadActions renders its own .lead-actions box
   holding Call / Contract / Close, and the list adds Park or Wake as a SIBLING of that box. So the outer
   row has two items, not four — the inner three are one atomic block that cannot reflow, and the fourth
   button drops to its own line the moment the block plus itself exceed the width. Widening the gutters
   bought pixels but could not fix the shape.

   display:contents dissolves the inner box so all four become items of the same row, wrapping (and
   sharing one gap) as a single set. */
.ll-actions > .lead-actions { display: contents; }

/* ===== Call-log note text =====
   The words somebody actually typed - a logged call, an emailed reply pasted in, a closing reason.
   Italic separates them at a glance from the CRM's own labels and dates around them, which is the
   convention the leads list already used for its last-call line (.ll-lastcall).

   The size is in rem, not em, deliberately: these notes appear inside cards of several different
   densities (the commission-schedule card is 0.85rem, list rows are .small) and .small compounding on
   a dense card had the same note rendering at 11.9px in one place and 14px in another. A fixed rem
   means a note reads identically wherever it is shown - contract, customer, lead editor, dashboard
   modals. Set the size here and nowhere else.

   The size is --data-sm: what the leads table's cells actually render at. A call log is data like the
   lists beside it, so it matches them rather than the page's 1rem body text. */
/* Shared activity rows (ActivityList): the house data size, so the customer profile feed and the
   contract call log read identically. The contract list used .small inside a 1rem card, which lands at
   0.875rem - visibly larger than everything around it. */
.act-row {
    font-size: var(--data-sm, 0.745rem);
}

.call-note {
    font-size: var(--data-sm, 0.745rem);
    font-style: italic;
}

/* ===== Expiry chip (ExpiryChip.razor) =====
   GLOBAL on purpose. Two components render this - the leads workspace and the dashboard - and a scoped
   sheet only ever reaches the component it is named after, which is how the activity list ended up
   unstyled on mobile earlier. One definition, in the one file both can see.

   Geometry is the risk pill's (.risk-pill in Dashboard.razor.css), because they are the same idea wearing
   the same clothes: a tinted band with a hairline border, dark text, never a solid fill. A column of
   solid red cells reads as an outage rather than as a work queue.

   Colours are the --act-* families, so danger/warning/positive here are the same three colours the action
   buttons use, and a palette change is still one edit at the top of this file. */
.exp-chip {
    display: inline-block;
    padding: 0.05rem 0.4rem;
    border-radius: 0.25rem;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.45;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Lapsed, or inside 30 days. One band, not two: an expired contract says so in words. */
.exp-chip--danger {
    background: var(--act-danger-bg);
    color: var(--act-danger-text);
}

.exp-chip--warn {
    background: var(--act-warning-bg);
    color: var(--act-warning-text);
}

.exp-chip--ok {
    background: var(--act-positive-bg);
    color: var(--act-positive-text);
}
