/* ==========================================================================
   cclg.css — the SHARED visual language of the Clear Choice Academy.

   This is the source of truth for the design tokens (`:root` below) and every
   shared component class. Graduated out of `layouts/app.blade.php` on
   2026-07-16 (design-system.md rule 8); it was inline for the whole build
   before that, which is why the history lives in git, not here.

   It is a PLAIN STATIC FILE on purpose. There is NO build step, no bundler,
   no npm in the deploy path — the owner is not a developer and must never
   need one to change a colour. Edit this file directly and deploy.

   Cache-busting: `layouts/app.blade.php` links it with `?v={filemtime}`, so a
   deploy changes the URL and nobody is stranded on stale CSS. Inline CSS could
   never go stale; this can, and that link is what stops it.

   Rules that live here are pinned by tests — breaking one fails the suite:
     · the shared typed-field box  -> tests/Feature/FormFieldStylingTest.php
     · the ≤820px mobile pair      -> tests/Feature/MobileTableOverflowTest.php
     · this file being linked at all, and the layout staying style-free
                                   -> tests/Feature/SharedStylesheetTest.php
   See docs/reference/design-system.md before changing anything in here.
   ========================================================================== */
:root {
    --brand:#35529b; --brand-2:#2a4179; --brand-ink:#1a1230; --navy:#110c1d;
    --ink:#1e2333; --mut:#6b7488; --line:#e7ebf3; --card:#ffffff; --ring:rgba(53,82,155,.18);

    /* THE RESERVED COMPLIANCE ALARM — one colour, one place (2026-07-25).
       Five comments in this file and both design docs had cited `--danger` for months while the
       token did not exist and `var(--danger)` was used zero times; design-system.md even stated the
       hex. The name was right and the definition was missing, so the most-cited rule in the codebase
       pointed at nothing while the colour it names sat hardcoded in seven places across the
       stylesheet and four views. Defining it is what makes those citations true, and what makes
       "the alarm's colour changes in ONE edit" a fact rather than an intention.
       🛑 Reserved: rule 13 — it marks a VERDICT, never a NOTICE, never on a live-call surface.
       NOT for ordinary form errors (`.flash.err`) and not for warnings (`.tag.warn` amber). */
    --danger:#c0392b; --danger-2:#a93226; --danger-ink:#a12626; --danger-glow:rgba(192,57,43,.65);

    /* THE LIVE-CALL COLOUR (owner 2026-07-31) — a rep is on the phone with this customer RIGHT NOW.
       🛑 A STATE, NOT A VERDICT, which is why it is emphatically not `--danger`: rule 13 bars the
       alarm from live-call surfaces outright, and spending it on a state would blunt it where it
       has to mean something. Deliberately the family `.tag.call-in` already uses — on the lead
       screen green already means PHONE, so this reads as that chip turned up rather than a sixth
       colour nobody has met. Paints `.page-head.lead-head.on-call` and `.live-mark`. */
    --live:#0f6b5d; --live-2:#12564c; --live-tint:#e6f4f0; --live-line:#b9ddd4;

    /* ═══ THE ELEVATION SCALE ═══ (2026-07-25)
       🛑 --z-callbar IS A CEILING, NOT A LAYER. Everything a person can click or that blocks a click
       sits BELOW it. The live-call controls — Hang up, Hold, Mute, Transfer, the dispositions — outrank
       every transient overlay, because a notification you can dismiss must never bury the button that
       ends a real call.

       This is the THIRD time this bug shipped, and the first two fixes were both numeric. The owner,
       on live calls: "i cant see anyting on top to hang up" and "could not reach Hang up, and had to
       end the call from VICIdial". Each time the answer was to bump one number, and each time the next
       overlay somebody wrote re-broke it — because 15 hand-picked integers across 8 files with no
       shared scale have no way to express "below the call bar". Now they do.

       Two facts that made the old comment here wrong, and are worth keeping:
         · position:sticky creates a stacking context ALWAYS, whatever the z-index. So .ph-strip's
           children are permanently ceilinged at its own level and NO child-side fix is possible —
           the fix belongs on the parent, which is here.
         · z-index applies to FLEX ITEMS even at position:static. That is why the strip keeps its
           rank on the call surface, where it is a static flex item.

       --z-decoration is the ONE layer allowed above the ceiling, and only for `pointer-events:none`
       surfaces (the confetti canvases). It paints over the bar and cannot take a click from it.
       Pinned by CallControlsUnburiableTest. */
    --z-raised:2;         /* a card lifting on hover, stats over a hero band */
    --z-sticky-inner:5;   /* a toolbar sticky INSIDE its own scroll box */
    --z-dropdown:30;      /* a menu attached to a control */
    --z-popover:50;       /* help bubbles */
    --z-scroll-proxy:90;  /* the kanban proxy scrollbar */
    /* The lead card's pinned identity + tabs. Above the page's own content, below EVERYTHING that
       covers a page on purpose — a modal, the note drawer, a toast, and above all the call bar. The
       strip is itself sticky at `top:0` on the ceiling, so it always paints over this header: that
       is the structural reason a wrong offset here could never bury Hang up. */
    --z-lead-pin:100;
    --z-modal:120;        /* a page modal + its dim */
    --z-popcard:130;      /* the new-caller pop */
    --z-drawer-dim:138;
    --z-drawer:140;       /* the objections slide-over */
    --z-drawer-close:142;
    --z-countdown:145;    /* the 3-2-1 before a drill */
    --z-toast:150;        /* every toast + transient banner */
    --z-callbar:160;      /* 🛑 THE CEILING — nothing interactive goes above this */
    --z-callbar-panel:165;/* the strip's OWN dropdowns; inside its context, so this is a label */
    --z-decoration:200;   /* pointer-events:none ONLY */

    /* ═══ SURFACE ═══
       --wash was CONSUMED at .vol-cover .tag while being defined nowhere, so that tag rendered with
       no background at all. It was defined only inside the two print views' own <style> blocks — a
       shared name living in two private places and missing from the shared one. Value taken from
       those views so nothing shifts. */
    --wash:#f6f8fc;

    /* ═══ SPACING ═══ measured, not invented: 1,110 padding/margin/gap declarations across 54
       distinct values — every whole pixel from 1 to 18 was in use, which is a continuum, not a
       scale. These are the steps that already carry the weight; new work picks from them. */
    --sp-1:4px; --sp-2:6px; --sp-3:8px; --sp-4:10px; --sp-5:12px; --sp-6:14px;
    --sp-7:16px; --sp-8:18px; --sp-9:22px; --sp-10:26px; --sp-11:32px; --sp-12:40px;

    /* ═══ RADIUS ═══ 262 declarations over 21 distinct atoms, collapsed to the six that matter. */
    --r-xs:6px; --r-sm:8px; --r-md:11px; --r-lg:14px; --r-xl:18px; --r-pill:999px;

    /* ═══ TYPE ═══ 537 font-sizes over 38 distinct values, eight of them half-pixel. The half-steps
       are kept where they already read well (this is a redesign, not a re-typeset), but new work
       picks a step. Line-heights pair with the size they sit under. */
    --t-xs:11.5px; --t-sm:12.5px; --t-md:13.5px; --t-base:14px; --t-lg:15.5px;
    --t-xl:18px; --t-2xl:21px; --t-3xl:26px; --t-4xl:32px;
    --lh-tight:1.25; --lh-snug:1.45; --lh-body:1.6; --lh-prose:1.7;
    --tracking-tight:-.02em; --tracking-flat:0;

    /* ═══ ELEVATION (shadow) ═══ 84 box-shadows of which 63 were distinct, drawn from 17 different
       base colours at 30+ alphas — which is why nothing looked like it belonged to one system. Four
       tiers on ONE ink, plus the brand glow a primary button already wore.
       These are the LOOK of depth; --z-* above is the ORDER of it. Different jobs, don't confuse them. */
    --sh-ink:26,32,60;
    --sh-1:0 1px 2px rgba(var(--sh-ink),.06), 0 1px 3px rgba(var(--sh-ink),.10);
    --sh-2:0 4px 10px -4px rgba(var(--sh-ink),.18);
    --sh-3:0 14px 26px -14px rgba(var(--sh-ink),.30);
    --sh-4:0 28px 60px -30px rgba(var(--sh-ink),.45);
    --sh-brand:0 10px 22px -12px rgba(53,82,155,.55);

    /* ═══ MOTION ═══ transitions explain a state change; they never announce themselves. Every one
       is disabled wholesale under prefers-reduced-motion at the foot of this file. */
    --ease:cubic-bezier(.2,.7,.3,1);
    --dur-fast:.12s; --dur-med:.2s; --dur-slow:.32s;
}
* { box-sizing:border-box; }
html,body { margin:0; }
body {
    min-height:100vh; color:var(--ink);
    font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    -webkit-font-smoothing:antialiased;
}
body.app {
    background:
        radial-gradient(1100px 500px at 100% -10%, rgba(53,82,155,.10), transparent 60%),
        linear-gradient(180deg,#f7f9fd,#eef2f9);
}
body.hero {
    background:
        radial-gradient(1000px 600px at 85% -5%, rgba(53,82,155,.55), transparent 60%),
        radial-gradient(800px 500px at 0% 100%, rgba(53,82,155,.35), transparent 55%),
        linear-gradient(160deg,#171225 0%, #201a3a 45%, #2a3b78 100%);
    display:flex; align-items:center; justify-content:center; padding:28px;
}
a { color:var(--brand); }

/* ---- App shell: navy sidebar + content ---- */
.shell { display:flex; min-height:100vh; align-items:flex-start; }
.sidebar {
    /* 256→232 (owner 2026-07-22, the wide-boards round): the boards earn the width the rail was
       spending. 232 is the measured floor that keeps "Payment Calculator" and "Company Settings"
       on one line at 14px (218 wrapped both) — re-measure the longest label before shaving
       further. The 64px collapsed rail is untouched. */
    width:232px; flex-shrink:0; position:sticky; top:0; height:100vh; overflow-y:auto;
    background:linear-gradient(185deg,#151024 0%, #1d1738 55%, #222c63 100%);
    display:flex; flex-direction:column; padding:22px 16px; gap:6px;
    border-right:1px solid rgba(255,255,255,.06);
}
/* The BRAND BAND (owner 7-18: "not a square that looks like a button — the whole top-left area,
   edge to edge, white"). Flush to the viewport's top and both sidebar edges (negative margins
   cancel .sidebar's padding), no radius — a band, not a chip. Its 3px brand keel line meets the
   blue phone strip beside it, so the white band + blue strip read as ONE designed top edge across
   the whole app. Still the home link; hover dims a breath so it feels alive without shouting. */
.logo-chip {
    background:#fff; display:block; border-radius:0;
    margin:-22px -16px 10px; padding:14px 24px 11px;
    border-bottom:3px solid var(--brand);
    box-shadow:0 14px 26px -16px rgba(0,0,0,.65);
    transition:filter .15s ease;
}
.logo-chip:hover { filter:brightness(.96); }
/* Letterhead, not a stretched image (owner round 5 polish): the mark floats centered at 92%
   width, so the band keeps even breathing room on both sides. */
.logo-chip img { width:92%; height:auto; display:block; margin:0 auto; }
.side-eyebrow { color:#9aa0c8; font-size:10.5px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; padding:16px 12px 6px; }
.side-nav { display:flex; flex-direction:column; gap:4px; }
.side-nav a {
    display:flex; align-items:center; gap:11px; padding:11px 11px; border-radius:11px;
    color:#c3c8e6; text-decoration:none; font-weight:600; font-size:14px;
}
.side-nav a:hover { background:rgba(255,255,255,.07); color:#fff; }
.side-nav a.active { background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#fff; box-shadow:0 10px 20px -12px rgba(53,82,155,1); }
.side-nav svg { width:18px; height:18px; flex-shrink:0; }
.side-foot { margin-top:auto; padding-top:16px; }
.side-user { display:flex; align-items:center; gap:9px; flex-wrap:wrap; color:#c3c8e6; font-size:13px; padding:12px 13px; background:rgba(255,255,255,.05); border-radius:12px; margin-bottom:10px; }
.side-user b { color:#fff; }
.side-foot .btn.secondary { background:rgba(255,255,255,.08); color:#eef; box-shadow:none; width:100%; justify-content:center; }
.side-foot .btn.secondary:hover { background:rgba(255,255,255,.15); }

/* ── THE DAILY CALL TALLY (rep-flow Q8, owner 2026-07-30: "put pill on bottom left side where side
   panel is") — how many calls YOU have made today, against your own goal.

   🛑 IT LIVES IN THE FLOW OF `.side-foot`, ABOVE the user chip — NOT `position:fixed`. A fixed
   bottom-left pill would sit on top of Log out, which is a real control, and this codebase has
   twice paid for a floating thing covering a control (see --z-callbar). In the flow it can cover
   nothing, it collapses with the 64px rail for free, and on the ≤820px top-row layout it simply
   flows into the row.

   🛑 COLOUR IS PICKED FOR NAVY, NOT COPIED FROM THE LIGHT SURFACES. The rail's gradient ends at
   #222c63, so: the fills are the rail's own rgba-white vocabulary (.05/.08, same as .side-user);
   the progress fill LIGHTENS toward #8fb0ff because --brand (#35529b) is nearly invisible against
   that background; and the goal-hit green is #1e9e5a — the phone strip's dark-surface green — never
   the light-surface --success #137a45, which reads as a dark smudge on navy. */
.side-tally {
    display:flex; align-items:center; gap:9px; flex-wrap:wrap;
    padding:10px 13px; margin-bottom:10px; border-radius:12px;
    background:rgba(255,255,255,.05); color:#c3c8e6; font-size:13px;
}
.side-tally .n { font-size:19px; font-weight:800; color:#fff; letter-spacing:-.02em; line-height:1; }
.side-tally .of { font-size:12.5px; font-weight:700; color:#98a2cd; }
.side-tally .lbl { flex-basis:100%; font-size:11.5px; font-weight:700; color:#98a2cd; letter-spacing:.01em; }
.side-tally .bar { flex-basis:100%; height:5px; border-radius:999px; background:rgba(255,255,255,.12); overflow:hidden; }
.side-tally .bar i { display:block; height:100%; border-radius:999px; background:linear-gradient(90deg,#4f74c9,#8fb0ff); }
/* Goal met — the whole chip turns, so it reads from the corner of the eye without being read. */
.side-tally.is-hit { background:rgba(30,158,90,.20); box-shadow:inset 0 0 0 1px rgba(30,158,90,.45); }
.side-tally.is-hit .n, .side-tally.is-hit .of, .side-tally.is-hit .lbl { color:#8ff0bb; }
.side-tally.is-hit .n { color:#fff; }
.side-tally.is-hit .bar i { background:linear-gradient(90deg,#1e9e5a,#8ff0bb); }
/* The box's clock fault, disclosed rather than hidden (DailyTally::stampedInFuture). Amber, which
   is this system's CAUTION — never the reserved compliance red: a wrong timestamp is a notice. */
.side-tally .warn { flex-basis:100%; font-size:11px; font-weight:700; color:#ffd782; line-height:1.45; }
/* On the 64px rail the labels, bar and notice go with every other `.side-label`; the NUMBER stays,
   centred, because a bare count is still legible and is the whole point of the chip. */
html.sidebar-collapsed .side-tally { justify-content:center; padding:10px 4px; }
.main { flex:1; min-width:0; }
/* WIDE EVERYWHERE (owner 2026-07-22, the boards round: "go to every page and expand the right
   area") — the 900px reading column is retired app-wide. READING stays readable because prose
   carries its own measure (`.lesson-body` max-width:72ch, design rule 6), so text never stretches
   to 200-char lines while every card, table and form earns the full screen. The call surface keeps
   its OWN fixed-pane layout below; the old `bodyClass=board-surface` flag is retired — these are
   its margins, everywhere. */
.wrap { max-width:none; margin:24px 26px; padding:0 26px; }
/* THE CALL SURFACE (design rule 1's one exception, owner-approved at the O0 gate, built 2026-07-17):
   the screen a rep works DURING a call goes full-width so the question + answers own the viewport.
   Set via @section('bodyClass','call-surface') — today that is scripts/run ONLY. The override lives
   HERE because `.wrap` is shared vocabulary (rule 8): a view may never redeclare it. */
/* auto margins kept EXPLICITLY: the old 900px base centered via `margin:auto`; the wide base
   (24px 26px) would left-pin this pane on monitors past its 1440 cap. */
.call-surface .wrap { max-width:1440px; margin-top:18px; margin-left:auto; margin-right:auto; }

/* 🛑 THE CALL SCREEN IS A FIXED PANE, NOT A SCROLLING PAGE (owner 2026-07-22, after taking a real
   call on it: "i cant see anyting on top to hang up … if i scroll on script it scrolls right side
   and the client info gets covered").
   Both faults were the same root cause — the whole page scrolled as one document, so the phone
   controls left the top, and a sticky panel in the rail was overlapped by the cards beneath it.
   A live call cannot have controls that scroll away. So the surface pins to the viewport and each
   column scrolls INSIDE itself; the page itself never scrolls at all.
   `min-height:0` on every flex ancestor is load-bearing, not decoration — a flex child defaults to
   min-height:auto and refuses to shrink below its content, which silently pushes the overflow back
   out to the document and restores exactly the behaviour this replaces. */
body.call-surface { height:100vh; overflow:hidden; }
body.call-surface .shell { height:100vh; min-height:0; align-items:stretch; }
body.call-surface .main { display:flex; flex-direction:column; min-height:0; overflow:hidden; }
body.call-surface .wrap {
    flex:1; min-height:0; display:flex; flex-direction:column;
    margin-top:12px; margin-bottom:0; padding-bottom:0; }
/* The strip is a row of the shell here, never a sticky overlay — sticky is what opened a stacking
   context and trapped the pop card behind it once before. */
body.call-surface .ph-strip { position:static; flex:0 0 auto; }
/* The sidebar keeps its own scroll so a long nav cannot stretch the shell past the viewport. */
body.call-surface .sidebar { overflow-y:auto; }
/* The two working columns fill the pane and scroll their OWN overflow. These name `.sr-` classes
   but are SHELL rules, so they live here rather than in scripts/run's own <style> — that view may
   only declare its own prefix, and `.call-surface` is not it (design rule 8). */
body.call-surface .sr-grid { flex:1; min-height:0; align-items:stretch; }
body.call-surface .sr-flow, body.call-surface .sr-rail {
    min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-bottom:10px; }
/* The callbar is a row of the pane now, so it no longer sticks to a document that never scrolls. */
body.call-surface .sr-callbar { position:static; flex:0 0 auto; }

@media (max-width:820px) {
    .shell { flex-direction:column; }
    /* These two are ONE fix and neither works alone (measured at 375px, /manage/licensed-states):
         the table alone scrolls but the page still pans (511px), the stretch alone stops main
         growing but the table still pushes the document out (454px); together = 375px, no pan.
         WHY the stretch: `align-items:flex-start` above is a DESKTOP rule (it stops .main
         stretching to the sidebar's 100vh). Once the shell turns into a column the cross axis is
         HORIZONTAL, so flex-start silently means "size .main to its content" — and one wide table
         drags the sidebar and every card sideways with it. That is why this was never one screen's
         bug: it is the amplifier under all of them.
         A page that pans horizontally is the defect; a dense admin table scrolling inside its own
         box is not. `display:block` is safe because `table` is already `width:100%` (see below),
         so nothing narrow changes shape — the header/body columns still line up. */
    .shell { align-items:stretch; }
    .wrap table { display:block; overflow-x:auto; }
    .sidebar { width:100%; height:auto; position:static; flex-direction:row; align-items:center; flex-wrap:wrap; gap:10px; padding:12px 16px; }
    /* The horizontal mobile bar keeps a compact CHIP — the full-bleed band is a desktop shape
       (negative margins against the column sidebar's padding have no meaning in a row). */
    .logo-chip { margin:0; padding:8px 12px; border-radius:12px; border-bottom:none; } .logo-chip img { height:38px; width:auto; }
    .side-eyebrow { display:none; }
    .side-nav { flex-direction:row; flex-wrap:wrap; }
    .side-foot { margin:0 0 0 auto; padding:0; display:flex; align-items:center; gap:10px; }
    .side-user { margin:0; }
    .side-foot .btn.secondary { width:auto; }
    .wrap { margin:22px auto; }
}

/* `.side-badge` — the amber "N left" chip on the trainee Start Here nav row: how many REQUIRED
   onboarding steps remain (server-computed in the layouts.app view composer from OnboardingStatus).
   Amber `.tag.warn` tones on PURPOSE — it is a nudge to finish, not an error; alarm red (--danger /
   .alert-compliance) stays reserved for compliance FAILs. `margin-left:auto` pins it to the row's
   right edge; it is absent at 0 remaining while the row itself stays. */
.side-badge {
    margin-left:auto; background:#fde6c7; color:#8a5a00;
    font-size:10.5px; font-weight:700; letter-spacing:.02em;
    padding:2px 7px; border-radius:7px; line-height:1.5; white-space:nowrap;
}
/* `.side-badge.alert` — a solid RED count pill for "N hires waiting on you to set up in ADP" on the
   New Hires row (owner ask 2026-07-20: he wants it to jump out). Deliberately a NOTIFICATION red, not
   the reserved compliance alarm (.alert-compliance / --danger full-card treatment): this is a "do
   this" count, not a crossed line. A round-ish white-on-red pill reads as an unread-style badge. */
.side-badge.alert {
    background:#d92d20; color:#fff; min-width:18px; text-align:center; padding:2px 6px;
}

/* ---- Icon-rail collapse (2026-07-18 sidebar addendum) ---------------------------------------
   The owner asked wide pages (Deal Board, training) for more room. A chevron at the top of the
   sidebar shrinks it to a slim ~64px icon-only rail: every nav row already carries an 18px SVG, so
   collapsing hides the `.side-label` (the label text — and, on Start Here, the amber `.side-badge`
   nested INSIDE that span, so the chip leaves WITH the word it annotates) plus the `.side-eyebrow`
   group headers, centers the icons, and leans on each row's `title` tooltip for the name. The
   choice persists in localStorage and is re-applied PRE-PAINT by a tiny <head> script that stamps
   `html.sidebar-collapsed` before this sheet paints — no wide-then-narrow flash.

   GUARDED to min-width:821px so the collapsed state can NEVER reach the ≤820px flat top-row layout,
   which owns small screens byte-for-byte untouched; the toggle button itself is hidden below 821 in
   its own separate block just below. `.side-label` has no base rule — its only job is to be a hook
   the collapsed rules switch off, so it renders as a normal inline run of text at every other width. */
.side-collapse {
    align-self:flex-end; display:inline-flex; align-items:center; justify-content:center;
    width:34px; height:34px; padding:0; margin:2px 0; border:0; border-radius:9px; cursor:pointer;
    background:rgba(255,255,255,.06); color:#c3c8e6;
}
.side-collapse:hover { background:rgba(255,255,255,.14); color:#fff; }
.side-collapse svg { width:18px; height:18px; transition:transform .15s ease; }

@media (min-width:821px) {
    html.sidebar-collapsed .sidebar { width:64px; padding:22px 8px; }
    html.sidebar-collapsed .logo-chip { margin:-22px -8px 10px; padding:12px 10px 9px; }
    html.sidebar-collapsed .side-collapse { align-self:center; }
    html.sidebar-collapsed .side-collapse svg { transform:rotate(180deg); }
    html.sidebar-collapsed .side-eyebrow { display:none; }
    html.sidebar-collapsed .side-nav a { justify-content:center; gap:0; padding:11px 0; }
    html.sidebar-collapsed .side-label { display:none; }
    html.sidebar-collapsed .side-user { justify-content:center; padding:10px 0; background:transparent; }
    html.sidebar-collapsed .side-foot .btn.secondary { gap:0; padding:10px 0; }
}
/* A NEW block (the existing ≤820px rule stays untouched): below 821 the sidebar is a flat top row
   with nothing to collapse, so the toggle has no meaning and is hidden. */
@media (max-width:820px) { .side-collapse { display:none; } }

/* ---- Hero band (dashboards) ---- */
.hero-band {
    position:relative; overflow:hidden; border-radius:22px; padding:34px 36px 46px; color:#fff;
    background:
        radial-gradient(700px 320px at 92% -30%, rgba(79,116,201,.5), transparent 62%),
        linear-gradient(120deg,#171225 0%, #241d44 45%, #35529b 100%);
    box-shadow:0 26px 54px -30px rgba(23,18,37,.7);
}
.hero-band h1 { font-size:31px; font-weight:800; letter-spacing:-.02em; margin:0 0 7px; color:#fff; }
.hero-band p { margin:0; color:#c9d0ec; font-size:15.5px; }
.hero-band .date { position:absolute; top:30px; right:34px; font-size:13px; font-weight:600; color:#aab3dd; }
.hero-band .roof { position:absolute; right:-30px; bottom:-38px; width:360px; opacity:.09; pointer-events:none; }
.stats.overlap { margin-top:-30px; position:relative; z-index:var(--z-raised); padding:0 16px; }
.btn.onhero { background:#fff; color:var(--brand-2); box-shadow:0 10px 24px -10px rgba(0,0,0,.5); }
.btn.onhero:hover { filter:none; background:#f2f5ff; }

/* ---- Action tiles ---- */
.tiles { display:grid; grid-template-columns:repeat(auto-fit,minmax(215px,1fr)); gap:14px; }
.tile {
    display:flex; align-items:center; gap:14px; background:var(--card); border:1px solid var(--line);
    border-radius:16px; padding:17px 18px; text-decoration:none; color:var(--ink);
    box-shadow:0 12px 30px -24px rgba(26,32,60,.45);
    transition:transform .12s ease, box-shadow .15s, border-color .15s;
}
.tile:hover { transform:translateY(-2px); border-color:rgba(53,82,155,.45); box-shadow:0 20px 38px -22px rgba(26,32,60,.5); }
.tile .ic {
    width:40px; height:40px; border-radius:12px; flex-shrink:0; color:#fff;
    background:linear-gradient(135deg,#4f74c9,var(--brand-2));
    display:flex; align-items:center; justify-content:center;
}
.tile .ic svg { width:19px; height:19px; }
.tile b { display:block; font-size:14.5px; color:var(--brand-ink); }
.tile .desc { font-size:12.5px; color:var(--mut); }
.tile .arrow { margin-left:auto; color:var(--brand); font-weight:800; font-size:17px; }
/* A tile that carries a READ-THROUGH value or a lock badge stacks instead of centering: the value
   is a second line under the description, not a thing competing with the title for the middle. */
.tile .tile-val { display:block; font-size:12.5px; font-weight:700; color:var(--brand-ink); margin-top:5px; }
.tile .tile-val .fresh { display:block; font-weight:500; font-size:11.5px; color:var(--mut); margin-top:1px; }
.tile-lock {
    display:inline-flex; align-items:center; gap:4px; margin-top:6px; padding:2px 8px; border-radius:999px;
    font-size:11px; font-weight:700; color:#5a6076; background:#eef1f7; border:1px solid var(--line);
}

/* ---- Grouped index: section jump rail + section headings (Company Settings) ---- */
.jumprail { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 22px; }
.jumprail a {
    display:inline-flex; align-items:baseline; gap:6px; padding:7px 13px; border-radius:999px;
    background:var(--card); border:1px solid var(--line); text-decoration:none; color:var(--brand-ink);
    font-size:13px; font-weight:700; box-shadow:0 8px 20px -18px rgba(26,32,60,.5);
    transition:border-color .15s, transform .12s ease;
}
.jumprail a:hover { border-color:rgba(53,82,155,.45); transform:translateY(-1px); }
.jumprail a .n { font-size:11.5px; font-weight:600; color:var(--mut); }
.setgroup { margin:0 0 26px; scroll-margin-top:18px; }
/* 🛑 WIDER CARDS THAN THE 215px DEFAULT, and it is not a preference. A settings door needs a
   sentence saying what is behind it; at 215px inside a grouped index those sentences wrapped to
   eight lines and the page read as the wall of cards the reorganisation exists to end. Measured at
   1280 before and after. The default stays untouched for dashboards, where a tile is two words. */
.setgroup .tiles { grid-template-columns:repeat(auto-fill,minmax(298px,1fr)); }
.setgroup > h2 { font-size:17px; font-weight:800; color:var(--brand-ink); margin:0 0 2px; letter-spacing:-.01em; }
.setgroup > p { font-size:13px; color:var(--mut); margin:0 0 12px; max-width:72ch; }

/* ---- Avatars / empty states / progress ring ---- */
.avatar {
    width:36px; height:36px; border-radius:50%; flex-shrink:0; color:#fff; font-size:12.5px; font-weight:700;
    background:linear-gradient(135deg,#4f74c9,var(--brand-2));
    display:inline-flex; align-items:center; justify-content:center; letter-spacing:.02em;
}
.empty { border:1.5px dashed #cdd7ea; border-radius:16px; padding:36px 22px; text-align:center; color:var(--mut); }
.empty .ic {
    width:46px; height:46px; border-radius:13px; background:rgba(53,82,155,.08); color:var(--brand);
    display:inline-flex; align-items:center; justify-content:center; margin-bottom:12px;
}
.empty .ic svg { width:22px; height:22px; }
.ring { position:relative; width:118px; height:118px; flex-shrink:0; }
.ring svg { transform:rotate(-90deg); display:block; }
.ring .val { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; }
.ring .val b { font-size:25px; font-weight:800; color:#fff; line-height:1; }
.ring .val span { font-size:10.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:#aab3dd; margin-top:3px; }

/* ---- Training volume cards ----
   The volumes sit in ONE attached list (owner 2026-07-20): no gaps, one soft shadow around the group,
   thin light seams between rows, only the first/last corners rounded. A COLLAPSED volume lifts a touch
   on hover (a "pop"); an open one stays put. */
.vol-list { margin:0 0 22px; border-radius:16px; box-shadow:0 1px 2px rgba(16,24,40,.05), 0 22px 46px -26px rgba(26,32,60,.34); }
.vol-list .vol-card { margin:0; padding:0; border:0; border-radius:0; box-shadow:none; }
.vol-list .vol-card:first-child { border-radius:16px 16px 0 0; }
.vol-list .vol-card:last-child { border-radius:0 0 16px 16px; }
.vol-list .vol-card + .vol-card > summary.vol-cover { border-top:0.5px solid var(--line); }
.vol-list .vol-card:not([open]):hover { transform:translateY(-2px); z-index:var(--z-raised); border-radius:14px; box-shadow:0 14px 26px -14px rgba(26,32,60,.30); }
.vol-card { padding:0; overflow:hidden; transition:transform .16s ease, box-shadow .16s ease, border-radius .16s ease; }
/* Light rows (owner 2026-07-20 — style C+A+D): a brand-blue left stripe, a number chip, the title over
   an "N modules" subline. The stripe turns gold for a licensed volume and green when it is complete. */
.vol-cover { padding:0; display:flex; align-items:stretch; color:var(--ink); }
.vol-stripe { width:4px; flex:none; background:var(--brand); }
.vol-stripe.is-lic { background:#c99a26; }
.vol-stripe.is-done { background:#2fae7a; }
.vol-cover-inner { display:flex; align-items:center; gap:11px; padding:11px 18px; flex:1; min-width:0; }
.vol-cover .vol-titles { min-width:0; display:flex; flex-direction:column; }
.vol-cover .vol-title { font-size:14.5px; font-weight:700; color:var(--brand-ink); letter-spacing:-.01em; }
.vol-cover .vol-sub { font-size:12px; color:var(--mut); margin-top:1px; }
.vol-cover .tag { background:var(--wash); color:var(--brand-ink); }
.vol-cover .vol-done-tag { display:inline-flex; align-items:center; gap:4px; background:#e4f7ec; color:#137a45; font-weight:800; }
.vol-cover .vol-done-tag svg { width:12px; height:12px; }
.vol-cover .vol-lic-tag { background:linear-gradient(180deg,#e7ba52,#cf9a26); color:#3a2a00; font-weight:800; }
.vol-body { padding:4px 20px 14px; }
/* Icon-only action buttons on the list (owner: icons, not "Read/Open", and no heavy shading). Flat,
   quiet by default; brand-tinted on hover. `.go-quiz` nudges the ready quiz in success green. */
.icon-btn.go-btn { padding:8px; background:transparent; color:var(--mut); box-shadow:inset 0 0 0 1px var(--line); }
.icon-btn.go-btn:hover { background:#eef2fb; color:var(--brand); box-shadow:inset 0 0 0 1px #cbd7ef; }
.icon-btn.go-btn.go-quiz { color:#137a45; box-shadow:inset 0 0 0 1px #bfe6cf; }
.icon-btn.go-btn.go-quiz:hover { background:#e4f7ec; color:#0f6a3b; box-shadow:inset 0 0 0 1px #9ed6b4; }
/* Collapsible volume cards (training index). Native <details>/<summary>: collapsed by default (no
   `open`), so the screen opens on just the volume headers and each volume's modules stay tucked until
   it is opened. Keyboard-accessible; works with JS off. */
details.vol-card > summary.vol-cover { cursor:pointer; list-style:none; justify-content:flex-start; }
details.vol-card > summary.vol-cover::-webkit-details-marker { display:none; }
.vol-cover .vol-chevron { display:inline-flex; flex:none; color:var(--mut); transition:transform .18s ease; }
.vol-cover .vol-chevron svg { width:17px; height:17px; }
.vol-cover .vol-cover-tags { margin-left:auto; display:flex; gap:8px; align-items:center; flex:none; }
details.vol-card[open] > summary.vol-cover .vol-chevron { transform:rotate(90deg); }
summary.vol-cover:focus-visible { outline:2px solid var(--brand); outline-offset:-2px; }
/* Nested module collapse: an unlocked module opens to show its lessons inline (owner 2026-07-20).
   The <summary> reuses .list-item for its row layout; the module opens collapsed by default. */
.mod-item > summary.mod-summary { list-style:none; cursor:pointer; }
.mod-item > summary.mod-summary::-webkit-details-marker { display:none; }
.mod-item:last-child > summary.list-item { border-bottom:0; }
.mod-item[open]:last-child > summary.list-item { border-bottom:1px solid var(--line); }
.mod-summary .mod-main { display:flex; align-items:center; gap:13px; min-width:0; }
.mod-summary .mod-titles { min-width:0; }
.mod-summary .mod-sum { display:block; }
.mod-chevron { display:inline-flex; flex:none; color:var(--mut); transition:transform .18s ease; }
.mod-chevron svg { width:15px; height:15px; }
.mod-item[open] > summary.mod-summary .mod-chevron { transform:rotate(90deg); }
summary.mod-summary:focus-visible { outline:2px solid var(--brand); outline-offset:-2px; border-radius:8px; }
.num-badge { display:inline-block; min-width:32px; text-align:center; font-size:12px; font-weight:800; color:var(--brand,#2b4a8b); background:#eef2fb; border-radius:6px; padding:1px 6px; margin-right:8px; vertical-align:middle; }
.num-badge.sm { min-width:26px; font-size:11px; }
.mod-lessons { padding:4px 0 12px 48px; }
.mod-lesson { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:8px 0; border-top:1px dashed var(--line); }
.mod-lesson:first-child { border-top:0; }
.mod-lesson .ml-title { font-size:14px; min-width:0; }
/* Unified navy (owner 2026-07-20): every volume wears the SAME cool navy→indigo gradient — the same
   family as the sidebar + hero — so the attached list reads as one material, many rows. Volumes differ
   by their title + the hover lift, not by hue. Licensed volumes are flagged by a gold pill, not a colour. */
.cover-0, .cover-1, .cover-2, .cover-3 { background:linear-gradient(135deg,#171226 0%, #201a3c 48%, #293c7e 100%); }
.mstat { width:32px; height:32px; border-radius:50%; flex-shrink:0; display:flex; align-items:center; justify-content:center; }
.mstat svg { width:15px; height:15px; }
.mstat.done { background:#e4f7ec; color:#137a45; }
.mstat.open { background:rgba(53,82,155,.13); color:var(--brand); }
.mstat.locked { background:#eef0f5; color:#9aa2b6; }

/* ---- COMPLIANCE ALARM (reserved — used for compliance results ONLY, see design-system.md) ---- */
.alert-compliance {
    display:flex; gap:14px; align-items:flex-start; padding:18px 20px; border-radius:14px;
    background:linear-gradient(180deg,var(--danger),var(--danger-2)); color:#fff; margin-bottom:18px;
    box-shadow:0 14px 30px -14px var(--danger-glow);
}
.alert-compliance .ic { width:38px; height:38px; border-radius:11px; background:rgba(255,255,255,.16);
    display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.alert-compliance .ic svg { width:20px; height:20px; }
.alert-compliance b { display:block; font-size:15.5px; margin-bottom:3px; }
.alert-compliance p { margin:0; color:#ffd9d3; font-size:14px; line-height:1.55; }
.alert-compliance a { color:#fff; font-weight:700; }
.compliance-pass {
    display:flex; gap:12px; align-items:center; padding:15px 18px; border-radius:14px;
    background:#e7f7ee; border:1.5px solid #9fd9b8; color:#136c3f; font-weight:600; margin-bottom:18px;
}
.score-dots { display:inline-flex; gap:5px; }
.score-dots i { width:14px; height:14px; border-radius:50%; background:#e3e8f2; }
.score-dots i.on { background:linear-gradient(180deg,var(--brand),#4f74c9); }

/* ---- Rendered lesson (markdown) ---- */
.lesson-body { font-size:15.5px; line-height:1.75; color:var(--ink); max-width:72ch; }
.lesson-body > :first-child { margin-top:0; }
.lesson-body h1 { font-size:22px; font-weight:800; color:var(--brand-ink); margin:26px 0 10px; letter-spacing:-.01em; }
.lesson-body h2 { font-size:18px; font-weight:700; color:var(--brand-ink); margin:24px 0 8px; }
.lesson-body h3 { font-size:15.5px; font-weight:700; color:var(--brand-ink); margin:20px 0 6px; text-transform:uppercase; letter-spacing:.03em; }
.lesson-body p { margin:0 0 14px; }
.lesson-body ul, .lesson-body ol { margin:0 0 14px; padding-left:22px; }
.lesson-body li { margin:4px 0; }
.lesson-body strong { color:var(--brand-ink); font-weight:700; }
.lesson-body a { color:var(--brand); }
.lesson-body blockquote { margin:16px 0; padding:12px 16px; border-left:4px solid var(--brand); background:#f3f6fd; border-radius:0 10px 10px 0; color:var(--ink); }
.lesson-body blockquote p:last-child { margin-bottom:0; }
.lesson-body code { background:#eef1f7; border-radius:5px; padding:1px 6px; font-size:13.5px; }
.lesson-body table { width:auto; margin:14px 0; border:1px solid var(--line); border-radius:8px; overflow:hidden; }
.lesson-body th, .lesson-body td { padding:8px 12px; border-bottom:1px solid var(--line); text-transform:none; letter-spacing:0; }
.lesson-body hr { border:0; border-top:1px solid var(--line); margin:22px 0; }

/* ---- Cards ---- */
.card {
    position:relative; background:var(--card); border:1px solid var(--line); border-radius:18px;
    box-shadow:0 1px 2px rgba(16,24,40,.04), 0 18px 40px -24px rgba(26,32,60,.28);
    padding:30px; margin-bottom:22px;
}
.card h1 { font-size:25px; font-weight:800; letter-spacing:-.02em; color:var(--brand-ink); margin:0 0 6px; }
.card h2 { font-size:17px; font-weight:700; color:var(--brand-ink); margin:0 0 14px; }
/* `.ttl` — the COMPACT card title: smaller and bolder than `.card h2`, for the dense ops screens
   where a card is one step of a procedure rather than a section of a document. Put it on the `h2`
   (`<h2 class="ttl">`) so the heading level stays honest for screen readers.
   Graduated 2026-07-16: six ops views were each carrying a byte-identical private copy of this
   rule. It outranks `.card h2` on specificity (0,2,0 vs 0,1,1), so it wins from here exactly as it
   won from their <style> blocks — the move is zero-pixel by construction. Two card titles is a
   deliberate choice, not drift: 17px/700 reads as prose, 15px/800 as a control panel. */
.card .ttl { font-size:15px; font-weight:800; margin:0 0 12px; color:var(--brand-ink); }
.sub { color:var(--mut); margin:0 0 20px; font-size:15px; }
.muted { color:var(--mut); font-size:13px; }

/* ---- Page header + stat cards + progress ---- */
.page-head { margin:2px 0 22px; }
.page-head h1 { font-size:28px; font-weight:800; letter-spacing:-.02em; color:var(--brand-ink); margin:0 0 4px; }
.page-head p { color:var(--mut); margin:0; font-size:15px; }

/* `.crumb` — the page breadcrumb's ONE margin, for `partials/crumb`. Emitted as
   `class="muted crumb"`: `.muted` keeps the colour + the 13px size it renders at outside a
   page-head, and this carries the gap.
   🛑 THE SECOND SELECTOR IS NOT REDUNDANT — do not fold these into one rule. `.page-head p`
   above sets `margin:0` at specificity (0,1,1), which BEATS a lone `.crumb` at (0,1,0). All 28
   breadcrumbs live inside a `.page-head` and only kept their 6px gap because an inline
   `style="margin:0 0 6px"` out-ranks every selector; a class does not. `.page-head .crumb` is
   (0,2,0), so it wins. Delete it and the gap silently closes on 28 screens at once — which is
   exactly the class of change a suite cannot see. */
.crumb { margin:0 0 6px; }
/* `.steps` — A MULTI-STEP JOB SHOWS ITS OWN STEPS (2026-07-27, owner-deferred 07-26 then ruled).
   For a job that is NOT finished by the visible control: the screen states each step, which one is
   done, and which is waiting — instead of explaining it in a paragraph the reader must parse.
   Born on /manage/hot-buttons, where publishing a button is act ONE of two and 21 plays once sat
   switched on with nothing to say. `design-system.md`'s gate asks whether a stranger can complete the
   task; a paragraph answers that only if they read it.
   🛑 STATE COMES FROM DATA, NEVER FROM A HAND-SET CLASS: `.step-done` and `.step-now` describe what
   IS true, so a step cannot claim done while its gate is false — the lying-chip failure this whole
   phase exists to stop. No colour outside the tokens; nothing here animates. */
.steps { list-style:none; margin:14px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:10px; }
.steps > li { flex:1 1 260px; display:flex; gap:11px; align-items:flex-start;
    background:var(--card); border:1px solid var(--line); border-radius:13px; padding:12px 14px; }
.steps .step-n { flex:0 0 auto; width:26px; height:26px; border-radius:999px; display:inline-flex;
    align-items:center; justify-content:center; font-size:13px; font-weight:800; }
.steps .step-b { display:flex; flex-direction:column; gap:2px; min-width:0; }
.steps .step-t { font-size:14px; font-weight:800; color:var(--brand-ink); }
.steps .step-d { font-size:12.5px; color:var(--mut); line-height:1.45; }
.steps .step-go { font-size:12.5px; font-weight:800; color:var(--brand); text-decoration:none; margin-top:5px; }
.steps .step-go:hover { text-decoration:underline; }
.steps > li.step-done { background:#f7fbf8; border-color:#cfe9d8; }
.steps .step-done .step-n { background:#e4f7ec; color:#137a45; box-shadow:inset 0 0 0 1px #b6e3c7; }
.steps > li.step-now { border-color:var(--brand); box-shadow:0 0 0 3px var(--ring); }
.steps .step-now .step-n { background:linear-gradient(135deg,var(--brand),var(--brand-2)); color:#fff; }
/* One column on a phone: two half-width step cards side by side stop being readable. */
@media (max-width:640px) { .steps > li { flex:1 1 100%; } }
.page-head .crumb { margin:0 0 6px; }

/* `.page-head.has-action` — the page header with ONE primary action pinned top-right.
   An OPT-IN modifier, not a change to `.page-head`: a screen without it renders byte-identically,
   so screens adopt this one at a time and a plain `.page-head` is never touched. The gate asks
   whether a stranger can complete the screen's primary task; that is hard when the only control is
   inside a collapsed fold halfway down the page (`/ops/phone-settings`, the first adopter).
   ONE action — if a screen needs two, the second is a `.btn.secondary`; if it needs three, the
   screen has more than one primary task and that is the real defect. */
.page-head.has-action { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; flex-wrap:wrap; }
.page-head.has-action > .head-main { flex:1 1 340px; min-width:0; }
.page-head.has-action > .head-action { flex:0 0 auto; display:flex; align-items:center; gap:8px; }
/* At phone width the action drops under the title and goes full-width — a right-pinned button in a
   wrapped flex row otherwise lands in the middle of nowhere. Matches rule 7: never pan sideways. */
@media (max-width:640px) {
    .page-head.has-action > .head-action { width:100%; }
    .page-head.has-action > .head-action .btn { width:100%; text-align:center; }
    /* 🛑 `.icon-only` — an ICON action stays pinned to the corner instead of dropping full-width.
       The rule above was written for a WORDED button, where a full-width tap target reads as the
       page's one action. A 42px circular `+` dropped below the copy lands hard LEFT and reads as a
       stray button, which is worse than the squeeze it was meant to fix.
       🛑 THIS CASE WAS KNOWN BEFORE THE COMPONENT EXISTED. `manage/rebuttals` hand-rolled its header
       WITHOUT `flex-wrap` and wrote the reason in a comment: "the `+` must hold the top-right corner.
       Wrapping dropped it under the copy and onto the LEFT." That was a tested judgement, not an
       omission — measured at 560px it is exactly right — and the first migration attempt (2026-07-27)
       silently overrode it. A screen that reimplements a component is not always behind it; sometimes
       it knows a case the component has not met yet. Read the comments before you call it debt. */
    .page-head.has-action > .head-action.icon-only { width:auto; }
}
.stats { display:flex; flex-wrap:wrap; gap:12px; margin-bottom:22px; }
.stat {
    flex:1 1 120px; min-width:0;
    background:var(--card); border:1px solid var(--line); border-radius:15px; padding:14px 15px;
    box-shadow:0 12px 30px -22px rgba(26,32,60,.4); display:flex; flex-direction:column; gap:4px;
}
.stat .ic { width:30px; height:30px; border-radius:9px; display:flex; align-items:center; justify-content:center;
            background:rgba(53,82,155,.10); color:var(--brand); margin-bottom:2px; }
.stat .ic svg { width:16px; height:16px; }
.stat.warn .ic { background:rgba(180,120,0,.12); color:#8a5a00; }
.stat.good .ic { background:rgba(19,122,69,.12); color:#137a45; }
.stat .num { font-size:25px; font-weight:800; color:var(--brand-ink); line-height:1.05; }
/* A TEXT-valued stat ("2 days ago", a stage label) — the catalogued modifier, never an inline
   resize: a style attribute always beats a later retune of `.num` here (review 2026-07-18). */
.stat .num.txt { font-size:16px; }
.stat .lbl { font-size:11px; font-weight:600; color:var(--mut); text-transform:uppercase; letter-spacing:.03em; line-height:1.25; }
@media (max-width:560px) { .stat { flex:1 1 44%; } }
.progress { height:10px; background:#e9edf6; border-radius:999px; overflow:hidden; }
.progress > i { display:block; height:100%; border-radius:999px; background:linear-gradient(90deg,var(--brand),#4f74c9); }
.grid-2 { display:grid; grid-template-columns:1fr; gap:0; }
.quick { display:flex; gap:10px; flex-wrap:wrap; }

/* ---- Forms ---- */
label { display:block; font-size:13px; font-weight:600; margin:12px 0 6px; color:var(--ink); }
/* Every typed field gets the same box, so this list has to name every type that takes typing.
     A type left out falls back to the browser's own control: `tel` rendered a 153px box beside
     its 430px siblings on the person page, and number/date rendered square, 13.3px and
     1px-padded next to their 15px/10px-radius siblings — inside the SAME label (the
     calculator's Balance sat beside an on-system Name box).
     number/date were held back on the theory that width:100% would stack the inputs some
     screens nest inline in a <label>. It doesn't: `label` is display:block, so a 100% field
     already sits under its text — exactly what the text/select fields in those same labels
     have always done. Widening the list made them MATCH. journeys and lead-types were never
     affected either way, for two DIFFERENT reasons: lead-types' scoped rule sets its own
     width, while journeys' sets only padding/border/font — its number field was already full
     width because `.stepform label` is a flex column that stretches it.
     A field that must stay narrow keeps its own width and still gets this box: an inline
     `style="width:100px"` wins over this rule (the quiz settings), and a whole row of compact
     fields uses `.field-row` below. */
input[type=text], input[type=email], input[type=tel], input[type=url], input[type=number], input[type=date], input[type=datetime-local], input[type=search], select, textarea {
    width:100%; padding:11px 13px; border:1px solid var(--line); border-radius:10px; font-size:15px;
    font-family:inherit; background:#fff; color:var(--ink); transition:border-color .15s, box-shadow .15s;
}
input:focus, select:focus, textarea:focus { outline:none; border-color:var(--brand); box-shadow:0 0 0 4px var(--ring); }
/* THE HERO INPUT — a screen's ONE primary typed box (born on the call surface: the rep types the
   number the customer said, so it reads at say-text size). Composes over the base field rule above;
   same focus ring. Use for a single centerpiece input, never for a form of them. */
input.input-hero { font-size:16px; font-weight:600; padding:9px 13px; border:1px solid var(--brand);
    border-radius:10px; color:var(--brand-ink); }
textarea { min-height:300px; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:14px; line-height:1.6; }

/* A form that must read as ONE line — a table row's inline editor, where the column headers
     above already name each field. Fields size to their content instead of filling the row.
     Without this, the width:100% above makes every field claim the whole row and wrap
     one-per-line, and the row stops lining up with the headers that label it. `min-width` alone
     does NOT hold a field back: it only sets a floor, so /manage/licensed-states asked for a
     150px licence box and rendered a 671px one on its own line.
     The `[type]` is load-bearing, not decoration: it makes this (0,2,1) against the shared
     rule's (0,1,1), so the override wins on specificity and not on being further down the
     file. `.field-row select` already outranks the bare `select` above. */
.field-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.field-row input[type], .field-row select { width:auto; padding:7px 10px; font-size:13px; }

/* A DENSE OPS FORM — the same box, smaller. Put it on the <form> (or on the label that wraps a
     lone field) and every field inside comes down a size; the field keeps its full width, so this
     is the grid-form counterpart to `.field-row` above, which is for a ROW of content-width fields
     under table headers. That difference is why the ops screens could not just use `.field-row`.
     It sets SIZE and WEIGHT, and never a colour. The border is inherited from the shared box above
     — a compact field is smaller, not a different colour — and that is load-bearing: six views
     each hard-coded the same off-token grey here (the hex is in the commit and in
     FormFieldStylingTest; naming it in THIS file would put it back in the stylesheet, inside the
     sentence saying it is not, and answer any future grep for it — a comment is served verbatim).
     It drifted to two radii and two font sizes because there was no one place to put it. Owner
     ruled 2026-07-16: compact is deliberate and keeps its size; the border adopts `--line`.
     The medium weight below is HIS too, ruled the same day. Three of the six views had reached for
     it independently and three had not — a 3-3 split, so there was no existing look to preserve and
     the component had to pick one; it first picked wrong (the shared box sets none), three screens
     quietly went lighter, and he asked for it back across all six rather than a variant for three.
     (Written in words, not as the literal declaration: a comment is served, so spelling it here
     would answer a grep for the real rule — it fooled this session's own mutation check twice.)
     NOTE the live inconsistency this leaves: `.field-row` above is 13px and sets NO weight, so the
     app's two compact fields now disagree about it. Nobody has ruled on `.field-row`; don't quietly
     align them, and don't let a third compact thing guess.
     The type list is spelled out to MATCH the shared rule above, type for type, because "the same
     box, smaller" has to be true of the same set of controls — FormFieldStylingTest compares the
     two lists, so adding a type up there and not here fails. A shortcut `input[type]` was tried and
     is wrong: it also grabs file/checkbox/radio/hidden, which the shared box deliberately excludes
     because a text box's padding deforms them. That is not hypothetical — the over-broad
     `.im-form input` it replaced had been quietly boxing /ops/import's file picker.
     The `[type=…]` is also load-bearing for the cascade, same reason as `.field-row` above: it
     makes this (0,2,1) against the shared rule's (0,1,1), so it wins on specificity rather than on
     sitting further down the file. A bare `.form-compact input` would exactly TIE — which is how
     the six in-view versions won: by accident of source order. */
.form-compact input[type=text], .form-compact input[type=email], .form-compact input[type=tel],
.form-compact input[type=url], .form-compact input[type=number], .form-compact input[type=date],
.form-compact input[type=datetime-local], .form-compact input[type=search],
.form-compact select, .form-compact textarea {
    padding:8px 10px; border-radius:8px; font-size:13.5px; font-weight:500; font-family:inherit;
}

/* ---- Buttons ---- */
/* 🛑 `hidden` MUST ACTUALLY HIDE A BUTTON — and until 2026-08-20 it did not, anywhere in this app.
   The rule below sets `display:inline-flex` from a CLASS, which outranks the browser's own
   `[hidden] { display:none }`, so every `<button class="btn" hidden>` in the codebase stayed on
   screen while its own JS believed it had put it away. Found by measuring the ring bar: *Pass to
   floor* was rendering 106px wide on every floor ring — a control that cannot do anything now that
   the owner window is off — because `pass.hidden = true` had no effect.
   ⚠️ SAME SHAPE AS `.ph-arrival`'s note, in reverse: there a `visibility:hidden` failed to beat
   `[hidden]`; here a `display` beats it. **Whenever an element's display comes from a class, its
   hidden state has to be stated too.** */
.btn[hidden] { display:none !important; }
.btn {
    display:inline-flex; align-items:center; gap:7px; border:0; cursor:pointer; text-decoration:none;
    font-family:inherit; font-weight:600; font-size:14.5px; padding:10px 18px; border-radius:11px;
    color:#fff; background:linear-gradient(180deg,var(--brand),var(--brand-2));
    box-shadow:0 1px 0 rgba(255,255,255,.15) inset, 0 8px 18px -8px rgba(53,82,155,.7);
    transition:transform .08s ease, box-shadow .15s, filter .15s;
}
.btn:hover { transform:translateY(-1px); filter:brightness(1.05); }
.btn:active { transform:translateY(0); }
.btn.secondary { background:#f1f4fa; color:var(--brand-ink); box-shadow:inset 0 0 0 1px var(--line); }
.btn.secondary:hover { background:#e9eef8; }
.btn.small { padding:7px 13px; font-size:13px; border-radius:9px; }
/* A real component state. The dialer's "add a cell first" button is what EVERY prod row shows,
   and it was faking this with an inline opacity/cursor pair. */
.btn[disabled] { opacity:.45; cursor:not-allowed; }
.btn[disabled]:hover { transform:none; filter:none; }

/* ---- Pills / tags ---- */
.pill { display:inline-flex; align-items:center; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; padding:3px 10px; border-radius:999px; }
.pill.admin { color:#5b3d8f; background:#ece3fb; }
.pill.manager { color:#7a4a00; background:#fde6c7; }
.pill.trainee { color:#0a5b8a; background:#d7ecfb; }
.tag { display:inline-flex; align-items:center; font-size:11px; font-weight:700; padding:3px 9px; border-radius:7px; }
.tag.pub { background:#e4f7ec; color:#137a45; }
.tag.draft { background:#eef0f5; color:#697089; }
.tag.lic { background:#fde6c7; color:#7a4a00; }
/* warn amber — every script-editor warning. The reserved compliance red (.alert-compliance)
   stays spent on compliance findings ONLY: an unreachable step is not a compliance problem. */
.tag.warn { background:#fde6c7; color:#8a5a00; }
.tag.type { background:var(--ring); color:var(--brand); }
.tag.retired { background:#eceef4; color:#8a90a3; text-decoration:line-through; }
/* `.tag.alert` — the muted ERROR tint, for a state that is wrong and wants a human (a departed
   rep whose dialer access survived; a quiz lock-out). It is the status-error pair, deliberately
   NOT `--danger`/`.alert-compliance`, which stays spent on compliance FAILs only. Extracted
   2026-07-16: this exact pair was hand-inlined in 7 places and was drifting into a second
   vocabulary. `.tag.warn` is amber and means "look at this"; `.alert` means "this is broken". */
/* 🛑 `.tag.alert` AND `.tag.alarm` ARE DISTINCT ON PURPOSE — ruled 2026-07-27. They were ONE rule
   under two names, which is a footgun on a Principle-4 surface: someone reaching for `.alert`
   believing it distinct from the compliance marking got the same red either way.
     · `.tag.alarm` — a COMPLIANCE VERDICT on something that was CHECKED. Keeps the FILLED wash.
     · `.tag.alert` — a BROKEN state wanting a human, nothing graded. OUTLINED, same hue.
   🛑 THE BASIS IS THE GRAMMAR ALREADY IN FORCE, NOT A FRESH ARGUMENT. Urgency reasoning cuts both
   ways — a departed rep whose dialer access survived arguably wants a human more than a verdict on a
   run already caught — and would flip this. But `--danger`/`.alert-compliance` is "spent on
   compliance FAILs only", so the heaviest treatment is already reserved for compliance; verdict
   heavier than broken INSIDE the muted tint follows the ordering the repo already has. Inventing a
   new principle to override a documented one, on a preference call, is how two contradicting rules
   get born.
   🛑 SAME HUE, DIFFERENT WEIGHT — never a different colour. `.warn` already owns amber ("look at
   this") and `.ok` green; a third hue here would spend a colour the vocabulary needs elsewhere.
   🛑 THE OUTLINE IS AN INSET SHADOW, NOT A BORDER, AND THAT IS LOAD-BEARING. `.tag` has no border, so
   a real 1.5px border grows every `.alert` chip. MEASURED in the browser against this stylesheet:
   95.55x20 either way with the shadow; 97.55x22 with a border — **+2px in both axes**, on 9 chips that
   sit inline in table rows. An inset shadow paints the same line with byte-identical geometry.
   ⚠️ Neither is the reserved `--danger` alarm — this is the MUTED tint (`--danger-ink` on a pale
   wash), which design-system rule 13 keeps distinct from `.alert-compliance`.
   Pinned by `ComplianceMarkingsSurviveTest`, which asserts they DIFFER and is negative-tested by
   making them equal again. */
.tag.alarm { background:#fbe4e4; color:#a12626; }
/* 🛑 A COLLEAGUE IS ON THE PHONE WITH THIS CUSTOMER (⚖️ Gil 2026-09-08). The LIVE family, because on
   these screens green already means PHONE — but OUTLINED rather than filled, with no pulse and no
   clock, so it can never be mistaken for `.live-mark`, which means *you* are on the call. Two facts,
   two weights: the loud one is reserved for the call the rep is actually holding. */
.tag.on-a-call { background:var(--live-tint); color:var(--live-2); box-shadow:inset 0 0 0 1px var(--live-line); font-weight:600; gap:6px; }
.tag.on-a-call .oac-dot { width:7px; height:7px; border-radius:50%; background:var(--live); flex:none; }
/* ============ A LIVE CALL, IN COLOUR (owner 2026-07-31) ============
   *"i called in and looked at the screen and it looks the same as a regular screen, wouldn't know
   if it was a live call... need to be obvious and apparent."* — and then, against the first fix,
   which was a navy band with two lines of copy: *"i was thinking coloring or something, not a
   couple words on the top of the screen. change the color of the lead detail card to be different
   than the coloring on regular view. or coloring of top of lead detail card."*

   He picked **options 1+2 off a render of the real screen**: the lead header TINTS and wears a
   THICK CAP along its top edge. Both together, both directions of call. The band is gone — replaced,
   not supplemented, because the words were the thing he rejected.

   🛑 GEOMETRY IS RESERVED AT ALL TIMES, IN TRANSPARENT INK — that is what `.lead-head` is for, and
   it is the whole reason this is two classes instead of one. The border and padding exist on every
   render; only their COLOUR changes when a call starts. So the page does not jump when the phone
   connects and does not jump back when it drops, on a screen a rep is reading mid-sentence. The
   negative margins cancel the reserved padding exactly, so an ordinary lead page is pixel-identical
   to what it was before this landed. Change any of the four numbers and change its pair.

   🛑 NOT THE ALARM RED, AND NOT A NEW HUE EITHER. `var(--danger)` marks a compliance VERDICT, never
   a notice, and never on a live-call surface at all (design-system RULE 13). `--live` is the family
   `.tag.call-in` already uses: on this screen, green already means PHONE.

   🛑 THE COLOUR IS NEVER THE ONLY SIGNAL — `.live-mark` below is load-bearing, not decoration.
   Tokens: `--live` / `--live-tint` / `--live-line`, defined with every other token in `:root`. */
/* ⚠️ THE VERTICAL NUMBERS CHANGED WHEN THE BREADCRUMB MOVED OUT (the sticky header, 2026-08-04) and
   the pairing rule above still governs them. The row that used to sit inside this card — the
   breadcrumb / tank bar — is now `.lead-headtop`, OUTSIDE the pin, so this card starts at the
   identity row. `padding-top` therefore went to 0 and the reserved 8px cap became the whole gap
   between the two, which is exactly the 8px the breadcrumb carried as its own margin-bottom (now
   zeroed, in the view and in `.lead-crumbs.is-tank`). Net: the identity row lands at the same y as
   before, and the header is no taller. The horizontal pair (-19 against 18+1) is untouched.
   ⚠️ NO NEGATIVE TOP MARGIN ANY MORE, and that is load-bearing rather than tidiness: this card is
   the first child of the sticky `.lead-pin`, and a negative top margin there would drag the pinned
   block up under the phone strip by that many pixels at every scroll position. */
.page-head.lead-head {
    border:1px solid transparent; border-top:8px solid transparent;
    border-radius:14px; padding:0 18px 16px; margin:0 -19px 5px;
}
.page-head.lead-head.on-call {
    background:var(--live-tint); border-color:var(--live-line); border-top-color:var(--live);
}

/* ═══ THE STICKY LEAD HEADER (owner 2026-08-04) ═══
   *"On scrolling when in lead card: when i scroll down i want the top portion to lock on screen
   while scrolling down."*

   🛑 TWO ROWS LOCK, NOT FOUR. The identity row (who am I looking at) and the tab row (how do I get
   elsewhere) are what a long sheet takes away. The fresh-data notice and the breadcrumb stay in
   `.lead-headtop` and scroll off, because he has TWICE asked this header to be shorter — a row that
   locks spends its height on every screenful, and pinning all four would turn a one-time height
   complaint into a permanent one. Reversing that is a decision, not a completion.

   🛑 `position:sticky`, NEVER `fixed`. Sticky keeps its own box in normal flow, which satisfies two
   of his standing rules BY CONSTRUCTION rather than by care: the space is reserved (nothing floats
   over a control) and nothing reflows when it locks (*"nothing moves when a call connects"*).

   🛑 THE LAYER IS A TOKEN AND IT SITS UNDER THE CEILING. `--z-lead-pin` (100) is below
   `--z-callbar` (160), and the strip is itself sticky at `top:0`, so the strip paints OVER this
   header at every scroll position — even a wrong offset could not bury Hang up. A raw integer here
   would be the same defect that shipped three times on this very screen.

   🛑 THE OFFSET IS THE STRIP'S MEASURED HEIGHT. `.ph-strip` WRAPS at narrow widths, so its height is
   not a constant and a hardcoded `top` is wrong at ~590px — the width he reviews at. The view
   publishes `--ph-strip-h` from a ResizeObserver; the 44px fallback only applies if that never runs,
   and its worst case is a small gap.

   🛑 AND NO "is-stuck" CLASS. The obvious build hides rows once pinned; a sticky element whose height
   changes WHILE STUCK moves every row below it, which breaks the reflow rule directly. The wrapper
   holds the right rows from the first paint, so there is no stuck state to get wrong.

   The background is opaque because the record scrolls UNDERNEATH this block — the page's own tint is
   a gradient on `body`, so a transparent pin would show the customer's own rows through their name.
   The hairline is what tells a rep the block is chrome rather than the top of the sheet.

   ⚠️ THE ±19px BLEED IS COPIED, NOT INVENTED: `.lead-head` already reaches 19px past the reading
   column on each side (its negative margin cancels its own padding + border). The pin matches it with
   an equal margin/padding pair so the header sits inside the pin's padding box instead of hanging out
   of it — which is what a CONTAINER-level overflow sweep sees, and it measured 948 against 929 before
   this pair existed. The opaque band then spans exactly the tinted card's width rather than stopping
   19px short of it on both sides. Change one number and change its pair. */
.lead-headtop { margin:2px 0 0; }
.lead-pin {
    position:sticky; top:var(--ph-strip-h, 44px); z-index:var(--z-lead-pin);
    margin:0 -19px; padding:0 19px;
    background:#f6f8fc; box-shadow:0 6px 12px -12px rgba(26,32,60,.5);
}

/* ═══ THE TANK BAR — the lead card's top row while the rep is working the Call Tank ═══
   Owner 2026-08-03: *"when they are on the tank, on the lead card there is a next on top right and
   previous on top left side"*.

   🛑 THE SAME ROW, NOT A NEW ONE. The plain card's breadcrumb is already a flex row in this exact
   position; tank mode swaps its CONTENTS and pins them to the two ends. Building a second bar above
   or below would have added height to the one block the owner has twice asked to keep short (Q12m,
   directly below) — and it would have moved the page when a call started, which `.lead-head` exists
   to prevent. `margin-bottom` matches the inline `8px` the plain row carries, so the two faces are
   the same height and nothing shifts between them.

   🛑 `margin-left:auto` RATHER THAN `space-between`, and the difference is load-bearing: with
   `space-between` a row whose Next is absent would push Previous to the right-hand edge. Next is
   never absent today, but the left group must be anchored LEFT by its own rule, not by the
   presence of something else.

   ⚠️ `margin-bottom:0` since the sticky header split this row out of the card (2026-08-04): the
   pinned card's reserved 8px cap is the gap now. It still MATCHES the plain row's inline value —
   which is the point of the number, and both were zeroed together — so the two faces of this row
   stay the same height and nothing shifts between them. */
.lead-crumbs.is-tank {
    display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:0;
    color:var(--mut); font-size:14px;
}

/* ═══ `.start-band` — THE ONE WAY IN, WHERE THE EYE ALREADY IS ═══════════════════════════════════
   ⚖️ Gil 2026-08-20, watching the floor use the Call Tank: *"they click the call tank on left panel,
   and they go to a screen witt all leads to call. they are calling from there instead of pressing
   start. they don't see the start button."*

   🛑 IT IS A PLACEMENT FIX, NOT A SECOND BUTTON. Start moved OUT of `.page-head.has-action`'s
   top-right corner and into this band — the same `_next-call-button` form, rendered once. Two
   controls wearing one name is the scar that partial's own docblock carries; the answer to "nobody
   sees it" is to put the one control where the reading eye lands, not to add another.

   🛑 WHY THE CORNER LOST. `has-action` pins ONE primary act top-right, which works on a screen whose
   body is a form. This screen's body is a LIST of clickable names, so the corner competes with 15
   rows of blue text and loses — every row is a door, and the only door that works the queue in
   order was the smallest thing on the page. The band spans the column above the list: it is read
   before any row, and it says who is first so pressing it feels like continuing rather than
   gambling.

   Tokens only, and the button grows through THIS scope rather than a new `.btn` size — a size
   modifier would be a vocabulary addition that every other screen then has to be protected from. */
.start-band {
    display:flex; align-items:center; justify-content:space-between; gap:18px; flex-wrap:wrap;
    margin:0 0 16px; padding:18px 22px; border-radius:16px;
    background:linear-gradient(135deg,#eef3ff,#f7f9ff);
    box-shadow:inset 0 0 0 1px var(--line);
}
.start-band > .start-main { flex:1 1 320px; min-width:0; }
.start-band > .start-main h2 { margin:0 0 4px; font-size:17px; color:var(--brand-ink); }
.start-band > .start-main p { margin:0; font-size:13.5px; color:var(--mut); }
.start-band > .start-main .start-next { color:var(--brand-ink); font-weight:700; }
.start-band > .start-act { flex:0 0 auto; }
.start-band > .start-act .btn { font-size:16.5px; padding:14px 28px; border-radius:13px; }
/* Rule 7 — at phone width the act drops under the copy and goes full width, the same behaviour
   `.page-head.has-action` already has, so the two headers cannot teach different habits. */
@media (max-width:640px) {
    .start-band { padding:16px; }
    .start-band > .start-act { width:100%; }
    .start-band > .start-act .btn { width:100%; justify-content:center; }
    .start-band > .start-act form { width:100%; }
}
.lead-crumbs.is-tank > .tank-prev { display:flex; align-items:center; gap:12px; flex-wrap:wrap; min-width:0; }
/* 🛑 CALL SITS BESIDE NEXT, AND `margin-left:auto` IS ON THIS ONE NOW (owner 2026-08-05, when the
   tank stopped dialling and the rep took the press back). It is the FIRST of the two right-hand
   controls, so it owns the push; Next follows it on its natural gap. Moving the rule and not
   copying it is the point — two elements both claiming `auto` would split the leftover space
   between them and unanchor the pair from the right edge. */
.lead-crumbs.is-tank > .tank-call { margin-left:auto; flex:0 0 auto; display:flex; align-items:center; gap:8px; }
.lead-crumbs.is-tank > .tank-next { flex:0 0 auto; }
/* 🛑 THE REFUSED STATE HAS TO LOOK REFUSED. A rep mid-call sees this button in the corner the whole
   time; without a visible difference they would press it, feel nothing happen, and press again —
   the "nothing happened" failure this call flow was fixed for on 2026-07-29. `not-allowed` plus the
   button's `title` says which. It turns back on by itself the moment the call is dispositioned. */
.lead-crumbs.is-tank > .tank-next .btn:disabled { opacity:.55; cursor:not-allowed; }
@media (max-width:640px) {
    /* Stacked, the Next stops being "top right" and becomes a full-width act of its own — the same
       thing `.page-head.has-action` does at this width, so the two headers behave alike.
       ⚠️ CALL AND NEXT SHARE THE ROW HERE RATHER THAN EACH TAKING ONE (2026-08-05). This bar is
       PINNED in the tank, so every row it stacks into is height spent on every screenful at the
       width he reviews at (~590px) — two full-width buttons would be two rows of it. Side by side
       they read as the pair they are: the one that rings this customer, the one that leaves them. */
    .lead-crumbs.is-tank > .tank-call { margin-left:0; flex:1 1 0; }
    .lead-crumbs.is-tank > .tank-next { flex:1 1 0; }
    .lead-crumbs.is-tank > .tank-call .btn,
    .lead-crumbs.is-tank > .tank-next .btn { width:100%; text-align:center; }
    .lead-crumbs.is-tank > .tank-call form { flex:1 1 0; }
}

/* ═══ Q12m — THE HEADER COMPACTION (owner 2026-07-31) ═══
   *"use teh right side of name to put data to make the top section shorter in height … phone
   Numbe, address and city state and zip, stacked … Status drop down and Send to RAD"*

   🛑 THE HEIGHT COMES FROM THE SHAPE, NOT FROM SHRINKING ANYTHING. Nothing here sets a smaller
   font or a tighter line — the block used to be the SUM of four stacked rows (name+facts, the
   correct-me line, then a whole row for Status and Send to RAD) and is now the MAX of three
   columns standing beside each other. Measured at 1280x800 on this page's worst case (two numbers,
   a full address, a manager so Send to RAD renders).

   🛑 `align-items:flex-start` IS LOAD-BEARING. Centring makes every column as tall as the tallest
   and hands the padding back, which is the whole saving.

   ⚠️ These rules must not touch padding, border or margin on `.lead-head` itself: `.on-call`
   changes COLOUR only, and the rule the owner set is that nothing moves when a call connects. */
.lead-idrow { display:flex; align-items:flex-start; gap:14px; flex-wrap:wrap; }
.lead-idname { min-width:0; flex:0 1 auto; }
/* ROUND 2 (owner 2026-08-01): *"make phone number font bigger and bolder"* · *"move adress to teh
   right of phone numbers, bigger font size"*. So the facts became two columns — phones, then
   address — instead of one four-line stack. Bigger type AND a shorter block, because the height is
   set by the tallest column (2 lines) rather than by the sum of four. */
/* ROUND 3 — the order HE dictated on the live screen: name (phone under it) · address · Type of lead
   · Status. Each is its own column of the identity row, so the block stays the height of the tallest.
   ⚠️ An earlier attempt made the chip row `flex:1 0 100%` inside a wrapping `.lead-facts`; a 100%
   basis makes the container demand the full width, which pushed the ACTION column onto its own line
   and took the header from 205px to 224px. Measured, not reasoned — the "fix" was taller than the
   thing it fixed. Columns, never a wrapping row. */
.lead-facts { display:flex; align-items:flex-start; min-width:0; flex:0 1 auto;
    color:var(--mut); line-height:1.4; padding-top:2px; }
.lead-addr { font-size:15px; font-weight:600; color:var(--mut); }
/* The ALERT chips beside the address — the ones that explain a MISSING control (DNC, no consent,
   unlicensed state, parked). The type chip left this column on 08-02 and the temperature never lived
   here; both now sit under Status as `.lead-typechips`. `.lead-typelabel` was deleted with them. */
.lead-tags { display:flex; flex-direction:column; align-items:flex-start; gap:4px; }
/* 🛑 `.dial-phone` is the double-click-to-call hook the phone strip binds to — this rule changes how
   it LOOKS and must never change what it IS. No display change, no wrapper, no rename. */
.lead-phones { font-size:17px; font-weight:700; color:var(--ink); letter-spacing:-.01em; }
.lead-phones .lead-fact-alt { font-size:15px; font-weight:600; color:var(--mut); }
.lead-addr { font-size:15px; font-weight:600; color:var(--mut); }
@media (max-width:820px) {
    .lead-facts { flex-direction:column; gap:2px; }
}
/* THE NAME IS THE DOOR (owner 2026-08-01, *"We can do teh behind the name thing"*). The contact
   editor opens from the customer's own name, so the header keeps its height and the capability.
   🛑 The name must still READ as a name, not as a button — so the affordance is a quiet pencil that
   only appears on hover/focus, and the disclosure triangle is removed on both engines. Keyboard
   users keep everything: <summary> is focusable and Enter/Space toggles it natively. */
.lead-nameedit > summary { list-style:none; cursor:pointer; display:inline-flex; align-items:center; gap:9px; }
.lead-nameedit > summary::-webkit-details-marker { display:none; }
.lead-nameedit > summary::marker { content:''; }
.lead-nameedit > summary > h1 { display:inline; }
.lead-nameedit-hint { color:var(--mut); font-size:15px; opacity:0; transition:opacity .12s ease; }
.lead-nameedit > summary:hover .lead-nameedit-hint,
.lead-nameedit > summary:focus-visible .lead-nameedit-hint,
.lead-nameedit[open] > summary .lead-nameedit-hint { opacity:1; }
.lead-nameedit > summary:focus-visible { outline:2px solid var(--brand); outline-offset:3px; border-radius:6px; }
/* 🛑 OPEN, IT MUST NOT BLOW THE COLUMN OUT. The form is six inputs wide; unconstrained it demands
   the whole row, which wrapped the avatar onto a line of its own above the name and pushed the
   phone/address column under the form. Caught by LOOKING at the open state — the closed one was
   perfect and every assertion passed. Capped, the inputs wrap inside the identity column and every
   other column stays where it was. */
.lead-nameedit[open] { max-width:440px; }

.lead-actions { margin-left:auto; display:flex; flex-direction:column; align-items:flex-end; gap:8px; }
.lead-chips, .lead-controls { display:flex; align-items:center; gap:8px; flex-wrap:wrap; justify-content:flex-end; }
/* 🛑 STATUS IS TOP-RIGHT, LITERALLY — owner 2026-07-31: *"On top right corner, Status"*, and on the
   live screen 08-01: *"Status not in top right."* He was right: with the fact-chips in this column
   the first row overflowed, so Status rendered on the THIRD line of the header. The chips have moved
   to the facts, and this puts the Status row FIRST in what remains. `order` rather than moving the
   markup, so the no-JS fallback button and the confirm script keep the DOM they were written for. */
.lead-actions > .lead-controls { order:-1; }
/* ≤820px: the columns stop being columns — the action stack goes back to the left edge rather than
   stranding controls against the right margin under a name that now spans the width. */
@media (max-width:820px) {
    .lead-actions { margin-left:0; align-items:flex-start; }
    .lead-chips, .lead-controls { justify-content:flex-start; }
}

/* THE MARK — the non-colour half. A filled dot, the word, and a clock that was not on the screen a
   second ago, for the ~1 man in 12 who cannot separate these hues on a shared floor at speed. */
.live-mark {
    display:inline-flex; align-items:center; gap:7px; white-space:nowrap;
    padding:4px 11px 4px 9px; border-radius:999px;
    background:var(--live); color:#fff; font-size:11.5px; font-weight:800; letter-spacing:.05em;
}
.live-mark .lm-dot {
    width:9px; height:9px; border-radius:50%; background:#fff; flex:none;
    box-shadow:0 0 0 0 rgba(255,255,255,.7); animation:lm-pulse 1.6s infinite;
}
/* The pulse is the "right now". Reduced-motion users get a solid dot — the word and the running
   clock carry the meaning on their own, so nothing is lost. */
@keyframes lm-pulse {
    70% { box-shadow:0 0 0 7px rgba(255,255,255,0); }
    100% { box-shadow:0 0 0 0 rgba(255,255,255,0); }
}
@media (prefers-reduced-motion: reduce) { .live-mark .lm-dot { animation:none; } }
/* Tabular figures so the second digit does not shuffle the row every tick. */
.live-mark .lm-clock { font-size:13px; font-weight:800; letter-spacing:0; font-variant-numeric:tabular-nums; }
.live-mark .lm-pipe {
    font-size:10.5px; font-weight:700; letter-spacing:.02em;
    padding:2px 8px; border-radius:999px; background:rgba(255,255,255,.2);
}

/* THE CUSTOMER RANG US (owner 2026-07-31) — a fact about the row, and deliberately NOT a variant of
   the 🔥 lane badge: there are already two "hot"s in this app and a third would fork. Answered is
   calm teal (good news, no action). Missed borrows `.tag.alarm`'s red family because it IS the
   action — it is also the row the call tank now puts on top of the queue. */
.tag.call-in { background:#e2f4f1; color:#12655a; white-space:nowrap; }
.tag.call-in.missed { background:#fbe4e4; color:#a12626; font-weight:800; }
.tag.alert { background:#fff; color:#a12626; box-shadow:inset 0 0 0 1.5px #d9a2a2; }
/* `.tag.ok` — green; the state is FINE and wants nothing from you (a lead that loaded, a vendor
   field we mapped, a consent certificate that arrived). It completes the severity trio that
   `.warn` (amber, "look at this") and `.alert` (red, "this is broken") already half-described.
   Deliberately NOT `.tag.pub`: that one means *published*, a fact about content, and reusing it
   for "this worked" is how a status vocabulary quietly stops meaning anything.
   Extracted 2026-07-16 from ops/sources/{log,sandbox}, which each defined their own green. */
.tag.ok { background:#e4f7ec; color:#137a45; }
.lock { color:var(--mut); font-weight:600; }

/* `.help-pop` — a "?" that reveals a rich explanation on hover/focus. A native title="" holds only
   flat text; this holds a structured what/why/how. The panel is a DESCENDANT of the hovered span,
   so the pointer can travel into it without closing (it stays :hover). Keyboard-reachable: the "?"
   is focusable and :focus-within opens it too. Bare .stat title="" hints cover the quick reminder;
   this is the full guide. Lives here (the only served sheet) so any admin card can reuse it. */
.help-pop { position:relative; display:inline-flex; vertical-align:middle; }
.help-pop .help-ic { display:inline-flex; align-items:center; justify-content:center; width:18px; height:18px; border-radius:50%; background:#eef2fb; color:var(--brand); font-size:12px; font-weight:800; border:1px solid #d6e0f5; cursor:help; }
.help-pop .help-panel { position:absolute; top:calc(100% + 8px); left:0; z-index:var(--z-popover); width:380px; max-width:min(380px,86vw); background:var(--card); border:1px solid var(--line); border-radius:12px; box-shadow:0 18px 40px -18px rgba(26,32,60,.4); padding:16px 18px; font-size:12.5px; line-height:1.5; color:var(--ink); font-weight:400; text-align:left; text-transform:none; letter-spacing:normal; opacity:0; visibility:hidden; transform:translateY(-4px); transition:opacity .12s ease, transform .12s ease, visibility .12s; }
.help-pop:hover .help-panel, .help-pop:focus-within .help-panel { opacity:1; visibility:visible; transform:translateY(0); }
.help-pop .help-panel h4 { font-size:11.5px; font-weight:800; color:var(--brand-ink); margin:12px 0 3px; text-transform:uppercase; letter-spacing:.03em; }
.help-pop .help-panel h4:first-child { margin-top:0; }
.help-pop .help-panel p { margin:0 0 5px; }
.help-pop .help-panel ul { margin:2px 0 6px; padding-left:16px; }
.help-pop .help-panel li { margin:3px 0; }
.help-pop .help-panel b { color:var(--brand-ink); font-weight:700; }

/* ---- Script editor: Level 1 (a list of scripts) + Level 2 (the call, in call order) ---- */
/* The ROW is the link (owner 2026-07-19: no Open button — clicking the card opens the script).
   Stretched-link: the title's ::after covers the card; the actions sit above it on z-index. */
.script-row { position:relative; }
.script-row:hover { border-color:var(--brand); }
.script-row-head { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
.script-row-head > div { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.script-row-title { font-size:17px; font-weight:800; color:var(--brand-ink); text-decoration:none; }
.script-row-title::after { content:""; position:absolute; inset:0; }
.script-row-title:hover { color:var(--brand); }
.script-row-actions { position:relative; z-index:var(--z-raised); }
.script-row-facts { margin:8px 0 0; font-size:13px; }
.script-row-warnings { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
.section-note { margin:4px 0 12px; font-size:13px; }
.opener-row { flex-wrap:wrap; justify-content:flex-start; gap:8px; }
.opener-line { font-size:13px; flex:1 1 240px; }
.orphan-card { border:1px solid #f3dea8; }

/* THE SCRIPT READER's findings on the L2 editor (2026-07-26). The three buckets are visually
   DISTINCT on purpose — Principle 4: a craft note must never read as gravely as a floor finding, and
   a floor finding must never be softened by sitting in the same grey list as one. Compliance uses the
   reserved alarm; structure is a defect (amber); craft is neutral, because it is a prompt to look, not
   a verdict. 🛑 --danger is rule-13 reserved: it marks a VERDICT and never appears on a live-call
   surface — this is an authoring screen, which is exactly where it belongs. */
.lint-h { font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.06em; margin:18px 0 8px; }
.lint-h:first-of-type { margin-top:4px; }
.lint-h.lint-compliance { color:var(--danger-ink); }
.lint-h.lint-structure { color:#8a5b00; }
.lint-h.lint-craft { color:var(--mut); }
.lint-row { display:flex; align-items:baseline; gap:9px; flex-wrap:wrap; padding:8px 0; border-top:1px solid var(--line); font-size:13px; }
.lint-row:first-of-type { border-top:none; }
.lint-key { font-family:ui-monospace,Menlo,monospace; font-size:11px; font-weight:700; color:#fff; background:var(--brand); padding:2px 7px; border-radius:6px; }
.lint-rule { font-size:10.5px; font-weight:800; letter-spacing:.04em; color:var(--mut); }
.lint-msg { color:var(--ink); flex:1 1 260px; }
.lint-advice { flex:1 1 100%; color:var(--mut); font-size:12.5px; padding-left:2px; }

.step { border:1px solid var(--line); border-radius:12px; margin-top:8px; background:#fff; }
.step[open] { border-color:var(--ring); box-shadow:0 2px 10px rgba(53,82,155,.06); }
.step-summary { display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:10px 12px;
                cursor:pointer; font-size:14px; }
.step-stage { text-transform:capitalize; }
.step-line { flex:1 1 220px; font-size:13px; }
.step-answers { font-size:12px; white-space:nowrap; }
/* "How do I edit?" — the row always expanded into the editor; nothing SAID so (owner 2026-07-19). */
.step-edit-hint { color:var(--mut); font-size:13px; }
.step-summary:hover .step-edit-hint { color:var(--brand); }
.step-body { padding:0 12px 14px; border-top:1px solid var(--line); }
/* The spoken line in full, above the editor — the reason to expand a step. */
.step-says { margin-top:12px; padding:12px 14px; border-radius:10px;
             background:#f6f8fc; border:1px solid var(--line); }
.step-says-label { display:block; font-size:11px; letter-spacing:.06em; text-transform:uppercase;
                   color:var(--mut); margin-bottom:4px; }
.step-says-text { margin:0; font-size:15px; line-height:1.6; color:var(--ink); }
.step-says-reentry { margin:10px 0 0; font-size:13px; line-height:1.5; color:var(--mut); }
.step-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:12px 18px; margin-top:12px; }
.step-field { display:block; margin-top:12px; }
.step-check { display:flex; gap:8px; align-items:center; margin-top:22px; }
.step-answers-block { margin-top:16px; }
.step-actions { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-top:14px; }
.step-action-note { font-size:12px; }
.step-park { margin-top:12px; display:block; }
.compliance-warn p { margin:4px 0; }
.retire-set { border:1px solid #f3dea8; background:#fff6e2; border-radius:10px; padding:10px 12px; margin-bottom:12px; width:100%; }
.retire-set p { margin:0 0 8px; font-size:13px; }
.retire-also { display:flex; gap:8px; align-items:center; font-size:13px; font-weight:600; }
.make-live .ack-label { display:flex; gap:8px; align-items:center; margin-top:10px; font-weight:600; }
.make-live form { display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.alert-compliance form { display:block; }
@media (max-width:640px) { .step { margin-left:0 !important; } }

/* ---- Flash + link box ---- */
.flash { padding:13px 16px; border-radius:12px; margin-bottom:18px; font-size:14px; font-weight:500; }
.flash.ok { background:#e7f7ee; color:#136c3f; border:1px solid #c3e9d3; }
.flash.err { background:#fdecec; color:#a12626; border:1px solid #f2c6c6; }
.flash.warn { background:#fff6e2; color:#8a5a00; border:1px solid #f3dea8; }
/* An icon inside a flash. Shared, because a NOTICE that needs an icon is not rare — and because the
   alternative is what happened on the calculator: the only iconned banner in the vocabulary was the
   reserved compliance alarm, so a standing disclosure that wanted an icon reached for the alarm and
   got the verdict red with it. Give the softer banners an icon and rule 13 costs nothing to obey. */
.flash .ic-warn { flex:0 0 auto; display:inline-flex; }
.flash .ic-warn svg { width:20px; height:20px; display:block; }
/* THEIR DETAILS ARRIVED MID-CALL (owner 2026-08-03). Rendered by `ops/crm/_fresh-data-watch`,
   above `page-head` and IN THE FLOW — so it takes its own line and cannot cover the call bar, the
   dispo capsule or the pop. 🛑 Deliberately carries NO z-index: the --z-callbar ceiling is a
   question only a floating thing has to answer, and the cheapest way to obey it is not to float.
   Reuses `.flash.ok` for the notice itself so a good-news banner reads the one way it always has. */
.fresh-data-note { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.fresh-data-show { font-weight:600; white-space:nowrap; }
.fresh-data-ask { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:18px; font-size:14px; }
/* 🛑 `display:flex` BEATS THE BARE `hidden` ATTRIBUTE, so both rows above must say `display:none`
   out loud — the `.ph-dispo-kids` trap, which `.ws-lien[hidden]` names five hundred lines below and
   obeys. Without this the partial's whole fails-toward-silence design inverts: `_fresh-data-watch`
   ships BOTH rows carrying `hidden`, so the notice announced "📄 Their details just arrived" on
   every lead a rep opened — including one where nothing had arrived and the file was never empty —
   and the manual re-check, offered ONLY on a nameless file, sat on all of them. A banner asserting
   an event that did not happen is the empty-frame defect this app forbids by name, pointed the
   other way: not a heading promising content that is not there, but content promising an event that
   never was. Proved by rendering the sheet in this stylesheet and looking at it, both ways. */
.fresh-data-note[hidden], .fresh-data-ask[hidden] { display:none; }

/* WHO ELSE IS IN THIS FILE (owner 2026-08-11 — he asked whether to allow only one person at a time;
   locking was refused because it would shut a covering rep out of a live call, his own 08-05 ruling).
   Reuses `.flash.warn`'s amber: this is the same family of message as a held save, and a new colour
   would imply a new severity. Quieter than a flash — it is a standing fact, not an event. */
.whos-here-note { font-size:13px; font-weight:600; padding:8px 13px; margin-bottom:14px; }
.whos-here-note[hidden] { display:none; }

/* A BOX THAT FOLLOWED SOMEBODY ELSE'S EDIT. 🛑 The point is that the rep SEES the file move — a
   value that changed silently under them is its own kind of lie, even when the new value is right.
   Fades on its own; nothing to dismiss, because it is not asking for anything. */
.is-followed { animation: wh-followed 2.2s ease-out; }
@keyframes wh-followed {
    0%   { background:#fff3d6; box-shadow:0 0 0 3px rgba(243,222,168,.55); }
    70%  { background:#fff9ec; box-shadow:0 0 0 3px rgba(243,222,168,0); }
    100% { background:transparent; box-shadow:none; }
}
@media (prefers-reduced-motion: reduce) { .is-followed { animation:none; background:#fff9ec; } }
/* The manual re-check. A quiet text control, never a primary button — pressing it is a hunch, not
   an action, and it sits on a screen where the loud buttons all place calls. */
.fresh-data-btn { background:none; border:0; padding:0; font:inherit; font-weight:600;
    color:var(--brand-2); cursor:pointer; text-decoration:underline; text-underline-offset:3px; }
.fresh-data-btn:hover { color:var(--brand); }
.fresh-data-btn:disabled { opacity:.5; cursor:default; text-decoration:none; }
.fresh-data-said { font-style:italic; }

.linkbox { background:#f3f6fd; border:1px dashed #b7cdf0; border-radius:12px; padding:14px 16px; margin-bottom:18px; font-size:14px; }
.linkbox code { display:block; word-break:break-all; font-size:12px; color:var(--brand-2); margin-top:8px; background:#fff; padding:9px 11px; border-radius:8px; border:1px solid var(--line); }

/* ---- Tables + lists ---- */
table { width:100%; border-collapse:collapse; font-size:14px; }
th, td { text-align:left; padding:11px 8px; border-bottom:1px solid var(--line); vertical-align:middle; }
th { color:var(--mut); font-size:11px; text-transform:uppercase; letter-spacing:.05em; }

/* ---- The lead table (`.lead-table` + `.lt-`) ----
   The SHARED dense-board vocabulary (owner 2026-07-22): Incoming Leads and My Leads render the
   same table shape, driven by public/js/lead-table.js — click-to-sort (client-side on the ops
   board; SERVER links on My Leads, whose book spans pages), drag a header to reorder, and the
   Columns menu to show/hide, both remembered per browser. The table scrolls in `.table-scroll`,
   its own box — the page body never pans sideways (design-system rule). */
.table-scroll { overflow-x:auto; }
.lead-table th { cursor:pointer; user-select:none; white-space:nowrap; }
.lead-table th a { color:inherit; text-decoration:none; display:inline-block; }
.lead-table th.lt-asc::after { content:" \25B2"; font-size:9px; }
.lead-table th.lt-desc::after { content:" \25BC"; font-size:9px; }
.lead-table th.lt-drop { box-shadow:inset 3px 0 0 var(--brand); }
.lt-cols { position:relative; display:inline-block; }
.lt-cols summary { cursor:pointer; list-style:none; font-size:13px; color:var(--mut); user-select:none; font-weight:700; }
.lt-cols summary::-webkit-details-marker { display:none; }
.lt-cols-menu {
    position:absolute; right:0; top:calc(100% + 6px); z-index:var(--z-dropdown); min-width:190px;
    background:var(--card); border:1px solid var(--line); border-radius:12px;
    box-shadow:0 12px 30px -12px rgba(26,32,60,.35); padding:10px 14px;
}
.lt-cols-menu label { display:flex; align-items:center; gap:8px; font-size:13px; padding:3px 0; white-space:nowrap; cursor:pointer; }
.inline { display:inline; }
.row-actions { display:flex; gap:7px; flex-wrap:wrap; align-items:center; }
/* ---- Tabs (`.tabstrip` + `.tab`) ----
   LINKS, not script, and the active tab is decided SERVER-side from `?tab=`. Two reasons this
   is not a `:target`/radio CSS tab and definitely not a JS one:
     1. Content is never gated on JS (rule) — a dead script must not blank the page.
     2. Every form on a detail page POSTs and redirects `back()`, which follows the referer and
        therefore carries `?tab=`. A client-side tab resets to the first tab on every save —
        on a page whose whole purpose is saving things. Server-side tabs also deep-link, so an
        error state or a bookmark can name its own tab.
   A tab the viewer may not open is never rendered — and never honoured if typed. */
.tabstrip {
    display:flex; gap:4px; flex-wrap:wrap; margin:0 0 18px;
    border-bottom:1px solid var(--line); padding-bottom:0;
}
.tabstrip .tab {
    display:inline-flex; align-items:center; gap:8px; padding:10px 15px; border-radius:10px 10px 0 0;
    text-decoration:none; color:var(--mut); font-weight:600; font-size:14px; white-space:nowrap;
    border:1px solid transparent; border-bottom:none; margin-bottom:-1px;
    transition:color .15s, background .15s;
}
.tabstrip .tab:hover { color:var(--brand); background:#f1f4fa; }
.tabstrip .tab.on {
    color:var(--brand-ink); background:var(--card); border-color:var(--line);
    box-shadow:0 -2px 0 var(--brand) inset;
}
.tabstrip .tab .dot { width:7px; height:7px; border-radius:50%; background:#a12626; flex:none; }
/* `.tab-n` — how many sit behind a tab (the deal board's phases, ⚖️ R-020). 🛑 PLAIN TEXT, never a
   round badge: Gil rejected the bubble look outright (*"you love teh bubble look, i don't like
   that"*), and a pill-shaped counter bolted onto a flat tab reintroduces exactly what he refused. */
.tabstrip .tab .tab-n { color:var(--mut); font-weight:600; font-variant-numeric:tabular-nums; }
.tabstrip .tab.on .tab-n { color:var(--brand); }
@media (max-width:640px) { .tabstrip { border-bottom:0; } .tabstrip .tab { border-radius:10px; margin-bottom:0; } }
/* `.tabstrip-scroll` — for a strip with too many tabs to fit a line (the 7-tab Leads hub): swap
   the default wrap for a single horizontal scroll, so the tabs never spill onto a second row at
   1280px. The row scrolls sideways instead of stacking; scrollbar hidden, swipe/wheel still work. */
.tabstrip.tabstrip-scroll { flex-wrap:nowrap; overflow-x:auto; overflow-y:hidden; scrollbar-width:none; }
.tabstrip.tabstrip-scroll::-webkit-scrollbar { height:0; }
/* `.tab-group` — a muted label that names WHY the tabs after it belong together (The Playbook,
   2026-07-21). The hub grew past the point where one flat row of a dozen tabs reads as anything:
   the owner's words were "all over the place... may be outdated". Three groups — what reps say
   live · what new hires learn · how the AI writes and grades — so a tab's neighbours explain it.
   🛑 NOT A TAB, and it must not LOOK like one — the owner clicked "THE CALL" and nothing happened
   ("Nothing on the call, tab"). Sitting at tab height inside the row was the whole problem, so it
   is a full-height divider label now: no tab padding, `pointer-events:none`, half-opacity, and
   hidden entirely on narrow screens where the row is tight. `aria-hidden` in the markup because
   each tab's own label already carries the meaning. */
.tabstrip .tab-group {
    display:inline-flex; align-items:center; align-self:stretch; white-space:nowrap;
    margin:0 4px 0 12px; padding:0 12px 0 12px; border-left:1px solid var(--line);
    color:var(--mut); font-size:10px; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
    opacity:.55; pointer-events:none; user-select:none;
}
.tabstrip .tab-group:first-child { border-left:0; margin-left:0; padding-left:0; }
@media (max-width:640px) { .tabstrip .tab-group { display:none; } }

/* Scoped to `.roster` on purpose: `table` styling is global, and the Teams/signatures tables
   are not doorways — only a table whose rows drill in should signal that it can be entered. */
.roster tbody tr:hover td { background:#f7f9fd; }
.roster a.who { color:inherit; text-decoration:none; }
.roster a.who:hover { color:var(--brand); text-decoration:underline; }
.list-item { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px 0; border-bottom:1px solid var(--line); }
.list-item:last-child { border-bottom:0; }
/* `.list-item.row-link` — the WHOLE roster/attention row is one clickable link (opens the rep's
   Progress tab). Looks like a plain row; the whole thing highlights + is clickable on hover. Uses
   inset padding so the hover tint reads as a full-width band, not a hairline strip. */
a.list-item.row-link { text-decoration:none; color:inherit; cursor:pointer; padding:12px 10px; margin:0 -10px; border-radius:10px; transition:background .12s; }
a.list-item.row-link:hover { background:#f4f7fd; }
a.list-item.row-link b { color:var(--ink); }

/* ---- Icon actions (owner direction 2026-07-15: "big boxes on every page") ----
   Row-level actions are a glyph + a label, NOT a stack of labeled buttons. The label is
   hidden on a mouse-driven viewport and revealed by the native tooltip on hover; on touch
   there IS no hover, so below 900px the label comes back as text (design-system.md: icon-only
   + hover-only is not a faithful reading of "everywhere"). Never let the glyph be the only
   thing a user gets — an undecodable icon is worse than the big button it replaced. */
.icon-btn {
    display:inline-flex; align-items:center; gap:7px; cursor:pointer; text-decoration:none;
    font-family:inherit; font-weight:600; font-size:13px; padding:7px 9px; border-radius:9px;
    border:0; background:#f1f4fa; color:var(--brand-ink); box-shadow:inset 0 0 0 1px var(--line);
    transition:background .15s, color .15s, box-shadow .15s;
}
.icon-btn svg { width:16px; height:16px; flex:none; }
.icon-btn:hover { background:#e9eef8; color:var(--brand); }
.icon-btn.primary { background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#fff; box-shadow:0 6px 14px -8px rgba(53,82,155,.9); }
.icon-btn.primary:hover { color:#fff; filter:brightness(1.06); }

/* `.copy-btn` — the one-click copy control beside each ADP field on the Paperwork card. A small,
   quiet glyph button that sits inline after the value (`.cp-val`); clicking swaps the clipboard glyph
   for a brief ✓ (`.copied`). Kept subtle so it never competes with the value it copies. */
.adp-fields td { white-space:normal; }
.cp-val { vertical-align:middle; }
.copy-btn {
    display:inline-flex; align-items:center; justify-content:center; vertical-align:middle;
    width:24px; height:24px; margin-left:8px; padding:0; cursor:pointer;
    border:0; border-radius:7px; background:#f1f4fa; color:var(--mut);
    box-shadow:inset 0 0 0 1px var(--line); transition:background .15s, color .15s;
}
.copy-btn:hover { background:#e9eef8; color:var(--brand); }
.copy-btn.copied { background:#d8f3e0; color:#1a7f43; box-shadow:inset 0 0 0 1px #a5e0b8; font-size:13px; font-weight:700; }
/* Muted error tint for reversible-but-serious controls. NOT --danger: the compliance alarm
   red stays spent on compliance FAILs only (design-system rule). */
.icon-btn.risk { background:#fdecec; color:#a12626; box-shadow:inset 0 0 0 1px #f2c6c6; }
.icon-btn.risk:hover { background:#fbdede; color:#a12626; }
.icon-btn[disabled] { opacity:.45; cursor:not-allowed; }
.icon-btn[disabled]:hover { background:#f1f4fa; color:var(--brand-ink); }
/* The gradient .primary swallows the UA's default focus ring, so keyboard users had nothing. */
.icon-btn:focus-visible { outline:none; box-shadow:0 0 0 3px var(--ring); }
.icon-btn .lbl { white-space:nowrap; }
/* Collapse to icon-only ONLY where the action repeats down a column and the row supplies the
   context. A DESTRUCTIVE control is never compressed: it appears once, it is the thing you must
   not click by accident, and a bare red glyph you have to hover to identify is worse than the
   big button it replaced. `.risk` therefore always keeps its words.

   `.keeps-words` is the same exemption for the OTHER case the rationale above excludes: a button that
   appears ONCE, in a header, with no row around it to supply the context. Added 2026-07-29 for the
   sticky-note opener, which collapsed to an unlabelled dog-eared page beside the Call button. Reach
   for it only on a lone control — on anything that repeats, the collapse is the correct behaviour. */
@media (min-width:900px) {
    .icon-btn:not(.risk):not(.keeps-words) .lbl { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
}

/* ---- THE PHONE STRIP (`.ph-`) ----
   Owner rulings 2026-07-18 (three rounds): the phone controls are AMBIENT CHROME — a NARROW,
   FULL-BLEED bar at the VERY top of every authed page (sidebar edge to screen edge, outside
   .wrap), everything on ONE line, on a solid surface of its own. The surface is THE CALLBAR BLUE
   (the same brand gradient `.sr-callbar` wears on the live-call screen — owner asked for a color
   that matches the blue + light gray): the two phone surfaces share one language — A BLUE BAR
   MEANS THE PHONE — and the green READY DOT stays as the status accent that pops against it.
   Provisioned agents only; never on the call-surface (the layout excludes it there — the callbar
   itself is there). Partial: partials/phone-strip. */
.ph-strip {
    display:flex; align-items:center; gap:8px 14px; flex-wrap:wrap;
    padding:7px 18px; background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#e8edfb;
    box-shadow:inset 0 -1px 0 rgba(0,0,0,.18), 0 4px 14px -8px rgba(23,18,37,.5);
    /* LOCKED to the top (owner): the phone chrome stays put while the page scrolls under it.
       Sticky inside .main; above page content, below nothing (the call-surface, which has its own
       sticky callbar, never renders this strip). */
    /* 🛑 THIS BAR IS THE CEILING. It holds Hang up, and the owner has been trapped on a live call by
       an overlay twice: "i cant see anyting on top to hang up" (2026-07-22) and, when the New-caller
       card covered it, "could not reach Hang up, and had to end the call from VICIdial".
       ⚠️ CORRECTION to what stood here: the earlier note blamed the value ("at z-index 60 this bar
       opened its own stacking context"). Wrong, and the wrong lesson — `position:sticky` opens a
       stacking context ALWAYS, at any z-index. Raising 60→160 never removed the trap on the transfer
       panel; it only raised the ceiling the panel is stuck under. The fix belongs on the PARENT
       because a child can never escape it, which is still right and is why this line is here.
       The rank itself now comes from the scale in :root — see --z-callbar. Do not hand-pick a number
       here again; that is what re-broke this twice. */
    position:sticky; top:0; z-index:var(--z-callbar);
}
/* ═══ SOMEBODY ELSE'S CLIENT — the owner band ═══════════════════════════════════════════════════
   ⚖️ Gil 2026-08-19: "Just need something that shows BIG, who owns the lead."

   🛑 IT IS A ROW OF THE BAR, NOT AN OVERLAY, and that is deliberate on a bar documented above as
   THE CEILING — the surface a rep has twice been trapped behind by something that covered Hang up.
   `flex:0 0 100%` makes it the bar's own row on a container that already wraps; `order:-1` puts it
   above the controls without moving them in the DOM. It therefore takes its own space, pushes
   nothing over anything, inherits the bar's stickiness, and cannot outlive the call. */
.ph-cover {
    flex:0 0 100%; order:-1;
    margin:-7px -18px 6px; padding:9px 18px 10px;
    background:linear-gradient(180deg,#fffaf0,#fbf1de);
    border-bottom:2px solid #c98a2f; color:#6b5410;
}
.ph-cover-head { display:flex; align-items:center; gap:13px; flex-wrap:wrap; }
.ph-cover-face {
    width:42px; height:42px; border-radius:999px; flex:none;
    background:#c98a2f; color:#fff; font-size:16px; font-weight:800; letter-spacing:.02em;
    display:flex; align-items:center; justify-content:center;
}
.ph-cover-who { display:flex; flex-direction:column; min-width:0; flex:1 1 210px; }
.ph-cover-kicker { font-size:10.5px; font-weight:800; letter-spacing:.07em; text-transform:uppercase; color:#8a6d1a; }
/* BIG is the requirement, not a flourish: it is the one fact the rep must not miss. */
.ph-cover-name { font-size:23px; font-weight:800; line-height:1.12; letter-spacing:-.02em; color:#6b5410; }
.ph-cover-sub { font-size:12.5px; color:#8a6d1a; margin-top:1px; }
.ph-cover-sub::before {
    content:''; display:inline-block; width:8px; height:8px; border-radius:999px;
    background:#c98a2f; margin-right:6px; vertical-align:1px;
}
.ph-cover-sub.is-free::before { background:#2e9e5b; }
.ph-cover-acts { display:flex; gap:8px; flex-wrap:wrap; }
.ph-strip .ph-cover-acts .btn { background:#8a6d1a; color:#fff; }
.ph-strip .ph-cover-acts .btn:hover { background:#6b5410; }
.ph-strip .ph-cover-acts .btn.ph-cover-xfer { background:#fff; color:#8a6d1a; box-shadow:inset 0 0 0 1px #e3d4a8; }
.ph-strip .ph-cover-acts .btn.ph-cover-xfer:hover { background:#fdf6e6; }

/* THE WORDS. The one they need is open; the rest are collapsed one-liners — his 2026-07-29 rail
   shape, reused rather than re-invented, so a rep learns one idiom for "click the one you hear". */
.ph-cover-say { margin-top:8px; }
.ph-cover-line {
    margin:0 0 6px; font-size:14.5px; line-height:1.5; color:#4a3a0c;
    padding-left:11px; border-left:3px solid #c98a2f;
}
.ph-cover-cap {
    margin:9px 0 3px; font-size:10.5px; font-weight:800; letter-spacing:.07em;
    text-transform:uppercase; color:#96792a;
}
/* 🛑 CAPPED AND SCROLLED. Every row open at once would make the bar tall enough to push Hang up off
   a short screen — the exact trap this bar's own comment records twice. The cap is what lets the
   list grow with however many push-backs he authors. */
.ph-cover-pushbacks { max-height:190px; overflow-y:auto; }
.ph-cover-push { border-bottom:1px solid #f1e6cd; }
.ph-cover-push:last-of-type { border-bottom:0; }
.ph-cover-push > summary {
    display:flex; align-items:center; gap:6px; padding:5px 0; cursor:pointer; list-style:none; min-width:0;
}
.ph-cover-push > summary::-webkit-details-marker { display:none; }
.ph-cover-push > summary::before { content:'▸'; color:#8a6d1a; font-size:10px; flex:0 0 auto; }
.ph-cover-push[open] > summary::before { content:'▾'; }
.ph-cover-push-name {
    min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
    font-size:11.5px; font-weight:800; color:#8a6d1f; text-transform:uppercase; letter-spacing:.05em;
}
.ph-cover-push-line { margin:0 0 7px; padding-left:16px; font-size:13px; line-height:1.55; color:#4a3a0c; }

.ph-cover-msg { margin-top:9px; display:flex; flex-direction:column; gap:6px; max-width:560px; }
.ph-cover-msg[hidden] { display:none; }
.ph-cover-msg-lbl { font-size:12px; font-weight:700; color:#8a6d1a; }
.ph-cover-msg textarea {
    width:100%; min-height:64px; border:1px solid #e3d4a8; border-radius:9px;
    padding:7px 10px; font:inherit; font-size:13px; color:var(--ink); background:#fff; resize:vertical;
}
.ph-cover-msg-said { font-size:12.5px; font-weight:700; color:#2e7d4f; }
.ph-cover-msg-said[hidden] { display:none; }
@media (max-width:700px) {
    .ph-cover-name { font-size:20px; }
    .ph-cover-pushbacks { max-height:150px; }
}

.ph-who { font-weight:800; font-size:13.5px; color:#fff; white-space:nowrap; }
.ph-who .ext { color:#b6c4ea; font-weight:700; }

/* ❓ THE SOUNDS DRAWER (Gil 2026-08-15: "maybe a ? somewhere that explains dialer things… maybe on
   Call bar"). Quiet by construction — a small mark on a bar that is otherwise all live-call
   controls, so it never competes with Take or Hang up for a rep's eye mid-call. */
.ph-help { position:relative; display:inline-flex; align-items:center; }
.ph-help-btn {
    width:19px; height:19px; border-radius:50%;
    border:1px solid rgba(182,196,234,.5); background:transparent; color:#b6c4ea;
    font:700 12px/1 system-ui,sans-serif; cursor:pointer; padding:0;
}
.ph-help-btn:hover, .ph-help-btn:focus-visible { background:rgba(182,196,234,.18); color:#fff; }
.ph-help-panel {
    position:absolute; top:calc(100% + 9px); left:50%; transform:translateX(-50%);
    z-index:var(--z-toast);
    width:min(330px, calc(100vw - 32px));
    background:#fff; color:#1d2330;
    border:1px solid #c9d2e4; border-radius:11px;
    padding:13px 15px; box-shadow:0 14px 34px -14px rgba(15,25,50,.45);
    display:flex; flex-direction:column; gap:9px;
    /* 🛑 The panel is a normal-weight reading surface even though the bar it hangs off is bold
       white-on-navy — inheriting the bar's type here made the first draft unreadable at 13.5px. */
    font:400 13px/1.45 system-ui,sans-serif; white-space:normal; text-align:left;
}
.ph-help-panel[hidden] { display:none; }
.ph-help-hd { font-weight:700; font-size:13px; color:#1d2330; }
.ph-help-note { margin:0; color:#5b6577; font-size:12px; }
.ph-help-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:7px; }
.ph-help-list li { display:grid; grid-template-columns:auto 1fr; grid-template-areas:"play what" "play how"; gap:0 9px; align-items:center; }
.ph-help-play {
    grid-area:play;
    width:24px; height:24px; border-radius:50%;
    border:1px solid #c9d2e4; background:#f3f6fc; color:#2a3550;
    font:700 10px/1 system-ui,sans-serif; cursor:pointer; padding:0;
}
.ph-help-play:hover, .ph-help-play:focus-visible { background:#e3eaf8; }
.ph-help-what { grid-area:what; font-weight:650; color:#1d2330; }
.ph-help-how  { grid-area:how; color:#6b7488; font-size:12px; }
/* "Good to know" — the behaviours a rep operates, under the sound legend. Tighter leading than the
   list above because these are sentences rather than labels, and the drawer opens mid-shift. */
.ph-help-notes { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.ph-help-notes li { color:#4a5364; font-size:12.5px; line-height:1.4; padding-left:11px; position:relative; }
.ph-help-notes li::before { content:"·"; position:absolute; left:2px; color:#9aa4b5; font-weight:700; }
.ph-help-notes strong { color:#1d2330; font-weight:650; }
.ph-help-notes em { font-style:normal; font-weight:650; color:#2a3550; }
/* 🛑 WHAT KIND OF CALL THIS IS, ON THE BAR (owner 2026-08-04) — the canonical home for the arrival
   badge. Colour comes from the shared `.ct-` vocabulary, applied as a second class by the poll, so
   this rule owns only the SHAPE and never a call type's colour.

   🛑 THE SLOT IS ALWAYS THERE AND HIDES BY `visibility`. `display:none` would collapse it and the
   bar would change width — and this bar is sticky at the top of every authed page, so every row
   underneath moves when a call connects. The codebase already pays for this rule once, in the
   live-call colour's "geometry reserved in transparent ink".

   🛑 FIXED WIDTH + ELLIPSIS, not `max-content`. "Transfer from a colleague" is more than twice the
   width of "Hot Lead Call In", so a slot that sized to its content would resize the bar between one
   call and the next — the same reflow by a slower route. No `z-index` here at all: `--z-callbar` is
   a ceiling and this element lives inside the bar's own stacking context.

   ⚠️ CORRECTION (2026-08-04) — THE PARAGRAPH ABOVE DESCRIBED AN INTENT THIS RULE DID NOT DELIVER,
   and it said so in three places (here, `phone-strip.blade.php`'s "geometry reserved in transparent
   ink", and `pop-watcher.blade.php`'s "`hidden` toggles VISIBILITY in CSS, not display"). All three
   were wrong for the same reason: **`visibility` and `display` are DIFFERENT PROPERTIES, so an
   author `visibility:hidden` never overrides the UA stylesheet's `[hidden] { display:none }`.** The
   slot collapsed on every hidden state. Measured against this file at a 1280 viewport, with the
   real markup: the summary moved **146px sideways** and the bar grew **7.5px taller** the moment a
   call connected — on a `position:sticky` bar, so every row of every authed page jumped with it.
   That is the exact harm the note above says the rule prevents.

   🛑 SO THE RESERVATION TAKES THREE DECLARATIONS, NOT ONE, and dropping any of them silently
   restores part of the jump:
     · `display:block` on `[hidden]` — puts the box back on the flex line (132px of it). Without
       it `visibility` has nothing to act on. `block` is what a flex item's `inline` computes to.
     · a `::after` NBSP strut on `[hidden]` — an EMPTY block box is 0px tall no matter its
       line-height, so the slot came back at full width and still 7.5px short. The JS clears the
       badge with `textContent = ''`, so the empty state is the normal state, not an edge case.
     · `line-height:15px` on the base — the visible label carries an EMOJI, whose inline box is
       taller than the NBSP's. Without a fixed line-height that difference alone left a 2px jump.
   ⚠️ Verify with GEOMETRY, never by eye: hidden and shown must report the same `getBoundingClientRect()`
   height, and the element after the badge the same `left`. `Ops/TheCallBarReservesItsGeometryTest`
   pins all three declarations so a tidy-up cannot quietly delete one. */
.ph-arrival {
    flex:0 0 auto; width:132px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    padding:4px 10px; border-radius:999px; font-size:11.5px; font-weight:800; letter-spacing:.02em;
    line-height:15px;
    background:rgba(255,255,255,.16); color:#fff; text-align:center;
}
.ph-arrival[hidden] { display:block; visibility:hidden; }
.ph-arrival[hidden]::after { content:"\00a0"; }
/* The call-type colours, reused verbatim from `.call-type-strip` — one vocabulary, not a second set
   tuned for navy. They sit on a brand-navy bar, which every one of them already contrasts against. */
.ph-arrival.ct-hot { background:#e8442a; }
.ph-arrival.ct-inbound { background:#2f6fd6; }
.ph-arrival.ct-callback { background:#e8a23d; color:#3a2703; }
.ph-arrival.ct-outbound { background:#1e9e5a; }
.ph-arrival.ct-internal { background:#4a5872; }

/* 🛑 WHO IS ON THE LINE (owner 2026-08-05: *"Can the lead also show the persons name instead of
   number on call bar. if no customer, say unknown. easier to identify its probably a shit call,
   lol."*). It sits immediately after the arrival badge because the two are one statement — WHAT
   kind of call, then WHO — and it is a second reserved box on the same sticky bar, so every rule
   above applies to it unchanged. The words come from `CallerHeadline` via the poll, never typed here.

   🛑 TWO LINES, AND THAT SHAPE IS WHAT LETS BOTH FACTS SURVIVE. His word is the NAME; the number
   still has to be readable, because a rep reads it back or dials it, and for a stranger — just over
   half his inbound — it is the only handle we hold. Side by side they need ~157px on a bar that has
   none to spare; stacked they need 132px and, measured against this file with the real markup, the
   bar stays 53.0px at a 1280 viewport because `.ph-xfer` next door is already 39px tall. A tooltip
   was the alternative and was rejected: a number a rep must hover to find is one they will not use.

   🛑 THE STRUTS ARE PER LINE, NOT PER BOX, AND THAT IS THE ONE THING THE BADGE DID NOT HAVE TO
   LEARN. The poll clears the name and the number INDEPENDENTLY with `textContent = ''`, and an
   empty box is 0px tall however its line-height is set — so a single strut on the wrapper would
   still let the slot lose a line the instant a call ended. `:empty::after` puts an NBSP in whichever
   line is blank. Together with `display:block` + `visibility:hidden` on the wrapper that is FOUR
   declarations holding one slot still; drop any one and part of the jump comes back silently.
   `Ops/TheCallBarReservesItsGeometryTest` pins each of them.

   ⚠️ FIXED WIDTH, ELLIPSIS PER LINE. "Constance Whitfield" is 120px at this weight and "Unknown" is
   58px; a slot that sized to its content would resize a sticky bar between one call and the next,
   which is the reflow this file has now paid for twice. A long surname truncates with an ellipsis
   ON PURPOSE — half a surname with no ellipsis is what gets typed onto a 1003 — and the full name
   and number both stay in the element's `title`. No z-index: `--z-callbar` is the ceiling. */
.ph-caller {
    flex:0 0 auto; width:132px; overflow:hidden; min-width:0;
}
.ph-caller[hidden] { display:block; visibility:hidden; }
/* 📄 START A FILE — the pop card's one survivor (owner 2026-08-06, *"who's calling is supposed to
   go in phone bar only now"*). 🛑 IT HIDES BY `display:none`, DELIBERATELY UNLIKE ITS NEIGHBOURS:
   `.ph-arrival` and `.ph-caller` reserve their geometry in transparent ink because they appear on
   EVERY call and a bar that grows would shift every page under it. This appears on an unmatched
   caller only — 20 presses in 7 days — so reserving 88px of a sticky bar all day to spare a reflow
   on a few calls is the wrong trade. It sits in the flex row's normal flow and pushes nothing when
   absent. No z-index: `--z-callbar` is the ceiling for everything in this bar. */
.ph-newfile { flex:0 0 auto; margin:0; }
.ph-newfile[hidden] { display:none; }
.ph-newfile .btn { padding:3px 9px; font-size:11.5px; }

/* 🔎 FIND THEIR FILE — the caller SEARCH, restored to the bar 2026-08-07 on the owner's ruling:
   *"on call ins that are unknown callers, we need to have a search in there too. because a client
   could be calling from a different number… pull up the other lead and add the number they called
   from to the file so it is tied to that one know too."*

   🪦 ITS PREDECESSOR SAT ON THE POP CARD AND WAS MEASURED OUT ON 08-06 — 0 presses in 19 days over
   266 inbound pops. That figure stands and is not being argued with; it measured a door on a
   surface he deleted, where attaching bought exactly one call and the same customer rang in Unknown
   again the next day. On the bar, and now that attaching FILES THE NUMBER onto the lead, it is a
   different control with a different payoff. Do not delete this on the strength of that number.

   🛑 IT RESERVES NO GEOMETRY, DELIBERATELY UNLIKE `.ph-arrival` / `.ph-caller` AND EXACTLY LIKE
   `.ph-newfile` ABOVE. Those two hold their width in transparent ink because they appear on EVERY
   call and this bar is `position:sticky` over every authed page, so a bar that grows shifts every
   row beneath it. This one is offered only for an unmatched INBOUND caller, so paying ~170px of a
   sticky bar all day to spare a reflow on those calls is the wrong trade — the same reasoning, the
   same conclusion, and it arrives and leaves on the same poll tick as Start a file.

   🛑 A BUTTON ON THE BAR, THE TYPING BOX IN THE PANEL — MEASURED, NOT PREFERRED. The first build
   put a 170px input on the bar; driven at 1280 with a live unknown caller the strip went
   **58px → 99px** and `.ph-actions`, which holds HANG UP, dropped onto a second row. A width probe
   beside `Start a file` found the slack at about **110px** before the row breaks (`.ph-actions` is
   1012px of the 1048px bar), so a typing box can never live out here. It also has no business here
   on looks: `.ph-xfer-panel` below records that form controls inherit the platform's light chrome
   and are unreadable on this blue. The bar pays for one `Start a file`-sized button; everything
   else rides the light card. */
.ph-find { flex:0 0 auto; position:relative; display:inline-flex; }
.ph-find[hidden] { display:none; }
.ph-find > .btn.small { padding:3px 9px; font-size:11.5px; }
/* 🛑 `input[type]` IS LOAD-BEARING, NOT DECORATION. The shared typed-field box at the top of this
   file lists `input[type=search]` and is (0,1,1); a bare `.ph-find-in` is (0,1,0) and LOSES — the
   fat 11px form box would draw itself here and nothing would be red anywhere. Same idiom as
   `.field-row input[type]` and `input[type].gcs-input`. */
input[type].ph-find-in {
    width:100%; padding:7px 9px; font-size:13.5px; font-weight:600;
    border:1px solid var(--line); border-radius:8px; background:#fff; color:var(--ink);
}
input[type].ph-find-in::placeholder { color:#98a0b3; font-weight:600; }
input[type].ph-find-in:focus { border-color:var(--brand); box-shadow:0 0 0 4px var(--ring); outline:none; }
.ph-find-lbl { font-size:12px; font-weight:800; color:var(--mut); text-transform:uppercase; letter-spacing:.04em; }
/* The panel — a LIGHT card dropped out of the dark strip, the same move (and the same reasons)
   `.ph-xfer-panel` documents: a typing box and a list of customer names are both unreadable on the
   strip's blue.

   🛑 THE LAYER IS A TOKEN AND IT MUST STAY A DROP, NOT A FLOAT. `--z-callbar-panel` is the label for
   a dropdown living INSIDE the bar's own stacking context — `position:sticky` opens that context
   always, so no number written here can push this below Hang up. What keeps Hang up reachable is
   GEOMETRY: this hangs below the control, clear of the bar's own row. Never hand-pick an integer
   here, and never let this panel grow upward or sideways over the strip. Proved with
   `document.elementFromPoint` at the centre of Hang up, never by eye. */
.ph-find-panel {
    position:absolute; top:calc(100% + 8px); left:0; z-index:var(--z-callbar-panel);
    display:flex; flex-direction:column; gap:6px; width:290px; max-width:82vw; padding:10px;
    margin:0; background:#fff; border:1px solid var(--line); border-radius:12px;
    box-shadow:0 12px 32px rgba(0,0,0,.22);
    max-height:min(52vh,340px); overflow-y:auto;
}
.ph-find-panel[hidden] { display:none; }
.ph-find-note { margin:0; font-size:11.5px; font-weight:700; color:var(--mut); line-height:1.4; }
.ph-find-rows { display:flex; flex-direction:column; gap:4px; }
/* A row is a real submit button carrying its own `lead_id` — see the partial. Left-aligned and
   stacked because the NAME is what a rep scans and the number is the confirmation under it. */
.ph-find-row {
    display:flex; flex-direction:column; align-items:flex-start; gap:1px;
    width:100%; padding:6px 9px; text-align:left; cursor:pointer;
    background:#fff; border:1px solid var(--line); border-radius:9px; color:var(--ink);
    font:inherit;
}
/* `--wash`, not an invented `--tint`: an UNDEFINED custom property makes the declaration invalid at
   computed-value time and the row silently keeps its white — the same silent-failure family as a
   class no stylesheet defines. Checked against `:root` before it shipped. */
.ph-find-row:hover, .ph-find-row:focus-visible { background:var(--wash); border-color:var(--brand); outline:none; }
.ph-find-row-n { font-size:13px; font-weight:800; line-height:1.25; }
.ph-find-row-m { font-size:11.5px; font-weight:600; color:var(--mut); line-height:1.25; }
/* The property, his 2026-08-07 ask. Same muted rank as the phone beside it — the NAME is what a rep
   scans for; this is what they confirm once they have a candidate. It wraps rather than truncating:
   a clipped street number is worse than a two-line row, and the panel already scrolls. */
.ph-find-row-a { font-size:11.5px; font-weight:600; color:var(--mut); line-height:1.3;
    overflow-wrap:anywhere; text-align:left; }
.ph-caller-name {
    display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    font-size:12.5px; font-weight:800; line-height:15px; color:#fff;
}
.ph-caller-num {
    display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    font-size:10.5px; font-weight:700; line-height:13px; color:#b6c4ea; letter-spacing:.01em;
}
/* 🛑 A STRANGER READS DIFFERENTLY AT A GLANCE — the whole point of his ask. A customer we hold is
   white and solid; "Unknown" is the same muted blue as the rest of the bar's secondary chrome, so
   the difference is legible before a single word is read. Driven by a CLASS the server sets from
   `known`, never by the client comparing the painted string against "Unknown". */
.ph-caller.is-unknown .ph-caller-name { color:#b6c4ea; font-weight:700; }
.ph-caller-name:empty::after { content:"\00a0"; }
.ph-caller-num:empty::after { content:"\00a0"; }

.ph-summary { color:#b6c4ea; font-size:12.5px; display:flex; flex-wrap:wrap; gap:2px 8px; align-items:baseline; min-width:0; }
.ph-summary b { color:#fff; }
/* The callback count is a link to the console (2026-08-01). No standing underline: nothing else on
   the strip carries one, and a permanently underlined number in the chrome reads as a stray default
   rather than an affordance. It arrives on hover, the way the rest of the app treats chrome links. */
.ph-summary a { color:#dfe7fa; text-decoration:none; }
.ph-summary a:hover { text-decoration:underline; }
/* BACK TO CALL (owner 2026-07-22) — the one bright affordance on the resting strip, because it is
   the way back to a LIVE customer the rep stepped away from. Amber, not the callbar blue it sits on,
   so it reads as "something is waiting for you". It sits between the summary and the right-aligned
   actions (which keep their own margin-left:auto). A gentle pulse the first two beats draws the eye
   once without nagging; reduced-motion users get a static chip. */
.ph-backtocall {
    background:#f0a921; color:#25180a; text-decoration:none; white-space:nowrap;
    padding:5px 13px; font-size:12.5px; border-radius:9px; font-weight:800;
    animation:ph-backpulse 1s ease-in-out 2; }
.ph-backtocall:hover { background:#ffc247; }
@keyframes ph-backpulse { 0%,100% { box-shadow:0 0 0 0 rgba(240,169,33,.0); }
    50% { box-shadow:0 0 0 4px rgba(240,169,33,.45); } }
@media (prefers-reduced-motion:reduce) { .ph-backtocall { animation:none; } }
/* ✕ PUT IT AWAY (owner 2026-07-27) — a joined-on end cap, deliberately darker and smaller than the
   pill it hangs off: the way BACK is still the loud half, this is the quiet way to make it go. Its
   own square inner corner + the pill's flattened right corner read the two as one control. */
.ph-backtocall-wrap { display:inline-flex; align-items:stretch; }
.ph-backtocall-wrap .ph-backtocall { border-radius:9px 0 0 9px; }
.ph-backtocall-x { display:inline-flex; margin:0; }
.ph-backtocall-x button {
    border:0; border-left:1px solid rgba(37,24,10,.28); cursor:pointer; background:#d9950f;
    color:#25180a; font-size:11.5px; font-weight:800; line-height:1; padding:0 9px;
    border-radius:0 9px 9px 0; }
.ph-backtocall-x button:hover { background:#c08000; }
.ph-actions { display:flex; gap:7px; align-items:center; margin-left:auto; flex-wrap:wrap; }
/* ⚖️ GIL'S ROW, PINNED BY `order` RATHER THAN BY DOM POSITION — 2026-08-28: *"from right to left:
   Ready, Phone, Dialer and Dial"*, then *"Lighting bolt needs to be moved next to Phone Icon"*.

   🛑 DOM ORDER ALONE COULD NOT DELIVER IT, AND THAT IS THE POINT OF DOING IT HERE. Between the
   dialer chip and the phone door sit five siblings — two ring clusters, the consult chip, the
   live-call bar and a notify slot — every one of which is hidden in the resting state and PRESENT in
   the markup. Any one of them becoming visible, or reserving width, drives the two chips apart, and
   the row he asked for silently stops being the row he asked for. `order` states the intent
   directly: these three end the row, in this sequence, whatever appears between them.

   Everything unnamed keeps the default 0 and therefore sits to the LEFT of all three — which is
   correct for the ring and call clusters: while a call is up they belong beside the customer, not
   wedged between the dialer and the phone. ⚠️ It also keeps Hang up away from the phone door, which
   is the property this bar has twice been hurt by losing. */
/* ⚖️ REVISED 2026-08-28 — *"move phone to the right of ready/Off button"*. His first order put the
   phone between the dialer and the state chip; having used it for an hour he moved it to the very
   end. Dialer, then Ready/Off, then Phone.
   🛑 The property that survives every reshuffle: everything unnamed keeps order 0, so the live-call
   controls — Hang up first among them — stay LEFT of all three and can never end up beside the phone
   door. That is the rule this bar has twice been hurt by losing; the sequence of these three is his. */
.ph-actions .ph-autodial     { order:1; }
.ph-actions .ph-off,
.ph-actions [data-ph-state-wrap] { order:2; }
.ph-actions .ph-dash-open    { order:3; }
/* Compact controls sized for a narrow bar — the strip overrides the shared .btn scale LOCALLY
   (scoped to .ph-strip; the shared button contract is untouched everywhere else).
   Owner round 5: ONE primary — "● Ready to connect" (the old pill's words as THE go-on button);
   Auto dial is MUTED while off and .ph-on GREEN when the outbound campaign runs (never red —
   off isn't broken; the compliance-alarm red stays reserved for findings). */
.ph-actions .btn { padding:5px 13px; font-size:12.5px; border-radius:9px; box-shadow:none; border:0; cursor:pointer; font-weight:800; }
.ph-actions form { display:inline-flex; margin:0; }
/* 🛑 THREE LOOKS, THREE STATES (owner 2026-07-20, reversing round 9's "off == paused").
   `.ph-ready` (solid green + FILLED dot) = live and taking calls — round 8's rule, born because a
   filled green dot on the launch button made the owner believe he was on with no session open.
   `.ph-paused` (amber + pause bars) = logged in but paused. `.ph-off` (grey + a hollow ring) =
   logged OUT. Round 9 rendered off and paused identically, and the owner read the amber "Paused"
   as "still on" and hung up on held calls twice — so OFF now looks distinct. Green still belongs
   to the live state alone; the two not-live looks are told apart, not merged. */
.ph-actions .btn.ph-ready { display:inline-flex; align-items:center; gap:7px; background:#1e9e5a; color:#fff; }
.ph-actions .btn.ph-ready:hover { background:#1a8b4f; }
.ph-actions .btn.ph-ready .dot { width:8px; height:8px; border-radius:999px; background:#fff; box-shadow:0 0 0 3px rgba(255,255,255,.25); }
.ph-actions .btn.ph-paused { display:inline-flex; align-items:center; gap:7px; background:#e8a23d; color:#3a2703; }
.ph-actions .btn.ph-paused:hover { background:#f0ad4a; }
/* ☎️ THE RESTING FACE (owner 2026-08-06, the flip) — wears `.ph-ready`'s own green and the word
   Ready, BY HIS RULING (*"just let it say ready, and ready just means they are on that way"*).
   A distinct teal was built first, reasoning that box-ready (bridges with no ring) and resting
   (rings first) must never share a colour; he ruled the rep's vocabulary wins over the box's.
   `.ph-resting` stays as the seam so the resting face can diverge again in one place if ever
   needed — today it adds nothing on purpose. */
/* OFF — a muted grey chip, clearly clickable (unlike the not-allowed .ph-muted) but unmistakably
   NOT the amber paused or the green ready. Its hollow ring reads as "power off". */
.ph-actions .btn.ph-off { display:inline-flex; align-items:center; gap:7px; background:rgba(255,255,255,.15); color:#e3eaf8; }
.ph-actions .btn.ph-off:hover { background:rgba(255,255,255,.24); }
.ph-icon-off { display:inline-block; width:9px; height:9px; border-radius:50%; border:2px solid currentColor; }
/* The pause mark: two bars drawn in currentColor — crisper and more consistent than the ⏸ emoji,
   which renders in colour on some platforms and would fight the button's own palette. */
.ph-hint { font-size:12px; font-weight:700; color:#ffe9c2; white-space:nowrap; }
.ph-hint[hidden] { display:none; }
.ph-icon-pause { display:inline-block; position:relative; width:9px; height:10px; }
.ph-icon-pause::before, .ph-icon-pause::after { content:''; position:absolute; top:0; width:3px; height:10px; border-radius:1px; background:currentColor; }
.ph-icon-pause::before { left:0; }
.ph-icon-pause::after { right:0; }
/* ---- THE CALL BAR (owner 2026-07-20) ----
   The strip's live-call state: the agent drives the call from here instead of the VICIdial screen.
   It replaces round 7's flat chip, so this slot is now a flex ROW of controls, not one button.
   Destructive-vs-safe is carried by colour: ending or dropping a leg is red, everything else is the
   strip's translucent white. `.is-busy` dims the whole row while a command is in flight — these act
   on a live customer and a double press is a second real command to the box. */
.ph-callbar { display:inline-flex; align-items:center; gap:8px; position:relative; }
/* THE STRIP LAYOUT, round 3 (owner 2026-07-24: "Off button needs to be on the right side too" +
   "Move mailbox to left of Extension 1001"). The 📬 is now the FIRST child of the strip, sitting
   left of the extension; the Ready/Off group + ⚡ ride the RIGHT edge (justify-content:flex-end on
   the flex:1 actions); the call cluster stays a CONTAINED darker pill centered in the free space
   (its own auto margins beat justify-content, so in-call it still centers). */
.ph-actions { flex:1; display:flex; align-items:center; justify-content:flex-end; gap:8px; }
.ph-actions .ph-callbar {
    margin:0 auto; background:rgba(9,13,32,.34); border-radius:999px; padding:4px 8px;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.14);
}
.ph-actions .ph-callbar .btn { padding:5px 11px; font-size:12.5px; }
/* 📬 rides the far LEFT now (first child of .ph-strip). Its dropdown must open RIGHTWARD from the
   left edge — the shared .ph-xfer-panel anchors right:0, which off the left edge would push a
   340px panel off-screen. */
.ph-vm-wrap { margin-right:6px; }
.ph-vm-panel { left:0; right:auto; }
.ph-autodial {
    display:inline-flex; align-items:center; gap:5px; font-size:15px; text-decoration:none;
    color:#8fd3a8; filter:drop-shadow(0 0 4px rgba(60,220,130,.55));
}
/* 🛑 THE WORDS COME OFF THE BAR, THE MEANING DOES NOT (⚖️ Gil 2026-08-28: *"Yes do icon"*, asked of
   the widest thing left on the strip). `⚡ Join auto dialer` was ~130px of a row he had just called
   *"way to tall"* — more width than every other trim of that round put together. Same treatment the
   phone dashboard's door got the same minute: the label moves to `title`, so it survives a hover and
   a screen reader and costs the row nothing.
   ⚠️ THE HIT AREA MUST NOT SHRINK WITH THE LABEL. This is a live control that puts a rep onto the
   predictive dialer; a 15px lightning bolt is a target you miss. `min-width`/`justify-content` keep
   it a button-sized thing wearing one glyph. */
.ph-autodial.is-join i { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
.ph-autodial.is-join {
    min-width:34px; justify-content:center; padding:5px 8px; border-radius:9px;
    background:rgba(255,255,255,.12); box-shadow:inset 0 0 0 1px rgba(140,240,180,.35);
}
.ph-autodial.is-join:hover { background:rgba(255,255,255,.22); }
.ph-autodial i { font-style:normal; font-size:10.5px; font-weight:800; letter-spacing:.04em;
    text-transform:uppercase; color:#bfe9cd; }
/* 🛑 THE JOIN IS AN ACTION, SO IT HAS TO LOOK LIKE ONE (owner 2026-08-03, live: "there is no ready
   button, it says ok" — he could not tell the bolt's three states apart while a rep waited).
   It used to be 10.5px pale-grey lettering at opacity .85, sitting beside a full-size Ready BUTTON.
   Next to a real control that reads as available, a whisper reads as decoration.
   ⚠️ AND THE ⚡ ITSELF CARRIES NO SIGNAL: it renders as a COLOUR EMOJI, so every `color:` on this
   rule is invisible on the glyph — on-auto green, join blue and idle grey all paint the same bolt.
   The words and the chip outline are therefore the ONLY things a human can tell apart, which is why
   the join state gets a border and readable type and the other two deliberately do not. */
.ph-autodial.is-join {
    color:#dde5ff; filter:none; opacity:1;
    padding:3px 9px; border-radius:999px;
    border:1px solid rgba(201,212,242,.55); background:rgba(201,212,242,.10);
}
.ph-autodial.is-join i { color:#dde5ff; font-size:12px; }
.ph-autodial.is-join:hover { background:rgba(201,212,242,.22); border-color:rgba(220,230,255,.85); }
/* The lit state stays a quiet STATUS — it is not clickable, and dressing it as a control is how a
   rep ends up pressing the thing that is already true. */
.ph-autodial.is-idle { color:#7d89ad; filter:none; opacity:.55; cursor:default; }

/* ⚡ THE JOIN-DIDN'T-TAKE BANNER (owner 2026-08-03). AMBER, not the red of .ph-lost-banner, and
   deliberately still: the rep is not being kicked off calls, they simply are not on the dialer they
   asked for. Red plus motion is reserved for "you are missing calls right now". Shares the strip's
   full-width banner slot, so it stacks under the lost alarm rather than fighting it for the row. */
.ph-join-banner {
    flex-basis:100%; display:flex; align-items:center; gap:12px;
    padding:8px 12px; border-radius:8px; margin-top:6px;
    background:#8a5a12; color:#fff; font-size:13.5px; font-weight:600;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.25);
}
.ph-join-banner[hidden] { display:none !important; }
.ph-join-banner b { font-weight:800; }
.ph-join-banner .btn.small { background:#fff; color:#6b430b; white-space:nowrap; }
.ph-join-banner .btn.small:hover { background:#ffeed6; }
/* ⏸ ON THE AUTO DIALER BUT PAUSED — the same banner slot, a DIFFERENT problem, so a different
   colour: this rep's join WORKED and their phone is simply idle. Teal-slate rather than the amber
   of "the dialer turned you down", because the fix is one press away and sits in the banner. */
.ph-join-banner.is-paused-on-auto { background:#1f5f6b; }
.ph-join-banner.is-paused-on-auto .btn.small { color:#12454f; }
.ph-join-banner.is-paused-on-auto .btn.small:hover { background:#ddf3f7; }
/* "Not now" is SECONDARY — going Ready is the action that fixes something; this only stops the
   nudge. A hollow chip on the teal, same shape as .ph-lost-ack on the red. */
.ph-join-banner .btn.small.ph-join-ack {
    background:transparent; color:#fff; border:1px solid rgba(255,255,255,.55); }
.ph-join-banner .btn.small.ph-join-ack:hover { background:rgba(255,255,255,.16); }
.ph-actions .btn.ph-dialpad-btn { background:rgba(255,255,255,.14); color:#fff; }
.ph-actions .btn.ph-dialpad-btn:hover { background:rgba(255,255,255,.26); }
/* THE STATE TRIMS (owner 2026-07-24, live: the transfer moment showed every button at once and he
   pressed the wrong one). While a TRANSFER LEG is live: only the two xfer buttons + status. While
   ON HOLD: Hang up + Resume + status — you cannot mute or transfer a customer who is on music. */
.ph-callbar.is-xfer .ph-hangup, .ph-callbar.is-xfer .ph-hold, .ph-callbar.is-xfer .ph-unhold,
.ph-callbar.is-xfer .ph-mute, .ph-callbar.is-xfer .ph-unmute,
.ph-callbar.is-xfer [data-cb-xfer], .ph-callbar.is-xfer [data-cb-dispo-wrap] { display:none !important; }
.ph-callbar.is-hold .ph-mute, .ph-callbar.is-hold .ph-unmute,
.ph-callbar.is-hold [data-cb-xfer] { display:none !important; }
.ph-callbar.is-busy { opacity:.55; pointer-events:none; }

/* ☎ THE TRANSFER RING (doctrine §0a, owner 2026-08-05: the accept lives in the CALL BAR, never a
   card — *"if we want all calls to be on call bar, this is way too big"*).

   🛑 IT SITS IN THE STRIP'S OWN FLOW, deliberately: nothing here is fixed, absolute or z-indexed.
   The strip is `position:sticky` and therefore its own stacking context, so a floating child could
   only ever be sealed UNDER the bar's ceiling — the trap that hid Hang up from the owner twice. A
   ring that is part of the bar cannot cover the bar.

   ⚠️ It only ever replaces the IDLE strip: a rep is not offered a transfer while they are on a
   call, so this never competes with Hang up for the rep's eye. */
.ph-ringing { display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap; min-width:0; }
.ph-ringing[hidden] { display:none; }
/* 🛑 A RINGING CALL CENTRES, EXACTLY LIKE A LIVE ONE — owner 2026-08-20: *"WHy is the call going on
   the right side and not centered in the middle of the bar?"* It was pinned to the right edge
   because `.ph-ringing` lives inside `.ph-actions` (`flex:1; justify-content:flex-end`) and, unlike
   the `.ph-callbar` pill two rules below, had never been given the auto margins that beat that
   justify-content. His own 2026-07-24 layout ruling already said the call cluster *"stays a
   CONTAINED darker pill centered in the free space"* — it reached the LIVE call and never the ring.
   ⚠️ IT CANNOT WRAP THE ROW OR ADD HEIGHT: auto margins only distribute space that is already free,
   and collapse to 0 when there is none. Nothing here changes any element's width. */
.ph-actions .ph-ringing { margin:0 auto; }
/* 🛑 A RINGING CALL GETS THE BAR TO ITSELF — ⚖️ Gil 2026-08-17: *"when calls come it gets TALL, where
   it is 1/3 of the screen size… they can be on the phone with a client and it makes it where you
   cant really see what you are working."* That complaint was answered with a measuring script which
   then only ever measured the QUIET bar, so it stayed true. Measured 08-20 by the new ring check:
   one row ONLY at 1512px and only with a short customer name — 1440 gave two rows, 1280 three,
   1024 four.
   📐 THE CULPRIT WAS EMPTY SPACE, MEASURED PER CHILD RATHER THAN GUESSED: `.ph-arrival` and
   `.ph-caller` each RESERVE 132px — 264px plus two gaps — describing a live call that does not
   exist while a customer is still ringing. Their reservation is load-bearing DURING a call (see
   their own note: collapsing it moved the summary 146px) and is pure cost BEFORE one.
   ⇒ While a ring is up and unanswered, the bar shows the ring and the controls that act on it, and
   stands down everything that describes a call nobody is on yet — the same trim `.ph-not-when-incall`
   already performs, for the same reason, one moment earlier.
   ⚠️ NOT APPLIED ONCE ACCEPTED. `is-ringing` is cleared the instant the rep presses Take, so the
   caller slots are back in place as the call lands and there is no flash of an empty bar. */
.ph-strip.is-ringing .ph-not-when-incall,
.ph-strip.is-ringing .ph-arrival,
.ph-strip.is-ringing .ph-caller,
/* …and the two controls that act on the REP rather than on this caller. Measured at 1024 (a 777px
   strip) the ring cluster alone is 679px, so these 81px plus their gaps are the difference between
   one row and three. Going off the phones can wait the ten seconds a customer is holding; the
   autodial chip is status, not a control. Both return the moment the ring clears. */
.ph-strip.is-ringing .ph-autodial,
.ph-strip.is-ringing .ph-off,
/* ☎️ …AND THE PHONE DASHBOARD'S DOOR, ADDED 2026-08-28 FOR EXACTLY THE REASON ABOVE. It arrived on
   this bar the day before and was NOT in this list, so it kept its width through a ring and pushed
   the centred cluster sideways — part of Gil's *"The take it pop up on call ins is moving around, it
   should be steady"*. It is a control that acts on the REP, not on this caller, and the dashboard
   can wait the ten seconds a customer is holding. ⚠️ Any future control added to `.ph-actions`
   belongs in this list unless it acts on the ringing caller. */
.ph-strip.is-ringing .ph-dash-open,
.ph-strip.is-ringing [data-ph-state-wrap] { display:none !important; }
/* 🛑 AND THE ACTIONS GROUP MUST SHRINK RATHER THAN WRAP WHILE A CALL IS RINGING. `.ph-actions` is
   `flex-wrap:wrap` (line ~1713), so it broke its children onto a second line instead of letting the
   customer's NAME give way — which meant the ellipsis rule on `.ph-ring-nm` could never fire and the
   bar grew anyway. Scoped to `is-ringing` so nothing about the idle or in-call bar changes.
   📐 Measured at 1024: 79px (two rows) without this, 46px (one row) with it. */
.ph-strip.is-ringing .ph-actions { flex-wrap:nowrap; min-width:0; }
/* …and the ring cluster itself, which was the container actually breaking: measured at 1024 it held
   a 540px chip + mute + a 105px Take inside 679px, so `Take the call` dropped to a second line while
   the customer's name sat there refusing to give way. `nowrap` here is what finally lets the ellipsis
   on `.ph-ring-nm` do its job. The three containers all had to agree — chip, cluster and actions —
   which is why fixing any one of them alone changed nothing. */
.ph-strip.is-ringing .ph-ringing { flex-wrap:nowrap; }
/* 🛑 EXCEPT WHILE A CALL IS LIVE — Gil asked the question that found this: *"but it wont interfere
   with anything if they are on a call on the call bar will it?"* It would have. `ringFor()` returns
   an ACCEPTED ring past the busy gate (the *Connecting…* state, up to ~8s while the box hands the
   call over, per `InboundRing`'s docblock), so on EVERY accepted inbound the strip is `incall` with
   the ring still up — two auto-centred siblings splitting one row. Measured at 1280: the live call
   pill slid **121px** left, at the moment a rep may be reaching for Hang up. Nothing overlapped and
   the height never moved; it is the MOVEMENT under a reaching hand that is the defect.
   ⇒ In `incall` and `wrapup` the ring keeps today's position and the call pill keeps the centre it
   has always had. The centring applies only where the bar is at rest. */
[data-ph-state="incall"] .ph-actions .ph-ringing,
[data-ph-state="wrapup"] .ph-actions .ph-ringing { margin:0; }
.ph-ring {
    /* 🛑 `nowrap` + `min-width:0` SO THE NAME SHRINKS INSTEAD OF THE ROW BREAKING. With `wrap` here
       flexbox had no reason to ever shrink `.ph-ring-nm`, so the ellipsis rule beside it could never
       fire and the chip simply grew a second line — which is the bar getting taller by another
       route. Measured at 1024: 79px (two rows) with wrap, 46px (one) with this. */
    display:inline-flex; align-items:center; gap:9px; flex-wrap:nowrap; min-width:0;
    background:rgba(255,255,255,.13); border:1px solid rgba(255,255,255,.28);
    border-radius:11px; padding:5px 10px;
    animation:ph-ring-pulse 1.8s ease-in-out infinite;
}
/* The colour is the CONFIRMATION, not the alarm — the sound is the alarm. Kept gentle on purpose:
   a hard flash on a bar that sits on screen all day is how reps learn to stop seeing it. */
@keyframes ph-ring-pulse {
    0%, 100% { background:rgba(255,255,255,.13); }
    50%      { background:rgba(255,217,168,.30); }
}
@media (prefers-reduced-motion: reduce) { .ph-ring { animation:none; } }
.ph-ring .ph-ring-from {
    font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.06em; color:#ffd9a8;
}
.ph-ring .ph-ring-who { font-size:13.5px; font-weight:800; color:#fff;
    display:inline-flex; align-items:baseline; gap:5px; min-width:0; }
/* 🛑 THE NAME GIVES WAY, THE NUMBER NEVER DOES. Measured 2026-08-20 at 1024px: a 28-character
   customer name beside an 18-character owner badge wrapped the bar onto a second row, against a
   ruling Gil has now made twice. Clipping the whole slot was the easy fix and the wrong one — it
   eats the phone number, which for a caller we hold no record for is the ONLY handle a rep has
   (`CallerHeadline`'s standing finding). `flex-shrink:0` on the number is the load-bearing half. */
/* 🛑 A FIXED BASIS, AND THAT IS WHAT MAKES `Take the call` HOLD STILL. ⚖️ Gil, 2026-08-28: *"The
   take it pop up on call ins is moving around, it should be steady"* — and the cause was not the
   Take button, it was this name.

   The ring cluster is centred by `margin:0 auto` (his own 08-20 ruling — *"WHy is the call going on
   the right side and not centered"*), so its width decides where everything inside it lands. With
   the name free to size itself, "Ed Ng" and "Gwendolyn Bell-Harrington" produced clusters ~150px
   apart, and Take moved by half that on every call. **A button a rep must hit in ten seconds cannot
   live at a different spot each ring.**

   `flex:0 1 150px` = never grow, may shrink. So at any normal width the slot is exactly 150px
   whatever the name, the cluster is a constant size and Take is nailed down; under real pressure it
   still gives way, which is what the narrow-width work below it depends on. The ellipsis was already
   here and now has a reason to fire on long names rather than only on cramped screens. */
.ph-ring .ph-ring-nm {
    flex:0 1 150px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.ph-ring .ph-ring-no { flex:0 0 auto; white-space:nowrap; }
/* A number we hold no file for is TOLD, never blanked — it just reads quieter than a name. */
.ph-ring .ph-ring-who.is-unknown { color:#e6ecff; font-weight:700; }
.ph-ring .ph-ring-clock {
    font-size:17px; font-weight:900; color:#ffd9a8;
    font-variant-numeric:tabular-nums; min-width:2.2ch; text-align:center;
}
.ph-actions .btn.ph-ring-take { background:#1e9e5a; color:#fff; }
.ph-actions .btn.ph-ring-take:hover { background:#1a8b4f; }
.ph-actions .btn.ph-ring-pass { background:rgba(255,255,255,.18); color:#fff; }
.ph-actions .btn.ph-ring-pass:hover { background:rgba(255,255,255,.28); }

/* The SENDING side: while a colleague's phone is ringing, the bar trims to this and the cancel —
   the same trim `.is-xfer` already does, for the same reason. Every other control is noise at the
   moment a customer is on hold waiting to find out whether anyone is taking them. */
.ph-ringout { display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap; }
.ph-ringout[hidden] { display:none; }
.ph-ringout .ph-ringout-word {
    font-size:12.5px; font-weight:800; color:#ffd9a8; font-variant-numeric:tabular-nums;
}
.ph-callbar.is-ringout .ph-hangup, .ph-callbar.is-ringout .ph-hold, .ph-callbar.is-ringout .ph-unhold,
.ph-callbar.is-ringout .ph-mute, .ph-callbar.is-ringout .ph-unmute,
.ph-callbar.is-ringout [data-cb-xfer], .ph-callbar.is-ringout [data-cb-dispo-wrap] { display:none !important; }

/* 🗣 THE PRIVATE WORD (owner: *"i want be able to talk to the rep without the client hearing, then
   transfer"*). Both reps carry the sentence permanently while it lasts — a toast would fade, and the
   one new risk this phase introduces is two people forgetting the customer is on hold. */
.ph-consult, .ph-consultout { display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap; }
.ph-consult[hidden], .ph-consultout[hidden] { display:none; }
.ph-consult-word {
    font-size:12.5px; font-weight:800; color:#bfe8d2;
    background:rgba(30,158,90,.22); border:1px solid rgba(120,220,170,.35);
    border-radius:999px; padding:4px 11px;
}
/* Same trim as a live transfer: while two reps are talking over a parked customer, every other
   control is noise. Hang up stays OUT of this list on purpose — see below. */
.ph-callbar.is-consult .ph-hold, .ph-callbar.is-consult .ph-unhold,
.ph-callbar.is-consult .ph-mute, .ph-callbar.is-consult .ph-unmute,
.ph-callbar.is-consult [data-cb-xfer], .ph-callbar.is-consult [data-cb-dispo-wrap] { display:none !important; }
/* 🛑 The consult chip's pair (Send the customer over / Keep the customer) is the ONLY pair on a
   colleague consult — the external 3-way cluster drives the same box commands through the generic
   routes and skips the handoff bookkeeping (owner, live 08-07: "this screen looks very confusing",
   with four buttons for two acts on his bar). The JS no longer arms both; this makes the ruled
   pair win even if some future path does. */
.ph-callbar.is-consult .ph-xfer-live { display:none !important; }
/* 🔔 The one-time ring-alert ask. Quiet on the bar — it is an offer, not an alarm — and it removes
   itself the moment the browser has an answer either way (owner 08-07 chose the permission over a
   flashing tab title; a chip that nags would be the thing reps Block, and a Block cannot be undone
   from code). */
/* 🔔 ICON-ONLY (owner 08-07: *"looks bad, put just a bell"*). Square, the same visual weight as the
   bar's other chips, and the words live in `title`/`aria-label` so nothing is lost to a screen reader
   or a hover. */
/* ⚖️ 28px → 22px AND OFF THE `.btn` CLASS, 2026-08-13 — *"on the left side and smaller, next to
   mail box"*. Geometry now comes from `.ph-alert-chip` (the pair's shared base, further down); the
   bell keeps only its three faces here. The old `!important` size war died with the `.btn` class:
   the chip base never carried the padding that made it necessary — a bare `.ph-notify-ask` once
   came out 77px wide with the bell floating in it, but that was `.btn`'s padding, and the bell no
   longer wears `.btn` at all. */
/* The three faces (owner 08-07: the vanishing bell told him nothing). ON is quiet confidence —
   slightly brighter, no colour shout. BLOCKED is amber because it is the one state a rep must act
   on, and it must read as "something is off" from across the room. */
/* ⚖️ GREEN = GOOD (owner 08-08: *"a green tint would be good too, shows its good"*). The same
   ready-green the Ready chip already owns, so the bar has ONE vocabulary for working. */
[data-ph-strip] .ph-notify-ask.is-on { color:#fff; background:#1e9e5a; }
[data-ph-strip] .ph-notify-ask.is-on:hover { background:#23b366; }
/* The press always says something IN the page — we can prove we asked for a notification, never
   that one appeared. Amber-on-dark for the bad news, quiet blue for the ordinary confirmation. */
.ph-notify-said { font-size:12px; font-weight:700; color:#dbe6fb; max-width:320px; line-height:1.35; }
.ph-notify-said.is-bad { color:#ffd9a0; }
.ph-notify-said[hidden] { display:none; }
/* ⚖️ QUIET, hours after the amber (08-08, his "find a resolution… optimally"). The urgency was
   right while the notification was the ONLY away-from-tab alert; the tab flash on the worker
   heartbeat is the floor now, and the notification is an optional bonus that provably cannot be
   delivered on some correctly-configured machines. An amber pulse demanding an optional and
   possibly unsatisfiable step teaches the floor to ignore amber. Faces stay distinct; volume drops. */
[data-ph-strip] .ph-notify-ask.is-ask { color:#dbe6fb; background:rgba(255,255,255,.16); }
[data-ph-strip] .ph-notify-ask.is-ask:hover { background:rgba(255,255,255,.26); }
[data-ph-strip] .ph-notify-ask.is-blocked { color:#3a2703; background:#e8a23d; }
[data-ph-strip] .ph-notify-ask.is-blocked:hover { background:#f0b054; }
.ph-notify-ico { display:inline-flex; align-items:center; }
.ph-notify-ask[hidden] { display:none; }

/* 🔔🔊 THE SETUP PAIR — far left, beside the mailbox, 22px, icon-only.
   ⚖️ Owner 2026-08-13, both halves his: *"put them on the left side and smaller, next to mail box"*
   and, on the broken state, *"no growing just make it red, and they press it to fix."* These are
   one-time permission asks, not working controls — the right edge is where Take-the-call and Hang
   up live, and a five-word chip there wrapped the bar onto two lines with a customer ringing. */
[data-ph-strip] .ph-alerts { display:inline-flex; align-items:center; gap:4px; margin-right:12px; }
[data-ph-strip] .ph-alert-chip {
    background:rgba(255,255,255,.16); color:#dbe6fb;
    display:inline-flex; align-items:center; justify-content:center;
    width:22px; min-width:22px; height:22px; padding:0; flex:0 0 auto;
    border:0; border-radius:7px; cursor:pointer; font-size:11.5px; line-height:1;
}
[data-ph-strip] .ph-alert-chip:hover { background:rgba(255,255,255,.26); }
[data-ph-strip] .ph-alert-chip[hidden] { display:none; }
/* 🛑 BROKEN IS RED AND PRESSABLE — his ruling, verbatim: "no growing just make it red, and they
   press it to fix." The MUTED error red (#a12626), never the reserved --danger compliance
   treatment: rule 13 bars the compliance alarm from live-call surfaces and that ruling stands;
   this is broken-wants-a-human, the same grammar as rule 13a, wearing the error family the system
   already owns. The sentence the icon cannot carry is in the title — hover explains, red summons. */
[data-ph-strip] .ph-alert-chip.is-broken { background:#a12626; color:#ffe2e2; }
[data-ph-strip] .ph-alert-chip.is-broken:hover { background:#b73030; }
/* ⚖️ SET IS A FACE, NOT A DISAPPEARANCE (owner 2026-08-13: *"so they are pressing it and the icon
   disappears, is that what is supposed to happen?"*). It used to hide, which gave the rep no
   confirmation AND — the real trap — removed the only control that could re-pick if the machine's
   speaker had been guessed wrong. Green is the bar's existing "this is working" (the same
   ready-green the bell's `.is-on` and the Ready chip wear); the tooltip names the speaker. */
[data-ph-strip] .ph-alert-chip.is-set { background:#1e9e5a; color:#fff; }
[data-ph-strip] .ph-alert-chip.is-set:hover { background:#23b366; }

/* 🔇 MUTE THIS CALLER — owner 2026-08-13 *"the mute option for the current call, not all calls"*.
   Sits with Take on the ring chip and stays quiet: it is the option a rep uses when they CANNOT
   answer, so it must never compete with the button that answers. No colour, no pulse. */
[data-ph-strip] .ph-inring-mute { flex:0 0 auto; padding:5px 8px; font-size:12px; line-height:1; }
/* 🛑 THE TRANSFER CHIPS HOLD ONE LINE (owner 08-07: the sentence wrapped and pushed his buttons into
   a blob; he approved the one-row render — *"I loke the transfer chips"*). The words are his; only
   the NAME is shortened, by the server, with the full name on hover. `nowrap` is the guard — without
   it a long name or a narrow window silently reintroduces the wrap this exists to kill. */
.ph-consult-word, .ph-ringout .ph-ringout-word { white-space:nowrap; }
.ph-consult, .ph-consultout, .ph-ringout { flex-wrap:nowrap; }
.ph-ringout .ph-ring-clock[hidden] { display:none; }
/* 🛑 HANG UP SURVIVES THE CONSULT, DELIBERATELY. The owner has twice been trapped on a live call with
   no way to end it, and during a private word there is a real customer parked on this rep's line. The
   two consult buttons are the tidy exits; Hang up is the one that always works. */
.ph-actions .ph-callbar .btn { white-space:nowrap; }
.ph-actions .ph-callbar .btn.ph-hangup, .ph-actions .ph-callbar .btn.ph-pullback { background:#d2402a; color:#fff; }
.ph-actions .ph-callbar .btn.ph-hangup:hover, .ph-actions .ph-callbar .btn.ph-pullback:hover { background:#e04a33; }
.ph-actions .ph-callbar .btn.ph-xfer-btn { background:rgba(255,255,255,.18); color:#fff; }
.ph-actions .ph-callbar .btn.ph-xfer-btn:hover { background:rgba(255,255,255,.28); }
.ph-actions .ph-callbar .btn.ph-complete { background:#1e9e5a; color:#fff; }
.ph-actions .ph-callbar .btn.ph-complete:hover { background:#1a8b4f; }
/* HOLD (owner 2026-07-24). Two faces, one visible: the bar's `is-hold` latch swaps Hold for the
   amber Resume — amber on purpose, the strip's "paused" colour, because a parked customer is the
   call equivalent of paused and the rep must FEEL that someone is waiting on music. */
.ph-actions .ph-callbar .btn.ph-hold { background:rgba(255,255,255,.18); color:#fff; }
.ph-actions .ph-callbar .btn.ph-hold:hover { background:rgba(255,255,255,.28); }
.ph-actions .ph-callbar .btn.ph-unhold { display:none; background:#e8a33d; color:#212b38; }
.ph-actions .ph-callbar .btn.ph-unhold:hover { background:#f2b04a; }
.ph-callbar.is-hold .btn.ph-hold { display:none; }
.ph-callbar.is-hold .btn.ph-unhold { display:inline-flex; }
/* MUTE (owner-approved box patch 2026-07-24). Muted face is RED, not amber — a dead mic on a live
   customer is closer to danger than to waiting, and it must not be confusable with On hold. */
.ph-actions .ph-callbar .btn.ph-mute { background:rgba(255,255,255,.18); color:#fff; }
.ph-actions .ph-callbar .btn.ph-mute:hover { background:rgba(255,255,255,.28); }
.ph-actions .ph-callbar .btn.ph-unmute { display:none; background:#b3261e; color:#fff; }
.ph-actions .ph-callbar .btn.ph-unmute:hover { background:#c93a31; }
.ph-callbar.is-muted .btn.ph-mute { display:none; }
.ph-callbar.is-muted .btn.ph-unmute { display:inline-flex; }
.ph-xfer-live { display:inline-flex; align-items:center; gap:8px; }
.ph-xfer-live[hidden] { display:none; }

/* The transfer panel — a LIGHT card dropped out of the dark strip, because it holds a select and a
   text input: form controls inherit the platform's own light chrome and are unreadable on the
   strip's blue. Anchored to the strip, above the page (the strip itself is sticky). */
.ph-xfer { position:relative; display:inline-flex; }
.ph-xfer-panel {
    position:absolute; top:calc(100% + 8px); right:0; z-index:var(--z-callbar-panel);
    display:flex; flex-direction:column; gap:8px; width:260px; padding:12px;
    background:#fff; border:1px solid var(--line); border-radius:12px;
    box-shadow:0 12px 32px rgba(0,0,0,.22);
}
.ph-xfer-panel[hidden] { display:none; }
.ph-actions .ph-callbar .btn.ph-finish { background:rgba(255,255,255,.18); color:#fff; }
.ph-actions .ph-callbar .btn.ph-finish:hover { background:rgba(255,255,255,.28); }
.ph-xfer-hd { font-size:12px; font-weight:800; color:var(--mut); text-transform:uppercase; letter-spacing:.04em; }
.ph-xfer-row { display:flex; flex-direction:column; gap:4px; margin:0; }
.ph-xfer-row span { font-size:12px; font-weight:700; color:var(--mut); }
/* The capsule's note hint sits between the label and the box, so it must NOT wear the label's
   weight — `.ph-xfer-row span` above styles every span in the row as a field label. Owner
   2026-08-17: the box asks the rep what happened "so you remember". */
.ph-xfer-row .ph-note-hint { font-size:11.5px; font-weight:500; line-height:1.4; color:var(--mut); margin:-2px 0 2px; }
/* 💬 The one line under the capsule's "…and tell" picker (⚖️ Gil 2026-09-01) — a rep picking a name
   needs to know they are writing to a colleague's panel and not sending them a text. Same quiet
   weight as the note hint above it, and it sits UNDER the select rather than beside it because this
   capsule is a narrow column, not a row. */
.ph-tell-why { font-size:11.5px; line-height:1.4; color:var(--mut); margin:4px 0 0; }
.ph-xfer-panel select, .ph-xfer-panel input {
    width:100%; padding:7px 9px; font-size:13.5px;
    border:1px solid var(--line); border-radius:8px; background:#fff; color:var(--ink);
}
.ph-actions .ph-xfer-panel .btn.small { width:100%; justify-content:center; font-size:13px; padding:7px 10px; }
/* HOT KEYS (owner 2026-07-22, "like qfbrain has"). The badge is left-ANCHORED while the label stays
   centred, so the eight labels keep one optical column no matter how wide their keys are — a row of
   labels shunted right by their badges is harder to scan, and scanning is the point. */
.ph-actions .ph-xfer-panel .btn.small[data-dispo-key] { position:relative; }
.ph-dispo-key {
    position:absolute; left:7px; top:50%; transform:translateY(-50%);
    min-width:16px; padding:1px 4px; border-radius:5px;
    background:rgba(255,255,255,.22); color:#fff;
    font-size:11px; font-weight:800; line-height:1.5; text-align:center; letter-spacing:.02em;
}
/* The keypress flash — the ONLY feedback that a key (rather than a click) landed on a real
   customer's record. Brief and bright enough to catch a mis-hit. */
.ph-actions .ph-xfer-panel .btn.small.is-hit { background:#1e9e5a; transform:scale(.97); }
.ph-dispo-hint { font-size:11.5px; color:var(--mut); text-align:center; margin-top:2px; }

/* ═══ THE HANG-UP CAPSULE (owner's v3 mock, 2026-07-30) ═══════════════════════════════════════
   One box when a call ends: what happened ABOVE, a callback in the middle, the outcome buttons at
   the bottom. His three revisions to v2 are the whole shape — "it could give you a pop up in the
   middle of the screen", wide enough for three columns, and "each column is a color… the last
   column are the reds".

   🛑 IT IS A SIBLING OF .ph-strip, NEVER A CHILD, AND THAT IS LOAD-BEARING. `position:sticky` opens
   a stacking context always (CallControlsUnburiableTest's own note), so the strip's descendants are
   sealed inside its layer — a full-viewport dim written in there paints OVER Hang up no matter what
   z-index it carries, because the bar's own buttons are z-auto. As a sibling it lands in the page's
   context where --z-modal (120) genuinely resolves below --z-callbar (160). This is the fourth time
   an overlay has met this rule; the first three shipped over the owner on live calls. */
.ph-capsule-dim {
    position:fixed; inset:0; z-index:var(--z-modal);
    background:rgba(17,12,29,.44);
    display:flex; align-items:center; justify-content:center;
    /* The bar is ~52px of the viewport's top and outranks this by design — pull the box down out
       from under it so a centred capsule never *looks* clipped by the thing it must not cover. */
    padding:64px 16px 16px;
}
.ph-capsule-dim[hidden] { display:none; }
.ph-capsule-modal {
    width:620px; max-width:100%; max-height:100%; overflow-y:auto;
    display:flex; flex-direction:column; gap:11px; padding:18px;
    background:var(--card); border:1px solid var(--line); border-radius:14px;
    box-shadow:0 24px 60px rgba(0,0,0,.32);
}
.ph-capsule-title { font-size:16.5px; font-weight:800; color:var(--ink); letter-spacing:-.01em; }
.ph-capsule-sub { font-size:12.5px; color:var(--mut); margin-top:2px; }
.ph-capsule-modal textarea {
    width:100%; min-height:66px; resize:vertical; padding:9px 11px; font-size:13.5px;
    font-family:inherit; line-height:1.45; box-sizing:border-box;
    border:1px solid var(--line); border-radius:9px; background:var(--card); color:var(--ink);
}
/* 🤝 WHO TOOK THE CALL (owner 2026-07-31, "picker shows all reps"). 🛑 IT NEEDS ITS OWN RULE FOR
   THE REASON THE COMMENT BELOW THE SEPARATOR ALREADY GIVES: `.ph-xfer-panel select` does NOT reach
   inside the capsule, because the capsule's controls left that panel when they left the bar. Left
   to the browser default this renders as a grey system dropdown in the middle of a styled card, on
   a live call. Built from the textarea's own values so the two fields a rep fills in cannot drift. */
.ph-capsule-modal select {
    width:100%; padding:8px 11px; font-size:13.5px; font-family:inherit; box-sizing:border-box;
    border:1px solid var(--line); border-radius:9px; background:var(--card); color:var(--ink);
}
.ph-capsule-sep { height:1px; background:var(--line); }
/* Said when there is no lead behind the call — a raw dial, or a pop we never matched. The note has
   nowhere to go, so the field goes away and SAYS so. Silently eating what a rep typed about a real
   conversation is the dead-button shape this codebase keeps paying for. */
.ph-capsule-noteless { font-size:12.5px; color:var(--mut); line-height:1.5; }
/* THE MESSAGE BOX, CONTINUED (owner 2026-08-01) — on a covering call the box the rep was already
   typing in simply carries on inside the hang-up capsule, labelled with the OWNER'S name. Styled
   as an ordinary field on purpose: nothing here may look like a recovery banner or an alert,
   because the rep should not be able to tell anything unusual happened. */
/* THE "YOU OWE" PANEL (owner 2026-08-01: "that should probably be a pop up not take over the whole
   screen"). Same family as .ph-vm-panel — it IS .ph-xfer-panel with a width; the button is a plain
   strip chip so the number still reads as a statement rather than a control shouting for a press. */
.ph-owe-wrap .ph-owe-btn { background:rgba(255,255,255,.15); color:#e9eefb; font-weight:700; }
.ph-owe-wrap .ph-owe-btn:hover { background:rgba(255,255,255,.26); }
.ph-owe-wrap .ph-owe-btn b { color:#fff; }
.ph-owe-panel { width:320px; left:0; right:auto; }
/* 🛑 THE PANEL IS WHITE AND IT LIVES INSIDE `.ph-summary`, WHOSE LINKS ARE NEAR-WHITE (see the
   `.ph-summary a` rule — named, not line-numbered, because the number rots) because that strip is
   navy. Inherited, the customer names rendered pale grey on white — legible in the DOM, invisible
   to a rep. No test can see this; the render is what caught it. Any FUTURE white panel nested in
   this navy strip inherits the same near-white link colour and needs its own override. */
.ph-owe-panel a { color:var(--brand); }
.ph-owe-panel a:hover { text-decoration:underline; }
.ph-owe-panel .muted { color:var(--mut); }
.ph-owe-row { display:flex; justify-content:space-between; gap:10px; align-items:baseline;
    padding:6px 0; border-bottom:1px solid var(--line); font-size:13px; }
.ph-owe-row:last-of-type { border-bottom:0; }
.ph-owe-tag { display:inline-block; margin-left:7px; font-size:10.5px; font-weight:800;
    letter-spacing:.03em; text-transform:uppercase; color:#35529b; border:1px solid #c3d0ee;
    border-radius:999px; padding:1px 7px; }
/* `.ph-owe-tag.is-noname` — OPEN-ITEMS 587. 173 rows floor-wide carry no captured customer name,
   and a rep dialling one needs to know before the call connects. The house `.tag.warn` amber ("look
   at this" — design-catalogue's severity trio) inside the pill this exact panel already uses for
   `inbound_badge`, rather than a third colour: it is worth a rep's attention, not a compliance
   verdict (`.tag.alarm`) and not a fact like a plain phone number (`.ph-owe-tag`'s own blue). */
.ph-owe-tag.is-noname { background:#fde6c7; color:#8a5a00; border-color:transparent; }

/* THE DRAWER'S SECTIONS (owner 2026-08-04: "along with missed calls. separated of course").
   Promises first, missed calls second, not-yet-due third, and the floor line last for staff.
   🛑 The separation is structural, not decorative: 97 of 98 rows eligible to appear are inbound
   missed calls, so in a flat list a rep's own promises — the ones with a time and a person waiting
   — were buried under strangers who rang and hung up. */
.ph-owe-sec + .ph-owe-sec { margin-top:10px; padding-top:9px; border-top:1px solid var(--line); }
.ph-owe-sec-hd { display:flex; align-items:baseline; justify-content:space-between; gap:8px;
    font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:var(--mut); }
.ph-owe-sec-n { font-size:11px; font-weight:800; color:var(--brand); }
.ph-owe-sec-note { font-size:11.5px; color:var(--mut); margin:2px 0 3px; }
/* The floor line is a COUNT, never a list — the console is one link below it and already exists. */
.ph-owe-floor .ph-owe-sec-n { color:#8a5a00; }

#ph-covering-msg { width:100%; min-height:74px; margin:4px 0 8px; }
[data-cb-covering-slot] label { display:block; margin-bottom:2px; }
/* 🛑 `.ph-capsule-modal .btn.small { width:100% }` BELOW is the DISPOSITION buttons' rule — they
   fill their three columns. Send is not one of them, and inheriting that width made it outweigh
   every outcome button underneath it (seen only by LOOKING at the render — no test can see it).
   ⚠️ THE SELECTOR CARRIES `.ph-capsule-modal` FOR SPECIFICITY, NOT FOR SCOPE: without it this is
   (0,3,0) against that rule's (0,3,0), and being EARLIER in the file it silently loses. Written
   the short way first, and the render showed a full-bleed Send twice before this line was right. */
.ph-capsule-modal [data-cb-covering-slot] .btn.small { width:auto; }

/* 🛑 THE BUTTONS LEFT `.ph-xfer-panel` WHEN THEY LEFT THE BAR, so the rules that made them full
   width and gave `.ph-dispo-key` something to be absolute INSIDE have to follow them here. Without
   `position:relative` the key badge escapes to the nearest positioned ancestor and lands somewhere
   else on the page entirely. */
.ph-capsule-modal .btn.small { width:100%; justify-content:center; font-size:13px; padding:7px 10px; }
.ph-capsule-modal .btn.small[data-dispo-key] { position:relative; padding-left:24px; }
.ph-capsule-modal .btn.small.is-hit { background:#1e9e5a; transform:scale(.97); }
.ph-capsule-modal .ph-dispo-hint[hidden] { display:none; }

/* THREE COLUMNS, ONE COLOUR EACH — and the colour is `ops_dispositions.tint`, his own column, not a
   map in this file. Green moves it forward, plain is still working it, red ends the lead. */
.ph-dispo-cols { display:grid; grid-template-columns:repeat(3,1fr); gap:9px; align-items:start; }
.ph-dispo-col { display:flex; flex-direction:column; gap:6px; }
/* THE DNQ REASONS, revealed by their parent (owner 2026-08-02, "it opens the three up"). Indented
   and rule-marked so the three read as belonging to the button above them rather than as three more
   top-level choices — the crowding he asked to remove would simply come back otherwise.
   🛑 `[hidden]` needs saying out loud: `.ph-dispo-kids` is `display:flex`, and a display value beats
   the `hidden` attribute's UA `display:none`. Without this line the three render permanently open
   and the feature silently does nothing — the same specificity trap `.gcs-say.gcs-bound[hidden]`
   already carries two files over. */
.ph-dispo-kids { display:flex; flex-direction:column; gap:6px; margin:2px 0 2px 11px;
    padding-left:9px; border-left:2px solid var(--line); }
.ph-dispo-kids[hidden] { display:none; }
.ph-dispo-col-hd {
    font-size:10.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase;
    padding:0 0 3px 2px;
}
.ph-dispo-col-hd.h-good { color:#17804a; }
.ph-dispo-col-hd.h-mid  { color:var(--mut); }
.ph-dispo-col-hd.h-bad  { color:var(--danger-ink); }
.ph-dispo-col .btn.small {
    position:relative; width:100%; justify-content:center;
    padding:9px 8px 9px 24px; font-size:12.5px; border-radius:9px;
    line-height:1.25; text-align:center; min-height:36px; box-sizing:border-box;
}
/* ⚖️ THE SECOND SELECTOR ON EACH OF THESE WENT WITH THE SHORTCUT IT SERVED (owner 2026-08-06 —
   *"Why bother… That is a confusing process"*). `.ph-cb-commit` was the **Save — Callback** press
   that sat beside the booked time, OUTSIDE any `.ph-dispo-col`, so it had to be named here or it
   would have grown a second green drifting away from the one he edits. The button is retired; the
   column rules below are the originals and are untouched. */
.ph-dispo-col .btn.small.tint-good { background:linear-gradient(180deg,#1e9e5a,#17804a); box-shadow:0 1px 0 rgba(255,255,255,.15) inset, 0 8px 18px -10px rgba(23,128,74,.8); }
.ph-dispo-col .btn.small.tint-bad  { background:linear-gradient(180deg,var(--danger),var(--danger-2)); box-shadow:0 1px 0 rgba(255,255,255,.15) inset, 0 8px 18px -10px rgba(169,50,38,.8); }
.ph-dispo-col .btn.small.is-hit { background:#1e9e5a; transform:scale(.97); }
/* 🛑 A ROW THAT CANNOT DO WHAT IT SAYS — drained of its colour and inert, with the reason UNDER it.
   Three states reach here: no `box_status_code` (the box cannot be told, so the agent cannot be
   released), the permanent DNC write (which needs the console's confirm step), and a callback with
   no lead behind the call. Greyed rather than gone, because a row the owner added must never
   silently vanish off his own list; and the reason is rendered, not just a `title`, because a
   tooltip is invisible to the person it is actually for. */
.ph-dispo-col .btn.small[disabled] {
    background:#eef0f5; color:#98a0b0; box-shadow:inset 0 0 0 1px var(--line);
    cursor:default; opacity:1;
}
.ph-dispo-why { font-size:10.5px; line-height:1.35; color:var(--mut); padding:0 2px 2px; margin-top:-2px; }

/* 🛑 THE CAPSULE'S SECOND FACE — the done state, after the box has taken the disposition. Done with
   ONE rule rather than a wrapper element around everything else: the note, the callback block and
   the buttons are all direct children of the modal, and wrapping them would have moved the markup
   that `DispoCapsuleRenderTest` pins for the note-above-buttons order and the sibling structure.
   Hiding siblings is reversible and pins nothing new; restructuring for a state swap is how a
   layering fix gets quietly undone. */
.ph-capsule-modal.is-done > *:not([data-cb-done]) { display:none; }
.ph-capsule-modal [data-cb-done][hidden] { display:none; }
.ph-done-actions { display:flex; align-items:center; gap:10px; margin-top:14px; }
.ph-done-actions form { flex:1; }
.ph-done-actions .btn { width:100%; justify-content:center; }
.ph-done-close { background:#eef0f5; color:var(--mut); box-shadow:inset 0 0 0 1px var(--line); flex:0 0 auto; width:auto; }

/* 🛑 THE CAPSULE'S FOURTH FACE — "your phone window isn't responding" (Carlos, 2026-08-07). Same
   sibling-hiding swap as the done face directly above, for the same reason it gives: a wrapper
   element would move the markup `DispoCapsuleRenderTest` pins. This face STANDS IN FOR the done
   face, so the two are mutually exclusive by construction — `showUnread()` and `showDone()` each
   clear the other's class, and neither can be reached without an accepted press.

   ⚠️ IT IS NOT CALLED `is-stuck`, AND DO NOT RENAME IT BACK. That name is RESERVED — the sticky
   lead header refuses the pin-then-hide idiom on the owner's "nothing may reflow" rule, and
   `TheStickyLeadHeaderTest` bans the token from this stylesheet and from every rendered page to
   stop a fresh session reaching for it. It caught this face on the full suite. `is-dialer-unread`
   also says more: the failure is that the DIALER never READ the order. */
.ph-capsule-modal.is-dialer-unread > *:not([data-cb-unread]) { display:none; }
.ph-capsule-modal [data-cb-unread][hidden] { display:none; }
/* Reads as the alarm it is, without wearing `--danger`: rule 13 reserves red for a VERDICT and
   never on a live-call surface, and the capsule is one. The weight is in the words. */
.ph-unread-why { margin:10px 0 0; font-size:13px; line-height:1.5; color:var(--mut); }
.ph-unread-why strong { color:var(--ink); }
/* 🛑 `flex:1` — THE SAME BUG THE DNC CONFIRM DOCUMENTS BELOW, MET AGAIN AND CAUGHT IN A BROWSER.
   `.ph-done-actions .btn` sets `width:100%` while `.ph-done-close` alone carries `flex:0 0 auto`,
   so a second button with no flex of its own refuses to shrink: Close took the whole row and
   "Reopen my phone window" collapsed to a ~50px column, one word per line — pixel-for-pixel the
   owner's 2026-07-31 screenshot. No server test can see this; a rendered page in a real browser is
   what found it, which is the entire argument for looking. */
.ph-unread-reopen { flex:1; }

/* 🛑 …AND `flex:1` WAS ONLY HALF OF IT — THE 2026-07-31 FIX NEVER LANDED, AND THE DNC CONFIRM HAS
   BEEN BROKEN ON THE FLOOR EVER SINCE. Measured in a browser 2026-08-07, both faces at 1280px:
   Cancel 582px × 30px, "Yes — they asked us to stop. Never call again." 58px WIDE and 158px TALL.
   `flex:1` cannot save a sibling that will not shrink, and `.ph-done-close`'s own `width:auto` is
   OUTRANKED by `.ph-done-actions .btn { width:100% }` — two classes beat one, and both rules
   arrived in the same commit (86e1daf9), so the width half has never once applied.

   🛑 SCOPED TO THE TWO-BUTTON FACES ON PURPOSE. The done face carries Close ALONE, and full-width
   is what the owner has seen there on every call since; widening this to `.ph-done-close` outright
   would silently redraw a screen he meets dozens of times a day to fix one he meets rarely. */
.ph-capsule-modal.is-dialer-unread .ph-done-close,
.ph-capsule-modal.is-confirm-dnc .ph-done-close { flex:0 0 auto; width:auto; }

/* THE DNC CONFIRM — the same swap mechanism as the done face above, for the same reason: hiding
   siblings pins nothing new, where a wrapper element would move the markup that `DispoCapsuleRender`
   pins for the note-above-buttons order and the SIBLING-of-.ph-strip structure.
   🛑 The buttons must not sit at equal weight. The destructive one is the wide red; Cancel keeps the
   grey pill the done face already uses — a rep meets this screen having just been told "never call
   me again", and a symmetrical pair of buttons is where a permanent, unundoable write gets a
   mis-click it can never take back. */
.ph-capsule-modal.is-confirm-dnc > *:not([data-cb-confirm-dnc]) { display:none; }
.ph-capsule-modal [data-cb-confirm-dnc][hidden] { display:none; }
/* 🛑 WHO THIS IS ABOUT — ⚖️ Gil 2026-09-09, after a do-not-call was inked against the wrong
   customer while the rep was on the phone with another. It sits ABOVE the question and is set
   LARGER than it, because the name is the only part of this face a human can actually check: the
   number below it was rendered correctly on the press that went wrong and was read straight past.
   No colour of its own — the gravity on this face lives in the WORDS (owner 2026-07-31, ruling out
   red here), and a name is a fact rather than a warning. */
.ph-dnc-who { font-size:19px; font-weight:800; color:var(--ink); letter-spacing:-.015em; margin:0 0 2px; }
.ph-dnc-who[hidden] { display:none; }
.ph-dnc-warn { margin:10px 0 0; font-size:13px; line-height:1.5; color:var(--mut); }
.ph-dnc-warn strong { color:var(--ink); }

/* 🛑 WHY THE CALL DID NOT GO OUT — ⚖️ Gil 2026-08-27: *"a pop up comes up to let tehm know why it is
   not calling."* Deliberately the SAME two lines as `.ph-unread-why` and `.ph-dnc-warn` above rather
   than a look of its own: these are three faces of one component (a box the strip opens to tell a
   rep something they must read), and a fourth typography invented here is how a family drifts apart.

   ⚠️ IT IS NOT RED, and that is Rule 13 plus the owner's own 07-31 ruling one shelf up. Nothing has
   gone wrong when this box opens — a rule worked. Red here would read as a fault and send the next
   fifteen minutes looking for a broken dialer, which is precisely the afternoon this box exists to
   prevent.

   ⚠️ `.ph-refusal-modal` IS NARROWER THAN THE CAPSULE IT INHERITS FROM. 620px is right for the
   disposition face, which carries a grid of buttons; this carries one sentence, and a sentence set
   620px wide is a sentence the eye loses its place in. */
/* 🛑 NARROW ON PURPOSE. 620px is right for the disposition face and its grid of buttons; this
   carries one sentence, and a sentence set 620px wide is a sentence the eye loses its place in.
   380px keeps the fact to two comfortable lines. */
/* ⚖️ TAKE A NUMBER OFF THE FILE (Gil 2026-08-27) — the control sits UNDER the number it acts on and
   stays shut until asked. 🛑 It is deliberately quiet: an alternate number is read far more often
   than it is corrected, and a control louder than the fact it hangs off turns the identity block on
   every lead card into a row of buttons. `<details>` for the same reason the DNC confirm uses a
   round trip — a press that changes who we may call should cost two deliberate actions. */
.lead-alt-drop { display:inline; }
.lead-alt-drop > summary {
    display:inline; cursor:pointer; margin-left:6px;
    font-size:11.5px; font-weight:700; color:var(--mut); list-style:none;
}
.lead-alt-drop > summary::-webkit-details-marker { display:none; }
.lead-alt-drop > summary:hover { color:var(--brand); text-decoration:underline; }
.lead-alt-drop[open] > summary { color:var(--brand); }
.lead-alt-drop form {
    margin:7px 0 10px; padding:10px 11px; max-width:330px;
    background:var(--wash); border:1px solid var(--line); border-radius:10px;
}
.lead-alt-drop textarea {
    width:100%; box-sizing:border-box; margin-bottom:7px; padding:7px 9px;
    font-family:inherit; font-size:12.5px; line-height:1.45;
    border:1px solid var(--line); border-radius:8px; background:var(--card); color:var(--ink);
}
/* Not red: the number being on the list is a FACT the rep needs, not a fault they caused. Rule 13
   keeps --danger for a verdict. */
.lead-alt-warn { margin:0 0 8px; font-size:12px; line-height:1.45; color:var(--mut); }
.lead-alt-warn b { color:var(--ink); }

.ph-refusal-modal { width:380px; gap:0; padding:20px 20px 16px; text-align:center; }

/* The VERDICT. Two to four words, and the only thing in the box that has to be read from across a
   desk — the rep's eye lands here and stops if it says "Do not call". */
.ph-refusal-modal .ph-capsule-title { font-size:17px; }

/* The FACT, plain. Full ink because it is the thing they came for. */
.ph-refusal-why { margin:8px 0 0; font-size:13.5px; line-height:1.5; color:var(--ink); }

/* The ERRAND, quiet. Muted and a step down, above a hairline that separates *what is true* from
   *what you can do about it* — the split ⚖️ Gil asked for when he called the one-paragraph draft
   too long. Hidden outright on the refusals where there is nothing a rep can do, rather than
   padding the box with a consoling line. */
.ph-refusal-hint {
    margin:12px 0 0; padding-top:11px; border-top:1px solid var(--line);
    font-size:12.5px; line-height:1.45; color:var(--mut);
}
.ph-refusal-hint[hidden] { display:none; }
.ph-refusal-modal .ph-done-actions { margin-top:16px; }
/* 🛑 THE CONFIRM BUTTON IS **NOT RED** — owner ruling 2026-07-31, on sight: *"red gives impression
   of 'stop'. change that."* He is right and the design system already said so twice over:
     · Red on an AFFIRMATIVE control fights itself. Every other red in this app means stop/ends-it
       (the `tint-bad` column, the alarm), so a red button whose job is "yes, go ahead" asks the rep
       to read the words to find out which way it points — on the one press that can never be undone.
     · Rule 13 RESERVES `--danger` for a VERDICT, never a notice, and **never on a live-call
       surface**. The capsule is a live-call surface. Wearing it here was already out of bounds.
   So the gravity lives in the WORDS and in the warning paragraphs above it, and the button wears the
   app's ordinary "proceed" look. Cancel stays the grey pill, so the two are still not equal weight.
   🛑 `flex:1` IS THE BUG FIX, not styling. `.ph-done-close` carries `flex:0 0 auto` while this had
   no flex at all, so Cancel refused to shrink, took the whole row, and the confirm button collapsed
   to a ~50px column with one word per line (owner's screenshot, 2026-07-31). The done face escapes
   this only because its Next sits inside a `form { flex:1 }`. */
.ph-dnc-go { flex:1; background:linear-gradient(180deg,var(--brand),var(--brand-2)); }

/* THE CALLBACK BLOCK — one button at rest, then a real calendar and a real clock. He rejected the
   preset dropdown outright: "So I can set callbacks whenever we want, not specific set times like
   you have it on the drop down." Today / Tomorrow / Next week survive as shortcuts, not as the
   only choices. */
.ph-cb-open { width:100%; justify-content:center; background:#f1f4fa; color:var(--brand-ink);
              box-shadow:inset 0 0 0 1px var(--line); font-size:13px; padding:10px 12px; }
.ph-cb-panel { border:1px solid var(--line); border-radius:11px; padding:12px; background:#fbfcfe; }
.ph-cb-panel[hidden] { display:none; }
.ph-cb-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; }
.ph-cb-month { font-size:13.5px; font-weight:800; color:var(--ink); }
.ph-cb-nav { display:flex; gap:5px; }
.ph-cb-nav button { border:1px solid var(--line); background:var(--card); color:var(--ink); cursor:pointer;
                    width:27px; height:27px; border-radius:7px; font-size:13px; font-weight:700; line-height:1; }
.ph-cal { display:grid; grid-template-columns:repeat(7,1fr); gap:4px; }
.ph-cal-dow { font-size:10.5px; font-weight:800; color:var(--mut); text-align:center; padding:2px 0; }
.ph-cal-day { border:0; background:var(--card); color:var(--ink); cursor:pointer; border-radius:8px;
              padding:8px 0; font-size:12.5px; font-weight:600; font-family:inherit;
              box-shadow:inset 0 0 0 1px var(--line); }
.ph-cal-day.is-muted { color:#c8cedb; box-shadow:none; background:transparent; }
.ph-cal-day.is-today { box-shadow:inset 0 0 0 2px var(--ring); font-weight:800; }
.ph-cal-day.is-picked { background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#fff; box-shadow:none; }
/* A day already gone is not bookable — greyed and inert rather than absent, so the month still
   reads as a month. */
.ph-cal-day[disabled] { color:#c8cedb; cursor:default; box-shadow:inset 0 0 0 1px var(--line); }

/* 📅 THE FOLLOW-UP CALENDAR — ⚖️ Gil 2026-08-19, on My Tasks. It reuses `.ph-cal` and `.ph-cal-day`
   from the BOOKING picker on purpose: the two calendars on this platform take and show the same
   thing, and letting them diverge visually would make one of them look like a different product.
   These rules only add what a VIEWER needs and the picker never did — a count on the day, and a
   colour for a day that is already going wrong. */
.fu-cal-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px; }
.fu-cal-month { font-size:14px; font-weight:800; color:var(--ink); }
/* Taller than the picker's cells because these carry a second line — the count pill. */
.fu-cal .ph-cal-day { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px; padding:9px 0 8px; }
.fu-cal-n {
    min-width:17px; padding:0 5px; border-radius:999px;
    background:var(--brand); color:#fff; font-size:9.5px; font-weight:800; line-height:15px;
}
/* 🛑 RED IS "SOMETHING HERE IS STILL OWED AND ITS TIME HAS PASSED", never "this day is in the past".
   A day whose promises were kept is a day that went right, and colouring it would teach a rep that
   the colour means nothing — the wallpaper failure this platform has already paid for once, when a
   "Missed call" label fired on 78% of the rows of a tab called Missed calls. */
.fu-cal .ph-cal-day.is-late { background:#fdecea; box-shadow:inset 0 0 0 1px #f0bcb3; color:#8f2416; }
.fu-cal .ph-cal-day.is-late .fu-cal-n { background:#d2402a; }
/* A day with nothing on it is inert — it renders so the month still reads as a month, and it is not
   a control because there is nothing behind it to open. */
.fu-cal .ph-cal-day.is-empty { cursor:default; color:var(--mut); font-weight:600; }
.fu-cal .ph-cal-day.is-picked .fu-cal-n { background:#fff; color:var(--brand); }
.fu-cal-day-list { margin-top:14px; padding-top:12px; border-top:1px solid var(--line); }
.fu-cal-day-list[hidden] { display:none; }
.fu-cal-day-ttl { font-size:13.5px; font-weight:800; color:var(--ink); margin:0 0 8px; }
.fu-cal-late {
    margin-left:8px; font-size:11px; font-weight:800; color:#a1281a;
    background:#fdecea; border-radius:999px; padding:2px 8px; vertical-align:middle;
}
.ph-cb-quick { display:flex; gap:6px; margin-top:10px; }
.ph-cb-quick button { flex:1; border:1px solid var(--line); background:var(--card); color:var(--brand-ink);
                      cursor:pointer; border-radius:8px; padding:7px 4px; font-size:11.5px; font-weight:700; font-family:inherit; }
.ph-time-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:6px; max-height:196px; overflow-y:auto; }
.ph-time-grid button { border:0; box-shadow:inset 0 0 0 1px var(--line); background:var(--card); color:var(--ink);
                       cursor:pointer; border-radius:8px; padding:8px 2px; font-size:12px; font-weight:600; font-family:inherit; }
.ph-time-grid button.is-picked { background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#fff; box-shadow:none; }
.ph-cb-crumb { font-size:12.5px; color:var(--mut); margin-bottom:10px; }
.ph-cb-crumb b { color:var(--ink); }
.ph-cb-crumb button { border:0; background:transparent; color:var(--brand); cursor:pointer;
                      font-size:12.5px; font-weight:700; text-decoration:underline; font-family:inherit; padding:0; }
/* 🕐 THE CUSTOMER'S OWN HOURS, DRAWN ON THE SLOTS — ⚖️ Gil 2026-08-20: *"when a time is selected for
   a lead that is not teh allowed time it say it must have been requested by customer because it is
   ____ their time, and not allowed"*.

   🛑 TWO STATES, NOT ONE, because they mean different things and a rep must not read them as the
   same warning: `is-shut` is a time we may NEVER call (the TCPA floor — the button is genuinely
   disabled), `is-ask` is a time only the CUSTOMER may choose for us (bookable, with the tick). One
   amber blur over both would turn a hard refusal into a suggestion. */
.ph-time-grid button.is-ask { box-shadow:inset 0 0 0 1px #e0b76a; background:#fdf6e6; color:#7a5a13; }
.ph-time-grid button.is-shut { opacity:.45; cursor:not-allowed; text-decoration:line-through; }
.ph-time-grid button.is-shut:hover { background:var(--card); }
.ph-cb-ask { border:1px solid #e0b76a; background:#fdf6e6; border-radius:10px; padding:10px 12px; margin-bottom:8px; }
.ph-cb-ask[hidden] { display:none; }
.ph-cb-ask-why { margin:0 0 7px; font-size:12.5px; color:#7a5a13; font-weight:600; }
.ph-cb-ask-tick { display:flex; align-items:flex-start; gap:8px; font-size:12.5px; color:var(--ink); cursor:pointer; }
.ph-cb-ask-tick input { margin:2px 0 0; flex:0 0 auto; }
.ph-cb-set { display:flex; align-items:center; justify-content:space-between; gap:8px;
             border:1px solid #bcd8c6; background:#e6f6ed; border-radius:10px; padding:10px 12px; }
.ph-cb-set[hidden] { display:none; }
.ph-cb-set-txt { font-size:13px; font-weight:700; color:#17804a; }
.ph-cb-set button { border:0; background:transparent; color:var(--mut); cursor:pointer;
                    font-size:11.5px; font-weight:700; text-decoration:underline; font-family:inherit; }

/* BOOKING THE CALL BACK **IS** THE DISPOSITION (owner 2026-08-05: "when they click follow up, it
   should dispo call as call back"). Sits directly under the green booked line, so the press the rep
   still owes is the next thing their eye lands on rather than a hunt through the grid below.
   🛑 `display:flex` under `[hidden]:none`, in that order — the element ships hidden and the picker's
   announcement is what reveals it, so a specificity slip here leaves a stale "Save — Callback" from
   the previous customer sitting on the next one's capsule. */
/* ⚖️ `.ph-cb-commit` AND `.ph-cb-commit-why` ARE DELETED — the **Save — Callback** shortcut and its
   caption, retired 2026-08-06 on the owner's report (*"i put a time and it then says to Save
   Callback. WHy bohter… Save and there is a callback below."*). Dead rules for a deleted control
   are how a retired button quietly comes back looking correct; the markup, the listener, the CSS
   and the tests went in one commit. The instruction line below SURVIVES — it is the picker's, not
   the button's, and it now says where the time goes. */
/* 📋 THE INSTRUCTION (owner 2026-08-05: *"One Press is right (put instructions, brief ones)"*).
   Sits under the "Schedule a callback" button, ABOVE the panel — quiet type, because an
   instruction that shouts competes with the customer on the other end of the line. Left-aligned
   rather than centred, because it belongs to the control above it. */
.ph-cb-howto { font-size:12px; color:var(--mut); margin:6px 0 0; line-height:1.35; }
/* ⏰ THE OWED-CALLBACK NOTICE — ⚖️ Gil 2026-08-19, on a promise closing silently when a rep reaches
   the customer: *"should it ask on new dispo when calling a lead with a follow up, do they wnat to
   make a new foloow up becasue the existing will be dismissed"*. Amber, not red: this is a fact the
   rep should know before they press, not an alarm — red here would compete with the live call.
   Sits directly under the time picker, which is the control that answers it. */
.ph-cb-owed {
    font-size:12.5px; line-height:1.4; margin:8px 0 0; padding:8px 10px;
    background:#fff8ec; border:1px solid #f0d9ac; border-radius:8px; color:#6b4d12;
}
.ph-cb-owed b { color:#4a3407; }
.ph-cb-owed-when { display:block; margin-top:3px; font-size:11.5px; opacity:.8; }
.ph-cb-owed[hidden] { display:none; }

/* THE HIDDEN-PHONE ALARM (owner 2026-07-24) — a full-width row inside the wrapping strip (the
   strip is flex-wrap, so flex-basis:100% is its own line, and it inherits the sticky top). Loud
   red on purpose: this replaces a SILENT kick, and quiet would repeat the failure it exists to
   end. [hidden] wins with !important because the strip's flex display would otherwise re-show it. */
.ph-lost-banner {
    flex-basis:100%; display:flex; align-items:center; gap:12px;
    padding:8px 12px; border-radius:8px;
    background:#d2402a; color:#fff; font-size:13.5px; font-weight:700;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.25);
    /* FLASH + MOVE (owner 2026-07-24: "flash and move to get attention") — a steady pulse plus a
       short shake every couple of seconds. This is an ALARM for a rep looking elsewhere; motion is
       the point, so only reduced-motion turns it off. */
    animation: phLostPulse 1.1s ease-in-out infinite, phLostShake 2.4s ease-in-out infinite;
}
@keyframes phLostPulse {
    0%, 100% { background:#d2402a; }
    50%      { background:#f95c3f; box-shadow:inset 0 0 0 1px rgba(255,255,255,.45), 0 0 14px rgba(249,92,63,.8); }
}
@keyframes phLostShake {
    0%, 78%, 100% { transform:none; }
    82% { transform:translateX(-5px); }
    86% { transform:translateX(5px); }
    90% { transform:translateX(-4px); }
    94% { transform:translateX(4px); }
    98% { transform:translateX(0); }
}
@media (prefers-reduced-motion: reduce) { .ph-lost-banner { animation:none; } }
.ph-lost-banner[hidden] { display:none !important; }
.ph-lost-banner .btn.small { background:#fff; color:#a32718; white-space:nowrap; }
.ph-lost-banner .btn.small:hover { background:#ffe9e4; }
/* 🛑 THE SCREEN-STOPPED-EXECUTING BANNER (Carlos, 2026-08-07) — the alarm the one above CANNOT
   raise. That one keys on a stale heartbeat: window closed or covered, rep not taking calls. HIS
   HEARTBEAT WAS FRESH for all eighteen minutes — the screen was polling and being counted alive
   while its command loop was dead, so the hidden-phone alarm stayed silent and was right to.

   🛑 DEFAULT-HIDDEN IN CSS, NOT BY `hidden` — and that is the load-bearing bit. The attribute lives
   on the STRIP (the poll rewrites it every tick), so the banner is derived from the truth on every
   frame and un-paints itself the moment the phone answers again. A JS-toggled `hidden` would need
   something to remember to take it down, which is exactly the class of bug this whole session was.

   Amber, not the red above it: the rep is still logged in and still recorded, and two identical
   red alarms about one phone teach them to read neither. Motion is deliberately absent for the
   same reason — the red one is the emergency, this one is a correction. */
.ph-unresponsive-banner { display:none; }
[data-ph-unresponsive="1"] .ph-unresponsive-banner {
    flex-basis:100%; display:flex; align-items:center; gap:12px;
    padding:8px 12px; border-radius:8px;
    background:#8a5a00; color:#fff; font-size:13.5px; font-weight:700;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.25);
}
.ph-unresponsive-banner .btn.small { background:#fff; color:#7a4f00; white-space:nowrap; }
.ph-unresponsive-banner .btn.small:hover { background:#fff3dc; }
.ph-unresponsive-banner b { font-weight:800; }

/* "I know — I'm off" (owner 2026-07-28) — SECONDARY on purpose: reopening the phone is the action
   that fixes something, this one only stops the noise. A hollow chip on the red, so it reads as
   available without competing with the white primary beside it. */
.ph-lost-banner .btn.small.ph-lost-ack {
    background:transparent; color:#fff; border:1px solid rgba(255,255,255,.55); font-weight:700; }
.ph-lost-banner .btn.small.ph-lost-ack:hover { background:rgba(255,255,255,.16); }

/* THE MULTI-WINDOW WARNING (owner 2026-07-24, live: pops landed in a window he wasn't watching).
   Fixed bottom-center so it nags without covering the strip's controls; amber because it is a
   correctable habit, not an emergency. Created by pop-watcher JS when a sibling tab heartbeats. */
.cclg-tabs-warn {
    position:fixed; bottom:18px; left:50%; transform:translateX(-50%); z-index:var(--z-toast);
    padding:10px 18px; border-radius:10px; background:#e8a33d; color:#212b38;
    font-size:13.5px; font-weight:800; box-shadow:0 10px 26px rgba(23,18,37,.35);
}
.cclg-tabs-warn[hidden] { display:none; }

/* 📬 VOICEMAIL (owner 2026-07-24: "a message button in the phone bar with # bubble when message").
   The bubble is the watcher's live INBOX count; the panel is the shared box — list, play, Done. */
.ph-strip .btn.ph-vm-btn { background:rgba(255,255,255,.14); color:#fff; position:relative; }
.ph-strip .btn.ph-vm-btn:hover { background:rgba(255,255,255,.26); }
/* 🛑 `z-index:2` PUTS THE VIEWER'S OWN NUMBER ON TOP OF THE TEAM BUBBLE (`z-index:1`) — added
   2026-08-20 when Gil asked *"But waht about my bubble taht shows mine?"* and the two were rendered
   together for the first time. They clear each other by 8px today, so nothing overlaps; this decides
   what happens if a future change ever brings them together, and **the one that must survive is his
   own**. That is the whole point of this corner, and the bar has already shipped one version of this
   collision — the retired grey pip stacked on the amber owed-count, which he reported himself. */
.ph-vm-count {
    position:absolute; top:-7px; right:-7px; min-width:18px; height:18px; padding:0 5px;
    border-radius:999px; background:#d2402a; color:#fff; font-size:11px; font-weight:800;
    display:inline-flex; align-items:center; justify-content:center; z-index:2;
}
.ph-vm-count[hidden] { display:none; }
/* 👥 THE TEAM BUBBLE — ⚖️ Gil, 2026-08-20: *"Can you do a sperate bubble on teh mailbox that is for
   team?"*, straight after *"not sure if i like how it makes the Call bar bigger"*. Measured in Chrome
   on the real dashboard, the row it replaces took the sticky bar 58px → 94px at 1280 — one row to
   two. This costs the bar NOTHING: it sits on the tile that was already there.

   🛑 THREE THINGS KEEP IT FROM READING AS THE VIEWER'S OWN WORK, and all three are load-bearing
   rather than decorative. His 08-19 ruling — *"Yours in the number, team on the red row"* — refused a
   second number beside the badge precisely because **a figure in that corner reads as more of your
   own**. He has now reversed it knowingly, so the separation has to be carried by SHAPE:
     · **the opposite corner** — his own two numbers are `top:-7px; right:-7px`, always, and this is
       the only thing that ever sits on the left. Same corner in a third colour would be the exact
       conflation he was protecting against;
     · **a colour neither of his own uses** — violet against `#d2402a` new and `#e8a33d` owed;
     · **the 👥 rides inside the bubble**, so it names itself without a hover.

   ⚠️ IT PULSES, AND THAT IS NOT DECORATION EITHER. Moving the team off a full-width pulsing bar and
   onto an 18px bubble makes it QUIETER — 44 unhandled hot leads announce themselves less than they
   did yesterday. The pulse is what stops "quieter" becoming "silent"; it is slower and shallower than
   the red row's, because this is somebody else's work and must never out-shout the rep's own. */
/* 🛑 THE TOP OFFSET IS CAPPED BY THE WINDOW, NOT BY TASTE (Gil, 2026-08-28: *"Appears tro cutoff
   the top of bubble on mailbox"*). `.ph-strip` is `position:sticky; top:0` with 7px of top padding,
   so it sits flush against the top of the VIEWPORT — and a badge offset above its tile is not drawn
   over the bar, it is drawn off the screen. At -8px, plus the 2px ring, this pip reached ~3px past
   the window edge and lost its crown.
   ⚠️ The fix is the OFFSET, never the bar's padding: raising the padding would fix the clip by
   making the bar taller, which is the complaint he raised in the same breath. His 08-20 ruling is
   untouched — still a separate bubble, still the opposite corner from his own count. */
.ph-mbx-team-pip {
    position:absolute; top:-2px; left:-6px; z-index:1;
    min-width:20px; height:18px; padding:0 5px; border:0; border-radius:999px;
    background:#7c5cff; color:#fff; font:800 10.5px/1 inherit; letter-spacing:-.01em;
    display:inline-flex; align-items:center; justify-content:center; gap:2px;
    box-shadow:0 0 0 2px rgba(16,23,37,.55);
    cursor:pointer; animation:phMbxTeamPulse 2.4s ease-in-out infinite;
}
.ph-mbx-team-pip:hover { background:#8f74ff; }
.ph-mbx-team-pip[hidden] { display:none; }
@keyframes phMbxTeamPulse {
    0%, 100% { box-shadow:0 0 0 2px rgba(16,23,37,.55); }
    50%      { box-shadow:0 0 0 2px rgba(16,23,37,.55), 0 0 10px rgba(124,92,255,.85); }
}
/* Same posture as the red row: motion is the point for someone looking elsewhere, so only a
   reduced-motion preference stops it. The colour, the corner and the 👥 all stay. */
@media (prefers-reduced-motion: reduce) {
    .ph-mbx-team-pip { animation:none; }
}
.ph-vm-panel { width:340px; max-height:420px; overflow-y:auto; }
.ph-vm-row { display:flex; flex-direction:column; gap:5px; padding:9px 0; border-top:1px solid var(--line); }
.ph-vm-row:first-child { border-top:0; }
.ph-vm-row.is-old { opacity:.6; }
.ph-vm-meta { display:flex; justify-content:space-between; gap:8px; font-size:12.5px; color:var(--ink); }
.ph-vm-meta span { color:var(--mut); font-size:11.5px; }
.ph-vm-row audio { width:100%; height:34px; }
.ph-vm-row .btn.small { align-self:flex-end; }

/* 📬 THE CORNER MAILBOX (owner 2026-08-05) — "it goes wide". The tabs run ACROSS; this is
   deliberately NOT a vertical list, which is what the 📬 dropdown used to be. */
/* ⚖️ THE RIGHT MARGIN WAS THE ✕'s SEAT, and it is now plain breathing room — the badge ✕ was
   removed on his 2026-08-06 instruction (*"why is there an 'x' by the number on the mailbox on top?
   remove that"*). Kept rather than reclaimed: 📬 is the FIRST child of `.ph-strip` and this gap is
   what stops the badge crowding "Ext. 1001" beside it. */
/* ⚖️ margin-right 22px → 6px, 2026-08-13: *"you can remove teh spacing beween mailbox and Ext from
   the buttons to save room"* — the gap now hosts the 🔔🔊 setup pair instead of air. */
.ph-mbx-wrap { position:relative; margin-right:6px; }
/* 🪦 `.ph-mbx-ic` SIZED AN INLINE SVG FOR ONE AFTERNOON, 2026-08-05. The rule and the SVG are both
   gone: the icon is the literal 📬 emoji again, restored on his ruling — it had rendered on his
   machine for twelve days and was never broken. The comment here claimed it was a "BROKEN GLYPH";
   that was our misreading of his words, not his screen. `MailboxShakeGeometryTest` now pins the
   emoji. ⛔ Do not re-add an SVG icon to this button. */
/* Wide enough that ALL FOUR tabs are visible without scrolling the strip sideways. A rep who has to
   discover a hidden fourth tab does not have "one inbox"; measured against the longest labels. */
/* 🛑 THE PANEL DOES NOT SCROLL — THE LIST DOES. He reported *"header is cut off when i open
   messaages"* (2026-08-05): the tab strip lives inside this box, so with `overflow-y:auto` HERE the
   Messages tab's 29 rows (each carrying an <audio> player) pushed the tabs straight out of view the
   moment he scrolled. The tabs are the panel's navigation — they may never scroll away from it.
   Scrolling moved to `.ph-mbx-list` below; this box now sizes to its content. */
.ph-mbx-panel { width:486px; overflow:visible; left:0; right:auto; }
/* 🛑 WIDER WHEN THE REP PICKER IS ON THE ROW (owner 2026-08-12: *"Tabs are hidden in the mailbox"*).
   486px was measured for FOUR TABS ALONE — before his 08-07 "same row" ruling put the person picker
   beside them and 08-10 added the "See the whole team →" link. For a manager the row now holds
   ~640px of content, so at 486px the Messages tab scrolled out of view behind the strip's hidden
   scrollbar: exactly the hidden-fourth-tab failure the width was chosen to prevent. A plain rep
   never gets the picker (`people` < 2 keeps `[data-ph-mbx-who]` hidden) and keeps the 486px panel;
   only the manager/admin panel widens.
   ⚠️ THE NARROW-WINDOW GUARD IS A MEDIA QUERY, NOT A `100vw` CLAMP — the panel's left edge sits
   ~250px in (sidebar + strip inset), which `max-width:calc(100vw - …)` cannot see: measured at a
   900px window, the clamped 680px panel still ran 30px off the right edge. Under 960px the panel
   stays 486px and `.ph-mbx-tabrow`'s flex-wrap (below) drops the picker under the tabs — proven to
   keep all four tabs visible — rather than either side hiding. */
.ph-mbx-panel:has([data-ph-mbx-who]:not([hidden])) { width:680px; }
@media (max-width: 959px) {
    .ph-mbx-panel:has([data-ph-mbx-who]:not([hidden])) { width:486px; }
}

/* 🪦 `.ph-mbx-x` — THE BADGE'S ✕, REMOVED 2026-08-06 on the owner's instruction (*"why is there an
   'x' by the number on the mailbox on top? remove that"*). It was his own control, asked for by
   name a day earlier (*"put the X on teh number on mailbox"*), and it marked everything seen
   without closing a debt. **"Mark all as seen" inside the panel still does exactly that**, so the
   badge kept its way to zero. The rules are deleted with the markup and the listener — a dead rule
   for a removed control is how a button quietly returns looking correct. ⛔ Restoring it means the
   markup, the listener and these rules together, not one of the three. */

/* THE TAB STRIP — one row, scrolls sideways rather than wrapping. A wrapped strip would change the
   panel's height between tabs and make the whole drawer jump under the cursor. */
.ph-mbx-tabs {
    display:flex; gap:4px; overflow-x:auto; padding-bottom:8px; margin-bottom:4px;
    border-bottom:1px solid var(--line); scrollbar-width:none;
}
.ph-mbx-tabs::-webkit-scrollbar { display:none; }
.ph-mbx-tab {
    flex:0 0 auto; display:inline-flex; align-items:center; gap:4px; position:relative;
    padding:6px 8px; border:1px solid var(--line); border-radius:8px; background:transparent;
    font-size:11px; font-weight:700; color:var(--mut); cursor:pointer; white-space:nowrap;
}
.ph-mbx-tab:hover { background:var(--wash); color:var(--ink); }
.ph-mbx-tab.is-on { background:var(--brand); border-color:var(--brand); color:#fff; }
.ph-mbx-tab-ic { font-size:12.5px; }
/* The per-tab count. On the ACTIVE tab it inverts, or a red pill on the brand fill reads as an
   alarm about the tab the rep is already looking at. */
.ph-mbx-tab-n {
    min-width:16px; height:16px; padding:0 4px; border-radius:999px;
    background:#d2402a; color:#fff; font-size:10px; font-weight:800;
    display:inline-flex; align-items:center; justify-content:center;
}
.ph-mbx-tab.is-on .ph-mbx-tab-n { background:#fff; color:var(--brand); }
.ph-mbx-tab-n[hidden] { display:none; }
/* 🛑 THE TOTAL IS THE PLAIN FIGURE; "N new" is the RED MARK beside it. They must never read as two
   badges of equal weight — "Hot leads 14 3" and a rep cannot tell which number is which. */
.ph-mbx-tab-t { font-size:12.5px; font-weight:800; color:inherit; opacity:.95; }
.ph-mbx-tab-t[hidden] { display:none; }

/* 🪦 `.ph-mbx-btn.is-dot` — THE QUIET GREY PIP, RETIRED 2026-08-19.
   ⚖️ Gil, on his own bar: *"the bubble where the number is supposed to fill in is a silver dot now
   instead of number."* He was seeing the pip and the new amber owed-count STACKED — both are drawn
   in the same 10px corner, and the rule suppressing the pip had been written for `is-new` only,
   because I guarded the state I was thinking about and not the one I had just created.
   🛑 RETIRED RATHER THAN SUPPRESSED AGAIN: the pip's whole state was *there is work and no number
   to show for it*, and `Mailbox::owedCount()` now always has that number. Seventeen hot leads look
   like one dot; "17" does not. ⛔ Do not re-add a pip beside the number. */

/* ══ THE CORNER THAT WILL NOT BE MISSED ═══════════════════════════════════════════════════════
   ⚖️ Gil, 2026-08-19: *"the mailbox on top needs to show someting that you cant miss to show a new
   message. reps are missing it"* — and, shown three shapes, he picked this one: a red bar that
   stays until opened. ⚖️ And on sound, given the choice: **no sound**. The 2026-08-13 alarm that
   rang forever in his and Carlos's earpieces is why that surface is hard-coded silent, and this one
   acquires no audio element by accident either.

   🛑 WHY THE ONE-SECOND SHAKE WAS NOT ENOUGH, MEASURED RATHER THAN ASSUMED. `phMailboxShake` fires
   `.5s × 2` ONCE per arrival signature and then stops for good. After that the entire signal is an
   18px badge in a bar holding ~14 other controls. A rep on a call, looking at the script, or in
   another window has already missed it — and the shake deliberately cannot repeat, because a bar
   that shakes every tick is a fault. **So the persistence had to move out of the animation and into
   a STATE**: these rules are loud for exactly as long as something is unseen, and not one moment
   longer.

   🛑 THREE STATES, AND THE MIDDLE ONE IS THE NEW ONE (see `Mailbox::owedCount()`):
     · NEW, unseen        → `.is-new` + the row below. Only opening the mailbox clears it.
     · SEEN, still owed   → `.is-owed` — an amber NUMBER where a grey pip used to be. No motion.
     · clear              → nothing. The only quiet state.
   The pip was right that the corner was not empty and silent about the size; on the floor the day
   this shipped that size was 45 on one rep and 16 on another, and both looked like the same dot. */

/* 🛑 TRANSFORM, COLOUR AND SHADOW ONLY — THE SAME HARD RULE AS `phMailboxShake`, FOR THE SAME
   REASON. `.ph-strip` is sticky at the top of every authed page: anything that changes this
   button's BOX — margin, padding, width, border-width, font-size — reflows the bar and shifts every
   row underneath it, which this file has already paid for once (146px on every call). `background`
   and `box-shadow` are safe; a border is not. */
@keyframes phMailboxAlarm {
    0%, 100% { background:#d2402a; box-shadow:0 0 0 0 rgba(255,110,80,.9); }
    50%      { background:#ff5c3f; box-shadow:0 0 0 9px rgba(255,110,80,0); }
}
.ph-strip .btn.ph-mbx-btn.is-new {
    background:#d2402a; color:#fff;
    animation:phMailboxAlarm 1.15s ease-in-out infinite;
}
.ph-strip .btn.ph-mbx-btn.is-new:hover { background:#e8543b; }
/* ⚠️ THE DOT MUST NOT SURVIVE ONTO A LOUD CORNER. Both states are driven from one repaint, but a
   stale pip beside a live red count in the same 10px is exactly the drift this panel shipped once
   already — so the quiet state is switched off structurally here, not only in the JavaScript. */

/* THE AMBER OWED-COUNT — seen, but nothing has been worked. Deliberately NOT red: red is "stop what
   you are doing", and a rep who has been told already must not be shouted at every minute of the
   day or they learn to ignore the colour that means something new. Deliberately not silent either. */
.ph-vm-count.is-owed { background:#e8a33d; color:#3a2c0c; }

/* THE ROW ACROSS THE BAR — the part that cannot be missed, and the part that says WHAT arrived.
   🛑 IT IS THE `.ph-lost-banner` SHAPE ON PURPOSE: `flex-basis:100%` inside the wrapping strip, so
   it takes its own line and inherits the sticky top. That banner is the one alarm on this bar reps
   demonstrably do NOT miss, and it is the owner's own "flash and move to get attention".
   ⚠️ `order:-1` PUTS IT ABOVE THE CONTROLS — a row that appears BELOW Hang up pushes nothing, but a
   rep's eye starts at the top of the bar. The covering band (another desk, 2026-08-19) also sits at
   `order:-1`; two full-width rows stack in source order and neither moves the controls, which is
   the property that matters on a bar holding Hang up.
   🛑 AND IT STANDS DOWN ON A LIVE CALL — `.ph-not-when-incall`, the same rule the mailbox button
   itself follows. A customer is on the phone; the mailbox can wait sixty seconds, and this bar's
   own history is of overlays trapping the owner on a live call. */
/* ⚖️ SLIMMED 2026-08-20 — Gil, on the row he had just pressed: *"the Red flashing bar, which is too
   big by the way"*. It sits on EVERY authed page all day, so its height is rent the whole platform
   pays; the pulse and the full-width red are what make it unmissable, and neither needs 31px of row.
   🛑 THE COLOUR AND THE MOTION ARE UNTOUCHED — they carry his *"someting that you cant miss"*, and
   trading them for height would answer a size complaint by breaking the feature. Only the padding,
   the type and the button's own box come down: ~31px → ~23px, measured against the 30px controls
   beside it, so it still reads as a band rather than a line of text. */
.ph-mbx-alarm {
    flex-basis:100%; order:-1; display:flex; align-items:center; gap:10px;
    padding:3px 10px; border-radius:7px;
    background:#d2402a; color:#fff; font-size:12.5px; font-weight:800;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.25);
    animation:phMbxAlarmPulse 1.15s ease-in-out infinite;
}
@keyframes phMbxAlarmPulse {
    0%, 100% { background:#d2402a; }
    50%      { background:#f95c3f; box-shadow:inset 0 0 0 1px rgba(255,255,255,.45), 0 0 14px rgba(249,92,63,.75); }
}
/* 🛑 `[hidden]` WINS WITH `!important`, AND THE ORDER OF THESE TWO RULES IS LOAD-BEARING. The strip
   is a flex container, so `display:flex` above would otherwise re-show a row the JavaScript had
   just hidden — the exact `visibility`/`[hidden]` collision that collapsed the arrival slot. */
.ph-mbx-alarm[hidden] { display:none !important; }
.ph-mbx-alarm-what { flex:1 1 auto; min-width:0; }
/* 🪦 `.ph-mbx-alarm-team` — the team's SECOND SENTENCE on this row, retired 2026-08-20 with its
   markup. ⚖️ Gil replaced it with the violet bubble on the tile (`.ph-mbx-team-pip` above) after
   *"not sure if i like how it makes the Call bar bigger"*. Deleted rather than left behind: a live
   rule for markup that no longer exists is how a control quietly returns looking correct. */
/* The button is white-on-red so it reads as the way OUT of the alarm, not another warning.
   ⚠️ ITS OWN PADDING IS WHAT SETS THE ROW'S HEIGHT — `.btn.small` is 7px/13px, taller than the text
   beside it, so trimming the band's padding alone would have moved almost nothing. A size complaint
   is answered by measuring the TALLEST child, not the container. */
.ph-strip .btn.ph-mbx-alarm-go {
    flex:0 0 auto; background:#fff; color:#a1281a; font-weight:800; border:0;
    padding:3px 10px; font-size:12px; border-radius:6px;
}
.ph-strip .btn.ph-mbx-alarm-go:hover { background:#ffe9e4; }
/* ⚠️ Motion is the point for a rep looking elsewhere, so ONLY a reduced-motion preference stops it —
   the colour and the words stay, so nothing is lost by not moving. Same posture as `.ph-lost-banner`. */
@media (prefers-reduced-motion: reduce) {
    .ph-mbx-alarm { animation:none; }
    .ph-strip .btn.ph-mbx-btn.is-new { animation:none; background:#d2402a; }
}

/* New rows are MARKED, not merely ordered — position alone does not tell a rep where new ends. */
.ph-mbx-row.is-new b, .ph-mbx-row.is-new a { font-weight:800; }
/* 🛑 THE NAME'S OWN LINE, AND IT EXISTS SO THE PILLS BESIDE IT STAY PILLS. `.ph-mbx-row` below is
   `flex-direction:column`; a flex child's `display:inline-block` is BLOCKIFIED and then stretched to
   the cross axis, so `.ph-mbx-new` painted as a full-width red band (438px measured, against the
   35px of its own text) and `.ph-owe-tag`'s "Overdue" did the same. Both were written with
   `margin-left:7px` — a declaration that only means anything beside a name — so the intent was
   always this line; the column layout had been discarding it since 08-05.
   ⚠️ `flex-wrap` is not optional: a long name plus a pill must wrap rather than squeeze the pill,
   and `min-width:0` lets the name ellipsis instead of forcing the row wider than the panel. */
.ph-mbx-line { display:flex; align-items:center; flex-wrap:wrap; gap:0; min-width:0; }
.ph-mbx-line > a, .ph-mbx-line > b { min-width:0; overflow:hidden; text-overflow:ellipsis; }
.ph-mbx-new {
    display:inline-block; margin-left:7px; font-size:10px; font-weight:800; letter-spacing:.04em;
    text-transform:uppercase; color:#fff; background:#d2402a; border-radius:999px; padding:2px 6px;
}
/* The labelled boundary. Messages only — it is the one tab with two real piles (INBOX vs Old). */
/* 👥 THE TEAM VIEW'S PER-PERSON HEADER — ⚖️ Gil 2026-08-19, keeping his 08-07 ruling: the rows stay
   grouped under the name of whoever owes them, never merged into one stream. The count sits on the
   header rather than beside each row, so a manager scanning for "who is behind" reads one line per
   person instead of counting rows. Red only when something under it is UNSEEN — that is the question
   he asked ("new calls that have nto been handles"), not how much the person holds. */
/* 🛑 EVERY PERSON STARTS SHUT — ⚖️ Gil, 2026-08-20: *"when i open team, it should collapse each
   person with summary"*. Measured on prod the hour he asked, the team's Hot leads rendered **51 rows**
   in this dropdown (Follow ups, 17): a manager opening the panel to ask *is anybody behind* had to
   scroll fifty rows to find out. A real `<button>` with `aria-expanded`, because it is a disclosure
   and a manager on a keyboard must be able to open one. */
.ph-mbx-team-grp + .ph-mbx-team-grp { border-top:1px solid var(--line); }
.ph-mbx-team-head {
    display:flex; align-items:baseline; gap:9px; width:100%; text-align:left;
    font-size:12px; font-weight:800; color:var(--ink); font-family:inherit;
    background:transparent; border:0; cursor:pointer;
    padding:10px 2px; border-radius:8px;
}
.ph-mbx-team-head:hover { background:var(--wash); }
/* ⚠️ 11px AND A FIXED 10px SLOT, BOTH MEASURED RATHER THAN CHOSEN. At 9px the ▸ rendered **5px
   wide** and read as a bullet, not a disclosure — checked in Chrome on the real panel, which is the
   only place a glyph's actual size is knowable. The fixed width is what keeps the NAMES aligned
   between the two states: a rotated triangle has a different footprint from an upright one, so
   without it every row shifted a pixel or two as it opened. */
.ph-mbx-team-caret {
    flex:0 0 10px; text-align:center; font-size:11px; line-height:1; color:var(--mut);
    transition:transform .12s ease;
}
.ph-mbx-team-grp.is-open .ph-mbx-team-caret { transform:rotate(90deg); }
/* The name takes the slack so the two figures stay pinned right and line up down the list — the
   same reason the row's own meta column has a fixed slot. An eye running down five people should
   never have to re-find either number. */
.ph-mbx-team-who { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis;
                   white-space:nowrap; }
/* ⏳ HOW LONG THE OLDEST HAS SAT — the fact a count cannot give. **17 hot leads an hour old and 17
   six days old are the same number and a completely different problem**, and that is the question
   this screen exists to answer. Quiet until something under it is unseen. */
.ph-mbx-team-age { flex:0 0 auto; font-size:10.5px; font-weight:700; color:var(--mut); }
.ph-mbx-team-age.is-new { color:#a1281a; }
/* 🛑 RENAMED FROM `.ph-mbx-team-n` ON 2026-08-20 — IT WAS THE SECOND CLASS COLLISION FOUND IN THIS
   FILE IN ONE DAY, and the same shape as `.ph-mbx-alarm`'s. `.ph-mbx-team-n` is ALSO the panel's
   caption paragraph ("Their list — the badge on your bar still counts your own work"), declared ~340
   lines below at equal specificity. Neither rule fully overrode the other, so both elements got the
   UNION: measured in Chrome, that caption was rendering with **this pill's grey background, 999px
   radius and 2px 8px padding** — a sentence dressed as a badge — while the count pill quietly took
   the caption's `font-size` and a 6px top margin. **Every PHP test was green; the markup was
   perfect.** A name collision is silent by construction. */
.ph-mbx-team-count {
    flex:0 0 auto; font-size:10.5px; font-weight:800; color:var(--mut);
    background:var(--wash); border-radius:999px; padding:2px 8px;
}
.ph-mbx-team-count.is-new { background:#fdecea; color:#a1281a; }
.ph-mbx-team-rows[hidden] { display:none; }
.ph-mbx-div {
    font-size:10.5px; font-weight:800; letter-spacing:.07em; text-transform:uppercase;
    color:var(--mut); padding:9px 0 5px; margin-top:4px; border-top:1px solid var(--line);
}

/* The scroll lives here, under a tab strip that stays put. Max-height is the panel's old budget
   minus the strip, so the drawer is the same size it always was. */
.ph-mbx-list { display:flex; flex-direction:column; max-height:372px; overflow-y:auto; }
/* ═══ THE ROW, RE-LAID-OUT 2026-08-09 ═══════════════════════════════════════════════════════
   ⚖️ He read the first cut and said: *"it's hard to follow. I have to actually read everything to
   understand it… a lot of things stick out easily. Miss call should maybe be all the way to the
   right. separate. from name and number… the date and time should be on the top line, and the
   dismiss button can be all the way to the right of the last row, to save room."*

   🛑 THE DIAGNOSIS UNDER HIS WORDS, because "make it prettier" is not actionable and this is: the
   old row was ONE COLUMN of six things at nearly the same weight, so **nothing was a landmark** and
   the eye had to start at the top of every row. Scanning fifteen of those is reading fifteen
   paragraphs. The fix is two anchors and a real hierarchy —
     · WHO on the left, at a size nothing else competes with;
     · WHEN and WHAT KIND pinned to the RIGHT EDGE, so they form columns down the list;
     · everything else quieter than both.
   A list becomes scannable when the eye can travel one column, not fifteen rows. */
.ph-mbx-row { display:flex; flex-direction:column; gap:1px; padding:11px 2px 11px 11px;
              border-top:1px solid var(--line); border-left:3px solid transparent; }
.ph-mbx-row:first-child { border-top:0; }

/* 🛑 UNREAD IS THE ROW'S LEFT EDGE NOW, NOT A PILL — and this HONOURS his 2026-08-05 ruling instead
   of reversing it. That ruling: *"new rows are MARKED, not only ordered — a rep scanning fifteen
   rows cannot infer 'the first four are new' from position alone."* A tinted row with a brand bar is
   a STRONGER mark than a 35px red pill: it is the whole row, it is the convention every mail client
   already taught these reps, and it costs ZERO horizontal space on a line he asked to have less on.
   ⚠️ `.ph-mbx-div` ("Earlier") still draws the boundary as well, so it is marked twice. */
.ph-mbx-row.is-new { border-left-color:var(--brand); background:rgba(53,82,155,.05); }

/* Line 1. `align-items:baseline` so a 15px name and an 11px timestamp sit on one optical line
   rather than being centred against each other. */
.ph-mbx-head { display:flex; align-items:flex-start; justify-content:space-between; gap:10px; }

/* 🛑 `min-width:0` IS LOAD-BEARING — without it a flex item refuses to shrink below its content and
   a long name pushes the right-hand column off the panel, which is the exact alignment this layout
   exists to create. The NAME truncates; the date and the chip never move. */
.ph-mbx-idn { display:flex; align-items:baseline; gap:8px; min-width:0; flex:1 1 auto;
               padding-top:1px; }
/* ⚖️ THE ALARM, ON THE NAME LINE (owner 08-09: *"move overdue on the top row in between the phone
   number and date"*) — and ⚖️ NOT A BUBBLE (*"with all the bubbles, it just confuses it and makes it
   not easily read"*).

   🛑 WEIGHT AND COLOUR, NO SHAPE. A filled pill is an outline the eye must decode before it can read
   the word inside; four of them in different colours on a 486px row is a field of blobs. Bold amber
   says "overdue" exactly as loudly and adds nothing to trip over. **This is the whole answer to his
   complaint** — the information survived, the shapes did not.

   ⚠️ ITS OWN CLASS, NOT AN OVERRIDE OF `.ph-owe-tag` — that class is still a filled pill on the
   console row and the lead page, where it sits ALONE and a shape is affordable. Restyling the shared
   class would have quietly de-bubbled three screens nobody asked about.

   ⚠️ `flex:0 0 auto` so the NAME gives way first: a row shouting "Do not call" has earned its space.

   🛑 RENAMED `.ph-mbx-alarm` → `.ph-mbx-flag`, 2026-08-20, AND THE RENAME IS A BUG FIX. This block
   and the STRIP's red row (`.ph-mbx-alarm`, ~120 lines up) had the same class name and equal
   specificity, so **this one won on every property it names** — and it names four. Measured in
   Chrome on the real dashboard before the fix:

   | the strip row asked for | it actually got | from |
   |---|---|---|
   | `color:#fff` | `#a8500b` on `#d2402a` | here |
   | `font-size:12.5px` | `11.5px` | here |
   | `flex-basis:100%` | `flex:0 0 auto` | here |
   | (wrapping) | `white-space:nowrap` | here |

   ⇒ **Gil's *"you cant miss"* bar had been rendering its sentence in dark brown on red since it
   shipped on 08-19** — about 1.4:1 contrast, which is a red slab with something written on it
   rather than a sentence. Nobody reported the words as unreadable; they reported the BAR. **A
   name collision is silent by construction, and this one is why the strip row's own rule looked
   correct in the file and wrong on the screen.**

   ⚠️ THE FLAG IS WHAT MOVED, NOT THE ROW, and that is deliberate: `.ph-mbx-alarm` is the name the
   blade, the JS, the tests and two reference docs use for the strip's red row, while this one is
   three template strings in `rowHtml`. Renaming the smaller reader is the change with the fewer
   places to miss. */
.ph-mbx-flag { flex:0 0 auto; font-size:11.5px; font-weight:800; letter-spacing:.01em;
               color:#a8500b; white-space:nowrap; }
.ph-mbx-flag.is-red { color:#a1281a; }
/* 📢 "Listen first" — ⚖️ Gil 2026-09-10, on reps ringing back without knowing a message was left.
   🛑 THE ONE FLAG THAT CARRIES A BACKGROUND, and it is not decoration: the other two are WARNINGS
   a rep reads and obeys, this is an ACTION they take before dialling, and it has to survive a
   fifteen-row scan that the plain-text flags already lose. Brand teal, never the reds — those mean
   *do not call this person*, and a badge that says LISTEN must not read as a prohibition. */
.ph-mbx-flag.is-msg { color:#0b5c63; background:#d7f0f2; border-radius:999px;
                      padding:1px 7px; letter-spacing:0; }
/* 🔊 …AND ONCE SOMEBODY HAS PLAYED IT, the same badge stops competing. Still present — the recording
   is still the best account of what the customer wants — but drained of the fill that made it carry
   across a fifteen-row scan. The contrast between the two faces IS the feature: mark every old
   message played (⚖️ Gil 09-10) and the loud face then only ever means "nobody has heard this". */
.ph-mbx-flag.is-msg.is-heard { color:var(--mut); background:transparent; font-weight:700;
                               padding:0; }

/* ⚖️ *"make the name bigger."* 15px/800 against the row's 11–12.5px everything-else — the single
   largest thing in the panel, which is what makes it the landmark. */
.ph-mbx-row a, .ph-mbx-row .ph-mbx-idn b {
    font-size:15px; font-weight:800; letter-spacing:-.01em; color:var(--brand-ink);
    text-decoration:none; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0;
}
.ph-mbx-row a:hover { text-decoration:underline; }

/* ⚖️ *"separate. from name and number"* — the right-hand column. `flex:none` + `nowrap` so it keeps
   its width and the name gives way instead. */
/* ⚖️ STACKED, 2026-08-09: *"why not put missed call underneath the day and time… moving day and
   time over to all the way to the right and putting missed call underneath it?"* He is right, and it
   buys two things at once: the NAME gets back the ~90px the chip was taking from its line, and the
   two facts become one right-hand block instead of a horizontal trail the eye has to traverse.
   ⚠️ `align-items:flex-end` is what keeps BOTH ends flush to the panel edge — without it the shorter
   of the two (usually the time) centres itself over the chip and the column stops being a column. */
.ph-mbx-meta { display:flex; flex-direction:column; align-items:flex-end; gap:4px;
               flex:0 0 auto; white-space:nowrap; }
/* 🛑 A FIXED CHIP SLOT, AND IT IS WHAT MAKES THE *TIME* A COLUMN TOO. Right-aligning the cluster
   lines up its right EDGE for free, but the timestamp's position then floats with whatever chip
   follows it — "Missed call", "Overdue" and "Do not call" are all different widths, so the times
   jittered ~20px row to row. Caught by looking at the render, not the markup. A minimum width on
   the chip pins the second column as well, which is the whole point of the redesign: an eye running
   down the list should never have to re-find either fact. */
/* ⚠️ THE MIN-WIDTH SLOT IS GONE WITH THE PILLS. It existed to stop a variable-width chip dragging
   the TIMESTAMP out of column — but once the two stacked, both right-align independently and the
   chip's width stopped mattering. A rule kept "just in case" here would be padding a text label out
   to 84px for no reason. */
/* ⚠️ AND THE FLEX GAP OWNS THE SPACING — `.ph-owe-tag` still carries a `margin-left:7px` from the
   days it sat beside a NAME, and inside this cluster that margin ADDS to the gap and shoves the
   timestamp 7px out of the column on exactly the rows that carry it. Measured in a browser, 978px
   against 971px; invisible to any assertion on markup. The margin is zeroed here rather than
   deleted at source because `.ph-owe-tag` is still used beside names elsewhere. */
.ph-mbx-meta > * { margin-left:0; margin-right:0; }
/* ⚖️ *"the date and time should be on the top line"* — it was a whole line of its own before, which
   is a line spent on the least urgent fact on the row. */
.ph-mbx-when { font-size:11px; color:var(--mut); font-variant-numeric:tabular-nums; }

/* The control row. ⚖️ *"the dismiss button can be all the way to the right of the last row, to save
   room."* `margin-left:auto` on the dismiss pins it right whether or not a transcript toggle or a
   try-count sits to its left. */
.ph-mbx-foot-row { display:flex; align-items:center; gap:10px; margin-top:7px; }
/* 📅 THE DOOR TO THE FORWARD BOOK — ⚖️ Gil 2026-08-19, replacing the "Follow-ups you have set"
   section that used to list fifteen future promises under today's work. Styled as a way OUT of the
   panel rather than as one of its row actions: it navigates, and this panel's own convention is
   that navigation looks different from the controls that act in place (the same reasoning as
   `.ph-mbx-oversight`). Brand fill because it is the tab's one forward affordance and a rep must
   find it without hunting — the count rides the label, so "21 ahead" is legible before the press. */
.btn.small.ph-mbx-cal {
    background:var(--brand); border-color:var(--brand); color:#fff; font-weight:800;
    text-decoration:none; white-space:nowrap;
}
.btn.small.ph-mbx-cal:hover { background:#2a4179; color:#fff; }
.ph-mbx-dsm { margin-left:auto; }
/* ✅ TICK A TO-DO OFF FROM THE CORNER — ⚖️ Gil 2026-09-01: *"how do we remove teh to-do. I don't think
   there is a way to complete the To-do."* It shares `.ph-mbx-dsm`'s right-pinned slot, because a row
   carries a dismiss OR a done and never both.
   🛑 QUIET, NOT A PRIMARY BUTTON. The brand fill on this panel means "the tab you are reading"; a
   solid button on every to-do row would out-shout the customer names beside it, on a list a rep scans
   rather than reads. Green only on hover — the act is safe and reversible-ish (idempotent, and the
   row simply leaves the tab), so it does not need the weight the dismiss's two-press confirm has. */
.ph-mbx-done-f { display:inline-flex; }
.ph-mbx-done {
    border:1px solid var(--line); border-radius:8px; background:#fff; cursor:pointer;
    padding:3px 9px; font:inherit; font-size:11px; font-weight:700; color:var(--mut);
    white-space:nowrap;
}
.ph-mbx-done:hover { border-color:#2f9e64; color:#1d7a4c; background:#f2fbf6; }
.ph-mbx-foot-row > .ph-mbx-tx { margin-top:0; }
/* ⚖️ The one line under a covering call's message box, replacing the "Send" button removed
   2026-08-06 (owner: the same two-press shape as the retired "Save — Callback"). Quiet type, right
   beside the control it explains — it says where the words GO, which is the only thing a rep needs
   to know now that pressing an outcome is the whole act. */
.ph-cb-covering-hint { display:block; font-size:12px; color:var(--mut); line-height:1.35; text-align:right; }
/* ⚖️ "tried 3×" ON A FOLLOW-UP ROW (owner 2026-08-06) — the attempt count that had been recorded
   since 08-05 and shown to nobody. 🛑 QUIET, NOT ALARMING: chasing a promise is the rep doing their
   job, so this is the muted fact grey of `.ph-mbx-sub` and never a warning colour — `.ph-owe-tag`
   beside it owns "Overdue", which IS a problem. Inline-block with a left margin, matching its two
   pill neighbours on `.ph-mbx-line` (see the flex note in phone-strip: a bare inline child of that
   column stretches to a full-width band). */
.ph-mbx-tries { font-size:11px; color:var(--mut); font-weight:600; }
/* ⏹️ CHASED ENOUGH — ⚖️ Gil 2026-08-19, *"use 5 attempts"*. Amber, not red: this is permission, not
   an alarm. The row is telling a rep that giving up on somebody they have rung five times without
   an answer is allowed — which is the thing reps will not do unprompted, because it feels like
   abandoning a customer. The Dismiss that acts on it already sits to its right. */
.ph-mbx-chased {
    font-size:11px; font-weight:800; color:#6b4d12;
    background:#fff8ec; border:1px solid #f0d9ac; border-radius:999px; padding:2px 8px;
}

/* ☎️ WHY THIS ROW IS ON THE LIST — "Missed call" / "After hours", ⚖️ owner 2026-08-07: *"the missed
   call screen is what we need added to Mailbox."* Follow-ups already CARRIED these (a missed caller
   becomes the same `CallbackObligation` a promised callback does) but read identically to a promise
   the rep made — the one thing the tab could not say was *they rang us and we did not answer*.
   🛑 THE BRAND TINT, NOT AN ALARM: this is a FACT about where the row came from, and rule 13 keeps
   `--danger` for compliance verdicts and off live-call surfaces entirely. Same inline-block +
   left-margin shape as its two pill neighbours, for the flex reason on `.ph-mbx-tries` above. */
/* ⚖️ WHY THIS ROW IS HERE — quiet TEXT under the clock since 2026-08-09, not the tinted pill it was.
   🛑 AND IT NOW FIRES ONLY WHEN IT SAYS SOMETHING THE TAB DOES NOT. Measured on prod: 80 open
   `queue_timeout` rows against 22 `after_hours` and 1 press-1 — so the old "Missed call" label
   rendered on 78% of the rows of a tab **titled Missed calls**, and its noise is what drowned the
   two labels that carry real information. See `MailboxController::row()` for why dropping it
   completes his 08-07 ruling rather than reversing it. */
.ph-mbx-kind { font-size:11px; color:var(--mut); }
/* 🪦 `.ph-mbx-why` — the tinted pill, retired 08-09 with the bubbles. The rule goes with the markup:
   a dead rule for a removed control is how a shape quietly returns looking correct. */
.ph-mbx-sub { font-size:11.5px; color:var(--mut); }
/* ☎️ The recording on a mailbox row (owner 2026-08-07). Full width of the row, short — it sits
   under the name and time, not beside them, because a native audio control has its own minimum
   width and would force the row to scroll. */
.ph-mbx-audio { display:block; width:100%; max-width:260px; height:30px; margin-top:4px; }
/* 🔊 THE VOICEMAIL LINE — ⚖️ Gil 2026-09-10: *"its confusing, i don't see how to listent ot teh
   voicemail… change wording to VM Message left and play button nex to to it or something."* The
   player used to live INSIDE the "What they said" disclosure, so hearing a customer meant noticing
   a small blue link named after a TRANSCRIPT, opening it, and finding the audio underneath.
   🛑 LABEL AND CONTROL ON ONE LINE, and the label names the THING while the control does the
   action — a rep must not have to expand anything to learn a message exists. It wraps on a narrow
   panel rather than squeezing the player, because a 260px audio bar with no room is a play button
   nobody can hit. */
.ph-mbx-vm { display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin-top:5px; }
.ph-mbx-vm-lbl { flex:0 0 auto; font-size:11.5px; font-weight:800; color:#0b5c63; }
/* The bar sits beside its label, so it drops the block layout and the top margin it needed when it
   was the only thing on its row. */
.ph-mbx-vm > .ph-mbx-audio { display:inline-block; width:auto; margin-top:0; }

/* ☎️ WHAT THE COACH HEARD IN THE MESSAGE — ⚖️ owner 2026-08-09: *"should all messages be transcribed
   and added to the mailbox row where we can expand and see it?"*

   🛑 THE GIST IS THE ONLY THING HERE IN READING TYPE, and the rest is deliberately quieter than it.
   A rep triaging ten messages reads ten of these and plays none — so it gets the row's ink, while
   the transcript hides behind a summary and the numbers sit in fact-grey. `--ink` at 12.5px is the
   same weight the row's own name carries: this IS the row's content now, not a note about it. */
.ph-mbx-gist { font-size:13px; color:var(--ink); line-height:1.45; margin-top:4px; }

/* ⚖️ RIDER 1 — the number they SAY, and when they asked to be called. Quiet, but the number itself
   is a live `tel:` link in brand colour: the whole point is that it is pressable, not readable. */
.ph-mbx-said { font-size:11.5px; color:var(--mut); margin-top:2px; }
.ph-mbx-said a { font-size:11.5px; }

/* 🛑 A NATIVE <details>. The panel re-renders this markup wholesale on the ~1.2s poll, so any
   open/closed state we tracked in JS would be wiped twice a second — the element keeps its own.
   The summary is a control, so it gets a pointer and the brand colour; the transcript underneath is
   a customer's own words and is set in the muted grey of a quotation, wrapping rather than
   scrolling (a scroll box inside a 372px drawer is two scrollbars fighting). */
.ph-mbx-tx { margin-top:3px; }
.ph-mbx-tx > summary { cursor:pointer; font-size:11.5px; font-weight:700; color:var(--brand); list-style:none; }
.ph-mbx-tx > summary::-webkit-details-marker { display:none; }
.ph-mbx-tx > summary::before { content:'▸ '; }
.ph-mbx-tx[open] > summary::before { content:'▾ '; }
.ph-mbx-tx > p { margin:4px 0 0; font-size:11.5px; line-height:1.45; color:var(--mut);
                 white-space:pre-wrap; overflow-wrap:anywhere; }
/* ☎️ The recording lives INSIDE the disclosure now (owner 08-09) — the words and the audio are one
   answer to "what did they say", and the collapsed row is a line shorter for every message. */
.ph-mbx-tx > .ph-mbx-audio { margin-top:7px; }

/* 🛑 THE ONE CHIP ON THIS PANEL THAT MAY WEAR THE DANGER COLOUR, and it earns the exception rule 13
   otherwise refuses: it is not a compliance VERDICT about a rep, it is an instruction about a
   CUSTOMER — this number is on the internal do-not-call list and calling it would be the breach.
   A rep glancing at the row has to see it before they see anything pressable. */
/* ⚠️ NOT UPPERCASE — it was, and that made it the one chip too wide for the shared slot above,
   breaking the alignment for every row beside it. It does not need the shout: it is the only chip
   on this panel wearing the danger colour, which is louder than capitals and costs no width. */
/* 🪦 `.ph-mbx-dnc` — retired 08-09 with the other bubbles; `.ph-mbx-flag.is-red` carries it now (renamed from `.ph-mbx-alarm` on 08-20 — it was colliding with the STRIP row of the same name). */

/* ⚖️ THE TWO ROW CONTROLS HE PICKED, 2026-08-09 — *"Dismiss and call now i can see that being good
   to add."* 🛑 THEY SIT BELOW THE ROW'S CONTENT, NOT BESIDE THE NAME. `.ph-mbx-row` is a flex
   COLUMN, so an inline control placed on the name line stretches to a full-width band — the exact
   defect that turned `.ph-mbx-new` into a 438px red stripe. Their own flex row keeps both buttons
   their natural size and lets the dismiss `<details>` open underneath without shoving anything.
   ⚠️ `align-items:flex-start` matters: without it the closed `<details>` stretches to the row's
   height and its summary drifts off the button's baseline. */
/* ☎️ THE NUMBER ON RECORD, beside the name — ⚖️ owner 2026-08-09. It inherits `.dial-phone`'s dotted
   underline and pointer (the platform-wide "double-click me" affordance), and adds only the muted
   weight that keeps the NAME the thing the eye lands on first. Inline-block with a left margin, the
   same shape as its pill neighbours, for the flex reason documented on `.ph-mbx-tries`. */
.ph-mbx-num { font-size:12px; color:var(--mut); font-weight:600; flex:0 0 auto;
              font-variant-numeric:tabular-nums; }
.ph-mbx-num:hover { color:var(--brand); }

/* ⚖️ THE BIN — owner 08-09: *"make the dismiss chip a little garbage icon. This way, it takes up
   less space."* 🛑 QUIET GREY AT REST, RED ONLY ON HOVER. It is a destructive control, but it is not
   an emergency and it must not read as one on a panel a rep opens forty times a day — rule 13 keeps
   the danger colour for compliance verdicts. The red on hover is the warning arriving exactly when
   the pointer does, and the two-press confirm behind it is unchanged. */
.cl-dsm.is-icon { display:inline-flex; }
.cl-dsm-ico { display:inline-flex; align-items:center; justify-content:center; width:26px; height:26px;
              border-radius:7px; cursor:pointer; color:var(--mut); list-style:none;
              transition:background .12s ease, color .12s ease; }
.cl-dsm-ico::-webkit-details-marker { display:none; }
.cl-dsm-ico:hover { background:#fbe9e6; color:#a1281a; }
.cl-dsm.is-icon[open] > .cl-dsm-ico { background:#fbe9e6; color:#a1281a; }

.ph-mbx-acts { display:flex; align-items:flex-start; gap:7px; margin-top:6px; flex-wrap:wrap; }
.ph-mbx-acts form { margin:0; }
.ph-mbx-acts .btn.small { font-size:11.5px; padding:4px 10px; }
/* The dismiss menu is absolutely positioned by `.cl-snz-menu`; inside a 372px scrolling drawer it
   needs to open ABOVE the fold rather than pushing the list. Its own partial owns the behaviour —
   this only bounds the width so a warning sentence cannot widen the panel. */
.ph-mbx-acts .cl-dsm-menu { max-width:250px; }

/* ⚖️ "3 filed" — what the Messages tab is holding back (dead air and the intents he ruled out of
   the pile). 🛑 IT IS THE SAME PROMISE `MAX_ROWS`' "and N more" MAKES: a rep must always be able to
   reconcile the list they are looking at with what is actually there. A filter nobody can see is
   the floor-wide 21 wearing different clothes. */
.ph-mbx-filed { font-size:11px; color:var(--mut); padding:7px 0 0; border-top:1px solid var(--line);
                margin-top:4px; }

/* ☎️ WHOSE MAILBOX — the rep picker, ⚖️ owner 2026-08-07: *"I want to be able to do it individually
   by rep… a drop down on the mailbox."* Hidden unless the server sends somebody to pick, so a rep
   never sees it. 🛑 THE NOTE BESIDE IT IS NOT DECORATION: the corner badge keeps counting the
   VIEWER'S own work while a colleague's list is on screen, which is correct and unreadable without
   a line saying so. Quiet grey — this is a statement of fact, not a warning. */
/* ⚖️ ONE ROW — *"Put the showing on same row as Missed Calls, HOt Lead etc."* 🛑 THE TABS KEEP
   THEIR OWN SCROLLER and the picker sits OUTSIDE it: four chips already overflow a narrow panel,
   and a control inside a horizontal scroller is a control that can scroll out of reach. `min-width:0`
   is what lets the tab strip shrink instead of shoving the picker off the edge.
   🛑 `flex-wrap` IS THE ESCAPE VALVE, ADDED 2026-08-12 (*"Tabs are hidden in the mailbox"*): when
   even the widened manager panel cannot hold the row, the picker WRAPS UNDER the tabs instead of
   either side hiding. Same row whenever it fits — his 08-07 ruling holds on any normal window; the
   wrap only exists so "same row" can never again cost him a tab. Row-gap 0: both sides carry their
   own padding-bottom, an 8px row gap doubled the seam when wrapped. */
.ph-mbx-tabrow { display:flex; flex-wrap:wrap; align-items:center; gap:0 8px; margin-bottom:4px;
                 border-bottom:1px solid var(--line); }
.ph-mbx-tabrow > .ph-mbx-tabs { flex:1 1 auto; min-width:0; margin-bottom:0; border-bottom:0; }
/* 🛑 IT SHARES THE ROW WITH FOUR TABS — and it no longer gives ground by SHRINKING. First cut
   carried a "Showing" label plus a 132px select and pushed Follow ups and Messages off the panel
   entirely (2026-08-07: *"Showing is blocking the tabs"*). The label is gone — a dropdown holding a
   person's name explains itself, and `title`/`aria-label` keep it for a screen reader.
   ⚖️ 2026-08-12: the old `flex:0 1 auto; min-width:0` crush was proven useless by his screenshot —
   the select had collapsed to a single letter ("G") and the Messages tab was STILL hidden; a picker
   crushed to nothing reads as broken and buys no room. Now it holds its content width and gives
   ground by WRAPPING below the tabs (see `.ph-mbx-tabrow`); the select's own `max-width` +
   `text-overflow` still stop a long name from forcing width. */
/* ⚖️ 2026-08-20: *"we should utilize teh top row for spacin and cleaning uo"*. `flex:1 0 auto` —
   **grow, never shrink**. Growing is what lets this block fill the line it has wrapped onto so the
   picker can sit left and the doors right, instead of everything huddling at one end with dead space
   beside it. 🛑 THE `0` IS THE 08-12 LESSON AND MUST NOT BECOME A `1`: shrinking crushed the select
   to a single letter ("G") in his own screenshot **and bought no room at all** — the Messages tab was
   still hidden. A picker crushed to nothing reads as broken. */
.ph-mbx-who { display:flex; align-items:center; gap:8px; flex:1 0 auto; padding-bottom:8px; }
/* 🚪 THE DOORS PIN RIGHT, and the pair of rules is because WHICH door comes first depends on the
   state: "See the whole team" is hidden once you are already in the team view (see `paintWho`), so
   the auto-margin has to move to the next one along. `[hidden]` elements still take part in sibling
   selectors, which is what makes this exact rather than a guess. */
.ph-mbx-who .ph-mbx-team-go { margin-left:auto; }
.ph-mbx-who .ph-mbx-team-go[hidden] + .ph-mbx-oversight { margin-left:auto; }
/* 🛑 `[hidden]` NEEDS `!important` HERE AND THE REASON IS THIS FILE'S OWN THIRD OFFENCE. `.btn` is
   `display:inline-flex`, which BEATS the browser's built-in `[hidden] { display:none }` — so
   `paintWho()` set the attribute, the element reported `hidden === true`, and it went on rendering
   167px wide in the middle of the row. **Measured in Chrome, not reasoned about**: `hidden` said
   true and `getComputedStyle().display` said `flex`.
   ⚠️ Its two siblings carry the same rule for the same reason and both were added the same way —
   `.ph-mbx-who[hidden]` (which had been leaking a manager-only link to every trainee since 08-07)
   and `.ph-mbx-alarm[hidden]`. **Any class in this panel that sets `display` needs this line.** */
.ph-mbx-who .ph-mbx-team-go[hidden] { display:none !important; }
/* 🛑 THE SAME `[hidden]` TRAP AS `.ph-mbx-who-n` BELOW, CAUGHT LIVE 2026-08-14: `display:flex`
   above beats the browser's own `[hidden] { display:none }`, so this block — the who-picker AND
   the "See the whole team →" manager link inside it — rendered for EVERY user from the day it
   shipped (08-07). Trainees clicked the link and got 403 from `role:` on /manage/oversight (Dee,
   3× on 08-14 alone; two more reps on 08-13). The server-side design was never wrong — the
   payload's `people` list still decides — this rule just lets the browser obey it. Do not delete
   as redundant. Guard: OversightScreenTest::test_the_picker_block_stays_hidden_when_the_server_says_so. */
.ph-mbx-who[hidden] { display:none; }
/* ⚖️ WIDE ENOUGH FOR A REAL NAME — Gil's own screenshot showed his picker reading "Gil Torr…",
   which is the control telling him it cannot hold the shortest name on the floor. */
.ph-mbx-who-s { font-size:11px; font-weight:600; color:var(--ink); background:var(--card);
                border:1px solid var(--line); border-radius:7px; padding:3px 4px;
                max-width:150px; min-width:0; text-overflow:ellipsis; }
/* The caption under the row — only while somebody else's list is open.
   🛑 `[hidden]` MUST BE HONOURED AND `display:block` DOES NOT DO IT. The browser's own
   `[hidden] { display:none }` is the SAME specificity as a class, so a later `display:block` beats
   it and the caption showed on the owner's OWN list (2026-08-07: *"Showing is blocking the tabs"* —
   he was on "Gil Torres (you)" with "Their list…" underneath). The same trap `.ph-arrival` carries
   a paragraph about in phone-strip. The explicit `[hidden]` rule below is the fix; do not delete it
   as redundant. */
.ph-mbx-who-n { display:block; margin:0 0 6px; font-size:10.5px; color:var(--mut); }
.ph-mbx-who-n[hidden] { display:none; }
.ph-mbx-list .btn.small { margin-top:9px; align-self:flex-start; }
/* Says out loud that these are the SHARED box, for as long as per-rep routing is off. */
.ph-mbx-note { font-size:11.5px; color:var(--mut); padding-bottom:7px; }
/* "and 3 more" — the remainder line. It is not decoration: the list is capped and the badge is not,
   so without this the two disagree on one panel with nothing explaining why. See Mailbox::MAX_ROWS. */
.ph-mbx-more { font-size:12px; color:var(--mut); padding:9px 0 2px; border-top:1px solid var(--line); }
.ph-mbx-more a { color:var(--brand); font-weight:700; }
/* The clear-all foot. Sits BELOW the scrolling list so it is always reachable without scrolling to
   the bottom of 29 messages — the same reason the tab strip sits above it. */
.ph-mbx-foot { display:flex; align-items:center; gap:10px; flex-wrap:wrap;
    margin-top:10px; padding-top:10px; border-top:1px solid var(--line); }
.ph-mbx-foot-note { font-size:11.5px; color:var(--mut); flex:1 1 180px; }
/* 👥 THE TEAM DOOR — ⚖️ corrected 2026-08-19 after Gil opened his mailbox, found nothing about his
   team, and asked *"how is this easy to read fo rme to see team"*. He was right: I had shipped the
   team view behind a dropdown he would have had to know to open, while the control he already reads
   as "show me the team" still navigated away to Oversight. It is now a real button, in the place he
   already looks, and it acts in place like everything else in this panel. */
.btn.small.ph-mbx-team-go {
    background:var(--brand); border-color:var(--brand); color:#fff; font-weight:800; white-space:nowrap;
}
.btn.small.ph-mbx-team-go:hover { background:#2a4179; color:#fff; }
/* The caption that says WHOSE work is on screen. It is not decoration: the one thing this panel may
   never do is let a manager read somebody else's list as their own. */
.ph-mbx-team-n {
    font-size:11.5px; color:var(--mut); margin:6px 0 0; line-height:1.5;
}
.ph-mbx-team-n[hidden] { display:none; }
.ph-mbx-team-n b { color:var(--ink); }
/* 🪦 `.ph-mbx-team-back` / `.ph-mbx-team-back-top` — the "← just mine" button, retired 2026-08-20
   with its markup and its handler. ⚖️ Gil: *"what does just mine? wouldnt we jsut go to drop down
   and do that?"* The picker is on screen, it reads `Whole team`, and changing a dropdown you can
   see is not a thing anyone has to be taught. Deleted rather than left behind: a live rule for
   markup that no longer exists is how a control quietly returns looking correct. */
/* 📼 THE SHARED ANSWERPHONE — quiet on purpose. It reaches the FLOOR's box (21 files on the dialer),
   which is not any one rep's work, so it must not compete with the tabs above it. It exists because
   retiring the Messages tab would otherwise have stranded that box: it was the only door. */
.ph-mbx-shared {
    border:0; background:transparent; color:var(--mut); cursor:pointer;
    font-size:11.5px; font-weight:700; font-family:inherit; padding:0; margin-left:auto;
}
.ph-mbx-shared:hover { color:var(--ink); text-decoration:underline; }

/* 🛑 THE SHAKE — `transform` ONLY, AND THIS IS A HARD RULE ON THIS ELEMENT.
   `.ph-strip` is sticky at the top of EVERY authed page. Anything that changes the button's box —
   margin, padding, width, border-width, font-size — reflows the bar and shifts every row on the
   page underneath it. This file has already paid for that exact mistake once: the arrival slot
   collapsed because `visibility:hidden` never beat `[hidden]{display:none}`, the summary moved
   146px and the bar grew 7.5px on every single call. `transform` and `rotate` are composited and
   affect NO layout, which is why they are the only two properties this animation touches.
   ⚠️ Honours a reduced-motion preference: the badge still updates, so nothing is lost by not moving.
   Fires only for a hot lead or a voicemail ROUTED TO THIS REP — never the shared box's count. */
@keyframes phMailboxShake {
    0%, 100% { transform:translateX(0) rotate(0deg); }
    10%, 50%, 90% { transform:translateX(-3px) rotate(-7deg); }
    30%, 70% { transform:translateX(3px) rotate(7deg); }
}
.ph-strip .btn.ph-mbx-btn.is-shaking { animation:phMailboxShake .5s ease-in-out 2; }
@media (prefers-reduced-motion: reduce) {
    .ph-strip .btn.ph-mbx-btn.is-shaking { animation:none; }
}

/* THE SAVE TOAST (owner 2026-07-24: "save on all should be pop up little indicator that says saved
   and disappears") — replaces the old .flash.ok banner for session('status'). Pure CSS lifecycle:
   fades itself out and drops out of the layout, no JS to fail. Errors keep their banner. */
.save-toast {
    position:fixed; top:52px; left:50%; transform:translateX(-50%); z-index:var(--z-toast);
    padding:9px 16px; border-radius:10px; background:#1e9e5a; color:#fff;
    font-size:13.5px; font-weight:700; box-shadow:0 10px 26px rgba(23,18,37,.35);
    animation:saveToastLife 2.6s ease forwards;
}
@keyframes saveToastLife {
    0% { opacity:0; transform:translateX(-50%) translateY(-6px); }
    8%, 78% { opacity:1; transform:translateX(-50%) translateY(0); }
    100% { opacity:0; visibility:hidden; }
}

/* DOUBLE-CLICK-TO-CALL (owner 2026-07-24). The number reads as an action, not decoration: a dotted
   underline + phone cursor hint on hover. The toast is fixed under the strip, centered — feedback
   for a gesture that otherwise changes nothing on the page it was made from. */
.dial-phone { cursor:pointer; border-bottom:1px dotted currentColor; }
.dial-phone:hover { color:var(--brand); }
.dial-toast {
    position:fixed; top:52px; left:50%; transform:translateX(-50%); z-index:var(--z-toast);
    padding:9px 16px; border-radius:10px; background:#1e9e5a; color:#fff;
    font-size:13.5px; font-weight:700; box-shadow:0 10px 26px rgba(23,18,37,.35);
}
.dial-toast.bad { background:#d2402a; }
.dial-toast[hidden] { display:none; }

/* The status line — the dialer's own answer, shown rather than swallowed. `.bad` is a refusal. */
.ph-cb-status { font-size:12.5px; font-weight:700; color:#dbe6fb; max-width:260px; }
.ph-cb-status.bad { color:#ffc9be; }
.ph-cb-status[hidden] { display:none; }

.ph-actions .btn.ph-on { background:#1e9e5a; color:#fff; }
.ph-actions .btn.ph-on:hover { background:#1a8b4f; }
.ph-actions .btn.ph-muted { background:rgba(255,255,255,.09); color:#9fb0d8; cursor:not-allowed; }
/* State gating — the poll swaps two data attributes; CSS shows exactly one variant of each slot.
   !important on PURPOSE, both directions: the slot styles above (e.g. .ph-actions .btn.ph-ready)
   out-rank these selectors, and a visibility gate that loses a specificity fight shows every
   state at once. Scoped to the .ph- family only. */
.ph-when-off, .ph-when-ready, .ph-when-paused, .ph-when-incall, .ph-when-auto,
.ph-when-wrapup { display:none !important; }
/* Its own rule, deliberately: the wrapup guard test pins the line above as an exact string, and a
   new class appended there silently breaks a gate test that exists to stop slots leaking into
   every state. Same default (hidden), no shared literal. */
.ph-when-dialable { display:none !important; }
/* 🛑 "PAUSING…" (owner 2026-07-25: "if pause takes time to land … instead of pause, it should say
   pausing then pause"). A dial from READY pauses first and can wait seconds for the agent screen to
   apply it; the chip says so instead of sitting there looking broken. Client-only state — the box
   has no "pausing", so the strip stamps data-ph-dialing for the life of the request. The chip also
   goes amber, because during those seconds the rep is NOT taking calls. */
.ph-chip-pausing { display:none; }
[data-ph-dialing="1"] .ph-chip-word { display:none; }
[data-ph-dialing="1"] .ph-chip-pausing { display:inline; }
[data-ph-dialing="1"] .ph-actions .btn.ph-ready { background:#e8a23d; color:#3a2703; cursor:default; }
[data-ph-dialing="1"] .ph-actions .btn.ph-ready .dot { background:#3a2703; box-shadow:none; animation:phPausePulse 1s ease-in-out infinite; }
@keyframes phPausePulse { 0%,100% { opacity:1; } 50% { opacity:.35; } }
@media (prefers-reduced-motion:reduce) { [data-ph-dialing="1"] .ph-actions .btn.ph-ready .dot { animation:none; } }
/* THE READY / PAUSED MENUS (owner 2026-07-25: "maybe when we press ready, a drop down or something
   to pause or logout?"). Same light card as the transfer panel — form-ish controls are unreadable
   on the strip's blue. Wider than the transfer box because each row is a full sentence: the rep
   must be able to tell "stay logged in" from "off the phones" WITHOUT hovering, since one of those
   costs a re-login. */
.ph-state-panel { width:300px; gap:6px; }
.ph-state-panel form { margin:0; display:block; }
.ph-state-panel .btn.small { width:100%; justify-content:flex-start; text-align:left; font-size:13px; padding:8px 10px; }
.ph-state-panel .btn.ph-menu-ready { background:#1e9e5a; color:#fff; }
.ph-state-panel .btn.ph-menu-ready:hover { background:#1a8b4f; }
.ph-state-panel .btn.ph-menu-pause { background:#e8a23d; color:#3a2703; }
.ph-state-panel .btn.ph-menu-pause:hover { background:#f0ad4a; }
/* Logout is the costly one — quiet by default so it is never the reflex click. */
.ph-state-panel .btn.ph-menu-logout { background:rgba(15,20,45,.08); color:var(--ink); }
.ph-state-panel .btn.ph-menu-logout:hover { background:rgba(15,20,45,.16); }
/* ⏱ THE OUTCOME STILL OWED (2026-09-02 — Carlos's twelve refused presses; the reasoning is on the
   button in phone-strip.blade.php). Hidden by default and shown ONLY off `data-ph-settle`, which
   the poll rewrites every tick — so it disappears by itself the moment the outcome lands.

   🛑 WITHHELD ON incall/wrapup ON PURPOSE, and not for tidiness: those two states are the bar's
   measured worst case for height (Gil, 08-17: "when calls come it gets TALL"), and during them the
   capsule is already open with the same buttons on it. This chip is for the states where it is NOT
   — a rep back at ready/paused/off with a debt the screen has forgotten.

   Amber, not red: an owed outcome is a thing to finish, never a fault. It reads against the strip's
   blue at the same weight as the Paused chip, which is the other "you are not taking calls" fact.

   🛑 `order:0` IS DECLARED, NOT INHERITED, AND THAT IS DELIBERATE. Gil pinned this row by `order`
   (08-28: "from right to left: Ready, Phone, Dialer and Dial") and his four carry 1/2/3 — so a new
   child with no rule of its own would silently take the front of HIS row by default. Stating it
   keeps the placement a decision: the chip sits ahead of his four, and their order to each other is
   untouched. It shows only in the three idle states, so the bar's measured incall height is
   likewise untouched.

   🛑 COLOURED THROUGH `.ph-actions .btn.ph-settle`, NOT `.ph-settle` — the strip's own button rules
   are two classes deep, so a single-class rule loses to them and the chip renders as bare text on
   the blue. That is not a hypothetical: it is what the first cut of this rule did, caught in the
   side-panel render. Same shape as `.ph-actions .btn.ph-paused` directly above, which is also the
   look being borrowed. */
.ph-settle { display:none !important; order:0; }
.ph-actions .btn.ph-settle {
    align-items:center; gap:7px; background:#e8a23d; color:#3a2703; font-weight:800;
}
.ph-actions .btn.ph-settle:hover { background:#f0ad4a; }
.ph-settle-who { font-weight:700; opacity:.85; margin-left:6px; }
/* ⏱ "Not now" — the settle capsule's only exit, shown by JS and hidden the rest of the time.
   🛑 THE `[hidden]` RULE IS NOT BELT-AND-BRACES: `.btn` sets `display:inline-flex`, and a class
   that sets `display` BEATS the browser's own `[hidden] { display:none }`. Without this the button
   would sit on every ordinary call-end capsule offering a way to walk away from an outcome the rep
   owes — the exact opposite of what this file exists to enforce. */
.ph-settle-close[hidden] { display:none !important; }
.ph-settle-who:empty { display:none; }
[data-ph-settle="1"][data-ph-state="off"] .ph-settle,
[data-ph-settle="1"][data-ph-state="ready"] .ph-settle,
[data-ph-settle="1"][data-ph-state="paused"] .ph-settle { display:inline-flex !important; }
[data-ph-state="off"] .ph-when-off { display:inline-flex !important; }
[data-ph-state="ready"] .ph-when-ready { display:inline-flex !important; }
[data-ph-state="paused"] .ph-when-paused { display:inline-flex !important; }
/* 🛑 A PAUSED PARTICIPANT HAS TWO TRUTHS, AND UNTIL 2026-08-14 ONLY ONE FACE (rings-first's green
   "Ready", correct for a rep waiting on the ring — the box's paused underneath is machinery). On
   the AUTODIALER paused means the opposite: the machine is dialling nothing for you. That chip read
   "Ready" through a live campaign's whole first afternoon, which is why nobody could see the rep was
   never resumed. Both faces now ship and `data-ph-auto` — the poll's own box-truth flag, the same
   one the join banner reads — picks. Specificity is deliberate: three attributes beat the two-attr
   `.ph-when-paused` rule above, so the resting face hides on an auto session without touching it. */
.ph-when-paused-auto { display:none !important; }
[data-ph-state="paused"][data-ph-auto="1"] .ph-when-paused-auto { display:inline-flex !important; }
[data-ph-state="paused"][data-ph-auto="1"] .ph-when-paused-resting { display:none !important; }
/* 🛑 DIALABLE = READY **OR** PAUSED (owner 2026-07-25). The box refuses external_dial unless the
   agent is PAUSED, so pressing ⌨ Dial pauses them — and a ready-only gate then HID the button
   mid-flow, leaving no way to retry and no visible way back. Off (no session) and incall/wrapup
   (a live call) still withhold it. */
[data-ph-state="ready"] .ph-when-dialable,
[data-ph-state="paused"] .ph-when-dialable { display:inline-flex !important; }
[data-ph-state="incall"] .ph-when-incall { display:inline-flex !important; }
/* 📐 THE BAR MUST NOT GROW WHEN A CALL ARRIVES — ⚖️ Gil 2026-08-17: *"when calls come it gets TALL,
   where it is 1/3 of the screen size… they can be on the phone with a client and it makes it where
   you cant really see what you are working."*

   MEASURED, not guessed (real `incall` state, real markup, `getBoundingClientRect`):

     effective width │ strip height │ rows │ share of screen
     ────────────────┼──────────────┼──────┼────────────────
       1440 · 1280   │     53px     │  1   │      6%
       1024          │    *94px*    │  2   │     12%
        640 (≈200% browser zoom on a 1280×800 laptop) │ 94px │ 2 │ **23.5%**

   The strip is `flex-wrap:wrap` and `position:sticky; top:0`, so the moment it takes a second row
   it is +77% taller AND every row of every authed page below it moves down — mid-call, while the
   rep is trying to read the customer's file. Zoom is what turns this from ugly into Gil's "1/3":
   it shrinks the viewport in BOTH axes, so the bar's share of the screen climbs while the wrap gets
   more likely.

   🛑 THE FIX IS NOT `flex-wrap:nowrap`, AND THAT IS THE WHOLE POINT. The wrap is what keeps **Hang
   up** reachable when the row overflows, and burying Hang up is a fault this codebase has already
   paid for TWICE in Gil's own words ("i cant see anyting on top to hang up", 2026-07-22; "could not
   reach Hang up, and had to end the call from VICIdial"). Nothing here may ever remove a row that
   Hang up could be on.

   ⇒ INSTEAD, THE CHROME THAT IS NOT ABOUT THIS CALL STANDS DOWN WHILE A CALL IS UP. A rep mid-call
   needs who is on the line and the controls; they do not need their own extension number, the sounds
   help drawer, or the voicemail/mailbox chips — all of which are back the instant the call ends.
   That frees ~173px of row (measured: 51+61+19 plus three 14px gaps), which is what keeps the bar on
   ONE row at the widths reps actually use.

   ⚠️ `.ph-arrival` and `.ph-caller` are deliberately NOT in this list. They are the call, and their
   reserved geometry is load-bearing — see the CORRECTION block on `.ph-arrival`, where collapsing a
   slot moved the summary 146px sideways and grew the bar 7.5px. Do not "save more room" there.
   ⚠️ `.ph-alerts` stays too: it is 22px, it is not what wraps the row, and it is how a rep learns
   something is wrong while they are busy. */
[data-ph-state="incall"] .ph-not-when-incall,
[data-ph-state="wrapup"] .ph-not-when-incall { display:none !important; }
/* WRAPUP — the customer hung up and the rep has not dispositioned yet (2026-07-22). The call bar
   STAYS (it is the way back to READY), so wrapup shows .ph-when-incall too; what leaves is
   everything that acts on a live customer — there is no call left to hang up or transfer. */
[data-ph-state="wrapup"] .ph-when-incall { display:inline-flex !important; }
[data-ph-state="wrapup"] .ph-when-wrapup { display:inline-flex !important; }
[data-ph-state="wrapup"] .ph-not-when-wrapup { display:none !important; }
/* 🛑 PAUSED SHOWS THE CALL BAR TOO — THE ON-SCREEN FAILOVER (owner 2026-07-24, live: hung up, no
   dispo panel came, Carlos stuck on Paused with "no way for us to do it either"). The box parks
   an agent PAUSED after a call whether or not the wrapup signal fired, so the dispositions must
   be reachable HERE, not only in states our detection happened to catch. Same trim as wrapup:
   everything that acts on a live customer leaves. */
[data-ph-state="paused"] .ph-when-incall { display:inline-flex !important; }
[data-ph-state="paused"] .ph-not-when-wrapup { display:none !important; }
[data-ph-auto="1"] .ph-when-auto { display:inline-flex !important; }
/* 🛑 WHICH FACE — "on the campaign" is not "the machine is calling for you", and the chip showing
   the first while meaning the second cost Gil's floor 45 minutes on 2026-09-04. CLOSER on the auto
   campaign already maps to `paused` in AgentStatus (the dialer skips CLOSER), so the state
   attribute is the honest discriminator and no new data is needed. Both selectors carry one more
   attribute than the base rule above, so they win on specificity whatever the source order. */
[data-ph-auto="1"][data-ph-state="paused"] .ph-when-auto-live { display:none !important; }
[data-ph-auto="1"]:not([data-ph-state="paused"]) .ph-when-auto-idle { display:none !important; }
/* Amber, not green: during this the rep is joined and NOT being called for — the same vocabulary
   the "Pausing…" chip already uses for "you are not taking calls right now". */
.ph-autodial.ph-when-auto-idle { background:rgba(232,162,61,.20); border-color:rgba(232,162,61,.75); }
.ph-autodial.ph-when-auto-idle i { color:#f0c07a; }
[data-ph-auto="1"] .ph-when-not-auto { display:none !important; }
[data-ph-state="paused"] .ph-not-when-paused { display:none !important; }
/* The one-tap manual dial: a bare phone emoji, no background (owner round 4) — the accessible
   name ("Call a customer") rides aria-label + title. */
.ph-dial { font-size:21px; line-height:1; text-decoration:none; padding:0 3px; }
.ph-dial:hover { transform:scale(1.12); }
@media (max-width:860px) {
    .ph-strip { align-items:flex-start; }
    .ph-actions { margin-left:0; width:100%; }
    .ph-actions .btn, .ph-actions .ph-off { flex:1 1 auto; justify-content:center; }
}

/* ---- The pop's New-caller card — 🛑 RETIRED 2026-08-06 ----
   Owner: *"who's calling is supposed to go in phone bar only now"*, after the card and the dispo
   modal shared his screen. The whole `.pop-card` / `.pop-side` / `.pop-when-*` / `.pop-search`
   family went with the markup; its identity half lives on `.ph-arrival` + `.ph-caller`, and its one
   surviving act is `.ph-newfile`, both up in the call-bar block.
   ⚠️ TWO RULINGS DIED HERE AND THE NEXT PERSON MUST NOT RE-DERIVE THEM: the 07-22 corner-yield
   (`[data-ph-state="wrapup"] .pop-card.pop-side` moved the card to the bottom so it stopped
   covering the disposition panel) and its short-window follow-up. Both existed only because two
   slabs fought over one corner. **There is one slab now.** If a floating call surface is ever
   reintroduced, it inherits that fight — read this before drawing it. */

/* THE CALL-TYPE STRIP (owner 2026-07-20) — a full-width coloured header on the call surface; the
   colour and the words together name the call type. Hot leads jump; nothing else can be mistaken
   for one. Amber callback text is dark for contrast (matches the paused chip). */
.call-type-strip { display:flex; align-items:center; gap:8px; padding:9px 14px; border-radius:10px;
    margin:0 0 12px; font-size:14px; font-weight:800; letter-spacing:.02em; }
.call-type-strip .ct-icon { font-size:16px; line-height:1; }
.call-type-strip.ct-hot { background:#e8442a; color:#fff; }
.call-type-strip.ct-inbound { background:#2f6fd6; color:#fff; }
.call-type-strip.ct-callback { background:#e8a23d; color:#3a2703; }
/* WE placed this one (green) and a colleague handed this one over (slate) — added 2026-07-20 with
   the call bar. Deliberately the two quietest colours in the set: neither is a lead arriving cold
   off a line, so neither may compete with the red that means exactly that. */
.call-type-strip.ct-outbound { background:#1e9e5a; color:#fff; }
.call-type-strip.ct-internal { background:#4a5872; color:#fff; }

/* THE RECORDING DISCLOSURE (owner wording; unmissable by design). 🛑 Moved here from the runner's
   own <style> 2026-07-30, the day the guided page grew the line — a shared partial needs shared CSS,
   and the guided modal rendered UNSTYLED while these rules lived on the other page. One family, two
   surfaces, one home. */
.sr-disclose { border-left:5px solid var(--brand-ink); padding:14px 18px; margin-bottom:12px;
    background:linear-gradient(120deg,var(--brand),var(--brand-2)); color:#fff;
    box-shadow:0 10px 24px -12px rgba(53,82,155,.9);
    animation:sr-read-me .45s ease-in-out .35s 2; }
.sr-disclose .sr-say-label { color:#cdd9f6; font-size:10.5px; font-weight:800;
    text-transform:uppercase; letter-spacing:.06em; margin-bottom:6px; }
.sr-disclose .sr-say-text { font-size:17px; font-weight:700; line-height:1.45; color:#fff; }
@media (prefers-reduced-motion:reduce) { .sr-disclose { animation:none; } }
.sr-disclose-modal { position:fixed; inset:0; z-index:var(--z-modal); background:rgba(15,18,38,.6);
    display:flex; align-items:center; justify-content:center; padding:24px; }
.sr-disclose-modal .sr-disclose { max-width:560px; margin:0; }
@keyframes sr-read-me { 0%,100% { transform:translateX(0); }
    30% { transform:translateX(-6px); } 70% { transform:translateX(6px); } }
/* 🛑 `pop-slide` OUTLIVED THE CARD THAT NAMED IT. The pop card was retired 2026-08-06 and this
   went with it in the first pass — but `.hs-toast` animates with it (three rules down), so deleting
   it silently killed the hot-seat toast's entrance. Kept, and renamed nothing: a keyframe rename is
   a second edit at every call site for no gain. If the last user goes, this goes with it. */
@keyframes pop-slide { from { opacity:0; transform:translateX(28px); } to { opacity:1; transform:none; } }

/* THE HOT SEAT (2026-07-21): the arena's motion. Keyframes live here, same as pop-slide/sr-read-me —
   the .hs-* rules that USE them stay in drill/hot-seat.blade.php's own <style>. Heat/amber + brand +
   green only; the compliance alarm red is never one of these (design invariant #1). */
@keyframes hs-ember { 0%,100% { text-shadow:0 0 10px rgba(232,115,12,.45), 0 0 22px rgba(245,166,35,.25); }
    50% { text-shadow:0 0 18px rgba(232,115,12,.75), 0 0 40px rgba(245,166,35,.5); } }
@keyframes hs-pulse { 0% { transform:scale(.4); opacity:0; } 35% { opacity:1; } 100% { transform:scale(1.35); opacity:0; } }
@keyframes hs-snap { 0% { transform:scale(.86); opacity:0; } 60% { transform:scale(1.03); } 100% { transform:scale(1); opacity:1; } }
@keyframes hs-thump { 0% { transform:scale(2.4) rotate(-9deg); opacity:0; } 55% { transform:scale(.9) rotate(3deg); opacity:1; } 100% { transform:scale(1) rotate(-4deg); opacity:1; } }
@keyframes hs-blink { 0%,100% { opacity:1; } 50% { opacity:.25; } }
@keyframes hs-rise { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }
/* THE HOT SEAT staff toasts (2026-07-21): the floor's corner pop when a trainee masters an objection
   or graduates the ladder — so a manager can make the announcement. Shared vocabulary (rides every
   authed staff page via partials/hot-seat-toasts, same as .pop-card), reusing the pop's top-right
   geometry and slide-in. Celebration palette — brand blue / success green — NEVER the compliance
   alarm red (this is a fun surface, design invariant #1). Stacks so a busy floor never loses one. */
.hs-toast-stack { position:fixed; top:76px; right:22px; z-index:var(--z-toast); display:flex; flex-direction:column; gap:10px; width:320px; max-width:calc(100% - 44px); pointer-events:none; }
.hs-toast {
    position:relative; pointer-events:auto; cursor:pointer;
    background:#fff; border:1px solid var(--line); border-left:4px solid #137a45;
    border-radius:12px; padding:12px 14px; box-shadow:0 10px 34px rgba(0,0,0,.22);
    animation:pop-slide .25s ease; overflow:hidden;
}
.hs-toast.hs-toast-grad { border-left-color:var(--brand); }
.hs-toast b { display:block; font-size:14px; color:var(--brand-ink); margin:0 0 2px; }
.hs-toast span { font-size:12.5px; color:var(--mut); }
.hs-toast canvas { position:absolute; inset:0; pointer-events:none; }
@media (prefers-reduced-motion:reduce) { .hs-toast { animation:none; } }
/* The BOARD surface (owner 7-18): the pipeline kanban is the SECOND approved wide surface (after
   the call surface) — full viewport width so another column fits. Same body-class seam, same rule:
   the override lives HERE, never as an in-view .wrap rule (design-system rule 8). */
/* ---- Filter bar (rep-CRM Stage 1, shared) ----
   One wrapping row above a list: search box + narrowing selects + one submit. LAYOUT only —
   widths and flex, never the field box itself (padding/border/font stay the shared contract;
   pair with `.form-compact` on the form for the dense-ops size). A GET form, so every filter
   is a bookmarkable URL and the paginator can carry it via withQueryString(). */
/* ⚖️ THE SEARCH ROW (Gil 2026-08-27: *"The whole search area has to be cleaned up"*). One box on its
   own line — a rep looking for a person meets a search box, not a search box wedged among five
   dropdowns. The dropdowns keep working; they fold behind one button.

   🛑 SHARED VOCABULARY, NOT THIS SCREEN'S PRIVATE CSS. `ops/crm/index.blade.php` owns an in-view
   block under the `crmadd-` prefix (StyleOwnershipTest), and that prefix means *add a lead* — a
   search control wearing it would be a name that lies. A genuinely new component belongs here.

   🛑 THE FOLD IS A NATIVE <details>, like the ＋ beside it: open costs ZERO JavaScript, so the
   button cannot become the dead control this repo keeps paying for. Inputs inside a CLOSED details
   still submit with the form, which is what lets the filters fold without changing what a Filter
   press does. */
.findfold { flex:0 0 auto; }
.findfold > summary { list-style:none; cursor:pointer; user-select:none; }
.findfold > summary::-webkit-details-marker { display:none; }
/* ⚠️ AN ACTIVE FILTER MUST NEVER HIDE. A narrowing the rep cannot see is a screen lying about what
   it is showing — so a live filter marks the button, and the view opens the fold on top of that. */
.findfold > summary.on { background:linear-gradient(135deg,var(--brand),var(--brand-2)); color:#fff; border-color:transparent; }
/* The panel: the five selects, laid out the way the one row used to lay them out. */
.findfold-panel { display:flex; align-items:center; gap:9px; flex-wrap:wrap; width:100%; margin-top:10px; }

/* ⚖️ WHERE YOU JUST WERE (Gil 2026-08-27: *"Can we show a list of recently viewed files."*) — a row
   of names under the search box. Chips, not a table: it is a way BACK, and a second list to read
   would compete with the real one underneath it. Scrolls sideways rather than wrapping to a block. */
.trail { display:flex; align-items:center; gap:8px; margin:-4px 0 18px; overflow-x:auto; padding-bottom:2px; }
.trail > * { flex:0 0 auto; }
.trail-label { font-size:12.5px; font-weight:700; color:var(--mut); }

.filter-bar { display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin-bottom:16px; }
.filter-bar input[type=search], .filter-bar input[type=text] { flex:2 1 200px; width:auto; min-width:0; }
.filter-bar select { flex:1 1 128px; width:auto; min-width:0; }
.filter-bar .btn { flex:0 0 auto; }

/* ---- Paginator (`partials/pager`) ----
   Laravel's default pagination views are Tailwind markup this platform never loads (they render
   off-system) — lists paginate with `{{ $rows->links('partials.pager') }}` instead. Square page
   chips; the current page wears the brand gradient like a sidebar active state. */
.pager { display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:14px; }
.pager .pg {
    min-width:32px; height:32px; padding:0 9px; display:inline-flex; align-items:center; justify-content:center;
    border-radius:9px; background:#f1f4fa; color:var(--brand-ink); font-size:13px; font-weight:700;
    text-decoration:none; box-shadow:inset 0 0 0 1px var(--line);
}
.pager a.pg:hover { background:#e9eef8; color:var(--brand); }
.pager .pg.on { background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#fff; box-shadow:0 6px 14px -8px rgba(53,82,155,.9); }
.pager .pg.gap, .pager span.pg[aria-disabled=true] { background:transparent; box-shadow:none; color:var(--mut); }

/* PQ Worksheet panel (2026-07-18) — the paper pre-qual sheet as a write-through form. Shared
   vocabulary: the panel rides both the My Leads person page and the in-call FILE rail. */
.sr-worksheet .ws-section { margin-bottom:12px; }
.sr-worksheet details.ws-section > summary { cursor:pointer; list-style:revert; }
/* 🛑 200px tracks put four boxes on a row and ran the sheet long (owner 2026-08-01: *"utilize space
   horizontally so it is not so long vertically"*). Most of these hold a word or a short number, so
   at 158px the same card carries six per row and loses a third of its height. `auto-fill` still
   collapses the count on a narrow window, so nothing clips — this widens the ceiling, not the floor. */
.ws-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(158px,1fr)); gap:9px 12px;
    /* 🛑 CONTROLS LINE UP EVEN WHEN A LABEL WRAPS. Each `.ws-f` is a column of label-then-box, so a
       two-line caption used to push its own box ~15px below every neighbour and the row read as
       broken. Measured 2026-08-12: SIX rows at 1280px and TEN at 1500px, all of them the same one
       cause — *"BK / foreclosure / short sale last 7 yrs"*, *"Anything else about what they want"*,
       *"Other person — signing or coming off?"*, and so on. Bottom-aligning puts every box on one
       line and lets the captions take the height they need above it. */
    align-items:end; }
.ws-f { display:flex; flex-direction:column; gap:3px; font-size:12px; color:var(--mut); }
.ws-f > span { font-weight:600; }
/* ⚖️ 2091 — a cell label that jumps to its question on the call screen wears the SAME face as the
   plain span (his paper layout gains no chrome); the pointer and a hover underline are the whole
   affordance, and only labels whose field a live question actually asks render as this at all. */
/* ⚠️ THE BUTTON FORM NEEDS THE BROWSER'S OWN CHROME STRIPPED, or the label renders as a grey 3D
   control in the middle of the sheet. `font:inherit` is spelled out per-property on purpose: the
   `font:` shorthand rejects `inherit` as a family and the browser drops the WHOLE declaration — the
   mistake that left this sheet's other buttons at Chrome's 13.33px default while a comment claimed
   they had been made smaller. */
/* 🛑 THE CHROME COMES OFF THE CLASS, NOT OFF A LIST OF PARENTS — 2026-09-08.
   ⚖️ Gil, on the live sheet: *"Why is email a button and the rest of the sheet is not like that
   when you want to go to the script for that question."*
   🛑 THIS IS THE SAME DEFECT AS 2026-08-23, AND IT CAME BACK BECAUSE THE FIX ENUMERATED HOSTS.
   `installJump` puts these classes on whatever caption can carry a jump, so every rule below that
   names a PARENT (`.ws-sub-label`, `.s2-dgroup-name`, `.ws-lien-head .ws-lien-cell`, `.ws-f`,
   `.ws-lien-rowlab`) is a list that a new host silently falls off. Measured with
   `getComputedStyle`: 10 of 115 jump buttons were drawing Chrome's default control — `2px outset`
   border, `#efefef` fill, Arial 13.3px black — beside siblings set in Inter 8px/800 uppercase grey.
   The ten sat in FOUR hosts nobody had added: `.ws-vlabel` (Email, Mortgage late in last 12 mos?,
   Own the land?, Their Value, Anything else about the property, Minimum or more on the cards, More
   than one?), `.ws-b-sechead` (Residence address), `.ws-oth-toggle` (Any other properties?) and
   `.ws-vlabel.ws-vals-cap` (Zillow).
   ⇒ The strip is unconditional on the class now, so a NEW host cannot re-create this. The
   parent-scoped rules below still stand and are still useful — they carry the TYPE each caption
   needs — but none of them is load-bearing for the chrome any more.
   ⚠️ SAME CONTRACT AS BEFORE: type inherited from the caption it replaced, no second styled thing
   in a dense grid, and the dotted underline waits for hover. */
button.ws-jump, button.ws-capjump, button.ws-rowjump {
    appearance:none; -webkit-appearance:none; background:none; border:0; border-radius:0;
    padding:0; margin:0; font:inherit; color:inherit; letter-spacing:inherit;
    text-transform:inherit; text-align:inherit; cursor:pointer; }

.ws-f > button.ws-jump { appearance:none; background:none; border:0; padding:0; margin:0;
    font-family:inherit; font-size:inherit; line-height:inherit; text-align:left; }
/* ⚖️ IT LOOKS CLICKABLE WITHOUT BEING SHOUTY — the affordance double-click could never carry (Gil
   2026-08-20). Dotted, because a solid underline on every jumpable label would draw 60 lines across
   the sheet. */
.ws-f > button.ws-jump { text-decoration:underline dotted; text-underline-offset:2px;
    text-decoration-color:#c3c0d4; }
.ws-f > button.ws-jump:hover { color:var(--brand); text-decoration-color:var(--brand); }
/* 🛑 A CHOICE ROW'S LABEL KEEPS ITS OWN WEIGHT AND COLOUR. `.ws-f > .ws-jump` (0,2,0) sets 600/inherit
   for the generic grid; a choice label carries `ws-vlabel` too and is 700/#4a5170, so without this
   the goal and cash-reason captions would have quietly changed weight and colour the moment they
   became jumpable — a styling regression bought by a feature, which is the kind nobody reports. */
.ws-f > .ws-jump.ws-vlabel { font-weight:700; color:#4a5170; }
/* ⚖️ A GRID ROW LABEL THAT JUMPS ("1st", "2nd", "B1") — Gil 2026-08-20. It must look EXACTLY like the
   label it replaces until you point at it: these sit in a dense table where a second styled thing per
   row is noise, and he has struck furniture off this sheet three times. Chrome stripped, type
   inherited, and the only standing hint is the pointer; the dotted underline waits for hover. */
.ws-lien-rowlab > .ws-rowjump { appearance:none; background:none; border:0; padding:0; margin:0;
    font:inherit; color:inherit; cursor:pointer; text-align:inherit; }
.ws-lien-rowlab > .ws-rowjump:hover { color:var(--brand);
    text-decoration:underline dotted; text-underline-offset:2px; }
/* A block caption that jumps keeps the caption's own type exactly — same rule as the row label: in a
   dense card a second styled thing is noise, so the only standing hint is the pointer. */
.ws-sub-label > .ws-capjump { appearance:none; background:none; border:0; padding:0; margin:0;
    font:inherit; color:inherit; letter-spacing:inherit; text-transform:inherit; cursor:pointer; }
.ws-sub-label > .ws-capjump:hover { color:var(--brand);
    text-decoration:underline dotted; text-underline-offset:2px; }
/* 🛑 EVERY OTHER HOST OF A `.ws-capjump`, AND THEY WERE RENDERING AS RAW GREY OS BUTTONS. The rule
   above names ONE parent (`.ws-sub-label`), but `installJump` puts this class wherever a caption can
   carry a jump — so a band caption and a grid column header got the class, inherited nothing, and
   drew Chrome's default control: `2px outset` border, `#efefef` fill, Arial 13.3px BLACK, sitting
   beside siblings set in Inter 8px/800 uppercase grey. Measured with `getComputedStyle`, not read
   off the markup — the button IS there and IS wired, which is exactly why no handle count could see
   it. The debts bands ("Credit cards", "Auto loans" …) have shipped that way since 2026-08-23.
   ⚠️ SAME CONTRACT AS THE OTHER TWO: chrome stripped, type INHERITED from the caption it replaced,
   and the only standing hint is the pointer — a dense grid gets no second styled thing, which is the
   rule Gil has enforced by striking furniture off this sheet three times. The dotted underline waits
   for hover.
   ⚠️ ONE BLOCK, NOT THREE — a class styled in two places is the silent-by-construction trap this
   stylesheet has paid for twice in one day; the `.ws-sub-label` rule above stays the only other. */
.s2-dgroup-name > .ws-capjump,
.ws-lien-head .ws-lien-cell > .ws-capjump,
.ws-lien-head .ws-b-cell > .ws-capjump { appearance:none; background:none; border:0; padding:0;
    margin:0; font:inherit; color:inherit; letter-spacing:inherit; text-transform:inherit;
    text-align:inherit; cursor:pointer; }
.s2-dgroup-name > .ws-capjump:hover,
.ws-lien-head .ws-lien-cell > .ws-capjump:hover,
.ws-lien-head .ws-b-cell > .ws-capjump:hover { color:var(--brand);
    text-decoration:underline dotted; text-underline-offset:2px; }
.ws-f > .ws-jump { font-weight:600; color:inherit; text-decoration:none; cursor:pointer; }
.ws-f > .ws-jump:hover { color:var(--brand); text-decoration:underline dotted; }
.ws-f-wide { grid-column:1 / -1; }
/* 🛑 TWO GRID BOXES HOLD A WHOLE SENTENCE, SO THEY TAKE TWO COLUMNS. Found by measuring every
   visible box in a browser against its longest possible answer, 2026-08-03 — the sweep the owner
   asked for when he said *"make it big enough to fit Yes or No"* about a box one card away.
   `decision_maker` offers "Someone else decides — they can answer now" (335px of text in a 199px
   box) and `loan_others_on` offers "Borrower(s) + another person" (227px). Both are values a rep
   PICKS from a list, so the box was showing them a choice they could not read back — and a
   truncated answer is indistinguishable from a wrong one.
   ⚠️ SPANNING, NOT A FIXED WIDTH: `.ws-grid` is `auto-fill minmax(158px,1fr)`, so a px cap would
   fight the grid on a narrow screen — the standing rule since 2026-07-23 is that a box may not
   balloon yet must shrink to fit. Two tracks scales with whatever the grid resolves to. */
/* 🛑 FOUR MORE JOINED THIS RULE 2026-09-08, FOUND BY THE SWEEP THE 08-03 PASS ASKED FOR AND NEVER
   GOT. `scripts/measure-field-boxes.py` asks the browser what each menu needs (a clone at
   `width:max-content`, arrow included) and compares it with the box on screen. Four `.ws-grid`
   menus were short of their own longest option by 13–37px in a 166px track:
     · `credit_event_type`   needs 203 — "Foreclosure or short sale"
     · `payment_position`    needs 195 — "Comfortable / has room"
     · `plan_to_stay`        needs 185 — "Staying till it's paid off"
     · `loan_removal_plan`   needs 179 — "Wants them removed"
   ⚠️ THE SAME DEFECT AS THE TWO ABOVE, AND IT SURVIVED THE PASS THAT FIXED THEM because that pass
   measured by eye against a list of suspects. A rep picking one of these read it back truncated,
   and a truncated answer is indistinguishable from a wrong one — his 2026-08-03 reason, unchanged.
   ⚠️ SPANNING, NOT A PX WIDTH, for the reason the note above gives: `.ws-grid` is
   `auto-fill minmax(158px,1fr)`, so a fixed width fights the grid on a narrow screen. */
.ws-grid > .ws-k-decision_maker, .ws-grid > .ws-k-loan_others_on,
.ws-grid > .ws-k-credit_event_type, .ws-grid > .ws-k-payment_position,
.ws-grid > .ws-k-plan_to_stay, .ws-grid > .ws-k-loan_removal_plan { grid-column:span 2; }
/* 🛑 AND THE BOX IS CAPPED INSIDE THE SPAN, OR THE FIX FOR A CLIP BECOMES THE DEFECT NEXT DOOR.
   Two tracks is 347px at 1920; these menus need 179–203. Un-capped they became the four widest
   boxes on the sheet the moment they stopped clipping — the same *"as much room as a street
   address"* Gil reported, arrived at from the other direction. The SPAN stays because it is what
   lets the box grow on a narrow screen (a px width would fight the grid); the CAP is what stops it
   ballooning on his. Each is its measured need plus 13px. */
.ws-grid > .ws-k-credit_event_type > .sr-adorn, .ws-grid > .ws-k-credit_event_type > select { max-width:230px; }
.ws-grid > .ws-k-payment_position > .sr-adorn,  .ws-grid > .ws-k-payment_position > select  { max-width:222px; }
.ws-grid > .ws-k-plan_to_stay > .sr-adorn,      .ws-grid > .ws-k-plan_to_stay > select      { max-width:212px; }
.ws-grid > .ws-k-loan_removal_plan > .sr-adorn, .ws-grid > .ws-k-loan_removal_plan > select { max-width:206px; }
.ws-f textarea, .ws-f input[type=text] { width:100%; }

/* ══ ⚖️ A BOX IS THE SIZE OF ITS ANSWER — Gil, 2026-09-08 ═══════════════════════════════════════
   ⚖️ His words, looking at the sheet on his own monitor: boxes are *"sized oddly — a middle initial
   getting as much room as a street address."* He was told that was a second pass. This is it.

   🛑 THE BOX NARROWS, THE CELL DOES NOT. `.ws-grid` stays `auto-fill minmax(158px,1fr)` and every
   `.ws-f` keeps its track, so the CAPTIONS still sit on a regular grid and the sheet's column rhythm
   is untouched — only the control inside shrinks to the answer it can hold. This is the answer to
   the objection recorded one screen up (*"a ragged right edge under labels reads as broken
   alignment"*): a ragged edge of CAPTIONS would be, and does not happen here. What Gil asked for is
   precisely that a two-digit answer stop wearing a street address's box.

   🛑 EVERY NUMBER BELOW IS THE BROWSER'S OWN ANSWER PLUS HEADROOM, MEASURED BY
   `scripts/measure-field-boxes.py` — a `<select>` cloned at `width:max-content` (arrow included) and
   a typed box filled with its longest REAL answer. Not one of them is a guess, and re-running that
   command re-derives all of them on the next font change. `size with headroom, then re-measure` is
   this stylesheet's own rule and it has now been broken three times (the phone box 2px short in
   August, `Where from` 6px short and `On time?` 8px short this evening, both found by that command),
   so each bucket carries ≥10px over what was measured.

   ⚠️ UNSCOPED, AND THAT IS LOAD-BEARING. `_worksheet-*.blade.php` is shared by `?tab=worksheet`
   (labelled **Prequal**) and `?tab=script2`; scoping a rule about this markup to `body.lead-slim`
   left the other tab broken once already this week. Both tabs measured, at 1920 and 1440.
   ⚠️ NOTHING HERE IS A NEW QUESTION, A RENAMED ONE, OR A MOVED ONE. Widths only. */
.ws-f > .sr-adorn > select, .ws-f > select,
.ws-f > .sr-adorn > input[type], .ws-f > input[type] { max-width:100%; }

/* 🛑 THE CAP GOES ON `.sr-adorn`, NOT ON THE CONTROL, AND THE FIRST CUT PUT IT ON THE CONTROL.
   `.sr-adorn` is the BOX a rep sees — it draws the border, the rounded corners and the amber
   "still needed" tint, and the `<select>` merely lives inside it. Capping the select left the
   wrapper at full width, so *"Staying till payoff, or moving?"* rendered as a 347px amber box with
   a dropdown arrow a third of the way across and a dead amber slab beside it. **The measurement was
   perfectly green throughout — the box really was wide enough for its answer.** Found by looking at
   the render, which is the third time this file has that sentence in it.
   ⚠️ Both halves are named, because a `.ws-f` may hold a bare control with no adornment at all. */
/* ── A YES/NO MENU. Needs 66px of select, ~14 of box chrome; 96 is the bucket. These are the boxes
   that read most oddly at 166 — two letters of answer in a box built for a street. */
.ws-f.ws-k-credit_events > .sr-adorn, .ws-f.ws-k-credit_events > select,
.ws-f.ws-k-in_trust > .sr-adorn, .ws-f.ws-k-in_trust > select,
.ws-f.ws-k-us_citizen > .sr-adorn, .ws-f.ws-k-us_citizen > select,
.ws-f.ws-k-next_step_open > .sr-adorn, .ws-f.ws-k-next_step_open > select,
.ws-f.ws-k-credit_pulled > .sr-adorn, .ws-f.ws-k-credit_pulled > select,
.ws-f.ws-k-debts_confirmed > .sr-adorn, .ws-f.ws-k-debts_confirmed > select,
.ws-f.ws-k-appraisal_ordered > .sr-adorn, .ws-f.ws-k-appraisal_ordered > select,
.ws-f.ws-k-mtg1_lates_12mo > .sr-adorn, .ws-f.ws-k-mtg1_lates_12mo > select { max-width:96px; }

/* ── A FOUR-DIGIT YEAR. Needs 54px; it cannot be five digits. */
.ws-f.ws-k-bk_discharged_year > .sr-adorn, .ws-f.ws-k-bk_discharged_year > input[type],
.ws-f.ws-k-year_acquired > .sr-adorn, .ws-f.ws-k-year_acquired > input[type] { max-width:82px; }

/* ── A SMALL COUNT. `# of dependents` needs 36px for "12" and `Years of school` 39 for "22".
   Nobody has ninety-nine dependents and nobody has a hundred years of school. */
.ws-f.ws-k-dependents > .sr-adorn, .ws-f.ws-k-dependents > input[type],
.ws-f.ws-k-school_years > .sr-adorn, .ws-f.ws-k-school_years > input[type] { max-width:70px; }

/* ── A MONTH **AND A YEAR**. 🛑 THE FIRST CUT SIZED THESE TO "September" (93px) AND CLIPPED — the
   captions say *"Late — which month"* and *"Discharged (month)"*, but a rep writing down a late
   payment or a discharge types what they were told on the phone, and that is **"September 2024"**
   as often as a bare month. 131px measured; 146 with the box's own chrome and headroom. Caught by
   re-running the sweep with the realistic value instead of the caption's literal noun — the same
   shape as this family's *"10 digits is 14 characters on screen"* rule. */
.ws-f.ws-k-bk_discharged_month > .sr-adorn, .ws-f.ws-k-bk_discharged_month > input[type],
.ws-f.ws-k-mtg1_lates_when > .sr-adorn, .ws-f.ws-k-mtg1_lates_when > input[type] { max-width:146px; }

/* ── A ONE-OR-TWO-WORD MENU. The longest options are "Don't know" (116px), "Chapter 13" (112),
   "Unmarried" (111) and "Afternoon" (107) — so the bucket is sized to the widest of the four and
   every one of them keeps at least 10px. */
.ws-f.ws-k-cc_min_or_more > .sr-adorn, .ws-f.ws-k-cc_min_or_more > select,
.ws-f.ws-k-bk_chapter > .sr-adorn, .ws-f.ws-k-bk_chapter > select,
.ws-f.ws-k-marital_status > .sr-adorn, .ws-f.ws-k-marital_status > select,
.ws-f.ws-k-callback_time > .sr-adorn, .ws-f.ws-k-callback_time > select { max-width:142px; }

/* THE PREQUAL, LAID TO THE OWNER'S PQ WORK SHEET (owner 2026-07-23). Borrower(s), Mortgage and
   Other properties are TABLES (columns = attributes, rows = entities); Property is a placed grid.
   The rest of the sheet still flows on the generic .ws-grid. Shared by the person page AND the
   in-call rail, so these live here, not in a view's <style> (design rule 8). */

/* 🛑 THE $ / % ADORNMENT, SHARED (owner 2026-07-23: "why is there a $ outside the box"). The
   `.sr-adorn` flex that puts the $ INSIDE the box lived only in the runner's own <style>, so on the
   person page — the other host of this same worksheet — it was unstyled and the $ stacked ABOVE the
   input. Scoped to the worksheet's hosts so it fixes both without touching the runner's own copy. */
/* 🛑 AND `.ws-vals-row` (Gil's 2026-08-19 values row) IS ON ALL THREE LISTS BECAUSE IT PAID THE EXACT
   PRICE THE PARAGRAPH ABOVE DESCRIBES. A new layout container is unstyled here by default, so the $
   stacked ABOVE the input: the wrapper measured 49px with the box 15px down inside it, which put the
   note's textarea 15px above the five money boxes and broke the one thing he asked for by name —
   *"values input row need to line up on the same row"*. The boxes themselves were level the whole
   time, so it looked almost right and measured wrong. A HOST LIST IS NOT DECORATION: joining it is
   part of adding a container, not a polish step.
   🛑 THE $ SITS INSIDE THE BOX — owner 2026-08-01: *"$ should be $xx,xxx,xxx. no need to have the $
   outside of it."* It used to be its own bordered chip welded to the left of the input, so every
   money field read as two controls and the pair never lined up with a plain box beside it. Now the
   WRAPPER carries the border and the input inside it is borderless: one control, the adornment
   living in it. Same for the % on the right.

   ⚠️ That also fixes what he asked for in the same breath — *"Subject and own boxes need to line up
   and same size"*. They could not, because an adorned box was border + chip + border while a plain
   box was just border; the two rendered different heights from the same grid cell. */
.ws-lay-property .sr-adorn, .ws-lay-values .sr-adorn, .ws-lay-residence .sr-adorn,
.ws-lay-contact .sr-adorn, .ws-lien-cell .sr-adorn, .ws-b-cell .sr-adorn, .ws-grid .sr-adorn,
.ws-vals-row .sr-adorn {
    display:flex; align-items:center; width:100%; min-width:0; box-sizing:border-box;
    border:1px solid var(--line); border-radius:8px; background:#fff; overflow:hidden; }
.ws-lay-property .sr-fix, .ws-lay-values .sr-fix, .ws-lay-residence .sr-fix,
.ws-lay-contact .sr-fix, .ws-lien-cell .sr-fix, .ws-b-cell .sr-fix, .ws-grid .sr-fix,
.ws-vals-row .sr-fix {
    flex:0 0 auto; padding:0 2px 0 8px; font-size:13px; font-weight:700; color:var(--mut);
    background:none; border:none; }
.ws-lien-cell .sr-fix { padding:0 2px 0 5px; font-size:11.5px; }
/* the input inside an adorned wrapper carries no border of its own */
.sr-worksheet .ws-lay-property .sr-adorn input, .sr-worksheet .ws-lay-values .sr-adorn input,
.sr-worksheet .ws-lay-residence .sr-adorn input, .sr-worksheet .ws-lay-contact .sr-adorn input,
.sr-worksheet .ws-lien-cell .sr-adorn input, .sr-worksheet .ws-b-cell .sr-adorn input,
.sr-worksheet .ws-grid .sr-adorn input, .sr-worksheet .ws-vals-row .sr-adorn input {
    border:none; background:transparent; border-radius:0; width:100%; min-width:0; }
.sr-worksheet .sr-adorn:focus-within { border-color:var(--brand-ink); }

/* ══ THE AMBER HAD TO SIT ON THE WRAPPER, BECAUSE ON THE INPUT IT WAS LOSING ═══════════════════
   ⚖️ 2026-08-20, Gil: *"we need to have the required fields, basically everything that script asks
   shaded yellow and when filled in it goes to white. so we can see easily what was not answered."*

   🛑 THE CLASS WAS ALREADY THERE AND THE COLOUR WAS NOT. `input[type].is-missing` (0,2,1) is
   OUTRANKED by the `background:transparent` rule directly above (0,3,1), so on the hand-laid cards
   — Borrower, Subject property, Other properties, the lien table — 58 empty boxes carried
   `is-missing` and rendered WHITE. Measured in Chrome by asking which rules matched, not by reading
   file order: the transparent rule wins on specificity wherever the box sits inside `.sr-adorn`.
   That is why the sheet looked ~30% marked when the markup said ~100%, and it is the whole of what
   Gil was pointing at. Counting the CLASS would have reported this as already built.

   🛑 IT GOES ON `.sr-adorn`, NOT ON THE INPUT. `.sr-adorn` is the thing with the border and the
   radius — it IS the box a rep sees; the input inside is deliberately transparent and the `$`/`%`
   sits outside it. Amber on the input would tint part of a box and leave its adornment white.
   Same ruling as the debts grid's R-028, "the amber sits on the BOX, never behind it".

   ⚠️ A MOOT FIELD MUST STAY GREY, AND IT WINS BY SPECIFICITY, NEVER BY FILE ORDER. His 2026-08-12
   "none" ruling greys a field another answer has already settled, and a grey box that turned amber
   would be a question a rep can never clear. The `[data-implied]` rules below carry (0,4,0) against
   this rule's (0,3,1) — b beats c, so the grey wins wherever both match, whatever order they land
   in. Two desks are editing this file; order is not a thing to rely on. */
.sr-worksheet .sr-adorn:has(> input.is-missing) { background:#fdf7e2; }
/* A BARE SELECT NEVER HAD A RULE AT ALL — `input[type].is-missing, textarea.is-missing` covers two
   of the three shapes `capture-input` renders and the menu was simply never named. 37 empty menus
   the live script asks were carrying the class and painting nothing. `[name]` buys the (0,3,1) that
   clears `.ws-lay-contact select`'s own transparent (0,2,1). */
.sr-worksheet select[name].is-missing { background:#fdf7e2; }
/* THE MOOT/ON-FILE GREY, RE-ASSERTED AT THE WRAPPER. Answered-by-another-answer beats still-needed. */
.sr-worksheet .ws-f[data-implied] .sr-adorn, .sr-worksheet .ws-f.is-implied .sr-adorn,
.sr-worksheet .ws-f[data-implied] select[name], .sr-worksheet .ws-f.is-implied select[name],
.sr-worksheet .ws-lien-scroll[data-implied] .sr-adorn { background:#f6f7fa; }
/* a percent sign trails its number */
.has-percent .sr-fix { order:2; padding:0 8px 0 2px; }
.has-percent input { text-align:left; }
/* 🛑 A BOX FITS ITS VALUE — owner 2026-08-01: *"make sure the boxes fit value"*. `$999,999,999` is
   the widest money this sheet holds and 160px clipped it once the $ moved inside; a percent needs
   room for `XX.XXX %`. Sized to content-max rather than to a guess. */
.ws-w-money .sr-adorn { max-width:none; min-width:132px; }
.has-percent .sr-adorn { min-width:96px; }
/* 🛑 AND WHEN IT STILL WILL NOT FIT, THE ROW SCROLLS RATHER THAN CLIPS — *"is grides, just make them
   scrollable to right if need to be, like Borrower"*. The lien tables already do this; these grids
   now do it too, so a narrow panel slides sideways instead of squeezing a number out of sight. */
.ws-lay-property, .ws-lay-values, .ws-lay-residence, .ws-lay-contact { min-width:0; }
.ws-scroll-x { overflow-x:auto; }   /* up to $999,999,999 */
.ws-w-city  .sr-adorn, .ws-w-city  input  { max-width:170px; }
/* 🛑 WIDENED — owner 2026-08-02: "Resize state and zip to fit the state and zip entry". They were
   sized for the CONTENT (2 letters, 5 digits) and not for the CONTROL: the shared capture-input is a
   datalist, so the browser reserves room for its own dropdown affordance inside the box and the text
   clipped — his screen showed "TX" as "T)" and "77489" as "77.". Content-width is the wrong measure
   for a control that carries chrome. Measured against the widest real value plus that affordance. */
.ws-w-state .sr-adorn, .ws-w-state input  { max-width:96px; }   /* 2 letters + datalist chrome */
.ws-w-zip   .sr-adorn, .ws-w-zip   input  { max-width:128px; }  /* 5 digits, or ZIP+4 */

/* Value labels above the three property $ boxes (they'd read "Amount" without one) + years-in-home. */
.ws-vlabel { display:block; font-size:11.5px; font-weight:700; color:#4a5170; margin-bottom:3px; }

/* Property grid: address block; usage OVER type on the right; years-in-home + the map pin UNDER the
   address; the three values on one row. A DEFAULT rule first so a new field lands somewhere sane. */
/* 🛑 THE SUBJECT-PROPERTY CARD, TO HIS WRITTEN SPEC (2026-08-01, via HUB-5):
     Address (row 1) · City/State/Zip (row 2) · Subject: Yes/No (one row) ·
     Property usage (one row) · then, only when usage is NOT Primary, the residence block.

   TWELVE TRACKS, because six could not express "narrow" — the smallest thing a six-track grid can
   draw is a sixth of the sheet, which is why a two-letter state box sat in a 170px cell however it
   was assigned. */
.ws-lay-property { display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:6px 10px;
    align-items:end; grid-auto-rows:min-content; }
.ws-lay-property > .ws-f { grid-column:span 2; }
.ws-lay-property .ws-k-property_address { grid-column:1 / span 5;  grid-row:1; }
/* 🛑 STATE GETS TWO TRACKS AND ZIP THREE — owner 2026-08-03, on his own lead: *"the state and zip
   code do not fit in cell."* State held ONE track of twelve and zip shared two with the 📍 and the
   Google button, and the tracks are `minmax(0,1fr)`, which is explicitly allowed to shrink BELOW
   its content — so the boxes did not overflow their card, they silently clipped their VALUE.
   Measured at a 1280px viewport before the change: zip 40px wide holding "75149", rendering as
   "75", exactly the screenshot he sent; state 61px, with the datalist chrome eating the X of "TX".
   🛑 THE ROW HAD THE SPACE ALL ALONG — city/state/zip occupied tracks 1-5 of 12 and 6-12 were
   empty, so this widens into dead space and moves nothing he has approved. */
.ws-lay-property .ws-k-property_city    { grid-column:1 / span 2;  grid-row:2; }
.ws-lay-property .ws-k-property_state   { grid-column:3 / span 2;  grid-row:2; }
.ws-lay-property .ws-k-property_zip     { grid-column:5 / span 3;  grid-row:2; }
/* his rows 3 and 4 — one row each, kept off to the right so the address column stays a column */
/* 🛑 HIS OWN ROWS — he wrote them out and marked each *"(All in one Row)"*, which settles the one
   thing I had left open (I had put them in a right-hand column to save height). His layout wins:
   the address stack, then Subject on its own row, then Property usage on its own row. */
/* 🛑 SUPERSEDED 2026-08-02 — "move Property Usage and Property Type to right of address, plenty of
   room there." The 08-01 layout above was his too, and its reasoning still holds for what it was:
   with Subject on its own row and usage a strip of radio pills, stacking them read cleanly. Both of
   those premises are gone — the Subject read-out is removed and both controls are now `<select>`s,
   which are narrow. So the seven empty columns beside a two-row address are where they belong, and
   the card loses two rows of height. `ws-k-subject_property_fork` is intentionally unplaced: it no
   longer renders in this card. */
.ws-lay-property .ws-k-prop_usage { grid-column:7 / span 3;  grid-row:1; }
.ws-lay-property .ws-k-prop_type  { grid-column:10 / span 3; grid-row:1; }
.ws-lay-property .ws-prop-pick .sr-adorn, .ws-lay-property .ws-prop-pick select { width:100%; }

/* The values, on their own row under everything. */
.ws-lay-values { display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:9px 10px;
    align-items:end; margin-top:10px; }
/* ⚠️ EVERY CHILD OF THIS GRID NEEDS A COLUMN. `prop_rental_income` was added without one and landed
   in a single 1/12 track — a box too narrow to read, with its label wrapping to three lines. A
   twelve-track grid defaults to span 1, so "forgot to place it" and "placed it badly" look the same
   on screen; caught by looking, not by any test. */
/* 🛑 RE-SPANNED 2026-08-04 BECAUSE THREE OF THESE BOXES OVERFLOWED THEIR OWN CELL AND SAT ON THE
   NEIGHBOUR'S LABEL. Measured on the real page at 1090px — the width the owner works at — not
   reasoned: `home_value` and `our_value` had **two tracks = 107px** against a money box whose
   `min-width` is **132px**, so each spilled **25px**; `zillow_value` had three tracks = 166px but
   carries the badge AND the box, and spilled **15px**.
   ⚠️ Five money boxes will not fit one twelve-track row at his width, and squeezing them is what
   produced the overlap. Four across, the fifth on the next line — his *"use space wisely"* means
   readable across the space there IS, and a box that cannot show its own number is not using space,
   it is losing it. A track is ~48px here, so a money cell needs THREE.
   🛑 Nothing may be added to this grid without a span. Pinned by `EveryValueBoxHasRoomForItsNumberTest`. */
.ws-lay-values .ws-k-years              { grid-column:1 / span 2;  grid-row:1; }
/* ⚖️ THE FOUR VALUE BOXES LEFT THIS GRID ON 2026-08-19 for Gil's own six-column row (`.ws-vals-row`,
   at the foot of this file). Their rules are DELETED rather than left behind: a `.ws-lay-values
   .ws-k-home_value` that matches nothing still satisfies a guard looking for the key by name, and
   `EveryValueBoxHasRoomForItsNumberTest` was doing exactly that — reporting a measured span over a
   rule that had not applied to anything since the row moved. A rule nobody's markup can reach is a
   green light with no road under it.
   ⚠️ THE TWO THAT STAYED MOVE UP TO ROW 1. They were placed on row 2 UNDER the value boxes; with
   those gone, row 1 is empty and the container opens with a blank band. */
.ws-lay-values .ws-k-estimated_equity   { grid-column:4 / span 3;  grid-row:1; }
.ws-lay-values .ws-k-prop_rental_income { grid-column:1 / span 3;  grid-row:1; }
.ws-lay-values .ws-k-years .ws-lien-ym { max-width:120px; }
/* ⚖️ 2026-08-10 — the mortgage-lates box moved into the Credit events card on his ruling. Its card
   uses the same twelve-track `ws-lay-values` grid, so it needs its own span or it auto-places into
   one 48px track. Three tracks: the answer is a short Yes/No but the CAPTION is long. */
.ws-lay-values .ws-k-mtg1_lates_12mo { grid-column:1 / span 3; grid-row:1; }

/* 🛑 WHAT IT COSTS TO CARRY — THE THREE CHILDREN I ADDED ON 2026-08-03 AND DID NOT PLACE, WHICH IS
   THE DEFECT THE OWNER SAW ON THE DEPLOYED SHEET: *"this is what you deploy??????? THIS IS SO
   FRUSTRATING."*

   `.ws-lay-costs` had NO RULE OF ITS OWN. The element is `class="ws-lay-values ws-lay-costs"`, so it
   inherited the twelve-track grid — and RE taxes, Insurance and HOA fell to AUTO-PLACEMENT: one
   track each, **71px at his 1090px window**, while `.ws-w-money .sr-adorn` carries `min-width:132px`.
   ⇒ **every one of the three boxes overflowed its own cell by 61px and sat on top of its neighbour.**

   ⚠️ THE WARNING WAS ALREADY WRITTEN, ONE SCREEN ABOVE, ABOUT `prop_rental_income` DOING EXACTLY
   THIS — *"a twelve-track grid defaults to span 1, so 'forgot to place it' and 'placed it badly'
   look the same on screen; caught by looking, not by any test."* It says caught by LOOKING because
   no measurement in this repo can see it: the box is not CLIPPED, it OVERFLOWS, so
   `scrollWidth > clientWidth` never fires and a clipping sweep reports the region clean. It did.

   🛑 `align-items:start` FOR THIS ROW ONLY. The grid is `align-items:end`, which is right where every
   cell is the same height — but the Insurance cell now carries the "No HOI" tick beneath its box, so
   bottom-alignment pushed it 30px ABOVE its neighbours and the three captions stopped sharing a
   baseline. That is the second half of what he was looking at.

   Three tracks each: 233px at 1090px and 224px at the narrow breakpoint, both comfortably past the
   132px a money box demands, so the value has room and the label does not wrap. */
.ws-lay-costs { align-items:start; }
.ws-lay-costs .ws-k-mtg1_re_taxes { grid-column:1 / span 3;  grid-row:1; }
.ws-lay-costs .ws-k-mtg1_hoi      { grid-column:4 / span 3;  grid-row:1; }
.ws-lay-costs .ws-k-mtg1_hoa      { grid-column:7 / span 3;  grid-row:1; }
/* ⚖️ 2026-08-10 — "Own the land?" joined this row on his mobile-home gate. A Yes/No chip pair is
   narrower than a money box but still needs THREE tracks: at one track it is 48px against the
   chips' own min-width and it overflows onto HOA, which is the same defect the three boxes above
   this line record. Pinned by `EveryValueBoxHasRoomForItsNumberTest`. */
.ws-lay-costs .ws-k-land_owned    { grid-column:10 / span 3; grid-row:1; }

/* WHERE THEY LIVE — the gated block. Two rows for the address, exactly like the one above it. */
.ws-residence-block { margin-top:10px; }
/* Grayed-because-implied: the answer is already known from the card it sits in (the Subject row), or
   the question does not apply (rental income on a primary residence). 🛑 IT IS STILL A BOX AND STILL
   HOLDS ITS VALUE — the muting says "you do not need to fill this", never "this was deleted". */
/* A choice row: label, then every answer visible on one line — his *"Subject  Yes  No  (All in one
   Row)"*. Chips, so the options are readable at a glance and clickable without opening anything. */
.ws-choice { flex-direction:row; align-items:center; gap:10px; flex-wrap:wrap; }
.ws-choice > .ws-vlabel { margin:0; white-space:nowrap; }
.ws-choice-opts { display:flex; gap:6px; flex-wrap:wrap; }
.ws-choice-opt { display:inline-flex; align-items:center; gap:5px; padding:5px 11px; border:1px solid var(--line);
    border-radius:999px; font-size:12.5px; color:var(--ink); cursor:pointer; background:#fff; line-height:1.2; }
.ws-choice-opt input { appearance:none; width:11px; height:11px; margin:0; border:1px solid #b9bfd0;
    border-radius:50%; flex:0 0 auto; }
.ws-choice-opt:has(input:checked) { border-color:var(--brand-ink); background:#eef1f9; font-weight:700; }
.ws-choice-opt input:checked { border:3px solid var(--brand-ink); }
/* ⚖️ A MULTI ROW READS AS "PICK ANY", NOT "PICK ONE" — his 2026-08-11 *"it can be 3 goals… as many
   goals as they ask for"*. The pill is identical so the row still reads as one line of answers; only
   the marker changes, SQUARE for a checkbox against the round radio dot. That is the one cue that
   tells a rep the row will take a second answer without them having to try it and watch the first
   one disappear — which is what the old radio row did, silently. */
.ws-choice-opt input[type="checkbox"] { border-radius:3px; }
.ws-choice-opt input[type="checkbox"]:checked { border:1px solid var(--brand-ink); background:var(--brand-ink);
    box-shadow:inset 0 0 0 2px #fff; }

/* THE ANSWERS BECOME TAP CARDS — ON THE SCRIPT V2 SCREEN ONLY, 2026-09-01.
   Gil, looking at the live v2 screen: "what about making it look like the Two Roads questions with
   selection boxes". Two Roads and the ICE funnel share one control — a card big enough to hit,
   marked THREE ways at once (a filled marker, a heavier border and a tint) because hue alone fails
   the ~45% of adults in their mid-70s with a colour deficit. This brings that control to the sheet.
   SCOPED TO `.s2` DELIBERATELY, AND THAT SCOPE IS THE WHOLE SAFETY OF THIS CHANGE. The partial these
   chips come from is included by THREE screens: the Script v2 sheet, the old script runner, and the
   ordinary Prequal tab on every lead. Restyling `.ws-choice-opt` bare would resize the answers on
   the screen every rep uses all day, with no gate and no warning, on the strength of a sentence
   about a screen only Gil can see. Scoped, it rides Script v2's own admin gate: nobody but Gil sees
   it, and when v2 reaches reps this reaches them with it, which is the correct pairing.
   THE ROW STILL READS AS A ROW. Height and hit area grow; `flex-wrap` and the label-then-answers
   shape are untouched, so a three-answer row still sits on one line at the widths the sheet uses. */
.s2 .ws-choice-opt { gap:9px; padding:10px 14px; border-radius:10px; font-size:15px;
    min-height:44px; border-width:1.5px; }
.s2 .ws-choice-opt input { width:17px; height:17px; border-width:1.5px; }
.s2 .ws-choice-opt:has(input:checked) { border-width:2.5px; padding:9.5px 13.5px; }
.s2 .ws-choice-opt input:checked { border-width:5px; }
.s2 .ws-choice-opt input[type="checkbox"] { border-radius:5px; }
.s2 .ws-choice-opt input[type="checkbox"]:checked { border-width:1.5px; box-shadow:inset 0 0 0 3px #fff; }
/* ⚖️ NO MARKER INSIDE THE CHIP — Gil, 2026-09-11: *"Remove teh bubble in teh select on all these
   selections so there is more room for the words."* On a narrow panel the dot and its gutter were
   ~26px off every chip, which is what pushed "Stayed the same" onto two lines while the row still had
   to fit four answers.
   🛑 THE ANSWER IS STILL MARKED THREE WAYS, AND TWO OF THEM ARE NOT COLOUR — a heavier border, a tint,
   and bold text. That is what the note above this block is protecting (hue alone fails the ~45% of
   adults in their mid-70s with a colour deficit), and it survives the dot going. Do NOT also drop the
   border weight or the bold: the tint alone would be a colour-only cue.
   🛑 THE INPUT IS HIDDEN, NEVER REMOVED. It is the real control — the label toggles it, it posts, it
   takes keyboard focus — so it is taken out of the FLOW rather than out of the page, and the focus ring
   moves to the chip so tabbing through a row still shows where you are.
   ⚠️ WHAT THE MARKER ALSO CARRIED, said out loud rather than quietly lost: its SHAPE told a rep whether
   a row takes one answer or several (round = pick one, square = pick any — the 2026-08-11 cue for his
   *"it can be 3 goals"*). Without it that is only discoverable by picking a second one. Flagged to Gil;
   his ask was explicit and the room was real.
   ⚠️ SCOPED TO `.s2` LIKE EVERYTHING ELSE IN THIS BLOCK — the same partial is drawn by the old script
   runner and by the ordinary Prequal tab that every rep uses all day. */
.s2 .ws-choice-opt input { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
/* ⚠️ AND THE CHIP IS DELIBERATELY LEFT SHRINKING. The words still fold onto two lines in the narrow
   panel, and the first instinct — `flex:0 0 auto`, so the ROW wraps instead of the WORDS — would
   REVERSE a ruling Gil already made: 2026-09-05, *"try to always fit selections on as little rows as
   possible. You can have wording go on 2 rows if needed."* Fewer rows with folded words is the trade he
   chose. Taking the dot out buys those words their room INSIDE that rule, which is what he asked for.
   Read `.s2-now-answer .ws-choice-opts` below before touching this. */
.s2 .ws-choice-opt { position:relative; gap:0; }
.s2 .ws-choice-opt:has(input:focus-visible) { outline:2px solid var(--brand); outline-offset:2px; }
.s2 .ws-choice-opts { gap:8px; }
/* The leading goal is the one the call was run on and the one `[goal-word]` speaks, so it is marked
   rather than merely first — the order survives a save and a rep should be able to see which it is. */
/* ⚖️ THE BREAKDOWN CHIPS on the collapsed goal line (his 2026-08-11 *"I want break down if
   possible"*). Lighter and marked with an arrow so the line still reads as goals-first: a rep
   scanning it sees three goals, and the detail sits under the one it belongs to rather than
   competing with it. */
/* ⚖️ THE CASH-OUT ARM READS TOTAL → WHAT FOR → THE PARTS → THE ARITHMETIC (his 2026-08-11 *"cash
   out amount total should be above the break down. and the break down has to add up total"*). The
   total gets a little air under it so the parts read as belonging to it rather than competing. */
/* ⚖️ DESIRED LTV — his *"somewhere prominent… that we can see even when we are on the top of
   prequal"*. Sticky, so it rides the scroll instead of being a figure you have to go and find.
   ⚠️ `top` clears the page's own sticky strip; z-index sits under the call bar and over the cards. */
.ws-ltv { position:sticky; top:var(--ph-strip-h, 44px); z-index:3; display:flex; align-items:baseline;
    gap:10px; background:var(--card,#fff); border:1px solid #dcdce8; border-left:3px solid var(--brand);
    border-radius:10px; padding:8px 14px; margin:0 0 12px; }
.ws-ltv-label { font-size:11px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:#5a6880; }
.ws-ltv-figure { font-size:21px; font-weight:800; color:var(--brand-ink); line-height:1; }
/* Over 80% is the number a rep should notice, not one the screen should refuse — amber, never red,
   on a surface that rides a live call (design-system §Compliance). */
.ws-ltv-figure.is-high { color:#9a6b00; }
.ws-ltv-figure.is-unknown { color:#b9bfd0; }
/* The bar is a button now (his *"make it clickable"*), so it states that it is one — but it must
   not look like a form control on a sheet full of them: no button chrome, a pointer, and a quiet
   affordance on the right. `text-align:left` because a button centres its text by default and this
   one holds a row. */
.ws-ltv { width:100%; text-align:left; font:inherit; color:inherit; cursor:pointer; }
.ws-ltv:hover { border-color:var(--brand); }
.ws-ltv:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.ws-ltv-more { margin-left:auto; font-size:11.5px; font-weight:700; color:var(--brand); white-space:nowrap; }
.ws-deal { border:1px solid #dcdce8; border-radius:12px; padding:0; max-width:900px; width:calc(100% - 32px); }
.ws-deal::backdrop { background:rgba(20,25,40,.42); }
.ws-deal-head { display:flex; align-items:center; justify-content:space-between; gap:12px;
    padding:12px 16px; border-bottom:1px solid #eef0f5; }
.ws-deal-head h3 { margin:0; font-size:15px; }
.ws-deal-x { border:0; background:transparent; font-size:22px; line-height:1; cursor:pointer; color:#8a90a2; }
.ws-deal-body { padding:14px 16px 18px; }
/* ⚖️ THEIR NUMBER → OUR PULL → WHAT WE'RE USING (his 2026-08-11). The last one is the decision the
   LTV rests on, so it is the only one that reads as solid; the inputs sit quieter beside it. */
.ws-deal-values { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:0 0 8px; }
.ws-dv { display:inline-flex; flex-direction:column; gap:1px; padding:5px 10px; border:1px solid var(--line);
    border-radius:8px; background:#fff; }
.ws-dv-cap { font-size:10px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; color:#8a90a2; }
.ws-dv b { font-size:14px; }
.ws-dv.is-blank b { color:#c4c9d4; font-weight:600; }
.ws-dv.is-driver { border-color:var(--brand-ink); background:#eef1f9; }
/* Waiting is stated, never dressed as a value — amber, and it is the one thing on this popup a rep
   can act on straight away. */
.ws-dv.is-driver.is-waiting { border-color:#f0e0b8; background:#fff8e6; }
.ws-dv.is-driver.is-waiting b { color:#9a6b00; }
.ws-dv-arrow { color:#b9bfd0; font-weight:800; }
/* Their own words about their own number — quiet, wrapping, and capped so a talkative answer cannot
   push the three figures out of line with each other. */
.ws-dv-said { display:block; max-width:230px; margin-top:2px; font-size:11px; font-style:italic;
    color:var(--mut,#6b7280); line-height:1.35; }
.ws-deal-basis { margin:0 0 10px; font-size:12.5px; color:var(--mut,#6b7280); }
.ws-ltv-pair { display:inline-flex; align-items:baseline; gap:5px; }
.ws-ltv-pair + .ws-ltv-pair { border-left:1px solid #e6e8f0; padding-left:12px; }
.ws-ltv-kind { font-size:11px; font-weight:800; color:#5a6880; }
/* The payment and DTI cells read smaller than the LTV — his ask was to ADD them to the bar, not to
   compete with the figure the bar is named after. The after-figure is the one a rep is working
   toward, so it is the solid one; today's sits quieter to its left. */
.ws-ltv-figure.is-sm { font-size:15px; color:var(--mut,#6b7280); }
.ws-ltv-figure.is-sm.is-after { color:var(--brand-ink); }
.ws-ltv-to { color:#b9bfd0; font-weight:800; font-size:12px; }

.ws-ltv-note { font-size:11.5px; color:var(--mut,#6b7280); }
.ws-ltv-basis { font-size:12px; color:var(--mut,#6b7280); }
.ws-ltv-src { color:#9aa1b1; }
.ws-cash-sum { display:flex; align-items:baseline; gap:6px; margin:6px 0 2px 12px; font-size:12px;
    color:var(--mut,#6b7280); }
/* 🛑 `hidden` MUST BE RESTATED BESIDE ANY `display` RULE — a class selector outranks the browser's
   own `[hidden]{display:none}`, so the server sent this counter `hidden` and it drew anyway. On a
   file with no per-reason amounts it then read "$0 of $15,000 — $15,000 not yet accounted for",
   warning about money that was fully accounted for. Same family as the `display:contents` rows.
   Measured: 1 of 56 `[hidden]` elements on the card rendered, and this was it. */
.ws-cash-sum[hidden] { display:none; }
.ws-cash-sum-total { font-weight:800; color:var(--ink); font-size:14px; }
/* ⚖️ THE WORD "Total", in the card's own caption voice — Gil 2026-08-31 asked for this block to
   be readable, and a bare figure with no noun beside it is the thing he has twice called a box you
   cannot tell what it is for. Same weight/size/colour as `.ws-sub-label` so it reads as a caption
   rather than a second answer. */
.ws-cash-sum-cap { font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em;
    color:#9a97ab; }
/* 🛑 THE PARTS/OF/GAP TRIO IS GONE AND SO ARE ITS RULES — the typed total they policed was removed
   on 2026-08-31, so nothing can disagree with the parts any more. Left in the stylesheet they would
   be dead selectors that the next reader takes for live behaviour: this sheet has already retired
   two controls whose CSS outlived them ("Coming up next", "nothing to say for this one"). */
.ws-gb-chip.ws-gb-sub { background:#f7f9fd; border-style:dashed; font-weight:600; }
.ws-gb-what { color:var(--mut,#6b7280); font-weight:600; }
.ws-choice-opt.is-lead { border-color:var(--brand-ink); box-shadow:0 0 0 2px rgba(53,82,155,.14); }
/* A stored answer that is not on the current list — shown so it cannot be overwritten unseen. Marked
   rather than styled as an error: it is usually just an answer he reworded in the script editor, and
   the file is right about what it holds. */
.ws-choice-opt.ws-choice-stray { border-style:dashed; }
.ws-f.is-implied .ws-choice-opt, .ws-f[data-implied] .ws-choice-opt { color:var(--mut); background:#f6f7fa; }
.ws-readout { display:block; padding:6px 2px; font-size:13px; color:var(--mut); font-style:italic; }
.ws-f.is-implied input, .ws-f[data-implied] input { background:#f6f7fa; color:var(--mut); cursor:default; }
.ws-f.is-implied > span:first-child, .ws-f[data-implied] > span:first-child { color:#a6abbd; }
.ws-f[data-implied] .sr-fix { color:#c2c6d4; }

/* ═══ HIS TWO TICK-BOXES (owner 2026-08-03) ═══ *"need to add a free and clear check box on here…
   it grays out all questions about mortgage except Taxes and insurance and HOA. check box for no
   insurance and it says no HOI on prequal."*

   🛑 THE TICK IS NOT A CAPTURE BOX AND MUST NOT LOOK LIKE ONE. Every other control on this sheet is
   an answer a rep types or picks; these two are a rep saying a whole area does not apply. Styled as
   a plain label with a real checkbox — deliberately NOT the `.ws-choice-opt` pill, which reads as
   one option of a set and would invite a rep to hunt for the "Yes, there is a mortgage" twin. */
.ws-fcbox { display:inline-flex; align-items:center; gap:6px; font-size:12px; font-weight:600;
    color:#4a5170; cursor:pointer; white-space:nowrap; }
.ws-fcbox input[type="checkbox"] { width:13px; height:13px; margin:0; flex:0 0 auto; cursor:pointer;
    accent-color:var(--brand-ink); }
.ws-fcbox-sm { margin-top:4px; font-size:11px; font-weight:500; }
/* The heading and its tick share a line — the tick answers that heading, so it reads as part of it
   rather than as the first question underneath. Wraps rather than clips on a narrow panel. */
.ws-mortgage-head { display:flex; align-items:baseline; justify-content:space-between; gap:12px;
    flex-wrap:wrap; }
.ws-mortgage-head .ws-sub-label { margin-bottom:0; }

/* 🛑 FREE AND CLEAR GREYS THE LOANS, NEVER HIDES OR EMPTIES THEM — the same rule the rental-income
   box already follows. A hidden table cannot be corrected, and a rep who ticks it by mistake would
   have no way back to what the call captured. Greyed, still there, still holding its numbers.
   ⚠️ Pointer-events stay ON: the boxes remain editable, because the tick is a statement about the
   house and not a lock on the file. It is a legibility cue, not a permission. */
.ws-lien-scroll[data-implied] .ws-lien-cell input,
.ws-lien-scroll[data-implied] .sr-adorn { background:#f6f7fa; color:var(--mut); }
.ws-lien-scroll[data-implied] .ws-lien-head,
.ws-lien-scroll[data-implied] .ws-lien-rowlab,
.ws-lien-scroll[data-implied] .sr-fix { color:#a6abbd; }
/* Net rental + the running total — file-level, under the borrower blocks, beside `vet_who`. Framed
   as a read-out and not as a card: it is arithmetic the sheet did, never something a rep types. */
.ws-income-total { margin:10px 0 2px; padding:9px 12px; background:#f6f7fa; border:1px solid var(--line);
    border-radius:8px; max-width:520px; }
.ws-inc-line { display:flex; justify-content:space-between; gap:16px; font-size:13px; color:#4a5170;
    padding:2px 0; }
.ws-inc-line b { color:#232838; font-variant-numeric:tabular-nums; white-space:nowrap; }
.ws-inc-sum { border-top:1px solid var(--line); margin-top:4px; padding-top:5px; font-weight:700; }
.ws-inc-sum b { font-size:15px; }
.ws-inc-note { display:block; margin-top:5px; font-size:11px; color:var(--mut); line-height:1.4; }
.ws-residence-block[hidden] { display:none; }
.ws-lay-residence { display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:9px 10px; align-items:end; }
/* His residence rows: street on 1, then city / state / zip each in their OWN box on row 2, with Own
   beside the street. *"No spacing between street and city state and zip"* — the two rows sit tight. */
.ws-lay-residence { row-gap:4px; }
.ws-lay-residence .ws-k-primary_address { grid-column:1 / span 5; grid-row:1; }
/* The residence block is the subject property's twin and clips the same way — same widening, or the
   fix would hold on one address row and not the one directly beneath it. */
.ws-lay-residence .ws-k-primary_city    { grid-column:1 / span 2; grid-row:2; }
.ws-lay-residence .ws-k-primary_state   { grid-column:3 / span 2; grid-row:2; }
.ws-lay-residence .ws-k-primary_zip     { grid-column:5 / span 3; grid-row:2; }
/* 🛑 *"No spacing between street and city state and zip"* — and the gap was not the row-gap, it was
   the NEIGHBOURS. Every grid row is as tall as its tallest cell, and Subject/Own each stacked a label
   ABOVE their box while the address cells had none, so each row grew by a label's height and pushed
   the two address lines apart. Laying those two labels INLINE beside their boxes collapses the row to
   the height of a box, which is what closes the gap — and it reads the way he wrote it, "Subject: Yes
   No" on one line. Fixing the row-gap alone would not have touched it. */
.ws-lay-residence .ws-k-subject_property_fork,
.ws-lay-residence .ws-k-owns_primary { flex-direction:row; align-items:center; gap:8px; }
.ws-lay-residence .ws-k-subject_property_fork > .ws-vlabel,
.ws-lay-residence .ws-k-owns_primary > .ws-vlabel { margin:0; white-space:nowrap; }
.ws-lay-residence .ws-k-subject_property_fork > .sr-adorn,
.ws-lay-residence .ws-k-owns_primary > .sr-adorn { flex:1 1 auto; min-width:0; }
/* 🛑 SUBJECT AND OWN LINE UP AND ARE THE SAME SIZE — owner 2026-08-01, and they were neither: the
   two labels are different lengths ("Subject" vs "Own"), so an inline label pushed each box to a
   different left edge and left them different widths. The label column is now a fixed track, so both
   boxes start in the same place and end in the same place, whatever the label says. */
.ws-lay-residence .ws-k-subject_property_fork,
.ws-lay-residence .ws-k-owns_primary { display:grid; grid-template-columns:64px minmax(0,1fr); align-items:center; gap:8px; }
.ws-lay-residence .ws-k-subject_property_fork { grid-column:7 / span 4; grid-row:1; }
.ws-lay-residence .ws-k-owns_primary          { grid-column:7 / span 4; grid-row:2; }
.ws-lay-residence .ws-w-city .sr-adorn,  .ws-lay-residence .ws-w-city input,
.ws-lay-residence .ws-w-state .sr-adorn, .ws-lay-residence .ws-w-state input,
.ws-lay-residence .ws-w-zip .sr-adorn,   .ws-lay-residence .ws-w-zip input { max-width:none; }

/* 🛑 THE GRID OWNS THE WIDTHS HERE, SO THE OLD PER-FIELD CAPS STAND DOWN. They cap the INPUT while
   the grid sizes the CELL, so each box sat short inside a wider track and the row read as boxes with
   holes punched between them. The caps still do their job on the generic grid. */
.ws-lay-property .ws-w-city .sr-adorn,  .ws-lay-property .ws-w-city input,
.ws-lay-property .ws-w-state .sr-adorn, .ws-lay-property .ws-w-state input,
.ws-lay-property .ws-w-zip .sr-adorn,   .ws-lay-property .ws-w-zip input,
.ws-lay-values .ws-w-money .sr-adorn { max-width:none; }
/* 🛑 A FLOOR, NOT `min-width:0` — the zip shares its cell with the 📍 and the Google button, and
   those are fixed-size, so `min-width:0` handed them the whole cell and let the zip take whatever
   was left (40px at 1280px). 96px holds a 5-digit zip with room for ZIP+4 to scroll rather than a
   plain zip to clip. The cap on the line above stays `max-width:none`, so the box still grows into
   the wider track on a large screen. */
.ws-lay-property .ws-zip-row .ws-w-zip { flex:1 1 auto; min-width:96px; }

/* Icon-only lookup button: a compact square, no words. The Zillow one sits LEFT of its box. */
.sr-lookup-icon { padding:6px 9px; font-size:15px; line-height:1; }
/* Redfin / Realtor (owner 2026-08-16) carry a WORD instead of a glyph — there is no emoji a rep
   reads as "Redfin", and three near-identical icons in the worksheet row would be guesswork on a
   live call. Sized to sit level with the 🏠 beside it rather than tower over it, and `white-space`
   pinned so a narrow prequal column wraps the ROW, never the word itself. */
.sr-lookup-word { font-size:11.5px; font-weight:800; letter-spacing:.01em; white-space:nowrap; }
.sr-lookup-icon.sr-lookup-word { padding:7px 8px; line-height:1; }
/* Zip box + its 📍 side by side (the pin to the RIGHT of the zip, owner 2026-07-23). */
.ws-zip-row { display:flex; align-items:center; gap:6px; }
.ws-zip-row .ws-w-zip { flex:0 1 auto; }
.ws-zillow-row { display:flex; align-items:center; gap:6px; }
.ws-zillow-row .sr-adorn { flex:1; }
.ws-enc-note { margin:5px 0 0; font-size:11.5px; }
/* Veteran — "everything below is about ___" (owner 2026-08-01: *"have the sheet show who/what it
   applies to"*). One box on its own line above the rest of the card, sized to its options. */
/* 🛑 `display:flex` BEATS THE `hidden` ATTRIBUTE — and that is how his removal shipped as an empty
   box sitting in the middle of the card. `[hidden]` is only a `display:none` in the browser's DEFAULT
   sheet, so ANY display rule in ours outranks it. The row was marked hidden, the key stopped being
   asked, and the box stayed on screen with no label attached to it.
   ⚠️ This applies to every `hidden` on this sheet: hiding a thing that carries its own display rule
   needs the rule to stand down explicitly. Marking the attribute is not enough on its own. */
.ws-vet-who[hidden] { display:none; }
.ws-vet-who { display:flex; align-items:center; gap:9px; margin:0 0 10px; }
.ws-vet-who .ws-vlabel { margin:0; white-space:nowrap; }
.ws-vet-who .sr-adorn { max-width:190px; }

/* — Goal & position hand-lay (owner 2026-07-24) — three groups: the top "what they want" row, the
   labelled "Working with another lender" offer-in-hand row sized to its entry, and the parked
   rep-read pair. Flex-wrap so a narrow screen wraps rather than clips; widths line the boxes up on a
   full-width sheet (same philosophy as the property/mortgage grids). */
.ws-lay-goal, .ws-lay-offer, .ws-goal-parked { display:flex; flex-wrap:wrap; gap:10px 14px; align-items:end; }
/* 🛑 A CLASS THAT SETS `display` BEATS THE BROWSER'S OWN `[hidden]`, AND THE ELEMENT STAYS ON SCREEN
   WHILE `el.hidden` READS TRUE. Measured 2026-08-19 on the offer block: `shopping_status = "Turned
   down"` set `hidden` on the six offer rows and every one of them was still visible, because the
   rule above says `display:flex` and the UA sheet's `[hidden]{display:none}` loses to it. ⚠️ A probe
   that asks `el.hidden` reports this as WORKING — the property is the thing that was set. Ask
   `checkVisibility({checkVisibilityCSS:true})`; the property is not the question. Any future layout
   class on this sheet that sets `display` needs the same line. */
.ws-lay-goal[hidden], .ws-lay-offer[hidden], .ws-goal-parked[hidden] { display:none; }
.ws-lay-goal { margin-top:2px; }
/* $/% adornment — same as the property/table cells; without it the $ stacks ABOVE the input. */
.ws-lay-goal .sr-adorn, .ws-lay-offer .sr-adorn, .ws-goal-parked .sr-adorn {
    display:flex; align-items:center; gap:0; min-width:0; width:100%; }
.ws-lay-goal .sr-fix, .ws-lay-offer .sr-fix, .ws-goal-parked .sr-fix {
    font-size:13px; font-weight:800; color:var(--mut); padding:0 8px; align-self:stretch;
    display:flex; align-items:center; background:#fbfcfe; border:1px solid var(--line); }
.ws-lay-goal .has-currency .sr-fix, .ws-lay-offer .has-currency .sr-fix { border-right:none; border-radius:8px 0 0 8px; }
.ws-lay-goal .has-currency input, .ws-lay-offer .has-currency input { border-top-left-radius:0; border-bottom-left-radius:0; }
.ws-lay-offer .has-percent .sr-fix { border-left:none; border-radius:0 8px 8px 0; }
.ws-lay-offer .has-percent input { border-top-right-radius:0; border-bottom-right-radius:0; }
/* top row — what they're trying to do (sized so the boxes line up, kept on top) */
.ws-lay-goal .ws-k-goal            { flex:1 1 190px; min-width:170px; }
.ws-lay-goal .ws-k-wanted_mtg_type { flex:0 0 118px; }
.ws-lay-goal .ws-k-cash_reason     { flex:1 1 210px; min-width:180px; }
/* 168px, not 132px: at 132 the label "Cash-out amount (total)" wrapped to two lines, and because the
   row is `align-items:end` the extra line pushed the reason chips to the bottom of a tall row — a
   finger-deep empty band under the CASH OUT heading. One track width, and the row is one line tall
   again. (Caught by looking at the render, not by any test — nothing here can go red on white space.) */
.ws-lay-goal .ws-k-cash_amount     { flex:0 0 168px; }
.ws-lay-goal .ws-k-decision_maker  { flex:1 1 190px; min-width:170px; }
/* ⚖️ WORKING WITH ANOTHER LENDER — THE SIX OFFER BOXES ON ONE ROW (Gil 2026-08-20, with a
   screenshot of the card open on `Yes`): *"Make the offer boxes smaller so it fits on one row."*

   🛑 SMALLER BOXES ALONE MAKE IT WORSE, AND THAT IS MEASURED, NOT ARGUED. Driven in real Chrome
   on all three surfaces: shrinking the bases with the old captions took the prequal from THREE
   rows to FOUR and clipped "Offered term" — the row is not governed by the inputs, it is governed
   by the CAPTIONS above them. Each box is `max(label, content)` wide, and four of the six labels
   were wider than the value they caption. So the captions shrank first (`CaptureWriter::LABELS`,
   the four `Offered …` numerics — the card header already says WHAT THEY WERE OFFERED) and these
   bases are the MEASURED floor underneath: the widest realistic value plus the input's own
   padding, border and its `$`/`%` adornment, never a number chosen by eye.

   📐 Measured minimums, widest realistic value, worst surface of the three:
     bank 135 ("Rocket Mortgage")  ·  stage 137 ("Application taken")  ·  amount 122 ("1,250,000")
     rate 105 ("10.875" + a 37px `%`)  ·  term 62 ("30")  ·  payment 101 ("12,500")
   ⚠️ `min-width` MATCHES the basis on every one. The basis alone still shrinks under flex, which
   is how a box gets narrower than the value inside it; the floor is what keeps a rep from reading
   a clipped figure off a live call. Six + five gaps = 722px of BLOCK to sit on one line.

   🛑 RATE WAS ALREADY CLIPPED ON THE RUNNER BEFORE ANY OF THIS — 94px against a 37px adornment, so
   a rate read back off the rail was cut. 105 is what fixes it; it is a defect repair riding along,
   not a consequence of the resize. Pinned by `scripts/measure-prequal-offer-section.py`. */
.ws-lay-offer .ws-k-offer_bank     { flex:1 1 135px; min-width:135px; }
.ws-lay-offer .ws-k-offer_stage    { flex:1 1 137px; min-width:137px; }
.ws-lay-offer .ws-k-offer_amount   { flex:0 1 122px; min-width:122px; }
.ws-lay-offer .ws-k-offer_rate     { flex:0 1 105px; min-width:105px; }
.ws-lay-offer .ws-k-offer_term     { flex:0 1 62px;  min-width:62px; }
.ws-lay-offer .ws-k-offer_pymt     { flex:0 1 101px; min-width:101px; }
.ws-lay-offer .ws-k-loan_others_on { flex:1 1 158px; min-width:140px; }
/* the parked pair — set apart by a hairline; slated to move out of Goal & position later */
.ws-sub-label { margin:15px 0 8px; font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--mut); }
/* 🛑 CONDENSED 2026-08-01 — owner, on seeing the card: *"we need to condense it so it isnt tall"*.
   Three stacked groups each carrying a full-height sub-label heading is most of the height; the
   groups themselves already pack horizontally. The heading shrinks to a hairline rule with small
   caps riding it, and the gaps between groups halve. Nothing is removed and nothing re-wraps — this
   is spacing only, which is why it is safe to do before he rules on the card's POSITION. */
.ws-lay-goal, .ws-lay-offer { gap:8px 12px; }
.ws-section .ws-sub-label { margin:9px 0 5px; }
.ws-lay-goal + .ws-sub-label, .ws-lay-offer + .ws-sub-label { margin-top:8px; }
.ws-lay-offer + .ws-grid, .ws-lay-goal + .ws-grid { margin-top:8px; }
/* 🛑 THE GOAL WRITE-UPS — owner 2026-08-01: *"based on what they choose, there's a write up for
   each."* An arm is the block that belongs to one answer, shut until that answer is picked. It is
   INSET rather than boxed: a rep who picked Cash out should see the follow-up hanging off the choice
   they just made, and a second card border inside a card reads as a new section instead. A sub-arm
   (the reason's own detail) insets again off the same rail, so two levels stay legible without a
   third border. `hidden` does the hiding — no `display` rule here can fight it, which is why the
   gate is an attribute and not a class. */
.ws-goal-arm { margin:9px 0 0; padding-left:12px; border-left:2px solid var(--line); }
.ws-goal-arm .ws-sub-label { margin-top:0; }
/* The reason row mixes two control SHAPES — chips carry their label inline, a money box carries its
   label above — and the sheet's default `align-items:end` drops the short chips to the bottom of the
   taller line, leaving a band of dead space under the heading. Centre just this row; the sub-arm is
   excluded because both of its controls are label-above and already line up. */
.ws-goal-arm:not(.ws-goal-sub) > .ws-lay-goal { align-items:center; }
.ws-goal-sub { margin-top:7px; border-left-color:var(--ring); }
/* ⚖️ Gil 2026-09-08: *"the amount shold be on the end of the same line as window, make window field
   smaller to be able to add $ field."*
   🛑 `flex:1 1` WAS THE WHOLE COMPLAINT. The first row's work select GREW to fill the line — 944px on
   his card for a value reading "Windows" — which shoved its `$` box out to the card's right edge.
   Rows 2-4 carry no such rule and were already compact and paired (193px + 207px, side by side), so
   the added rows were right and the FIRST one was the odd one out. `0 1` keeps the same basis and
   the same floor; it simply stops it eating the line. */
.ws-lay-goal .ws-k-cash_hi_work              { flex:0 1 240px; min-width:190px; }
.ws-lay-goal .ws-k-cash_other_detail         { flex:1 1 240px; min-width:190px; }
.ws-lay-goal .ws-k-cash_amt_home_improvement { flex:0 0 132px; }
.ws-lay-goal .ws-k-cash_amt_other            { flex:0 0 132px; }
.ws-lay-goal .ws-k-debt_payoff_target        { flex:0 0 150px; }
.ws-goal-parked { margin-top:15px; padding-top:12px; border-top:1px dashed var(--line); }
.ws-goal-parked .ws-k-payment_position { flex:1 1 230px; min-width:190px; }
.ws-goal-parked .ws-k-plan_to_stay     { flex:1 1 230px; min-width:190px; }
/* ══ WORKING WITH SOMEONE ELSE — the card that changes STATE ════════════════════════════════════
   🛑 R-029. Gil 2026-08-19, with a screenshot: *"Have new section called Working with Someone else and have
   it show a different way if they are working someone, so it is obvious and we expand."* A customer
   already being courted by another lender is the most consequential answer on this sheet, and it
   used to read as six more boxes in the middle of the goal card.

   🛑 THE SIGNAL IS SECTION-LEVEL — A FILLED BAND AND A RAIL, NEVER A TINTED FIELD OR A RING, and
   that is a correctness rule, not taste. Two colours on this sheet already carry meaning a rep reads
   at speed: amber (`.is-missing`) = "still needed", a TEAL outline (`.s2-here`) = "the script is
   pointing here". A third field-level colour makes all three ambiguous, so this one operates at a
   level neither of those touches. `--danger` is barred outright — design-system rule 13: the alarm
   marks a compliance VERDICT and never appears on a live-call surface, and a competitor is not a
   compliance problem. The band is `--brand-ink`, which is furniture ink on every other screen and
   carries no state meaning to spend.

   🛑 EVERY `display` BELOW CARRIES ITS OWN `[hidden]` OVERRIDE, and the reason is live in this exact
   feature. An AUTHOR `display` rule beats the UA's `[hidden] { display:none }` whatever the
   specificity, so `.ws-lay-offer`'s `display:flex` left a block marked hidden fully on screen while
   `el.hidden` read `true` — and a probe that asked the property reported it working while a rep
   could read six boxes for an offer nobody made. Anything checking these blocks must ask
   `checkVisibility({checkVisibilityCSS:true})`, never `.hidden`. ADD THE OVERRIDE IN THE SAME EDIT
   AS THE `display`, never after. */
.ws-shop { display:block; }
.ws-shop[hidden] { display:none; }
/* ⚖️ THE LEFT BAR IS GONE — Gil, 2026-09-07: *"That line makes it confusing and appears that is the
   section script is on."* It was R-029's *"show a different way"*, and it was doing that job until a
   SECOND thing on this sheet needed to say "you are here": two 3px left edges of similar weight, one
   meaning *another lender is in this deal* and one meaning *the call is here*, is a vocabulary a rep
   has to learn their way out of mid-call. The fact it carried now rides the card's SHUT LINE, which
   reads "Yes · Lender" and follows the rep's click — and "you are here" keeps the ring.
   The indent went with it: an offset that only ever existed to make room for the bar is one more
   thing that makes a live block look different for no reason a rep can name. `is-live` still lands
   on the block — the JS toggles it and the flag band is drawn from it — it just draws nothing. */
.ws-shop-flag {
    display:flex; align-items:center; gap:9px; margin:11px 0 3px; padding:8px 12px;
    border-radius:10px; background:var(--brand-ink); color:#fff;
    font-size:12.5px; font-weight:700; letter-spacing:.01em;
}
.ws-shop-flag[hidden] { display:none; }
.ws-shop-flag-mark { font-size:14px; line-height:1; opacity:.85; }
/* The lender's own name, echoed out of the box below as the rep types — "another lender" tells a rep
   nothing they did not just hear; the name is what they act on. Empty prints nothing rather than a
   dangling dash, the same rule the collapsed goal line follows. */
.ws-shop-flag-who { font-weight:800; }
.ws-shop-panel { display:block; margin-top:2px; }
.ws-shop-panel[hidden] { display:none; }
/* ⚖️ `.ws-shop-offer` RETIRED 2026-09-11 with the shopping-road rebuild — its two blocks became
   `.ws-shop-step`, one per question (Gil: *"as i answer it opens new question"*). 🛑 THE STEPS
   DECLARE NO `display` ON PURPOSE, so the UA's own `[hidden] { display:none }` is what hides them and
   there is no author rule to defeat it — the trap `.ws-lay-offer` fell into below. Give a step a
   `display` and it needs a `[hidden]` override IN THE SAME EDIT; `TheOfferBecomesItsOwnSectionTest`
   pins both halves. The heading's own spacing comes from `.ws-section .ws-sub-label` above, which is
   where the retired rule's 9px was coming from anyway. */
/* ⚠️ "What happened" TAKES THE WHOLE ROW. It is a sentence — *"They pulled credit Tuesday and have
   not called back since"* — and on the goal card's default flex it drew at the width of a money box
   and clipped its own text at about four words. A rep cannot read back what they wrote, which on
   this field is the entire point of the field: it is the only account of WHY another lender said no.
   Seen on the rendered page, not reasoned about. */
.ws-shop-panel .ws-lay-goal .ws-k-offer_story { flex:1 1 100%; }

/* "Any other properties?" — answer to the RIGHT of the question (owner 2026-07-23), not below. */
.ws-oth-toggle { display:flex; align-items:center; gap:10px; margin-bottom:9px; }
.ws-oth-toggle .ws-vlabel { margin-bottom:0; white-space:nowrap; }
.ws-oth-toggle .sr-adorn { max-width:120px; }

/* THE LIEN / PROPERTY TABLE — one row per entity, header centered on top, scrolls right to hold
   every column. `min-width:max-content` makes it scroll rather than crush the columns. */
.ws-lien-scroll { overflow-x:auto; border:1px solid var(--line); border-radius:10px; background:#fff; }
.ws-lien { display:flex; align-items:stretch; border-bottom:1px solid #eef0f5; min-width:max-content; }
.ws-lien:last-child { border-bottom:none; }
/* An empty borrower row sits behind ＋ (owner's row-reveal ruling, 2026-08-01). `display:none` must
   be said out loud: the flex display above beats the bare attribute — the `.ph-dispo-kids` trap. */
.ws-lien[hidden] { display:none; }
.ws-b-add { margin:8px 0 0; }
.ws-b-add[hidden] { display:none; }
.ws-lien-head { background:#f3f5fa; font-size:10px; font-weight:800; text-transform:uppercase;
    letter-spacing:.02em; color:var(--mut); }
.ws-lien-head .ws-lien-cell, .ws-lien-head .ws-b-cell { padding:7px 8px; justify-content:center; text-align:center; }
/* 🛑 THE ROW LABEL IS FROZEN WHILE THE TABLE SLIDES — owner Q12i, 2026-07-31, his words:
   *"Mortgage and other properties, slides which is good i like that but lock the first column on
   sliding"*. Scrolled right to the PMI and HOA columns, every row read as an anonymous strip of
   boxes and a rep could not tell the 1st lien from the 3rd — which is the one place on this sheet
   where putting a number on the wrong row changes the loan.

   `position:sticky` on a flex child inside the `overflow-x:auto` parent: it pins to the left edge
   of the SCROLLER, so it stays put horizontally and still scrolls away vertically with its row,
   which is what it should do. A z-index is required — without it the sibling cells paint over the
   label as they slide under it — and it stays BELOW the app's named scale on purpose: this is a
   cell inside a card, not an overlay, and it must never compete with the call bar or a modal.
   `background` is now load-bearing rather than decoration: a transparent sticky cell shows the
   moving columns straight through itself. */
.ws-lien-rowlab { width:var(--ws-rowlab); min-width:var(--ws-rowlab); display:flex; align-items:center; justify-content:center;
    font-size:11.5px; font-weight:800; color:var(--brand-ink); background:#f8f9fc; border-right:1px solid #eef0f5;
    position:sticky; left:0; z-index:1; }
/* The header's own label cell sits above the body labels where they meet in the corner. */
.ws-lien-head .ws-lien-rowlab { background:#f3f5fa; z-index:2; }

/* 🛑 TWO COLUMNS, NOT ONE — owner 2026-08-01, correcting his own Q12i on seeing it: *"when i meant
   lock first column on sliding i meant, Bank. so i guess lock first 2 columns. here and on property
   slider too"*. The 1st/2nd/3rd strip alone told him WHICH LIEN but not WHOSE — and the bank name is
   how a rep actually recognises the row. So the first DATA column pins too.

   `left:44px` is the row label's own width, and that is the whole coupling: the second sticky column
   must start exactly where the first one ends or they overlap or gap. If `.ws-lien-rowlab`'s width
   ever changes, this number changes with it — pinned by test.

   ONE RULE COVERS BOTH TABLES, which is what makes "here and on property slider too" free: the
   mortgage liens and the other-properties slider are the same `_worksheet-lien-table` partial, so
   the first data cell is Bank on one and Address on the other and neither needs its own rule.
   `:first-of-type` is safe because the row label is a different class, so the first `.ws-lien-cell`
   in a row IS the first data column. */
/* 🛑 THE SECOND FROZEN COLUMN OFFSETS BY THE FIRST ONE'S WIDTH, AND THAT PAIR IS ONE FACT. A literal
   `left:44px` here silently assumed every table using `.ws-lien` has a 44px row label. The Debts grid
   (owner 2026-08-01, from his script screenshot) has a 150px one carrying words like "Personal loan",
   so on scroll its Balance column would have pinned at 44px and slid under its own label. Found by
   verifying the lock rather than assuming Q12i covered it. The width is a variable now, so the next
   table with a different label declares it once instead of inheriting a wrong number. */
.ws-lien-scroll { --ws-rowlab: 44px; }
.ws-debts { --ws-rowlab: 150px; }
/* 🛑 THE BORROWER-KEYED CHARTS DECLARE THEIR OWN, FOR THE `.ws-debts` REASON EXACTLY. Veteran and
   Income became a row-per-borrower table on 2026-08-03 (*"List Borrowers and columns with
   questions"*), and their row label is a PERSON — his *"Borrower 2, Borrower 3"*, or the first name
   when the call captured one. At the inherited 44px "Borrower 2" would wrap to three lines and the
   frozen first column would pin at 44px on scroll while its own label slid out from under it. The
   Borrower(s) card itself keeps 44px on purpose: its rows carry the name three columns along, so
   the label only ever says B1..B5 and a wider frozen column would cost readable width on the one
   table that has the most of it. */
/* 🛑 ONE BLOCK PER BORROWER — everything about a person in one place (owner ~18:0x 2026-08-03:
   *"Moving the income and [veteran] questions up to the borrower section… So it's all on one
   section."*). Each block is a Who row, an Income row and a Veteran row for the SAME person, each
   under its own column headers, with the person's name on the first row only — his *"It doesn't
   have to say the name again."*
   ⚠️ The row-label column holds "Veteran" and a first name, so it declares its own width for the
   `.ws-debts` reason: at the inherited 44px the frozen first column pins at 44px on scroll while its
   own label slides out from under it. */
.ws-b-blocks { --ws-rowlab: 112px; }
/* 🛑 THE SCROLL IS HIS OWN WORD — *"in a scrollable form underneath borrowers"* — and it is capped
   rather than conditional on purpose. A borrower block is ~245px, so one or two borrowers (which is
   almost every file) never reach this and the card behaves exactly as it did. At three the section
   would otherwise run past 900px and push the whole sheet down for a case that is rare. A max-height
   bites only when the content earns it, which is the version that needs no JS and cannot disagree
   with the row-reveal about how many borrowers there are.
   ⚠️ 660px shows two full blocks and the top of a third — the cut edge is what tells a rep there is
   more below. A cap at an exact multiple of the block height reads as the end of the list. */
.ws-b-blocks { max-height:660px; overflow-y:auto; }
.ws-b-blocks .ws-lien-rowlab { justify-content:flex-start; padding-left:10px; text-align:left; }
/* A block is a plain div, so the bare `hidden` attribute works — unlike `.ws-lien`, which is
   display:flex and beats it (the `.ph-dispo-kids` trap). Stated anyway so a later display change
   cannot silently un-hide every borrower the file does not have. */
.ws-b-block[hidden] { display:none; }
.ws-b-block + .ws-b-block { border-top:3px solid var(--line); }
.ws-b-block .ws-lien:last-child { border-bottom:none; }
/* The person's name leads the block; the sub-rows say what they hold, not who. */
.ws-b-who     { font-weight:800; color:var(--brand-ink); }
.ws-b-rowtype { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--mut); }
/* The sub-headers are quieter than the block's own header — they label a row inside a person's
   block rather than opening a new table. */
.ws-b-subhead { background:#f8fafc; }
.ws-b-subhead .ws-b-cell { font-size:9.5px; }
/* Widths sized to the longest OPTION each cell can hold, measured in a browser — never the header.
   `veteran` offers "Yes — in the family" (196px) and `vet_branch` "National Guard". */
/* ⚠️ 122 AND 96 FOR BOTH TABS SINCE 2026-09-08 (evening) — they were 176 and 150 here and 122/96
   under `body.lead-slim`, so the two hosts of this one partial disagreed by 54px and 54px about the
   same two cells. The trimmed pair was measured this afternoon on Script v2 with the widest real
   answers loaded (`construction supervisor`, `250,000`, `Self-Employed`) and nothing clipped; the
   Prequal tab was simply never re-measured, and went on drawing a 176px box around a menu whose
   longest option needs 106. That is the *"as much room as a street address"* Gil reported, and the
   fix for it was already sitting one scope away. `scripts/measure-field-boxes.py` measures both
   tabs and holds this. */
.ws-b-inc-type   { flex:0 0 122px; width:122px; }
.ws-b-inc-money  { flex:0 0 96px;  width:96px; }
.ws-b-inc-se     { flex:0 0 130px; width:130px; }
.ws-b-vet-elig   { flex:0 0 196px; width:196px; }
.ws-b-vet-branch { flex:0 0 156px; width:156px; }
.ws-b-vet-yn     { flex:0 0 116px; width:116px; }
/* 🛑 `:first-of-type` IS BY TAG, NOT BY CLASS — so this rule matched NOTHING on any table that has
   a row label, and the sticky first data column that this file and `pq-worksheet-layout.md` both
   describe has never existed there. `.ws-lien-rowlab` and `.ws-lien-cell` are both `div`, so the
   FIRST div in a labelled row is the label; a `.ws-lien-cell` is therefore never `:first-of-type`.
   Measured on the other-properties table (22 columns, 2315px of content in 1207px): scrolled hard
   right, the label "Prop 1" held at 1px — and the address scrolled to −1063px and left the screen.
   A rep six properties in was steering by a SLOT NUMBER, not by a house.
   ⚠️ The original rule is kept, because it is the one that works on the SUBJECT row — that table has
   no label, so its first div really is a cell (which is why `--ws-rowlab: 0` was needed there). The
   added rule names what was meant: the cell immediately after the label. */
.ws-lien .ws-lien-cell:first-of-type,
.ws-lien > .ws-lien-rowlab + .ws-lien-cell { position:sticky; left:var(--ws-rowlab); z-index:1; background:var(--card); }
.ws-lien-head .ws-lien-cell:first-of-type { background:#f3f5fa; z-index:2; }
.ws-lien-cell { padding:4px 6px; border-right:1px solid #f2f4f8; display:flex; align-items:center; box-sizing:border-box; }
.ws-lien-cell:last-child { border-right:none; }
.ws-lien-cell .sr-adorn { width:100%; }
.ws-lien-cell input { width:100%; min-width:0; }
/* A first-lien-only cost has no spot on a junior lien — hatched, not a box (owner 2026-07-23). */
.ws-lien-na { justify-content:center; color:#c4c9d4; font-weight:700;
    background:repeating-linear-gradient(45deg,#fafbfd,#fafbfd 6px,#f3f5fa 6px,#f3f5fa 12px); }
/* How long = yr + mo, two boxes in one cell. */
.ws-lien-ym { gap:2px; }
.ws-lien-ym input { text-align:center; padding-left:4px; padding-right:4px; }
.ws-ym-x { font-size:10.5px; color:var(--mut); }
.ws-ym-inline { display:flex; align-items:center; gap:2px; }
.ws-ym-inline input { border:1px solid var(--line); border-radius:7px; padding:5px 4px; font:inherit;
    font-size:12.5px; text-align:center; width:100%; min-width:0; }

/* Borrower table cells FLEX to fit (no min-width:max-content on this one) — fits without scrolling,
   scrolls only if the rail is truly narrow. */
/* 🛑 `.ws-fit` IS GONE, AND IT WAS THE CUT-OFF TEXT — owner 2026-08-01, with a screenshot of the
   borrower table reading "Whitfie", "Ye", "MM/DD/YYY" and a 54 showing as "5": *"make sure that the
   information fits in the box so if the screen is too small, it doesn't cut off the info… just make
   it scrollable to the right."*

   The rule set `min-width:0` on the row, overriding the `min-width:max-content` that makes these
   tables SCROLL. So the borrower and debts grids compressed to whatever width they were given and
   clipped their own contents, while the mortgage and other-property grids — which never carried the
   class — scrolled correctly. One class was the whole difference, and on screen it looked like a
   font problem rather than a layout one.

   Every ledger on this sheet now behaves the same way: cells hold their content, and the table
   slides sideways with its first two columns frozen. */
.ws-b-cell { padding:4px 6px; border-right:1px solid #f2f4f8; display:flex; align-items:center; box-sizing:border-box; }
.ws-b-cell:last-child { border-right:none; }
.ws-b-cell .sr-adorn { width:100%; }
.ws-b-cell input { width:100%; min-width:0; }
/* 🛑 FIXED WIDTHS, NOT FLEX — a scrolling ledger must line its columns up ROW TO ROW. Each `.ws-lien`
   is its own flex container, so `flex:N 1 0` let every row size itself independently: with the table
   scrolling, B1's Middle box came out wider than B2's and the columns staggered down the page. The
   mortgage and other-property tables never had this because their columns carry explicit widths.
   Same treatment here, so the header and all three rows share one grid. */
/* 🛑 SIZED TO A REAL SURNAME, AND THIS ONE IS NOT COSMETIC — HUB-11 ruled it FIX on 2026-08-04,
   separating it from the three lien-table clips recorded beside it (OPEN-ITEMS 576). At 150px the
   box gave a value 133px and "Vandenberg-Ashworth" needs 180: it rendered truncated, silently, with
   nothing on the screen saying so.
   🛑 A CLIPPED SURNAME IS WHAT GETS TYPED ONTO A 1003. A clipped lender name is an annoyance a rep
   works around; a name read back wrong off this sheet lands on a legal document, and hyphenated and
   double-barrelled surnames are ordinary rather than edge cases. That is the whole difference in
   severity, and it is why this one moved and the other three did not.
   MEASURED IN A BROWSER, NOT REASONED: 204px is the content width of the longest surname in the
   widest-value fixture plus the cell's own padding and border. The table scrolls, so the cost of the
   extra width is a little more sideways travel and never a squeezed neighbour.
   ⚠️ First and Last SHARE this class deliberately — the note above is about columns lining up row to
   row, and giving Last its own width would stagger the header against the rows it labels. */
.ws-b-name { flex:0 0 204px; width:204px; }
/* ⚠️ MIDDLE IS A NAME FIELD TOO, and the widest-value fixture is what said so: at 84px the box gave
   69px and ordinary middle names — "Nathaniel" 81, "Wilhelmina" 91 — both truncated. It goes on the
   1003 beside the surname, so it is the same ruling and not a separate judgement call. Widened with
   Last rather than after somebody reports it. */
.ws-b-mid  { flex:0 0 128px; width:128px; }
.ws-b-id   { flex:0 0 132px; width:132px; }
/* Age rides the borrower row as its own column (owner 2026-08-01) — a two-digit number, so it takes
   the narrowest track on the table and B2/B3 hatch out of it. */
.ws-b-age  { flex:0 0 68px;  width:68px; }
/* On loan / On title ride the borrower row as columns (owner 2026-08-01) — short answers. */
.ws-b-yn   { flex:0 0 92px;  width:92px; }
/* Contact rides the borrower card (owner 2026-08-01) — five short answers on one row. */
.ws-f[hidden] { display:none; }
/* Agreed the plan, under the goal groups — one short answer on its own line. */
.ws-lay-agreed { display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:9px 10px; margin-top:8px; }
.ws-lay-agreed .ws-k-cash_plan_confirmed { grid-column:1 / span 3; }
/* Contact, one row: number · type · the other number · email (owner 2026-08-01). */
.ws-lay-contact { display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:9px 10px; align-items:end; }
/* 🛑 THE THREE BOXES SIT TOGETHER — owner 2026-08-03: *"Move cell and email over next to Home #."*
   They were not spread for looks: `contact_phone_type` held tracks 4-5 as a visible <select> until
   his 08-02 ruling (*"You might as well have a Home and Cell phone fields and get rid of Type"*)
   turned it into a bare `<input type="hidden">`. A hidden input is `display:none`, so it stopped
   being a grid item — but its two tracks stayed reserved, and the hole where the control used to be
   is the gap he is pointing at. Closing it is deleting the rule for a control that no longer
   renders, not a new layout. The 12-track grid is shared with the address rows, so the boxes keep
   the same track widths their neighbours have. */
/* ⚠️ The number rows are laid by `.ws-phones` (a flex column) since Gil's 2026-08-20 block, not by
   this grid — the placements below stayed behind naming two keys that no longer take part. Kept as
   ONE rule on the row class so a new number row cannot arrive un-placed. */
.ws-lay-contact .ws-phone-row             { grid-column:1 / span 3; }
.ws-lay-contact .ws-k-contact_email       { grid-column:7 / span 4; }
/* the type picker is a real <select>; it wears the same bordered wrapper as every other box */
.ws-lay-contact select { border:none; background:transparent; width:100%; min-width:0; padding:7px 8px;
    font:inherit; color:var(--ink); }
/* Debts — the script's own grid (owner 2026-08-01, from his screenshot): rows are the debt kinds,
   columns are Balance / Payment / Rate / P-O. The label column is wider than a lien's because it
   carries words ("Personal loan") and, on the Other rows, a box. */
.ws-debt-lab   { width:var(--ws-rowlab); min-width:var(--ws-rowlab); justify-content:flex-start; padding-left:10px;
    font-size:11.5px; text-transform:none; letter-spacing:0; }
.ws-debt-lab .sr-adorn { width:100%; }
.ws-debts .ws-lien-head .ws-lien-cell { text-align:center; }
.ws-debt-money { width:150px; min-width:150px; }
.ws-debt-rate  { width:110px; min-width:110px; }
.ws-debt-po    { width:80px;  min-width:80px; }
/* The P/O tick (owner 2026-08-03). Same 18px accent-coloured box as the call grid's
   `.gcs-grid-tick` — one control, two screens, so the sheet and the script cannot drift into
   looking like different questions. Centred in its cell because the column header is centred. */
.ws-debt-tick  { width:18px; height:18px; accent-color:var(--brand); cursor:pointer; margin:0 auto; }

/* A phone or a narrow rail cannot hold the property's six columns — fall back to the plain stack. */
/* 🛑 THE NARROW LAYOUT IS THE ONE HE ACTUALLY LOOKS AT, AND IT WAS THROWING THE WHOLE CARD AWAY.
   This block used to collapse `.ws-lay-property` to ONE column and force every child to
   `grid-column:1 !important` — so on his panel (~590px) the address, city, state and zip each took a
   full-width row of their own, stacked. Every layout instruction he has given for weeks — two rows,
   city/state/zip together, use the width — was being discarded before it reached his screen, and my
   renders at 1000px+ never showed it. He sent a screenshot and it was the first time I saw the sheet
   the way he does.

   ⚠️ THE LESSON, NOT JUST THE FIX: a render is only evidence at the WIDTH IT WAS TAKEN. Mine were
   all wide. Check the narrow one too, or a `!important` in a media query silently outranks the whole
   design.

   So: narrow keeps his STRUCTURE and only sheds the luxury. Address full width; city/state/zip stay
   on ONE row because they are short and belong together; the choice rows stay inline and wrap; the
   value boxes go two-up instead of five-up. */
@media (max-width:860px) {
    .ws-lay-property, .ws-lay-residence, .ws-lay-values, .ws-lay-contact { grid-template-columns:repeat(6,minmax(0,1fr)); }
    /* the address stack — street, then city / state / zip on the line under it */
    .ws-lay-property .ws-k-property_address, .ws-lay-residence .ws-k-primary_address { grid-column:1 / -1; grid-row:auto; }
    .ws-lay-property .ws-k-property_city,  .ws-lay-residence .ws-k-primary_city  { grid-column:1 / span 3; grid-row:auto; }
    .ws-lay-property .ws-k-property_state, .ws-lay-residence .ws-k-primary_state { grid-column:4 / span 1; grid-row:auto; }
    .ws-lay-property .ws-k-property_zip,   .ws-lay-residence .ws-k-primary_zip   { grid-column:5 / span 2; grid-row:auto; }
    /* 🛑 THE VALUE BOXES ARE PLACED ON A TWELVE-TRACK PLAN AND THIS GRID HAS SIX. A box left at
       `grid-column:10` is off the end here, so the browser invents implicit tracks and the row
       scatters. Nothing errors; it simply lays out wrong, which is the same shape as the
       unplaced-children defect that produced *"this is what you deploy???????"*.
       ⚠️ THE FOUR VALUE BOXES ARE NO LONGER IN THIS GRID (Gil's 08-19 row owns them, and it is a
       six-column grid of its own that SCROLLS rather than re-flows — so it needs nothing here). Only
       what actually stayed is re-placed for six. */
    .ws-lay-values .ws-k-estimated_equity { grid-column:4 / span 3; grid-row:auto; }
    .ws-lay-values .ws-k-prop_rental_income { grid-column:1 / span 6; grid-row:auto; }
    .ws-lay-values .ws-k-mtg1_lates_12mo  { grid-column:1 / span 6; grid-row:auto; }
    /* the choice rows and everything else take the full width, one per line */
    .ws-lay-property > .ws-f:not([class*="ws-k-property_"]),
    .ws-lay-residence > .ws-f:not([class*="ws-k-primary_"]),
    .ws-lay-contact > .ws-f { grid-column:1 / -1; grid-row:auto; }
    /* values two-up rather than five across */
    .ws-lay-values > .ws-f { grid-column:span 3; grid-row:auto; }
    .ws-lay-values .ws-k-years { grid-column:span 3; }
}
/* Send to RAD, top right of the Prequal tab (owner 2026-08-01). Above the worksheet form, because
   it carries its own form and HTML cannot nest them. */
.ws-rad { display:flex; align-items:center; justify-content:flex-end; gap:8px; flex-wrap:wrap;
    margin:0 0 10px; }
.sr-worksheet-foot { position:sticky; bottom:0; padding:10px 0 2px; background:linear-gradient(180deg,transparent,var(--card,#fff) 40%); }

/* Hot Buttons panel (`hbp-`, 2026-07-19 Stage 4) — the rep-facing catalogue surface. Shared
   vocabulary: like the worksheet it rides BOTH the person page and the in-call FILE rail, so it
   lives here, not in an in-view block. Reuses `.card`/`.tag`/`.tag.warn`; only its own layout is here.
   Amber `.tag.warn` on PURPOSE for the derived numbers — internal, never alarm-red on a live call. */
.hbp-panel { display:grid; gap:12px; }
.hbp-btn { padding:14px 16px; }
.hbp-name { margin:0; font-size:15px; color:var(--brand-ink); }
.hbp-one { margin:3px 0 0; font-size:13px; line-height:1.5; color:var(--ink); }
.hbp-derived { display:flex; align-items:center; flex-wrap:wrap; gap:7px; margin-top:10px; }
.hbp-derived-tag { font-size:10px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; color:#8a5a00; }
.hbp-chip { font-variant-numeric:tabular-nums; }
.hbp-plan { margin-top:10px; }
.hbp-plan > summary { cursor:pointer; color:var(--mut); font-size:12.5px; width:max-content; }
.hbp-plan-body { margin:8px 0 0; font-size:12.5px; line-height:1.55; color:var(--ink); white-space:pre-line; }
.hbp-line { margin:8px 0 0; font-size:13px; line-height:1.5; color:var(--ink); }
.hbp-sell { background:#eef1f9; color:var(--brand-ink); }
/* WHOSE LINE IT IS (owner ruling 2026-07-28). An unlicensed LOA now SEES the sell line — "it is good
   for them when learning" — so the screen has to say the thing the gate used to say by hiding it.
   Shown ONLY to a reader who is not licensed to speak it; a licensed LO gets nothing extra. Wording
   is COMPLIANCE-RULES.md's own: "LOAs sell the conversation, not the loan… and warm-handoff to a
   licensed LO." 🛑 The ruling opened the LINE, not the LANE. Shared, because three surfaces render it
   (the rail, the person page, the pinned Prequal) — see route-cues.md. */
.hbp-handoff, .ws-cue-handoff { display:block; margin-top:3px; font-size:11.5px; font-weight:700;
    color:#8a6d1f; }
.hbp-situational > summary { cursor:pointer; color:var(--mut); font-size:13px; font-weight:600; width:max-content; }
.hbp-situational-body { display:grid; gap:12px; margin-top:12px; }
.hbp-none { margin:0; }
/* WANTED (H2): a surfaced button the customer decided they want reads green — a left accent + tint,
   the same green vocabulary as `.tag.ok` (a state that is FINE). Display only, never a firing state. */
.hbp-wanted { border-left:3px solid #137a45; background:#f2fbf6; }
.hbp-wanted-tag { margin-left:7px; vertical-align:middle; }
/* "ON FILE" (2026-07-28) — a value intake already sent, shown beside an UNANSWERED capture box so the
   rep confirms instead of asking. Lives here, not in the runner's own <style>, because capture-input
   is a SHARED partial: the worksheet hosts it too, and a rule that reached only the runner is the
   `.sr-adorn` bug that put the $ outside the box on the worksheet for a week. Quiet on purpose —
   it is a hint next to an empty box, never a value competing with what the rep is about to type,
   and never the amber `internal` chip, which means a derived number that must not be spoken. */
.sr-onfile { display:inline-block; margin-left:6px; padding:1px 7px; border-radius:999px;
    background:#eef2fb; border:1px solid #dbe4f7; color:var(--mut);
    font-size:10.5px; font-weight:700; white-space:nowrap; vertical-align:middle; }
/* 🛑 ON THE SHEET IT TAKES ITS OWN LINE UNDER THE FIELD (2026-08-14). It is a sibling of the box now
   rather than a child of it — see `capture-input.blade.php` for why — and these layouts size their
   fields to the grid, so a `nowrap` chip left on the same line would push the row wide or be cut off
   again by the next ancestor. Its own line costs 15px and is always fully readable, which is the
   entire point of the control: a rep confirms a number we already hold instead of asking for it. */
.ws-lay-property .sr-onfile, .ws-lay-values .sr-onfile, .ws-lay-residence .sr-onfile,
.ws-lay-contact .sr-onfile, .ws-lien-cell .sr-onfile, .ws-b-cell .sr-onfile, .ws-grid .sr-onfile {
    display:block; margin:3px 0 0; width:fit-content; max-width:100%; overflow:hidden;
    text-overflow:ellipsis; }

/* THE STRATEGY header (`hbp-strategy`, 2026-07-27 design §6 / approval §S) — the combination the call
   is selling, sitting above the plays it orders. Rides BOTH the person page and the in-call rail, so
   it lives here with the rest of the `hbp-` vocabulary. Deliberately the QUIETEST loud thing on the
   panel: a brand-ink left accent and a small uppercase label, no tint competing with `.hbp-wanted`'s
   green or the amber internal chips. It is a heading, not an alert — nothing here is ever spoken. */
.hbp-strategy { padding:12px 16px; border-left:3px solid var(--brand-ink); }
.hbp-strategy-lbl { font-size:10px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:var(--mut); }
.hbp-strategy-name { margin:2px 0 0; font-size:16px; line-height:1.3; color:var(--brand-ink); }
.hbp-strategy-nolead { margin:5px 0 0; font-size:12.5px; line-height:1.5; color:var(--mut); }
.hbp-strategy-plan { margin-top:8px; }
/* The LEAD play — the ONE thing this combination sells. A left accent in the same brand ink ties it
   to the header above it; the supports under it stay plain, which is the entire point of the object. */
.hbp-lead { border-left:3px solid var(--brand-ink); }
.hbp-lead-tag { margin-left:7px; vertical-align:middle; background:var(--brand-ink); color:#fff; }

/* The MANUAL MARK (2026-07-27) — on the live rail only, and only while a play is still neutral.
   Deliberately quiet: it is a rep's own bookkeeping mid-call, not a call to action competing with
   the words above it. Once marked the control is gone and the green tag is the state. */
.hbp-mark { margin-top:10px; }
.hbp-mark .icon-btn { font-size:12px; padding:5px 10px; }

/* ---- THE PLAY (`play-`, design 2026-08-01) ----------------------------------------------------
   His review page: the call's answers read together, ending in two figures — TOTAL AMOUNT NEEDED and
   LTV NEEDED. Owner-only, between calls, so it may be dense and it may show internal numbers.

   🛑 IT LIVES HERE RATHER THAN IN AN IN-VIEW BLOCK because `play-why` and `play-react` are declared
   by `_hot-buttons`, which is SHARED with the live rail — a rule reaching only `_play` would style
   half of its own markup. Design-system rule 8's own worked example (the `.sr-adorn` bug that put
   the $ outside the box on the worksheet for a week) is exactly this shape.

   🛑 NOTHING HERE IS ALARM-RED. Every internal number wears the amber `.tag.warn` vocabulary (R3);
   a blank is grey and carries its reason as text, never a red state — a missing answer is not a
   fault, and painting it as one would train him to stop reading the page. */
.play { display:grid; gap:14px; }
.play-standfirst { margin:0; padding:11px 14px; border-radius:10px; background:#f7f8fb;
    border:1px solid var(--line,#e6e9f2); font-size:13px; line-height:1.55; color:var(--ink); }
.play-sec { padding:14px 16px; }
.play-sec > .ttl { margin:0 0 10px; }
/* The answers block — label above value, wrapping into as many columns as fit. */
.play-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:10px 18px; }
.play-cell { display:flex; flex-direction:column; gap:2px; min-width:0; }
.play-k { font-size:10.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; color:var(--mut); }
.play-v { font-size:14px; line-height:1.4; color:var(--ink); word-break:break-word; }
.play-piti { margin-left:6px; vertical-align:middle; background:#eef2fb; color:var(--brand-ink); }
/* A figure we cannot state, and WHY. Grey and small — the reason is the content, not an alarm. */
.play-blank { color:var(--mut); font-size:12.5px; font-style:italic; }
.play-num { font-variant-numeric:tabular-nums; font-weight:700; color:var(--ink); }
/* 🛑 THE ESTIMATE MARK RIDES EACH FIGURE (P10), never only a page footer — one figure is what gets
   screenshotted, and it has to carry its own caveat when it travels alone. */
.play-est { display:inline-block; margin-left:6px; padding:0 6px; border-radius:999px;
    background:#fdf3e0; color:#8a5a00; font-size:9.5px; font-weight:800; letter-spacing:.05em;
    text-transform:uppercase; vertical-align:middle; }
.play-pct { margin-left:6px; color:var(--mut); font-size:12px; font-variant-numeric:tabular-nums; }
.play-tbl { width:100%; border-collapse:collapse; }
.play-tbl td, .play-tbl th { padding:7px 0; border-bottom:1px solid var(--line,#eef0f6);
    font-size:13.5px; line-height:1.45; text-align:left; }
.play-tbl th { font-size:10.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; color:var(--mut); }
.play-tbl tr:last-child td { border-bottom:0; }
.play-amt { text-align:right; white-space:nowrap; }
.play-tbl-sub td { border-top:2px solid var(--line,#e6e9f2); border-bottom:0; font-weight:700; padding-top:9px; }
.play-tbl-off td { color:var(--mut); }
.play-detail { display:block; font-size:12px; color:var(--mut); }
.play-note { margin:10px 0 0; font-size:12.5px; line-height:1.55; color:var(--mut); }
.play-empty { margin:0; font-size:13px; }
.play-gap { margin:11px 0 0; font-size:13px; line-height:1.5; color:var(--ink); }
/* THE TWO HEADLINE FIGURES — the page exists to produce these, so they are the only large numbers. */
.play-headline { margin-top:12px; padding:12px 14px; border-radius:10px; background:#f4f6fc;
    border-left:3px solid var(--brand-ink); display:grid; gap:2px; }
.play-headline-k { font-size:10.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; color:var(--mut); }
.play-headline-v { font-size:26px; line-height:1.15; font-weight:800; color:var(--brand-ink);
    font-variant-numeric:tabular-nums; }
.play-headline-v .play-blank { font-size:14px; font-weight:400; color:var(--mut); }
.play-headline-sub { font-size:12.5px; color:var(--mut); }
/* 🛑 THE ESCROW REFUND SITS BELOW THE HEADLINE AND IS VISIBLY NOT PART OF IT (P11). It offsets what
   the deal costs THEM; it never shrinks the loan and never moves the LTV. Styled as an aside — no
   left accent, no large type — because above the line it would quietly make every deal look doable. */
.play-offset { margin:10px 0 0; padding:9px 12px; border-radius:9px; background:#f4faf6;
    border:1px dashed #b9dfc8; display:grid; gap:2px; }
.play-offset-k { font-size:10.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; color:#137a45; }
.play-offset-v { font-size:16px; font-weight:700; color:#0f5c34; font-variant-numeric:tabular-nums; }
.play-offset-sub { font-size:12px; line-height:1.5; color:var(--mut); }
/* Estimated value + its Zillow button, on one line. */
.play-value { display:flex; align-items:center; flex-wrap:wrap; gap:9px; }
.play-value-v { font-size:20px; font-weight:800; color:var(--ink); font-variant-numeric:tabular-nums; }
.play-value-src { font-size:11.5px; color:var(--mut); }
/* 🛑 THE ADDRESS IS TWO ROWS — his standing rule, *"Address top row; city, state, zip in the second
   row."* Here it is display text, not boxes, so the rule is the block layout itself. */
.play-addr { margin:0 0 9px; display:grid; gap:1px; }
.play-addr-1 { font-size:14px; color:var(--ink); }
.play-addr-2 { font-size:12.5px; color:var(--mut); }
.play-sec-draft > .ttl .tag { margin-left:8px; vertical-align:middle; }
.play-tbl-draft tbody .play-empty { color:var(--mut); font-style:italic; padding:12px 0; }
/* P15 — WHY A PLAY FIRED, under the play itself. Quiet: it is provenance, not a second one-liner. */
.play-why { margin-top:10px; padding:9px 11px; border-radius:9px; background:#f7f8fb; }
.play-why-lbl { display:block; font-size:9.5px; font-weight:800; letter-spacing:.06em;
    text-transform:uppercase; color:var(--mut); margin-bottom:5px; }
.play-why-leg { display:flex; align-items:center; flex-wrap:wrap; gap:6px; margin-top:5px; }
.play-why-clause { font-size:12.5px; color:var(--ink); }
.play-why-cell { background:#eef2fb; color:var(--brand-ink); font-variant-numeric:tabular-nums; }
.play-why-derived { font-variant-numeric:tabular-nums; }
/* PHASE 2 — how the customer took the play. One row of chips; the lit one is the recorded answer and
   pressing it again clears it. Draft vocabulary (CaptureWriter::REACTIONS) — his to cut. */
.play-react { display:flex; align-items:center; flex-wrap:wrap; gap:6px; margin-top:11px;
    padding-top:10px; border-top:1px solid var(--line,#eef0f6); }
.play-react-lbl { font-size:9.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase;
    color:var(--mut); margin-right:2px; }
.play-react-f { display:inline; }
.play-react-b { font-size:12px; padding:4px 10px; }
.play-react-on { background:var(--brand-ink); color:#fff; border-color:var(--brand-ink); }
.play-react-none { font-size:11.5px; color:var(--mut); font-style:italic; }

/* Auto-grow sentence textareas in the script editor — a line the box hides is a line edited blind
   (owner 2026-07-20). The `.js-grow` JS in manage/scripts/show.blade.php sizes them to content. */
textarea.js-grow { width:100%; box-sizing:border-box; resize:vertical; overflow:hidden; min-height:2.6em; line-height:1.4; font:inherit; }

/* ---- SPARRING PIT review queue + transcript --------------------------------------------------
   🛑 THESE LIVED IN `resources/css/app.css` AND WERE NEVER SERVED (found 2026-07-22). The layout
   loads ONLY /css/cclg.css (design-system rule 8: styles live here, never in a view, never in a
   second sheet). app.css is a Vite input nothing on the page requests, so from the day the review
   screen shipped the whole transcript rendered as undifferentiated paragraphs — no REP/CUSTOMER
   bands, no tint on the flagged line, no red on the flag note. The owner's report was exactly that
   symptom: "I have to read the whole thing to find what's wrong." A 3,032-test suite cannot see an
   unstyled page. Ported here verbatim, then extended below.

   PALETTE NOTE: the compliance alarm red (--danger / .alert-compliance) stays reserved for a
   compliance FAIL — which is precisely what a floor hit on a rep line IS, so `.pit-flagged` and
   `.pit-hit` are the one place in this file entitled to it. */
.pit-row .pit-row-head { display:flex; align-items:baseline; gap:.5rem; flex-wrap:wrap; }
.pit-row .pit-row-head h2 { margin:0; font-size:1.05rem; }
.pit-reasons { margin:.5rem 0; padding-left:1.1rem; }
.pit-reasons li { margin:.2rem 0; }
/* "seen in an earlier sweep" — muted on purpose. The finding is still listed (a recurring
   compliance hit is never hidden); it just stops competing with what is actually new. */
.pit-seen { color:#7a8290; font-size:.85rem; font-style:italic; }
.pit-meta { color:#5b6472; font-size:.9rem; }
.pit-note { background:#fff8e6; border-left:3px solid #e0a800; padding:.5rem .75rem; }
.pit-filters { display:flex; align-items:center; gap:.75rem; flex-wrap:wrap; }
.pit-count { margin-left:auto; color:#5b6472; font-size:.9rem; }
.pit-inline { display:inline-flex; align-items:center; gap:.35rem; margin-right:.9rem; }
.pit-turn { padding:.45rem .7rem; border-radius:6px; margin:.35rem 0; }
.pit-turn p { margin:.15rem 0; }
.pit-turn .pit-who { font-size:.72rem; letter-spacing:.06em; text-transform:uppercase; color:#5b6472; }
.pit-turn.pit-rep { background:#f4f6fb; border-left:3px solid var(--brand, #35529b); }
.pit-turn.pit-cust { background:#fafafa; border-left:3px solid #c9cdd4; }
.pit-turn .pit-flagged { background:#ffecec; }
.pit-turn .pit-flag-note { color:#a12020; font-size:.85rem; }


/* THE LINE THAT IS ACTUALLY WRONG — findable without reading the call (owner, 2026-07-22).
   Every finding already stored `idx` (which turn) and `phrase` (the exact words); neither had ever
   been shown, so a 17-turn transcript was a reading task. The turn gets a red rail; the offending
   WORDS get the mark. Nothing else on the page is allowed to use this treatment. */
.pit-turn.pit-turn-hit { background:#fff4f4; border-left:4px solid #a12020; box-shadow:0 0 0 1px #f3cfcf; }
.pit-turn.pit-turn-hit .pit-who { color:#a12020; font-weight:700; }
mark.pit-hit { background:#ffd6d6; color:#7a1414; font-weight:700; padding:0 3px; border-radius:3px; }

/* A rapport tell — ADVISORY coaching from the rapport doctrine ("this line is cold"), so amber like
   everything that is a nudge rather than a violation. The alarm red stays reserved for the floor. */
.pit-turn .pit-rapport-note { color:#8a5a00; font-size:.85rem; }
.pit-turn .pit-rapport-note .muted { color:#a98f56; font-size:.78rem; }

/* An improvised turn is not a fault by itself — it is the SCRIPT having had no line to read. Amber,
   never the alarm red: "the library ran out here" is a coverage gap, not a crossed line. */
.pit-turn.pit-turn-improv { border-left:3px dashed #b8860b; background:#fffdf5; }
.pit-turn .pit-improv-tag { color:#8a5a00; font-weight:700; }

/* The jump link from a queue reason to the line that caused it. */
.pit-jump { font-size:.85rem; font-weight:600; white-space:nowrap; }
.pit-turn:target { animation:pit-flash 1.6s ease-out 1; }
@keyframes pit-flash { 0%,40% { box-shadow:0 0 0 3px #f6b8b8; } 100% { box-shadow:none; } }

/* The teach drawer under each rep line. Closed by default so the transcript stays readable. */
.pit-teach { margin-top:.5rem; }
.pit-teach > summary { cursor:pointer; color:var(--brand, #35529b); font-size:.85rem; font-weight:600; width:max-content; }
.pit-teach form { margin-top:.6rem; }
.pit-teach label { display:block; margin:.5rem 0 .2rem; font-size:.85rem; font-weight:600; }
.pit-teach textarea { width:100%; box-sizing:border-box; }

/* ── Conversation Log (owner 2026-07-23) ─────────────────────────────────────────────────────────
   The merged Timeline+Notes feed: a rep types an entry (with an ACTION), edits their own (history
   kept), and calls + deal moves appear automatically as locked grey rows. Light theme only, house
   tokens (--brand/--ink/--mut/--line). The table already scrolls inside its box (.wrap table). */
.cl-legend { font-size:12.5px; color:var(--mut); margin:0 0 12px; }
.cl-logger { margin-bottom:14px; }
.cl-logger-row { display:flex; gap:10px; align-items:flex-start; flex-wrap:wrap; }
.cl-logger select { flex:0 0 auto; min-width:150px; padding:9px 11px; border:1px solid var(--line);
    border-radius:8px; background:var(--card); color:var(--ink); font:inherit; }
.cl-logger textarea { flex:1 1 260px; padding:9px 11px; border:1px solid var(--line); border-radius:8px;
    background:var(--card); color:var(--ink); font:inherit; resize:vertical; }
.cl-logger-actions { margin-top:12px; }

/* "＋ Schedule a follow-up" — collapsed reveal inside the Add box. */
.cl-fu { margin-top:10px; }
.cl-fu > summary { cursor:pointer; color:var(--brand); font-size:13px; font-weight:600; width:max-content; list-style:none; }
.cl-fu > summary::-webkit-details-marker { display:none; }
.cl-fu-box { margin-top:10px; padding:12px; border:1px dashed var(--line); border-radius:8px;
    background:#fafbfe; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.cl-fu-box label { font-size:12px; color:var(--mut); font-weight:600; }
.cl-fu-box input[type=datetime-local], .cl-fu-box input[type=text] { font:inherit; padding:8px 10px;
    border:1px solid var(--line); border-radius:8px; background:#fff; color:var(--ink); }
.cl-fu-box input[type=text] { flex:1 1 220px; }

/* 💬 …AND TELL SOMEBODY — the log writer's one-line way to hand a colleague what just happened
   (⚖️ Gil 2026-08-31, on Carlos taking a call from Max's client).

   🛑 IT SITS OPEN, NOT INSIDE A FOLD, unlike the follow-up drawer directly above it. Scheduling a
   callback is the rarer act; telling the rep whose customer you just spoke to is the ordinary one on
   an inbound that was never yours. A control folded away is the "New chat" button that recorded zero
   messages ever sent.
   ⚠️ QUIETER THAN THE FOLLOW-UP BOX ON PURPOSE — no dashed frame. Two boxed panels stacked under one
   textarea makes the writer look like a form to fill in, which is what pushed it off the screen in
   the first place. */
.cl-tell { margin-top:10px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.cl-tell label { font-size:12px; color:var(--mut); font-weight:600; }
.cl-tell select { font:inherit; padding:8px 10px; border:1px solid var(--line);
    border-radius:8px; background:#fff; color:var(--ink); }
.cl-tell-why { font-size:12px; flex:1 1 240px; }

/* The two log views (owner 2026-08-04). Plain links, styled as a quiet segmented control — the
   selected one is the only thing that looks pressed, so which list you are reading is never a
   guess. Deliberately NOT the tabstrip's own styling: this sits inside a tab and must not read as
   a second row of tabs competing with it. */
.cl-views { display:flex; gap:6px; margin:0 0 10px; }
.cl-view { font-size:12.5px; font-weight:700; padding:5px 12px; border:1px solid var(--line);
    border-radius:999px; color:var(--mut); background:#fff; text-decoration:none; white-space:nowrap; }
.cl-view:hover { border-color:var(--brand-ink); color:var(--ink); }
.cl-view.is-on { background:#eef1f9; border-color:var(--brand-ink); color:var(--brand-ink); }

.cl-table { width:100%; border-collapse:collapse; font-size:14px; }
.cl-table thead th { text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.05em;
    color:var(--mut); font-weight:700; padding:8px 12px; border-bottom:1px solid var(--line); }
.cl-table tbody td { padding:11px 12px; border-bottom:1px solid var(--line); vertical-align:top; }
.cl-table tbody tr:last-child td { border-bottom:0; }
.cl-sysrow td { background:#f6f7fb; }               /* automatic entries read as a quieter band */
.cl-when { white-space:nowrap; color:var(--mut); font-size:13px; }
.cl-when .muted { font-size:12px; }
.cl-who { white-space:nowrap; font-weight:600; }
.cl-act { white-space:nowrap; }
/* 🛑 THE SUB-LINES WRAP EVEN THOUGH THE COLUMN DOES NOT, AND THIS WAS A REAL REGRESSION CAUGHT BY
   LOOKING. `.cl-act` is `nowrap` so a chip never breaks away from its duration — right for
   "36s · Wrong Number". But 2026-08-04 the folded script line put PROSE in that column ("Refinance ·
   Closed when the call was dispositioned"), and unwrappable prose made the Action column wider than
   the whole table: on a 590px panel — the width the owner reviews at — it pushed the NOTE COLUMN
   CLEAN OFF the right-hand edge, header and all.
   ⚠️ AND THE AUTOMATED CHECK SAID IT WAS FINE. `.stage`/the log wrapper scrolls (`overflow-x:auto`),
   so the overflow lands on the CONTAINER and every cell still reports `scrollWidth == clientWidth`.
   A per-cell clipping sweep cannot see this class of defect at all; a screenshot at his width shows
   it instantly. Measure AND look.
   The chip keeps `nowrap` from the rule above; only the prose beneath it is allowed to break. */
.cl-act .cl-sub { white-space:normal; }
.cl-body { white-space:pre-wrap; }
/* 🛑 NOBODY SAID THIS — ⚖️ Gil 2026-09-09: *"we need to change activity log to be more clear as far
   as what are notes that were written by rep or not."* He had just read a machine sentence under a
   column headed "What was said" and asked whether his rep had typed it.

   ITALIC AND MUTED, AND NOTHING ELSE. No glyph, no badge, no legend to learn: the distinction has to
   survive being read at a glance by somebody who has never been told a rule. It works by INVERSION —
   plain dark type in this column now means a person typed these words, and it is the only thing that
   does. That is the same lesson the 🔒 taught on 2026-08-18 (*"what is the reason for the Lock next
   to missed call?"*): a mark on the MAJORITY marks the normal case and says nothing, so the emphasis
   belongs on the rare thing, which here is a human being.

   ⚠️ IT IS NOT `--mut` ON ITS OWN. `.cl-sub` is already muted and reads as "secondary", not as "not
   a person" — a second muted grey would have said nothing new. The slant is what carries it. */
.cl-auto { font-style:italic; color:var(--mut); }
/* The key, ABOVE the table rather than inside a column heading — `ActivityLogCallColumnsTest` guards
   Gil's four column names and went red when it was first written into the *What was said* cell,
   correctly: those names are his own words and a caption is not one of them. It also belongs to the
   whole table, because the italics reach the Action sub-lines too. Quiet by design — an aside, not a
   fifth thing competing for the same glance. */
.cl-key { margin:0 0 8px; font-size:12px; color:var(--mut); }
.cl-key i { font-style:italic; }
.cl-sub { font-size:13px; margin-top:3px; }
.cl-rec { display:flex; align-items:center; gap:10px; }

/* ⚖️ RIDER 6 (owner 2026-08-09) — the message in TEXT on the lead's own timeline, beside the
   player. 🛑 The gist reads as the row's content (ink), the full transcript hides behind a summary
   and wears quotation grey — the same hierarchy the mailbox row uses, so a rep meets one shape in
   both places. Native <details>: no JS, and it degrades to visible text rather than vanishing. */
.cl-vm-read { margin-top:4px; }
.cl-vm-gist { display:block; color:var(--ink); }
.cl-vm-read > details > summary { cursor:pointer; font-size:12.5px; font-weight:700; color:var(--brand);
                                  list-style:none; margin-top:2px; }
.cl-vm-read > details > summary::-webkit-details-marker { display:none; }
.cl-vm-read > details > summary::before { content:'▸ '; }
.cl-vm-read > details[open] > summary::before { content:'▾ '; }
.cl-vm-read > details > p { margin:4px 0 0; font-size:12.5px; line-height:1.5; color:var(--mut);
                            white-space:pre-wrap; overflow-wrap:anywhere; max-width:60ch; }
.cl-lock { font-size:12px; color:var(--mut); }
/* ⚖️ THE ROW'S ATTACHMENTS ARE ICONS, NOT BUBBLES — owner 2026-08-18: *"we need to make it look
   better though. with icons instead of big bubbles and a pop up when pressed."* The recording,
   the transcript and a customer's voicemail each became a full-width line inside the Action cell,
   so a call carrying all three pushed everything else down the page. One row of small round
   buttons instead, each opening its own panel over the row. */
.cl-atts { display:flex; align-items:center; gap:6px; margin-top:5px; }
.cl-att { position:relative; }
.cl-att > summary { list-style:none; cursor:pointer; display:inline-flex; align-items:center;
    justify-content:center; width:26px; height:26px; border-radius:8px; font-size:13px;
    background:#eef1f7; box-shadow:inset 0 0 0 1px var(--line); }
.cl-att > summary::-webkit-details-marker { display:none; }
.cl-att > summary:hover { background:#e4e9f3; }
.cl-att[open] > summary { background:var(--brand); }
/* 🛑 THE PANEL IS `position:fixed`-LIKE IN INTENT BUT ABSOLUTE IN FACT, and it must escape the
   table cell it is born in: `.cl-table td` clips nothing, but a panel anchored inside a narrow
   Action column would be 200px wide. Anchored to the row's own positioned wrapper instead. */
.cl-att-pop { position:absolute; left:0; top:calc(100% + 6px); z-index:6;
    width:min(520px, 78vw); padding:13px 15px; border:1px solid var(--line); border-radius:11px;
    background:var(--card); box-shadow:0 14px 34px -14px rgba(17,12,29,.4);
    font-size:12.5px; line-height:1.55; color:var(--brand-ink); font-weight:400;
    max-height:340px; overflow-y:auto; white-space:normal; }
.cl-att-pop h4 { margin:0 0 7px; font-size:12px; font-weight:800; color:var(--brand-ink);
    text-transform:uppercase; letter-spacing:.05em; }
.cl-att-pop audio { width:100%; margin:2px 0 6px; }
.cl-att-pop .muted { display:block; margin-top:7px; }
/* The right-hand columns must not squeeze the words. Note is where the long text lives now. */
.cl-table td.cl-act { width:34%; }
.cl-edited { font-size:11px; color:var(--mut); font-style:italic; margin-top:3px; }

/* Action tags — a coloured pill per typed action; grey for automatic rows. */
.cl-tag { display:inline-block; font-size:11px; font-weight:700; padding:3px 9px; border-radius:6px;
    white-space:nowrap; }
.cl-a-note        { background:#fff3e0; color:#b26a00; }
.cl-a-call        { background:#e8f1ff; color:#1f5fd0; }
.cl-a-voicemail   { background:#eef0fb; color:#4b3fb0; }
.cl-a-text        { background:#f0eafb; color:#6f42c1; }
.cl-a-email       { background:#e3f4f7; color:#0b6b7d; }
.cl-a-docs_received { background:#e6f4ea; color:#0f7d43; }
.cl-sys { background:#eceef4; color:#5a6473; border:1px solid var(--line); }

/* ⚖️ THE CALL CHIPS CARRY THEIR DIRECTION IN COLOUR — Gil 2026-09-10: *"Add some color to Inbound
   and outbound call like you have for missed call. Maybe phone with arrow in and arrow out."* The
   glyph rides the title in `LeadTimeline`; these are only the tints, so a relabel cannot grey a
   chip out.
   🛑 MISSED IS AMBER, NOT RED — his ruling the same evening, and his reasoning: a missed call is
   work to do, not a failure, and red would be the only alarm colour on the page. It stays the
   warmest of the three so it still reads first. */
.cl-call-in      { background:#e8f1ff; color:#1f5fd0; }
.cl-call-out     { background:#e6f4ea; color:#0f7d43; }
.cl-call-missed  { background:#fdf1dd; color:#8a5a00; }

/* JS-free inline editor: <details> holds the PATCH form. */
.cl-editbox { margin-top:6px; }
.cl-editbox > summary { cursor:pointer; color:var(--brand); font-size:12px; font-weight:600; width:max-content; list-style:none; }
.cl-editbox > summary::-webkit-details-marker { display:none; }
.cl-editbox form { display:flex; gap:8px; align-items:flex-start; margin-top:6px; }
.cl-editbox textarea { flex:1 1 auto; padding:7px 9px; border:1px solid var(--brand); border-radius:6px;
    background:var(--card); color:var(--ink); font:inherit; resize:vertical; }

/* ── Follow-ups: snooze control + bucket rows (owner 2026-07-23) ─────────────────────────────────── */
.fu-row-right { display:flex; align-items:flex-start; gap:8px; flex-wrap:wrap; justify-content:flex-end; }

/* The booking door on Overview (design verdict 15 §2 — the Follow-ups tab died and booking became
   an ACT; owner Q12l — it uses the SAME picker as the hang-up capsule).

   🛑 THIS BLOCK IS A WIDTH CONSTRAINT AND NOTHING ELSE. Every .ph-cb-* / .ph-cal-* rule above is
   global, so the picker arrives here already styled — that is the point of sharing the partial, and
   nothing here may restyle it or the two doors start looking like two controls. The one thing that
   does not travel is SIZE: the capsule is a narrow modal where `.ph-cb-open { width:100% }` and a
   full-bleed calendar are right, and an Overview card is three times wider, where the same rules
   give a stretched pill and a calendar with enormous day cells. Bounded, not overridden. */
.fu-book { max-width:320px; }
.fu-book .ph-xfer-row { margin-bottom:4px; }

/* Follow-ups beside To-dos on Overview — owner 2026-08-01: *"i would put the 2 side by side, follow
   up and to-do's"*. They answer one question between them ("what do I still owe this person"), and
   stacked they pushed the second below the fold on the screen that exists to answer it at a glance.
   `align-items:start` so a card with three callbacks does not stretch an empty one to match it.
   One column below 900px: side-by-side on a phone would give two ~170px columns, and the callback
   calendar alone needs more than that. */
.fu-pair { display:grid; grid-template-columns:1fr 1fr; gap:16px; align-items:start; }
.fu-pair > .card { margin-bottom:0; }
@media (max-width:900px) { .fu-pair { grid-template-columns:1fr; } }
.cl-snz { position:relative; }
.cl-snz > summary { list-style:none; cursor:pointer; }
.cl-snz > summary::-webkit-details-marker { display:none; }
.cl-snz-menu { margin-top:6px; display:flex; gap:6px; flex-wrap:wrap; align-items:center;
    padding:8px; border:1px solid var(--line); border-radius:8px; background:#fafbfe; }
.cl-snz-menu form { margin:0; }
.cl-snz-custom { display:flex; gap:6px; align-items:center; }
.cl-snz-custom input[type=datetime-local] { font:inherit; padding:6px 8px; border:1px solid var(--line);
    border-radius:6px; background:#fff; color:var(--ink); }

/* THE DISMISS CONFIRM (owner 2026-08-04: "make dismiss ask are you sure before it deletes").
   Borrows the .cl-snz <details> idiom wholesale and only re-stacks it: the warning sentence has to
   sit ABOVE the button, so this is a column where snooze is a wrapping row.
   🛑 IN-FLOW, NOT ABSOLUTE — inherited from .cl-snz-menu and load-bearing on the callback corner,
   which is `position:fixed; bottom:22px`. Opening it there grows the card UPWARD from its own
   bottom edge: it cannot reflow the page, cannot push anything, and cannot reach the phone strip
   and its Hang up, which own the TOP of every authed page. Making this a positioned popover would
   give up that property and put an overlay near the call controls. */
.cl-dsm-menu { flex-direction:column; align-items:stretch; min-width:230px; }
.cl-dsm-warn { margin:0 0 8px; font-size:12.5px; line-height:1.45; color:var(--mut); }
.cl-dsm-warn strong { color:var(--ink); }

/* "No next step" flag (owner 2026-07-23) — a live lead with no scheduled follow-up. */
/* Day headers on My Tasks' Upcoming list — the cheap calendar (owner 2026-08-04). A month grid at
   12 future callbacks floor-wide would be thirty empty boxes; this answers "what does my week look
   like" at today's volume. Reads as a divider, never as a row: it is a label on the list, not an
   item in it. */
.fu-daybar { font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.05em;
    color:var(--mut); padding:10px 0 4px; border-bottom:1px solid var(--line); }
.fu-daybar:first-of-type { padding-top:0; }

.nns-chip { display:inline-block; font-size:11px; font-weight:700; padding:2px 8px; border-radius:999px;
    background:#fff3e0; color:#b26a00; border:1px solid #f2d9a8; white-space:nowrap; }

/* ============ THE OBJECTION DRAWER + MERGED LIST (owner rework 2026-07-24) ============
   Shared vocabulary for /manage/objections: the merged list row (the row IS the link) and the
   server-drawn slide-over the drawer rides on (?open=). Reusable by any future drill-in that wants a
   slide-over. Tokens only; the drawer/dim are position:fixed so they clear the sticky sidebar. */

/* The merged list row — one base objection per row, whole row links into its drawer. */
.obj-row { display:flex; align-items:center; gap:10px; flex-wrap:wrap; background:var(--card);
    border:1px solid var(--line); border-radius:13px; padding:13px 16px; margin:8px 0;
    text-decoration:none; color:var(--ink); transition:border-color .12s, box-shadow .12s; }
.obj-row:hover { border-color:#c4d1ea; box-shadow:0 8px 20px -14px rgba(53,82,155,.45); }
.obj-row .q { font-weight:800; color:var(--brand-ink); font-size:15.5px; }
.obj-row .right { margin-left:auto; display:flex; align-items:center; gap:8px; }
.obj-row .chev { color:var(--mut); font-size:17px; line-height:1; }
.obj-row:hover .chev { color:var(--brand); }

/* Lane-presence chip — which lead-type versions of an objection exist (cold / hot-warm). A FACT
   about the row, brand-tint like `.tag.type` but its own blue so it reads as a pair. */
.tag.lane { background:#e8eefb; color:#2a4179; font-weight:800; }

/* Lane section header — lighter than a `.card` h2; the merged rows sit directly under it. */
.lane-head { display:flex; align-items:baseline; gap:10px; margin:26px 0 4px; }
.lane-head h2 { font-size:16px; font-weight:800; color:var(--brand-ink); margin:0; }

/* The slide-over + its dim. Fixed so both clear the sticky sidebar (z above it). */
.drawer-dim { position:fixed; inset:0; background:rgba(17,12,29,.38); z-index:var(--z-drawer-dim); }
/* 🛑 THE DRAWER STARTS BELOW THE PHONE STRIP, NOT AT THE EDGE. The strip is sticky at top:0 holding
   --z-callbar, so the first strip-height of pixels belongs to it on every page it rides — a drawer
   drawn into that band loses its title AND its ✕ to the strip for every phone-provisioned rep
   (owner 2026-08-11: "I opened the calculator and now i cant close it"). The strip publishes its
   MEASURED height as --ph-strip-h (it wraps at narrow widths, so no constant is right); with no
   strip nothing publishes and the 0px fallback keeps the whole edge. CalculatorPadTest pins this. */
.drawer { position:fixed; top:var(--ph-strip-h, 0px); right:0; bottom:0; width:min(620px,100%); background:#fff;
    box-shadow:-28px 0 60px -30px rgba(17,12,29,.55); padding:22px 24px 40px; overflow:auto; z-index:var(--z-drawer);
    animation:obj-drawer-in .28s cubic-bezier(.2,.8,.25,1); }
@keyframes obj-drawer-in { from { transform:translateX(40px); opacity:.4; } to { transform:none; opacity:1; } }
@media (prefers-reduced-motion: reduce) { .drawer { animation:none; } }
.drawer .close { position:fixed; top:calc(var(--ph-strip-h, 0px) + 16px); right:18px; color:var(--mut); font-size:20px; font-weight:700;
    text-decoration:none; line-height:1; z-index:var(--z-drawer-close); }
.drawer .close:hover { color:var(--ink); }
.drawer-title { font-size:21px; font-weight:800; letter-spacing:-.02em; color:var(--brand-ink);
    margin:0 0 6px; padding-right:30px; }
.drawer-chips { margin:0 0 12px; display:flex; gap:6px; flex-wrap:wrap; }
.drawer-quiet { display:flex; justify-content:flex-end; gap:10px; margin-top:26px;
    border-top:1px solid var(--line); padding-top:14px; }
@media (max-width:820px) { .drawer { width:100%; } }

/* ── THE STICKY NOTE (owner 2026-07-29) ─────────────────────────────────────────────────────────
   Shared vocabulary, not one screen's: the opener rides the lead header AND the guided call bar, and
   the pad is the same drawer on both. Rides `.drawer`/`.drawer-dim` above — only what a PAD needs is
   here.

   🛑 The dim and the ✕ are <button type=submit>, because closing this drawer SAVES (the objections
   drawer closes with links, which discard nothing). Buttons inherit the `.btn`-less native chrome, so
   both are stripped back to the shapes the drawer expects. */
.sticky-dim { border:0; padding:0; appearance:none; cursor:pointer; }
.drawer .close.sticky-close { background:none; border:0; padding:0; cursor:pointer; font-family:inherit; }
.sticky-drawer { width:min(560px,100%); }
.sticky-sub { margin:0 0 16px; font-size:13px; }
.sticky-stamp { margin:10px 0 0; font-size:12.5px; }
.sticky-hint { margin:12px 0 0; font-size:12.5px; text-align:right; }

/* The pad is an actual sticky — paper, not a form field wearing a yellow hint. */
.sticky-pad { background:linear-gradient(180deg,#fff8dc,#fdf2c4); border:1px solid #ecdca4;
    border-radius:4px; padding:14px 15px 12px;
    box-shadow:0 10px 22px -14px rgba(120,95,20,.55), 0 1px 0 #fffdf2 inset; }
/* 🛑 Overriding the bare-`textarea` rule above, which is built for editing CONTENT BODIES: monospace
   and a 300px floor. A sticky note is handwriting, and its height is the `rows` the pad asks for. */
.sticky-text { width:100%; min-height:0; border:0; background:transparent; resize:vertical; outline:none;
    padding:0; box-shadow:none; font-family:inherit; font-size:14.5px; font-weight:500; line-height:1.62;
    color:#4a3c0e; }
.sticky-text:focus { box-shadow:none; }
.sticky-text::placeholder { color:#b0a06a; }

/* The opener. 🛑 The yellow is CONDITIONAL (`.has-note`) — the whole value of the colour is telling a
   rep this lead has notes without opening the pad, which an always-on state would destroy. */
.note-btn { text-decoration:none; position:relative; }
.note-btn.has-note { background:#fdf3d0; color:#8a6a12; box-shadow:inset 0 0 0 1px #efdca0; }
.note-btn.has-note:hover { background:#fbecbb; color:#7a5d0d; }
.note-dot { position:absolute; top:-3px; right:-3px; width:9px; height:9px; border-radius:50%;
    background:#e0a41c; box-shadow:0 0 0 2px #fff; }
/* Practice with no customer file: present and named, plainly OFF. A <span>, not a disabled button —
   there is nothing to press, and the tooltip says why rather than leaving a dead control to poke. */
.note-off { opacity:.5; cursor:default; }
.note-off:hover { background:#f1f4fa; color:var(--brand-ink); }

/* The lane pill inside the drawer — flips which twin the drawer shows (they keep separate content). */
.laneswitch { display:inline-flex; border:1px solid var(--line); border-radius:999px; overflow:hidden; margin:2px 0 14px; }
.laneswitch a { padding:6px 16px; font-size:13.5px; font-weight:800; color:var(--mut); text-decoration:none; }
.laneswitch a.on { background:linear-gradient(135deg,var(--brand),var(--brand-2)); color:#fff; }
/* `.many` — 3+ members, i.e. an owner merge group (Objection::MERGE_GROUPS, 2026-07-25) rather than
   a lead-type twin pair. Four labels cannot fit one segmented pill inside the drawer, so the switch
   becomes a wrapping chip row. The 2-member twin switch keeps the pill it has always been — this
   rule is scoped so that approved shape is untouched. */
.laneswitch.many { flex-wrap:wrap; gap:6px; border:0; border-radius:0; overflow:visible; }
.laneswitch.many a { border:1px solid var(--line); border-radius:999px; }
.laneswitch.many a.on { border-color:transparent; }

/* AI-draft card in the Ammunition tab — each draft with its Jury verdict; Publish/Discard act on it. */
.obj-draft { border:1px solid var(--line); border-radius:13px; padding:13px 16px; margin:10px 0; background:var(--card); }
.obj-draft .cap { font-size:13px; color:var(--mut); font-weight:700; margin-bottom:6px; }
.obj-draft .cap .jury { color:#8a5a00; background:#fde6c7; border-radius:999px; padding:2px 9px; font-weight:800; }
.obj-draft .cap .juryok { color:#137a45; background:#e4f7ec; border-radius:999px; padding:2px 9px; font-weight:800; }
.obj-draft .line { font-size:15px; line-height:1.55; margin:0 0 10px; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   KEYBOARD FOCUS — the accessibility layer (2026-07-25)

   Before this, the whole stylesheet held SIX focus rules. `.btn` — 379 instances, the most-used
   interactive component in the app — had none, so a keyboard user tabbing through any screen saw
   whatever the browser defaulted to, which on a brand-gradient button is nothing at all. The one
   comment that noticed said it about `.icon-btn`: "the gradient .primary swallows the UA's default
   focus ring, so keyboard users had nothing." That was true of nearly every control here.

   :focus-visible, not :focus — a mouse click must not leave a ring behind, or people style it away
   again and we are back to nothing. Placed at the FOOT of the file deliberately: source order is
   the tie-breaker at equal specificity (design-system rule 8), so these win over the component rules
   above without needing !important or a specificity fight.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* The default ring. Two layers so it reads on both light cards and dark chrome: a white/dark inner
   separator, then the brand ring. */
:where(a, button, summary, [tabindex]:not([tabindex="-1"]), .btn, .icon-btn, .tab, .obj-row, .src-mini):focus-visible {
    outline:2px solid var(--brand);
    outline-offset:2px;
    border-radius:var(--r-sm);
}

/* On dark ground the brand blue disappears into the navy — the sidebar, the phone strip, the call
   bar. Those get a light ring instead. The strip is a live-call surface: a rep reaching for Hang up
   by keyboard needs to see where they are. */
:where(.sidebar, .ph-strip, .sr-callbar, .hero-band) :where(a, button, summary, [tabindex]:not([tabindex="-1"])):focus-visible {
    outline:2px solid #cfe0ff;
    outline-offset:2px;
}

/* Inputs already had a ring (border + 4px --ring) and it is good — this only adds the keyboard case
   for the controls a browser treats differently. */
:where(input[type="checkbox"], input[type="radio"], input[type="file"], select):focus-visible {
    outline:2px solid var(--brand);
    outline-offset:2px;
}

/* A row whose title carries a stretched ::after link: ring the ROW, not the invisible overlay. */
.script-row:has(> .script-row-title a:focus-visible),
.obj-row:has(a:focus-visible) { outline:2px solid var(--brand); outline-offset:2px; border-radius:var(--r-md); }

/* Skip link — the first Tab on every page. Off-screen until focused. */
.skip-link {
    position:absolute; left:-9999px; top:0; z-index:var(--z-toast);
    background:var(--brand); color:#fff; font-weight:700; font-size:var(--t-md);
    padding:var(--sp-4) var(--sp-7); border-radius:0 0 var(--r-md) 0; text-decoration:none;
}
.skip-link:focus { left:0; }

/* ── MOTION, globally damped ──────────────────────────────────────────────────────────────────
   Individual @media (prefers-reduced-motion) blocks already existed for five animations, which
   means five were remembered and the rest were not. This is the backstop: it covers every
   transition and animation in the app, including ones written after today, and including the ones
   inside view <style> blocks. Vestibular disorders are not a preference setting we get to forget. */
@media (prefers-reduced-motion:reduce) {
    *, *::before, *::after {
        animation-duration:.01ms !important;
        animation-iteration-count:1 !important;
        transition-duration:.01ms !important;
        scroll-behavior:auto !important;
    }
}

/* ============ THE DEAL BOARD (`/ops/pipeline`) ============================================
   Moved out of ops/pipeline.blade.php 2026-07-25 (770 lines / 500 max). Every selector is
   `.kb-*` namespaced (kanban board), so nothing here reaches another screen — but this IS the
   only served sheet, so the rules load everywhere. Appended at the END on purpose: an inline
   <style> in the body used to win ties against this file by source order, and keeping the block
   last preserves exactly that precedence. Behaviour with `public/js/pipeline-board.js`.
   🛑 The z-index values here are ELEVATION TOKENS (--z-raised / --z-scroll-proxy / --z-modal /
   --z-decoration), landed upstream 2026-07-25 while this block was being moved. Never hand-pick
   a number here — see the elevation scale above.
   ---------------------------------------------------------------------------------------- */
/* The board scrolls sideways when the columns don't fit — the page itself never pans. */
/* The SHELL wraps the board so edge fades + the ‹ › paddles can float over it. */
.kb-shell { position:relative; }
.kb-shell::before, .kb-shell::after {
    content:''; position:absolute; top:0; bottom:8px; width:46px; z-index:var(--z-raised);
    pointer-events:none; opacity:0; transition:opacity .2s ease;
}
.kb-shell::before { left:0;  border-radius:14px 0 0 14px; background:linear-gradient(90deg,  rgba(30,35,51,.14), rgba(30,35,51,0)); }
.kb-shell::after  { right:0; border-radius:0 14px 14px 0; background:linear-gradient(270deg, rgba(30,35,51,.14), rgba(30,35,51,0)); }
.kb-shell.has-left::before { opacity:1; }
.kb-shell.has-right::after { opacity:1; }
/* The PADDLES — a mouse user's obvious way sideways: click to glide one screen of columns.
   Auto-hide at the ends and when nothing overflows. */
.kb-paddle {
    position:absolute; top:42%; z-index:var(--z-raised); width:46px; height:46px; border-radius:999px;
    border:1px solid var(--line); background:#fff; color:var(--brand);
    box-shadow:0 8px 22px rgba(17,12,29,.22); cursor:pointer;
    display:flex; align-items:center; justify-content:center;
    transition:transform .12s ease, opacity .2s ease;
}
.kb-paddle svg { width:20px; height:20px; }
.kb-paddle:hover { transform:scale(1.1); background:#f4f7fd; }
.kb-paddle.prev { left:-10px; }
.kb-paddle.next { right:-10px; }
.kb-paddle[hidden] { display:none; }
.kb-board {
    /* stretch: EVERY column runs the full board height — a real full-page kanban even when
       empty; whole columns are drop targets; the joined block's seams run full length.
       🛑 NO scroll-snap: snap re-settled programmatic scrolls and FOUGHT the bottom bar's
       two-way sync — the "it's stuck" bug (owner, 7-18). Free scrolling only. */
    display:flex; align-items:stretch; overflow-x:auto; padding-bottom:8px;
    min-height:calc(100vh - 210px);
    /* The v2 family band (§9b.6): every column carries a MEANING-encoded tint — `--kb-edge` (the
       strong line) + `--kb-tint` (the wash) — set inline per column from the stage's KIND, so the
       won-run reads as one licensed territory. Defaults = the open family. */
    --kb-edge:var(--brand); --kb-tint:#eef2fb;
}
.kb-board.is-panning { cursor:grabbing; user-select:none; }
.kb-hscroll {
    position:fixed; bottom:0; z-index:var(--z-scroll-proxy); height:16px; overflow-x:auto; overflow-y:hidden;
    padding:2px 0 4px;
}
.kb-hscroll::-webkit-scrollbar { height:10px; }
.kb-hscroll::-webkit-scrollbar-thumb { background:rgba(53,82,155,.7); border-radius:999px; }
.kb-hscroll::-webkit-scrollbar-thumb:hover { background:rgba(53,82,155,.75); }
.kb-hscroll::-webkit-scrollbar-track { background:rgba(17,12,29,.08); border-radius:999px; }
.kb-hscroll > div { height:1px; }
.kb-col {
    /* ⚖️ R-020 — was `flex:0 0 236px`, a FIXED width from the days of one long 9-column run. Under
       Gil's phase tabs a phase holds 3 to 6 columns, and a fixed width got both ends wrong: six
       overflowed a 1440px screen by ~340px (the sideways scroll the tabs exist to kill), while three
       left half the board empty. Flexing between a floor and a ceiling fits either without a
       per-phase special case. The floor keeps a card readable; the ceiling stops three columns
       sprawling to 360px each. Paddles still handle anything narrower than the floor allows. */
    flex:1 1 236px; min-width:190px; max-width:300px;
    background:var(--kb-tint); border:1px solid var(--line);
    border-top:4px solid var(--kb-edge);
    border-radius:14px; display:flex; flex-direction:column;
    margin-right:12px;   /* the gap lives HERE so joined columns can drop theirs */
    box-shadow:inset 0 1px 0 rgba(255,255,255,.6), 0 1px 3px rgba(17,12,29,.05);
}
.kb-col:last-child { margin-right:0; }
/* The fulfillment block (owner 7-18): Processing · Underwriting · Clear to Close TOUCH — one
   connected run of the same green, internal seams dashed, rounded only at the block's ends.
   Equal heights come from the board's align-items:stretch (round 3), so the seam always runs
   the full shared height and the block reads as one surface. */
.kb-col.kb-join-next { margin-right:0; border-top-right-radius:0; border-bottom-right-radius:0; border-right:0; }
.kb-col.kb-join-prev { border-top-left-radius:0; border-bottom-left-radius:0; border-left:1px dashed #b7d6c4; }
.kb-col.kb-join-prev > .kb-col-head { border-radius:0; }
/* The head, cleaned up (owner 7-18): name + a QUIET count sit together on the left, the small
   ＋ rides alone on the far right — nothing reads as "plus three" any more. */
.kb-col-head {
    display:flex; align-items:center; gap:7px; padding:9px 11px; border-bottom:1px solid var(--line);
    background:var(--kb-tint); border-radius:10px 10px 0 0;
}
.kb-col-name { font-weight:800; font-size:13px; color:var(--brand-ink); letter-spacing:-.01em; }
.kb-count {
    font-size:11px; font-weight:800; color:var(--kb-edge);
    background:rgba(255,255,255,.75); border:1px solid var(--line);
    border-radius:999px; padding:0 7px; min-width:18px; text-align:center;
}
.kb-add {
    margin-left:auto; width:20px; height:20px; border-radius:999px; flex:0 0 20px;
    display:inline-flex; align-items:center; justify-content:center; text-decoration:none;
    background:var(--kb-edge); color:#fff; font-size:13px; font-weight:800; line-height:1;
    opacity:.85; transition:transform .12s ease, opacity .12s ease;
}
.kb-add:hover { transform:scale(1.2); opacity:1; }
.kb-drop { padding:8px; display:flex; flex-direction:column; gap:7px; min-height:56px; flex:1; }
/* The live drop hint, only while a card is over this column (JS toggles it). */
.kb-drop.is-over { background:var(--ring); border-radius:0 0 14px 14px; }
.kb-drop.is-over .kb-empty { border-color:var(--kb-edge); color:var(--brand-ink); }

/* Cards sit WHITE on the column's wash with the family line down their left edge — the v2
   "cooler, more distinct" card (§9b.6): each reads as its own object, not a stripe of the column. */
.kb-card {
    border:1px solid var(--line); border-left:4px solid var(--kb-edge);
    border-radius:10px; padding:8px 9px; background:#fff;
    box-shadow:0 1px 2px rgba(17,12,29,.06);
    transition:box-shadow .12s ease, transform .12s ease;
}
.kb-card:hover { box-shadow:0 4px 14px rgba(17,12,29,.14); transform:translateY(-2px); }
.kb-card.is-draggable { cursor:grab; }
/* The picked-up card TILTS (Trello's trick) — it reads as lifted, not faded. */
.kb-card.is-dragging { opacity:.85; transform:rotate(3deg) scale(1.03); box-shadow:0 12px 28px rgba(17,12,29,.25); }
.kb-card-name { font-weight:800; font-size:13px; color:var(--brand-ink); display:flex; align-items:center; gap:6px; }
/* The card's way IN — a real link (the no-JS fallback IS this href); JS upgrades it to the modal. */
.kb-card-link { color:inherit; text-decoration:none; }
.kb-card-link:hover { text-decoration:underline; }
/* The initials chip — a face for the card, tinted by the column family. */
.kb-avatar {
    flex:0 0 21px; width:21px; height:21px; border-radius:999px; background:var(--kb-edge);
    color:#fff; font-size:10px; font-weight:800; display:inline-flex; align-items:center;
    justify-content:center; letter-spacing:.02em;
}
/* How long the deal has sat in its stage — and it CHANGES COLOR as it goes stale: quiet grey,
   amber past 3 days, red past 7. A stuck deal announces itself from across the room. */
.kb-age { color:var(--mut); font-size:10.5px; font-weight:700; white-space:nowrap; }
.kb-age.warn { color:#8a5a00; background:#fde6c7; border-radius:999px; padding:1px 7px; }
.kb-age.late { color:#a12626; background:#fdecec; border-radius:999px; padding:1px 7px; }
.kb-card-phone { font-size:11.5px; margin-top:1px; }
.kb-card-meta { font-size:11px; margin-top:4px; line-height:1.45; }
.kb-card-closed { font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.03em; color:var(--mut); }
/* The empty column INVITES — a dashed drop zone, not an apology. */
.kb-empty {
    flex:1; display:flex; align-items:center; justify-content:center; text-align:center;
    font-size:12.5px; font-weight:600; color:var(--mut); padding:18px 10px;
    border:2px dashed rgba(30,35,51,.14); border-radius:12px; margin:2px;
    transition:border-color .15s ease, color .15s ease;
}

/* The no-JS control — a slim disclosure so the card stays compact but the move never needs a mouse-drag. */
.kb-more { margin-top:8px; }
.kb-more > summary {
    cursor:pointer; font-size:11px; font-weight:700; color:var(--brand); list-style:none;
    display:inline-block; padding:2px 0;
}
.kb-more > summary::-webkit-details-marker { display:none; }
.kb-more[open] > summary { margin-bottom:7px; }
.kb-more-forms { display:flex; flex-direction:column; gap:7px; }
.kb-more-forms form { display:flex; gap:6px; flex-wrap:wrap; align-items:center; }

/* A client-side status line for drag outcomes — the layout owns SESSION flashes, this owns the
   live drag result (its own aria-live region so a screen reader hears the refusal too). */
.kb-flash {
    margin:0 0 14px; padding:10px 13px; border-radius:11px; font-size:13.5px; font-weight:600;
    border:1px solid var(--line); background:#fff; color:var(--ink);
}
.kb-flash.is-bad { border-color:#e8a9a9; background:#fdecec; color:#a12626; }
.kb-flash[hidden] { display:none; }

/* The card popup (§9b.6) — same centered-over-a-dim idiom as the console's DNC confirm, but built
   by JS on demand (the content is fetched), so a real overlay element takes the dim. With JS off
   none of this exists and the card link goes to the standalone page instead. */
.kb-modal-overlay {
    position:fixed; inset:0; z-index:var(--z-modal); background:rgba(15,20,35,.45);
    display:flex; align-items:center; justify-content:center; padding:24px;
}
.kb-modal {
    background:#fff; border:1px solid var(--line); border-radius:14px;
    width:100%; max-width:560px; max-height:min(82vh, 720px); overflow-y:auto;
    padding:18px 20px; box-shadow:0 12px 40px rgba(0,0,0,.25); position:relative;
}
.kb-modal-close {
    position:sticky; top:0; float:right; margin:-6px -8px 0 12px;
    border:0; background:none; cursor:pointer; font-size:22px; line-height:1;
    color:var(--mut); padding:4px 8px; border-radius:8px;
}
.kb-modal-close:hover { color:var(--ink); background:var(--line); }
.kb-modal-open-full { display:inline-block; margin-top:12px; font-size:13px; font-weight:700; }

/* The celebration canvas — full-screen, click-through, gone when the party ends. */
.kb-confetti { position:fixed; inset:0; z-index:var(--z-decoration); pointer-events:none; }

/* ---- THE CLOCK (2026-08-16, design 2026-08-16-deal-board-clock-design.md move 3) -------------
   Gil: *"how can we order it so something that has to be done shows on top?"* Measured first: 55 of
   64 open deals carried NO next step, so the board's problem was never its sort order — most cards
   said nothing. These rules paint what a card now says and which band it sits in.

   🛑 THE COLOURS ARE THE HOUSE STATUS SET (design-system "Tokens"): error `#a12626`/`#fdecec`,
   warn `#8a5a00`/`#fde6c7`. NOT `var(--danger)` — rule 13 reserves the alarm for a compliance
   VERDICT, and a callback we are late on is a NOTICE. `.kb-age.late` above already made this exact
   choice on this exact board; these match it on purpose rather than opening a second red. */
.kb-band {
    display:flex; align-items:center; gap:7px;
    font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em;
    color:var(--mut); padding:7px 2px 1px;
}
/* The rule runs to the edge so the band reads as a divider, not a card. */
.kb-band::after { content:''; flex:1; height:1px; background:var(--line); order:3; }
.kb-band-n { order:2; font-weight:800; }
.kb-band.overdue { color:#a12626; }
.kb-band.today   { color:#8a5a00; }
/* A column's FIRST band needs no gap above it — the drop zone already pads. */
.kb-drop > .kb-band:first-child { padding-top:1px; }

/* The card's left edge carries its urgency, reusing the family-band slot: a glance down a column
   reads as a stripe of colour before a single word is read. */
.kb-card.band-overdue { border-left-color:#a12626; }
.kb-card.band-today   { border-left-color:#c77f0a; }
.kb-card.band-none    { border-left-color:#98a1b3; background:#fcfcfe; }
.kb-card.band-overdue .kb-avatar { background:#a12626; }
.kb-card.band-today   .kb-avatar { background:#c77f0a; }
.kb-card.band-none    .kb-avatar { background:#98a1b3; }

/* WHAT IS NEXT — the line the build exists for. Tinted by band so the card answers "when" before
   it is read, and left plain-grey when nothing is scheduled, because a drifting deal is not an
   emergency to be shouted at — it is work nobody has decided yet. */
.kb-next {
    margin:6px 0 0; padding:5px 7px; border-radius:6px;
    font-size:11.5px; line-height:1.35; background:#f7f9fd; color:var(--ink);
}
.kb-next .when { font-weight:800; }
.kb-next.overdue { background:#fdecec; color:#a12626; }
.kb-next.today   { background:#fde6c7; color:#8a5a00; }
.kb-next.none    { background:#f2f4f8; color:var(--mut); font-style:italic; }

/* ⚖️ Gil 2026-08-16: *"you can double click the phone number on the little card."* The hook is the
   global `.dial-phone` listener; this only makes it LOOK like the live control it now is — the
   board used to render the number as dead muted text. */
.kb-card-phone .dial-phone {
    color:var(--brand); font-weight:600; font-variant-numeric:tabular-nums;
    border-bottom:1px dotted var(--ring); cursor:pointer;
}
.kb-card-phone .dial-phone:hover { border-bottom-style:solid; }

/* ⚖️ MOVE 2 — the "what happens next?" prompt (Gil 2026-08-16). A card, not a full-screen modal:
   it answers a question about ONE deal and the board behind it is the context. It sits under the
   call bar like everything else (elevation scale) — a rep on a live call must never lose Hang up
   behind a question about paperwork. */
.kb-ask {
    position:fixed; left:50%; transform:translateX(-50%); bottom:26px; z-index:var(--z-modal);
    width:min(440px, calc(100vw - 32px));
    background:var(--card); border:1px solid var(--line); border-top:4px solid var(--brand);
    border-radius:16px; padding:16px 18px; box-shadow:0 18px 50px -12px rgba(17,12,29,.42);
}
.kb-ask[hidden] { display:none; }
.kb-ask h2 { font-size:15px; margin:0 0 4px; color:var(--brand-ink); letter-spacing:-.01em; }
.kb-ask > form > p { font-size:12.5px; margin:0 0 12px; line-height:1.45; }
.kb-ask-fields { display:flex; flex-direction:column; gap:2px; }
.kb-ask-fields label { margin:8px 0 4px; font-size:12.5px; }
.kb-ask-actions { display:flex; gap:9px; align-items:center; margin-top:14px; }
@media (max-width:560px) { .kb-ask { bottom:12px; } }

/* A board-private modifier on the SHARED `.stat`: the overdue headline goes red when it is not
   zero, and stays plain when it is — a permanently red zero teaches a rep to stop reading it. */
.stat.kb-stat-late { border-color:#f0c7c7; }
.stat.kb-stat-late .num { color:#a12626; }

/* The deal's own detail, demoted out of the card's click into the disclosure below it. */
.kb-deal-detail { font-size:11.5px; font-weight:700; color:var(--brand); text-decoration:none; }
.kb-deal-detail:hover { text-decoration:underline; }

@media (max-width:560px) { .kb-col { flex-basis:82vw; } }

/* CA-02 — the check-question review pane on the lesson editor (2026-07-27). Owner ruling: not a
   dedicated screen, because the need is latent and a screen for a latent need is one nobody visits.
   🛑 The consequence line is styled to be READ, not skimmed past — it is the whole condition on
   Unpublish existing: a click that can stop a compliance gate asking may never be quiet. */
.check-review { margin-top:16px; }
.check-review h3 { font-size:15px; font-weight:800; color:var(--brand-ink); margin:0 0 3px; }
.check-review > .muted { font-size:12.5px; margin:0 0 14px; }
.cq { border:1px solid var(--line); border-radius:12px; padding:15px 17px; margin-bottom:11px; background:#fff; }
.cq-head { margin-bottom:8px; }
.cq-stem { font-size:14.5px; line-height:1.55; color:var(--ink); font-weight:600; margin:0 0 9px; }
.cq-opts { list-style:none; padding:0; margin:0 0 10px; }
.cq-opts li { font-size:13px; color:var(--mut); padding:3px 0; }
.cq-opts li.ok { color:var(--ink); font-weight:600; }
.cq-expl { background:#fbfcfe; border:1px solid var(--line); border-radius:9px; padding:10px 12px; font-size:12.5px; line-height:1.55; color:var(--mut); }
.cq-expl b { display:block; font-size:10px; text-transform:uppercase; letter-spacing:.05em; color:var(--brand); margin-bottom:4px; }
.cq-conseq { background:#fffaf0; border:1px solid #f0e0c0; border-radius:9px; padding:10px 12px; font-size:12.5px; line-height:1.6; color:var(--ink); margin-top:10px; }
.cq-conseq b { color:#8a5b00; }
.cq-acts { margin-top:11px; }

/* ── 🔥 THE HOT-LEAD OFFER BANNER (routing step 5) ───────────────────────────────────────────────
   A lead is being OFFERED to this rep for fifteen seconds. Not a fourth 🔥 badge — decision 11
   stands and nothing about the existing 🔥 changes. A badge LABELS; this DEMANDS, because it has a
   countdown and a button, and that is a far bigger difference than colour.

   🛑 GOLD + the existing hot orange (#e8442a, the `ct-hot` token), NEVER the alarm — that is
   var(--danger), and its literal is deliberately NOT repeated here: the alarm colour is defined in
   exactly one place and ComplianceAlarmSurfacesTest fails on any second copy, a comment included.
   design-system RULE 13: the alarm marks a VERDICT, never a notice, and never on a live-call
   surface. Genuinely different tokens, and the banner must not drift into the reserved red.

   🛑 --z-toast, not a hand-picked number: this IS a transient banner, and it must sit BELOW the
   --z-callbar ceiling so a live call's controls are never buried by an offer (the .ph-strip scar).

   ⚖️ SMALL, AND IN THE TOP-LEFT CORNER — owner 2026-08-04, reversing his own card removal in the
   same breath: *"lets keep the dibs card, but keep it small like and get rid of the have a hot lead
   card we just made. make it smaller and have it coem up on the top left corner where i told you to
   put the you have a hot lead card came up."*
   🛑 IT WAS A 620px BANNER ACROSS THE MIDDLE OF THE SCREEN and that is what he was cutting down —
   `left:50%; transform:translateX(-50%); max-width:620px`. Do not re-centre it.
   🛑 THE LEFT EDGE TRACKS THE SIDEBAR, which is why there are three positions and not one: 232px
   expanded, 64px collapsed (`html.sidebar-collapsed`, ≥821px only), and a flat top row at ≤820px
   where the sidebar is not on the left at all. A single hardcoded `left` puts the card ON TOP of the
   nav for whichever of the three a rep happens to be in. */
/* ═══ 💬 TEXTING A CUSTOMER (2026-08-27) ══════════════════════════════════════════════════════════
   The ✉ beside the number on the lead card, its composer, and the text rows in the Activity Log.
   ⚠️ Every class here was grepped against this file before it was written — a class declared twice
   is silent by construction, and this stylesheet has paid for that twice in one day already. */
.lead-phone-row { display:flex; align-items:center; gap:8px; }

/* The icon lives ON the number, which is already a control (double-click dials). Deliberately not
   a `.btn`: this is an affordance on a fact, not an action bar — the bar came off this card on
   2026-08-02 and is not coming back. */
.sms-pop { position:relative; display:inline-block; }
/* ⚖️ THE LETTERS, NOT A GLYPH — Gil 2026-09-08: the envelope "looks like email icon". Three
   characters do not fit the 26px square this was, so it becomes a small pill: auto width with
   padding, a smaller bolder face, and the SAME height as before so the number's baseline does not
   shift. Hover, focus and open states below are unchanged — they target the same selector. */
.sms-pop > summary.sms-ic {
    list-style:none; cursor:pointer; display:inline-flex; align-items:center; justify-content:center;
    min-width:34px; height:26px; padding:0 7px; border:1px solid var(--line); border-radius:8px;
    background:#fff; color:#6f42c1; font-size:10.5px; font-weight:700; letter-spacing:.04em;
    line-height:1;
}
.sms-pop > summary::-webkit-details-marker { display:none; }
.sms-pop > summary.sms-ic:hover { background:#f7f4fd; border-color:#d8caf3; }
.sms-pop > summary.sms-ic:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.sms-pop[open] > summary.sms-ic { background:#f0eafb; border-color:#c9b3ee; }

.sms-pop-box {
    position:absolute; left:0; top:calc(100% + 6px); z-index:5; width:360px; max-width:88vw;
    background:var(--card); border:1px solid var(--line); border-radius:12px; padding:14px;
    box-shadow:0 18px 40px -18px rgba(26,32,60,.35);
}
.sms-pop-box textarea { width:100%; font:inherit; font-size:14px; border:1px solid var(--line);
    border-radius:9px; padding:10px; resize:vertical; min-height:74px; }
.sms-to { font-size:12.5px; color:var(--mut); margin:0 0 8px; }
.sms-to b { color:var(--ink); }
/* 💬 THE QUICK-TEXT PICKER (⚖️ Gil 2026-09-09: *"Premade has to be a drop down on teh bottom. when
   selected it appears in text box"*) — BELOW the box, under the Send row.
   🛑 QUIETER THAN SEND, ALWAYS. Send is the only filled navy control in this pop and the only one
   that reaches a customer; the picker merely fills the box. Two controls of equal weight beside
   each other is how a rep learns to press either without reading. */
.sms-quick { display:flex; align-items:center; gap:8px; margin:11px 0 0;
    padding-top:10px; border-top:1px solid var(--line); }
.sms-quick-lbl { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.06em;
    color:var(--mut); flex:none; }
.sms-quick-pick { flex:1; min-width:0; font:inherit; font-size:13px; color:var(--ink);
    background:#fff; border:1px solid var(--line); border-radius:8px; padding:6px 9px; cursor:pointer; }
.sms-quick-pick:hover { border-color:#c9b3ee; }
.sms-quick-pick:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.sms-meter { display:flex; justify-content:space-between; align-items:center; gap:10px; margin-top:9px; }
.sms-count { font-size:12.5px; color:var(--mut); font-variant-numeric:tabular-nums; }
.sms-count.is-over { color:#8a5a00; font-weight:700; }

/* 🛑 THE REASON A DARK BUTTON IS DARK. Never rendered empty — a grey Send with nothing beside it
   is indistinguishable from a broken one, which cost two days in August. */
.sms-stop { margin-top:10px; border-left:3px solid #f2c6c6; background:#fdecec;
    color:#a12626; padding:9px 12px; border-radius:0 8px 8px 0; font-size:13px; line-height:1.5; }
.sms-stop .btn { margin-top:8px; }
/* 📵 The carrier bounced this number. Amber, not the red refusal: nobody asked us to stop and
   nothing is wrong with the customer — a handset could not take a text. Red is reserved for a
   verdict, and this is a fact about a phone line. */
.sms-dead { border-left-color:#b8860b; background:#fdf7e7; color:#6b5310; }
.sms-warn { margin-top:10px; border-left:3px solid #b8860b; background:#fdf7e7; color:#6b5310;
    padding:9px 12px; border-radius:0 8px 8px 0; font-size:13px; }
.sms-consent { margin-top:10px; font-size:12.5px; color:var(--mut); line-height:1.5; }

/* The Activity Log's delivery chip. `pending` is the state a row is BORN in and it says "Sending"
   — only the provider's webhook may ever make it read Delivered. */
.cl-sms-st { display:inline-block; font-size:11px; font-weight:700; padding:2px 8px;
    border-radius:6px; margin-left:6px; white-space:nowrap; }
.cl-st-ok   { background:#e8f5ee; color:#1a7a4c; }
.cl-st-pend { background:#eef1f9; color:#5a6473; }
.cl-st-bad  { background:#fdecec; color:#a12626; }


.offer-banner {
    position:fixed; top:76px; left:250px; z-index:var(--z-toast);
    display:flex; align-items:center; gap:9px; flex-wrap:wrap;
    width:272px;
    padding:10px 12px; border-radius:12px;
    background:linear-gradient(135deg,#fff4d6,#ffe9b0);
    border:1px solid #e8c46a; border-left:5px solid #e8442a;
    box-shadow:0 14px 34px -14px rgba(60,40,0,.45);
    animation:offer-in .22s ease;
}
.offer-banner[hidden] { display:none; }
@keyframes offer-in { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:none; } }

.offer-banner .offer-flame { font-size:18px; line-height:1; }
.offer-banner .offer-body { flex:1 1 120px; min-width:0; }
.offer-banner .offer-kicker {
    font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em; color:#8a5b00;
}
.offer-banner .offer-name {
    font-size:14px; font-weight:800; color:var(--brand-ink);
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* The countdown. Tabular figures so the digit does not shuffle the layout each second. */
.offer-banner .offer-clock {
    font-size:19px; font-weight:800; color:#8a5b00; font-variant-numeric:tabular-nums;
    min-width:2.2ch; text-align:center;
}
/* 🛑 THE BUTTONS TAKE THEIR OWN ROW at this width — `flex-basis:100%`. Squeezed onto the name's
   row they were ~54px wide with the name ellipsised to nothing, which is a smaller card that has
   stopped being usable rather than a smaller card. */
/* 🛑 "Nobody is on the line" — ORIENTATION, NOT AN ALARM (owner 2026-08-04: *"this appears like it
   is a call in, and 'take it' means answer the phone"*). Every other thing that lands on a rep's
   screen means a human is already talking; this one means the opposite, and the card used to say
   nothing about it.
   The styling IS the rule: a rep meets this fifty times a day, so it is quiet body text in the
   card's own gold — never `--danger` (rule 13 bars the alarm from live-call surfaces outright) and
   never bold or animated. Dressed as an alarm it becomes wallpaper in a week, and then the one
   distinction a rep can be hurt by is the thing they have trained themselves to look past. */
.offer-banner .offer-silent {
    flex:1 1 100%; margin:1px 0 0; font-size:11.5px; line-height:1.45; color:#8a5b00;
}
/* ☎️ THE SAME LINE'S OPPOSITE, for when the customer on this card is already holding on our queue
   (⚖️ Gil 2026-09-09 — Janet, dealt to Dee and answered by Carlos twenty-four seconds apart).
   🛑 IT BORROWS THIS COMPONENT'S OWN HOT EDGE (#c0390f, the open-floor tier), never --danger:
   design rule 13 bars the alarm from a live-call surface, and this card IS one while it wears this
   face. Weight, not colour, is what makes it read differently from the silent line it replaces. */
.offer-banner .offer-live {
    flex:1 1 100%; margin:1px 0 0; font-size:11.5px; line-height:1.45; color:#c0390f; font-weight:600;
}
/* Both faces set `flex`, so [hidden]'s display:none has to be said out loud or neither ever hides. */
.offer-banner .offer-live[hidden], .offer-banner .offer-silent[hidden] { display:none; }
.offer-banner .offer-acts { display:flex; gap:7px; align-items:center; flex:1 1 100%; }
.offer-banner .offer-acts .btn { flex:1 1 auto; padding:7px 10px; font-size:12.5px; }
.offer-banner .offer-take { background:linear-gradient(135deg,#f0673f,#e8442a); }
.offer-banner .offer-pass[hidden] { display:none; }

/* The open-floor broadcast reads as the whole floor's, not yours: hotter edge, no Pass. */
.offer-banner[data-tier="open_floor"] { border-left-color:#c0390f; background:linear-gradient(135deg,#ffeccc,#ffdca0); }

/* 🛑 manualDial's OWN sentence ("bring your VICIdial phone window to the front"). Amber, NOT the
   alarm: the rep still holds the lead for two minutes and this is a nudge to fix their phone
   window, not a verdict on anything. */
.offer-banner .offer-trouble {
    flex:1 1 100%; margin:2px 0 0; padding:9px 12px; border-radius:9px;
    background:#fffaf0; border:1px solid #f0e0c0; color:#8a5b00;
    font-size:12.5px; line-height:1.5; font-weight:600;
}
.offer-banner .offer-trouble[hidden] { display:none; }

/* ⚖️ THE THREE LEFT EDGES (2026-08-04) — see the geometry note above. The card hugs the inside of
   `.main`, never the nav, in all three of the sidebar's shapes. */
@media (min-width:821px) { html.sidebar-collapsed .offer-banner { left:82px; } }
@media (max-width:820px) {
    /* The sidebar is a flat TOP ROW here, so "top left" is the screen's own left edge — and the
       card drops below the row rather than sitting on it. */
    .offer-banner { left:12px; right:12px; width:auto; max-width:320px; top:96px; }
}
@media (max-width:620px) {
    .offer-banner { gap:8px; padding:10px 12px; }
}

/* ☎️ THE FLOOR ALARM — a caller is holding and NOBODY is on the phones (owner 2026-08-12: "make a
   noise or something if everyone is logged out when a call comes in… we are missing a lot of
   calls"). The rarest banner on the platform by design: it renders only when the server says not
   one live dialer session exists, which on the day that produced it was a 93-minute lunch gap.

   🛑 NOT the reserved alarm. Rule 13 spends `--danger` on compliance VERDICTS only, and rule 13a's
   grammar names this exactly: BROKEN, WANTS A HUMAN, NOTHING GRADED — the outlined muted-error
   treatment (#a12626 on #fdecec), never the filled `--danger` wash. Urgency comes from size, the
   pulse and the SOUND, not from spending the one colour the compliance vocabulary reserves.
   Unlike the offer's quiet line above, animation is EARNED here: a rep meets this a few times a
   month, not fifty times a day — wallpaper arithmetic runs the other way. */
.floor-alarm {
    position:fixed; top:76px; left:50%; transform:translateX(calc(-50% + 125px));
    z-index:var(--z-toast);
    display:flex; align-items:center; gap:12px;
    width:min(480px, calc(100vw - 290px));
    padding:13px 16px; border-radius:14px;
    background:#fdecec; border:2px solid #a12626;
    color:#a12626;
    animation:floor-alarm-pulse 1.6s ease-in-out infinite;
}
.floor-alarm[hidden] { display:none; }
/* 🔔 SOMEBODY IS HOLDING WHILE THIS REP IS MID-CALL (owner 2026-08-14). Deliberately the QUIET
   cousin of the banner above: same corner of the screen, none of the volume. No pulse, no red, no
   siren — a rep reading this is mid-sentence with a customer and must be able to ignore it for
   thirty seconds without being harassed. It sits BELOW the alarm's slot so that on the rare tick
   where both are true (an unattended floor while this rep is on a call) they stack instead of
   overlapping — never two paints fighting for one pixel. */
.waiting-chip {
    position:fixed; top:76px; left:50%; transform:translateX(calc(-50% + 125px));
    z-index:var(--z-toast);
    display:inline-flex; align-items:center; gap:8px;
    padding:7px 13px; border-radius:999px;
    background:#fff7e6; border:1px solid #b7791f; color:#8a5a05;
    font-size:.86rem; font-weight:600;
    box-shadow:0 6px 18px -10px rgba(0,0,0,.35);
}
.waiting-chip[hidden] { display:none; }
.floor-alarm:not([hidden]) ~ .waiting-chip { top:146px; }
@keyframes floor-alarm-pulse {
    0%, 100% { box-shadow:0 14px 34px -14px rgba(161,38,38,.45); }
    50%      { box-shadow:0 14px 44px -8px rgba(161,38,38,.75); }
}
.floor-alarm .floor-alarm-bell { font-size:22px; line-height:1; }
.floor-alarm .floor-alarm-body { flex:1 1 auto; min-width:0; }
.floor-alarm .floor-alarm-kicker {
    font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.07em;
}
.floor-alarm .floor-alarm-line { font-size:13.5px; font-weight:700; line-height:1.4; color:var(--ink); }
/* The one-click speaker setup. Quiet secondary: the banner is the alarm, this is plumbing. */
.floor-alarm .floor-alarm-speaker {
    flex:0 0 auto; padding:7px 10px; font-size:12px;
    background:#fff; border:1px solid #a12626; color:#a12626; border-radius:9px; cursor:pointer;
    font-weight:700;
}
.floor-alarm .floor-alarm-speaker[hidden] { display:none; }
.floor-alarm .floor-alarm-mute {
    flex:0 0 auto; padding:7px 10px; font-size:12px;
    background:transparent; border:1px solid rgba(161,38,38,.45); color:#a12626;
    border-radius:9px; cursor:pointer; font-weight:700;
}
.floor-alarm .floor-alarm-mute[hidden] { display:none; }
.floor-alarm .floor-alarm-mute:disabled { opacity:.55; cursor:default; }

/* 🛑 THE QUIET LEVEL — everyone is logged in and on a call. Same words-and-place, no pulse and no
   sound: the reps are each mid-conversation and the alarm's audio goes to the ROOM, so ringing
   would land in a live customer's ear. Amber `warn`, not the error tint: nobody has abandoned the
   phones, they are all busy on them. Measured 08-12: ~1.7 of these a day. */
.floor-alarm[data-level="all_busy"] {
    background:#fde6c7; border-color:#8a5a00; color:#8a5a00;
    animation:none;
    box-shadow:0 14px 34px -14px rgba(138,90,0,.4);
}
.floor-alarm[data-level="all_busy"] .floor-alarm-line { color:var(--ink); }
@media (min-width:821px) { html.sidebar-collapsed .floor-alarm { transform:translateX(calc(-50% + 41px)); } }
@media (max-width:820px) {
    .floor-alarm { left:12px; right:12px; width:auto; transform:none; top:96px; flex-wrap:wrap; }
}

/* 🔇 THE AUDIO ALARM BANNER — a call is up and one side's audio is not arriving (Gil 2026-08-14:
   "It needs to be somewhere prominent so I can see it no matter what screen I'm on"). Manager+ only;
   the partial renders nothing for a rep.

   🛑 SAME OUTLINED MUTED-ERROR FAMILY AS THE FLOOR ALARM, deliberately — rule 13a's grammar reads
   the same for both: BROKEN, WANTS A HUMAN, NOTHING GRADED. Never the reserved `--danger` wash, which
   rule 13 spends on compliance VERDICTS only.

   🛑 AND NEVER THE PULSE, which is the one place it parts company with the floor alarm. Animation
   there is EARNED by rarity — a caller holding on an empty floor happens a few times a month. This
   one fired every five minutes for days during the incident that built the subsystem, and a banner
   that can persist for hours must not throb for hours.

   🛑 STACKING IS DELIBERATE AND IT LOSES EVERY CONTEST. `--z-toast` (150) sits UNDER the
   `--z-callbar` ceiling (160), so the strip, its dropdowns and every live-call control paint over it.
   It sits BELOW the floor alarm on the screen (top 132px vs 76px) rather than sharing that band: a
   customer holding with nobody on the phones outranks an infrastructure fault, and two fixed banners
   in one lane means the later one in the DOM covers the more important one. The offset is a fixed
   geometry, not a reaction to whether the floor alarm is up — a banner that moved when a sibling
   appeared would be a coupling across two partials, which is the shape that gets deleted by accident. */
.audio-alarm {
    position:fixed; top:132px; left:50%; transform:translateX(calc(-50% + 125px));
    z-index:var(--z-toast);
    display:flex; align-items:center; gap:12px;
    width:min(480px, calc(100vw - 290px));
    padding:12px 15px; border-radius:14px;
    background:#fdecec; border:2px solid #a12626;
    color:#a12626;
    box-shadow:0 14px 34px -16px rgba(161,38,38,.45);
}
.audio-alarm[hidden] { display:none; }
.audio-alarm .audio-alarm-mark { font-size:20px; line-height:1; }
.audio-alarm .audio-alarm-body { flex:1 1 auto; min-width:0; }
.audio-alarm .audio-alarm-kicker {
    font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.07em;
}
.audio-alarm .audio-alarm-line { font-size:13px; font-weight:700; line-height:1.4; color:var(--ink); }
.audio-alarm .audio-alarm-more { font-size:11.5px; font-weight:700; margin-top:2px; }
.audio-alarm .audio-alarm-more[hidden] { display:none; }
/* The way through to the record. Quiet outline: the sentence is the alarm, this is the door. */
.audio-alarm .audio-alarm-link {
    flex:0 0 auto; padding:7px 10px; font-size:12px; text-decoration:none;
    background:#fff; border:1px solid #a12626; color:#a12626; border-radius:9px; font-weight:700;
}
/* ⚖️ THE CLOSE BUTTON — Gil, 2026-08-17: *"Let me close them out because they stay on my screen, and
   I can't close them out."* Deliberately quieter than Open: the way THROUGH to the record is the
   action worth encouraging, and dismissing is the one worth being a little deliberate about. Same
   height as the link beside it so the pair reads as one control group rather than two decisions. */
.audio-alarm .audio-alarm-close {
    flex:0 0 auto; padding:7px 10px; font-size:12px; font-weight:700; cursor:pointer;
    background:transparent; border:1px solid transparent; color:#a12626; border-radius:9px;
}
.audio-alarm .audio-alarm-close:hover { border-color:#a12626; background:#fff; }
.audio-alarm .audio-alarm-close:disabled { opacity:.5; cursor:default; }
.audio-alarm[data-audio-alarm-test="1"] .audio-alarm-close { color:var(--mut); }
.audio-alarm[data-audio-alarm-test="1"] .audio-alarm-close:hover { border-color:var(--line); }

/* 🛑 THE WIRE TEST IS VISIBLY NOT A FAULT. It exists so a real push can be SEEN arriving from the
   box, and it lives five minutes rather than thirty — but for those five minutes it must not be able
   to pass for the real thing across a room. Neutral grey, no error tint. */
.audio-alarm[data-audio-alarm-test="1"] {
    background:#f4f6fa; border-color:var(--line); color:var(--mut);
    box-shadow:0 12px 28px -18px rgba(26,32,60,.35);
}
.audio-alarm[data-audio-alarm-test="1"] .audio-alarm-link { border-color:var(--line); color:var(--mut); }
@media (min-width:821px) { html.sidebar-collapsed .audio-alarm { transform:translateX(calc(-50% + 41px)); } }
@media (max-width:820px) {
    .audio-alarm { left:12px; right:12px; width:auto; transform:none; top:152px; flex-wrap:wrap; }
}

/* ── THE HOT-LEAD BOARD (routing step 7) ────────────────────────────────────────────────────────
   Reps down the side, Days 1–5 across. Green called · amber the day passed with no dial · grey not
   due yet. 🛑 GREY IS NOT A SOFT AMBER: a day that has not arrived is not a failure, and colouring
   it like one is how a board teaches a manager to ignore its own warnings. */
.hot-grid .hot-cell { text-align:center; font-weight:800; width:64px; font-size:15px; }
.hot-grid .hot-green { background:#e8f7ee; color:#1c7a43; }
.hot-grid .hot-amber { background:#fff5e0; color:#8a5b00; }
.hot-grid .hot-grey  { background:#f4f6fa; color:var(--mut); }

/* The accountability numbers sit UNDER the grid, on the same card: one screen, one question. */
.hot-numbers { display:flex; flex-wrap:wrap; gap:12px; margin-top:14px; }
.hot-number {
    flex:1 1 160px; min-width:150px; padding:11px 13px; border-radius:11px;
    background:#f8fafc; border:1px solid var(--line);
}
.hot-number b { display:block; font-size:22px; font-weight:800; color:var(--brand-ink); line-height:1.1; }
.hot-number span { display:block; margin-top:3px; font-size:12px; color:var(--mut); line-height:1.45; }
.hot-number i { font-style:normal; opacity:.8; }


/* ═══ THE GUIDED CALL SURFACE (design 2026-07-28, phase 4) ═══════════════════════════════════════
   Three areas and a bar: the section bar across the top, questions left, the question's push-backs
   right, and the sheet LOCKED to the bottom of the window while the questions scroll above it.

   🛑 IT LIVES HERE BECAUSE THIS IS THE ONLY SERVED SHEET. A second stylesheet is not loaded by the
   layout — the Pit screens shipped styles nobody ever saw that way.
   🛑 NO NEW COLOURS. Everything below is the design tokens in `:root`; a screen that invents its own
   palette is the one that looks bolted on. ────────────────────────────────────────────────────── */

.gcs { display:flex; flex-direction:column; gap:14px; min-height:0; flex:1; }

/* — the section bar: ONE bordered container, dividers, flat cells. No pills, no bubbles. — */
.gcs-bar {
    display:flex; width:100%; border:1px solid #dcdce8; border-radius:10px;
    /* 🛑 SCROLL, NEVER CLIP. `overflow:hidden` with cells that shrink past their own text cut the
       CURRENT tab in half on any window under ~1100px — the one tab whose label carries the `· 4/17`
       the rep is reading. The nine still share the width evenly when there is room; below that the
       bar scrolls and every label stays whole. */
    overflow-x:auto; overflow-y:hidden;
    background:#fff; box-shadow:0 1px 2px rgba(17,12,29,.04);
}
/* The tabs are SUBMIT BUTTONS now (owner 2026-07-29: leaving a page must save it) — strip the UA
   button chrome so the bar reads exactly as the anchors did. (0,1,1) on purpose, ABOVE the state
   rules so :hover / .is-current keep winning by source order on equal or higher specificity. */
button.gcs-tab { appearance:none; -webkit-appearance:none; background:none; border:0;
    border-right:1px solid #ececf4; font:inherit; cursor:pointer; }
button.gcs-tab:last-child { border-right:0; }
.gcs-tab {
    /* `1 0 auto`: grow to share the row, but NEVER shrink below the label. */
    flex:1 0 auto; display:flex; align-items:center; justify-content:center; gap:5px;
    padding:11px 6px; font-size:12.5px; font-weight:700; text-decoration:none;
    color:#6b6685; border-right:1px solid #ececf4; white-space:nowrap; min-width:0;
}
.gcs-tab:last-child { border-right:0; }
.gcs-tab:hover { background:#f7f7fb; }
.gcs-tab.is-current { background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#fff; }
.gcs-tab.is-done { color:var(--brand); }
.gcs-tab-count { font-weight:600; opacity:.85; }
.gcs-tab-mark { font-weight:800; }
/* ⚠ amber — a page the call left questions on. A MARK, never a wall: the arrow does not block. */
.gcs-warn { color:#b8860b; }
.gcs-tab.is-skipped { color:#8a6d1f; background:#fdfaef; }
/* The SAME warning, inside the page (owner 2026-07-29: "highlight the missing info in yellow
   inside the page also") — the chip's amber family on the rows its own count calls holes. A soft
   wash, background only: the question text keeps its ruled bold+dark, and answering it clears the
   tint on the next render exactly as the chip's count moves. */
.gcs-row.is-missing, .gcs-strip-box.is-missing, .gcs-fill.is-missing {
    background:#fdfaef; border-radius:8px; box-shadow:0 0 0 4px #fdfaef;
}

/* — the middle band — */
.gcs-body { display:grid; grid-template-columns:minmax(0,1fr) 350px; gap:16px; align-items:start; }
@media (max-width:1100px) { .gcs-body { grid-template-columns:minmax(0,1fr); } }

.gcs-questions { background:#fff; border:1px solid #e6e6ef; border-radius:12px; padding:18px 20px; }
.gcs-questions-head { display:flex; align-items:center; gap:12px; margin-bottom:14px; }
.gcs-page-name { margin:0; font-size:18px; color:var(--brand-ink); }
.gcs-expand-all {
    margin-left:auto; background:none; border:0; color:var(--brand); font-weight:700;
    font-size:11.5px; cursor:pointer; padding:4px 2px;
}

.gcs-basket { margin-bottom:16px; }
/* 🛑 A HIDDEN GATE MUST ACTUALLY BE HIDDEN — and it was not, found by LOOKING at the screen with
   every gate green (owner's per-reason cash cut, 2026-07-29). `[hidden]` is a UA `display:none`, so
   any rule that sets `display` outranks it: `.gcs-basket` sets none and hid correctly, which is why
   the BEAT gate never met this — but every ROW shape does (`.gcs-strip-box` and `.gcs-fill` are both
   `display:flex`). So a shut row rendered at full width with `hidden` set on it: measured
   `hiddenAttr:true, display:flex, width:164px`, and one of them was even carrying `is-current`, so
   the rail was coaching a row the walk deliberately skips. Visible and unreachable is the worst pair.
   🛑 A DOM PROBE MISSED IT AND THE EYE CAUGHT IT: `offsetParent` is NOT null for these, so a
   visibility check written that way reads clean. The property that answers this question is
   `getComputedStyle().display` — the same discrimination the rail-panel count turned on.
   Keyed on the GATE MARKER, not on a class list, so a row shape added later cannot silently lose its
   hide; `!important` for the reason `.ph-lost-banner[hidden]` already spells out two thousand lines
   up — this codebase has hit this exact trap before, and that rule is the idiom being reused. */
[data-gate-field][hidden] { display:none !important; }
/* A basket's spoken header — the beat's own live say, read as one invitation. */
.gcs-say {
    margin:0 0 8px; font-size:15.5px; line-height:1.55; font-weight:600; color:var(--brand-ink);
    background:#f3f6fd; border-left:3px solid var(--brand); border-radius:6px; padding:10px 13px;
}

/* — a ROW: question left, answer written on an underline to the right — */
.gcs-row { border-radius:8px; padding:2px 6px; cursor:pointer; }
.gcs-row:hover { background:#fafaff; }
.gcs-row-main { display:flex; align-items:baseline; gap:9px; padding:7px 0; }
.gcs-tick { width:14px; flex:0 0 14px; color:var(--brand); font-weight:800; font-size:12.5px; }
/* 🛑 A QUESTION IS BOLD AND DARK, AND THE STATE IS A SHADE (owner 2026-07-28: "questions should all
   need to be darker font and bold"). The three states still read apart — upcoming, answered, the one
   the rep is on — but the weight no longer carries any of that: an UPCOMING question at #6f6a86 and
   400 was a grey a rep had to lean into on a call, and the row they had not reached yet is the one
   they are about to read aloud. Colour alone now, all three bold, all three darker than that grey. */
.gcs-q { flex:1 1 auto; font-size:14px; line-height:1.5; color:#4c4866; font-weight:700; min-width:0; }
.gcs-quote { color:#b9b6c9; margin-right:4px; }
.gcs-row.is-current .gcs-q { color:var(--brand-ink); }
.gcs-row.is-answered .gcs-q { color:#3a3552; }
/* 🛑 A LABEL IS FURNITURE, NOT A LINE (owner 2026-07-29: "make this look cooler, small Goal").
   `.gcs-q` holds the verbatim ask AND, where there is none, the field's dictionary label — and it
   dressed both at 14px/700, which is the weight of something a rep says out loud. A label is read
   once and never spoken, so it took the treatment this sheet ALREADY declares for furniture three
   times over: `.gcs-fill-label` (a covered box's caption), `.gcs-strip-label`, `.gcs-pick-hint`
   ("furniture the rep reads and never says"). Reused rather than invented — a fourth, slightly
   different quiet grey would be the fork the one-look rule exists to stop.
   🛑 NOT A COLOUR SCHEME. The say/record/missing three-meaning colour proposal is UNRULED and stays
   unbuilt; this carries no meaning a rep has to learn, it is the same caption grey already on screen.
   🛑 The two overrides below are not decoration: `.gcs-row.is-current .gcs-q` and
   `.gcs-row.is-answered .gcs-q` are (0,2,1) and would silently beat a bare `.gcs-q.is-label` at
   (0,2,0) — the specificity trap this file has already been bitten by. Answered darkens rather than
   brightening, which is exactly what `.gcs-fill.is-answered .gcs-fill-label` does. */
.gcs-q.is-label { font-size:11.5px; font-weight:600; line-height:1.45; color:#a4a1b4; }
.gcs-row.is-current .gcs-q.is-label { color:#a4a1b4; }
.gcs-row.is-answered .gcs-q.is-label { color:#6f6a86; }
.gcs-a { flex:0 0 260px; }
/* 🛑 `input[type]` IS LOAD-BEARING, NOT DECORATION — the same idiom `.field-row input[type]` records
   above. The shared typed-field box is `input[type=text], …` at (0,1,1); a bare `.gcs-input` is
   (0,1,0) and LOSES, so this screen's underline was written, shipped, and never once rendered: every
   answer box on the guided surface was drawing the standard 10px-radius, 11px-padded white form box
   while the design said "the answer written on an underline to the right". Green suite, correct CSS
   in the file, wrong screen — found by looking at it. `[type]` makes this (0,2,1) and it wins on
   specificity rather than on being further down the file. */
input[type].gcs-input {
    width:100%; border:0; border-bottom:1.5px solid #d7d7e4; background:transparent;
    padding:3px 2px; font-size:14px; color:var(--brand-ink); border-radius:0;
}
.gcs-input:focus { outline:none; border-bottom-color:var(--brand); box-shadow:0 1px 0 var(--brand); }
/* 🛑 THE READ-ONLY ANSWER — the lead page's always-on Script tab, where the surface renders with no
   form to post into (owner 2026-08-02, "the script is always triggered, always on the tab").
   It deliberately does NOT wear the input's underline: an underline is this screen's promise that
   something may be typed there, and on that tab nothing may. Same metrics so the rows keep their
   rhythm beside the live screen; muted where a live answer is ink, because it is a reading of the
   file rather than an answer taken on a call. An em-dash stands in for empty, so a question nobody
   has answered still reads as a question rather than as a gap. */
.gcs-ro { display:inline-block; width:100%; padding:3px 2px; font-size:14px; color:var(--mut); }
.gcs-ro:empty::before { content:'—'; }
.gcs-row.is-current .gcs-input { border-bottom-color:var(--brand); }
.gcs-more { background:none; border:0; color:#a9a5bb; font-size:12.5px; cursor:pointer; padding:0 2px; }

.gcs-coach {
    margin:0 0 8px 23px; padding:9px 13px; background:#f7f7fb; border-radius:8px;
    font-size:12.5px; line-height:1.65; color:#4d4866;
}
.gcs-coach p { margin:0 0 5px; }
.gcs-coach p:last-child { margin-bottom:0; }

.gcs-move { display:flex; align-items:center; gap:10px; margin-top:18px; flex-wrap:wrap; }
.gcs-nonblock { font-size:11.5px; color:#8b8ba0; }
/* `.gcs-saved` — autosave's receipt, beside the section buttons. Quieter than `.gcs-nonblock` on
   purpose: it is reassurance a rep glances at, never a thing to read mid-call, and it appears only
   after a CONFIRMED background save (the script sets it; the markup ships `hidden`). Deliberately
   NOT a `.tag` — a chip would read as an outcome on a screen whose chips mean call outcomes. */
.gcs-saved { font-size:11.5px; color:#6f8f6f; margin-left:auto; }
/* `.gcs-unsaved` — the receipt turned WARNING (owner 2026-08-02: "a message in red when not saved").
   The same element, re-dressed rather than a second one: one place on the row ever speaks about
   saving, so a stale "Saved" can never sit beside a fresh "Not saved". Bolder and darker than the
   quiet green because this one is meant to be caught out of the corner of an eye.
   🛑 NOT `--danger`/`.alert-compliance`, which stays spent on compliance FAILs — this is broken
   plumbing wanting a human, the same register as `.tag.alert` (design-system rule 13). */
.gcs-saved.gcs-unsaved { color:#b4342a; font-weight:700; }
/* `.gcs-unsaved-pop` — the notice he asked to accompany it. FIXED to the corner, deliberately NOT a
   centred modal and with no backdrop: a rep is mid-call and the script underneath must stay readable
   and clickable. It ships `hidden` and only a failed save opens it. */
.gcs-unsaved-pop {
    position:fixed; right:18px; bottom:18px; z-index:60; max-width:330px;
    background:#fff; border:1px solid #e6b3ad; border-left:4px solid #b4342a;
    border-radius:10px; padding:13px 15px; box-shadow:0 10px 28px rgba(23,29,48,.16);
}
.gcs-unsaved-pop b { display:block; font-size:13.5px; color:#8f2a22; margin-bottom:4px; }
.gcs-unsaved-pop p { margin:0 0 10px; font-size:12.5px; line-height:1.55; color:var(--mut); }
.gcs-none { color:#8b8ba0; font-style:italic; }

/* — the rail: its own scroll, fixed width, push-backs WRITTEN OUT (nothing to click) — */
.gcs-rail {
    background:#fff; border:1px solid #e6e6ef; border-radius:12px; padding:14px 15px;
    max-height:calc(100vh - 300px); overflow-y:auto; position:sticky; top:14px;
}
/* The rail echoes the QUESTION, so it is set as a line to read — not as a shouted column
   heading. Upper-casing a sentence is what made this look like a different typeface again. */
.gcs-rail-q {
    margin:0 0 10px; font-size:12.5px; font-weight:700; color:var(--brand-ink);
    line-height:1.5;
}
/* Every top block on the rail wears a CAPTION (owner 2026-07-29: "label each top comment to explain
   what it is") — the sheet-caption treatment, furniture a rep can never mistake for a line to say. */
.gcs-rail-cap { margin:0 0 5px; font-size:10.5px; font-weight:800; text-transform:uppercase;
    letter-spacing:.07em; color:#8b8ba0; }
.gcs-rail-cap:not(:first-child) { margin-top:12px; }
/* Objections are ONE-LINERS, collapsed (owner 2026-07-29: "make objection 1 liner so we see more
   objections… collapsed and expandable when clicked"). Native <details>, the drawers' own no-JS
   idiom. The name keeps .gcs-pushback-name's gold, on one line however long the tag title runs. */
.gcs-objection { border-bottom:1px solid #f0f0f7; }
.gcs-objection:last-of-type { border-bottom:0; }
.gcs-objection > summary { display:flex; align-items:center; gap:6px; padding:6px 0;
    cursor:pointer; list-style:none; min-width:0; }
.gcs-objection > summary::-webkit-details-marker { display:none; }
.gcs-objection > summary::before { content:'▸'; color:var(--brand); font-size:10px; flex:0 0 auto; }
.gcs-objection[open] > summary::before { content:'▾'; }
.gcs-objection > summary:hover .gcs-objection-name { color:#6d5518; }
.gcs-objection-name { min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
    font-size:11.5px; font-weight:800; color:#8a6d1f; text-transform:uppercase; letter-spacing:.05em; }
.gcs-objection[open] { padding-bottom:8px; }
.gcs-objection .gcs-line { padding-left:16px; }
.gcs-line { margin:0 0 7px; font-size:12.5px; line-height:1.6; color:#3a3552; }
.gcs-rail-none { font-size:12.5px; color:#8b8ba0; font-style:italic; margin:0; }

/* — THE SHEET: locked to the bottom of the PANE, and the pane is the content column —
   🛑 IT IS A ROW OF THE SHELL, NEVER A FIXED OVERLAY (owner 2026-07-28: "this bottom area… should
   not span the whole bottom. it should stop as the left side pane"). `position:fixed; left:0` is
   measured from the WINDOW, so the bar ran the full width and passed under the sidebar. Pinning it
   to a sidebar width would break the moment the rail collapses (232px → 64px). Laying it out as the
   last row of a viewport-height flex column solves both without knowing any width, and it is the
   pattern this shell already uses for `.ph-strip` and `.sr-callbar` — where the comment beside them
   records that a sticky/fixed overlay trapped the pop card behind a stacking context once before.
   No z-index at all now, because it no longer floats over anything. */
.gcs-sheet {
    flex:0 0 auto;
    display:flex; flex-wrap:wrap; gap:0 18px; align-items:baseline;
    padding:9px 18px 11px; background:#fff; border-top:2px solid var(--brand);
    box-shadow:0 -6px 18px -12px rgba(17,12,29,.5); max-height:132px; overflow-y:auto;
}
/* The questions + rail take the space that is left and scroll their OWN overflow, so the sheet
   never leaves the screen while they move.
   🛑 THE FORM IS THE COLUMN NOW. The sheet moved INSIDE the form so its cells can be real inputs
   ("tap any cell to fix" — an input outside a form submits nothing), which put one more element
   between `.gcs` and its rows. The form has to pass the height straight through, or the sheet stops
   being the last row of a viewport-height column and the whole page scrolls as one again. */
body.call-surface .gcs-form { display:flex; flex-direction:column; gap:14px; flex:1; min-height:0; }
body.call-surface .gcs-body { flex:1; min-height:0; align-items:stretch; }
body.call-surface .gcs-questions, body.call-surface .gcs-rail {
    min-height:0; overflow-y:auto; overscroll-behavior:contain;
}
body.call-surface .gcs-rail { position:static; max-height:none; }
/* 🛑 THE TWO LAUNCHER CIRCLES CLEAR THE SHEET ON THE CALL SURFACE (2026-08-13). At their usual
   bottom:22px the calculator (right:86) and messenger (right:22) painted over the sheet's rightmost
   cells — values unreadable and "tap any cell to fix" untappable under 52px of button; the sheet is
   the one region of this screen that is DATA, while what sits behind the raised circles is the
   rail's scrollable coaching text. 150px clears the sheet at its 132px cap; when the sheet runs
   shorter the circles float a little above its top rule, which costs nothing. The messages pop
   keeps its 12px gap above the messenger circle, and its height clamp grows by the same 128px the
   anchor rose, or a short viewport pushes its head off the top — the very bug the clamp exists to
   stop. Same disease and fix-shape as every drawer starting at --ph-strip-h. */
body.call-surface .msg-launch, body.call-surface .calc-launch { bottom:150px; }
body.call-surface .msg-pop { bottom:214px; height:min(520px, calc(100vh - 278px)); }
/* A TIDY GRID, not a ragged wrap. Auto-fit columns give every cell the same rhythm, so the eye
   scans DOWN a column instead of hunting a wrapped line. */
.gcs-sheet { display:grid; grid-template-columns:repeat(auto-fill, minmax(158px, 1fr)); gap:2px 20px;
    align-items:start; align-content:start; }
.gcs-cell { display:flex; flex-direction:column; min-width:0; padding:3px 0; }
.gcs-cell-label { font-size:10px; text-transform:uppercase; letter-spacing:.06em; color:#9a97ab;
    font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.gcs-cell-value { font-size:12.5px; color:var(--brand-ink); font-weight:600; line-height:1.35;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* who + where + how-to-reach anchor EVERY page (owner) — marked so they read as the constants. */
.gcs-cell.is-anchor .gcs-cell-label { color:var(--brand); }
/* amber for a gap the call left — the same mark the section tab carries, never a block. */
.gcs-cell.is-empty.is-anchor .gcs-cell-value { color:#b8860b; }
/* derived, so it carries no box: a typed equity is an opinion in a calculation's clothes. */
.gcs-cell.is-derived .gcs-cell-value { color:var(--brand-2); font-weight:800; }

/* the mock's `fix` — the unit the box wears, $ before and % after. */
.gcs-a { display:flex; align-items:baseline; gap:4px; }
.gcs-fix { color:#8b8ba0; font-size:12.5px; font-weight:700; }
/* who you are talking to — the mock's own opening line. */
.gcs-who {
    display:flex; align-items:baseline; gap:7px; font-size:13.5px; color:var(--brand-ink);
    background:#fff; border:1px solid #e6e6ef; border-radius:10px; padding:9px 14px;
}
.gcs-who b { font-size:14px; }
.gcs-who span { color:#6b6685; font-size:12.5px; }
.gcs-who-back { text-decoration:none; color:var(--brand); font-weight:800; margin-right:2px; }

/* 🛑 WHAT INTAKE SENT, MARKED WITHOUT SHOUTING. It printed the words "ON FILE" under every anchor
   cell — five times in a row, a third of the bar's height spent saying the same thing. It is a
   STATE, not a caption: the value goes lighter and takes a small hollow dot. The legend sits once,
   at the end of the bar, so the mark is explained without being repeated. */
.gcs-cell.is-onfile .gcs-cell-value { color:#6f6a86; font-weight:500; }
.gcs-cell.is-onfile .gcs-cell-value::before {
    content:"○"; font-size:8px; color:#a9a5bb; margin-right:5px; vertical-align:middle;
}
.gcs-sheet-legend { grid-column:1/-1; font-size:10px; color:#a9a5bb; padding-top:2px; }

/* 🛑 THE TRUE-COST READOUT — three derived figures under the debts table, and it is DERIVED
   FURNITURE, not an answer. It wears the same hollow, quiet treatment the calculated equity does, for
   the same reason: nothing here has a box, nothing here is typed, and a rep must be able to tell at a
   glance which numbers on this screen came out of the customer's mouth and which came out of
   arithmetic. Every row carries its own provenance line, because a figure a rep cannot source is a
   figure a rep should not read. Declared ONCE, here — a second declaration of any of these classes
   would silently win somewhere and this readout would change shape on one screen only. */
.gcs-truecost {
    margin:10px 0 2px; padding:10px 14px; background:#f7f7fb;
    border:1px solid #e6e6ef; border-left:3px solid var(--brand); border-radius:10px;
}
.gcs-truecost-head {
    font-size:10px; font-weight:800; letter-spacing:.05em; text-transform:uppercase;
    color:#6b6685; margin-bottom:6px;
}
.gcs-truecost-row {
    display:flex; flex-wrap:wrap; align-items:baseline; gap:8px; padding:3px 0;
}
.gcs-truecost-label { font-size:12.5px; color:#3a3550; flex:1 1 auto; }
.gcs-truecost-value { font-size:15px; font-weight:800; color:var(--brand); font-variant-numeric:tabular-nums; }
.gcs-truecost-from { flex:1 0 100%; font-size:10px; color:#a9a5bb; }

/* 🛑 THE SAME MARK ON THE QUESTION ROW, AND HERE IT IS A CONTROL — the sheet's ○ is a state, this one
   is the confirm. It is the same glyph and the same grey on purpose: a rep who learns the mark on the
   summary bar must read it identically in the row they are working, and a second vocabulary for one
   idea is how a mark stops being read at all.
   It is sized and coloured as FURNITURE until hovered — the value beside it is the thing to read, and
   a button loud enough to compete with the datum would pull the eye off the answer. */
.gcs-onfile {
    flex:0 0 auto; border:0; background:transparent; cursor:pointer; padding:0 0 0 4px;
    font-size:9px; line-height:1; color:#a9a5bb; font-family:inherit;
}
.gcs-onfile:hover { color:var(--brand); }
/* The ring is the keyboard's, not the mouse's — `:focus-visible` so a click does not paint it, which
   is the same rule the pick rows already follow. */
.gcs-onfile:focus-visible { outline:2px solid var(--brand); outline-offset:2px; border-radius:2px; }
/* An on-file placeholder is the DATUM, not a hint, so it gets the sheet's on-file ink rather than the
   near-invisible grey a hint wears. It must still read as unconfirmed — darker than a hint, lighter
   than a typed answer.
   🛑 ALL THREE BOX-DRAWING ROW SHAPES, not just `.gcs-row`: `property_address` renders as a
   `.gcs-strip-box` and city/state/zip as covered `.gcs-fill`s, so a rule naming one shape styled none
   of the fields the owner asked to see marked. */
.gcs-row.is-onfile .gcs-input::placeholder,
.gcs-strip-box.is-onfile .gcs-input::placeholder,
.gcs-fill.is-onfile .gcs-input::placeholder { color:#6f6a86; font-weight:500; }
/* An empty cell is a placeholder, not a row of its own — it keeps the column and gives up the ink. */
.gcs-cell.is-empty .gcs-cell-value { color:#d3d1dd; font-weight:500; }

/* — TAP ANY CELL TO FIX (design) —
   A cell whose fact already has a box upstairs JUMPS to it (a plain <label for>, so the browser does
   the focusing and no script is involved). A cell nothing on the page asks IS the box. Never both:
   two inputs sharing one name means the last submitted wins, and the summary would overwrite what
   the rep just typed into the question. */
.gcs-cell-jump { cursor:pointer; }
.gcs-cell:hover .gcs-cell-jump { color:var(--brand); }
.gcs-cell-box { display:flex; align-items:baseline; gap:3px; min-width:0; }
.gcs-cell-box .gcs-fix { font-size:11.5px; }
/* `input[type]` for the same reason as `.gcs-input` above — at (0,1,0) the shared box wins and the
   sheet's tight rows become a column of 44px form fields. */
input[type].gcs-cell-input {
    width:100%; min-width:0; border:0; border-bottom:1px dashed #cfcdde; background:transparent;
    padding:0 1px 1px; border-radius:0; font-size:12.5px; font-weight:600;
    color:var(--brand-ink); line-height:1.35; font-family:inherit;
}
.gcs-cell-input::placeholder { color:#c6c3d4; font-weight:500; }
/* The ○ follows the fact, not the markup. An editable cell carries the on-file value as a
   PLACEHOLDER (it must never be inherited into a capture), so it has no `.gcs-cell-value` to hang
   the mark on — and the cells that turned into boxes are precisely the ones the rep has to confirm. */
.gcs-cell.is-onfile .gcs-cell-box::before {
    content:"○"; font-size:8px; color:#a9a5bb; margin-right:4px; align-self:center;
}
.gcs-cell-input:hover { border-bottom-color:#a9a5bb; }
.gcs-cell-input:focus { outline:none; border-bottom:1px solid var(--brand); }

/* — a SATELLITE box: a field someone else's ask baskets. It sits under that question, indented
     to the question's text, labelled small — a spot for the answer, never a second ask. */
.gcs-fill { display:flex; align-items:baseline; gap:9px; padding:4px 0 4px 23px; }
.gcs-fill-label { flex:1 1 auto; font-size:11.5px; color:#a4a1b4; }
.gcs-fill .gcs-a { flex:0 0 260px; }
.gcs-fill.is-answered .gcs-fill-label { color:#6f6a86; }
.gcs-fill .gcs-input { font-size:13.5px; color:#3f3b58; }

/* — A CLICK-RECORDED FIELD: buttons, never a box. The design's "Goal = a CLICK that turns the page:
   four buttons, two lines tall, equal width". The value IS the label picked, so a text box there was
   an answer field with no question above it. Radios under the hood, so the pick posts with the form
   and no script is involved. */
.gcs-row-pick { cursor:default; }
.gcs-row-pick:hover { background:transparent; }
/* 🛑 THE BUBBLE GEOMETRY IS TRIMMED FOR A LONG OPTION LABEL — owner 2026-07-29: "the bubbke for cash
   out reason could be smaller to fit all on row." MEASURED on the running screen at a 1440 viewport
   before touching anything: the picks container is 704px and cash-out's four buttons needed 711px, so
   the fourth wrapped by 7px. 🛑 What actually put them on one row is REMOVING "Investment / business"
   (his other instruction, same afternoon) — three buttons need 538px. This trim is the HEADROOM on
   top of it, because 538 of 704 leaves only ~6px of margin at a 1280 viewport and the row would wrap
   again on a narrower screen.
   🛑 IT IS UNIFORM, AND DELIBERATELY SO. If a tighter bubble is right for the widest option label on
   the screen ("Big expense (medical / tuition / etc.)", 250px) it is right for every long label, and
   one look drawn by one partial with three callers is the rule this row must not fork. Horizontal
   padding 14→11px and gap 8→7px: GEOMETRY ONLY — 12.5px type, 44px touch height, weight and colour
   are all untouched, so nothing on any of the nine tabs got harder to read. Three buttons now need
   520px, which holds down to a ~1256px viewport. */
.gcs-picks { display:flex; flex-wrap:wrap; gap:7px; padding:2px 0 6px 23px; }
.gcs-pick { position:relative; display:flex; align-items:center; justify-content:center; text-align:center;
    min-height:44px; padding:8px 11px; border:1px solid #d7d7e4; border-radius:9px; background:#fff;
    font-size:12.5px; font-weight:700; color:#4c4866; cursor:pointer; line-height:1.3; }
.gcs-pick input { position:absolute; opacity:0; pointer-events:none; }
.gcs-pick:hover { border-color:var(--brand); color:var(--brand-ink); }
/* 🛑 THE CLICK MUST LIGHT THE BUTTON AT CLICK TIME, not at the next server render (owner, on a live
   practice call 2026-07-29: "Can't select a goal"). The radio inside is opacity:0 and `.is-picked`
   was only ever stamped server-side, so a rep's click DID select — invisibly — and read as broken.
   `:has(input:checked)` lights it the instant the hidden radio checks; `.is-picked` stays for the
   server-rendered state after a save. Same rule serves every pick row on the screen, not just goal. */
.gcs-pick.is-picked, .gcs-pick:has(input:checked) { background:linear-gradient(180deg,var(--brand),var(--brand-2)); border-color:var(--brand-2); color:#fff; }
.gcs-pick input:focus-visible + span { outline:2px solid var(--brand); outline-offset:4px; border-radius:3px; }
/* WHAT AN ACRONYM ON A BUTTON MEANS (PITI / PI / PIT). A button stacks its label over its expansion,
   so the two read as one press rather than as two lines; the expansion takes the caption treatment —
   small, light, never the spoken weight — because it is furniture the rep reads and never says. */
.gcs-pick { flex-direction:column; gap:1px; }
.gcs-pick-hint { display:block; font-size:10px; font-weight:500; letter-spacing:.01em; opacity:.72;
    text-transform:none; }

/* — LOOK B: THE ODD ANSWER, at the end of the buttons (owner 2026-07-29: "dropdown B, A for long
   lists, everywhere"). A slim underline, deliberately UNLIKE the buttons beside it — it is the door
   for the answer the list does not have, not a fifth option. It stays quiet until it holds
   something, because a rep's eye should land on the buttons first.
   🛑 The `input[type]` prefix is not decoration: the sheet's global input rule is (0,1,1) and a bare
   class loses to it silently — the recorded cclg.css specificity trap. */
input[type=text].gcs-pick-other {
    align-self:center; width:112px; min-height:34px; border:0; border-bottom:1.5px solid #d7d7e4;
    border-radius:0; background:transparent; font-size:13px; color:var(--brand-ink);
    padding:5px 2px; box-shadow:none;
}
input[type=text].gcs-pick-other:focus { border-bottom-color:var(--brand); box-shadow:none; outline:none; }
input[type=text].gcs-pick-other::placeholder { color:#a4a1b5; font-style:italic; }
/* A written-in answer IS the row's answer — it reads as filled, the way a lit button does. */
input[type=text].gcs-pick-other:not(:placeholder-shown) { border-bottom-color:var(--brand-2); font-weight:700; }

/* A covered box whose dropdown became buttons is no longer a BOX, so it must stop being laid out as
   one — the label above, the buttons on their own line, and none of the 260px answer column. */
.gcs-fill.is-picks { display:block; }
.gcs-fill.is-picks .gcs-picks { padding-left:0; }

/* ═══ A CLOSED YES/NO SITS ON ONE LINE, ITS BUTTONS TO THE RIGHT ═══ (owner 2026-08-01, looking at
   the Property page: *"move question, Subject is where they live? a little down and put Yes No
   bubbles to teh right of it"*.)

   🛑 SCOPED TO THE FIELD, NOT TO `.is-picks`. Every button row carries that class — `prop_usage`,
   `mtg1_term`, the goal toggles — and most of them have three to seven long labels that need the
   full width `display:block` gives them. Widening this to `.is-picks` would re-wrap those rows to
   chase a two-button case, which is the divergence `data-gate-any` already taught this codebase.
   A second closed Yes/No gets one selector added here and nothing else.

   The breathing room above is the other half of his sentence ("a little down") — it separates the
   question from the address block that precedes it rather than nudging the whole page. */
.gcs-fill.is-picks[data-field="subject_property_fork"] {
    display:flex; align-items:center; gap:12px; margin-top:14px;
}
.gcs-fill.is-picks[data-field="subject_property_fork"] .gcs-picks { margin-top:0; }

/* — LOOK A: OUR OWN LIST UNDER THE UNDERLINE — for a list too long to be buttons (`property_state`,
   51). The box is unchanged; what changes is that the list which opens under it is OURS, in the
   rail's card treatment, instead of the browser's grey system menu — which is the thing he said
   looked off. Opens on focus, filtered by whatever has been typed, click to fill.
   🛑 It is NOT a tab trap: the rows are plain elements, so the whole control is ONE tab stop and a
   rep who types the answer and tabs on never meets the panel at all. */
.gcs-drop { position:relative; display:block; flex:1 1 auto; min-width:0; }
.gcs-drop-panel {
    position:absolute; left:0; right:0; top:100%; z-index:30; margin-top:5px; padding:5px;
    background:#fff; border:1px solid #e0deeb; border-radius:10px;
    box-shadow:0 14px 30px -14px rgba(26,18,48,.35);
    max-height:212px; overflow-y:auto; overscroll-behavior:contain;
}
.gcs-drop-panel[hidden] { display:none; }
.gcs-drop-row { display:flex; align-items:center; gap:8px; padding:7px 10px; border-radius:7px;
    font-size:13.5px; color:#3a3552; cursor:pointer; }
.gcs-drop-row[hidden] { display:none; }
.gcs-drop-row:hover { background:#f3f6fd; color:var(--brand-2); }
.gcs-drop-row.is-current { font-weight:700; color:var(--brand-2); }
.gcs-drop-row.is-current::after { content:'✓'; margin-left:auto; color:var(--brand); font-weight:800; }
.gcs-drop-free { display:block; border-top:1px solid #f0f0f7; margin-top:4px; padding:7px 10px;
    font-size:12px; color:#8b8ba0; }

/* THE BOUND LINE — a spoken line that belongs to exactly one answer (owner 2026-07-28: pressing FHA
   pops the MIP line). It IS `.gcs-say`, because a rep must be able to tell at a glance that this is
   theirs to read; the rule below only sets it apart from the header it sits under, so nobody reads
   the pair as one long question. Indented to the answers it hangs off, and marked as arriving. */
.gcs-say.gcs-bound { margin:4px 0 10px 23px; padding-left:11px; border-left:3px solid var(--brand-2); }
.gcs-say.gcs-bound[hidden] { display:none; }

/* — THE REP'S CLICK: a road the rep TAKES and the file does not RECORD, whose only job is to reveal
   the bound line above. PQ3E's balloon is the case — "they mention a balloon" is the rep noticing,
   not the customer answering, so there is nothing to store and the control carries no `name`.
   🛑 It is deliberately NOT a `.gcs-pick` and deliberately NOT `.gcs-say`. A solid answer button
   would read as one of the customer's answers, and the spoken styling would put a label in a rep's
   mouth. Dashed, quiet, aside — a switch the rep presses, sitting where the line it reveals lands. */
.gcs-trigger { margin:2px 0 4px; }
.gcs-trigger-switch {
    display:inline-flex; align-items:center; gap:8px; min-height:34px; margin-left:23px;
    padding:5px 12px; border:1px dashed #cfcde0; border-radius:9px; background:#fff;
    font-size:11.5px; font-weight:700; letter-spacing:.01em; color:#7d7993; cursor:pointer;
}
.gcs-trigger-switch:hover { border-color:var(--brand); color:var(--brand-ink); }
.gcs-trigger-input { width:15px; height:15px; margin:0; accent-color:var(--brand); cursor:pointer; }
.gcs-trigger-input:checked ~ span { color:var(--brand-ink); }
.gcs-trigger-switch:focus-within { outline:2px solid var(--brand); outline-offset:3px; }
.gcs-trigger .gcs-say.gcs-bound { margin-top:7px; }

/* A CAPTURE-ONLY LANE'S HEADING — the design's fourth debt lane, which the call never asks for.
   🛑 Deliberately NOT `.gcs-say`: that is the spoken styling, and a rep who reads a section caption
   aloud is the defect this screen's "it invents no spoken words" rule exists to stop. Small, grey,
   upper-case — it reads as furniture, not as a line. */
.gcs-block-label {
    margin:14px 0 4px; font-size:11px; font-weight:800; text-transform:uppercase;
    letter-spacing:.07em; color:#9a97ab;
}

/* — THE DRAWER: a stretch of the call that only happens sometimes, folded behind one strip.
   🛑 Owner ruling 2026-07-28, after the Mortgage tab was measured at 39 boxes and seven screens of
   scroll: "Option one, but the second mortgage/HELOC is listed right below 1st mortgage on prequal
   view. other properties under separate." The second HOUSE folds; the second LIEN does not.
   A native <details>, so the fold is JS-free and the boxes stay inside the form. -- */
.gcs-drawer { margin:6px 0 16px; border:1px dashed #d3d1e2; border-radius:9px; background:#fbfbff; }
.gcs-drawer[open] { background:#fff; border-style:solid; border-color:#e6e6ef; }
.gcs-drawer-strip {
    display:flex; align-items:center; gap:10px; padding:10px 13px; cursor:pointer;
    font-size:13.5px; font-weight:700; color:#4c4866; list-style:none;
}
/* Safari still paints the default triangle from ::-webkit-details-marker, and the ＋ is the affordance. */
.gcs-drawer-strip::-webkit-details-marker { display:none; }
.gcs-drawer-strip:hover { color:var(--brand-ink); }
.gcs-drawer-plus { color:var(--brand); font-weight:800; }
.gcs-drawer[open] .gcs-drawer-plus { transform:rotate(45deg); }
.gcs-drawer-name { flex:1 1 auto; min-width:0; }
.gcs-drawer-count { font-size:11.5px; font-weight:600; color:#9a97ab; white-space:nowrap; }
.gcs-drawer .gcs-basket { padding:0 13px; }
.gcs-drawer .gcs-basket:last-child { padding-bottom:12px; }
@media (prefers-reduced-motion:no-preference) { .gcs-drawer-plus { transition:transform .15s ease; } }

/* -- THE CELEBRATION BURST (shared 2026-07-28, when THE GAUNTLET became the second screen that
   celebrates). These three rules lived in drill/hot-seat.blade.php's private <style>, so any other
   screen using the classes got the markup and none of the look — `public/css/cclg.css` is the ONLY
   served sheet. The `hs-` prefix is kept deliberately: hot-seat still OWNS this vocabulary and other
   views BORROW it (run-/gym- already borrow hs- keyframes); renaming would have churned four files
   to say the same thing. Keyframes (hs-thump) were already here. -- */
.hs-stamp { text-align:center; margin:6px 0 18px; }
.hs-stamp-letter { display:inline-block; font-size:96px; font-weight:900; line-height:1; animation:hs-thump .5s ease-out; }
.hs-confetti { position:fixed; inset:0; z-index:var(--z-decoration); pointer-events:none; }
@media (prefers-reduced-motion:reduce) { .hs-stamp-letter { animation:none; } }

/* The coaching, now that it lives on the RAIL rather than under the question (owner 2026-07-28:
   "move the info under teh question that we have now" to the right side, "with posible rebuttals
   you may use for that question below it"). Sits between the echoed question and its push-backs. */
.gcs-rail-coach {
    margin:0 0 12px; padding:9px 11px; background:#f7f7fb; border-radius:8px;
    font-size:12.5px; line-height:1.6; color:#4d4866;
}
.gcs-rail-coach p { margin:0 0 6px; }
.gcs-rail-coach p:last-child { margin-bottom:0; }
.gcs-rail-coach b { color:var(--brand-ink); }

/* — A BASKET'S ANSWERS READ ACROSS, NOT DOWN (owner 2026-07-28) —
   One breath can cover eight facts (PQ7C asks what it is, where, what it's worth, how long and what
   it rents for). Stacked question-left/answer-right that is eight rows and a scroll, and the rep is
   typing below the invitation they just read instead of inside it. A wrapping flex row keeps a whole
   basket on screen: a small caption over each underline, in the order the breath asks them.
   🛑 ONLY A BASKET. A beat with ONE ask keeps the row shape — a lone box in a strip is a label
   floating in white space, and the spoken question needs the width. */
.gcs-strip { display:flex; flex-wrap:wrap; gap:6px 18px; padding:2px 0 8px 23px; }
.gcs-strip-box { display:flex; flex-direction:column; gap:1px; flex:1 1 150px; max-width:230px; min-width:0; }
/* The caption is FURNITURE, never an ask — upper-case and small for the same reason the sheet's is:
   nothing a rep could mistake for a line to read. */
.gcs-strip-label { font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.06em;
    color:#6f6a86; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.gcs-strip-box.is-answered .gcs-strip-label { color:var(--brand); }
/* `.gcs-a` is 260px fixed in a row; in a strip the box takes the column it was given. */
.gcs-strip .gcs-a { flex:1 1 auto; min-width:0; }
.gcs-strip input[type].gcs-input { font-size:13.5px; }
/* A click-recorded field inside a basket is still buttons (they ARE the answer), so it takes the
   whole line rather than folding four two-line buttons into a 150px column. */
.gcs-strip > .gcs-row-pick { flex:1 1 100%; max-width:none; }
/* the strip already carries the indent; the row's own would step the buttons in a second time. */
.gcs-strip > .gcs-row-pick, .gcs-strip > .gcs-row-pick .gcs-picks { padding-left:0; }

/* — THE DEBTS GRID (owner approved on sight 2026-07-29; his one look change: headers CENTERED) —
   Row names and headers are furniture; the cells are ordinary capture inputs on an underline, so
   the $-grouping, the missing tint and the current ring all apply. The P/O column is his checkbox
   ("if we are paying it off on our loan") — ticked, never spoken. */
.gcs-grid-wrap { overflow-x:auto; margin:2px 0 14px; }
table.gcs-grid { width:100%; border-collapse:collapse; }
table.gcs-grid th.gcs-grid-h { font-size:10.5px; letter-spacing:.07em; text-transform:uppercase;
    font-weight:800; color:#6f6a86; text-align:center; padding:0 10px 8px;
    border-bottom:1px solid #e6e6ef; white-space:nowrap; }
table.gcs-grid th.gcs-grid-first { width:27%; }
table.gcs-grid th.gcs-grid-po { width:64px; }
table.gcs-grid th[scope=row] { text-align:left; font-size:13.5px; font-weight:700; color:#3a3552;
    padding:10px 10px; white-space:nowrap; }
table.gcs-grid td { padding:10px 10px; }
table.gcs-grid tbody tr + tr th[scope=row], table.gcs-grid tbody tr + tr td { border-top:1px solid #f0f1f7; }
.gcs-grid-cell { display:flex; align-items:baseline; gap:5px; border-bottom:1px solid #d3d1e2; padding-bottom:3px; }
.gcs-grid-cell.is-current { border-bottom-color:var(--brand); }
.gcs-grid-cell.is-missing { background:#fdfaef; border-radius:4px 4px 0 0; }
/* input[type] prefix — the shared form box is (0,1,1) and a bare class silently loses to it. */
table.gcs-grid input[type=text].gcs-input { border:0; border-radius:0; background:transparent;
    font-size:14px; color:#241f45; padding:0 2px 2px; min-height:18px; box-shadow:none; }
table.gcs-grid input[type=text].gcs-input:focus { outline:none; box-shadow:none; }
input[type=text].gcs-grid-typed { font-style:italic; font-size:13px; border-bottom:1px dashed #b9bed6; }
td.gcs-grid-po { text-align:center; }
.gcs-grid-tick { width:18px; height:18px; accent-color:var(--brand); vertical-align:middle; cursor:pointer; }
tr.gcs-grid-added th[scope=row] { color:#3a3552; }
tr.gcs-grid-addrow td { padding-top:12px; }
button.gcs-grid-add { display:inline-flex; align-items:center; gap:7px; border:1px dashed #b9bed6;
    background:#fbfbfe; color:#5d5980; border-radius:9px; padding:7px 13px; font-size:12.5px;
    font-weight:700; font-family:inherit; cursor:pointer; margin-right:8px; }
button.gcs-grid-add:hover { border-color:var(--brand); color:var(--brand-2); }

/* The line to say for a fact the story skipped — ON THE RAIL, where the rest of the help for the box
   under the cursor already lives (owner 2026-07-28). It rode the box itself until then, which put a
   second spoken line inside the form area under a header that had already said it; the strip above
   has no room for one either. Set as speech, because it is: the rep reads it verbatim. */
.gcs-rail-ask {
    margin:0 0 10px; font-size:13.5px; line-height:1.5; font-weight:600; color:var(--brand-ink);
    background:#f3f6fd; border-left:3px solid var(--brand); border-radius:6px; padding:8px 11px;
}

/* ── THE GUIDED PAGE WEARS THE LEAD PAGE'S TABSTRIP (owner 2026-07-29: the guided surface IS the
      lead page's Script tab — "top Nav tabs too"). The tabs are SUBMIT BUTTONS (leaving by tab is
      a save, the same live-call ruling as the section bar), so the anchor styling must hold on a
      <button> too. ── */
.tabstrip button.tab { background:none; border:none; cursor:pointer; font:inherit; }

/* ＋ ADD A LOG, from any tab — owner Q12j, 2026-07-31: "Put a '+' on top of tab to add a log, a pop
   up appear". A `<details>` popover (the .cl-snz / .cl-editbox idiom), so no script and no z-index
   competing with the call bar or the dispo capsule.
   `position:relative` on the details + `absolute` on the panel is what makes it a POPOVER rather
   than a block that shoves the page down every time it opens — the tabstrip is flex, and an
   in-flow panel would re-wrap the tabs underneath it. */
.tab-add { position:relative; margin-left:auto; }
.tab-add > summary { list-style:none; font-size:17px; line-height:1; padding-left:12px; padding-right:12px; }
.tab-add > summary::-webkit-details-marker { display:none; }
.tab-add > summary:focus-visible { outline:2px solid var(--brand); outline-offset:-2px; }
.tab-add[open] > summary { color:var(--brand); background:#f1f4fa; }
/* 🛑 A LOCAL z-index, NOT the app's named scale. `--z-popcard` (130) is the new-caller POP — a
   viewport-level overlay — and borrowing it here would put a little dropdown on the same plane as
   the thing that interrupts a rep mid-call. This only has to clear the card immediately below it.
   Same reasoning as .ws-lien-rowlab: inside a card, never an overlay. */
.tab-add-pop { position:absolute; right:0; top:calc(100% + 6px); z-index:5;
    width:min(320px, 84vw); padding:12px; border:1px solid var(--line); border-radius:11px;
    background:var(--card); box-shadow:0 14px 34px -14px rgba(17,12,29,.4); }
.tab-add-pop select, .tab-add-pop textarea { width:100%; margin-bottom:8px; }
.tab-add-pop .btn { width:100%; justify-content:center; }
/* 🛑 THE ACTIVITY LOG'S WRITER, NOW A POPUP — owner 2026-08-02: "the whole adding a note that you
   see is off screen… when you press ＋, a pop up comes up". The inline writer stood between the
   heading and the first entry, so opening the tab showed a FORM and pushed the log below the fold.
   Modelled on `.tab-add` rather than invented: same details/summary shape, same local z-index
   reasoning — this only has to clear the table under it, so it does NOT borrow `--z-popcard` (130),
   which is the mid-call caller POP and belongs on a different plane entirely. */
/* ⚖️ THE ＋ SHARES THE FILTER ROW SINCE 2026-08-31 — Gil: *"have '+ Add to the log' just be a big
   bold '+' on the right hand side on same row as Everything, Calls Only and Everything Else."* It
   had a line of its own, which cost a whole row of vertical space on a tab that exists to be READ.
   🛑 `margin-left:auto` rather than `order`, so the markup order and the visual order agree — a
   keyboard reaches the filters then the button, which is what the eye does. */
.cl-viewsrow { display:flex; align-items:center; gap:10px; margin:0 0 10px; }
.cl-viewsrow .cl-views { margin:0; }
.cl-viewsrow .cl-add { margin:0 0 0 auto; }

.cl-add { position:relative; display:inline-block; margin:0 0 12px; }
/* ⚖️ A BIG BOLD ＋, HIS WORDS. Square rather than a pill: it is one glyph now, and a pill sized for
   four words around a single character reads as a button somebody forgot to fill in. The accessible
   name lives on `aria-label`/`title` in the Blade — a glyph with no name would be the wrong version
   of this instruction, not a faithful one. */
.cl-add > summary { list-style:none; cursor:pointer; display:inline-flex; align-items:center;
    justify-content:center; width:32px; height:32px; font-size:19px; line-height:1; font-weight:800;
    color:var(--brand); border-radius:9px; border:1px solid var(--line); background:var(--card); }
.cl-add > summary::-webkit-details-marker { display:none; }
.cl-add > summary:hover { background:#f1f4fa; }
.cl-add > summary:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.cl-add[open] > summary { background:#f1f4fa; }
/* ⚖️ WIDER AND SHORTER — Gil, 2026-08-31: *"Make the add activity log pop up wider and shorter so
   you can see the whole box without having to scroll down."*
   🛑 IT OPENS FROM THE RIGHT NOW (`right:0`), because its opener moved to the right edge of the
   filter row — anchored `left:0` a 720px panel would hang off the page. */
.cl-add-pop { position:absolute; right:0; left:auto; top:calc(100% + 6px); z-index:5;
    width:min(720px, 94vw); padding:14px; border:1px solid var(--line); border-radius:11px;
    background:var(--card); box-shadow:0 14px 34px -14px rgba(17,12,29,.4); }
.cl-add-pop input[type=text], .cl-add-pop input[type=datetime-local] { width:100%; }
/* 🛑 SIDE BY SIDE, NOT STACKED. The stack is what made it tall — and at 720px the kind and the
   sentence fit on one line, which is also how a person says it: "note — spoke with borrower". */
.cl-add-pop .cl-logger-row { display:flex; gap:10px; align-items:stretch; margin-bottom:10px; }
.cl-add-pop .cl-logger-row select { flex:0 0 170px; }
/* ⚠️ A FIXED, MODEST HEIGHT — `rows="3"` was rendering ~330px inside the pop-up. Still resizable by
   the rep, so a long note is one drag away rather than one scroll away. */
.cl-add-pop .cl-logger-row textarea { flex:1 1 auto; min-height:76px; height:76px; }
/* The foot: the follow-up drawer, the tell picker and the submit on ONE line, submit pushed right. */
.cl-add-foot { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.cl-add-foot .cl-fu, .cl-add-foot .cl-tell { margin-top:0; }
/* 🛑 THE SELECT MUST BE GIVEN A WIDTH HERE OR IT EATS THE WHOLE ROW, and this is the defect Gil
   photographed on 2026-08-31 an hour after the row shipped: *"add to log, is hovering over words."*
   Ambient form styling gives a `select` `width:100%`, which inside a flex row resolves against the
   TELL BLOCK — so the select took all 395px of it, the label crushed to 22px and wrapped to three
   lines, and "Lands on their To-Do's" overflowed underneath the Add-to-log button.
   ⚠️ **AND MY OWN PROBE HAD CLEARED IT** — the harness inlined only the `.cl-*` rules, so the
   `width:100%` that causes this was not in it. A harness that omits the cascade cannot see a
   cascade defect; the probe was rebuilt with the WHOLE stylesheet and reproduced it immediately.
   ⇒ every child is pinned: the label and the hint never wrap, the select shrinks first, and the
   whole tell block drops to its own line before anything is crushed. */
.cl-add-foot .cl-tell { flex:1 1 auto; flex-wrap:nowrap; min-width:0; }
.cl-add-foot .cl-tell label { flex:0 0 auto; white-space:nowrap; }
.cl-add-foot .cl-tell select { width:auto; flex:0 1 200px; min-width:140px; }
.cl-add-foot .cl-tell-why { flex:0 0 auto; white-space:nowrap; }
.cl-add-foot .cl-fu { flex:0 0 auto; }
.cl-add-foot .cl-logger-actions { margin:0 0 0 auto; }
/* ⚠️ AND THE FOLLOW-UP BOX STILL SPANS THE WHOLE WIDTH WHEN IT OPENS — it is a `<details>` inside a
   flex row, so without this its opened box would try to share the line it was folded on. */
.cl-add-foot .cl-fu[open] { flex:1 1 100%; }

/* The lead's two facts UNDER Status — owner 2026-08-03: "Ans that and Cashout/ Equity should be on
   under status." `.lead-controls` is a flex ROW, so Status and its chips need their own stacked box
   or the chips land beside the select instead of beneath it.
   ⚠️ REPLACES `.lead-typeslot`/`.lead-typelabel` (08-02), which stacked a LITERAL "Type of lead"
   caption over the product chip. He asked for the temperature itself, not a heading — see the Blade.
   Right-aligned to match `.lead-actions`, which is `align-items:flex-end` above 820px. */
.lead-statusstack { display:flex; flex-direction:column; align-items:flex-end; gap:6px; }
.lead-typechips { display:flex; align-items:center; gap:6px; flex-wrap:wrap; justify-content:flex-end; }
/* The temperature rides a DOT on the shared `.tag.type` fact-tint rather than its own chip wash:
   `.tag.warn` amber and `.tag.alarm`/`.tag.alert` red are the severity vocabulary and stay spent on
   things a human must act on. A lead being Hot is a fact, not an alarm. Same 7px dot the product
   chip already uses, so the pair reads as one row. */
.temp-dot { width:7px; height:7px; border-radius:50%; margin-right:5px; display:inline-block;
    background:var(--brand); flex:0 0 auto; }
.temp-dot.is-hot { background:#c2381a; }
.temp-dot.is-warm { background:#c98a00; }
.temp-dot.is-cold { background:#4a7fb5; }
@media (max-width: 820px) {
    .lead-statusstack { align-items:flex-start; }
    .lead-typechips { justify-content:flex-start; }
}

/* WHERE DID THIS COME FROM? — the panel the server sends back when a rep picks a status that STOPS
   THE CALLING (owner 2026-08-04: "if anyone ever asks why we stopped calling someone, that sentence
   is the answer. Compliance, not decoration."). It is a FORM, not a dialog: the refusal happens on
   the server, so this still works with scripting off, and there is nothing here that can fail open.
   Left-aligned inside the right-aligned stack — it is prose to read, not a control to line up. */
.lead-askwhy { display:flex; flex-direction:column; gap:8px; align-items:flex-start; text-align:left;
    max-width:340px; margin-top:8px; padding:11px 13px; border:1px solid var(--line);
    border-left:3px solid var(--brand); border-radius:10px; background:var(--panel,#fff); }
.lead-askwhy-head { margin:0; font-size:13px; }
.lead-askwhy-src { display:flex; flex-direction:column; gap:5px; font-size:12px; font-weight:700;
    color:#4a5170; width:100%; }
.lead-askwhy-acts { display:flex; gap:8px; }
.lead-askwhy-hint { font-weight:400; font-size:11.5px; line-height:1.45; }
/* The one press with no undo wears the compliance red the rest of the app reserves for exactly this.
   `--danger-ink` is that token; a hex here would be a red nobody registered. */
.lead-askwhy.is-dnc { border-left-color:var(--danger-ink); }
.lead-askwhy-warn { margin:0; font-size:12px; line-height:1.5; color:var(--danger-ink); }
.lead-askwhy-check { display:flex; align-items:flex-start; gap:7px; font-size:12px; font-weight:700; }
.lead-askwhy-check input { width:auto; margin:2px 0 0; flex:0 0 auto; }
@media (max-width: 820px) { .lead-askwhy { max-width:none; } }

.gcs-crm-tabs { margin:10px 0 2px; }
/* The sticky-note opener sits at the far end of the lead-file tabs, off the tab rhythm — it is not a
   tab (it opens a pad over this page, it does not go anywhere), so it must not read as one. */
/* The sticky-note opener at the far right of the who-bar — the ONE home for it on this screen, so it
   is there on a practice run too (the lead-file tabstrip below is lead-attached-only).
   🛑 The `.gcs-who span` rule above paints every span in this bar grey at 12.5px, which would eat the
   button's own label and its off-state; scoped back here rather than loosening that rule. */
.gcs-who-note { margin-left:auto; padding-left:10px; align-self:center; }
.gcs-who-note span { color:inherit; font-size:inherit; }

/* ── CALL FLOW (/ops/call-flow, owner 2026-07-29) — the derived process chart. Two lanes of boxes
      and arrows; everything else is the house .card/.table/.tag vocabulary, so this adds only the
      lane shape and nothing that could drift from the design system. ── */
.cf-h { font-size:13px; text-transform:uppercase; letter-spacing:.06em; color:var(--mut);
    font-weight:800; margin:26px 0 12px; }
.cf-lanes { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.cf-lane { display:flex; flex-direction:column; }
.cf-lane-h { font-size:15px; font-weight:800; color:var(--brand-ink); margin-bottom:12px; }
.cf-step { border:1.5px solid var(--line); border-radius:11px; padding:10px 13px; }
/* The step's TITLE is the direct-child <b> — scoped with `>` on purpose: an emphasised word inside
   the description must stay inline, and an unscoped rule broke each one onto its own line. */
.cf-step > b { display:block; font-size:13.5px; color:var(--brand-ink); }
.cf-step span { font-size:12.5px; color:var(--mut); }
/* A step whose behaviour is LIVE gets the brand rule — the eye needs to tell built from designed. */
.cf-step.cf-live { border-left:5px solid var(--brand); }
.cf-arrow { text-align:center; color:var(--mut); font-size:15px; line-height:1; margin:5px 0; }
.cf-cols { display:grid; grid-template-columns:1fr 1fr; gap:18px; }
.cf-list { list-style:none; margin:8px 0 0; padding:0; }
.cf-list li { font-size:13px; padding:5px 0; border-top:1px solid var(--line); }
.cf-say { font-size:12.5px; color:var(--mut); max-width:460px; }
/* The derived-gap notice. 🛑 Amber, never the alarm (RULE 13): a gap is a notice, not a verdict. */
.cf-gap { background:#fff8ec; border:1px solid #f0d9ae; border-radius:10px; padding:9px 13px;
    font-size:13px; margin:0 0 10px; }
@media (max-width:900px) { .cf-lanes, .cf-cols { grid-template-columns:1fr; } }

/* ── THE SCRIPT TAB BETWEEN CALLS (owner 2026-07-29: "what if I want to see what info we got on the
      call"). The opener PREVIEW reuses the call screen's say-line shape; the known-facts grid is a
      compact read-only echo of the sheet — never an editor (Prequal owns the writing). ── */
.crm-script-main { border-left:5px solid var(--brand); }
.crm-script-label { font-size:10.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em;
    color:var(--mut); margin-bottom:6px; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.crm-script-say { font-size:19px; font-weight:700; color:var(--brand-ink); line-height:1.45;
    white-space:pre-line; }
.crm-known { display:grid; grid-template-columns:repeat(auto-fill, minmax(190px, 1fr)); gap:10px 16px; }
.crm-known-cell { border-top:1px solid var(--line); padding-top:7px; min-width:0; }
.crm-known-label { display:block; font-size:10.5px; font-weight:800; text-transform:uppercase;
    letter-spacing:.05em; color:var(--mut); }
.crm-known-value { display:block; font-size:14.5px; font-weight:700; color:var(--brand-ink);
    overflow-wrap:anywhere; }

/* ── THE DECISION LOG (/manage/decision-log, owner 2026-07-29) — the RECORD of his rulings. His words
      are quoted in `.dl-ruling`, set apart so a quote can never be mistaken for our summary of one. ── */
.dl-when { font-size:17px; color:var(--brand-ink); margin:0 0 3px; display:flex; align-items:center; gap:10px; }
.dl-theme { color:var(--mut); font-size:14px; margin:0 0 6px; }
.dl-h { font-size:12.5px; text-transform:uppercase; letter-spacing:.06em; color:var(--mut);
    font-weight:800; margin:26px 0 10px; }
.dl-item { padding:15px 19px; }
.dl-item p { font-size:14px; margin:8px 0 0; }
.dl-item ul { margin:4px 0 0 18px; padding:0; font-size:14px; }
.dl-item li { margin:5px 0; }
/* HIS WORDS. Quoted, never paraphrased — the left rule and the italic say "he said this". */
.dl-ruling { border-left:4px solid var(--brand); background:#f4f7ff; border-radius:0 9px 9px 0;
    padding:9px 14px; font-size:15px; font-style:italic; font-weight:600; color:var(--brand-ink); }
.dl-shipped { color:var(--mut); font-size:13px; }
.dl-shipped code { font-size:12px; background:var(--ring); color:var(--brand); padding:1px 5px; border-radius:4px; }
/* A spoken line, rendered as a rep reads it. */
.dl-say { font-size:16px; font-weight:700; color:var(--brand-ink); border-left:4px solid var(--line);
    padding:7px 14px; margin:8px 0 0; }
.dl-fix { border-left:4px solid #f0d9ae; }
.dl-open { border-left:4px solid var(--brand-ink); }

/* ═══ THE CALLBACK DUE NOTICE (owner ask Q4, 2026-08-01) ═══
   "when a scheduled callback's time arrives, it POPS UP on the rep's screen." The read side
   (ops/callbacks/due) shipped 07-31 and NOTHING on any screen referenced it, so no rep was ever
   told — the endpoint was the easy half. This is the telling.

   🛑 IT LOSES EVERY CONTEST FOR SCREEN SPACE, and it loses on THREE independent mechanisms, because
   one of them failing silently is how the hang-up bug shipped three times:

     1. GEOMETRY. Fixed BOTTOM-LEFT. The phone strip is sticky at the TOP and the dispo capsule
        centres itself; nothing this notice can ever do covers Hang up, Hold, Mute or Transfer, even
        with every stylesheet rule below deleted. That is the floor, and it does not depend on a
        cascade, a token or a browser feature.
     2. ELEVATION. --z-toast, which is what this is: a transient banner. Below --z-callbar by the
        scale's own construction, so CallControlsUnburiableTest already covers it.
     3. SUPPRESSION. The four selectors below BLANK it outright while a contender is on screen. Not
        dimmed, not moved — gone. A live call, wrap-up dispositioning, the dispo capsule and the
        hot-lead offer each win absolutely, and the notice comes back by itself when they end.

   Suppression is CSS and not JS deliberately: it must hold on the tick where the poll is dead, the
   tab is throttled or the script threw. `:has()` is already vocabulary here (see .gcs-pick).

   Amber, never the alarm. Rule 13 — the alarm marks a VERDICT, and a callback coming due is a
   NOTICE about a promise, on a surface a rep may be mid-call on. The documented warn pair.

   ⬜ THREE PRESENTATIONS, AND THE CHOICE IS THE OWNER'S (parked 2026-08-01). `[data-cbd-style]`
   carries it — badge · corner (the placeholder default) · banner — set from
   `config('cclg.callback_notice_style')`. 🛑 The SHARED rule below holds the safety property for
   ALL of them: every style is fixed and anchored to the BOTTOM, and none may declare `top`. Swapping
   the presentation therefore cannot put the notice over Hang up, whichever one he picks. A modal is
   deliberately absent — it would take the screen from a rep who may be mid-call. */
.cbd-note {
    position:fixed; bottom:22px; z-index:var(--z-toast);
    display:flex; gap:12px; align-items:flex-start;
    background:#fde6c7; border:1px solid #e3c48d; color:#8a5a00;
    box-shadow:0 14px 32px -12px rgba(23,18,37,.42);
}
.cbd-note[hidden] { display:none; }

/* CORNER — the placeholder. 🛑 WIDENED TO 430px ON HIS INSTRUCTION, 2026-08-02: "All on one line,
   make box wider if you need to". The history, because the number keeps moving and each move had a
   reason: 368 was measured at THREE acts (at 340 "Not now" wrapped); a fourth was added and it
   wrapped again; he then collapsed the row to THREE — Open · Snooze · Dismiss — and asked for one
   line. 430 fits those three with the picker at its widest option ("Tomorrow 9am") and still clears
   the viewport rule below.
   🛑 `flex-wrap` stays on `.cbd-acts` deliberately: this is a fixed-width card on a rep's screen and
   a wrap is the safe failure. Do not swap it for `nowrap` to "guarantee" the line — that guarantees
   overflow instead. If a fourth act ever returns, LOOK at the card before choosing a width. */
.cbd-note[data-cbd-style="corner"] {
    left:22px; width:430px; max-width:calc(100% - 44px);
    padding:13px 15px 14px; border-radius:14px;
}

/* BADGE — the least interrupting thing that still tells them. One line, no body copy; the acts
   collapse to the primary. Everything else stays in the DOM for the screen reader. */
.cbd-note[data-cbd-style="badge"] {
    left:22px; max-width:calc(100% - 44px);
    padding:8px 13px; border-radius:999px; align-items:center; gap:9px;
}
.cbd-note[data-cbd-style="badge"] .cbd-kicker,
.cbd-note[data-cbd-style="badge"] .cbd-when,
.cbd-note[data-cbd-style="badge"] .cbd-more { display:none; }
.cbd-note[data-cbd-style="badge"] .cbd-body { display:flex; align-items:center; gap:10px; }
.cbd-note[data-cbd-style="badge"] .cbd-who { font-size:13.5px; margin:0; white-space:nowrap; }
.cbd-note[data-cbd-style="badge"] .cbd-acts { margin:0; }
/* 🛑 `.cl-dsm` IS LISTED BESIDE `form` BECAUSE DISMISS STOPPED BEING ONE (2026-08-04, when it gained
   its confirm and became a <details> wrapping the form). The badge is "the least interrupting thing
   that still tells them" — its acts collapse to the primary — and a rule matching only `form` would
   have let a destructive control reappear on precisely the presentation chosen to be quiet. */
.cbd-note[data-cbd-style="badge"] .cbd-acts form,
.cbd-note[data-cbd-style="badge"] .cbd-acts .cl-dsm,
/* …and the whole destroy row. Badge is the presentation chosen to be QUIET; a control that ends a
   customer's follow-up has no business on it. Listed separately from `.cl-dsm` above because the
   dismiss moved out of `.cbd-acts` into its own row on 2026-08-04 and a descendant selector rooted
   at `.cbd-acts` silently stopped matching it. */
.cbd-note[data-cbd-style="badge"] .cbd-destroy { display:none; }

/* BANNER — hardest to miss. Across the BOTTOM, never the top: the strip and its Hang up own the
   top of every authed page and no presentation of this notice is allowed near them. */
.cbd-note[data-cbd-style="banner"] {
    left:0; right:0; bottom:0; width:auto; max-width:none;
    padding:13px 22px; border-radius:0; border-left:0; border-right:0; border-bottom:0;
    align-items:center; gap:14px;
}
.cbd-note[data-cbd-style="banner"] .cbd-body { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.cbd-note[data-cbd-style="banner"] .cbd-who { margin:0; }
.cbd-note[data-cbd-style="banner"] .cbd-acts { margin:0; margin-left:auto; }

/* 🛑 THE SUPPRESSION RULES. Pinned by CallbackDueNoticeTest — each selector is a separate assertion,
   because a contender quietly dropped from this list is invisible on every screen that is not
   currently in that state. Do not merge them into one selector for tidiness. */
body:has(.ph-strip[data-ph-state="incall"]) .cbd-note,
body:has(.ph-strip[data-ph-state="wrapup"]) .cbd-note,
body:has(.ph-capsule-dim:not([hidden])) .cbd-note,
body:has(.offer-banner:not([hidden])) .cbd-note { display:none; }

.cbd-bell { font-size:19px; line-height:1.15; }
.cbd-body { min-width:0; flex:1; }
.cbd-kicker { font-size:10.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em; }
.cbd-who { font-size:16px; font-weight:800; color:#6d4700; margin-top:2px; overflow-wrap:anywhere; }
.cbd-when { font-size:12.5px; font-weight:600; margin-top:2px; }
/* The honest overflow line: the payload's TRUE count, never the length of the capped array. */
.cbd-more { font-size:11.5px; font-weight:700; margin-top:3px; opacity:.85; }
.cbd-more[hidden] { display:none; }
/* 🛑 HIS LAYOUT, 2026-08-02: "move openit over to the right of the Cleint name row… Put Snooze
   beofre drop doen and Go button to right of it. and then dismiss to right hand side of same row".
   Open belongs on the customer's line because it is the act about THAT PERSON; the row below is
   purely "what do I do with this card", with the snooze sentence left and Dismiss pushed right. */
.cbd-whorow { display:flex; align-items:center; gap:10px; }
.cbd-whorow .cbd-who { flex:1 1 auto; min-width:0; }
.cbd-whorow .btn.small { flex:0 0 auto; }

.cbd-acts { display:flex; gap:7px; align-items:center; flex-wrap:wrap; margin-top:9px; }
.cbd-acts form { margin:0; display:flex; gap:6px; align-items:center; }
/* 🛑 CLOSE to the far right of the row — 2026-08-04, and it TOOK OVER this slot from Dismiss, which
   left the row entirely (see `.cbd-destroy` below). The old rule was `form:last-of-type`, which
   pushed the last FORM right; with the dismiss form gone that would have shoved the snooze sentence
   to the right margin instead. Targeted at Close by name so the row cannot be re-broken by whatever
   the last element happens to be.
   The gap absorbs whatever the picker's widest option needs, rather than a fixed width. */
.cbd-acts [data-cbd-close] { margin-left:auto; }

/* 🛑 THE DESTRUCTIVE ACT, DELIBERATELY FAR FROM THE BENIGN ONE (owner 2026-08-04; NN/g, *Dangerous
   UX: Consequential Options Close to Benign Options*). On repetitive work people act from
   automaticity and mis-fire high-consequence controls placed beside benign ones — measured here as
   nine dismissals inside two minutes of a follow-up coming due, one at ten seconds, seven customers
   left with nobody scheduled to call them.
   NN/g's prescription is distance PLUS redundant signals, so this differs on FOUR axes at once:
   its own row below a rule (position), left where Close is right (alignment), 11.5px where the row
   above is 12–13px (size), and quiet underlined text where everything above it is a button (form).
   ⚠️ Every one of those is load-bearing. Restoring button chrome here, or moving it back up onto
   the acts row, rebuilds the exact adjacency that destroyed the seven. */
.cbd-destroy { margin-top:10px; padding-top:8px; border-top:1px solid #e3c48d; }
.cbd-destroy > .cl-dsm > summary {
    background:transparent; box-shadow:none; padding:0; border-radius:0;
    font-size:11.5px; font-weight:700; color:#8a5a00; opacity:.8; text-decoration:underline;
}
.cbd-destroy > .cl-dsm > summary:hover { opacity:1; }
.cbd-destroy .cl-dsm-menu { background:#fff; border-color:#e3c48d; }
.cbd-snooze-lbl { font-size:12px; font-weight:700; color:#8a5a00; }
/* The snooze duration picker (owner 2026-08-02: "sleep for 1 min or 5 min... or an hour, or day").
   🛑 Its own rule because the bare `select` above is sized for full-width form fields and would
   blow this 368px card apart. Amber to match the card's own palette rather than inheriting the
   blue form chrome, which reads as a foreign control dropped into the notice. */
.cbd-snooze-in {
    width:auto; font-size:12px; font-weight:700; padding:5px 7px; border-radius:7px;
    background:transparent; color:#8a5a00; border:1px solid rgba(138,90,0,.42); cursor:pointer; }
/* It is a BUTTON that happens to be a picker, so it matches the two real buttons beside it rather
   than the form chrome it inherits from. Height parity is the whole job: a select sitting 2px taller
   than its neighbours is what makes a row of three read as untidy. */
.cbd-snooze-in:hover { background:rgba(138,90,0,.10); }
.cbd-snooze-in:focus { outline:none; border-color:#8a5a00; box-shadow:0 0 0 3px rgba(138,90,0,.18); }
.cbd-note .btn.small { font-size:12px; padding:5px 11px; }
.cbd-note .btn.secondary.small {
    background:transparent; color:#8a5a00; border:1px solid rgba(138,90,0,.42); font-weight:700; }
.cbd-note .btn.secondary.small:hover { background:rgba(138,90,0,.10); }

/* ═══ AN ADDRESS IS TWO ROWS, EVERYWHERE, ALWAYS ═══ (owner standing rule, 2026-08-01, verbatim:
   *"Every time you do an address… I don't like it in one row. Address top row; city, state, zip in
   the second row."*)

   🛑 IT IS A RULE ABOUT ADDRESSES, NOT ABOUT A BLOCK, which is why it is declared here once and keyed
   on the address fields themselves rather than added to whichever emitter happened to need it. Before
   this, the subject address and its City shared row one inside the wrapping `.gcs-strip` and State/Zip
   wrapped below — a 2×2 grid, not the two rows he asked for — because a `.gcs-strip-box` is
   `flex:1 1 150px` and simply flows. Giving the ADDRESS box the whole basis pushes its three parts
   onto the next line together, which is exactly his shape and needs no new markup.

   ⚠️ THIS DOES NOT WIDEN THE INPUT. `.gcs-strip-box` caps at `max-width:230px`, and that cap is what
   keeps a street box from becoming a page-wide bar; only the FLEX BASIS is released, so the box keeps
   its size and the row keeps its break. Both address blocks on the Property page are covered:
   `property_address` (the subject) and `primary_address` (where they live), each followed by its own
   city/state/zip. A third address block gets one line here and nothing else.
   🛑 Do NOT reach for this by adding a class in a Blade file — there are two emitters (the strip box
   and the covered `.gcs-fill`) and a class on one of them is the divergence `data-gate-any` already
   taught this codebase once. */
.gcs-strip > [data-field="property_address"],
.gcs-strip > [data-field="primary_address"] { flex:1 1 100%; max-width:none; }
/* …and the INPUT keeps the cap the box used to carry, so releasing the row does not turn a street
   line into a page-wide bar. The width moves from the box to the field, which is where every other
   `ws-w-*` cap already lives. */
.gcs-strip > [data-field="property_address"] .gcs-a,
.gcs-strip > [data-field="primary_address"] .gcs-a { max-width:230px; }
/* …and the three PARTS share one row rather than wrapping onto two. They draw as covered
   `.gcs-fill`s, whose answer slot is a fixed `flex:0 0 260px` — right under a spoken question, wrong
   for a city/state/zip trio, which already carries its own `ws-w-*` cap on the input. Named field by
   field rather than by shape: every OTHER covered box on every page keeps its 260px slot. */
.gcs-strip > .gcs-fill[data-field$="_city"] .gcs-a,
.gcs-strip > .gcs-fill[data-field$="_state"] .gcs-a,
.gcs-strip > .gcs-fill[data-field$="_zip"] .gcs-a { flex:0 1 auto; }
.gcs-strip > .gcs-fill[data-field$="_city"],
.gcs-strip > .gcs-fill[data-field$="_state"],
.gcs-strip > .gcs-fill[data-field$="_zip"] { flex:0 1 auto; }
/* …and the caption stops stretching. `.gcs-fill-label` is `flex:1 1 auto` so that a label under a
   spoken question pushes its answer to the right margin; three of them side by side ate the row and
   put Zip on a line of its own. */
.gcs-strip > .gcs-fill[data-field$="_city"] .gcs-fill-label,
.gcs-strip > .gcs-fill[data-field$="_state"] .gcs-fill-label,
.gcs-strip > .gcs-fill[data-field$="_zip"] .gcs-fill-label { flex:0 0 auto; }

/* ═══ HIS THREE SIZING NOTES ON THE RESIDENCE BLOCK ═══ (owner 2026-08-01, at the screen: *"make
   where they put where they live longer and a little more spacong between where they live row"* and
   *"stat can be a lot smaller than that, only abbreviations go in there"*.)

   🛑 A STATE BOX IS TWO CHARACTERS AND THE CAP IS THE POINT. It was sharing the trio's default
   width with City, so a two-letter answer sat in a box built for "Coral Gables". Capped at the
   field, not at `_state` generally — `property_state` is the SUBJECT's and sits in a different row
   shape; if it wants the same cap it gets its own line here, deliberately.
   ⚠️ Kept comfortably above 2ch: a rep mistypes into a box that looks too small to hold an answer,
   and this screen already carries the `ws-w-*` idiom for exactly this.

   The street line goes the other way — it holds "1420 Maple Street" and had the same 230px cap as
   the SUBJECT address above it. His "longer" is about the answer he actually types most. */
.gcs-strip > [data-field="primary_address"] .gcs-a { max-width:380px; }
.gcs-strip > [data-field="primary_address"] .gcs-input { max-width:380px; }
.gcs-strip > .gcs-fill[data-field="primary_state"] .gcs-a,
.gcs-strip > .gcs-fill[data-field="primary_state"] .gcs-input { max-width:74px; }

/* …and the block gets air above it. The residence block opens on a USAGE answer, so it lands
   directly under the usage buttons with nothing between them — his "a little more spacing". The
   rule is on the block's FIRST row, so nothing else on the page moves. */
.gcs-strip > [data-field="primary_address"] { margin-top:16px; }
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   THE MESSENGER (owner 2026-07-31) — the floor texting each other: a full page at /messages, and
   the same body inside a drawer that opens over whatever screen a rep is working.

   🛑 SHARED VOCABULARY, NOT ONE SCREEN'S. The `.msg-` family is used by THREE surfaces — the page,
   the drawer, and the read-only oversight screen — so it belongs here rather than in any one view's
   <style> block (design-system rule 8).

   🛑 ELEVATION COMES FROM THE SCALE (rule 9). The dock takes `--z-drawer`, which sits BELOW
   `--z-callbar` — the phone strip holds Hang up and is the ceiling. That is the STRUCTURAL half of
   "chat never covers the call controls". The other half cannot live in CSS at all: the dispo capsule
   (`--z-modal`) and the pop/offer (`--z-popcard`) rank BELOW this drawer, so the drawer stands itself
   down in JS when one appears (partials/messenger-drawer). Raising the drawer's rank to "fix" an
   overlap would re-create the trap the owner has hit twice.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

.msg-card { padding:0; overflow:hidden; }
.msg-split { display:flex; min-height:520px; }
.msg-people { width:300px; flex-shrink:0; border-right:1px solid var(--line); overflow-y:auto; }
.msg-people-head { padding:12px 14px; border-bottom:1px solid var(--line); }
/* 🛑 THE LIST IS SHAPED ON THE OWNER'S OWN MESSAGES APP (2026-08-06: *"this is how text looks on
   our computer.. lets ake it look like that"*, with a screenshot). What was copied is the GEOMETRY —
   a selected row filled solid rather than tinted, an unread DOT in a left gutter, inset separators,
   pill bubbles. What was NOT copied is Apple's blue: the fill and the sent bubble stay `--brand`
   (#35529b, Clear Choice's own, straight off mycclg.com). Swapping in #0a7cff would have made one
   screen out of thirty belong to a different company. */
.msg-person { position:relative; display:flex; align-items:center; gap:10px; padding:11px 14px;
    text-decoration:none; color:var(--ink); }
/* The separator starts where the TEXT starts, not at the card edge — the Mac's inset rule. Drawn as
   a pseudo-element so it can be inset; a `border-bottom` cannot, and the last row keeps its line
   off the card's own bottom edge. */
.msg-person::after { content:""; position:absolute; left:56px; right:0; bottom:0; height:1px;
    background:var(--line); }
.msg-person:last-child::after { display:none; }
.msg-person:hover { background:#f7f9fd; }
/* SELECTED IS FILLED, and every scrap of text inside it has to come with it — a solid brand fill
   with default-ink names on top is the one way this change can ship unreadable. */
.msg-person.on, .msg-person.on:hover { background:var(--brand); }
.msg-person.on .msg-pmain b, .msg-person.on .msg-prev, .msg-person.on .msg-time { color:#fff; }
.msg-person.on .msg-prev, .msg-person.on .msg-time { color:rgba(255,255,255,.82); }
.msg-person.on::after { background:transparent; }
.msg-pmain { flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; }
.msg-pmain b { font-size:14px; }
/* Unread is PART OF THE FEATURE, not polish — "a message nobody is told about didn't arrive". */
.msg-person.unread .msg-pmain b, .msg-person.unread .msg-prev { font-weight:700; color:var(--ink); }
.msg-prev { font-size:12.5px; color:var(--mut); font-weight:400; white-space:nowrap; overflow:hidden;
    text-overflow:ellipsis; }
.msg-pside { display:flex; flex-direction:column; align-items:flex-end; gap:4px; }
.msg-time { font-size:11px; color:var(--mut); white-space:nowrap; }
/* 🛑 THE GUTTER IS ALWAYS RENDERED, THE DOT ONLY WHEN UNREAD. Sizing the gutter only on unread rows
   would slide every name sideways the instant a message landed — a list that twitches under a rep
   mid-click. So the span always occupies its 8px; the colour is what changes. */
/* The unread count, for a screen reader only — the dot is colour, and colour says nothing out loud. */
.msg-sr { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
.msg-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; background:transparent; }
.msg-person.unread .msg-dot { background:var(--brand); }
/* 🛑 `.on.unread`, NOT `.on`. Opening a conversation marks it read, so a selected row almost never
   HAS unread — and `.msg-person.on .msg-dot { background:#fff }` painted a white dot on it anyway,
   announcing news that was already read. Caught by looking at the render, not by a test: every
   assertion about the dot was about the unread case, which was correct throughout. */
.msg-person.on.unread .msg-dot { background:#fff; }
.msg-left { font-size:11px; color:var(--mut); }
.msg-gavatar { display:inline-flex; flex-shrink:0; }
.msg-gavatar .avatar + .avatar { margin-left:-8px; box-shadow:0 0 0 2px #fff; }

/* 🛑 A COLLEAGUE YOU HAVE NEVER MESSAGED IS A BUTTON, NOT A LINK — clicking mints a thread, which is
   a write, so the row is a real POST to `messages.start`. These rules only make that button sit in
   the list as though it were one of the anchors above it; nothing about the row's LOOK may depend on
   which element it is, or the seam between "people you talk to" and "everyone else" becomes visible
   and the single list he asked for reads as two. */
.msg-person-form { margin:0; display:block; }
/* 🛑 NO `border-bottom` HERE ANY MORE — the separator is `.msg-person::after`, shared with the
   anchors, so the button cannot grow a second line of its own at a different inset. And `background`
   must stay unset rather than `none`: `.msg-person.on` fills the row, and a `background:none` on the
   element itself outranks nothing but is exactly the sort of reset that quietly wins later. */
button.msg-person { width:100%; background:transparent; border:0; font:inherit; text-align:left;
    cursor:pointer; }
button.msg-person.on { background:var(--brand); }
.msg-person-new { color:var(--brand); font-weight:700; }
.msg-newmark { display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px;
    border-radius:50%; background:#f0f4fb; color:var(--brand); font-size:15px; flex-shrink:0; }

.msg-thread { flex:1; display:flex; flex-direction:column; min-width:0; }
.msg-thread-head { display:flex; align-items:center; gap:10px; padding:12px 16px;
    border-bottom:1px solid var(--line); }
.msg-thread-head b { font-size:15px; display:block; }
/* The admin-only Delete, pinned right of the conversation's name. Quiet by construction: it is the
   one destructive control on this screen and it should never compete with the name it sits beside
   (Gil 2026-09-02, "Let only me have ability to delete chat"). It turns red only under the pointer,
   which is where a person is when they mean it. */
.msg-thread-del { margin-left:auto; }
.msg-thread-delbtn { background:none; border:0; color:var(--mut); cursor:pointer;
    font-size:12.5px; padding:3px 7px; border-radius:6px; }
.msg-thread-delbtn:hover { background:#fdecec; color:#a33; }
.msg-sub { font-size:12px; color:var(--mut); }
/* White, not off-white: the Mac's transcript is the page, and the grey received bubble is what
   carries the contrast. On #fafbfe the two greys sat almost on top of each other. */
.msg-scroll { flex:1; padding:16px 18px; overflow-y:auto; background:#fff; }
/* The day marker is bare centred text — NO rule across the pane. The line was the thing that made
   this read as a mail thread rather than a Messages transcript. */
.msg-day { text-align:center; margin:14px 0 10px; }
.msg-day span { font-size:11.5px; color:var(--mut); font-weight:600; }

.msg-row { display:flex; align-items:flex-end; gap:8px; margin:3px 0; }
.msg-row.mine { justify-content:flex-end; }
/* 22px is a PILL at this line-height, which is the single biggest cue that this is a texting app —
   14px read as a card. The tail is the one squared corner on the speaker's side. */
.msg-b { max-width:66%; padding:8px 14px 7px; border-radius:22px; font-size:14.5px; line-height:1.4; }
.msg-b.theirs { background:#e9e9eb; color:var(--ink); border-bottom-left-radius:6px; }
.msg-b.mine { background:linear-gradient(135deg,var(--brand),var(--brand-2)); color:#fff;
    border-bottom-right-radius:6px; }
.msg-who { display:block; font-size:11.5px; font-weight:700; color:var(--brand); margin-bottom:2px; }
.msg-meta { display:block; font-size:10.5px; color:var(--mut); margin-top:3px; text-align:right; }
.msg-b.mine .msg-meta { color:rgba(255,255,255,.75); }
/* Filed = it now lives on the customer's record and OUTLIVES the retention window. Success green,
   because it is a thing that worked — never a severity. */
.msg-filed { display:block; font-size:11.5px; font-weight:600; color:#137a45; margin-top:5px; }
/* 🛑 ON YOUR OWN BUBBLE THE DARK GREEN IS INVISIBLE. `.msg-b.mine` is a dark blue gradient, and
   #137a45 on it fails contrast outright — so "✓ Filed to <customer>" was unreadable on EVERY message
   a rep sends and files, which is the half of filing that tells them it worked. The lighter green
   keeps the meaning (green = filed, the same as on a received bubble) and can actually be read. */
.msg-b.mine .msg-filed { color:#9fe8c0; }
/* 🛑 `.msg-file` IS GONE ON PURPOSE — the rule went with the markup (owner 2026-08-06, *"delete the
   dead link"*). It styled a link that pointed at a GET the controller never read: 0 of 13 messages
   on prod had ever been filed through it. Leaving an orphan rule here is how a dead control gets
   quietly re-added later by someone who finds a class waiting for it. The FILED indicator above
   stays — that one is the record, and it works. */

/* The composer is a rounded field on a plain bar, the Mac's shape. */
.msg-compose { display:flex; gap:10px; padding:12px 14px; align-items:center;
    border-top:1px solid var(--line); }
.msg-compose input { flex:1; border-radius:999px; padding-left:15px; padding-right:15px; }
.msg-dock-foot { border-top:0; }
/* 🛑 The disclosure sits ON the composer, not only in a page footer: a rep is entitled to know
   before they type. The WORDS are the owner's to cut; the telling is not optional. */
.msg-disclose { margin:0; padding:0 14px 10px; font-size:11px; color:var(--mut); }
.msg-foot { font-size:12.5px; margin-top:10px; }
.msg-readonly { padding:10px 16px; border-top:1px solid var(--line); background:#fde6c7;
    color:#8a5a00; font-size:12.5px; font-weight:600; }

/* The people picker — one person = a direct chat, several = a group.
   Gil 2026-09-02, on the live panel: "this design has to change because we get more employees.
   This list needs to be shorter and connected and not so much spacing. You don't have to scroll
   down as much. So it has to be able to accommodate a lot of reps to be able to choose from."
   So: no avatar and no role pill inside the picker (both are decoration when the job is ticking
   names, and both are what made each row wide), tighter chips, and a fixed ceiling with its own
   scroll so the picker can never push the composer off the bottom of a 660px panel. At this size a
   floor of thirty fits in four rows instead of fifteen. */
.msg-picker { display:flex; flex-wrap:wrap; gap:5px; margin-bottom:10px;
    max-height:150px; overflow-y:auto; padding:2px; }
.msg-pick { display:inline-flex; align-items:center; gap:6px; border:1px solid var(--line);
    border-radius:999px; padding:3px 10px 3px 8px; font-size:12.5px; line-height:1.5; cursor:pointer; }
.msg-pick:has(input:checked) { border-color:var(--brand); background:#f0f4fb; font-weight:600; }
.msg-pick input { margin:0; }
/* 🛑 `hidden` DOES NOTHING ONCE A RULE SETS `display` — the filter above hides rows with the hidden
   property, and `display:inline-flex` two lines up would win. This project has shipped that exact
   defect before; the attribute selector is what makes the filter actually filter. */
.msg-pick[hidden] { display:none; }
/* Type to narrow the names — the real answer to a floor that keeps growing. It hides itself when
   there is nobody to filter, so a three-person floor never sees a search box for three names. */
.msg-picker-find { width:100%; margin-bottom:7px; font-size:12.5px; padding:5px 9px; }
.msg-start-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.msg-start-row input { flex:1 1 140px; max-width:320px; min-width:0; }
/* 🛑 IN THE POP-UP THE PICKER LIVES IN A ~240px COLUMN, and a one-row name-plus-button simply does
   not fit there: Gil photographed the field squeezed to the three letters "Gro" with the button
   half off the edge ("Bottom left button cut off", 2026-09-03). Two full-width rows instead — the
   name, then the press — which is also the order a person does them in. `min-width:0` is what lets
   a flex child actually shrink; without it the input keeps its default intrinsic width and pushes
   the button out of the box no matter what the container says. */
.msg-pop .msg-start-row input,
.msg-pop .msg-start-row .btn { flex:1 1 100%; max-width:none; }
.msg-pop .msg-group-new { padding:10px 12px; }
.msg-pop .msg-picker { max-height:120px; }

/* 🧹 FEWER CONTROLS ON THE CHAT TAB (Gil 2026-09-02: "there's a lot of buttons everywhere on this
   thing... There's too much going on"). Every one of these is about work owed to CUSTOMERS — the
   team's unhandled calls, book health, marking call badges seen, the floor's shared answerphone —
   and none of it means anything on a conversation between two colleagues. Hidden here, not deleted:
   each is one chip away on the tabs it belongs to. */
[data-ph-mbx-on="chat"] .ph-mbx-team-go,
[data-ph-mbx-on="chat"] .ph-mbx-oversight,
[data-ph-mbx-on="chat"] .ph-mbx-foot { display:none; }

/* ---- THE LAUNCHER + THE DOCK — the drawer half (owner: "pulled up and seen easier than the left
   panel"). The launcher is a real <a> to /messages so JS-off still reaches the page. -------------- */
.msg-launch { position:fixed; right:22px; bottom:22px; width:52px; height:52px; border-radius:50%;
    color:#fff; background:linear-gradient(135deg,var(--brand),var(--brand-2));
    box-shadow:0 12px 28px -10px rgba(42,65,121,.65); display:flex; align-items:center;
    justify-content:center; z-index:var(--z-drawer); }
.msg-launch:hover { filter:brightness(1.06); }
.msg-launch-n { position:absolute; top:-4px; right:-4px; min-width:20px; height:20px; border-radius:999px;
    background:#fde6c7; color:#8a5a00; font-size:11.5px; font-weight:800; display:flex;
    align-items:center; justify-content:center; padding:0 5px; box-shadow:0 0 0 2px #fff; }
.msg-launch-n[hidden] { display:none; }

/* ── THE POP-UP PANEL (owner 2026-08-06) ────────────────────────────────────────────────────────
   *"why does it open full screen like that, i want it to be pop up in teh same area where you click
   teh messneger and make it a lot smaller, fonts and box size"* — plus the bug behind it: on his
   screen the full page pushed the COMPOSER BELOW THE FOLD, so the one control the screen exists for
   needed a scroll to reach.

   🛑 THE DRAWER WAS NEVER WHAT HE REJECTED, AND THE RECORD SAID OTHERWISE FOR THREE DAYS. His
   correction, 2026-08-06: *"i said it looked terrible what was inside of the drawer. the drawer is
   a good idea though."* On 08-03 *"I open messaging/chat and it opens like this. that looks
   terrible"* was read as a verdict on the CONTAINER, and the container was deleted. It was a verdict
   on the CONTENTS — a 400px panel too narrow to stand the names and the conversation side by side,
   so it stacked them and capped the list at 190px, the dead grey half he photographed.

   ⇒ **The width was the defect; the panel was fine.** At 660px both columns stand, so the layout he
   approved on the full page survives at panel size. Do not narrow this below ~600px — shrink the
   FONTS instead, which is what he actually asked for. And do not "restore" the full-screen-only
   shape citing 08-03: that ruling never existed.

   🛑 THE COMPOSER IS PINNED BY CONSTRUCTION, NOT BY A HEIGHT GUESS. The panel is a flex column with
   ONE scrolling child (`.msg-scroll`); everything else — head, list, composer, disclosure — is
   fixed. A panel that sized to its content is exactly how the full page put the composer off-screen.

   🛑 HEIGHT IS CLAMPED TO THE VIEWPORT, not a fixed 560px. On a 13" laptop a fixed height runs off
   the bottom and re-creates the very bug this replaces. */
.msg-pop { position:fixed; right:22px; bottom:86px; z-index:var(--z-drawer);
    width:min(660px, calc(100vw - 44px)); height:min(520px, calc(100vh - 150px));
    background:#fff; border:1px solid var(--line); border-radius:14px;
    box-shadow:0 24px 60px -18px rgba(17,12,29,.45); display:flex; flex-direction:column;
    overflow:hidden; }
.msg-pop[hidden] { display:none; }
.msg-pop-head { display:flex; align-items:center; gap:8px; padding:8px 10px 8px 14px;
    border-bottom:1px solid var(--line); flex-shrink:0; }
.msg-pop-head b { font-size:13.5px; flex:1; }
.msg-pop-btn { background:none; border:0; color:var(--mut); cursor:pointer; font-size:15px;
    line-height:1; padding:5px 7px; border-radius:7px; text-decoration:none; }
.msg-pop-btn:hover { background:#f0f4fb; color:var(--brand); }
.msg-pop-body { flex:1; min-height:0; display:flex; }
.msg-pop-body > .msg-split { flex:1; min-width:0; min-height:0; }

/* ── The smaller scale he asked for. SCOPED TO THE PANEL — the full page keeps its own sizing, and
      these are the same classes, so a bare override here would shrink both. ─────────────────────── */
.msg-pop .msg-split { min-height:0; }
/* 240, not 200: at 200 the avatar, the unread gutter and the timestamp left ~100px for the name and
   "Dee Kovandzic" clipped to "Dee Kovan…". A list of half-names is not a list of people. The
   conversation still gets 420px, which is wider than the whole drawer that failed. */
.msg-pop .msg-people { width:240px; }
/* At panel width a two-word name wrapped to a second line and made its row twice as tall as its
   neighbours — a ragged list. Clipped like `.msg-prev` already is, rather than given more width:
   widening the list is what would eventually squeeze the conversation back into a stack. */
.msg-pop .msg-pmain { min-width:0; }
.msg-pop .msg-pmain b { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; display:block; }
.msg-pop .msg-person { padding:7px 10px; gap:7px; }
.msg-pop .msg-person::after { left:40px; }
.msg-pop .msg-person .avatar { width:24px; height:24px; font-size:9px; }
.msg-pop .msg-pmain b { font-size:12px; }
.msg-pop .msg-prev { font-size:10.5px; }
.msg-pop .msg-time { font-size:9.5px; }
.msg-pop .msg-dot { width:6px; height:6px; }
.msg-pop .msg-newmark { width:24px; height:24px; font-size:13px; }
.msg-pop .msg-thread-head { padding:8px 12px; }
.msg-pop .msg-thread-head b { font-size:12.5px; }
.msg-pop .msg-scroll { padding:10px 12px; }
.msg-pop .msg-b { font-size:12.5px; padding:6px 11px 5px; border-radius:16px; max-width:78%; }
.msg-pop .msg-meta { font-size:9px; }
.msg-pop .msg-day span { font-size:10px; }
.msg-pop .msg-compose { padding:8px 10px; gap:7px; }
.msg-pop .msg-compose input { font-size:12.5px; padding-top:6px; padding-bottom:6px; }
.msg-pop .msg-compose .btn { font-size:12px; padding:6px 13px; }
.msg-pop .msg-disclose { font-size:9.5px; padding:0 10px 7px; }

/* Under ~820px the shared `.msg-split` media query stacks the columns — at that size the panel
   should be the screen rather than a floating box fighting it for room. */
@media (max-width:820px) {
    .msg-pop { right:10px; left:10px; bottom:76px; width:auto; height:min(70vh, calc(100vh - 130px)); }
    .msg-pop .msg-people { width:auto; max-height:150px; }
}

/* 🛑 THE SLIDE-OUT DOCK IS GONE (owner 2026-08-03), and its rules went with it rather than being
   left to rot. It was `width:min(400px,100%)`, which could not hold the name list and a
   conversation side by side — so `.msg-split-drawer` stacked them and capped the list at 190px,
   which is precisely the dead grey half he photographed and rejected. The launcher below now goes
   to `/messages`, where the two columns fit at full width. Do not reintroduce a narrow copy of this
   screen: one place a conversation can be is the point, not an accident of the layout. */

/* The poll swaps the whole body in, so the fragment's wrapper is what has to carry the height —
   without this the card sized to its CONTENT and left dead white space under the composer. */
.msg-card > [data-msg-body] { display:flex; }
.msg-card > [data-msg-body] > .msg-split { flex:1; min-width:0; }

/* Groups are the one thing a list of names cannot express, so the multi-select survives — FOLDED,
   and opened from the "＋ New group chat" row at the bottom of the list. Open by default it is the
   picker he rejected; closed it costs the default screen nothing. */
.msg-group-new > summary { cursor:pointer; }
.msg-group-new[open] > summary { margin-bottom:4px; }

@media (max-width:820px) {
    .msg-split { flex-direction:column; }
    .msg-people { width:auto; max-height:220px; border-right:0; border-bottom:1px solid var(--line); }
}
/* The refusal when the phones are busy. Bottom-right with the launcher, deliberately FAR from the
   phone strip: a notice explaining that chat will not cover the call controls must not cover them
   either. Amber = a nudge, never the reserved alarm (rule 13 — this is a notice, not a verdict). */
.msg-busy { position:fixed; right:22px; bottom:84px; z-index:var(--z-drawer); margin:0;
    max-width:250px; background:#fde6c7; color:#8a5a00; font-size:12.5px; font-weight:600;
    border-radius:12px; padding:9px 13px; box-shadow:0 12px 26px -14px rgba(26,32,60,.5); }
.msg-busy[hidden] { display:none; }

/* ── 🧮 THE CALCULATOR PAD ─────────────────────────────────────────────────────────────────────
   Owner 2026-08-03: *"we need to have a calculator easily accessible by rep. this calculator is
   for regular calculator use but also as a mortgage calculator"*.

   🛑 WHAT WAS BROKEN WAS THE REACH, NOT THE SCREEN. /calculator already worked and was measured
   at 43 opens in 14 days — 39 of them the owner's own admin login — against 3,141 call opens.
   An unlicensed LOA had no sidebar door at all, and the guided call screen (2,174 of those opens)
   linked to it nowhere. So the pad rides EVERY authed page instead of living behind a rail row.

   🛑 IT RIDES .drawer / .drawer-dim LIKE THE STICKY NOTE, and that is the whole safety argument:
   --z-drawer (140) sits under --z-callbar (160), so the pad can never cover Hang up, Hold, Mute or
   the dispositions on a live call. Never hand-pick an integer here — CallControlsUnburiableTest is
   what holds that, and it can only read the tokens.

   Narrower than the note pad (400px against 620px): a keypad is not a document. */
.calc-drawer { width:min(400px,100%); }
.calc-sub { margin:0 0 14px; font-size:13px; }

.calc-screen { background:var(--brand-ink); border-radius:14px; padding:14px 16px; margin:0 0 12px;
    text-align:right; }
.calc-expr { color:#a9a4c4; font-size:13px; min-height:18px; font-variant-numeric:tabular-nums;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.calc-now { color:#fff; font-size:32px; font-weight:800; letter-spacing:-.02em; line-height:1.15;
    font-variant-numeric:tabular-nums; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.calc-pad { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; }
.calc-k { font:inherit; font-size:19px; font-weight:700; color:var(--ink); background:#fff;
    border:1px solid var(--line); border-radius:12px; padding:14px 0; cursor:pointer;
    -webkit-tap-highlight-color:transparent; }
.calc-k:hover { background:#f1f4fa; }
.calc-k.fn { background:#f1f4fa; color:var(--mut); font-size:16px; }
.calc-k.op { background:#eef1f9; color:var(--brand); }
.calc-k.eq { background:linear-gradient(135deg,var(--brand),var(--brand-2)); color:#fff; border-color:transparent; }
.calc-k.wide { grid-column:span 2; }

/* The mortgage half. Deliberately THREE fields — the debt grid, the talk track and the required
   disclosure stay on /calculator, which is a table-stage workspace rather than a pad. */
.calc-fields label { display:block; margin:0 0 10px; }
.calc-pair { display:flex; gap:10px; }
.calc-pair label { flex:1; }
.calc-out { background:#f6f7fb; border:1px solid var(--line); border-radius:12px;
    padding:12px 14px; margin:12px 0 0; }
.calc-out-lbl { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
    color:var(--mut); }
.calc-out-num { font-size:28px; font-weight:800; color:var(--brand-ink); letter-spacing:-.02em; }
.calc-note { margin-top:12px; font-size:12.5px; }
.calc-more { display:block; margin-top:14px; font-size:13px; font-weight:700; }
/* 🛑 WRITTEN AS `.flash.calc-locked`, and the doubled class is load-bearing. `.flash.warn` above is
   two classes and sets a 1px border on all four sides; a single-class rule here loses to it on
   specificity no matter how far down the file it sits, and the marker bar silently does not render.
   Caught by looking at the screen — the suite cannot see it. */
.flash.calc-locked { font-weight:700; border-left:5px solid #8a5a00; }
.calc-locked span { font-weight:500; }

/* The launcher. LEFT of the messenger's circle (right:22px) so neither covers the other, and on
   the same --z-drawer rank — under the call bar, like everything else a rep can click. */
.calc-launch { position:fixed; right:86px; bottom:22px; width:52px; height:52px; border-radius:50%;
    color:var(--brand); background:#fff; border:1px solid var(--line);
    box-shadow:0 12px 28px -10px rgba(26,32,60,.45); display:flex; align-items:center;
    justify-content:center; z-index:var(--z-drawer); cursor:pointer; padding:0; }
.calc-launch:hover { background:#f1f4fa; }
/* ⚠️ `display:flex` above beats the UA's `[hidden]{display:none}` — without this line the launcher
   cannot be hidden by attribute at all, and the instant toggle hides it exactly that way. */
.calc-launch[hidden] { display:none; }
@media (max-width:820px) { .calc-drawer { width:100%; } }

/* ── 🛑 THE SUBJECT-PROPERTY STATE NOTICE ──────────────────────────────────────────────────────
   Owner 2026-08-04, three rulings in one day, and the third reverses the second FOR THIS NOTICE.

   1. It shipped IN FLOW at the top of `.main`. He rejected that outright:
      *"this just pushes the main section down, and i never want that. Make this a rule. I rather
      it be a pop up that comes up, that they dismiss. Cant miss it and doesnt push the screen down
      like this, making us have to scroll down to get what we want."*
   2. His standing instruction was the corner: *"on all pop ups of notification make it show on top
      left corner of screen, and make it so they can be grayed out to navigate."*
   3. Then, for THIS notice specifically: *"I know i told you to put all popups in corner but
      putting the state warning in the corner in this case may not be the right thing. because we
      actually want them to see this and not call it until they read and dismiss this. So urgent,
      dont continue messages should be in middle somewhere."*

   ⇒ TWO CLASSES, and the class is a property of the MESSAGE, not of the screen:
      · ordinary ("you may want to know") → corner, displaces nothing, blocks nothing;
      · urgent ("don't continue until you've read this") → CENTRE, read and dismissed first.

   🛑 THE ONE THING BOTH CLASSES MUST DO IS LEAVE THE FLOW. That is the rule he asked for and it is
   why neither of these is a block-level element in `.main`: `position:fixed` means appearing and
   dismissing reflow exactly nothing, so the sheet behind never moves and he never scrolls past a
   notice to reach his work.

   ⚠️ THIS REVERSES THE IN-FLOW PATTERN that the deleted `.hot-alert` was built on (*"so it doesnt
   block anything"*, 2026-08-03). Both of his constraints now stand TOGETHER — must not cover a
   control, must not push the page — and leaving the flow while staying under the --z-callbar
   ceiling is the only shape that satisfies both. The old alert is gone from the tree entirely (the
   dibs card took its corner), so there is no longer a comment to annotate at its old home; this
   block is the record.

   🛑 NO RAW z-index ANYWHERE HERE. The dim is --z-modal (120) which is BELOW --z-callbar (160), so
   the phone strip and the hang-up capsule stay above it and reachable even if a call starts while
   the modal is open. That is not a nicety: burying Hang up is a bug this codebase has shipped three
   times, and both earlier fixes were hand-picked integers. The corner degrade is --z-toast (150),
   also below the ceiling. */
.psn-dim {
    position:fixed; inset:0; z-index:var(--z-modal);
    display:flex; align-items:center; justify-content:center; padding:24px;
    background:rgba(20,24,45,.44);
}
.psn-dim[hidden] { display:none; }
.psn-modal {
    width:min(540px,100%); max-height:calc(100vh - 48px); overflow-y:auto;
    background:var(--card); border:1px solid var(--line); border-radius:16px;
    box-shadow:0 30px 70px -24px rgba(20,24,45,.62);
    padding:20px 22px 18px;
    display:flex; flex-direction:column; align-items:flex-start; gap:10px;
    /* 🛑 IT MOVES, AND THAT IS AN OWNER RULING (2026-08-04): *"i just think blinking or moving on
       any urgent message makes it so it cant be missed."* Put to him that motion was reserved on
       this platform for the hidden-phone alarm; he reaffirmed, and his framing is the better rule:
       MOTION MARKS URGENCY AS A CLASS. The alarm moves because it is urgent, not because it is
       privileged — which is something a rep can actually learn: if it moves, it matters now.
       ⇒ urgent/centre MOVES · ordinary/corner does NOT (see `.psn-corner`, which is silent by
       design and says so).

       Same idiom as `.ph-lost-banner` deliberately — a steady pulse plus a periodic nudge — so the
       two urgent things on this platform move the same way rather than inventing a second dialect.
       🛑 AMBER, NOT THE ALARM RED: rule 13 reserves the alarm colour for a VERDICT and bars it from
       live-call surfaces. This is a notice, so it pulses in its own `.tag.warn` family.
       🛑 NOTHING HERE REFLOWS. Only `box-shadow` and `transform` animate — both paint outside
       layout, so no row below ever moves. Animating width/padding/margin would make a blinking box
       shove the whole sheet on every cycle, which is the very complaint that caused this rebuild.
       🛑 AND IT NEVER STOPS ON ITS OWN: `infinite`, no timer, no fade-out — *"it doesnt go away
       until they click it."* */
    animation: psnPulse 1.15s ease-in-out infinite, psnNudge 2.6s ease-in-out infinite;
}
@keyframes psnPulse {
    0%, 100% { box-shadow:0 30px 70px -24px rgba(20,24,45,.62), 0 0 0 0 rgba(217,154,43,0); }
    50%      { box-shadow:0 30px 70px -24px rgba(20,24,45,.62), 0 0 0 7px rgba(217,154,43,.45); }
}
@keyframes psnNudge {
    0%, 80%, 100% { transform:none; }
    84% { transform:translateX(-5px); }
    88% { transform:translateX(5px); }
    92% { transform:translateX(-4px); }
    96% { transform:translateX(3px); }
}
/* 🛑 REDUCED MOTION DROPS THE MOTION, NEVER THE NOTICE. A rep who has asked their machine to stop
   animating still has to be told we are not licensed where this property is — hiding it would turn
   an accessibility preference into a compliance gap. Matches `.ph-lost-banner`'s own handling. */
@media (prefers-reduced-motion: reduce) { .psn-modal { animation:none; } }
/* "Before you call" — it names WHEN this matters, which is the whole reason it interrupts. */
.psn-head { font-size:16.5px; font-weight:800; color:var(--brand-ink); letter-spacing:-.01em; }
.psn-say { font-size:13.5px; line-height:1.55; color:var(--ink); }
.psn-act { margin:4px 0 0; }
/* 🛑 BRAND, NEVER --danger. Rule 13 reserves the alarm for a VERDICT and bars it from live-call
   surfaces; this is a notice, and its button is an acknowledgement rather than a warning of its
   own. Full-height hit area — the one control that clears this must be impossible to miss or to
   mis-click past. */
.psn-go { font-weight:700; }
/* ── The live-call degrade: corner, blocks nothing, covers nothing.
   🛑 BOTTOM-left, NOT top-left, and that is a collision fix rather than taste: `.offer-banner` (the
   dibs card, top:76px left:250px) already owns the top-left corner and carries real controls —
   Take and Pass. Two fixed cards at one anchor is one covering the other's buttons. Bottom-left
   also keeps it far from the phone strip, the same reasoning `.msg-busy` records. */
.psn-corner {
    position:fixed; left:250px; bottom:18px; z-index:var(--z-toast);
    width:320px; max-width:calc(100vw - 32px);
    display:flex; flex-direction:column; align-items:flex-start; gap:8px;
    background:var(--card); border:1px solid var(--line); border-left:5px solid #e8a33a;
    border-radius:12px; padding:12px 14px;
    box-shadow:0 18px 40px -18px rgba(20,24,45,.5);
}
.psn-corner[hidden] { display:none; }
/* 🛑 THE CORNER DOES NOT MOVE, AND ITS STILLNESS IS THE RULE WORKING, NOT A GAP. Motion marks the
   URGENT class; this is the ordinary treatment — and it is what the urgent notice becomes while a
   call is LIVE, where a blinking box beside a rep on the phone is the one version that could cost a
   real customer. Do not "finish the job" by animating it. */
.psn-corner .psn-say { font-size:12.5px; }
@media (min-width:821px) { html.sidebar-collapsed .psn-corner { left:82px; } }
@media (max-width:820px) {
    /* The sidebar is a flat TOP ROW here, so the main column's left edge is the screen's. */
    .psn-corner { left:16px; }
    /* 🛑 THE MODAL DROPS TO THE LOWER HALF ON THIS LAYOUT, AND IT IS A MEASURED FIX, NOT A TASTE.
       Below 820px the sidebar becomes a top ROW, which pushes the sticky phone strip ~364px down
       the page; the strip correctly outranks the dim (--z-callbar, and it must), so a
       viewport-centred modal gets SLICED IN HALF BY IT — measured at 590px, strip at y=364-422 over
       a modal at y=323-537, hiding the notice's own first sentence. Raising the modal is forbidden
       and would be the exact bug the ceiling exists to stop, so the modal moves instead.
       Bottom-anchored is safe at EVERY scroll position on this layout: the strip is either pinned
       at the top or sitting below the top row, and both are the upper half. Pure CSS on purpose —
       a JS-measured offset would leave the no-script case showing a half-hidden warning. */
    .psn-dim { align-items:flex-end; padding-bottom:18px; }
}
@media (max-width:620px) {
    .psn-dim { padding:14px; }
    .psn-modal { padding:16px 16px 14px; gap:9px; }
    .psn-head { font-size:15.5px; }
    .psn-corner { left:12px; right:12px; width:auto; }
}
/* ── 👤 WHOSE CUSTOMER THIS IS (owner 2026-08-04) ────────────────────────────────────────────────
   *"When Jorge received the lead it needs to be apparent that it a lead of Dee's so jorge cant
   miss."* A rep had NO ownership signal on the lead card — the "Assigned to" block is manager-only.

   🛑 IN FLOW, NOT FIXED, AND NO z-index. A rep meets this ON A LIVE CALL, and nothing here may
   cover the phone strip's Hang up or the disposition capsule — the scar this board has paid for twice.
   It sits above the name and pushes the page down, so it can hide nothing.
   🛑 AMBER, NEVER THE RESERVED ALARM COLOUR: RULE 13 — the alarm marks a VERDICT, and this is a
   fact about custody. ⚠️ The token is deliberately NOT spelled here: `OfferBannerTest` greps
   this file for it, and a comment quoting the thing it forbids trips the guard. Same trap as
   the offer-service name in a controller comment earlier today — prose is not inert when a
   test reads source. */
/* 🛑 THE SLOT IS RESERVED ON EVERY RENDER, EMPTY OR NOT — owner 2026-08-04: *"this just pushes the
   main section down, and i never want that. Make this a rule."* The flag paints INTO space that is
   always there, so the geometry is identical whether a rep is covering somebody else's customer or
   not and nothing below ever shifts. Same technique as the live-call colour reservation
   (*"nothing moves when a call connects"*).
   🛑 GEOMETRY IS RESERVED; CONTENT IS NOT. The sentence renders only when it applies — a hidden
   element carrying a colleague's name would break the reviewed "no roster oracle" rule.
   ⚠️ Pinned to ONE line at this font size. If the sentence ever grows to two, the slot grows with it
   or the flag overflows its own reservation. */
.lead-owner-slot { min-height:38px; margin:0 0 10px; }

.lead-owner-flag {
    display:flex; align-items:center; gap:8px; margin:0 0 10px; padding:9px 13px;
    border-radius:10px; background:#fff6e6; border:1px solid #f0dcb0; border-left:4px solid #d99a2b;
    font-size:13.5px; font-weight:600; color:#7a5410;
    margin:0;                     /* the SLOT owns the spacing now */
}
.lead-owner-flag b { color:var(--brand-ink); }
@media (max-width:620px) { .lead-owner-flag { padding:8px 11px; font-size:12.5px; } }

/* ── 💬 THE MESSAGE BOX, DOCKED ON THE LEAD CARD (owner 2026-08-04) ──────────────────────────────
   *"the screen needs to let Jorge look at lead card but have the message box still be accessible to
   send message… Something that is easy to navigate."*

   🛑 IN FLOW, LIKE ITS NEIGHBOUR ABOVE. Not fixed, not a modal, no z-index: a rep meets this on a
   LIVE call and nothing on this page may cover Hang up or the disposition capsule. */
/* 🛑 COLLAPSED BY DEFAULT so it does not displace the record — his *"never push"* rule answered in
   substance (~34px closed against ~200px open) WITHOUT floating a typing surface over a live call.
   ⚠️ Still in flow, still no z-index: opening it must never be able to cover Hang up. */
.lead-msg-dock { border-left:4px solid #3c3a8f; margin:0 0 12px; padding:10px 14px; }
.lead-msg-open { cursor:pointer; font-weight:800; font-size:13.5px; color:var(--brand-ink);
    list-style:none; }
.lead-msg-open::-webkit-details-marker { display:none; }
.lead-msg-dock[open] .lead-msg-open { margin-bottom:2px; }
/* ⚠️ HEIGHT CAPPED — unconstrained it rendered ~300px tall and dominated the record. It is a
   two-line note about a call, not a document. */
.lead-msg-dock textarea { width:100%; font-family:inherit; font-size:13.5px;
    min-height:64px; max-height:110px; resize:vertical; }


/* ==========================================================================
   SCRIPT V2 — THE MERGED SCRIPT+PREQUAL WORKSHEET (`.s2-*`), owner 2026-08-08.

   The whole prequal on one screen with the script's words beside it — Idea 2 of the rendered
   four-way he chose. 🛑 THE RENDER IS THE SPEC and these rules are measured off it:
   docs/plans/renders/2026-08-08-script-plus-prequal-one-screen-ideas.html; the thirteen rulings are
   in docs/plans/2026-08-08-script-v2-merged-prequal-screen.md. Admin-only `script2` tab until he
   rules the cutover (S7).

   🛑 THE DENSITY IS MEASURED, NOT TASTE. He asked for smaller type to fit more ("it doesn't have to
   be these such big fonts… we have to fit more on"), and the deep dive put numbers on it: dense
   enterprise screens run 12–13px body on 28–36px rows (IBM Carbon "compact" 24px; Salesforce
   Compact mode fits ~40% more with the label above/beside rather than in its own column). So:
   12.5px values, 9.5px captions, ~26px cells, 11.5px grid cells. Below 12px nothing on a sales
   floor stays readable at arm's length — that is the floor, not a target.

   🛑 AMBER MEANS "STILL NEEDED" AND ONLY THAT. The cursor's ring is TEAL (`.s2-here`, blue until
   2026-08-20 — Gil: it read as the card's own title band): one colour
   may not carry two meanings on a screen a rep reads at speed.

   ⚠️ Lives here, not in a view block: `ops/crm/show.blade.php` has no registered prefix in
   `StyleOwnershipTest::PREFIXES` (design rule 8). Catalogued in design-catalogue.md.
   ========================================================================== */
.s2 { display:flex; gap:12px; align-items:flex-start; }
.s2-sheet { flex:1; min-width:0; display:flex; flex-direction:column; gap:9px; }
/* A ROW of the render: two cards side by side, or one spanning the width (the two grids, the
   pricing block). Flex, not a fixed grid, so a pair splits evenly at any width. */
.s2-row { display:flex; gap:9px; align-items:stretch; }
.s2-sec { flex:1 1 0; min-width:0; background:var(--card); border:1px solid #e6e6ef;
    border-radius:10px; padding:7px 12px 9px; }
.s2-sec.is-wide { flex:1 1 100%; }
.s2-sec-head { display:flex; align-items:baseline; gap:8px; margin:0 0 5px; }
.s2-sec-name { margin:0; font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.07em;
    color:var(--brand); }
/* ✓ or "3 left" — coverage, in whatever order the customer gave it. */
.s2-sec-state { font-size:10.5px; font-weight:700; color:#9a97ab; }
.s2-cells { display:flex; flex-wrap:wrap; gap:3px 12px; }
.s2-cell { display:flex; flex-direction:column; min-width:0; cursor:pointer; }
/* FURNITURE, deliberately unreadable-aloud — the spoken words are the panel's, never a caption. */
.s2-cap { font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:#9a97ab; }
.s2-val { border-bottom:1.5px solid #cfcfe0; min-width:92px; font-size:12.5px; font-weight:600;
    color:var(--brand-ink); padding:1px 2px 2px; line-height:1.45; }
.s2-val.is-missing { background:#fdf7e2; color:#b8860b; }
.s2-val.is-onfile { color:#6f6a86; font-weight:500; }
.s2-onfile { color:#a9a5bb; font-size:10px; }
/* THE CURSOR'S OWN CELL — blue, never amber. This is what the panel is currently speaking for.
   🛑 IT IS A FILL NOW, NOT JUST AN OUTLINE (2026-08-19). Gil, driving the live screen: on the
   property-type beat *"the highlighted field in prequal is Years in home"* — it was not. Years in
   the home was AMBER ("still needed"); the blue ring was on the property type, doing its job.
   **Measured: 11 to 31 amber boxes are on screen at once through the body of the call, and amber is
   a FILL while the ring was a 2px OUTLINE.** A fill beats an outline every time, so the loudest
   thing on the sheet was never the thing the script was pointing at, and the rep reads the loudest
   thing. The colours still mean exactly what they meant — amber "still needed", blue "you are
   here" — but the one that means HERE now carries the weight to be found first.
   ⚠️ The tint sits on the CELL and the amber sits on the VALUE inside it, so a box that is both
   still-needed AND current reads as both. That pair is information, not a clash. */
/* ⚖️ 2026-08-20 — THE RING IS TEAL, AND IT IS TEAL BECAUSE THE BLUE ONE WAS THE FURNITURE.
   Gil, on the live screen: *"The highlighting of where we are working is similar to header shading,
   confusing. make a different color for where we are working. make sure it looks good since we are
   doing below with light yellow."* He was right and the numbers are small enough to explain it: the
   fill was `#e4ebfb` and `.ws-section > .ttl`'s band is `#eef2fa` — **two degrees apart in hue and
   the same lightness.** The thing that means HERE was drawn in the colour of the thing that means
   nothing.
   🛑 HE PICKED TEAL AND ASKED FOR "whatever looks better", SO THE TONE WAS MEASURED, NOT TAKEN.
   His swatch was `#0e7490`; this is the same colour deeper. Against the card white it goes 5.36 →
   **7.08**, and it moves AWAY from the one colour on this system it could be confused with —
   `#137a45`, success, "it worked" — 44° → **48°** of hue. A ring that reads as *done* on a box
   nobody has answered is the worst thing this colour could do.
   🛑 THE OUTLINE AND THE GLOW CARRY IT, NOT THE FILL — and that is NEW, caused by the amber fix
   landing the same day. With 153 of 176 unanswered boxes now amber, the ring sits ON an amber box
   for most of the call and the fill barely shows. The 2026-08-19 ruling that made this a FILL was
   right for a sheet where amber was mostly invisible; on today's sheet the border is what a rep
   finds, so it is 3px and the halo is 5px at .20 rather than .14.
   ⚠️ STILL EXACTLY TWO MEANINGS. Amber = "still needed", this = "the script is pointing here".
   Changing which hue carries the second one is not a third colour. `--danger` stays barred outright
   (design-system rule 13 — the alarm marks a compliance VERDICT and never a cursor). */
.s2-here { outline:3px solid #0c5f80; outline-offset:2px; border-radius:4px;
    background:#cbe9f5; box-shadow:0 0 0 5px rgba(12,95,128,.20); }
/* THE OTHER BOXES THIS ONE SENTENCE FILLS, 2026-09-01.
   Gil, reading `BR1` off the live screen — a line that asks the address, whether it is the subject
   property, AND how they use it: "so those questions don't highlight on the screen. So they
   wouldn't know where to fill those in." The ring is singular and the beat is plural; `BR1` alone
   captures nine fields.
   QUIETER THAN THE RING, ON PURPOSE, AND THE GAP IS THE MESSAGE. `.s2-here` keeps the solid 3px
   teal, the tint and the halo: it is the CURSOR — where Tab goes and where the sheet scrolls. This
   is a dashed 2px in the same hue with no tint and no halo, meaning "this sentence fills this one
   too". Matching them would put two cursors on the screen and answer "which box is this line for"
   with "several", which is the one question the ring exists to answer.
   ⚠️ SAME HUE, DIFFERENT WEIGHT — never a second colour. Teal already means "the script is pointing
   here" and amber already means "still needed"; a third hue would need a rep to learn a new word
   mid-call to gain nothing over a dash. */
.s2-also { outline:2px dashed #0c5f80; outline-offset:2px; border-radius:4px; }
/* A COLUMN THIS QUESTION DOES NOT ASK FOR, 2026-09-02.
   ⚖️ Gil: "lets do B, folding should happen on every question." The address row wants ~1,100px in
   ~900px of space, so marking a box was never enough — Usage and Type sat past the edge. Folding
   what is not asked buys the width back without moving a single field out of the row it lives in.
   🛑 IT STAYS VISIBLE AND IT STAYS CLICKABLE. A folded column is a sliver, never `display:none`: a
   rep must be able to SEE that something is folded and get it back in one press, because his
   standing rule is that the sheet may be filled OUT OF ORDER and a screen that decides which boxes
   may be answered is worse than a box being off to the right. `width` rather than `display` also
   keeps the column's place in the row, so the heading and the body cannot drift apart.
   ⚠️ NO TRANSITION ON WIDTH. The fold runs immediately before the sheet scrolls to the ringed box,
   and an animating width means the scroll is computed against a layout still in motion — the same
   class of defect as the smooth-scroll that landed nowhere and put the ring on the wrong box. */
.ws-lien-cell.is-folded { width:16px !important; min-width:16px !important; max-width:16px !important;
    padding:0 !important; overflow:hidden; cursor:pointer; background:#f6f7fb;
    border-right:1px solid #e4e8f2; }
.ws-lien-cell.is-folded > * { display:none !important; }
.ws-lien-cell.is-folded::after { content:"⋮"; display:block; width:100%; text-align:center;
    color:#aeb6c6; font-size:12px; line-height:1.1; }
.ws-lien-cell.is-folded:hover { background:#eef2fb; }
.ws-lien-cell.is-folded:hover::after { color:var(--brand-ink,#35529b); }

/* ── THE TWO GRIDS — his paper worksheet's blocks, one row per lien / per debt ── */
.s2-gridwrap { overflow-x:auto; }   /* wide tables scroll INSIDE themselves (design rule 7) */
.s2-grid { width:100%; border-collapse:collapse; font-size:11.5px; }
.s2-grid th { font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:.04em;
    color:#9a97ab; text-align:center; padding:1px 5px; white-space:nowrap; }
.s2-grid td { border-bottom:1px solid #f0f0f6; padding:2px 5px; text-align:center;
    color:var(--brand-ink); font-weight:600; cursor:pointer; min-width:52px; }
.s2-grid td.is-missing { background:#fdf7e2; }
/* A column this lien simply does not have (a 2nd has no RE taxes of its own) — not a hole. */
.s2-grid td.is-na { background:repeating-linear-gradient(135deg,#fafafd,#fafafd 3px,#f3f3f8 3px,#f3f3f8 6px); cursor:default; }
.s2-grid .s2-grid-lane, .s2-grid th.s2-grid-lane { text-align:left; font-size:10.5px; font-weight:700;
    color:#6b6685; white-space:nowrap; }
/* ⚠️ THE DEBT BLOCKS WERE TWO CATEGORIES ACROSS — and stopped being that on 2026-08-11, when the
   four blocks became ONE table. The two-column grid outlived them and went on giving that single
   table HALF the card: measured 2026-08-19 at a 1440 viewport, 603px of table inside a 1149px
   wrapper, which is why every cell was tight enough to make a box look wrong. One child, one column.
   ⚠️ `.s2-debtblock` below is DEAD and is left standing rather than removed — grepped repo-wide
   2026-08-19: not one blade emits it, and its only other appearances are frozen render snapshots
   under `docs/renders/` that carry their own copy of this file. It was the per-block wrapper of the
   same four-block layout. Removing it is tidying, not this change; naming it here is what stops the
   next reader assuming it is load-bearing. */
.s2-debts { display:block; overflow-x:auto; }
.s2-debtblock { min-width:0; }
/* PAYOFF vs THEIR NUMBER — a MARK, never a wall. */
.s2-track { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; margin-top:6px;
    padding-top:5px; border-top:1px solid #eee; font-size:11.5px; font-weight:700; }
.s2-track-cap { font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em; }
.s2-track.is-under { color:#8a6d1f; } .s2-track.is-under .s2-track-cap { color:#b8860b; }
.s2-track.is-over { color:#8a5a00; } .s2-track.is-over .s2-track-cap { color:#b8860b; }
.s2-track.is-met { color:#137a45; } .s2-track.is-met .s2-track-cap { color:#137a45; }

/* ── THE PRICING BLOCK — new loan amount, LTV, DTI. Last on the sheet, by his instruction. ── */
.s2-price { border-left:3px solid var(--brand); }
.s2-pricewrap { display:flex; gap:26px; align-items:flex-start; flex-wrap:wrap; }
/* ⚠️ THE `width:auto` IS LOAD-BEARING — cclg.css sets a global `table { width:100% }`, so this
   build-up stretched the entire card and pushed the rate box and both figures onto their own row. */
.s2-buildup { border-collapse:collapse; font-size:11.5px; flex:0 0 auto; width:auto; min-width:280px; }
.s2-buildup td { padding:1px 10px 1px 0; color:#4c4866; }
.s2-buildup td:last-child { text-align:right; font-weight:700; color:var(--brand-ink); }
.s2-buildup-total td { border-top:2px solid var(--brand); padding-top:3px; font-weight:800;
    color:var(--brand-ink); }
.s2-buildup-total td:last-child { color:var(--brand-2); }
.s2-rate { max-width:340px; }
.s2-note { margin:4px 0 0; font-size:10.5px; color:#a9a5bb; line-height:1.5; }
.s2-figures { display:flex; gap:24px; margin-left:auto; text-align:right; }
.s2-figure { display:flex; flex-direction:column; align-items:flex-end; }
.s2-big { font-size:19px; font-weight:800; color:var(--brand-ink); line-height:1.2; }
.s2-big.is-error { color:#c9c6d6; }
.s2-arrow { color:var(--brand); }
.s2-pending { color:#a9a5bb; }
/* 🛑 A MISSING `our_value` IS AN ERROR THE SCREEN STATES (his ruling 10) — never a quiet fall back
   to Zillow or to the customer's own guess. It wears the error INK, not the reserved compliance
   alarm: this is a gap in the file, not a verdict about a call (design rule 13). */
.s2-err { font-size:10.5px; font-weight:700; color:#a12626; }
/* The reverse block's Yes/No pairs — an answered No must LOOK answered (F1's secondary: a stored
   No rendered pixel-identical to never-asked on the tick it replaced). */
.s2-choice { display:flex; gap:10px; align-items:center; padding-top:2px; }
.s2-choice label { display:flex; gap:3px; align-items:center; font-size:11px; font-weight:600;
    color:#4c4866; cursor:pointer; }
.s2-choice input { margin:0; }

/* THE AUTOSAVE RECEIPT — beside the Save button. On failure it BECOMES the warning (red), never a
   modal: a rep on this sheet is mid-call (the guided screen's loud-not-silent-never-blocking law). */
.s2-saved { font-size:10.5px; font-weight:700; color:#8a879c; align-self:center; }
.s2-saved.is-unsaved { color:#a12626; }

/* THE NEXT STEP — 1003 & Appraisal (S6). Locked until the rep marks "they picked an option"; the
   bar goes solid green when it opens, straight off the render's own `.mk-next`. */
.s2-next { flex:1; border:1px solid #bfd6c8; border-radius:10px; overflow:hidden; }
/* ⚠️ THE LOCKED BAR IS PART OF THE ONE-SCREEN BUDGET (the sheet's own §"one-screen budget": 1077px
   against a 1080 monitor, nothing hidden to make it fit). The strip this band replaced drew ~29px;
   the first cut of this bar drew 51 and pushed the locked page to 1115 — over the monitor. Hence the
   tight padding and line-height here: measured back to ~31px. The OPEN band may be tall — pricing is
   a scroll-down moment by his own ruling — but LOCKED is on the everyday screen. */
.s2-next-bar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; line-height:1.3;
    background:#e4f7ec; color:#137a45; font-weight:800; font-size:11.5px; padding:4px 14px; }
.s2-next-bar input[type=checkbox] { width:12px; height:12px; margin:0; }
.s2-next-bar small { font-weight:600; color:#4e7a61; }
.s2-next.is-open .s2-next-bar { background:#137a45; color:#fff; }
.s2-next.is-open .s2-next-bar small { color:#cfe9d9; }
/* The mark itself is the label — the whole bar phrase is the hit target, so a rep reaching for it
   mid-call does not have to find a 13px box.
   ⚠️ The margin/font resets are LOAD-BEARING: the global `label` rule carries `margin:12px 0 6px` +
   `font-size:13px`, which silently inflated the locked bar from ~29px to 43 — the input[type]
   specificity scar's cousin, found by MEASURING the built page, not by the suite. */
.s2-next-mark { display:flex; align-items:center; gap:8px; cursor:pointer;
    margin:0; font-size:inherit; font-weight:inherit; }
.s2-next-fields { background:var(--card); padding:8px 14px 10px;
    display:flex; gap:6px 18px; flex-wrap:wrap; }
/* A tick sits UNDER its caption and hard LEFT, exactly as the render draws the three of them —
   right-aligning them read as a different kind of control from the boxes beside them. */
.s2-next-fields .s2-cell.is-tick { align-items:flex-start; padding-bottom:2px; }
.s2-next-fields .s2-cell.is-tick input { margin-top:2px; }
/* 🛑 THE SENTENCE THIS BAND MAY NOT WRITE. Amber, because amber means "still needed" on this sheet
   and the consent wording genuinely is — it is waiting on the compliance officer. */
.s2-next-note { margin:0; background:#fdf7e2; border-top:1px solid #f0e3b0; padding:7px 14px;
    font-size:11px; color:#6b5a1c; line-height:1.5; }

/* ── THE WORDS COLUMN — one panel per cell, one visible ── */
/* ⚖️ THE WHOLE CALL LIVES HERE NOW (Gil 2026-08-16, "full script") — so it MUST cap and scroll
   inside itself. It is `position:sticky`: with every question drawn and no ceiling, a long script
   grows past the viewport, the pin has nothing left to pin, and the rep loses both the words and
   the sheet. The cap is the viewport minus the phone strip it sits under, minus a little air. */
/* ⚖️ THE SCRIPT SITS ON THE LEFT AND THE PREQUAL ON THE RIGHT — Gil 2026-08-20: *"I'm thinkg maybe
   the script should be on teh left hand side and the prequal on teh right."*
   🛑 BY `order`, NOT BY MOVING THE MARKUP, AND THAT IS DELIBERATE. The sheet is a FORM and the panel
   is an `<aside>` outside it; swapping them in the DOM would put a 66-row list of unlabelled speech
   ahead of every input in both the tab order and a screen reader's reading order, for a surface a rep
   drives entirely from the boxes. `order` moves the picture and leaves the document alone.
   ⚠️ The narrow-screen rule below already used `order:-1` to stack the panel on top; the two now
   agree instead of one overriding the other.
   ⚖️ AND IT IS WIDER, because it is a column now rather than a rail: it carries every spoken line at
   once (see the leak rule below), so 330px would set the opener at ~29 characters a line. */
.s2 > .s2-panel { order:-1; }
/* ⚠️ THE WIDTH IS NOT SET HERE. A later top-level `.s2-panel` rule owns it (the `clamp()` that makes
   the panel give ground continuously as the window narrows) and beats this one on source order at
   equal specificity — a width written here renders correctly in this file and is DEAD on the screen,
   which is exactly what happened on the first build of this column: 360px asked for, 269px measured. */
/* ⚖️ ROOM TO SCROLL PAST THE LAST BEAT — the bottom padding, and it is not decoration.
   `readingZone()` lifts the open row into a 12%–55% band, and it does that by scrolling; near the
   END of a call there is nothing left below to scroll AGAINST, so the panel saturates and the line a
   rep is reading aloud slides down to the bottom edge — the exact defect that stopped Phase 1 at 87%,
   returning by arithmetic instead of by layout. The last beat of the call could never be lifted at
   all. This is the editor's "scroll past end": empty space below the final row so every beat,
   including the last, can reach the band.
   🛑 IT SITS ON `.s2-panel`, NOT INSIDE `.s2-lines`, AND THAT IS DELIBERATE. The position
   gauge (`.s2-pos`) is `align-self:stretch`, so its height is the LIST's height — a spacer added
   inside the list would stretch the gauge past the last beat and the mark would read low for the
   whole call. Padding on the scroll container leaves the list's box exactly as it was.
   ⚠️ FOUND BY A CHECK, NOT BY EYE: gathering the roads-not-taken into one heading (2026-09-04)
   removed a heading's worth of height from the tail and the live line moved 60% → 62% down the panel.
   The heading was never what was holding it up; nothing below it was. */
.s2-panel { background:var(--card);
    border:1px solid #dcdce8; border-left:3px solid var(--brand); border-radius:10px;
    padding:10px 14px 12px; position:sticky; top:var(--ph-strip-h, 44px);
    max-height:calc(100vh - var(--ph-strip-h, 44px) - 24px); overflow-y:auto; overscroll-behavior:contain;
    display:flex; gap:11px; align-items:flex-start; }
/* 🛑 `flex-start`, NOT `stretch`, AND IT IS LOAD-BEARING. This panel has a DEFINITE height (the
   `max-height` above) and scrolls, so `align-items:stretch` sizes each item to the CONTAINER — the
   list came out 854px tall with 3,700px of lines overflowing it. Nothing looked wrong: the rails all
   drew, the panel scrolled normally. What broke was the way-back marker, whose `position:sticky` is
   constrained by its PARENT's box — pinned inside an 854px parent, it scrolled off the moment the rep
   went further than one screen, i.e. exactly when a way back is wanted.
   ⚠️ FOUND BY LOOKING AT THE SCREEN. Every DOM check passed: the chip was present, not hidden,
   `position:sticky`, `top:0`, inside an `overflow:auto` ancestor. All true, and all useless. */
/* ⚠️ `flex:1`, NOT `flex:1 0 auto`. The first fix here set `flex-shrink:0`, which is a MAIN-axis
   (width) property and had nothing to do with the height problem being solved — the lines stopped
   shrinking to the column and every sentence was clipped at the right edge. `align-self` is the whole
   of the height fix; the width was already right. */
.s2-lines { flex:1; min-width:0; align-self:flex-start; }

/* THE POSITION BAR — see the markup note in `_script2-sheet.blade.php` for why it shows POSITION and
   not the section Gil first asked for (measured: 13 sections in 32 runs across 66 beats).
   🛑 IT STRETCHES WITH THE CONTENT, so it scrolls with the script instead of floating over it — the
   fill is a percentage of the whole list, which is only meaningful against the whole list's height. */
/* The bar keeps the container's height and PINS, so it reads as a fixed gauge beside a moving list
   rather than a stripe that scrolls away with it. `align-self:stretch` against a `flex-start`
   container is what gives it the panel's height while the list keeps its own. */
.s2-pos { flex:0 0 5px; align-self:flex-start; position:sticky; top:0;
    border-radius:99px; background:#eeecf6; }
.s2-pos-fill { position:absolute; left:0; right:0; background:var(--brand); border-radius:99px;
    transition:top .12s ease-out; }

/* ⚖️ EVERY QUESTION IS A LINE; ONLY THE CURRENT ONE OPENS (Gil 2026-08-16: *"one continuos will be
   too much"* — 67 questions on the live refi script, ~3,000px of scroll if each drew in full).
   🛑 COLLAPSED SHOWS THE LABEL, NEVER THE WORDS — and that is a safety rule, not a space one. A
   truncated ❝ line is a line a rep can read aloud by accident, half-finished. The label ("Home
   value", "Credit score") is a signpost nobody would ever say to a customer, so the panel can be
   scanned without becoming a script with 67 competing lines in it.
   🛑 NO SECTION HEADINGS, DELIBERATELY. Grouping was tried three ways and every taxonomy is wrong
   here: the sheet's SEVEN cards interleave once the call is walked properly; the script's STAGES
   repeat (opener opens AND closes the call, confirm appears three times); and `GuidedSurface::PAGES`
   is stale — it still carries `veteran` and `income` as late sections two weeks after his 08-03
   ruling folded both into the borrower row. Ordering by the call itself needs none of them and
   cannot rot. */
/* ⚖️ EVERY LINE IS ON SCREEN AND THE TITLES ARE GONE — Gil 2026-08-20: *"the titles are confusing,
   looking at iquickly will make us read that first some times… why not jsut show jsut the script"*,
   and *"we can jsut scroll, but the question we are on should highlight."*
   🛑 THIS RETIRES "COLLAPSED SHOWS THE LABEL, NEVER THE WORDS", AND BOTH HALVES MOVED FOR HIS
   REASONS. The label came off because he watched himself read it as the heading for the line below
   it — a filing name ("Offered by") sitting on top of speech, which is the very hazard his 08-17
   ruling was written against, arriving from the other side. And once the label is gone a shut row has
   NO content at all, so the words had to come forward or the panel would be 63 blank rows.
   🛑 THE OLD SAFETY ARGUMENT WAS ABOUT A **TRUNCATED** LINE — *"a half-drawn sentence is one a rep
   can read aloud by accident"* — and nothing here is truncated: every row carries its whole sentence.
   ⚠️ HIS *"one continuos will be too much"* (08-16) IS THE RULING THIS REVERSES, and he reversed it
   himself. It was measured against a 330px rail where all 66 lines run ~3,800px; the panel is now a
   column of its own that he has asked to scroll. If it reads as a wall on the floor, THAT sentence is
   the one to come back to.
   🛑 SO THE WHOLE JOB IS CARRIED BY CONTRAST NOW. The line being said is bigger, darker and tinted;
   every other line is small, grey and unquoted-looking. Weakening that gap is not a styling change,
   it is removing the only thing telling a rep which line is theirs to read. */
.s2-rail { padding:6px 0; border-bottom:1px solid #f4f3f8; cursor:pointer; }
.s2-rail:last-child { border-bottom:0; }
.s2-rail:hover:not(.is-here) { background:#f8f9fd; }
/* The coaching prose and the caps stay off every row but the open one — his 08-16 *"those lines, if
   they hesitate and listen for are confusing"*, unchanged by any of this. */
.s2-rail:not(.is-here) .s2-panel-cap,
.s2-rail:not(.is-here) .s2-line,
/* 🛑 `.s2-tie` BELONGS ON THIS LIST AND NOWHERE ELSE (2026-08-19). This rule names every speakable
   class one by one, so a NEW one is shown on all 66 rows until it is added here — the wall he
   refused, arriving through the back door. The tie-down is also deliberately kept OFF the two
   look-ahead rows below: he asked for the next QUESTION, and a second spoken line on each would
   double what the look-ahead puts in front of the eye. Open row only. */
.s2-rail:not(.is-here) .s2-tie,
.s2-rail:not(.is-here) .s2-ob { display:none; }
.s2-rail:not(.is-here) .s2-head-cap { display:none; }
/* ⚖️ THE NEXT TWO LINES STAY READABLE — Gil 2026-08-17: *"right now you have to click the next
   question in order to see it, that prevents flow. i want the next question or 2 to always appear
   so there is flow."*
   🛑 THIS REVERSES "ONLY THE CURRENT ROW OPENS", AND THE REVERSAL IS HIS. That rule came from his
   own *"one continuos will be too much"* — which was about all SIXTY-SIX rows open at once, not
   about three. Both readings of his words are now on the record; this follows the later, more
   specific one. The original worry stands and is answered by CONTRAST, not by hiding: the reason a
   closed row showed no words was that *a half-drawn sentence is one a rep can read aloud by
   accident*, so the two lines ahead are deliberately smaller, lighter and un-quoted — legible when
   you look at them, never mistakable for the line you are saying.
   ⚠️ THE OBJECTIONS (`.s2-ob`) AND THE CAPS STAY HIDDEN on those rows. He asked for the QUESTION,
   and pulling the coaching prose forward is what he called *"confusing"* when it sat under the live
   line on 08-16. One extra thing to read, not four. */
/* ⚖️ THE FILING LABEL HAS LEFT THE PANEL ALTOGETHER — Gil 2026-08-20: *"the titles are confusing,
   looking at iquickly will make us read that first some times. so we need to change this. Way to
   confusing how it is now with teh titles."*
   🛑 HIS 08-17 RULING IS NOT REVERSED, IT IS FINISHED. That one took the label off the row being read
   and the two ahead, on the grounds that a filing name next to speech gets read aloud — and it left
   the label on every shut row, where it was "the whole content and nothing near it is speakable".
   What it could not foresee is that the run of shut rows ends directly ABOVE the open one, so the
   last label standing sat on top of the line he was saying and read as its heading. He hit exactly
   that ("the header on that screen is called, offered by") three days later. The hazard was never
   "a label on the open row", it was "a label anywhere near speech".
   🛑 THE ELEMENT IS DELETED FROM THE MARKUP, NOT HIDDEN HERE. A permanently-`display:none` node with
   a live hook is what this file has twice had to retire ("Coming up next", "nothing to say for this
   one") — the shape a later session re-wires without knowing why it went dark. These rules go with
   it; `.s2-panel-head` no longer exists on this surface.
   ⚠️ WHAT THE LABELS WERE ALSO DOING — telling a rep where they were in the call — is now the
   POSITION BAR (`.s2-pos`), which carries no word a mouth can reach. */

/* ⚖️ WHERE AM I IN A 66-BEAT CALL? Still answered with a sibling selector and no counter: only one
   row carries `.is-here`, so everything after it is still to come and everything before it is behind.
   🛑 IT IS POSITION, NOT PROGRESS, and the difference is honest — the panel cannot know a beat was
   actually said (the rep may have jumped, or the call may have skipped a branch), only that it is
   behind the cursor. Fading is the strongest claim the data supports; a tick would be a lie.
   ⚠️ IT MOVED FROM THE LABEL TO THE LINE when the labels went. Left on `.s2-panel-head` it would have
   been styling a node that no longer renders — green, silent, and doing nothing. */
/* ⚖️ BOTH TONES DROPPED HARD, 2026-08-20 — Gil, on the deployed screen: *"not really easy to just
   ee where you're at."* He is right, and the diagnosis is that this was a CONTRAST problem and not a
   how-many-lines one: the line being said was already bigger and bolder, but everything AHEAD of it
   sat at full strength, so the column read as one uniform block and the highlight had nothing to win
   against.
   🛑 THE PAST/AHEAD SPLIT SURVIVES, COMPRESSED. Both tones move down, and they keep their order —
   behind you is quieter than ahead of you — so the fade still answers "where am I" without either of
   them competing with the card. Flattening the two to one value would buy contrast by throwing away a
   second position cue that costs nothing. */
.s2-rail:not(.is-here) .s2-say { opacity:.38; }
.s2-rail.is-here ~ .s2-rail .s2-say { opacity:.58; }

/* 🛑 EVERY LINE CARRIES ITS WHOLE SENTENCE, AND CONTRAST IS THE ONLY THING SEPARATING THEM. Small,
   grey, tighter — legible when you look at it, never mistakable for the line you are saying. The
   `max-width` is the same reading measure the open line uses: past ~62 characters a rep loses their
   place on the return sweep, out loud, mid-sentence. */
.s2-rail:not(.is-here) .s2-say {
    font-weight:400; font-size:11px; line-height:1.45;
    color:#9a97ab; margin:2px 0 0; max-width:62ch;
}

/* ⚖️ THE LINE BEING SAID IS A CARD THAT LIFTS OFF THE PAGE — his pick from a rendered three-way,
   2026-08-20, over rebuilding the panel as a one-at-a-time slide screen.
   🛑 WHY THE CHEAP FIX WAS OFFERED FIRST, and it is not thrift. A slide screen fixes "where am I" by
   putting nothing else on screen — but he had asked for the OPPOSITE twice (08-14, when one question
   at a time left him unable to see what was coming; and 08-17, *"i want the next question or 2 to
   always appear so there is flow"*). Spending the look-ahead to buy contrast is a trade worth making
   only once contrast itself has been tried and failed.
   🛑 THE LIST BEHIND IT IS TINTED SO THE CARD HAS SOMETHING TO FLOAT ON. A white card on a white panel
   is a border and a shadow doing all the work; against the grey it reads as a different plane, which
   is what a glance resolves. That is why `.s2-lines` carries the tint and not `.s2-panel` — the
   position bar must stay on the panel's own ground, or it reads as part of the list. */
.s2-lines { background:#f1f2f8; border-radius:8px; padding:6px 12px; }

/* ══ DONE · NOW · AHEAD — the panel stops being a wall (2026-09-04) ═════════════════════════════
   ⚖️ Gil, on four rendered screens: the beats already said fold to a trail carrying the ANSWER, the
   beat being said is shaded, and the rest of the call is the next two then an outline of stages.
   🛑 THE CAP IS WHAT FIXES THE DEFECT THAT STOPPED PHASE 1 — measured at beat 40 of 66 the live beat
   sat 87% down the panel, hard on the bottom edge, because the trail grew above it all call. Two
   lines above it instead of forty puts it around a third down and keeps it there, which is a
   position a rep's eye can learn.
   ⚠️ `display:none` ON AHEAD RAILS IS THE WHOLE SAVING, and it is safe because THE ANSWER BOXES LIVE
   ON THE SHEET, never in these rows: hiding a rail hides words, never a place to type. A rail
   reappears the moment the call reaches it (`zoneRails` runs on every move), and the sheet jump opens
   a hidden rail exactly as it always did. */
.s2-rail[data-s2-zone="ahead"] { display:none; }
.s2-rail.s2-said-folded { display:none; }

/* ── DONE: one line each, carrying what the customer said ── */
.s2-rail[data-s2-zone="done"] { padding:3px 0; }
.s2-rail[data-s2-zone="done"] .s2-lines { background:none; padding:2px 4px; }
.s2-rail[data-s2-zone="done"] .s2-say { font-size:12.5px; line-height:1.35; color:#98a0b0;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin:0; }
/* 🛑 THE ANSWER IS THE POINT OF THE TRAIL. A list of questions a rep has already asked is a list
   they must remember the answers to; this is what makes a glance upward worth taking. Read live
   from the boxes, so a correction on the sheet corrects here too. */
.s2-said-answer { display:block; padding:0 4px 2px; font-size:12px; font-weight:700;
    color:var(--brand-ink,#35529b); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.s2-said-answer:empty { display:none; }

/* ── NOW: shaded and ruled, so the eye lands on it without reading ── */
.s2-rail[data-s2-zone="now"] .s2-lines { background:linear-gradient(90deg,#eaf0fb 0%,#f3f6fd 100%);
    border-left:5px solid var(--brand-ink,#35529b); border-radius:0 10px 10px 0;
    box-shadow:inset 0 1px 0 #d9e2f5, inset 0 -1px 0 #d9e2f5; padding:12px 14px 13px 13px; }

/* ── NEXT: the two ahead — readable, never mistakable for the line being said ── */
.s2-rail[data-s2-zone="next"] .s2-lines { background:none; padding:4px 12px; }
.s2-rail[data-s2-zone="next"] .s2-say { font-size:13.5px; line-height:1.4; color:#8d94a6; }

/* ── The two ways back into what was said, and the way home from up there ── */
.s2-trail-toggle { display:block; width:100%; text-align:left; appearance:none; background:none;
    border:0; padding:4px 10px; margin:0 0 2px; font:inherit; font-size:11.5px; font-weight:700;
    color:#7d8598; cursor:pointer; border-radius:7px; }
.s2-trail-toggle:hover { background:#eef2fb; color:var(--brand-ink,#35529b); }
.s2-trail-toggle[hidden] { display:none; }
.s2-back-to-now { position:sticky; top:2px; z-index:4; display:block; margin:0 auto 4px;
    appearance:none; border:1px solid #c9d5f0; background:#fff; border-radius:999px;
    padding:4px 12px; font:inherit; font-size:11.5px; font-weight:700;
    color:var(--brand-ink,#35529b); cursor:pointer; box-shadow:0 2px 8px -4px rgba(17,23,38,.35); }
.s2-back-to-now[hidden] { display:none; }

/* ⚖️ ASKED ON THE SCRIPT, NOT DRAWN ON THE PREQUAL — Gil 2026-09-04 (`Script2Sheet::SCRIPT_ONLY`).
   Hidden where it sits, shown when the beat that asks it carries it under the words. */
[data-s2-script-only] { display:none; }
.s2-now-answer [data-s2-script-only] { display:block; }

/* ⚖️ THE TWO ARROWS (Gil 2026-09-05) — *"small arrows forward and back… small"*, and small is the
   whole brief: they steer the call, they are not the point of the screen. Drawn only on the beat
   being said, right-aligned under its answers, quiet until reached for. */
/* 🛑 PINNED TO THE FOOT OF THE PANEL AND CENTRED — his 09-05 *"center the arrow to bottom page"*.
   `sticky` with `bottom:0` keeps them in the same place while the call scrolls under them, the way
   the way-back chip pins to the top; drawn per-line they moved with the beat and a rep had to hunt
   for them. `z-index` clears the lines, and the small backdrop stops words reading through them. */
/* ⚠️ `sticky` IS THE FALLBACK, NOT THE MECHANISM. `placeStepBar()` fixes this to the panel's foot;
   if its script never runs, sticky at least keeps the arrows near the end of the list rather than
   halfway up the call. */
/* 🛑 IT IS NOT A BAR ANY MORE — it sits at the foot of the live question (Gil, 2026-09-06), so
   it needs no pinning, no backdrop and no rule to stop words reading through it: nothing is behind
   it. A collapsed row draws none of this, because the row's own body is not rendered there. */
.s2-step { display:flex; justify-content:center; gap:8px;
    margin:9px 0 0; padding:7px 0 1px; box-sizing:border-box;
    /* 🛑 SOLID, NOT A FADE, AND WITH A RULE ABOVE IT. A pinned footer inside a scrolling list sits
       OVER whatever is behind it — that is what pinning is — so the only question is whether it reads
       as a bar or as arrows floating on top of a sentence. A fade let the outline show through and it
       read as the second. */
    border-top:1px solid #eef0f6; }
/* …and the list gets room to scroll clear of it, so the last heading is never stuck underneath. */
/* 🛑 THE ARROWS ONLY PIN WHILE THEIR CONTAINING BLOCK REACHES PAST THE FOLD, and `.s2-lines` ends
   where the last heading does — so on a short call they simply sat at the end and did not move with
   the scroll (Gil, 2026-09-05: *"arrows do not move with scrolling"*). The room to scroll past the
   last beat moves here from `.s2-panel`, so the list always extends below the fold and the bar has
   somewhere to pin to. `.s2-panel` keeps a small pad; the tail does the work.
   ⚠️ AND THE POSITION GAUGE IS NO LONGER STRETCHED BY IT — `.s2-pos` used to take the LIST's height,
   which now includes that tail, and the mark would have read low for the whole call. It is sized to
   the beats themselves in `markPosition`. */
.s2-lines { padding-bottom:34vh; }
.s2-step-btn { appearance:none; border:1px solid #d7dcea; background:#fff; color:#7d8598;
    border-radius:7px; width:26px; height:24px; line-height:1; font-size:15px; font-weight:700;
    cursor:pointer; padding:0; }
.s2-step-btn:hover { background:#eef2fb; color:var(--brand-ink,#35529b); border-color:#c9d5f0; }

/* ── THE ANSWER, UNDER THE WORDS ── ⚖️ Phase 2.2: the rep answers where they read.
   🛑 THE SLOT IS EMPTY UNTIL THE CALL ARRIVES — the sheet's own choice block is MOVED in, so an
   empty slot must take no room at all or every rail ahead would carry a grey stripe for a control
   that is somewhere else. */
.s2-now-answer:empty { display:none; }

/* ⚖️ A BOX LIFTED OUT OF A GRID WEARS THE SAME CLOTHES AS EVERY OTHER ANSWER — Phase 2.6. It arrives
   with no caption of its own (a grid keeps those in its heading row), so the panel draws one, and
   the box beneath it takes the full width it never had inside a 118px column.
   🛑 THE SPACER LEFT BEHIND MUST NOT BE VISIBLE AND MUST NOT COLLAPSE: it holds a column open and
   nothing else. */
.s2-now-answer .s2-lifted { margin:6px 0 0; }
/* ⚖️ Click through (Gil 2026-09-12, checklist step 2) — a box answered on this call wears a ✓ by its label;
   a question whose every box is answered gets one green "Next ›" strip. */
.s2-now-answer .s2-said > .ws-vlabel::after { content:' \2713'; color:#0f6b5d; font-weight:700; }
.s2-already { display:flex; align-items:center; justify-content:space-between; gap:8px; margin:0 0 8px;
    padding:6px 10px; border-radius:8px; background:#e6f4f0; color:#0f6b5d; font-size:13px; font-weight:600; }
.s2-already-next { border:0; background:#0f6b5d; color:#fff; border-radius:7px; padding:4px 12px;
    font-size:13px; font-weight:700; cursor:pointer; }
/* ⚖️ Gil's section name above the question being said (2026-09-12) — only on the open row. */
.s2-section-tag { display:none; }
.s2-rail.is-here .s2-section-tag { display:inline-block; margin:0 0 4px; font-size:11px; font-weight:700;
    letter-spacing:.06em; text-transform:uppercase; color:#35529b; background:var(--card);
    border:1px solid #cdd7ee; border-radius:999px; padding:1px 9px; }
/* ⚖️ The checklist under an open question (Gil 2026-09-12) — a ✓ when the fact has a value, amber when
   the file already held it and nobody has said it on this call, an empty circle when it is still owed. */
/* ⚖️ Gil 2026-09-12 — the subject question's answer shows only on the beat that asks it; the subject's
   address (on "No") reads street, then city · st · zip, like the sheet; Zillow sits beside the address. */
.s2-now-answer[data-s2-no-fork] .ws-k-subject_property_fork { display:none; }
/* 🛑 `display:grid` beat the `hidden` attribute — the block showed on Yes on the live screen. */
.s2-subject-addr[hidden], .s2-own-where[hidden] { display:none !important; }
.s2-res-off, .s2-live-head[hidden] { display:none !important; }
.s2-live-head { margin:12px 0 6px; }
.s2-own-where { margin:10px 0 0; }
.s2-own-where .s2-sa-head { margin-bottom:6px; }
.s2-own-where .s2-own-btns { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.s2-own-where button { border:1px solid var(--line); border-radius:10px; background:#fff; padding:10px; font-size:14px; cursor:pointer; }
.s2-own-where button.is-on { border:2px solid #1d2233; background:#eef0f8; }
.s2-zillow.is-go { font-size:15px; padding:8px 14px; }
.s2-subject-addr { display:grid; grid-template-columns:minmax(0,1fr) 96px 128px; gap:8px; margin:10px 0 0; }
.s2-subject-addr .s2-sa-head, .s2-subject-addr .ws-k-property_address { grid-column:1 / -1; }
.s2-sa-head { font-size:11px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--mut); }
.s2-now-answer .s2-subject-addr .ws-vlabel { display:none; }
.s2-zillow { display:inline-flex; align-items:center; gap:4px; margin:8px 0 0; padding:5px 10px; border:1px solid var(--line); border-radius:8px; background:#fff; font-size:13px; font-weight:600; color:var(--brand-ink); text-decoration:none; }
.s2-zillow:hover { border-color:var(--brand-ink); }
.s2-outline-row[role="button"] { cursor:pointer; border-radius:6px; }
.s2-outline-row[role="button"]:hover, .s2-outline-row[role="button"]:focus-visible { background:rgba(60,80,160,.08); outline:none; }
.s2-outline-row[role="button"]:hover .s2-outline-name { color:var(--brand-ink); }
.s2-checklist { margin:10px 0 0; border:1px solid var(--line); border-radius:10px; background:var(--card); overflow:hidden; }
.s2-cl-head { padding:7px 10px 2px; font-size:11px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--mut); }
.s2-cl-row { display:grid; grid-template-columns:20px minmax(0,1fr); gap:8px; align-items:center; padding:3px 10px; }
.s2-now-answer .s2-checklist .s2-cl-row .s2-lifted { margin:0; }
.s2-cl-ic { width:18px; height:18px; border-radius:50%; border:2px solid #cfd6e4; box-sizing:border-box;
    display:grid; place-items:center; font-size:11px; line-height:1; font-weight:700; color:#fff; }
.s2-cl-row.is-done .s2-cl-ic { background:#0f6b5d; border-color:#0f6b5d; }
.s2-cl-row.is-done .s2-cl-ic::after { content:'\2713'; }
.s2-cl-row.is-onfile .s2-cl-ic { background:#e9b949; border-color:#e9b949; }
.s2-cl-row.is-later { opacity:.8; }
.s2-cl-count { margin-top:4px; padding:6px 10px; border-top:1px solid var(--line); font-size:12.5px; color:var(--mut); }
.s2-cl-count b { color:var(--ink); }
.s2-now-answer .s2-lifted > input, .s2-now-answer .s2-lifted > select { width:100%; max-width:none;
    box-sizing:border-box; }
/* ⚖️ THE NOTE BOXES READ BIGGER AND BOLDER — Gil, 2026-09-07: *"Make text in Anything else about
   what they want box bigger and bolder."* It is free prose a rep types mid-call and reads back on
   the next one, and at the grid's 13px regular it was the faintest thing on a card full of numbers.
   🛑 THE BOX, NOT ITS CAPTION. The caption is `_worksheet-label`'s and is shared by every field on
   the sheet; changing it there would re-weight forty labels to fix one box. */
.ws-f.ws-k-goal_note textarea, .ws-f.ws-k-property_note textarea,
.ws-f.ws-k-mortgage_note textarea, .ws-f.ws-k-debts_note textarea {
    font-size:15px; font-weight:600; line-height:1.45; color:var(--brand-ink); }
[data-s2-ghost] { visibility:hidden; pointer-events:none; }
/* ⚖️ THE CARD READS ITSELF BACK WHILE ITS BOXES ARE AWAY — Gil, 2026-09-07: *"goals is empty on
   prequal"*, then, shown a read-back printed into each vacated slot, *"no it should be grouped
   together on what it is for by category. that looks terrible."* So: ONE strip per category, under
   that category's own heading, rather than words scattered wherever a grid happened to put the
   holes. The categories are the sheet's own `.ws-goal-arm` groups, not a second opinion.
   🛑 IT MUST NOT LOOK LIKE A BOX. No white field, no input border — a tinted strip that reads as a
   summary, so nobody tries to type into the copy while the real control is under the words.
   🛑 AND NO LEFT EDGE: on this sheet a left edge means "the call is here" (R-127) and nothing else
   may wear one. */
.s2-echo-group {
    display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 16px;
    margin:0 0 10px; padding:7px 11px; border-radius:9px;
    background:#f2f5fb; border:1px solid #e2e8f4; pointer-events:none; }
.s2-eg-head { flex:0 0 100%; font-size:10.5px; font-weight:800; letter-spacing:.06em;
    text-transform:uppercase; color:#6b6480; }
.s2-eg-item { display:inline-flex; align-items:baseline; gap:6px; min-width:0; }
.s2-eg-cap { font-size:10.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
    color:#8b93a7; white-space:nowrap; }
.s2-eg-val { font-size:13.5px; font-weight:700; color:var(--brand-ink); overflow-wrap:anywhere; }
.s2-eg-none { font-size:12.5px; font-style:italic; color:#9aa2b4; }
/* ⚠️ A CELL CARRIED UP HERE IS NO LONGER IN A COLUMN, so the widths a grid gave it are wrong — and
   `is-folded` travelled with it once, which drew a 16px sliver under the words. */
.s2-now-answer .s2-lifted-cell > .ws-lien-cell,
.s2-now-answer .s2-lifted-cell > .ws-b-cell {
    width:auto !important; min-width:0 !important; max-width:none !important;
    display:block; padding:0; }
.s2-now-answer .s2-lifted-cell > .is-folded > * { display:revert !important; }
.s2-now-answer .s2-lifted-cell > .is-folded::after { content:none; }
/* ⚖️ AND A DEBTS CELL ARRIVES DRESSED — Gil, 2026-09-10, on the auto-loan beat: *"All wrong, teh
   script and teh prequal."* The four money boxes came up as bare captions with a naked figure under
   them and the empty ones showing *$xx,xxx* as though it were an answer.
   🛑 THE CAUSE IS THE SCOPE OF THE DRESS, NOT THE MOUNT. Every box the debts table draws is styled by
   `.s2-debts-one input[type].s2-in` — the white field, the amber "still needed", the italic hint. The
   lift MOVES the `<td>` out of the table, `.s2-debts-one` stops being an ancestor, and the box falls
   back to the bare `.s2-in` rule, which is transparent and border-less because inside a grid a border
   per cell would draw seventy of them. Right in the grid, wrong the moment the cell is standing on
   its own under a spoken line.
   ⚠️ AND A `<td>` IS STILL A TABLE CELL IN A DIV — it shrink-wraps and keeps the grid's centring, so
   it is laid out as a block here before anything else is said about it. */
.s2-now-answer .s2-lifted-cell > td[data-s2-field] {
    display:block; width:auto !important; min-width:0; max-width:none; padding:0; }
.s2-now-answer .s2-lifted-cell input[type].s2-in {
    border:1px solid #d7d5e4; border-radius:6px; background:#fff;
    padding:4px 8px; text-align:left; width:100%; box-sizing:border-box; }
.s2-now-answer .s2-lifted-cell input[type].s2-in:focus {
    border-color:var(--brand); box-shadow:0 0 0 2px rgba(53,82,155,.13); outline:none; }
.s2-now-answer .s2-lifted-cell td.is-missing input[type].s2-in {
    background:#fdf7e2; border-color:#e6d49b; }
.s2-now-answer .s2-lifted-cell input[type].s2-in::placeholder {
    color:#bdb8cc; font-style:italic; font-weight:500; }
.s2-now-answer .s2-lifted-cell td.is-missing input[type].s2-in::placeholder { color:#c2ab6d; }
/* The Pay off / Keep pair travels as a cell too, and its buttons sit in a row of their own here. */
.s2-now-answer .s2-lifted-cell .s2-po { display:flex; gap:6px; justify-content:flex-start; }
/* ⚠️ ONLY THE BEAT BEING SAID SHOWS ITS ANSWERS. The moved blocks are only ever in the live row, but
   the drawn road buttons are kept for the life of the page so the fact they store survives — without
   this they would sit in the trail under a line already spoken. */
/* The door to a card, drawn only on a beat that carries no boxes of its own. It follows the same rule
   as the answers above it: only the beat being said shows it. */
.s2-now-jump { margin:8px 0 2px; }
.s2-rail:not(.is-here) .s2-now-jump { display:none; }
.s2-rail:not(.is-here) .s2-now-answer { display:none; }
/* ⚖️ A ROAD IS AN ANSWER, so it wears the same tap card. `is-taken` marks the one the call is on —
   a rep coming back to a beat sees which way it went, the same as a ticked box. */
.s2 .s2-now-answer .s2-road { cursor:pointer; font:inherit; text-align:left; }
.s2 .s2-now-answer .s2-road.is-taken { border-width:2.5px; border-color:var(--brand);
    background:#eef2fb; font-weight:700; }
.s2-now-answer { margin:9px 0 2px; padding:9px 11px 7px; border-radius:9px;
    background:#f7f8fc; border:1px solid #e7e9f2; }
.s2-now-answer .ws-f { display:block; margin:0; }
.s2-now-answer .ws-vlabel { display:block; font-size:10.5px; font-weight:800; letter-spacing:.05em;
    text-transform:uppercase; color:#7d8598; margin:0 0 5px; }
/* ⚖️ ONE ROW WHERE ONE ROW FITS — Gil, 2026-09-04, looking at the live screen: *"Should try to make
   the Goals all fit on one row."* The panel column is ~430px and the sheet's tap cards are sized for
   the sheet (44px tall, 15px type, 14px of side padding), so three goals wrapped onto two rows.
   🛑 IT SHRINKS THE CONTROL, IT DOES NOT FORBID WRAPPING. `flex-wrap:nowrap` would put *"Should try"*
   ahead of the truth: `cash_reason` carries far more answers than a 430px column can ever hold on one
   line, and forcing them would squeeze the words to nothing or push a control off the edge. Wrapping
   stays as the fallback; the sizing is what makes the common cases — the three goals, Yes/No/Turned
   down, Subject Yes/No — land on one line.
   ⚠️ 40px, NOT 44px. His tap-card size was ruled for the SHEET, where a rep hunts for the box; in the
   panel the control sits directly under the sentence being read and is the only thing there. */
/* ⚖️ A TYPED BOX AND THE ADDRESS BLOCK IN A 430px COLUMN (2.4 / 2.5). The sheet lays these out for
   a wide card; the panel is one narrow column, so the address restacks — street on its own line,
   then city/state/zip across — which is the shape Gil drew for the sheet in the first place. */
.s2-now-answer .ws-f input, .s2-now-answer .ws-f select, .s2-now-answer .ws-f textarea { width:100%; }
.s2-now-answer .ws-residence-block { margin:0; padding:0; border:0; background:none; }
/* ⚖️ WHICH FIELDS BELONG TO WHICH REASON — Gil, 2026-09-05: *"Is there a way to seperate questions
   to easily see which is for Home impr and which is for debt"*. Each arm the panel carries keeps its
   own heading and sits in its own bordered block, so two reasons picked at once read as two groups
   rather than one run of boxes.
   ⚠️ The residence block's sub-label ("Where they live") stays hidden — it names the block, and the
   beat's own words already say it. An arm's label names a CHOICE, which is the thing being told
   apart here. */
.s2-now-answer .ws-residence-block .ws-sub-label { display:none; }
/* ⚠️ 9px → 6px OF SIDE PADDING, AND IT IS THE DIFFERENCE BETWEEN ONE ROW AND TWO. The same three
   cash reasons fitted with 10px to spare on `CASH1`, where the block sits loose, and wrapped on the
   OPENER, where it sits inside the goal's arm. 337px of answers against 329px of room — the frame
   was spending exactly what the third answer needed. */
.s2-now-answer [data-ws-goal-arm], .s2-now-answer [data-ws-reason-arm] {
    margin:8px 0 0; padding:8px 6px 6px; border:1px solid #e3e7f2; border-radius:9px;
    background:#fff; }
/* 🛑 A NESTED ARM GIVES ITS PADDING BACK, because it is spending width the ANSWERS need. The cash
   reasons sit inside the goal's own arm on the opener — two frames deep — and 36px of side padding
   was enough to push the third answer onto a second row there while the SAME three fitted with 10px
   to spare one beat later. Measured, not guessed: 337px of answers in a 347px space. */
.s2-now-answer [data-ws-goal-arm] [data-ws-reason-arm],
.s2-now-answer [data-ws-goal-arm] [data-ws-goal-arm] {
    padding-left:5px; padding-right:5px; border-left:0; border-right:0; border-radius:0;
    margin-left:0; margin-right:0; }
.s2-now-answer [data-ws-goal-arm] .ws-sub-label,
.s2-now-answer [data-ws-reason-arm] .ws-sub-label {
    display:block; font-size:10.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase;
    color:var(--brand-ink,#35529b); margin:0 0 6px; }
.s2-now-answer .ws-lay-residence { display:flex; flex-wrap:wrap; gap:6px; }
/* 🛑 THE ORDER IS SET HERE, NOT INHERITED. On the sheet the *"is this the home we're working on?"*
   fork sits BESIDE the street (his 09-02 layout, where there is room for two columns); dropped into
   one narrow column in source order it landed BETWEEN the street and the city, splitting one address
   in half. The address reads as an address first — street, then city/state/zip — and the question
   about it comes after, which is also the order the rep says it in. */
.s2-now-answer .ws-lay-residence > .ws-f { flex:1 1 100%; order:2; }
.s2-now-answer .ws-lay-residence > .ws-k-primary_address { order:1; }
.s2-now-answer .ws-lay-residence > .ws-w-city { flex:2 1 120px; }
/* 🛑 MEASURED, NOT GUESSED — Gil, 2026-09-06: *"And i cant see the state."* The box was 52px
   wide carrying 13px of padding on EACH side, so "FL" at 15px needed 63px of room and had 50: the
   L was cut off and the field looked like a typo. The sheet's own state box is fine; this is the
   panel's narrower column, where a padding written for a full-width sheet eats the whole field.
   ⚖️ THE PADDING GIVES WAY BEFORE THE ROW DOES. Widening the basis alone would take the width out of
   City, which is the box with the longest thing to say ("Fort Lauderdale"); trimming the padding on
   the one box that cannot afford it costs nothing anywhere else. Both together leave 44px of text
   room for a two-letter answer that needs 37. */
.s2-now-answer .ws-lay-residence > .ws-w-state { flex:0 1 62px; }
.s2-now-answer .ws-lay-residence > .ws-w-state input { padding-left:8px; padding-right:8px; }
.s2-now-answer .ws-lay-residence > .ws-w-zip { flex:1 1 80px; }
.s2-now-answer .ws-lay-residence > .ws-choice { flex:1 1 100%; order:3; }
.s2-now-answer .ws-choice-opts { display:flex; flex-wrap:wrap; gap:4px; }
/* 🛑 A ROW OF FOUR OR FEWER HOLDS ITS LINE — Gil, 2026-09-05: *"try to always fit selections on as
   little rows as possible. You can have wording go on 2 rows if needed."*
   ⚠️ AND THIS IS WHY SHRINKING ALONE DID NOTHING: a wrapping flex row wraps BEFORE it shrinks, so
   three answers needing 337px in a 333px space put the third on a line of its own rather than each
   giving up two pixels. Told not to wrap, they shrink instead and the words wrap INSIDE the card —
   which is exactly the licence he gave.
   ⚖️ FIVE OR MORE STILL WRAP. Six rate bands squeezed onto one line would be six columns of stacked
   syllables; the rule is *as few rows as possible*, not one row at any cost. */
.s2-now-answer .ws-choice-opts:not(:has(> .ws-choice-opt:nth-child(5))) { flex-wrap:nowrap; }
/* ⚖️ AS FEW ROWS AS THE ANSWERS WILL ALLOW — Gil, 2026-09-05: *"try to always fit selections on as
   little rows as possible. You can have wording go on 2 rows if needed."*
   🛑 `white-space:nowrap` WAS MINE AND IT WAS THE CAUSE. It forced every answer onto one long line,
   so three of them could not share a row however much space was left — "Home improvement" alone took
   more width than half the column. Letting the words wrap inside the card is exactly the licence he
   gave, and it is what makes the row fit.
   ⚠️ The card keeps its height so a wrapped label does not make one answer taller than its
   neighbours, and the tap target stays the size his ruling set. */
/* ⚠️ THEY SHRINK BEFORE THEY WRAP. Letting the words wrap was not enough on its own — three cards
   at their natural width still overflowed a 380px column by ~20px, so the third dropped to a second
   row with the first two half empty beside it. `flex:1 1 auto; min-width:0` lets them give up the
   spare width instead, and the wrap his ruling allows then happens INSIDE a card rather than by
   pushing one out of the row. */
/* ⚖️ THE QUESTION LEADS, THE ANSWERS FOLLOW — Gil, 2026-09-11: *"why are teh selection fonts bigger
   than teh questions. make questions bold and teh selection not bold. lets see how that looks."* The
   answers were 13.5px/700-when-picked against a 13px question, so the row a rep ANSWERS with was
   louder than the line they READ OUT. Answers drop to 12.5px and to normal weight; the spoken line
   goes bold (`.ws-debt-say` below).
   🛑 THE PICKED ANSWER LOSES ITS BOLD AND KEEPS A NON-COLOUR CUE. It is still marked by a HEAVIER
   BORDER (2.5px against 1.5px) as well as the tint — which is what the accessibility note further up
   this file is protecting, since hue alone fails ~45% of adults in their mid-70s. Do not also drop the
   border weight, or the tint becomes the only thing left saying which one is picked. */
/* 🛑 AND THE SIDE PADDING BUYS THE HEADROOM — 8px a side over four answers is 64px of the row spent on
   air, on a panel that had 223px to give. At 6px the four balances answers clear their words with room
   over instead of fitting exactly, so the row survives a word Gil lengthens later. */
.s2 .s2-now-answer .ws-choice-opt { gap:5px; padding:7px 6px; font-size:12.5px; min-height:40px;
    white-space:normal; line-height:1.2; text-align:center; flex:0 1 auto; min-width:0;
    justify-content:center; font-weight:400; }
.s2 .s2-now-answer .ws-choice-opt:has(input:checked) { font-weight:400; }
/* ⚖️ THE ANSWERS FILL THE ROW — Gil, 2026-09-11: *"Yuo have rom to make boxes wider. utilize all teh
   space you have horizontally."* Measured on the panel first: the **What for** row was 332px wide with
   three answers needing 276px, so **56px sat unused** on every call while the words inside were being
   kept narrow.
   🛑 GROWING IS SCOPED TO THE SAME ROWS THAT DO NOT WRAP, and that is the whole safety of it. The note
   above records `flex-grow:1` making things WORSE on 09-05 — two answers grew to fill the row and the
   third had nowhere to go but a line of its own. That failure needs a WRAPPING row; a row of four or
   fewer is told `nowrap` just above, so growing can only hand out slack that was already there. A row
   of five or more still wraps, and is deliberately left shrinking.
   ⚠️ IT CANNOT INVENT SPACE. The balances row needs 380px in 318px, so its longest answer still folds
   onto two lines — which is the trade Gil chose on 09-05 (*"You can have wording go on 2 rows if
   needed"*). This only stops space being wasted where there was some. */
/* ⚖️ EVERY ANSWER GETS ITS OWN WORD FIRST, AND THE LEFT-OVER SPACE IS SHARED — Gil, 2026-09-11:
   *"Not Sure should just be one row, make it fit."*
   🛑 `flex-basis:0` (equal widths) IS WHAT BROKE IT, AND IT LOOKED LIKE THE RIGHT ANSWER TO HIS
   PREVIOUS ASK. Equal widths ignore the content, so on the balances row every answer got 52px while
   "Not Sure" needs 70px on one line — the shortest answer set the width for the longest. MEASURED: the
   four answers need 223px and the row IS 223px; they fit exactly, and only the equal split folded one.
   `flex-basis:auto` starts each answer at its own width and shares any SLACK from there, so a row that
   fits does not fold, and a row that genuinely does not fit still folds — which is the 09-05 licence
   (*"You can have wording go on 2 rows if needed"*) and is what "Home Improvements" still does.
   ⚠️ `align-items:stretch` on the row (set just below) keeps them the SAME HEIGHT when one does fold —
   without it a two-line answer stands taller than its neighbours and the row looks broken.
   🛑 STILL SCOPED TO THE ROWS THAT DO NOT WRAP. A row of five or more wraps, and growing there
   reproduces the 09-05 defect the note above records. */
.s2 .s2-now-answer .ws-choice-opts:not(:has(> .ws-choice-opt:nth-child(5))) > .ws-choice-opt {
    flex:1 1 auto; }
/* 🛑 SHRINK, NEVER GROW — and `flex-grow:1` was mine, and made the wrap WORSE. Measured on the
   opener: the first two answers grew to 171px and 158px, exactly filling the 333px row between
   them, and the third had nowhere to go but a line of its own at full width. Growing is what a
   toolbar wants; a row of answers wants each to take what its words need and give up the rest. */
.s2 .s2-now-answer .ws-choice-opt input { flex:none; }
.s2 .s2-now-answer .ws-choice-opts { align-items:stretch; }
.s2 .s2-now-answer .ws-choice-opt:has(input:checked) { padding:7.5px 8.5px; }
.s2 .s2-now-answer .ws-choice-opt input { width:15px; height:15px; }

/* ⚖️ THE WORK FITS ON TWO ROWS — Gil, 2026-09-10: *"make The work wider (and smaller if needed) to fit
   on 2 rows."* Eight jobs wrapped to three or four rows in the panel's narrow column, which pushed the
   money below the fold on the very beat that asks for it. Four to a row, sized to the column rather than
   to the longest word, so the block is the same height whatever he cuts the list to.
   🛑 A BASIS, NOT A FIXED WIDTH: the panel is `clamp(300px, 30vw, 470px)` and the sheet's own copy of
   this block is wider, so anything absolute is wrong on one of the two surfaces. */
/* ⚖️ NO BLUE PILL ON IT — Gil, 2026-09-10: *"why did you highlight blue Total of Debt?"* The blue is
   this sheet's LIVE-STATE colour: the beat being spoken, the box being filled. A figure that simply
   states a sum is not a state, and dressing it in that blue told a rep something was happening here.
   It reads exactly like the TOTAL at the foot of the card, which is the same kind of thing. */
.ws-debt-sum { display:flex; align-items:baseline; gap:8px; margin:8px 0 2px; }
.ws-debt-sum[hidden] { display:none; }
.ws-debt-sum-cap { font-size:9.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase;
    color:var(--mut); }
.ws-debt-sum-total { font-size:14px; font-weight:800; color:var(--ink); font-variant-numeric:tabular-nums; }

/* ⚖️ AND THE IMPROVEMENTS TOTAL IS DRESSED THE SAME — Gil, same message: *"Make Total of Improvements
   match Total of DEbt layout and font. Again, text and fonts and design should be teh same and make
   sense."* Two totals on one card, one of them a stacked sentence-case label over a full-width box and
   the other an inline small-caps line, read as two different kinds of fact. Same rank, same dress:
   caption inline in small caps, the money at the debt figure's size and weight.
   🛑 IT IS STILL A BOX A REP TYPES IN, and it has to keep saying so — the debt figure is computed and
   this one is not. It keeps its underline and its caret; what it loses is the column layout and the
   full-column width, which is what made it look like a different species. Sized in `ch` so it holds a
   seven-figure number without stretching the row. */
.ws-goal-sub .ws-f[class*="ws-k-cash_amt_home_improvement"] {
    /* 🛑 AND IT MUST BE ALLOWED TO BE AS WIDE AS ITS OWN LINE. The money fields on this sheet carry a
       fixed `flex:0 0 132px` basis — right for a stacked box in a grid of them, and it is what wrapped
       *"TOTAL OF / IMPROVEMENTS"* onto two lines with the figure beneath, measured before this line
       existed. Laid out as one line it sizes to its content. */
    flex:0 1 auto; flex-direction:row; align-items:baseline; gap:8px; flex-wrap:nowrap; }
/* 🛑 THE CAPTION IS NOT ALWAYS A `<span>` ON THIS SHEET, AND THAT IS WHY THE FIRST CUT MISSED IT: a
   captioned field whose fact lives on another card wears a `<button class="ws-jump ws-vlabel">`, so
   the rep can walk to it. Measured on the rendered card — the improvements caption is a BUTTON. */
.ws-goal-sub .ws-f[class*="ws-k-cash_amt_home_improvement"] > :first-child {
    font-size:9.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:var(--mut); }

/* ⚖️ AND IT KEEPS THAT SHAPE UNDER THE WORDS TOO — Gil, 2026-09-10: *"move the $ amount to teh right
   of Total of Improvments, like it is for Total of Debt."* The panel stacks every answer it carries
   (`.s2-now-answer .ws-f { display:block }`), which is right for a box a rep types a sentence into and
   wrong for a total: beside it stands *TOTAL OF DEBT $25,000* on one line, and the same fact in two
   shapes on one screen is the thing he keeps having to point at. */
.s2-now-answer .ws-f[class*="ws-k-cash_amt_home_improvement"] {
    display:flex; flex-direction:row; align-items:baseline; gap:8px; flex-wrap:nowrap; }
.s2-now-answer .ws-f[class*="ws-k-cash_amt_home_improvement"] > :first-child {
    display:inline; flex:0 0 auto; margin:0;
    font-size:9.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:var(--mut); }
.s2-now-answer .ws-f[class*="ws-k-cash_amt_home_improvement"] > .sr-adorn {
    width:auto; flex:0 1 auto; border:0; background:none; border-bottom:1px solid var(--line);
    border-radius:0; padding:0; }
.s2-now-answer .ws-f[class*="ws-k-cash_amt_home_improvement"] > .sr-adorn > .sr-fix {
    font-size:14px; font-weight:800; color:var(--mut); padding:0 1px 0 0; }
.s2-now-answer .ws-f[class*="ws-k-cash_amt_home_improvement"] > .sr-adorn > input[type=text] {
    width:9ch; min-width:0; border:0; background:none; padding:1px 0; text-align:left;
    font-size:14px; font-weight:800; color:var(--ink); font-variant-numeric:tabular-nums; }

/* ⚖️ AND EVERY CAPTION ON THIS CARD SITS ON THAT SAME RANK — Gil, 2026-09-10: *"organize Prequal
   Goals section better"*, after *"text and fonts and design should be teh same and make sense."*
   *Goal*, *What for*, *Total of Improvements* and *More info* all do one job — they NAME a control —
   and were arriving in three dresses (a 11.5px button, a 12px span, a small-caps line), which reads
   as three different kinds of thing on one card.
   🛑 A RANK ABOVE THEM STAYS A RANK ABOVE THEM. `.ws-sub-label` — CASH OUT, HOME IMPROVEMENT, DEBT
   CONSOLIDATION — is a SECTION heading, not a caption, and keeps its larger size so the card still
   reads as headings with fields under them rather than one flat list of shouting labels.
   ⚠️ Scoped to `.ws-lay-goal`, which travels with the block into the words panel, so the card looks
   the same wherever a rep is standing. */
.ws-lay-goal > .ws-f > .ws-vlabel,
.ws-lay-goal > .ws-f > button.ws-jump,
.ws-lay-goal > .ws-f > span:first-child,
/* ⚠️ AND THE NOTE BOX, WHICH IS NOT IN THAT ROW. *More info* sits in the card's own `.ws-grid`
   beneath the goal layout, so a rule scoped to `.ws-lay-goal` cannot see it — and it was the one
   caption left in sentence case on a card where everything else had lined up. */
.ws-section > .ws-grid > .ws-f.ws-k-goal_note > span:first-child {
    font-size:9.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:var(--mut); }
.ws-goal-sub .ws-f[class*="ws-k-cash_amt_home_improvement"] > .sr-adorn {
    width:auto; flex:0 0 auto; border:0; background:none; border-bottom:1px solid var(--line);
    border-radius:0; padding:0; }
.ws-goal-sub .ws-f[class*="ws-k-cash_amt_home_improvement"] > .sr-adorn > .sr-fix {
    font-size:14px; font-weight:800; color:var(--mut); padding:0 1px 0 0; }
.ws-goal-sub .ws-f[class*="ws-k-cash_amt_home_improvement"] > .sr-adorn > input[type=text] {
    width:9ch; min-width:0; border:0; background:none; padding:1px 0; text-align:left;
    font-size:14px; font-weight:800; color:var(--ink); font-variant-numeric:tabular-nums; }
/* ⚖️ ONE LADDER FOR THE WHOLE CARD — Gil, 2026-09-10: *"we need to organize teh bold, font size and
   style to make sense."* Four ranks, and nothing may sit between them:
     1. SECTION      `.ws-sub-label`      11px / 700 / uppercase / muted   — HOME IMPROVEMENT
     2. SPOKEN LINE  `.ws-debt-say`       13px / 600 / ink                 — what the rep reads aloud
     3. CAPTION      `.ws-vlabel`, `-cap`  9.5px / 800 / uppercase / muted — names a number
     4. MONEY        `-total`, the inputs  14px / 800 / ink / tabular      — the number itself
   The card had rank 3 in two different dresses: the money captions inside these arms were sentence-case
   11.5px while the TOTAL beneath them was an uppercase 9.5px cap, so two labels doing the same job read
   as different kinds of thing. Scoped to the cash-out arms so it cannot reach the sheet's long labels
   elsewhere, where upper case would shout. */
.ws-goal-sub .ws-lay-goal > .ws-f > .ws-vlabel,
.ws-goal-sub .ws-lay-goal > .ws-f > .ws-jump.ws-vlabel {
    font-size:9.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:var(--mut); }

/* ⚖️ BOLD, AND BIGGER THAN THE ANSWERS UNDER IT — Gil, 2026-09-11: *"make questions bold and teh
   selection not bold."* This is the sentence a rep says out loud; the buttons are how they record what
   comes back. The band was inverted — the answers were the heavier of the two. */
.ws-debt-say { margin:6px 0 4px; font-size:13.5px; line-height:1.5; font-weight:700; color:var(--brand-ink);
    max-width:62ch; }
/* The quote mark is furniture: it says "read this out", it is not part of the sentence, so it does not
   carry the sentence's weight. */
.ws-say-mark { color:var(--brand); font-weight:700; margin-right:5px; opacity:.75; }
.ws-debt-jump { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
/* ⚖️ THE TOTAL LEADS THE LINE AND THE DOOR SITS AT ITS END — Gil put the figure where the instruction
   sentence had been (2026-09-10), so the row is now a number and a way to change it, not prose. Its own
   vertical margin comes off inside the row; the row already spaces itself. */
.ws-debt-jump > .ws-debt-sum { margin:0; flex:0 1 auto; }
.ws-debt-jump > .muted { font-size:11.5px; line-height:1.45; flex:1 1 200px; min-width:0; }
.ws-b-jump { appearance:none; border:1px solid var(--brand); background:#eef2fb; color:var(--brand);
    border-radius:999px; padding:5px 12px; font:inherit; font-size:12px; font-weight:800; cursor:pointer;
    white-space:nowrap; }
.ws-b-jump:hover { background:var(--brand); color:#fff; }
/* ⚖️ ONE QUESTION AT A TIME — R-146, Gil 2026-09-10: *"Can we make all teh debt questions jsut show one
   at a time right in this section."* The steps take turns in the same place, so the block must not jump
   in height as they swap: a minimum keeps the card still while the rep reads.
   🛑 TWO BUTTONS, TWO RANKS, AND THAT IS THE WHOLE OF THE DRESS. "Next question" is the way FORWARD and
   is filled; "Go to Debts" beside it is a detour and stays outlined; "Back to the debts" is a
   correction and is a quiet text link. Three controls in one dress would read as three equal choices. */
.ws-debt-step[hidden] { display:none; }
/* ⚖️ CENTRED, AND SMALLER — Gil, 2026-09-11: *"Center Continue and make smaller."* The way on and the
   way back stack down the middle of the block, so the eye finds them in one place rather than tracking
   to a left edge; and the pill stops shouting now that it is the only one on the card. */
/* ⚖️ AND THE BLOCK GIVES ITS WIDTH BACK TO THE ANSWERS — same ask, 2026-09-11. The Debt-consolidation
   arm sits three boxes deep inside the panel (`.s2-lines` → `.s2-rail` → `.s2-now-answer` → the arm),
   and each one was charging its own horizontal padding: the answers ended up in 318px of a 450px panel.
   Trimmed where it is pure indent and nothing reads as a margin. Measured, not guessed. */
.s2 .s2-now-answer .ws-goal-arm.ws-goal-sub { padding-left:3px; padding-right:3px; }
.s2 .s2-now-answer { padding-left:6px; padding-right:6px; }

/* ⚖️ ONE ROW: THE WAY BACK PINNED LEFT, THE WAY ON IN THE MIDDLE — Gil, 2026-09-11: *"move teh Previous
   question to same row as Continue. Put it all the way to the left side. and make smaller."*
   🛑 A THREE-COLUMN GRID, NOT `space-between`. Continue has to sit in the CENTRE of the block (his
   earlier ask); `space-between` only centres it while the control beside it happens to be a particular
   width, so it would drift the day the back link is reworded. The empty third column holds the middle
   still whatever the first one says. */
.ws-debt-go { margin:8px 0 2px; text-align:center;
    display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:6px; }
.ws-debt-go > .ws-b-step-back { justify-self:start; }
/* The lone way back, standing in before the answer lands — no Continue to centre against, so it keeps
   the left edge its twin above has. */
.ws-debt-go-back { margin:2px 0 0; display:block; text-align:left; }
.ws-debt-go[hidden], .ws-debt-go-back[hidden] { display:none; }
.ws-b-step { appearance:none; border:1px solid var(--brand); background:var(--brand); color:#fff;
    border-radius:999px; padding:4px 12px; font:inherit; font-size:11.5px; font-weight:800; cursor:pointer;
    white-space:nowrap; }
.ws-b-step:hover { filter:brightness(1.08); }
.ws-b-step-back { appearance:none; border:0; background:none; color:var(--mut); padding:2px 0;
    font:inherit; font-size:10.5px; font-weight:700; cursor:pointer; white-space:nowrap; }
.ws-b-step-back:hover { color:var(--brand); text-decoration:underline; }
/* ⚖️ ONE BUBBLE PER BLOCK — Gil, 2026-09-11: *"Too many bubbles… You need to stop loving putting
   everything in a bubble. Change Go to Debts to something else and move it to the right edge and a
   little smaller."* A pill reads as "press this to go on". Two pills on one line say it twice, and the
   rep has to read both to find out which one continues the call. So the DETOUR loses its pill, shrinks,
   and sits at the right edge where a side door belongs; the one filled pill left is the way forward. */
.ws-b-quiet { appearance:none; border:0; background:none; color:var(--brand); padding:2px 0;
    font:inherit; font-size:11.5px; font-weight:700; cursor:pointer; white-space:nowrap;
    margin-left:auto; }
.ws-b-quiet:hover { text-decoration:underline; }
/* ⚖️ THE BUILD-OUT WIPE LOOKS LIKE WHAT IT IS — Gil, 2026-09-12, asked for it *"only for build out"*, so
   it must never read as part of the call. Quiet, outlined, and deliberately NOT `--danger`: design-system
   rule 13 reserves the alarm for a compliance VERDICT and bars it from a live-call surface. It sits to
   the right of the two controls a rep actually uses, so the eye reaches it last. */
.ws-b-wipe { background:none; border:1px solid #d8c48f; color:#8a6100; font-weight:700;
    font-size:12px; margin-left:auto; }
.ws-b-wipe:hover { background:#fdf7e2; }
/* The row is now "a number, then space, then a side door" — the total keeps the left edge it was
   moved to and the link is pushed out by its own `margin-left:auto`, so nothing depends on the count
   of things on the line. */
.ws-debt-jump > .ws-b-quiet { flex:0 0 auto; }
.ws-debt-go[hidden] { display:none; }
.ws-f.ws-k-cash_hi_work > .ws-choice-opts,
.ws-f[class*="ws-k-cash_hi_work"] > .ws-choice-opts { flex-wrap: wrap; gap: 5px; }
/* ⚖️ EIGHT JOBS, TWO ROWS, WHOLE WORDS — Gil, 2026-09-10, in three passes: *"make The work wider (and
   smaller if needed) to fit on 2 rows"*, then *"make the works wider"* when a quarter-column clipped
   them to "Windo" and "Kitche", then *"make the work buttons all fit on 2 rows"*.
   🛑 A GRID, NOT WRAPPING FLEX, AND THAT IS THE WHOLE FIX. Flex decides each row by what fits, so the
   count per row changed with the longest word and the block reflowed to three rows or four. A four-column
   grid is told the shape: eight cells, two rows, every column the same width whatever the words are.
   🛑 AND THE TEXT SHRINKS BEFORE THE BUTTON DOES. `clamp` on the font and a tight gutter buy the space
   in the narrow panel, so "Windows" stays "Windows" — a clipped label is a button a rep cannot read,
   which is worse than the third row he was trying to get rid of. */
.ws-f[class*="ws-k-cash_hi_work"] > .ws-choice-opts {
    /* ⚖️ THE ROW SPANS THE COLUMN — Gil, 2026-09-10: *"'The work' options need to spread across all the
       way to right."* The grid was already four across, but `.ws-choice` lays its parent out as a FLEX
       ROW (label beside options), so the options box was sized to its content and sat squeezed against
       the left with the four columns sharing whatever that came to — which is also what kept clipping
       the words. Told to take the whole width, the four columns are a quarter of the card each. */
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 5px;
    width: 100%; flex: 1 1 100%; min-width: 0;
}
/* 🛑 AND THE FIELD ITSELF TAKES THE WHOLE ROW. `.ws-lay-goal` is a WRAPPING FLEX row, so a field sits
   at its natural width beside its neighbours — the block was full-width INSIDE a box that was not.
   Measured after Gil's *"still not going all teh way across"*: the grid was right and its container was
   the wrong size, one level further out than I had looked. */
/* ⚖️ …and the caption row collapses with it (Gil 2026-09-10: *"you can remove 'Teh Work' also"*), so an
   empty label leaves no gap above the buttons. */
.ws-f[class*="ws-k-cash_hi_work"] > .ws-vlabel:empty,
.ws-f[class*="ws-k-cash_hi_work"] > .ws-vlabel:blank { display: none; }
.ws-f[class*="ws-k-cash_hi_work"] { display: block; flex: 1 1 100%; width: 100%; min-width: 0; }
.ws-f[class*="ws-k-cash_hi_work"] > .ws-choice-opts > .ws-choice-opt {
    min-width: 0; justify-content: flex-start; white-space: nowrap;
}
.s2 .s2-now-answer .ws-f[class*="ws-k-cash_hi_work"] > .ws-choice-opts {
    gap: 4px;
}
.s2 .s2-now-answer .ws-f[class*="ws-k-cash_hi_work"] > .ws-choice-opts > .ws-choice-opt {
    padding: 6px 5px; min-height: 34px; gap: 4px;
    font-size: clamp(10px, 1.6vw, 12.5px);
}
.s2 .s2-now-answer .ws-f[class*="ws-k-cash_hi_work"] > .ws-choice-opts > .ws-choice-opt input {
    width: 12px; height: 12px; border-width: 1.5px;
}
.s2 .s2-now-answer .ws-f[class*="ws-k-cash_hi_work"] > .ws-choice-opts > .ws-choice-opt:has(input:checked) {
    padding: 5.5px 4.5px;
}

/* ⚖️ NO BLUE BOX ROUND THE ANSWER — Gil, 2026-09-10: *"remove teh outlinign and highlighting of blue"*.
   `.s2-here` and `.s2-also` are the script cursor's marks, and on a field they painted a filled teal
   panel with a heavy outline around the very block a rep is reading — loud enough that it read as the
   thing being wrong rather than the thing being asked.
   🛑 THE MARK SURVIVES WHERE IT EARNS ITS KEEP: the script LINE still highlights (that is `.s2-rail`,
   and he asked for it), and a CELL elsewhere on the sheet still takes the ring. Only the field blocks
   on this card stop painting. */
.ws-f.s2-here, .ws-f.s2-also,
.ws-f[class*="ws-k-cash_hi_work"].s2-here, .ws-f[class*="ws-k-cash_hi_work"].s2-also,
.ws-f.ws-k-cash_reason.s2-here, .ws-f.ws-k-cash_reason.s2-also,
.ws-f.ws-k-goal.s2-here, .ws-f.ws-k-goal.s2-also {
    outline: 0 !important; background: none !important; box-shadow: none !important;
}

/* ── AHEAD: the rest of the call, as stages ── ⚖️ *"i dont think its bad to show them how many left."* */
.s2-outline { margin:10px 2px 4px; border-top:1px solid #e7e9f2; padding-top:7px; }
.s2-outline:empty { display:none; }
.s2-outline-row { display:flex; align-items:center; gap:9px; padding:5px 7px; border-radius:7px;
    font-size:12.5px; color:#5f6a80; cursor:pointer; }
.s2-outline-row:hover { background:#eef2fb; }
.s2-outline-chev { color:#b5bccb; font-size:10px; width:10px; flex:none; }
.s2-outline-name { flex:1; font-weight:700; letter-spacing:.02em; }
.s2-outline-n { color:#9aa2b4; font-size:12px; font-variant-numeric:tabular-nums; }
/* ⚖️ THE WAY BACK — see the markup note in `_script2-sheet.blade.php`. It sits at the head of the
   list and it is QUIET: it is not a line to read, so it must not compete with the card that is.
   🛑 IT CARRIES NO SPOKEN WORDS AT ALL, so nothing here can be read to a customer by accident — which
   is why it may sit this close to speech when a filing label may not. */
/* 🛑 IT STICKS TO THE TOP OF THE PANEL, AND THAT IS NOT POLISH — IT IS THE WHOLE CONTROL. Drawn as an
   ordinary first child it scrolls away with the list: a jump scrolls the panel to the line it landed
   on, so the marker was off-screen at exactly the moment it existed to be used. **A way back you
   cannot see is not a way back.** Caught by LOOKING at the rendered screen — every DOM check said it
   was present and not hidden, and all of them were true and useless.
   ⚠️ The sticky ancestor is `.s2-panel` (the `overflow-y:auto` box), not `.s2-lines`. */
.s2-wayback { position:sticky; top:0; z-index:1;
    display:flex; align-items:center; gap:7px; width:100%;
    margin:2px 0 8px; padding:6px 9px; cursor:pointer; text-align:left;
    background:var(--card); border:1px solid #c9d2ee; border-left:3px solid var(--brand);
    border-radius:7px; box-shadow:0 1px 3px rgba(30,35,51,.07);
    font-family:inherit; }
.s2-wayback[hidden] { display:none; }
.s2-wayback:hover { background:#f4f6ff; border-color:var(--brand); }
.s2-wb-ic { font-size:12px; font-weight:800; color:var(--brand); line-height:1; }
.s2-wb-tx { font-size:11px; font-weight:600; color:#5b5876; line-height:1.35; }
.s2-rail.is-here { background:var(--card); border-left:4px solid var(--brand);
    margin:9px -12px; padding:13px 13px 14px 14px; border-radius:0 10px 10px 0;
    box-shadow:0 4px 16px rgba(30,35,51,.13); border-bottom:0; }
/* The card carries its own separation, so the hairline that divides list rows would draw a line
   across it. */
.s2-rail.is-here + .s2-rail { border-top:0; }
.s2-rail.is-here .s2-say { font-size:15.5px; line-height:1.62; font-weight:650; color:var(--ink,#1e2333); }
.s2-panel-cap { font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em;
    color:#9a97ab; margin:9px 0 2px; }
/* 🛑 600, NOT 700, AND IT IS A READING DECISION. The open row is already told apart FOUR ways — a
   tint, a 3px brand rule, its own indent, and being the only row with words at all — so the weight
   was a fifth signal doing no work, and a 350-character opener set in bold is ten solid lines a rep
   has to read ALOUD. Weight is for the word you stress, not the paragraph. The line-height goes up
   with it: a rep's eye has to find the start of the next line without losing the sentence. */
/* 🛑 AND THE MEASURE IS CAPPED, WHICH ONLY BITES WHEN THE PANEL IS WIDE. Below 1180px the layout
   stacks and the panel takes the full width — measured at 1100px, the spoken line ran to 116
   CHARACTERS per line. That is roughly double the width an eye can track, and the failure mode is
   the specific one that matters here: on the return sweep a rep loses which line they were on,
   mid-sentence, out loud. The side-by-side column is ~300px so this changes nothing there. */
.s2-say { margin:0 0 6px; font-size:14px; line-height:1.6; font-weight:600; color:var(--brand-ink);
    max-width:62ch; }
/* THE SPOKEN LINE GROWS WITH ITS COLUMN, 2026-09-01.
   Gil, after reading his own opener back at four sizes: "it def needs to be bigger than what it is
   now." It was 12px — the size of a caption, on the one run of text in this product that a human
   READS ALOUD to another human while looking away from the screen and back again.
   THE SIZE IS FLUID BECAUSE THE COLUMN IS. `.s2-panel` is `clamp(300px, 30vw, 470px)`, so a single
   fixed size is wrong at one end by construction: measured on his real opener (345 characters, the
   longest line in the call), 20.5px runs 8 lines at the wide end and 12 at the narrow one. This
   tracks the same 30vw the panel does — 16px when the column is at its 300px floor, 20px when it
   reaches 470px — so the line is as large as the space honestly allows and never more.
   THE FAMILY IS DELIBERATELY UNCHANGED. The render he was reading sets this in a serif, and he
   asked for BIGGER, not for a different face. A typeface change is loud, it was not requested, and
   it can be made in one line once he has looked at this on a live call. */
.s2-line { margin:0; font-size:clamp(16px, 8.94px + 0.706vw, 20px); line-height:1.5; color:#3a3552; }
.s2-ob { margin:6px 0 0; font-size:11.5px; font-weight:700; color:var(--brand); }
/* ⚖️ THE TIE-DOWN — the beat's SECOND spoken line, what the rep says once the customer has
   answered. Drawn since 2026-08-19; before that the panel showed none of the 16 the live tree
   holds. 🛑 IT IS SPEECH AND IT LOOKS LIKE SPEECH: same family as `.s2-say`, quoted the same way,
   because dressing it as coaching is how "If they hesitate" and "Listen for" got struck off — a
   rep reads what is on the row.
   ⚠️ SET APART BY A RULE, NOT BY A CAPTION. It answers a reply that has not happened yet, so
   reading it straight on would ask a question nobody has answered; the divider is that beat of
   separation. A label would break his signpost-or-sentence rule. Slightly smaller and lighter than
   the opening line for the same reason the look-ahead is — legible when looked at, never mistaken
   for the sentence being said right now. */
.s2-tie { margin:7px 0 0; padding-top:7px; border-top:1px dashed #cfd6ea;
    font-size:13px; line-height:1.55; font-weight:500; color:#4a4766; max-width:62ch; }

/* 🛑 …AND IT IS NOT ALWAYS THE LINE TO SAY. Eleven of the fifteen tie-downs on the live call are
   gated in the author's own tree (`tie_down_when`) and this panel drew every one of them at full
   strength — `SHOP1`'s *"Oh, that's great — smart to shop it"* on ROW 2 OF EVERY CALL including to a
   customer who said nobody, and `PQ3D`'s *"you've got the MIP on there"* to a conventional borrower.
   OPEN-ITEMS 2250.
   ⚖️ DIMMED AND LABELLED, NEVER HIDDEN — exactly the contrast he approved for the look-ahead:
   *legible when looked at, never mistakable for the line being said*. Hiding it is OPEN-ITEMS 2236
   coming back (words a rep never sees), and this panel has already paid for that once.
   🛑 THE MARK IS NOT THE CAPTION HIS SIGNPOST-OR-SENTENCE RULING STRUCK. That ruling is about a
   row's NAME sitting over speakable words, which a rep could read out; *"only if FHA"* is the
   condition under which the words below are true at all. It is deliberately un-quoted, lower-case
   and small so nothing about it reads as script.
   ⚠️ IT CLEARS ITSELF the moment the answering box holds that answer (the JS in
   `_script2-sheet.blade.php`), so on the road the line WAS written for the rep sees it plain. */
/* ⚖️ AN UNEARNED TIE-DOWN IS NOT THERE AT ALL — Gil, 2026-09-11: *"❝ Oh, that's great — smart to shop
   it, any big decision you should. Who are you working with?, should not appear until Yes or Turned
   down."* It was drawn at 62% with an "only if Yes" cap above it, which is still a sentence on the
   screen: a rep reads it, and on a live call a half-visible line is a line they may say.
   🛑 SAME RULING HE MADE TWICE THE SAME DAY — the *What work* box (*"it should only appear if we select
   other"*) and the rent box before it on 08-10, which used to grey on a primary residence and now
   simply is not there. Greying is for a question that is MOOT; a question not yet EARNED has not been
   asked at all.
   ⚠️ IT IS RENDERED AND HIDDEN, NEVER RENDERED-IF: `promoteTieDowns` un-hides it the moment the answer
   lands, and the sheet does not reload mid-call — a line that only existed once the SERVER had seen the
   answer would arrive on the next call, not this one. */
.s2-tie.s2-tie-if { display:none; }
/* ⚠️ NO `text-transform` — THE CONDITION IS THE AUTHOR'S OWN ANSWER TEXT. Lower-casing it turned
   *"only if FHA"* into *"only if fha"* and *"680 or better"* would have gone the same way: the words
   after "only if" are the branch's `answerMatch`, which is what the rep sees on the sheet, and a
   rep matching the mark against the button they are about to click needs them to be the same string.
   Small, italic and grey is enough to say "this is not the line". */
.s2-tie-when { display:block; font-size:10px; font-weight:700; letter-spacing:.04em;
    color:#8f8ca1; margin:0 0 3px; font-style:italic; }

/* ⚰️ "WHAT IS COMING" IS RETIRED (2026-08-16) — its rules deleted with it rather than left to rot.
   It existed because the panel showed exactly ONE question and a rep could not see what followed
   (Gil 2026-08-14, given with the pin). His *"full script"* the same week made it redundant: the next
   beat is now literally the next row, so the block printed the same sentence twice a few inches
   apart — and on a live-call surface the duplicate is the one somebody reads aloud.
   🛑 ITS SAFETY ARGUMENT SURVIVED THE FEATURE, and that is why this note stays. It said a rep must
   never mistake a preview for the line they are on, so it kept the two a visual mile apart. The
   collapsed rows above carry that same rule further: a closed question shows its LABEL and never its
   words, so there is only ever ONE sayable sentence on the panel at a time.
   ⚠️ `TheComingUpNextLineIsTheNextBeatsOwnWordsTest` went with it — it guarded a second spoken line
   against being interpolated, truncated, or degraded into a field label. There is no second spoken
   line now. If one is ever added back, that guard is what to restore first. */

/* Under 1180px the debt blocks go one-up and the two halves stack — the page may never pan sideways
   (design rule 7), and two 5-column tables side by side cannot hold below that. */
@media (max-width:1180px) {
    /* 🛑 STRETCH, NOT THE ROW'S flex-start. `.s2` aligns its items to the start, which is a VERTICAL
       rule while the container is a row and costs nothing — but in a COLUMN it becomes the width
       rule, and a shrink-to-fit `.s2-sheet` takes its max-content width. Measured on the running
       screen before this line existed: at a 1000px viewport the sheet drew 1651px and the document
       1767px, so the page panned 767px sideways — the exact thing the note above forbids, in the
       block that claims to enforce it. */
    .s2 { flex-direction:column; align-items:stretch; }
    .s2-row { flex-wrap:wrap; }

    /* ⚖️ THE SCRIPT STAYS PINNED ON THE SCRIPT TAB AT EVERY WIDTH — Gil, 2026-08-14: *"pin the
       script on script tab, not prequal tab"*, following his *"we need to always see the script
       anyway"*. Scoped to `.s2`, so the standalone Prequal tab is untouched, which is the half of
       his sentence that says where the pin does NOT belong.

       WHAT THIS REPLACED, AND WHY IT MOVED: `.s2-panel { flex:1 1 auto; max-width:none; width:100%;
       position:static; }`. Under it the panel dropped below the WHOLE sheet and lost its pin —
       measured at a 1000px viewport, panel top 2844px against sheet top 87px, i.e. 2,757px down and
       off the screen for the entire call. (Two thirds of that rule was already dead anyway: the
       `flex`/`max-width` pair is overridden further down this file by the ORDER-OF-SURRENDER clamp,
       which is why the panel still measured 340px wide while stacked. Only `position:static` bit.)

       🛑 THE SELECTOR IS `.s2 > .s2-panel` (0,2,0) ON PURPOSE — a bare `.s2-panel` here loses to that
       same later clamp rule, exactly as the rule this replaced did. Do not flatten it.

       ORDER, NOT POSITION: the panel moves to the HEAD of the column and stays sticky. A sticky
       element that is the LAST child of a column has nowhere to travel — it can only come to rest at
       the bottom, which is the failure above stated in one sentence. At the head it pins under the
       phone strip and the worksheet scrolls beneath it.

       AND IT IS A STRIP, NOT A BLOCK: capped height with its own scroll, so a long rail can never
       take the screen from the worksheet. That is the same order of surrender the wide layout states
       in flex (*"the Prequal side as wide as possible… the script sacrifices first"*) — here the
       script gives up HEIGHT rather than width, because height is what it costs when stacked.

       ⚠️ `max-height` AND NOT A FIXED HEIGHT, AND THE REFLOW RULE SURVIVES IT — CHECKED, NOT ASSUMED.
       `.lead-pin` above rightly forbids a stuck element whose height changes, because that moves
       every row below it. This panel's height DOES change (each rail is a different length: 76px to
       229px on lead 1 alone). It is safe here only because the panel sits ABOVE the viewport when
       stuck, so the browser's own scroll anchoring absorbs the delta. Measured on the running screen
       at a 1000px viewport, scrolled to 1400: 23 rail swaps changed the panel's height, the sheet's
       document position moved 148px on the largest of them, and the probe element's VIEWPORT position
       was 558px before and 558px after — nothing a rep is looking at moved. If a future change puts
       `overflow-anchor:none` on this page or on any ancestor, that guarantee is gone and this must
       become a fixed height. */
    .s2 > .s2-panel { order:-1; flex:0 0 auto; width:auto; max-width:none; min-width:0;
        position:sticky; top:var(--ph-strip-h, 44px); z-index:var(--z-sticky-inner);
        max-height:min(320px, 34vh); overflow-y:auto; }

    /* 🛑 AND THE LTV BAR STICKS BELOW IT, NOT BEHIND IT. `.ws-ltv` is his *"somewhere prominent…
       even when we are on the top of prequal"* and is itself sticky at `--ph-strip-h`; two sticky
       bars at one offset means one of them is invisible, and neither of his rulings may eat the
       other. The panel's height is not a constant (each rail is a different length), so it publishes
       its MEASURED height as `--s2-panel-h` — the same shape `.ph-strip` uses for `--ph-strip-h`.
       ⚠️ THE PUBLISHER IS EVENT-DRIVEN, NOT ONLY OBSERVED: see the note in `_script2-sheet.blade.php`
       — a ResizeObserver alone is dead in a background tab, which is a tab a rep leaves open all day.
       The 0px fallback simply restores the old shared offset if none of it runs. `.s2-sheet` scopes
       this to the Script v2 surface: the Prequal tab has no `.s2-*` wrapper, so its own LTV bar keeps
       the offset it has today. */
    .s2-sheet .ws-ltv { top:calc(var(--ph-strip-h, 44px) + var(--s2-panel-h, 0px) + 9px); }

    /* 🛑 AND TABBING MAY NOT PARK A BOX UNDERNEATH THE PIN. A rep moves through this sheet on the
       keyboard; the browser scrolls a focused field only just into view, which with two pinned bars
       above means "just into view" can be behind both of them — the rep would then be typing into a
       box they cannot see, which is the pin covering the worksheet by another route. The reserve is
       the two pinned bars plus their gaps: `--ph-strip-h` + the panel's measured height + 57px,
       which is the LTV bar's own 39px, the 9px gap set above it, and 9px of air. */
    .s2-sheet input, .s2-sheet select, .s2-sheet textarea {
        scroll-margin-top:calc(var(--ph-strip-h, 44px) + var(--s2-panel-h, 0px) + 57px);
    }
}

/* ═══ THE SLIM LEAD CHROME — Script v2's tab only (`body.lead-slim`), owner 2026-08-08 ═══
   *"right now, we can make that where it's thinner so everything pushes up further up. And the
   headers overview prequal, the play, script, and note, somehow make that maybe thinner."*

   🛑 SCOPED TO A BODY FLAG, NOT APPLIED TO THE HEADER ITSELF. The identity row and the tabstrip are
   shared by all five tabs and PIN ON SCROLL to his 2026-08-04 ruling; compacting them globally
   would re-rule a header he has already had measured twice (189 → 149 → 139px) and would move every
   other screen he did not ask about. Measured: ~150px of chrome → ~96px.
   ⚠️ GEOMETRY ONLY — nothing is hidden and nothing moves position; the tabs keep a hittable target. */
body.lead-slim .page-head.lead-head { padding:0 18px 8px; border-top-width:4px; }
body.lead-slim .lead-head h1 { font-size:22px; }
body.lead-slim .lead-headtop { margin:0; }
body.lead-slim .tabstrip .tab { padding:7px 13px; font-size:13px; }
body.lead-slim .lead-head .stats { margin-top:6px; }
/* ── S3: THE SHEET IS TYPEABLE. A cell's box wears the cell's own look, so nothing on the screen
   moves when it becomes editable — the amber wash, the underline and the density are the same
   rules; only the element changed. ── */
/* 🛑 `input[type]` IS WHY THESE WIN, AND THE CODEBASE HAS ALREADY PAID FOR LEARNING IT ONCE. The
   shared typed-field box is `input[type=text], …` at (0,1,1); a bare `.s2-val` is (0,1,0) and LOSES,
   so every box on this sheet drew the standard 10px-radius, 11px-padded white form box — the whole
   worksheet's density gone, on a screen whose entire point is fitting on one monitor. Same trap,
   same fix, as `input[type].gcs-input` above. Caught by LOOKING at the built page: the suite was
   green and the CSS was in the file. */
input[type].s2-val { background:transparent; border:0; border-bottom:1.5px solid #cfcfe0;
    border-radius:0; padding:1px 2px 2px; font:inherit; font-size:12.5px; font-weight:600;
    color:var(--brand-ink); width:100%; min-width:74px; height:auto; box-shadow:none; }
input[type].s2-val.is-missing { background:#fdf7e2; color:#b8860b; }
input[type].s2-val:focus, input[type].s2-in:focus { outline:none; border-bottom-color:var(--brand);
    box-shadow:0 2px 0 -1px var(--brand); }
input[type].s2-val::placeholder { color:#b8a76a; font-style:italic; font-weight:500; }
/* Grid boxes: no chrome at all — the table's own lines are the structure, and a box per cell would
   draw seventy borders on the mortgage row alone. */
input[type].s2-in { width:100%; min-width:40px; background:transparent; border:0; border-radius:0;
    padding:1px 2px; font:inherit; font-size:11.5px; font-weight:600; color:var(--brand-ink);
    text-align:center; height:auto; box-shadow:none; }
input[type].s2-in-name { text-align:left; font-size:11px; }
input[type].s2-in::placeholder { color:#c9c6d6; font-weight:500; }
/* ⚖️ THE DEBT CELLS ARE BOXES — ⚖️ R-028, Gil 2026-08-19: *"The rest of prequal is boxes this is lines to add
   in value."* 🛑 THIS OVERRULES THE UNDERLINE, AND THE HISTORY IS THE POINT: he asked for borders on
   2026-08-12 (*"yes add the borders"*), and what shipped was an UNDERLINE chosen for density and to
   match `.s2-val` next door. He looked at it a week later and named the gap himself — the debt grid
   was the one place on the prequal a rep types into a LINE while every other answer on the sheet sits
   in a box. Density was never his complaint; the odd-one-out was.

   🛑 STILL SCOPED TO THE DEBTS TABLE. The `no chrome at all` rule above is deliberate and its reason
   still holds — *"a box per cell would draw seventy borders on the mortgage row alone"* — so this
   names `.s2-debts-one` rather than relaxing `.s2-in`. The debts table has four cells across.
   ⚠️ THE ROOM CAME FROM THE WRAPPER, NOT FROM THE TYPE. Measured 2026-08-19 at a 1440 viewport: the
   table was rendering 603px wide inside a 1149px card, because `.s2-debts` was still the two-column
   grid the FOUR-block layout needed and has held a single table since 08-11. Boxes at half width
   would have been the cramped thing the underline was avoiding; at full width they fit. */
.s2-debts-one input[type].s2-in {
    border:1px solid #d7d5e4; border-radius:6px; background:#fff;
    padding:3px 6px; text-align:center;
}
.s2-debts-one input[type].s2-in:focus {
    border-color:var(--brand); box-shadow:0 0 0 2px rgba(53,82,155,.13); outline:none;
}
.s2-debts-one input[type].s2-in-name { text-align:left; padding-left:7px; }
/* 🛑 THE AMBER MOVES OFF THE CELL AND ONTO THE BOX, and it has to: a cell tinted behind a white
   box would draw a band of amber around a box that reads as answered, which is the opposite of what
   *"still needed"* means. Same colour, same meaning, now on the control it is about — no third
   highlight is introduced (blue `.s2-here` still means "the script is pointing here"). */
.s2-debts-one td.is-missing { background:transparent; }
.s2-debts-one td.is-missing input[type].s2-in { background:#fdf7e2; border-color:#e6d49b; }
/* 🛑 "None" SILENCES THE AMBER, and until now it silenced only the two boxes UNDER the table. The
   tick already sets `data-implied` on `[data-ws-debts-body]` — both server-side and live from
   `_worksheet`'s own sync — and `.ws-f[data-implied] input` greys the foot; nothing had ever been
   written for the table itself, so a debt-free file ticked None and still faced thirteen amber
   boxes demanding a balance. Amber means STILL NEEDED, and on this file nothing is.
   ⚠️ Greyed, never disabled — same rule the foot keeps: a rep who ticks None and then remembers a
   card can still type into it, and typing is what makes the tick wrong, not this styling. */
.s2-debts[data-implied] .s2-debts-one input[type].s2-in,
.s2-debts[data-implied] .s2-debts-one td.is-missing input[type].s2-in {
    background:#f6f7fa; border-color:#e2e4ec; color:var(--mut);
}
.s2-debts[data-implied] .s2-debts-one input[type].s2-in::placeholder { color:#c6c9d6; }
.s2-debts[data-implied] .s2-dgroup-cell { background:#f4f5f8; }
.s2-debts[data-implied] .s2-dgroup-name { color:#9096a8; }
/* ⚖️ THE SHAPE OF THE ANSWER, IN THE EMPTY BOX (⚖️ R-028, *"$xxx,xxx"* / *"$xx,xxx"* /
   *"xx.xxx%"*). It must read as a HINT and not as a value a rep might leave standing, so it is
   lighter and italic — the same treatment `.s2-val::placeholder` already uses on an amber box. */
.s2-debts-one input[type].s2-in::placeholder { color:#bdb8cc; font-style:italic; font-weight:500; }
.s2-debts-one td.is-missing input[type].s2-in::placeholder { color:#c2ab6d; }
.s2-actions { display:flex; align-items:center; gap:12px; margin-top:8px; }
.s2-actions .btn { padding:6px 14px; font-size:12.5px; }
.s2-after { color:#137a45; }
/* ＋ add — reveals the spare lane already drawn below (never a fetch, never a promise without a
   place to save; see the partial for why cards have none yet). */
.s2-add { background:none; border:0; padding:2px 0; font:inherit; font-size:10.5px; font-weight:700;
    color:var(--brand); cursor:pointer; }
.s2-add:hover { text-decoration:underline; }

/* 🛑 THE ONE-SCREEN TRIM, MEASURED — not guessed. With the boxes typeable the sheet stood at 1417px
   against a 1080px monitor, so it did not do the one thing it exists to do. Measured per row, then
   trimmed where the height actually was: card padding, the caption's line box, and the input's own
   line box. Type stays at the researched floor (12px values, 9px captions — IBM Carbon compact runs
   24px rows and Salesforce Compact puts the label above the field, which is this shape); nothing
   here goes below what a rep can read at arm's length, and no information is hidden to make it fit. */
.s2-sec { padding:5px 10px 7px; }
.s2-sec-head { margin:0 0 3px; }
.s2-cap { line-height:1.15; font-size:9px; display:block; }
.s2-cells { gap:2px 11px; }
input[type].s2-val { font-size:12px; padding:0 2px 1px; min-width:66px; line-height:1.35; }
.s2-val { line-height:1.35; }
.s2-grid td { padding:1px 4px; }
.s2-pricewrap { gap:18px; }
.s2-row { gap:7px; }
.s2-sheet { gap:7px; }
.s2-banner { font-size:10.5px; color:#a9a5bb; }

/* THE SLIM WHO-BAR (Script v2's tab only) — the render's own top row. Who you are talking to on the
   left, the file's tabs on the right, in ~34px where the standard header takes 145. That difference
   is five more question rows on the one screen in the app that must not scroll.
   🛑 The standard header is HIDDEN HERE, never deleted: every other tab keeps the pinned identity
   block his 2026-08-04 ruling asked for, and this tab is his to judge before any cutover. */
body.lead-slim .lead-pin, body.lead-slim .lead-headtop { display:none; }
.s2-who { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; background:var(--card);
    border:1px solid #e6e6ef; border-radius:9px; padding:5px 12px; margin:0 0 7px; font-size:11.5px;
    color:#6b6685; }
.s2-who b { font-size:13px; color:var(--brand-ink); }
.s2-who-back { text-decoration:none; color:var(--brand); font-weight:800; }
.s2-who-tabs { margin-left:auto; display:flex; gap:2px; }
.s2-who-tabs a { padding:3px 9px; border-radius:6px; font-size:11px; font-weight:700; color:#6b6685;
    text-decoration:none; white-space:nowrap; }
.s2-who-tabs a:hover { background:#f1f4fa; color:var(--brand); }
.s2-who-tabs a.on { background:var(--brand); color:#fff; }
/* The pricing build-up is six lines of arithmetic, not a table to browse — tighten its rhythm. */
.s2-buildup td { padding:0 10px 0 0; line-height:1.5; }

/* ═══ THE PREQUAL GETS THE WIDTH; THE SCRIPT GIVES IT UP FIRST (owner 2026-08-08) ═══
   *"We need the Prequal side be as wide as possible. so if screen gets smaller, the script
   sacrifices first."*

   🛑 THE ORDER OF SURRENDER IS THE RULING, and flex is what states it: the sheet may not shrink at
   all (`flex:1 1 auto` with `min-width:0` for its own tables), while the panel is `flex:0 1 340px`
   with a floor — SHRINKABLE, down to the narrowest width a spoken sentence still reads at, and only
   then does the sheet feel anything. Below that the panel drops under the sheet full-width, which is
   the same ruling taken to its end: the words never take the worksheet's room. */
/* 🛑 THE PANEL GIVES GROUND CONTINUOUSLY, not at a breakpoint — and the first build got this exactly
   backwards. `flex:0 1 340px` beside a growing sheet means the SHEET absorbs every lost pixel while
   the panel sits at its full width: measured at 1440 the panel was still 340 and the sheet had
   dropped to 752. `clamp()` ties the panel's width to the viewport, so every pixel the window loses
   comes off the WORDS first and the worksheet only feels it once the panel is at its floor. */
/* ⚖️ WIDENED FOR THE COLUMN, 2026-08-20 — the panel carries EVERY spoken line now (Gil: *"why not
   jsut show jsut the script"*), so its old 232–340px rail set the opener at ~29 characters a line.
   The ruling above is unchanged and only its numbers move: the panel still gives ground continuously
   and the worksheet still feels nothing until the words are at their floor. */
.s2-panel { flex:0 1 clamp(300px, 30vw, 470px); max-width:470px; min-width:300px; }

/* ═══ THE TEXT FITS ITS BOX (owner 2026-08-08) ═══
   *"the fields, need to fit the text in it, so we can either make it so the left side shows on
   words, like creditor names and/or make font smaller in that specific box… cant make it
   microscopic lol."*

   Both halves of his answer: a NAME reads from its left edge (a creditor is recognised by its first
   word, so "Chase Sapphire Preferred" beats "…re Preferred"), and a box whose value still overruns
   steps its own font down — that box only, never the screen.
   🛑 THE FLOOR IS 9.5px AND IT IS A FLOOR, NOT A TARGET — "within reason", his words. A value that
   will not fit even there keeps its size and shows its head rather than shrinking to nothing;
   `title` carries the whole string for a hover, and the box scrolls to the rest on focus. */
input[type].s2-in, input[type].s2-val { text-overflow:ellipsis; }
input[type].s2-in-name, input[type].s2-in-name:focus { text-align:left; }
input[type].is-fit-1 { font-size:10.5px; letter-spacing:-.01em; }
input[type].is-fit-2 { font-size:9.5px; letter-spacing:-.02em; }
/* A focused box shows its whole value rather than an ellipsis — you are typing in it. */
input[type].s2-in:focus, input[type].s2-val:focus { text-overflow:clip; }

/* ═══ THE CONVERSATIONS COLUMN (owner 2026-08-09) ═══════════════════════════════════════════
   *"the script takes up a portion of the right side… on the prequel page where the script was is
   now empty. Why don't we put that there?"* — same place, same width as `.s2-panel`, so a rep
   moving between the two pages finds it where their eye already goes. */
.hoc-split { display:flex; gap:12px; align-items:flex-start; }
.hoc-main { flex:1; min-width:0; }
.hoc-col { flex:0 0 328px; max-width:328px; position:sticky; top:var(--ph-strip-h, 44px); }
/* 🛑 COLLAPSED GIVES THE WIDTH BACK — his words, *"there's more room for the prequel"*. This is a
   real re-layout, not a hidden panel: the sheet grows into the space. The rail stays so the badge
   is reachable; a collapse you cannot undo without leaving the page is a trap. */
.hoc-col.is-shut { flex:0 0 34px; max-width:34px; }
.hoc-col.is-shut .hoc-body { display:none; }
.hoc-col .hoc-rail { display:none; }
.hoc-col.is-shut .hoc-rail { display:flex; flex-direction:column; align-items:center; gap:8px;
    width:34px; padding:10px 0; background:var(--card); border:1px solid #dcdce8; border-radius:10px;
    cursor:pointer; }
.hoc-rail-txt { writing-mode:vertical-rl; font-size:10px; font-weight:800; letter-spacing:.06em;
    text-transform:uppercase; color:#9a97ab; }
.hoc-body { background:var(--card); border:1px solid #dcdce8; border-left:3px solid var(--brand);
    border-radius:10px; padding:10px 11px; }
.hoc-head { display:flex; align-items:center; font-size:10px; font-weight:800; text-transform:uppercase;
    letter-spacing:.06em; color:#9a97ab; margin:0 0 8px; }
.hoc-shut { margin-left:auto; background:none; border:none; color:#9a97ab; font-size:15px;
    font-weight:800; cursor:pointer; line-height:1; padding:0 2px; }
/* one row per CALL — stacked, never tabs across: six date tabs overflow 328px, and the ones that
   fall off are the recent ones, which are the only ones with work on them. */
.hoc-conv { border:1px solid #e6e6ef; border-radius:9px; background:var(--card); margin-bottom:7px;
    overflow:hidden; }
.hoc-conv.is-open { border-color:#c9d3f5; box-shadow:0 1px 6px rgba(53,82,155,.09); }
.hoc-convbar { display:flex; align-items:center; gap:7px; padding:8px 10px; }
.hoc-date { font-size:12px; font-weight:800; color:var(--brand-ink); }
.hoc-meta { font-size:10px; color:#9a97ab; }
.hoc-badge { margin-left:auto; background:#b8860b; color:#fff; font-weight:800; font-size:10.5px;
    min-width:19px; height:19px; border-radius:999px; display:flex; align-items:center;
    justify-content:center; padding:0 5px; }
.hoc-badge.is-done { background:#eef4ee; color:#137a45; border:1px solid #bfd6c8; }
.hoc-item { border-top:1px solid #f2f2f7; padding:9px 10px; }
.hoc-ifield { font-size:9px; font-weight:800; text-transform:uppercase; letter-spacing:.05em;
    color:#9a97ab; }
.hoc-ival { font-weight:800; font-size:14px; margin:2px 0; color:var(--brand-ink); }
/* the customer's own words — the safety mechanism, never decoration */
.hoc-iq { font-size:11.5px; color:#7d7a91; font-style:italic; line-height:1.45; }
.hoc-ibtns { margin-top:8px; display:flex; gap:7px; }
.hoc-ibtns form { display:inline; }
.hoc-take { background:#eef4ee; border:1px solid #bfd6c8; color:#137a45; font-weight:800;
    font-size:11px; border-radius:6px; padding:5px 11px; cursor:pointer; }
.hoc-no { background:#faf1f1; border:1px solid #e3c8c8; color:#a12626; font-weight:800;
    font-size:11px; border-radius:6px; padding:5px 9px; cursor:pointer; }
.hoc-rest { padding:7px 10px; border-top:1px solid #f2f2f7; font-size:11px; color:#9a97ab; }
/* things with no box — his wedding case. Readable, never a value. */
.hoc-note { padding:7px 10px; border-top:1px solid #f2f2f7; font-size:11px; color:#6b5a1c;
    background:#fdf7e2; font-style:italic; line-height:1.5; }
.hoc-older { border:1px dashed #dcdce8; border-radius:9px; padding:8px 10px; font-size:11px;
    color:#7d7a91; }
.hoc-quiet { padding:12px 8px; font-size:11.5px; color:#9a97ab; text-align:center; line-height:1.6;
    margin:0; }
/* Under 1180px the column drops beneath the sheet full-width — the page may never pan sideways
   (design rule 7), the same breakpoint the script sheet already uses. */
@media (max-width:1180px) {
    /* 🛑 STRETCH, NOT THE ROW'S flex-start. `.hoc-split` aligns its items to the start, which is a
       VERTICAL rule while the container is a row and costs nothing — but in a COLUMN it becomes the
       width rule, and a shrink-to-fit `.hoc-main` takes its max-content width. (`.hoc-col` escaped
       it only because the line below hands it `width:100%` outright.) Measured on the running screen
       before this line existed: at a 1000px viewport `.hoc-split` drew 817px while `.hoc-main` inside
       it drew 1651px and the document 1767px, so the page panned 767px sideways — the exact thing the
       note above forbids, in the block that claims to enforce it. `.s2` above carries the identical
       line for the same reason (8dff7d44) — this is the THIRD site after `.shell`, so treat it as the
       rule in design-system.md §7, not as two coincidences. TWO guards, and they cover different
       halves: `measure-prequal-widths.py` fails the run if the document is wider than the window at
       any measured width (the pixels — 1300/1180/1000/768/375px all clean, and re-creating
       `align-items:flex-start` puts the pan back at 1260–1850px, which is what proves the probe can
       see the defect at all); `TheSheetsDoNotPanSidewaysTest` pins the declaration itself in the
       suite, because the script only runs when someone remembers to run it, and deleting this line
       is the failure that actually happens. */
    .hoc-split { flex-direction:column; align-items:stretch; }
    .hoc-col, .hoc-col.is-shut { flex:1 1 auto; max-width:none; width:100%; position:static; }
    .hoc-col.is-shut .hoc-rail { flex-direction:row; width:100%; justify-content:center; }
    .hoc-rail-txt { writing-mode:horizontal-tb; }
}

/* ── HIS SIX NOTE BOXES (2026-08-09) ─────────────────────────────────────────────────────────
   A remark beside the section it bears on — the daughter's October wedding. It is a TEXTAREA and
   not a text input for one concrete reason: accepting a second call's remark APPENDS with a
   newline, and a single-line box strips it on the next save, fusing two remarks into one word-run
   and destroying the first. Two rows by default so a second line is visibly invited without the
   box competing with the fact grid for the one-screen budget. */
textarea.cap-note {
  width: 100%; min-height: 2.6em; resize: vertical; font: inherit; line-height: 1.35;
  padding: 3px 6px; border: 1px solid #d9d6e6; border-radius: 4px; background: #fff;
  color: inherit;
}
textarea.cap-note::placeholder { color: #c9c6d6; font-weight: 500; }
textarea.cap-note:focus { outline: none; border-color: var(--brand); }

/* ── THE PAY-OFF ANSWER (his 2026-08-09 ruling, Option A of a rendered two-way) ───────────────
   Two words, one tap each. A checkbox has two positions and this question has three answers —
   pay it off · keep it · nobody has asked yet — and conflating the last two priced 35 of 59
   debts at zero. The radios themselves are visually hidden; the LABELS are the control, so the
   hit target is the word a rep is reading rather than a 13px dot in a dense grid. */
.s2-po { display:inline-flex; border:1px solid #d9d6e6; border-radius:5px; overflow:hidden; line-height:1; }
.s2-po-b { position:relative; margin:0; cursor:pointer; }
.s2-po-b + .s2-po-b { border-left:1px solid #e6e3f0; }
.s2-po-b input { position:absolute; opacity:0; width:100%; height:100%; left:0; top:0; margin:0; cursor:pointer; }
.s2-po-b span { display:block; font-size:10px; padding:3px 8px; background:#fff; color:#5c5775; white-space:nowrap; }
.s2-po-b input:checked + span { color:#fff; font-weight:700; }
.s2-po-b input[value="Yes"]:checked + span { background:#1f7a3d; }   /* rolling it into the loan */
.s2-po-b input[value="No"]:checked  + span { background:#6b6683; }   /* keeping it — answered, not amber */
.s2-po-b input:focus-visible + span { outline:2px solid var(--brand); outline-offset:-2px; }

/* ── HIS NOTE BOX ON THE MERGED SHEET (2026-08-10) ───────────────────────────────────────────
   *"look at the prequal we built on the script page. I want it to be the same one."* Full width,
   last in its card, wearing the sheet's own furniture rather than the old worksheet's. A remark
   ABOUT the section, not another fact in the grid — so it spans, and it does not take a grid slot. */
.s2-note { grid-column: 1 / -1; margin-top: 6px; }
.s2-note .s2-cap { display: block; margin-bottom: 2px; }
textarea.s2-note-in {
  width: 100%; box-sizing: border-box; font: inherit; font-size: 11.5px; line-height: 1.35;
  min-height: 2.4em; resize: vertical; padding: 3px 5px; color: inherit;
  background: transparent; border: 0; border-bottom: 1px solid #e6e3f0; border-radius: 0;
}
textarea.s2-note-in::placeholder { color: #c9c6d6; font-style: italic; }
textarea.s2-note-in:focus { outline: none; border-bottom-color: var(--brand); background: #fcfbff; }

/* ── HIS FOUR CENTERED BORROWER SECTIONS (2026-08-10): Borrower(s) · Income · Credit · Veterans —
   one row per borrower, the row labelled by the person. --- */
.ws-b-sechead { text-align:center; font-size:12px; font-weight:800; text-transform:uppercase;
  letter-spacing:.05em; color:#3c3752; margin:14px 0 4px; }
.ws-b-sechead:first-child { margin-top:2px; }
/* ⚖️ 2026-08-10 "Center Borrower(s)" — the card's own title, centered for this card ALONE. That is
   what the rule below replaces, and the reason is his own, 2026-08-14, reading the sheet: *"Borrower
   centered but Subject PRoperty not. Make this uniform through out and make it look nice with clear
   separation, shading or something."*
   🛑 IT IS THE SAME RULING, WIDENED — NOT A REVERSAL. He was offered centered-everywhere (A) against
   left-everywhere (B) on a render and picked **A**, so his 08-10 taste now governs all nine sections
   instead of one. A treatment that applies to one card out of nine is not a style, it is an
   exception nobody remembers to extend — which is exactly how these two drifted apart.
   The band gives him the "clear separation, shading" he asked for: one shaded strip per section,
   flush to the card's own edges, so the eye lands on where a section STARTS rather than hunting for
   the gap between two white blocks. The negative margins are the card's own padding pulled back
   out — set as a variable so a host with different padding cannot tear the band off its edge.
   🛑 THE PULL IS THE CARD'S OWN PADDING AND IT IS DECLARED WHERE THE PADDING IS, NEVER GUESSED HERE.
   A first cut hardcoded 22px and added a "dense surfaces are tighter" override at 14px; both were
   invented. Measured on the running sheet, `.card.ws-section` computes **30px** on the prequal tab
   INSIDE `.sr-worksheet` — so the override fired on the roomy surface and left the band floating
   17px inside the card, the ragged edge the band exists to remove. `--ws-head-pull` now defaults to
   the `.card` padding it is actually pulling against, and any host that repads its cards sets the
   variable in the SAME rule that sets the padding, so the two can never drift apart again. */
/* WHERE THE CALL IS, ON THE PREQUAL — the card whose questions the rep is being asked right now.
   ⚖️ Gil, 2026-09-07: "prequal not highlghted on what question we are on".
   🛑 SHARED, AND THAT IS THE RUNNER'S OWN INSTRUCTION CARRIED OUT. This lived inside
   `scripts/run.blade.php`, scoped to `.sr-worksheet-pinned`, with a note saying it should move here
   under a shared name the moment a second surface needed it. Script v2's split view then marked the
   card with the same class and DREW NOTHING — the class existed, the rule did not reach it, and the
   browser checks asserting `classList.contains('sr-ws-here')` passed on a highlight nobody could see.
   That is the MARKED-versus-SEEN trap, in CSS.
   ⚠️ It stays on while the call is on that card — the split view shows both halves at once, so this
   answers "where am I" continuously rather than flashing once and leaving the question open. */
.ws-section.sr-ws-here {
    /* 🛑 NOT A LEFT BAR, AND THAT IS THE WHOLE POINT — ⚖️ Gil, 2026-09-07: *"why is their a line on
       teh side of it that looks like it is where we are at on the script, confusing."* The line he
       meant was `.ws-shop.is-live`'s own 3px `--brand-ink` edge, which said *another lender is in
       this deal*; R-127 removed it, because a rep read it as the script's position. Do not bring a
       left edge back here EITHER — the reason it misread is that a left bar on this sheet looks
       like a place-marker, whoever draws it. This reads as CURRENT — a ring and a tint around the
       whole card, which is
       what "you are standing here" looks like everywhere else on this platform. */
    outline:2px solid var(--ring); outline-offset:-2px; background:#f7f9ff;
    transition:background .25s ease, outline-color .25s ease;
}
@media (prefers-reduced-motion:reduce) { .ws-section.sr-ws-here { transition:none; } }

.ws-section > .ttl {
  --ws-head-pull: 30px;                    /* = .card's own padding; override beside any repadding */
  display:flex; align-items:center; justify-content:center; gap:2px; flex-wrap:wrap;
  margin:calc(-1 * var(--ws-head-pull)) calc(-1 * var(--ws-head-pull)) 16px;
  padding:11px 18px;
  background:linear-gradient(180deg,#f8fafd,#eef2fa);
  border-bottom:1px solid #e2e7f3; border-radius:17px 17px 0 0;
  font-size:12px; font-weight:800; text-transform:uppercase; letter-spacing:.06em; color:#3c3752;
}
/* A section rendered as a <details> puts the band on the SUMMARY, which is the row that opens it. */
.ws-section > summary > .ttl { margin:0; padding:0; background:none; border:0; }

/* ⚖️ 2026-08-19 — GOALS AND WORKING-WITH-SOMEONE SHARE ONE ROW, AND A SHUT CARD STOPS PAYING FOR A
   BODY IT IS NOT SHOWING. Gil, with a screenshot: *"goals section and working with someone else
   needs to be default collapsed… we need to combine them next to each other not separate the 2
   sections. want to save room."* and, asked which split: *"give goals the wider half."*

   🛑 THE DEAD SPACE WAS NEVER THE CARD'S CONTENTS — it is 47px of padding on a card showing nothing.
   `.card` pads 30px; `.ws-section > .ttl` pulls the band flush with `margin-top:-30px` but leaves
   `margin-bottom:16px` under it. Shut, that 16 + the card's own 30px bottom padding is pure white
   under a one-line title — the empty band under GOALS in his screenshot, and it was on EVERY shut
   card on the sheet, not just these two. Measured in a browser (159px card over a 111px summary),
   not read off the stylesheet.

   🛑 FLEX-WRAP, NOT A MEDIA QUERY, AND THAT IS THE LOAD-BEARING CHOICE. These partials render on
   THREE screens, and on Script v2 the sheet sits in a narrow column beside the words panel inside a
   wide browser — the proving file measures its widest child at 1584px inside a 958px box, so the
   sheet has NEGATIVE slack while the WINDOW is roomy. A `min-width` media query reads the window and
   would drive two columns into a sheet that cannot hold them. `flex-basis` + `wrap` reads the
   CONTAINER: when the two cannot both hold their basis they stack themselves, on whichever screen
   they are on, with nothing to keep in sync.
   ⚠️ `min-width:0` because a flex item's default `min-width:auto` refuses to shrink below its
   content — the goal chips are wide, and without it the row would push the sheet sideways instead of
   wrapping, which is the one failure this sheet cannot afford. */
.ws-pair { display:flex; flex-wrap:wrap; gap:12px; align-items:flex-start; margin-bottom:22px; }
.ws-pair > .card { flex:1 1 300px; min-width:0; margin-bottom:0; }
/* ⚖️ "give goals the wider half" — Gil 2026-08-19, asked which split he wanted.
   🛑 THE WIDTH IS IN THE BASIS, NOT ONLY IN THE GROWTH, and that is measured rather than styled.
   `flex-grow` alone shares only the SPARE width, so on the sheet widths that matter it produced a
   54/46 split — wider by arithmetic and not by eye. Giving Goals the larger BASIS makes the ratio
   hold at every width instead of only on a big screen.
   📐 Why 420: with his worst real case on file (three goals, two of them carrying a sub-goal, plus
   credit — six chips) the collapsed Goals card measures 114px under 580px wide, 101px to 700, and
   88px beyond. The row is governed by the taller card, so buying Goals width buys back real height;
   420 is the widest basis that still lets the pair sit side by side inside the Script v2 sheet,
   which is the narrowest of the three screens these partials render on.
   ⚠️ Together the bases set where the row stacks: 420 + 300 + 12 = 732px of SHEET, not of window. */
.ws-pair > .card:first-child { flex:1.7 1 420px; }
/* ⚖️ 2026-08-20 — GIL ASKED FOR THE SIX OFFER BOXES ON ONE ROW, and one row is not a sizing
   problem: it is a WIDTH problem, measured. Six boxes at their legible floor need **722px of
   block**; inside the 41% share this card takes under [R-031] it has **401px** at his own window
   width and 298px on Script v2. No caption and no basis closes a gap that size — the only thing
   that puts them on one line is giving the card the row while a competitor is actually in the deal.

   🛑 IT DOES NOT TOUCH R-031's STRUCTURE, AND THAT DISTINCTION IS THE WHOLE OF WHY THIS IS SAFE.
   Both cards stay inside the one `.ws-pair`, in his order, Goals first — `PrequalSheetLayoutTest::
   test_goals_and_working_with_someone_share_one_row` reads the MARKUP and stays green because the
   markup is unchanged. What changes is the visual share, in ONE state: `.is-live`, which is `Yes`
   and nothing else. Shut, blank, `No` and `Turned down` all keep the 59/41 row he asked for, so
   *"want to save room"* is untouched everywhere it was actually about room.
   ⚖️ And on `Yes` it is [R-029] that governs — *"show a different way… so it is obvious and we
   expand"*. This is that expansion carried to the only width at which the six boxes fit.

   ⚠️ THE RUNNER CANNOT DO IT AND IS NOT PRETENDING TO. Its whole sheet lives in a 560px rail, so
   the block is 471px whatever this rule says; six will not fit there at any caption length, and it
   settles at two rows. Measured, not assumed — the alternative was clipping a rate on a live call.
   ⚠️ `.is-live` is toggled CLIENT-SIDE the moment the rep clicks (`_worksheet.blade.php`), so the
   row widens on the answer, not on a reload — which is what R-029 requires of this card.
   ⚠️ `:has()` DEGRADES SAFELY: a browser without it simply keeps the 59/41 row. Nothing breaks;
   the boxes wrap the way they do today. */
.ws-pair > details.card.ws-section:has(.ws-shop.is-live) { flex:1 1 100%; }
details.card.ws-section:not([open]) { padding-bottom:0; }
details.card.ws-section:not([open]) > .ttl { margin-bottom:0; }
.ws-k-primary_years .ws-vlabel { white-space:nowrap; }

/* ⚖️ 2026-08-10 — the borrower card's three little boxes: Residence · Income · Credit. */
/* THE BORROWER ROW AND THE RESIDENCE, SIDE BY SIDE — 2026-09-02, Script v2 only.
   ⚖️ Gil: "permanently add residence to right of B1. and that is what is highlighted on that
   question." The address beat's boxes have to sit TOGETHER for the highlight to read as one group;
   scattered across two cards, marking them was never going to help.
   🛑 THE RESIDENCE IS OUTSIDE THE SIDEWAYS SCROLLER, WHICH IS THE WHOLE POINT OF PUTTING IT HERE
   RATHER THAN IN THE ROW. `.ws-lien-scroll` scrolls horizontally; anything dropped inside it
   inherits exactly the defect fixed hours earlier — a box the script rings and the rep cannot reach
   because it is off the side. As a sibling it is always on screen, whatever the grid is scrolled to.
   ⚠️ IT WRAPS BELOW ON A NARROW WINDOW rather than squeezing the borrower row to nothing. The row
   holds eight columns and is the denser of the two; a residence pinned beside it at 300px would take
   the width the names need. `flex-wrap` gives the old stacked layout back exactly when there is no
   room for the new one. */
.ws-b-rowres { display:flex; gap:10px; align-items:flex-start; flex-wrap:wrap; }
/* ⚠️ THE BASES ARE SMALL ON PURPOSE, AND THE FIRST PAIR WERE WRONG. At `520px + 330px` the two
   could not both fit the card's ~750px and the residence wrapped underneath — which is the OLD
   layout wearing new markup, and the browser check caught it sitting at the same left edge as the
   row rather than beside it. The borrower row SCROLLS sideways by design, so giving it a smaller
   basis costs it nothing it cannot recover; the residence does not scroll and must keep its width. */
.ws-b-rowres > .ws-lien-scroll { flex:1 1 340px; min-width:0; }
.ws-b-rowres > .ws-b-3col { flex:0 0 360px; margin-top:0; }

/* THE SHEET RUNS TO THE EDGE ON SCRIPT V2 — 2026-09-02.
   ⚖️ Gil: *"we need to make sure prequal goes all the way to the right hand side. Right now it ends
   just short of the right hand side. take it all the way to the end."* The shared `.wrap` carries
   26px of margin AND 26px of padding on each side — 104px of chrome the sheet never gets.
   ⚠️ SCOPED TO `body.lead-slim`, WHICH IS THE SCRIPT V2 TAB AND NOTHING ELSE (`show.blade.php` sets
   it only when `$tab === 'script2'`). `.wrap` is shared vocabulary and a view may never redeclare
   it; widening it globally would move every screen in the product to answer a request about one. A
   little gutter is kept rather than none, so the card edges do not fuse with the window frame. */
body.lead-slim .wrap { margin-left:8px; margin-right:8px; padding-left:8px; padding-right:8px; }

/* THE SHEET FITS THE MONITOR, AND ONLY OTHER PROPERTIES SCROLLS — 2026-09-08.
   ⚖️ Gil: *"the only one that needs scrolling like that for now is the Other properties area."*
   Measured on the live sheet: the page ran 653px off the right of a 1568px screen, so reaching the
   Residence address block meant dragging the WHOLE page sideways — the script column went with it,
   and a rep lost their place on the call to fill one box.
   🛑 THE CAUSE IS THE UA DEFAULT ON `<fieldset>`, NOT ANYTHING WE WROTE. Browsers give every
   fieldset `min-width:min-content` — "never shrink below my widest un-wrappable child". Other
   Properties is 21 columns; that one table therefore set the width of the ENTIRE sheet, and
   Borrower, Income, Credit and the Residence inherited ~1629px whether they needed it or not. The
   stretched rows were a SYMPTOM. Nothing here chose min-content, so overriding it honours no
   earlier decision — it restores the block behaviour the rest of the sheet was written against.
   🛑 AND IT IS WHAT SWITCHES THE EXISTING SCROLLERS ON. `.ws-lien-scroll` is already `overflow-x:auto`
   in three places (Other Properties, Subject Property, the borrower row). All seven measured
   `scrollWidth === clientWidth` — not one had ever scrolled, because a frame cannot scroll while the
   fieldset around it keeps growing to fit that frame's own contents. Measured after: the page will
   not move sideways at all (447px → 0) and Other Properties scrolls 465px inside its own frame,
   which is his ruling stated as a measurement.
   ⚠️ LAYOUT ONLY — no question added, removed, renamed or reordered. Verified at 1600/1440/1280/1100
   /900: fits at every width, nothing clipped, no element loses width, and the Residence still sits
   beside B1 on his monitor per the 2026-09-02 ruling (it wraps under only on a narrow window, as it
   already did).
   🛑 RE-SCOPED OFF `body.lead-slim` 2026-09-08 (evening) — AND THE NOTE THAT USED TO STAND HERE
   ASKED FOR EXACTLY THIS. It read: *"Checked on the box: that tab keeps min-content and does not pan
   today (-12px at 1787) … If it ever pans, this is the one line to re-scope."* It pans. The Prequal
   tab (`?tab=worksheet`, the tab a rep sees LABELLED "Prequal") was measured on all **fourteen**
   dumped files by `scripts/measure-field-boxes.py`: **every one of them pans at 1440 (339–1008px),
   and five of them pan at 1920 as well** — 53px up to 528px on the widest file.
   🛑 AND THE VARIABLE IS HOW WIDE THE OTHER-PROPERTIES TABLE RENDERS, NOT THE SCREEN WIDTH AND NOT
   THE PROPERTY COUNT — WHICH IS WHY TWO HONEST MEASUREMENTS DISAGREED. The old note re-checked at
   ONE width on ONE lead, and both halves of that were the easy case: 1787px is WIDER THAN GIL'S OWN
   ~1568px SCREEN (the screen the 653px pan was measured on), and its lead's other-property row was
   barely filled. 🛑 **The two one-property fixtures settle it: `properties-one` fills ten of the
   twenty-one columns and pans 53px at 1920; `one-borrower-one-property` fills five and pans ZERO.**
   Same screen, same property count, opposite answer. ⇒ A fixture whose other-property row is thinly
   filled cannot reproduce this at any width, and re-checking at a single width reads clean forever.
   🛑 THE EARLIER CAUTION WAS RIGHT ABOUT THE PRINCIPLE AND WRONG ABOUT THE FACT. Widening a change
   to a surface nobody asked about IS a second ruling — but this is not a new decision, it is
   [R-025] applied to the other host of the same markup: *"the only one that needs scrolling like
   that for now is the Other properties area."* A rep dragging the Prequal tab sideways to reach the
   Residence block is the complaint he already made, on the tab he did not happen to have open.
   ⚠️ SCRIPT V2 IS BELT-AND-BRACES AND STAYS SO. That tab also carries `container-type:inline-size`
   below, which is `contain:inline-size` — its fieldset's width already cannot depend on its
   contents, so the rule is inert there and load-bearing here. (Measured: planting `min-content`
   back on the script2 fieldset moves the page 0px; on the Prequal tab it moves it 528.) */
.sr-worksheet-body { min-width: 0; }

/* THE BORROWER CARD IN TWO COLUMNS, AND INCOME BESIDE CREDIT — 2026-09-08.
   ⚖️ Gil: *"why do we have the space between B1 and this. It should be right below the borrower
   information"* and *"I am thinking we can move Income and Credit to be side by side and save
   same."*
   🛑 THE GAP WAS A HEIGHT DIFFERENCE, NOT A MARGIN. The borrower row is 77px; the Residence beside
   it is 186px. `.ws-phones` is a sibling of `.ws-b-rowres`, so it cleared the TALLER column and
   opened **110px of dead space** under B1. Measured, not read off the markup.
   ⇒ The card becomes a 2-column grid and `.ws-b-rowres` / `.ws-lay-contact` go `display:contents`,
   so the borrower row and phones stack in column 1 while the Residence holds column 2 across both.
   Card height 822px → 546px on his monitor.
   🛑 THE QUERY IS ON THE FIELDSET, BECAUSE A CONTAINER CANNOT QUERY ITSELF. First cut put
   `container-type` on the card and the rules never fired — a `@container` block styles DESCENDANTS
   of the named container, never the container element. The fieldset is the nearest ancestor that is
   not itself being restyled here.
   ⚠️ 1040px IS A FALLBACK THRESHOLD, NOT A TARGET WIDTH — and it came DOWN from 1150 on 09-08 after
   Gil hit the fallback on his own screen. Two columns need the borrower row's 732px + 300px + the
   gap; below that the old single-column flow is correct, and
   `.ws-b-rowres`'s own `flex-wrap` still drops the Residence under the row exactly as it did before
   (2026-09-02). Verified 1600/1400/1200 two-column and 1100/1000/900/800/700 falling back, with the
   borrower row's by-design sideways scroll unchanged at every one.
   ⚠️ `auto-fit` RATHER THAN A SECOND BREAKPOINT for the bands: `minmax(544px,1fr)` is Income's
   measured floor, so the pair sits side by side exactly while both fit and stacks itself otherwise —
   no viewport guess to go stale.
   🛑 AND THE FIT COMES FROM SIZING BOXES TO THEIR ANSWERS, NEVER FROM A NEW SIDEWAYS SCROLL — his
   ruling this morning was that Other Properties is the only thing that scrolls like that. At half
   width Income was 63px over. `Monthly` was a 150px box holding `5000`, and `Income type` a 176px
   box whose longest option is `Self-Employed`. Measured in the browser with the widest real answers
   loaded — `construction supervisor`, `250,000`, `Self-Employed` — nothing clips at 122/96 and the
   grid sits at 544 of 544. `What they do` KEEPS its 150px: `construction supervisor` needs 137 of
   it, and it was the one column with no slack to give. */
body.lead-slim .sr-worksheet-body { container-type:inline-size; container-name:sheet; }
@container sheet (min-width: 1040px) {
    body.lead-slim .ws-section:has(> .ws-b-rowres) { display:grid;
        grid-template-columns:minmax(0,1fr) 300px; column-gap:10px; align-items:start; }
    body.lead-slim .ws-section > .ws-b-rowres,
    body.lead-slim .ws-section > .ws-lay-contact { display:contents; }
    body.lead-slim .ws-section > h3.ttl,
    body.lead-slim .ws-section > .ws-vet-who,
    body.lead-slim .ws-lay-contact > .ws-b-bands { grid-column:1 / -1; }
    body.lead-slim .ws-b-rowres > .ws-b-blocks,
    body.lead-slim .ws-lay-contact > .ws-phones { grid-column:1; }
    body.lead-slim .ws-b-rowres > .ws-b-3col { grid-column:2; grid-row:span 2; margin-top:0; }
}
/* 🛑 THE TWO BANDS ARE NOT HALVES, AND TREATING THEM AS HALVES COST GIL THE LAYOUT — 2026-09-08.
   `repeat(auto-fit, minmax(544px,1fr))` forced both to Income's floor, so the pair demanded
   **1098px** when Income needs 544 and Credit only **308**. On his own window — narrower than the
   1787px this was measured at — that tipped over and the bands stacked, which he reported as
   *"everything reverted back"*. Nothing had reverted; the fallback was simply too eager.
   ⇒ `row wrap` with a real basis each. They sit side by side from ~830px instead of 1098 and wrap
   themselves when they genuinely cannot fit — no breakpoint to be wrong about a screen I cannot see.
   ⚠️ `flex-flow`, NOT `display:flex`. An existing rule already sets `flex-direction:column` here, so
   a bare `display:flex` inherits it and stacks them while every width reads correct — measured
   `flexWrap:wrap` and `flexDirection:column` together before this line was written. */
body.lead-slim .ws-b-bands { display:flex; flex-flow:row wrap; gap:10px; align-items:flex-start;
    grid-template-columns:none;
    /* 🛑 `width:100%` IS LOAD-BEARING, NOT TIDINESS. As a grid this element took its width from its
       own tracks; as a FLEX container it sizes to content, and its parent is a flex COLUMN whose
       items do not stretch here — so the whole bands block collapsed to **50px** and Income, Credit
       and the lates column were crushed to 40px and 14px. Measured on the render dump before this
       line existed; nothing in the markup or the suite would have said a word. */
    width:100%; align-self:stretch; }
body.lead-slim .ws-b-bands > .ws-b-band { min-width:0; }
body.lead-slim .ws-b-bands > .ws-b-band[data-ws-band="income"] { flex:1 1 544px; }
body.lead-slim .ws-b-bands > .ws-b-band[data-ws-band="credit"] { flex:1 1 320px; }
body.lead-slim .ws-b-band .ws-lien-scroll { min-width:0; }
/* 🛑 THE SCRIPT-V2-ONLY PAIR IS GONE — the width lives once, on `.ws-b-inc-type` /
   `.ws-b-inc-money`, at the same 122/96 this scope introduced. Income's 544px floor below is
   unchanged: these are the same two numbers it was measured against. */
/* ⚖️ Gil 2026-09-08: *"three different areas that we can remove to make not as wide"* — and then
   *"the Reps are using 23 in screens, but lets allow for smaller screens too."* At 1920 the sheet has
   room to spare; these trims buy headroom for the smaller screens, which is where the layout was
   giving up and stacking on him.
   🛑 EVERY VALUE HERE IS A MEASURED FLOOR, NOT A GUESS, AND ONE CANDIDATE WAS REFUSED. The row label
   looked like the fattest column (54px for "B1", 13px of text) — but it also renders **"2nd source"**
   on an added income line, which needs 43px plus padding. It is already at its minimum; trimming the
   obvious-looking column would have clipped the label on exactly the row a second job creates.
   ⚠️ AND THE HONEST TOTAL IS ~48px, NOT THE ~107 I FIRST TOLD HIM. Measured: the ＋ cell is 38 around
   a 22px button, `Verif.` is 39 around 22px of text, and `Where from` is 150 around a longest option
   (`Bank / Experian`) of 75 plus a select's own chrome. I said the bigger number before measuring the
   floors; the smaller one is what the boxes will actually give up without clipping. */
body.lead-slim .ws-b-inc .ws-b-plusc { flex:0 0 28px; width:28px; min-width:28px; }
/* 34px, not 33 — `.ws-b-inc .ws-b-tick` already carries a deliberate `min-width:34px` for the tick's
   own target size. Undercutting a stated minimum to win one pixel is not a trade worth making. */
body.lead-slim .ws-b-inc .ws-b-tick  { flex:0 0 34px; width:34px; }
/* 🛑 THE SCRIPT-V2-ONLY OVERRIDE IS GONE, NOT RETUNED. It shipped at 118 on the reasoning
   *"`Bank / Experian` is 75px plus a select's own chrome"* — measured, the select needs 111 and had
   105, so the answer a rep picked read back clipped. THIS STYLESHEET'S OWN RULE, THE THIRD TIME
   THIS WEEK: size with headroom, then RE-MEASURE. A calculation gives you a floor; only a probe
   gives you the screen. The width now lives once, on `.ws-b-basis` above, at 128 for both tabs. */

/* THE CONTACT GRID, THE RESIDENCE'S TYPE, AND THE CREDIT BAND'S ORDER — 2026-09-08 round three.
   ⚖️ Gil, in one message: *"Make Cell/Home a check box, put cell home on top of column as header…
   Line up fill in fields also, number, check boxes and email"* · *"move cell and home columns closer
   to phone number to know they go together"* · *"add a plus button to add an email"* · *"the
   Residence address has to be same size fonts as the rest… especially subject, Yes and No"* ·
   *"Move Anything else about their credit, down to bottom"* · *"make Where from on credit smaller,
   add Mortgage Lates in last 12 months next to it."*

   ── 1 · THE CONTACT GRID ────────────────────────────────────────────────────────────────────
   Number, the two ticks and the email sit on ONE baseline under their own headers. The rows are
   `display:contents` (the device `.ws-lien` already uses) so `ws-f ws-k-KEY` stays each row's
   leading class — several readers match that literal string, `PrequalPlacementRuleTest` among them.
   🛑 THE GAP BEFORE EMAIL IS A REAL COLUMN, NOT A MARGIN. *"move cell and home columns closer to
   phone number to know they go together"* is a grouping instruction, and a margin collapses under
   a narrow card exactly when the grouping matters most. `--ws-cg-gap` holds it open.
   🛑 THE CONTACT-GRID RULES BELOW ARE UNSCOPED, AND THAT IS DELIBERATE — everything else in this
   block is `body.lead-slim`. `_worksheet-borrower.blade.php` is SHARED, so the new markup renders on
   `?tab=worksheet` too; scoping its styling to Script v2 left that tab with headers jumbled into the
   row and the tick boxes at **0×0** with a 2px radio behind them — measured on the render dump,
   which is the worksheet card. A CSS scope narrower than the MARKUP it styles is a regression on
   every other host. Layout-only changes here (the residence's type, the credit band's order, the
   income widths) keep their `lead-slim` scope because their markup is untouched.
   ⚠️ THE TICKS ARE RADIOS WEARING A CHECKBOX. `contact_phone_type` is ONE value that the script
   reads — `Home` sends PQ1B on to ask for their cell, `Cell` suppresses that duplicate ask (his
   2026-08-20 call, pinned by `TheirCellIsAskedOnlyOnTheHomeRoadTest`). A genuinely independent pair
   would create a both-ticked state that gate has no answer for. Told to Gil before building.

   ── 2 · THE RESIDENCE'S TYPE ────────────────────────────────────────────────────────────────
   🛑 ONE DECLARATION WAS DOING ALL OF IT: `.ws-residence-block` at 16px, which everything inside
   inherited. Measured against the rest of the card — boxes 13.5px vs 10px, `Subject` 12px vs 8px,
   and Yes/No 15px vs 10px, which is why he named those two. Setting the block to the sheet's own
   size fixes all three at once; no box, label or answer is touched.

   ── 3 · THE CREDIT BAND ─────────────────────────────────────────────────────────────────────
   The note moves to the foot of the band and the lates sit beside `Where from`, which comes down
   from 268px. `.ws-b-sechead-split` goes `display:contents` so its two children become band
   children and `order` can separate them — the caption stays put, the note drops to last.
   🛑 THE LATES BOX IS BESIDE THE ROW, NEVER INSIDE IT, and that is a data rule rather than a taste.
   `mtg1_lates_12mo` is about the MORTGAGE; the credit grid's rows are per borrower. Inside the row
   it would draw a second lates box the moment B2 is added, and whichever a rep filled would
   silently claim the answer belongs to that borrower — the shape this card already refuses for net
   rental income. Told to Gil before building. */
.ws-phones.ws-cgrid { --ws-cg-gap:18px;
    display:grid; align-items:start; column-gap:4px; row-gap:3px; justify-content:start;
    /* ⚠️ FIVE TRACKS AND `justify-content:start`, NOT A TRAILING `1fr`. The spare sixth column a
       `1fr` created was not inert: the header row filled only five of six, and the leftover cell
       swallowed the next row's number box, which is why Cell/Home fell onto a line of their own.
       Fixed tracks plus `start` leave the slack OUTSIDE the grid, where nothing can auto-place into
       it. The card on `?tab=worksheet` is far wider than Script v2's, so a stretchy track also drew
       a 667px box for a phone number — the same defect Gil named on the Income row. */
    grid-template-columns:168px 34px 34px var(--ws-cg-gap) 260px; }
/* The headers, in the sheet's own 8px caps, sitting on the boxes they title. Bottom-aligned so the
   ＋ inside two of them cannot push `Cell` and `Home` off the line. */
/* ⚖️ Gil 2026-09-08, pointing at the header block: *"three different areas that we can remove to
   make not as wide and can still move areas up to consolidate."* The ＋ sat on a line of its own
   under the label, so the header row measured 34px for 12px of text. Inline, it costs nothing. */
.ws-cg-h { font-size:8px; font-weight:800; letter-spacing:.05em; text-transform:uppercase;
    color:#7b8296; padding:0 2px 2px; display:flex; align-items:center; gap:5px;
    white-space:nowrap;
    /* The phone caption also carries `ws-sub-label`, which `wireBlockCaptionJumps` reads to find the
       block — see the note in the blade. That class paints 11px type and `margin:15px 0 8px`, none of
       which belongs on a grid header, so the margin is zeroed here rather than the class dropped. */
    margin:0; }
.ws-cg-h .ws-b-plus { flex:0 0 auto; }
.ws-cg-h-tick { text-align:center; }
.ws-cgrid > .ws-cg-h { align-self:end; }
.ws-cg-gap { min-width:0; }
/* 🛑 `hidden` MUST BE RESTATED, AND THIS FILE ALREADY SAYS SO ONE SCREEN DOWN (the `.ws-lien-grid`
   note: *"it and the contents rule have equal specificity, and without this an un-added second
   mortgage would appear on every file"*). The same trap, walked into again: the placement rules
   above are (0,2,0)/(0,3,0) and beat `.ws-phone-row[hidden]` at (0,1,1), so **all four numbers and
   all three emails rendered open** — every box the ＋ exists to keep back. Caught by looking at the
   rendered page, not by any test. */
.ws-lay-contact .ws-cgrid > .ws-phone-row[hidden],
.ws-lay-contact .ws-cgrid > .ws-email-row[hidden],
.ws-cgrid > .ws-phone-row[hidden],
.ws-cgrid > .ws-email-row[hidden] { display:none; }
/* The tick's own label text is for screen readers only — the column header carries it on screen.
   ⚠️ THIS CLASS IS DEFINED HERE BECAUSE THE STYLESHEET HAD NO VISUALLY-HIDDEN CONVENTION. A first
   cut used `.sr-only` on the assumption that every codebase has one; it does not, so `Cell` and
   `Home` rendered as visible text beside each tick on every row. */
.ws-cg-sr { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
    clip-path:inset(50%); white-space:nowrap; border:0; }
/* The ✕ sits at the right edge of the box it closes, inside the field's own padding — the sheet's
   own quiet-until-needed treatment, not a second styled control competing with the ＋. */
.ws-cg-del { appearance:none; background:none; border:0; padding:0; margin:0; cursor:pointer;
    font-size:11px; line-height:1; color:#b6bccb; align-self:center; justify-self:end;
    grid-column:1; margin-right:5px; }
.ws-cg-del:hover { color:#a12b2b; }
.ws-cgrid > .ws-phone-row > .sr-adorn input[type] { padding-right:17px; }
.ws-cgrid > .ws-email-row { display:flex; align-items:center; gap:3px; }
.ws-cgrid > .ws-email-row > .sr-adorn { flex:1 1 auto; }
.ws-cgrid > .ws-email-row > .ws-cg-del { flex:0 0 auto; grid-column:auto; margin-right:0; }
/* 🛑 ROWS ARE PLACED, NOT PROMOTED. `display:contents` on the rows looked right and was wrong twice
   over: the email row emits TWO elements (the box and its `sr-onfile` hint), so it claimed two
   columns and pushed everything after it, and the phone row lost the box `grid-column` needs.
   A subgrid keeps `ws-f ws-k-KEY` as the row's own leading class — which several readers match as a
   literal string — while its parts still land in the parent's real columns. Only `.ws-phone-type`
   is promoted, because its two labels genuinely belong in two different columns. */
.ws-lay-contact .ws-cgrid > .ws-phone-row,
.ws-cgrid > .ws-phone-row { display:grid; grid-column:1 / span 3;
    grid-template-columns:subgrid; align-items:start; gap:0 4px; }
.ws-lay-contact .ws-cgrid > .ws-email-row,
.ws-cgrid > .ws-email-row { display:block; grid-column:5; min-width:0; }
.ws-lay-contact .ws-cgrid .ws-phone-type,
.ws-cgrid .ws-phone-type { display:contents; }
/* The number box fills its column rather than the 156px the old row pinned it to. */
.ws-lay-contact .ws-cgrid .ws-phone-row > .sr-adorn,
.ws-cgrid .ws-phone-row > .sr-adorn { width:100%; max-width:none; grid-column:1; }
.ws-lay-contact .ws-cgrid .ws-phone-row input[type],
.ws-cgrid .ws-phone-row input[type] { min-width:0; width:100%; }
.ws-cgrid > .ws-email-row > * { min-width:0; }
.ws-cgrid > .ws-email-row .sr-adorn { width:100%; max-width:none; }
/* The tick itself: a checkbox to look at, a radio to answer. */
/* ⚠️ THE TICKS GET THE BOX'S HEIGHT, NOT THE ROW'S. Everything in the row is top-aligned so the
   number, the ticks and the email box share one baseline — *"Line up fill in fields also"* — and a
   13px tick centred in a 30px row would have sat visibly low against boxes it is meant to match.
   The email cell is the tall one (its `sr-onfile` hint sits under the box), which is what made the
   difference legible in the first place. */
.ws-cg-tick { display:flex; align-items:center; justify-content:center; cursor:pointer;
    min-height:26px; }
.ws-cg-tick input { position:absolute; opacity:0; width:0; height:0; }
.ws-cg-tick .ws-cg-box { width:13px; height:13px; border:1.5px solid #9aa3b8;
    border-radius:3px; background:#fff; display:inline-block; position:relative; }
.ws-cg-tick input:checked + .ws-cg-box { background:var(--brand,#35529b);
    border-color:var(--brand,#35529b); }
.ws-cg-tick input:checked + .ws-cg-box:after { content:"✓"; position:absolute;
    left:1.5px; top:-2.5px; font-size:10px; font-weight:800; color:#fff; }
.ws-cg-tick input:focus-visible + .ws-cg-box { outline:2px solid var(--brand,#35529b);
    outline-offset:1px; }
/* 2 · the residence, down to the sheet's own size */
body.lead-slim .ws-b-3box .ws-residence-block { font-size:10px; }
body.lead-slim .ws-b-3box .ws-residence-block input[type],
body.lead-slim .ws-b-3box .ws-residence-block select { font-size:10px; }
body.lead-slim .ws-b-3box .ws-residence-block .ws-vlabel,
body.lead-slim .ws-b-3box .ws-residence-block .ws-choice-lab { font-size:8px; }
body.lead-slim .ws-b-3box .ws-residence-block .ws-choice-opts { font-size:10px; }
/* 🛑 THE Yes/No NEEDED ITS OWN LINE, AND IT IS THE ONE HE NAMED. `.s2 .ws-choice-opt` sets 15px
   for the whole of Script v2, so setting the CONTAINER to 10px moved nothing — the answers are
   children and kept winning. Measured after the first cut: block 10px, boxes 10px, `Subject` 8px,
   and Yes/No still 15px, which is exactly the half he could see.
   ⚠️ SCOPED TO `.ws-b-3box`, NOT TO `.s2`. The same 15px is on Goals and Working-with-someone and
   is right there — those are primary answers in a full-width card. It reads oversized ONLY in the
   360px residence box, beside 10px boxes, which is the comparison he actually made. */
body.lead-slim .ws-b-3box .ws-choice-opt { font-size:10px; }
body.lead-slim .ws-b-3box > .ws-b-sechead { font-size:8px; }
/* 3 · the credit band */
/* 🛑 THE BAND WRAPS INSTEAD OF SQUEEZING, AND THE FIRST CUT SQUEEZED. A grid of
   `auto minmax(150px,1fr)` gave the lates column a floor that the credit GRID then paid for: at
   1096-1196px of sheet the grid lost width and started scrolling sideways — the one thing his
   morning ruling forbids outside Other Properties. Flex with `wrap` lets the lates sit beside the
   grid while there is room for both and drop to its own line when there is not, so neither is ever
   squeezed into a scroller. */
body.lead-slim .ws-b-band[data-ws-band="credit"] { display:flex; flex-flow:row wrap;
    align-items:flex-start; gap:0 8px; }
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-b-sechead-split { display:contents; }
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-b-sechead-split > :first-child {
    order:0; flex:1 1 100%; }
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-b-cred { order:1; flex:0 1 auto;
    min-width:0; }
/* ⚖️ Gil 2026-09-08: *"Mortgage lates last 12 mo, should be in line with Credit score and where
   from."* Its label now reads as a THIRD COLUMN HEADER beside those two, and its box sits on B1's
   line — the block is `display:contents` so the label and the control can be placed on the header
   row and the data row separately.
   🛑 IT IS STILL NOT INSIDE THE BORROWER ROW, AND THAT IS THE WHOLE CARE HERE. `mtg1_lates_12mo` is
   about the MORTGAGE while the grid's rows are per borrower: inside, adding B2 would draw a second
   lates box for one loan and whichever a rep filled would claim the answer for that borrower. This
   gives him the alignment he asked for without the duplication he did not. Same refusal the card
   already makes for net rental income. */
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-k-mtg1_lates_12mo { order:2;
    flex:1 1 150px; min-width:0; display:block; }
/* Its label wears the grid's own header type and padding, so it reads as a third column heading
   beside `Credit score` and `Where from` rather than as a stray question — ⚖️ *"Mortgage lates last
   12 mo, should be in line with Credit score and where from."* Matching `.ws-lien-head > *`'s
   `5px 7px` and 8px caps is what puts the two on one line without either being placed by hand. */
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-k-mtg1_lates_12mo > .ws-vlabel {
    display:block; font-size:8px; font-weight:800; letter-spacing:.05em; text-transform:uppercase;
    color:#7b8296; padding:5px 7px; white-space:nowrap; }
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-k-mtg1_lates_12mo > :not(.ws-vlabel) {
    padding:3px 5px 0 7px; }
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-k-mtg1_lates_12mo select,
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-k-mtg1_lates_12mo input[type] {
    width:100%; min-width:0; }
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-b-unsure-link { order:3; flex:1 1 100%; }
body.lead-slim .ws-b-band[data-ws-band="credit"] > .ws-b-sechead-split > .ws-k-credit_note {
    order:9; flex:1 1 100%; }

/* THE RESIDENCE, RE-LAID FOR THE NARROW BOX IT NOW LIVES IN — 2026-09-02.
   ⚖️ Gil, on the live screen: *"Look how residence looks on the screen?????"* He was right, and it
   was my doing: the block moved into a ~300px box beside B1 while its grid was still the twelve
   column layout built for the full-width property card. Twelve columns of ~19px each, so the street
   clipped mid-word, the city rendered "Fo", and Subject took a block wider than the address it
   belongs to.
   🛑 THE FIX IS ROWS, NOT NARROWER COLUMNS. Squeezing the same twelve-across arrangement smaller
   only clips everything more politely. In a column this narrow the honest shape is his own written
   layout stacked: street on its own line, city/state/zip beneath it, then Subject, then Own — each
   getting the full width instead of a twelfth of it.
   ⚠️ SCOPED TO `.ws-b-3box`, so the property card's full-width residence — which the Prequal tab
   every rep uses still renders — keeps the twelve-column layout it was designed for and is not
   touched by a fix for a box it does not live in. */
.ws-b-3box .ws-lay-residence .ws-k-primary_address { grid-column:1 / -1; grid-row:1; }
.ws-b-3box .ws-lay-residence .ws-k-primary_city { grid-column:1 / span 5; grid-row:2; }
.ws-b-3box .ws-lay-residence .ws-k-primary_state { grid-column:6 / span 2; grid-row:2; }
.ws-b-3box .ws-lay-residence .ws-k-primary_zip { grid-column:8 / span 5; grid-row:2; }
.ws-b-3box .ws-lay-residence .ws-k-subject_property_fork { grid-column:1 / -1; grid-row:3; }
.ws-b-3box .ws-lay-residence .ws-k-owns_primary { grid-column:1 / -1; grid-row:4; }
/* Each Yes/No reads as one line — label, then the answers beside it — rather than the answers
   wrapping under their own label, which is what stacked Yes above No in the narrow box. */
.ws-b-3box .ws-lay-residence .ws-choice-opts { flex-wrap:nowrap; }
.ws-b-3col { display:flex; gap:10px; align-items:flex-start; margin-top:10px; flex-wrap:wrap; }
.ws-b-3box { flex:1 1 330px; border:1px solid #e6e3f0; border-radius:8px; padding:8px 10px; min-width:0; }
.ws-b-3box .ws-b-sechead { margin-top:0; }
.ws-b-3box[hidden] { display:none; }

/* ⚖️ 2026-08-20 — FOUR PHONE ROWS, EACH TAGGED, AND THE CARD DIVIDED INTO INCOME AND CREDIT.
   Gil: *"we should have the number on the lead show and a radial or check box to the right to pick
   cell or home. have an add button to add more numbers… the \"+\"."* · *"maybe divide the section
   somewhat, have income a section and credit another section."* · *"Move email under phone
   number(s) to make room."*
   ⚠️ The ＋ reuses `.ws-corner-add` and the rows reuse `[data-ws-row-add-target]`, so this block
   styles LAYOUT only — the reveal behaviour is the sheet's existing one and is not re-implemented. */
/* 🛑 THE HOST IS A GRID, NOT A STACK — `.ws-lay-contact` lays its children in COLUMNS, which is
   right for the row of little boxes it was built for and wrong for three full-width blocks. Without
   the span these three landed side by side and the phone label was squeezed to 75px. Caught by
   rendering it, not by reading the rule: nothing errors, the card just comes out unreadable. */
.ws-lay-contact > .ws-phones,
.ws-lay-contact > .ws-email-row,
.ws-lay-contact > .ws-b-bands { grid-column:1 / -1; }
/* ⚖️ *"We can move email to right side of phone #'s since we are not using that spage tot eh
   right"* — Gil 2026-08-20, reversing his own *"Move email under phone number(s)"* from the same
   morning once he could see the space. The numbers keep their column; the email sits beside it.
   ⚠️ `flex-wrap` AND `align-items:flex-start`, NOT A MEDIA QUERY. These partials draw on three
   screens and the narrow two are narrow because the sheet shares its window with a panel, not
   because the WINDOW is small — a viewport query reads the window and would keep two columns on the
   485px call screen. Wrapping reads the container, so the email drops under the numbers exactly
   where it stops fitting, on whichever screen that is. */
.ws-phones { display:flex; flex-wrap:wrap; align-items:flex-start; gap:6px 22px; margin-bottom:10px; }
.ws-phones > .ws-sub-label { flex:0 0 100%; }
.ws-phone-col { display:flex; flex-direction:column; gap:6px; min-width:0; }
.ws-phones .ws-sub-label { display:flex; align-items:center; gap:8px; margin:0 0 2px; }

/* ⚖️ THE IMPROVEMENT ＋ SITS ON ITS HEADING — Gil 2026-08-20: *"on Goals tab, we need a + to add more
   home improvements."* Same shape as the phone block above it and for the same reason: `.ws-b-plus`
   is `display:flex`, so inside a plain block heading it becomes a block-level flex container and drops
   onto its own line under the words. The heading has to be the flex row. */
.ws-hi-head { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
/* The ceiling note is the ＋'s replacement, so it reads as quiet furniture and never as an error. */
.ws-hi-head .ws-enc-note { margin:0; text-transform:none; letter-spacing:0; font-weight:400; }
.ws-hi-head .ws-enc-note[hidden] { display:none; }
/* Rows 2–4 are the same grid as row 1; the rule is only the reveal state and a little breathing room
   so a four-row list does not read as one block of eight boxes. */
.ws-hi-row { margin-top:2px; }
/* The improvement row's ✕ sits at the end of the pair rather than inside a box, because this row is
   a flex line of two fields and not a grid cell like the contact rows. */
.ws-hi-row { position:relative; }
.ws-hi-del { grid-column:auto; margin-right:0; align-self:center; margin-left:2px; }
.ws-hi-row[hidden] { display:none; }
/* The number keeps its natural box and the tag pair sits straight after it, both hugging their
   content — nothing grows into the leftover width, which is what put 378px of air inside the
   Cell/Home pair on the first render. */
.ws-phone-row > * { flex:0 0 auto; }
.ws-phone-type { flex:0 0 auto; }
/* 🛑 `flex-direction` IS THE WHOLE BUG, AND IT IS WHY GIL SAW TWO SEPARATE FAULTS. He reported
   *"put Cell and home buttons to the right of number box, not under"* and *"Put Phone number and
   email on left side"* as two asks; they are ONE rule. `.ws-f` (line ~3389) sets
   `flex-direction:column` and this rule — same specificity (0,1,0), later in the file — overrode
   `display`, `gap`, `align-items` and `flex-wrap` but never `flex-direction`. So the row was a
   COLUMN with `align-items:center`: the tags stacked UNDER the box, and the whole stack sat dead
   centre of a 909px card. Measured, not guessed — the box's left edge came out at 524px in a row
   starting at 147px, on every surface and at every width from 1090 to 1440, so this was never about
   his window size.
   ⚠️ DECLARED AT (0,2,0) ON PURPOSE. An equal-specificity fix here would TIE with `.ws-f` and win
   only on file order — which is exactly how the radio-width fix on this same block failed twice on
   2026-08-20, reading like a fix while losing the cascade. Winning by specificity cannot be undone
   by somebody moving a rule. Verified by asking the browser which declaration won, not by re-reading
   the stylesheet. */
.ws-lay-contact .ws-phone-row { display:flex; flex-direction:row; align-items:center;
  justify-content:flex-start; gap:10px; flex-wrap:wrap; }
.ws-phone-row { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.ws-phone-row[hidden] { display:none; }
.ws-phone-row .ws-f, .ws-phone-row > .sr-adorn, .ws-phone-row > input { flex:0 0 auto; }
/* The pair of radios sits to the RIGHT of the number, which is where he put it. */
.ws-phone-type { display:inline-flex; gap:4px; align-items:center; }
.ws-phone-opt { display:inline-flex; align-items:center; gap:4px; cursor:pointer;
  font-size:11px; font-weight:700; color:var(--brand-ink); background:#eef1f9;
  border:1px solid #dfe4f2; border-radius:20px; padding:3px 9px; }
.ws-phone-opt:has(input:checked) { background:var(--brand); border-color:var(--brand); color:#fff; }
/* 🛑 THE RADIO INHERITS THE SHEET'S TEXT-INPUT WIDTH AND MUST OUTRANK IT, NOT MERELY CONTRADICT IT.
   `.ws-lay-contact .ws-k-contact_phone input[type] { width:100% }` already exists to make the NUMBER
   box fill its cell, and it matches the radio too — so the bare radio measured 136px and each pill
   came out 182px wide, two of them eating half the row. A first fix at `.ws-phone-opt
   input[type=radio]` lost on specificity (0,2,1 against 0,3,1) and changed nothing on screen while
   reading like a fix. A second try at `.ws-lay-contact .ws-phone-opt …` only TIED (0,3,1 each) and lost on file order,
   because the competing rule is a grouped selector declared later. The selector now carries THREE
   classes so it wins outright — never rely on order across 8,000 lines two people are editing.
   ⚠️ Found by asking the BROWSER which rules matched, not by re-reading the stylesheet. */
.ws-lay-contact .ws-phone-row .ws-phone-opt input[type="radio"] { width:auto; min-width:0; flex:0 0 auto; margin:0; }
/* Email sits under the numbers rather than beside them — his "to make room". */
/* ⚖️ *"Put Phone number and email on left side"* — the phone half was the `flex-direction` bug
   above; the email half is this. `.ws-f input[type=text] { width:100% }` made the box as wide as the
   card, so an address floated across 900px of white with the label stranded at the left. A max-width
   sized to a real address (not a short one) keeps it beside the numbers above it. */
.ws-email-row { margin-bottom:0; flex:0 1 320px; min-width:220px; max-width:360px; }
/* Income | Credit. Same box as his 08-10 Residence/Income/Credit trio, two up instead of three,
   and it wraps rather than squeezing — these partials render on three screens and the narrowest
   of them has no width to give. */
/* ⚖️ 2026-08-20 — INCOME AND CREDIT ARE BANDS NOW, NOT TWO LITTLE BOXES SIDE BY SIDE. Gil asked
   for *"a thick line between these section or shading or something so it shows separation from left
   side"* AND, in the same breath, for four more boxes inside those columns. Measured on all three
   surfaces before choosing: the identity row's eleven columns already summed 703px against a card
   691px wide on Script v2 and 485px on the runner, so the row was overflowing before anything was
   added and his additions cost ~500 more. A rule between columns that do not fit is a rule drawn
   across a scrollbar.
   ⇒ Each band gets the separation he asked for as a BORDER PLUS A SHADED HEAD STRIP — the same
   device the card's own section headers already use (`.ws-section > .ttl`), so the card gains a
   division and not a second visual language.
   ⚠️ STACKED, NOT SIDE BY SIDE. Two 300px-basis boxes was the old shape and it fitted because each
   held one note box. An Income band is five columns and a Credit band is three; at 300px they would
   both spend their lives inside their own horizontal scrollers on the two narrow surfaces. */
.ws-b-bands { display:flex; flex-direction:column; gap:12px; margin-top:4px; }
.ws-b-band { border:1px solid #e0dcec; border-radius:10px; overflow:hidden; min-width:0;
  background:var(--card,#fff); }
.ws-b-band > .ws-b-sechead { margin:0; padding:8px 12px;
  background:linear-gradient(180deg,#f8fafd,#eef2fa); border-bottom:1px solid #e2e7f3; }
/* The band's own contents breathe inside its border; the table goes edge to edge under the strip so
   the head strip and the table's head row read as one object rather than two stacked captions. */
.ws-b-band > .ws-lien-scroll { border:0; border-radius:0; border-bottom:1px solid #eef0f5; }
.ws-b-band > .ws-f, .ws-b-band > .ws-b-unsure, .ws-b-band > .ws-b-unsure-link { margin:8px 12px; }
.ws-b-band > .ws-f:last-child { margin-bottom:10px; }

/* 🛑 A GROUP IS A PLAIN DIV, SO THE BARE `hidden` ATTRIBUTE WORKS — unlike `.ws-lien`, which is
   `display:flex` and beats it (the `.ph-dispo-kids` trap this stylesheet is already named for).
   Stated anyway so a later display change cannot silently un-hide every borrower on the file. */
/* 🛑 THE ROW BOX DOES NOT EXIST, SO THE TINT GOES ON THE CELLS. These rows are `display:contents`
   inside the shared grid — a background on `.ws-b-inc2` would simply never paint, which is the same
   trap this stylesheet already records for the head row's own background. `[hidden]` is handled by
   `.ws-lien-grid > .ws-lien[hidden]`, which these rows are direct children of. */
.ws-b-inc2 > * { background:#fbfbfe; }
/* The second source is the same person, so its row label says WHAT it is rather than WHO — quieter
   than the name above it, which is what tells the eye the two rows belong together. */
.ws-b-src2 { font-weight:600; font-size:10px; text-transform:uppercase; letter-spacing:.04em;
  color:var(--mut); }
/* Sized to the longest answer each cell holds, measured in a browser rather than off the header.
   `credit_score_basis` offers "Experian, freecreditreport or bank app" — the widest option on this
   card by a distance, and a cell sized to its header would clip it to "Experian, freecreditrepo…". */
.ws-b-occ    { flex:0 0 150px; width:150px; }
/* ⚠️ 128, DOWN FROM 268, AND THE OLD WIDTH WAS SIZED FOR AN OPTION THIS MENU NO LONGER OFFERS.
   The note above it names *"Experian, freecreditreport or bank app"* as the widest option; the list
   has since become four short arms and the widest is now "Bank / Experian", which the browser sizes
   at 111px. The width never followed the content — 140px of empty box on every render since, on the
   Prequal tab, where nothing was scoped to trim it. Measured 2026-09-08 by
   `scripts/measure-field-boxes.py`; 128 leaves 10px of headroom.
   🛑 ONE WIDTH FOR BOTH TABS. `body.lead-slim` had already trimmed this to 118 for Script v2 alone,
   so the two hosts of one partial disagreed by 150px about the same cell. */
.ws-b-basis  { flex:0 0 128px; width:128px; }
.ws-b-plusc  { flex:0 0 38px;  width:38px; justify-content:center; }
/* The bands inherit the identity table's density rather than inventing their own — his 2026-08-10
   pass is a rule about the dense row tables on this card, and a band that opted out would read as a
   different table on the same screen. Row label sized to a first name, like the table above. */
.ws-b-inc, .ws-b-cred { --ws-rowlab: 54px; }
.ws-b-inc > .ws-lien-head > *, .ws-b-cred > .ws-lien-head > * { padding:5px 7px; font-size:8px;
  letter-spacing:.04em; }
.ws-b-inc .ws-b-cell, .ws-b-inc .ws-lien-rowlab,
.ws-b-cred .ws-b-cell, .ws-b-cred .ws-lien-rowlab { padding:3px 5px; }
.ws-b-inc .ws-lien-rowlab, .ws-b-cred .ws-lien-rowlab { justify-content:flex-start; padding-left:6px;
  text-align:left; }
.ws-b-inc .ws-b-tick, .ws-b-cred .ws-b-tick { min-width:34px; text-align:center; }
.ws-b-inc .ws-b-tick .ws-tick { display:flex; align-items:center; justify-content:center; width:100%; }

/* ⚖️ *"we need to add those answers that comes up if not sure"* — shut until the score is a guess,
   and always one click from open. See the partial for which fork opens it and why it never shuts
   itself once an answer is in it. */
/* ⚖️ *"Put the Anything else about thier credt can be on teh topo right hand side of the section"*
   — Gil 2026-08-20. It rides the band's own head strip opposite the title, so it costs no row.
   ⚠️ The strip is the shared `.ws-b-sechead` (centred, uppercase); this variant makes it a row and
   puts the title back in the middle with `flex:1` on both sides, so the heading stays centred over
   the table rather than drifting left as the note grows. */
.ws-b-sechead-split { display:flex; align-items:center; gap:14px; text-align:left; }
.ws-b-sechead-split > span:first-child { flex:1 1 auto; text-align:center; padding-left:var(--ws-headnote, 260px); }
.ws-b-headnote { --ws-headnote:0; flex:0 0 260px; max-width:260px; text-transform:none;
  letter-spacing:0; font-weight:600; display:flex; flex-direction:column; gap:2px; }
.ws-b-headnote .ws-vlabel { font-size:10px; font-weight:700; color:var(--mut); }
.ws-b-headnote textarea.cap-note { width:100%; min-height:30px; font-size:11px; }
@media (max-width:760px) {
  .ws-b-sechead-split { flex-direction:column; align-items:stretch; }
  .ws-b-sechead-split > span:first-child { padding-left:0; }
  .ws-b-headnote { flex:1 1 auto; max-width:100%; }
}

/* ⚖️ *"we can add these 3 questions to the B rows, only appears if they don't know"* — Gil
   2026-08-20. They were a block underneath; they are COLUMNS now.
   🛑 A HIDDEN COLUMN IS EVERY CELL IN IT, header and hatch included. Each row is `display:contents`
   over one grid, so a `max-content` track with nothing visible collapses to zero and the columns to
   its right close up — but miss the HEADER cell and three boxes sit under no caption, and miss the
   HATCH on B2-B5 and that row is three cells short, sliding every later cell one track left. */
/* ⚠️ 112, NOT 104. `has_credit_ontime` offers "Tight months", which needs 99px of select in a
   column that was giving it 91 — measured, not read off the header. Same 8px shortfall shape as
   `Where from` two blocks up, found in the same sweep. */
.ws-b-unsure-col { min-width:112px; }
/* 🛑 COLLAPSED, NEVER `display:none`. Every row here is `display:contents` over ONE grid, so a cell
   set to `display:none` stops being a grid item — with three columns hidden the header contributed
   three items and each body row three, and the auto-placer packed header-then-row into a single line
   of six tracks. Measured before the fix: header cells at x 148/202/306, B1's boxes at 574/628/732.
   ⚠️ `visibility:hidden` keeps the slot AND keeps the inputs posting, so a stored answer survives the
   column being shut — the same "hiding is not deleting" rule the VA panel is built on. */
.ws-b-cred[data-ws-unsure-shut] .ws-b-unsure-col {
  visibility:hidden; width:0; min-width:0; padding:0; border-right:0; overflow:hidden;
}

.ws-b-unsure-link { border:1px dashed #cfc9e2; background:#fbfaff; color:var(--brand,#3f6ad8);
  font-size:11px; font-weight:700; border-radius:7px; padding:5px 9px; cursor:pointer; }
.ws-b-unsure-link:hover { border-color:var(--brand,#3f6ad8); background:#f4f1ff; }
.ws-b-unsure-link[hidden] { display:none; }
input[type].ws-b-mi-in { text-align:center; }
.ws-b-mi { flex:0 0 44px; width:44px; }
/* ⚖️ 2026-08-10 — the goal summary carries the three numbers a rep quotes. */
/* ⚖️ THE TOTAL HOLDS THE LEFT, THE BREAKDOWN THE RIGHT — Gil, 2026-09-11: *"Thh header should
   summarize. (Left side) Total - $90,000 . Then right side - Home Improvements- Roof and Solar $40,000
   Debt Consolidation - $50,000."* Everything was already in this band; it was CENTRED as one run, so
   the sum and its parts read as four figures of equal standing.
   🛑 THE BAND ITSELF HAS TO STOP CENTRING, and only on this card. `.ws-section > .ttl` is
   `justify-content:center` for every section on the sheet, which is right for a plain heading and wrong
   for a heading that has become a summary. Scoped by the card's own anchor so no other header moves. */
.ws-section[data-ws-section="goal-position"] > summary > .ttl,
.ws-section[data-ws-section="goal-position"] > .ttl { justify-content:flex-start; }
.ws-goal-brief { display:inline-flex; gap:6px; margin-left:10px; vertical-align:middle;
    flex-wrap:wrap; align-items:center; }
/* The sum sits with the card's name; everything after it is pushed to the far edge, so the gap between
   them is the thing that says "this is the total, those are its parts". */
.ws-gb-chip.ws-gb-total { background:var(--brand-ink,#1a1230); color:#fff; }
.ws-gb-chip.ws-gb-total + .ws-gb-sub { margin-left:auto; }
.ws-goal-brief { flex:1 1 auto; }
.ws-gb-chip { font-size:11px; font-weight:700; background:#eef1f9; color:var(--brand-ink);
  border-radius:20px; padding:2px 9px; letter-spacing:.01em; }
.ws-subject-row input[type] { text-align:left; }

/* ── HIS 2026-08-10 REPORT: "I cant see City or state. There are cells all over the place." ──────
   🛑 ONE ROOT CAUSE, MEASURED IN THE BROWSER, NOT GUESSED: the subject row's cells are flex
   children carrying an explicit width, and `flex-shrink` defaults to 1 — so the moment the row is
   wider than its container (1072px of content in 926px) EVERY cell squeezes and the input scrolls,
   showing the TAIL of the value. That is why "Tampa" read "pa", "FL" read "L", and the ZIP box was
   a 20px sliver holding a 62px number: the box was not too narrow by design, it was being
   compressed at render.
   ⇒ Cells do not shrink. The row scrolls instead — `.ws-lien-scroll` is `overflow-x:auto` and the
   other-properties table has always worked this way. A value a rep cannot read back is worse than
   a scrollbar (his own "the box you fill in… don't put any words in it" reasoning, one level up). */
/* ⚠️ `min-width:0` HERE OUTRANKED EVERY COLUMN FLOOR BELOW — (0,2,0) against a bare class's (0,1,0),
   so specificity won no matter the order and an empty column could collapse to nothing. It was
   written for the flex row that no longer exists. The grid needs no such release. */
.ws-subject-row .ws-lien-cell { flex: 0 0 auto; }
/* ⚖️ THE USAGE BUTTONS WRAP INSIDE THEIR COLUMN — Gil, 2026-09-12: *"▾ Primary · Investment · Second Home,
   needs to be seperate answers."* The subject row is a `max-content` grid, so a column GROWS to fit what
   it holds rather than clipping it.
   🛑 MEASURED BOTH WAYS AT 1280 BEFORE WRITING THIS: the row ALREADY scrolled with the old dropdown
   (1,159px of row in a 692px card), and three buttons in one line pushed it to 1,416px — the usage
   column alone went 112px → 369px. Nothing overlapped; the row just got further to scroll. Capping the
   column and letting the buttons wrap keeps "separate answers" and hands most of that width back.
   ⚠️ Scoped to this one cell: the same choice partial draws goal, what-for and subject rows elsewhere,
   where one line is the rule Gil set. */
.ws-subject-row .ws-k-prop_usage { max-width: 190px; }
.ws-subject-row .ws-k-prop_usage .ws-choice-opts { flex-wrap: wrap; gap: 4px; }
.ws-subject-row .ws-k-prop_usage .ws-choice-opt { padding: 3px 8px; font-size: 12px; }
.ws-subject-row .ws-lien-cell .sr-adorn { min-width: 0; width: 100%; }
/* ⚠️ NOT `width:100%` — that overrides `field-sizing:content` and the auto-size he asked for
   silently does nothing (measured: "FL" still needed 54px in a 45px box). The CELL keeps its fixed
   width so values stay aligned under their headers; the INPUT inside simply sizes to its text. */
/* 🛑 `min-width:0` HERE MADE AN EMPTY BOX ZERO PIXELS WIDE — measured, on the finished screen. With
   `field-sizing:content` an input with no value has no content to size to, so the only thing holding
   it open is its floor, and this rule (0,3,0) was cancelling the 3.5em floor set below. A box a rep
   cannot click is worse than a box that is too wide. */
/* ⚠️ THE FLOOR IS IN `em`, SO IT SHRANK WITH THE TYPE — at 10px, 3.2em is 32px, and an empty box
   that small is a poor target. A pixel floor sits under the em one so dropping the font two levels
   cannot quietly make the boxes unclickable. */
.ws-subject-row .ws-lien-cell input[type] { min-width: max(3.2em, 44px); max-width: 100%; }
/* The 📍 sits beside the ZIP, and the ZIP keeps room for five digits + the pin. */
.ws-subject-row .ws-zip-row { display: flex; align-items: center; gap: 2px; }
.ws-subject-row .ws-zip-row .sr-adorn { flex: 1 1 auto; }
.ws-subject-row .ws-zillow-row { display: flex; align-items: center; gap: 2px; }
.ws-subject-row .ws-zillow-row .sr-adorn { flex: 1 1 auto; }

/* ⚖️ THE PER-CARD COUNTER, BACK ON ALL THREE SCREENS (his 2026-08-10 go). Quiet furniture: it says
   how many boxes are still owed, and goes to a ✓ when the card is whole. Never the alarm colour —
   an unanswered box is work, not a compliance hit. */
.ws-left { margin-left: 8px; font-size: 11px; font-weight: 700; letter-spacing: .03em;
  color: #8a7a2f; background: #fdf7e2; border-radius: 20px; padding: 2px 9px; vertical-align: middle; }
.ws-left.is-done { color: #2c6b3f; background: #e4f2e7; }

/* ⚖️ FIELDS SIZE TO WHAT IS IN THEM — his 2026-08-10 ask: *"can you make all the fields in Borrower,
   Mortgage and property rows to auto adjust on size based on the text in it? would help with
   spacing."* `field-sizing: content` is the browser doing it natively — no JS, no reflow loop, and
   it re-measures as the rep types.
   🛑 BOUNDED AT BOTH ENDS, because unbounded is worse than fixed: `min-width` keeps an EMPTY box
   clickable (a 0px target is not a box), and `max-width` stops one long value — "Chase Sapphire
   Preferred" — pushing every column after it off the screen, which is the cells-all-over-the-place
   he reported. Cells still refuse to shrink (see the subject-row note above), so the row scrolls
   rather than squeezing.
   ⚠️ Scoped to the three ROW TABLES he named. The loose captioned cells elsewhere keep their grid
   widths — those sit under labels and a ragged right edge there reads as broken alignment. */
@supports (field-sizing: content) {
  .ws-b-blocks input[type], .ws-lien input[type], .ws-subject-row input[type] {
    field-sizing: content;
    min-width: 3.5em;
    max-width: 100%;
  }
  .ws-subject-row select, .ws-lien select { field-sizing: content; max-width: 100%; }
}
/* The shared capture box, unanswered — same amber as the sheet's own cells (his 2026-08-10 check). */
input[type].is-missing, textarea.is-missing { background:#fdf7e2; }

/* ══ THE SUBJECT ROW'S COLUMNS LIVE HERE, NOT IN `style="width:…"` ══════════════════════════════
   🛑 THIS IS THE FIX FOR A REVIEW LOOP THAT COST HIM A WHOLE EVENING. He judges these screens from
   a rendered artifact, and the artifact's viewer STRIPS inline style attributes. Every width set
   inline therefore existed on the server and nowhere he could see: the cells fell back to sizing by
   content, and he reported *"I cant see City or state"* and *"cells all over the place"* about a row
   that measured perfectly for me. I told him twice the measurement disagreed with his eyes. His eyes
   were right about the artifact I handed him; my probe was measuring the wrong document.
   ⇒ A width a reviewer cannot see is a width that is not being reviewed. They are classes now, so
   the render he judges and the screen a rep uses are the same screen. */
/* ══ THE COLUMNS SIZE THEMSELVES ═══════════════════════════════════════════════════════════════
   ⚖️ His 2026-08-10 follow-up: *"what happened to making the cells auto adjusting to fit whats
   inside of them?"* — a fair catch. The first pass gave him half of it: `field-sizing:content` sized
   the INPUTS to their text while the CELLS kept fixed widths, so the boxes breathed inside columns
   that did not. The reason for that half-measure was real — a flex row cannot size a column, because
   the header row and every data row are separate flex containers that know nothing about each
   other's content, and independent auto-sizing makes them disagree by a pixel or twenty.
   ⇒ ONE GRID FOR THE WHOLE TABLE, with each row `display:contents` so its cells become items in the
   SHARED tracks. `max-content` then means what he asked for: every column is as wide as the widest
   thing in it, header included, and no wider.
   🛑 A DIRECT CHILD OF A ROW IS NOW A COLUMN. Anything loose in a `.ws-lien` — even a hidden
   input — claims a track and shifts that one row out of step with the rest. Cells only.
   ⚠️ The row's own borders and header background move to the CELLS: `display:contents` removes the
   row box, so a border on `.ws-lien` would simply stop painting. */
.ws-subject-row {
  display: grid;
  grid-auto-rows: min-content;
  grid-template-columns: repeat(var(--ws-cols), max-content);
  align-items: stretch;
}
.ws-subject-row > .ws-lien { display: contents; }
/* 🛑 HIDDEN MUST STILL WIN. `.ws-lien[hidden]{display:none}` and the rule above have equal
   specificity, so the later one takes it — and this file's later one used to be `display:contents`,
   which would have UNHIDDEN every borrower the file does not have. Stated explicitly, after. */
.ws-subject-row > .ws-lien[hidden] { display: none; }
.ws-subject-row > .ws-lien > * { border-bottom: 1px solid #eef0f5; }
.ws-subject-row > .ws-lien:last-child > * { border-bottom: none; }
.ws-subject-row > .ws-lien-head > * { background: #f3f5fa; }
/* 11 now, not 13 — Value and Zillow moved down beside Our value on his 2026-08-10 word.
   🛑 THIS NUMBER IS THE GRID ITSELF. Move a column and forget this, and every cell after the change
   lands in the wrong track — the header says one thing and the box below it holds another, which is
   the worst kind of wrong on a sheet a rep reads back to a customer. It is pinned by test. */
.ws-subject-row { --ws-cols: 11; }
/* 🛑 `.ws-b-blocks` NO LONGER DECLARES ITS OWN COUNT — 2026-08-20, and it was removed because it was
   WRONG and nothing said so. The line above it read "It is pinned by test"; the pin
   (`PrequalSheetLayoutTest`) covers the SUBJECT row's hardcoded count and every
   `.ws-lien-grid[data-ws-cols]` table, and the borrower table was neither. Moving Income and Credit
   into their own bands left 8 cells rendering into 12 declared tracks: the header said VA? over On
   loan's checkbox, the page rendered perfectly, and the whole suite stayed green.
   ⇒ The borrower table is a `.ws-lien-grid` now and takes its count off `data-ws-cols` in the Blade
   that renders the cells — one fact in one file, and the existing guard walks it. */

/* The widths below are now a FLOOR, not a fixed size — `min-width` on a `max-content` track keeps a
   column clickable when it is empty, and lets it grow when a long value arrives. */
.ws-subject-row .ws-c-addr { min-width:130px; }
.ws-subject-row .ws-c-city { min-width:92px; }
/* ⚠️ 74px, not 62px — measured, not guessed. `field-sizing:content` still honours the 3.5em
   min-width that keeps an EMPTY box clickable, so a two-letter state needs the floor plus the cell's
   own padding. At 62px "FL" clipped to "L", which is the exact complaint that started this. */
.ws-subject-row .ws-c-st { min-width:54px; }
.ws-subject-row .ws-c-zip { min-width:96px; }
.ws-subject-row .ws-c-usage { min-width:112px; }
.ws-subject-row .ws-c-type { min-width:112px; }
.ws-subject-row .ws-c-years { min-width:56px; }
.ws-subject-row .ws-c-money { min-width:84px; }
.ws-subject-row .ws-c-zillow { min-width:124px; }
.ws-subject-row .ws-c-hoi { min-width:96px; }
/* The flex floor is gone with the flex row — tracks are `max-content` now. */

/* ⚖️ *"Why is address indented on row"* — because the first data column is sticky at the row-label
   width (`left:var(--ws-rowlab)`), and the subject row is the ONE `.ws-lien` table with no row
   label. With nothing occupying those 44px the cell still parked at 44px, so the row opened with an
   empty gutter. The subject row declares the label width it actually has: none.
   ⚠️ The variable, NOT the sticky rule — the pin still works, it just pins at the left edge. */
.ws-subject-row { --ws-rowlab: 0px; }

/* The years cell holds a plain number now (the script asks years, never months). */
.ws-c-years input[type] { width:100%; text-align:left; }
/* "None" rides under the insurance box; it is the answer that explains an empty one. */
.ws-c-hoi .ws-fcbox-sm { display:flex; align-items:center; gap:4px; font-size:10px; margin-top:2px; }

/* ══ THE BORROWER ROW IS THE WHOLE PERSON ══════════════════════════════════════════════════════
   Veterans · Income · Credit fold into the identity row (his 2026-08-10 *"why not add things
   there"*), so these are the columns that arrived with them. */
.ws-b-credit { min-width:104px; }

/* ⚠️ THE COLUMNS THAT CAME FROM THE OLD STANDALONE TABLES ARE NARROWER NOW, and they have to be:
   each was sized to fill a table of its own. Carried over untouched, the twelve-column row measured
   1526px against a 1500px screen and scrolled sideways on the very screen he widened by hiding the
   side panel. Income type 176→132, Monthly 150→116, VA 196→84 (it holds Yes or No — the rest is in
   the panel). Measured at 1500 after: no horizontal scroll, nothing clipped. */
.ws-b-blocks .ws-b-inc-type  { min-width:120px; }
.ws-b-blocks .ws-b-inc-money { min-width:104px; }
.ws-b-blocks .ws-b-vet-elig  { min-width:76px; }

/* ⚖️ *"just show the VA Column that shows Yes or no… If Yes have it pop up a box with the other
   questions."* The panel is absolutely positioned so opening it does NOT change the row's height —
   a row that grows when one borrower answers Yes shoves every row below it, which is the jumping
   layout he has complained about twice. */
/* 🛑 FIXED, NOT ABSOLUTE — and this is a trap, not a preference. The row lives inside
   `.ws-lien-scroll`, which is `overflow-x:auto`; CSS does not allow one axis to scroll while the
   other stays visible, so `overflow-y:visible` there SILENTLY computes to `auto` and an absolutely
   positioned panel gets clipped by the scroller it grew out of. "Pop up a box" would have shipped as
   a box cut in half, on a screen nobody would think to scroll sideways to fix.
   ⇒ Viewport-anchored, placed by JS off the trigger's own rect, so no ancestor can clip it. */
.ws-vetwrap { position:relative; }
.ws-vetpop { position:fixed; z-index:1200; min-width:210px;
  background:var(--card,#fff); border:1px solid var(--line,#e3e6ef); border-radius:10px;
  box-shadow:0 10px 28px rgba(20,25,50,.16); padding:8px 10px; text-align:left; }
.ws-vetpop[hidden] { display:none; }
.ws-vetpop-h { font-size:10px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--muted,#7b8194); margin-bottom:6px; }
.ws-vetpop-f { display:flex; flex-direction:column; gap:2px; margin-bottom:6px; }
.ws-vetpop-f:last-child { margin-bottom:0; }
.ws-vetpop-f .ws-vlabel { font-size:11px; color:var(--muted,#7b8194); }
/* The ● says "service detail is on file here" without covering the rows below it. */
.ws-vet-dot { color:#3f6ad8; font-size:9px; margin-left:3px; cursor:pointer; }
.ws-vetpop-h { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.ws-vetpop-x { appearance:none; border:0; background:none; cursor:pointer; font-size:15px; line-height:1;
  color:var(--muted,#8b90a0); padding:0 2px; }
.ws-vetpop-x:hover { color:var(--ink,#1e2333); }
.ws-vetpop-f input[type] { width:100%; }

/* ⚖️ *"make the ＋ Add borrower, just the '+' and add it to the top left of the box."* It sits in the
   header's corner cell, which was empty furniture — so the control costs no row of its own. */
.ws-b-plus { appearance:none; border:1px solid var(--line,#dfe3ee); background:#fff; cursor:pointer;
  width:22px; height:22px; line-height:1; border-radius:6px; font-size:13px; font-weight:700;
  color:var(--muted,#6b7280); display:flex; align-items:center; justify-content:center; padding:0; }
.ws-b-plus:hover { background:#f2f5ff; color:var(--ink,#1e2333); border-color:#c9d2ee; }
.ws-b-plus[hidden] { display:none; }

/* ⚖️ SMALLER TYPE ON THE ROW TABLES — his 2026-08-10 *"Font size can be a lot smaller. doesn't have
   to be so big."* 12px in the boxes, 9px in the headers. It compounds with the auto-sizing above:
   narrower glyphs mean narrower `max-content` tracks, so the whole row tightens rather than just
   getting quieter.
   ⚠️ SCOPED TO THE ROW TABLES, not the sheet. The card titles, the counters and the teaching text
   stay where they are — he asked about the dense grids he was looking at, and shrinking prose he did
   not mention is a change he never gets to approve. */
.ws-subject-row, .ws-b-blocks, .ws-lien-scroll .ws-lien { font-size:10px; }
.ws-subject-row input[type], .ws-subject-row select,
.ws-b-blocks input[type], .ws-b-blocks select,
.ws-lien-scroll .ws-lien input[type], .ws-lien-scroll .ws-lien select { font-size:10px; }
.ws-lien-head, .ws-subject-row > .ws-lien-head, .ws-b-blocks > .ws-lien-head { font-size:8px; letter-spacing:.04em; }
.ws-subject-row > .ws-lien-head > *, .ws-b-blocks > .ws-lien-head > * { padding:5px 7px; }
.ws-subject-row .ws-lien-cell, .ws-b-blocks .ws-b-cell, .ws-b-blocks .ws-lien-rowlab { padding:3px 5px; }
/* The old per-column flex bases are dead weight under a grid — cleared so a stale `flex:0 0 176px`
   cannot quietly re-impose a fixed width on a track that is meant to breathe. */
.ws-b-blocks .ws-b-cell { flex:none; width:auto; }
.ws-vetpop, .ws-vetpop input[type] { font-size:11px; }
/* ═══ OVERSIGHT (owner 2026-08-10: "can we have something that does Manager Oversight in") ═══
   🛑 THE TABLE SCROLLS INSIDE ITS OWN BOX. He reviewed the mockup in a narrow panel and reported
   two columns MISSING — they were there, and the viewport had cut the right-hand edge off with no
   sign that it had. A table that silently truncates is worse than one that scrolls, because the
   reader cannot tell the difference between "no data" and "not shown". */
.ovs-wrap { overflow-x: auto; }
.ovs { border-collapse: collapse; width: 100%; font-size: 13px; }
.ovs th { text-align: right; font-size: 9.5px; font-weight: 800; letter-spacing: .03em;
    text-transform: uppercase; color: var(--mut); padding: 9px 8px; line-height: 1.25;
    border-bottom: 1px solid var(--line); white-space: nowrap; }
.ovs th.l, .ovs td.l { text-align: left; }
.ovs td { padding: 10px 8px; text-align: right; border-bottom: 1px solid var(--line);
    font-variant-numeric: tabular-nums; white-space: nowrap; }
.ovs tbody tr:last-child td { border-bottom: 0; }
.ovs tbody tr:hover { background: rgba(53,82,155,.045); cursor: pointer; }
.ovs-name { font-weight: 800; color: var(--brand-ink); text-decoration: none; font-size: 13.5px; }
.ovs-name:hover { text-decoration: underline; }
.ovs-role { font-size: 10.5px; color: var(--mut); font-weight: 600; margin-left: 7px; }
/* ⚖️ WEIGHT AND COLOUR, NO PILL — his 08-09 ruling on the mailbox row ("with all the bubbles, it
   just confuses it"). A zero stays quiet type: here a zero is good news and must not shout. */
.ovs-bad  { color: #a1281a; font-weight: 800; }
.ovs-warn { color: #8a5a00; font-weight: 800; }
.ovs-empty { color: var(--mut); font-style: italic; padding: 18px 8px; }
.ovs-note { margin-top: 12px; font-size: 12.5px; line-height: 1.55; max-width: 780px; }
/* ⚖️ The door from the mailbox to Oversight (owner 2026-08-10: *"how do I pop this screen up?"*).
   Quiet, beside the rep picker, and hidden with it — see the Blade note for why it lives INSIDE
   `[data-ph-mbx-who]` rather than carrying a visibility rule of its own. */
.ph-mbx-oversight { font-size: 12px; font-weight: 800; color: var(--brand); text-decoration: none;
    white-space: nowrap; border-bottom: 1px solid var(--brand); padding-bottom: 1px; }
.ph-mbx-oversight:hover { color: var(--brand-2); }

/* ══ HIS 2026-08-10 DENSITY PASS ═══════════════════════════════════════════════════════════════
   Everything here is space he asked for back, and each rule says which ask it answers. */

/* ⚖️ *"First column, B1, can be a lot smaller."* It was 112px — sized when the label carried a
   borrower's full first name in three other tables that no longer exist. One row per person now, so
   the label is "B1" or a short name and 112px was mostly gutter. It is still the row's identity and
   still the sticky anchor the first data column offsets from, so the variable moves, never the rule. */
.ws-b-blocks { --ws-rowlab: 54px; }
.ws-b-blocks .ws-lien-rowlab { padding-left:6px; }

/* ⚖️ *"make the 3 columns… a check box, instead of Yes or no. Checked means Yes."* — On loan, On
   title, VA and Verifiable. A menu wide enough to say "Yes" cost ~90px each; a tick costs 34.
   ⚠️ The HEADER keeps its words — a bare column of ticks with no caption is unreadable, and the
   caption is the only thing saying what being ticked MEANS. */
.ws-b-blocks .ws-b-tick { min-width:34px; text-align:center; }
.ws-b-blocks .ws-b-tick .ws-tick { display:flex; align-items:center; justify-content:center; width:100%; }
.ws-tick input[type="checkbox"] { width:15px; height:15px; margin:0; cursor:pointer; accent-color:var(--brand,#3f6ad8); }

/* ══ THE TICK'S THIRD STATE — "No" finally looks different from "nobody asked" ═════════════════
   ⚖️ 2026-08-20, Gil: *"i rather have them shaded, as long as when they answer teh shading goes
   away on tehm"*, and on the render: *"yeah that looks good."*

   🛑 IT IS STILL A REAL CHECKBOX; ONLY THE PAINT CHANGED. `appearance:none` takes the native tick
   away and the glyph is drawn from `[data-state]` on the label, so the element every listener and a
   dozen tests address by `input[type=checkbox][name=…]` is untouched. Three states, one control,
   the SAME 15px footprint — the columns on the mortgage grid do not move, which is the thing his
   density rulings care about.
   ⚠️ `.ws-tick.ws-tick3` (0,3,1) rather than `.ws-tick3` (0,2,1), so this cannot be decided by file
   order against the rule directly above it. Two desks edit this stylesheet. */
.ws-tick.ws-tick3 input[type="checkbox"] {
    appearance:none; -webkit-appearance:none;
    width:15px; height:15px; min-height:15px; margin:0; flex:0 0 auto; cursor:pointer;
    display:flex; align-items:center; justify-content:center;
    border:1px solid #c8ccd8; border-radius:3px; background:#fff;
    font-size:11px; font-weight:800; line-height:1; color:#1a1230; }
/* nobody has answered it — the same amber every other unanswered box on the sheet wears */
.ws-tick.ws-tick3[data-state=""] input[type="checkbox"] { background:#fdf7e2; border-color:#e6d49b; }
.ws-tick.ws-tick3[data-state="yes"] input[type="checkbox"] { border-color:var(--brand,#35529b); color:var(--brand,#35529b); }
.ws-tick.ws-tick3[data-state="yes"] input[type="checkbox"]::before { content:"\2713"; }
.ws-tick.ws-tick3[data-state="no"] input[type="checkbox"] { border-color:#c8ccd8; color:#9aa0b0; }
.ws-tick.ws-tick3[data-state="no"] input[type="checkbox"]::before { content:"\2715"; }
/* 🛑 AND IT HAS TO OUTRANK THE GRID'S TEXT-BOX FLOOR, WHICH IS NOT OPTIONAL AND WAS NOT OBVIOUS.
   `.ws-lien-grid .ws-lien-cell input[type]` carries `min-width:max(3.2em,44px)` — a rule written for
   text boxes that reaches every input in the table. It is (0,3,1); the rule above is also (0,3,1),
   so the tie went to FILE ORDER and the floor won, stretching the ORIGINAL tick into a 44px amber
   slab. Seen in a render, then confirmed by reading the computed width rather than by eye.
   ⚠️ This is the THIRD time this exact selector has done this — it already carries a warning of its
   own further down for the fixed/ARM radios. A rule that floors "every input" outranks anything
   written about one KIND of input; scope past it, never reorder against it. */
.ws-lien-grid .ws-lien-cell .ws-tick.ws-tick3 input[type="checkbox"],
.ws-lien-grid .ws-lien-cell .ws-tick.ws-tick3 input[type] {
    width:15px; min-width:15px; max-width:15px; }
/* a tick answered on file, or made moot by another answer, is not "still needed" */
.ws-tick.ws-tick3[data-onfile][data-state=""] input[type="checkbox"],
.ws-f[data-implied] .ws-tick.ws-tick3[data-state=""] input[type="checkbox"],
.ws-lien-scroll[data-implied] .ws-tick.ws-tick3[data-state=""] input[type="checkbox"] {
    background:#f6f7fa; border-color:#e2e5ee; }
/* the cell's own box has nothing to draw around a tick — it never did for the native one */
.sr-adorn:has(> .ws-tick3) { border:0; background:none; width:auto; overflow:visible; }

/* ══ A RADIO PAIR IS ONE QUESTION, SO BOTH HALVES SHADE AND BOTH CLEAR TOGETHER ════════════════
   ⚖️ 2026-08-20, Gil, reading the tick render: *"but what about teh radial buttons, like Fixed and
   Arm. Picking one clears both"* — he is describing the behaviour AND naming the unit. Fixed/ARM,
   Cell/Home and the rest are not two boxes, they are one unanswered question.
   🛑 HE ALSO CAUGHT A HOLE IN MY OWN MEASUREMENT. The count that found "124 boxes shade white"
   scored every radio as FILLED, because it read the input's `value` attribute — which is always
   "Fixed" or "ARM" — instead of asking whether anyone had PICKED one. 21 radio groups the live
   script asks were never in the population at all. **Ask whether the question is ANSWERED, never
   whether the control has a value.**
   ⚠️ THE MARK IS PUT ON BY THE PAGE, NOT THE SERVER, and that is a deliberate difference from every
   other amber here. A group has no single element to hang a server-side class on, and its parts are
   drawn by three different partials; the page can see `:checked` across the whole group in one line.
   It fails safe — if the script does not run, a group simply is not shaded. */
.ws-optshade { background:#fdf7e2; border-radius:5px; }
/* 🛑 AND ON THE CONTROL ITSELF, BECAUSE ON A BARE LABEL THE COLOUR IS A HAIRLINE. Fixed/ARM are
   inline labels with no padding — the tint behind "Fixed" is text-height and reads as nothing, which
   is how it looked in the first render (the class was applied and the computed colour WAS amber; it
   simply could not be seen). The Cell/Home and shopping options are padded chips and did read.
   ⚠️ NO WIDTH OR HEIGHT IS SET HERE, ON PURPOSE. The Terms cell was measured at 0px of slack when
   fixed/ARM became radios (`measure-mortgage-grid.py` ④), so anything that changes their box would
   push PMI off the right edge. This only repaints the dot the existing rules already sized. */
.ws-optshade input[type="radio"] {
    appearance:none; -webkit-appearance:none;
    background:#fdf7e2; border:1px solid #d8c48f; border-radius:50%; }


/* ⚖️ 2026-08-20 — THE NAMES GET SOME OF THE ROOM BACK. `.ws-b-blocks .ws-b-cell { flex:none;
   width:auto }` (the 08-10 density pass) sizes every cell to its content, which was right when
   eleven columns were fighting for 691px. With Income and Credit moved to their own bands the row
   sums 330px inside a 907px card, and an empty First box was rendering 38px wide — narrower than
   the word "First" above it, and too narrow to read "Jennifer" back off the screen.
   ⚠️ A MIN, NEVER A FIXED WIDTH. The cells still grow with what is typed, which is the behaviour the
   density pass bought; this only stops them collapsing below what a name needs. MI stays 44 — it
   holds one letter, and that is his own 08-10 ruling. Measured after: 462px of row inside 907. */
.ws-b-blocks .ws-b-name { min-width:104px; }
.ws-b-blocks .ws-b-age  { min-width:52px; }

/* ⚖️ *"if you make the Home and cell fields, big enough just to fit 10 digits and email auto sizing.
   we have enough room to put an anything else about box… in right hand side."* Two phone boxes that
   were a third of the row each are the width of the number they hold; the room goes to the notes. */
.ws-lay-contact { align-items:start; }
.ws-lay-contact .ws-phone-row { grid-column:span 2; }
/* ⚠️ A MAX WITHOUT A MIN COLLAPSED THE BOX TO 20px. `field-sizing:content` gives an EMPTY input no
   content to size to, so capping the width without flooring the input left a 20px sliver where a
   10-digit number goes — "big enough just to fit 10 digits" turned into big enough for none. Both
   ends, always, on anything that auto-sizes. */
/* ⚠️ "10 digits" ON SCREEN IS 14 CHARACTERS. The stored number is ten digits; the box SHOWS
   "(954) 818-2888" — brackets, a space and a dash — and a box sized to the digits clipped the number
   it was sized for. Measured with a real formatted number in it, not counted off the spec. */
/* 🛑 EVERY NUMBER ROW, NOT THE FIRST TWO BY NAME — 2026-08-20, and it was caught by LOOKING at the
   card rather than by reading this rule. These two lines named `contact_phone` and `contact_cell`,
   which was the whole block when Gil's 08-02 shape had exactly two boxes. His four-number redesign
   added rows 3 and 4 and they inherited nothing: `.ws-f input[type=text] { width:100% }` stretched
   them the full width of the card, which pushed each row's Cell/Home tags onto a line of their own —
   *"put Cell and home buttons to the right of number box, not under"*, still true on half the rows,
   in the very change that was fixing it.
   ⚠️ KEYED ON `.ws-phone-row` NOW, so a fifth number would inherit it the day it is added. A rule
   that lists the keys it governs is a rule that stops governing the next one. */
.ws-lay-contact .ws-phone-row .sr-adorn { width:156px; max-width:156px; }
.ws-lay-contact .ws-phone-row input[type] { min-width:136px; width:100%; }
.ws-lay-contact .ws-k-contact_email input[type] { min-width:180px; }
.ws-lay-values .ws-w-money input[type] { min-width:96px; }
/* ⚠️ Sized for a REAL address, not a short one — a 31-character email still overflowed a 3-track
   cell. Four tracks, and the box grows into them as it is typed. */
.ws-lay-contact .ws-k-contact_email { grid-column:span 4; }
.ws-lay-contact .ws-k-contact_email .sr-adorn { max-width:100%; }
.ws-lay-contact .ws-k-income_note, .ws-lay-contact .ws-k-credit_note { grid-column:span 6; }
.ws-note-cell textarea.cap-note { width:100%; min-height:34px; }

/* ⚖️ *"Why is Value indented?"* was answered here by re-placing the three values at the left edge of
   the twelve-track grid. Those rules are gone with the boxes — Gil's 2026-08-19 row (`.ws-vals-row`)
   owns them now, and it starts at the left edge by construction, so his question stays answered. */
.ws-lay-values .ws-k-estimated_equity[hidden] { display:none; }

/* ⚖️ *"Make the value boxes the right size, enough to fit 7 digits with commas."* 9,999,999 — seven
   digits and two commas. Measured at the sheet's own size rather than guessed, and it is a MAX, so a
   shorter number still sits in a box the eye can group with its neighbours. */
.ws-lay-values .ws-w-money .sr-adorn { max-width:132px; }
.ws-zillow-row { display:flex; align-items:center; gap:5px; }
.ws-zillow-row .sr-adorn { flex:0 1 132px; }
/* ⚠️ NOTHING ADDED HERE ON 2026-08-16, DELIBERATELY. Redfin/Realtor were briefly put in this row
   and it needed `flex-wrap` + a `min-width` floor to survive them — which then made the ORIGINAL
   🏠-plus-box row wrap onto two lines where it had always been one. The buttons moved to the
   full-width strip on the address row instead, so this row is exactly as Gil left it. Do not add
   a third control here: the grid track is ~118px. */
/* ⚖️ `.ws-lookup-bar` IS GONE — Gil 2026-08-19, asked directly and answered: *"drop the ones in the
   look up bar."* His 08-16 *"move them all up by the Subject Property header"* is what built it; by
   08-19 it held only Redfin and Realtor (the 📍 and G had gone back to the zip cell that morning) and
   those two now caption their own value boxes, so the bar was a second door to two links that already
   have one. The RULES are deleted with the markup: a stylesheet keeping a block for an element nobody
   renders is the same green-light-with-no-road-under-it the value-box spans were.
   ⚠️ THE MEASUREMENT THAT BUILT IT STILL STANDS and is why the row can hold them now: three lookups
   never fitted a ~118px value cell — they wrapped into a ragged three-line stack, and widening the
   ITEM only made them paint over "Our value" because the TRACK never grew. `.ws-vals-row` gives each
   site a 126px COLUMN, which is the room that cell never had. */

/* ⚖️ THE MORTGAGE AND OTHER-PROPERTIES TABLES AUTO-FIT TOO (his 2026-08-10). Same machinery as the
   subject row — one grid, rows `display:contents`, `max-content` tracks.
   🛑 `hidden` must be restated: it and the contents rule have equal specificity, and without this an
   un-added second mortgage would appear on every file. Same trap as the borrower rows. */
.ws-lien-grid { display:grid; grid-auto-rows:min-content;
    grid-template-columns:repeat(var(--ws-cols), max-content); align-items:stretch; }
/* 🛑 THE COUNT COMES OFF AN ATTRIBUTE, BECAUSE AN INLINE `style="--ws-cols:…"` IS INVISIBLE IN THE
   ARTIFACT HE REVIEWS — and unlike a width, losing THIS one does not merely mis-size a column: with
   no count, `repeat(var(--ws-cols), max-content)` is invalid, `grid-template-columns` computes to
   `none`, and every cell auto-places into ONE column. Measured with inline styles stripped: the
   mortgage table 202px → 1826px tall, other-properties 349px → 6676px. The blade keeps deriving the
   number (`count($columns) + 1`); these rules only carry it across the strip. Range 2–30 covers
   every table today (3, 11, 22) with room to grow; `ALienTableSurvivesTheRenderStrip` fails loudly
   if a table ever emits a count with no rule, rather than letting it collapse quietly. */
.ws-lien-grid[data-ws-cols="2"] { --ws-cols:2; }
.ws-lien-grid[data-ws-cols="3"] { --ws-cols:3; }
.ws-lien-grid[data-ws-cols="4"] { --ws-cols:4; }
.ws-lien-grid[data-ws-cols="5"] { --ws-cols:5; }
.ws-lien-grid[data-ws-cols="6"] { --ws-cols:6; }
.ws-lien-grid[data-ws-cols="7"] { --ws-cols:7; }
.ws-lien-grid[data-ws-cols="8"] { --ws-cols:8; }
.ws-lien-grid[data-ws-cols="9"] { --ws-cols:9; }
.ws-lien-grid[data-ws-cols="10"] { --ws-cols:10; }
.ws-lien-grid[data-ws-cols="11"] { --ws-cols:11; }
.ws-lien-grid[data-ws-cols="12"] { --ws-cols:12; }
.ws-lien-grid[data-ws-cols="13"] { --ws-cols:13; }
.ws-lien-grid[data-ws-cols="14"] { --ws-cols:14; }
.ws-lien-grid[data-ws-cols="15"] { --ws-cols:15; }
.ws-lien-grid[data-ws-cols="16"] { --ws-cols:16; }
.ws-lien-grid[data-ws-cols="17"] { --ws-cols:17; }
.ws-lien-grid[data-ws-cols="18"] { --ws-cols:18; }
.ws-lien-grid[data-ws-cols="19"] { --ws-cols:19; }
.ws-lien-grid[data-ws-cols="20"] { --ws-cols:20; }
.ws-lien-grid[data-ws-cols="21"] { --ws-cols:21; }
.ws-lien-grid[data-ws-cols="22"] { --ws-cols:22; }
.ws-lien-grid[data-ws-cols="23"] { --ws-cols:23; }
.ws-lien-grid[data-ws-cols="24"] { --ws-cols:24; }
.ws-lien-grid[data-ws-cols="25"] { --ws-cols:25; }
.ws-lien-grid[data-ws-cols="26"] { --ws-cols:26; }
.ws-lien-grid[data-ws-cols="27"] { --ws-cols:27; }
.ws-lien-grid[data-ws-cols="28"] { --ws-cols:28; }
.ws-lien-grid[data-ws-cols="29"] { --ws-cols:29; }
.ws-lien-grid[data-ws-cols="30"] { --ws-cols:30; }
.ws-lien-grid > .ws-lien { display:contents; }
.ws-lien-grid > .ws-lien[hidden] { display:none; }
.ws-lien-grid > .ws-lien > * { border-bottom:1px solid #eef0f5; }
.ws-lien-grid > .ws-lien:last-child > * { border-bottom:none; }
.ws-lien-grid > .ws-lien-head > * { background:#f3f5fa; }
/* One shared floor instead of eleven inline ones — enough to click, small enough to let the content
   decide. The header text already gives each column a sensible natural width. */
.ws-lien-grid .ws-lien-cell { min-width:58px; }
.ws-lien-grid .ws-lien-cell input[type] { min-width:max(3.2em, 44px); max-width:100%; }

/* ══ HIS MORTGAGE-ROW IDEA, 2026-08-10 ═════════════════════════════════════════════════════════
   *"make the mortgage row a little taller so we can have the rate on top and below in smaller font
   a check box for Fixed and arm. A check box for PMI. Year and month boxes could be a lot smaller,
   need to fit 2 digits each."* The row trades a little height for a lot of width. */
/* ⚖️ RATE + TERM ON ONE LINE, FIXED/ARM UNDER THEM — his 2026-08-11 *"Make rate field smaller and
   add the Term to the right of it. make rate only big enough for xx.xxx."* The rate is capped at its
   own longest real answer (10.125 = six characters plus the % adornment) instead of being handed the
   column's whole width, which is what left room for the term beside it. */
/* The free-and-clear tick, now under the 1st lien's current balance (his 2026-08-11).
   🛑 IT STAYS BLACK WHEN THE TABLE GREYS. The greying is what this tick CAUSES, so rendering it in
   the same muted grey as the boxes it disables makes the one live control on a greyed table look
   dead — and a rep who cannot see it cannot untick it. */
/* ⚖️ THE DEBTS CARD, REORGANISED (his 2026-08-11). One table across the card so a column can be
   scanned top to bottom. ⚖️ The kind of debt was a slim left column until 2026-08-19, when Gil said
   *"no seperation, visually"* and it became the BAND below. */
/* 🛑 A FLOOR UNDER THE TABLE, AND THE CARD SCROLLS RATHER THAN THE HINT VANISHING (design rule 7:
   wide content scrolls INSIDE its own container). MEASURED, not chosen: the runner pins this sheet
   in a 485px rail, which squeezed Balance to 69px and Rate to 64px — and at that size *"$xxx,xxx"*
   and *"xx.xxx%"* were CLIPPED. 🛑 A CLIPPED HINT IS INVISIBLE TO EVERY WIDTH HARNESS, because a
   placeholder is not content and never trips `scrollWidth` — `measure-debts-grid.py` only sees it by
   typing the hint in as a value, which is the same trap the debt-name column fell into in August.
   ⚠️ 530px IS A MEASURED TRADE-OFF, not a round number, and BOTH sides of it were measured on the
   runner: below ~505px the hints clip again; above ~535px the Pay off / Keep buttons are pushed off
   the 485px rail, and a rep mid-call cannot tick the one control that moves a debt into the loan.
   530 clips nothing and keeps Pay off on screen; only the ✕ column falls off the edge, which is the
   one control on the row that can wait for a sideways nudge.
   ⚠️ NORMALLY THE RULE HERE IS "SIZE WITH HEADROOM, NOT TO THE MEASUREMENT" (the 2px-short lesson
   from the name boxes) — and it is deliberately not applied, because headroom in one direction is a
   lost control in the other. What replaces it is a HARNESS: `measure-debts-grid.py` types each hint
   in as a VALUE and fails on a clip, at whatever width it is asked for. A guessed margin cannot do
   that; a probe re-run on the next font change can.
   On the prequal tab (1149px) and Script v2 (881px) nothing scrolls at all. */
.s2-debts-one { width:100%; min-width:530px; border-collapse:separate; border-spacing:0 3px; }
/* ── ⚖️ THE CATEGORY BAND (Gil 2026-08-19) ────────────────────────────────────────────────────
   ⚖️ R-028. *"no seperation, visually"* — the seam was a 1px hairline, chosen so the four groups
   would still read as one list. It read as nothing at all: four categories, thirteen lanes, and the
   only thing telling a rep where Credit cards ended was a line the same weight as every row border
   above it. A tinted full-width band is what a category is.
   🛑 IT IS FURNITURE, SO IT TAKES NO HIGHLIGHT COLOUR. Amber means "still needed" and blue means
   "the script is pointing here"; the band is a neutral slate tint and stays out of both. */
.s2-debts-one tr.s2-dgroup > th.s2-dgroup-cell {
    text-align:left; padding:5px 9px 4px; background:#eef1f7;
    border-top:1px solid #d8dfeb; border-bottom:1px solid #d8dfeb; border-radius:5px;
}
.s2-debts-one .s2-dgroup-name { font-size:10.5px; font-weight:800; letter-spacing:.05em;
    text-transform:uppercase; color:#41506b; }
/* ⚖️ R-028: *"The add button should just be a '+' button in each category."* A square target, not a
   word: on four bands at once, four repetitions of "＋ add" read as instructions and the eye has to
   discard them. The label a screen reader and a hover get is still the whole sentence. */
.s2-debts-one .s2-dgroup-cell .s2-add {
    display:inline-flex; align-items:center; justify-content:center; margin-left:8px;
    width:17px; height:17px; padding:0; font-size:14px; font-weight:700; line-height:1;
    border:1px solid #c3cddd; border-radius:5px; background:#fff; color:var(--brand);
    vertical-align:middle;
}
.s2-debts-one .s2-dgroup-cell .s2-add:hover { background:var(--brand); border-color:var(--brand);
    color:#fff; text-decoration:none; }
.s2-debts-one .s2-dgroup-cell .s2-add:focus-visible { outline:2px solid var(--brand); outline-offset:1px; }
.s2-debts-one .s2-grid-name { width:26%; }
/* The name header sits over LEFT-aligned boxes, so it is left-aligned too — a centred caption above
   a left-aligned column reads as belonging to the column beside it. */
.s2-debts-one th.s2-grid-name { text-align:left; padding-left:9px; }
/* 🛑 THE HINT HAS TO FIT, AND A CLIPPED HINT IS INVISIBLE TO THE WIDTH HARNESS. A placeholder is not
   content, so `scrollWidth > clientWidth` never fires on one — `measure-prequal-widths.py` reported
   0 clips while all four debt-name boxes showed *"what i…"* at 1280px (47px of hint in 40px of box).
   The column is a PERCENTAGE, so it only bites at laptop widths and looks fine at 1500. Sized with
   headroom rather than to the measurement, which is the 2px-short lesson from the name boxes. */
.s2-debts-one .s2-grid-name input { min-width:64px; }
.s2-debts-one .s2-grid-po { width:1%; white-space:nowrap; }
/* ✕ REMOVE THIS DEBT (2026-08-12) — a slim last column, quiet until it is wanted. It is grey, not
   red: deleting a row a rep typed by mistake is ordinary tidying, and a red control on every filled
   line would read as a warning about the debt rather than a button about the row. It carries no
   header caption — see the partial. */
.s2-debts-one .s2-grid-x { width:1%; white-space:nowrap; text-align:right; padding-left:6px; }
.s2-del { background:none; border:0; padding:2px 3px; font:inherit; font-size:11px; line-height:1;
    color:#a9a5bb; cursor:pointer; border-radius:4px; }
.s2-del:hover { color:#b3261e; background:#fbeceb; }
.s2-del:focus-visible { outline:2px solid var(--brand); outline-offset:1px; }
/* ⚠️ THE HAIRLINE SEAM IS GONE — the band above IS the seam now, and keeping both would draw a rule
   directly under the band. `.is-group-top` stays on the row because the ✕ handler reads it: the first
   row of a group is never hidden, it carries the group's legacy keys and hiding it would take a
   whole category off the screen with no way back. It is a JS hook, no longer a painted thing.
   ⚠️ And the per-row bottom rule goes with it: `border-spacing` above already separates the rows, and
   a line under a boxed input is the underline Gil asked us to stop drawing. */
.s2-debts-one td { border-bottom:0; }
/* The two card-wide questions, on one line under the table rather than stacked loose beneath it. */
.ws-debts-foot { display:flex; gap:18px; align-items:flex-end; flex-wrap:wrap; margin-top:10px; }
.ws-debts-foot .ws-f { flex:0 1 auto; }
.ws-debts-foot .ws-k-debts_note { flex:1 1 320px; }
.ws-debts-foot .ws-k-debts_note textarea { width:100%; }
.ws-fcbox-inline { display:flex; align-items:center; gap:5px; margin-top:3px; font-size:10.5px;
    line-height:1.2; white-space:nowrap; cursor:pointer; }
.ws-lien-scroll[data-implied] .ws-fcbox-inline,
.ws-lien-scroll[data-implied] .ws-fcbox-inline span { color:var(--ink); }
.ws-lien-scroll[data-implied] .ws-fcbox-inline input { background:#fff; }
.ws-rate-line { display:flex; align-items:center; gap:4px; width:100%; }
/* 🛑 `width:auto` IS THE HALF THAT WAS MISSING, AND IT IS THE ROOT OF HIS *"the term is behind the
   interest rate field"*. `.ws-lien-cell .sr-adorn { width:100% }` gives EVERY adornment the full
   cell, and this rule then pins the rate's copy so it cannot shrink — so the rate's wrapper claimed
   all 189px of the strip and the term had literally nowhere to sit. Measured: the term's own wrapper
   was squeezed to 2px and its select overflowed to 22px, 20px past the cell, on top of the next
   column. Pinning the term without this line only moved the spill from one box to the other; both
   declarations are the fix and neither is enough alone. */
.ws-rate-line .sr-adorn:first-child { flex:0 0 auto; width:auto; }
.ws-lien-grid .ws-rate-line input[type],
.ws-subject-row .ws-rate-line input[type] { width:auto; min-width:0; }
/* xx.xxx and no more — measured, not guessed: six characters at this type size plus the % that sits
   inside the adornment. `field-sizing` still shrinks a shorter answer; this only stops it growing. */
.ws-rate-line .ws-rate-box input[type] { max-width:58px; }
.ws-rate-line select { max-width:74px; }
/* 🛑 THE TERM BOX HAD NO FLOOR AND THE RATE HAD A PIN, so the flex row spent every pixel on the rate
   and squeezed the term's wrapper to 2px — the select then overflowed it to 22px (arrow, no text)
   and its right edge landed 20px past the cell, on top of the next column. That is what the owner
   reported on 2026-08-19 as *"the terms tab is missing the term, 30 year etc. Is it behind the
   interest rate field"*: not an overlap he imagined, a real one, measured in Chrome.
   ⚠️ 58px IS MEASURED, NOT CHOSEN: "Other" is the widest option at 27px of 10px Inter, plus this
   control's own 20px of padding and ~10px of select arrow. `flex:0 0 auto` is the load-bearing half
   — a min-width alone still lets a flex item shrink below its basis, which is how it got to 2px
   with `min-width:auto` already set. Proven by `scripts/measure-mortgage-grid.py`, which reverts
   this rule and requires the overlap to come back. */
.ws-rate-line .ws-term-box { flex:0 0 auto; display:flex; align-items:center; }
.ws-rate-line .ws-term-box select { min-width:58px; max-width:74px; }
/* Fixed/ARM on the left, the PMI tick hard right — his 2026-08-19 *"Move PMI check box on bottom row
   with Fixed, Arm. To the right hand side."* One strip, the same 13px height the bare `.ws-arm` had,
   so the row keeps the height it was measured at rather than growing a fourth line. */
/* ⚠️ `flex-start`, NOT `space-between`, AND THAT IS ABOUT BEING ABLE TO PROVE IT. Both would park the
   PMI tick on the right — `space-between` sends the last of two items to the end, `margin-left:auto`
   on the tick does the same — and a position held by TWO sufficient rules cannot be controlled:
   removing either one leaves the tick exactly where it was, so the harness reported BLIND on a
   screen that was correct. One mechanism, on the element being positioned, and the mutation that
   removes it moves the tick 200px. `space-between` would also have spread a THIRD item oddly if one
   is ever added here. */
.ws-arm-line { display:flex; align-items:center; justify-content:flex-start; gap:4px; width:100%;
    min-height:13px; }
.ws-arm-line > .sr-adorn { width:auto; flex:1 1 auto; min-width:0; }
/* 🛑 `margin-left:auto` IS WHAT ACTUALLY PARKS IT RIGHT, and saying so cost a control. The strip's
   `justify-content:space-between` READS like the rule that does it and does not: at the grid's
   natural width the Terms cell is sized by the RATE line above, the fixed/ARM strip fills it to
   within the 8px gap, and swapping `space-between` for `flex-start` moved the tick by ONE pixel —
   so ④ stayed green under a mutation that should have reddened it. Worse, on a "Not sure" file the
   fixed/ARM control is a `.sr-adorn` that GROWS, so the tick was being pushed right by a rule about
   a different element entirely, and on a Fixed/ARM file nothing was pushing it at all.
   ⇒ One declaration on the thing being positioned, true for both shapes and for any cell width.
   `scripts/measure-mortgage-grid.py`'s right-edge control widens the cell to 420px and requires
   removing this line to move the tick, which is the check the `justify-content` version could not
   pass. */
.ws-pmi-tick { display:flex; align-items:center; gap:2px; flex:0 0 auto; margin-left:auto; }
.ws-pmi-tick .ws-tick { margin:0; }
.ws-pmi-cap { font-size:9px; font-weight:700; color:var(--mut,#6b7280); line-height:1; }
/* A "Doesn't know" file keeps its MENU rather than the tick, and that select must not eat the strip
   the radios are sharing — same cap the term box gets, for the same reason.
   ⚠️ 112, NOT 74 — AND THE OLD CAP WAS CLIPPING THE ONE ANSWER IT EXISTS FOR. This control only
   appears in place of the tick when PMI is a MENU, and the menu is there for exactly one option:
   "Doesn't know", *"an answer reps actually give"* (pq-worksheet-controls.md). Measured by
   `scripts/measure-field-boxes.py`: that option needs 108px of select and had 74, so the file this
   shape exists to serve was the file that could not read its own answer back. */
.ws-pmi-tick select { max-width:112px; min-width:0; }
/* ⚖️ FREE AND CLEAR **UNDER** THE BALANCE — his 2026-08-19, and the layout is the whole fix. The tick
   has been inside the balance cell since 08-11, but `.ws-lien-cell` is a ROW flex, so it sat to the
   RIGHT of the box and widened the column. This one cell stacks. `align-items:stretch` keeps the
   balance input full width; without it a column cell shrink-wraps its children. */
.ws-lien-grid .ws-fc-host { flex-direction:column; align-items:stretch; justify-content:center; }
.ws-lien-grid .ws-fc-host .ws-fcbox-inline { align-self:flex-start; }
/* ⚖️ ONLY WHEN IT IS AN ARM — his balloon ask, and `visibility` rather than `display` for two
   reasons that are both about not losing data. A `display:none` CELL on a grid whose rows are
   `display:contents` would collapse its track and slide every later cell one column left, so the
   header would stop sitting over its own box. And a hidden-by-visibility input STILL POSTS — a
   balloon date already on file survives a save made while the loan happens to read Fixed, which
   `display:none` would also have done but `@unless` around the markup would NOT: skipping it drops
   the key to the complement pass, which prints it back as the loose labelled box he asked to move. */
.ws-lien-grid .ws-armonly[data-ws-arm-off] > * { visibility:hidden; }
/* ⚖️ THE ＋ IN THE EMPTY CORNER — *"just add '+' bolded"*. It fills the head row's row-label square,
   which is the one cell on this grid that has never carried anything. Sized to the whole cell so the
   target is the corner itself rather than a glyph a rep has to hit. */
.ws-corner-add { appearance:none; -webkit-appearance:none; background:none; border:0; cursor:pointer;
    width:100%; height:100%; padding:0; margin:0; line-height:1; font-family:inherit;
    font-size:16px; font-weight:900; color:var(--brand,#3f6ad8); }
.ws-corner-add:hover { background:#e6ecfb; }
.ws-lien-addcell { padding:0; }
/* ⚖️ "Anything else about the loan" — RIGHT-HAND SIDE, directly under the mortgage rows (his
   2026-08-19). It is where the balloon tick's prompt sends a rep, so it has to be the next thing
   their eye lands on rather than a box further down the card. `margin-left:auto` parks the block
   right; the box itself is wide enough to hold a sentence and shrinks with the card. */
.ws-mortgage-note { display:flex; justify-content:flex-end; margin:8px 0 2px; }
.ws-mortgage-note .ws-f { flex:0 1 460px; max-width:100%; }
.ws-mortgage-note textarea { width:100%; }
/* ⚖️ THE TICK'S PROMPT — his *"a pop up that says to put when in Anything else about the loan box."*
   🛑 A BUBBLE, NOT `window.alert`. An alert takes the keyboard from a rep on a live call and must be
   dismissed before they can type the very thing it asks for, so the prompt itself would be why the
   answer never got written.
   🛑 AND IT IS ANCHORED TO THE **NOTE BLOCK**, NOT TO THE TICK, because the tick lives inside
   `.ws-lien-scroll` and that scroller CLIPS. Measured on the real page: drawn in the cell, the
   bubble came out 23px tall with its bottom 4px past the scroller's edge and `elementFromPoint` at
   its own centre returned NOTHING — it rendered, it reported itself visible to every width check,
   and no human could see it. Anything that must be READ stays out of that table. */
.ws-mortgage-note { position:relative; }
.ws-tick-bubble { position:absolute; z-index:6; right:0; bottom:calc(100% + 6px);
    white-space:nowrap; background:#1e2432; color:#fff; font-size:11px; font-weight:600; line-height:1.3;
    padding:6px 9px; border-radius:6px; box-shadow:0 4px 14px rgba(20,26,40,.28); pointer-events:none; }
/* the tail points DOWN at the box, so the message and its destination read as one thing */
.ws-tick-bubble::after { content:""; position:absolute; right:22px; bottom:-4px;
    border-left:5px solid transparent; border-right:5px solid transparent; border-top:5px solid #1e2432; }
/* The box it points at answers back, so the rep's eye is carried the whole way rather than told to
   go looking. Fades on its own — a permanent ring reads as an error state. */
.ws-mortgage-note.is-called textarea { outline:2px solid var(--brand,#3f6ad8); outline-offset:1px;
    transition:outline-color .5s ease; }
.ws-lien-grid .ws-rate-arm { flex-direction:column; align-items:stretch; gap:1px; padding-top:3px; padding-bottom:3px; }
.ws-rate-arm .sr-adorn { width:100%; }
/* Smaller than the row's own type, because it QUALIFIES the number above rather than competing
   with it — the rep reads "6.5%" first and "fixed" second, which is the order they say it. */
/* ⚠️ MEASURED AT 31px FOR 9px TEXT — the labels inherit padding from the sheet's form styles, so the
   strip was nearly three times the height of what it contains and the row wore the difference. Both
   the strip and its labels state their own box. */
/* ⚖️ *"squeeze Fixed Arm and PMI column to fit under rate and term above. it s too wide."* — his
   2026-08-19. The gaps came down with the radios; the fat was the 44px floor above, not this. */
.ws-arm { display:flex; align-items:center; gap:3px; font-size:9px; color:var(--mut,#6b7280);
    line-height:1; height:13px; margin:0; padding:0; flex:0 0 auto; }
.ws-arm label { display:flex; align-items:center; gap:1px; cursor:pointer; white-space:nowrap;
    margin:0; padding:0; line-height:1; font-size:9px; }
/* 🛑 THE 11px NEVER APPLIED, AND THAT IS WHY HIS *"squeeze Fixed Arm and PMI… it's too wide"* WAS
   RIGHT. `.ws-lien-grid .ws-lien-cell input[type] { min-width:max(3.2em,44px) }` floors EVERY input
   in the table — a floor written for TEXT boxes, silently applied to radios and checkboxes too. So
   each radio rendered **44px wide instead of 11**, and the strip measured 143px against 24px+21px of
   actual text. Two radios and a checkbox were costing ~99px, which made Terms the widest column on
   the grid (230px, next widest 98px) and forced the whole table to scroll further.
   ⇒ The selectors below OUTRANK that floor by naming the grid too — the same specificity trap the
   `.ws-ym-2` rule beneath already carries a warning about. Measured after: strip 143px → ~66px.
   ⚠️ `min-width` AND `width` both, because the floor sets `min-width` and a bare `width` loses to it. */
.ws-arm input[type="radio"],
.ws-lien-grid .ws-lien-cell .ws-arm input[type="radio"] {
    width:10px; min-width:10px; max-width:10px; height:10px; margin:0; flex:0 0 auto;
    accent-color:var(--brand,#3f6ad8); }
.ws-lien-grid .ws-lien-cell .ws-pmi-tick input[type="checkbox"],
.ws-lien-grid .ws-lien-cell .ws-lien-tick input[type="checkbox"] {
    width:12px; min-width:12px; max-width:12px; height:12px; margin:0; flex:0 0 auto; }
/* the tick's own wrapper must stop claiming a text box's width for the same reason */
.ws-pmi-tick .sr-adorn { width:auto; flex:0 0 auto; }
/* The whole table gains the height once, so every row still lines up with its neighbours — a taller
   rate cell alone would make the rows ragged. */
/* ⚠️ "a little taller" — measured, not eyeballed. The first cut left the row at 69px against 39
   before; the stacked cell needs about twelve of those pixels and the rest was padding doing
   nothing. 50px holds the rate, the qualifier under it, and a comfortable target. */
.ws-lien-grid > .ws-lien > * { min-height:46px; }
.ws-lien-grid > .ws-lien-head > * { min-height:0; }
.ws-lien-grid .ws-lien-tick { justify-content:center; min-width:44px; }
/* ⚠️ TWO DIGITS MEANS TWO DIGITS. `field-sizing:content` would still grow these as a rep typed, so
   the cap is explicit — paired with `maxlength` in the markup, which is what stops the value
   outgrowing the box rather than merely the box outgrowing its column. */
/* 🛑 THE SELECTOR HAS TO OUTRANK `.ws-lien-grid .ws-lien-cell input[type]`, which floors every box
   at 44px so an empty one stays clickable. That floor is right for the rest of the table and wrong
   for a two-digit box — measured at 44px when it should have been 26. Specificity, not `!important`:
   the general floor still applies everywhere it should. */
.ws-lien-grid .ws-lien-cell.ws-ym-2 input[type],
.ws-lien-grid .ws-lien-cell.ws-ym-1 input[type] { width:28px; min-width:28px; max-width:28px;
    text-align:center; padding-left:2px; padding-right:2px; field-sizing:fixed; }
.ws-ym-2, .ws-ym-1 { gap:2px; }
.ws-ym-2 .ws-ym-x, .ws-ym-1 .ws-ym-x { font-size:9px; color:var(--mut,#6b7280); }
/* ⚖️ `ws-ym-1` — YEARS ONLY, his 2026-08-19 *"the how long column can be just Years, no need to have
   months."* Same two-digit box and the same specificity fight with the 44px floor above; it is a
   separate class rather than a modifier so the two-box cell that Other properties still needs (its
   months ARE beat-captured) cannot be changed by accident from here. */
.ws-lien-grid .ws-lien-cell.ws-ym-1 { justify-content:center; }

/* ═══ GIL'S SUBJECT-PROPERTY VALUES ROW, 2026-08-19 ═══════════════════════════════════════════
   *"Change Value to Their Value. then put Online Value, under that our little icons for Zillow,
   RedFin and Realtor. then under that have a box to fill in value under each. values input row need
   to line up on the same row."*

       row 1                    ┌──── Online Value ────┐
       row 2   Their Value      Zillow  Redfin  Realtor      Our value   Anything else…
       row 3   [        ]       [    ]  [    ]  [     ]      [       ]   [            ]

   🛑 IT IS ITS OWN GRID, NOT `.ws-lay-values`, AND THAT IS DELIBERATE. That grid is twelve equal
   tracks — the right shape for four boxes and a remark, and the wrong one for six columns whose
   widths differ by kind (five money boxes that must not shrink below their number, and one remark
   that should eat whatever is left). Sharing it would also mean fighting the `.ws-lay-values
   .ws-k-home_value` spans in two places in this file at equal specificity, decided by source order —
   a cascade race is not a layout.
   ⚠️ `.ws-lay-values` KEEPS `prop_rental_income`, `estimated_equity` and the costs row. Nothing there
   moved, so its measured spans still mean what they say.

   🛑 THE CAPTION ROW HAS A FIXED HEIGHT AND THAT IS THE WHOLE ALIGNMENT MECHANISM. Six cells each
   stacking its own label above its own box line up only while every label is the same height — and
   three of these labels are BUTTONS, one is two words, one is five. A 🏠 one pixel taller than the
   word "Redfin" drops that column's input a pixel below its neighbours, which is exactly the thing
   he asked for by name and exactly the thing a screenshot cannot show you. Pinning the caption box
   makes "same row" structural rather than lucky. Proved by measuring every input's
   `getBoundingClientRect().top` in headless Chrome — `scripts/measure-property-values-row.py`.

   🛑 `display:grid` BEATS THE `hidden` ATTRIBUTE, so this container says `display:none` out loud —
   the same trap `.ws-f[hidden]` and `.ph-dispo-kids` are already named for in this stylesheet. The
   children are `.ws-f`, which the general rule at `.ws-f[hidden]` already covers. */
.ws-vals-row { display:grid;
    /* 🛑 EVERY NUMBER ON THIS LINE WAS MEASURED IN A BROWSER AT **HIS** 1090px WINDOW, WHERE THE
       PREQUAL GIVES THIS ROW 845px. Not one of them is a preference:
         · a money box needs 21px for the `$` and its two borders, then 87px for `$9,999,999` (his
           own 2026-08-01 spec, *"enough to fit 7 digits with commas"*) or 104px for the nine-digit
           ceiling `FIELD_WIDTH` names ⇒ **126px** clears both.
         · *"Anything else about the property"* is 185px of bold 11.5px Inter ⇒ the remark's floor is
           **185px**, because a caption that reads "Anything else about the pr…" looks broken in a way
           a sideways scroll never does. THAT is what a first cut at 140px shipped, and no check saw
           it — the boxes all fitted, so every clip sweep read clean. Captions are walked now too.
         · 126×5 + 6×5 + 185 = **845**, which is the width he has. It fits with nothing to spare, and
           that is the point: below his width it scrolls, at his width it does not.
       🛑 DO NOT SHRINK A MONEY COLUMN TO MAKE SOMETHING ELSE FIT. That is how the number goes
       missing, and it is the defect he read as *"this is what you deploy???????"*. Re-measure. */
    grid-template-columns:repeat(5, 126px) minmax(185px, 1fr);
    grid-template-rows:auto auto;
    column-gap:6px; row-gap:2px; align-items:start; margin-top:10px; min-width:0;
    /* 🛑 SO IT SLIDES SIDEWAYS RATHER THAN CLIPPING, WHICH IS HIS OWN RULING — *"is grides, just
       make them scrollable to right if need to be, like Borrower"*. Six columns at their measured
       width need ~878px; the prequal tab has ~989px and never scrolls, Script v2 has ~754px and
       Gil's runner pins the sheet into ~485px. Without this the last two columns are cut clean off
       with no way to reach them, and NOTHING on the page says so — the document does not pan,
       because an ancestor clips it, so every page-level check reads clean.
       ⚠️ A GRID MAY SCROLL INSIDE ITS OWN BOX; THE DOCUMENT MAY NOT BE WIDER THAN THE WINDOW. Those
       are different rules and `measure-property-values-row.py` asserts both separately. */
    overflow-x:auto; }
.ws-vals-row[hidden] { display:none; }

/* The group caption spans its three columns and NOT the two ends — "Their Value" is the customer's
   figure and "Our value" is ours; neither is an online pull, and a heading stretched over them
   would say they were. */
.ws-vals-online { grid-column:2 / span 3; grid-row:1;
    font-size:10.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase;
    color:var(--mut,#6b7280); border-bottom:1px solid var(--line,#e5e7eb);
    padding-bottom:2px; margin-bottom:1px; text-align:center; }

.ws-vals-row > .ws-f { grid-row:2; gap:3px; min-width:0; }
.ws-vals-row .ws-k-home_value    { grid-column:1; }
.ws-vals-row .ws-k-zillow_value  { grid-column:2; }
.ws-vals-row .ws-k-redfin_value  { grid-column:3; }
.ws-vals-row .ws-k-realtor_value { grid-column:4; }
.ws-vals-row .ws-k-our_value     { grid-column:5; }
.ws-vals-row .ws-k-property_note { grid-column:6; }

/* 🛑 THE LINE THAT MAKES THE SIX INPUTS SHARE A ROW. Delete the height and the caption row sizes to
   its tallest cell per COLUMN — the buttons drop their boxes below the plain captions.
   ⚖️ AND `justify-content:center` IS GIL'S, 2026-08-19: *"Center word and/or icons on top of data
   box."* Every caption, whether it is a word ("Their Value") or a button (🏠 Zillow, Redfin,
   Realtor) — his "and/or" is what makes it all six rather than only the icon ones. Centred over the
   box it names, these read as column headers; left-aligned they read as five labels that happen to
   be near five boxes. */
.ws-vals-row .ws-vlabel { display:flex; align-items:center; justify-content:center; height:22px;
    margin:0; font-size:11.5px; font-weight:700; color:#4a5170; white-space:nowrap; overflow:hidden; }
.ws-vals-row .ws-vals-cap { gap:4px; }
/* The lookups are CAPTIONS here, not toolbar buttons: sized to the caption row so they cannot be
   what breaks it, and so the three column heads read as one row of labels. */
.ws-vals-row .ws-vals-cap .sr-lookup { display:inline-flex; align-items:center; height:20px;
    padding:0 6px; font-size:11.5px; line-height:1; border-radius:5px; }
.ws-vals-row .ws-vals-cap .sr-lookup-off { opacity:.55; }
.ws-vals-row .ws-vals-word { font-weight:700; }

/* 🛑 THE GLOBAL `.ws-w-money .sr-adorn { min-width:132px }` MUST NOT REACH THIS ROW. It is a FLOOR
   written for a twelve-track grid, and against a 108px column it overflows the cell and paints onto
   the neighbour — the 2026-08-04 defect, exactly. The column IS the floor here; the box fills it. */
.ws-vals-row .ws-w-money .sr-adorn { min-width:0; max-width:none; width:100%; }
.ws-vals-row .ws-w-money input[type] { min-width:0; width:100%; }
.ws-vals-row .ws-note-cell .sr-adorn,
.ws-vals-row .ws-note-cell textarea { min-width:0; max-width:100%; width:100%; }

/* ═══ THE FLOOR BOARD ═══════════════════════════════════════════════════════════════════════════
   👥 Who is on the phones right now — `/manage/floor`, stage A of
   docs/plans/2026-08-27-phone-dashboard-design.md. ⚖️ Gil 2026-08-27: *"right now there is no screen
   that shows when people are on and things like that."*

   🛑 TWO RULES HERE ARE HIS, NOT PREFERENCES, AND BOTH ARE THE KIND A LATER TIDY-UP UNDOES:

   1. **ONE ROW PER PERSON, NEVER FOLDED.** *"i want it to be on on row… one row for the floor. Go
      across horizontally."* — said after an early cut broke each rep onto three lines in a narrow
      pane, with the state overlapping the customer name. A board whose rows wrap stops being a
      board: the whole value is running the eye DOWN a column, and a three-line row destroys that.
      ⚠️ **So there is deliberately NO responsive rule that restacks `.fl-row`.** The row keeps its
      columns at every width and `.fl-board` scrolls sideways instead. If a future narrow-screen pass
      wants to "fix" the horizontal scroll by folding the row, it is undoing the ruling — scroll the
      container, never fold the row.
   2. **TIGHT ENOUGH TO SEE EVERYBODY.** *"These need to be shorter to fit more."* Rows are ~38px:
      the state chip and its clock sit SIDE BY SIDE rather than stacked (that swap is where half the
      height went), the face is 26px, and the buttons are sized to the row rather than the row
      stretched to fit the buttons. A floor board is a density screen; scrolling to see who is on
      defeats it. */
/* Quiet by construction: it is always present, so it must not compete with the board until somebody
   wants it. Amber, never --danger — stopping is a normal act, not a fault, and rule 13 keeps the
   alarm colour for verdicts. */
.fl-stop { float:right; margin:0 0 8px 10px; }
.fl-stop-btn {
    display:inline-flex; align-items:center; gap:6px; cursor:pointer; border:0;
    background:#fdf4e3; color:#8a5a00; box-shadow:inset 0 0 0 1px #f0dcb4;
    font-family:inherit; font-size:12px; font-weight:800; padding:5px 11px; border-radius:9px;
}
.fl-stop-btn:hover { background:#fbecd0; }
.fl-stats { display:flex; gap:6px; flex-wrap:wrap; margin:0 0 12px; }
.fl-stat { background:var(--wash); border:1px solid var(--line); border-radius:9px; padding:5px 11px; min-width:70px; }
.fl-stat b { display:block; font-size:16px; font-weight:800; letter-spacing:-.02em; color:var(--brand-ink); line-height:1.2; }
.fl-stat span { font-size:9.5px; font-weight:700; color:var(--mut); text-transform:uppercase; letter-spacing:.05em; }
.fl-stat.live b { color:var(--live); }

/* the scroller that makes rule 1 above possible */
.fl-board { overflow-x:auto; padding-bottom:2px; }
.fl-head, .fl-row {
    display:grid; grid-template-columns:26px minmax(104px,1fr) 150px minmax(130px,1.25fr) 186px;
    align-items:center; gap:10px; min-width:640px;
}
.fl-head {
    padding:0 10px 5px; font-size:9.5px; font-weight:800; letter-spacing:.07em;
    text-transform:uppercase; color:#a3abbd;
}
.fl-head-r { text-align:right; }
.fl-row { padding:5px 10px; border:1px solid var(--line); border-radius:9px; margin-bottom:3px; background:#fff; }
/* ⚖️ THE THREE SHADES ARE GIL'S, 2026-08-27: *"if they ar eon teh phone, green shading, pause,
   yellow. and off light red"*. Read as a traffic light down the board — the state is legible from
   across a room before any word on the row is.

   🛑 THE RED IS A TINT, NEVER `--danger`. Rule 13 reserves the compliance red for a VERDICT and bars
   it from live-call surfaces outright; "off the phones" is a state, and half the rows wearing this
   shade belong to people who simply have not signed in yet. Spending the alarm colour on them would
   both blunt it where it has to mean something and accuse someone of nothing.
   ⚠️ Available is deliberately UNSHADED. He named three colours; a fourth leaves nothing quiet for
   them to stand against, and "available" is the state nobody needs to look for. */
.fl-row.is-live { border-color:var(--live-line); background:var(--live-tint); }
.fl-row.is-wrapup { border-color:var(--live-line); background:var(--live-tint); }
.fl-row.is-paused { border-color:#f0dcb4; background:#fdf4e3; }
.fl-row.is-off { border-color:#f3cfc9; background:#fdeeec; }
.fl-face {
    width:26px; height:26px; border-radius:999px; background:#dbe3f4; color:var(--brand-2);
    font-size:10.5px; font-weight:800; display:flex; align-items:center; justify-content:center;
}
.fl-row.is-live .fl-face { background:var(--live); color:#fff; }
.fl-who { min-width:0; line-height:1.2; }
.fl-name { font-size:12.5px; font-weight:800; color:var(--brand-ink); line-height:1.2;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fl-you { font-size:9.5px; font-weight:800; color:var(--mut); text-transform:uppercase;
    letter-spacing:.05em; margin-left:6px; }
.fl-role { font-size:10px; font-weight:600; color:var(--mut); line-height:1.25;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* side by side, not stacked — rule 2's biggest single saving */
.fl-state { display:flex; align-items:center; gap:7px; }
.fl-chip { display:inline-flex; align-items:center; font-size:10px; font-weight:800;
    padding:2px 7px; border-radius:6px; width:fit-content; white-space:nowrap; }
.fl-chip.live { background:var(--live); color:#fff; }
.fl-chip.ready { background:#e4f7ec; color:#137a45; }
.fl-chip.paused { background:#fde6c7; color:#8a5a00; }
.fl-chip.off { background:#eef0f5; color:#697089; }
.fl-elapsed { font-size:10.5px; font-weight:700; color:var(--mut); font-variant-numeric:tabular-nums; white-space:nowrap; }
/* 🛑 AMBER, NEVER --danger. A long pause is a thing to go and ASK ABOUT, not a verdict on a person,
   and rule 13 reserves the compliance red for verdicts. Paints "paused 41 minutes" — the row Gil
   named as the point of the whole screen. */
.fl-elapsed.long { color:#a9601b; }
.fl-with { min-width:0; line-height:1.2; }
.fl-with-n { font-size:11.5px; font-weight:700; color:var(--ink); line-height:1.25;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* 🚪 THE NAME AS A DOOR — ⚖️ Gil, 2026-09-11: *"let us have ability to click on with and it goes to
   the lead card."* It must still READ as the plain customer name it has always been (a blue
   underlined link down a density board turns a quiet column into noise), so it keeps the ink colour
   and earns its underline on hover. The ↗ is what says "this opens".
   🛑 FLEX, AND THE ELLIPSIS MOVES INSIDE. `.fl-with-n` truncates long names; with the arrow as a
   sibling of the text inside one truncating box, the arrow is the first thing the ellipsis eats —
   so the NAME truncates and the arrow never shrinks. */
a.fl-with-n { display:flex; align-items:baseline; gap:3px; text-decoration:none; cursor:pointer; }
a.fl-with-n .fl-with-t { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
a.fl-with-n .fl-with-go { flex:0 0 auto; font-size:10px; font-weight:800; color:var(--mut); }
a.fl-with-n:hover .fl-with-t { text-decoration:underline; }
a.fl-with-n:hover .fl-with-go { color:var(--brand-2); }
.fl-with-m { font-size:10px; color:var(--mut); line-height:1.25;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fl-acts { display:flex; gap:4px; justify-content:flex-end; }
/* 🛑 THE THREE EARS, AND THEY HAD NO RULES AT ALL UNTIL 2026-08-28 — they shipped rendering as bare
   browser buttons because the class was styled in the MOCKUP and never carried into the stylesheet.
   Nothing failed: markup correct, tests green, three grey boxes on the floor board.
   The colours are the escalation, deliberately: Listen wears the live-call green (you are hearing,
   not speaking), Whisper amber (your voice reaches the rep), Join the reserved-adjacent red tint
   (your voice reaches the CUSTOMER). ⚠️ Not `--danger` itself — rule 13 keeps that for verdicts, and
   joining a call is an act, not a judgement. */
.fl-act {
    display:inline-flex; align-items:center; gap:4px; padding:3px 8px; border-radius:7px; border:0;
    font-family:inherit; font-size:11px; font-weight:700; cursor:pointer; white-space:nowrap;
    background:#fff; color:var(--live-2); box-shadow:inset 0 0 0 1px var(--live-line);
}
.fl-act:hover { background:var(--live-tint); }
.fl-act.warn { color:#8a5a00; box-shadow:inset 0 0 0 1px #eccf9c; }
.fl-act.warn:hover { background:#fdf4e3; }
.fl-act.hot { color:var(--danger-ink); box-shadow:inset 0 0 0 1px #eec2bc; }
.fl-act.hot:hover { background:#fdeeec; }
.fl-act[disabled] { opacity:.4; cursor:default; }
/* The row you are IN. Filled rather than outlined, so the one live ear is findable at a glance down
   a floor of twenty — and so the way out is the loudest thing on its own row. */
.fl-act.is-listening { background:var(--live); color:#fff; box-shadow:none; }
.fl-act.is-listening:hover { background:var(--live-2); }
/* The three ears ride one form (one row, one target), so the form must not become a flex item that
   collapses their spacing — it IS the row of buttons. */
.fl-acts form { display:flex; gap:4px; }
.fl-quiet { font-size:10.5px; color:var(--mut); font-weight:600; white-space:nowrap; }
/* 🎧 "YOUR PHONE IS OFF" — why the three ears are dark. Amber, never red: nothing has gone wrong and
   nobody has done anything wrong, which is the same family of message as a held save (`.flash.warn`)
   and reuses its colours rather than minting a fourth. Compact because this board is a DENSITY
   screen (Gil: *"These need to be shorter to fit more"*) and the note rides above rows a manager
   opened the drawer to read — `.flash`'s own 13px/18px would spend a third of the panel saying it. */
.fl-earoff { margin:0 0 8px; padding:7px 10px; border-radius:8px; font-size:11.5px; line-height:1.45;
    background:#fff6e2; color:#8a5a00; border:1px solid #f3dea8; }
.fl-group { display:flex; align-items:center; gap:8px; margin:14px 0 5px;
    font-size:9.5px; font-weight:800; letter-spacing:.07em; text-transform:uppercase; color:#a3abbd; }
.fl-group::after { content:''; flex:1; height:1px; background:var(--line); }

/* ═══ THE PHONE DASHBOARD ═══════════════════════════════════════════════════════════════════════
   ☎️ Stage B of docs/plans/2026-08-27-phone-dashboard-design.md. ⚖️ Gil 2026-08-27: *"maybe click up
   top and it drops down or drawere with a nive dashboard to do all feature of phone"*.

   🛑 IT DROPS FROM UNDER THE BAR AND IT SITS BELOW IT — the two are the same rule. `--z-drawer`
   (140) is under the `--z-callbar` ceiling (160) that holds Hang up, Hold, Mute and the
   dispositions, and the panel starts at `--ph-strip-h`, the strip's own MEASURED height (it wraps at
   narrow widths, so no constant is right — the strip publishes it from a ResizeObserver).
   ⚠️ NEVER hand-pick a z-index here. That is what re-broke this bar twice; the note on --z-callbar
   records both attempts. Two tests hold it: CallControlsUnburiableTest generically, and
   ThePhoneDashboardOpensFromTheBarTest by name.

   🛑 A DROP, NOT THE RIGHT-HAND `.drawer` — deliberately a variant rather than a reuse. The floor
   board inside is a five-column grid that must not fold (Gil's one-row ruling); the shared drawer is
   min(620px) and would force it into a sideways scroll on every screen rather than only on narrow
   ones. Same chrome, same dim, same link-based exits, different width. */
.ph-dash-open {
    display:inline-flex; align-items:center; gap:6px; padding:6px 12px; border-radius:10px;
    font-size:13px; font-weight:800; text-decoration:none; white-space:nowrap;
    background:rgba(255,255,255,.16); color:#fff; box-shadow:inset 0 0 0 1px rgba(255,255,255,.26);
}
.ph-dash-open:hover { background:rgba(255,255,255,.26); color:#fff; }
/* Open = the button reads as pressed, so the control that opened it visibly IS the one to press
   again. Gil pressed it to close and nothing happened; a toggle that looks identical in both states
   is half of why. */
.ph-dash-open.is-open { background:#fff; color:var(--brand-ink); box-shadow:none; }
.ph-dash-dim { position:fixed; inset:0; background:rgba(17,12,29,.38); z-index:var(--z-drawer-dim); }
/* 🛑 `hidden` IS NOT ENOUGH ONCE YOU HAVE SET `display`, AND THIS SHIPPED TO A LIVE FLOOR.
   ⚖️ Gil, 2026-08-28: *"still showing the white band acrross the top below call bar."*

   The HTML `hidden` attribute is only a UA-stylesheet `display:none`, and ANY author rule setting
   `display` outranks it. `.ph-dash` sets `display:grid` (it is a two-column panel), so from the
   moment the shell began riding every page closed, `hidden` did nothing and an empty white drawer
   sat under the call bar on EVERY SCREEN IN THE APP. The markup was correct; the attribute was
   correct; the suite was green — a `hidden` element that renders is invisible to every assertion
   that only asks whether it is marked hidden.

   ⚠️ IT ALSO EXPLAINS AN EARLIER MISDIAGNOSIS. The "drawer opens and no rows arrive" screenshot was
   this, not a failed fetch: the shell was simply always on screen, empty. The loading fallback
   added for that is still worth having, but it was a fix for a symptom of THIS.

   🛑 ANY `.ph-*` PANEL THAT SETS `display` AND HIDES BY ATTRIBUTE NEEDS ITS OWN `[hidden]` RULE.
   Pinned by ThePhoneDashboardOpensFromTheBarTest::test_the_closed_drawer_is_actually_hidden. */
.ph-dash[hidden],
.ph-dash-dim[hidden] { display:none !important; }
.ph-dash {
    position:fixed; top:var(--ph-strip-h, 0px); left:0; right:0; max-height:calc(100vh - var(--ph-strip-h, 0px));
    background:#fff; border-bottom:1px solid var(--line); box-shadow:0 34px 60px -34px rgba(17,12,29,.55);
    display:grid; grid-template-columns:1fr; overflow:auto; z-index:var(--z-drawer);
    animation:ph-dash-in .22s cubic-bezier(.2,.8,.25,1);
}
/* The rail only exists once there is somewhere to go — see the note in the partial. One section
   means one column, so the pane owns the width instead of sitting beside an empty 212px gutter. */
.ph-dash:has(.ph-dash-nav) { grid-template-columns:212px 1fr; }
@keyframes ph-dash-in { from { transform:translateY(-14px); opacity:.3; } to { transform:none; opacity:1; } }
@media (prefers-reduced-motion: reduce) { .ph-dash { animation:none; } }
.ph-dash-close {
    position:absolute; top:14px; right:16px; color:var(--mut); font-size:19px; font-weight:700;
    text-decoration:none; line-height:1; z-index:var(--z-drawer-close);
}
/* ⚖️ Gil, 2026-08-28: *"Move the dial button on top left, to top right."* It was landing top-left
   because the pad is a direct child of the drawer's GRID — it took the first cell, ahead of the
   content. Pinned instead, beside the ✕.
   🛑 POSITIONED RATHER THAN MOVED IN THE MARKUP, deliberately: the pad must be reachable from EVERY
   section, and the header it would otherwise live in is rendered inside the floor section's own
   branch. Re-parenting it there would quietly make Dial a floor-only control. */
.ph-dash > [data-ph-dialpad-wrap] { position:absolute; top:11px; right:46px; z-index:var(--z-drawer-close); }
.ph-dash > [data-ph-dialpad-wrap] .ph-dialpad-btn {
    display:inline-flex; align-items:center; gap:6px; border:0; cursor:pointer;
    background:#f1f4fa; box-shadow:inset 0 0 0 1px var(--line); color:var(--brand-ink);
    font-family:inherit; font-size:12px; font-weight:800; padding:5px 11px; border-radius:9px;
}
.ph-dash > [data-ph-dialpad-wrap] .ph-dialpad-btn:hover { background:#e9eef8; color:var(--brand); }
.ph-dash-close:hover { color:var(--ink); }
.ph-dash-nav { background:var(--wash); border-right:1px solid var(--line); padding:16px 12px 20px; }
.ph-dash-nav a {
    display:flex; align-items:center; gap:9px; padding:8px 10px; border-radius:9px; margin-bottom:2px;
    text-decoration:none; color:#48506a; font-size:13.5px; font-weight:600;
}
.ph-dash-nav a.on {
    background:#fff; color:var(--brand-ink); font-weight:800;
    box-shadow:inset 0 0 0 1px var(--line), 0 4px 10px -8px rgba(26,32,60,.5);
}
.ph-dash-pane { padding:20px 24px 26px; min-width:0; }
.ph-dash-hd { display:flex; align-items:baseline; gap:12px; flex-wrap:wrap; margin:0 0 14px; }
.ph-dash-hd h3 { font-size:19px; font-weight:800; letter-spacing:-.02em; color:var(--brand-ink); margin:0; }
/* The refresh + its age. The age is not decoration: it is the only way to tell a live board from a
   frozen one, and a frozen board that looks live is the failure this whole surface exists to avoid. */
.ph-dash-refresh {
    margin-left:auto; display:inline-flex; align-items:center; gap:6px; cursor:pointer;
    border:0; background:#f1f4fa; box-shadow:inset 0 0 0 1px var(--line); color:var(--mut);
    font-family:inherit; font-size:12px; font-weight:700; padding:5px 10px; border-radius:9px;
}
.ph-dash-refresh:hover { background:#e9eef8; color:var(--brand); }
.ph-dash-full { font-size:12.5px; font-weight:700; color:var(--brand); text-decoration:none; }
.ph-dash-full:hover { text-decoration:underline; }
.ph-dash-soon { margin:0; font-size:13.5px; color:var(--mut); }
@media (max-width:900px) {
    .ph-dash { grid-template-columns:1fr; }
    .ph-dash-nav { display:flex; gap:6px; overflow-x:auto; border-right:0; border-bottom:1px solid var(--line); padding:10px; }
    .ph-dash-nav a { white-space:nowrap; }
}

/* ═══ WHO MAY SIT IN ═══ Gil's D7/D9 grant panel, on the floor board itself.
   Collapsed by construction — granting is a thing you go and do once, not a thing you watch. */
.perm { margin-top:18px; border:1px solid var(--line); border-radius:13px; background:var(--wash); padding:11px 15px; }
.perm[open] { padding-bottom:14px; }
.perm-hd { display:flex; align-items:center; gap:10px; flex-wrap:wrap; cursor:pointer; list-style:none; }
.perm-hd::-webkit-details-marker { display:none; }
.perm-hd::before { content:'▸'; font-size:11px; color:var(--mut); }
.perm[open] .perm-hd::before { content:'▾'; }
.perm[open] .perm-hd { margin-bottom:8px; }
.perm-t { font-size:13px; font-weight:800; color:var(--brand-ink); }
.perm-count { font-size:11px; font-weight:700; color:var(--mut); }
.perm-note { margin-left:auto; font-size:10.5px; font-weight:700; color:#a9601b; background:#fde6c7; padding:2px 8px; border-radius:6px; }
.perm-row { display:flex; align-items:center; gap:10px; padding:6px 0; border-top:1px solid var(--line); }
.perm-n { font-size:12.5px; font-weight:700; color:var(--ink); }
.perm-n em { font-style:normal; font-weight:600; color:var(--mut); font-size:10.5px; margin-left:6px; }
.perm-v { margin-left:auto; font-size:11px; font-weight:800; color:#137a45; white-space:nowrap; }
.perm-v.perm-off { color:var(--mut); }
.perm-btn {
    border:0; cursor:pointer; background:#f1f4fa; box-shadow:inset 0 0 0 1px var(--line);
    color:var(--brand-ink); font-family:inherit; font-size:11px; font-weight:800;
    padding:4px 10px; border-radius:8px; white-space:nowrap;
}
.perm-btn:hover { background:#e9eef8; color:var(--brand); }
.perm-foot { margin:10px 0 0; font-size:11.5px; line-height:1.6; color:var(--mut); }
.perm-foot b { color:var(--brand-ink); }

.ws-gb-total { font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em;
    color:#9a97ab; margin:0 3px 0 5px; }

/* ── WHAT WE ALREADY KNEW WHEN THE FILE ARRIVED (2026-08-31) ─────────────────────────────────
   ⚖️ Gil: *"i just want them when they first open the file to see ok this is what they filled in
   on the landing page"* — one block, read once, collapsible, open on a rep's FIRST view.
   🛑 IT IS STYLED TO LOOK UNLIKE THE SHEET ON PURPOSE. The prequal is a working surface of dense
   boxes; this is a letter. A card that looked like the others would be skimmed past, which is
   precisely what the badge-on-every-box design got wrong (*"very confusing to follow"*). */
.lead-intro { border:1px solid #cfdcf5; border-left:4px solid var(--brand,#35529b);
    background:linear-gradient(180deg,#fbfcff 0%,#fff 42%); padding:0; overflow:hidden;
    margin-bottom:12px; }
/* the LIST variant is deliberately quieter — a bought list has not earned the brand's own blue */
.lead-intro.is-list { border-left-color:#b9bccb; background:linear-gradient(180deg,#fafafc 0%,#fff 42%); }
.li-head { display:flex; align-items:center; gap:12px; padding:13px 16px; cursor:pointer;
    list-style:none; user-select:none; }
/* 🛑 BOTH, AND NEITHER IS OPTIONAL. Safari draws the disclosure triangle from a ::-webkit- marker
   and every other engine from `list-style` — a rule naming one leaves the native arrow sitting
   beside our own chevron on half the floor's browsers. */
.li-head::-webkit-details-marker { display:none; }
.li-head::marker { content:''; }
.li-head:hover { background:#f7f9ff; }
.li-chev { flex:0 0 auto; width:9px; height:9px; border-right:2px solid #8fa3cd;
    border-bottom:2px solid #8fa3cd; transform:rotate(-45deg); transition:transform .15s ease;
    margin-left:2px; }
.lead-intro[open] .li-chev { transform:rotate(45deg); }
.li-titles { display:flex; flex-direction:column; min-width:0; flex:1 1 auto; }
.li-eyebrow { font-size:10px; font-weight:800; letter-spacing:.08em; text-transform:uppercase;
    color:#7d8ab0; margin-bottom:2px; }
.lead-intro.is-list .li-eyebrow { color:#9a97ab; }
.li-title { font-size:16px; font-weight:700; color:var(--ink,#1e2333); letter-spacing:-.01em; }
.li-count { flex:0 0 auto; font-size:10.5px; font-weight:700; color:#7d8ab0; background:#eef2fb;
    border:1px solid #dbe4f7; border-radius:999px; padding:2px 9px; white-space:nowrap; }
.li-body { display:grid; grid-template-columns:repeat(auto-fit,minmax(215px,1fr));
    border-top:1px solid #eef1f9; }
.li-grp { padding:11px 16px 13px; border-right:1px solid #f2f4fa; }
.li-grp:last-child { border-right:0; }
.li-grp-t { font-size:9.5px; font-weight:800; letter-spacing:.07em; text-transform:uppercase;
    color:#a7a4b8; margin-bottom:6px; }
.li-row { display:flex; justify-content:space-between; align-items:baseline; gap:12px; padding:3px 0; }
.li-q { font-size:12.5px; color:#6f6c85; }
.li-a { font-size:13px; font-weight:700; color:var(--ink,#1e2333); text-align:right; }
.li-foot { padding:10px 16px 12px; border-top:1px solid #eef1f9; font-size:12px; color:#7d7a93;
    line-height:1.55; }
.li-foot b { color:var(--ink,#1e2333); }

/* ⚖️ "ALSO STOP CALLING THEM" on the dismiss confirm — Gil 2026-09-01 (R-108). A tick, not a
   why-picker: it defaults off and a rep who ignores it gets the behaviour they always had.
   🛑 Its own line above the commit button, never inline beside it — a destructive button and a
   consequence tick sharing a row is how somebody presses one meaning the other. */
.cl-dsm-stop {
    display:flex; align-items:flex-start; gap:7px; margin:0 0 10px;
    font-size:12.5px; line-height:1.4; color:var(--ink); cursor:pointer;
}
.cl-dsm-stop input { margin:1px 0 0; flex:0 0 auto; }

/* ═══ THE CALL TANK'S SALES-FLOOR LANGUAGE (CRM-CRAFT 2026-08-01, adopted 2026-09-02 with the
   cadence suite). Rep-facing screens get the energetic half of the system the platform already
   owns: ONE big number the eye lands on, position circles, the up-next ring, hot as the strip's
   "waiting for you" amber (#f0a921 — never .tag.warn's caution amber, never --danger). Colour is
   never the only signal: every warm surface keeps its word. Catalogued in design-catalogue.md. */

/* .callday — the rep's day said with one number; light-surface sibling of the sidebar's .side-tally
   (same fact family — the numbers come from the readers that own them, never a second query). */
.callday { display:flex; align-items:center; gap:12px; margin-top:10px; flex-wrap:wrap; }
.callday .n { font-size:26px; font-weight:800; color:var(--brand-ink); line-height:1; letter-spacing:-.02em; }
.callday .lbl { font-size:var(--t-sm, 13px); font-weight:700; color:var(--mut); }
.callday .progress { flex:0 1 220px; height:8px; margin:0; }
.callday.is-done .n { color:#137a45; }
.callday.is-done .progress > i { background:linear-gradient(90deg,#137a45,#3fbf7f); }

/* .q-item / .q-n — the queue reads as a QUEUE: position circles in the .steps .step-n language
   (extended, not forked). .q-now = where the eye lands first — honest, because the page says the
   order updates itself. */
.q-item { display:flex; align-items:center; gap:14px; padding:12px 0; border-bottom:1px solid var(--line); }
.q-item > .q-who { flex:1; min-width:0; }
.q-n { width:28px; height:28px; border-radius:50%; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center;
    font-size:13px; font-weight:800; background:#eef2fb; color:var(--brand); }
.q-item.q-now { background:var(--card); margin:0 -12px; padding:12px; border-radius:14px;
    box-shadow:inset 0 0 0 2px var(--ring), 0 8px 24px -18px rgba(26,32,60,.35); border-bottom:0; }
.q-item.q-now .q-n { background:linear-gradient(180deg,var(--brand),var(--brand-2)); color:#fff; }

/* .card.tank-hot — the hot tier wears the "waiting for you" warmth as a card. */
.card.tank-hot { border-color:#f2d9a8; background:linear-gradient(180deg,#fffdf9,#fff8ee);
    box-shadow:inset 4px 0 0 #f0a921, 0 1px 2px rgba(26,32,60,.06), 0 18px 40px -24px rgba(26,32,60,.28); }
/* .tag.hot — the 🔥 chip warms to match. A FACT (the lane), so it sits beside .tag.type, not the severity trio. */
.tag.hot { background:#fff3e0; color:#b26a00; }

/* .cad-strip — WHERE THIS PERSON STANDS IN THEIR CADENCE: dots for calls, the current one in the
   hot amber, then "call 3 of 11 · day 1 AM · last: yesterday, voicemail". Right-aligned on a
   queue row; inline on the lead card's tank bar. */
.cad-strip { flex:0 0 auto; text-align:right; font-size:12px; color:var(--mut); font-weight:600; line-height:1.35; min-width:150px; }
.cad-strip .dots { display:inline-flex; gap:3px; margin-bottom:3px; }
.cad-strip .dots i { width:8px; height:8px; border-radius:50%; background:#d6dbe8; }
.cad-strip .dots i.on { background:var(--brand); }
.cad-strip .dots i.now { background:#f0a921; box-shadow:0 0 0 2px #fde6c7; }
.cad-strip b { color:var(--brand-ink); }
.cad-strip.inline { text-align:left; min-width:0; display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.cad-strip.inline .dots { margin:0; }
.cad-strip.inline .dots i { width:10px; height:10px; }

/* .empty.is-done — the day-done state. A STATE, so it may wear success (the .good/.warn rule). */
.empty.is-done { border-style:solid; border-color:#bfe6cf; background:#f2fbf5; color:#2f5c43; }
.empty.is-done h2 { color:#0f6a3b; margin:0 0 8px; }

/* 🔒 A SHEET THIS REP MAY READ BUT NOT SAVE — ⚖️ Gil 2026-09-04, *"do it"*.
   The disabled fieldset is what actually stops the typing; this is the sentence that says why, so
   the rep is not left guessing at greyed-out boxes. Deliberately quiet: it is an explanation, not
   an alarm — nothing is wrong, this simply is not their file to work. */
.sr-readonly-note {
    margin: 0 0 10px;
    padding: 9px 12px;
    border-left: 3px solid var(--flag, #8a6d1f);
    background: var(--flag-bg, #fdf6e3);
    color: var(--ink-soft, #4a5462);
    font-size: 14px;
    line-height: 1.45;
    border-radius: 0 4px 4px 0;
}
/* The greying itself, so a disabled sheet reads as deliberate rather than broken. */
.sr-worksheet-body[disabled] { border: 0; margin: 0; padding: 0; opacity: 0.72; }

/* ── THE HEI TAB (owner 2026-09-09) ──────────────────────────────────────────────────────────
   Splitero and CHEIFS on one screen. Tokens only — no new colours; the alarm palette is NOT
   used here (design-system rule 13): nothing on this tab is a verdict about a checked thing. */
.hei { max-width: 1080px; }
.hei-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
.hei-h { margin: 0; font-size: 19px; color: var(--brand-ink); }
.hei-sub { margin: 2px 0 0; font-size: 13px; color: var(--mut); max-width: 52ch; }
.hei-pick { display: flex; gap: 6px; flex-wrap: wrap; }
.hei-prod { background: #fff; border: 1.5px solid var(--line); border-radius: 8px; padding: 8px 13px;
            font: 600 13px inherit; color: var(--mut); cursor: pointer; min-height: 40px; }
.hei-prod:hover { border-color: var(--brand); color: var(--brand); }
.hei-prod.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.hei-warn { background: var(--warn-bg); border-left: 4px solid var(--warn); border-radius: 0 8px 8px 0;
            padding: 10px 13px; margin: 0 0 12px; font-size: 13.5px; color: #6b4a08; }
.hei-from { background: #f2f5fd; border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px; margin-bottom: 10px; }
.hei-from-t, .hei-panel-t { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; font-weight: 700; color: var(--brand); margin-bottom: 7px; }
.hei-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 9px; }
.hei-facts > div { display: flex; flex-direction: column; }
.hei-facts span { font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--mut); }
.hei-facts b { font-size: 15px; color: var(--brand-ink); }
.hei-facts i { font-size: 11.5px; color: var(--mut); font-style: normal; }
.hei-dim { color: var(--mut); font-weight: 400; font-size: 13px; }
/* 🛑 THE EDITABLE ROW ANNOUNCES ITSELF. Dressed like the read-only strip above, it read as more
   prequal data and the owner asked for controls that were already there. A tinted well, a heading
   and a real input border are what separate "you may type here" from "here is what we know". */
.hei-play { background: #eef2fb; border: 1px solid #cfd9f0; border-radius: 10px; padding: 11px 13px; margin-bottom: 12px; }
.hei-play-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 8px; }
.hei-play-t { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; font-weight: 700; color: var(--brand); }
.hei-reset { background: #fff; border: 1px solid #cfd9f0; border-radius: 7px; padding: 5px 10px;
             font: 600 12px inherit; color: var(--brand); cursor: pointer; min-height: 30px; }
.hei-reset:hover { border-color: var(--brand); }
.hei-inputs { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }
.hei-inputs label { display: block; font-size: 12px; color: var(--brand-ink); font-weight: 700; }
.hei-inputs input { display: block; width: 100%; margin-top: 4px; font: 700 16px/1.2 inherit; color: var(--brand-ink);
                    padding: 10px 11px; background: #fff; border: 1.5px solid #9fb0d8; border-radius: 8px;
                    min-height: 44px; box-shadow: inset 0 1px 2px rgba(20,30,60,.06); }
.hei-inputs input::placeholder { color: #9aa4b8; font-weight: 400; }
.hei-inputs input:hover { border-color: var(--brand); }
.hei-inputs input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--ring); }
.hei-inputs em { display: block; font-style: normal; font-size: 11px; font-weight: 400; color: var(--mut); margin-top: 3px; min-height: 14px; }
.hei-out { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-bottom: 12px; }
.hei-card { background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px; }
.hei-card[hidden] { display: none; }
.hei-k { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--mut); font-weight: 700; }
.hei-v { font-size: 22px; font-weight: 800; color: var(--brand-ink); line-height: 1.15; margin-top: 2px; }
.hei-n { font-size: 11.5px; color: var(--mut); margin-top: 3px; }
.hei-est { background: var(--warn-bg); color: var(--warn); font-size: 10px; font-weight: 700; letter-spacing: .05em;
           text-transform: uppercase; padding: 1px 5px; border-radius: 4px; }
.hei-tbl { width: 100%; border-collapse: collapse; font-size: 13px; background: #fff;
           border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.hei-tbl th { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--mut);
              text-align: right; padding: 8px 9px; border-bottom: 2px solid var(--line); }
.hei-tbl td { padding: 6px 9px; border-bottom: 1px solid var(--line); text-align: right; }
.hei-tbl th:first-child, .hei-tbl td:first-child { text-align: left; }
.hei-tbl tr:last-child td { border-bottom: 0; }
.hei-tbl .hei-on { color: var(--brand); font-weight: 700; }
.hei-tbl .hei-pay { font-weight: 800; color: var(--brand-ink); }
.hei-tbl .hei-last td { background: var(--warn-bg); }
.hei-tbl .hei-empty { text-align: center; color: var(--mut); padding: 16px; }
.hei-note { font-size: 12.5px; color: var(--mut); margin: 7px 0 14px; }
.hei-two { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; }
.hei-panel { background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px; }
.hei-panel ul { margin: 0; padding-left: 17px; font-size: 13px; }
.hei-panel li { margin: 4px 0; }
.hei-no { color: var(--danger-ink); }
.hei-disc { background: #fff; border: 1px solid var(--line); border-left: 4px solid var(--brand); border-radius: 0 10px 10px 0; padding: 11px 14px; }
.hei-say { background: #f2f5fd; border-radius: 7px; padding: 9px 12px; margin: 0 0 8px;
           font-weight: 600; color: #16203a; font-size: 13.5px; }
.hei-lane { font-size: 12.5px; color: #6b4a08; background: var(--warn-bg); border-radius: 7px; padding: 8px 11px; margin: 0 0 8px; }
.hei-must { margin: 0; padding-left: 17px; font-size: 12.5px; color: var(--mut); }
.hei-must li { margin: 3px 0; }
.hei-src { font-size: 11.5px; color: var(--mut); margin: 12px 0 0; }
@media (max-width: 700px) { .hei-two { grid-template-columns: 1fr; } }
