/* ──────────────────────────────────────────────────────────────────
   theme.css — orchestrator + small additions on top of the design
   bundle's shell.css / prototype.css / v1.css / vibe-soft.css.

   Loaded LAST in base.html so overrides win. Keep this file thin —
   real changes belong upstream in the matching bundle file so the
   design language stays in one place.
   ────────────────────────────────────────────────────────────────── */

/* ════════════════════════════════════════════════════════════════════════════
   Phase 9 palette redesign (2026-06-01)
   ════════════════════════════════════════════════════════════════════════════
   The original V1 design bundle palette was deliberately vibrant — cyan
   #00B2E3, purple #7C5BD9, orange #E89019, plus a saturated radial-gradient
   mesh. Tanveer flagged it as too "demo" / not enough "professional tool."
   Shift toward slate / muted plum / burnished bronze. Less saturation, same
   information hierarchy. Tokens land here so the rest of the codebase doesn't
   need to know.

   Single rule for new code: reference the tokens (var(--brand) etc.), not
   the literal hex. Anything that hard-codes #00B2E3 etc. is a paper cut and
   should be rewritten when touched. */
:root {
    /* Primary brand: slate-blue. Reserved enough for a tool that runs all day. */
    --brand:           #3D6A8C;
    --brand-deep:      #244763;
    --brand-soft:      rgba(61, 106, 140, 0.10);
    --brand-line:      rgba(61, 106, 140, 0.30);

    /* Secondary tools — for the CX (purple) and Calls (orange) per-tool
       accents and the rare cross-tool callout. All desaturated. */
    --accent-plum:     #5B5D7C;
    --accent-plum-soft:rgba(91, 93, 124, 0.10);
    --accent-bronze:   #9C7B3D;
    --accent-bronze-soft: rgba(156, 123, 61, 0.10);
    /* Added 2026-08-14 with Time Off. Five leave types need five hues a person
       can tell apart at 24px on a wall chart, and the palette had four
       (brand / plum / bronze / ok). Muted to match the rest — this is not the
       prototype's #0B7285. */
    --accent-teal:     #2E6E7E;
    --accent-teal-soft: rgba(46, 110, 126, 0.10);

    /* Status tones — kept identifiable but lower-key.
       Green for ok, amber for warn, ember for bad, slate for neutral.  */
    --ok:              #2F7D5C;
    --warn:            #A57C2A;
    --bad:             #B14A4D;
    --neutral:         #6B7280;
    --ok-soft:         rgba(47, 125, 92, 0.12);
    --warn-soft:       rgba(165, 124, 42, 0.12);
    --bad-soft:        rgba(177, 74, 77, 0.12);

    /* Vivid metric-number tones — HEADLINE NUMBERS ONLY (KPI values + report-
       table counts/rates). The muted status palette above (--ok/--warn/--bad)
       reads washed-out on a 32px number, which is why Tanveer flagged the KPI
       numbers as hard to see vs the legacy dashboard (2026-06-16). These are
       the original/heritage vibrant status tones still documented in
       docs/design-system.md, reused here for numbers so they pop. Chips,
       badges, charts and backgrounds keep the muted Phase-9 tones — only bare
       numbers use these. Paired with the saturate(1.4) allowlist below so they
       survive the global `body { filter: saturate(0.72) }`. */
    --num-good:        #1AAE8E;   /* vivid teal-green — at/above target, up */
    --num-bad:         #E5484D;   /* vivid coral-red — well behind target, down */
    --num-warn:        #E89019;   /* vivid amber — warning band */
    --num-info:        #00B2E3;   /* vivid cyan — neutral/brand number accent */
}
.theme-dark {
    /* Slightly brighter for legibility on the deep background. */
    --brand:           #6CA0C4;
    --brand-deep:      #93B9D6;
    --brand-soft:      rgba(108, 160, 196, 0.16);
    --brand-line:      rgba(108, 160, 196, 0.36);
    /* Brighter number tones for the deep canvas (mirrors the legacy dark set). */
    --num-good:        #2BD0AE;
    --num-bad:         #FF6B6F;
    --num-warn:        #FFB04A;
    --num-info:        #4FC3F7;
}

/* Per-tool color identity — used by the rail-active inset, eyebrow swatches.
   Defined as a fallback chain so callers can keep using the literal colors
   from tools_schema.py but the design system also exposes named tokens. */
:root {
    --tool-sales:    var(--brand);            /* slate-blue */
    --tool-cx:       var(--accent-plum);      /* muted plum */
    --tool-calls:    var(--accent-bronze);    /* burnished bronze */
    --tool-sops:     var(--ok);               /* muted green (2026-08-13) */
    --tool-timeoff:  var(--accent-teal);      /* muted teal (2026-08-14) */
    --tool-workspace:#6B7280;                 /* graphite */
}

/* Tone the active rail item to the new palette. Replaces the cyan glow. */
.rail .rail-item.active {
    background: rgba(61, 106, 140, 0.18) !important;
    color: #fff;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.08),
        0 6px 16px rgba(61, 106, 140, 0.28),
        0 0 0 1px rgba(61, 106, 140, 0.25) !important;
}
.theme-dark .rail .rail-item.active {
    background: rgba(108, 160, 196, 0.18) !important;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.10),
        0 8px 18px rgba(108, 160, 196, 0.34),
        0 0 0 1px rgba(108, 160, 196, 0.36) !important;
}

/* Mesh background — the vibrant radial gradients in vibe-soft.css are still
   defined but we drop the layer's saturation by ~35% via a CSS filter so
   the room mood stays cool without nuking the gradient itself. The filter
   composes with the backdrop-filter blur on glass cards so cards still pop. */
body { filter: saturate(0.72); }
/* The filter cascades but elements that need pop (status colors, brand chips)
   re-saturate themselves. Cheaper than recoloring every gradient. */
.kpi .k-delta, .sk-delta, .status-pill, .filter-pill.active,
.tool-icon, .swatch, .vc-eyebrow .swatch { filter: saturate(1.4); }
/* Time Off's leave-type dots and calendar bars (2026-08-14). Five muted hues
   have to stay tellable apart at 10px and at 24px on a wall chart; the global
   0.72 desaturation collapses bronze and plum into the same grey-brown. */
.to-dot, .to-bar, .to-legend-swatch { filter: saturate(1.4); }
/* Headline metric numbers (KPI values + report-table colored counts/rates)
   re-saturate so the vivid --num-* tones read as bright as the legacy
   dashboard instead of washed-out (Tanveer 2026-06-16). */
.soft-kpi .sk-value.ok, .soft-kpi .sk-value.bad,
.tbl td.num-ok, .tbl td.num-bad,
.tbl td .cell-sub.rate-ok, .tbl td .cell-sub.rate-warn, .tbl td .cell-sub.rate-bad,
.funnel-stale-tbl td.funnel-stale-days-bad, .funnel-stale-tbl td.funnel-stale-days-warn,
.funnel-stale-tbl td .funnel-stale-touch-bad, .funnel-stale-tbl td .funnel-stale-touch-warn,
.funnel-stale-tbl td .funnel-stale-touch-ok { filter: saturate(1.45); }

/* ════════════════════════════════════════════════════════════════════════════
   Tilt / 3D scoping fix
   ════════════════════════════════════════════════════════════════════════════
   The original tilt CSS applied perspective+rotateX/Y to EVERY .vc surface
   on hover. With 15+ cards on /sales/summary, every scroll triggered constant
   transforms + repaints, and the radial-gradient `::before` pseudo-element
   forced a new stacking context per card with mix-blend-mode: overlay.

   We keep the effect but scope it to ONLY explicitly opt-in cards
   (.tilt-card class). Everything else gets static shadows. This is the
   single biggest performance fix for choppy scrolling.

   The OLD rules in the "Visual refinements" block below (which set tilt
   on .vc:not([data-no-tilt])) are overridden here. */
.vibe-soft .vc:not(.tilt-card),
.vibe-soft .kpi:not(.tilt-card),
.vibe-soft .cx-kpi:not(.tilt-card),
.vibe-soft .tier-card:not(.tilt-card) {
    transform-style: flat !important;
    will-change: auto !important;
    isolation: auto !important;
    perspective: none !important;
}
.vibe-soft .vc:not(.tilt-card)::before,
.vibe-soft .kpi:not(.tilt-card)::before,
.vibe-soft .cx-kpi:not(.tilt-card)::before,
.vibe-soft .tier-card:not(.tilt-card)::before {
    display: none !important;
    content: none !important;
}
/* Also kill any inline JS-applied tilt transform on non-tilt cards. */
.vibe-soft .vc.is-tilting:not(.tilt-card),
.vibe-soft .kpi.is-tilting:not(.tilt-card),
.vibe-soft .cx-kpi.is-tilting:not(.tilt-card),
.vibe-soft .tier-card.is-tilting:not(.tilt-card) {
    transform: none !important;
}
/* Disable the heavy hover-shadow on non-opt-in cards too — the static
   shadow on .vc / .kpi (set earlier in this file) is enough. */
.vc:not(.tilt-card):hover,
.kpi:not(.tilt-card):hover,
.cx-kpi:not(.tilt-card):hover,
.tier-card:not(.tilt-card):hover {
    box-shadow:
        0 1px 2px rgba(15, 23, 42, 0.03),
        0 8px 24px rgba(15, 23, 42, 0.05),
        0 24px 64px rgba(15, 23, 42, 0.05);
}

/* ════════════════════════════════════════════════════════════════════════════
   Density: new "comfort" level (default for /sales/* + /cx/* + /calls/*)
   ════════════════════════════════════════════════════════════════════════════
   Tanveer prefers the page at 90% browser zoom over 100%. Add a new density
   level that delivers that feel at 100% zoom. Sits between .density-dense
   and .density-spacious. Set as the default in base.html. */
.density-comfort .page-wrap   { padding: 24px 40px 48px; }
.density-comfort .page-head   { margin-bottom: 18px; }
.density-comfort .page-title  { font-size: 22px; }
.density-comfort .page-sub    { font-size: 12px; margin-top: 2px; }
.density-comfort .grid-soft   { gap: 14px; }
.density-comfort .vc          { padding: 16px 18px; }
.density-comfort .kpi         { padding: 14px; border-radius: 12px; }
.density-comfort .kpi .sk-value { font-size: 24px; }
.density-comfort .kpi .sk-label { font-size: 10.5px; }
.density-comfort .card        { padding: 16px; margin-bottom: 14px; }
.density-comfort .card-title  { font-size: 14px; }
.density-comfort .card-sub    { font-size: 11px; }
.density-comfort .tbl th      { padding: 10px 12px; font-size: 10.5px; }
.density-comfort .tbl td      { padding: 10px 12px; font-size: 12.5px; }
.density-comfort .mockup-section-eyebrow { margin: 4px 4px 6px; }
.density-comfort .weekly-kpi-grid { gap: 12px; grid-template-columns: repeat(auto-fill, minmax(195px, 1fr)); }

/* Card stack — vertical rhythm for a column of full-width cards (e.g. the
   Settings page). Bare stacked `.vc` cards have NO inter-card margin, so they
   touch; wrap them in `.card-stack` for consistent 24px separation (matches
   `.report-bento`, and clears the cards' soft ambient-shadow halos). */
.card-stack { display: flex; flex-direction: column; gap: 24px; }

/* ════════════════════════════════════════════════════════════════════════════
   Scrolling — kept INSTANT (scroll-behavior: auto). Smooth scrolling animates
   every programmatic scroll / anchor jump, and on this glass-heavy page each
   animation frame repaints the backdrop-filter cards, which reads as choppy.
   ════════════════════════════════════════════════════════════════════════════ */
html { scroll-behavior: auto; }

/* ════════════════════════════════════════════════════════════════════════════
   Phase 9 reusable card patterns
   ════════════════════════════════════════════════════════════════════════════ */

/* scroll-card — fixed-height + scrollable body + optional visual hero.
   Used for: "Calls scheduled this week", every detailed report card.
   Macro: _components/scroll_card.html */
.scroll-card { display: flex; flex-direction: column; }
.scroll-card .sc-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    padding-bottom: 12px;
    border-bottom: 1px solid rgba(15, 23, 42, 0.05);
}
.scroll-card .sc-head .sc-eyebrow {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-3); font-weight: 700;
}
.scroll-card .sc-head .sc-title {
    font-size: 15px; font-weight: 700; color: var(--ink-1);
    margin-top: 2px;
}
.scroll-card .sc-head .sc-sub  { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.scroll-card .sc-visual { padding: 14px 0 4px; }
.scroll-card .sc-list {
    overflow-y: auto;
    flex: 1; min-height: 0;
    padding: 4px 0;
    /* Native momentum scrolling on macOS/iOS. */
    -webkit-overflow-scrolling: touch;
}
.scroll-card .sc-list::-webkit-scrollbar { width: 8px; }
.scroll-card .sc-list::-webkit-scrollbar-thumb { background: rgba(15,23,42,0.10); border-radius: 999px; }
.scroll-card .sc-row {
    display: grid;
    gap: 12px; padding: 10px 4px;
    align-items: center;
    border-bottom: 1px solid rgba(15, 23, 42, 0.04);
    text-decoration: none; color: inherit;
    transition: background-color 0.15s ease;
}
.scroll-card .sc-row:hover { background: rgba(15, 23, 42, 0.025); }
.scroll-card .sc-row:last-child { border-bottom: 0; }

/* filter-pills sliding selector — used by Rep + Range chips.
   The "slider" is an absolutely-positioned highlight that animates between
   pills via JS-set CSS variables (--slider-x, --slider-w). */
.pill-group {
    position: relative;
    display: inline-flex; align-items: center; gap: 4px;
    /* Wrap onto extra rows when the group can't fit, instead of scrolling
       sideways (Tanveer 2026-07-03 — hidden-scrollbar overflow made the
       trailing pills undiscoverable). The thumb follows the pill's row via
       --slider-y below. */
    flex-wrap: wrap;
    max-width: 100%;
    /* 2px padding (was 4px) so the active "thumb" nearly fills the track —
       a tight gap makes the thumb's corner radius read as the SAME as the
       track's stadium, instead of a visibly tighter pill floating inside a
       roomier one (Tanveer 2026-06-13). */
    padding: 2px;
    background: rgba(15, 23, 42, 0.04);
    border-radius: 999px;
    border: 1px solid rgba(15, 23, 42, 0.05);
}
/* A multi-row track can't read as a stadium (a 999px radius on a two-row box
   clips the corner pills) — soften to the 18px design radius. app.js toggles
   .is-wrapped whenever the pills span more than one row. The thumb keeps its
   own stadium so it still matches the pills. */
.pill-group.is-wrapped { border-radius: 18px; }
.pill-group .pill-slider {
    position: absolute;
    top: 0; left: 0;
    width: var(--slider-w, 0);
    height: var(--slider-h, 0);
    transform: translate(var(--slider-x, 0), var(--slider-y, 0));
    background: rgba(255, 255, 255, 0.92);
    border-radius: 999px;
    box-shadow:
        0 1px 2px rgba(15, 23, 42, 0.06),
        0 4px 10px rgba(15, 23, 42, 0.08);
    transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
                width 0.32s cubic-bezier(0.22, 1, 0.36, 1),
                height 0.32s cubic-bezier(0.22, 1, 0.36, 1);
    pointer-events: none;
    z-index: 0;
}
.theme-dark .pill-group { background: rgba(255, 255, 255, 0.05); border-color: rgba(255,255,255,0.05); }
.theme-dark .pill-group .pill-slider { background: rgba(255, 255, 255, 0.12); }
.pill-group .pill {
    position: relative; z-index: 1;
    padding: 6px 14px;
    border-radius: 999px;
    border: 0; background: transparent;
    color: var(--ink-2);
    font-family: inherit; font-size: 12px; font-weight: 600;
    cursor: pointer;
    transition: color 0.18s ease;
    white-space: nowrap;
}
.pill-group .pill:hover { color: var(--ink-1); }
.pill-group .pill.active { color: var(--ink-1); }
.pill-group .pill[aria-pressed="true"] { color: var(--ink-1); }

/* range-picker — pills + a calendar trigger that opens a small popover.
   The popover itself is the .range-cal element below. */
.range-picker { display: inline-flex; align-items: center; gap: 8px; }
.range-cal {
    /* position: absolute, resolved against .filter-bar (which is
       position: relative + backdrop-filter, so it's the containing block).
       JS sets top/left in the bar's coordinate space. We deliberately do NOT
       use position: fixed: <body> carries `filter: saturate(...)`, which makes
       any fixed descendant resolve against the body box instead of the
       viewport — that's what threw this popover to a random spot. Staying in
       the bar also keeps the date inputs inside the <form> so Apply still
       submits. The bar's z-index:60 context + this z-80 keep it above cards. */
    position: absolute; z-index: 80;
    background: rgba(255, 255, 255, 0.92);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    backdrop-filter: blur(20px) saturate(140%);
    border: 1px solid rgba(15, 23, 42, 0.06);
    border-radius: 14px;
    box-shadow:
        0 8px 24px rgba(15, 23, 42, 0.10),
        0 24px 64px rgba(15, 23, 42, 0.12);
    padding: 14px;
    display: none;
}
.range-cal[data-open="true"] { display: block; }
.range-cal input[type=date] {
    font: inherit; padding: 8px 10px; border-radius: 8px;
    border: 1px solid rgba(15, 23, 42, 0.08);
    background: rgba(255, 255, 255, 0.6);
}
.range-cal .rc-row { display: flex; gap: 10px; align-items: center; }
.range-cal .rc-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 10px; }

