/* Admin overrides on top of Tabler. Keep minimal. */
.nav-item-header { pointer-events: none; }

/* Per-page "What this is" help box -- auto-injected by the layout from the PageHelp registry.
   Only shown while Help mode is on (the "?" toggle in the top bar). */
.adm-pagehelp { display: none; position: relative; background: #eff6ff; border: 1px solid #dbeafe; border-left: 4px solid #3b82f6;
    border-radius: 0 8px 8px 0; padding: 12px 40px 12px 14px; margin-bottom: 1rem; }
body.help-on .adm-pagehelp { display: block; }
.adm-pagehelp-title { font-weight: 500; color: #1e40af; }
.adm-pagehelp-body { font-size: 13px; color: #1e3a5f; line-height: 1.6; }
.adm-pagehelp-body code { background: #dbeafe; color: #1e3a5f; padding: 0 4px; border-radius: 4px; }
.adm-pagehelp-x { position: absolute; top: 10px; right: 10px; }

/* Block-toolbar "AI" control -- purple so AI actions read as distinct from the
   neutral (grey) editing/template tools. Matches the purple AI accent used elsewhere. */
.rt-ai-toggle { color: #534AB7; background: #EEEDFE; border: 1px solid #AFA9EC; }
.rt-ai-toggle:hover, .rt-ai-toggle:focus, .rt-ai-toggle.show { color: #3C3489; background: #CECBF6; border-color: #7F77DD; }
.rt-ai-group .dropdown-menu .dropdown-item i { color: #7F77DD; }

/* -- Admin design tokens ------------------------------------------------------
   Single source of truth for the admin's semantic colours/shape. Layered on top
   of Tabler's own --tblr-* vars. Tune values HERE rather than hardcoding hex in
   page markup or scattered rules. The status-* pairs (bg + fg) are each chosen
   for >=4.5:1 contrast (WCAG AA) and drive the .adm-chip status component below. */
:root {
    --adm-text-muted: #5a6270;   /* muted labels/hints (AA on white) */
    --adm-link:       #1c64c4;   /* link / link-button blue (AA on white) */
    --adm-accent:     #206bc4;   /* admin accent (Tabler primary) -- used for hovers/active chrome */
    --adm-accent-soft: #e7effb;  /* the accent as a GROUND: selected rows, chosen cards. Body text passes AA on it. */
    --adm-border:     var(--tblr-border-color, #dadfe5);
    --adm-radius:     .375rem;
    --adm-radius-pill: 999px;

    --adm-status-published-bg: #e6f6ec; --adm-status-published-fg: #1a7f43;
    --adm-status-draft-bg:     #fdf3d8; --adm-status-draft-fg:     #8a6d1a;
    --adm-status-pending-bg:   #ffe9d6; --adm-status-pending-fg:   #b45309;
    --adm-status-scheduled-bg: #e2eefb; --adm-status-scheduled-fg: #1c64c4;
    --adm-status-expired-bg:   #fde2e2; --adm-status-expired-fg:   #b42318;
    --adm-status-muted-bg:     #eceef1; --adm-status-muted-fg:     #4a5160;
    --adm-status-info-bg:      #e7f0fb; --adm-status-info-fg:      #185fa5;

    /* THE MISSING TOKENS, DEFINED (audit 2026-08-19 #20): these were referenced across the admin with
       raw fallbacks leaking through — three near-identical greens and four blues coexisted because
       every usage site guessed its own hex. One definition each; the fallbacks never fire again. */
    --adm-primary:   #206bc4;   /* Tabler primary — .crm-who-name hover, page-editor accents */
    --adm-success:   #2fb344;   /* the floating Save */
    --adm-danger:    #d63939;   /* the floating Delete */
    --adm-bg-subtle: #f6f8fa;   /* quiet card wash (.crm-cover-item.is-free) */
    --adm-chip-bg:   #e9edf3;   /* neutral chip/pill ground (toolbar, jump links, Next bands) */

    /* Light green outline for FINISHED steps in a stepper (.adm-step-done). Deliberately weaker than
       --adm-status-published-fg: done work should read as reference, not as a call to action. */
    --adm-status-done-border:  #9fd4b4;
}

/* Accessibility: bump a few Tabler defaults that fail WCAG AA contrast (4.5:1).
   Now driven by the tokens above. */
.page-pretitle { color: var(--adm-text-muted) !important; }   /* was too light grey on white */
.btn-link { color: var(--adm-link) !important; }              /* link buttons need darker blue */
.nav-tabs .nav-link { color: #4a5160; }                       /* inactive tab labels */
.text-secondary { color: var(--adm-text-muted) !important; }  /* generic muted text used as labels/hints */
.badge.bg-secondary { background-color: #525e70 !important; } /* white-on-secondary needs >=4.5:1 */

/* -- One-line header (2026-08-24, mock option 5) --------------------------------
   The page's identity in the top bar: parent path small and linked, page name bold,
   all on one baseline beside the full search box. */
.adm-crumbtitle { white-space: nowrap; min-width: 0; }
/* Header divider between the Deploy/Sync pair and the icon cluster (2026-08-24 header mock). */
.adm-hdr-divider { width: 1px; height: 24px; background: var(--adm-border, #e2e6ec);
    margin: 0 .35rem; vertical-align: middle; align-self: center; }
/* The page line: trail + page name left, the page's own Back/Action buttons (adopted by the
   layout's script) right. ON THE GREY, not a white band, and barely taller than its text
   (Graham 2026-08-24: "Move the breadcrumbs into grey, move back to grey, divide the height
   to much less"). */
/* Compact but SEPARATED (Graham 2026-08-24, fourth pass: "It needs to be compact but separated.
   Not dark. Breadcrumbs and title a LITTLE bigger but not a huge header"): a white band with a
   hairline under it — a distinct edge, no weight. The top bar gets the same hairline so the two
   read as two crisp rows, not one wash. */
.adm-pageline { background: var(--tblr-bg-surface, #fff); border-bottom: 1px solid var(--adm-border, #e6e9ee);
    padding: .35rem 0; }
header.navbar { border-bottom: 1px solid var(--adm-border, #e6e9ee); }
@media (min-width: 992px) {
    /* The page line already separates header from content — the page body's default 24px top
       margin on top of it read as dead grey (Graham 2026-08-24: "There is too much grey"). */
    .page-body { margin-top: .5rem; }
}
#pagelineActions .btn { padding: .125rem .6rem; font-size: .8125rem; }
.adm-crumbline { display: inline-flex; align-items: baseline; gap: .45rem; font-size: .9rem; }
.adm-crumbline a { color: var(--adm-accent, #2e6bd6); text-decoration: none; }
.adm-crumbline a:hover { text-decoration: underline; }
.adm-crumbline .adm-crumbsep { color: #93a1b5; }
.adm-crumbline b { font-size: 1.25rem; font-weight: 700; color: var(--tblr-body-color, #1d273b);
    max-width: 24rem; overflow: hidden; text-overflow: ellipsis; }
@media (min-width: 992px) {
    /* EVERY page's big pretitle/title yields to the top bar's crumb-title — including the ~143 pages
       that hand-roll their .page-header instead of using the _PageHeader partial. Only the TITLE TEXT
       hides: Back/Action buttons and any descriptive line in the same header keep rendering. */
    .page-header .page-pretitle, .page-header .page-title { display: none; }
    .page-header { margin-bottom: .5rem; }
}

/* -- Status chip --------------------------------------------------------------
   Consistent status pill used across admin lists/editors (page & post approval,
   visibility, publish window...). One look everywhere; colours come from tokens.
   Usage: <span class="adm-chip adm-chip--pending">Pending</span> */
.adm-chip {
    display: inline-flex; align-items: center; gap: .25rem;
    padding: .125rem .5rem; border-radius: var(--adm-radius-pill);
    font-size: .6875rem; font-weight: 600; line-height: 1.5;
    text-transform: uppercase; letter-spacing: .02em; white-space: nowrap;
    background: var(--adm-status-muted-bg); color: var(--adm-status-muted-fg);
}
/* #127 (2026-08-16 desktop dive) — a chip's count is a dimmed badge, not parentheses in the label. */
.adm-chip .adm-chip-n { font-weight: 800; opacity: .55; }
.adm-chip--published { background: var(--adm-status-published-bg); color: var(--adm-status-published-fg); }
.adm-chip--draft     { background: var(--adm-status-draft-bg);     color: var(--adm-status-draft-fg); }
.adm-chip--pending   { background: var(--adm-status-pending-bg);   color: var(--adm-status-pending-fg); }
.adm-chip--scheduled { background: var(--adm-status-scheduled-bg); color: var(--adm-status-scheduled-fg); }
.adm-chip--expired   { background: var(--adm-status-expired-bg);   color: var(--adm-status-expired-fg); }
.adm-chip--muted     { background: var(--adm-status-muted-bg);     color: var(--adm-status-muted-fg); }

/* Click-an-amber-chip landing (Leads Settings, 2026-08-24: "The orange items should expand and make
   the text box active where the error is"): brief ring around the control the chip points at. */
.adm-chip[data-fix]{ cursor:pointer; }
.adm-fix-flash{ box-shadow:0 0 0 3px rgba(246,139,31,.45) !important; border-radius:4px; transition:box-shadow .2s; }

/* -- Row flash --------------------------------------------------------------
   Deep-linked-to-one-record landings ("open this exact row"): a brief highlight
   so the eye finds it immediately, same effect js-grid.js's own row-flash gives
   grid pages, for plain <table>/<tr> pages that aren't a js-grid.
   Usage: row.classList.add('adm-row-flash') after scrollIntoView(). */
.adm-row-flash > td { animation: adm-rowflash 1.2s linear; }
@keyframes adm-rowflash { from { background-color: rgba(47,179,68,.30); } to { background-color: transparent; } }

/* -- Collapsible card header ---------------------------------------------------
   A card that folds to its title row (Integrations etc.): whole header is the
   toggle, chevron flips when open. Pair with Bootstrap collapse:
   <div class="card-header adm-collapse-toggle collapsed" data-bs-toggle="collapse" ...>
     ... <i class="ti ti-chevron-down ms-auto adm-collapse-chevron"></i> */
.adm-collapse-toggle { cursor: pointer; user-select: none; }
.adm-collapse-toggle .adm-collapse-chevron { transition: transform .2s ease; color: var(--adm-text-muted); }
.adm-collapse-toggle:not(.collapsed) .adm-collapse-chevron { transform: rotate(180deg); }

/* == THE DEAL PAGE: progress bar + the one action ==========================
   Graham, after ~30 rejected designs: "I open it and I say what should be done
   next, where do I look." The answer was already on screen and he could not
   see it, because six bordered cards all carried the same visual weight.

   The fix is SUBTRACTIVE. One saturated dark object on the page, and nothing
   else with a fill. Blur the page and exactly one thing survives.
   His own picks: the blue from the working-step design, the segmented progress
   bar with a word per segment, and a drafted plan/setup/email he can send. */

/* -- the progress bar: nine segments, one word each ------------------------ */
.crm-prog { display: grid; gap: 4px; margin: 6px 0 0; }
.crm-prog-bars { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 4px; }
.crm-prog-seg {
    height: 6px; border-radius: 3px; background: var(--adm-status-muted-bg);
    display: block; text-decoration: none;
}
.crm-prog-seg.is-done { background: var(--adm-status-published-fg); }
.crm-prog-seg.is-now  { background: var(--adm-accent); }
.crm-prog-seg.is-lost { background: var(--adm-status-expired-fg); }
.crm-prog-seg:hover   { filter: brightness(1.15); }
/* TWO DIFFERENT TRUTHS, TWO DIFFERENT MARKS (Graham 2026-08-15: "some sort of colour visual on what step
   we are currently on, but we can click on any tab we want").
     is-now   = where the DEAL is. Filled. It moves only when the deal moves.
     is-focus = the tab you are READING. A ring, not a fill, so it can sit on any rung — including one the
                deal is nowhere near — without ever being mistaken for progress.
   Clicking is exploration; it must not repaint the deal's position, which is the mistake the old
   click-to-set-stage bar made. */
.crm-prog-seg.is-focus {
    outline: 2px solid var(--adm-accent);
    outline-offset: 2px;
}
/* One word under each segment. Clickable, so the bar is navigation as well as
   status — he asked to "click on colour to go back and forward". */
.crm-prog-words { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 4px; }
.crm-prog-words > a {
    display: block; text-decoration: none; min-width: 0; padding-top: 1px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Two lines: the step's NAME never changes, the line under it is the live state. Graham's own shape —
   "word on top (stay the same), next line status (change)". */
.crm-prog-word  { display: block; font-size: 11px; font-weight: 600; color: var(--tblr-body-color, #1f2733);
                  overflow: hidden; text-overflow: ellipsis; }
.crm-prog-state { display: block; font-size: 10.5px; color: var(--adm-text-muted);
                  overflow: hidden; text-overflow: ellipsis; }
.crm-prog-state.is-idle { color: #a6acb5; }          /* nothing has happened here yet — present, not shouting */
/* The pin: where the RECORD says the deal is. Blue tracks the stored stage (which clicking the bar
   writes); the pin tracks evidence and cannot be moved by a click, so it survives exploring. */
/* .crm-prog-pin deleted (audit #5): one mark now — the filled segment sits at the evidence position, and
   genuine stage-vs-paperwork disagreement is a sentence (.crm-prog-drift), not a second symbol. */

/* PHASE HEADINGS over the rungs (audit #3): same 9-column grid as the bars, so each heading spans its own
   rungs exactly. Structure for the eye; every rung below stays a clickable tab. */
.crm-prog-phases { display: grid; grid-template-columns: repeat(9, 1fr); gap: 4px; margin-bottom: 3px; }
.crm-prog-phases > span { font-size: 10px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
                          color: var(--adm-text-muted); border-bottom: 1px solid var(--adm-status-muted-bg);
                          padding-bottom: 2px; }

/* Stage-vs-paperwork drift, said in words (audit #5). */
.crm-prog-drift { margin-top: 6px; font-size: 12.5px; color: var(--adm-status-pending-fg, #8a5a00);
                  background: var(--adm-status-pending-bg, #fff4dd); border-radius: 6px; padding: 5px 10px; }

/* The Decision rung's own Won / Lost (audit #7) — buttons drawn as the state line they replace. */
.crm-word-decide { border: 0; background: none; padding: 0; font-size: 10.5px; font-weight: 700;
                   cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }

/* "Viewing X — the deal is at Y" strip on a focused slab (audit #6). */
.crm-slab-viewing { font-size: 12px; color: #b8c7dc; margin-bottom: 6px; }
.crm-slab-viewing b { color: #fff; }

/* IDENTITY AVATARS (visual #30): deterministic colour per contact — background is inline hsl() from a
   stable hash of the key, so the same person wears the same colour on the board, the header and tomorrow. */
.crm-av { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
          border-radius: 50%; color: #fff; font-size: 10px; font-weight: 700; letter-spacing: .02em; }

/* URGENCY STRIPE + ONE COLOUR LANGUAGE (visual #29 + #31): the card's traffic light gains a 4px edge that
   reads at arm's length, drawn in the SAME status tokens the chips and the rail use — one green, one amber,
   one red across every surface, instead of three vocabularies that almost match. */
.crm-card.bg-green-lt  { border-left: 4px solid var(--adm-status-published-fg); }
.crm-card.bg-yellow-lt { border-left: 4px solid var(--adm-status-pending-fg); }
.crm-card.bg-red-lt    { border-left: 4px solid var(--adm-status-expired-fg); }
/* A FACT THAT DOES NOT EARN A CARD (client record, redesign stage 4). One deal, one line — the chrome of
   a card header and a six-column table head was more markup than the six facts it carried. Same left
   inset as a card body so it still reads as part of the column. */
.crm-oneline {
    display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem;
    padding: .6rem .9rem; font-size: 14px;
    border: 1px solid var(--adm-border, #e2e8f0); border-radius: 6px;
    background: var(--tblr-card-bg, #fff);
}

/* READ MODE on the client record (redesign stage 3). A definition list rather than a table: these are
   label/value pairs, not rows of a grid, and a <dl> says so to a screen reader without any ARIA. Two
   columns on anything wider than a phone, stacked below it. */
.crm-readlist { display: grid; grid-template-columns: 7rem 1fr; gap: .45rem 1rem; margin: 0; }
.crm-readlist dt { font-weight: 400; color: var(--adm-text-muted, #64748b); }
.crm-readlist dd { margin: 0; }
@media (max-width: 575.98px) {
    .crm-readlist { grid-template-columns: 1fr; gap: 0 0; }
    .crm-readlist dd { margin-bottom: .6rem; }
}

/* THE STATE LINE (client record, redesign stage 1). One row of facts about the relationship, above the
   money. Wraps to as many lines as it needs on a phone rather than scrolling sideways — it is the first
   thing on the page and must never be the thing that puts a horizontal scrollbar on it. The separators
   are borders rather than "·" characters so a wrapped item never starts with an orphaned dot. */
.crm-stateline { display: flex; flex-wrap: wrap; gap: .35rem 1.1rem; font-size: 14px; align-items: baseline; }
.crm-state-item { display: inline-flex; align-items: baseline; gap: .35rem; }
.crm-state-item + .crm-state-item { padding-left: 1.1rem; border-left: 1px solid var(--adm-border, #e2e8f0); }
/* The person-level actions ride the same row (stage 3). No separator before them — they are not another
   fact, and a divider would read as one. */
.crm-state-actions { border-left: 0 !important; padding-left: 0 !important; }
@media (max-width: 575.98px) {
    /* Stacked, so the borders would be hanging off the left of each row. */
    .crm-state-item + .crm-state-item { padding-left: 0; border-left: 0; }
    /* ms-auto does nothing once the row wraps; give the buttons their own line with some air. */
    .crm-state-actions { margin-left: 0 !important; margin-top: .5rem; }
}


/* ── THE CLIENT RECORD HEADER (tabs restructure, phase 1, 2026-08-21) ─────────────────────────────
   One block above the tabs carrying identity, state, money and the primary action, so all four stay
   on screen whichever of the four panels you are reading. It replaces the state line PLUS the
   CLIENT-HEALTH card that used to sit under it repeating the same money in tile form. */
.crm-record-head { border: 1px solid var(--adm-border, #e2e8f0); border-radius: 12px;
    background: var(--tblr-bg-surface, #fff); padding: .85rem 1rem; }
.crm-record-head .crm-stateline { padding-bottom: .7rem; margin-bottom: .7rem;
    border-bottom: 1px solid var(--adm-border, #e2e8f0); }
/* The money and the facts read as one row; the actions are pushed to the far end by ms-auto and drop
   onto their own line on a phone, where ms-auto stops meaning anything. */
.crm-record-money { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem 2rem; }
.crm-record-mrr { line-height: 1.15; }
.crm-record-fact { line-height: 1.2; }
@media (max-width: 767.98px) {
    .crm-record-money { gap: .6rem 1.4rem; }
    .crm-record-actions { margin-left: 0 !important; width: 100%; }
}

/* ── THE FOUR TABS ────────────────────────────────────────────────────────────────────────────────
   The strip reuses the deal bench's .crm-bench-strip / .crm-bench-tab so the two CRM surfaces look
   like the same product. The panel below sits FLUSH on the tray — square top corners on whatever card
   comes first, whether it is a bare card or one wrapped in its form. :first-of-TYPE inside the form,
   because every form on this record opens with a hidden input and :first-child would never match. The
   third selector is the Details tab, whose form opens into a two-column row — BOTH columns start at the
   tray, so both of their leading cards are squared. */
.crm-record-tabs { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; }
.crm-record-panel { outline: none; }
.crm-record-panel > .card:first-child,
.crm-record-panel > form:first-child > .card:first-of-type,
.crm-record-panel > form:first-child > .row:first-of-type > [class*="col"] > .card:first-of-type {
    margin-top: 0 !important; border-top-left-radius: 0; border-top-right-radius: 0;
}
/* CLIENT RECORD — two folds that must not look like disclosure widgets (2026-08-21).
   The Ended cell's summary is a dash you can click; the record-a-signed-contract summary is a sentence.
   Neither wants a native triangle, and both want a pointer so the affordance is discoverable. Marker
   removed on both engines — ::marker for the standard, ::-webkit-details-marker for older WebKit. */
.crm-ended-sum, .crm-rec-sum { cursor: pointer; list-style: none; }
.crm-ended-sum::marker, .crm-rec-sum::marker { content: ""; }
.crm-ended-sum::-webkit-details-marker, .crm-rec-sum::-webkit-details-marker { display: none; }
.crm-ended[open] .crm-ended-sum { display: none; }   /* the dash has done its job once the picker is out */
.crm-rec-sum:hover { text-decoration: underline; }

/* A SECOND OPEN DEAL (F-33). Not a person, so it must not look like one: dashed edge, no urgency colour,
   no drag grip. It reads as something clipped to the zone rather than another card competing in it —
   which is what it is, since the person it belongs to already has a card, usually in a different zone. */
.crm-deal-card { border-style: dashed !important; background: var(--tblr-bg-surface-secondary, #f6f8fb); }
.crm-deal-card:hover { border-color: var(--tblr-primary, #206bc4) !important; }
/* TILES + THE EDITOR BAY (2026-08-16, Graham: "same height… a hidden section just above Owed").
   Tiles are buttons of ONE height; their editors live in a fixed bay below the row. */
.crm-tiles { align-items: stretch; }
.crm-tile { min-height: 72px; }
.crm-tile-btn { border: 0; text-align: left; font-family: inherit; font-size: inherit; cursor: pointer;
    display: block; width: 100%; }
.crm-tile-btn:hover { filter: brightness(.98); }
.crm-tile-btn:focus-visible { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: 1px; }

/* #135 WORKBENCH (2026-08-16, Graham's pick) — Quote / Message / History as modes of one bench.
   REAL FOLDER TABS (Graham 2026-08-18: "Can this look more like tabs and add some sort of background
   colour. It doesn't flow as well as it could"): the strip is a muted TRAY, each mode a rounded-top
   tab, and the ACTIVE tab is white and joined to the white panel below — it dips 1px over the seam
   (tray z-index above the panel) so tab and panel read as one piece of furniture.
   flex-wrap (2026-08-18 mobile dive): three tabs plus the rail toggle overflowed 375 by 56px. */
#crmBenchTabs { border-bottom: 0; border-radius: 10px 10px 0 0;
    background: var(--tblr-bg-surface-secondary, #eef2f7); position: relative; z-index: 2; }
.crm-bench-strip { display: flex; flex-wrap: wrap; gap: 4px; padding: .5rem .6rem 0; border-bottom: 0; }
.crm-bench-tab { border: 1px solid transparent; border-bottom: 0; background: transparent;
    color: var(--tblr-secondary, #667382); font-weight: 700; font-size: .9rem;
    padding: .6rem 1rem .55rem; border-radius: 9px 9px 0 0;
    display: inline-flex; align-items: center; min-height: 40px; cursor: pointer; }
.crm-bench-tab:hover { color: var(--tblr-body-color, #1f2733); background: rgba(255, 255, 255, .55); }
.crm-bench-tab.on { color: var(--tblr-primary, #206bc4); background: var(--tblr-bg-surface, #fff);
    border-color: var(--tblr-border-color, #dbe0e6); margin-bottom: -1px; padding-bottom: calc(.55rem + 1px); }
/* The mode panels sit flush under the tray: square top corners so tray + panel are one shape. */
#packagesCard, #quoteCard, #composerCard, #crmHistory { border-top-left-radius: 0; border-top-right-radius: 0; }
/* The tab already names the panel — its header title is a duplicate when the bench is driving
   (Graham 2026-08-18: "We dont need 'Packages' then 'Packages'"). The eyebrow line, the composer's
   Note/Email/Text switch and the History controls all stay; only the repeated name goes. */
.page:has(#crmBenchTabs:not(.d-none)) #packagesCard > .card-header > .card-title,
.page:has(#crmBenchTabs:not(.d-none)) #quoteCard > .card-header > .card-title,
.page:has(#crmBenchTabs:not(.d-none)) #composerCard #composerOpen > .card-header > .card-title,
.page:has(#crmBenchTabs:not(.d-none)) #crmHistory > .card-header > .card-title { display: none; }

/* ── DESKTOP DIVE #108–#117 (2026-08-16) — the deal page ───────────────────────────────────────── */
/* #111 — timeline layout: flag · when · channel · line · bin. The Way/From/To columns (and the header
   row) belong to the table mode behind the toggle. */
#crmHistoryBody.crm-hist--tl .crm-row-head { display: none; }
#crmHistoryBody.crm-hist--tl .crm-row { grid-template-columns: 20px 118px minmax(0, 1fr) 30px; }
#crmHistoryBody.crm-hist--tl .crm-mail > summary > span:nth-child(1),
#crmHistoryBody.crm-hist--tl .crm-mail-who { display: none; }
#crmHistoryBody.crm-hist--tl .crm-row-note { grid-column: 3; }
#crmHistoryBody.crm-hist--tl .crm-row-del { grid-column: 4; }
/* #112 — the copy glyphs appear with the pointer; four permanent ⧉ were identity-block chrome. */
@media (hover: hover) and (pointer: fine) {
    .crm-who .crm-copy { opacity: 0; transition: opacity .1s; }
    .crm-who span:hover > .crm-copy, .crm-who .crm-copy:focus-visible { opacity: 1; }
}
/* #113 — the stage tile's forward line. */
.crm-tile-s { display: block; font-size: .72rem; color: var(--tblr-secondary, #667382); margin-top: 1px; }
/* #115 — the slab tips' summary keeps its heading look as a control. */
.crm-slab-aside > summary { cursor: pointer; list-style: none; min-height: 40px; display: flex; align-items: center; }
.crm-slab-aside > summary::-webkit-details-marker { display: none; }
.crm-slab-aside > summary::after { content: "▾"; margin-left: .4rem; opacity: .6; }
.crm-slab-aside:not([open]) > summary::after { content: "▸"; }

/* ── DESKTOP DIVE #98–#107 (2026-08-16) ────────────────────────────────────────────────────────── */
/* #98 — the stripe and the red date carry the state; the full-card wash was three alarms per card,
   and 22 of them made a wall of pink where nothing stood out. */
.crm-card.bg-green-lt, .crm-card.bg-yellow-lt, .crm-card.bg-red-lt { background: var(--tblr-card-bg, #fff) !important; }
/* #99 — on hover-capable screens a card's action row sleeps until pointed at (or focused, so keyboard
   users lose nothing). Touch screens keep them always-on. */
@media (hover: hover) and (pointer: fine) {
    .crm-card .crm-card-acts { visibility: hidden; }
    .crm-card:hover .crm-card-acts, .crm-card:focus-within .crm-card-acts { visibility: visible; }
    /* #107 — the drag affordance appears with the pointer: grip dots at the card's edge. */
    .crm-card { position: relative; }
    .crm-card:hover::after { content: "⠿"; position: absolute; right: 6px; top: 4px;
        font-size: .7rem; color: var(--tblr-secondary, #99a3b0); pointer-events: none; }
}
/* #102 — the phase header's stage-hue underline breathes. */
.crm-phase-head { padding-bottom: .35rem; }
/* #104 — compact density: one-line cards, twice the survey per screen. The second line (due + actions)
   collapses to ride inline; ambient badges hide (their facts live on the open deal). */
.crm-board--compact .crm-card .card-body { padding: .3rem .5rem !important; display: flex; align-items: center; gap: .4rem; }
.crm-board--compact .crm-card .card-body > div { margin-top: 0 !important; }
.crm-board--compact .crm-card .card-body > div:first-child { flex: 1 1 auto; min-width: 0; }
.crm-board--compact .crm-card .crm-av { width: 20px; height: 20px; font-size: .6rem; }
.crm-board--compact .crm-card .badge.d-block { display: none !important; }
.crm-board--compact .crm-card { margin-bottom: .35rem !important; }
/* #103 — the --fold variant keeps the toolbar disclosure at EVERY width (plain .adm-toolbar still
   unwraps on desks). Per the <details> rules: display only under [open], containment reverted. */
@media (min-width: 768px) {
    .adm-toolbar.adm-toolbar--fold > summary { display: inline-flex; }
    .adm-toolbar.adm-toolbar--fold > .adm-toolbar-items { display: revert; }
    .adm-toolbar.adm-toolbar--fold[open] > .adm-toolbar-items { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .5rem; }
    .adm-toolbar.adm-toolbar--fold::details-content { content-visibility: revert; }
}

/* EMPTY ZONES SAY SO (visual #32). */
.crm-zone-empty { color: var(--adm-text-muted); font-size: 12.5px; padding: 10px 4px 12px;
                  border: 1px dashed var(--adm-status-muted-bg); border-radius: 8px; text-align: center;
                  margin: 2px 0 6px; }

/* CONVERSATION HIERARCHY (visual #27): direction rails and whispered system rows. */
.crm-hist-day { display: flex; align-items: center; gap: 10px; font-size: 10.5px; font-weight: 700;
                letter-spacing: .05em; text-transform: uppercase; color: var(--adm-text-muted); margin: 10px 0 4px; }
.crm-hist-day::before, .crm-hist-day::after { content: ""; flex: 1; border-top: 1px solid var(--adm-status-muted-bg); }
/* ONE COLOUR PER DIRECTION (audit 2026-08-19 #19): the edges and the chips were exactly crossed —
   an inbound row wore a blue edge and a green IN chip, outbound the reverse, so the same green
   meant "theirs" and "yours" centimetres apart. The CHIPS were right (their reply is the good
   thing): IN = green everywhere, OUT = blue everywhere, and a lead row's edge matches its own
   amber chip instead of borrowing inbound's. */
.crm-row--in   { border-left: 3px solid var(--adm-status-published-fg); }
.crm-row--out  { border-left: 3px solid var(--adm-status-scheduled-fg); }
.crm-row--lead { border-left: 3px solid var(--adm-status-draft-fg); }
.crm-row--sys { opacity: .68; font-size: 12.5px; }

/* MONEY READS AS MONEY (visual #33): tabular figures so columns of values line up digit-for-digit.
   One class, applied wherever a figure renders; the tiles get it by name below. .crm-money is the
   same rule under the name two views already used — it existed in markup with no stylesheet behind
   it (audit #21). */
.adm-money { font-variant-numeric: tabular-nums; }
.crm-money { font-variant-numeric: tabular-nums; }
.crm-tile-n { font-variant-numeric: tabular-nums; }
/* The live cart was the one money column without aligned digits (audit #21). */
.crm-cart-line, .crm-cart-total { font-variant-numeric: tabular-nums; }

/* PHONE: the draft row must not scroll away from the choosing (visual #35). Sticky above the tab bar
   while the picker is open; a background so tiers slide under it, not through it. */
@media (max-width: 767.98px) {
  #quoteCard .crm-quote-actions { position: sticky; bottom: calc(env(safe-area-inset-bottom, 0px) + 62px);
      background: var(--tblr-bg-surface, #fff); padding: 8px 4px; margin: 0 -4px; z-index: 5;
      box-shadow: 0 -6px 12px -8px rgba(20,30,50,.25); }
}

/* PHONE STEP CHIP (audit #21): the rail's words are hidden under 768px, so this names the rung being read
   and walks the focus. Targets meet the 40px floor. */
.crm-prog-chiprow { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.crm-prog-chipnav { display: inline-flex; align-items: center; justify-content: center; min-width: 40px;
                    min-height: 40px; font-size: 20px; color: var(--adm-link); text-decoration: none;
                    border: 1px solid var(--adm-status-muted-bg); border-radius: 8px; }
.crm-prog-chipnav.is-off { color: #c3c9d2; border-color: transparent; }
.crm-prog-chipbadge { flex: 1 1 auto; text-align: center; background: var(--adm-status-info-bg, #e7f0fb);
                      color: var(--adm-status-info-fg, #185fa5); font-weight: 600; font-size: 13px;
                      border-radius: 999px; padding: 9px 12px; text-decoration: none;
                      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-prog-words > a:hover .crm-prog-word { color: var(--adm-link); text-decoration: underline; }
.crm-prog-words > a.is-now .crm-prog-word  { color: var(--adm-accent); }
.crm-prog-words > a.is-now .crm-prog-state { color: var(--adm-accent); font-weight: 700; }
/* The tab you are reading: a tinted chip behind the word. Deliberately NOT the accent colour — the accent
   already means "the deal is here", and two things in one colour is how the bar stopped meaning anything
   the first time round. */
.crm-prog-words > a.is-focus {
    background: var(--adm-status-muted-bg, #eef1f6);
    border-radius: 6px; padding: 2px 4px; margin: -2px -4px 0;
}
.crm-prog-words > a.is-focus .crm-prog-word { text-decoration: underline; text-underline-offset: 2px; }

/* -- the slab: the only filled object on the page -------------------------- */
/* ONE colour, #0d2340, in every state — Graham's call (2026-07-29: "make the colour back to 0d2340").
   I argued for urgency-coloured backgrounds (slate when waiting, dark red when overdue); he saw the slab
   change colour under him and wanted it constant. Urgency still shows — the eyebrow pill says DUE / WAS
   DUE, and the follow-up rows carry red/orange — it just no longer repaints the whole slab. */
.crm-slab {
    background: #0d2340; border-radius: 12px; padding: 24px 30px 26px; color: #fff;
    margin-bottom: 18px; position: relative;
}
/* The slab's corner way-home (2026-08-24: "move Back to the table onto every blue heading… I don't
   want it beside Email Won Lost"): a quiet pill, top-right, far from the action buttons. */
.crm-slab-back { position: absolute; top: .7rem; right: .8rem; z-index: 2;
    color: rgba(255,255,255,.78); font-size: .8125rem; text-decoration: none;
    border: 1px solid rgba(255,255,255,.28); border-radius: 2rem; padding: .15rem .65rem; white-space: nowrap; }
.crm-slab-back:hover, .crm-slab-back:focus-visible { color: #fff; border-color: rgba(255,255,255,.6); }
/* WEIGHT = COLOUR (visual #26). Navy is reserved for YOUR move; a slab that says "waiting on them" must
   not shout as loudly as one that says "send it today". Overdue keeps navy — the red eyebrow carries the
   alarm — while waiting drops to a calm slate and done goes quietly green. */
.crm-slab--waiting { background: #42506b; }
.crm-slab--waiting .crm-slab-sub { color: #c3cede; }
/* OVERDUE IS A REAL STATE NOW (rail audit cross-cutting #4). This rule used to set #0d2340 — byte-identical
   to the default navy — so a class named for a state changed nothing and only looked like it did. The red
   eyebrow does still carry the alarm, which was the original defensible choice; what was missing was any
   reason for the class to exist. A left edge is the smallest honest difference: it reads at a glance in a
   column of slabs without turning the whole panel into an emergency. */
.crm-slab--overdue { background: #0d2340; box-shadow: inset 4px 0 0 #e05a47; }
.crm-slab--done { background: #12432a; }
.crm-slab--done .crm-slab-sub { color: #a8cbb4; }
.crm-slab--done .crm-slab-eyebrow { color: #7fbf95; }
/* THE QUIET SHAPE (rail audit cross-cutting #5). Five rungs have a state where nothing is yours to do, and
   each spent a full-height panel saying so — several with no button at all. Size is a promise about how
   much a thing is asking of you, and on those rungs the promise was false. Same element, same place in the
   page, a third of the room: the headline drops to body scale and the padding closes up, so the eye passes
   over it on the way to the conversation instead of stopping. Applied only when there is genuinely nothing
   to press — a waiting step carrying Follow up has something to offer and keeps its full size. */
/* THE COMPOSER'S RICH BODY (Graham 2026-08-20). Sized like the textarea it replaces so the modal does not
   change shape, and scrollable rather than growing without limit — a pasted thread should not push Send off
   the bottom of the screen. The placeholder is CSS-only because contenteditable has no placeholder
   attribute; :empty alone is not enough, since a browser leaves a stray <br> behind after you clear the box. */
.crm-cmp-rich {
    min-height: 11rem; max-height: 40vh; overflow-y: auto; height: auto;
    white-space: pre-wrap; word-break: break-word; cursor: text;
}
.crm-cmp-rich:empty::before,
.crm-cmp-rich:has(> br:only-child)::before {
    content: attr(data-placeholder); color: var(--tblr-secondary, #6c7a91); pointer-events: none;
}
.crm-cmp-rich p { margin: 0 0 .65rem; }
.crm-cmp-rich p:last-child { margin-bottom: 0; }
.crm-cmp-rich ul, .crm-cmp-rich ol { margin: 0 0 .65rem 1.25rem; padding-left: .5rem; }
.crm-cmp-rich a { text-decoration: underline; }
.crm-cmp-tools .btn { min-width: 2.25rem; }

/* A CART LINE THE CATALOGUE NO LONGER SELLS. Amber, not red: the line is not an error, it is a question
   nobody has answered — the package left and the price went with it. The warning row below the lines
   carries the instruction, since a colour alone never says what to do about it. */
.crm-cart-line--retired { color: #b45309; }
.crm-cart-line--retired .ti { color: #b45309; }
.crm-cart-warn {
    margin-top: 8px; padding: 8px 10px; border-radius: 6px; font-size: 12.5px; line-height: 1.45;
    background: #fff5e6; border: 1px solid #f0c98a; color: #7a4a09;
}

.crm-slab--compact { padding: 12px 16px; margin-bottom: 12px; }
.crm-slab--compact .crm-slab-head { font-size: 1rem; margin-bottom: 2px; }
.crm-slab--compact .crm-slab-sub { font-size: .8125rem; }
.crm-slab--compact .crm-slab-eyebrow { font-size: .625rem; margin-bottom: 3px; }
.crm-slab-eyebrow {
    display: inline-block; font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
    padding: 3px 9px; border-radius: 20px;
    background: var(--adm-status-draft-bg); color: var(--adm-status-draft-fg);
}
.crm-slab-head {
    font-size: 34px; line-height: 1.14; font-weight: 600; letter-spacing: -.015em;
    margin: 12px 0 8px; color: #fff; text-wrap: balance;
}
.crm-slab-sub { font-size: 13.5px; color: #9fb6d2; margin-bottom: 18px; max-width: 62ch; }
.crm-slab--waiting .crm-slab-sub, .crm-slab--overdue .crm-slab-sub { color: #d3d8de; }
/* #96 (2026-08-16 deep dive) — the slab's provenance paragraph folds behind one quiet line. Per the
   <details> rules: nothing sets display on the collapsing child. */
.crm-slab-more { margin: -10px 0 14px; }
.crm-slab-more > summary { list-style: none; cursor: pointer; font-size: 12px; font-weight: 600;
    color: #7f95b3; display: inline-flex; align-items: center; min-height: 40px; }
.crm-slab-more > summary::-webkit-details-marker { display: none; }
.crm-slab-more > summary::after { content: "▸"; margin-left: .35rem; transition: transform .12s; }
.crm-slab-more[open] > summary::after { transform: rotate(90deg); }
.crm-slab-more-b { font-size: 12.5px; color: #9fb6d2; max-width: 62ch; padding-top: 2px; }
/* The single bright target. One per slab — two would answer "where do I look"
   and then immediately re-ask "which one do I press". */
.crm-slab-go {
    display: inline-flex; align-items: center; gap: 8px;
    background: var(--adm-status-published-fg); color: #fff; border: 0;
    font-size: 17px; font-weight: 600; padding: 14px 28px; border-radius: 8px;
    text-decoration: none; cursor: pointer;
}
.crm-slab-go:hover { filter: brightness(1.08); color: #fff; }
/* Header #3 tiles: styled at the END of this file (the base .crm-tile rules must not out-cascade them). */
#hdrWho .crm-who { margin-bottom: 0; }

/* Won / Lost are one decision's two answers — one size (Graham 2026-08-18). !important because the
   mobile floor's `.btn:has(> i:only-child)` icon-sliver rule matches icon+TEXT buttons too (text nodes
   don't count for :only-child) and would re-pin these at 40px. */
/* The slab's exits carry Back's proportions (Graham 2026-08-19: "the font on the lost after appears a
   little small… we need more margin more like back"). btn-sm was leaving them at 12px with 1px 4px of
   padding against Back's 14px and 7px 16px — same height, but visibly meaner. Rounded to match too. */
.btn.crm-exit { min-width: 6.5rem !important; min-height: 40px; display: inline-flex; align-items: center;
    justify-content: center; font-size: 14px; padding: 7px 18px; border-radius: 8px; }
.crm-slab-alt { font-size: 12.5px; color: #8fb4e6; }
.crm-slab-alt a { color: #cfe0f5; text-decoration: underline; }
.crm-slab-card { background: #fff; color: var(--tblr-body-color, #1f2733); border-radius: 9px; padding: 16px 18px; margin-bottom: 18px; }
/* Two slab columns: the instruction left, "what you can do here" right (hidden when narrow — the aside is
   orientation, and orientation must never squeeze the button). */
.crm-slab-cols { display: flex; gap: 28px; align-items: flex-start; }
.crm-slab-main { flex: 1 1 auto; min-width: 0; }
.crm-slab-aside { flex: 0 0 300px; border-left: 1px solid rgba(255,255,255,.16); padding-left: 22px; }
.crm-slab-aside-h { font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: #8fb4e6; margin-bottom: 8px; }
/* TIP ROWS, not a paragraph stack (visual #34): each hint is a compact row with a hairline between, so
   three tips read as three glances. The icon is a quiet dot-in-circle, not an arrow shouting "go". */
.crm-slab-hint { font-size: 12.5px; color: #b9c9dd; line-height: 1.45; padding: 7px 0 7px 20px; position: relative;
                 border-top: 1px solid rgba(255,255,255,.10); }
.crm-slab-hint:first-of-type { border-top: 0; padding-top: 0; }
.crm-slab-hint::before { content: "◦"; position: absolute; left: 4px; color: #7d9cc4; font-size: 14px; }
@media (max-width: 991px) { .crm-slab-aside { display: none; } }

@media (max-width: 720px) {
    .crm-slab { padding: 18px 18px 20px; }
    .crm-slab-head { font-size: 26px; }
    .crm-prog-words { display: none; }   /* nine words do not fit; the bar still reads */
}

/* == HISTORY: one list, one line per item ==================================
   Notes and History were two lists of the same thing, and notes appeared in
   both. Now: column one is the date IN FULL (it was being clipped to
   "Jul 28, 2026 19:"), column two is the item, left-aligned. No chat bubbles
   — alternating left/right made two columns out of what is one sequence. */
/* FIVE REAL COLUMNS, so IN/OUT, sender, recipient and subject line up down the
   page instead of each row packing them at its own widths (Graham 2026-07-28:
   "create columns for Out/In, You --> robinsong@, Subject"). A note spans the
   three mail columns, because it has no from or to. */
.crm-row {
    display: grid;
    /* col 1 is the TYPE icon (Graham 2026-08-18: the channel icon replaced the check glyph and its word
       column — Lead/Note/Email/Text/Call is the icon, delivery state is its colour), col 3 the DIRECTION
       (Graham 2026-08-04: "a clear In/Out column by itself").
       The LAST column belongs to the delete button. It has to be declared: without a column of its own the
       bin was simply the next grid item after a note that already spanned to the end, so it wrapped onto a
       second line and sat under the status dot, in the date column (Graham 2026-08-01: "trash icon not
       right"). A mail row fills 3-6 from its summary's four display:contents children, a note spans 4-6,
       and both then land the bin in 7. */
    grid-template-columns: 20px 152px 40px minmax(0, 1.1fr) minmax(0, 1.1fr) minmax(0, 2.4fr) 30px;
    gap: 4px 12px;
    padding: 11px 14px;                        /* was 7px — more air between rows */
    border-bottom: 1px solid var(--adm-status-muted-bg);
    align-items: baseline;
}
/* The flag: green tick = it left the building, red "!" = it did not. Colour AND glyph, so it survives
   being printed, being colour-blind, and being glanced at (Graham 2026-07-29). */
.crm-flag        { font-size: 13px; line-height: 1; }
.crm-flag--ok    { color: var(--adm-status-published-fg); }
.crm-flag--bad   { color: var(--adm-status-expired-fg); }
.crm-flag--wait  { color: var(--adm-status-draft-fg); }
/* A note stays visibly a lesser thing than a message that went to a client — same icon size, quieter
   grey. That contrast is the point: "a good visual of notes over emails". */
.crm-flag--note  { color: #b9bfc7; }
/* A failed send tints its whole row, because a tick you have to hunt for is not a warning. */
.crm-row--bad    { background: var(--adm-status-expired-bg); }
.crm-row--bad .crm-mail-subj { color: var(--adm-status-expired-fg); }
.crm-row-head {
    font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--adm-text-muted); padding-top: 12px; padding-bottom: 6px;
    border-bottom: 1px solid var(--adm-border); position: sticky; top: 0;
    background: var(--tblr-card-bg, #fff); z-index: 1;
}
.crm-row-note { grid-column: 4 / 7; }          /* a note has no direction or from/to — col 3 stays honestly blank and the text spans from/to/subject (never the bin's column) */
/* grid-ROW 1 as well as column 7. Giving it a column stopped it landing in the date cell, but not from being
   auto-placed onto a SECOND row: the summary's four display:contents children fill row 1, and the bin — the
   next item in source order — was pushed underneath them, making every deletable row half again as tall as a
   note (Graham 2026-08-03: "each row fit on one line"). Naming the row is what actually pins it. */
.crm-row-del  { grid-column: 7; grid-row: 1; justify-self: end; align-self: center; }
/* An email body: hidden until its row is opened, and then spanning the whole row. It is a SIBLING of the
   <details> rather than its child, so this stylesheet owns both the hiding and the span — see the note in
   Index.cshtml for why the child version could only ever have one of the two. */
.crm-row-open { display: none; grid-column: 1 / -1; }
details.crm-mail[open] + .crm-row-open { display: block; }
.crm-row:last-child { border-bottom: 0; }
.crm-row-when {
    font-size: 12px; color: var(--adm-text-muted); white-space: nowrap;
    font-variant-numeric: tabular-nums;   /* dates line up as a column */
}
.crm-row-sys { color: var(--adm-text-muted); }

/* ── THE LEAN HISTORY (client record Activity tab; cut down further 2026-08-21) ────────────────────
   "About 50px tall carrying about 13px of text — the worst combination: generous space spent on small
   type, most of it on things that never change" (Graham). Fixed as a unit: smaller box, bigger type.

   Six columns now — flag · time · direction · subject · bin, the dot/glyph column having replaced the
   text IN/OUT pill — at a shorter row (9-10px vertical padding, ~40px tall) carrying LARGER text (15px
   event / 13px-minimum metadata) than the 50px row it replaces carried at 12.5-14px. FROM and TO are
   still not rendered, not hidden, for the reason the original lean pass gave: the summary's children
   ARE the grid items, and hiding them would leave two columns holding the width that was the point. */
.crm-hist--lean .crm-row { grid-template-columns: 20px 52px 28px minmax(0, 1fr) 30px; padding: 9px 14px; }
/* A note has no FROM/TO either, but it DOES now have a direction dot (col 3) — see .crm-dir-dot below —
   so its text starts one column later than the original lean pass had it, at col 4 only. */
.crm-hist--lean .crm-row-note { grid-column: 4; }
.crm-hist--lean .crm-row-del  { grid-column: 5; }
/* ONE LINE PER EVENT. Contract receipts used to run to three hundred characters of package names;
   they're summarised now (see .crm-hist-fold below) but the rule stays for anything else that runs
   long — a note typed with no line breaks, an imported enquiry blurb. Clipped with the full text in
   the title, the way the subject cell has always behaved. The opened body is exempt — that is the
   thing you clicked for. */
.crm-hist--lean .crm-row-note, .crm-hist--lean .crm-row-sys {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.crm-hist--lean .crm-row-open { white-space: normal; overflow: visible; }
/* A note carries its text AND its stage chip. .crm-note-text is display:block, so the chip was landing
   on a second line and making every editable note half again as tall as the messages around it. One flex
   row: the text takes the room and clips, the chip keeps its width at the end. */
.crm-hist--lean .crm-row-note { display: flex; align-items: baseline; gap: .4rem; min-width: 0; }
.crm-hist--lean .crm-note-text {
    display: inline-block; flex: 0 1 auto; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.crm-hist--lean .crm-row-note > .crm-mail-stage { flex: 0 0 auto; margin-left: 0 !important; }

/* ── TYPE AND RHYTHM (Graham 2026-08-21) ─────────────────────────────────────────────────────────
   "Event text 15px/1.45, in the primary ink colour. Metadata 13px muted. Nothing below 13px." A
   system receipt (a contract note, a stage-change line) is still an EVENT — it earns the same size and
   the same ink as a real message, which is why .crm-row--sys's inherited dimming is switched off here
   rather than just left to apply underneath the new rule; the day separator above is left alone, since
   that is section chrome, not a row's own metadata. */
.crm-hist--lean .crm-mail-subj, .crm-hist--lean .crm-row-note {
    font-size: 15px; line-height: 1.45; color: var(--tblr-body-color, #1f2733);
}
.crm-hist--lean .crm-row--sys { opacity: 1; font-size: inherit; }
.crm-hist--lean .crm-row-sys { color: var(--tblr-body-color, #1f2733); }
.crm-hist--lean .crm-row-when { font-size: 13px; }
.crm-hist--lean .crm-mail-stage {
    font-size: 10.5px; padding: 2px 8px;   /* the chip stays a small pill by shape; ITS TEXT clears the floor */
}
.crm-hist--lean .crm-mail-stage, .crm-hist--lean .badge { font-size: 13px; }
.crm-hist--lean .badge i.ti { font-size: 13px; }

/* ── ONE LEFT INDICATOR, MEANING ONE THING (Graham 2026-08-21: "a coloured border bar AND an icon,
   both unlabelled — keep one, and make it mean exactly one thing") ─────────────────────────────────
   The flag (col 1) already means something specific — what kind of event, and how it went — and stays.
   The border-left bar duplicated DIRECTION, which the new glyph in col 3 now states outright; keeping
   both would be the same fact rendered two ways at once, which is the "unlabelled and overlapping"
   complaint itself. crm-row--bad's whole-row failure tint is a THIRD, different fact (this one didn't
   land) and is untouched — it was never about direction. */
.crm-hist--lean .crm-row--in, .crm-hist--lean .crm-row--out, .crm-hist--lean .crm-row--lead { border-left: 0; }

/* ── THE DIRECTION GLYPH (Graham: "down-arrow inbound, up-arrow outbound, dot for system events and
   notes... colour only the inbound ones — they are the rows that need answering") ───────────────────
   Icon-only, no pill background — the full reading's IN/OUT badge is unchanged (see .crm-mail-dir
   above); this is lean's OWN narrower rendering of the same column. Outbound and the note-dot are
   deliberately the muted ink: an answered message and a private note are not asking for anything. */
.crm-hist--lean .crm-mail-dir {
    background: none; padding: 0; border-radius: 0; font-size: 15px; font-weight: 400; letter-spacing: 0;
}
.crm-hist--lean .crm-mail-dir.out { color: var(--adm-text-muted); }
.crm-hist--lean .crm-mail-dir.in  { color: var(--adm-status-published-fg); }
.crm-hist--lean .crm-mail-dir.lead { color: var(--adm-status-draft-fg); }
.crm-dir-dot {
    grid-column: 3; display: flex; align-items: center; justify-content: center;
    color: var(--adm-text-muted); font-size: 15px; line-height: 1;
}
.crm-dir-dot::before { content: "•"; }

/* ── THE DELETE ICON, ON DEMAND (Graham: "reveal on row hover and on keyboard focus... twenty-five
   red trash cans down the right edge give a destructive action permanent visual presence") ──────────
   Gated to hover-capable pointers, same convention as the copy-glyphs beside the identity block
   further down this file: a touch device has no hover to reveal it on, so it stays shown there rather
   than becoming unreachable. :focus-within keeps it reachable by keyboard on every device — Tab finds
   the button and its own focus ring shows it, hover or not. */
@media (hover: hover) and (pointer: fine) {
    .crm-hist--lean .crm-row-del { opacity: 0; transition: opacity .1s; }
    .crm-hist--lean .crm-row:hover .crm-row-del,
    .crm-hist--lean .crm-row-del:focus-within { opacity: 1; }
}

/* ── COLLAPSED ROWS (bursts of the same note, and long contract receipts) — Graham 2026-08-21 ─────
   Native <details>/<summary> rather than a JS toggle: no click handler to wire, the row's height is
   simply however tall the open content is (the grid auto-sizes to it), and it still works with the
   script that drives the filter/order controls off, because nothing about opening one depends on it. */
.crm-hist-fold summary {
    cursor: pointer; list-style: none;
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.crm-hist-fold summary::-webkit-details-marker { display: none; }
.crm-hist-fold summary::before { content: "▸ "; color: var(--adm-text-muted); font-size: 11px; }
.crm-hist-fold details[open] summary::before { content: "▾ "; }
.crm-hist-fold-body {
    margin-top: 4px; padding-left: 14px; border-left: 2px solid var(--adm-status-muted-bg);
    color: var(--adm-text-muted); font-size: 13px; line-height: 1.5; white-space: normal;
}
.crm-hist-fold-item + .crm-hist-fold-item { margin-top: 3px; }
.crm-hist-fold-item .crm-row-when { margin-right: 6px; }

/* A note: one line, click the text to edit it in place. */
.crm-note-text { display: block; cursor: text; border-radius: 4px; padding: 1px 4px; margin: -1px -4px; }
.crm-note-text:hover, .crm-note-text:focus { background: var(--adm-status-draft-bg); outline: none; }
.crm-note-edit { display: flex; gap: 6px; align-items: center; }
.crm-note-edit input { flex: 1 1 auto; }

/* An email: one line — direction, from, to, subject — opening to the body. */
/* The mail row is laid out by the parent grid now, so the summary is display:contents —
   its children become grid items and line up with every other row's. */
.crm-mail { display: contents; }
.crm-mail > summary { display: contents; cursor: pointer; list-style: none; }
/* The body used to live INSIDE this <details>, which Chrome now wraps in a ::details-content box. That box became
   the grid item, so the body was 20px wide and rendered one character per line; making the pseudo transparent
   fixed the width but took away the box that hides a CLOSED row, so every row rendered expanded. Neither state
   could be had without losing the other, so the body moved out to be a sibling — see .crm-row-open above. */
.crm-mail > summary::-webkit-details-marker { display: none; }
.crm-mail > summary > * { cursor: pointer; }

/* -- the deal rail: person, three tiles, stage trail -----------------------
   Replaced six identical property rows (Status / First name / Last name / Email / Phone / Website)
   that read as a form to fill in rather than a deal to act on. Each tile is a <details> whose
   SUMMARY is the tile, so the editor opens in place and nothing below it moves. */
/* ONE declaration (audit #25): a second .crm-who five lines down re-set align-items — merged. */
.crm-who { display: flex; align-items: flex-start; gap: 10px; padding-bottom: 12px; }
.crm-who-av { width: 38px; height: 38px; flex: none; border-radius: 50%; display: flex;
              align-items: center; justify-content: center; font-weight: 700; font-size: 13px;
              background: var(--adm-status-info-bg, #e6f1fb); color: var(--adm-status-info-fg, #185fa5); }
.crm-who-txt { min-width: 0; }
/* SPLIT VIEW — two language panes beside each other in the page editor. Bootstrap tabs hide every pane but
   the active one, so the two chosen panes are forced visible and laid out as equal columns. min-width:0 is
   load-bearing: without it a long block label or a wide toolbar pushes a flex column past its share and the
   right pane runs off the screen. Collapses back to one column on narrow screens, where side-by-side is worse
   than paging between them. */
.tab-content.cms-split { display: flex; gap: 1rem; align-items: flex-start; position: relative; }
/* The add-block toolbar spans BOTH panes, because everything on it applies to both — a block added on the
   left is mirrored to the right. Full width also stops it wrapping to three rows, which is what made the
   half-width version unreadable.
   It is floated out of the left pane's header rather than moved to a new element on purpose: its buttons are
   driven by a delegated handler bound to that pane, so a node moved outside it would go dead. Absolute
   against .tab-content keeps it in the pane's DOM while rendering across the top of both. */
.tab-content.cms-split .cms-split-left > .block-editor > .card-header > .btn-list.ms-auto {
    /* Absolute resolves against .block-editor, not .tab-content — a Bootstrap .card is itself positioned, and
       making it static to escape that would re-home every other absolute child inside it. So it stays in the
       card and is stretched instead; the exact width is set in JS from the real container, because a
       percentage here resolves against the card's padding box and lands a few pixels short. */
    position: absolute; top: 0; left: 0; right: auto; margin: 0; z-index: 3;
    display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: .375rem;
    padding: .5rem .75rem;
    background: var(--tblr-card-bg, #fff);
    border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 8px;
}
.tab-content.cms-split > .tab-pane { display: none; }
.tab-content.cms-split > .tab-pane.cms-split-show {
    display: block; opacity: 1; flex: 1 1 0; min-width: 0;
}
/* ONE toolbar, not two. A block added on the left is mirrored to the right anyway, so a second copy of
   RichText / Columns / Controls / … is a duplicate control for an action that already applies to both — and at
   half width the two of them wrapped to three rows each and shoved the blocks apart. It stays on the LEFT,
   the pane that owns the structure. The right pane keeps what translating needs: Expand all, Copy page from,
   Translate with AI, and the blocks. */
.tab-content.cms-split .cms-split-right > .block-editor > .card-header > .btn-list.ms-auto { display: none; }
/* The optional label field is the other width hog: without it the buttons fit on one line instead of
   wrapping and doubling every row's height. It's still there in single view. */
.tab-content.cms-split .block-label { display: none; }
/* One set of buttons, not two. Full-width, Hide, Delete and the More menu all act on STRUCTURE, which is
   mirrored — pressing Delete on either side removes the block from both — so a second copy is two controls
   for one action, and it was the thing wrapping each row onto a second line. They stay on the left, beside
   the structure they own. Edit is the exception and stays on BOTH, because the words are per-language and
   editing them is the entire point of this view. */
.tab-content.cms-split .cms-split-right .block-list .card-header > .btn-list.ms-auto > *:not(.block-editbody) {
    display: none;
}
/* Stop the type chip truncating to "CALL TO…" now that there's room for it. */
.tab-content.cms-split .block-list .badge { white-space: nowrap; }
/* Adding blocks belongs to the structure-owning pane; the right one is words-only. */
.tab-content.cms-split .cms-split-right .qadd-bar { display: none; }
/* "AI Trans" only on the LEFT (Graham). Both copies do the same thing — translate this block into the other
   language — so side by side they are one action offered twice. It belongs with the SOURCE text: you read the
   English and ask for that to be translated. */
.tab-content.cms-split .cms-split-right [data-aitrans] { display: none; }
/* The default language's name field, moved down from the header so it sits beside its translation. It arrives
   wearing the big borderless heading style it had up there, which reads as a title rather than a field next
   to a normal input — so it's dressed to match the one it's paired with. */
.cms-split-name .cms-title-input {
    flex: 1 1 auto; min-width: 0; font-size: 1rem; font-weight: 400; line-height: 1.4;
    padding: .4375rem .75rem; background: var(--tblr-card-bg, #fff);
    border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 4px;
}

@media (max-width: 1399.98px) {
    /* Below this the two panes are too narrow to read side by side even trimmed down. */
    .tab-content.cms-split { display: block; }
    .tab-content.cms-split > .tab-pane.cms-split-show:not(.active) { display: none; }
}
/* The name links to the client record. It keeps the plain heading look it had as a span — turning the
   person's name blue would read as a mailto or an external site — and only announces itself on hover. */
.crm-who-name { display: block; font-weight: 600; line-height: 1.25; color: inherit; text-decoration: none; }
.crm-who-name:hover, .crm-who-name:focus-visible { color: var(--adm-primary, #0d6efd); text-decoration: underline; }
/* Email, phone and website: one stack of click-to-act lines, each with its own icon so the kind is
   readable without a label column. Truncated rather than wrapped — a rail is narrow. */
.crm-who-line { display: block; font-size: 12.5px; line-height: 1.6;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-who-line > .ti { margin-right: 6px; color: var(--adm-text-muted); }
/* The address is a button now (it opens the CRM composer, not the mail client) but must still read as
   the line of contact detail it replaced — no button chrome, just the link colour and an underline. */
.crm-who-mail { border: 0; background: none; padding: 0; text-align: left; width: 100%;
                color: var(--adm-link); text-decoration: underline; text-underline-offset: 2px; }
.crm-who-mail:hover { color: var(--adm-accent); }

.crm-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
             border-top: 1px solid var(--adm-status-muted-bg); padding-top: 12px; }
.crm-tile { background: var(--adm-status-muted-bg, #f6f8fa); border-radius: 8px; padding: 8px 10px;
            min-width: 0; }
.crm-tile > summary { list-style: none; cursor: pointer; display: block; }
.crm-tile > summary::-webkit-details-marker { display: none; }
.crm-tile-l { display: block; font-size: 11px; color: var(--adm-text-muted); line-height: 1.3; }
.crm-tile-n { display: block; font-size: 16px; font-weight: 600; line-height: 1.3; margin-top: 2px;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-tile--due  { background: var(--adm-status-pending-bg); }
.crm-tile--due  .crm-tile-l, .crm-tile--due  .crm-tile-n  { color: var(--adm-status-pending-fg); }
.crm-tile--late { background: var(--adm-status-expired-bg); }
.crm-tile--late .crm-tile-l, .crm-tile--late .crm-tile-n { color: var(--adm-status-expired-fg); }
/* Booked and comfortably ahead: a date is set and it is not upon you. Green because that is the whole answer
   to "does this need me?" — the same green the board cards use for a deal with nothing owed on it today. */
.crm-tile--ok   { background: var(--adm-status-published-bg); }
.crm-tile--ok   .crm-tile-l, .crm-tile--ok   .crm-tile-n  { color: var(--adm-status-published-fg); }
/* The editor is wider than one third of the rail, so it breaks out across all three columns. */
.crm-tile[open] { grid-column: 1 / -1; }

/* .crm-trail deleted (audit #1): the 4-row retelling of the rail is gone; its dates live on the rail. */


/* "NOTHING CHANGED" — the receipt for a move that was abandoned. Dismissing the why-lost prompt rightly
   leaves the deal where it was, but doing that in silence is how Graham walked away certain a deal had
   been marked Lost when it was still Won (2026-08-19). Amber, not red: nothing went wrong. */
.crm-abandon { margin-top: 6px; font-size: 12px; font-weight: 600; border-radius: 7px; padding: 5px 9px;
               background: var(--adm-status-pending-bg); color: var(--adm-status-pending-fg);
               transition: opacity .45s ease; }
.crm-abandon--float { position: fixed; z-index: 1080; top: 14px; left: 50%; transform: translateX(-50%);
                      margin-top: 0; box-shadow: 0 6px 24px -8px rgba(16,24,40,.45); }
.crm-abandon.is-out { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .crm-abandon { transition: none; } }

/* HISTORY UNDER THE WORK (Graham 2026-08-19: "have the history as a tab AND at the bottom of each page").
   One #crmHistory in the DOM, always shown; the History tab is just the state where none of the four work
   cards is. When it sits UNDER one of them it gets a gap and a rule, so it reads as "and here is what has
   happened" rather than as the bottom of the card above it. On its own tab it needs neither. */
.crm-hist-under { margin-top: 1rem; border-top: 2px solid var(--adm-status-muted-bg); padding-top: .75rem; }

/* -- THE ROSTER TABLE: contacts that are controls -------------------------- */
/* Graham 2026-08-19: the email opens the CRM composer, the number opens Quo, a greyed action opens the
   box that fills in what is missing. So none of these are decoration — every one is a button, and has to
   look like the data it replaced rather than like a toolbar bolted onto a table. */
.crm-trow { border-left: 4px solid transparent; }
/* The SAME three tones the board circles and the queue rails speak. Lost and managed clients get no
   colour at all: a closed client burning red would be a lie about work going undone. */
.crm-trow--red   { border-left-color: var(--adm-status-expired-fg); }
.crm-trow--amber { border-left-color: var(--adm-status-pending-fg); }
.crm-trow--green { border-left-color: var(--adm-status-published-fg); }
/* THE DUE DATE IS COLOURED (Graham 2026-08-20: "Can you colour code the due date (green, orange, red)").
   It was SUPPOSED to be already — the classes were on the cell and had been since the table view was built.
   They never showed. Tabler paints table cells through `.table > :not(caption) > * > *`, which carries one
   class and one element of specificity, exactly matching a bare `.crm-due--red`; on a tie the later rule
   wins, and the two files load in the other order. The proof was in the computed style: font-weight came
   through at 700 while colour stayed the default ink #182433, because that rule sets colour and not weight.
   A td qualifier is the whole fix — it costs one element of specificity and the tie breaks our way.

   Green is new. There was no rule for it at all, so "on track" simply inherited body text and the scale had
   two steps where it claimed three. Kept muted rather than the full chip green: a column where every healthy
   row shouts is a column you stop reading, and green here means "nothing to do", which should be restful. */
/* SORTABLE HEADERS. A button so the keyboard reaches it, styled to look like the header text it replaces —
   the affordance is the arrow, which is enough once you know the column is clickable and out of the way
   when you don't. */
.crm-sort {
    background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer;
    display: inline-flex; align-items: center; gap: .3rem; text-transform: inherit; letter-spacing: inherit;
}
.crm-sort:hover { color: var(--adm-link); }
/* A TEXT ARROW, not an icon-font class. ti-arrows-sort is not in this Tabler build, so the original <i>
   drew nothing at all and the headers read as plain text — the affordance was invisible, which is exactly
   what got reported. Raised from .45 to .6 as well: the point of the mark is to say "this is clickable"
   before you hover, and at .45 it was there without being noticed. */
.crm-sort-ar { opacity: .6; font-size: .95em; line-height: 1; }
.crm-sort:hover .crm-sort-ar { opacity: 1; }
th[aria-sort="ascending"] .crm-sort-ar,
th[aria-sort="descending"] .crm-sort-ar { opacity: 1; color: var(--adm-link); font-weight: 700; }

td.crm-due--red    { color: var(--adm-status-expired-fg);   font-weight: 700; }
td.crm-due--amber  { color: var(--adm-status-pending-fg);   font-weight: 700; }
td.crm-due--green  { color: var(--adm-status-published-fg); font-weight: 500; }

.crm-reach { display: inline-flex; align-items: center; gap: 5px; max-width: 100%; }
/* .crm-reach-btn is GONE (Graham 2026-08-20: "remove these icons on the left of email and phone"). The
   leading ✉/✆ duplicated the action row three columns along, and the address itself is the control. Its
   rules go with it — dead CSS naming a deleted control is how the next person concludes the markup is
   missing rather than retired. NOTE the hover below: it used to read ".crm-reach-btn:hover,
   .crm-reach-txt:hover", so deleting the whole line would have quietly stripped the hover colour off the
   address button, which is very much still there. Only the dead half was removed. */
.crm-reach-txt { border: 0; background: none; padding: 0; color: var(--adm-link); font-size: inherit;
                 text-decoration: underline; text-underline-offset: 2px; min-width: 0; overflow: hidden;
                 text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.crm-reach-txt:hover { color: var(--adm-accent); }
.crm-copy { border: 0; background: none; padding: 0 2px; color: var(--adm-text-muted); font-size: 12px; flex: 0 0 auto; }
.crm-copy:hover { color: var(--adm-accent); }
.crm-copy.ok { color: var(--adm-status-published-fg); }
/* A gap states itself in words and is clickable — "no number on file" is a question with an answer. */
.crm-missing { border: 0; background: none; padding: 0; color: var(--adm-text-muted); font-size: 12.5px;
               font-style: italic; text-decoration: underline dotted; text-underline-offset: 3px; }
.crm-missing:hover { color: var(--adm-accent); }

.crm-acts { display: flex; gap: 4px; justify-content: flex-end; }
.crm-ico { width: 28px; height: 26px; border: 1px solid var(--adm-border); border-radius: 6px;
           display: inline-grid; place-items: center; font-size: 12px; line-height: 1;
           color: var(--adm-link); background: transparent; text-decoration: none; }
.crm-ico:hover { border-color: var(--adm-accent); color: var(--adm-accent); }
/* Greyed means "not on file", NOT "disabled" — it still opens the popup that fixes it, so it keeps a
   pointer and a hover. A dead-looking control that does nothing teaches you to stop clicking. */
.crm-ico.off { color: var(--adm-text-muted); opacity: .45; }
.crm-ico.off:hover { opacity: .85; border-color: var(--adm-accent); }
.crm-stagepick { cursor: pointer; }
.crm-stagesel { min-width: 9rem; }
.crm-prevnote { background: var(--adm-status-muted-bg); border-radius: 7px; padding: 8px 10px; font-size: 13px; }

@media (max-width: 575.98px) {
    .crm-ico { width: 34px; height: 34px; }
    /* The .crm-reach-btn phone sizing went with the control itself — see the note above. */
}

/* -- THE FOLLOW-UP QUEUE: two lines, two panes ----------------------------- */
/* Graham 2026-08-19, study 10: "detail opens beside, not below". Replaces .crm-follow and the separate
   "Follow-ups due" table — one list, one row per person. The panel is sticky rather than in-flow so the
   list never reflows when you pick somebody, which is the whole reason it beat the drawer. */
.crm-queue-split { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(260px, .9fr);
                   gap: 12px; padding: 12px; align-items: start; }
.crm-queue-list  { border: 1px solid var(--adm-status-muted-bg); border-radius: 9px; overflow: hidden; min-width: 0; }
.crm-queue-panel { border: 1px solid var(--adm-status-muted-bg); border-radius: 9px; padding: 12px 14px;
                   position: sticky; top: 12px; min-width: 0; }
/* Below a wide desk the panel goes ABOVE the list: a panel under fifteen rows is a panel nobody scrolls to. */
@media (max-width: 1100px) {
    .crm-queue-split { grid-template-columns: minmax(0, 1fr); }
    .crm-queue-panel { position: static; order: -1; }
}

.crm-qrow { display: flex; align-items: center; gap: 10px; padding: 8px 11px; font-size: 13px;
            border-bottom: 1px solid var(--adm-status-muted-bg); border-left: 4px solid transparent;
            color: var(--tblr-body-color, #1f2733); cursor: pointer; }
.crm-qrow:last-of-type { border-bottom: 0; }
/* The heat reads as an EDGE, not a wash. Fifteen tinted two-line rows is more colour than the fact
   deserves, and the chip counts in the header already say the same three words. */
.crm-qrow--red    { border-left-color: var(--adm-status-expired-fg); }
.crm-qrow--amber  { border-left-color: var(--adm-status-pending-fg); }
.crm-qrow--green  { border-left-color: var(--adm-status-published-fg); }
.crm-qrow.is-on   { background: var(--adm-accent-soft); }
.crm-qrow:hover   { background: var(--adm-status-muted-bg); }
.crm-qrow.is-on:hover { background: var(--adm-accent-soft); }
.crm-qrow:focus-visible { outline: 2px solid var(--adm-accent); outline-offset: -2px; }

.crm-qav { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
           font-size: 11px; font-weight: 700; flex: 0 0 auto; text-transform: uppercase;
           background: var(--adm-status-muted-bg); color: var(--adm-text-muted); }
/* Same rule the board's circles took in v2026.08.19.15 — a colour means one thing everywhere. */
.crm-qav--red   { background: var(--adm-status-expired-bg);   color: var(--adm-status-expired-fg); }
.crm-qav--amber { background: var(--adm-status-pending-bg);   color: var(--adm-status-pending-fg); }
.crm-qav--green { background: var(--adm-status-published-bg); color: var(--adm-status-published-fg); }

.crm-qtext { flex: 1; min-width: 0; }
.crm-ql1, .crm-ql2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-ql1 .crm-qname { font-weight: 700; }
.crm-qverb { color: var(--adm-text-muted); }
.crm-ql2   { font-size: 12px; color: var(--adm-text-muted); margin-top: 1px; }
/* Line two is a list of facts, so it separates them itself rather than the markup carrying bullets. */
.crm-ql2 > span + span::before { content: " · "; }
.crm-qdue--red   { color: var(--adm-status-expired-fg); font-weight: 700; }
.crm-qdue--amber { color: var(--adm-status-pending-fg); font-weight: 700; }
.crm-qbtn { flex: 0 0 auto; }

.crm-qp-head { display: flex; align-items: center; gap: 9px; margin-bottom: 4px; }
.crm-qp-head b { font-size: 15px; }
.crm-qp-line { font-size: 12.5px; margin-bottom: 9px; }
.crm-qp-tbl  { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.crm-qp-tbl td { padding: 4px 6px; border-bottom: 1px solid var(--adm-status-muted-bg); word-break: break-word; }
.crm-qp-tbl tr:last-child td { border-bottom: 0; }
.crm-qp-tbl td:first-child { color: var(--adm-text-muted); width: 84px; }
/* WHAT THEY ARE ON, one line each — the live cart, not a quote. A one-time line is marked because a
   reader scanning a column of "/mo" will otherwise add it into the monthly total. */
.crm-qp-pkg { list-style: none; margin: 0; padding: 0; }
.crm-qp-pkg li { padding: 1px 0; }
.crm-qp-pkg li.is-once { color: var(--adm-text-muted); }
.crm-qp-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.crm-qchips .btn { font-size: 11.5px; }

/* Phone floor: 40px targets, and the two lines WRAP rather than ellipsising away the answer.
   Measured at 375 before this rule: line one ran 388px into a 184px box, so "21d late" — the single
   most important fact on the row — was clipped off the right edge on every card, and line two showed a
   truncated address and nothing else. A row that hides how late it is has no reason to exist, so on a
   phone it is allowed to be three lines tall instead. */
@media (max-width: 575.98px) {
    .crm-queue-split { padding: 8px; gap: 8px; }
    .crm-qrow { min-height: 40px; padding: 8px 9px; align-items: flex-start; }
    .crm-qav { margin-top: 1px; }
    .crm-ql1, .crm-ql2 { white-space: normal; overflow: visible; text-overflow: clip; }
    .crm-qbtn { min-height: 32px; margin-top: 1px; }
}
/* QUOTE STAGING — one collapsible group per service, so all 28 packages can be LISTED without all 28 having
   to be READ. Open where something is ticked; the heading keeps the price range, so a shut group still says
   what is inside it. */
.crm-stage-group > summary        { cursor: pointer; padding: 3px 0; user-select: none; }
.crm-stage-group > summary:hover  { color: var(--tblr-body-color, #1f2733); }
.crm-stage-group[open] > summary  { margin-bottom: 2px; }

/* WHICH AGREEMENT COVERS WHICH SERVICE, at a glance.
   GREEN = this agreement covers it. NEUTRAL = it is on the fallback, which is a normal arrangement and not a
   problem — amber was wrong here: on this admin every amber and red means "something needs attention", and
   using them for an ordinary state trains you to ignore the ones that matter (Graham 2026-08-01: "Can we do
   not orange or red. It means warning i find."). Muted = another agreement has it.

   Laid out as its own flex row rather than leaning on .form-check-inline: that class positions the input with
   a negative margin so it can hang outside the label box, which is fine until the box gets padding and a
   border — then the tick floats away from the words it belongs to, which is what it did. */
.crm-cover-item {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: 3px 10px; margin: 0 .4rem .4rem 0;
    border-radius: 6px; border: 1px solid var(--adm-border, #dfe3e8); background: #fff;
}
.crm-cover-item .form-check-input { margin: 0; flex: 0 0 auto; }
.crm-cover-item .form-check-label { margin: 0; }

.crm-cover-item.is-mine  { background: var(--adm-status-published-bg); border-color: var(--adm-status-published-fg); }
.crm-cover-item.is-mine  .form-check-label { color: var(--adm-status-published-fg); font-weight: 600; }
/* Deliberately quiet: a service on the fallback is covered, just not by a named agreement. */
.crm-cover-item.is-free  { background: var(--adm-bg-subtle, #f6f8fa); }
.crm-cover-item.is-other { background: transparent; border-style: dashed; }
.crm-cover-item.is-other .form-check-label { color: var(--adm-text-muted); }

/* The step headings are editable, but they are still HEADINGS — a row of full-width form controls would make
   the page read as a form rather than as a workflow. Borderless until you go near it, then obviously a field. */
.crm-step-title {
    font-weight: 600; font-size: 1.05rem; width: auto; min-width: 22rem; max-width: 100%;
    border-color: transparent; background: transparent; padding-left: .25rem;
}
.crm-step-title:hover  { border-color: var(--adm-border, #dfe3e8); background: #fff; }
.crm-step-title:focus  { background: #fff; }

/* The clock, editable in place. It sits with the step it governs rather than only on a settings page, because
   "how long have I got here" is a question you ask while looking at the step. Same setting either way. */
.crm-step-clock { display: inline-flex; align-items: center; gap: .3rem; flex: 0 0 auto; }
.crm-step-clock input  { width: 4.5rem; text-align: right; }
.crm-step-clock select { width: auto; }

/* THE CALENDAR uses the board's traffic light, not FullCalendar's default blue — the whole point of the page
   is that a deal which is red on the board is red here, on the same day. */
.crm-cal-green  { background: var(--adm-status-published-bg) !important; border-color: var(--adm-status-published-fg) !important; }
.crm-cal-green  .fc-event-title, .crm-cal-green .fc-event-time { color: var(--adm-status-published-fg) !important; }
.crm-cal-yellow { background: var(--adm-status-pending-bg) !important; border-color: var(--adm-status-pending-fg) !important; }
.crm-cal-yellow .fc-event-title, .crm-cal-yellow .fc-event-time { color: var(--adm-status-pending-fg) !important; }
.crm-cal-red    { background: var(--adm-status-expired-bg) !important; border-color: var(--adm-status-expired-fg) !important; }
.crm-cal-red    .fc-event-title, .crm-cal-red .fc-event-time { color: var(--adm-status-expired-fg) !important; font-weight: 600; }

.crm-mail-dir {
    font-size: 10px; font-weight: 700; letter-spacing: .05em; padding: 1px 6px;
    border-radius: 999px; flex: 0 0 auto;
}
.crm-mail-dir.out { background: var(--adm-status-scheduled-bg); color: var(--adm-status-scheduled-fg); }
.crm-mail-dir.in  { background: var(--adm-status-published-bg); color: var(--adm-status-published-fg); }
/* LEAD — the enquiry that started the whole thing, sitting in the WAY column beside IN and OUT (Graham
   2026-08-02). Its own colour, not a shade of IN: a lead arriving is a different event from a reply
   arriving, and a history where they look alike is one you have to read twice to find the beginning of. */
.crm-mail-dir.lead { background: var(--adm-status-draft-bg); color: var(--adm-status-draft-fg); }
/* The whole line is a target now, so it says so. The cursor is the only affordance a bare row has. */
.crm-row:not(.crm-row-head):has(details.crm-mail) { cursor: pointer; }
/* And a hover tint, so "this opens" reads before you've moved the pointer over it — the cursor alone
   was too quiet a hint that the whole row is clickable (Graham 2026-08-05). Scoped to the rows that
   actually open on click, so a note row (which does nothing) never lights up as if it would. */
.crm-row:not(.crm-row-head):has(details.crm-mail):hover { background: var(--adm-status-muted-bg); }
.crm-row--bad:not(.crm-row-head):has(details.crm-mail):hover { background: var(--adm-status-expired-bg); }
.crm-row:not(.crm-row-head) a,
.crm-row:not(.crm-row-head) button,
.crm-row:not(.crm-row-head) .crm-note { cursor: auto; }
/* ONE ROW, ONE LINE (Graham 2026-08-03). These already asked for an ellipsis and never got one:
   `text-overflow: ellipsis` does NOTHING without `white-space: nowrap`, because there is nothing to overflow
   — the text simply wraps instead. So a long address like 2lcgraphics@houstonprintingtx.com broke over two
   lines, the row grew to three with the bin sitting under it, and eight emails filled a screen that should
   hold twenty. The full value is still there on hover (title) and in the body when the row is opened. */
.crm-mail-who   { color: var(--adm-text-muted); font-size: 12.5px; flex: 0 0 auto; max-width: 22ch;
                  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.crm-mail-subj  { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* A note has no from/to and spans those columns — it gets the same single-line treatment, for the same
   reason: the history is a LOG, and a log you scan is one where every entry is the same height. */
.crm-row-note   { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.crm-row-note .crm-note-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-mail[open] > summary { margin-bottom: 8px; }
.crm-mail-body {
    border-left: 2px solid var(--adm-status-muted-bg); padding: 2px 0 6px 12px;
    white-space: normal; font-size: 13.5px;
}

@media (max-width: 720px) {
    .crm-row { grid-template-columns: 1fr; gap: 2px; }
}

/* -- crm-hist-hidden: a conversation row the history filter has hidden -----
   The filter used to set style="display:none" inline. That silently did
   nothing, because the rows carry Bootstrap's .d-flex — declared
   `display: flex !important` — and !important in a stylesheet beats a plain
   inline declaration. The rows really were being told to hide and really did
   stay on screen, which is exactly what "the toggle does not work" looked
   like from the outside.

   So the filter now toggles this class instead. It is also !important, so the
   tie is broken by source order — and admin.css loads after tabler.min.css,
   which is what makes it win. Keep it in this file for that reason. */
.crm-hist-hidden { display: none !important; }

/* -- crm-body: a message in the conversation thread ------------------------
   Rendered markup, so it needs sane defaults: a pasted email can carry a
   1200px-wide image or a table that would otherwise stretch the column and
   push the whole page sideways. Plain-text bodies keep pre-wrap instead, so
   line breaks a person actually typed survive. */
.crm-body { overflow-wrap: anywhere; }
.crm-body--plain { white-space: pre-wrap; }
.crm-body img, .crm-body video { max-width: 100%; height: auto; }
.crm-body table { max-width: 100%; display: block; overflow-x: auto; }
.crm-body > :first-child { margin-top: 0; }
.crm-body > :last-child { margin-bottom: 0; }
.crm-body p { margin-bottom: .4rem; }


/* -- the rail's typographic scale, three levels and no more ----------------
   It had three competing weights and no rhythm: a bold uppercase title, bold
   <h4> section headings with icons, and semibold row labels, with nothing
   between blocks (Graham 2026-07-29: "some are bold some are not. There is no
   padding between sections").

   One title, one section heading, one row label. Sections are separated by
   space and a hairline, not by whichever heading happened to be heaviest. */
.adm-rail-title {
    font-size: 12px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
    color: var(--adm-text-muted); margin-bottom: 10px;
}
.adm-sec { padding-top: 16px; margin-top: 16px; border-top: 1px solid var(--adm-border); }
.adm-sec:first-of-type { padding-top: 0; margin-top: 0; border-top: 0; }
/* Plan / Quote / Contract are the three things the rail is FOR, and they were 11px uppercase muted
   grey — the faintest type on the panel, quieter than the values beneath them (Graham 2026-07-30:
   "We need a clear larger title"). They are headings now: readable size, body colour, sentence case. */
.adm-sec-h {
    font-size: 15px; font-weight: 600; letter-spacing: 0; text-transform: none;
    color: var(--tblr-body-color, #1f2733); margin-bottom: 8px;
}
/* An action section stacks its control and its button — a 90px button beside a
   select steals the width the option text needs ("Ecommerce SEO — Platinum"). */
.adm-sec-act { display: grid; gap: 6px; }
.adm-sec-act .btn { justify-self: start; }

/* -- adm-prop: a property row in a detail rail -----------------------------
   Label left, value right, hairline between. Replaces the pattern of a bold
   <h4> plus a form control per field, which cost three lines and a heading to
   hold a value that is usually one word.

   The point is UNIFORMITY: six rows that look identical scan in one pass,
   where six differently-shaped widgets have to be read one at a time. An
   editable row is a <details> whose <summary> IS the row, so opening it swaps
   the value for the real form in place and nothing moves on the page. */
.adm-prop, .adm-prop > summary {
    display: flex; align-items: center; gap: .5rem;
    padding: .35rem 0; font-size: .875rem; line-height: 1.35;
    border-bottom: 1px solid var(--adm-border);
    list-style: none;
}
.adm-prop > summary { border-bottom: 0; padding: 0; cursor: pointer; }
details.adm-prop { display: block; padding: .35rem 0; }
details.adm-prop[open] { padding-bottom: .5rem; }
.adm-prop > summary::-webkit-details-marker { display: none; }
/* The WHO block (first/last/email/phone/website) that opens the panel: same rows, but set apart from the deal
   sections below it by a slightly stronger closing rule — these are facts about the person, not the deal. */
.adm-prop-group { margin-bottom: .35rem; }
.adm-prop-group > .adm-prop:last-child { border-bottom-width: 2px; }
.adm-prop-k { color: var(--adm-text-muted); white-space: nowrap; }
.adm-prop-v { margin-left: auto; text-align: right; }
.adm-prop-v--set { color: var(--adm-link); }        /* an empty value that invites a click */
.adm-prop:last-of-type { border-bottom: 0; }

/* -- adm-step-done: a finished step in a progress stepper ------------------
   Graham 2026-07-28: "items complete have a light green border, normal text
   maybe slightly greyed but still readable."

   The point is to STOP shouting. Solid green on every finished step made a
   seven-stage bar a wall of colour, so nothing stood out and the eye had
   nowhere to land. Done work is reference, not instruction: it earns a green
   border to say "this happened" and muted-but-readable text to stay out of
   the way. The saturated colours are then free to mean something -- blue =
   where you are, solid green = what's next.
   Text stays at --adm-text-muted (AA on white), NOT a light grey: greyed has
   to remain readable, because a finished step still answers questions. */
.adm-step-done {
    background: transparent;
    border: 1px solid var(--adm-status-done-border);
    color: var(--adm-text-muted);
    cursor: default;
}
.adm-step-done:hover, .adm-step-done:focus {
    background: var(--adm-status-published-bg);
    color: var(--adm-text-muted);
}
.adm-step-done > .ti { color: var(--adm-status-published-fg); }   /* the tick keeps its green */

/* -- adm-flash-ok: "the system just changed this for you" ------------------
   Put on a control whose value the app updated as a side effect of something
   else the user did (CRM: picking a plan carries its price into the deal
   value). A field that rewrites itself with no acknowledgement is
   indistinguishable from one that failed to save, so the change has to be
   visible for long enough to be noticed after the eye has moved on — hence
   five seconds, not the usual sub-second transition.
   Holds full strength for 3s, then fades, so it reads on a page you land on
   mid-scroll. Usage: <select class="form-select adm-flash-ok"> */
@keyframes adm-flash-ok {
    0%, 60% { background-color: var(--adm-status-published-bg); border-color: var(--adm-status-published-fg); }
    100%    { background-color: transparent; border-color: transparent; }
}
.adm-flash-ok { animation: adm-flash-ok 5s ease-out 1; }

/* Reduced motion still needs the signal — it just doesn't animate. The tint
   stays until the next render, which is the point at which it stops being
   news anyway. */
@media (prefers-reduced-motion: reduce) {
    .adm-flash-ok {
        animation: none;
        background-color: var(--adm-status-published-bg);
        border-color: var(--adm-status-published-fg);
    }
}

/* -- Content Tree Picker (searchable hierarchical dropdown) ----------------- */
.ctp { position: relative; }
.ctp-toggle { width: 100%; cursor: pointer; }
.ctp-label { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctp-panel {
    position: absolute; z-index: 1050; left: 0; right: 0; top: calc(100% + 2px);
    background: #fff; border: 1px solid var(--tblr-border-color, #dadfe5); border-radius: .375rem;
    box-shadow: 0 .5rem 1rem rgba(0,0,0,.12); padding: .5rem;
}
.ctp-search { margin-bottom: .375rem; }
.ctp-list { list-style: none; margin: 0; padding: 0; max-height: 18rem; overflow-y: auto; }
/* "New page" action pinned under the list. */
.ctp-foot { margin-top: .375rem; padding-top: .375rem; border-top: 1px solid var(--tblr-border-color, #e6e9ee); }
.ctp-newpage { display: inline-flex; align-items: center; gap: .35rem; padding: .3rem .5rem; border-radius: .25rem;
    color: var(--tblr-primary, #206bc4); font-weight: 600; text-decoration: none; font-size: .875rem; }
.ctp-newpage:hover { background: var(--tblr-primary, #206bc4); color: #fff; }
.ctp-item {
    padding: .25rem .5rem; border-radius: .25rem; cursor: pointer; line-height: 1.3;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ctp-item.is-active { background: var(--tblr-primary, #066fd1); color: #fff; }
.ctp-item.is-selected { font-weight: 600; }
.ctp-item.is-selected::after { content: " ✓"; color: var(--tblr-success, #2fb344); }
.ctp-item.is-active.is-selected::after { color: #fff; }

/* -- Theme families: Original anchors the left, clones flow right ------------ */
.theme-family { scrollbar-width: thin; }
.theme-original { border-left: 3px solid var(--tblr-secondary, #6c757d); }
.theme-original.border-primary { border-left-color: var(--tblr-primary, #066fd1); }

/* Sticky "Save" bar on long editor forms -- stays put while scrolling. */
/* Floating "Save" button (corner FAB) -- no full-width bar, so it never writes over page content. */
.cms-save-fab { position: fixed; right: 1.5rem; bottom: 1.5rem; z-index: 1030;
    border-radius: 999px; padding: .55rem 1.15rem; box-shadow: 0 6px 20px rgba(0,0,0,.22); }
/* ...and the page RESERVES the corner it floats in (Graham's walkthrough: the green Save sat on top of the
   bottom row of the editor). Fixed elements don't push content; this padding does it for them, so the last
   card/toolbar on every FAB-carrying page scrolls clear of the button instead of underneath it. */
body:has(.cms-save-fab) .page-body { padding-bottom: 5.5rem; }

/* Page-editor block-setting popovers (shape divider, section, animation, control list) are Bootstrap dropdowns
   at z-index 1000 -- below the floating Save FAB (1030), which would otherwise cover one opened near it. Lift them. */
.block-editor .dropdown-menu { z-index: 1040; }

/* Add-on store -- master-detail (Layout 3): a searchable list on the left, a live preview + details on the right. */
.addon-browser { min-height: 26rem; }
.addon-list { max-height: 34rem; overflow-y: auto; }
.addon-group-h { position: sticky; top: 0; z-index: 1; padding: .4rem .75rem; font-size: .7rem; font-weight: 700;
    letter-spacing: .04em; text-transform: uppercase; color: var(--adm-text-muted, #5a6270);
    background: var(--tblr-bg-surface-secondary, #f5f7fa); border-bottom: .5px solid var(--tblr-border-color, #e6e9ee); }
.addon-row { display: flex; align-items: center; width: 100%; text-align: left; border: 0; background: none;
    padding: .55rem .75rem; border-bottom: .5px solid var(--tblr-border-color, #e6e9ee); cursor: pointer; }
.addon-row:hover { background: var(--tblr-bg-surface-secondary, #f5f7fa); }
.addon-row.is-active { background: #eef4fb; box-shadow: inset 3px 0 0 var(--tblr-primary, #206bc4); }
.addon-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; margin-left: .35rem; }
.addon-dot.is-installed { background: var(--tblr-green, #2fb344); }
.addon-dot.is-available { background: #b4b8bd; }
.addon-dot.is-structural { background: var(--tblr-blue, #4299e1); }
/* The preview iframe gets a generous, viewport-relative height so it uses the available space instead of a
   short fixed box with white space below. The embedded preview page centres itself to fill whatever height
   the iframe has, so a short placeholder shows no scrollbar -- only a genuinely tall preview scrolls. */
.addon-detail { padding: 1.1rem 1.25rem; }
.addon-detail-empty { color: var(--adm-text-muted, #5a6270); text-align: center; padding: 4rem 1rem; }
.addon-detail-prev { width: 100%; height: clamp(20rem, 58vh, 44rem); border: 1px solid var(--tblr-border-color, #e6e9ee);
    border-radius: 10px; background: #fff; }
@media (max-width: 991.98px) { .addon-list { max-height: 18rem; } }

/* Product category multi-select tree. */
.cat-tree .cat-row { display: flex; align-items: center; gap: .4rem; }
.cat-tree .cat-row .form-check-input { margin-top: 0; }
.prod-manage .list-group-item { padding-top: .6rem; padding-bottom: .6rem; }

/* Theme Layout editor -- declutter the toolbar's token-insert buttons (CSS-only, no markup change). The 7 token
   buttons shrink to small grey "chips" so they read as one compact insert-cluster instead of seven full-size
   buttons, and an "Insert:" label is shown before the first one. */
.card-header .btn-list .token {
    font-size: .72rem; padding: .15rem .5rem; line-height: 1.3; color: #51607a;
    background: #f1f3f5; border: 1px solid #e4e7eb; border-radius: 6px; box-shadow: none;
}
.card-header .btn-list .token:hover { background: #e6eaef; color: #1a2230; border-color: #cfd6de; }
.card-header .btn-list .token:first-of-type { margin-left: .15rem; }
.card-header .btn-list .token:first-of-type::before {
    content: "Insert:"; font-weight: 600; color: #8a94a6; margin-right: .4rem;
    text-transform: uppercase; font-size: .66rem; letter-spacing: .03em;
}

/* Media Library toolbar: keep the search + upload controls a uniform height so the Search and Upload
   buttons line up even when the native file input renders taller than the other inputs. */
.media-toolbar .form-control,
.media-toolbar .form-select,
.media-toolbar .btn { height: 38px; }
.media-toolbar input[type="file"].form-control { padding-top: .35rem; line-height: 1.5; }

/* Floating Save (admin detail/edit pages) -- fixed bottom-right; mirrors the form's real Save button. */
.adm-floating-save {
    position: fixed; right: 1.25rem; bottom: 1.25rem; z-index: 1030;
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .7rem 1.25rem; border: 0; border-radius: 999px;
    background: var(--adm-success, #2fb344); color: #fff; font-weight: 600; font-size: .95rem;
    box-shadow: 0 6px 20px rgba(16,24,40,.25); cursor: pointer;
    transition: transform .06s ease, filter .12s ease, box-shadow .12s ease;
}
.adm-floating-save:hover { filter: brightness(1.05); box-shadow: 0 8px 26px rgba(16,24,40,.3); }
/* #129/#130/#131 (2026-08-16 desktop dive) — the contract page's plan grid, taller picker, current card.
   The row is flex on a phone (where #92 folds the extras) and a labelled grid on the desk — its own class,
   not .d-flex, because that utility's !important display would veto the grid. */
.crm-plan-head { display: none; }
.crm-plan-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
@media (min-width: 768px) {
    .crm-plan-head, .crm-plan-row {
        display: grid; grid-template-columns: 1.1rem minmax(10rem, 1fr) .8rem 3.6rem 4rem 6rem minmax(8rem, 11rem);
        align-items: center; gap: .25rem .5rem;
    }
    .crm-plan-head { font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
        color: var(--tblr-secondary, #667382); margin-bottom: .15rem; }
    .crm-plan-row .form-control { width: auto; min-width: 0; }
    /* The slab's plan list may breathe on a tall screen — 16 rows was a laptop cap (#130). */
    .crm-slab .adm-scroll-16r { max-height: min(62vh, 44rem); }
}
/* #118/#119/#122/#123 (2026-08-16 desktop dive) — the Clients page's sections, bar, dot and avatars. */
.crm-cl-sec td { font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
    color: var(--tblr-secondary, #667382); background: var(--tblr-bg-surface-secondary, #f6f8fb); padding-top: .4rem; padding-bottom: .4rem; }
.crm-cl-av { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 8px;
    font-size: .72rem; font-weight: 800; margin-right: .45rem; vertical-align: middle; }
.crm-cl-good { color: var(--tblr-success, #2fb344); font-size: .8rem; cursor: default; }
.crm-mrr-bar { display: flex; gap: 2px; height: 9px; border-radius: 5px; overflow: hidden; }
.crm-mrr-bar i { display: block; min-width: 3px; }
.crm-mrr-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: .3rem; }
.crm-ct-current { display: inline-flex; align-items: center; gap: .5rem; background: var(--tblr-bg-surface, #fff);
    border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 10px; padding: .35rem .75rem; }
.crm-ct-current .crm-ct-id { font-size: .72rem; font-weight: 700; color: var(--tblr-secondary, #667382); }
.crm-ct-current .crm-ct-amt { font-weight: 700; font-variant-numeric: tabular-nums; }

/* #97 (2026-08-16 deep dive) — the notices chip in the dark bar: compact, amber, count only (the word
   made the bar wrap to two rows, spending the exact space this move reclaims). */
.adm-notices--bar .adm-notices-btn { background: transparent; border: 0; color: #f5c453; padding: .35rem .4rem;
    font-size: .8rem; font-weight: 700; min-height: 40px; }
.adm-notices--bar .adm-notices-btn .ti { font-size: 1rem; }
.adm-notices--bar .adm-notices-w { display: none; }
.adm-notices--bar .adm-notices-btn.dropdown-toggle::after { display: none; }
/* #95 (2026-08-16 deep dive) — on a phone the floating verbs dock ABOVE the tab bar (they used to sit on
   it, 20px from the bar's own Send), and the page reserves room so they never cover the last inputs. */
@media (max-width: 767.98px) {
    .adm-floating-save, .adm-floating-delete {
        bottom: calc(60px + env(safe-area-inset-bottom, 0px));
    }
    body:has(.adm-floating-save) .page-body, body:has(.adm-floating-delete) .page-body {
        padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 6.5rem);
    }
    /* #92 — an unticked plan row folds its qty/discount/override inputs; tick it and they appear. Inputs
       that already carry a value stay visible (:placeholder-shown is false), so an untick never hides data. */
    .crm-plan-row:has(> input[type="checkbox"]:not(:checked)) .crm-plan-adv:placeholder-shown,
    .crm-plan-row:has(> input[type="checkbox"]:not(:checked)) span.crm-plan-adv,
    .crm-plan-row:has(> input[type="checkbox"]:not(:checked)) input.crm-plan-adv[name^="qty_"] {
        display: none;
    }
    /* Graham 2026-08-18: "The draft is not mobile friendly" — a TICKED plan's four inputs used to pack
       one wrapping flex line ("$ overri…" clipped mid-word). A ticked row is now its own small grid:
       name and × qty on the first line, −$ and override splitting the second, the why-note full-width
       on the third. The !importants exist to beat the inputs' inline desktop widths (6rem/11rem). */
    /* Two fr columns and nothing auto (an auto track let the spanning inputs' intrinsic widths crush
       the label to nothing). The name owns its full line; qty and −$ split the next; override and the
       why-note each get a full line — every box wide enough to read its own placeholder. The × goes on
       ticked phone rows only: it had no line left to share with the qty box. */
    .crm-plan-row:has(> input[type="checkbox"]:checked) {
        display: grid;
        grid-template-columns: 1.4rem minmax(0, 1fr) minmax(0, 1fr);
        align-items: center;
        gap: .4rem .5rem;
    }
    .crm-plan-row:has(> input[type="checkbox"]:checked) .form-control {
        width: 100% !important; min-width: 0;
    }
    .crm-plan-row:has(> input[type="checkbox"]:checked) span.crm-plan-adv { display: none; }
    .crm-plan-row:has(> input[type="checkbox"]:checked) label { grid-column: 2 / 4; }
    .crm-plan-row:has(> input[type="checkbox"]:checked) input.crm-plan-adv[name^="qty_"] { grid-column: 2; }
    .crm-plan-row:has(> input[type="checkbox"]:checked) input.crm-plan-adv[name^="disc_"] { grid-column: 3; }
    .crm-plan-row:has(> input[type="checkbox"]:checked) input.crm-plan-adv[name^="ovr_"] { grid-column: 2 / 4; }
    .crm-plan-row:has(> input[type="checkbox"]:checked) input.crm-plan-adv[name^="ovrnote_"] { grid-column: 2 / 4; }
}
.adm-floating-save:active { transform: translateY(1px); box-shadow: inset 0 2px 5px rgba(0,0,0,.2); }
.adm-floating-save .ti { font-size: 1.15rem; }
@media print { .adm-floating-save { display: none; } }

/* Menu search: add-on results sit on the normal row background (same as nav results); only the "Add Ons"
   sub-label is recoloured so they're identifiable without washing out the row. */
#adminMenuResultsList .adm-search-addon .small { color: #4dabf7; font-weight: 600; opacity: 1; }

/* -- Command palette (Ctrl/Cmd-K) ------------------------------------------- */
#cmdk { position: fixed; inset: 0; z-index: 2000; background: rgba(15,23,42,.5); display: flex; align-items: flex-start; justify-content: center; padding-top: 11vh; }
#cmdk[hidden] { display: none; }
#cmdk .cmdk-box { width: 92%; max-width: 600px; background: var(--tblr-bg-surface, #fff); border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 14px; box-shadow: 0 24px 60px rgba(0,0,0,.35); overflow: hidden; }
#cmdk .cmdk-top { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--tblr-border-color, #e6e7e9); }
#cmdk .cmdk-search { font-size: 20px; color: var(--tblr-secondary, #6b7280); }
#cmdk-input { flex: 1; border: 0; padding: 4px 0; font-size: 16px; outline: none; background: transparent; color: inherit; }
#cmdk .cmdk-kbd { font-size: 11px; color: var(--tblr-secondary, #6b7280); border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 6px; padding: 2px 7px; white-space: nowrap; }
/* Close X: hidden on desktop (Esc/backdrop close there); shown by the phone block below. */
#cmdk .cmdk-close { display: none; border: 0; background: transparent; color: var(--tblr-secondary, #6b7280); font-size: 22px; padding: 4px 6px; line-height: 1; align-items: center; }
#cmdk-list { list-style: none; margin: 0; padding: 8px; max-height: 56vh; overflow: auto; }
#cmdk-list .cmdk-grp { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--tblr-secondary, #9ca3af); padding: 10px 12px 4px; cursor: default; }
/* Public / site-wide results: a divider + blue so it's unmistakable these open the live site, not admin. */
#cmdk-list .cmdk-grp--pub { margin-top: 6px; border-top: 1px solid var(--tblr-border-color, #e5e7eb); padding-top: 12px; color: #2563eb; }
#cmdk-list li[data-i] { display: flex; align-items: center; gap: 12px; padding: 8px 12px; border-radius: 8px; cursor: pointer; }
#cmdk-list li.on { background: #eef2ff; }

/* CRM audit (Graham 2026-08-12): a wide table becomes a stack of cards on a phone so CRM pages read like
   the app instead of scrolling sideways. Add class "crm-cardify" to any .table and data-label to the cells
   that need a caption when stacked. */
@media (max-width: 767.98px) {
    .crm-cardify thead { display: none; }
    .crm-cardify, .crm-cardify tbody, .crm-cardify tr, .crm-cardify td { display: block; width: auto; }
    .crm-cardify tr { border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 12px; padding: 10px 12px; margin-bottom: 10px; background: var(--tblr-card-bg, #fff); }
    .crm-cardify td { border: 0 !important; padding: 3px 0 !important; text-align: left !important; }
    .crm-cardify td[data-label]::before { content: attr(data-label) ": "; font-weight: 600; color: var(--tblr-secondary, #6c757d); }
    .crm-cardify td:first-child { font-size: 1.05rem; margin-bottom: 2px; }
    .crm-cardify td:empty { display: none; }
}
#cmdk-list .cmdk-ic { width: 30px; height: 30px; flex: 0 0 auto; display: flex; align-items: center; justify-content: center; border-radius: 8px; background: rgba(79,70,229,.1); color: #4f46e5; font-size: 17px; }
#cmdk-list li.on .cmdk-ic { background: #fff; }
#cmdk-list .cmdk-l { font-weight: 500; flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--tblr-body-color, #1f2937); }
#cmdk-list li.on .cmdk-l { color: #312e81; }
#cmdk-list .cmdk-sub { font-size: 12px; color: var(--tblr-secondary, #6b7280); margin-left: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
#cmdk-list li.on .cmdk-sub { color: #6366f1; }
#cmdk-list .cmdk-none { color: var(--tblr-secondary, #6b7280); padding: 16px 12px; cursor: default; }
#cmdk .cmdk-foot { display: flex; align-items: center; gap: 16px; padding: 9px 16px; border-top: 1px solid var(--tblr-border-color, #e6e7e9); font-size: 11px; color: var(--tblr-secondary, #6b7280); }
#cmdk .cmdk-foot-r { margin-left: auto; }

/* "Start from a layout" picker -- shown in the block list on an empty/new page so first-timers get a
   starting scaffold instead of a blank canvas. Not a .card, so Sortable + serialize ignore it. */
.page-template-picker { border: 0.5px dashed var(--adm-border); border-radius: 12px; padding: 18px; background: #fbfcfd; }
.page-template-picker .ptpl-head { font-size: 1rem; font-weight: 600; }
.page-template-picker .ptpl-sub { font-size: .8rem; color: var(--adm-text-muted); margin: 2px 0 14px; }
.page-template-picker .ptpl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }
.ptpl-card { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; text-align: left;
    border: 0.5px solid var(--adm-border); border-radius: 10px; background: #fff; padding: 12px; cursor: pointer; transition: border-color .12s, box-shadow .12s; }
.ptpl-card:hover { border-color: var(--adm-link); box-shadow: 0 0 0 1px var(--adm-link); }
.ptpl-card .ptpl-thumb { width: 34px; height: 34px; border-radius: 8px; display: flex; align-items: center; justify-content: center;
    background: #eef2fb; color: var(--adm-link); margin-bottom: 6px; }
.ptpl-card .ptpl-thumb .ti { font-size: 1.1rem; }
.ptpl-card .ptpl-title { font-size: .85rem; font-weight: 500; }
.ptpl-card .ptpl-desc { font-size: .74rem; color: var(--adm-text-muted); line-height: 1.35; }
.ptpl-card.ptpl-blank .ptpl-thumb { background: #f1f3f5; color: var(--adm-text-muted); }

/* Quick-add palette -- press "/" (when not typing) to insert a block by name. Fast finder over the toolbar. */
.qadd-ov { position: fixed; inset: 0; z-index: 1090; background: rgba(15,23,42,.35);
    display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; }
.qadd-box { width: 100%; max-width: 30rem; background: #fff; border-radius: 12px; box-shadow: 0 12px 40px rgba(0,0,0,.28); overflow: hidden; }
.qadd-input { width: 100%; border: 0; border-bottom: 1px solid var(--adm-border); padding: 14px 16px; font-size: 1rem; outline: none; }
.qadd-list { max-height: 46vh; overflow-y: auto; padding: 6px; }
.qadd-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; border: 0; background: none;
    padding: 8px 10px; border-radius: 8px; font-size: .9rem; cursor: pointer; color: var(--tblr-body-color, #1e293b); }
.qadd-item .ti { color: var(--adm-text-muted); width: 18px; text-align: center; }
.qadd-item.active, .qadd-item:hover { background: #eef2fb; }
.qadd-item.active .ti { color: var(--adm-link); }
.qadd-none { padding: 14px 16px; color: var(--adm-text-muted); font-size: .875rem; }

/* Persistent "type to add" bar under the block list -- the visible twin of the "/" palette (reuses .qadd-item/.qadd-none). */
.qadd-bar { position: relative; display: flex; align-items: center; gap: 8px; margin-top: 10px;
    border: 1px dashed var(--adm-border); border-radius: 8px; padding: 8px 12px; background: #fff; }
.qadd-bar:focus-within { border-style: solid; border-color: var(--adm-link); }
.qadd-bar-ic { color: var(--adm-link); }
.qadd-bar-input { flex: 1; border: 0; outline: none; background: none; font-size: .9rem; color: inherit; }
.qadd-bar-input::placeholder { color: var(--adm-text-muted); }
.qadd-bar-list { position: absolute; left: 0; right: 0; bottom: calc(100% + 4px); z-index: 1050;
    background: #fff; border: 0.5px solid var(--adm-border); border-radius: 10px; box-shadow: 0 -6px 24px rgba(0,0,0,.14);
    max-height: 40vh; overflow-y: auto; padding: 6px; display: none; }
.qadd-bar-list.open { display: block; }

/* CMS-EDITOR-AUDIT Phase 5 (2026-08-22): the bar now leads the block list instead of trailing it, so its
   results pop DOWN, not up over the header above it. */
.qadd-bar--top { margin-top: 0; margin-bottom: 12px; }
.qadd-bar--top .qadd-bar-list { bottom: auto; top: calc(100% + 4px); box-shadow: 0 10px 28px rgba(0,0,0,.14); }
/* Categories-as-filters: chips pinned above the results instead of separate always-visible toolbar buttons. */
.qadd-bar-filters { display: flex; gap: 6px; flex-wrap: wrap; padding: 2px 4px 8px; margin-bottom: 4px; border-bottom: 1px solid var(--adm-border); }
.qadd-filter-chip { border: 1px solid var(--adm-border); background: #fff; border-radius: 999px; padding: 2px 10px;
    font-size: .75rem; line-height: 1.6; color: var(--adm-text-muted); cursor: pointer; }
.qadd-filter-chip:hover { background: #f4f6fb; }
.qadd-filter-chip.active { background: var(--adm-link); border-color: var(--adm-link); color: #fff; }
/* Every result gets a one-line description (its search keywords doing double duty) instead of a bare label. */
.qadd-item { align-items: flex-start; }
.qadd-item-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.qadd-item-title { line-height: 1.3; }
.qadd-item-desc { font-size: .72rem; font-weight: 400; color: var(--adm-text-muted); white-space: normal; line-height: 1.3; }

/* Hover "+" between blocks (CMS-EDITOR-AUDIT Phase 5): a thin strip on a card's own bottom edge, so
   inserting isn't add-then-drag. Sits half in the card, half in the gap to the next one; invisible until
   the card (or the button itself, for keyboard/touch) is focused-within or hovered. */
.block-list > .card { position: relative; }
/* Design pass (2026-08-22), "make it like your design" (Block Editor Blueprint mockup): block cards as real
   panels - a considered border + gentle elevation instead of Bootstrap's flat default .card, so a page
   reads as a stack of composed pieces. Draws on the --cms-* tokens Edit.cshtml defines on :root for this
   page; falls back to matching literals if this class combo is ever rendered somewhere those aren't set
   (defensive - .block-list is CMS-editor-specific in practice). */
.block-list > .card {
    border-color: var(--cms-line, #ddd5c4);
    border-radius: 10px;
    box-shadow: 0 1px 2px rgba(35,32,27,.05);
}
.block-list > .card > .card-header { border-top-left-radius: 10px; border-top-right-radius: 10px; }
/* The block you're actually typing in gets an ambient ring + a left "spine" instead of nothing extra -
   "you are here," not a hard rectangle. Keys off .cms-ed-active, which the editor already toggles on
   focus/blur (richtext-providers.js) for the "one toolbar, on the section you're in" behaviour - this is
   a look for a state that already existed, not new JS. */
.block-list > .card:has(.cms-ed-active) {
    border-color: var(--cms-accent-soft-line, #c9d7e6) !important;
    outline: 3px solid var(--cms-accent-soft, #e8eef4);
    outline-offset: 0;
}
.block-list > .card:has(.cms-ed-active)::after {
    content: ''; position: absolute; left: -1px; top: 10px; bottom: 10px; width: 3px; border-radius: 3px;
    background: var(--cms-accent, #2c4a6e); pointer-events: none;
}
/* Type badges: the mono face for anything structural (was the same sans as body copy), so "this word is
   UI chrome" reads apart from "this word is your content" at a glance - and the soft-tint colour (already
   bg-secondary-lt, a generic Bootstrap grey) moves onto the same accent family as the rest of the page. */
.block-type-badge { font-family: var(--cms-font-mono, ui-monospace, monospace); font-weight: 500; letter-spacing: .03em; }
.block-type-badge.bg-secondary-lt { background: var(--cms-accent-soft, #e8eef4) !important; color: var(--cms-accent-ink, #1c3350) !important; }
/* The formatting toolbar visually "docks" above the content it edits instead of sitting flush at the same
   depth - purely additive (shadow only, no width/flex/display changes) after the max-width attempt earlier
   this session broke TinyMCE's own layout; a shadow can't fight its internal measurement the way a forced
   width did. */
.tox .tox-editor-header { box-shadow: 0 2px 0 var(--cms-accent-soft-line, #c9d7e6) !important; }
/* Columns count <select>: still a native form control (a full custom segmented control is a bigger,
   riskier rebuild than this pass calls for), just carrying the same border/focus language as everything
   else on the page instead of the browser's bare default. */
.block-col-count.form-select {
    border-color: var(--cms-line, #ddd5c4); font-family: var(--cms-font-mono, ui-monospace, monospace);
}
.block-col-count.form-select:focus { border-color: var(--cms-accent-soft-line, #c9d7e6); box-shadow: 0 0 0 3px var(--cms-accent-soft, #e8eef4); }
.block-insert-between { position: absolute; left: 0; right: 0; bottom: -11px; height: 20px; z-index: 5;
    display: flex; align-items: center; justify-content: center; border: 0; background: none; padding: 0;
    opacity: 0; cursor: pointer; color: var(--adm-link); transition: opacity .1s; }
.block-insert-between::before { content: ''; position: absolute; left: 8%; right: 8%; top: 50%; height: 1px;
    background: var(--adm-link); opacity: .35; }
.block-insert-between i { position: relative; background: #fff; border: 1px solid var(--adm-link); border-radius: 999px;
    width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 13px; }
.block-list > .card:hover .block-insert-between,
.block-insert-between:hover, .block-insert-between:focus-visible { opacity: 1; }
/* The card a hover-+ is about to insert after — a visible cue while the bar above is open for that choice. */
.block-list > .card.block-insert-target { outline: 2px solid var(--adm-link); outline-offset: 2px; }

/* Admin card headers: the bold card-title and its inline hint (card-subtitle) were running together
   ("DownloadsBrochure, course outline...") -- give the hint breathing room on the same line. */
.card-header .card-title + .card-subtitle { margin-left: .75rem; }
/* ============================================================================
   Utility classes -- inline-style cleanup (v2026.07.05.21).
   Presentation moved out of style="..." attributes across the admin views.
   Deliberately still inline in the views: JS-toggled STATE (display:none,
   visibility, progress widths) and styles computed from data (@Model.../JS).
   Single-property utilities use !important (Bootstrap convention) so they win
   exactly like the inline styles they replace.
   ==========================================================================*/
/* widths (rem) */
.adm-w-1-2r{width:1.2rem!important}.adm-w-3r{width:3rem!important}.adm-w-3-5r{width:3.5rem!important}
.adm-w-4r{width:4rem!important}.adm-w-4-5r{width:4.5rem!important}.adm-w-5r{width:5rem!important}
.adm-w-5-5r{width:5.5rem!important}.adm-w-6r{width:6rem!important}.adm-w-7r{width:7rem!important}
.adm-w-8r{width:8rem!important}.adm-w-9r{width:9rem!important}.adm-w-10-5r{width:10.5rem!important}
.adm-w-11r{width:11rem!important}.adm-w-14r{width:14rem!important}.adm-w-18r{width:18rem!important}
/* widths (px / % / misc) */
.adm-w-42{width:42px!important}.adm-w-46{width:46px!important}.adm-w-48{width:48px!important}
.adm-w-60{width:60px!important}.adm-w-64{width:64px!important}.adm-w-70{width:70px!important}
.adm-w-82{width:82px!important}.adm-w-92{width:92px!important}.adm-w-130{width:130px!important}
.adm-w-200{width:200px!important}.adm-w-260{width:260px!important}.adm-w-max{width:max-content!important}
.adm-w-1pct{width:1%!important}.adm-w-8pct{width:8%!important}.adm-w-12pct{width:12%!important}
.adm-w-34pct{width:34%!important}.adm-w-40pct{width:40%!important}.adm-w-42pct{width:42%!important}
.adm-w-46pct{width:46%!important}
/* max-widths (px) */
.adm-mw-90{max-width:90px!important}.adm-mw-130{max-width:130px!important}.adm-mw-150{max-width:150px!important}
.adm-mw-160{max-width:160px!important}.adm-mw-180{max-width:180px!important}.adm-mw-190{max-width:190px!important}
.adm-mw-200{max-width:200px!important}.adm-mw-220{max-width:220px!important}.adm-mw-240{max-width:240px!important}
.adm-mw-260{max-width:260px!important}.adm-mw-280{max-width:280px!important}.adm-mw-300{max-width:300px!important}
.adm-mw-320{max-width:320px!important}.adm-mw-340{max-width:340px!important}.adm-mw-360{max-width:360px!important}
.adm-mw-380{max-width:380px!important}.adm-mw-440{max-width:440px!important}.adm-mw-560{max-width:560px!important}
.adm-mw-720{max-width:720px!important}
/* max-widths (rem) */
.adm-mw-8r{max-width:8rem!important}.adm-mw-10r{max-width:10rem!important}.adm-mw-14r{max-width:14rem!important}.adm-mw-16r{max-width:16rem!important}
.adm-mw-17r{max-width:17rem!important}.adm-mw-18r{max-width:18rem!important}.adm-mw-20r{max-width:20rem!important}
.adm-mw-22r{max-width:22rem!important}.adm-mw-24r{max-width:24rem!important}.adm-mw-26r{max-width:26rem!important}
.adm-mw-28r{max-width:28rem!important}.adm-mw-32r{max-width:32rem!important}.adm-mw-34r{max-width:34rem!important}
.adm-mw-36r{max-width:36rem!important}.adm-mw-40r{max-width:40rem!important}.adm-mw-48r{max-width:48rem!important}
/* min-widths */
.adm-minw-0{min-width:0!important}.adm-minw-4{min-width:4px!important}.adm-minw-72{min-width:72px!important}
/* capped scrollable lists (plan pickers etc.) */
.adm-scroll-16r{max-height:16rem;overflow-y:auto}
.adm-w-qty{width:4.25rem!important}
/* Bilingual editor: the AI Trans button wears the OTHER side's state — amber = that language has no
   content for this block yet; green = it does (translating overwrites it). */
.cms-trans-missing{border-color:#f59f00!important;color:#b45309!important;background:#fff7e6!important}
.cms-trans-ok{border-color:#2fb344!important;color:#1a7f37!important;background:#e9f7ec!important}
.adm-minw-240{min-width:240px!important}.adm-minw-300{min-width:300px!important}.adm-minw-6r{min-width:6rem!important}
.adm-minw-8r{min-width:8rem!important}.adm-minw-18r{min-width:18rem!important}.adm-minw-22r{min-width:22rem!important}
.adm-minw-24r{min-width:24rem!important}
/* heights / max-heights / scroll boxes */
.adm-h-6{height:6px!important}.adm-h-8{height:8px!important}.adm-h-200{height:200px!important}
.adm-mh-48{max-height:48px!important}.adm-mh-90{max-height:90px!important}.adm-mh-110{max-height:110px!important}
.adm-mh-120{max-height:120px!important}.adm-mh-140{max-height:140px!important}.adm-mh-160{max-height:160px!important}
.adm-minh-125em{min-height:1.25em!important}
.adm-scroll-7r{max-height:7rem;overflow:auto}.adm-scroll-9r{max-height:9rem;overflow:auto}
.adm-scroll-10r{max-height:10rem;overflow:auto}.adm-scroll-16r{max-height:16rem;overflow:auto}
.adm-scroll-18r{max-height:18rem;overflow:auto}.adm-scroll-24r{max-height:24rem;overflow:auto}
.adm-scroll-240{max-height:240px;overflow:auto}.adm-scroll-280{max-height:280px;overflow:auto}
.adm-scroll-320{max-height:320px;overflow:auto}.adm-scroll-340{max-height:340px;overflow:auto}
.adm-scroll-360{max-height:360px;overflow:auto}.adm-scroll-420{max-height:420px;overflow:auto}
.adm-scroll-38vh{max-height:38vh;overflow:auto}
.adm-pop-scroll-280{z-index:1050;max-height:280px;overflow:auto}
.adm-pop-scroll-60vh{max-height:60vh;overflow:auto;z-index:1080}
.adm-pop-menu-300{max-height:70vh;overflow:auto;min-width:300px;z-index:1080}
.adm-pop-menu-260{max-height:70vh;overflow:auto;min-width:260px;z-index:1080}
/* deploy-page recovery actions: equal-width buttons in a left column so the rows line up
   (and stay clear of the floating Save button bottom-right) */
.adm-recover-btn{min-width:16.5rem;text-align:left}
/* direction-board cells: full-height arrow rail on the left (green = can't lose live data, red = overwrites live) */
.adm-board-rail{width:44px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1.4rem}
/* font sizes / text */
.adm-fs-3r{font-size:3rem!important}.adm-fs-2-5r{font-size:2.5rem!important}.adm-fs-2r{font-size:2rem!important}
.adm-fs-1-45r{font-size:1.45rem!important}.adm-fs-1-4r{font-size:1.4rem!important}.adm-fs-1-35r{font-size:1.35rem!important}
.adm-fs-1-25r{font-size:1.25rem!important}.adm-fs-1-2r{font-size:1.2rem!important}.adm-fs-1-05r{font-size:1.05rem!important}
.adm-fs-0-95r{font-size:.95rem!important}.adm-fs-0-85r{font-size:.85rem!important}.adm-fs-0-82r{font-size:.82rem!important}
.adm-fs-0-8r{font-size:.8rem!important}.adm-fs-0-78r{font-size:.78rem!important}.adm-fs-0-75r{font-size:.75rem!important}
.adm-fs-0-72r{font-size:.72rem!important}.adm-fs-0-65r{font-size:.65rem!important}.adm-fs-0-6r{font-size:.6rem!important}
.adm-fs-14px{font-size:14px!important}.adm-fs-13px{font-size:13px!important}.adm-fs-11px{font-size:11px!important}
.adm-fs-10px{font-size:10px!important}
.adm-op-40{opacity:.4!important}.adm-op-65{opacity:.65!important}.adm-op-80{opacity:.8!important}.adm-op-90{opacity:.9!important}
.adm-bw-2{border-width:2px!important}.adm-tt-none{text-transform:none!important}
/* Settings rows that show their value and open only when you mean to change it (Setup -> Email).
   Closed = inheriting or simply untouched; open = this site has its own value. No JS: <details>. */
.adm-setting{border-bottom:.5px solid var(--tblr-border-color,#e6e7e9)}
.adm-setting:last-child{border-bottom:0}
.adm-setting>summary{display:flex;align-items:center;gap:.5rem;padding:.55rem 0;cursor:pointer;list-style:none}
.adm-setting>summary::-webkit-details-marker{display:none}
.adm-setting>summary::before{content:"\ea61";font-family:"tabler-icons";font-size:14px;color:var(--tblr-secondary,#6b7280);transition:transform .12s}
.adm-setting[open]>summary::before{transform:rotate(90deg)}
.adm-setting>summary:hover{color:var(--tblr-primary,#206bc4)}
.adm-setting-label{min-width:11rem}
.adm-setting-value{font-size:.8125rem;color:var(--tblr-secondary,#6b7280);background:transparent;padding:0;overflow-wrap:anywhere}
/* Open state: a card indented under the label, so the editing area is unmistakably separate from the row
   you clicked. The flush-against-the-summary version read as cramped (Graham 2026-07-28). */
.adm-setting-panel{margin:.65rem 0 .35rem 1.6rem;padding:1rem 1.25rem;border:.5px solid var(--tblr-border-color,#e6e7e9);border-radius:12px;background:var(--tblr-bg-surface,#fff)}
.adm-setting-panel .form-hint{margin-top:.75rem;line-height:1.65}
.adm-setting-panel>.form-label{margin-bottom:.35rem}
/* Revert-to-inherited lives INSIDE the open panel: without it the only way back is to guess that clearing
   the box and saving works. */
.adm-setting-revert{margin-top:.9rem;padding-top:.7rem;border-top:.5px solid var(--tblr-border-color,#e6e7e9)}
.adm-prewrap{white-space:pre-wrap!important}.adm-pre-scroll{white-space:pre;overflow-wrap:normal;overflow-x:auto}
.adm-list-revert{list-style:revert!important}
.adm-overline{font-size:.62rem;text-transform:uppercase;letter-spacing:.04em;font-weight:700;color:#64748b;margin-bottom:.35rem}
.adm-wordmark{font-weight:800;font-size:1.1rem;letter-spacing:.04em}
.adm-stat-num{font-size:1.5rem;font-weight:800;margin-bottom:.35rem}
/* Websites-grid KPI cell that navigates (switch site + open the page behind the number). Fills the td
   (td gets p-0) so the whole cell is the click target; link affordance on hover only. */
.adm-cell-link{border:0;background:transparent;width:100%;padding:.5rem .25rem;color:var(--tblr-secondary,#6c757d);cursor:pointer;line-height:1.35}
.adm-cell-link:hover,.adm-cell-link:focus-visible{color:var(--tblr-primary,#206bc4);text-decoration:underline}
/* KPI tile that navigates: keep the card look, add link affordance on hover only. */
a.adm-stat-link{color:inherit;text-decoration:none;display:block}
a.adm-stat-link:hover,a.adm-stat-link:focus-visible{color:inherit;border-color:var(--tblr-primary,#206bc4);box-shadow:0 1px 6px rgba(24,36,55,.10);text-decoration:none}
a.adm-stat-link:hover .text-secondary{color:var(--tblr-primary,#206bc4)!important}
/* layout misc */
.adm-resize-y{resize:vertical!important}.adm-grid-span-all{grid-column:1/-1!important}
.adm-r-2{border-radius:2px!important}.adm-border-dashed{border-style:dashed!important}
.adm-border-heavy{border-width:3px!important;border-color:#dadfe5!important}
.adm-accent-left-purple{border-left:4px solid #ae3ec9!important}
.adm-mt-75{margin-top:.75rem!important}.adm-mt-15{margin-top:.15rem!important}
.adm-compact-note{margin:5px 6px 6px;line-height:1.3}
/* Sidebar identity card (brand block): version pill + site/db/user rows on a shared left edge.
   Dark-sidebar only -- colours are literal because the card never renders on a light ground. */
.adm-idcard{margin-top:10px;background:rgba(255,255,255,.055);border:1px solid rgba(255,255,255,.09);border-radius:8px;padding:6px 8px;text-align:left}
.adm-idrow{display:flex;align-items:center;gap:8px;padding:4px 2px;min-width:0;color:rgba(255,255,255,.62);text-decoration:none;font-size:11.5px;font-weight:400;line-height:1.35}
.adm-idrow i{flex:0 0 14px;font-size:13px;opacity:.55;text-align:center}
a.adm-idrow:hover,a.adm-idrow:focus{color:#fff;text-decoration:none}
.adm-idval{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.adm-idrow-site{background:rgba(255,255,255,.06);border-radius:6px;padding:6px;color:#fff;font-weight:600;font-size:12.5px}
.adm-idrow-site i{opacity:.85}
.adm-iddot{width:7px;height:7px;border-radius:50%;background:#2fbf71;flex:0 0 7px}
.adm-idswap{margin-left:auto;opacity:.5}
.adm-idrow-site.is-override{color:#ffd43b}
.adm-idrow-site.is-override .adm-iddot{background:#ffd43b}
/* Working-language chips in the identity card (multilingual sites only). Same shape as the public EN/FR
   toggle so the two read as one idea, in sidebar colours. */
.adm-lang-chips{display:inline-flex;gap:4px;margin-left:auto}
.adm-lang-chip{appearance:none;border:1px solid rgba(255,255,255,.22);background:transparent;color:rgba(255,255,255,.62);
  border-radius:5px;padding:1px 7px;font-size:11px;font-weight:700;letter-spacing:.02em;line-height:1.45;cursor:pointer}
.adm-lang-chip:hover{color:#fff;background:rgba(255,255,255,.12)}
.adm-lang-chip.active{background:#fff;color:#1a2b3c;border-color:#fff}
.adm-lang-chip[disabled]{opacity:.5;cursor:default}
.adm-flex-1{flex:1 1 0%!important}.adm-flex-fill-min0{min-width:0;flex:1 1 auto}
.adm-sticky-top-2{position:sticky;top:.5rem;z-index:5}
.adm-abs-bottom-center{position:absolute;bottom:8px;left:50%;transform:translateX(-50%)}
.adm-mini-col{display:flex;flex-direction:column;gap:.25rem;min-width:8rem}
.adm-meter-bar{min-width:10rem;max-width:22rem;height:8px}
/* images / thumbs / covers */
.adm-cover-120{height:120px;object-fit:cover}.adm-cover-56{width:56px;height:56px;object-fit:cover}
.adm-cover-34{width:34px;height:34px;object-fit:cover}
.adm-cover-max160{max-width:160px;max-height:160px;object-fit:cover}
.adm-cover-mh120{max-height:120px;object-fit:cover}
.adm-cover-top-fill{object-fit:cover;object-position:top center;width:100%;height:100%}
.adm-thumb-34{height:34px;max-width:90px;object-fit:cover;border-radius:4px}
.adm-thumb-30x44{height:30px;width:44px;object-fit:cover;border-radius:4px}
.adm-thumb-30x48{height:30px;width:48px;object-fit:cover;border-radius:4px}
.adm-avatar-28{height:28px;width:28px;object-fit:cover;border-radius:3px}
.adm-avatar-34{height:34px;width:34px;object-fit:cover;border-radius:6px}
.adm-avatar-64{height:64px;width:64px;object-fit:cover;border-radius:6px;border:1px solid #e0e2e6}
.adm-icon-32{height:32px;width:32px;object-fit:contain}
.adm-flag-sm{height:1rem;width:1.4rem;object-fit:cover;border-radius:2px}
.adm-img-h90{max-height:90px;max-width:100%;border-radius:6px}
.adm-img-h56{height:56px;border-radius:6px;margin-top:.4rem}.adm-img-h48{height:48px;border-radius:6px;margin-top:.4rem}
.adm-logo-box{max-height:48px;background:#f4f5f7;padding:.25rem;border-radius:6px}
.adm-logo-box-dark{max-height:42px;background:#222;padding:4px;border-radius:6px}
.adm-logo-thumb{max-height:40px;max-width:90px;object-fit:contain;border:1px solid #e6e9ee;border-radius:6px;padding:2px}
.adm-logo-h48{height:48px;max-width:180px;object-fit:contain;background:#f6f8fa;border-radius:6px;padding:4px}
.adm-logo-h30{height:30px;max-width:110px;object-fit:contain}
.adm-fit-60vh{max-width:100%;max-height:60vh;object-fit:contain}
.adm-fit-full{max-width:100%;max-height:100%;object-fit:contain}
.adm-img-framed{max-width:100%;border-radius:8px;border:1px solid #e6e9ee}
.adm-img-320{display:block;max-width:320px;max-height:320px;border-radius:3px}
.adm-card-img-square{width:100%;aspect-ratio:1;object-fit:cover;border-radius:6px 6px 0 0}
.adm-thumb-43{aspect-ratio:4/3;background:#f1f3f5;display:flex;align-items:center;justify-content:center;overflow:hidden}
.adm-thumb-box-110{height:110px;background:#f6f6f6;display:flex;align-items:center;justify-content:center;overflow:hidden}
.adm-preview-box{background:#f1f3f5;border-radius:8px;display:flex;align-items:center;justify-content:center;min-height:220px;max-height:60vh;overflow:hidden}
.adm-preview-strip{position:relative;height:120px;overflow:hidden;background:linear-gradient(135deg,#f7f9fc,#eceff4)}
.adm-icon-125{width:1.25rem;height:1.25rem;flex:0 0 1.25rem}
.adm-sq-1-25r{width:1.25rem;height:1.25rem}.adm-icon-w-125{width:1.25rem;flex:0 0 1.25rem}
/* tints / swatches / panels */
.adm-tint-red{background:#fde8e8;color:#b42318}.adm-tint-orange{background:#fff4e5;color:#b54708}
.adm-tint-amber{background:#ffe2c2;color:#9a3412}.adm-tint-green{background:#d7f5dd;color:#15803d}
.adm-tint-grey{background:#eef0f2;color:#475467}.adm-text-slate{color:#475569!important}
.adm-swatch-blue{background:#206bc4}.adm-swatch-green{background:#2fb344}
.adm-swatch-purple{background:#ae3ec9}.adm-swatch-yellow{background:#f59f00}.adm-bg-soft{background:#eef1f5}
.adm-panel-soft{background:#f6f8fb;border:1px solid #e6e9ee}
.adm-dropzone{border:1px dashed #cbd5e1;border-radius:8px;padding:1rem;background:#fff}
.adm-dropzone-tint{border:1px dashed #cbd5e1;border-radius:8px;padding:1rem;background:#f8fafc}
.adm-dash-frame{border:1px dashed #d0d5dd;border-radius:10px;overflow:hidden}
/* skeleton loaders */
.adm-skel{height:.6rem;background:#eef2f7;border-radius:3px;margin:.3rem 0}
.adm-skel-90{width:90%}.adm-skel-80{width:80%}.adm-skel-70{width:70%}
/* iframes / frames / maps / panes */
.adm-iframe-fill{width:100%;border:0}.adm-iframe-fill-white{width:100%;border:0;background:#fff}
.adm-iframe-720{width:100%;height:720px;border:0}
.adm-iframe-70vh{width:100%;height:70vh;border:0;background:#fff}
.adm-iframe-280{width:100%;height:280px;border:0;border-radius:6px;background:#fff;display:block;margin:0 auto}
.adm-iframe-full{width:100%;height:100%;border:0;display:block}
.adm-iframe-min400{width:100%;border:0;min-height:400px;display:block;background:transparent}
.adm-frame-75vh{height:75vh;min-height:480px}
.adm-offscreen-frame{position:fixed;left:-9999px;width:1280px;height:900px;border:0}
.adm-theme-thumb-frame{position:absolute;top:0;left:0;width:1280px;height:533px;max-width:none;border:0;transform:scale(.225);transform-origin:top left;pointer-events:none}
.adm-map-320{height:320px;border:1px solid var(--tblr-border-color,#dee2e6);border-radius:8px;overflow:hidden;background:#eef2f5}
.adm-map-280{height:280px;border:1px solid #e6e8eb;border-radius:8px;overflow:hidden}
.adm-split-pane-textarea{flex:1 1 50%;resize:none;min-height:0}
.adm-split-pane-frame{flex:1 1 50%;border:0;border-left:1px solid #e6e8eb}
/* floating / positioned chrome */
.adm-fab-save{right:28px;bottom:28px;z-index:1040;border-radius:999px;padding:.75rem 1.5rem}
.adm-fab-save-sm{right:24px;bottom:24px;z-index:1060;border-radius:999px;padding:.7rem 1.4rem}
.adm-corner-badge{top:-2px;right:-4px;font-size:.6rem;line-height:1;padding:.2rem .3rem;border-radius:999px}
.adm-dropdown-panel-230{position:absolute;top:calc(100% + 4px);left:0;z-index:1060;width:230px}
.adm-carousel-next{position:absolute;right:8px;top:50%;transform:translateY(-50%);font-size:1.6rem;opacity:.85}
.adm-carousel-prev{position:absolute;left:8px;top:50%;transform:translateY(-50%);font-size:1.6rem;opacity:.85}
.adm-toast-center{z-index:2000;top:1rem;left:50%;transform:translateX(-50%);min-width:20rem;max-width:42rem}
.adm-score-circle{width:74px;height:74px;border-radius:50%;background:var(--tblr-bg-surface,#fff);display:flex;flex-direction:column;align-items:center;justify-content:center}
.adm-input-17r{width:17rem;padding-right:1.9rem}
.adm-top-0{top:0}.adm-top-50{top:50%}.adm-top-100{top:100%}

/* Grip cells rendered as .dragcell are draggable (js-grid module 9 accepts both handle classes). */
.js-grid .dragcell{cursor:grab}


/* Keyboard-shortcut chips (v2026.07.16.13). .adm-kbd-btn = the header "Ctrl K" quick-jump button:
   transparent at rest, a soft tint on hover -- the old btn-ghost-secondary hover painted a solid pill
   behind the <kbd> and made it unreadable. .adm-menu-kbd = the "Ctrl M" hint inside the dark sidebar
   search box; it hides while typing so it never overlaps the text. */
.adm-kbd-btn{border:0;background:transparent;color:var(--tblr-secondary,#6c7a91);display:inline-flex;align-items:center;gap:.35rem;padding:.25rem .5rem}
.adm-kbd-btn:hover,.adm-kbd-btn:focus-visible{background:rgba(98,105,118,.12);color:var(--tblr-body-color,#1d273b)}
.adm-kbd-btn kbd{background:transparent;color:inherit;border:1px solid rgba(98,105,118,.35);border-radius:4px;padding:.1rem .35rem;font-size:.7rem;box-shadow:none}
.adm-menu-search{position:relative}
.adm-menu-kbd{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);pointer-events:none;background:transparent;color:rgba(255,255,255,.45);border:1px solid rgba(255,255,255,.25);border-radius:4px;padding:.05rem .3rem;font-size:.65rem;box-shadow:none}
.adm-menu-search input:focus ~ .adm-menu-kbd,.adm-menu-search input:not(:placeholder-shown) ~ .adm-menu-kbd{display:none}
.adm-menu-search.input-icon input.form-control{padding-right:3.4rem}

/* Website switcher panel on the sidebar identity card (v2026.07.16.14): the yellow site row IS the
   switcher (click or Alt+W). Panel overlays the sidebar; items are full-width forms posting SwitchSite. */
.adm-idcard{position:relative}
.adm-idkbd{background:transparent;color:rgba(255,255,255,.45);border:1px solid rgba(255,255,255,.25);border-radius:4px;padding:.05rem .3rem;font-size:.62rem;box-shadow:none;margin-left:auto}
/* Full sidebar swap (v2026.07.16.17): the panel is a static block — opening it hides #sidebar-menu and
   this list takes the menu's place, with a "Back to menu" row on top. The hide needs !important because
   Bootstrap's .navbar-expand-lg .navbar-collapse is display:flex !important on desktop. */
.navbar-collapse.adm-menu-swapped{display:none !important}
/* Sidebar: pack the brand + menu to the TOP. Bootstrap's `.navbar > .container-fluid` uses
   justify-content:space-between, which on the VERTICAL (flex-column) sidebar spreads the identity card, the
   menu and the collapse toggle apart — the large gap under the identity card (Graham 2026-08-13). Pack them
   to the top. The rail toggle now sits ABOVE the brand (Graham 2026-08-13), so it no longer needs the
   margin-top:auto that pinned it to the foot — with the column packed, it simply leads the stack. Desktop
   only — the phone sidebar is an off-canvas drawer. */
@media (min-width: 992px) {
    .navbar-vertical > .container-fluid { justify-content: flex-start !important; }
}
/* The phone icon dock (#darkbarIcons) is EMPTY on tablet/desktop — but it's a .navbar-nav, which Tabler
   grows (flex) inside the sidebar column, so the empty div silently claimed half the sidebar's free
   space: the void between the identity card and the menu, growing with the monitor (Graham 2026-08-18:
   "The menu needs to be top aligned not centered"). It only receives icons under 768px; above that it
   simply doesn't exist. */
@media (min-width: 768px) { #darkbarIcons { display: none; } }
.adm-site-panel{background:#182135;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:.25rem;margin-top:.5rem;max-height:70vh;overflow-y:auto}
.adm-site-back{color:rgba(255,255,255,.55);border-bottom:1px solid rgba(255,255,255,.12);border-radius:6px 6px 0 0;margin-bottom:.25rem}

/* AI-builder colour toggle (v2026.07.16.19): palette greys out while "the AI picks the colours" (default). */
.ab-pal-off{opacity:.45;pointer-events:none}
/* Switcher domain search: same dark chrome as the panel; the icon sits inside the field's left edge. */
.adm-site-search{position:relative;margin:.15rem .15rem .3rem}
.adm-site-search .ti-search{position:absolute;left:.55rem;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.45);font-size:.85rem;pointer-events:none}
.adm-site-search input{width:100%;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.18);border-radius:6px;color:#fff;font-size:.82rem;padding:.4rem .6rem .4rem 1.7rem;outline:none}
.adm-site-search input::placeholder{color:rgba(255,255,255,.45)}
.adm-site-search input:focus{border-color:rgba(255,255,255,.4);background:rgba(255,255,255,.1)}
.adm-site-none{color:rgba(255,255,255,.5);cursor:default}
.adm-site-item{display:flex;align-items:center;width:100%;border:0;background:transparent;color:rgba(255,255,255,.8);text-align:left;padding:.45rem .6rem;border-radius:6px;font-size:.82rem;text-decoration:none}
.adm-site-item:hover,.adm-site-item:focus-visible{background:rgba(255,255,255,.08);color:#fff}
.adm-site-item.is-current{color:#ffd43b}
/* PHONE (Graham 2026-07-29: "does not work well on mobile"): the switcher was inheriting the squeezed
   sidebar column — thumb-size rows, bigger type, and real breathing room on small screens. */
@media (max-width: 767.98px) {
  .adm-site-panel{max-height:60vh;padding:.4rem}
  .adm-site-item{min-height:44px;font-size:.95rem;padding:.6rem .75rem}
  .adm-site-search input{font-size:16px;padding:.55rem .6rem .55rem 1.9rem}  /* 16px stops iOS zoom-on-focus */
}


/* ── CRM on a phone (Graham 2026-08-03: the desk should work from the thumb, not just the desk) ──────────
   Measured at 375×812, not guessed: card action buttons rendered 22×14px, the deal-page header row pushed
   30px past the viewport, and the board showed 1.7 squeezed columns. Desktop is untouched — everything
   here lives behind the phone breakpoint. */
@media (max-width: 767.98px) {
  /* PHONE DEFAULT (Graham 2026-08-09: "the desktop board is way too busy on mobile"): stack the pipeline
     VERTICALLY — scroll DOWN through the phases and their already-collapsible stages, instead of swiping one
     squeezed column at a time. Calmer and easier to navigate. The "Use desktop version" button on the board
     sets a cookie → .crm-force-desktop on <html>, which brings the swipe-columns board back for anyone who
     prefers it. Desktop (≥768) is untouched either way. */
  html:not(.crm-force-desktop) .crm-board{flex-direction:column;overflow-x:visible !important;scroll-snap-type:none}
  html:not(.crm-force-desktop) .crm-board>div{min-width:0 !important;width:100%;flex:0 0 auto !important}
  html.crm-force-desktop .crm-board{scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}
  html.crm-force-desktop .crm-board>div{min-width:84vw !important;scroll-snap-align:start}
  /* Card controls become tap targets. 38px, not 44: three of them + a name must still share a 315px card
     row without wrapping every card to three lines. */
  .crm-card button,.crm-card .btn{min-width:38px;min-height:38px}
  /* Headers built as one non-wrapping flex row (title … buttons) push their buttons off-screen at 375px —
     the deal page's "Back to all leads" measured right:405. Let them wrap. */
  .card-header.d-flex{flex-wrap:wrap;row-gap:.25rem}
  /* Same disease, inline forms: wrap instead of poke out. Any flex form on a 375px screen should wrap —
     the pinned-fact editor (a d-flex form inside a <details> in the deal header) measured right:615 open. */
  form.d-flex{flex-wrap:wrap;row-gap:.25rem}
  form.d-flex input{max-width:100%;min-width:0}
  /* The floating Save sat half off the right edge (right:385 at 375 wide). */
  .adm-floating-save{right:.5rem;max-width:calc(100vw - 1rem)}
  /* To-do / history tables are wider than the card — scroll them inside it rather than clipping. */
  .card-body>.table-responsive,.card-body table{max-width:100%}
  .card-body:has(>table){overflow-x:auto}
}
.adm-site-foot{border-top:1px solid rgba(255,255,255,.12);border-radius:0 0 6px 6px;margin-top:.25rem;color:rgba(255,255,255,.55)}

/* ── Admin chrome on a phone (Graham 2026-08-04: "The header needs to be small") ─────────────────────
   Below lg the dark sidebar renders as a TOP BLOCK, and it was showing the entire identity card — site,
   database, user, version, two preview links and the view toggle — before any actual page content.
   Folded: hamburger + brand + the yellow working-site row (the one fact that changes what edits do).
   Opening the hamburger menu unfolds the full card — those rows are navigation, so they live with it.
   Universal search on phones = the search icon in the white header (added in _AdminLayout), which opens
   the same ⌘K palette the desktop shortcut does. */
@media (max-width: 991.98px) {
  .navbar-vertical .container-fluid{padding-top:.25rem;padding-bottom:.25rem}
  .navbar-vertical .navbar-brand{margin:0;padding:.25rem 0}
  .adm-idcard{margin:.25rem 0 0;padding:0}
  /* The identity card stays FOLDED on phones even with the menu open (Graham 2026-08-04 round 2: "the
     drop down menu should not bring up the old large display") — the hamburger shows navigation only.
     Database/user/version/preview/view-toggle remain desktop rows; the phone paths are the search icon
     (palette) and the yellow site row (palette in sites mode). */
  .adm-idcard .adm-idrow:not(.adm-idrow-site){display:none}
}
@media (max-width: 767.98px) {
  /* The white header row: slimmer, so chrome (dark block + header) stays under ~2 compact rows. */
  header.navbar{min-height:0}
  header.navbar .container-xl{min-height:2.6rem}
  header.navbar .nav-link{padding-top:.25rem;padding-bottom:.25rem}
  /* The ⌘K palette on a phone is a FULL-SCREEN takeover (Graham 2026-08-04: "bring up full width search
     where we can type what feature you want") — one surface for features AND the site switcher (sites
     mode). Keyboard hints (footer, Alt-K chip) are meaningless on touch and hide; the X closes. 16px
     input stops iOS zoom-on-focus. */
  #cmdk{padding-top:0;align-items:stretch}
  #cmdk .cmdk-box{min-width:0;width:100%;max-width:100%;height:100dvh;max-height:100dvh;border:0;border-radius:0;display:flex;flex-direction:column}
  #cmdk .cmdk-foot,#cmdk .cmdk-kbd{display:none}
  #cmdk .cmdk-close{display:inline-flex}
  #cmdk-list{max-height:none;flex:1 1 auto;overscroll-behavior:contain}
  #cmdk-input{font-size:16px}
}

/* ══ THE PHONE FLOOR (mobile audit 2026-08-08) ═══════════════════════════════════════════════════════
   Graham: the phone should be a first-class way to run this system, not an add-on. Two things stood in
   the way of that everywhere at once, and both were measured at 375×812 rather than guessed:

   1. 45 of the 46 typable controls on the CRM board rendered UNDER 16px. Below 16px, iOS Safari ZOOMS
      the page every time a field takes focus — so on an iPhone, tapping the board's own quick-add,
      the find box, or any quantity field threw the layout sideways and left the operator pinching back
      out. Every single time.
   2. Every toolbar button rendered 20–21px tall. Apple asks for 44pt and Google for 48dp; 20px is under
      half, and it is why hitting "Add lead" on a phone is a game of chance.

   Both have one cause: the admin leans on Bootstrap's COMPACT DESKTOP sizes — .btn-sm and
   .form-control-sm, 160 and 41 of them on the CRM board alone and 549 form-control-sm on the Packages
   editor. Those sizes are right under a mouse and wrong under a thumb. So this is fixed ONCE, here,
   behind the phone breakpoint, rather than in eight hundred call sites that would drift apart again.

   Desktop is untouched: everything below lives inside the media query. */
@media (max-width: 767.98px) {
  /* — Typable controls: 16px is not a style choice, it is the iOS zoom threshold — */
  .form-control, .form-select, .form-control-sm, .form-select-sm,
  input[type=text], input[type=email], input[type=tel], input[type=url], input[type=search],
  input[type=number], input[type=password], input[type=date], input[type=datetime-local],
  input[type=time], select, textarea {
    font-size: 16px;
    min-height: 40px;
  }
  /* Bootstrap sizes the CHILDREN of an .input-group-sm with a two-part selector, which outranks the
     single-class rule above — so the CRM board's find box stayed at 12px and kept zooming while every
     other field was fixed. Matched at the same specificity here. */
  .input-group-sm > .form-control, .input-group-sm > .form-select,
  .input-group-sm > .input-group-text, .input-group-sm > .btn { font-size: 16px; min-height: 40px; }
  /* No control is wider than the screen. Desktop widths set in rem (the lead-form picker was 22rem =
     352px, sitting 29px in from the left edge) do not shrink on their own and drag the page sideways. */
  .form-control, .form-select, textarea, select, input { max-width: 100%; }
  /* A textarea sized by rows must keep its height; only the zoom floor applies. */
  textarea { min-height: 0; }
  /* Bootstrap's -sm padding is built for 12px type and clips 16px text. */
  .form-control-sm, .form-select-sm { padding: .375rem .625rem; }

  /* — Buttons become things a thumb can hit —
     40px, not 44: it clears the "game of chance" threshold while still letting a label plus an icon sit
     in one row at 375px. .crm-card's own controls keep the deliberate 38px set above — that rule is more
     specific and wins — because three of them share a card row with a name. */
  .btn, .btn-sm { min-height: 40px; display: inline-flex; align-items: center; justify-content: center; }
  /* Icon-only buttons need width as well as height, or they stay a 20px-wide sliver. */
  .btn-icon, .btn:has(> i:only-child) { min-width: 40px; }
  /* Buttons sitting inside an input group must match the field they are attached to, not overhang it. */
  .input-group .btn { min-height: 40px; }

  /* — Checkboxes and radios are tap targets too, and shipped at 1em (~14px) — */
  .form-check-input { width: 1.35rem; height: 1.35rem; margin-top: .1rem; }
  .form-check { min-height: 40px; display: flex; align-items: center; }
  .form-check-label { padding-left: .35rem; }
  /* A SWITCH IS NOT A SQUARE (Graham 2026-08-14: "why does the store button look different on small
     screen?" — the Store On/Off switch rendered as a blue crescent while the Show switches beside it were
     proper pills). Two things above did it, and both were mine. The square sizing on the line above is
     written for a checkbox and flattens a switch's 2rem pill; and making .form-check a FLEX row leaves
     Bootstrap's switch geometry — padding-left:1.5em on the box, margin-left:-1.5em on the input, float —
     pulling in a direction flex no longer honours, so the control ends up clipped against its own label.
     Restate the pill explicitly (higher specificity, both axes pinned) and drop the float-era offsets that
     the flex row already made meaningless. */
  .form-switch .form-check-input { width: 2.25rem; height: 1.35rem; flex: 0 0 auto; background-position: left center; }
  .form-switch .form-check-input:checked { background-position: right center; }
  .form-check { padding-left: 0; }
  .form-check .form-check-input { margin-left: 0; float: none; }

  /* — Body copy floor. 12px was the single most common size on the board (50 elements); it is a
       squint on a phone held at arm's length. Only the smallest tier is lifted, so the visual
       hierarchy that distinguishes a heading from a hint survives. — */
  body { font-size: 15px; }
  .small, small, .fs-6 { font-size: .9375rem; }   /* 15px */
  .text-secondary.small, .form-text { font-size: .875rem; }

  /* — Wide tables scroll inside their own box rather than stretching the page.
       The page body must never scroll sideways; a table may. — */
  .table-responsive { -webkit-overflow-scrolling: touch; }
  .card > .table-responsive, .card-body > .table-responsive { max-width: 100%; overflow-x: auto; }
  .table td, .table th { padding: .55rem .5rem; }
  /* A table with NO .table-responsive wrapper stretched the whole page instead: the products grid measured
     742px inside a 375px screen, so the page itself scrolled sideways and every other row was dragged with
     it. Give the table's own container the scroll, whatever the markup called it. */
  .card:has(> .table), .card:has(> table), .card-body:has(> table),
  div:has(> table.card-table) { overflow-x: auto; }

  /* — Rows built as one non-wrapping flex line push their tail off-screen —
     The pages list is the clearest case and it is the page Graham lands on after login: a page name, then
     an ms-auto Edit/Copy/Delete cluster 207px wide, on a row that never wraps — so the actions sat between
     406 and 474px on a 375px screen and the whole list scrolled sideways to reach them. */
  .page-actions { margin-left: 0 !important; width: 100%; justify-content: flex-end; }
  .list-group-item.d-flex, .list-group-item > .d-flex { flex-wrap: wrap; row-gap: .4rem; }
  /* The row itself must be allowed to wrap, or a full-width actions span still has nowhere to go —
     .page-row is a d-flex with no flex-wrap, so the actions sat at right:406–474 on a 375px screen. */
  .page-row { flex-wrap: wrap; row-gap: .35rem; }

  /* The workflow editor's step title carries a desktop min-width of 22rem (352px), which alone is wider
     than the content box; and its clock ("due after / late after / days / hours") is a 370px inline row
     with flex:0 0 auto, so it refused to shrink and pushed the page to 398px. */
  .crm-step-title { min-width: 0; width: 100%; }
  .crm-step-clock { margin-left: 0 !important; flex-wrap: wrap; row-gap: .3rem;
                    width: 100%; max-width: 100%; flex: 1 1 auto; }

  /* General backstop: a child of a wrapping flex row must not be wider than the row. Filter groups on the
     product and order grids were a few pixels over and dragged the whole page sideways with them. */
  .d-flex.flex-wrap > * { max-width: 100%; }
}

/* ══ THE CHROME BUDGET (mobile audit 2026-08-08, suggestions 1-6) ════════════════════════════════════
   Measured at 375x812: the first CRM lead card began at 755px — 93% of the screen spent before a single
   piece of work. 261px of that was chrome present on EVERY page: dark brand block 79, white header 56,
   breadcrumb 21, page title 44, "site isn't public" alert 61. Much of it said the same thing twice —
   the breadcrumb ended "› Orders" directly above a title reading "E-COMMERCE / Orders".

   Everything here is phone-only. The desk has the room and keeps the fuller chrome. */

/* The title bar folds to ONE line on a phone: "Store · Orders" instead of a stacked pretitle. */
.adm-titlebar .page-pretitle { display: block; }
@media (max-width: 767.98px) {
  .adm-titlebar { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; }
  .adm-titlebar .page-pretitle { font-size: .7rem; letter-spacing: .04em; }
  /* The separator is CSS, not markup, so the desktop stack stays clean. */
  .adm-titlebar .page-pretitle::after { content: " ·"; margin-left: .15rem; opacity: .55; }
  .adm-titlebar .page-title { font-size: 1.15rem; }
  .page-header { margin-bottom: .6rem !important; }

  /* BREADCRUMB OFF (#4). Its two jobs are orientation and up-navigation. On a phone the back gesture does
     up-navigation and the title bar above does orientation — so it is 21px plus margin of pure repetition.
     Kept in the DOM for screen readers and for the desk. */
  .breadcrumb { display: none !important; }

  /* THE BRAND ROW OFF (#1). "Web Management" is the product name; it does not change, it is not a
     destination, and the icon beside it is not a control. The SITE row stays — it is the switcher, and the
     one fact on screen that changes what your edits do. */
  .navbar-vertical .navbar-brand > a { display: none !important; }
  .navbar-vertical .container-fluid { padding-top: .15rem; padding-bottom: .15rem; }
  .navbar-vertical .navbar-brand { padding: 0 !important; }
}

/* NOTICES IN A BUTTON. A site notice is a STATE, not an event: "this site isn't public" is true for the
   whole of a build, and "email is in test mode" for as long as the client hasn't given you their key. As
   stacked banners they charged a band of every screen to say what you already knew. One button carries the
   count and opens them, each with its fix. (Graham 2026-08-14 — replaces the .adm-warn strip.) */
/* The notices button sits IN a row of buttons now (the dashboard's View site / Website settings / More),
   so it carries no band margin and takes its size from that row rather than setting its own. */
.adm-notices-btn { display: inline-flex; align-items: center; gap: .45rem;
                   background: var(--tblr-yellow-lt, #fff3bf); color: #7a5b00;
                   border: 1px solid rgba(245,159,0,.45); font-weight: 600; }
.adm-notices-btn .ti { font-size: 1.05rem; }

/* SITE ACTIONS ROW: every button in it is a peer, so they all measure the same. */
.adm-siteact .btn { min-height: 38px; padding: .35rem .8rem; font-size: .9rem; display: inline-flex;
                    align-items: center; }

/* ── THE ADMIN HOME ON A PHONE ────────────────────────────────────────────────────────────────
   Nothing overflowed at 375px, so this was never a broken page — it was a desktop page shrunk. Four
   peer buttons ended up 56-75px wide, and "Website settings" set in a 75px box is not a label; then
   a wall of full-width tiles each spent a second line on prose that repeats its own title. Two
   phone-only changes: the actions become a 2x2 grid of real buttons, and every tile becomes one
   scannable row. Desktop is untouched. */
@media (max-width: 767.98px) {
    /* ms-auto is a DESKTOP instruction — "sit to the right of the site name". At this width the row
       belongs under the name, at full width, where there is room to be four buttons. */
    .adm-siteact { width: 100%; margin-left: 0 !important; flex-wrap: wrap; }
    .adm-siteact > * { flex: 1 1 calc(50% - .5rem); min-width: 0; }
    .adm-siteact .dropdown > .btn { width: 100%; }
    /* 38px is this row's DESKTOP measurement, and it is specific enough to beat the 40px tap-target
       floor set above — exactly the quiet regression that floor exists to catch. */
    .adm-siteact .btn { min-height: 44px; font-size: 1rem; width: 100%; justify-content: center;
                        padding: .4rem .6rem; }

    /* A TILE IS A ROW HERE. The description earns its place in a desktop grid, where it fills width a
       three-up card already occupies. On a phone the cards stack one per line, so that sentence doubles
       the height of every item in a list of thirty to say what the title above it just said. Title,
       count and state stay; the whole row stays one target well over 40px. */
    .dash-tile .card-body { padding: .6rem .8rem; }
    .dash-tile .card-body > .d-flex { margin-bottom: 0 !important; }
    .dash-tile .card-title { font-size: 1rem; }
    .dash-tile-d { display: none; }
}
.adm-notices-btn:hover { background: #ffeeb0; color: #6a4f00; }
/* Header-bar form (2026-08-24): the "!" icon with a red count, a peer of the Notifications envelope —
   the yellow chip styling above must not bleed onto it. */
.adm-notices-btn--icon, .adm-notices-btn--icon:hover { background: transparent; border: 0; color: inherit; font-weight: 400; gap: 0; }
.adm-notices-menu { min-width: min(560px, 92vw); max-width: 92vw; padding: .35rem; }
.adm-notice { display: flex; align-items: center; gap: .6rem; padding: .5rem .55rem; font-size: .875rem;
              border-bottom: 1px solid var(--adm-border); }
.adm-notice:last-child { border-bottom: 0; }
.adm-notice > .ti { color: #b7791f; flex: none; font-size: 1.05rem; }
.adm-notice-t { flex: 1 1 auto; }
@media (max-width: 575.98px) {
  .adm-notice { flex-wrap: wrap; }
  .adm-notice-t { flex: 1 1 100%; }
}
/* SAFE AREA (#6). viewport-fit=cover is already set; nothing was using the inset, so the most reachable
   strip of the screen sat blank. Reserved here so the bottom tab bar can own it. */
@media (max-width: 767.98px) {
  .page-body { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + .5rem); }
}

/* SECONDARY PAGE ACTIONS FOLD ON A PHONE. Six toolbar buttons measured 88px of the first screen on the CRM
   board — Import leads, Export CSV, Settings, Contracts: desk errands occupying the space the work wants.
   The DESK keeps every button visible (Graham 2026-08-05: "keep all these as visible buttons"); only the
   phone folds them, and "Add lead" — the one thing you actually do FROM a phone — never hides. */
/* ── THE TWO RULES FOR <details> IN THIS ADMIN ───────────────────────────────────────────────────────
   Chromium wraps a <details>'s content in a ::details-content box, and closes it with TWO mechanisms:
   `display: none` on that box AND `content-visibility: hidden`. They fail differently, so both matter.

   RULE 1 — never set an explicit `display` on the collapsing child. That defeats the first mechanism and
   the content PAINTS while closed. Bootstrap's `.d-flex` is the usual culprit: display:flex !important,
   so merely putting that utility on the child breaks the disclosure.

   RULE 2 — when a breakpoint wants a <details> permanently open (the desk treats both of these as plain
   layout, not disclosures), display alone is NOT enough: content-visibility:hidden makes ::details-content
   a size-containment root, so the <details> still MEASURES 0x0 and its contents overflow instead of taking
   up room. Un-contain it explicitly. Measured before the fix at 1280px: the toolbar was 0x0 with its five
   buttons stacked vertically and running to x=1377 — off the right edge of the screen. After: 491x20, one
   row, no horizontal scroll. Browsers without ::details-content simply ignore the rule and Rule 1's
   display override is all they ever needed.

   (I previously recorded here that "<details> does not collapse anywhere in this admin". THAT WAS WRONG,
   and the note is corrected rather than deleted because the wrong version was committed. The probe behind
   it measured the CHILD's bounding box, and a child inside a closed <details> still reports a rect — so it
   proved nothing. Measured properly, on the <details> element itself, every disclosure here collapses:
   .adm-prop 30->389px, .crm-tile 53->101px, .crm-mail's sibling body 0->2762px.) */
@media (min-width: 768px) {
  /* Desk: not a disclosure at all. The summary disappears and the items are always a plain row. */
  .adm-toolbar > summary { display: none; }
  .adm-toolbar > .adm-toolbar-items { display: flex; gap: .5rem; flex-wrap: wrap; }
  .adm-toolbar::details-content, .adm-warn::details-content { content-visibility: visible; }   /* Rule 2 */
}
@media (max-width: 767.98px) {
  .adm-toolbar[open] > .adm-toolbar-items { display: flex; gap: .5rem; flex-wrap: wrap; }
  .adm-toolbar[open] > summary { margin-bottom: .5rem; background: var(--adm-chip-bg, #e9edf3); }
}
.adm-toolbar > summary { list-style: none; }
.adm-toolbar > summary::-webkit-details-marker { display: none; }
@media (max-width: 767.98px) {
  .adm-toolbar-wrap { width: 100%; margin-left: 0 !important; }
  .adm-toolbar-more { display: inline-flex; align-items: center; gap: .35rem; min-height: 40px;
                      padding: .3rem .7rem; border: 1px solid var(--tblr-border-color, #e6e7e9);
                      border-radius: 8px; font-size: .875rem; font-weight: 600; cursor: pointer;
                      color: var(--tblr-secondary, #667382); background: #fff; }
  .adm-toolbar[open] { width: 100%; }
  .adm-toolbar[open] .adm-toolbar-more { margin-bottom: .5rem; }
  /* Button labels at 12px are a squint on a phone; the floor lifted their HEIGHT but not their type. */
  .btn, .btn-sm { font-size: .875rem; }
}

/* ══ PHONE NAVIGATION (mobile audit 2026-08-08, suggestions 7-12) ═══════════════════════════════════
   138 links sat behind one hamburger in the top-LEFT corner — the hardest place on a phone for a thumb,
   and a flat list nobody scrolls to the end of. */

/* #7 BOTTOM TAB BAR. Where the thumb already is. Phone only: above 768 the sidebar is the better answer
   and this disappears completely. */
.adm-tabbar { display: none; }
@media (max-width: 767.98px) {
  .adm-tabbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 1030;
    background: #fff; border-top: 1px solid var(--tblr-border-color, #e6e7e9);
    /* The inset Wave A reserved — on a notched phone the bar sits above the home indicator, not under it. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
    box-shadow: 0 -1px 6px rgba(0,0,0,.05);
  }
  .adm-tab { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
             gap: .1rem; min-height: 52px; padding: .35rem .2rem; border: 0; background: transparent;
             color: var(--tblr-secondary, #667382); font-size: .68rem; font-weight: 600;
             text-decoration: none; line-height: 1.1; }
  /* min-width:0 (above) lets each tab shrink below its label width so N tabs always share the bar equally —
     without it, flex:1 1 0 still floors at the label's content width, and 5 tabs (Back/Forward/Pages/Store/
     More) overflowed a 375px phone and dragged every page sideways. The label then truncates rather than
     widening the tab. */
  .adm-tab span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .adm-tab i { font-size: 1.25rem; }
  .adm-tab.is-on { color: var(--tblr-primary, #206bc4); }
  .adm-tab:active { background: rgba(32,107,196,.06); }
  /* THE PRIMARY SLOT (2026-08-15). One obvious do-it button per screen: a bar of five equal buttons is a
     menu, one accented action with helpers either side is a tool. Tinted rather than filled — a solid block
     at the bottom of every screen competes with the page's own content for the eye all day. */
  .adm-tab.is-primary { color: var(--tblr-primary, #206bc4); background: rgba(32,107,196,.08); }
  .adm-tab.is-primary i { font-size: 1.35rem; }
  /* Back on the dashboard: the slot stays (the shape never moves) but reads as "you are at the top"
     instead of offering a trip to the page you are already on. */
  .adm-tab.is-top { opacity: .38; }
  /* The page must be able to scroll clear of the bar, or the last row is permanently under it. */
  .page-body { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 64px) !important; }
}

/* PAGE EDITOR on a phone: the per-block "nudge up / nudge down" chevrons are inline-styled 16x11 to match
   the collapse caret on a desk — impossible to hit with a thumb. Enlarge just the tap target on a phone
   (the icons stay small); the block header wraps so the wider control never pushes the row sideways. The
   inline styles force !important. */
@media (max-width: 767.98px) {
  .block-editor .card-header, .cms-block .card-header { flex-wrap: wrap; row-gap: .2rem; }
  .block-move { justify-content: center; }
  .block-move .block-up, .block-move .block-down { width: 36px !important; height: 26px !important; }
  .block-move .block-up i, .block-move .block-down i { font-size: 17px !important; }
}

/* PHONE SAFETY NET — no form control may hold a min-width wider than its container. Several pickers carry a
   desk-friendly min-width (e.g. a 22rem lead-form / recipients select), sometimes as an INLINE style set in
   markup or JS. On a phone that min-width beats the container and drags the whole page sideways a few px —
   and because min-width outranks max-width in CSS, a max-width cap can't undo it. Zero it here: on a phone
   these controls are block/full-width and fill their container anyway, so nothing shrinks that shouldn't. */
@media (max-width: 767.98px) {
  .form-control, .form-select { min-width: 0 !important; }
}

/* #10 HEADER TAP TARGETS. Hamburger, search, notifications, help and avatar all rendered 32px — under the
   44pt minimum, and they are the controls every session touches first. The v341 floor missed them because
   it was scoped to .btn and these are .nav-link. */
@media (max-width: 767.98px) {
  /* .navbar-nav is in the selector to match Tabler's own `.navbar .navbar-nav .nav-link{min-height:2rem}`,
     which is three classes and outranks a two-class rule — the bell, the help toggle and the avatar stayed
     32px on the first attempt while the hamburger and search (not .navbar-nav children) took the fix. */
  header.navbar .navbar-nav .nav-link,
  header.navbar .nav-link,
  .navbar-vertical .navbar-toggler,
  header.navbar .navbar-toggler { min-width: 44px; min-height: 44px;
                                  display: inline-flex; align-items: center; justify-content: center; }
}

/* #8 SEARCH IS THE PRIMARY NAVIGATION ON A PHONE. With 138 destinations the palette is genuinely the best
   way through the admin. It began as a bare icon, then a labelled CTA button in the header row; Graham
   asked for the real search BAR back (2026-08-10), so on a phone it is now a full-width strip under the
   header — a box you type into, wired to the same ⌘K palette (data-cmdk-live). Phone only; the desk keeps
   its header bar. */
.adm-mobile-search { display: none; }
@media (max-width: 767.98px) {
  .adm-mobile-search {
    display: block; position: sticky; top: 0; z-index: 1020;
    padding: .45rem .75rem; margin: 0;
    background: var(--tblr-body-bg, #f6f8fb);
    box-shadow: 0 1px 0 var(--tblr-border-color, #e6e7e9);
  }
  /* 16px keeps iOS from zooming the page when the field is focused; 44px is the tap floor. */
  .adm-mobile-search .form-control { font-size: 16px; min-height: 44px; }
}

/* #9 THE MENU GROUPS COLLAPSE. 138 links in one flat scroll is a list you give up on. Each top-level
   section folds; admin-nav.js remembers which was last open and opens the current section by default, so
   the menu you land in is the one you were using. Phone only — the desk has room for the full tree. */
@media (max-width: 767.98px) {
  .navbar-vertical .navbar-nav > .nav-item > .admin-subnav { display: none; }
  .navbar-vertical .navbar-nav > .nav-item.is-open > .admin-subnav { display: block; }
  .navbar-vertical .navbar-nav > .nav-item > .nav-link { min-height: 44px; }
  /* The caret says "there is more behind this" — without it a collapsed group looks like a dead link. */
  .navbar-vertical .navbar-nav > .nav-item:has(> .admin-subnav) > .nav-link::after {
    content: "\ea5f"; font-family: "tabler-icons"; margin-left: auto; opacity: .55;
    transition: transform .15s; }
  .navbar-vertical .navbar-nav > .nav-item.is-open > .nav-link::after { transform: rotate(180deg); }
}

/* ══ TABLES BECOME CARDS ON A PHONE (mobile audit 2026-08-08, suggestions 13-17) ════════════════════
   The orders grid measured 7 columns and 766px on a 375px screen. It "worked" — it scrolled sideways
   inside its own box — but nobody reads a table sideways, and only 3 of 20 rows fitted above the fold.

   A row becomes a card: each cell on its own line, labelled by its column header, which the markup already
   carries. Opt in per table with .adm-cards so a grid that genuinely wants to stay a grid can.

   The column header text comes from a data-label attribute, set by admin-cards.js from the <th> — no
   Razor page has to repeat its own headings, and a table that gains a column keeps working. */
@media (max-width: 767.98px) {
  .adm-cards thead { display: none; }                 /* headings move onto each cell */
  .adm-cards, .adm-cards tbody, .adm-cards tr, .adm-cards td { display: block; width: 100%; }
  .adm-cards tr {
    border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 10px;
    margin-bottom: .6rem; padding: .15rem .25rem; background: #fff;
  }
  .adm-cards td {
    display: flex; align-items: baseline; gap: .6rem;
    padding: .35rem .6rem !important; border: 0 !important;
    text-align: left !important;
  }
  /* The column name, from the <th>. Fixed width so the values line up down the card. */
  .adm-cards td::before {
    content: attr(data-label); flex: 0 0 6.5rem;
    font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .02em;
    color: var(--tblr-secondary, #667382);
  }
  .adm-cards td:empty { display: none; }               /* an empty cell is noise, not a row */
  .adm-cards td[data-label=""]::before { display: none; }

  /* #14 THE TWO THAT MATTER LEAD. A card whose first line is "#" and second is "CUSTOMER" buries the
     answer; mark the identity and the number .adm-key and they sit at the top, larger, unlabelled. */
  .adm-cards td.adm-key::before { display: none; }
  .adm-cards td.adm-key { font-size: 1.02rem; font-weight: 600; padding-bottom: .1rem !important; }

  /* …AND THE REST STEP BACK. Stacking all seven columns made an order card 285px tall — ONE card above
     the fold where the unreadable sideways table at least showed three. A grid that shows everything on a
     phone shows nothing. .adm-secondary marks the cells that are detail-view facts (an email address, a
     date) rather than the ones you scan a list for; they are hidden here and remain one tap away on the
     record itself. The card drops to four lines. */
  .adm-cards td.adm-secondary { display: none; }
  /* Two chips — paid and status — read better side by side than as two labelled lines. Their labels are
     dropped: a chip reading "UNPAID" does not need the word "Paid" in front of it, and the pair of them
     was printing "Paid UNPAID · Status PENDING". A chip that says what it is IS its own label. */
  .adm-cards td.adm-inline { display: inline-flex; width: auto; padding-right: .35rem !important; }
  .adm-cards td.adm-inline::before { display: none; }

  /* #16 ROW ACTIONS: one line at the foot of the card, full width, not three 40px buttons competing
     mid-row. */
  .adm-cards td.adm-rowactions { border-top: 1px solid var(--tblr-border-color, #e6e7e9) !important;
                                 margin-top: .2rem; padding-top: .45rem !important; }
  .adm-cards td.adm-rowactions::before { display: none; }
  .adm-cards td.adm-rowactions .btn-list { display: flex; gap: .4rem; width: 100%; }
  .adm-cards td.adm-rowactions .btn { flex: 1 1 0; }

  /* Don't let the card treatment leak into a table NESTED inside a card-ified cell (e.g. an inline history
     sub-table) — keep the inner table a normal table. */
  .adm-cards table thead { display: table-header-group; }
  .adm-cards table tr { display: table-row; }
  .adm-cards table td { display: table-cell; }
  .adm-cards table td::before { display: none; }

  /* #15 THE LIST'S OWN HEADER STICKS, not the page's. While scrolling a long grid, "what am I filtering
     by" matters more than the site name — and the page header has already scrolled away by then. */
  .adm-listbar { position: sticky; top: 0; z-index: 3;
                 background: var(--tblr-body-bg, #f6f8fb);
                 padding: .4rem 0; margin-bottom: .4rem;
                 box-shadow: 0 1px 0 var(--tblr-border-color, #e6e7e9); }

  /* THE OPENED SIDEBAR MENU MUST WIN. On a phone the hamburger opens #sidebar-menu, but its z-index is
     auto (~0) — so on any page carrying an overlay (the CRM phone app is z1045, the tab-bar z1030) the
     menu opened INVISIBLY behind it and the hamburger looked dead (Graham 2026-08-12). The mobile sidebar
     (its top bar and the expanded menu) sits above every admin overlay. */
  aside.navbar-vertical { position: relative; z-index: 1050; }

  /* Pages list: fit each row's actions on the SAME line as the page name (Graham 2026-08-12: they were
     wrapping to their own line). Edit/Copy/Delete go icon-only, the URL slug (shown on the edit page
     anyway) is dropped, and the name truncates — so the row reads: name … [✎][⧉][🗑]. */
  .page-row { flex-wrap: nowrap; }
  .page-row .page-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
  .page-row .page-slug { display: none; }
  /* width:auto UNDOES the earlier block's width:100% (2026-08-15 audit). Those two rules are answers to
     the same layout from opposite directions: the first made the actions a full-width row of their own so
     they could wrap under the name; this one puts them back on the name's line as icons. The flex-wrap was
     overridden and the WIDTH was not, so the span stayed 341px wide on a row that could no longer wrap —
     which put it at right:455 on a 375px screen and made the whole pages list scroll sideways. The page
     Graham lands on after login, scrolling horizontally, because two correct fixes only half-met. */
  .page-actions { flex: 0 0 auto; width: auto; }
  .page-actions .btn-lbl { display: none; }
  .page-actions .page-ico { margin: 0; }
  .page-actions .btn { padding-left: .45rem !important; padding-right: .45rem !important; }

  /* ── PAGES LIST: BROWSE vs MANAGE, + SWIPE (Graham 2026-08-15) ──────────────────────────────────
     Even icon-only, three buttons plus a checkbox plus a handle left the NAME — the one thing you scan
     for — the smallest part of its own row. So the row now shows the name and nothing else, and the two
     jobs get separated: BROWSE (swipe a row for edit/copy/delete) and MANAGE (checkboxes + drag handles
     + bulk bar). Only one is on at a time, which is also what keeps swipe and drag from competing. */

  /* Browse mode: the row is the page.
     !important because the span also carries Bootstrap's .d-flex, which is itself !important — without
     it the buttons stayed on screen and the swipe panel opened behind them. */
  .page-actions { display: none !important; }
  .page-row .page-sel,
  .page-row .page-drag { display: none; }
  .page-row { padding-top: .45rem !important; padding-bottom: .45rem !important; min-height: 44px; }
  .pages-hint-manage { display: none; }

  /* Manage mode: bring back exactly what managing needs. */
  .pages-manage .page-row .page-sel { display: inline-block; }
  .pages-manage .page-row .page-drag { display: inline-flex; align-items: center;
    /* A real grab target. The handle is not decoration on touch: SortableJS starts a drag ONLY from it,
       which is what stops a long-press on the row from stealing the page scroll. */
    min-width: 40px; min-height: 40px; justify-content: center; font-size: 1.15rem; }
  .pages-manage .pages-hint-browse { display: none; }
  .pages-manage .pages-hint-manage { display: inline; }
  .pages-manage #pagesManage { background: var(--tblr-primary, #206bc4); border-color: var(--tblr-primary, #206bc4); color: #fff; }

  /* ── Swipe ────────────────────────────────────────────────────────────────────────────────────── */
  .page-rowwrap { position: relative; overflow: hidden; }
  .page-swipe { position: absolute; inset: 0 0 0 auto; display: flex; align-items: stretch; }
  .page-sw { border: 0; color: #fff; width: 72px; display: flex; flex-direction: column;
             align-items: center; justify-content: center; gap: 2px; font-size: .7rem; line-height: 1; }
  .page-sw i { font-size: 1.15rem; }
  .page-sw-edit { background: var(--tblr-primary, #206bc4); }
  .page-sw-copy { background: #6c7a91; }
  .page-sw-del  { background: var(--tblr-danger, #d63939); }
  /* The row itself is the sliding surface. pan-y keeps vertical scrolling with the browser — we only
     ever claim the horizontal axis, and only once a swipe has clearly started. */
  .page-row { position: relative; background: var(--tblr-bg-surface, #fff); touch-action: pan-y;
              transition: transform .18s ease; will-change: transform; }
  .page-node.swiped > .page-rowwrap > .page-row { transform: translateX(-216px); }
  .page-node.swiping > .page-rowwrap > .page-row { transition: none; }
  /* Managing means selecting and dragging, so the swipe panel is inert and cannot be reached. */
  .pages-manage .page-swipe { display: none; }
  .pages-manage .page-row { transform: none !important; }
}

/* Desktop keeps the buttons and never sees the swipe panel — the whole feature is phone-only. */
@media (min-width: 768px) {
  .page-swipe { display: none; }
}

/* ══ DETAIL PAGES AND FORMS ON A PHONE (mobile audit 2026-08-08, suggestions 18-21) ═════════════════
   The CRM contact page measured 3.7 screens tall, one 2,396px card, with the composer 1.5 screens down. */

/* #19 THE COMPOSER COMES FIRST. On a phone you are almost always looking at a contact BECAUSE you are
   about to contact them — the note/email/SMS box is the reason the page is open, and it was below the
   fold. Reordered, not moved: the markup and the desktop layout are untouched. */
@media (max-width: 767.98px) {
  /* THREE levels have to agree, because the composer is nested three deep inside the contact card:
     card-body > .row > .col > #composerCard. Ordering only the innermost changed nothing (the column was
     already first); the ~980px above it is the identity, plans and pinned-note content that sits BEFORE
     the row inside the same card-body. So the row lifts within the body, the column lifts within the row,
     and the composer lifts within the column. */
  .card-body:has(.row > * > #composerCard) { display: flex; flex-direction: column; }
  .card-body > .row:has(> * > #composerCard) { order: -1; }
  .row:has(> * > #composerCard) { display: flex; flex-direction: column; }
  .row > *:has(> #composerCard) { order: -1; }
  *:has(> #composerCard) { display: flex; flex-direction: column; }
  #composerCard { order: -1; }
}

/* #18 JUMP BAR. A sticky row of anchors built from the page's own card headings by admin-sections.js, so a
   3.7-screen record becomes one tap to any part of it. */
.adm-jump { display: none; }
@media (max-width: 767.98px) {
  .adm-jump { display: flex; gap: .35rem; overflow-x: auto; -webkit-overflow-scrolling: touch;
              position: sticky; top: 0; z-index: 4; padding: .45rem 0; margin-bottom: .5rem;
              background: var(--tblr-body-bg, #f6f8fb);
              box-shadow: 0 1px 0 var(--tblr-border-color, #e6e7e9); scrollbar-width: none; }
  .adm-jump::-webkit-scrollbar { display: none; }
  .adm-jump a { flex: 0 0 auto; min-height: 36px; display: inline-flex; align-items: center;
                padding: .25rem .7rem; border-radius: 999px; font-size: .8rem; font-weight: 600;
                white-space: nowrap; text-decoration: none;
                background: var(--adm-chip-bg, #e9edf3); color: var(--tblr-secondary, #667382); }
  .adm-jump a.is-here { background: var(--tblr-primary, #206bc4); color: #fff; }
}

/* #20 STICKY SAVE. A Save you have to scroll to find is a Save people forget — and on a phone every form
   longer than a screen hides it. Any form carrying .adm-stickysave keeps its action bar on screen. */
@media (max-width: 767.98px) {
  .adm-stickysave { position: sticky; bottom: 0; z-index: 5;
                    background: #fff; border-top: 1px solid var(--tblr-border-color, #e6e7e9);
                    padding: .55rem .25rem calc(.55rem + env(safe-area-inset-bottom, 0px));
                    margin: .75rem -.25rem 0; display: flex; gap: .5rem; }
  .adm-stickysave .btn { flex: 1 1 0; min-height: 46px; }
  /* Clear of the tab bar, which is also fixed to the bottom. */
  .adm-stickysave { bottom: 56px; }
}

/* Dictation button state (#24). Red while listening, because a microphone that is on and does not look
   on is the one control where "did I stop it?" actually matters. */
.adm-voice-btn.is-listening { background: var(--tblr-red, #d63939); border-color: var(--tblr-red, #d63939);
                              color: #fff; }
.adm-voice-btn.is-listening i { animation: admPulse 1.1s ease-in-out infinite; }
@keyframes admPulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .adm-voice-btn.is-listening i { animation: none; } }
.adm-share-btn { margin-left: auto; }

/* ══ THE NEXT-UP QUEUE (phone-native CRM list, 2026-08-08) ═══════════════════════════════════════════
   Designed for a thumb first and allowed to grow up to a desk, which is the opposite of everything else
   in this file. Full-width rows, one column at every size, no horizontal anything — the board already
   owns the wide shape, so this one never competes for it. On a large screen it simply centres and stops
   growing, because a 1600px-wide to-do row is harder to read, not easier. */
.nx { max-width: 44rem; margin: 0 auto; }

/* Band headings. Sticky so you always know which pile you are in while you scroll a long queue. */
.nx-band { position: sticky; top: 0; z-index: 2; background: var(--tblr-body-bg, #f6f8fb);
           padding: 1rem 0 .35rem; }
.nx-band-h { display: flex; align-items: baseline; gap: .5rem; }
.nx-band-t { font-weight: 700; font-size: 1.05rem; }
.nx-band-n { font-weight: 700; font-size: .8rem; background: var(--adm-chip-bg, #e9edf3);
             color: var(--tblr-secondary, #667382); border-radius: 999px; padding: .05rem .5rem; }
.nx-band-s { color: var(--tblr-secondary, #667382); font-size: .85rem; margin-bottom: .35rem; }

/* A row is a card with a colour spine. The spine carries the RAG so the colour is readable at a glance
   without tinting the whole card — a wall of red backgrounds stops meaning "urgent" by the third one. */
.nx-row { background: #fff; border: 1px solid var(--tblr-border-color, #e6e7e9); border-radius: 10px;
          border-left-width: 5px; margin-bottom: .6rem; overflow: hidden; }
.nx-red   { border-left-color: var(--tblr-red,    #d63939); }
.nx-amber { border-left-color: var(--tblr-yellow, #f59f00); }
.nx-green { border-left-color: var(--tblr-green,  #2fb344); }
.nx-red   .nx-dot { color: var(--tblr-red,    #d63939); }
.nx-amber .nx-dot { color: var(--tblr-yellow, #f59f00); }
.nx-green .nx-dot { color: var(--tblr-green,  #2fb344); }

/* The body is one big link. Tapping anywhere that is not an action button opens the real record — a row
   with a small "open" link at the end makes you aim, and aiming is what a phone is bad at. */
.nx-main { display: block; padding: .7rem .85rem .55rem; color: inherit; text-decoration: none; }
.nx-main:hover, .nx-main:focus-visible { background: rgba(32,107,196,.04); color: inherit; }

.nx-top { display: flex; align-items: center; gap: .45rem; }
.nx-dot { font-size: 1rem; flex: 0 0 auto; }
.nx-name { font-weight: 600; font-size: 1.02rem; flex: 1 1 auto;
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The due wording never wraps or shrinks — it is the reason the row is on the screen. */
.nx-due { flex: 0 0 auto; font-size: .82rem; font-weight: 600; color: var(--tblr-secondary, #667382);
          white-space: nowrap; }
.nx-red .nx-due { color: var(--tblr-red, #d63939); }

/* What to do, in a sentence, from BuildTodos. This is the line that replaces opening the card. */
.nx-act { margin-top: .15rem; font-size: .95rem; }
.nx-meta { display: flex; flex-wrap: wrap; gap: .35rem .6rem; margin-top: .3rem;
           font-size: .8rem; color: var(--tblr-secondary, #667382); }
.nx-stage { background: var(--adm-chip-bg, #e9edf3); border-radius: 999px; padding: .05rem .5rem; }
.nx-val { font-weight: 600; color: var(--tblr-green, #2fb344); }
.nx-pin { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; font-style: italic; }

/* The action strip. Separated by a rule so it reads as "do" rather than as more "read". */
.nx-do { display: flex; align-items: stretch; gap: 0;
         border-top: 1px solid var(--tblr-border-color, #e6e7e9); }
.nx-snooze { margin: 0; display: flex; flex: 1 1 0; }
.nx-btn { flex: 1 1 0; display: inline-flex; align-items: center; justify-content: center; gap: .3rem;
          min-height: 44px; padding: .4rem .3rem; border: 0; background: transparent;
          color: var(--tblr-secondary, #667382); font-size: .85rem; font-weight: 600;
          text-decoration: none; border-left: 1px solid var(--tblr-border-color, #e6e7e9); }
.nx-do > :first-child, .nx-do > .nx-snooze:first-child .nx-btn { border-left: 0; }
.nx-btn:hover, .nx-btn:focus-visible { background: rgba(32,107,196,.06); color: var(--tblr-primary, #206bc4); }
/* CALL is the primary action and the one thing a desk cannot do. It reads as the answer, not an option. */
.nx-call { color: var(--tblr-green, #2fb344); }

/* Nothing owed is a RESULT, not an error — it gets the same care as a full list. */
.nx-clear { text-align: center; padding: 3rem 1rem; color: var(--tblr-secondary, #667382); }
.nx-clear i { font-size: 2.5rem; color: var(--tblr-green, #2fb344); }
.nx-clear-t { font-weight: 700; font-size: 1.15rem; margin-top: .5rem; color: var(--tblr-body-color, #1a2434); }
.nx-clear-s { font-size: .9rem; max-width: 26rem; margin: .25rem auto 0; }

@media (max-width: 767.98px) {
  /* Edge-to-edge on a phone: the 10px card radius is enough separation, and side gutters cost width the
     name and the due wording both want. */
  .nx-row { border-radius: 8px; }
  .nx-name { font-size: 1.05rem; }
  .nx-btn { font-size: .8rem; min-height: 46px; }
  /* Icon-only below ~360px so five actions still fit without wrapping to two rows. */
  @media (max-width: 359.98px) { .nx-btn span { display: none; } }
}

/* The zoom floor alone extends to TABLETS. Everything above is phone-width because that is where the
   layout has to change; but iOS zooms a sub-16px field on an iPad too, and an iPad is a device somebody
   will genuinely run this desk from. Scoped to touch (pointer: coarse) AND tablet width, so a touch-screen
   desktop keeps its compact sizing — the layout there has the room and the user has a mouse. */
@media (min-width: 768px) and (max-width: 1024px) and (pointer: coarse) {
  .form-control, .form-select, .form-control-sm, .form-select-sm,
  .input-group-sm > .form-control, .input-group-sm > .form-select,
  .js-grid .grid-filters .form-control, .js-grid .grid-filters .form-select,
  input[type=text], input[type=email], input[type=tel], input[type=url], input[type=search],
  input[type=number], input[type=password], input[type=date], input[type=datetime-local],
  input[type=time], select, textarea { font-size: 16px; }
}

/* Secret inputs (API keys, secrets): masked like a password but NOT type=password, because a password
   field makes Chrome treat the card as a LOGIN form — it autofills the saved site username into the
   nearest text box and the login password into the key box, silently corrupting saved keys on Save
   (the "northerncs in the Model box / key replaced by the login password" incident, 2026-07-17).
   -webkit-text-security masks in Chrome/Edge; other browsers show plain text on this owner-only page. */
.adm-secret{-webkit-text-security:disc}

/* ══ PHONE POLISH v2 (mobile deep-dive 2026-08-09) ═══════════════════════════════════════════════════
   The v341 floor made the phone usable — nothing overflows, nothing zooms, every button is a real target.
   This wave is the next tier: three measured defects that survived the floor because each has a cause the
   floor's broad selectors could not reach. All phone-only; the desk is untouched. */
@media (max-width: 767.98px) {
  /* BUG — the floating Save button OVERLAPPED the bottom tab bar. Both sat at z-index 1030; the FAB's
     bottom:1.25rem (20px) placed it INSIDE the 52px-tall nav band, so the green pill covered the "Store"
     and "More" tabs on every edit page (measured on the page editor: FAB 748–792, tab bar 759–812).
     Lift it a full bar-height clear, and put it one layer above so it is never occluded either way. */
  .adm-floating-save { bottom: calc(env(safe-area-inset-bottom, 0px) + 64px); z-index: 1031; }

  /* Status chips carry the one word that says what a row IS (Paid / Draft / Pending). At .6875rem they
     rendered 11px — the smallest readable thing on the orders list had 30 of them. Lift just the chip;
     the uppercase + weight that make it a chip are unchanged, so the visual language holds. */
  .adm-chip { font-size: .8rem; padding: .15rem .55rem; }

  /* The site switcher's current-site row — the one control on screen that changes what your edits DO —
     rendered 29px tall, under the 40px floor, because it is an .adm-idrow (not a .btn the floor caught).
     Give it the tap height without touching the collapsed sub-rows above it on the desk. */
  .adm-idrow-site { min-height: 40px; }
}


/* admin-mobile.css — styles for the phone-native niceties in admin-mobile.js (mobile upgrade 2026-08-10).
   Everything is gated to a phone; desktop is untouched. */

/* ── INSTALL BAR ── slim, above the bottom tab bar, dismissible. */
.adm-install-bar { display: none; }
@media (max-width: 767.98px) {
  .adm-install-bar {
    display: flex; align-items: center; gap: .5rem;
    position: fixed; left: .5rem; right: .5rem;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 60px);   /* clear the 52px tab bar + a little air */
    z-index: 1035;
    background: #fff; border: 1px solid var(--tblr-border-color, #e6e7e9);
    border-radius: 12px; padding: .5rem .6rem;
    box-shadow: 0 6px 20px rgba(0,0,0,.14);
    font-size: .85rem; font-weight: 600; color: var(--tblr-body-color, #1f2d3d);
  }
  .adm-install-bar .adm-install-txt { flex: 1 1 auto; min-width: 0; }
  .adm-install-bar .adm-install-go { flex: 0 0 auto; }
  .adm-install-bar .adm-install-x { flex: 0 0 auto; color: var(--tblr-secondary, #667382); padding-left: .35rem; padding-right: .35rem; }
}

/* ── KEYBOARD OPEN ── the tab bar must not sit on top of the on-screen keyboard / the field being typed. */
@media (max-width: 767.98px) {
  .adm-kb-open .adm-tabbar { display: none; }
  .adm-kb-open .adm-install-bar { display: none; }
  /* The bottom padding that reserves room for the tab bar isn't needed while it's hidden. */
  .adm-kb-open .page-body { padding-bottom: 1rem !important; }
}

/* ── PULL TO REFRESH ── a single chip that slides down from the top and spins on release (standalone app). */
.adm-ptr {
  position: fixed; top: 6px; left: 50%; transform: translateX(-50%);
  width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: #fff; box-shadow: 0 2px 8px rgba(0,0,0,.18);
  color: var(--tblr-primary, #206bc4); opacity: 0; z-index: 2000; pointer-events: none;
}
.adm-ptr i { font-size: 1.15rem; transition: transform .15s; }
.adm-ptr.is-armed i { transform: rotate(180deg); }
.adm-ptr.is-spinning i { animation: adm-ptr-spin .7s linear infinite; }
@keyframes adm-ptr-spin { to { transform: rotate(360deg); } }

/* ── BOTTOM-SHEET MENUS ── on a phone an open dropdown becomes a thumb-reachable sheet at the bottom. */
@media (max-width: 767.98px) {
  .dropdown-menu.show:not(.adm-no-sheet) {
    position: fixed !important;
    inset: auto 0 0 0 !important;
    transform: none !important;
    width: 100% !important; max-width: 100% !important;
    margin: 0 !important;
    border: 0 !important; border-top: 1px solid var(--tblr-border-color, #e6e7e9) !important;
    border-radius: 16px 16px 0 0 !important;
    max-height: 72vh; overflow-y: auto;
    padding: .35rem .35rem calc(env(safe-area-inset-bottom, 0px) + .5rem) !important;
    box-shadow: 0 -8px 30px rgba(0,0,0,.22) !important;
    z-index: 1056 !important;
    animation: adm-sheet-up .18s ease-out;
  }
  /* A grab handle so it reads as a sheet. */
  .dropdown-menu.show:not(.adm-no-sheet)::before {
    content: ""; display: block; width: 40px; height: 4px; border-radius: 2px;
    background: var(--tblr-border-color, #d7dbe0); margin: .25rem auto .4rem;
  }
  .dropdown-menu.show:not(.adm-no-sheet) .dropdown-item {
    padding-top: .7rem; padding-bottom: .7rem; border-radius: 8px; white-space: normal;
  }
  .dropdown-menu.show:not(.adm-no-sheet) .dropdown-item i { font-size: 1.1rem; }
  .adm-sheet-backdrop {
    position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 1055;
    animation: adm-fade-in .18s ease-out;
  }
}
@keyframes adm-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes adm-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ── TAP-TO-TEXT button (admin-phone.js wireTelText) — matches the tel: link beside it. */
.adm-sms-btn { text-decoration: none; }
.adm-sms-btn i { font-size: 1rem; vertical-align: middle; }

/* FEWER BARS ON A PHONE (admin-phone.js wireMobileHeaderIcons) — the envelope/help/account icons move up
   into the dark brand bar and the emptied white header is hidden, dropping a whole stacked bar. */
@media (max-width: 767.98px) {
  header.navbar.adm-header-hidden { display: none; }
  #darkbarIcons .nav-link,
  #darkbarIcons .nav-link i { color: #fff; }
  #darkbarIcons .nav-item { display: flex; align-items: center; }
}

/* RECLAIM LEFT/RIGHT SPACE ON A PHONE (Graham: "sub-sections keep adding more and more margins when the
   left/right space is at a premium"). The desk's roomy card padding (1.25rem each side) reads as generous
   margins on a wide screen, but on a phone it compounds with every nested sub-section — a card inside a
   card-body inside a card loses that padding twice or three times, squeezing content into a narrow column.
   Tighten the SIDE padding only (top/bottom kept for legibility), and don't pad a card-body that only wraps
   more cards — let the nested cards sit nearly flush so the indentation stops accumulating. Desk unchanged. */
@media (max-width: 767.98px) {
  .card-body   { padding-left: .8rem; padding-right: .8rem; }
  .card-header { padding-left: .8rem; padding-right: .8rem; }
  .card-footer { padding-left: .8rem; padding-right: .8rem; }
  /* A card-body whose direct children are themselves cards (or a row of column-cards) is pure nesting —
     drop its side padding so the inner cards don't sit inside a second full margin. */
  .card-body:has(> .card),
  .card-body:has(> .row > [class*="col"] > .card) { padding-left: .35rem; padding-right: .35rem; }
  /* Outermost gutter: a hair tighter than Bootstrap's default so the first card starts near the edge. */
  .page-body > .container-xl, .page-wrapper .container-xl { padding-left: .5rem; padding-right: .5rem; }
}

/* A BETTER MOBILE WAY TO EDIT PAGES (Graham: "content edit pages ... very confusing with buttons
   everywhere"). The desk shows [↔ full-width] [Hide] [Edit] [Delete] + ⋯ on every block row, aligned into
   columns — great on a wide screen, but on a phone those wrap into a ~146px pile per block. On a phone we
   collapse each collapsed block to a SLIM row: the block-type badge + Edit + ⋯. Every other row action
   (move, full-width, hide, delete, label, and the existing duplicate/section/shape/animation/sticky) lives
   behind the ⋯, which becomes the single action panel for that block. .card-header:has(.block-drag) targets
   ONLY block headers; desktop is untouched. */
@media (max-width: 767.98px) {
  .card-header:has(.block-drag) { flex-wrap: nowrap; gap: .15rem; row-gap: 0; }
  .card-header:has(.block-drag) > .block-drag,
  .card-header:has(.block-drag) > .block-move,
  .card-header:has(.block-drag) > .block-collapse,
  .card-header:has(.block-drag) > .block-label,
  .card-header:has(.block-drag) [data-aitrans],
  .card-header:has(.block-drag) .block-fullwidth,
  .card-header:has(.block-drag) label[for$="-fw"],
  .card-header:has(.block-drag) .block-act[data-vis],
  .card-header:has(.block-drag) .block-act[data-del] { display: none !important; }
  .card-header:has(.block-drag) .btn-list { margin-left: auto; gap: .25rem; flex-wrap: nowrap; }
  .card-header:has(.block-drag) .block-editbody,
  .card-header:has(.block-drag) .block-editctl { min-height: 36px; }
  .card-header:has(.block-drag) .block-more { min-height: 36px; min-width: 36px; }

  /* ⋯ open → this row IS the action menu: reveal what we hide and let it wrap into a panel. */
  .card-header.mob-adv-open { flex-wrap: wrap !important; }
  .card-header.mob-adv-open .block-move,
  .card-header.mob-adv-open .block-fullwidth,
  .card-header.mob-adv-open label[for$="-fw"],
  .card-header.mob-adv-open .block-act[data-vis],
  .card-header.mob-adv-open .block-act[data-del] { display: inline-flex !important; }
  .card-header.mob-adv-open > .block-label { display: block !important; width: 100%; max-width: none; margin: .25rem 0 0 !important; }
  .card-header.mob-adv-open .block-adv { display: flex !important; flex-wrap: wrap; width: 100%; margin-top: .25rem; }
  .card-header.mob-adv-open .btn-list { flex-wrap: wrap; }
}

/* Design pass, idea #17: let a short block's excerpt wrap to two lines instead of ellipsis-truncating at
   one, when the row has room to show it - more information for free on a block that isn't already packed. */
.block-excerpt-clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; white-space: normal !important; }

/* CMS-EDITOR-AUDIT Phase 4 (2026-08-22): six targets on a collapsed block row at EVERY width, not just on
   a phone - grip · expand chevron · type · excerpt · Edit · "..." — reusing the exact reveal-behind-"..."
   pattern the phone rule above already established (same .mob-adv-open toggle, same "..." click handler;
   only the width gate is new). Grip and the expand chevron stay put; move/full-width/Hide/Delete/an empty
   label move behind "...". A block-editctl/block-editbody Edit button is never in this hidden list, and
   neither is .block-drag or .block-collapse — those are the brief's own two "always visible" targets. */
.card-header:has(.block-drag) > .block-move,
.card-header:has(.block-drag) .block-fullwidth,
.card-header:has(.block-drag) label[for$="-fw"],
.card-header:has(.block-drag) .block-act[data-vis],
.card-header:has(.block-drag) .block-act[data-del] { display: none !important; }
.card-header:has(.block-drag) > .block-label:not(.has-value) { display: none !important; }
.card-header.mob-adv-open .block-move,
.card-header.mob-adv-open .block-fullwidth,
.card-header.mob-adv-open label[for$="-fw"],
.card-header.mob-adv-open .block-act[data-vis],
.card-header.mob-adv-open .block-act[data-del] { display: inline-flex !important; }
.card-header.mob-adv-open > .block-label { display: block !important; }

/* RETHINK NESTED LAYOUTS IN THE EDITOR ON A PHONE (Graham: "indentation and sub-indent — the margins keep
   getting bigger"). A columns block renders its columns as side-by-side Bootstrap .col cells; on a 375px
   phone two columns are ~168px each (three or four are 80-110px), and a block nested inside one shrinks to
   ~148px of editing width — unusable. On a phone the editor's columns STACK vertically (full width each,
   grouped by their bordered box) exactly like they'll render on the live site, and the nested wrappers go
   near-flush so depth is shown by the border, not paid for in width. Desktop keeps the side-by-side layout. */
@media (max-width: 767.98px) {
  .block-cols { --bs-gutter-x: .3rem; }
  .block-cols > .col,
  .block-cols > .block-col-edit,
  .block-cols > [class*="col-"] { flex: 0 0 100% !important; max-width: 100% !important; }
  .block-col-edit > .border { padding: .3rem !important; }
  .col-list > .card > .card-body,
  .col-list > .card > .block-body { padding-left: .4rem; padding-right: .4rem; }
}

/* CONTEXTUAL EDIT BAR (admin-page-editor.js) — docks when a block is open on a phone: Done + Preview +
   move/duplicate/delete, thumb-reachable, in the app's dark bar. Replaces the bottom nav while editing so
   they never stack. Desktop never sees it (phone-gated + JS-gated). */
.cms-editbar { display: none; }
@media (max-width: 767.98px) {
  .cms-editbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1041;
    align-items: center; gap: .4rem;
    padding: .4rem .55rem calc(env(safe-area-inset-bottom, 0px) + .4rem);
    background: #182433; box-shadow: 0 -6px 22px rgba(0,0,0,.28);
    transform: translateY(100%); transition: transform .18s ease;
  }
  .cms-editbar.is-on { display: flex; transform: translateY(0); }
  .cms-editbar .ceb {
    min-height: 40px; min-width: 40px; border: 0; border-radius: 9px;
    background: rgba(255,255,255,.09); color: #e2e9f1;
    display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
    font-size: 12.5px; font-weight: 650; padding: 0 .55rem; cursor: pointer;
  }
  .cms-editbar .ceb:active { background: rgba(255,255,255,.18); }
  .cms-editbar .ceb i { font-size: 1.15rem; }
  .cms-editbar .ceb-done { background: var(--tblr-primary, #206bc4); color: #fff; padding: 0 .85rem; }
  .cms-editbar .ceb-del { color: #ff9ea6; }
  .cms-editbar .ceb-t { font-size: 12px; }
  .cms-editbar .ceb-name {
    flex: 1; min-width: 0; font-size: 11.5px; font-weight: 700; color: #93a2b6;
    text-transform: uppercase; letter-spacing: .04em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .cms-editbar .ceb-actions { display: flex; gap: .3rem; }
  /* While the edit bar is up it stands in for the bottom nav — hide the nav so they don't stack. */
  body.cms-editing .adm-tabbar { display: none; }
  body.cms-editing .page-body { padding-bottom: 72px !important; }
}

/* OUTLINE HOME VIEW (admin-page-editor.js) — the concept-pitch outline made real on a phone: each COLLAPSED
   block is a clean row (drag · colour dot · name · TYPE), the whole row a tap target that opens it; the
   editing chrome (badge, label input, action buttons, kebab) hides until the block is open, where the
   contextual edit bar carries the actions. Desktop never sees any of this. */
.ol-dot, .ol-name, .ol-type { display: none; }
@media (max-width: 767.98px) {
  .card[data-type] > .card-header .ol-dot { display: block; width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
  .card[data-type] > .card-header .ol-name { display: block; flex: 1 1 auto; min-width: 0; font-size: 14px;
    font-weight: 650; color: var(--tblr-body-color, #1a2433); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .card[data-type] > .card-header .ol-type { display: block; flex: 0 0 auto; margin-left: auto; font-size: 10px;
    font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #9aa6b6; }
  /* Collapsed: the row IS the control. Hide everything else; actions live in the contextual bar once open. */
  .blk-collapsed > .card-header { padding: 12px 12px !important; cursor: pointer; gap: 10px; }
  .blk-collapsed > .card-header .block-type-badge,
  .blk-collapsed > .card-header .block-label,
  .blk-collapsed > .card-header .btn-list,
  .blk-collapsed > .card-header .block-more,
  .blk-collapsed > .card-header .block-collapse,
  .blk-collapsed > .card-header .block-move,
  .blk-collapsed > .card-header [data-aitrans] { display: none !important; }
  /* Open: drop the outline text, let the real chrome (label, actions) show for editing. */
  .card[data-type]:not(.blk-collapsed) > .card-header .ol-dot,
  .card[data-type]:not(.blk-collapsed) > .card-header .ol-name,
  .card[data-type]:not(.blk-collapsed) > .card-header .ol-type { display: none; }
  /* Nested (column-child) blocks indent one notch, like the mockup's tree. */
  .col-list > .card > .card-header { padding-left: 14px !important; }
}

/* PIPELINE STACK on a phone (CRM, 2026-08-12) — each stage collapses to a summary row: stage name · count ·
   $ total · a chevron you tap to open. The compact default (start collapsed) is set in Leads/Index.cshtml's
   zone script; this styles the rows into a clean, scannable list so the ~8,800px board becomes ~9 rows.
   Desktop and the tablet swipe-board (.crm-force-desktop) are untouched. */
@media (max-width: 767.98px) {
  html:not(.crm-force-desktop) .crm-zone-toggle {
    background: var(--tblr-bg-surface, #fff);
    border: 1px solid var(--tblr-border-color, #e6e7e9);
    border-radius: 10px; padding: 10px 12px; margin-bottom: 8px; min-height: 46px;
  }
  html:not(.crm-force-desktop) .crm-zone-toggle .badge { font-size: .8rem; padding: .34em .6em; }
  html:not(.crm-force-desktop) .crm-zone-toggle [data-zchev] { font-size: 1.05rem; }
  /* The four phase wrappers don't need their own bordered box on a phone — the stage rows are the cards. */
  html:not(.crm-force-desktop) .crm-board > div { border: 0 !important; padding: 0 0 2px !important; }
}

/* CRM CONTACT declutter on a phone (Wave 1) — when you're working a lead on a phone you need: name + stage,
   call/text/email, the next action, the composer, and recent history. The desk-only chrome
   (copy-to-clipboard buttons, history type-filter) is noise here — hidden. Kept: .crm-who, .crm-tiles,
   .crm-follow, the composer, history rows. */
@media (max-width: 767.98px) {
  html:not(.crm-force-desktop) .crm-copy { display: none !important; }
  html:not(.crm-force-desktop) #crmHistory .btn-group:has([data-hist]) { display: none !important; }
  /* Contact lines: real 40px tap rows for call / text / email — on EVERY touch layout (audit
     2026-08-19 #17): the tablet swipe-board mode (html.crm-force-desktop) is still a touch screen,
     and it was the one phone-width mode where these rows kept desktop 12.5px hit heights. */
  .crm-who-line { min-height: 40px; display: inline-flex; align-items: center; }
}

/* CRM board top chrome on a phone (Wave 1) — above the now-compact stage stack sat a stat row + a package
   filter + view toggles. On a phone the package filter is a secondary "who's on plan X" pass (off the board
   here; search/filter still reachable), and the stat bar tightens so the stages start higher. */
@media (max-width: 767.98px) {
  html:not(.crm-force-desktop) .crm-pkgbar { display: none !important; }
  html:not(.crm-force-desktop) .crm-statbar { margin-bottom: .5rem !important; row-gap: .3rem; }
}

/* CRM Inbox on a phone (Wave 1) — the two auto-accept settings cards are set-once config, not inbox content;
   folded on a phone so the queue starts at the top. (Toggle them from a desk.) */
@media (max-width: 767.98px) {
  html:not(.crm-force-desktop) .crm-inbox-settings { display: none !important; }
}

/* CALL OUTCOME SHEET (admin-phone.js wireCallOutcome) — after a call on a phone: outcome + next step, one
   tap each, logs the note + sets the follow-up. Bottom sheet, thumb-reachable, app dark accent. */
.crm-callsheet { display: none; }
@media (max-width: 767.98px) {
  .crm-callsheet {
    display: flex; flex-direction: column; gap: .5rem;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1057;
    background: var(--tblr-bg-surface, #fff); color: var(--tblr-body-color, #1a2433);
    border-radius: 16px 16px 0 0; padding: .5rem 1rem calc(env(safe-area-inset-bottom, 0px) + 1rem);
    box-shadow: 0 -10px 32px rgba(0,0,0,.24); animation: adm-sheet-up .2s ease-out;
  }
  .crm-callsheet .cs-grab { width: 40px; height: 4px; border-radius: 2px; background: var(--tblr-border-color, #d7dbe0); margin: .1rem auto .4rem; }
  .crm-callsheet .cs-h { font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--tblr-secondary, #667382); }
  .crm-callsheet .cs-row { display: flex; gap: .5rem; }
  .crm-callsheet .cs-row button {
    flex: 1; min-height: 46px; border-radius: 10px; border: 1px solid var(--tblr-border-color, #e6e7e9);
    background: var(--tblr-bg-surface-secondary, #f6f8fb); color: var(--tblr-body-color, #1a2433);
    font-size: .82rem; font-weight: 650; padding: 0 .3rem;
  }
  .crm-callsheet .cs-row button.on { background: var(--tblr-primary, #206bc4); color: #fff; border-color: transparent; }
  .crm-callsheet .cs-save { min-height: 48px; border-radius: 11px; border: 0; background: var(--tblr-primary, #206bc4);
    color: #fff; font-size: .95rem; font-weight: 700; margin-top: .3rem; display: flex; align-items: center; justify-content: center; }
  .crm-callsheet .cs-save:disabled { opacity: .5; }
  .crm-callsheet .cs-cancel { min-height: 40px; border: 0; background: transparent; color: var(--tblr-secondary, #667382); font-size: .85rem; font-weight: 600; }
}

/* ── SIMPLE-MODE PAGE EDITOR: one continuous surface (Graham 2026-08-12) ──────────────────────────────
   In Simple interface mode the block cards drop their chrome and stack as a single scrolling document —
   no per-block card box, type badge, name field or button row; just the content, with formatting coming
   from the editor's own toolbar. Advanced mode keeps the full block workspace untouched. */
.block-editor.cms-simple .block-list > .card { border: 0; box-shadow: none; margin: 0; border-radius: 0; background: transparent; }
.block-editor.cms-simple .block-list > .card > .card-header { display: none !important; }
.block-editor.cms-simple .block-list > .card > .card-body { padding: .15rem .1rem !important; }
/* Design pass (2026-08-22): a 1px dashed hairline with a .3rem margin read, in practice, as no separation
   at all — several stacked blocks looked like one undifferentiated run of text with no visible boundary
   (Graham's own "no per-block card box" intent still stands; the line marking where one block ends was
   just too faint to do its one job). A solid line with real breathing room around it, without restoring
   the badge/header/button row this mode deliberately drops. */
.block-editor.cms-simple .block-list > .card + .card { border-top: 1px solid var(--tblr-border-color, #dde1e6); margin-top: .9rem; padding-top: .9rem; }
/* RichText/HTML sub-toolbar extras (Visual/HTML tabs, Preview, Template tools, AI) hide — TinyMCE keeps
   its own formatting toolbar, which is all Simple mode needs. */
.block-editor.cms-simple .block-body > .d-flex:has(.rt-views),
.block-editor.cms-simple .block-body > .d-flex:has(.block-preview) { display: none !important; }
/* ONE editor, not one-per-section: drop the TinyMCE box border/shadow so the text areas run together, and
   show only the toolbar of the editor you're currently in (marked .cms-ed-active by JS on focus) — floating
   sticky at the top. The other editors' toolbars stay hidden, so three stacked editors read as one document
   with a single formatting bar (Graham 2026-08-12). */
/* Simple mode chrome consolidation (Graham 2026-08-13: "put everything on one line, then the editor"):
   drop the auto-generated PAGE HEADER band and the whole "Content Blocks" tools bar, so the ONLY thing above
   the content is the one-line page header (title + slug + save/publish/gear). Adding blocks still works via the
   always-present "Type to add a block" bar at the foot; TinyMCE's own (single, sticky) toolbar handles
   formatting. With the tools bar gone the sticky toolbar docks at the very top — nothing above it to overlap. */
.block-editor.cms-simple .cms-pageheader-row { display: none !important; }
.block-editor.cms-simple > .card-header { display: none !important; }
/* One-line header in ADVANCED too (Graham 2026-08-23: "the whole header is one line"): the default-language
   pane's Content Blocks bar is folded away — its tools (Collapse all, pattern, AI, Undo/Redo/History) moved
   into the page-header row, which click-proxies back to the hidden originals here so no JS binding moves.
   Razor adds the class only on the default pane in Advanced; translation panes keep their bar (copy/translate). */
.block-editor > .card-header.cms-adv-fold { display: none !important; }
/* Simple-mode editor: collapse the whole TOP section (the global header bar — search + Deploy/Sync + account)
   so the editor gets full HEIGHT; a small centred arrow at the very top toggles it back (Graham 2026-08-13:
   "we were wanting the top section to collapse on this screen"). Desktop only — the phone top chrome differs. */
/* Everything above the editor goes too (Graham 2026-08-13: "hide everything except the fixed editor"): the
   breadcrumb and the site-warnings strip (the "Email test mode is ON" chip). */
body.cms-topzen ol.breadcrumb,
body.cms-topzen #admWarnStrip { display: none !important; }
/* Simple mode: lock the page-header row (title + slug + Save/Publish/Delete) to the top as you scroll, so
   it's always reachable on a long page (Graham 2026-08-13: "lock this at the top of every page — fixed to
   the top"). The editor's own sticky toolbar is offset (in JS) by exactly this header's height, so the two
   stack rather than overlap. cms-simple-editor is the stable marker (present whether or not the top bar is
   toggled open). */
body.cms-simple-editor .page-header {
    position: sticky; top: 0; z-index: 1035; margin-bottom: .5rem !important;
    background: var(--tblr-body-bg, #f6f7fb); box-shadow: 0 1px 0 rgba(0,0,0,.06);
    padding-top: .35rem; padding-bottom: .35rem;
}
@media (min-width: 992px) {
    body.cms-topzen header.navbar.navbar-expand-md { display: none !important; }
    body.cms-topzen .page-wrapper > .page-body { padding-top: .25rem !important; }
    /* One-line header (2026-08-23): with the block-tools relocated into the page-header row, keep title +
       actions on a single line on desktop — the title column shrinks (it is min-width:0) instead of the
       action column wrapping underneath. Below lg the row wraps as before. */
    body.cms-simple-editor .page-header > .row { flex-wrap: nowrap; }
    body.cms-simple-editor .page-header .btn-list { flex-wrap: nowrap; align-items: center; }
}
/* Red "Delete Page" pill beside the floating Save (Simple mode only, injected on the editor page). Its
   horizontal position is set by JS so it sits just left of the Save pill whatever that pill's width. */
.adm-floating-delete { position: fixed; bottom: 1.25rem; right: 9rem; z-index: 1030; display: inline-flex;
    align-items: center; gap: .4rem; padding: .7rem 1.1rem; border: 0; border-radius: 999px;
    background: var(--adm-danger, #d63939); color: #fff; font-weight: 600; font-size: .95rem;
    box-shadow: 0 6px 20px rgba(16,24,40,.25); cursor: pointer;
    transition: transform .06s ease, filter .12s ease, box-shadow .12s ease; }
.adm-floating-delete:hover { filter: brightness(1.05); box-shadow: 0 8px 26px rgba(16,24,40,.3); }
/* Inbox thread: daisy-chain (indented) vs flat (Graham 2026-08-24). Flat mode zeroes the per-message indent;
   the toggle's active button is driven purely by the <html>.crm-thread-flat class, so it stays correct across
   the reading pane's AJAX swaps without any JS re-wiring. */
html.crm-thread-flat .crm-thread-item { margin-left: 0 !important; }
html:not(.crm-thread-flat) .crm-thread-toggle [data-thread-mode="chain"],
html.crm-thread-flat .crm-thread-toggle [data-thread-mode="flat"] {
    background: var(--tblr-primary, #206bc4); border-color: var(--tblr-primary, #206bc4); color: #fff;
}
/* Inbox COLUMN CHOOSER (Graham 2026-08-24). Each html.crm-col-hide-* class hides one list column — its
   caption in the header row AND the matching cell in every row. Name has no hide rule: it's always shown.
   Status hides only the ROW stage dots, never the footer legend's swatches (a different context). The
   trailing read/status/date cells share one .crm-inbox-trail ms-auto wrapper, so hiding any of them leaves
   the rest right-aligned. */
html.crm-col-hide-subj .crm-inbox-cols-subj,
html.crm-col-hide-subj .crm-inbox-compact-subj { display: none !important; }
html.crm-col-hide-date .crm-inbox-cols-date,
html.crm-col-hide-date .crm-inbox-when { display: none !important; }
html.crm-col-hide-status .crm-inbox-row-top .crm-stage-dot { display: none !important; }
/* Inbox: resizable split (Graham 2026-08-24). Desktop turns the two Bootstrap columns into a flex row with a
   draggable divider — the list takes --inbox-left of the width (server sets it wide before an item is opened,
   narrow after; JS drag + localStorage override it), the reading pane takes the rest. Below lg the panes stack
   as normal columns and the divider hides. */
@media (min-width: 992px) {
    .crm-inbox-panes { flex-wrap: nowrap; align-items: flex-start; }
    .crm-inbox-panes > .crm-inbox-listcol { flex: 0 0 var(--inbox-left, 40%); max-width: none; width: auto; }
    .crm-inbox-panes > .crm-inbox-readcol { flex: 1 1 0%; max-width: none; width: auto; min-width: 0; }
    .crm-inbox-split { flex: 0 0 12px; align-self: stretch; cursor: col-resize; position: relative; margin: 0 -4px; padding: 0; }
    .crm-inbox-split::before { content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 2px;
        transform: translateX(-50%); background: var(--tblr-border-color, #e6e7e9); border-radius: 2px; }
    .crm-inbox-split:hover::before, body.crm-inbox-resizing .crm-inbox-split::before {
        background: var(--tblr-primary, #206bc4); width: 3px; }
    body.crm-inbox-resizing { cursor: col-resize; user-select: none; }
}
/* Inbox list: Subject caption + a draggable Name/Subject boundary (Graham 2026-08-24). Name is a fixed,
   resizable column (--inbox-col-name); Subject fills the rest; the lead spacer matches the row's glyph +
   direction cells so the captions line up over their columns. */
.crm-inbox-cols-lead { flex: 0 0 2.75rem; }
.crm-inbox-cols-name { flex: 0 0 var(--inbox-col-name, 8rem); }
.crm-inbox-cols-resize { flex: 0 0 9px; align-self: stretch; cursor: col-resize; position: relative; }
.crm-inbox-cols-resize::before { content: ""; position: absolute; top: 1px; bottom: 1px; left: 50%; width: 2px;
    transform: translateX(-50%); background: var(--tblr-border-color, #e6e7e9); }
.crm-inbox-cols-resize:hover::before, body.crm-cols-resizing .crm-inbox-cols-resize::before { background: var(--tblr-primary, #206bc4); }
.crm-inbox-cols-subj { flex: 1 1 0; min-width: 0; }
.crm-inbox-row-name { flex: 0 0 var(--inbox-col-name, 8rem) !important; }
.crm-inbox-compact-subj { flex: 1 1 0; min-width: 0; }
body.crm-cols-resizing { cursor: col-resize; user-select: none; }
/* Filtered to one contact: the Name column becomes a FROM column — "You" for your sends, the contact for
   their replies (Graham 2026-08-24: "this is all the same name" then "we are missing from"). Narrower than a
   full name, since "You" is short. */
.crm-inbox-onecontact { --inbox-col-name: 5rem; }
.adm-floating-delete:active { transform: translateY(1px); }
.adm-floating-delete .ti { font-size: 1.1rem; }
@media print { .adm-floating-delete { display: none; } }
.cms-zen-toggle { position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 1055;
    width: 46px; height: 20px; display: flex; align-items: center; justify-content: center; padding: 0;
    cursor: pointer; color: #868e96; background: #fff; border: 1px solid var(--tblr-border-color, #e6e7e9);
    border-top: 0; border-radius: 0 0 8px 8px; box-shadow: 0 1px 3px rgba(0,0,0,.12); }
.cms-zen-toggle:hover { background: #f1f3f5; color: #495057; }
@media (max-width: 991.98px) { .cms-zen-toggle { display: none; } }
.block-editor.cms-simple .tox.tox-tinymce { border: 0 !important; box-shadow: none !important; }
/* Design pass (2026-08-22), idea #8 - REVERTED: capping .tox-toolbar__primary's width was meant to force
   TinyMCE's sliding toolbar to overflow into its "..." chevron instead of showing every button in one wide
   row. In practice (confirmed by the user on a real screen, which this environment's non-composited browser
   tab couldn't verify) it wrapped into FOUR rows instead of collapsing - worse than the original single
   dense row, plus a stray horizontal scrollbar. toolbar_mode:'sliding' apparently doesn't treat a
   CSS-imposed max-width the same as a genuinely narrow container. Left as a note rather than retried blind:
   a real fix needs measuring what TinyMCE actually keys off (likely the .tox-tinymce root's own width, not
   the inner toolbar row) and verifying visually before landing again. */
/* idea #9: a visible divider between each formatting group instead of whitespace alone — a wall of ~8
   icons reads as four short groups, not one undifferentiated row. */
.block-editor .tox .tox-toolbar__group:not(:last-child) { border-right: 1px solid var(--tblr-border-color, #e6e7e9); margin-right: 4px; padding-right: 4px; }
/* ADVANCED-EDITOR-LAYOUT item 5 (2026-08-22) - REPLACES the old idea #12 tint rule above, which targeted
   .block-mode-row (then named generically) sitting inside .block-body; the JS now moves that same element
   into .card-header instead (see makeBlock in admin-page-editor.js), so styling has to key off its new
   home. "When a block is expanded its excerpt is redundant — you can see the content" - the mode row
   (Visual/HTML/Preview/Clean up imported content/AI) takes the excerpt's place in the SAME header row,
   collapsing three strips (header, mode row, toolbar) to two (header-with-mode-row, toolbar). Both halves
   toggle off .blk-collapsed - no JS re-parenting needed on every expand/collapse, only visibility. */
.card-header .block-mode-row { display: none !important; margin: 0 !important; flex-wrap: nowrap; gap: 6px !important; }
.block-list > .card:not(.blk-collapsed) .card-header .block-mode-row { display: flex !important; }
.block-list > .card:not(.blk-collapsed) .card-header .block-excerpt { display: none !important; }
/* The AI dropdown's menu still opens flush with its own toggle (dropdown-menu-end) regardless of where the
   toggle itself sits now - only its OWN ms-auto (a "push me to the row's far right" rule meant for when
   this was a standalone strip) needed to go; that's a template change (admin-page-editor.js), not CSS. */
/* Only the section you're editing shows its toolbar, and it sits at the top of THAT section. It used to pin
   to the top of the viewport (TinyMCE's toolbar_sticky), which docked it over the admin header and the
   collapsible settings strip — the buttons ended up hidden behind the chrome they floated over. */
.block-editor.cms-simple .tox .tox-editor-header { display: none !important; }
.block-editor.cms-simple .cms-ed-active .tox-editor-header { display: block !important; }
/* The editor's bottom status bar (element path "p", "Press Alt+0 for help", word count, resize grip) is
   developer chrome — hide it in Simple mode so a section ends cleanly at its text (Graham 2026-08-13). */
.block-editor.cms-simple .tox .tox-statusbar { display: none !important; }

/* No underlined links anywhere in the admin (Graham 2026-08-16) — colour carries the cue.
   Sits at the end of the file so it wins ties; deliberate underlines (rung-word hover,
   crm-word-decide) live on spans/buttons and are unaffected. */
.page a, .crmapp a { text-decoration: none; }

/* One green on the slab (Graham 2026-08-16: "Email and Text and Call as green button") — the channel
   buttons and Won share the crm-slab-go verb's green rather than Tabler's brighter success, so the
   row reads as one family, not two shades arguing. */
.crm-slab .btn-success { background: var(--adm-status-published-fg); border-color: var(--adm-status-published-fg); }
.crm-slab .btn-success:hover { background: var(--adm-status-published-fg); border-color: var(--adm-status-published-fg); filter: brightness(1.08); }

/* THE SLAB'S EXITS ARE SOLID (Graham 2026-08-19: "Buttons need to be solid. Red, Green background,
   white text"). Stated outright rather than inherited: on the slab's dark ground an outline button
   reads as disabled, and Tabler's own fills wash out against it. Won/Approve green, Lost red, white
   lettering on both — and the icons inherit that white with them. */
.crm-slab .btn.crm-exit.btn-success,
.crm-slab .btn.crm-exit.btn-success:hover {
    background: var(--adm-status-published-fg); border-color: var(--adm-status-published-fg); color: #fff; }
.crm-slab .btn.crm-exit.btn-danger,
.crm-slab .btn.crm-exit.btn-danger:hover {
    background: var(--adm-status-expired-fg); border-color: var(--adm-status-expired-fg); color: #fff; }
.crm-slab .btn.crm-exit:hover { filter: brightness(1.08); }
.crm-slab .btn.crm-exit .ti { color: inherit; }

/* Board search results as a floating panel (Graham 2026-08-16: "This does not line up nicely") — the
   count line and the found-elsewhere list used to render inline in the toolbar, stretching the search
   column so every neighbouring control re-centred around the extra height. They float over the board
   now, type-ahead style; the panel hides itself when both children are hidden. */
.crm-find-wrap { position: relative; }
.crm-find-drop { position: absolute; top: calc(100% + 4px); left: 0; width: min(26rem, 82vw); z-index: 1040;
                 background: #fff; border: 1px solid #e6e7e9; border-radius: 8px; padding: 8px 10px;
                 box-shadow: 0 6px 20px rgba(30, 41, 59, .10); }
.crm-find-drop:has(> #crmFindCount[hidden]):has(> #crmFindElsewhere[hidden]) { display: none; }

/* ── Mobile audit 2 (2026-08-17, after the v483-v504 desktop wave) ────────────────────────────────── */
@media (max-width: 767px) {
    /* #154: the Workflow clock's number DROPDOWNS (v490) squeezed to 40px on a phone — the form-select
       chevron padding alone is 36px, so "30" rendered as a clipped sliver. Give each a real floor and
       let the clock row wrap; the row was already allowed to wrap for the old number inputs. */
    /* !important because the phone backstop at ".form-control, .form-select { min-width: 0 !important }"
       otherwise flattens these — that rule exists to stop desk min-widths dragging pages sideways, and
       these floors are deliberate, later in the file, and 4.6rem wide on purpose (36px of that is the
       select's own chevron padding). */
    .crm-step-clock select { min-width: 4.6rem !important; flex: 0 0 auto; }
    /* #155: the step caret (v491) is appended AFTER the clock, so when the header wraps on a phone it
       landed mid-row on the left. Anchor it to the card's top-right corner at every wrap state. */
    .wf-step > .card-header { position: relative; padding-right: 2.2rem; }
    .wf-step > .card-header .wf-caret { position: absolute; top: .9rem; right: .8rem; margin: 0 !important; }
}
@media (max-width: 575px) {
    /* #153: the Notifications bulk bar stacked 106px tall at 375. One scrolling row instead — the same
       pattern the Inbox chips use on a phone: every action keeps its full label, the bar keeps one
       calm height, and the thumb swipes for the rest. */
    #noteBulk { flex-wrap: nowrap !important; overflow-x: auto; gap: .4rem !important;
                padding-top: .45rem !important; padding-bottom: .45rem !important; }
    #noteBulk > * { flex: 0 0 auto; }
    #noteBulk .btn { padding-left: .55rem; padding-right: .55rem; white-space: nowrap; }
}

/* Board action row, DESK (Graham 2026-08-17: "This header is not lined up. Do this order…"). The order
   lives in the DOM — Add lead renders a second, desk-only copy inside the row at position 2, because
   Chromium refuses to interleave a <details>' children with a sibling by flex order under
   display:contents. This rule only sets Settings off with its own gap at the row's end. */
@media (min-width: 768px) {
    .crm-actrow .adm-toolbar-items > :last-child { margin-left: .9rem; }
}

/* HEADER STATS as gallery-#5 TILES (Graham 2026-08-18, third pass: "change the existing section to
   have these background colours (make sure each one is the same size). Make it right justified to
   the far right back button"). Rounded soft-grey cards, radius 10 — the Due tile's urgency TINTS its
   background (amber tint due-soon, red tint late) with the label+figure coloured to match, per the
   gallery's .tile.warn. Every tile the SAME fixed size; the group hugs the Back button on the right.
   Text hierarchy unchanged from the #3 pass. Lives at the END of the file so the base .crm-tile
   rules can't out-cascade it. */
#hdrStats { margin-left: auto; }
.crm-tiles--hdr { display: flex; gap: 10px; align-items: stretch; border-top: 0; padding-top: 0; }
.crm-tiles--hdr .crm-tile { display: block; background: var(--adm-status-muted-bg, #f5f7fa); border: 0;
    box-shadow: none; min-height: 0; width: 9rem; flex: 0 0 9rem; padding: 8px 13px; text-align: left;
    border-radius: 10px; white-space: nowrap; overflow: hidden; }
.crm-tiles--hdr .crm-tile-l { display: block; font-size: 10px; font-weight: 800; letter-spacing: .07em;
    text-transform: uppercase; color: var(--tblr-secondary, #97a3b3); }
.crm-tiles--hdr .crm-tile-n { display: block; font-size: 17px; font-weight: 800; line-height: 1.15;
    color: var(--tblr-body-color, #1d273b); }
.crm-tiles--hdr .crm-tile-s { display: inline; font-size: 11.5px; font-weight: 600;
    color: var(--tblr-secondary, #667382); }
.crm-tiles--hdr .crm-tile-s + .crm-tile-s::before { content: "· "; }
/* URGENCY IN THE ONE SHARED LANGUAGE (audit 2026-08-19 #20): the header tiles spoke their own
   Tabler-red/amber/green while every chip, calendar and follow-up row speaks the -expired /
   -pending / -published tokens. Same tokens now — one date reads one colour wherever it appears. */
.crm-tiles--hdr .crm-tile--late { background: var(--adm-status-expired-bg); }
.crm-tiles--hdr .crm-tile--due { background: var(--adm-status-pending-bg); }
.crm-tiles--hdr .crm-tile--late .crm-tile-l, .crm-tiles--hdr .crm-tile--late .crm-tile-n { color: var(--adm-status-expired-fg); }
.crm-tiles--hdr .crm-tile--due .crm-tile-l, .crm-tiles--hdr .crm-tile--due .crm-tile-n { color: var(--adm-status-pending-fg); }
.crm-tiles--hdr .crm-tile--ok .crm-tile-l { color: var(--adm-status-published-fg); }
.crm-tiles--hdr .crm-tile-btn:hover .crm-tile-n { text-decoration: underline; }
/* THE MONEY TILES ARE GREEN (Graham 2026-08-19: "Can you make these green") — the same published
   green every other good-news surface speaks, so money reads as money at a glance beside the
   grey stage/due tiles. They also carry a SECOND line (the monthly/one-time split), so they size
   to their content instead of the fixed 9rem the other tiles share — 9rem clipped it. */
.crm-tiles--hdr .crm-tile--money { background: var(--adm-status-published-bg); width: auto;
    flex: 0 1 auto; min-width: 9rem; }
.crm-tiles--hdr .crm-tile--money .crm-tile-n { color: var(--adm-status-published-fg); }
.crm-tiles--hdr .crm-tile--money .crm-tile-l { color: var(--adm-status-published-fg); opacity: .75; }
/* The split sits on its own row under the figure. Direct children only: the "/mo" inside the
   figure is a nested .crm-tile-s and must stay inline (it carries d-inline anyway). */
.crm-tiles--hdr .crm-tile--money > .crm-tile-s { display: block; font-weight: 600;
    color: var(--adm-status-published-fg); opacity: .8; }
.crm-tiles--hdr .crm-tile--money > .crm-tile-s + .crm-tile-s::before { content: none; }
@media (max-width: 767.98px) {
    .crm-tiles--hdr { flex-wrap: wrap; }
    .crm-tiles--hdr .crm-tile { width: auto; flex: 1 1 auto; }
}
/* Package picker rows as a GRID on a desk (Graham 2026-08-18: "The text boxes need to line up") — flex
   let each label's own width stagger the qty / price / note boxes row by row. Fixed and fr tracks ONLY
   (an auto track absorbs a spanning item's intrinsic width — the contract-grid lesson). Unticked rows
   keep their empty columns, so the ticked ones align across the whole service. Phone keeps the wrap. */
@media (min-width: 768px) {
    /* The label column has a FLOOR and the note column's max is a FR, not a length: a fixed-length
       max (24rem) is claimed in the maximize-tracks step BEFORE any 1fr is paid, so inside the #30
       split the empty note track ate the row and the label resolved to 0px — every tier name went
       vertical, one letter per line (Graham's screenshot). fr-vs-fr shares proportionally instead. */
    /* Note column is just the ＋ button now (the note itself lives in a popup) — a slim fixed track. */
    .crm-stage-row { display: grid !important; grid-template-columns: minmax(15rem, 1fr) 7.6rem 6.2rem 2.6rem;
        gap: .25rem .5rem; align-items: center; }
    .crm-stage-row .crm-stage-price { width: auto !important; }
    .crm-stage-row .crm-note-btn { padding: 1px 7px; }
    .crm-stage-row .crm-note-btn.has-note { color: var(--tblr-primary, #206bc4); border-color: var(--tblr-primary, #206bc4); }
    /* One line per tier, CLIPPED — nowrap alone let a long label run under the stepper (Graham's
       screenshot); the cell hides its overflow with an ellipsis instead. min-width 0 lets the grid
       cell actually shrink to its track. */
    .crm-stage-row .form-check { min-width: 0; }
    .crm-stage-row .form-check-label { white-space: nowrap; display: block;
        overflow: hidden; text-overflow: ellipsis; }
}
/* Gallery #30 "Everything inline" (Graham: "Do #30") — catalogue left, LIVE CART right, and the qty
   is a ± stepper. The cart panel sticks while the catalogue scrolls; below lg the panel drops under
   the picker (the columns stack). */
#pkgSplit { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(15rem, 1fr); gap: 14px; align-items: start; }
@media (max-width: 991.98px) { #pkgSplit { grid-template-columns: 1fr; } }
.crm-cart-side { background: var(--tblr-bg-surface-secondary, #f8fafc); border: 1px solid var(--tblr-border-color, #e6e7e9);
    border-radius: 10px; padding: 12px 14px; position: sticky; top: 1rem; }
.crm-cart-line { display: flex; justify-content: space-between; gap: 10px; font-size: 13px; padding: 2px 0; }
.crm-cart-total { display: flex; justify-content: space-between; gap: 10px; border-top: 1px solid var(--tblr-border-color, #e6e7e9);
    margin-top: 8px; padding-top: 8px; font-weight: 800; }
.crm-cart-cut { color: var(--adm-status-published-fg, #2fa24d); }
.crm-cart-disc { color: var(--tblr-primary, #206bc4); }
/* Cadence group headings inside the cart (Graham 2026-08-18: "Monthly Header … One Time title") — and
   the second total drops the divider so Monthly / One Time read as one block in one style. */
.crm-cart-h { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
    color: var(--tblr-secondary, #97a3b3); margin: 8px 0 2px; }
.crm-cart-h:first-child { margin-top: 0; }
.crm-cart-total + .crm-cart-total { border-top: 0; margin-top: 0; padding-top: 0; }
/* No native up/down arrows on the picker's number boxes (Graham 2026-08-18) — the ± stepper owns the
   qty and the price/spend get typed; the browser spinner was a second, tinier control saying the same. */
.crm-stage-row input[type=number]::-webkit-outer-spin-button,
.crm-stage-row input[type=number]::-webkit-inner-spin-button,
#crmBayPpcSpend::-webkit-outer-spin-button,
#crmBayPpcSpend::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.crm-stage-row input[type=number], #crmBayPpcSpend { -moz-appearance: textfield; appearance: textfield; }
.crm-qty-step { display: inline-flex; align-items: center; gap: 2px; }
.crm-qty-step .btn { padding: 1px 8px; line-height: 1.5; }
.crm-qty-step .crm-stage-qty { width: 2.9rem !important; text-align: center; }
/* (The navy header band lived here for twenty minutes on 2026-08-18 — Graham saw it rendered and
   chose the light header back. If a dark band returns, remember it needs the whole palette: identity
   text, tiles, urgency tints, pin, copy buttons.) */

/* The pinned note as a quiet identity line (re-homed 2026-08-18 from the name-row chip): a button so it
   opens the tile popup, dressed as the click-to-act rows above it. Unlike those rows it WRAPS inside a
   capped width (Graham: "Can we have max width with a wrap?") — a 300-char note was one long line that
   stretched the identity block across the whole band and shoved the stats off the row. */
button.crm-who-pin { background: none; border: 0; padding: 0; font: inherit; font-size: 12.5px;
    color: var(--tblr-secondary, #667382); cursor: pointer; text-align: left;
    /* FIXED cap, no percentage: min(30rem, 100%) dies in the flex intrinsic-sizing pass (percentages
       resolve to auto there, the whole min() is dropped) and the unwrapped 300-char note then set the
       header item's preferred width — identity block 1036px wide, stats shoved off the band. A plain
       length survives the pass; narrower containers still force earlier wrapping on their own. */
    max-width: 30rem; white-space: normal; overflow-wrap: anywhere; line-height: 1.45; }
button.crm-who-pin:hover { color: var(--tblr-body-color, #1d273b); }

/* PHONE CART STRIP (audit 2026-08-19 #13). Below tablet width the cart panel stacks under the whole
   catalogue, so a tick gave zero feedback at the thumb. The strip sticks above the tab bar while the
   picker scrolls (same recipe as the Quote tab's sticky draft row), repaints from every cart sync,
   and a tap lands on the real cart. Desktop never sees it. */
#pkgCartStrip { display: none; border: 0; background: transparent; }
@media (max-width: 767.98px) {
  #pkgCartStrip { position: sticky; bottom: calc(env(safe-area-inset-bottom, 0px) + 62px); z-index: 5;
      display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
      margin-top: 10px; padding: 10px 14px; border: 1px solid var(--tblr-border-color, #e6e7e9);
      border-radius: 10px; background: var(--tblr-bg-surface, #fff); text-align: left;
      box-shadow: 0 -6px 12px -8px rgba(15, 23, 42, .35); font-size: 14px; font-weight: 600; }
  #pkgCartStrip .adm-money { font-weight: 700; }
  /* Sticky is inert in the stacked one-column grid — stop pretending. */
  .crm-cart-side { position: static; }
  /* TAP-WIDTH FLOOR for the text-glyph buttons (audit #14): the 40px width floor only covers
     .btn-icon and :has(> i:only-child), and −/＋/✎ are bare TEXT glyphs — they rendered as
     ~28px slivers 2px apart. Same floor, named explicitly. */
  .crm-qty-step { gap: 6px; }
  .crm-qty-step .btn, .crm-note-btn { min-width: 40px; }
}

/* nav-sm IS REAL NOW (audit 2026-08-19 #22): the composer's mode pills wore a class Tabler never
   shipped, so they rendered full-size against the folder tabs. The class stays in the markup; this
   is the rule it always believed it had. */
.nav-pills.nav-sm .nav-link { padding: .25rem .6rem; font-size: .8125rem; }

/* THE HEADER'S ACTION ROW, LEFT-JUSTIFIED (Graham 2026-08-19: "Back, Go to Active Stage, Make this
   Active (left justified)"). Its own full-width line under the identity + tiles, so the three sit
   together at the left instead of being flung apart by the header's space-between — which is what
   happened while they were three separate flex children. */
.crm-hdr-actions { width: 100%; justify-content: flex-start; }

/* ONE ROUNDING FOR THE DEAL PAGE'S BUTTONS (Graham 2026-08-19: "Can the buttons be rounded"). The
   header's action row and the slab's exits share the same 8px, so Back and the verbs beside it read
   as one family rather than two eras of button. */
.crm-hdr-actions .btn { border-radius: 8px; }

/* THE STEP A MESSAGE WAS SENT FROM (Graham 2026-08-19). Quiet by design — it is context for the row,
   not a second headline: the direction chip still carries the colour, this just says where the deal
   stood at the time. Derived from the stage trail, so it appears on old history too. */
.crm-mail-stage { flex: 0 0 auto; font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; color: var(--adm-text-muted); background: var(--adm-status-muted-bg);
    border-radius: 999px; padding: 1px 7px; white-space: nowrap; }

/* THE BOARD AVATAR SPEAKS STATE (Graham 2026-08-19: "can the circles colours match their current state —
   Green no contact needed, orange contact soon, red missed contact"). It was a per-name hue: decorative,
   and silent about the one thing the column is for. Same three tokens the chips, tiles and calendar use,
   so a colour means the same thing wherever it appears. Neutral for lost/live clients, which are windows
   counting down rather than work going undone. */
.crm-av-rag.av--red   { background: var(--adm-status-expired-bg);   color: var(--adm-status-expired-fg); }
.crm-av-rag.av--amber { background: var(--adm-status-pending-bg);   color: var(--adm-status-pending-fg); }
.crm-av-rag.av--green { background: var(--adm-status-published-bg); color: var(--adm-status-published-fg); }
.crm-av-rag.av--none  { background: var(--adm-status-muted-bg);     color: var(--adm-status-muted-fg); }

/* THE STATED LINES on a ledger row. A <details> inside a table cell: per the two rules, nothing sets
   `display` on the collapsing child (the table is left alone) and the summary carries the whole affordance,
   so opening one row never reflows the columns beside it. */
.crm-stmt-lines > summary { cursor: pointer; list-style: revert; }
.crm-stmt-lines > table { max-width: 34rem; }

/* THE SHARED COMPOSER'S CONVERSATION FOLD (Graham 2026-08-19). Direction is a left edge and one word, the
   same in/out language the deal page's history speaks — a reply written without the last thing they said
   in front of you answers a question they did not ask. */
.crm-cmp-thread > summary { cursor: pointer; }
.crm-cmp-thread-body { max-height: 15rem; overflow-y: auto; }
.crm-cmp-msg { border-left: 3px solid var(--adm-border); padding: 4px 0 4px 9px; margin-bottom: 7px; }
.crm-cmp-msg--in  { border-left-color: var(--adm-status-published-fg); }
.crm-cmp-msg--out { border-left-color: var(--adm-link); }
.crm-cmp-msg-h { font-size: 11px; font-weight: 600; color: var(--adm-text-muted); }
.crm-cmp-msg-b { font-size: 12.5px; white-space: pre-wrap; }

/* ── NOTIFICATIONS: HOW LATE, AND WHAT YOU'D DO (designs 10 and 9, Graham 2026-08-20) ──────────────────
   A left edge rather than a tinted row: fifteen tinted rows is a stripy mess, whereas fifteen edges read
   as one column you can scan down. Applied ONLY to follow-ups, where Item.When genuinely means "due" —
   an order's When is when it happened, and colouring that would be a lie about work going undone. */
.note-item.note-late   { box-shadow: inset 3px 0 0 var(--adm-status-expired-fg); }
.note-item.note-today  { box-shadow: inset 3px 0 0 var(--adm-status-pending-fg); }
.note-item.note-ahead  { box-shadow: inset 3px 0 0 var(--adm-status-published-fg); }
/* The number, because a hue cannot say "21 days" and that is the reason the row matters. */
.note-lateflag  { background: var(--adm-status-expired-bg); color: var(--adm-status-expired-fg); font-weight: 700; }
.note-todayflag { background: var(--adm-status-pending-bg); color: var(--adm-status-pending-fg); font-weight: 700; }
/* The verb stays quiet until the row is under the cursor — present on every row, shouting on none. */
.note-verb { opacity: .55; transition: opacity .12s ease; }
.note-row:hover .note-verb, .note-verb:focus-visible { opacity: 1; }
@media (hover: none) { .note-verb { opacity: 1; } }

/* NOTIFICATION LANES (design 1, Graham 2026-08-20). A heading, not a card: the rows below it are already
   bordered, and boxing a group inside a list of boxes just adds a frame nobody asked for. Sticky so the
   lane you are working stays named while you scroll past fifteen rows of it. */
.note-lane {
    display: flex; align-items: baseline; gap: .5rem;
    padding: .55rem .85rem .35rem; background: var(--adm-surface, #fff);
    position: sticky; top: 0; z-index: 2; border-bottom: 1px solid var(--adm-border);
}
.note-lane-h { font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .09em; }
.note-lane[data-lane="0"] .note-lane-h { color: var(--adm-status-expired-fg); }
.note-lane[data-lane="1"] .note-lane-h { color: var(--adm-text-muted); }
.note-lane-hint { font-size: .75rem; color: var(--adm-text-muted); }
.note-lane-n { margin-left: auto; font-size: .75rem; font-weight: 700; color: var(--adm-text-muted);
               font-variant-numeric: tabular-nums; }

/* ── A CARD CONTAINS ITS OWN WIDE TABLE ─────────────────────────────────────────────────────────────
   Surface sweep, 2026-08-20. Measured on the Monthly report at phone width: viewport 359px, document
   461px (286px on the internal view) — the whole page slid sideways, headings and tiles with it, while
   every other CRM page contained its own wide content correctly.

   The table's .table-responsive scroller was already working. The overflow escaped one level up: the
   .card reported a scrollWidth of 636px against a 343px box.

   SIX CANDIDATES WERE TESTED IN THE LIVE PAGE before this one was written, because the obvious
   diagnosis was wrong. min-width:0 on the card does nothing here — Tabler already sets it. Nor did
   min-width:0 on .table-responsive, .card-body, or the grid children, nor max-width on the table.
   Only clipping the card's horizontal overflow worked.

   Scoped with :has() to cards that actually contain a scroller, so a card with a dropdown or popover
   that needs to escape is untouched. */
.card:has(> .table-responsive) { overflow-x: hidden !important; }
/* !important because a later Tabler rule sets overflow on .card and won the cascade: the selector
   matched and :has() was supported (both verified in the page), yet the computed overflow-x stayed
   "visible". A scoped, single-purpose rule is the right place to spend an !important. */

/* ── THE SIDEBAR'S ID CARD MUST SHRINK ──────────────────────────────────────────────────────────────
   Surface sweep, 2026-08-20 — and a correction to my own finding. I reported this as "the Monthly
   report scrolls sideways on a phone". It is not the report: measured at 375px, the offending elements
   are .navbar-brand and the .adm-idcard inside it, both 753px wide, in a sidebar that is correctly 375.
   The report's tables were contained the whole time.

   .adm-idrow already carries min-width:0 and .adm-idval already truncates — but truncation only happens
   if an ancestor is actually constrained, and neither the brand nor the card was. So the site name and
   the signed-in address set the width, the shell grew to fit them, and every admin page inherited the
   sideways scroll at phone width.

   Written with plain selectors on purpose: an earlier :has() rule appended here reached the served
   bundle but never applied, which is its own open question — this fix does not depend on the answer. */
.navbar-brand { min-width: 0; max-width: 100%; overflow: hidden; }
.adm-idcard   { min-width: 0; max-width: 100%; }

/* ── THE ADMIN USES THE SCREEN IT WAS GIVEN ─────────────────────────────────────────────────────────
   Graham, 2026-08-20, looking at the Mail log on a wide monitor: "Why are we not using all the space."

   Tabler ships .container-xl at max-width:1140px, so every admin page was painted into 1,140 pixels no
   matter how wide the window — roughly 760px of empty margin on his screen — while the Mail log's own
   To column was wrapping addresses mid-word ("graham@pagetr / affic.com") for want of room.

   Scoped to the PAGE BODY, not to .container-xl globally: the layout uses the same class for the top
   navbar and the footer, and those are chrome that should stay aligned with the brand, not stretch.

   Capped at 1800px rather than removed. A table wants the width; a paragraph of help text at 1900px is
   a line your eye loses its place on halfway across. 1800 is wide enough that the widest thing here —
   the roster, the mail log, the board — stops truncating, and short of the point where reading suffers. */
.page-body > .container-xl { max-width: min(1800px, 100%); }

/* Addresses and tokens are single unbreakable things. The mail log's From/To columns were being squeezed
   until the browser broke them mid-word, which turns an address into two things that look like neither.
   Give them a floor and let the subject take the slack. */
#mailLog td.ml-addr, #mailLog th.ml-addr { min-width: 13rem; overflow-wrap: normal; word-break: normal; }
#mailLog td.ml-when, #mailLog th.ml-when { white-space: nowrap; }

/* ── CLICKABLE ROWS: Clients and Packages sold ──────────────────────────────────────────────────────
   Graham 2026-08-21: "Add row hover. If anywhere without a link, open the client."

   A row that navigates has to LOOK like it navigates before the click, or the first one is an accident.
   The hover tint is the same faint blue the admin uses for a focused surface, and the pointer confirms
   it; the cells that keep their own behaviour (the next-contact editor) reset the cursor so the one
   square of the row that is not a doorway does not claim to be. */
.crm-cl-row, .crm-sold-head { cursor: pointer; transition: background-color .12s ease; }
.crm-cl-row:hover, .crm-sold-head:hover { background-color: rgba(32, 107, 196, .05); }
.crm-cl-row:hover .crm-cl-av { filter: brightness(.97); }
.crm-cl-row .crm-nc-read, .crm-cl-row .crm-nc-form { cursor: auto; }
/* Keyboard parity: the row is reachable by tabbing to the name link, and that link's focus ring should
   read as the whole row being the target rather than four characters of it. */
.crm-cl-row:focus-within, .crm-sold-head:focus-within { background-color: rgba(32, 107, 196, .07); }

/* THE DETAIL BLOCK inside Packages sold used bg-light and a default <thead>, which put a second grey
   header bar a few rows under the real one (Graham: "This is a bit confusing having the same colour as
   the header"). The block is gone now with the in-place editor, but the tokens stay defined so the
   pattern is available to the next nested table rather than being reinvented as bg-light again. */
.crm-sold-detail { background: #fff; border-left: 3px solid var(--tblr-primary, #206bc4); }
.crm-sold-inner > thead th { background: transparent; text-transform: uppercase; letter-spacing: .04em;
    font-size: .6875rem; color: var(--tblr-secondary, #6c7a91); border-bottom: 1px solid rgba(0,0,0,.06); }

/* ── THE ROSTER TABLE'S ROWS ARE DOORWAYS TOO ───────────────────────────────────────────────────────
   Graham 2026-08-21: "On the table, add a on hover that changes the background of each row as you mouse
   over the row … Maybe on click brings up the deal instead?"

   Same tint and same rule as the Clients list and Packages sold: hovering says the row is clickable
   before the first click proves it. The status stripe down the left edge (crm-trow--red/amber/green)
   is unaffected — the tint sits behind it, so the colour that says how late somebody is still reads at
   a glance while the row is under the pointer. */
tr.crm-trow { cursor: pointer; transition: background-color .12s ease; }
tr.crm-trow:hover { background-color: rgba(32, 107, 196, .05); }
tr.crm-trow:focus-within { background-color: rgba(32, 107, 196, .07); }
/* The cells that own their own click get the ordinary cursor back, so the one part of the row that is
   not a doorway does not claim to be. */
tr.crm-trow .crm-reach, tr.crm-trow .crm-stagecell, tr.crm-trow .crm-ico,
tr.crm-trow .crm-copy, tr.crm-trow .crm-missing { cursor: auto; }
tr.crm-trow a, tr.crm-trow button, tr.crm-trow input { cursor: pointer; }

/* The client block in the Packages tab's right rail (Graham 2026-08-21: "The right side can be dedicated
   to the client"). Separated from the money above it by a rule rather than a card, because it is the same
   column about the same person — a second card here would read as a second subject. */
.crm-cart-client { border-top: 1px solid rgba(0,0,0,.08); padding-top: .65rem; }
.crm-cart-client .nx-do { display: flex; border: 1px solid rgba(0,0,0,.10); border-radius: 8px; overflow: hidden; }

/* ── STAGE SECTIONS IN THE ROSTER TABLE ─────────────────────────────────────────────────────────────
   Graham 2026-08-21: "Maybe add a colapse cevron on the first row of every stage", "Can you put a
   background colour on the lost to indicate that they are lost?", "Lost default to hidden."

   The heading row is a heading, not a record: no hover tint, no pointer over the whole width, and the
   chevron is a real button so the keyboard reaches it. */
tr.crm-tsec > td { background: var(--tblr-bg-surface-secondary, #f6f8fb); padding: .3rem .5rem; }
tr.crm-tsec:hover > td { background: var(--tblr-bg-surface-secondary, #f6f8fb); }
.crm-tsec-btn { display: inline-flex; align-items: center; gap: .4rem; border: 0; background: none;
    padding: .1rem .2rem; font-size: .75rem; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; color: var(--tblr-secondary, #6c7a91); cursor: pointer; }
.crm-tsec-btn:hover, .crm-tsec-btn:focus-visible { color: var(--tblr-primary, #206bc4); }
.crm-tsec-car { font-size: 1rem; }

/* LOST, at a glance. A wash rather than a strong fill: these rows are still readable records you
   occasionally need, not warnings — and the red left stripe already carries urgency elsewhere, so a
   loud red band here would read as "urgent" for the one group where nothing is owed. */
tr.crm-trow[data-tstage="lost"] > td { background-color: rgba(214, 57, 57, .045); }
tr.crm-trow[data-tstage="lost"]:hover > td { background-color: rgba(214, 57, 57, .085); }
tr.crm-tsec[data-tsec="lost"] > td { background: rgba(214, 57, 57, .07); }
tr.crm-tsec[data-tsec="lost"] .crm-tsec-btn { color: var(--adm-status-expired-fg, #b42318); }

/* ── FIND A SETTING (Graham 2026-08-21: "This page needs a search (like the alt k, ajax drop down)") ──
   The results sit over the page rather than pushing it down: a list that reflows seventeen cards every
   keystroke makes the thing you were reading move while you type. */
.adm-setfind-results { z-index: 1030; top: 100%; max-height: 22rem; overflow-y: auto; }
/* WHERE IT LANDED. Scrolling to a card and stopping leaves you looking at a page that looks the same as
   before — the flash is what says "this one". Brief and soft: a permanent highlight would still be there
   an hour later, and a hard one reads as an error. */
.adm-setfind-hit { animation: admSetFindFlash 1.6s ease-out 1; }
@keyframes admSetFindFlash {
    0%   { box-shadow: 0 0 0 3px rgba(32,107,196,.45); }
    60%  { box-shadow: 0 0 0 3px rgba(32,107,196,.25); }
    100% { box-shadow: 0 0 0 3px rgba(32,107,196,0); }
}

/* ── ROOM FOR THE HOVER ROW ─────────────────────────────────────────────────────────────────────────
   Graham 2026-08-21: "Need to make delete taller again to accomodate the mouse over menu."

   A card's action row (.crm-card-acts) is hidden until you point at it. On a normal card that is fine —
   visibility:hidden still reserves its space — but a LOST card renders one-line, and in compact density
   the body is a flex row with .3rem of padding. There was nothing to reserve: the actions had to squeeze
   in beside the name and the stage chip, so the chip clipped mid-word ("Lost — Wer", "Lost -", "L(") and
   the buttons had nowhere to go.

   A floor on the body rather than a fixed height: cards that need two lines still take two. */
.crm-card .card-body { min-height: 2.75rem; }
.crm-board--compact .crm-card .card-body { min-height: 2.5rem; }
/* The actions never give up their width to the text beside them — they are the reason for the height. */
.crm-card .crm-card-acts { flex: 0 0 auto; }
/* And the chip that was being cut off ends in an ellipsis instead, so a truncated reason reads as
   truncated rather than as a typo. */
.crm-card .card-body .adm-chip, .crm-card .card-body .badge {
    max-width: 11rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* THE LOST CARD'S REASON GETS ITS OWN LINE. Measured before changing anything: in a 224px column the
   badge needed 150px and was being given 16 — so "Lost — Went elsewhere" rendered as an ellipsis and
   nothing else. It is a flex row, the name takes what it wants, and a badge with no flex-basis loses.

   Wrapping is the fix rather than a wider max-width, because there is no width that fits a name AND a
   reason AND the hover actions on a column this narrow. The card grows to two lines, which is what
   Graham asked for ("make it taller again to accomodate the mouse over menu") — the actions get their
   room back and the reason becomes readable at the same time. */
.crm-card-1line { flex-wrap: wrap; }
.crm-card-1line > .badge, .crm-card-1line .badge.d-block {
    flex: 1 0 100%; max-width: 100%; margin-top: .15rem; text-align: left;
}

/* ── HOVER HELP ON THE DEAL BENCH'S BUTTONS ─────────────────────────────────────────────────────────
   Graham 2026-08-21: "add hover help (question mark top right) to each button on this page".

   A small ? in the corner of every button that has something to explain, and hovering the button says
   what it does. It rides the help toggle that already exists in the header (body.help-on) rather than
   being a second help system: the admin already has one switch for "explain this page to me", and a
   permanent ? on twenty buttons would be exactly the visual noise this review has spent two days
   deleting. Turn help on and the whole bench annotates itself; turn it off and it is clean again.

   Only buttons carrying a real title get a mark — a ? that reveals nothing is worse than no ?. */
body.help-on .crm-bench-tab[title],
body.help-on .crm-hdr-actions .btn[title],
body.help-on #crmWorkNext[title],
body.help-on #crmWorkPrev[title],
body.help-on #crmWorkSkip[title],
body.help-on .crm-work-bar .btn[title],
body.help-on .crm-tile-btn[title],
body.help-on .crm-exit[title] { position: relative; overflow: visible; }

body.help-on .crm-bench-tab[title]::after,
body.help-on .crm-hdr-actions .btn[title]::after,
body.help-on #crmWorkNext[title]::after,
body.help-on #crmWorkPrev[title]::after,
body.help-on #crmWorkSkip[title]::after,
body.help-on .crm-work-bar .btn[title]::after,
body.help-on .crm-tile-btn[title]::after,
body.help-on .crm-exit[title]::after {
    content: "?";
    position: absolute; top: -6px; right: -6px;
    width: 14px; height: 14px; line-height: 14px;
    border-radius: 50%;
    background: var(--tblr-primary, #206bc4); color: #fff;
    font-size: 10px; font-weight: 700; text-align: center;
    pointer-events: none;   /* the ? must never eat the click that belongs to the button */
    z-index: 2;
}

/* ── TWO PANELS IN THE PACKAGES RAIL (Graham 2026-08-21) ────────────────────────────────────────────
   "Can you have two seperate boxes? The running total and the contact card just above each other. The
   contact card still on bottom." The client used to sit inside the cart panel behind a rule; it is its
   own box now, stacked under the money, with real space between them so they read as two answers. */
#pkgSplit .crm-client-side { margin-top: .75rem; }
/* The rail is a grid column: both panels take the full width of it and stack. */
#pkgSplit > .crm-cart-side { align-self: start; }
/* The old in-panel divider is no longer needed now the client has its own box. */
.crm-cart-client { border-top: 0; padding-top: 0; }

/* BIGGER DRAFT BUTTONS (Graham 2026-08-21: "Make these button bigger"). "Draft the quote" and "Draft a
   Contract" are the two things the whole tab is building towards, and they were btn-sm in a rail of
   small print — the smallest-looking controls on screen doing the largest job. */
#pkgCartPanel .btn { font-size: .875rem; padding: .45rem .85rem; }
#pkgCartPanel .btn i { font-size: 1rem; }

/* BREATHING SPACE where the two removed lines used to sit (Graham: "I do like a small white space above
   the quote box and above the check boxes"). The eyebrow and the instruction line were doing this job by
   accident; with them gone the list and the rail would butt straight against the card header. */
#pkgSplit { padding-top: .5rem; }
#quoteBuild #crmStage { padding-top: .25rem; }

/* ── THE COMMISSION LEDGER'S DISCLOSURE (Graham 2026-08-21) ─────────────────────────────────────────
   "Just have one line. Have a chevron far left that will expand." A borderless chevron at the head of
   the period cell, sized so the statements still read as one line each. The empty variant keeps the
   indent on statements with no stored lines, so the period column does not jag in and out. */
.crm-stmt-toggle { display: inline-flex; align-items: center; justify-content: center;
    width: 1.25rem; height: 1.25rem; margin-right: .25rem; padding: 0;
    border: 0; background: none; color: var(--tblr-secondary, #6c7a91); cursor: pointer; vertical-align: middle; }
.crm-stmt-toggle:hover, .crm-stmt-toggle:focus-visible { color: var(--tblr-primary, #206bc4); }
.crm-stmt-toggle--none { cursor: default; }
/* The opened lines read as belonging to the statement above them, not as more statements. */
tr.crm-stmt-line > td { background: var(--tblr-bg-surface-secondary, #f8fafc); border-top: 0; }
tr.crm-stmt-total > td { border-top: 1px solid rgba(0,0,0,.08); }
