/* 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); }
/* SALES FLOW STRIP on the board card (Graham 2026-08-28): ① Response · ② Quote · ③ Contract. Three pips wired
   by a connector; the first not-done step is the card's one-click next action. */
.crm-flow { display: flex; align-items: center; gap: 0; margin-bottom: .15rem; }
.crm-flow-pip { flex: none; width: 19px; height: 19px; border-radius: 50%; display: inline-flex;
    align-items: center; justify-content: center; font-size: 11px; line-height: 1; border: 1.5px solid #dfe3e8; color: #adb5bd; background: #fff; }
.crm-flow-pip i { font-size: 11px; }
.crm-flow-done { background: #d3f9d8; border-color: #2fb344; color: #2fb344; }
.crm-flow-now  { border-color: #206bc4; color: #206bc4; box-shadow: 0 0 0 2px rgba(32,107,196,.14); }
.crm-flow-wait { border-color: #206bc4; background: #e7f2fd; color: #206bc4; }
.crm-flow-line { flex: 1 1 auto; height: 1.5px; background: #dfe3e8; margin: 0 2px; min-width: 8px; }
.crm-flow-line--done { background: #2fb344; }
.crm-flow-cta { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
    font-size: .78rem; font-weight: 600; text-decoration: none; margin-bottom: .1rem; }
.crm-flow-cta--now  { color: #206bc4; }
.crm-flow-cta--now:hover { color: #17548f; text-decoration: underline; }
.crm-flow-cta--wait { color: #6c757d; font-weight: 500; }
.crm-flow-cta--wait:hover { color: #4b5259; }
.crm-flow-cta--done { color: #2fb344; }
/* Compact board: keep the pips, drop the wordy CTA so tiny cards stay tiny. */
.crm-board--compact .crm-flow-cta { display: none; }
.crm-board--compact .crm-flow-pip { width: 16px; height: 16px; }
/* Deal-bench tab step numbers (① Message · ② Quote · ③ Paperwork) — the same flow the board card shows. */
.crm-tab-step { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px;
    border-radius: 50%; background: #e7f2fd; color: #206bc4; font-size: 10px; font-weight: 700; line-height: 1;
    margin-right: 5px; vertical-align: 1px; }
.crm-bench-tab.on .crm-tab-step { background: #206bc4; color: #fff; }
/* Paperwork tab gated until a quote is approved (Graham 2026-08-28): looks muted, still clickable (a popup
   offers a manual override). */
.crm-bench-tab.crm-tab-gated { opacity: .5; }
.crm-bench-tab.crm-tab-gated:hover, .crm-bench-tab.crm-tab-gated.on { opacity: 1; }
/* The due/call date, moved to the card's top-right (Graham 2026-08-28). On hover the drag handle (⠿) appears
   at the top-right corner, so shift the date left just then to clear it. */
.crm-card-when { white-space: nowrap; }
.crm-card:hover .crm-card-when, .crm-card:focus-within .crm-card-when { margin-right: .85rem; }
/* SMALLER BOARD CARDS (Graham 2026-08-28: "the whole card smaller"). Tighter padding and less air between
   the name, the flow strip and the money line — same information, less height, so more of each column shows
   at once. The one-line Lost card keeps its own layout; compact mode (which hides the CTA) is unaffected. */
.crm-card { margin-bottom: .35rem; }
.crm-card .card-body:not(.crm-card-1line) { padding: .4rem .55rem !important; }  /* beats Tabler's !important .p-2 */
.crm-card .crm-flow { margin: .15rem 0 .1rem; }
.crm-card .crm-flow-cta { font-size: .74rem; margin-bottom: 0; line-height: 1.25; }
.crm-card .card-body > .d-flex.mt-1 { margin-top: .15rem !important; }
/* THE MONEY FLUSH BOTTOM-RIGHT (Graham 2026-08-28): margin-left:auto pushes the value to the right edge on
   its own — the hover controls no longer share this row (they float, see below), so the row holds only the
   money, and on a card with no money it collapses to nothing. */
.crm-card .card-body > .d-flex.mt-1 [data-money] { margin-left: auto; }
.crm-card .card-body > .d-flex.mt-1:empty,
.crm-card .card-body > .d-flex.mt-1:not(:has([data-money])) { margin-top: 0 !important; min-height: 0; }
/* Small inline spinner for AJAX popups (AI reply draft, etc.). */
@keyframes crm-spin { to { transform: rotate(360deg); } }
.crm-spin { display: inline-block; animation: crm-spin .8s linear infinite; }
/* Message-tab conversation fold (Graham 2026-08-28): collapsed by default so the composer leads; the History
   tab keeps the full record. Native <details> — don't set display on the collapsing child (disclosure rules). */
.crm-thread-foldsum { list-style: none; cursor: pointer; display: flex; align-items: center; gap: .1rem;
    padding: .6rem .75rem; font-weight: 500; color: #495057; user-select: none; }
.crm-thread-foldsum::-webkit-details-marker { display: none; }
.crm-thread-foldsum:hover { background: #f6f7fb; }
.crm-thread-chev { transition: transform .15s ease; color: #8492a1; }
.crm-thread-fold[open] > .crm-thread-foldsum .crm-thread-chev { transform: rotate(90deg); }
/* 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; }

/* ── ONE GRID FOR THE DEAL'S PAPERWORK (Graham 2026-09-01: "display consistency between quote and
   contract pages … text and buttons bigger") — the Quote and Contract tabs list their documents in the
   SAME table shape: one row per document, readable size, actions right. ── */
.crm-doc-table { font-size: .95rem; }
.crm-doc-table > :not(caption) > * > * { padding-top: .55rem; padding-bottom: .55rem; vertical-align: middle; }
/* EVERY button in the grid is the same size (Graham 2026-09-01: "Buttons same size") — !important
   because the two tabs' cascades disagreed (27px vs 32px for identical btn-sm markup), and the generic
   .crm-copy chip rule (border:0, 12px) was flattening the bordered "Copy link" button. */
.crm-doc-table .btn { font-size: .85rem !important; padding: .3rem .7rem !important; line-height: 1.3 !important; }
.crm-doc-table .btn.crm-copy { border: 1px solid var(--tblr-success, #2fb344); background: transparent;
    color: var(--tblr-success, #2fb344); }
.crm-doc-table .btn.crm-copy:hover { background: var(--tblr-success, #2fb344); color: #fff; }
.crm-doc-table .badge { font-size: .8rem; }
.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); }
/* CLIENT RECORD strip on a phone: 4 tabs wrapped to two rows (CRM mobile audit 2026-08-31). The deal
   bench keeps flex-wrap (8 tabs, and the phone gets the overlay there anyway); the record's copy has 4
   and reads better as ONE scrollable row — the same scroll-strip answer as the inbox chips. */
@media (max-width: 767.98px) {
  .crm-record-tabs .crm-bench-strip { flex-wrap: nowrap; overflow-x: auto;
      -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .crm-record-tabs .crm-bench-strip::-webkit-scrollbar { display: none; }
  .crm-record-tabs .crm-bench-tab { white-space: nowrap; flex: 0 0 auto; }
  /* Round-2 #23: the Activity panel opens with the shared contact-header card — on this page the state
     header two cards up already says who this is; on a phone the duplicate costs a whole screen-row. */
  .crm-record-panel .crm-contact-header { display: none; }
  /* Round-2 #24: the History header's filter + sort wrapped mid-row around the counts; give the
     controls their own right-aligned line under the title. */
  #crmHistory > .card-header { flex-wrap: wrap; }
  #crmHistory > .card-header .ms-auto { flex: 0 0 100%; justify-content: flex-end; margin-left: 0 !important; }
}
/* 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: "▸"; }

/* COUNTERSIGNATURE PAD + saved-signature preview (Graham 2026-08-26: "store my signature block … add preview
   of signature block if I want to clear"). White so the ink reads on the dark slab; sized to fit the card. */
.crm-sig-canvas { border: 1px dashed #94a3b8; border-radius: 6px; background: #fff; touch-action: none; max-width: 100%; cursor: crosshair; }
.crm-sig-preview { display: inline-block; border: 1px solid var(--adm-border, #e6e7e9); border-radius: 6px; background: #fff; padding: 4px 8px; }
.crm-sig-preview img { max-height: 70px; max-width: 220px; display: block; }

/* INLINE SIGNATURE RECORD on the contract preview (Graham 2026-08-26: "yes to signatures"). Sits under the
   agreement body as white "paper", so its colours are fixed dark-on-white regardless of the admin theme —
   it mirrors the executed PDF's signature block. */
.crm-sig-record { color: #1f2933; }
.crm-sig-record-h { font-weight: 700; font-size: 1.05rem; color: #0f3d63; margin-bottom: .15rem; }
.crm-sig-record-state { font-weight: 600; font-size: .85rem; margin-bottom: .5rem; }
.crm-sig-record-state.is-exec { color: #166534; }
.crm-sig-record-state.is-part, .crm-sig-record-state.is-draft { color: #b45309; }
.crm-sig-role { font-size: .75rem; font-weight: 700; letter-spacing: .04em; color: #64748b; text-transform: uppercase; margin-bottom: .35rem; }
.crm-sig-img { max-width: 220px; max-height: 80px; display: block; margin-bottom: .15rem; }
.crm-sig-name { border-bottom: 1px solid #0f3d63; padding-bottom: .15rem; font-size: 1rem; min-height: 1.4rem; }
.crm-sig-meta { font-size: .75rem; color: #475569; margin-top: .15rem; }
.crm-sig-consent { font-size: .72rem; font-style: italic; color: #64748b; margin-top: .3rem; }
.crm-sig-line { border-bottom: 1px solid #94a3b8; height: 1.6rem; }

/* PAPERWORK TAB FACELIFT (Graham 2026-08-26: "words are small, bad use of space, buttons small, just needs a
   little facelift"). Scoped to the paperwork section only — bumps the type scale, opens the spacing and gives
   the buttons a real target, without touching the dense layouts elsewhere. */
.crm-paper-face { font-size: .95rem; line-height: 1.5; }
.crm-paper-face .adm-sec-h { font-size: 1.15rem; font-weight: 700; margin-bottom: .5rem; }
.crm-paper-face .small, .crm-paper-face .form-hint { font-size: .85rem !important; }
.crm-paper-face .form-hint { margin-top: .4rem !important; }
.crm-paper-face .badge { font-size: .8rem; padding: .38em .65em; }
/* Buttons get a real target — override btn-sm's cramped padding and the py-0 flatteners in the row. */
.crm-paper-face .btn-sm { padding: .45rem .9rem; font-size: .9rem; border-radius: 7px; }
.crm-paper-face .btn.py-0 { padding-top: .35rem !important; padding-bottom: .35rem !important; }
.crm-paper-face .btn .ti { font-size: 1.05em; }
/* The contract line breathes: a padded, hairline-separated row instead of a cramped one-liner. */
.crm-paper-face .crm-ct-row { font-size: .92rem !important; padding: .55rem .65rem; margin-bottom: .5rem !important;
    row-gap: .45rem; background: var(--adm-surface-2, rgba(0,0,0,.02)); border-radius: 8px; }
.crm-paper-face .crm-ct-row .adm-money { font-weight: 600; }
.crm-paper-face details > summary { padding: .3rem 0; }

/* ── 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; }
    /* SHORTEN THE CARD (Graham 2026-08-28: "Leave them on and shorten the card"). The hover action row used
       to reserve an empty strip of height on every card even when unhovered. Float it as a bottom-right
       overlay so it stops taking a row; the buttons are unchanged and still appear on hover. Not on the
       compact one-line Lost card, whose actions sit inline on its single row. */
    .crm-card .card-body:not(.crm-card-1line) .crm-card-acts {
        position: absolute; right: .3rem; bottom: .25rem; z-index: 3;
        background: var(--tblr-card-bg, #fff); padding: 1px 3px; border-radius: 7px;
        box-shadow: 0 1px 5px rgba(0,0,0,.16);
    }
    /* #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; }

/* WAITING APPROVAL (Graham 2026-09-08) — the zone at the head of the Prospecting column, holding mail
   that has arrived but is not a lead yet. Deliberately does NOT look like a stage zone: a dashed amber
   edge rather than a solid card, because nothing in here is in the pipeline and nothing can be dragged
   into it. It reads as a doorstep, not a rung. See Pages/Admin/Leads/_WaitingZone.cshtml. */
.crm-wait-zone { border: 1px dashed var(--tblr-orange, #f76707); background: rgba(247,103,7,.05);
                 padding: 8px 8px 6px; }
.crm-wait-card { border: 1px solid var(--adm-status-muted-bg, #e6e9ee); border-radius: 8px;
                 background: #fff; padding: 7px 9px; margin-bottom: 6px; font-size: 12.5px; }
.crm-wait-card:last-child { margin-bottom: 0; }
/* The header wraps instead of clipping: a phase column is 13rem and the badge, its count and the
   Inbox link do not fit on one line there. Letting the badge break keeps the name readable. */
.crm-wait-head { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; }
.crm-wait-head .badge { white-space: normal; text-align: left; line-height: 1.25; }
/* The subject is context, not the headline — the person is. */
.crm-wait-subj { color: var(--adm-text-muted); font-size: 12px; margin-top: 2px; }

/* 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; }
/* "Draft a Contract" before there's a quote (Graham 2026-08-27): greyed but still clickable — a hover
   tip recommends drafting the quote first. Comes up to full strength on hover so it never reads as disabled. */
.crm-contract-noquote { opacity: .6; }
.crm-contract-noquote:hover, .crm-contract-noquote:focus-visible { opacity: 1; }
/* Import-lead inline tier list (Graham 2026-08-27): tick a service, its packages show with price + a qty box. */
.crm-svc-tiers { display: flex; flex-direction: column; border: 1px solid var(--adm-border, #e6eaf0); border-radius: 8px; overflow: hidden; margin-top: 4px; }
.crm-svc-tierrow { display: flex; align-items: center; gap: 12px; padding: 7px 12px; border-top: 1px solid var(--adm-border-subtle, #eef1f5); }
.crm-svc-tierrow:first-child { border-top: 0; }
.crm-svc-tiername { font-weight: 600; color: var(--adm-text, #1b2733); }
.crm-svc-tierprice { margin-left: auto; color: var(--adm-text-muted, #8492a1); font-size: .9rem; white-space: nowrap; }
.crm-svc-tierqty { width: 4.5rem; flex: none; }
.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; }
.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); }
/* Per-row enable (+) / disable (−) toggle beside the state dot. Available add-on → green plus; installed →
   red minus. (Graham 2026-08-27: a plus/minus beside each circle.) */
.addon-toggle { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; margin-left: .4rem;
    display: inline-flex; align-items: center; justify-content: center; cursor: pointer; border: 1px solid #cfd6e0;
    color: var(--tblr-green, #2fb344); background: #fff; transition: background .12s, color .12s, border-color .12s; }
.addon-toggle i { font-size: 14px; }
.addon-toggle:hover { background: #eaf7ee; border-color: var(--tblr-green, #2fb344); }
.addon-toggle.is-on { color: #c0453c; }
.addon-toggle.is-on:hover { background: #fdecea; border-color: #c0453c; }
.addon-toggle:focus-visible { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: 1px; }
/* 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; }

/* Slim site-wide navigation setting, docked as a footer under the two panes so nothing dangles below. */
.addon-navbar { display: flex; align-items: center; gap: .6rem; padding: .5rem .85rem;
    background: var(--tblr-bg-surface-secondary, #f7f8fa); }
.addon-navbar-ico { font-size: 1.05rem; color: var(--adm-text-muted, #5a6270); }
.addon-navbar-lbl { line-height: 1.15; margin-right: auto; }
.addon-navbar-lbl strong { font-size: .875rem; }
.addon-navbar-lbl span { display: block; font-size: .75rem; }
.addon-navbar-form { display: flex; align-items: center; gap: .5rem; margin: 0; }
.addon-navbar-form .form-select { max-width: 30rem; }

/* On wide screens the browser is a full-height, two-pane app: the list and the detail each scroll inside their
   own pane so the column height is always used (no short list floating in white space). The nav footer stays
   pinned at the bottom. Below lg the layout stacks and reverts to the simple fixed-height list. */
@media (min-width: 992px) {
    .addon-shell { display: flex; flex-direction: column; height: calc(100vh - 13.5rem); min-height: 32rem; overflow: hidden; }
    .addon-shell .addon-browser { flex: 1 1 auto; min-height: 0; flex-wrap: nowrap; }
    .addon-shell .addon-browser > [class*="col-"] { display: flex; flex-direction: column; min-height: 0; }
    .addon-shell .addon-list { flex: 1 1 auto; max-height: none; min-height: 0; }
    .addon-shell .addon-detail { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-bottom: 0; }
    /* Pin the Enable / Disable / Manage buttons to the foot of the detail pane so the tall preview can never
       scroll them out of reach (Graham 2026-08-27: "enable button hidden by scroll"). */
    .addon-shell .addon-action { position: sticky; bottom: 0; z-index: 2; background: #fff;
        margin-top: .6rem; padding: .7rem 0 1.1rem; border-top: 1px solid var(--tblr-border-color, #e6e9ee); }
}
@media (max-width: 991.98px) { .addon-list { max-height: 18rem; }
    .addon-navbar { flex-wrap: wrap; } .addon-navbar-form { width: 100%; } .addon-navbar-form .form-select { flex: 1 1 auto; max-width: none; } }

/* 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. 40px (house rule:
   40px minimum touch target), bumped from 38px while touching this rule for entry 15, 2026-09-23. */
.media-toolbar .form-control,
.media-toolbar .form-select,
.media-toolbar .btn { height: 40px; }
.media-toolbar input[type="file"].form-control { padding-top: .35rem; line-height: 1.5; }
/* Library health group (entry 15): Unused only + Find broken images read as one cluster, and wrap as one
   unit at phone width rather than interleaving with the search/upload controls either side of it. */
.library-health-group { flex-wrap: wrap; }

/* 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}
/* The header search's Alt K, inside the field like the sidebar's Alt M (2026-09-24) - but a real button: it opens
   quick-jump. Light-surface colours; hidden once something is typed, where it would sit on the words. */
.adm-top-search{position:relative;width:100%}
.adm-top-search.input-icon input.form-control{padding-right:3.6rem}
.adm-top-kbd{position:absolute;right:.35rem;top:50%;transform:translateY(-50%);z-index:5;border:0;background:transparent;padding:.2rem;border-radius:5px;line-height:1;cursor:pointer}
.adm-top-kbd kbd{display:inline-block;background:transparent;color:var(--tblr-secondary,#6c7a91);border:1px solid rgba(98,105,118,.35);border-radius:4px;padding:.12rem .38rem;font-size:.7rem;line-height:1.2;box-shadow:none}
.adm-top-kbd:hover kbd,.adm-top-kbd:focus-visible kbd{color:var(--tblr-body-color,#1d273b);border-color:rgba(98,105,118,.6);background:rgba(98,105,118,.08)}
.adm-top-search input:not(:placeholder-shown) ~ .adm-top-kbd{display:none}

/* 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. */
/* TOUCH FLOOR — extended to the TABLET band too (parity audit 2026-08-28): a 768–991 iPad is a touch
   device, and the 16px-input / 40px-target floor is exactly as needed there as on a phone. Everything in
   this block is a defensive minimum (sizes up, never down), so widening the breakpoint only helps. */
@media (max-width: 991.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; }
  /* …but a row that ENDS in a chevron is one tappable line — the wrap above dropped the arrow onto its
     own row under a long label (Dashboard "Needs you"; full-admin mobile audit 2026-08-31). */
  .list-group-item.d-flex:has(> .ti-chevron-right.ms-auto) { flex-wrap: nowrap; }
  .list-group-item.d-flex:has(> .ti-chevron-right.ms-auto) > span { min-width: 0; }
  /* 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. Shown up to 991.98 (parity audit 2026-08-28): the sidebar
   is navbar-expand-LG, so it is behind the hamburger for the whole 768–991 band too — the old "above 768 the
   sidebar is the better answer" was false, leaving tablets with no thumb navigation. The bar (and its
   page-body clearance, below) now covers tablet as well. */
.adm-tabbar { display: none; }
@media (max-width: 991.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; }
  /* …and the min-width UTILITY columns that WRAP them (parity audit 2026-08-28): a `col-auto.adm-minw-24r`
     is forced to 24rem = 384px, overflowing a 375px phone sideways even after the field inside it is capped
     (Import / PII / Migrations). Zero the utility on the narrowest tier; the column then wraps to fit. */
  [class*="adm-minw-"] { 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; }
}

/* HEADER CLUSTER BREATHES ON A TABLET (parity audit 2026-08-28). At 768 the search (360px) competes with
   Sync + Deploy + notices + bell + help + avatar in one row. Narrow the search across the tablet band so the
   buttons keep their labels; it widens back to 360 on true desktop. */
@media (min-width: 768px) and (max-width: 991.98px) {
  header.navbar form[role="search"].adm-mw-360 { max-width: 240px !important; }  /* beats the !important utility */
}
/* THE HEADER SEARCH'S WORDS NEVER CUT OFF MID-LETTER (Graham 2026-09-24: "it cuts the right part off of
   textbox"). The Alt K button takes ~77px of the box, and whenever the top-bar buttons squeeze the form below
   its cap the placeholder ran straight under that button - "customer" sliced at the "r". Two parts: 400px on
   a real desktop (placeholder + padding measured at 248px, which the 360 cap left only ~9px to spare), and an
   ellipsis for any width still too narrow, so it ends in "…" instead of a sliced letter. */
@media (min-width: 992px) {
  header.navbar form[role="search"].adm-mw-360 { max-width: 400px !important; }
}
header.navbar form[role="search"] input[type="search"],
header.navbar form[role="search"] input[type="search"]::placeholder { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
header.navbar form[role="search"] input[type="search"] { min-width: 0; }

/* EMBEDDED CONFIG IFRAMES ON A PHONE (parity audit 2026-08-28). The SQL console (Database) and the export
   packager (Deploy) embed a whole page in a fixed 520-560px frame — nested double-scroll under a thumb.
   Hide the frame on a phone; the "Open full page" link that already sits above each one is the way in there
   (these are desk-admin tasks anyway). */
@media (max-width: 767.98px) {
  .adm-iframe-fill-white { display: none !important; }
}

/* #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, then a sticky full-width
   strip under the header (2026-08-10); Graham 2026-08-31: "add it to the black bar at the top and you
   don't need to make it sticky" — so it now sits IN the dark brand bar, beside the site pill, which goes
   icon-only on phones to make the room. Not sticky: the tab bar's Find button is the always-there way in.
   Same ⌘K palette wiring (data-cmdk-live). Phone only; the desk keeps its header bar. */
.adm-mobile-search { display: none; }
@media (max-width: 767.98px) {
  /* ── ONE dark line, everything on it (Graham 2026-08-31 round 3: "make the black area one line
     instead of two" / "Move search to the top"): hamburger → search (takes the slack) → site icon →
     the moved header icons. Explicit orders because the DOM order is toggler, brand, icons, search;
     the opened hamburger menu (w-100) wraps below whatever the order says, pushed last. ── */
  .navbar-vertical .container-fluid { flex-wrap: wrap; align-items: center; }
  .navbar-vertical .navbar-toggler { order: -3; }
  .adm-mobile-search { display: block; flex: 1 1 0%; min-width: 0; order: -2; margin: 0 .25rem; }
  .navbar-vertical .navbar-brand { order: -1; flex: 0 0 auto; min-width: 0; }
  #darkbarIcons { order: 0; }
  .navbar-vertical #sidebar-menu { order: 9; }
  /* Dark-bar field: quiet glassy box, same recipe as the idcard itself.
     16px keeps iOS from zooming the page when the field is focused; 40px is the tap floor. */
  .adm-mobile-search .form-control {
    font-size: 16px; min-height: 40px;
    background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.12); color: #fff;
  }
  .adm-mobile-search .form-control::placeholder { color: rgba(255,255,255,.45); }
  .adm-mobile-search .input-icon-addon { color: rgba(255,255,255,.5); }
  /* Everything else on the line goes on a diet so the search box gets real width: tighter icon
     paddings, no help "?" on a phone (a desktop affordance — the tab bar's More and each page's help
     live on), smaller language chips. */
  #darkbarIcons .nav-link { padding: .25rem .3rem !important; }   /* beats Bootstrap's !important px-2 */
  #darkbarIcons .nav-item:has(#helpToggle) { display: none; }
  .adm-lang-chips .adm-lang-chip { padding: .1rem .3rem; font-size: .65rem; }
  /* SITE SWITCHER = A COMPACT ICON, NOT A BOX (Graham 2026-08-31: "Change the change website from
     large box to pop up icon with list"). The idcard's glassy card frame goes; the pill keeps its own
     small rounded background, dot + swap arrows only. Tapping it already opens the full-screen site
     LIST (the ⌘K palette in sites mode) — that part was always the popup he asked for. */
  .adm-idcard { margin-top: 0; background: transparent; border: 0; padding: 0; }
  .adm-idrow-site .adm-idval { display: none; }
  .adm-idrow-site { flex: 0 0 auto; }
  .adm-idrow-site .adm-idswap { margin-left: .15rem; }
  /* THE MOVED HEADER MENUS OPEN UNDER THE BAR THEY LIVE IN (Graham 2026-08-31: "Make the G button
     work"). The G and "!" dropdowns DID open — Popper placed them at the BOTTOM of the screen,
     full-width, behind the tab bar, because their offset context is the whole aside. Pin them where
     the finger just was: fixed, right under the one-line dark bar. !important beats Popper's inline
     transform; the fixed strategy ignores the aside entirely. */
  #darkbarIcons .dropdown-menu {
    position: fixed !important; left: auto !important; right: .5rem !important;
    top: calc(3.4rem + env(safe-area-inset-top, 0px)) !important;
    bottom: auto !important; transform: none !important;
    width: auto !important; min-width: 13rem; max-width: calc(100vw - 1rem);
    max-height: 70vh; overflow-y: auto; z-index: 1060;
  }
  /* ── NO BREADCRUMBS ON PHONES (Graham 2026-08-31: "Drop bread crumbs on all mobile pages") — the
     "CRM · Inbox" titlebar goes on every page; a header that then holds no visible button collapses
     entirely so it can't leave an empty band. Back/Action buttons keep showing — they are controls,
     not identity. ── */
  .page-header .adm-titlebar { display: none !important; }
  .page-header:not(:has(a.btn, button.btn, .col-auto)) { display: none !important; }
  /* …and with the titlebar gone, the desk's 24px+24px of top margins is a blank band above the first
     card (CRM mobile audit 2026-08-31: ~90px of nothing before "Back to the contact"). Tighten both. */
  .page-body { margin-top: .5rem !important; }
  .page-header { margin-top: .5rem !important; }
}

/* #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. */
/* Folds on TABLET too (parity audit 2026-08-28): the widest opt-in grids (Agency's 14-col sites list, the
   orders/products grids) don't fit a 768–991 tablet any more than a phone. admin-cards.js sets the class +
   data-labels unconditionally at init, so widening the CSS breakpoint alone is enough. js-grid stays
   phone-only — those grids mostly fit a tablet. */
@media (max-width: 991.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;
    /* An unbroken token (a long email in "chase rashid.khan@…") clipped clean off the card's right
       edge (CRM mobile round-2 #20) — wrap anywhere rather than overflow. */
    overflow-wrap: anywhere;
  }
  /* A desktop text-nowrap cell must wrap once it becomes a card line (full-admin audit 2026-08-31:
     the recycle bin's Restore/Delete-forever/inline-confirm cluster rode one nowrap cell). */
  .adm-cards td.text-nowrap { white-space: normal !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; }
  /* A cell holding SEVERAL lines (one <div> per package on a contract, a name over its key) — as flex
     items they sit side by side in cramped columns that clip (CRM mobile audit 2026-08-31). .adm-stack
     turns that cell into label-above, lines-below. */
  .adm-cards td.adm-stack { flex-direction: column; align-items: flex-start; gap: .1rem; }
  .adm-cards td.adm-stack::before { flex-basis: auto; }
  /* 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; }
}

/* BROWSE/MANAGE/SWIPE is PHONE-ONLY (Graham 2026-08-29 mobile QA fix). It used to live inside the
   max-width:991.98px block above, so it hid the row's actions/checkbox/handle across the whole 0-991 range —
   while swipe is gated at min-width:768px. That left the 768-991 TABLET band with NO action buttons AND no
   swipe: no way to edit/copy/delete or reorder a page from the list. Scoped to phones now; the tablet band
   keeps the icon-only Edit/Copy/Delete buttons from the 991 block above. */
@media (max-width: 767.98px) {

  /* ── 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; }
  /* Tab mode (Graham 2026-08-29): the bar shows ONE section at a time. Phone-scoped so a section is NEVER
     hidden on desktop — even if the viewport is resized up after the tabs initialised. */
  .adm-sec-hidden { display: none !important; }
  .adm-tabs { gap: .4rem; }
}

/* #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: the 40px tap floor made Name / Email / Phone read as four gapped bands (Graham
     2026-08-31: "see Name, Email, Phone without the space between" / "keep pin a note but remove the
     margin"). 30px keeps a usable full-width tap row while the stack reads as ONE tight block; the
     rows span the card's width, which is most of what makes them easy to hit. */
  .crm-who-line { min-height: 30px; display: inline-flex; align-items: center; line-height: 1.3; }
  button.crm-who-pin { min-height: 30px; }
}

/* 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-due .crm-inbox-cols-due,
html.crm-col-hide-due .crm-inbox-due { display: none !important; }
html.crm-col-hide-responded .crm-inbox-responded { display: none !important; }
html.crm-col-hide-cat .crm-inbox-row-top .crm-stage-dot,
html.crm-col-hide-cat .crm-inbox-cat-cell { 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 fits the widest tenant now: the PPC row wears a labelled "Spend" button (Graham 2026-09-01,
       "the spend button is not fitting" — the 2.6rem track was sized for the bare ＋ and Spend spilled out).
       One shared width keeps the qty/price columns aligned across rows. */
    .crm-stage-row { display: grid !important; grid-template-columns: minmax(15rem, 1fr) 7.6rem 6.2rem 5.4rem;
        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.) */

/* BACK TO THE VERTICAL STACK (Graham 2026-08-31, round 9: "Change this back to vertical stack —
   Label Text (Copy link) …, Notes") — the same-day one-line row is reverted; what SURVIVES from it is
   the larger type (14px, "Can these be larger font?") and each line now leads with a text LABEL
   (Email / Phone / Website / Notes, .crm-who-lbl in the markup) with its copy chip after the value. */
.crm-who-line{ font-size:14px; }
.crm-who-lbl{ display:inline-block; min-width:4.6rem; font-size:11px; font-weight:700; letter-spacing:.04em;
    text-transform:uppercase; color:var(--adm-text-muted,#8b95a3); }
button.crm-who-pin{ font-size:13px; }

/* 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); }

/* CONTACT HEADER (_ContactHeader.cshtml, 2026-08-25) — the one identity card the inbox and the deal bench
   both render at the top of the conversation ("one control in two spots"). Admin tokens so it reads the
   same on either page. */
.crm-contact-avatar{ width:2.25rem; height:2.25rem; border-radius:50%; flex-shrink:0;
    display:inline-flex; align-items:center; justify-content:center;
    background:var(--adm-accent-soft,#e7effb); color:var(--adm-accent,#206bc4); font-size:1.1rem; }
.crm-contact-reach{ font-size:.8125rem; }
.crm-contact-reach a{ text-decoration:none; }
.crm-contact-reach a:hover span{ text-decoration:underline; }
/* The avatar + name link to the client record — a quiet affordance that lifts on hover. */
a.crm-contact-avatar{ text-decoration:none; transition:background .12s, color .12s; }
a.crm-contact-avatar:hover{ background:var(--adm-accent,#206bc4); color:#fff; }
.crm-contact-namelink{ text-decoration:none; cursor:pointer; }
.crm-contact-namelink:hover{ text-decoration:underline; }
/* PHONE ACCORDION (Graham 2026-08-31, compact mockup 05): the reach rows live in <details
   class="crm-contact-more" open>. Desktop hides the summary AND un-contains ::details-content, so the
   lines show whatever the open state is (the rotate-while-closed case); a phone shows the one-line
   "Details" summary and lets <details> do its native thing — no display rules on the collapsing child. */
.crm-contact-more > summary{ list-style:none; }
.crm-contact-more > summary::-webkit-details-marker{ display:none; }
.crm-contact-more-s{ display:none; cursor:pointer; user-select:none; font-size:.78rem; font-weight:600;
    color:var(--adm-accent,#206bc4); margin-top:.15rem; }
.crm-contact-more-s .ti{ transition:transform .15s; display:inline-block; }
.crm-contact-more[open] > .crm-contact-more-s .ti{ transform:rotate(180deg); }
@media (min-width: 768px){
    .crm-contact-more::details-content{ content-visibility:visible; }
}
@media (max-width: 767.98px){
    .crm-contact-more-s{ display:block; }
    /* The whole card on a diet: one compact line unless Details is opened. */
    .crm-contact-header{ margin-bottom:.5rem !important; }
    .crm-contact-header .card-body{ padding:.35rem .6rem; }
    .crm-contact-avatar{ width:1.75rem; height:1.75rem; font-size:.85rem; }
    .crm-contact-header .fs-3{ font-size:1rem !important; }
}

/* QUOTE VIEW LOG (Graham 2026-08-31): the quotes-list "opened N×" opens a small anchored panel of
   per-view rows — date · time · IP. A <details> with an absolute panel so the one-line quote row
   never grows; per the disclosure rules, no display set on the collapsing child. */
.crm-qviews{ position:relative; display:inline-block; }
.crm-qviews > summary{ list-style:none; cursor:pointer; }
.crm-qviews > summary::-webkit-details-marker{ display:none; }
.crm-qviews > summary:hover{ text-decoration:underline; }
.crm-qviews-panel{ position:absolute; left:0; top:calc(100% + 4px); z-index:1050; min-width:17rem;
    background:var(--tblr-bg-surface,#fff); border:1px solid var(--adm-border,#dde4ec); border-radius:8px;
    box-shadow:0 6px 18px rgba(10,20,40,.12); padding:.4rem .6rem; }
.crm-qviews-panel .qvr{ display:flex; gap:1rem; font-size:.8rem; padding:.15rem 0; white-space:nowrap; }
.crm-qviews-panel .qvr span:first-child{ min-width:6.5rem; }

/* CONVERSATION READING — the newest-first email thread (_ConversationReading.cshtml). Moved out of
   inbox.css (2026-08-25) so the deal page's Message tab renders the SAME bubbles as the inbox; the deal
   bench does not load inbox.css. The flat/chain margin control already lives here (html.crm-thread-flat),
   and a plain .crm-thread-flat wrapper forces flat where there is no toggle (the deal Message tab). */
.crm-focused-body{ line-height:1.5; word-break:break-word; }
.crm-focused-body--plain{ white-space:pre-wrap; }
.crm-focused-body img{ max-width:100%; height:auto; }
.crm-inbox-thread{ display:flex; flex-direction:column; gap:.25rem; }
.crm-thread-item{ border:1px solid var(--tblr-border-color); border-radius:.375rem; background:var(--tblr-bg-surface,#fff); }
.crm-thread-item--out > .crm-thread-bar{ background: #f2f7ff; }
.crm-thread-bar{ padding:.3rem .6rem; cursor:pointer; list-style:none; user-select:none; }
.crm-thread-bar::-webkit-details-marker{ display:none; }
.crm-thread-item[open] > .crm-thread-bar{ border-bottom:1px solid var(--tblr-border-color); background:var(--tblr-bg-surface-secondary,#f7f9fb); }
.crm-thread-body{ padding:.6rem .75rem; }
.crm-thread-flat .crm-thread-item{ margin-left:0 !important; }

/* SCHEDULER CALENDAR (Scheduler.cshtml, 2026-08-25) — a month-at-a-glance grid of bookings/blocks. */
.crm-cal-grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:.25rem; }
.crm-cal-dow{ text-align:center; font-size:.68rem; font-weight:700; color:var(--adm-text-muted,#94a3b8);
    text-transform:uppercase; padding:.2rem 0; }
.crm-cal-day{ position:relative; aspect-ratio:1; display:flex; flex-direction:column; align-items:center;
    justify-content:center; border-radius:8px; font-size:.85rem; color:var(--adm-text-muted,#94a3b8); }
.crm-cal-day.crm-cal-has{ color:var(--adm-text,#1f2937); background:var(--adm-accent-soft,#e7effb); font-weight:600; }
.crm-cal-today{ box-shadow:0 0 0 2px var(--adm-accent,#206bc4) inset; }
.crm-cal-dot{ margin-top:2px; font-size:.6rem; min-width:1rem; height:1rem; line-height:1rem; border-radius:1rem;
    background:var(--adm-accent,#206bc4); color:#fff; padding:0 .2rem; }

/* OUTLOOK-STYLE FULL CALENDAR (Graham 2026-08-25: "Calendar should be full width … clickable to add … think
   outlook"). The --full variant grows the cells so each day can list its bookings/blocks as chips, and every
   day is a real <button> (reset here) so clicking it pre-fills the block-time bar. */
.crm-cal-grid--full{ gap:.35rem; }
.crm-cal-grid--full .crm-cal-day{ aspect-ratio:auto; min-height:6.75rem; align-items:stretch; justify-content:flex-start;
    padding:.3rem; border:1px solid var(--tblr-border-color,#e5e7eb); background:var(--tblr-bg-surface,#fff);
    text-align:left; cursor:pointer; overflow:hidden; font:inherit; }
.crm-cal-grid--full .crm-cal-day:hover{ background:var(--adm-accent-soft,#e7effb); border-color:var(--adm-accent,#206bc4); }
.crm-cal-grid--full .crm-cal-day.crm-cal-picked{ box-shadow:0 0 0 2px var(--adm-accent,#206bc4) inset; }
.crm-cal-num{ font-size:.8rem; font-weight:600; color:var(--adm-text,#1f2937); align-self:flex-end; }
/* Past days sit quiet — you book forward, not back (Graham 2026-08-25: "cannot select out of date"). */
.crm-cal-grid--full .crm-cal-past{ background:var(--tblr-bg-surface-secondary,#f7f9fb); }
.crm-cal-grid--full .crm-cal-past .crm-cal-num{ color:var(--adm-text-muted,#94a3b8); font-weight:400; }
.crm-cal-grid--full .crm-cal-day:disabled{ cursor:not-allowed; opacity:.55; }
.crm-cal-grid--full .crm-cal-day:disabled:hover{ background:var(--tblr-bg-surface-secondary,#f7f9fb); border-color:var(--tblr-border-color,#e5e7eb); }

/* IN-PLACE "About this client" note (Graham 2026-08-25) — the read view reads as clickable to edit. */
.crm-about-inplace[role="button"]{ cursor:pointer; border-radius:8px; padding:.35rem .5rem; margin:-.35rem -.5rem;
    transition:background .12s ease; }
.crm-about-inplace[role="button"]:hover{ background:var(--adm-accent-soft,#e7effb); }

/* Quiet link — no underline until hovered (Graham 2026-08-26: "add link, no line, hover only"). */
.crm-hover-link{ text-decoration:none; }
.crm-hover-link:hover, .crm-hover-link:focus-visible{ text-decoration:underline; }
.crm-cal-chip{ display:block; font-size:.66rem; line-height:1.25; margin-top:2px; padding:1px 5px; border-radius:4px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.crm-cal-chip--book{ background:var(--tblr-green-lt,#e6f6ec); color:var(--tblr-green,#2fb344); }
.crm-cal-chip--block{ background:var(--tblr-bg-surface-secondary,#eef1f6); color:var(--adm-text-muted,#667085); }
.crm-cal-more{ display:block; font-size:.62rem; color:var(--adm-text-muted,#94a3b8); margin-top:1px; padding-left:5px; }

/* SETTINGS GEAR POP-DOWN (Graham 2026-08-25: "settings should be in a gear and a pop down"). A native <details>
   whose panel drops as an overlay under the gear — nothing sets display on the collapsing child, per the repo's
   <details> rule; the summary marker is hidden so the gear button reads as a button. */
.crm-sched-gear{ position:relative; }
.crm-sched-gear > summary{ list-style:none; cursor:pointer; }
.crm-sched-gear > summary::-webkit-details-marker{ display:none; }
.crm-sched-gear[open] > summary{ background:var(--adm-accent-soft,#e7effb); border-color:var(--adm-accent,#206bc4); }
/* An absolutely-positioned panel escapes the native <details> collapse (::details-content content-visibility),
   so it would show even when closed. This is a pop-over, not animated collapsing content, so explicitly hiding
   it while closed is correct here (the repo's "never display:none a collapsing child" rule guards animation we
   are not using). */
.crm-sched-gear:not([open]) > .crm-sched-panel{ display:none; }
.crm-sched-panel{ position:absolute; right:0; top:calc(100% + .4rem); z-index:1040; width:min(94vw,460px);
    max-height:80vh; overflow-y:auto; background:var(--tblr-bg-surface,#fff); border:1px solid var(--tblr-border-color,#e5e7eb);
    border-radius:12px; box-shadow:0 12px 32px rgba(0,0,0,.16); padding:.85rem; }
@media (max-width: 575.98px){ .crm-sched-panel{ position:fixed; left:1rem; right:1rem; width:auto; } }

/* The cart panel ADOPTED INTO THE SUMMARY RAIL (Graham 2026-09-01) — the rail card already frames it, so
   the panel sheds its own frame and its sticky chase; it scrolls with the rail like every other card. */
.crm-cart-inrail { position: static; top: auto; background: transparent; border: 0; padding: 0; }
.crm-cart-inrail .crm-cart-h:first-child { margin-top: .25rem; }

/* ONE LINE OF TABS (Graham 2026-09-01: "These should all fit on one line") — eight tabs were wrapping
   History onto a second row. On a desk the tabs tighten and the strip refuses to wrap, scrolling
   sideways in the rare window too narrow for all eight; phones keep the wrapping they had. */
@media (min-width: 768px) {
    .crm-bench-strip { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin; }
    .crm-bench-tab { padding: .6rem .7rem .55rem; white-space: nowrap; flex: 0 0 auto; }
    .crm-bench-tab.on { padding-bottom: calc(.55rem + 1px); }
}
/* Mid-width desks: tighten further so all eight genuinely FIT rather than scroll. */
@media (min-width: 768px) and (max-width: 1500px) {
    .crm-bench-tab { font-size: .85rem; padding: .6rem .5rem .55rem; }
    .crm-bench-strip { gap: 2px; padding-left: .35rem; padding-right: .35rem; }
}
/* …and in that same band the 1/2/3 step chips step aside — the tab names already carry the meaning,
   and those ~70px are what let all eight tabs fit a 3-column bench at laptop widths. */
@media (min-width: 768px) and (max-width: 1600px) {
    .crm-bench-tab .crm-tab-step { display: none; }
}

/* WIDER RAIL + COLLAPSE (Graham 2026-09-01: "make the right side fit by making it a little wider … add a
   collapse toggle so the email etc is full width"). 70/30 instead of Bootstrap's 75/25 so the cart lines
   stop wrapping mid-price; .crm-main-full is the collapsed-rail state — the workspace takes everything. */
@media (min-width: 1200px) {
    #crmMainCol { width: 70%; }
    #crmSummaryRail { width: 30%; }
    #crmMainCol.crm-main-full { width: 100%; }
    /* The toggle pins to the strip card's right edge; the strip reserves the lane so tabs never slide under it. */
    #crmBenchTabs { position: relative; }
    #crmBenchTabs .crm-bench-strip { padding-right: 2.6rem; }
    #crmRailShow { position: absolute; top: .55rem; right: .45rem; z-index: 3;
        border: 0; background: transparent; color: var(--tblr-secondary, #667382);
        width: 32px; height: 32px; border-radius: 8px; align-items: center; justify-content: center; cursor: pointer; }
    #crmRailShow:hover { background: rgba(255, 255, 255, .7); color: var(--tblr-body-color, #1f2733); }
}

/* Audit 2026-09-01: the Lost fold's toggle strip measured 21px tall — under the 40px touch floor on any
   width a finger drives (the desktop board serves tablets too). */
@media (max-width: 1199.98px) {
    #crmLostToggle { min-height: 40px; }
}


/* TinyMCE "View HTML" (code plugin) dialog fills the page (Graham 2026-09-04: "expand the HTML editor so
   it fills the page. Every edit should be 80% ish"). Scoped by :has(.tox-textarea) so link/table dialogs
   keep their compact size; only the source-editing dialog grows. */
.tox .tox-dialog:has(textarea.tox-textarea) { width: min(1100px, 94vw) !important; max-width: 94vw !important; max-height: 92vh !important; }
.tox .tox-dialog:has(textarea.tox-textarea) .tox-textarea { font-family: var(--tblr-font-monospace, monospace); font-size: .85rem; }
/* THE SAVE BUTTON WAS OFF THE BOTTOM (Graham 2026-09-10: "Is there a save option. it did not take the
   html changes"). TinyMCE fixes its large dialog at height:650px, and the rule above used to force the
   textarea to min-height:72vh - on any screen taller than about 900px that is more than 650px, so the
   header and footer were squeezed out of the box and clipped by overflow:hidden. Closing with the X
   discards, so the edit was lost. Now the DIALOG is the thing that is 92vh tall and the textarea fills
   whatever is left after the header and footer, which never move. */
.tox .tox-dialog:has(textarea.tox-textarea) { height: 92vh !important; }
.tox .tox-dialog:has(textarea.tox-textarea) .tox-dialog__header,
.tox .tox-dialog:has(textarea.tox-textarea) .tox-dialog__footer { flex: 0 0 auto; }
.tox .tox-dialog:has(textarea.tox-textarea) .tox-dialog__body-content { max-height: none !important; }
.tox .tox-dialog:has(textarea.tox-textarea) .tox-form,
.tox .tox-dialog:has(textarea.tox-textarea) .tox-form__group { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.tox .tox-dialog:has(textarea.tox-textarea) .tox-textarea { flex: 1 1 auto; min-height: 0; resize: none; }
/* ── CONTROL EDITOR SHELL (2026-09-05) ─────────────────────────────────────────────────────────
   Graham's pick from the 25-option report: #1 classic three-column (Style | Content | Preview),
   #23 adaptive density (laptop folds preview into a drawer, phone becomes tabs #15),
   #12 device toggle inside the preview, #20 bulk grid, #24 compare overlay.
   Markup contract + runtime: wwwroot/js/control-editor.js. Shared by every converted control. */
.ce-shell { display: flex; align-items: stretch; }
/* EACH PANE SCROLLS, NOT THE PAGE (2026-09-09). .ce-item-scroll has been on the shell's card bodies
   since it was built, but its only rule lived in FeatureSection/Index.cshtml's page-local <style>
   block - admin.css carried two `max-height: none` overrides of a base rule that was not here. So on
   every OTHER page using the class the whole document scrolled instead: reaching the foot of one pane
   took the item list, the Look pane and the save chip off screen with it, which is most of what
   "things are not fitting well" describes.

   Written as flex rather than as that page's `calc(100vh - 300px)`: the constant was measured against
   different chrome, and copying it would put some controls' last field under the fold. The panes are
   bounded by the shell, the card fills the pane, the body takes what is left. Left alone below 900px,
   where the panes stack and the page SHOULD scroll.

   AND NOT INSIDE THE FRAME (Graham 2026-09-09: "Please use all the space"). The page editor embeds
   this editor in an iframe that the parent SIZES TO ITS CONTENT (editor-edit.js sync(): the tallest
   child's bottom, floor 420). Inside that frame 100vh is the frame's own height, so "content is as
   tall as the frame" and "frame is as tall as the content" chase each other and settle on the floor -
   which is why the embedded editor collapsed into a short box with a screen of blank page under it.
   In the frame the content sets the height and the PARENT page scrolls, which is correct there; the
   panes only take over the scrolling when they own the viewport. */
@media (min-width: 900px) {
    /* --hdr-live-h: the header designer's "Make this one live" bar above the editor takes its own height off the window
       (set inline on .ce-standalone when the bar is showing; nothing elsewhere sets it, so it is 0 for every other editor). */
    /* --ce-lang-h: the language bar above the generic control editor (ControlEditor.cshtml, multilingual sites only). */
    html:not(.ce-embedded) .ce-standalone { height: calc(100vh - 104px - var(--hdr-live-h, 0px) - var(--ce-lang-h, 0px)); min-height: 420px; }
    html:not(.ce-embedded) .ce-standalone > .ce-shell { height: 100%; min-height: 0; }
    html:not(.ce-embedded) .ce-standalone .ce-shell > [data-ce-pane] { min-height: 0; }
    html:not(.ce-embedded) .ce-standalone .ce-shell > [data-ce-pane] > .card { height: 100%; min-height: 0; display: flex; flex-direction: column; }
    html:not(.ce-embedded) .ce-standalone .ce-item-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
    /* The Expand overlay gives a pane 90% of the screen; it must not inherit the shell's height. */
    html:not(.ce-embedded) .ce-standalone .ce-max-body { height: auto; }
}
/* Default width sized so the layout picker shows THREE tiles per row (Graham 2026-09-06 "the
   default left column to have 3 items"): 3 x 104px tiles + gaps + card padding. Still fluid —
   dragging the handle re-flows the auto-fill grid; a user's dragged width (--ce-w1) wins. */
/* 452px was the floor, so the drag handle stopped almost where it started and the pane read as fixed
   (Graham 2026-09-08: "My left coloumn is locked at wide. It needs to shrink at least by 1/3"). 300px
   is that third, and it is the range a style pane already lives in — .ce-style.ce-look sits at 296.
   Nothing in here needs 452: the tile pickers are fluid grids and the selects are full-width. */
.ce-style { flex: 0 0 var(--ce-w1, 484px); min-width: 300px; }
/* The generic LOOK pane (2026-09-07) is a plain field list, NOT the Section Designer's tile
   picker — inheriting that 484px/452px floor left half the column empty and squeezed the item
   form (Graham's standing "wasted space on right"). Narrower default, same drag behaviour. */
/* Deliberately NOT --ce-w1: that global carries a percentage older builds saved from Section
   Designer drags, which overrode this default and re-widened the column (review 2026-09-07).
   Ad-hoc drags set inline flex, and Save layout stores per-control widths, so both still win. */
.ce-style.ce-look { flex: 0 0 340px; min-width: 296px; }
/* An Items row whose control has no heading shows what the item HOLDS instead (the alert's message,
   the slide's image, the embed's URL). Muted and italic so it never reads as a title somebody typed. */
.fs-item-title.fs-item-standin { color: #7b8797; font-style: italic; }

/* THE ITEMS LIST IS A LIST YOU MOVE THROUGH, and it did not look like one (2026-09-19: "some way of
   letting people know this is a way to navigate between items"). Only the title was blue, which reads
   as "edit this text in place"; the row you were on was a pale tint easy to miss beside the others.
   Three signals, no new control to learn: the whole row answers the pointer, a chevron on the right
   says it opens something, and the open row carries a solid accent bar down its edge. */
.fs-items tbody tr[data-id] { cursor: pointer; }
.fs-items tbody tr[data-id]:hover td { background: #f1f5fb; }
.fs-items td.fs-item-go { width: 1%; padding-left: 0; text-align: right; color: #c3ccd9; }
.fs-items tbody tr[data-id]:hover .fs-item-go { color: #6b86a8; }
/* The bar is drawn on the FIRST cell rather than the row: a <tr> ignores border-left in a collapsed
   table, and a box-shadow inset on every cell would draw a line between each column. */
.fs-items tbody tr.ce-item-on > td:first-child { box-shadow: inset 3px 0 0 0 #206bc4; }
.fs-items tbody tr.ce-item-on .fs-item-title { font-weight: 600; }
.fs-items tbody tr.ce-item-on .fs-item-go { color: #206bc4; }
/* The preview answers the pointer where clicking it opens an item (2026-09-20). While the pencil is on, the
   preview is text you are typing into, so the caret comes back. */
.ce-shell:has(.ce-item-form) .ce-pv-frame { cursor: pointer; }
.ce-shell.ce-editing .ce-pv-frame { cursor: auto; }
/* OTHER SECTIONS ON THIS PAGE (2026-09-19): the jump list above "Also on these pages". Open by default and
   tinted a shade cooler, so the two blocks under the Items list read as two different journeys - what else is
   on this page, and where else this control lives. */
.ce-also.ce-others { background: #f5f8fd; border-top-color: #e2e8f2; }
.ce-others-list li { gap: .45rem; }
.ce-others-list i { color: #8fa3bd; font-size: 14px; width: 16px; text-align: center; }
.ce-others-list li.is-here { background: #e9eef6; }
.ce-others-here { font-weight: 600; color: #3f4854; }
.ce-others-list li.is-here .ce-also-n { margin-left: auto; font-style: italic; }
/* The count beside the pane's title: "how many am I stepping through". Quiet - it is a fact, not a button. */
.ce-items-n { font-weight: 400; color: #98a2b3; font-variant-numeric: tabular-nums; margin-left: .35rem; }
/* One line per field, at this width too (the standing quick-edit rule, Graham 2026-09-06 "all one
   line"): the shared partial's 25% label column is ~69px inside a 340px pane, so even a two-word
   label wrapped. Fixed 96px label cell here — fits "Button style" — and the field takes the rest. */
.ce-look .row > .col-3 { flex: 0 0 96px; max-width: 96px; }
.ce-look .row > .col-9 { flex: 1 1 0; max-width: none; }
.ce-look .row > .col-9.offset-3 { margin-left: 96px; }
.ce-content { flex: 1; min-width: 0; }
.ce-preview { flex: 0 0 var(--ce-w2, 24%); min-width: 240px; }
/* Item pane (2026-09-06, Graham: "three options editable on the screen at the same time") — a fourth
   column between the items list and the preview. When present the style/preview defaults tighten so
   four columns share the width; dragged widths (--ce-w1/w2/w3) still win. */
.ce-item { flex: 0 0 var(--ce-w3, 28%); min-width: 280px; }
/* Four-pane shares (Graham 2026-09-06 "items can be smaller"): the style column keeps its 392px
   three-tile width (this override once shrank it to 26% — the "still 2 tiles" bug), and the Item
   form + preview take bigger defaults so the leftover Items list stays modest. Dragged widths win. */
.ce-shell:has(.ce-item) .ce-style { flex-basis: var(--ce-w1, 484px); }
.ce-shell:has(.ce-item) .ce-style.ce-look { flex-basis: 340px; }
/* THE ITEMS LIST IS A COLUMN OF TITLES (Graham 2026-09-08: "all the default have the left column
   way to big it can be smaller"). It was flex:1, so it swallowed every pixel the fixed panes left
   over — half the screen to show one title — while the FORM beside it, the pane that actually
   needs width, sat at a fixed 28%. Swap which one flexes. Scoped to shells that HAVE a separate
   Item pane: a singleton's form is itself .ce-content and must keep the room. */
.ce-shell:has(.ce-item) .ce-content { flex: 0 0 clamp(200px, 20%, 340px); min-width: 200px; }
.ce-shell:has(.ce-item) .ce-item { flex: 1 1 auto; min-width: 300px; }
/* Four panes: a Look column or the Section Designer's tile picker already claims fixed width, so
   the list tightens again rather than taking it out of the form — the form is what flexes. */
.ce-shell:has(.ce-item):has(.ce-style) .ce-content { flex-basis: clamp(200px, 16%, 280px); }
.ce-shell:has(.ce-item) .ce-preview { flex-basis: var(--ce-w2, 24%); }
.ce-h { flex: 0 0 12px; cursor: col-resize; position: relative; }
.ce-h::after { content: ''; position: absolute; top: 12px; bottom: 12px; left: 5px; width: 2px; border-radius: 1px; background: #dde2ea; }
.ce-h:hover::after { background: var(--tblr-primary, #206bc4); }
.ce-tabs { display: none; gap: .35rem; margin-bottom: .6rem; }
.ce-tab { flex: 1; border: 1px solid #d6dde8; background: #fff; border-radius: 8px; padding: .45rem; font-weight: 600; color: #5e6675; cursor: pointer; }
.ce-tab.active { border-color: var(--tblr-primary, #206bc4); color: var(--tblr-primary, #206bc4); background: #eef4fd; }
.ce-pv-toggle { display: none; }
.ce-pv-head { display: flex; align-items: center; gap: .4rem; }
.ce-pv-name { font-size: .78rem; font-weight: 700; color: #5e6675; text-transform: uppercase; letter-spacing: .04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ce-shell.ce-pv-transient .ce-pv-name { color: var(--tblr-primary, #206bc4); }
.ce-pv-dev { border: 1px solid #d6dde8; background: #fff; border-radius: 7px; padding: .15rem .5rem; cursor: pointer; color: #5e6675; }
.ce-pv-dev.active { border-color: var(--tblr-primary, #206bc4); color: var(--tblr-primary, #206bc4); }
.ce-pv-body { overflow: auto; max-height: calc(100vh - 220px); background: #fff; }
.ce-pv-frame { zoom: .72; padding: .8rem; }
.ce-pv-body.ce-pv-phone { display: flex; justify-content: center; background: #f0f2f6; }
.ce-pv-body.ce-pv-phone .ce-pv-frame { zoom: .9; width: 390px; flex: 0 0 390px; background: #fff; border: 6px solid #26303e; border-radius: 18px; margin: .8rem 0; }
.ce-bulk-bar { display: flex; align-items: center; padding: .6rem .8rem; border-bottom: 1px solid #e3e8ef; }
.ce-bulk-t input, .ce-bulk-t textarea { min-width: 110px; }
.ce-cmp { position: fixed; inset: 0; background: rgba(20, 28, 40, .55); z-index: 90; display: flex; align-items: center; justify-content: center; padding: 2rem; }
.ce-cmp-in { background: #fff; border-radius: 14px; width: min(1500px, 96vw); height: min(860px, 92vh); display: flex; flex-direction: column; overflow: hidden; }
.ce-cmp-head { display: flex; align-items: center; padding: .7rem 1rem; border-bottom: 1px solid #e3e8ef; }
.ce-cmp-cols { flex: 1; display: grid; grid-template-columns: 1fr 1fr; gap: 0; min-height: 0; }
.ce-cmp-col { display: flex; flex-direction: column; min-width: 0; border-right: 1px solid #e3e8ef; }
.ce-cmp-col:last-child { border-right: 0; }
.ce-cmp-bar { display: flex; gap: .5rem; padding: .55rem .8rem; border-bottom: 1px solid #e3e8ef; }
.ce-cmp-pv { flex: 1; overflow: auto; }
.ce-cmp-pv .ce-pv-frame { zoom: .8; }
/* #23 fixed collapse order: <1400px the preview pane folds into a right drawer behind a button */
@media (max-width: 1399.98px) {
    .ce-h2 { display: none; }
    .ce-pv-toggle { display: inline-flex; }
    .ce-preview { display: none; }
    .ce-shell.ce-pv-open .ce-preview { display: block; position: fixed; top: 60px; right: 10px; bottom: 10px; width: min(430px, 92vw); z-index: 70; box-shadow: 0 16px 44px rgba(16, 24, 40, .25); border-radius: 12px; overflow: hidden; background: #fff; }
    .ce-shell.ce-pv-open .ce-pv-body { max-height: none; height: calc(100% - 42px); }
}
/* <900px: tabs (#15). Content is the default tab; panes show one at a time. */
@media (max-width: 899.98px) {
    .ce-tabs { display: flex; }
    .ce-shell { flex-direction: column; }
    .ce-h { display: none; }
    .ce-style, .ce-content, .ce-item, .ce-preview { flex: 1 1 auto; min-width: 0; display: none; position: static; width: auto; }
    .ce-shell[data-ce-show="style"] .ce-style { display: block; }
    .ce-shell[data-ce-show="content"] .ce-content { display: block; }
    .ce-shell[data-ce-show="item"] .ce-item { display: block; }
    .ce-shell[data-ce-show="preview"] .ce-preview { display: block; box-shadow: none; }
    .ce-shell:not([data-ce-show]) .ce-content { display: block; }
    /* TWO WAYS TO THE SAME PANE IS ONE TOO MANY (2026-09-09). Below 1400px the Preview folds into a
       fixed drawer behind a button in the Items header; below 900px the tab bar now owns the panes,
       and the drawer would open on top of whichever tab is showing. The tabs win here - and the
       toggle is a no-op for a singleton anyway, since it only renders when there is an Items list. */
    .ce-pv-toggle { display: none; }
    .ce-shell.ce-pv-open .ce-preview { position: static; width: auto; box-shadow: none; border-radius: 0; }
}
/* Shell standards #15/#16/#20 (2026-09-05): page arrows, on-this-page rail, appears-on footer */
.ce-pn { padding: .3rem .45rem; }
.ce-rail { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-bottom: .8rem; }
.ce-rail-l { font-size: .74rem; font-weight: 700; color: #8a97a8; text-transform: uppercase; letter-spacing: .04em; }
.ce-rail-b { display: inline-flex; align-items: center; gap: .35rem; border: 1px solid #d6dde8; background: #fff; border-radius: 999px; padding: .2rem .7rem; font-size: .8rem; color: #3b4656; text-decoration: none; }
a.ce-rail-b:hover { border-color: #206bc4; color: #206bc4; }
.ce-rail-b.on { border-color: #206bc4; background: #eef4fd; color: #206bc4; font-weight: 600; }
.ce-pv-appears { border-top: 1px solid #e3e8ef; padding: .5rem .8rem; font-size: .78rem; color: #5e6675; display: flex; flex-wrap: wrap; gap: .25rem .5rem; align-items: center; }
.ce-ap { color: #206bc4; text-decoration: none; }
.ce-ap:hover { text-decoration: underline; }
.ce-ap.on { color: #3b4656; font-weight: 700; }
/* One page per row with the count in its own right-hand column (Graham 2026-09-08, picked from three
   options). Run together on one line the names ran into each other - "Burk's Falls (5) Dental
   Technology (4)" reads as one phrase until you find the brackets - and as a bullet list the counts
   still floated wherever the name ended, so 4 against 6 took reading rather than looking.
   flex-basis:100% rather than :has() on the parent, so the header keeps its own line and the three
   surfaces still using the inline run are untouched. */
.ce-ap-h { flex: 0 0 100%; display: flex; align-items: baseline; gap: .5rem; }
.ce-ap-h .ce-ap-n { margin-left: auto; }
.ce-ap-list { flex: 0 0 100%; list-style: none; margin: .35rem 0 0; padding: 0; }
.ce-ap-list li { display: flex; align-items: baseline; gap: .5rem; padding: .12rem .25rem; border-radius: 5px; }
.ce-ap-list li:hover { background: #f2f5f9; }
/* The count column: right-aligned and tabular, so the digits stack whatever the name did. */
.ce-ap-list .ce-ap-n { margin-left: auto; font-variant-numeric: tabular-nums; }
.ce-ap-n { color: #98a2b3; }
.ce-ap-all { flex: 0 0 100%; margin-top: .3rem; align-self: flex-start; border: 0; background: none;
    padding: .1rem 0; color: #206bc4; font: inherit; cursor: pointer; text-align: left; }
.ce-ap-all:hover { text-decoration: underline; }

/* ── STYLE TILES ────────────────────────────────────────────────────────────────────────────────
   The picker every control uses to choose a look (field type "tiles" in _EditorField, drawings from
   Services/LayoutThumbs). These rules were copied into FOUR pages — Section Designer twice, the
   document library and FAQ — which is exactly the duplication that makes adding a fifth control feel
   expensive (Graham 2026-09-08: "Reuse code across pages so this does not turn into a monster").
   One copy here; the pages keep only what is genuinely theirs, like a per-pane column count.
   .fs-layout-* keeps its name so the four existing pickers keep working while they convert. */
/* AUTO-FIT, NOT THREE FIXED COLUMNS (Graham 2026-09-09: "Even the side bar is mismatched"). At
   repeat(3, 1fr) a two-option picker drew two tiles and left the third column empty inside a
   full-width bordered box, so Corners and Button style sat in the sidebar as ragged half-filled
   panels next to a full one. auto-FIT collapses the empty tracks, so every picker fills its box
   whatever it offers: two options become two half-width tiles, seven fit across the wide Item pane
   in one row instead of three rows behind a scrollbar.

   The 80px floor is measured, not guessed. The left pane's tile row has 285px of usable width and
   the pane's own scrollbar takes 15 of them: at 96px that bought two columns, at 80 it buys three,
   and a fourth would need 344px so it cannot creep in. Template's three options land on one row and
   Height's six on two rather than three. */
/* NO INNER SCROLLBAR (Graham 2026-09-09: "remove any scroll bars, just expand"). The 340px cap and its
   overflow:auto were written when a picker held six tiles and the pane had to stay compact. With the
   templates grouped under headings, a scroll box inside a scrolling pane means a heading can be visible
   while its own tiles are hidden below the fold of a box the reader did not know was there - and the
   scrollbar itself lands between the groups, which reads as a divider. The group headings do the job
   the cap was doing: they break the run up so it can be skimmed rather than scrolled. */
.ce-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: .5rem;
    padding: .35rem; border: 1px solid #e6e9ee; border-radius: 10px; background: #fbfcfd; }
/* THREE A SIDE once there is enough to be worth scanning (Graham 2026-09-09: "this should be 3 a
   side"). auto-fit with an 80px floor was giving two in the side pane, which turns twenty-seven
   templates into a fourteen-row column nobody reads to the bottom of. Three fixed columns fit the pane
   and halve the scroll.
   ONE TILE SIZE EVERYWHERE (Graham 2026-09-19: "All these buttons can be same size even if there is just
   two"). This used to be guarded by :has(> :nth-child(4)) so a two-tile picker (Corners, Button style)
   stretched each tile to half the pane rather than leave a gap beside them; two giant tiles under a row
   of normal ones read worse than the gap. Every picker is now three a side. */
.ce-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Expanded to 90% of the screen, three fixed columns would be three enormous tiles. */
.ce-max .ce-tiles,
.ce-max .ce-tiles:has(> :nth-child(4)) { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); max-height: none; overflow-y: visible; }
/* ONE OBJECT, NOT THREE FRAMES (Graham 2026-09-09: "the banners are getting small with the border,
   border border border… you could just have the image with rounded corners and then the text tight to
   the image", then: "add a colour to the bottom of the banner with the title in it so it will become
   the button").
   The tile used to be a bordered card, holding a bordered drawing, holding a picture inset inside its
   own margin - three nested edges, each stealing width from the only part anybody looks at. Now the
   card IS the button: one rounded rectangle, the drawing flush to its edges, and the name in a coloured
   bar across the foot. Nothing is nested inside anything.
   The outline is a box-shadow rather than a border on purpose. A border changes the box, so selecting a
   tile would nudge every tile in the row; a shadow paints outside the flow and nothing moves. */
.fs-layout-card { position: relative; display: flex; flex-direction: column;
    gap: 0; padding: 0; cursor: pointer; border: 0; border-radius: 9px; overflow: hidden;
    background: #fff; box-shadow: 0 0 0 1px #e3e8ee; transition: box-shadow .12s; }
.fs-layout-card input { position: absolute; opacity: 0; pointer-events: none; }
.fs-layout-card:hover { box-shadow: 0 0 0 1px #aab6c4; }
.fs-layout-card:has(input:checked) { box-shadow: 0 0 0 2px var(--tblr-primary, #206bc4); }
/* THE ONE IN USE, FOUND AMONG SIXTY-TWO (Graham 2026-08-21: "Highlight and set focus on the button
   (scroll to active spot)" — written for the Section Designer, which offers that many layouts).
   The checked border alone is a 2px colour change you have to already be looking at; this adds a ring
   that carries across a scroll, and the focus-visible rule below adds a second, separate signal —
   focus moves as soon as you arrow away, while the mark stays on the layout that is actually saved.
   Applied by the picker's script, which also scrolls it into view. */
/* The saved-and-scrolled-to mark. An outline rather than a shadow now, because the shadow is carrying
   the selected state above and two shadows on one element would fight for the same ring. */
.fs-layout-card.is-current { outline: 3px solid rgba(32,107,196,.28); outline-offset: -3px; }
.fs-layout-card:has(input:focus-visible) { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: 2px; }
.fs-layout-thumb { display: block; width: 100%; }
/* Flush to the card's edges, which the card clips for it - no radius and no border of its own. */
.fs-layout-thumb svg { display: block; width: 100%; height: auto; background: #fff;
    border-radius: 0; border: 0; }
/* The name is the button's face, not a caption under a picture: full width, its own ground, and it
   takes the accent when the tile is the chosen one. */
.fs-layout-name { display: block; width: 100%; padding: .3rem .35rem;
    font-size: .72rem; font-weight: 600; line-height: 1.25; text-align: center;
    background: #eef1f5; color: #4a5563; }
.fs-layout-card:has(input:checked) .fs-layout-name {
    background: var(--tblr-primary, #206bc4); color: #fff;
}
/* The sentence under the tiles, which changes with the choice. Tight to the tiles (its own mt-1) and
   inside the field's wrapper, so picker + explanation read as one field rather than two blocks — the
   gap to whatever follows is the wrapper's, not this line's. */
.ce-tiles-desc { line-height: 1.35; }

/* ── ALSO ON THESE PAGES ───────────────────────────────────────────────────────────────────────
   The shared block at the foot of the centre pane on every control editor (Pages/Shared/_CeAlsoOn).
   Awareness was the first ask, so it is visible rather than folded; it renders nothing at all when
   the control is on this page alone. Quiet by construction: no card, no shadow, a hairline and a
   tinted ground so it reads as a footnote to the pane rather than another section competing with it. */
/* A small gap above it (Graham 2026-09-08: "Can you add a small space / above shared"). Flush against
   the last row of the list the two ran together as one table; the space is what makes it read as a
   footnote to the pane rather than another row of it. */
.ce-also { margin-top: .6rem; border-top: 1px solid #e6e9ee; background: #fafbfd;
    padding: .6rem .85rem .7rem; font-size: .82rem; color: #5e6675; }
/* The header is the <summary> of a folded block (2026-09-19): the default disclosure triangle is
   replaced by a caret of our own on the right of the title, so the count stays hard right. */
.ce-also-h { display: flex; align-items: baseline; gap: .5rem; cursor: pointer; list-style: none; }
.ce-also-h::-webkit-details-marker { display: none; }
.ce-also-h b { font-size: .82rem; }
.ce-also-h b::after { content: "›"; display: inline-block; margin-left: .35rem; color: #98a2b3;
    font-weight: 400; transform: rotate(90deg); transform-origin: 50% 45%; transition: transform .12s; }
.ce-also:not([open]) .ce-also-h b::after { transform: none; }
.ce-also-h:hover b { color: #206bc4; }
.ce-also-n { color: #98a2b3; font-variant-numeric: tabular-nums; }
.ce-also-h .ce-also-n { margin-left: auto; }
.ce-also-list { list-style: none; margin: .35rem 0 0; padding: 0; }
.ce-also-list li { display: flex; align-items: baseline; gap: .5rem; padding: .12rem .25rem; border-radius: 5px; }
.ce-also-list li:hover { background: #eef2f7; }
.ce-also-list a { color: #206bc4; text-decoration: none; }
.ce-also-list a:hover { text-decoration: underline; }
.ce-also-list .ce-also-n { margin-left: auto; }
.ce-also-all { margin-top: .3rem; border: 0; background: none; padding: .1rem 0; color: #206bc4;
    font: inherit; font-size: .82rem; cursor: pointer; }
.ce-also-all:hover { text-decoration: underline; }
/* The one case where an edit really does reach the other pages. Ochre, not the accent, because it is
   a caution and not a link. */
.ce-also.is-shared { background: #fdf7ec; border-top-color: #ecdcb8; }
.ce-also.is-shared .ce-also-h b { color: #8a5a12; }
.ce-also-warn { color: #8a5a12; margin-top: .15rem; }
/* VS-style pane docking (2026-09-06): grips, tab stacks, drop cues, the inline Layout gallery */
.ce-grip { cursor: grab; color: #9aa7b8; margin-right: .5rem; user-select: none; font-size: .9rem; }
.ce-grip:active { cursor: grabbing; }
/* THE ROW GRIP, which had no rule at all. .ce-grip drags a PANE and got styled when docking was built;
   .fs-grip drags an ITEM in the list and never did, so it was a bare span holding the character ⠿ —
   the browser gave it the text I-beam and let you select it, which is the opposite of "you can pick
   this up" (Graham 2026-09-09: "The move shows ibeam, not hand"). Same treatment as its sibling, and
   shared, because the items list is rendered by the control editor AND the Section Designer. */
.fs-grip { cursor: grab; color: #9aa7b8; user-select: none; font-size: .9rem; }
.fs-grip:active { cursor: grabbing; }
.ce-stack { display: flex; flex-direction: column; min-width: 0; }
.ce-stack-tabs { display: flex; gap: .25rem; margin-bottom: .45rem; }
.ce-stack-tab { border: 1px solid #d6dde8; border-bottom: 0; background: #eef1f5; border-radius: 8px 8px 0 0; padding: .3rem .8rem; font-size: .8rem; font-weight: 600; color: #5e6675; cursor: pointer; }
.ce-stack-tab.active { background: #fff; color: #206bc4; border-color: #b9cef2; }
.ce-stack > [data-ce-pane] { flex: 1; min-height: 0; }
/* STACKED WINDOWS (2026-09-09). The third of Visual Studio's three modes: panes one above another in
   one column, all visible at once, which is exactly what .ce-stack is NOT — that shows one and hides
   its siblings behind tabs. Same column flexbox, no tab bar, and a horizontal handle between each
   pair.
   min-height is the load-bearing line. Every pane in this editor was written to fill its column, so
   two sharing one vertically will happily let the lower one collapse to nothing under a drag; 90px
   keeps a card header and a field on screen, which is enough to grab and pull back. */
.ce-vstack { display: flex; flex-direction: column; min-width: 0; }
.ce-vstack > [data-ce-pane] { min-height: 90px; min-width: 0; overflow: auto; }
/* The handle turns through 90 degrees: a full-width bar with a row cursor, rather than the 12px
   column the shell's own handles are. Same .ce-h class, so one wiring covers both axes. */
.ce-h.ce-hy { flex: 0 0 10px; cursor: row-resize; width: auto; }
.ce-h.ce-hy::after { content: ""; position: absolute; left: 12px; right: 12px; top: 50%;
    height: 2px; transform: translateY(-50%); border-radius: 2px; background: #dbe2ea; }
.ce-h.ce-hy:hover::after { background: var(--tblr-primary, #206bc4); }
.ce-stack-hidden { display: none !important; }
.ce-slot { min-width: 0; }
/* THE DOCK GUIDE. Each indicator draws where the pane will land, so the edge bars point sideways for a
   new column and the stack bars point along the edge they will sit against. The dashed outline stays
   the "tab these together" signal it already was. */
.ce-drop-before { box-shadow: -4px 0 0 0 #206bc4; }
.ce-drop-after { box-shadow: 4px 0 0 0 #206bc4; }
.ce-drop-into { outline: 2px dashed #206bc4; outline-offset: -2px; }
.ce-drop-above { box-shadow: inset 0 4px 0 0 #206bc4; }
.ce-drop-below { box-shadow: inset 0 -4px 0 0 #206bc4; }
/* ── THE TOP BAND (2026-09-18, Graham: "Drag and drop need to allow attaching at top (full width)") ────
   A full-width row above the columns, docked by dropping a pane on the bar that appears across the top
   while one is being dragged. control-editor.js holds the model ({t:[..]} first in the arrangement);
   this is the whole of its layout, and it is deliberately NOT a flex wrap or a wrapper around the
   columns. The band is taken out of the flow and the shell is given top padding to match, so the
   columns underneath are the same flex row with the same shares, dividers and stretch they always had:
   every rule written against ".ce-shell > [data-ce-pane]" still matches them, and a shell with no band
   cannot tell one exists. One custom property, --ce-top-h, sets the band's height, the padding and the
   divider's position together, capped at 62% of the window so the columns always keep room.
   Below 900px none of this applies: the shell is already a column of tabbed panes and the band is just
   the first of them. */
/* "HIDDEN: Look, Preview" - the way back from an × (2026-09-19). Reopening lived only in the Layout
   gallery, whose button is on the strip, and the on-page popup hides the strip: closing a pane there was
   permanent with nothing on screen to say so. The bar is out of the flex row (like the top band) and takes
   no room from the columns; it only exists while something is closed. */
.ce-shell { position: relative; }
.ce-closedbar { position: absolute; top: 2px; right: 8px; z-index: 6; display: flex; align-items: center; gap: .3rem;
    background: #fff; border: 1px solid #e3e8ef; border-radius: 999px; padding: 2px 6px 2px 10px;
    box-shadow: 0 2px 8px rgba(16, 24, 40, .10); font-size: .78rem; }
.ce-closedbar-l { color: #7b8797; }
.ce-closedbar-chip { border: 0; background: #eef2f7; color: #206bc4; border-radius: 999px; cursor: pointer;
    padding: 2px 9px; display: inline-flex; align-items: center; gap: .25rem; font-size: .78rem; }
.ce-closedbar-chip:hover { background: #dbe7f6; }
/* With the preview across the top the bar would sit on the band's own header, so it drops below it. */
.ce-shell.ce-has-top:not(.ce-top-off) .ce-closedbar { top: calc(var(--ce-top-eff, 340px) + 14px); }
/* In the language row (where it belongs) it is part of the row, pushed right - no pill, no shadow, and
   nothing floating over a pane's own buttons. */
.ce-langtop .ce-closedbar { position: static; margin-left: auto; background: none; border: 0; box-shadow: none; padding: 0; }
.ce-topdrop { display: none; }
.ce-shell.ce-dragging { position: relative; }
/* 28px, the very top edge of the shell: like a dock guide at the frame's edge, and no taller than it must be.
   A pane's header (and so its grip) sits just below that in an editor with no band, and this bar used to be
   44px, which put it over the grips of every editor - so a drag that barely started, or one dropped on
   another pane's header, docked a full-width row nobody asked for (review 2026-09-18). */
.ce-shell.ce-dragging > .ce-topdrop { display: flex; position: absolute; top: 0; left: 0; right: 0; height: 28px; z-index: 30;
    align-items: center; justify-content: center; border: 2px dashed #9dbbe8; border-radius: 8px;
    background: rgba(32, 107, 196, .08); color: #206bc4; font-size: .78rem; font-weight: 600; line-height: 1; }
/* While a divider is being dragged nothing under the pointer may swallow it: an iframe (the header preview)
   takes every pointer event for its own document the moment the pointer crosses it, and the drag stalls. */
.ce-shell.ce-resizing iframe { pointer-events: none; }
.ce-shell.ce-dragging > .ce-topdrop.ce-drop-top { background: rgba(32, 107, 196, .22); border-color: #206bc4; }
@media (min-width: 900px) {
    .ce-shell.ce-has-top { position: relative; --ce-top-eff: min(var(--ce-top-h, 340px), 62vh); padding-top: calc(var(--ce-top-eff) + 10px); }
    /* WHERE THE EDITOR OWNS THE WINDOW the columns must keep room too, which 62vh of the window does not
       guarantee: the shell is only 100vh - 104px, so on a 1366x768 laptop a 340px band left the Items and
       Settings panes about 190px (review 2026-09-18). The band is capped by what the shell has: 260px for the
       columns, 104px of page chrome, 10px for the divider, and never under 120px itself. The default is a
       proportion (38vh, which is the old 340px on a 900px-tall window) rather than a fixed length. Not in the
       page editor's frame, where 100vh is the frame's own height, which the parent sizes FROM its content:
       a cap written against it would chase itself down. */
    html:not(.ce-embedded) .ce-shell.ce-has-top { --ce-top-eff: max(120px, min(var(--ce-top-h, 38vh), 62vh, calc(100vh - 374px - var(--hdr-live-h, 0px)))); }
    .ce-shell.ce-has-top.ce-top-off { padding-top: 0; }
    /* THREE CLASSES, NOT TWO, and it is not decoration: the laptop rule further down
       (".ce-shell.ce-custom .ce-preview { position: static; width: auto ... }", under 1400px) is three
       classes deep, so a band written as ".ce-shell > .ce-top" lost to it and sat in the column row
       whenever the band pane was the Preview - which is the case the header designer opens in. */
    .ce-shell.ce-custom.ce-has-top > .ce-top { position: absolute; top: 0; left: 0; right: 0; width: auto; height: var(--ce-top-eff); overflow: hidden; z-index: 2; min-width: 0; }
    /* The pane in the band fills it and scrolls inside it, whether the editor owns the window or sits in
       the page editor's frame (where the standalone rules above deliberately do not apply). */
    .ce-shell.ce-has-top > .ce-top > .card, .ce-shell.ce-has-top > .ce-top > [data-ce-pane] > .card { height: 100%; min-height: 0; display: flex; flex-direction: column; }
    .ce-shell.ce-has-top > .ce-top .ce-item-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
    /* A pane with no .ce-item-scroll of its own (the FAQ, Document library and Business hours editors) would be
       cut off at the band's edge with no way to reach the rest: whichever body the card has scrolls instead. */
    .ce-shell.ce-has-top > .ce-top .card > .card-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
    /* scrollbar-gutter: the frame is sized from the body's width BEFORE its own height makes the body
       scroll, so without a reserved gutter the scrollbar then took ~15px and clipped the page's edge. */
    .ce-shell.ce-has-top > .ce-top .ce-pv-body { flex: 1 1 auto; min-height: 0; max-height: none; scrollbar-gutter: stable; }
    /* The preview wrapper's side-column treatment (a 72% zoom and a margin, so a page fits a narrow
       pane) is wrong for a preview that owns the whole width: it shrank the page to 72% of itself and
       fenced it in. In the band the page is drawn at its real size, edge to edge. */
    .ce-shell.ce-has-top > .ce-top .ce-pv-frame { zoom: 1; padding: 0; }
    .ce-shell.ce-has-top > .ce-hb { position: absolute; left: 0; right: 0; top: var(--ce-top-eff); height: 10px; z-index: 3; flex: none; cursor: row-resize; }
    .ce-h.ce-hb::after { top: 50%; bottom: auto; left: 12px; right: 12px; width: auto; height: 2px; transform: translateY(-50%); background: #dbe2ea; }
    .ce-h.ce-hb:hover::after { background: var(--tblr-primary, #206bc4); }
}
.ce-arr-panel { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; background: #fff; border-bottom: 1px solid #e3e8ef; padding: .6rem 1rem; }
/* Picture cards with pane initials, restored (Graham 2026-09-06 "Go back to the layout images
   with the initials"): I / T / S / P name each coloured box; a stack shows every member. */
.ce-arr-card { display: flex; flex-direction: column; align-items: center; gap: .3rem; border: 1px solid #d6dde8; background: #f8fafc; border-radius: 9px; padding: .5rem .7rem; cursor: pointer; font-size: .74rem; color: #3b4656; }
.ce-arr-card:hover { border-color: #206bc4; }
.ce-arr-card.on { border-color: #206bc4; background: #eef4fd; font-weight: 600; }
.ce-arr-mock { display: flex; gap: 2px; width: 74px; height: 34px; }
.ce-arr-mock i, .ce-arr-mock b { flex: 1; border-radius: 3px; display: flex; align-items: center; justify-content: center;
    font: 700 9px/1 system-ui, sans-serif; font-style: normal; color: rgba(15, 25, 40, .55); }
.ce-arr-mock .m-style { background: #b9cef2; }
.ce-arr-mock .m-content { background: #b7e2cf; }
.ce-arr-mock .m-item { background: #e2d5f0; }
.ce-arr-mock .m-preview { background: #f2ddab; }
.ce-arr-mock .m-stack { background: #e3e8ef; padding: 5px 2px 2px; display: flex; gap: 2px; }
/* The stacked slot: the same two letters, turned through 90 degrees. The tab version keeps its 5px of
   top padding to suggest a tab strip; this one has none, because every pane in it is on screen. */
.ce-arr-mock .m-vstack { background: #e3e8ef; padding: 2px; display: flex; flex-direction: column; gap: 2px; }
.ce-arr-mock .m-vstack > b { flex: 1; }
/* A card with a top band: the band is a full-width strip and the columns sit under it. */
.ce-arr-mock.m-has-top { flex-wrap: wrap; align-content: flex-start; }
.ce-arr-mock .m-top { flex: 0 0 100%; height: 11px; }
.ce-arr-mock .m-top.m-stack { padding: 2px; }
.ce-arr-mock.m-has-top > :not(.m-top) { height: 21px; }
/* (the in-gallery Save Custom button was retired 2026-09-06 — the strip's Save layout is the one save) */
/* Gallery row tools + the hint on its own line (2026-09-06 "Fill the space with usefull options.
   Move the or drag and drop to its own lin"). */
.ce-arr-sep { flex: 1; }
.ce-arr-tool { display: inline-flex; align-items: center; gap: .3rem; border: 1px dashed #c3cddb; background: #f7f9fc; border-radius: 999px; padding: .28rem .8rem; cursor: pointer; font-size: .8rem; font-weight: 600; color: #3b4656; align-self: center; }
.ce-arr-tool:hover { border-color: #206bc4; color: #206bc4; }
.ce-arr-hint { flex-basis: 100%; margin-top: .1rem; }
/* Closable panels (2026-09-06 "we need option to clse each panel (x)"): the × in every pane head;
   closed panes and their tabs vanish, the gallery's Hidden: chips bring them back. */
/* Expand label rides only where there's room; a narrow pane drops to the icon (2026-09-06). */
.ce-max-lbl { margin-left: .3rem; }
.ce-narrow .ce-max-lbl { display: none; }
/* ── THE HELP PARAGRAPHS, ON A SWITCH (Graham 2026-09-10) ────────────────────────────────────────
   .ce-help is worn ONLY by the four `<div class="form-hint ce-help">@f.Help</div>` renders in
   _EditorField.cshtml - the descriptor's help text, which is guidance. It is deliberately NOT the
   whole of .form-hint: that class is also worn by the chosen position's name (.ce-pos9-name), the
   chosen tile's description (.ce-tiles-desc) and the "manage this" link (.ce-optlink), and those
   three are READOUTS OF STATE, not explanations. Hiding "Split left" because somebody turned help
   off would take away the only thing on screen saying which template is selected.
   The button reads pressed when help is OFF, because that is when it is doing something. */
.ce-shell.ce-nohelp .ce-help { display: none; }
.ce-help-btn[aria-pressed="true"] { background: #eef2f7; color: #4b5a6b; border-color: #cfd8e3; }
/* ── PANE-HEADER BUTTONS GO BACK TO btn-sm (Graham 2026-09-10: "i asked you to make these button
   bigger...but make them smaller again") ──────────────────────────────────────────────────────────
   They were enlarged this afternoon in v2026.09.10.39 — 48px against 40, the header pinned at 73px so
   nothing moved around them. Seen in place they read as heavy: Add item, Full form, Help and Expand
   are four controls across one header, and at that size they stop being chrome above the work and
   start competing with it. The size was the thing asked for and the size is the thing withdrawn, so
   the rules are deleted rather than re-tuned — Tabler's own .btn-sm is what the markup already says,
   and the shortest way back to it is to stop overriding it.
   The header's min-height goes too. It existed only to stop the taller buttons growing the header;
   with the buttons back at 40px the header sizes itself to 73px the way it always did, and a pin left
   behind would be a number nobody could explain a month from now. */
.ce-x { border: 0; background: none; color: #9aa7b8; cursor: pointer; padding: .1rem .3rem; margin-left: .35rem; border-radius: 6px; line-height: 1; }
.ce-x:hover { color: #b02a37; background: #fbeaec; }
.ce-pane-closed { display: none !important; }
/* Custom arrangements opt OUT of the laptop preview-drawer collapse - the user owns the layout */
@media (max-width: 1399.98px) {
    .ce-shell.ce-custom .ce-h2, .ce-shell.ce-custom .ce-hx, .ce-shell.ce-custom .ce-hy { display: block; }
    .ce-shell.ce-custom .ce-pv-toggle { display: none; }
    .ce-shell.ce-custom .ce-preview { display: block; position: static; width: auto; box-shadow: none; border-radius: 0; }
}
/* Phones: the shell tabs rule; stacks dissolve so the pane tabs govern every pane directly */
@media (max-width: 899.98px) {
    .ce-stack { display: contents; }
    .ce-stack-tabs { display: none; }
    /* THE PREVIEW OBEYS THE TABS ON A PHONE IN A CUSTOM LAYOUT TOO. The laptop rule above ("custom
       arrangements opt OUT of the drawer": display:block on .ce-shell.ce-custom .ce-preview) is written
       for under 1400px and so also covers under 900px, where it out-ranked the base ".ce-preview {
       display:none }" and left the Preview showing under every tab. It went unnoticed while no editor
       opened in a custom layout; the header and footer designers now do (preview across the top). The
       show rules below still win for the selected tab, and they are !important. Only where there IS a
       Preview tab to bring it back: Business hours and Cookie consent have a Preview pane and no tab bar,
       and for them this would be the pane's grave (review 2026-09-18). */
    .ce-shell.ce-custom:has(> .ce-tabs .ce-tab[data-ce-tab="preview"]) .ce-preview { display: none; }
    .ce-shell[data-ce-show="style"] .ce-style { display: block !important; }
    .ce-shell[data-ce-show="content"] .ce-content { display: block !important; }
    .ce-shell[data-ce-show="item"] .ce-item { display: block !important; }
    .ce-shell[data-ce-show="preview"] .ce-preview { display: block !important; }
}
/* EXPAND AS OVERLAY (2026-09-06, "needs to break out of box and fill 95% of screen" — supersedes
   the inline takeover): .ce-max-btn lifts its pane into a fixed overlay at 95% of the viewport;
   Done, Esc or the backdrop restores it. In the embed the frame clamps to the parent viewport
   while open (embedClamp in control-editor.js), so "the screen" is the real screen there too. */
.ce-max { position: fixed; inset: 0; z-index: 95; background: rgba(20, 28, 40, .45); display: flex; align-items: center; justify-content: center; }
.ce-max-in { width: 95%; height: 95%; background: #f6f7fb; border-radius: 12px; box-shadow: 0 24px 70px rgba(16, 24, 40, .35); display: flex; flex-direction: column; overflow: hidden; }
.ce-max-head { display: flex; align-items: center; gap: .5rem; padding: .45rem .9rem; background: #fff; border-bottom: 1px solid #e3e8ef; color: #3b4656; font-size: .9rem; flex: none; }
.ce-max-body { flex: 1; min-height: 0; overflow: auto; padding: 1rem; }
.ce-max-body > [data-ce-pane] { width: 100%; }
.ce-max-body .ce-item-scroll, .ce-max-body .ce-pv-body { max-height: none; }
.ce-max-body .ce-pv-frame { zoom: 1; }   /* expanded preview at true size, not the pane-fit 72% */
.ce-max .ce-x { display: none; }
/* One scroll context while the overlay is open (Graham 2026-09-06 "make sure no scroll bars"):
   the page behind it stops scrolling; only the overlay body scrolls, and only when it must. */
html.ce-max-open { overflow: hidden !important; }
/* Fields the CHOSEN layout ignores are HIDDEN outright (2026-09-06 — dimming shipped first, then
   Graham: "Just hide items not used."). The values are kept, only the inputs disappear; switching
   to a layout that uses them brings them straight back (the layout change reloads the page). */
.ce-f-dim { display: none; }
/* Autosave chip (2026-09-06 Graham "auto save is good" + "more animated or noticable save button
   change"): Saving… spins, Saved pops in as a green pill and STAYS until the next edit. */
.ce-save-chip { display: inline-flex; align-items: center; gap: .35rem; font-size: .8125rem; font-weight: 600; min-height: 1.6rem; }
.ce-save-chip:empty { visibility: hidden; }
.ce-save-chip.is-saving { color: #5e6675; }
.ce-save-chip.is-saving::before { content: ''; width: .7rem; height: .7rem; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: ce-spin .6s linear infinite; }
.ce-save-chip.is-saved { color: #2fb344; background: rgba(47, 179, 68, .12); border-radius: 999px; padding: .1rem .65rem; animation: ce-chip-pop .5s cubic-bezier(.2, 1.4, .4, 1); }
.ce-save-chip.is-saved::before { content: '✓'; font-weight: 800; }
.ce-save-chip.is-err { color: #d63939; background: rgba(214, 57, 57, .1); border-radius: 999px; padding: .1rem .65rem; }
@keyframes ce-spin { to { transform: rotate(360deg); } }
@keyframes ce-chip-pop { 0% { transform: scale(.5); opacity: 0; } 55% { transform: scale(1.18); } 100% { transform: scale(1); opacity: 1; } }
/* Embedded picker scroll (Graham 2026-09-06 "just have a scroll bar on the section layout so the
   jump to button doesn't jump the whol pagee"): in the embed the frame has no scrollbar, so the
   scroll-to-current-card was moving the WHOLE editor page. The tile area gets its own capped
   scrollbar instead — a px cap, because inside the grown frame 100vh is the frame's full height.
   overscroll contain keeps a wheel at its edge from shoving the outer page. The heading and the
   option fields sit below the box, always reachable. Uncapped inside the Expand overlay. */
.ce-embedded .fs-pick-wrap { max-height: 600px; overflow-y: auto; overscroll-behavior: contain;
    border: 1px solid #e3e8ef; border-radius: 8px; padding: .5rem .5rem 0; margin-bottom: .75rem; }
.ce-max .fs-pick-wrap { max-height: none; overflow-y: visible; border: 0; padding: 0; }
/* Quick-edit item pane (Graham 2026-09-06 "condense to one line ... Think quick edit, all without
   scroll if we can"): swatches ride inside the colour input-groups as full-height cells, and the
   image picker's Choose-image bar joins its input's group instead of dropping below. */
.ce-clr-line .fs-swatch { width: 2rem; height: auto; border-radius: 0; border: 1px solid #cbd3de; padding: 0; flex: 0 0 auto; }
.ce-clr-line .fs-swatch:hover { outline: none; filter: brightness(.9); }
.ce-clr-line .ce-clr-box { min-width: 6rem; }
/* ALL ON ONE LINE, PICKER FULL SIZE (Graham 2026-09-30, a narrow Section look pane): the group wrapped, so the clear "x"
   dropped to a line of its own and the colour picker shrank to a sliver. The picker and every swatch keep their size and
   the typed value gives way instead - it is the only part that reads fine narrower. */
.ce-clr-line { flex-wrap: nowrap !important; }
.ce-clr-line .ce-clr-pick { flex: 0 0 2.75rem; width: 2.75rem; min-width: 2.75rem; max-width: 2.75rem; padding: .2rem; }
.ce-clr-line .ce-clr-box { min-width: 0; flex: 1 1 3.5rem; }
.ce-clr-line .cms-role-swatches { display: flex; flex: 0 0 auto; }
.ce-clr-line .cms-role-sw { flex: 0 0 1.9rem; width: 1.9rem; min-width: 1.9rem; }
.ce-clr-line .fs-swatch-none { flex: 0 0 2rem; min-width: 2rem; }
.ce-img-line .mp-actions { margin-top: 0 !important; }
/* THE PREVIEW RIDES IN THE GROUP (Graham 2026-09-10: "move the image preview to the right of the
   button, have it the same height as the textbox, have it an on click that brings up the popup").
   No height is stated anywhere here, and that is the point: as a cell of the input-group it is
   stretched to the row by the group itself, so it matches the box at every size the box ever takes
   and cannot drift the way the old hard-coded 34px did. padding:0 and overflow:hidden make the
   picture the whole button; the square keeps a panorama from stretching the row.

   THE PICTURE IS TAKEN OUT OF THE FLOW, and it took two measurements to get here. Any arrangement
   where the <img> contributes to the button's size is circular: the button wants its height from the
   row and the row wants its height from the button, so the picture's own 1026px won both times and the
   preview came out 1026 square on a line of its own. Absolute positioning makes the picture contribute
   nothing, so the button's height comes purely from align-self:stretch - which is the row, which is the
   textbox, which is what was asked for - and the picture then fills whatever that turns out to be.
   The width is stated because it is the one number that CANNOT come from the row; 2.25rem is the height
   a Bootstrap form-control takes, so the cell reads square at the size it is actually used at, and a
   taller row makes it a little tall rather than making it wrong. */
.mp-thumb-btn { position: relative; padding: 0; overflow: hidden; flex: 0 0 auto;
    align-self: stretch; width: 2.25rem; }
.mp-thumb-btn[hidden] { display: none; }
/* admin-link-picker.js: the "Pick a page" list under a link field. Positioned by the script (page coordinates). */
.lp-panel { position: absolute; z-index: 1080; max-width: calc(100vw - 16px); }
.lp-panel[hidden] { display: none; }
.lp-list { max-height: 18rem; overflow-y: auto; padding: .25rem; }
.lp-item { display: flex; flex-direction: column; align-items: flex-start; width: 100%; text-align: left;
    border: 0; background: none; padding: .35rem .5rem; border-radius: 4px; }
.lp-item:hover, .lp-item.lp-active { background: var(--tblr-primary-lt, #e8f0fb); }
.lp-item.lp-current .lp-title::after { content: " \2713"; color: var(--tblr-green, #2fb344); }
.lp-title { font-weight: 500; }
.lp-path { font-size: .78rem; color: var(--tblr-secondary, #6c7a91); word-break: break-all; }
.lp-empty { padding: .5rem; color: var(--tblr-secondary, #6c7a91); font-size: .875rem; }
.mp-thumb-btn .mp-thumb { position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; display: block; }
.mp-thumb-btn:hover .mp-thumb { opacity: .82; }
/* Outside an input-group there is no row to match, so the old fixed size stands. */
.mp-actions .mp-thumb-btn { height: 42px; width: 42px; border: 1px solid #e5e7eb; border-radius: 6px;
    background: none; cursor: pointer; }

/* THE POPUP. A plain overlay: the picture at its own size up to the viewport, its filename under it,
   and one close button. Above the control editor's Expand overlay (z 1200) and the media picker sits
   higher again at 200000, so opening the picker from behind this is not a thing that can happen. */
/* TOP ALIGNED, NOT CENTRED (Graham 2026-09-10: "Top align image pop up"). Centring a tall picture puts
   its middle at the middle of the window, so the top of the picture - which is the part you opened it
   to look at - sits above the fold and the whole thing has to be scrolled to. Aligned to the top it
   starts where the eye already is, and a portrait photo runs off the BOTTOM, which is the edge a
   scroll can reach. overflow-y lets it be reached. */
.mp-lightbox { position: fixed; inset: 0; z-index: 150000; display: flex; align-items: flex-start;
    justify-content: center; padding: 4vmin; background: rgba(10,12,16,.72); overflow-y: auto; }
.mp-lightbox[hidden] { display: none; }
.mp-lightbox-fig { margin: 0; display: flex; flex-direction: column; align-items: center; gap: .6rem;
    max-width: 100%; max-height: 100%; }
/* VIEWPORT UNITS, NOT PERCENTAGES, and the first attempt showed why: a percentage max-height resolves
   against the parent, the figure's height is auto, and a percentage against an auto height resolves to
   none - so a 1024px picture painted at 1024px and ran off the bottom of the screen. Measured. vh and
   vw answer to the window, which is the thing actually doing the constraining here. */
.mp-lightbox-fig img { max-width: 92vw; max-height: 82vh; object-fit: contain;
    border-radius: 10px; background: #fff; box-shadow: 0 24px 70px rgba(0,0,0,.5); }
.mp-lightbox-fig figcaption { color: #dfe5ee; font-size: .78rem; word-break: break-all; text-align: center; }
.mp-lightbox-x { position: absolute; top: 1rem; right: 1.2rem; border: 0; background: none;
    color: #fff; font-size: 2rem; line-height: 1; cursor: pointer; padding: .2rem .5rem; border-radius: 6px; }
.mp-lightbox-x:hover { background: rgba(255,255,255,.14); }
/* Empty-preview note: the layout rendered nothing (its fields are blank on every item) — explain
   what it draws from instead of a silent blank pane (2026-09-06 "before after preview broke"). */
.ce-pv-empty { margin: .75rem; padding: .6rem .8rem; border: 1px dashed #c9d2de; border-radius: 8px; background: #f6f8fb; color: #5e6675; font-size: .8125rem; line-height: 1.45; }
.ce-max .fs-layout-pick { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.ce-max .ce-grip { display: none; }
/* Edit-in-preview (2026-09-06): editable fields + the small toolbar */
.ce-fse { outline: 1px dashed rgba(32, 107, 196, .5); outline-offset: 2px; border-radius: 3px; cursor: text; min-width: 1ch; }
.ce-fse:hover { outline-color: #206bc4; background: rgba(32, 107, 196, .05); }
/* The ring, not a repaint (2026-09-20): white heading text on a blue band vanished into a white box. */
.ce-fse:focus { outline: 2px solid #206bc4; background: transparent; }
/* HOVER, CLICK, TYPE, AUTOSAVE (2026-09-20). Quiet until you hover it - the preview is meant to look like
   the page, so words that happen to be editable must not be dressed up as form fields. The hover says "you
   can type here", the caret confirms it, and the tick says it saved. */
/* The preview draws the SITE's css, whatever it happens to be, so the editor's own marks are set with
   outline and !important - a box-shadow ring was simply overruled by the control's heading rule and nothing
   showed at all. Outline also costs no layout, so nothing shifts as you click into a line. */
.ce-fse-live { border-radius: 3px; cursor: text; transition: background-color .12s; }
.ce-fse-live:hover { background: rgba(32, 107, 196, .07); outline: 1px dashed rgba(32, 107, 196, .55) !important; outline-offset: 2px; }
/* NEVER repaint the words while they are being typed (2026-09-20): a white box under white heading text on
   a blue band hid what was being typed. The ring alone says "you are in here"; the text keeps the page's
   own colour, which is the whole point of editing in the preview. */
.ce-fse-live.ce-fse-on { background: transparent; outline: 2px solid #206bc4 !important; outline-offset: 2px; }
.ce-fse-live.ce-fse-saving { outline: 2px solid #f59e0b !important; outline-offset: 2px; }
.ce-fse-live.ce-fse-saved { outline: 2px solid #16a34a !important; outline-offset: 2px; }
.ce-fse-live.ce-fse-failed { outline: 2px solid #dc2626 !important; outline-offset: 2px; }
.ce-fse-bar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: .5rem; background: #fffbe6; border-bottom: 1px solid #efe3ad; padding: .4rem .7rem; font-size: .82rem; color: #6b6540; }
.ce-fse-bar .ti-pencil { color: #b3541e; }
.ce-fse-hint { color: #6b6540; }
.ce-shell.ce-editing .ce-pv-editbtn { border-color: #b3541e; color: #b3541e; }
/* STRIP BREADCRUMB (2026-09-06, Graham: chevron crumbs, click-through, hover drops the siblings).
   Markup: Pages/Shared/_CeBreadcrumb.cshtml; the caret's touch toggle: control-editor.js. */
.ce-bc { display: flex; align-items: center; gap: .1rem; min-width: 0; }
.ce-bc-item { position: relative; display: flex; align-items: center; }
.ce-bc-btn { display: inline-flex; align-items: center; gap: .35rem; padding: .3rem .5rem; border-radius: 7px; color: #5e6675; font-weight: 600; text-decoration: none; white-space: nowrap; }
a.ce-bc-btn:hover { background: #eef2f7; color: #206bc4; }
.ce-bc-btn.on { color: #1f2733; }
.ce-bc-sep { color: #b6c0cd; font-size: .8rem; }
.ce-bc-dd { border: 0; background: none; padding: .3rem .15rem; margin-left: -.3rem; color: #8a97a8; cursor: pointer; border-radius: 6px; line-height: 1; }
.ce-bc-dd:hover { color: #206bc4; }
.ce-bc-menu { display: none; position: absolute; top: 100%; left: 0; z-index: 85; min-width: 240px; max-width: 340px; max-height: 62vh; overflow: auto; background: #fff; border: 1px solid #dde3ec; border-radius: 10px; box-shadow: 0 12px 34px rgba(16, 24, 40, .16); padding: .3rem; }
.ce-bc-item:hover > .ce-bc-menu, .ce-bc-item:focus-within > .ce-bc-menu, .ce-bc-item.open > .ce-bc-menu { display: block; }
.ce-bc-mh { font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #8a97a8; padding: .3rem .6rem .25rem; white-space: nowrap; }
.ce-bc-mi { display: flex; align-items: center; gap: .45rem; padding: .32rem .6rem; border-radius: 7px; color: #3b4656; text-decoration: none; font-size: .86rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
a.ce-bc-mi:hover { background: #eef4fd; color: #206bc4; }
.ce-bc-mi.on { color: #206bc4; font-weight: 700; }
/* Embedded slim mode (2026-09-06): inside editor's iframe the host header owns page context and
   View page, so the strip keeps only the Layout gear — no second breadcrumb row, and no nav links
   that would trap navigation inside the little frame. Class set by control-editor.js. */
.ce-embedded .ce-bc, .ce-embedded .e2-strip a.e2-back { display: none; }
/* The whole strip row goes when embedded (Graham 2026-09-06 "move these to beside block settings"):
   editor proxies the Layout gear + template cluster into its embed head bar, driving these hidden
   originals. The gear's inline gallery panel inserts AFTER the strip, so it still shows. */
.ce-embedded .e2-strip { display: none; }
.ce-embedded .ce-standalone { padding: 8px 10px 24px; }
/* No nested scrollbars in the embed (Graham 2026-09-06: "All these scrolls can be removed and then
   have all the content on the page. Keep the right main scroll"): the frame grows to its content
   (editor syncs its height), so the pane bodies flow at full height instead of scrolling inside. */
.ce-embedded .ce-pv-body, .ce-embedded .ce-item-scroll { max-height: none; }
/* And no scroll NODE at all while embedded: Chromium's compositor latches wheel gestures onto an
   iframe's inner scroller even when it has nothing to scroll, killing the parent page's scroll over
   the frame. clip removes the scroller (the frame is height-synced to this document anyway). */
html.ce-embedded, html.ce-embedded body { overflow: clip; }
/* THE ON-PAGE POPUP (control-editor.js marks it ce-fixed-frame): ONE scrollbar, down the whole popup, and
   no scrollbar inside a pane (2026-09-19: "the item should auto expand and never have an inline scroll -
   there should be a top to bottom scroll bar as we can't access data").
   Each pane used to fill the popup's height and scroll its own body, which put three short scrollers on one
   screen: the Item pane's last fields sat below the fold of a box 300px tall, the wheel scrolled whichever
   one the pointer happened to be over, and fields further down could not be reached at all. Now every pane
   grows to its content, the tallest sets the height, and the frame's own document scrolls - which is the
   scrollbar people expect down the right of the popup. */
/* EACH COLUMN SCROLLS AS ONE COLUMN, top to bottom (2026-09-19: "the item should auto expand and never have
   an inline scroll ... each vertical can have its own"). A pane fills the popup's height and scrolls itself -
   what must not happen is a SECOND, shorter scroller boxed inside it, which is what put the Item pane's last
   fields out of reach: the box ended above the fold, and the wheel over it moved neither the box nor the pane. */
@media (min-width: 900px) {
    html.ce-fixed-frame .ce-standalone { height: calc(100vh - var(--ce-lang-h, 0px)); min-height: 420px; box-sizing: border-box; }
    html.ce-fixed-frame .ce-standalone > .ce-shell { height: 100%; min-height: 0; }
    html.ce-fixed-frame .ce-standalone .ce-shell > [data-ce-pane] { min-height: 0; }
    /* ONE scroller per column, and it is the CARD: everything in the pane scrolls with it - the item form,
       and under the Items list the "Other sections" and "Also on these pages" blocks, which sat outside the
       body's own scroller and pushed the card past the fold with no way to reach them. */
    html.ce-fixed-frame .ce-standalone .ce-shell > [data-ce-pane] > .card {
        height: 100%; min-height: 0; display: block; overflow-y: auto; overscroll-behavior: contain; }
    /* Nothing inside a pane scrolls on its own any more: the boxed scroller ended above the fold, and a
       wheel over it moved neither the box nor the column. */
    html.ce-fixed-frame .ce-standalone .ce-item-scroll,
    html.ce-fixed-frame .ce-standalone .ce-pv-body,
    html.ce-fixed-frame .ce-standalone .table-responsive { max-height: none; overflow: visible; flex: none; }
}
/* Below 900px the panes stack, so the frame's document must scroll instead. */
@media (max-width: 899.98px) { html.ce-fixed-frame, html.ce-fixed-frame body { overflow: auto; } }
/* THE LANGUAGE BAR at the top of the generic control editor (Graham 2026-09-19: "every control has the
   language block at the top"). One row, 44px with its margin - the figure .ce-standalone takes off the
   window as --ce-lang-h. Indented to the shell's own padding when embedded, where the strip is hidden. */
.ce-langtop { display: flex; align-items: center; flex-wrap: wrap; gap: .25rem .75rem; min-height: 36px; margin-bottom: 8px; }
.ce-langtop .nav-link { padding-top: .3rem; padding-bottom: .3rem; }
.ce-embedded .ce-langtop { padding: 8px 10px 0; margin-bottom: 0; }

/* ── FIELD GROUPS, AND WHAT EACH PANE GOVERNS ───────────────────────────────────────────────────
   Graham 2026-09-09: "This is very disorganized. I don't know where to start" / "Style normally is on
   the left and text is on the right." The Item pane now holds the CONTENT of a row and the left pane
   holds the styling: the page's, then the selected row's, in labelled disclosures.

   A closed group is a SUMMARY, not a hiding place - .ce-fg-state carries what is set inside it, so
   the house rule ("a layout may rearrange, never decide part of it should not be shown") holds. */
.ce-scope { font-size: .74rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    color: #8a97a8; margin: 0 0 .6rem; }
/* The rule separates the two scopes, so the first one on the pane does not draw it. */
.ce-scope-line { display: flex; align-items: baseline; gap: .5rem; margin: 1.1rem 0 .6rem;
    padding-top: .85rem; border-top: 1px solid #e6e9ee; }
.ce-item-scroll > form:first-of-type .ce-scope-line { margin-top: 0; padding-top: 0; border-top: 0; }
.ce-scope-line .ce-save-chip { margin-left: auto; }
/* The row's own name, so "This slide" is never a guess about which one. */
.ce-slide-name { font-weight: 600; text-transform: none; letter-spacing: 0; color: #5e6675; }
/* Nothing selected: readable, listed, not typable. Bootstrap dims a disabled fieldset's controls; the
   groups stay closed and the summary reads "nothing set", which is the truth. */
.ce-slide-fs { border: 0; padding: 0; margin: 0; min-width: 0; }
.ce-slide-fs[disabled] { opacity: .5; }
.ce-slide-fs[disabled] .ce-fg-sum { cursor: default; }

.ce-fg { border: 1px solid #e6e9ee; border-radius: 10px; background: #fbfcfd; margin-bottom: .5rem; }
.ce-fg-sum { list-style: none; cursor: pointer; display: flex; align-items: center; gap: .5rem;
    padding: .5rem .7rem; border-radius: 10px; font-size: .85rem; font-weight: 600; color: #3b4656; }
.ce-fg-sum::-webkit-details-marker { display: none; }
/* A caret drawn rather than an icon font, so a group never waits on the icon sheet to look right. */
.ce-fg-sum::before { content: ""; flex: 0 0 auto; width: 0; height: 0; border-left: 5px solid #94a1b2;
    border-top: 4px solid transparent; border-bottom: 4px solid transparent; transition: transform .12s ease; }
.ce-fg[open] > .ce-fg-sum::before { transform: rotate(90deg); }
.ce-fg-sum:hover { background: #f1f5f9; }
.ce-fg-sum:focus-visible { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: -2px; }
.ce-fg-name { flex: 0 0 auto; }
.ce-fg-state { margin-left: auto; min-width: 0; font-weight: 500; font-size: .76rem; color: #94a1b2;
    text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ce-fg-body { padding: .5rem .7rem .1rem; border-top: 1px solid #eef1f5; }
/* The last field in a group should not push a gap past the group's own border. */
.ce-fg-body > *:last-child { margin-bottom: .6rem !important; }
/* A PAGE-WIDE SETTING THE ITEMS ARE ALREADY BEATING (Graham 2026-09-10: "put a warning that if the item
   fade is set the 'every item' image fade will not"). Amber rather than red: the page is not broken and
   nothing needs undoing - a per-item override is a legitimate thing to have done on purpose. It only has
   to explain why this control appears to do nothing, which is the complaint that produced it.
   It sits directly under the field it is about, so it reads as that field's own footnote; the left rule
   ties it to the row above rather than letting it float as a separate notice. */
.ce-ovr-note { display: flex; gap: .4rem; align-items: flex-start; margin: -.35rem 0 .75rem;
    padding: .4rem .55rem; border-left: 3px solid #e8a33d; border-radius: 0 4px 4px 0;
    background: #fdf6e9; color: #6b5424; font-size: .76rem; line-height: 1.4; }
.ce-ovr-note i { flex: 0 0 auto; margin-top: .1rem; color: #c47f17; font-size: .9rem; }
.ce-ovr-note strong { font-weight: 600; }
/* In the narrow left pane the 3/9 label split leaves nothing for the control; stack instead. */
/* FIELDS THAT ARE WORTH LESS THAN A ROW ({"w":"third"|"half"}, Script0690). Width, Height and Padding
   are a handful of characters each and were taking a full row apiece, three tall boxes of mostly empty
   space pushing everything below them off screen. The group body becomes a wrapping flex row where a
   normal field still claims the whole width, so nothing that omits the option moves at all — the only
   visible change is to the fields that ask. */
.ce-fg-body { display: flex; flex-wrap: wrap; column-gap: .5rem; }
.ce-fg-body > * { flex: 1 1 100%; min-width: 0; }
.ce-fg-body > [data-ce-w="third"] { flex: 1 1 calc(33.333% - .5rem); }
.ce-fg-body > [data-ce-w="half"]  { flex: 1 1 calc(50% - .5rem); }
/* A third of a column is too narrow for a help line to sit on one line; let it wrap tightly rather
   than stretch the row. */
.ce-fg-body > [data-ce-w] .form-hint { font-size: .72rem; line-height: 1.25; }
/* THREE BOXES, ONE ROW (Graham 2026-09-09: "text boxes need to align properly"). A field row is the
   3/9 label-and-control split, and a third of a column has no room for it, so the pair wraps onto two
   lines. A wrapped `.row.align-items-center` then centres the control inside a line whose height its
   OWN help text set — and Width, Height and Padding have two, three and four lines of help, so the
   three boxes landed at three different heights. Stack the pair deliberately and align it to the top:
   the labels were already level, and now the boxes and the help under them are too.
   Note the selectors: the row IS the [data-ce-w] element, not something inside it, so a descendant
   selector matches nothing here — silently, which is exactly how this looked fixed once already. */
/* align-CONTENT, not just align-items: the cell stretches to the tallest of the three, and with the
   pair wrapped onto two lines the spare height gets shared out BETWEEN those lines. That pushes the
   second line — the box — down by half of whatever slack that particular field had, which is the last
   few pixels of the misalignment and the part that survives fixing everything else. */
.ce-fg-body > [data-ce-w].row              { align-items: start !important; align-content: start; }
.ce-fg-body > [data-ce-w].row > .col-3     { flex: 0 0 100%; max-width: 100%; padding-bottom: .1rem; }
.ce-fg-body > [data-ce-w].row > .col-9     { flex: 0 0 100%; max-width: 100%; }
.ce-fg-body > [data-ce-w].row > .col-9.offset-3 { margin-left: 0; }
.ce-look .ce-fg-body .row > .col-3 { flex: 0 0 100%; max-width: 100%; padding-bottom: .1rem; }
.ce-look .ce-fg-body .row > .col-9 { flex: 0 0 100%; max-width: 100%; }
.ce-look .ce-fg-body .col-9.offset-3 { margin-left: 0; }
/* A DATE OR TIME FIELD IS ONE LINE (Graham 2026-09-19, the banner's Starts / Ends: "This could be two lines").
   The box is compact and the label one short word, so stacking them doubled the height for nothing:
   label beside the box, even in the narrow pane. :has() keeps every other field as it is. */
.ce-look .ce-fg-body .row:has(> .col-9 > input:is([type=date], [type=datetime-local], [type=time])) { align-items: center !important; }
.ce-look .ce-fg-body .row:has(> .col-9 > input:is([type=date], [type=datetime-local], [type=time])) > .col-3 { flex: 0 0 64px; max-width: 64px; padding: 0; }
.ce-look .ce-fg-body .row:has(> .col-9 > input:is([type=date], [type=datetime-local], [type=time])) > .col-9 { flex: 1 1 0; max-width: none; }

/* ── DRAG BAR, WITH THE PICTURE IN IT ───────────────────────────────────────────────────────────
   Field type "range" in the shared _EditorField (Graham 2026-09-09: "Maybe have a left to right drag
   bar. Have the bar with an image in the background where the drag make the image look like it will
   look... The control needs to be small and not take a lot of vertical space").

   A number box for a magnitude tells you nothing until you save and go and look - which is how a
   banner ends up sitting at strength 0 with an overlay chosen and nothing on screen to say the two
   disagree. The bar carries the item's own picture, with the chosen wash over it at the dragged
   value, so the control shows its own result. 34px tall: the same row height as a text input, no
   more.

   THE SIX WASH SHAPES ARE MIRRORED FROM cms-content.css (the .banner-ov-* family, ~5225-5250) rather
   than shared, because this is the admin stylesheet and that one is the public storefront's. They are
   driven by the SAME two custom properties the renderer emits - --banner-ov-a and --banner-ov-c - so
   the two stay in step by using one vocabulary; if a wash is ever added there, add it here. */
.ce-range { position: relative; height: 34px; border-radius: 8px; overflow: hidden;
    border: 1px solid #dfe3e9; background: #eef1f5; display: flex; align-items: center; }
.ce-range-img, .ce-range-wash { position: absolute; inset: 0; pointer-events: none; }
.ce-range-img { background-size: cover; background-position: center; }
/* No picture yet: a neutral ramp so the bar still reads as a scale rather than as a broken image. */
.ce-range-art .ce-range-img:empty { background-color: #c9d3e0; }
.ce-range-in { position: relative; z-index: 2; width: 100%; margin: 0; padding: 0 66px 0 8px;
    background: none; -webkit-appearance: none; appearance: none; height: 100%; cursor: ew-resize; }
.ce-range-in::-webkit-slider-runnable-track { height: 100%; background: none; }
.ce-range-in::-moz-range-track { height: 100%; background: none; }
/* The handle is a bright bar rather than a dot: it has to stay findable over any photograph. */
.ce-range-in::-webkit-slider-thumb { -webkit-appearance: none; width: 8px; height: 22px; border-radius: 3px;
    background: #fff; border: 1px solid rgba(16,24,40,.35); box-shadow: 0 1px 3px rgba(16,24,40,.4); margin-top: 5px; }
.ce-range-in::-moz-range-thumb { width: 8px; height: 22px; border-radius: 3px; background: #fff;
    border: 1px solid rgba(16,24,40,.35); box-shadow: 0 1px 3px rgba(16,24,40,.4); }
.ce-range-in:focus-visible { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: -2px; }
/* The number, pinned right, on its own plate so it reads over a dark wash or a light one. */
.ce-range-val { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); z-index: 3;
    min-width: 48px; text-align: center; font-size: .78rem; font-weight: 600; color: #1b2027;
    background: rgba(255,255,255,.9); border-radius: 5px; padding: 1px 6px; pointer-events: none;
    font-variant-numeric: tabular-nums; }
/* ONE HEADING PER OVERLAY. The Expand overlay draws its own header from the pane's data-ce-title, and
   the card inside carries the same word again — invisible in a narrow column, glaring across 95% of a
   screen, and the first thing anyone notices when Add item opens it (Graham 2026-09-09, screenshotted
   with "Item" twice). The card header stays: it holds Full form and Collapse. Only the duplicated
   title goes. */
.ce-max .ce-max-body .card-header .card-title { display: none; }

/* THE DRAG BOX (Script0692). A miniature of the banner carrying the slide's own picture, with a marker
   you drag anywhere inside it. It replaces a nine-square picker, so it keeps roughly a banner's
   proportions — you are aiming at a small copy of the real thing rather than an abstract rectangle. */
/* A HEADING INSIDE A TILE PICKER. Only appears when the descriptor groups its options, which today is
   the banner template picker and nothing else. Quiet on purpose: it is a signpost between runs of
   drawings, not a section title competing with the field's own label above it. The first one loses its
   top margin so the picker does not start with a gap. */
/* A COLLAPSIBLE HEADING, and one tile per row inside it (Graham 2026-09-09: "maybe have one per line
   but much bigger, have each title toggle down, include a search"). The drawing is the whole point of a
   style picker, and three to a row in a side pane makes it the size of a postage stamp; full width it is
   a picture somebody can actually judge. Twenty-seven of those is only tolerable because the groups
   start shut - the markup opens the one holding the current choice, so the pane opens on the four or
   five options nearest to what the page already does. */
.ce-tiles-group {
    display: flex; align-items: center; gap: .5rem;
    margin: .55rem 0 .35rem; padding: .35rem .5rem; cursor: pointer;
    font-size: .68rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
    color: #64707f; background: #f1f4f8; border-radius: 7px;
    list-style: none;                       /* the default marker; ::before below replaces it */
}
.ce-tiles-group::-webkit-details-marker { display: none; }
.ce-tiles-group:hover { background: #e7ecf3; color: #3d4756; }
/* The twisty, drawn rather than borrowed so it turns with the state and needs no icon font. */
.ce-tiles-group::before {
    content: ""; flex: none; width: 0; height: 0;
    border: 4px solid transparent; border-left-color: currentColor; border-right-width: 0;
    transition: transform .12s; transform-origin: 25% 50%;
}
.ce-tiles-grp[open] > .ce-tiles-group::before { transform: rotate(90deg); }
.ce-tiles-grp[open] > .ce-tiles-group { background: #e7ecf3; color: #3d4756; }
.ce-tiles-count {
    margin-left: auto; font-size: .66rem; font-weight: 600; letter-spacing: .02em;
    color: #8a95a3; background: #fff; border-radius: 10px; padding: .05rem .4rem;
}
/* ONE PER ROW inside a group by default. Ungrouped pickers - Overlay, Height, Corners - keep the small
   grid above, because their shapes are simple and a full-width drawing of a corner radius would be
   comic. The author can override the count with the toggle; [data-ce-cols] on the field carries it. */
.ce-tiles-grp .ce-tiles { grid-template-columns: 1fr; gap: .45rem; }
.ce-tiles-grp .ce-tiles .fs-layout-name { font-size: .8rem; padding: .4rem .5rem; }
[data-ce-cols="2"] .ce-tiles-grp .ce-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
[data-ce-cols="3"] .ce-tiles-grp .ce-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
[data-ce-cols="4"] .ce-tiles-grp .ce-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* Past two to a row the name has to shrink or it wraps to three lines inside a button 80px wide. */
[data-ce-cols="3"] .ce-tiles-grp .ce-tiles .fs-layout-name,
[data-ce-cols="4"] .ce-tiles-grp .ce-tiles .fs-layout-name { font-size: .68rem; padding: .28rem .3rem; }

/* The bar above the tiles: the search takes the room, the count sits at the end. */
.ce-tiles-bar { display: flex; align-items: center; gap: .5rem; }
.ce-tiles-bar .ce-tiles-search { flex: 1 1 auto; min-width: 0; }
.ce-cols { flex: none; display: flex; border: 1px solid #dfe4ea; border-radius: 7px; overflow: hidden; }
.ce-cols-btn {
    width: 24px; height: 28px; padding: 0; border: 0; background: #fff; cursor: pointer;
    font-size: .72rem; font-weight: 600; color: #7a8698; line-height: 1;
}
.ce-cols-btn + .ce-cols-btn { border-left: 1px solid #eef1f5; }
.ce-cols-btn:hover { background: #f1f4f8; color: #3d4756; }
.ce-cols-btn.is-on { background: var(--tblr-primary, #206bc4); color: #fff; }
.ce-cols-btn:focus-visible { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: -2px; }
/* ── TWELVE TO A PAGE (Graham 2026-09-10: "12 per page in the picker") ───────────────────────────
   ce-pg-off, not [hidden], and the two are deliberately separate channels: the search filters with
   `hidden`, so if paging used it too the last one to run would win and a search would resurrect
   tiles from page three while paging would hide tiles the search had just matched. Kept apart, a
   tile shows only when neither has an opinion against it, which is the correct rule. */
.fs-layout-card.ce-pg-off { display: none; }
/* SEARCH SUSPENDS PAGING ENTIRELY. Matches on page three are still matches, and a search that found
   four things and showed one would be worse than no search. The pager goes with it - it would be
   pointing at pages that are not being used. */
.ce-tiles-searching .fs-layout-card.ce-pg-off { display: revert; }
.ce-tiles-searching .ce-tiles-pager { display: none; }
.ce-tiles-pager { display: flex; gap: .25rem; margin: .5rem 0 .1rem; }
.ce-pg-btn {
    min-width: 26px; height: 26px; padding: 0 .35rem; border: 1px solid #dfe4ea; border-radius: 6px;
    background: #fff; cursor: pointer; font-size: .74rem; font-weight: 600; color: #7a8698; line-height: 1;
}
.ce-pg-btn:hover { background: #f1f4f8; color: #3d4756; }
.ce-pg-btn.is-on { background: var(--tblr-primary, #206bc4); border-color: var(--tblr-primary, #206bc4); color: #fff; }
.ce-pg-btn:focus-visible { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: 1px; }
/* ── THE STYLING POPOVER ON A TEXT BOX (Graham 2026-09-10) ───────────────────────────────────────
   The row this opens is a REAL field row that has been moved here, not a copy, so everything inside
   it keeps the styling it already had. The panel only has to be a panel. */
.ce-pop-btn {
    border: 0; background: none; padding: 0 .25rem; margin-left: .3rem; cursor: pointer;
    color: #9aa7b8; font-size: .95rem; line-height: 1; vertical-align: middle;
}
.ce-pop-btn:hover { color: var(--tblr-primary, #206bc4); }
.ce-pop-btn[aria-expanded="true"] { color: var(--tblr-primary, #206bc4); }
.ce-pop-btn:focus-visible { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: 2px; border-radius: 4px; }
/* THE ICON IS THE SWATCH once anything is set (Graham 2026-09-10: "Can you make the pen icon reflect
   the text and background colour"). The glyph wears the text colour and the button wears the
   background - the same relationship the two have on the banner itself.
   THE RING IS NOT DECORATION. A pale ink on the pane's white ground, or a white panel colour, would be
   an invisible swatch: indistinguishable from nothing set, which is the one thing this exists to tell
   apart. The ring gives every set state an edge whatever colour is in it, and the white outer half
   keeps it legible against a dark background colour too.
   The hover and open rules above would otherwise repaint a glyph that is carrying the author's own
   colour, so both are answered here at a higher specificity. */
.ce-pop-btn.ce-pop-set {
    border-radius: 5px; padding: 1px 4px;
    box-shadow: inset 0 0 0 1px rgba(16,24,40,.22), 0 0 0 1px rgba(255,255,255,.9);
}
.ce-pop-btn.ce-pop-set:hover, .ce-pop-btn.ce-pop-set[aria-expanded="true"] { color: inherit; }
.ce-pop-btn.ce-pop-set:hover { box-shadow: inset 0 0 0 1px rgba(16,24,40,.4), 0 0 0 2px rgba(32,107,196,.25); }
.ce-pop-panel {
    margin: .5rem 0 .1rem; padding: .7rem .8rem .1rem; border: 1px solid #e3e8ef; border-radius: 8px;
    background: #f8fafc;
}
/* The rows inside lose their bottom margin on the last one, so the panel closes up neatly rather than
   carrying a stripe of empty ground under the final control. */
.ce-pop-panel > .mb-3:last-child { margin-bottom: .6rem !important; }
/* A moved row must not keep a width its old group gave it - the panel is narrower than the pane. */
.ce-pop-panel > [data-ce-w] { width: auto; }
/* The sentence shown where a per-line control would be if one could work (Script0724). */
.ce-pop-none { margin: 0 0 .5rem; font-size: .78rem; color: #6c7a8a; }
/* Nothing matched the search: the group hides rather than sitting open and empty. */
.ce-tiles-grp[hidden] { display: none !important; }
.ce-tiles-search::placeholder { color: #9aa5b3; }
.ce-tiles-noresult { font-size: .78rem; color: #8a95a3; padding: .4rem .1rem; }

/* ── A SEARCH OVER THE WHOLE PANE ───────────────────────────────────────────
   Graham 2026-09-10: "Can you add a search at the top to filter out all features not in the filter?"
   The box above narrows ONE tile set. This one narrows the pane: fields, groups, and the scope heading
   over a section that empties out. Two boxes rather than one because they answer two different
   questions - "which of these twenty-seven templates" and "where in sixty settings is the thing I
   want" - and the pane one drives the picker one while it has a term, so the two can never be showing
   contradictory answers at once. The filtering lives in control-editor.js; these are the rules it needs.

   STICKY, AND THE OFFSETS ARE NOT DECORATION. .ce-item-scroll is the scroll container, so a bar that is
   merely the first child scrolls away after half a turn of the wheel and the filter becomes unclearable
   without scrolling back up to it. The negative margin and the equally negative `top` are a matched
   pair: a sticky element is held so its MARGIN box stays inside the scrollport, so pulling the margin
   box up by the card's own padding and offsetting `top` by the same amount is what puts the BORDER box
   flush against the top edge. With `top: 0` alone the bar would stick one card-padding below the edge
   and field rows would slide visibly through the strip above it. The values are read from Tabler's own
   card variables, never declared here, so the bar stays flush if the admin theme re-spaces its cards.
   It works unchanged in the Expand overlay, where .ce-max-body becomes the scrollport instead. */
.ce-find {
    position: sticky; z-index: 3;
    top: calc(-1 * var(--tblr-card-spacer-y, 1rem));
    margin: calc(-1 * var(--tblr-card-spacer-y, 1rem)) calc(-1 * var(--tblr-card-spacer-x, 1.25rem)) .35rem;
    padding: var(--tblr-card-spacer-y, 1rem) var(--tblr-card-spacer-x, 1.25rem) .5rem;
    display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .5rem;
    background: #fff; border-bottom: 1px solid #eef1f5;
}
.ce-find-in { flex: 1 1 9rem; min-width: 0; }
.ce-find-in::placeholder { color: #9aa5b3; }
/* The count speaks only while it has something to say; an idle "64 of 64" is noise on every pane. */
.ce-find-count { flex: 0 0 auto; font-size: .72rem; color: #8a95a3; font-variant-numeric: tabular-nums; }
.ce-find-none { flex: 1 1 100%; font-size: .78rem; line-height: 1.35; color: #8a95a3; padding: .15rem .1rem 0; }
/* A way out that is not a keystroke. On a tablet there is no Escape key, and the cross a browser draws
   inside type=search is not drawn by all of them - so the one screen that is otherwise a dead end, the
   empty state, carries its own. Hidden until there is a term, because a Clear beside an empty box is a
   control that does nothing. */
.ce-find-clear { flex: 0 0 auto; display: none; border: 0; background: none; padding: 0; cursor: pointer;
    font-size: .74rem; color: #7a8698; text-decoration: underline; }
.ce-finding .ce-find-clear { display: inline-block; }
.ce-find-clear:hover { color: #206bc4; }
/* THE FILTER'S OWN CHANNEL. `hidden` is spoken for twice over in this pane - a conditional row writes
   it (data-ce-when) and the tile search writes it on cards - and the pager had to invent .ce-pg-off for
   the same reason. A third writer would mean the last one to run wins: clearing a filter would
   resurrect a row whose condition is unmet, and meeting a condition would resurrect a row the filter
   had just excluded. With a channel each, a thing shows only when NO channel has an opinion against it.
   SCOPED TO .ce-finding ON THE PANE, which is the safety net rather than the selector being fussy: the
   class goes the moment the box is empty, so nothing can be left hidden by a cleared search even if a
   later bug leaves a stale .ce-find-off behind on a row.
   !important because .ce-fg-body is a flex container and its children carry their own display. */
.ce-finding .ce-find-off { display: none !important; }
/* Greyed rather than hidden while a search runs. Both buttons write the remembered open state for every
   group in the pane, which is exactly the state the search has borrowed and promised to give back. A
   control that vanishes when you type teaches nothing; one that is dim with a reason on hover says
   which key to press first. */
.ce-finding .ce-changes-expand[disabled], .ce-finding .ce-changes-collapse[disabled] {
    opacity: .45; cursor: default; text-decoration: none;
}

/* THE BOX DOES NOT CLIP, THE BACKDROP DOES. overflow:hidden used to sit here, which was tidy until the
   picture was dragged flush to an edge: the resize handles hang half outside the picture, so at x=100
   the two right-hand ones fell outside the box and were clipped away - unreachable at exactly the
   position an author is most likely to choose. Moving the clip onto the backdrop keeps the photograph
   inside its rounded corners and lets the handles escape by the few pixels they need. */
.ce-xy { position: relative; width: 100%; aspect-ratio: 16 / 7; border-radius: 8px;
    border: 1px solid #d6dde8; background: #1a2230; cursor: crosshair; touch-action: none;
    user-select: none; }
.ce-xy-img { position: absolute; inset: 0; background-size: cover; background-position: center;
    opacity: .55; border-radius: 7px; overflow: hidden; }
.ce-xy-img:empty { background-color: #2a3448; }
/* What gets dragged is the picture itself, at the size and proportion it will really have, because the
   only question this control answers is "where does that end up" — a ring marker makes the author judge
   that from a point and guess the rest. JS sets left/top/width/height in pixels; everything the picture
   is standing on is here. The white outline is what keeps it legible against a dark photograph. */
.ce-xy-pic { position: absolute; padding: 0; border: 1px solid rgba(255,255,255,.9); border-radius: 2px;
    background: rgba(32, 107, 196, .35) center / contain no-repeat;
    box-shadow: 0 0 0 1px rgba(16,24,40,.45), 0 2px 6px rgba(16,24,40,.45); cursor: grab; }
.ce-xy-pic:active { cursor: grabbing; }
.ce-xy-pic:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.ce-xy-unset .ce-xy-pic { display: none; }
/* The corners resize, and they are the only part of the picture that is not a move target, so each one
   says so with its own cursor. They sit half outside the picture: a handle wholly inside a small
   picture covers the thing the author is trying to see. */
.ce-xy-h { position: absolute; width: 11px; height: 11px; background: #fff; border: 1px solid #206bc4;
    border-radius: 2px; box-shadow: 0 1px 2px rgba(16,24,40,.5); }
.ce-xy-h[data-ce-corner="nw"] { left: -6px;  top: -6px;    cursor: nwse-resize; }
.ce-xy-h[data-ce-corner="ne"] { right: -6px; top: -6px;    cursor: nesw-resize; }
.ce-xy-h[data-ce-corner="sw"] { left: -6px;  bottom: -6px; cursor: nesw-resize; }
.ce-xy-h[data-ce-corner="se"] { right: -6px; bottom: -6px; cursor: nwse-resize; }
/* SMALL PICTURES MOVE THE HANDLES OUT, THEY DO NOT LOSE THEM. The first version of this rule hid the
   corners once the picture fell under 30px, reasoning that four handles would cover it entirely. That
   was true and still wrong: a logo at 10% of the banner draws about 26px here, which is an ordinary
   setting rather than an edge case, so the control quietly dropped its resize corners in the exact
   place an author is most likely to want them (Graham 2026-09-09, at 10%: "we need to be able to
   adjust the image by clicking on any corner"). Sitting them fully outside keeps every corner grabbable
   AND leaves the whole picture as a move target, which is what a small object needs from both gestures. */
.ce-xy-pic.ce-xy-tiny .ce-xy-h { width: 9px; height: 9px; }
.ce-xy-pic.ce-xy-tiny .ce-xy-h[data-ce-corner="nw"] { left: -11px;  top: -11px; }
.ce-xy-pic.ce-xy-tiny .ce-xy-h[data-ce-corner="ne"] { right: -11px; top: -11px; }
.ce-xy-pic.ce-xy-tiny .ce-xy-h[data-ce-corner="sw"] { left: -11px;  bottom: -11px; }
.ce-xy-pic.ce-xy-tiny .ce-xy-h[data-ce-corner="se"] { right: -11px; bottom: -11px; }
.ce-xy-read { position: absolute; left: 6px; bottom: 6px; z-index: 2; pointer-events: none;
    font-size: .72rem; font-weight: 600; color: #1b2027; background: rgba(255,255,255,.9);
    border-radius: 5px; padding: 1px 6px; font-variant-numeric: tabular-nums; }
.ce-xy-unset .ce-xy-read { font-weight: 500; font-style: italic; color: #5e6675; }
.ce-xy-clear { position: absolute; right: 4px; top: 4px; z-index: 3; width: 18px; height: 18px;
    line-height: 1; padding: 0; border: 0; border-radius: 4px; background: rgba(255,255,255,.85);
    color: #5e6675; font-size: .9rem; cursor: pointer; }
.ce-xy-clear:hover { background: #fff; color: #1b2027; }
.ce-xy-unset .ce-xy-clear { display: none; }   /* nothing to clear */

/* A FADE PREVIEW NEEDS SOMETHING TO FADE INTO, and it has to be the SAME something the page uses. The
   overlay bar paints a wash ON the photo so the bar behind it never shows; a fade makes the photo
   itself transparent, so whatever sits behind it is the whole point.
   #1a2230 is not a taste choice: it is .control-banners .banner's own background, the "fallback when
   there is no image", and cms-content.css says in words that a faded photo fades "toward the banner's
   dark base". This bar started on a pale ground, so turning Fade down made the preview go WHITE while
   the page went BLACK — the preview showed the opposite of the result (Graham 2026-09-09: "The fade on
   the admin should show it getting darker"). Change this only if that background changes. */
.ce-range[data-ce-preview="fade"] { background: #1a2230; }
.ce-range[data-ce-preview="fade"] .ce-range-img { transition: opacity .08s linear; }

/* UNSET, AND SAYING SO. A blank-capable bar whose hidden input is empty means "inherited" or "off",
   which is not a position on the bar - so the track goes flat and grey, the thumb recedes, and the
   readout reads a word instead of a percentage. The point is that "not set" and "set to 50" stopped
   being the same picture; before this they were pixel-identical (Script0684). */
.ce-range-nullable { padding-right: 26px; }
.ce-range-unset .ce-range-img, .ce-range-unset .ce-range-wash { opacity: .25; filter: grayscale(1); }
.ce-range-unset .ce-range-in::-webkit-slider-thumb { background: #e9edf2; border-color: rgba(16,24,40,.2); box-shadow: none; }
.ce-range-unset .ce-range-in::-moz-range-thumb { background: #e9edf2; border-color: rgba(16,24,40,.2); box-shadow: none; }
.ce-range-unset .ce-range-val { color: #6c7a8a; font-weight: 500; font-style: italic; }
/* The clear sits OUTSIDE the bar's padding box, so it never covers the track it switches off. */
.ce-range-clear { position: absolute; right: 4px; top: 50%; transform: translateY(-50%); z-index: 4;
    width: 18px; height: 18px; line-height: 1; padding: 0; border: 0; border-radius: 4px;
    background: rgba(255,255,255,.9); color: #6c7a8a; font-size: .9rem; cursor: pointer; display: none; }
.ce-range-nullable .ce-range-clear { display: block; right: 4px; }
.ce-range-nullable .ce-range-val { right: 26px; }
.ce-range-unset .ce-range-clear { display: none; }   /* nothing to clear */
.ce-range-clear:hover { background: #fff; color: #1b2027; }

/* The washes, keyed on the same names and variables the storefront uses. */
.ce-range-wash { --banner-ov-c: #0d1420; }
.ce-range[data-ov="flat"] .ce-range-wash,
.ce-range[data-ov="tint"] .ce-range-wash { background: var(--banner-ov-c); opacity: var(--banner-ov-a, 0); }
/* 95% everywhere, mirroring cms-content.css. These were 75/75/80/85 and were lengthened with the
   storefront in the same change; the whole value of this bar is that it shows the real thing. */
.ce-range[data-ov="up"] .ce-range-wash { background: linear-gradient(to top,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
.ce-range[data-ov="down"] .ce-range-wash { background: linear-gradient(to bottom,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
.ce-range[data-ov="left"] .ce-range-wash { background: linear-gradient(to right,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
.ce-range[data-ov="right"] .ce-range-wash { background: linear-gradient(to left,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
/* The vignette's stop is 45%, matching the storefront rule exactly. On a 34px-tall bar an ellipse reads
   as a soft band with clear ends, which is honestly what a vignette does to a wide banner too. */
.ce-range[data-ov="vignette"] .ce-range-wash { background: radial-gradient(ellipse at center,
    transparent 45%, color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent) 100%); }
/* The four corners, mirroring cms-content.css angle for angle. The angle points where the gradient
   TRAVELS, so the dark corner is the opposite one: 135deg darkens the top-left, 225deg the top-right,
   45deg the bottom-left, 315deg the bottom-right. "diagonal" is kept for banners saved before the
   corners were split out, and is the top-left one. */
.ce-range[data-ov="diag-tl"] .ce-range-wash,
.ce-range[data-ov="diagonal"] .ce-range-wash { background: linear-gradient(135deg,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
.ce-range[data-ov="diag-tr"] .ce-range-wash { background: linear-gradient(225deg,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
.ce-range[data-ov="diag-bl"] .ce-range-wash { background: linear-gradient(45deg,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
.ce-range[data-ov="diag-br"] .ce-range-wash { background: linear-gradient(315deg,
    color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent), transparent 95%); }
/* The circle keeps closest-side and its 38/78 stops from the storefront. On a 34px-tall bar that is a
   round clear patch in the middle with both ends properly dark - which is the difference from the
   vignette above, and the difference is the reason the tile exists, so the bar has to show it. */
.ce-range[data-ov="circle"] .ce-range-wash { background: radial-gradient(circle closest-side at center,
    transparent 38%, color-mix(in srgb, var(--banner-ov-c) calc(var(--banner-ov-a, 0) * 100%), transparent) 78%); }
@supports not (background: color-mix(in srgb, #000 50%, transparent)) {
    .ce-range[data-ov] .ce-range-wash { background: var(--banner-ov-c); opacity: var(--banner-ov-a, 0); }
}
/* No overlay chosen: say so on the bar rather than showing a wash that will not appear. */
.ce-range[data-ov=""] .ce-range-wash, .ce-range:not([data-ov]) .ce-range-wash { background: none; }

/* ── CHECK SETS ─────────────────────────────────────────────────────────────────────────────────
   Field type "checks" in the shared _EditorField: a closed set of options as checkboxes, stored as
   the CSV the column already held (Graham 2026-09-08: "Should these be checkboxes?").
   Wraps rather than scrolls - these sets are five or six items, not fifty - and each box keeps its
   label on one line so a two-word option never breaks across the tick. */
.ce-checks { display: flex; flex-wrap: wrap; gap: .15rem 1.1rem; padding: .35rem .5rem;
    border: 1px solid #e6e9ee; border-radius: 8px; background: #fbfcfd; }
.ce-checks .ce-check { margin: 0; display: flex; align-items: center; gap: .4rem; white-space: nowrap; }
.ce-checks .form-check-input { margin: 0; float: none; }
.ce-checks .form-check-label { font-size: .82rem; }

/* ── NINE SQUARES ───────────────────────────────────────────────────────────────────────────────
   Field type "pos9" in the shared _EditorField: a position chosen as a position (Graham 2026-09-08:
   "Should we have the text location (nine squares)"). A dropdown makes you read "Bottom centre" and
   translate it into a place; the grid says it directly, and it is the control every design tool uses
   for this. Small on purpose - it is a picker, not a picture, so it sits in the normal field row
   beside its label rather than taking the full width the way tiles do. */
.ce-pos9 { display: grid; grid-template-columns: repeat(3, 34px); grid-auto-rows: 26px; gap: 3px;
    width: max-content; padding: 4px; border: 1px solid #e6e9ee; border-radius: 8px; background: #fbfcfd; }
.ce-pos9-cell { position: relative; display: flex; margin: 0; cursor: pointer; border-radius: 4px;
    border: 1px solid #e6e9ee; background: #fff; padding: 3px; }
.ce-pos9-cell input { position: absolute; opacity: 0; pointer-events: none; }
.ce-pos9-cell:hover { border-color: #cfd6de; }
.ce-pos9-cell:has(input:checked) { border-color: var(--tblr-primary, #206bc4); background: #eef5ff; }
.ce-pos9-cell:has(input:focus-visible) { outline: 2px solid var(--tblr-primary, #206bc4); outline-offset: 1px; }
/* An empty square keeps the grid square when a control does not offer all nine. */
.ce-pos9-empty { border-style: dashed; background: #f6f8fa; cursor: default; }
/* The mark inside each cell sits where the thing itself would sit — that IS the information. */
.ce-pos9-dot { display: block; width: 12px; height: 3px; border-radius: 2px; background: #9fb4d0; }
.ce-pos9-cell:has(input:checked) .ce-pos9-dot { background: var(--tblr-primary, #206bc4); }
.ce-pos9-tl { align-items: flex-start; justify-content: flex-start; }
.ce-pos9-tc { align-items: flex-start; justify-content: center; }
.ce-pos9-tr { align-items: flex-start; justify-content: flex-end; }
.ce-pos9-ml { align-items: center; justify-content: flex-start; }
.ce-pos9-mc { align-items: center; justify-content: center; }
.ce-pos9-mr { align-items: center; justify-content: flex-end; }
.ce-pos9-bl { align-items: flex-end; justify-content: flex-start; }
.ce-pos9-bc { align-items: flex-end; justify-content: center; }
.ce-pos9-br { align-items: flex-end; justify-content: flex-end; }
.ce-pos9-name { font-weight: 600; color: #5e6675; }

/* ── WHAT HAS BEEN CHANGED, at the foot of the Look pane ───────────────────────────────────────────
   The other half of collapsing everything. Nine shut headings make a clean pane and a silent one, so
   this names what is actually set - scope, group, field, value - in the order the groups appear above,
   and each line opens its group and jumps to the row. Quiet by construction: a tinted shelf and a
   hairline, so it reads as a footnote to the pane rather than a tenth section competing with it. */
/* THE HEADING IS OUTSIDE THE BUBBLE (Graham 2026-09-10), the way "THIS ITEM: …" sits above the groups
   it heads. So .ce-changes is now only a wrapper and the box has moved to .ce-changes-body: the border,
   the radius and the ground belong to the LIST, not to the title above it. */
.ce-changes { margin: 1rem .1rem .2rem; }
.ce-changes-body { padding: .6rem .85rem .55rem;
    border: 1px solid #e6e9ee; border-radius: 10px; background: #fbfcfd; }
.ce-changes.is-folded .ce-changes-body { display: none; }
/* The title row. The two group toggles ride here rather than in the bubble because they act on the
   groups BELOW this panel, not on the list inside it - being outside is where they were always
   describing themselves from. Restore Defaults stays in the bubble for the mirror of that reason. */
.ce-changes-top { display: flex; align-items: center; gap: .5rem; margin-bottom: .35rem; padding: 0 .15rem; }
.ce-changes-fold { border: 0; background: none; cursor: pointer; padding: 0; line-height: 1;
    color: #9aa7b8; display: inline-flex; }
.ce-changes-fold:hover { color: #206bc4; }
.ce-changes-fold i { transition: transform .12s ease; }
.ce-changes-fold[aria-expanded="false"] i { transform: rotate(-90deg); }
.ce-changes-sp { flex: 1 1 auto; }
.ce-changes-expand { border: 0; background: none; cursor: pointer;
    font-size: .74rem; font-weight: 600; color: #6c7a8a; padding: 0; }
.ce-changes-expand:hover { color: #206bc4; text-decoration: underline; }
.ce-changes-title { font-size: .68rem; font-weight: 700; letter-spacing: .09em;
    text-transform: uppercase; color: #7a8698; }
.ce-changes-collapse { border: 0; background: none; cursor: pointer;
    font-size: .72rem; font-weight: 600; color: #6b93c9; padding: .1rem .2rem; }
.ce-changes-collapse:hover { color: #206bc4; text-decoration: underline; }
/* THE MASTER RESET (Graham 2026-09-10: "a master reset that accepts the defaults for the slide"). It
   carries the margin-left:auto that used to sit on Collapse all, so the pair stays together at the
   right whether or not this one is showing - and it IS hidden whenever the slide has nothing to put
   back, because a button that is always there and usually does nothing teaches people it does nothing.
   Amber rather than the blue of its neighbours: it is the only control in this box that throws work
   away, and with autosave on there is nothing behind it to undo with. */
/* margin-left:auto is gone with the move to its own row: it used to push this away from the heading,
   and on the new row it would push it to the RIGHT and sit it against Collapse all - the opposite of
   what the spacer is for. */
/* Back inside the bubble, above the list it empties ("keep restore defaults inside the customized
   bubble"), so the one destructive control here sits with the thing it acts on. */
.ce-changes-resetall { margin-bottom: .45rem; border: 1px solid #e5c893; border-radius: 5px;
    background: #fdf6e9; cursor: pointer; font-size: .72rem; font-weight: 600; color: #8a6a24;
    padding: .12rem .45rem; }
.ce-changes-resetall:hover { background: #f8ecd6; color: #6b5424; }
.ce-changes-resetall[hidden] { display: none; }
.ce-changes-list { list-style: none; margin: 0; padding: 0; }
/* The row is the link plus its own reset. The reset is a SIBLING of the link, not inside it, or
   clicking it would also fire the link and scroll the pane away from the button just pressed. */
.ce-changes-list li { display: flex; align-items: center; gap: .15rem; }
.ce-changes-list li + li { border-top: 1px dashed #eef1f5; }
/* Shown on hover or keyboard focus anywhere in the row, so a column of arrows does not sit down the
   side of a list whose job is to be read. It keeps its space either way - appearing on hover must not
   reflow the line under the cursor. */
.ce-changes-reset { flex: 0 0 auto; border: 0; background: none; cursor: pointer; opacity: 0;
    font-size: .8rem; line-height: 1; color: #8a95a3; padding: .2rem .3rem; border-radius: 5px; }
.ce-changes-list li:hover .ce-changes-reset,
.ce-changes-reset:focus-visible { opacity: 1; }
.ce-changes-reset:hover { background: #fdf6e9; color: #8a6a24; }
@media (hover: none) { .ce-changes-reset { opacity: 1; } }
.ce-changes-link { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem;
    flex: 1 1 auto; min-width: 0; text-align: left; border: 0; background: none; cursor: pointer;
    padding: .3rem .15rem; font-size: .76rem; color: #5e6675; border-radius: 5px; }
.ce-changes-link:hover { background: #f1f4f8; color: #3d4756; }
/* THE SCOPE, AS ONE GLYPH. It used to be "This item" or "Every item" spelled out, which was the widest
   thing on every line and repeated all the way down. A photograph for this slide, a stack for all of
   them; the pair has to read as one-versus-many at this size, so they are deliberately not two
   variations on the same shape. The page-wide one takes the accent, because "this changes every banner
   on the page" is the half worth noticing. The words survive in a title and an aria-label. */
.ce-ch-scope { flex: 0 0 auto; display: inline-flex; align-items: center; color: #9aa5b3; font-size: .95rem; }
.ce-ch-scope.ce-ch-scope-page { color: #6b93c9; }
.ce-ch-scope i { line-height: 1; }
.ce-ch-sep { color: #c3ccd6; }
.ce-ch-group { color: #7a8698; }
.ce-ch-field { font-weight: 600; color: #3d4756; }
/* The value is the thing being reported, so it gets the only strong colour on the line. */
.ce-ch-val { margin-left: auto; font-weight: 600; color: #206bc4; font-variant-numeric: tabular-nums; }
/* Where it landed. A flash rather than focus - focusing a colour or a range would change it. */
@keyframes ce-ch-flash { from { background: rgba(32,107,196,.16); } to { background: transparent; } }
.ce-ch-flash { animation: ce-ch-flash 1.4s ease-out; border-radius: 6px; }
@media (prefers-reduced-motion: reduce) { .ce-ch-flash { animation: none; outline: 2px solid rgba(32,107,196,.45); } }


/* THEME COLOUR ROLES (teardown 4.5, wwwroot/js/colour-roles.js): the site's palette beside every colour field,
   stored by name so the colour follows the theme. The chip shows the site's colour, or the role's initial when the
   site has none set. */
.cms-role-swatches { display: inline-flex; flex: 0 0 auto; align-items: stretch; }
.cms-role-sw { width: 2rem; min-height: 2rem; padding: 0; border: 1px solid #cbd3de; border-radius: 0; background: #f1f5f9; color: #475569; font-size: .72rem; font-weight: 600; line-height: 1; }
.cms-role-sw:hover { filter: brightness(.92); }
.cms-role-sw.on { box-shadow: inset 0 0 0 2px #fff, inset 0 0 0 4px #1d4ed8; }
/* A theme colour the site has not set: visibly a gap, not a choice (colour-roles.js). */
.cms-role-sw.cms-role-sw-unset, .cms-role-sw.cms-role-sw-unset:disabled { border-style: dashed; background: transparent; color: #94a3b8; opacity: 1; cursor: not-allowed; filter: none; }
.cms-role-swatches--below { display: flex; gap: .25rem; margin-top: .3rem; }
.cms-role-swatches--below .cms-role-sw { width: 1.75rem; min-height: 1.5rem; border-radius: 4px; }

/* A number box with common choices (_EditorField.cshtml): the drop-down takes the room, the box the number. */
.ce-num-presets { display: flex; gap: .4rem; align-items: center; }
.ce-num-presets > select { flex: 1 1 auto; min-width: 0; }
.ce-num-presets > input[type="number"] { flex: 0 0 6.5rem; }
.e2-strip .ce-closedbar { position: static; margin-left: .25rem; background: none; border: 0; box-shadow: none; padding: 0; }