/* Sync-status badge — replaces the "Live data / Mockup data" pill. */
.sync-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 11px;
    border-radius: 999px;
    background: rgba(47, 125, 92, 0.12);
    color: #1F6F3E;
    border: 1px solid rgba(47, 125, 92, 0.22);
    font-size: 11px; font-weight: 600; letter-spacing: 0.01em;
    cursor: help;
}
.sync-badge.stale {
    background: rgba(165, 124, 42, 0.12);
    color: #7B5B0A;
    border-color: rgba(165, 124, 42, 0.30);
}
.sync-badge.cold {
    background: rgba(177, 74, 77, 0.12);
    color: #842424;
    border-color: rgba(177, 74, 77, 0.30);
}
.sync-badge .dot {
    width: 6px; height: 6px; border-radius: 999px;
    background: currentColor; opacity: 0.85;
}

/* In-place filter swap — softens the page while the JS fetches new content
   so the user gets feedback that the click registered. Includes a slow
   pulse so the user can tell the page IS doing work (a frozen dim looks
   like the click had no effect — what the user actually reported). */
.v1-main.is-filtering .home-top-row,
.v1-main.is-filtering .weekly-kpi-grid,
.v1-main.is-filtering .report-bento,
.v1-main.is-filtering .report-row,
.v1-main.is-filtering .indiv-board,
.v1-main.is-filtering .chart-card {
    pointer-events: none;
    animation: sb-filter-pulse 1.4s ease-in-out infinite;
    transition: opacity 0.18s ease;
}
@keyframes sb-filter-pulse {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 0.7; }
}

/* Top-of-page progress strip — a 2px cyan bar that slides across the
   filter bar while a fetch is in flight. Always-visible signal that
   the click is doing something. */
.v1-main.is-filtering #summary-filters::after,
.v1-main.is-filtering #indiv-filters::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -3px;
    height: 2px;
    background: linear-gradient(90deg,
        transparent, var(--brand) 30%, var(--brand-deep) 50%, var(--brand) 70%, transparent);
    background-size: 200% 100%;
    animation: sb-filter-stripe 1.1s linear infinite;
    border-radius: 2px;
}
#summary-filters, #indiv-filters { position: relative; }
@keyframes sb-filter-stripe {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Hide the topbar when a page opts into full-bleed via body data attr.
   Used by /sales/summary + the workspace home which want the rail but no
   breadcrumb. v1.css declares `#app-v1 { grid-template-rows: 56px 1fr }`,
   so hiding the topbar still leaves a 56px empty row at the top. Collapse
   that row to 0 here. */
body[data-chrome="rail-only"] #app-v1 {
    grid-template-rows: 0 1fr;
}
body[data-chrome="rail-only"] .v1-topbar { display: none; }
body[data-chrome="rail-only"] .v1-main { padding-top: 0; }
body[data-chrome="rail-only"] .v1-main .page-wrap { padding-top: 12px; }
body[data-chrome="rail-only"] .v1-main .page-head { margin-bottom: 14px; }

/* ════════════════════════════════════════════════════════════════════════════
   KPI value tone — vibrant numbers (Tanveer 2026-06-16).
   ════════════════════════════════════════════════════════════════════════════
   Phase 9 had muted .bad → plain ink and .ok → muted green so a wall of
   mid-week "behind target" cards wouldn't read as alarming. Tanveer's review
   on /sales/summary flagged the result as washed-out vs the legacy dashboard,
   where the KPI numbers themselves were vividly colored. Restore that: the
   number carries the status color in the vivid --num-* tones (the heritage
   status palette). Status is still computed value-vs-target in routes.py
   (>=100% = ok, <50% = bad, in between = neutral ink) so most mid-week cards
   stay neutral — only genuinely-behind cards go red, the rest pop in ink. */
.vibe-soft .soft-kpi .sk-value.bad,
.vibe-soft .kpi .sk-value.bad { color: var(--num-bad) !important; }
.vibe-soft .soft-kpi .sk-value.ok,
.vibe-soft .kpi .sk-value.ok  { color: var(--num-good) !important; }
/* Keep reinforcing the "behind target" signal on the target line too. */
.vibe-soft .soft-kpi .sk-value.bad + .sk-delta,
.vibe-soft .soft-kpi.is-bad .sk-target strong { color: var(--num-bad); }

/* ════════════════════════════════════════════════════════════════════════════
   /sales/summary page-specific layout
   ════════════════════════════════════════════════════════════════════════════ */

/* Page head — eyebrow + title on left, sync badge on right. */
.sales-summary .page-head {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 24px; flex-wrap: wrap;
}
.sales-summary .page-actions {
    display: flex; align-items: center; gap: 10px;
}

/* Activity strip — full width inline SVG inside a glass card. */
.activity-strip { padding: 4px 6px 0; }
.activity-strip svg { display: block; }

/* ════════════════════════════════════════════════════════════════════════════
   Homepage layout — Phase 9 (2026-06-01)
   ════════════════════════════════════════════════════════════════════════════
   Welcome header, then a 2-col row (Activity by Day + Calls Scheduled),
   then a 3-col row of tool jump cards. Each card opts into .tilt-card so
   the design system's 3D-rotate-on-hover lights up uniformly. */
.home-wrap .page-head {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 24px; flex-wrap: wrap;
}
.home-wrap .page-head .page-actions { display: flex; align-items: center; gap: 10px; }

.kbd-pill {
    display: inline-block; padding: 2px 8px;
    border-radius: 6px;
    background: rgba(15, 23, 42, 0.06);
    border: 1px solid rgba(15, 23, 42, 0.08);
    font-family: var(--font-mono, ui-monospace);
    font-size: 11px; font-weight: 600;
    color: var(--ink-2);
    margin: 0 2px;
}

.home-top-row {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: 18px;
    margin-top: 20px;
    margin-bottom: 8px;
}
@media (max-width: 1100px) {
    .home-top-row { grid-template-columns: minmax(0, 1fr); }
}

/* ════════════════════════════════════════════════════════════════════════════
   Calls Scheduled card — day-grouped rows + color time chips (2026-06-02)
   ════════════════════════════════════════════════════════════════════════════ */
.calls-scheduled-card {
    /* Inline `style="height: …"` from the macro sets the outer height.
       box-sizing: border-box so that height IS the outer height (padding
       doesn't tack 36px extra on top → activity + calls cards align). */
    box-sizing: border-box;
    border-radius: 22px;
    padding: 22px 24px 14px;
    background:
        radial-gradient(ellipse at 86% 12%, rgba(156, 123, 61, 0.10), transparent 60%),
        linear-gradient(180deg, rgba(247, 245, 240, 0.75), rgba(244, 240, 230, 0.55));
}
.theme-dark .calls-scheduled-card {
    background:
        radial-gradient(ellipse at 86% 12%, rgba(156, 123, 61, 0.16), transparent 60%),
        linear-gradient(180deg, rgba(24, 22, 18, 0.85), rgba(20, 18, 14, 0.65));
}
.calls-scheduled-card .sc-head {
    border-bottom: 0; padding-bottom: 4px;
}
.calls-scheduled-card .sc-title {
    font-size: 22px; font-weight: 800; letter-spacing: -0.025em;
    margin-top: 2px;
}
.calls-scheduled-card .sc-eyebrow {
    font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase;
    color: var(--ink-3); font-weight: 700;
}

.calls-scheduled-list { padding: 4px 0; }

.calls-day-group {
    font-size: 9.5px; letter-spacing: 0.10em;
    text-transform: uppercase; font-weight: 700;
    color: var(--ink-3);
    padding: 6px 0 2px;
}

.calls-row {
    display: flex; align-items: center; gap: 10px;
    padding: 3px 4px;
    text-decoration: none; color: inherit;
    border-top: 1px solid rgba(15, 23, 42, 0.05);
    transition: background-color 0.15s ease;
}
.calls-day-group + .calls-row { border-top: 0; }
.calls-row:hover { background: rgba(15, 23, 42, 0.025); }

/* Colored time chip — 6 palette variants. Compact for dense list. */
.calls-time-chip {
    width: 36px; height: 36px;
    border-radius: 9px;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    flex-shrink: 0;
    font-family: 'Montserrat', sans-serif;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.30),
        0 2px 6px rgba(15, 23, 42, 0.05);
}
.calls-time-chip .time { font-size: 11px; font-weight: 800; line-height: 1; letter-spacing: -0.01em; }
.calls-time-chip .ampm { font-size: 7.5px; font-weight: 700; opacity: 0.70; margin-top: 2px; letter-spacing: 0.04em; }
.calls-time-chip.color-purple { background: #C4B6E8; color: #2E1B5A; }
.calls-time-chip.color-amber  { background: #F5D061; color: #4D3A0B; }
.calls-time-chip.color-teal   { background: #94D8DC; color: #0F4D52; }
.calls-time-chip.color-green  { background: #A2D9AE; color: #14442A; }
.calls-time-chip.color-coral  { background: #F0A88E; color: #5D2814; }
.calls-time-chip.color-indigo { background: #A6B5D6; color: #1C2A4F; }

.calls-meta { flex: 1; min-width: 0; }
.calls-title {
    font-size: 12px; font-weight: 700; color: var(--ink-1);
    letter-spacing: -0.005em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.calls-tags-row {
    display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
    margin-top: 2px;
}
.calls-rep { font-size: 10px; color: var(--ink-3); margin-left: 2px; }

/* Soft chip — neutral tag pill used by the calls card and other lists. */
.chip-soft {
    display: inline-block; padding: 1px 7px;
    border-radius: 999px;
    background: rgba(15, 23, 42, 0.06);
    color: var(--ink-2);
    font-size: 9.5px; font-weight: 600;
    letter-spacing: 0;
}
.theme-dark .chip-soft { background: rgba(255, 255, 255, 0.07); color: var(--ink-2); }

/* Calls row with a matched call-analysis run: stretched-link pattern so the
   whole row still opens HubSpot while the "Analysis" pill links in-portal. */
.calls-row--linked { position: relative; }
.calls-row-stretch {
    position: absolute; inset: 0; z-index: 0;
    border-radius: 10px;
}
.calls-row--linked .calls-time-chip,
.calls-row--linked .calls-meta { position: relative; z-index: 1; }
/* Brand-tinted, clickable variant of .chip-soft for the in-portal link. */
.calls-analysis-link {
    position: relative; z-index: 1;
    margin-left: 2px;
    background: var(--brand-soft);
    color: var(--brand-deep);
    border: 1px solid var(--brand-line);
    text-decoration: none; font-weight: 700;
}
.calls-analysis-link:hover { background: var(--brand-line); }

/* Legacy "home-calls-card" name still used by home.html — alias to the new
   pattern so we don't have to touch every template. */
.home-calls-card {
    height: 330px;
    border-radius: 22px;
    padding: 22px 24px 14px;
    background:
        radial-gradient(ellipse at 86% 12%, rgba(156, 123, 61, 0.10), transparent 60%),
        linear-gradient(180deg, rgba(247, 245, 240, 0.75), rgba(244, 240, 230, 0.55));
}
.theme-dark .home-calls-card {
    background:
        radial-gradient(ellipse at 86% 12%, rgba(156, 123, 61, 0.16), transparent 60%),
        linear-gradient(180deg, rgba(24, 22, 18, 0.85), rgba(20, 18, 14, 0.65));
}
.home-calls-card .sc-head { padding-bottom: 14px; }
.home-calls-card .sc-title { font-size: 20px; font-weight: 800; letter-spacing: -0.02em; }
.home-calls-card .sc-sub { font-size: 12px; }

/* Activity card matches calls card height for a balanced row.
   Both use box-sizing: border-box + the same outer height so they line up
   exactly (Tanveer 2026-06-02). */
.home-top-row .activity-card,
.home-top-row .calls-scheduled-card {
    box-sizing: border-box;
    height: 364px;
}

/* Tool jump cards — row of 3 with live headline metric. */
.home-tool-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 18px;
    margin-top: 10px;
}
@media (max-width: 900px) {
    .home-tool-row { grid-template-columns: minmax(0, 1fr); }
}

.home-tool-card {
    display: block;
    padding: 22px 24px;
    border-radius: 18px;
    text-decoration: none;
    color: var(--ink-1);
    position: relative;
    /* Cards must opt-in to tilt via the .tilt-card class on the markup. */
}
.home-tool-card .home-tool-head {
    display: flex; align-items: center; gap: 14px;
    margin-bottom: 18px;
}
.home-tool-card .home-tool-meta { flex: 1; min-width: 0; }
.home-tool-card .home-tool-name {
    font-size: 15px; font-weight: 700; letter-spacing: -0.01em;
}
.home-tool-card .home-tool-sub {
    font-size: 12px; color: var(--ink-3); margin-top: 2px;
}
.home-tool-card .home-tool-chev {
    width: 28px; height: 28px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 8px;
    background: rgba(15, 23, 42, 0.04);
    color: var(--ink-3);
    transition: background-color 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
.home-tool-card .home-tool-chev svg {
    width: 14px; height: 14px;
    stroke: currentColor; stroke-width: 2; fill: none;
}
.home-tool-card:hover .home-tool-chev {
    background: var(--tool-color, var(--brand));
    color: #fff;
    transform: translateX(2px);
}
.home-tool-card .home-tool-headline {
    font-size: 30px; font-weight: 800;
    letter-spacing: -0.025em;
    color: var(--tool-color, var(--ink-1));
    line-height: 1;
}
.home-tool-card .home-tool-delta {
    font-size: 12px; color: var(--ink-3);
    margin-top: 6px;
}

/* Section eyebrow override for home — slightly bigger so it doesn't
   look squashed between the top row and tool cards. */
.home-wrap .mockup-section-eyebrow { font-size: 11.5px; margin: 8px 4px 8px; }

/* ════════════════════════════════════════════════════════════════════════════
   Activity by day — Phase 9 reference design (2026-06-01)
   ════════════════════════════════════════════════════════════════════════════ */
.activity-card {
    padding: 22px 26px 18px;
    border-radius: 22px;
    background:
        radial-gradient(ellipse at 14% 14%, rgba(168, 159, 228, 0.18), transparent 60%),
        linear-gradient(180deg, rgba(244, 246, 252, 0.85), rgba(238, 240, 250, 0.65));
    position: relative;
    margin-bottom: 16px;
}
.theme-dark .activity-card {
    background:
        radial-gradient(ellipse at 14% 14%, rgba(108, 96, 178, 0.20), transparent 60%),
        linear-gradient(180deg, rgba(24, 26, 38, 0.85), rgba(20, 22, 32, 0.65));
}
.activity-card .activity-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; margin-bottom: 10px;
}
.activity-card .activity-head .vc-title {
    font-weight: 700; letter-spacing: -0.01em;
}
.activity-card .activity-body {
    position: relative;
}
.activity-card .activity-headline {
    display: flex; align-items: baseline; gap: 14px;
    padding: 4px 0 2px;
}
.activity-card .activity-headline .big-num {
    font-size: 44px; font-weight: 800; letter-spacing: -0.03em;
    color: var(--ink-1); line-height: 1;
}
.activity-card .activity-headline .sub-text {
    font-size: 14px; color: var(--ink-3); font-weight: 500;
}
.activity-card .activity-chart {
    margin-top: 14px;
    position: relative;
}
.activity-card .activity-chart svg { display: block; }

/* Phone / Email stat pills floating in the upper right of the card. */
.activity-card .activity-stats {
    position: absolute;
    top: -6px; right: 0;
    display: flex; gap: 12px;
    pointer-events: none;  /* purely informational; no click target */
}
.activity-card .stat-pill {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 18px;
    border-radius: 14px;
    box-shadow:
        0 6px 16px rgba(15, 23, 42, 0.10),
        0 1px 0 rgba(255, 255, 255, 0.30) inset;
}
.activity-card .stat-pill .stat-ic {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: 10px;
}
.activity-card .stat-pill .stat-num {
    font-size: 22px; font-weight: 800; line-height: 1;
    letter-spacing: -0.02em;
}
.activity-card .stat-pill .stat-lbl {
    font-size: 11px; color: rgba(255, 255, 255, 0.70);
    text-transform: uppercase; letter-spacing: 0.08em; margin-top: 3px;
    font-weight: 600;
}

/* Phone pill — sky-blue surface, dark text (the lighter blue reads better
   with dark text, mirroring the Email pill's dark-text pattern). Matches the
   dials bar segment + hover dot, so blue=dials / amber=emails reads as one
   legend across bar, pill, and tooltip. */
.activity-card .stat-phone {
    background: #4BADCF; color: #06303B;
}
.activity-card .stat-phone .stat-ic {
    background: rgba(6, 48, 59, 0.12); color: #06303B;
}
.activity-card .stat-phone .stat-num { color: #06303B; }
.activity-card .stat-phone .stat-lbl { color: rgba(6, 48, 59, 0.68); }

/* Email pill — warm amber surface */
.activity-card .stat-email {
    background: #F8E0A4;
    color: #2D241A;
}
.activity-card .stat-email .stat-ic {
    background: rgba(20, 16, 8, 0.10); color: #2D241A;
}
.activity-card .stat-email .stat-num { color: #2D241A; }
.activity-card .stat-email .stat-lbl { color: rgba(45, 36, 26, 0.65); }

/* Activity-by-day hover tooltip — mirrors the ECharts tooltip styling
   (white card, hairline border, soft shadow, 8px radius, Montserrat 12px)
   so the hand-rolled SVG chart matches every other chart on the site.
   Singleton element created + positioned by app.js. */
.sb-act-tip {
    position: fixed;
    z-index: 1200;
    pointer-events: none;
    min-width: 150px;
    padding: 8px 12px;
    background: #FFFFFF;
    border: 1px solid var(--hairline, rgba(15, 20, 25, 0.08));
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(15, 23, 42, 0.10);
    font-family: Montserrat, sans-serif;
    font-size: 12px;
    color: var(--ink-1, #0F1722);
}
.theme-dark .sb-act-tip {
    background: var(--bg-elev, #1B2230);
    border-color: rgba(255, 255, 255, 0.10);
}
.sb-act-tip .tip-head {
    font-weight: 700;
    margin-bottom: 6px;
}
.sb-act-tip .tip-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    line-height: 1.75;
    font-weight: 500;
}
.sb-act-tip .tip-row .tip-lbl { display: inline-flex; align-items: center; }
.sb-act-tip .tip-row .tip-dot {
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 2px;
    margin-right: 7px;
}
.sb-act-tip .tip-row b { font-weight: 700; margin-left: 16px; }
.sb-act-tip .tip-total {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 4px;
    padding-top: 4px;
    border-top: 1px solid var(--hairline, rgba(15, 20, 25, 0.08));
    color: var(--ink-2, #4E5862);
}
.sb-act-tip .tip-total b { color: var(--ink-1, #0F1722); font-weight: 700; margin-left: 16px; }

/* Filter bar — same height as a button row, padded glass card.
   Bottom margin trimmed (was 20px) per Tanveer 2026-06-02: too much air
   between the filter bar, the "This week" eyebrow, and the KPI grid. */
.filter-bar {
    display: flex; align-items: center; gap: 16px;
    margin: 14px 0 8px;
    padding: 12px 16px;
    background: rgba(255, 255, 255, 0.42);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    backdrop-filter: blur(20px) saturate(140%);
    border: 1px solid rgba(15, 23, 42, 0.05);
    border-radius: 14px;
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.04);
    flex-wrap: wrap;
    /* `backdrop-filter` creates a stacking context, so the .range-cal
       popover inside this form is z-trapped relative to the filter-bar.
       Lift the whole filter-bar above the page content (cards, links,
       activity rows) so the popover renders on top. Modal overlays use
       higher z-indices, so this stays out of their way. */
    position: relative; z-index: 60;
}
.theme-dark .filter-bar {
    background: rgba(20, 20, 28, 0.45);
    border-color: rgba(255, 255, 255, 0.04);
}

/* Filter segment — eyebrow + pill-group cluster. */
.filter-segment {
    display: flex; align-items: center; gap: 10px;
    min-width: 0;
}
.filter-eyebrow {
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.10em;
    color: var(--ink-3); font-weight: 700;
}

/* Sticky tool sub-nav (Sales tool — Tanveer 2026-07-03). The tab row
   (Summary / Individual Performance / Leadership / Settings / Admin / Kiosk
   Display) pins to the top of the scroll; once it's actually stuck, app.js
   adds .is-stuck (sentinel + IntersectionObserver — CSS has no :stuck) and
   the current page's title slides in on the left. z-index 70 keeps the
   scrolling filter bar (z-60) passing UNDER it; modals (100+) stay above. */
.subnav-wrap.subnav-sticky {
    position: sticky;
    top: 0;
    z-index: 70;
    display: flex; align-items: center; gap: 16px;
    /* The tab-row capsule's own margin-bottom (22px desktop / 14px mobile)
       is "space below the sub-nav" duty — inside this align-items:center
       flex it shoved the capsule ~11px above true center. Zero it here and
       carry the spacing on the wrap instead, where it doesn't participate
       in the centering. */
    margin-bottom: 22px;
    transition: padding 0.25s ease, background 0.2s ease;
}
.subnav-wrap.subnav-sticky .tab-row { margin-bottom: 0; }
/* Shared PINNED-CHROME surface (Tanveer 2026-07-22): every bar that pins —
   the stuck tool sub-nav AND the CX sticky filter strips — uses this ONE
   glass recipe, so they always match. Frosted, but notably less
   transparent than the old 0.42 sub-nav glass: content scrolling beneath
   reads as a hint, not a distraction. */
:root {
    --pinned-glass-bg: rgba(255, 255, 255, 0.8);
    --pinned-glass-filter: blur(20px) saturate(140%);
}
.theme-dark {
    --pinned-glass-bg: rgba(20, 20, 28, 0.8);
}
.subnav-wrap.subnav-sticky.is-stuck {
    background: var(--pinned-glass-bg);
    -webkit-backdrop-filter: var(--pinned-glass-filter);
    backdrop-filter: var(--pinned-glass-filter);
    border-bottom: 1px solid rgba(15, 23, 42, 0.05);
    padding-top: 10px; padding-bottom: 10px;
}
.theme-dark .subnav-wrap.subnav-sticky.is-stuck {
    border-bottom-color: rgba(255, 255, 255, 0.04);
}
.subnav-stuck-title {
    max-width: 0; opacity: 0; overflow: hidden;
    white-space: nowrap;
    font-size: 13.5px; font-weight: 800; letter-spacing: 0.01em;
    color: var(--ink-1);
    flex: 0 0 auto;
    transition: max-width 0.28s cubic-bezier(0.22, 1, 0.36, 1),
                opacity 0.2s ease;
}
.subnav-wrap.is-stuck .subnav-stuck-title { max-width: 340px; opacity: 1; }

/* Section-eyebrow rows that carry controls (e.g. the Weekly-trends weeks
   pill) — keep pill + scope chip on one line, right-aligned. */
.mockup-section-eyebrow .eyebrow-controls {
    display: flex; align-items: center; gap: 12px;
}
/* Per-report window pills live in the card header's action cluster next to
   the expand button. */
.report-bento .vc .card-actions { align-items: center; gap: 10px; }
/* The header actions cluster never shrinks: a long title/subtitle otherwise
   squeezes it (.filter-segment's min-width:0 + .pill-group's flex-wrap) until
   the window pills stack one per row. Let the text column wrap instead; on
   phones responsive.css wraps .vc-head so the cluster drops below the title. */
.vc-head .card-actions,
.card-head .card-actions { flex-shrink: 0; }

/* Detail-reports grid — 2-col on desktop, single-col on narrow widths. */
.detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
    gap: 16px;
    margin-bottom: 20px;
}

/* Phase 9 redesign — detail-report cards. Old .report-row-3 kept for any
   sub-tools still using it; primary layout now is the bento grid below. */
.report-row {
    display: grid;
    gap: 16px;
    margin-bottom: 16px;
}
.report-row-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.report-row-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 1180px) {
    .report-row-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
    .report-row-3, .report-row-2 { grid-template-columns: minmax(0, 1fr); }
}

/* Bento grid for detail reports — breaks the rigid 3x2. 12-col base, three
   distinct row shapes: (8+4), (12 feature), (4+4+4). Each chart gets a span
   that matches the shape of its data.

   Gap = 32px (one step up from design-system 24px). The .vc cards carry a
   heavy 64px-blur ambient shadow that bleeds ~24px past each card edge —
   at gap 24, adjacent cards' shadow halos overlap and visually merge the
   cards together. 32px clears the halos and gives real separation. */
.report-bento {
    /* One column — each report card spans full width, stacked vertically.
       Multi-column bento packed cards too tightly, the "raw data" details
       inside narrow cards then overflowed sideways. Full-width is what the
       user asked for ("not 1 by 1"). */
    display: flex;
    flex-direction: column;
    gap: 24px;
    margin-top: 16px;
    margin-bottom: 32px;
}
.report-bento > * { width: 100%; min-width: 0; }

/* Standalone glass cards stacked at the bottom of /sales/summary —
   Funnel, Contractor Type Scorecard, State + Province, BDR→AE Handoff,
   Recent Demos. Without a margin they butt right up against each other;
   the 64px-blur ambient shadow on .vc bleeds past the card edge, which
   makes adjacent cards look visually merged. Matches the 32px gap the
   .report-bento uses for the same reason. */
.sales-summary > .vc { margin-bottom: 32px; }
.sales-summary > .vc:last-child { margin-bottom: 0; }
/* Section eyebrow that introduces the bento — beat the density-comfort
   override (.density-comfort .mockup-section-eyebrow has specificity 0,2,0,
   so this needs to match or exceed). */
.report-bento-section-eyebrow,
.density-comfort .report-bento-section-eyebrow {
    margin: 32px 4px 16px;
}

/* Inline sparkline bar — used in the 12-week trend table cells.
   Displays as a colored block whose width carries the magnitude. */
.spark-bar {
    display: inline-block;
    height: 8px;
    border-radius: 3px;
    vertical-align: middle;
    margin-right: 6px;
    min-width: 2px;
    opacity: 0.85;
}

/* Funnel card — ECharts funnel on the left, companion stats table on the
   right (matches the legacy weekly report layout). Collapses to a single
   column under 1080 px so the table sits below the chart on narrow widths. */
.funnel-card-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
    gap: 20px;
    align-items: stretch;
}
@media (max-width: 1080px) {
    .funnel-card-grid { grid-template-columns: minmax(0, 1fr); }
}
.funnel-card-chart { min-width: 0; }
.funnel-card-table { min-width: 0; overflow-x: auto; }

/* Horizontal bar funnel (replaced the ECharts trapezoid 2026-07-03) — one
   row per stage: label · proportional bar · stage-to-stage conversion badge.
   Bar widths carry a ~7% floor in the template so tiny stages stay visible;
   the count sits just past the bar end. Rows drill into the funnel modal
   via the shared [data-kpi-drilldown] delegation. */
.funnel-bars {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px 0 4px;
}
.funnel-bar-row {
    display: grid;
    grid-template-columns: 168px minmax(0, 1fr) 112px;
    gap: 12px;
    align-items: center;
}
.fb-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-2);
    text-align: right;
    line-height: 1.25;
}
.fb-track {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    height: 30px;
    padding: 3px;
    border-radius: 10px;
    background: rgba(15, 23, 42, 0.04);
    cursor: pointer;
}
.fb-track:hover .fb-bar { filter: saturate(1.15) brightness(1.05); }
.fb-track:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.fb-bar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex: 0 0 auto;
    max-width: 100%;
    height: 100%;
    border-radius: 6px;
    transition: width 0.35s ease;
}
.fb-val {
    font-size: 12px;
    font-weight: 700;
    color: var(--ink-1);
    font-variant-numeric: tabular-nums;
}
/* Wide bars carry their count inside, right-aligned at the bar end (the
   same white-on-stage-color the old ECharts funnel labels used). */
.fb-bar .fb-val-in { color: #FFFFFF; padding-right: 9px; }
.fb-conv { min-width: 0; }
.fb-conv-first {
    font-size: 11px;
    font-weight: 600;
    color: var(--ink-3);
}
.funnel-stage-swatch {
    display: inline-block;
    width: 10px; height: 10px;
    border-radius: 2px;
    vertical-align: middle;
    margin-right: 6px;
}
.funnel-tbl td .funnel-conv,
.fb-conv .funnel-conv {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 999px;
    font-weight: 700;
    font-size: 11px;
    white-space: nowrap;
}
.funnel-tbl td .funnel-conv-good,
.fb-conv .funnel-conv-good {
    background: rgba(26, 174, 142, 0.14); color: var(--ok);
}
.funnel-tbl td .funnel-conv-warn,
.fb-conv .funnel-conv-warn {
    background: rgba(232, 144, 25, 0.14); color: var(--warn);
}
.funnel-tbl td .funnel-conv-bad,
.fb-conv .funnel-conv-bad {
    background: rgba(229, 72, 77, 0.14); color: var(--bad);
}
.funnel-tbl td .funnel-stale {
    color: var(--bad); font-weight: 700;
}
.funnel-tbl td .funnel-stale-meta {
    color: var(--ink-3); font-weight: 500; font-size: 10px;
    margin-left: 2px;
}
.funnel-tbl tr.funnel-terminal-row td {
    background: rgba(15, 23, 42, 0.03);
    color: var(--ink-2);
    font-size: 12px;
    border-top: 1px solid var(--hairline);
}

/* Stale-deals reminder table — sits beneath the funnel + companion table.
   Heading is red so it reads as an action item. Per-row coloring on
   Days Stuck + Last Touch surfaces the worst offenders at a glance. */
.funnel-stale-section {
    margin-top: 20px;
    padding-top: 16px;
    border-top: 1px dashed var(--hairline);
}
.funnel-stale-heading {
    color: var(--bad);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    margin-bottom: 10px;
}
.funnel-stale-tbl td .funnel-stale-days,
.funnel-stale-tbl td.funnel-stale-days {
    font-weight: 700;
}
.funnel-stale-tbl td.funnel-stale-days-bad  { color: var(--num-bad); }
.funnel-stale-tbl td.funnel-stale-days-warn { color: var(--num-warn); }
.funnel-stale-tbl td .funnel-stale-touch-bad  { color: var(--num-bad);   font-weight: 600; }
.funnel-stale-tbl td .funnel-stale-touch-warn { color: var(--num-warn);  font-weight: 600; }
.funnel-stale-tbl td .funnel-stale-touch-ok   { color: var(--num-good);  font-weight: 600; }
.funnel-stale-tbl td .funnel-stale-touch-ink-3 { color: var(--ink-3); }
.funnel-stale-tbl a.lead-link {
    color: var(--brand-deep); text-decoration: none; font-weight: 600;
}
.funnel-stale-tbl a.lead-link:hover { text-decoration: underline; }

/* Report-table cell decorations — legacy-parity formatting for the State /
   Contractor / Handoff tables: small sub-line under a count (% of leads,
   connect rate), green/red outcome numbers, and clickable drill-down cells
   that open the shared KPI modal. */
.tbl td .cell-sub {
    display: block;
    font-size: 11px;
    line-height: 1.2;
    color: var(--ink-3);
    font-weight: 600;
}
/* Colored report-table numbers use the vivid --num-* tones so the
   connect-rate / outcome counts read as clearly as the legacy dashboard
   (Tanveer 2026-06-16). Re-saturated via the allowlist near the top. */
.tbl td .cell-sub.rate-ok   { color: var(--num-good); }
.tbl td .cell-sub.rate-warn { color: var(--num-warn); }
.tbl td .cell-sub.rate-bad  { color: var(--num-bad); }
.tbl td.num-ok  { color: var(--num-good); font-weight: 700; }
.tbl td.num-bad { color: var(--num-bad);  font-weight: 700; }
.tbl td.num-zero { color: var(--ink-3); }
/* Drill-able report cells stay clickable (pointer cursor) but get NO hover
   highlight — the brand-cyan tint + underline were removed per request
   (looked inconsistent: only some cells in a row carry it). */
.tbl td.drill-cell { cursor: pointer; }

/* Totals footer row on report tables (State / Contractor). */
.tbl tfoot .tbl-total td {
    font-weight: 700;
    border-top: 2px solid var(--hairline);
    background: var(--paper-2, transparent);
}

/* HubSpot record links inside report tables (same treatment as the
   drilldown modal's .hs-link). */
.tbl a.hs-link {
    color: var(--brand-deep);
    text-decoration: none;
    font-weight: 600;
}
.tbl a.hs-link:hover { color: #00B2E3; text-decoration: underline; }

/* Handoff summary strip — clickable mini-KPIs above the handoff table. */
.handoff-strip {
    display: flex; gap: 28px; flex-wrap: wrap;
    margin-bottom: 14px;
}
.handoff-stat { cursor: pointer; }
.handoff-stat:hover .hs-num { text-decoration: underline; }
.handoff-stat .hs-label {
    font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--ink-3);
}
.handoff-stat .hs-num {
    font-size: 22px; font-weight: 700; color: var(--ink-1);
    font-variant-numeric: tabular-nums; line-height: 1.25;
}
.handoff-stat .hs-num.ok    { color: var(--num-good); filter: saturate(1.45); }
.handoff-stat .hs-num.bad   { color: var(--num-bad);  filter: saturate(1.45); }
.handoff-stat .hs-num.brand { color: var(--num-info); filter: saturate(1.45); }
.handoff-stat .hs-sub { font-size: 11px; color: var(--ink-3); }

/* Contractor type scorecard — parent rows expand to show sub-types. Hover uses
   the neutral row tint shared by every other table row (was a brand-cyan tint,
   removed per request — the blue read as inconsistent vs the plain rows). */
.contractor-tbl .contractor-parent.has-subs { cursor: pointer; }
.contractor-tbl .contractor-parent.has-subs:hover { background: rgba(15, 23, 42, 0.025); }
.contractor-tbl .contractor-caret {
    display: inline-block;
    width: 12px;
    color: var(--ink-3);
    transition: transform 0.18s ease;
    margin-right: 4px;
}
.contractor-tbl .contractor-parent.is-expanded .contractor-caret {
    transform: rotate(90deg);
}
.contractor-tbl .contractor-child { display: none; }
.contractor-tbl .contractor-child.is-shown { display: table-row; }
.contractor-tbl .contractor-child td { background: rgba(15, 23, 42, 0.02); }

/* (Removed .oct-table styles — the Optimal Calling Timezone reference moved
   to the office kiosk /sales/screen and is styled inline there.) */
/* Bento span classes are noops under the new single-column flex layout —
   every card spans the full width. Kept here as a stub so existing
   `.bento-8` etc. markup doesn't break, in case anything else queries them. */
.report-bento > .bento-4,
.report-bento > .bento-6,
.report-bento > .bento-8,
.report-bento > .bento-12 { width: 100%; }
/* The bento cells inherit the same internal padding/typography rules as
   .report-row so the look is unchanged inside each card. */
.report-bento .vc { padding: 18px 20px 14px; }
.report-bento .vc .vc-head { margin-bottom: 8px; }
.report-bento .vc .vc-title { font-size: 14px; font-weight: 700; }
.report-bento .vc .vc-eyebrow {
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-3); font-weight: 700; margin-bottom: 2px;
}
.report-bento .vc .vc-caption {
    font-size: 11px; color: var(--ink-3); margin-top: 1px;
}
.report-bento .vc details.chart-rows summary {
    padding: 8px 4px 6px;
    cursor: pointer;
    display: flex; align-items: center; gap: 10px;
    color: var(--ink-2); font-size: 11px;
    border-top: 1px dashed rgba(15, 23, 42, 0.07);
    margin-top: 8px;
}
.report-bento .vc details.chart-rows summary::-webkit-details-marker { display: none; }
.report-bento .vc details.chart-rows summary::before {
    content: "▸"; color: var(--ink-3); transition: transform 0.18s ease;
}
.report-bento .vc details.chart-rows[open] summary::before { transform: rotate(90deg); }
.report-bento .vc details.chart-rows summary .ct {
    margin-left: auto; color: var(--ink-3); font-size: 10.5px;
}
/* Raw-data body inside .vc bento cards. Previously the scroll-cap CSS was
   scoped to `.chart-card`, so when users expanded "Raw data" inside a
   `.vc` bento card the table extended past the bottom of the card.
   These rules clip to a max-height + add overflow scroll, and the table
   typography matches `.chart-card .chart-rows-tbl`. */
.report-bento .vc .chart-rows-scroll {
    overflow-y: auto;
    overflow-x: auto;
    max-height: 240px;
    padding: 4px 0;
    border-radius: 8px;
}
.report-bento .vc .chart-rows-tbl {
    width: 100%; border-collapse: collapse; font-size: 12px;
}
.report-bento .vc .chart-rows-tbl th,
.report-bento .vc .chart-rows-tbl td {
    padding: 6px 10px; text-align: left;
    border-bottom: 1px solid rgba(15, 23, 42, 0.04);
}
.report-bento .vc .chart-rows-tbl th {
    /* Opaque surface token, not a 3%-alpha tint — this header is position:sticky,
       so a translucent fill lets scrolled rows bleed through it. */
    background: var(--bg-sunken); font-size: 10px;
    text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-3); font-weight: 700;
    position: sticky; top: 0;
}
.report-bento .vc .chart-rows-tbl td:nth-child(n+2) {
    text-align: right; font-variant-numeric: tabular-nums;
}
/* Soft loading state for in-place filter swap. */
.v1-main.is-filtering .report-bento { opacity: 0.5; transition: opacity 0.15s ease; }
/* Single-column layout — no responsive breakpoints needed. Each card already
   spans full width at every viewport. */

/* Each report card's chart area — pad consistently, leave room above + below. */
.chart-frame {
    padding: 10px 8px 6px;
    min-height: 200px;
}
.chart-frame.donut-frame {
    padding: 6px 8px 0;
}

/* Title row inside report cards — slightly smaller eyebrow than the big
   page-level eyebrow so cards look like part of a set, not standalone hero. */
.report-row .vc { padding: 18px 20px 14px; }
.report-row .vc .vc-head { margin-bottom: 8px; }
.report-row .vc .vc-title { font-size: 14px; font-weight: 700; }
.report-row .vc .vc-eyebrow {
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-3); font-weight: 700; margin-bottom: 2px;
}
.report-row .vc .vc-caption {
    font-size: 11px; color: var(--ink-3); margin-top: 1px;
}

/* Raw-data details below the chart — collapsed by default unless useful */
.report-row .vc details.chart-rows summary {
    padding: 8px 4px 6px;
    cursor: pointer;
    display: flex; align-items: center; gap: 10px;
    color: var(--ink-2); font-size: 11px;
    border-top: 1px dashed rgba(15, 23, 42, 0.07);
    margin-top: 8px;
}
.report-row .vc details.chart-rows summary::-webkit-details-marker { display: none; }
.report-row .vc details.chart-rows summary::before {
    content: "▸"; color: var(--ink-3); transition: transform 0.18s ease;
}
.report-row .vc details.chart-rows[open] summary::before { transform: rotate(90deg); }
.report-row .vc details.chart-rows summary .ct {
    margin-left: auto; color: var(--ink-3); font-size: 10.5px;
}

/* scroll-card row variants used by /sales/summary. */
.sc-row-calls {
    grid-template-columns: minmax(0, 1fr) auto;
}
.sc-row-calls .sc-cell-title {
    min-width: 0;
}
.sc-row-calls .sc-row-name {
    font-size: 13px; font-weight: 600; color: var(--ink-1);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sc-row-calls .sc-row-meta {
    font-size: 11px; color: var(--ink-3); margin-top: 2px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sc-row-2col {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
}
.sc-row-2col > :first-child {
    font-size: 13px; color: var(--ink-1);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sc-row-2col strong { font-size: 14px; font-weight: 700; color: var(--ink-1); }
.sc-row-2col .row-sub {
    font-size: 11px; color: var(--ink-3); margin-left: 6px;
}

.empty-row {
    padding: 24px 12px; text-align: center;
    font-size: 12px; color: var(--ink-3); font-style: italic;
}

/* Status pill on scroll-card rows (e.g. meeting outcome). */
.sc-row .status-pill {
    display: inline-block;
    padding: 3px 9px; border-radius: 999px;
    font-size: 10px; font-weight: 700;
    letter-spacing: 0.04em; text-transform: uppercase;
    flex-shrink: 0;
}

/* Page sub — keep small under page title. */
.page-eyebrow {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--brand); font-weight: 700;
    display: flex; align-items: center; gap: 8px;
}
.page-eyebrow .swatch {
    width: 8px; height: 8px; border-radius: 2px;
    display: inline-block;
}
.page-title { font-size: 26px; font-weight: 700; margin: 4px 0 4px; letter-spacing: -0.01em; }
.page-sub   { font-size: 13px; color: var(--ink-2); }

/* Smaller density bump for the comfort level. */
.density-comfort .page-title { font-size: 22px; }
.density-comfort .page-sub   { font-size: 12px; }
.density-comfort .filter-bar { padding: 10px 14px; margin: 10px 0 6px; }
.density-comfort .detail-grid { gap: 14px; }

/* Section eyebrow used between strata. Tight top margin so it reads as a
   "header" for the section below rather than its own floating row. */
.mockup-section-eyebrow {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-2); font-weight: 700;
    display: flex; justify-content: space-between; align-items: baseline;
    margin: 6px 4px 8px;
}
.mockup-section-eyebrow .hint {
    font-size: 10.5px; color: var(--ink-3); font-weight: 500;
    text-transform: none; letter-spacing: 0;
}

/* The bundle assumed it was rendered inside the artboard mode without
   a fixed body. We're running for real, so body must fill the viewport. */
html, body {
    height: 100%;
    margin: 0;
    overflow: hidden;
    font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: var(--bg-app);
    color: var(--ink-1);
}

/* Anchor reset — tabs are <a> in our port, default underline is wrong. */
a { color: inherit; text-decoration: none; }
button { font-family: inherit; }

/* The icon macro emits <svg> without explicit width/height attrs.
   Most contexts size via class (.btn .ic, .icon-btn svg, etc.) but
   the topbar's search pill doesn't have a sizing rule in the bundle.
   Belt-and-suspenders: cap any "naked" SVG inside a topbar / search-pill /
   user-menu item to 14px so it never goes huge. */
.v1-topbar .search-pill svg,
.v1-topbar .icon-btn svg,
.user-menu .um-item svg,
.am-tool .am-tool-ic svg {
    width: 14px; height: 14px;
    stroke: currentColor; stroke-width: 2; fill: none;
}
.am-tool .am-tool-ic svg { width: 20px; height: 20px; }
.rail .rail-item svg { width: 22px; height: 22px; }
.rail .brand-mark svg { width: 18px; height: 18px; }
/* SVG icons inside the cmdk circle icons */
.c-ic svg, .fly-ic svg { width: 14px; height: 14px; stroke: currentColor; stroke-width: 2; fill: none; }
/* SVG icons inside list-row thumbnails on the home page */
.list-row .thumb svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2; fill: none; }

/* ── Tool icon component ─────────────────────────────────────────
   Colored rounded square holding a tool's glyph. Used on home cards,
   add-tool modal, anywhere a tool needs a small visual marker.
   Defined here so home.html stops inline-styling its own version. */
.tool-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    flex-shrink: 0;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.22),
        0 6px 14px rgba(15, 23, 42, 0.08);
}
.tool-icon svg {
    stroke: currentColor;
    stroke-width: 1.75;
    fill: none;
}
.tool-icon-sm { width: 32px; height: 32px; border-radius: 9px; }
.tool-icon-md { width: 44px; height: 44px; border-radius: 12px; }
.tool-icon-lg { width: 56px; height: 56px; border-radius: 14px; }
.tool-icon-sm svg { width: 16px; height: 16px; }
.tool-icon-md svg { width: 20px; height: 20px; }
.tool-icon-lg svg { width: 24px; height: 24px; }
.theme-dark .tool-icon { box-shadow: inset 0 1px 0 rgba(255,255,255,0.10), 0 6px 14px rgba(0,0,0,0.35); }

/* ── Design-system color swatch — bigger square + labels under it. ── */
.ds-swatch {
    display: flex; flex-direction: column; gap: 6px;
    min-width: 100px;
}
.ds-swatch .ds-swatch-chip {
    width: 56px; height: 56px;
    border-radius: 12px;
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,0.22),
        0 4px 10px rgba(15, 23, 42, 0.08),
        0 0 0 1px rgba(15, 23, 42, 0.05);
}
.ds-swatch .ds-swatch-name {
    font-size: 12px; font-weight: 600; color: var(--ink-1);
    font-family: var(--font-mono, monospace);
}
.ds-swatch .ds-swatch-val {
    font-size: 11px; color: var(--ink-3);
    font-family: var(--font-mono, monospace);
}

/* KPI delta: a tiny pill that pops in green/red instead of bare text. */
.kpi .k-delta {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
}
.kpi .k-delta.up   { color: #137a52; background: rgba(26, 174, 142, 0.16); }
.kpi .k-delta.down { color: #a1262b; background: rgba(229, 72, 77, 0.16); }
.kpi .k-delta.flat { color: var(--ink-3); background: rgba(136, 146, 160, 0.16); }
.theme-dark .kpi .k-delta.up   { color: #5BE08F; background: rgba(91, 224, 143, 0.16); }
.theme-dark .kpi .k-delta.down { color: #FF8C8C; background: rgba(255, 140, 140, 0.18); }
.theme-dark .kpi .k-delta.flat { color: var(--ink-3); background: rgba(136, 146, 160, 0.16); }
.kpi .k-delta::before {
    content: ""; width: 0; height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
}
.kpi .k-delta.up::before   { border-bottom: 5px solid currentColor; }
.kpi .k-delta.down::before { border-top: 5px solid currentColor; }
.kpi .k-delta.flat::before {
    border: 0; width: 7px; height: 2px; background: currentColor; border-radius: 2px;
}

/* Per-tool accent applied to the swatch in the eyebrow + the rail-active
   inset. Most of this is in prototype.css; this is a safety net for the
   "home" pseudo-tool. */
#app-v1[data-tool="home"] .v1-topbar .crumbs .here { color: var(--ink-1); }

/* Make the .vibe-mode + main pane scroll inside its grid cell. Instant scroll
   (scroll-behavior: auto) — smooth animations repaint the glass cards every
   frame, which reads as choppy on this page. */
.v1-main { scroll-behavior: auto; }
.v1-main::-webkit-scrollbar { width: 10px; }
.v1-main::-webkit-scrollbar-thumb { background: rgba(15,23,42,0.10); border-radius: 999px; }
.v1-main::-webkit-scrollbar-thumb:hover { background: rgba(15,23,42,0.18); }
.theme-dark .v1-main::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.10); }

/* Modal show/hide hook — bundle uses React conditional render. Here we
   toggle hidden via JS. */
.add-tool-modal-overlay[hidden] { display: none; }
.user-menu[hidden] { display: none; }
.cmdk-overlay[hidden] { display: none; }

/* Design-system page-specific helpers. */
.ds-section { margin: 36px 0 18px; }
.ds-section h2 {
    font-size: 18px; font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--ink-1);
    margin: 0 0 4px;
}
.ds-section p {
    font-size: 13px; color: var(--ink-2); margin: 0 0 16px;
    max-width: 64ch;
}
.ds-stage {
    background: rgba(255,255,255,0.30);
    border: 0.5px solid rgba(255,255,255,0.85);
    border-radius: 18px;
    padding: 22px;
    margin-bottom: 14px;
    -webkit-backdrop-filter: blur(20px);
    backdrop-filter: blur(20px);
}
.ds-stage > .ds-stage-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-3); font-weight: 700;
    margin-bottom: 12px;
}
.ds-grid { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
/* Each child takes its natural width. Stretching small things (icons, chips)
   to 200px makes them look broken. Use .ds-grid-wide if you specifically
   want every child to share a column width. */
.ds-grid-wide > * { min-width: 200px; }
.ds-kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 12px;
}

/* ──────────────────────────────────────────────────────────────────
   Visual refinements — 2026-05-31
   Tanveer flagged two issues on the platform:
   (a) Cards have a glaring white outline that reads worse in dark mode.
       Cause: the design bundle gives every .vc / .card / .kpi a
       0.5px solid rgba(255,255,255,0.85) border + bright top-edge
       inset highlight. We soften both — the actual glass effect comes
       from backdrop-filter on the vibrant mesh, not from the outline.
   (b) The active tool in the icon rail has a 2px brand line on the
       LEFT edge only (inset 2px 0 0 var(--brand)). Tanveer wanted a
       floating/lifted feel instead. We swap the side bar for an outer
       glow + soft ring so the active item lifts off the rail.
   Per the kickoff rule, edits go here in theme.css, not in the
   design-bundle CSS files.
   ────────────────────────────────────────────────────────────────── */

.vibe-soft .vc,
.vibe-soft .glass,
.vibe-soft .card {
    border: 1px solid rgba(15, 23, 42, 0.05);
    box-shadow:
        0 1px 2px rgba(15, 23, 42, 0.03),
        0 8px 24px rgba(15, 23, 42, 0.05),
        0 24px 64px rgba(15, 23, 42, 0.05);
}
.theme-dark .vibe-soft .vc,
.theme-dark .vibe-soft .glass,
.theme-dark .vibe-soft .card {
    border: 1px solid rgba(255, 255, 255, 0.04);
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.40),
        0 12px 40px rgba(0, 0, 0, 0.30);
}

.vibe-soft .kpi {
    border: 1px solid rgba(15, 23, 42, 0.05);
    box-shadow:
        0 1px 2px rgba(15, 23, 42, 0.03),
        0 6px 20px rgba(15, 23, 42, 0.04);
}
.theme-dark .vibe-soft .kpi {
    border: 1px solid rgba(255, 255, 255, 0.04);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.30);
}

.vibe-soft .seg {
    border: 1px solid rgba(15, 23, 42, 0.06);
}
.theme-dark .vibe-soft .seg {
    border: 1px solid rgba(255, 255, 255, 0.06);
}

/* Active tool in the icon rail — floating glow instead of left bar. */
.rail .rail-item.active {
    background: rgba(0, 178, 227, 0.22);
    color: #fff;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.10),
        0 6px 16px rgba(0, 178, 227, 0.35),
        0 0 0 1px rgba(0, 178, 227, 0.30);
}
.theme-dark .rail .rail-item.active {
    background: rgba(0, 178, 227, 0.20);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.12),
        0 8px 18px rgba(0, 178, 227, 0.42),
        0 0 0 1px rgba(0, 178, 227, 0.38);
}

/* ──────────────────────────────────────────────────────────────────
   Phase 2 mockup helpers — additions used by /mockup/* pages.
   ────────────────────────────────────────────────────────────────── */

/* Density variants. Applied as .density-dense or .density-spacious on the
   .v1-main wrapper. Variants the user picks one per page from in Phase 2,
   which locks the per-tool page templates for Phases 4–6. */
.density-dense .page-wrap { padding: 18px 28px 36px; }
.density-dense .page-head { margin-bottom: 14px; }
.density-dense .page-title { font-size: 24px; }
.density-dense .page-sub { font-size: 12px; margin-top: 2px; }
.density-dense .grid-soft { gap: 10px; }
.density-dense .vc { padding: 14px 16px; }
.density-dense .kpi { padding: 12px; border-radius: 12px; }
.density-dense .kpi .k-value { font-size: 22px; margin-top: 4px; }
.density-dense .kpi .k-label { font-size: 10px; }
.density-dense .card { padding: 14px; margin-bottom: 12px; }
.density-dense .card-title { font-size: 14px; }
.density-dense .card-sub { font-size: 11px; }
.density-dense .tbl th { padding: 8px 10px; font-size: 10px; }
.density-dense .tbl td { padding: 8px 10px; font-size: 12px; }
.density-dense .mockup-section-eyebrow { margin: 4px 4px 8px; }

.density-spacious .page-wrap { padding: 36px 56px 64px; }
.density-spacious .page-head { margin-bottom: 28px; }
.density-spacious .grid-soft { gap: 18px; }
.density-spacious .vc { padding: 22px 24px; }
.density-spacious .kpi { padding: 18px; }
.density-spacious .kpi .k-value { font-size: 28px; }
.density-spacious .card { padding: 22px; margin-bottom: 20px; }
.density-spacious .tbl th { padding: 12px 14px; }
.density-spacious .tbl td { padding: 14px; }
.density-spacious .mockup-section-eyebrow { margin: 16px 4px 14px; }

/* Mockup section eyebrow — re-uses .vc-eyebrow tone in the page flow. */
.mockup-section-eyebrow {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ink-2);
    font-weight: 700;
    display: flex; align-items: baseline; justify-content: space-between;
}
.mockup-section-eyebrow .hint {
    font-size: 11px; color: var(--ink-3); font-weight: 500;
    text-transform: none; letter-spacing: 0;
}

/* Filter pills — used by CX (range, tier) and Calls (workflow, status).
   Inline-styled in the source JSX; consolidated here because the same
   markup appears across both mockup pages. */
.filter-pill {
    padding: 6px 12px;
    border-radius: 999px;
    border: 1px solid var(--hairline);
    background: var(--bg-canvas);
    color: var(--ink-2);
    font-size: 11px; font-weight: 600;
    cursor: pointer;
    font-family: inherit;
    transition: background .12s, color .12s, border-color .12s;
}
.filter-pill:hover { background: var(--bg-sunken); color: var(--ink-1); }
.filter-pill.active {
    background: var(--brand-soft);
    color: var(--brand-deep);
    border-color: var(--brand-line);
}

/* SoftKPI — the sales KPI card with eyebrow + value + delta pill on top,
   target line, then a 3-col mini-grid for 4w / 12w / MTD averages. */
.soft-kpi { display: flex; flex-direction: column; }
.soft-kpi .sk-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--ink-3); font-weight: 700;
}
.soft-kpi .sk-headline {
    display: flex; align-items: baseline; gap: 8px;
    margin-top: 6px;
}
.soft-kpi .sk-value {
    font-size: 32px; font-weight: 700;
    letter-spacing: -0.02em; line-height: 1;
    color: var(--ink-1);
}
.soft-kpi .sk-value.bad  { color: var(--num-bad); }
.soft-kpi .sk-value.ok   { color: var(--num-good); }
.soft-kpi .sk-value.warn { color: var(--num-warn); }
.soft-kpi .sk-value.flat { color: var(--ink-1); }
.soft-kpi .sk-delta {
    font-size: 11px; font-weight: 700;
    padding: 2px 8px; border-radius: 999px;
    display: inline-flex; align-items: center;
}
.soft-kpi .sk-delta.up   { background: rgba(26,174,142,0.16); color: #137a52; }
.soft-kpi .sk-delta.down { background: rgba(229,72,77,0.18);  color: #a1262b; }
.soft-kpi .sk-delta.flat { background: rgba(136,146,160,0.16); color: var(--ink-3); }
.soft-kpi .sk-target {
    font-size: 11px; color: var(--ink-3); margin-top: 10px;
}
.soft-kpi .sk-target strong { color: var(--ink-1); }
.soft-kpi .sk-averages {
    margin-top: 12px; padding-top: 10px;
    border-top: 1px dashed rgba(15,23,42,0.10);
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px;
}
.soft-kpi .sk-avg-label { font-size: 10px; color: var(--ink-3); }
.soft-kpi .sk-avg-value { font-size: 12px; font-weight: 700; color: var(--ink-1); }
.soft-kpi .sk-sub { font-size: 11px; color: var(--ink-3); margin-top: 8px; }

/* ════════════════════════════════════════════════════════════════════════════
   Global hover transitions
   ════════════════════════════════════════════════════════════════════════════
   Every interactive surface gets a smooth fade in/out instead of a snap. The
   transition properties are deliberately narrow (no `all`) so we don't end
   up animating layout properties accidentally. */
.btn, .icon-btn, .filter-pill, .tab, .seg-item, .rail-item, .chip,
.list-row, .platform, .vc-action, .am-tool, .kpi {
    transition:
        background-color 0.22s ease,
        border-color     0.22s ease,
        color            0.22s ease,
        box-shadow       0.40s ease,
        transform        0.22s cubic-bezier(0.22, 1, 0.36, 1),
        opacity          0.18s ease;
}

/* Tabs in the sub-nav row — when the active tab swaps (clicking between
   pages on the same tool), the new tab's background, color, and shadow
   should slide in instead of snapping. Pinned with !important because the
   per-theme .tab rules in vibe-soft.css use higher specificity. */
.vibe-soft .tab-row .tab,
.tab-row .tab {
    transition:
        background-color 0.28s ease,
        color            0.22s ease,
        box-shadow       0.32s ease,
        transform        0.22s cubic-bezier(0.22, 1, 0.36, 1) !important;
}
.tab:active { transform: scale(0.97); }

/* TAB BADGE — the count beside a sub-nav tab (2026-08-21). Was an inline style
   on the span in tab_row.html, which meant it could only ever be one colour and
   the mobile layer could not touch it (the inline-style pitfall in CLAUDE.md).

   Two states, and the distinction is the point:
     plain      a static label from `Page.badge` — universal, quiet.
     .is-alert  a COUNT of things waiting for THIS person. Uses --bad, the
                token every other "needs you" signal in the platform uses, so
                the rail bell and a tab agree about what red means.

   `min-width` with centred text so 1 and 12 are the same shape and the tab
   doesn't change width as a queue drains. Never rendered at 0 — the macro
   omits it — so there is no "0" badge to explain. */
.tab-badge {
    display: inline-flex; align-items: center; justify-content: center;
    /* box-sizing so `min-width` is the WHOLE badge. Without it the padding is
       added on top and a single digit rendered 28x18 — a stretched pill rather
       than a round one. Fourth time this no-global-reset trap has bitten today
       (the login card, `.ipt`, the 44px tap floor, this). */
    box-sizing: border-box;
    min-width: 18px; height: 18px; padding: 0 5px;
    margin-left: 4px; border-radius: 999px;
    font-size: 10px; font-weight: 700; line-height: 1;
    background: var(--bg-sunken); color: var(--ink-2);
}
.tab-badge.is-alert {
    background: var(--bad); color: #FFFFFF;
}
/* The active tab sits on a light pill, so the red needs no adjustment — but on
   a dark rail-adjacent surface the white text is what carries it. */
.theme-dark .tab-badge { background: rgba(255, 255, 255, 0.10); }
.theme-dark .tab-badge.is-alert { background: var(--bad); color: #FFFFFF; }

/* Clickable KPI — adds a corner expand glyph + cursor hint, slight lift on hover. */
.kpi-clickable { cursor: pointer; position: relative; }
.kpi-clickable:focus-visible {
    outline: 2px solid var(--brand, #00B2E3);
    outline-offset: 2px;
}
.kpi-clickable .kpi-expand {
    /* Higher specificity (0,2,0) so it beats any future .tilt-card > * rule
       and stays anchored top-right of the card. */
    position: absolute; top: 10px; right: 12px;
    width: 18px; height: 18px; border-radius: 6px;
    background: rgba(15,23,42,0.06); color: var(--ink-2);
    font-size: 14px; line-height: 16px; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
    transition: background-color 0.18s ease, color 0.18s ease;
    z-index: 2;  /* above the tilt highlight */
}
.kpi-clickable:hover .kpi-expand {
    background: var(--brand, #00B2E3); color: #fff;
}

/* ════════════════════════════════════════════════════════════════════════════
   Tilt + parallax glow — applied to every card surface
   ════════════════════════════════════════════════════════════════════════════
   AppleTV-style: the card rotates around its center to "follow" the cursor,
   with a radial highlight that tracks the mouse. Driven by two CSS custom
   properties (--tx, --ty in −1..1) that `static/js/app.js` writes from the
   pointer position.

   Selectors include every card surface: clickable KPIs (.tilt-card),
   glass cards (.vc), all KPI variants, tier cards. An element can opt OUT
   by adding `data-no-tilt` (used for the filter bar inside the summary
   page — it's a .vc but really a toolbar). */
.tilt-card,
.vc:not([data-no-tilt]),
.kpi.soft-kpi,
.cx-kpi,
.tier-card {
    --tx: 0;
    --ty: 0;
    --tilt-deg-x: 6;  /* per-axis lean, overwritten by JS so each edge moves */
    --tilt-deg-y: 6;  /* at most a few px regardless of card size */
    --tilt-glow-x: 50%;
    --tilt-glow-y: 50%;
    transform-style: preserve-3d;
    position: relative;
    /* will-change is intentionally NOT set here. Declaring it on all ~25 card
       surfaces permanently promotes each to its own GPU compositing layer; with
       the backdrop-filter blur on every card the compositor then re-blurs all
       of them every scroll frame — the cause of the choppy scrolling. We move
       the hint to :hover (below) so only the card under the cursor — the only
       one that can actually tilt — gets promoted. Visually identical, but at
       most one or two layers exist at a time instead of two dozen. */
    isolation: isolate;
    transition:
        box-shadow 0.40s ease,
        transform  0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Hover lift — slightly stronger shadow + a hint of color tint. `will-change`
   pre-warms the layer for the tilt the instant the pointer enters (hover always
   precedes the JS-driven .is-tilting), so the tilt stays glassy-smooth. */
.tilt-card:hover,
.vc:not([data-no-tilt]):hover,
.kpi.soft-kpi:hover,
.cx-kpi:hover,
.tier-card:hover {
    will-change: transform;
    box-shadow:
        0 18px 44px -16px rgba(0, 178, 227, 0.32),
        0 8px 22px -8px rgba(15, 23, 42, 0.22);
}

/* Tilt state — applied by JS via `.is-tilting` while the pointer moves over a
   `.tilt-card`.

   ROOT-CAUSE FIX (2026-06-29): what reads as "motion" is how far a card's EDGE
   travels = (dimension / 2) · sin(angle), so a FIXED angle moves a big card's
   edges much further than a small card's (a tall table looked like it was
   buckling). Earlier attempts fixed the ANGLE (8°, then a per-context 1.5°
   override, then size-scaled) — all wrong, because none bounded the actual
   displacement. Now JS solves the angle PER AXIS so every edge moves at most
   ~6px, capped (see static/js/app.js TILT_DISP_PX / TILT_MAX_DEG) and writes
   `--tilt-deg-x` (height-axis → rotateX) and `--tilt-deg-y` (width-axis →
   rotateY). So every card moves the same small amount and big cards barely
   rotate. One rule, all cards, all pages. */
.tilt-card.is-tilting {
    transition:
        box-shadow 0.40s ease,
        transform 0.28s ease-out;
    transform:
        perspective(1000px)
        rotateX(calc(var(--ty) * var(--tilt-deg-x, 6) * -1deg))
        rotateY(calc(var(--tx) * var(--tilt-deg-y, 6) *  1deg))
        translateZ(0);
}
.vc.is-tilting:not(.kpi):not(.cx-kpi):not(.tier-card):not(.tilt-card) {
    transition:
        box-shadow 0.40s ease,
        transform 0.32s ease-out;
    transform:
        perspective(1200px)
        rotateX(calc(var(--ty) * -3deg))
        rotateY(calc(var(--tx) *  3deg))
        translateZ(0);
}

/* Parallax glow — soft white highlight that tracks the cursor.
   z-index: 0 keeps it behind content (which paints at the default auto level
   inside the stacking context created by isolation: isolate). */
.tilt-card::before,
.vc:not([data-no-tilt])::before,
.kpi.soft-kpi::before,
.cx-kpi::before,
.tier-card::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    background:
        radial-gradient(
            280px circle at var(--tilt-glow-x) var(--tilt-glow-y),
            rgba(255, 255, 255, 0.48),
            rgba(255, 255, 255, 0.10) 30%,
            transparent 60%
        );
    mix-blend-mode: overlay;
    opacity: 0;
    transition: opacity 0.32s ease;
    z-index: 0;
}
.tilt-card:hover::before,
.vc:not([data-no-tilt]):hover::before,
.kpi.soft-kpi:hover::before,
.cx-kpi:hover::before,
.tier-card:hover::before { opacity: 1; }

/* Respect reduced-motion preferences — disable the tilt + glow entirely. */
@media (prefers-reduced-motion: reduce) {
    .tilt-card, .vc, .kpi.soft-kpi, .cx-kpi, .tier-card,
    .tilt-card.is-tilting, .vc.is-tilting, .kpi.is-tilting,
    .cx-kpi.is-tilting, .tier-card.is-tilting { transform: none !important; }
    .tilt-card::before, .vc::before, .kpi.soft-kpi::before,
    .cx-kpi::before, .tier-card::before { display: none; }
    .btn, .icon-btn, .filter-pill, .tab, .seg-item, .rail-item, .chip,
    .list-row, .platform, .vc-action, .am-tool, .kpi {
        transition: none;
    }
}

/* ════════════════════════════════════════════════════════════════════════════
   KPI drilldown modal — design-system styling + fixed-height scrollable body
   ════════════════════════════════════════════════════════════════════════════
   Phase 9 polish: the modal shouldn't grow with row count. Cap body at
   60vh and scroll within. The sticky thead keeps column headers visible. */
.add-tool-modal-overlay#modal-kpi-drilldown .add-tool-modal {
    max-height: 80vh;
    display: flex; flex-direction: column;
}
.add-tool-modal-overlay#modal-kpi-drilldown .am-body {
    flex: 1; min-height: 0;
    max-height: 60vh;
    overflow-y: auto;
    padding: 0;  /* table handles its own padding */
    /* Same scrollbar treatment as .v1-main */
    scrollbar-width: thin;
}
.add-tool-modal-overlay#modal-kpi-drilldown .am-body::-webkit-scrollbar { width: 8px; }
.add-tool-modal-overlay#modal-kpi-drilldown .am-body::-webkit-scrollbar-thumb {
    background: rgba(15, 23, 42, 0.10); border-radius: 999px;
}

/* Generic drilldown modal body — fills the modal frame when JS swaps content in. */
.kpi-drilldown-body table { width: 100%; border-collapse: collapse; font-size: 13px; }
.kpi-drilldown-body th, .kpi-drilldown-body td {
    padding: 10px 14px; border-bottom: 1px solid rgba(15,23,42,0.06);
    text-align: left; vertical-align: top;
}
.kpi-drilldown-body th {
    /* Opaque surface token, not a 3%-alpha tint — this header is position:sticky,
       so a translucent fill lets scrolled rows bleed through it. */
    background: var(--bg-sunken); font-size: 11px;
    text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-3); font-weight: 700;
    position: sticky; top: 0; z-index: 1;
}
.kpi-drilldown-meta { font-size: 12px; color: var(--ink-3); margin-bottom: 14px; }
.kpi-drilldown-body tbody tr:hover { background: rgba(15, 23, 42, 0.025); }

/* Needs Attention drilldown — reason pills + manual dismissals.
   Dismissed rows are struck through and dimmed at the bottom of the list;
   chips are atomic inline boxes so the line-through skips them (deliberate —
   opacity carries the state there). */
.kpi-drilldown-body .na-pills { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.kpi-drilldown-body .na-why-cell { text-align: left; }
.kpi-drilldown-body tr.na-dismissed td { text-decoration: line-through; opacity: 0.55; }
.kpi-drilldown-body .na-dismissed-tag {
    margin-left: 8px; font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-3);
}
.kpi-drilldown-body .na-action-col { width: 1%; }
.kpi-drilldown-body .na-action-cell { text-align: right; }
.kpi-drilldown-body .na-toggle-btn { height: 24px; padding: 0 10px; font-size: 11px; }
.kpi-drilldown-body tr.na-dismissed .na-action-cell { text-decoration: none; opacity: 1; }

/* ════════════════════════════════════════════════════════════════════════════
   Report "expand fully" — lifts any .vc report card into a near-fullscreen
   lightbox (annotation: "in all reports, an option to expand fully"). Reuses
   the .add-tool-modal-overlay scrim; app.js MOVES the live card node in (not a
   clone) so its ECharts canvas + ResizeObserver come along and the chart
   auto-resizes to the larger frame.
   ════════════════════════════════════════════════════════════════════════════ */
.card-expand-btn { color: var(--ink-3); }
.card-expand-btn:hover { color: var(--brand); }

.report-expand-overlay {
    position: fixed; inset: 0;
    background: rgba(11, 18, 27, 0.55);   /* same scrim as .add-tool-modal-overlay */
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    z-index: 120;                          /* above the add-tool / drilldown modals (100) */
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
}
.report-expand-overlay[hidden] { display: none; }

/* Sizing frame the live card is moved into; the card keeps its own .vc glass.
   Must be wider than the in-page card (≈90% of content width) or "expand"
   reads as shrinking — so use most of the viewport, capped on huge monitors. */
.report-expand-host {
    width: min(1700px, 94vw);
    max-height: calc(100vh - 48px);
    overflow-x: hidden;   /* reports must never scroll horizontally */
    overflow-y: auto;
    border-radius: var(--r-4);
}
/* While expanded, fill the frame and drop the summary page's internal height
   caps so the whole report shows. */
/* The card is MOVED out of the .vibe-soft pane into this body-level overlay, so
   it loses every .vibe-soft .vc rule and computes to fully transparent — that
   was the "transparent tables" bug (table text floating on the dark scrim).
   Re-skin it as a solid, opaque panel: glass is for frosting the light page;
   over the dark scrim the card must be opaque to read. Tokens only. */
.report-expand-host .vc {
    width: 100%;
    box-sizing: border-box;   /* keep padding + border INSIDE 100% so the card
                                 can't overflow the host (caused the h-scroll) */
    background: var(--bg-canvas);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 1px solid var(--hairline);
    border-radius: var(--r-4);
    box-shadow: 0 24px 64px rgba(15, 23, 42, 0.28);
    padding: 22px;
}
/* Header layout/typography also live on .vibe-soft — restore them on the
   detached card so the title/eyebrow/subtitle don't fall back to defaults. */
.report-expand-host .vc-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px; margin-bottom: 16px;
}
.report-expand-host .vc-title { font-size: 15px; font-weight: 600; color: var(--ink-1); }
.report-expand-host .vc-caption { font-size: 12px; color: var(--ink-2); margin-top: 4px; }
.report-expand-host .vc-eyebrow {
    font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--ink-3);
}
/* In the lightbox the whole report flows — inner scroll regions are disabled
   (no inner vertical OR horizontal scrollbars); the host scrolls vertically. */
.report-expand-host .vc [style*="max-height"] { max-height: none !important; overflow: visible !important; }
.report-expand-host .vc .chart-rows-scroll { max-height: none; overflow: visible; }
/* Charts fill the taller frame too, so "expand" gains height as well as width. */
.report-expand-host .sb-chart { min-height: min(560px, 64vh); }
/* No 3D tilt while centered in the lightbox — it would look broken. */
.report-expand-host .vc.is-tilting { transform: none !important; }

/* Close — a solid dark pill so it reads clearly on BOTH the white panel and the
   dark scrim (the old white icon-btn vanished against the panel's corner). */
.report-expand-close {
    position: absolute; top: 20px; right: 24px; z-index: 2;
    width: 40px; height: 40px; border-radius: 999px;
    background: rgba(15, 23, 42, 0.78);
    color: var(--bg-canvas);
    border: 1px solid rgba(255, 255, 255, 0.18);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
}
.report-expand-close:hover { background: rgba(15, 23, 42, 0.92); }
.report-expand-close svg { width: 18px; height: 18px; }

/* ════════════════════════════════════════════════════════════════════════════
   ChartCard — visual + scrollable raw-data contract
   ════════════════════════════════════════════════════════════════════════════ */
.chart-card .vc-subtitle {
    font-size: 11px; color: var(--ink-3); margin-top: 4px;
}
.chart-card .chart-visual { padding: 4px 0 10px; }
.chart-card .chart-rows {
    margin-top: 10px;
    border-top: 1px dashed rgba(15, 23, 42, 0.08);
}
.chart-card .chart-rows > summary {
    cursor: pointer; list-style: none; padding: 10px 4px 8px;
    display: flex; align-items: center;
    transition: background-color 0.2s ease;
}
.chart-card .chart-rows > summary::-webkit-details-marker { display: none; }
.chart-card .chart-rows > summary::before {
    content: "▸"; display: inline-block; margin-right: 8px;
    color: var(--ink-3); font-size: 10px;
    transition: transform 0.2s ease;
}
.chart-card .chart-rows[open] > summary::before { transform: rotate(90deg); }
.chart-card .chart-rows-scroll { overflow-y: auto; padding: 4px 0 4px; }
.chart-card .chart-rows-tbl { width: 100%; border-collapse: collapse; font-size: 12px; }
.chart-card .chart-rows-tbl th, .chart-card .chart-rows-tbl td {
    padding: 6px 10px; text-align: left;
    border-bottom: 1px solid rgba(15, 23, 42, 0.04);
}
.chart-card .chart-rows-tbl th {
    /* Opaque surface token, not a 3%-alpha tint — this header is position:sticky,
       so a translucent fill lets scrolled rows bleed through it. */
    background: var(--bg-sunken); font-size: 10px;
    text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-3); font-weight: 700;
    position: sticky; top: 0;
}
.chart-card .chart-rows-tbl td:nth-child(n+2) {
    text-align: right; font-variant-numeric: tabular-nums;
}
.chart-card .chart-row-swatch {
    display: inline-block; width: 10px; height: 10px;
    border-radius: 3px; margin-right: 6px;
    vertical-align: middle;
}

/* HubSpot link in the Name column — plain bold text, cyan on hover.
   No underline (per 2026-05-31 design pass). */
.kpi-drilldown-body .hs-link {
    color: var(--ink-1); font-weight: 600; text-decoration: none;
    transition: color 0.18s ease;
}
.kpi-drilldown-body .hs-link:hover { color: #00B2E3; }

/* Status pill in the Status column — translucent fill, matching outline. */
.kpi-drilldown-body .status-pill {
    display: inline-block; padding: 3px 10px; border-radius: 999px;
    font-size: 10px; font-weight: 700; letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* Sortable column headers — show an arrow when the column is the current sort. */
.sortable-table th { cursor: pointer; user-select: none; }
.sortable-table th[data-sort-col]::after {
    content: "";
    display: inline-block; margin-left: 6px;
    width: 0; height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    opacity: 0;
    transition: opacity 0.15s, transform 0.15s;
}
.sortable-table th[aria-sort="ascending"]::after {
    border-bottom: 5px solid var(--ink-2);
    opacity: 1;
}
.sortable-table th[aria-sort="descending"]::after {
    border-top: 5px solid var(--ink-2);
    opacity: 1;
}
.sortable-table th[data-sort-col]:hover::after { opacity: 0.5; border-bottom: 5px solid var(--ink-3); }
.sortable-table th[aria-sort]:hover::after { opacity: 1; }

/* CX KPI — tinted gradient strip across the top, large status-colored value. */
.cx-kpi { padding: 16px; border-radius: 14px;
    border: 1px solid var(--hairline);
    background: var(--bg-canvas);
    position: relative; overflow: hidden;
}
.cx-kpi.tone-info    { background: linear-gradient(to bottom, rgba(0,178,227,0.05), var(--bg-canvas) 40%); }
.cx-kpi.tone-warn    { background: linear-gradient(to bottom, rgba(232,144,25,0.06), var(--bg-canvas) 40%); }
.cx-kpi.tone-success { background: linear-gradient(to bottom, rgba(26,174,142,0.06), var(--bg-canvas) 40%); }
.cx-kpi.tone-bad     { background: linear-gradient(to bottom, rgba(229,72,77,0.06), var(--bg-canvas) 40%); }
.cx-kpi .ck-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--ink-3); font-weight: 700;
}
.cx-kpi .ck-value {
    font-size: 28px; font-weight: 700;
    letter-spacing: -0.01em; line-height: 1.1;
    margin-top: 8px;
}
.cx-kpi.tone-info    .ck-value { color: var(--brand); }
.cx-kpi.tone-warn    .ck-value { color: var(--warn); }
.cx-kpi.tone-success .ck-value { color: var(--ok); }
.cx-kpi.tone-bad     .ck-value { color: var(--bad); }
.cx-kpi .ck-sub { font-size: 11px; color: var(--ink-3); margin-top: 6px; line-height: 1.4; }

/* Tier card — used in CX "Active Companies by Tier" row. */
.tier-card {
    padding: 16px; border-radius: 14px;
    border: 1px solid var(--hairline);
}
.tier-card .tc-tier {
    font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
}
.tier-card .tc-count {
    font-size: 32px; font-weight: 700;
    margin-top: 6px; color: var(--ink-1);
}
.tier-card .tc-arr { font-size: 11px; color: var(--ink-3); margin-top: 4px; }

/* Sales weekly KPI grid — keeps every card the same min width.
   Phase 9: auto-fit so the trailing orphan stretches to fill its row instead
   of sitting alone with empty whitespace next to it. */
.weekly-kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 14px;
}
.density-dense .weekly-kpi-grid { gap: 10px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.density-comfort .weekly-kpi-grid {
    gap: 12px;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

/* Calls queue row — fixed-grid list with workflow chip + status pill. */
.calls-row {
    display: grid;
    grid-template-columns: 1fr 160px 90px 110px 18px;
    gap: 14px; align-items: center;
    padding: 14px 4px;
    border-bottom: 1px solid var(--hairline);
}
.calls-row:last-child { border-bottom: 0; }
.calls-row .topic-title {
    font-size: 13px; font-weight: 600; color: var(--ink-1);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.calls-row .topic-meta {
    font-size: 11px; color: var(--ink-3); margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.calls-row .reviewer { font-size: 11px; color: var(--ink-1); }
.calls-row .reviewer.unassigned { color: var(--ink-3); font-style: italic; }
.calls-row .workflow-chip {
    height: 22px; padding: 0 8px; border-radius: 999px;
    font-size: 11px; font-weight: 600;
    display: inline-flex; align-items: center;
    border: 1px solid transparent;
    text-transform: capitalize;
}
/* Colors follow the trend chart's workflow mapping (routes._summary_specs):
   demo = tool orange, training = plum, other = muted ink (not a coached
   workflow) — so orange never means two things. */
.calls-row .workflow-chip.demo     { background: rgba(232,144,25,0.10); color: #A05E0E; }
.calls-row .workflow-chip.training { background: var(--accent-plum-soft); color: var(--accent-plum); }
.calls-row .workflow-chip.other    { background: rgba(136,146,160,0.14); color: var(--ink-2); }
/* No Show — the call didn't happen. Muted rose (soft --bad hue), distinct from
   the neutral "other" grey but not alarming delete-red. Label reads "No Show". */
.calls-row .workflow-chip.no_show  { background: rgba(177,74,77,0.08); color: #9a4a4d; border-color: rgba(177,74,77,0.20); text-transform: none; }
/* Per-row coaching score badge (demos/trainings only). Tone cut matches the
   run-page score numeral: ok >=8, bad <=4, else neutral warn. */
.calls-row .calls-score-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 30px; height: 22px; padding: 0 7px; border-radius: 6px;
    font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
    background: rgba(136,146,160,0.16); color: var(--ink-2);
}
.calls-row .calls-score-badge.ok  { background: var(--ok-soft); color: var(--ok); }
.calls-row .calls-score-badge.bad { background: var(--bad-soft); color: var(--bad); }
/* Count chip in a queue box header. */
.cq-box-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 20px; height: 18px; padding: 0 6px; border-radius: 999px;
    background: rgba(136,146,160,0.16); color: var(--ink-2);
    font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums;
}
/* AI-suggested reclassification — dashed = tentative (same convention as the
   CX untracked-module pills), muted so it never competes with the filed type. */
.calls-row .workflow-chip.suggest  {
    background: transparent; color: var(--ink-3);
    border: 1px dashed rgba(136,146,160,0.55);
}
.density-dense .calls-row { padding: 10px 4px; }

/* Tiny uppercase column caption (e.g. the "Reviewer" label above a queue
   row's assignee, and the Demos/Trainings section headers). */
.calls-col-label {
    font-size: 9px; font-weight: 700; letter-spacing: 0.12em;
    text-transform: uppercase; color: var(--ink-3); margin-bottom: 2px;
}
/* Divider inside a queue section separating the active calls from the
   reviewed/completed ones below. Reuses the .calls-row hover tint. */
.calls-subhead {
    display: flex; align-items: center; gap: 8px;
    padding: 9px 22px;
    background: rgba(15, 23, 42, 0.025);
    border-bottom: 1px solid var(--hairline);
}
/* Numbered "what to do here" flow on /calls/queue. */
.calls-howto { margin-bottom: 18px; }
.calls-steps { margin: 4px 0 0; padding-left: 20px; display: grid; gap: 8px; }
.calls-steps li { font-size: 13px; line-height: 1.5; color: var(--ink-2); }
.calls-steps li b { color: var(--ink-1); font-weight: 600; }

/* Per-row pipeline-stage indicator on /calls/queue (left of each row). */
.calls-stage {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 11px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap;
}
.calls-stage .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: currentColor; }
.calls-stage.stage-wait  { color: var(--ink-3); }
.calls-stage.stage-run   { color: var(--num-warn); }
.calls-stage.stage-ready { color: var(--brand); }
.calls-stage.stage-done  { color: var(--ok); }
.calls-stage.stage-fail  { color: var(--bad); }
.calls-stage.stage-run .dot { animation: calls-stage-pulse 1.2s ease-in-out infinite; }
@keyframes calls-stage-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }

/* Side-by-side BDR/AE table row in sales handoff section — sticks to .tbl
   but the deal id needs monospace. */
.tbl td.mono { font-family: ui-monospace, Menlo, monospace; font-size: 12px; }


/* ════════════════════════════════════════════════════════════════════════════
   /sales/individual — per-rep performance cards (2026-06-10)
   ════════════════════════════════════════════════════════════════════════════ */

/* Rep cards grid. */
.rep-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
    gap: 14px;
    margin-bottom: 18px;
}
@media (max-width: 560px) {
    .rep-card-grid { grid-template-columns: minmax(0, 1fr); }
}
.rep-card { padding: 18px 20px 16px; }

.rep-card-head {
    display: flex; align-items: center; gap: 12px;
    margin-bottom: 6px;
}
.rep-avatar {
    width: 42px; height: 42px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 14px; font-weight: 800; letter-spacing: 0.02em;
    color: #fff;
    background: linear-gradient(135deg, var(--brand), var(--brand-deep));
    flex: 0 0 auto;
}
.rep-name { font-size: 15px; font-weight: 750; color: var(--ink-1); }
.rep-meta { display: flex; align-items: center; gap: 6px; margin-top: 3px; }

/* Goal-attainment ring — conic-gradient donut, % centered inside. Replaces
   the corner text the user flagged as easy to miss. --pct (0–100) drives the
   sweep; tone-* sets the ring + number color. */
.rep-attain-ring {
    --pct: 0;
    --ring-color: var(--neutral);
    position: relative;
    width: 66px; height: 66px;
    margin-left: auto;
    flex: 0 0 auto;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    border-radius: 50%;
}
.rep-attain-ring::before {
    content: "";
    position: absolute; inset: 0;
    border-radius: 50%;
    background: conic-gradient(var(--ring-color) calc(var(--pct) * 1%),
                               rgba(15, 23, 42, 0.08) 0);
    -webkit-mask: radial-gradient(closest-side,
                  transparent calc(100% - 6.5px), #000 calc(100% - 5.5px));
            mask: radial-gradient(closest-side,
                  transparent calc(100% - 6.5px), #000 calc(100% - 5.5px));
    filter: saturate(1.4);  /* counter the body desaturation — status color */
}
.theme-dark .rep-attain-ring::before {
    background: conic-gradient(var(--ring-color) calc(var(--pct) * 1%),
                               rgba(255, 255, 255, 0.12) 0);
}
.rep-attain-ring .pct {
    font-size: 15px; font-weight: 800;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    color: var(--ring-color);
    filter: saturate(1.4);
}
.rep-attain-ring .sub {
    font-size: 8px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.07em;
    color: var(--ink-3);
}
.rep-attain-ring.tone-ok   { --ring-color: var(--ok); }
.rep-attain-ring.tone-warn { --ring-color: var(--warn); }
.rep-attain-ring.tone-bad  { --ring-color: var(--bad); }

.rep-section-label {
    font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-3);
    margin: 12px 0 4px;
}

/* Goal row — label + value/goal + progress bar. Clickable rows open the
   shared KPI drilldown modal (data-kpi-drilldown, handled by app.js). */
.goal-row {
    padding: 7px 8px 8px;
    margin: 0 -8px;
    border-radius: 10px;
}
.goal-row.clickable { cursor: pointer; }
/* Neutral row hover (was a brand-blue tint, removed per request — matches the
   other table/report row hovers). */
.goal-row.clickable:hover { background: rgba(15, 23, 42, 0.025); }
.goal-row-top {
    display: flex; align-items: baseline; justify-content: space-between;
    font-size: 12.5px; color: var(--ink-2);
}
.goal-row-top .goal-nums {
    font-variant-numeric: tabular-nums;
    color: var(--ink-3); font-size: 12px;
}
.goal-row-top .goal-nums strong {
    color: var(--ink-1); font-size: 13px; font-weight: 750;
}
.goal-bar {
    height: 6px; border-radius: 4px;
    background: rgba(15, 23, 42, 0.07);
    margin-top: 6px; overflow: hidden;
}
.theme-dark .goal-bar { background: rgba(255, 255, 255, 0.10); }
.goal-bar-fill {
    height: 100%; border-radius: 4px;
    background: var(--neutral);
    transition: width 0.4s ease;
}
.goal-bar-fill.tone-ok   { background: var(--ok); }
.goal-bar-fill.tone-warn { background: var(--warn); }
.goal-bar-fill.tone-bad  { background: var(--bad); }

/* Stat chips — secondary metrics without goals. */
.rep-chip-row {
    display: flex; flex-wrap: wrap; gap: 7px;
    margin: 7px 0 2px;
}
.stat-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 10px;
    border-radius: 999px;
    border: 1px solid rgba(15, 23, 42, 0.08);
    background: rgba(255, 255, 255, 0.45);
    font-size: 11.5px;
}
.theme-dark .stat-chip {
    border-color: rgba(255, 255, 255, 0.10);
    background: rgba(255, 255, 255, 0.05);
}
.stat-chip.clickable { cursor: pointer; }
/* Neutral pill hover (was a brand-blue tint/border, removed per request —
   matches the filter-pill / button hover surface). */
.stat-chip.clickable:hover { background: var(--bg-sunken); }
.stat-chip .sc-label { color: var(--ink-3); font-weight: 600; }
.stat-chip .sc-value {
    color: var(--ink-1); font-weight: 750;
    font-variant-numeric: tabular-nums;
}
.stat-chip .sc-value.tone-ok   { color: var(--ok); }
.stat-chip .sc-value.tone-warn { color: var(--warn); }
.stat-chip .sc-value.tone-bad  { color: var(--bad); }


/* ════════════════════════════════════════════════════════════════════════════
   Filter-scope affordance (2026-06-12, annotation #5)
   ════════════════════════════════════════════════════════════════════════════
   Problem: the Rep / Range pills only drive SOME sections (KPI grid + top
   row on Summary; everything on Individual) and users couldn't tell which.
   Two layers:
     1. A persistent `.scope-chip` pill in section eyebrows / card actions
        naming the filters that apply ("Rep · Range") — or `.locked` for
        sections pinned to this-week / full-team.
     2. Hovering the pill bar lights up every [data-filter-scope] section
        and dims [data-filter-locked] ones (CSS :has(), hover-capable
        devices only — the chips carry the message on touch). */
.scope-chip {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 9.5px; font-weight: 750;
    text-transform: uppercase; letter-spacing: 0.06em;
    background: var(--brand-soft);
    color: var(--brand-deep);
    border: 1px solid var(--brand-line);
    white-space: nowrap;
    vertical-align: middle;
}
.scope-chip::before {
    content: ""; width: 5px; height: 5px; border-radius: 50%;
    background: currentColor; opacity: 0.8;
}
.scope-chip.locked {
    background: rgba(15, 23, 42, 0.04);
    color: var(--ink-3);
    border-color: rgba(15, 23, 42, 0.10);
}
.theme-dark .scope-chip.locked {
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(255, 255, 255, 0.10);
}

/* Filter zone — a persistent panel that visually encloses every section the
   Rep / Range pills drive, so it's always clear (not just on hover) which
   reports the filters touch. The fixed all-time reports sit OUTSIDE the zone,
   each carrying a grey `.scope-chip.locked` label. Replaces the earlier
   hover-only glow, which vanished on mouse-out and read as flaky. */
.filter-zone {
    background: rgba(61, 106, 140, 0.045);
    border: 1px solid var(--brand-line);
    border-radius: 20px;
    padding: 2px 16px 16px;
    margin-bottom: 24px;
}
.theme-dark .filter-zone {
    background: rgba(108, 160, 196, 0.07);
}
.filter-zone-head {
    display: flex; align-items: center; gap: 10px;
    padding: 12px 4px 6px;
}
.filter-zone-head .fz-hint {
    font-size: 11.5px; color: var(--ink-3); font-weight: 500;
}
/* The section eyebrows inside the zone lose their top margin — the zone
   header already provides the breathing room. */
.filter-zone .mockup-section-eyebrow:first-of-type { margin-top: 2px; }

/* ════════════════════════════════════════════════════════════════════════
   CX Reports — pinned filter bar ("Filters", 2026-07-16): ONE page-wide
   Audience pill + the trends Group-by, together in a sticky capsule that
   sits between Escalations and the KPI grid and pins to the top of the
   scroll area (#app-main) as the user scrolls — both always reachable. The
   Range pill scopes only the KPI tiles, so it sits with them and scrolls
   away. The bar lives OUTSIDE both swap regions so the in-place swaps never
   tear it down.
   ════════════════════════════════════════════════════════════════════════ */
/* Full-bleed machinery for the sticky filter strips: each bar overshoots
   its box by (100vw - 100%)/2 per side — more than any gutter can ever be
   — and the scroll area clips it at ITS edges, so the band is exactly as
   wide as the main column at EVERY viewport width. Merely cancelling the
   .page-wrap padding wasn't enough: .page-wrap is max-width:1600px and
   centered, so wide screens (1920+) still showed 90px+ gutters either
   side (Tanveer 2026-07-22: "definitely not full width"). The matching
   positive padding puts the controls back on the page grid.
   overflow-x was already hidden ≤900 (responsive.css); nothing may rely
   on horizontal page overflow — that would have meant a sideways
   scrollbar all along. */
.v1-main { overflow-x: hidden; }

/* TWO STATES (Tanveer 2026-07-22, fourth pass): at REST the bar is the
   normal glass capsule — same look as /sales/summary's filter bar, sitting
   on the page grid like every other element. Once ACTUALLY PINNED
   (.is-stuck — app.js sentinel, same trick as the sub-nav; CSS has no
   :stuck) it becomes the full-bleed opaque toolbar strip above. Only
   inline margins/padding + surface change between states, so the pin
   never shifts vertical layout. */
.cx-reports-groupbar {
    position: sticky;
    /* Pins FLUSH beneath the stuck tool sub-nav (bottom edge 63px on
       desktop) — 62px overlaps its border by 1px so no strip of raw page
       content peeks through the seam. Mobile override in responsive.css. */
    top: 62px;
    z-index: 30;                         /* above cards/charts, below modals (100) */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px 28px;
    padding: 12px 14px;
    margin: 4px 0 12px;
    /* Same liquid-glass capsule recipe as .filter-bar / sales summary. */
    background: rgba(255, 255, 255, 0.42);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    backdrop-filter: blur(20px) saturate(140%);
    border: 1px solid rgba(15, 23, 42, 0.05);
    border-radius: 14px;
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.04);
    transition: margin 0.25s ease, padding 0.25s ease, background 0.2s ease,
                border-radius 0.25s ease, border-color 0.2s ease,
                box-shadow 0.2s ease;
}
.theme-dark .cx-reports-groupbar {
    background: rgba(20, 20, 28, 0.45);
    border-color: rgba(255, 255, 255, 0.04);
}
/* CX overview — the Type/Range filter bar pins beneath the stuck tool sub-nav
   (Tanveer 2026-07-08: "top bar should be sticky"). At rest it's just the
   .filter-bar capsule; this adds the pinning + the stuck-state transition.
   Double-class so the stuck rules below can outrank
   `.density-comfort .filter-bar` (0,2,0). */
.filter-bar.cx-overview-filterbar {
    position: sticky;
    top: 62px;                           /* flush under the stuck sub-nav */
    z-index: 30;                         /* above cards/charts, below modals (100) */
    transition: margin 0.25s ease, padding 0.25s ease, background 0.2s ease,
                border-radius 0.25s ease, border-color 0.2s ease,
                box-shadow 0.2s ease;
}
/* PINNED state — the full-bleed toolbar strip. Each bar overshoots its box
   by (100vw - 100%)/2 per side (more than any gutter can ever be —
   .page-wrap is max-width:1600px and centered, so wide screens have real
   gutters) and .v1-main clips it at ITS edges, so the band spans exactly
   the main column at every viewport width. The matching positive padding
   keeps the controls on the page grid. Surface = the SAME pinned-chrome
   glass as the stuck sub-nav (--pinned-glass-*, defined by the sub-nav
   block above) — Tanveer 2026-07-22: "make both of them the same type of
   glassy". Border goes transparent (not 0-width — height must not change
   mid-pin); the bottom hairline rides a box-shadow instead. */
.cx-reports-groupbar.is-stuck,
.filter-bar.cx-overview-filterbar.is-stuck {
    margin-left: calc((100% - 100vw) / 2);
    margin-right: calc((100% - 100vw) / 2);
    padding-left: calc((100vw - 100%) / 2);
    padding-right: calc((100vw - 100%) / 2);
    background: var(--pinned-glass-bg);
    -webkit-backdrop-filter: var(--pinned-glass-filter);
    backdrop-filter: var(--pinned-glass-filter);
    border-color: transparent;
    border-radius: 0;
    box-shadow: 0 1px 0 var(--hairline);
}
.theme-dark .cx-reports-groupbar.is-stuck,
.theme-dark .filter-bar.cx-overview-filterbar.is-stuck {
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06);
}
.cx-reports-groupbar .cx-groupbar-title {
    font-size: 11px; font-weight: 800; letter-spacing: 0.08em;
    /* ink-1 on the bright bar — part of the 2026-07-22 readability pass. */
    text-transform: uppercase; color: var(--ink-1);
}
/* Group-by + Audience pills sit together on the right of the sticky bar. */
.cx-reports-groupbar .cx-groupbar-controls {
    display: flex; align-items: center; flex-wrap: wrap; gap: 10px 20px;
}
/* Small heading above a chart when a card holds more than one (e.g. Stalled). */
.cx-subchart-label {
    font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--ink-2); margin: 2px 0 6px;
}

/* CX overview — the Health Tier Distribution card holds two views side by side:
   the stacked tier-mix "line" (left) + the score-range bars (right), both built
   from the same counts. Flex so they sit side by side on a wide card and wrap to
   stacked on narrow widths. */
.cx-tier-split { display: flex; flex-wrap: wrap; gap: 12px 28px; align-items: center; }
.cx-tier-split-col { min-width: 0; }
/* The stacked "tier mix" bar is horizontal — give it the extra width. The
   score-range vertical bars are capped so the four bars stay at a natural
   density instead of being stretched across half the card (which left big gaps
   between them). Both wrap to full width on narrow screens. */
.cx-tier-split-col.is-mix  { flex: 1 1 340px; }
.cx-tier-split-col.is-dist { flex: 0 1 440px; }

/* Concentration flag inside the module drilldown — amber note chip (matches the
   platform's warn tint), rendered only when the data layer flags a module as
   driven by a few accounts. */
.cx-drill-note {
    margin: -2px 0 14px;
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 12px;
    line-height: 1.45;
    background: rgba(232, 144, 25, 0.10);
    color: #8a5614;
    border: 1px solid rgba(232, 144, 25, 0.28);
}
.theme-dark .cx-drill-note { color: var(--num-warn); }

/* ── Help tip — a small "?" chip that reveals an explanatory bubble on hover
   or keyboard focus. Reusable via {{ help_tip("…") }}
   (templates/_components/help_tip.html). Pure CSS; keyboard-accessible through
   tabindex + :focus-visible. */
.help-tip {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* no global border-box reset in this codebase (see notes.css) — without
       this the 1px ring inflates the declared size by 2px (review 2026-07-09:
       "a lot smaller" + "not exactly in the middle") */
    box-sizing: border-box;
    width: 12px; height: 12px;
    margin-left: 4px;
    border-radius: 999px;
    border: 1px solid var(--ink-3);
    color: var(--ink-3);
    font-size: 9px; font-weight: 700; line-height: 1;
    /* Reset text styling inherited from callers (e.g. the uppercase, letter-
       spaced .cx-rail-label): letter-spacing adds trailing space that shoves
       the "?" off-center inside the circle. */
    letter-spacing: 0; text-transform: none; font-style: normal;
    text-indent: 0;
    vertical-align: middle;
    cursor: help;
    transition: color .15s ease, border-color .15s ease;
}
.help-tip:hover, .help-tip:focus-visible {
    color: var(--brand); border-color: var(--brand); outline: none;
}
.help-tip-bubble {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    width: max-content; max-width: 240px;
    padding: 8px 10px;
    background: #1f2430; color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.08);
    font-size: 11.5px; font-weight: 500; line-height: 1.45;
    text-align: left;
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(15, 23, 42, 0.28);
    opacity: 0; visibility: hidden;
    transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
    z-index: 60;
    pointer-events: none;
    white-space: normal;
}
.help-tip-bubble::after {   /* caret */
    content: ""; position: absolute; top: 100%; left: 50%;
    transform: translateX(-50%);
    border: 5px solid transparent; border-top-color: #1f2430;
}
.help-tip:hover .help-tip-bubble,
.help-tip:focus-visible .help-tip-bubble {
    opacity: 1; visibility: visible;
    transform: translateX(-50%) translateY(0);
}
/* Wrapper variant ({{ tip_wrap("…") }} in help_tip.html): the wrapped content
   itself — a module chip, a value — is the hover/focus target for the same
   bubble, with no "?" affordance added. */
.tip-wrap {
    position: relative;
    display: inline-flex;
    cursor: help;
}
.tip-wrap:focus-visible { outline: none; }
.tip-wrap:hover .help-tip-bubble,
.tip-wrap:focus-visible .help-tip-bubble {
    opacity: 1; visibility: visible;
    transform: translateX(-50%) translateY(0);
}
/* Flip DOWN when there's no room above (class set by app.js on hover/focus).
   The pinned sub-nav / filter capsules (z-index 30+) out-stack every glass
   card — backdrop-filter seals each card's stacking context, so a bubble
   opening upward from a card head parked under the sticky chrome hides
   behind it (2026-07-22 root-cause). A below-opening bubble can't collide
   with the chrome by construction; entrance slides down instead of up. */
.tip-below .help-tip-bubble {
    bottom: auto;
    top: calc(100% + 8px);
    transform: translateX(-50%) translateY(-4px);
}
.tip-below .help-tip-bubble::after {
    top: auto;
    bottom: 100%;
    border-top-color: transparent;
    border-bottom-color: #1f2430;
}
.help-tip.tip-below:hover .help-tip-bubble,
.help-tip.tip-below:focus-visible .help-tip-bubble,
.tip-wrap.tip-below:hover .help-tip-bubble,
.tip-wrap.tip-below:focus-visible .help-tip-bubble {
    transform: translateX(-50%) translateY(0);
}

/* A tip inside a card's actions slot anchors its bubble to the RIGHT.
   `.card-actions` is right-aligned by construction, so a 240px bubble centred
   on a tip that sits there always hangs ~120px past the page edge. It is
   `visibility: hidden` until hover, but a hidden absolutely-positioned element
   still extends the scrollable area — so the page gains that much silent
   horizontal scroll, which is exactly what responsive.css exists to prevent and
   what the screenshot harnesses flag as OVERFLOW. Found 2026-08-14 building
   Time Off (+86px on /timeoff/admin), but it was never specific to that tool.
   Right-anchoring is unconditionally correct here: the container's contents are
   flush to the right edge, so there is nothing to the right to open into. */
.card-actions .help-tip-bubble,
.card-actions .tip-wrap .help-tip-bubble {
    left: auto;
    right: 0;
    transform: translateY(4px);
}
.card-actions .help-tip:hover .help-tip-bubble,
.card-actions .help-tip:focus-visible .help-tip-bubble,
.card-actions .tip-wrap:hover .help-tip-bubble,
.card-actions .tip-wrap:focus-visible .help-tip-bubble {
    transform: translateY(0);
}
.card-actions .help-tip-bubble::after {
    left: auto;
    right: 10px;
    transform: none;
}

/* ── Form field — `.ipt` ──────────────────────────────────────────────────
   The class every modal, settings row and comment box in the platform puts on
   its inputs (34 uses across /sops, /account and /workspace/users) — and which
   until 2026-08-14 had NO definition anywhere. It came over from the smrtOPS
   mockup as a bare class name and its rule didn't. So every one of those fields
   was rendering as a raw browser control: default border, default font, and on
   textareas a drag handle that let you pull the box wider than its container
   (what Tanveer reported).

   Values match the platform's existing hand-rolled field style (the roster
   table on /sales/settings) so these look native rather than like a new idea:
   hairline border, 6px radius, brand focus ring.
   ------------------------------------------------------------------------ */
.ipt {
    box-sizing: border-box;      /* no global reset in this codebase — without
                                    this, padding + border overflow the row */
    width: 100%;
    padding: 7px 9px;
    font: inherit;
    font-size: 13px;
    border: 1px solid var(--hairline);
    border-radius: 6px;
    background: var(--bg-canvas);
    color: var(--ink-1);
}
.ipt:focus {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 2px var(--brand-soft);
}
.ipt::placeholder { color: var(--ink-3); }
.ipt:disabled { opacity: .6; cursor: not-allowed; }

/* A textarea must never be draggable. `resize` is the reported bug — the handle
   pulls the box past the edge of whatever contains it, and nothing about these
   boxes wants a hand-tuned size. `field-sizing: content` grows them to what
   you've typed instead (same mechanism the SOPs editor uses), capped so a long
   paste scrolls rather than pushing the buttons off screen. Browsers without
   field-sizing fall back to the `rows` attribute, which is still correct. */
textarea.ipt {
    resize: none;
    field-sizing: content;
    min-height: 2.9em;
    max-height: 40vh;
    overflow-y: auto;
    line-height: 1.5;
}

/* A select sizes to its CONTENT, not to its container. `width:100%` on a select
   in a tight box (a table cell, a filter strip) collapses it to whatever the
   column gave it — that is how the /workspace/users Role select ended up 30px
   wide showing "use" instead of "user", which makes the control lie about its
   own value. `min-width: fit-content` does NOT rescue it: Chrome accepts the
   value and still renders 30px. `width: auto` is the only thing that works, and
   it is what every select on the platform did before `.ipt` existed.
   Layouts that genuinely want a full-width select opt in — see
   `.sops-modal-grid select.ipt` in sops.css. */
select.ipt { width: auto; max-width: 100%; padding-right: 6px; }


/* ════════════════════════════════════════════════════════════════════════════
   sb-datepicker — an in-page date picker
   ════════════════════════════════════════════════════════════════════════════
   The browser's own `<input type="date">` popup is chrome: Safari and Chrome
   render it at the OS scale and it ignores page zoom, so on a 4K screen at 250%
   the page grows around a calendar that stays its original size (Tanveer,
   2026-08-20). Nothing here can reach that panel, so this replaces it — drawn
   in the page, in CSS pixels, scaling with zoom like everything else.

   Opt-in per field with `data-sb-datepicker`; the JS is inert without it.
   ------------------------------------------------------------------------- */
.sb-dp { display: contents; }

/* The native input stays in the DOM as the value holder — it is what the form
   posts and what every existing listener reads — but it is never seen, so its
   popup can never be summoned. `display: none` would take it out of the form on
   some browsers' reckoning; this keeps it submittable and unreachable. */
.sb-dp-native {
    position: absolute !important;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
    pointer-events: none;
}

.sb-dp-field { cursor: pointer; }
.sb-dp-field::placeholder { color: var(--ink-3); }

/* FIXED, and appended to <body>. Inside the request dialog the field lives in
   `.am-body`, which scrolls — an absolutely-positioned panel would be clipped
   by that scrollport. z-index sits above the modal (100) and below the Time Off
   toast (130), so a confirmation still lands on top. */
.sb-dp-panel {
    position: fixed; z-index: 125;
    width: 268px; padding: 10px;
    background: var(--bg-canvas);
    border: 1px solid var(--hairline);
    border-radius: var(--r-3);
    box-shadow: var(--shadow-lift);
    font-size: 13px;
    -webkit-user-select: none; user-select: none;
}

.sb-dp-head {
    display: flex; align-items: center; gap: 4px;
    margin-bottom: 6px;
}
.sb-dp-label {
    flex: 1; text-align: center;
    font-size: 13px; font-weight: 700; color: var(--ink-1);
}
.sb-dp-nav {
    width: 28px; height: 28px;
    border: 1px solid var(--hairline); border-radius: var(--r-2);
    background: transparent; cursor: pointer;
    font: inherit; font-size: 15px; line-height: 1; color: var(--ink-2);
}
.sb-dp-nav:hover { background: var(--brand-soft); color: var(--brand-deep); }

.sb-dp-dow,
.sb-dp-grid {
    display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 2px;
}
.sb-dp-dow > span {
    text-align: center; padding: 2px 0;
    font-size: 10px; font-weight: 700; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--ink-3);
}

.sb-dp-day {
    height: 30px; padding: 0;
    border: 0; border-radius: var(--r-2);
    background: transparent; cursor: pointer;
    font: inherit; font-size: 12.5px; color: var(--ink-1);
    font-variant-numeric: tabular-nums;
}
.sb-dp-day:hover:not(:disabled) { background: var(--brand-soft); }
/* Days from the neighbouring months. Shown rather than blanked, because six
   fixed rows means there are always some and an empty cell reads as broken. */
.sb-dp-day.is-out { color: var(--ink-3); opacity: .55; }
/* A weekend reads as a weekend without reading as UNAVAILABLE (2026-08-25).
   This was `background: var(--bg-sunken)`, the same tint the picker uses for
   nothing else — and against the plain white weekdays beside it, a sunken cell
   is the universal look of a disabled one. Weekends have always been fully
   selectable here (only min/max bounds ever set `disabled`), and the server
   simply prices them at zero, so the tint was telling the reader the opposite
   of the truth on a picker whose job is booking Friday to Monday. The muted
   NUMBER says "not a working day" without claiming it cannot be clicked. */
.sb-dp-day.is-weekend { color: var(--ink-3); }
/* A company day off, with its name on the tooltip — the one thing this picker
   knows that the browser's never could. */
.sb-dp-day.is-closed { background: var(--accent-bronze-soft); }
.sb-dp-day.is-today { box-shadow: inset 0 0 0 1px var(--brand); font-weight: 700; }
.sb-dp-day.is-on {
    background: var(--brand); color: #fff; font-weight: 700;
}
.sb-dp-day.is-on:hover { background: var(--brand); }
.sb-dp-day:disabled { color: var(--ink-3); opacity: .35; cursor: default; }

.sb-dp-foot {
    display: flex; justify-content: space-between; gap: 8px;
    margin-top: 8px; padding-top: 8px;
    border-top: 1px solid var(--hairline);
}
.sb-dp-today,
.sb-dp-clear {
    border: 0; background: transparent; padding: 2px 4px;
    font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
    color: var(--brand-deep);
}
.sb-dp-clear { color: var(--ink-3); }
.sb-dp-today:hover, .sb-dp-clear:hover { text-decoration: underline; }

/* On a phone the panel is a centred sheet rather than a tethered popup: there
   is not reliably room beside a field, and a 268px panel flipped above a focused
   input ends up under the keyboard. */
@media (max-width: 700px) {
    .sb-dp-panel {
        left: 50% !important; top: 50% !important;
        transform: translate(-50%, -50%);
        width: min(320px, calc(100vw - 32px));
    }
    .sb-dp-day { height: 36px; font-size: 14px; }
}
