/* Optivate Executive Dashboard — design system
   Brand floor: Optivate Brand Guidelines v1.1 (navy #151E49, Inter only per
   James 2026-06-11). Aesthetic (R3): borderless luxe — white cards floating
   on soft diffuse shadows over a slightly deeper paper, hairline rules inside
   blocks, generous whitespace, tabular numerals, no uppercase anywhere,
   one restrained staggered-reveal motion. All $ display in 000s. */

:root{
  /* palette */
  --navy:#151E49;
  --navy-2:#1D2A5E;          /* rail hover */
  --navy-3:#0F1636;          /* rail deep */
  --ink:#1A2142;             /* body text on paper */
  --slate:#5A6379;           /* secondary text */
  --faint:#687088;           /* tertiary text (was #8B92A6 — 3.11:1 on white,
                                 failed WCAG AA 4.5:1; darkened same hue/sat,
                                 now 4.93:1 white / 4.52:1 paper) */
  --paper:#F4F5F9;           /* canvas — one step deeper so borderless cards read */
  --card:#FFFFFF;
  --line:#E5E8F0;            /* control borders (inputs, buttons) */
  --hairline:#EDEFF5;        /* inner dividers */
  --cyan:#2F74FF;
  --cyan-text:#226BFF;       /* --cyan used AS TEXT (sort-dir, mtoggle, btn-ghost
                                hover): #2F74FF is 4.15:1 on white, fails WCAG AA
                                4.5:1. Darkened for text only — chart/decorative
                                cyan fills (pbar, hover borders) keep --cyan. */
  --indigo:#6F43FF;
  --turquoise:#32E6D7;
  --pink:#D149FF;
  --iceberg:#D8F7FF;
  --lavender:#E1D7FF;
  --mint:#BEF4F0;
  --lilac:#F0E3F9;
  --pos:#0E7B5B;             /* gains */
  --neg:#A11C29;             /* losses: dark-red, parentheses */
  --amber:#98640D;           /* preliminary badge (was #B97A10 — 3.25:1 on tan,
                                 failed WCAG AA 4.5:1; darkened same hue,
                                 now 4.57:1 tan / 4.63:1 paper) */
  --focus:#2F74FF;

  --rail-w:284px;
  --radius:14px;
  --shadow:0 1px 2px rgba(21,30,73,.03), 0 10px 30px rgba(21,30,73,.07);
  --shadow-pop:0 12px 40px rgba(21,30,73,.16);
  --font-head:'Inter', sans-serif;   /* Inter only per James 2026-06-11 (Albert Sans dropped) */
  --font-body:'Inter', sans-serif;

  /* Type scale (James 2026-07-01 v02): one size per role, shared across every
     tab. Collapses 21 ad-hoc sizes into 9 tiers so a table on one tab matches a
     table on another. Chart-internal sizes (ECharts) can't use CSS vars, so they
     use the same NUMBERS (see echarts-core FONT/AXIS_FONT). Responsive @media
     hero/KPI reductions are intentionally kept as literals. */
  --fs-hero:40px;      /* hero headline value (Ending ARR) */
  --fs-display:30px;   /* big bumper KPI value */
  --fs-stat:22px;      /* secondary big stats: reach/potential, hero-secondary, card metrics */
  --fs-title:16px;     /* block / section titles, hero subtitle */
  --fs-base:14px;      /* global body default */
  --fs-body:13px;      /* all table cells + primary body text */
  --fs-label:12px;     /* KPI labels, toggles/buttons/filters, card titles, table headers */
  --fs-note:11px;      /* block subtitles, notes/captions, Y/Y rows */
  --fs-micro:10px;     /* chart axis, chips, badges, legend, smallest labels */
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--font-body); font-size:var(--fs-base); line-height:1.5;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4{font-family:var(--font-head); font-weight:600; color:var(--navy); margin:0}
button{font-family:inherit}
::selection{background:var(--lavender)}

.num, .kpi__value, td.num, th.num{
  font-variant-numeric:tabular-nums; font-feature-settings:"tnum";
}
.neg{color:var(--neg)}
.pos{color:var(--pos)}

/* ---------------------------------------------------------------- layout */
.app{display:grid; grid-template-columns:var(--rail-w) 1fr; min-height:100vh}
.main{min-width:0; display:flex; flex-direction:column}
/* generous side margins (James 2026-06-16: it was too edge-to-edge). The
   "too far apart" was an INNER-spacing problem (hero columns, tile text/spark),
   fixed there, not by removing outer margin. */
.content{flex:1; padding:16px clamp(20px,2vw,48px) 28px; max-width:none; width:100%; margin:0}

/* ---------------------------------------------------------------- rail (white/airy) */
.rail{
  position:sticky; top:0; height:100vh; overflow-y:auto;
  background:var(--card); border-right:1px solid var(--line);
  color:var(--ink); display:flex; flex-direction:column; z-index:40;
}
.rail__brand{padding:24px 22px 16px; text-align:center}
.rail__logo{width:124px; display:block; margin:0 auto}
.rail__sub{
  margin-top:9px; font-size:var(--fs-label); letter-spacing:.01em; color:var(--slate);
}
.rail__nav{flex:1; padding:8px 12px 16px; display:flex; flex-direction:column; gap:2px}
.nav-item{
  display:flex; align-items:center; gap:11px; width:100%;
  padding:9px 12px; border:0; background:none; cursor:pointer; text-align:left;
  color:var(--slate); font-size:var(--fs-body); letter-spacing:.01em; border-radius:10px;
  transition:background .15s, color .15s, box-shadow .15s;
}
.nav-item__ico{flex:none; color:var(--faint); transition:color .15s}
.nav-item__lbl{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.nav-item:hover{background:rgba(111,67,255,.06); color:var(--ink)}
.nav-item:hover .nav-item__ico{color:var(--indigo)}
.nav-item:focus-visible{outline:2px solid var(--focus); outline-offset:1px}
.nav-item.active{
  background:var(--card); color:var(--indigo); font-weight:500;
  box-shadow:0 1px 2px rgba(21,30,73,.04), 0 6px 16px rgba(111,67,255,.13);
}
.nav-item.active .nav-item__ico{color:var(--indigo)}
.rail__foot{padding:14px 18px 20px; border-top:1px solid var(--hairline)}
.rail__asof{font-size:var(--fs-note); color:var(--faint); margin-bottom:10px}
.rail__review{
  width:100%; padding:8px 10px; border-radius:9px; cursor:pointer;
  border:1px solid var(--line); background:none; color:var(--slate);
  font-size:var(--fs-label); transition:all .15s;
}
.rail__review:hover{border-color:var(--indigo); color:var(--indigo)}
.rail__review.on{background:var(--indigo); border-color:var(--indigo); color:#fff; font-weight:600}
.rail-scrim{display:none}

/* ---------------------------------------------------------------- topbar */
.topbar{
  display:flex; align-items:center; gap:12px;
  padding:14px 40px; background:var(--card); box-shadow:0 1px 0 var(--hairline);
  position:sticky; top:0; z-index:30;
}
.topbar__menu{display:none}
.topbar__logo{display:none; height:26px}
.topbar__spacer{flex:1}
.badge{
  font-size:var(--fs-note); padding:4px 10px; border-radius:999px; white-space:nowrap;
  letter-spacing:.01em;
}
.badge--prelim{background:#FBF3E2; color:var(--amber); border:1px solid #EFDDB4;
  font-weight:600; font-size:var(--fs-note); letter-spacing:.02em}
.badge--asof{background:var(--indigo); color:#fff; border:1px solid var(--indigo); font-weight:700}

/* ---------------------------------------------------------------- section header */
.section-head{margin:2px 0 12px}
.section-head h1{font-size:var(--fs-stat); letter-spacing:-.02em; color:#14161D}
.section-head__units{font-size:var(--fs-label); color:var(--faint); margin-top:3px}

/* ---------------------------------------------------------------- blocks */
.grid{display:grid; gap:12px; grid-template-columns:repeat(12, 1fr)}
.block{
  background:var(--card); border:0; border-radius:var(--radius);
  box-shadow:var(--shadow); padding:14px 16px; min-width:0;
  grid-column:span 12;
  /* reveal animates FROM hidden with backwards fill during the delay; no
     'forwards' retention (retained fills keep animations alive forever and
     stall headless screenshots) */
  animation:reveal .45s cubic-bezier(.2,.7,.3,1) backwards;
}
.grid > .block:nth-child(2){animation-delay:.05s}
.grid > .block:nth-child(3){animation-delay:.1s}
.grid > .block:nth-child(4){animation-delay:.15s}
.grid > .block:nth-child(5){animation-delay:.2s}
.grid > .block:nth-child(n+6){animation-delay:.25s}
@keyframes reveal{from{opacity:0; transform:translateY(6px)}}
@media (prefers-reduced-motion: reduce){
  .block{animation:none}
}
.block--6{grid-column:span 6}
.block--5{grid-column:span 5}
.block--7{grid-column:span 7}
.block--4{grid-column:span 4}
.block--8{grid-column:span 8}
.block--3{grid-column:span 3}
/* the head hairline is the "private-bank rule" that replaces card borders */
.block__head{display:flex; align-items:baseline; gap:10px;
  padding-bottom:7px; border-bottom:1px solid var(--hairline)}
.block__title{font-size:var(--fs-body); font-weight:600; letter-spacing:-.01em;
  color:#14161D; font-family:var(--font-head); flex-shrink:0}
.block__sub{font-size:var(--fs-label); color:var(--faint); min-width:0}
.block__spacer{flex:1}
.block__body{margin-top:9px}

/* ---- InfoTip (hover/focus ⓘ → definition popover) ---------------------- */
/* margin-left:3px (was 4px, was -4px before that): the icon sits right next
   to the term it defines — tight enough to read as attached, not a separate
   affordance. Batch-24 U1: shrunk 14px→11px icon / 9px→7.5px glyph (James:
   "so large") while keeping the popover trigger area comfortably tappable. */
.info-tip{position:relative; display:inline-flex; align-items:center; margin-left:3px;
  vertical-align:middle; cursor:help; outline:none}
.info-tip__icon{display:inline-flex; align-items:center; justify-content:center;
  width:11px; height:11px; border-radius:50%; border:1px solid var(--faint);
  color:var(--faint); font-family:var(--font-head); font-size:7.5px; font-weight:700;
  font-style:italic; line-height:1; user-select:none; transition:color .15s, border-color .15s}
.info-tip:hover .info-tip__icon, .info-tip:focus-visible .info-tip__icon{
  color:var(--indigo); border-color:var(--indigo)}
.info-tip__pop{position:absolute; top:calc(100% + 6px); left:0; z-index:60;
  width:max-content; max-width:min(300px, 80vw); padding:9px 11px;
  background:var(--navy); color:#fff; font-family:var(--font-body);
  font-size:var(--fs-note); font-weight:400; font-style:normal; line-height:1.45;
  letter-spacing:0; text-transform:none; white-space:normal; border-radius:9px;
  box-shadow:var(--shadow-pop); opacity:0; visibility:hidden; transform:translateY(-3px);
  transition:opacity .15s, transform .15s, visibility .15s; pointer-events:none}
.info-tip:hover .info-tip__pop, .info-tip:focus-within .info-tip__pop{
  opacity:1; visibility:visible; transform:translateY(0)}

/* ---------------------------------------------------------------- KPI card */
/* fixed column count per grid (set by kpiGrid via --kpi-cols): every card in
   a grid is identical width — an orphan last-row card never stretches */
.kpi-grid{display:grid; gap:16px;
  grid-template-columns:repeat(var(--kpi-cols, 4), minmax(0, 1fr));
  margin-bottom:28px}
.kpi{
  background:var(--card); border:0; border-radius:var(--radius);
  box-shadow:var(--shadow); padding:18px 20px; position:relative; min-width:0;
  animation:reveal .45s cubic-bezier(.2,.7,.3,1) backwards;
  /* metric text left, spark right (James 2026-06-18, Overview-tile layout) */
  display:flex; align-items:center; gap:14px;
}
.kpi__main{flex:0 1 auto; min-width:118px}
.kpi-grid .kpi:nth-child(2){animation-delay:.04s}
.kpi-grid .kpi:nth-child(3){animation-delay:.08s}
.kpi-grid .kpi:nth-child(4){animation-delay:.12s}
.kpi-grid .kpi:nth-child(5){animation-delay:.16s}
.kpi-grid .kpi:nth-child(6){animation-delay:.2s}
.kpi-grid .kpi:nth-child(n+7){animation-delay:.24s}
.kpi__label{
  font-size:var(--fs-body); color:var(--slate); font-weight:600;
}
/* bumper spec: period in parens on its own line under the metric name */
.kpi__period{font-size:var(--fs-note); color:var(--faint); margin-top:3px; min-height:14px; letter-spacing:.01em}
.kpi__value{
  font-family:var(--font-head); font-weight:600; font-size:var(--fs-display); color:var(--navy);
  margin-top:8px; letter-spacing:-.02em; line-height:1.05;
}
.kpi__delta{font-size:var(--fs-label); margin-top:5px; font-variant-numeric:tabular-nums}
.kpi__delta.pos{color:var(--pos)}
.kpi__delta.neg{color:var(--neg)}
.kpi__yoy-tag{color:var(--faint); font-size:var(--fs-note)}
.kpi__note{font-size:var(--fs-note); color:var(--faint); margin-top:6px}
/* spark layout = the Overview-tile recipe (James B30, 2026-07-06, rejecting
   B29's 50/50 split): the metric text hugs a compact column and the spark
   flexes to fill EVERYTHING else — same structure as .ov-tile__l (fixed
   118px) / .ov-tile__spk (flex:1). The text column is content-sized with the
   same 118px floor (flex-basis:auto, min-width:118px): rows whose labels and
   values fit 118px (Margin GM row, Retention row) get a pixel-uniform 118
   start-x and the maximum spark; a card whose 30px value or label genuinely
   needs more (Revenue Quality's $10.57M / "Nonrecurring Revenue", up to
   ~156px) grows just enough — the value never wraps, and the dead-space-
   inside-a-fixed-column defect the B30 pixel judge caught (160px fixed on
   the narrow 5-up cards) can't recur. Spark keeps the batch-26 84px floor. */
.kpi__spark{flex:1 1 84px; min-width:84px; height:74px; align-self:center; margin:0; overflow:hidden}
/* EChartsBase renders a nested .chart (default 300px); clamp it to the spark box
   so the sparkline canvas never overflows below the white card (James 2026-06-19) */
.kpi__spark .chart{width:100%; height:100%; min-height:0}

/* ------------------------------------------------- New-Logo rail (Growth) */
/* N channel cards, each a single white card: title → New Logos → separator →
   New Logo ARR. Sparks sit right of each metric value (James 2026-06-18). */
.nl-rail{display:grid; gap:16px; margin-bottom:28px;
  grid-template-columns:repeat(var(--nl-cols, 6), minmax(0, 1fr));}
.nl-card{
  display:flex; flex-direction:column; gap:0; min-width:0;
  background:var(--card); border-radius:var(--radius); box-shadow:var(--shadow);
  padding:14px 15px;
  animation:reveal .45s cubic-bezier(.2,.7,.3,1) backwards;
}
.nl-card__title{
  font-size:var(--fs-body); font-weight:700; color:var(--navy); text-align:center;
  padding-bottom:9px; margin-bottom:10px;
  border-bottom:2px solid var(--hairline); letter-spacing:.01em;
}
/* as-of-month period next to the channel title (e.g. "All Company (Apr26)") */
.nl-card__per{font-size:var(--fs-micro); font-weight:400; color:var(--faint); margin-left:5px; letter-spacing:0}
/* card body grid: auto column sizes to the widest left block across BOTH metric
   rows, so the two sparklines (col 2) share one left edge (James 2026-06-19) */
.nl-card__metrics{display:grid; grid-template-columns:auto 1fr; column-gap:10px;
  align-items:center; flex:1; min-height:0}
.nl-card__left{min-width:0}
.nl-card__label{font-size:var(--fs-note); font-weight:500; color:var(--slate); line-height:1.2; white-space:nowrap}
.nl-card__val{font-family:var(--font-head); font-size:var(--fs-stat); font-weight:600; color:var(--navy); letter-spacing:-.02em; line-height:1; margin-top:2px}
/* Y/Y delta under the value, colored pos/neg like the Overview cluster tiles */
.nl-card__yoy{font-size:var(--fs-note); margin-top:4px; font-variant-numeric:tabular-nums; line-height:1}
.nl-card__yoy.pos{color:var(--pos)}
.nl-card__yoy.neg{color:var(--neg)}
.nl-card__yoy .ov-yoy-tag{color:var(--faint); font-size:var(--fs-micro)}
.nl-card__spark{min-width:60px; height:52px; align-self:center}
.nl-card__spark .chart{width:100%; height:100%; min-height:46px}
.nl-card__sep{grid-column:1 / -1; border:none; border-top:1px solid var(--line); margin:2px 0}

/* -------------------------------------------- cohort diverging legend pill */
.cohort-legend{display:flex; align-items:center; justify-content:center; gap:12px; margin-top:14px}
.cohort-legend__lbl{font-size:var(--fs-note); color:var(--faint); font-weight:600}
.cohort-legend__bar{width:200px; height:10px; border-radius:6px;
  background:linear-gradient(90deg, #A53C3C 0%, #C97979 22%, #E9E9EE 48%, #8C7AD0 72%, #381A8C 100%)}

/* ---------------------------------------------------- GAAP collapsible tree */
.gaap-tbl td{padding:6px 14px}
.gaap-tbl td.gaap-lbl{white-space:nowrap; color:var(--ink)}
.gaap-tbl .gaap-tri{display:inline-block; width:14px; color:var(--faint); font-size:var(--fs-micro)}
.gaap-tbl .gaap-tri--none{color:transparent}
.gaap-row.gaap-clickable{cursor:pointer}
.gaap-row.gaap-clickable:hover{background:var(--paper)}
.gaap-tbl .gaap-total > td{color:var(--navy)}
.gaap-tbl tr.lvl0 > td{font-weight:600}
/* main subtotals (Software / RCM / Payment / Nonrecurring) shaded gray (James 2026-06-18) */
.gaap-tbl .gaap-total.lvl0 > td{border-top:1px solid var(--line); background:var(--paper)}
.gaap-tbl tr.gaap-total.lvl0:hover td{background:var(--paper)}
/* sub-subtotals (Subscription Revenue, Usage Revenue, ...): dark-gray upper border,
   present only when the group is expanded (class applied in JS) */
.gaap-tbl .gaap-subrule > td{border-top:1px solid #9AA3B5}
/* header row: black bottom border only (no navy fill) */
.gaap-tbl thead th{border-bottom:2px solid #111}
.gaap-tbl .gaap-row.gaap-root > td{font-weight:700; border-top:2px solid #111}
.gaap-tbl tr.gaap-root:hover td{background:var(--paper)}
/* vertical section dividers between TTM/YTD (annual), quarters, or months */
.gaap-tbl .gaap-divider{border-left:2px solid var(--line)}
.gaap-tbl thead th.gaap-divider{border-left:2px solid var(--line)}
.gaap-tbl .gaap-yoy{color:var(--slate); font-size:var(--fs-label)}
/* Y/Y growth colored green/red like every other delta on the dashboard (James 2026-06-19) */
.gaap-tbl .gaap-yoy.pos{color:var(--pos)}
.gaap-tbl .gaap-yoy.neg{color:var(--neg)}
.gaap-tbl th.gaap-yoy-h{color:var(--faint); font-size:var(--fs-note)}
.gaap-gaps{margin:14px 0 0; padding-left:18px; color:var(--slate); font-size:var(--fs-label)}
.gaap-gaps li{margin:3px 0}

/* ---------------------------------------------------------------- tables */
.tbl-wrap{overflow-x:auto; margin:0 -4px; -webkit-overflow-scrolling:touch}
/* table-layout:fixed + width:100% so columns share the container EQUALLY and the
   table always fills the available width on any monitor (James 2026-06-19). Long
   text cells truncate with an ellipsis. DataTable sets a per-table min-width
   (≈ cols × 84px) so on narrow screens the table scrolls instead of cramming.
   The dense bespoke wide tables (GAAP tree, Margin component table) keep
   content-sizing + horizontal scroll — see the exemption below. */
table.tbl{border-collapse:collapse; width:100%; font-size:var(--fs-body); table-layout:fixed}
.tbl.gaap-tbl, .tbl.mtbl{table-layout:auto}
.tbl th{
  text-align:left; font-size:var(--fs-label); letter-spacing:.01em;
  color:var(--slate); font-weight:600; padding:9px 14px;
  border-bottom:2px solid #111; white-space:nowrap;
  position:sticky; top:0; background:var(--card);
}
.tbl td{padding:9px 14px; border-bottom:1px solid var(--hairline); white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; max-width:280px}
.tbl tr:hover td{background:var(--paper)}
.tbl th.num, .tbl td.num{text-align:right}
/* multi-line headers: a label may carry "\n" to force a wrap (e.g. "Channel\nType").
   The th is nowrap, so the inner label span owns its own wrapping (James 2026-06-19). */
.tbl th .th-lbl{white-space:pre-line; display:inline-block; vertical-align:middle; line-height:1.15}
.tbl td:first-child{font-weight:500}
/* cells with a hover tooltip (roll-forward New/Churned name lists) signal it
   with a help cursor; the names render via the native title attribute. */
.tbl td.has-tip{cursor:help}
.tbl th{cursor:pointer; position:relative; user-select:none}
.tbl th.sorted{color:var(--navy)}
/* sort-dir: active column direction arrow; sort-hint: faint ↕ on all other columns */
/* vertical-align:middle keeps the glyph centered against the FULL label block on
   wrapped (multi-line) headers, where .th-lbl is inline-block (James 2026-06-19) */
.sort-dir{margin-left:4px; font-size:var(--fs-note); font-weight:600; color:var(--cyan-text); vertical-align:middle}
.sort-hint{margin-left:4px; font-size:var(--fs-micro); opacity:0.3; pointer-events:none; vertical-align:middle}
.col-grip{
  position:absolute; right:0; top:0; bottom:0; width:7px; cursor:col-resize;
  /* persistent faint vertical guide so the resize handle is discoverable */
  border-right:1px solid var(--line);
}
.col-grip:hover{border-right-color:var(--cyan); background:linear-gradient(to right, transparent, rgba(47,116,255,.12))}
.tbl tr.tbl-total td{
  font-weight:600; color:var(--navy); border-top:1.5px solid var(--navy);
  border-bottom:none; background:var(--paper);
}
/* mid-table subtotal: border + bold, no fill */
.tbl tr.tbl-subtotal td{ font-weight:600; color:var(--navy); border-top:1px solid var(--line); }
/* below-total summary row (e.g. Products RCM under the Total): only the row
   label (first cell) is bold — the rest of the row reads as normal data,
   consistent with every other data row (James, batch-24: RCM row over-bolded) */
.tbl tr.tbl-aftertotal td{ font-weight:400; color:var(--navy); border-top:1px solid var(--line); }
.tbl tr.tbl-aftertotal td:first-child{ font-weight:600; }
/* auto-width utility: table sizes to content instead of the equal-width fixed
   layout (opt-in per table, e.g. Products scorecard) */
.tbl.tbl-auto{ table-layout:auto; }
/* second total row immediately under tbl-total (e.g. Products "Total ARR + TTM RCM"):
   same gray fill + bold navy as tbl-total, but NO top border - the black divider
   stays above the FIRST total row only (James 2026-07-02). */
.tbl tr.tbl-total2 td{ font-weight:600; color:var(--navy); border-top:none; background:var(--paper); }
/* indented child rows (e.g. rollforward movements under Net New ARR) */
.tbl tr.tbl-indented td:first-child{ padding-left:26px }
/* Y/Y % lines under a subtotal (faint, italic label, no fill) */
.tbl tr.tbl-yoy td{ font-size:var(--fs-note); color:var(--faint) }
.tbl tr.tbl-yoy td:first-child{ font-weight:400; font-style:italic; padding-left:26px }
/* last-digit alignment: trailing unit (%, ), %), pts) sits in a fixed gutter so the
   rightmost digit of every value in a column lines up (James 2026-06-21). */
.tbl td.num .nt{ display:inline-block; min-width:1.4ch; text-align:left }
.tbl td.has-dim{ background:#EEF0F4; color:var(--faint) }
/* Net New ARR: black upper border, NOT bold, no fill (James 2026-06-18) */
.tbl tr.tbl-topline td{ border-top:1.5px solid #111; font-weight:400 }
.tbl tr.tbl-topline td:first-child{ font-weight:400 }
/* Ending ARR: bold + light gray shading + black upper border */
.tbl tr.tbl-strong td{ font-weight:700; color:var(--navy); background:var(--paper); border-top:1.5px solid #111 }
/* a subtotal that also wants the heavier black upper border (e.g. Total Direct,
   sitting above its indented child channels) — compound class wins cleanly */
.tbl tr.tbl-subtotal.tbl-topline td{ border-top:1.5px solid #111; font-weight:600 }

/* margin by-customer: collapsible Excel-style component columns (James 2026-06-18).
   Component cells sit on a light-gray fill; the group toggle on the total header
   shows/hides them. */
.mtbl td.mcomp, .mtbl th.mcomp{ background:var(--paper); color:var(--slate) }
/* a negative value cell stays dark-red even on the slate component fill */
.mtbl td.neg{ color:var(--neg) }
.mtbl .mtot{ font-weight:600 }
.mtbl td.mcust{ font-weight:500 }
.mtbl thead th{ border-bottom:2px solid #111 }
.mtbl thead tr.mcols th.mgrp-sep, .mtbl tbody td.mgrp-sep{ border-left:1px solid var(--line) }
.mtbl .mtoggle{ cursor:pointer; color:var(--cyan-text); font-size:var(--fs-micro); margin-left:6px; user-select:none; vertical-align:middle }
.mhide-rev .mcomp-rev, .mhide-cost .mcomp-cost, .mhide-gm .mcomp-gm{ display:none }
/* sortable affordance on the custom margin table headers */
.mtbl thead tr.mcols th{ cursor:pointer; user-select:none }

/* margin: GM%-forward product cards (replaces the 3-row by-product table) */
.mgn-prod{display:flex; gap:12px; flex-wrap:wrap}
.mgn-prod__card{flex:1 1 0; min-width:96px; background:var(--paper);
  border:1px solid var(--hairline); border-radius:12px; padding:14px 14px; text-align:center}
.mgn-prod__name{font-size:var(--fs-label); font-weight:600; color:var(--slate); margin-bottom:6px}
.mgn-prod__gm{font-family:var(--font-head); font-size:var(--fs-display); font-weight:600; color:var(--navy); letter-spacing:-.02em; line-height:1}
.mgn-prod__sub{font-size:var(--fs-note); color:var(--faint); margin-top:8px}
/* inline percent bars (ARR band / channel mix style). STACKED: the bar sits on
   its own line and the % reads directly beneath it. table-layout:fixed pins these
   columns to ~84px, which is too narrow for a 64px bar + "100.0%" side by side —
   the old side-by-side layout overflowed and the % was clipped (hidden). Stacking
   keeps both fully visible at any column width (James 2026-06-19). */
.pbar{display:block; width:100%; max-width:80px; min-width:34px; height:8px;
  background:var(--hairline); border-radius:999px; overflow:hidden}
.pbar__fill{display:block; height:100%; border-radius:999px; background:var(--cyan, #2F74FF)}
.pbar__txt{display:block; margin:3px 0 0; font-size:var(--fs-note); color:var(--slate)}
/* bar cells left-aligned (not .num); not clipped so the stacked label always shows */
.tbl th.bar, .tbl td.bar{text-align:left}
.tbl td.bar{overflow:visible}
.tbl-actions{display:flex; justify-content:flex-end; margin-top:8px}
.btn-ghost{
  border:1px solid var(--line); background:none; color:var(--slate);
  font-size:var(--fs-label); border-radius:7px; padding:5px 12px; cursor:pointer;
}
.btn-ghost:hover{border-color:var(--cyan); color:var(--cyan-text)}

/* ---------------------------------------------------------------- toggles */
.toggle{display:inline-flex; border:1px solid var(--line); border-radius:8px; overflow:hidden}
.toggle button{
  border:0; background:var(--card); color:var(--slate); padding:6px 13px;
  font-size:var(--fs-label); cursor:pointer;
}
.toggle button + button{border-left:1px solid var(--line)}
.toggle button.on{background:var(--navy); color:#fff; font-weight:500}

/* margin component-column pills: one per group (Revenue / Cost / GM %), each
   expands/collapses its own component columns independently (James 2026-06-19,
   replaces the dated single "Collapse all" button). */
.mcol-toggles{display:flex; align-items:center; gap:8px; justify-content:flex-end;
  flex-wrap:wrap; margin-bottom:10px}
.mcol-toggles__lbl{font-size:var(--fs-note); color:var(--faint); margin-right:2px; letter-spacing:.01em}
.mcol-pill{display:inline-flex; align-items:center; gap:7px; font-size:var(--fs-label);
  font-family:var(--font-body); border:1px solid var(--line); background:var(--card);
  color:var(--slate); border-radius:999px; padding:5px 13px; cursor:pointer;
  transition:background .15s, border-color .15s, color .15s; line-height:1}
.mcol-pill:hover{border-color:var(--cyan); color:var(--navy)}
.mcol-pill.on{background:var(--navy); border-color:var(--navy); color:#fff}
.mcol-pill__chev{font-size:var(--fs-body); font-weight:600; opacity:.8; line-height:1; width:9px; text-align:center}

/* ---------------------------------------------------------------- charts */
.chart{width:100%; height:300px}
.chart--tall{height:380px}
.chart--xtall{height:460px}
.chart--short{height:220px}
/* nested chart (wrapper div + EChartsBase div both carry .chart): inner inherits outer height */
.chart > .chart{width:100%; height:100%}
/* Customer Distribution (Customers tab) only: +70px over the shared chart--tall
   380px, so the concentration-curve plot area consumes the dead white space
   under the x-axis instead of the gap growing (James 2026-07-02). Scoped to
   #customer-distribution, not a chart--tall bump, since chart--tall is shared
   by 7 other charts across other tabs (ArrComposition/Drilldown/Margin/
   ProductsAdoption/Growth/Retention) this task doesn't own. */
[data-block-id="customer-distribution"] .chart--tall{height:450px}

/* ---------------------------------------------------------------- pills/chips */
.chip{
  display:inline-block; font-size:var(--fs-note); padding:2px 9px; border-radius:999px;
  background:var(--paper); border:1px solid var(--line); color:var(--slate);
}
.chip--tier1{background:var(--mint); border-color:#9BE3DC; color:#0B5E54}
.chip--tier2{background:var(--iceberg); border-color:#BFE9F5; color:#114E78}
.chip--tier3{background:var(--lilac); border-color:#E2C8F0; color:#6E2D8A}
.chip--tier4{background:#FBF3E2; border-color:#EFDDB4; color:var(--amber)}

/* ---------------------------------------------------------------- filter bar */
.filter-bar{
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  background:var(--card); border:0; border-radius:var(--radius);
  box-shadow:var(--shadow); padding:8px 14px; margin-bottom:12px;
}
.filter-bar__item{display:flex; align-items:center; gap:8px; font-size:var(--fs-label);
  color:var(--slate); letter-spacing:.01em; font-weight:500; min-width:0}
/* fixed EQUAL width for every gray filter box (James 2026-07-02: Channel's
   longest option, e.g. "Foxfire Systems Group", was making that box visibly
   wider than Product — content-sized max-width let each box size itself
   independently). One shared width for select + the search-select combobox
   so every box on every FilterBar tab (Overview/Growth/Retention/Customers/
   Margin/RevenueQuality) lines up. */
.filter-bar__item select{
  border:1px solid var(--line); border-radius:7px; background:#EEF0F4;
  color:var(--navy); font-family:var(--font-body); font-size:var(--fs-label); padding:6px 10px;
  width:210px;
}
.filter-bar__reset{margin-left:auto}
/* was --amber (the "Preliminary" badge color) — this is a plain informational
   note, not a warning, so it takes the same muted gray as every other note
   in the app (James 2026-07-02, item 4). --amber itself is untouched; other
   badges still use it. */
.filter-note{font-size:var(--fs-note); color:var(--faint); margin:0 0 8px}
/* the search-select combobox (Drilldown Customer/Product/etc.) has no intrinsic
   width of its own — without a basis it either overflows the bar or gets crushed
   to nothing when the bar wraps (mobile sweep: "empty bordered box peeking in
   from the right edge", high). Same fixed width as the native <select> above
   so every gray box aligns. */
.filter-bar__item .search-select{width:210px; max-width:210px; min-width:120px}

/* filter-rail variant: vertical FILTERS column (drilldown) + content right */
.filter-rail-layout{display:grid; grid-template-columns:220px minmax(0,1fr); gap:16px}
.filter-bar.filter-rail{
  flex-direction:column; align-items:stretch; gap:12px; margin-bottom:0;
  padding:14px 16px; position:sticky; top:14px;
}
.filter-rail__title{
  font-size:var(--fs-body); font-weight:600; color:var(--navy); font-family:var(--font-head);
  letter-spacing:.01em;
}
.filter-bar.filter-rail .filter-bar__item{flex-direction:column; align-items:stretch; gap:5px}
.filter-bar.filter-rail .filter-bar__item select,
.filter-bar.filter-rail .filter-bar__item .search-select{max-width:none; width:100%; min-width:0}
.filter-bar.filter-rail .filter-bar__reset{margin-left:0; margin-top:2px}
@media (max-width: 880px){
  .filter-rail-layout{grid-template-columns:1fr}
  .filter-bar.filter-rail{position:static}
}

/* ---------------------------------------------------------------- definitions */
.defs{display:grid; grid-template-columns:max-content 1fr; gap:4px 16px; margin:0; font-size:var(--fs-note)}
.defs dt{font-weight:600; color:var(--navy); white-space:nowrap}
.defs dd{margin:0; color:var(--slate)}
@media (max-width: 880px){ .defs{grid-template-columns:1fr} .defs dd{margin-bottom:8px} }
/* Overview Definitions: 3-up to match the reference layout (Overview-scoped;
   collapses to 2-up then 1-up as width drops) */
.overview-tab .defs{grid-template-columns:max-content 1fr max-content 1fr max-content 1fr;
  gap:8px 18px; font-size:var(--fs-note); line-height:1.4}
.overview-tab .defs dd{line-height:1.4}
@media (max-width:1180px){ .overview-tab .defs{grid-template-columns:max-content 1fr max-content 1fr} }
@media (max-width:760px){ .overview-tab .defs{grid-template-columns:max-content 1fr} }
@media (max-width:520px){ .overview-tab .defs{grid-template-columns:1fr} }

/* ---------------------------------------------------------------- review mode */
body.reviewing .block, body.reviewing .kpi{cursor:context-menu}
body.reviewing .block:hover, body.reviewing .kpi:hover{
  outline:2px dashed var(--pink); outline-offset:2px;
}
.block.has-comment, .kpi.has-comment{box-shadow:0 0 0 2px var(--pink), var(--shadow)}
.comment-dot{
  position:absolute; top:-7px; right:-7px; width:18px; height:18px; border-radius:50%;
  background:var(--pink); color:#fff; font-size:var(--fs-note); font-weight:600;
  display:flex; align-items:center; justify-content:center; z-index:5;
}
.block{position:relative}
.review-pop{
  position:absolute; z-index:90; width:300px; background:var(--card);
  border:0; border-radius:10px; box-shadow:var(--shadow-pop);
  padding:12px;
}
.review-pop textarea{
  width:100%; min-height:74px; border:1px solid var(--line); border-radius:7px;
  font-family:var(--font-body); font-size:var(--fs-label); padding:8px; resize:vertical;
}
.review-pop__row{display:flex; gap:8px; justify-content:flex-end; margin-top:8px}
.btn-primary{
  background:var(--navy); color:#fff; border:0; border-radius:7px;
  padding:6px 14px; font-size:var(--fs-label); cursor:pointer; font-weight:500;
}
.btn-primary:hover{background:var(--navy-2)}
.review-tray{
  position:fixed; right:18px; bottom:18px; z-index:80; background:var(--navy);
  color:#fff; border-radius:12px; box-shadow:0 8px 30px rgba(21,30,73,.35);
  padding:12px 16px; display:none; align-items:center; gap:12px; font-size:var(--fs-label);
}
body.reviewing .review-tray{display:flex}
.review-tray button{
  border:1px solid rgba(255,255,255,.3); background:none; color:#fff;
  border-radius:7px; padding:5px 10px; font-size:var(--fs-label); cursor:pointer;
}
.review-tray button:hover{border-color:var(--turquoise)}
.review-tray .count{
  background:var(--pink); border-radius:999px; min-width:20px; height:20px;
  display:inline-flex; align-items:center; justify-content:center; font-weight:600; font-size:var(--fs-note);
  padding:0 6px;
}

/* ---------------------------------------------------------------- loading / error */
.loading{color:var(--faint); padding:60px 0; text-align:center; font-size:var(--fs-body)}
.error{color:var(--neg); padding:40px 0; text-align:center}

/* ---------------------------------------------------------------- mobile */
@media (max-width: 880px){
  .app{grid-template-columns:1fr}
  .rail{
    position:fixed; left:0; top:0; width:min(82vw, 300px); height:100vh;
    transform:translateX(-102%); transition:transform .25s cubic-bezier(.2,.7,.3,1);
    box-shadow:0 0 40px rgba(0,0,0,.3);
  }
  .rail.open{transform:none}
  .rail-scrim{
    display:block; position:fixed; inset:0; background:rgba(15,22,54,.5);
    opacity:0; pointer-events:none; transition:opacity .25s; z-index:35;
  }
  .rail-scrim.show{opacity:1; pointer-events:auto}
  .topbar{padding:10px 16px; flex-wrap:wrap; row-gap:6px}
  .topbar__menu{
    display:flex; flex-direction:column; gap:4px; background:none; border:0;
    padding:8px; cursor:pointer;
  }
  .topbar__menu span{width:20px; height:2px; background:var(--navy); border-radius:2px}
  .topbar__logo{display:block}
  .badge--prelim{display:none}
  /* as-of badge: never clip. .topbar__spacer collapses to full-width so the
     badge drops to its own second row instead of being squeezed off the right
     edge (was clipped on every tab at ≤480px — mobile sweep, high). */
  .topbar__spacer{flex:1 1 100%; min-width:0}
  .badge--asof{white-space:normal; text-align:right}
  .content{padding:16px 14px 32px}
  .block--6, .block--5, .block--7, .block--4, .block--8, .block--3{grid-column:span 12}
  .kpi-grid{grid-template-columns:repeat(2, minmax(0, 1fr)); gap:12px; margin-bottom:22px}
  .nl-rail{grid-template-columns:repeat(2, minmax(0, 1fr)); gap:12px; margin-bottom:22px}
  .kpi__value{font-size:26px}
  .chart{height:260px}
  .chart--tall{height:300px}
  .section-head h1{font-size:22px}
  .foot{padding:14px 16px 22px}
}
@media (max-width: 480px){
  .kpi-grid{grid-template-columns:minmax(0, 1fr)}
  .nl-rail{grid-template-columns:minmax(0, 1fr)}
  /* filter bar: let trailing controls (search combobox, Include-RCM/Exclude-Usage
     toggle, Channel select) drop to their own row instead of overflowing the
     viewport (mobile sweep: overview/growth/customers/retention, high). Desktop
     layout (single row) is untouched above this breakpoint. Boxes share each
     row EQUALLY (flex:1 1 0, not a fixed px width) so two fit side by side
     without overflowing a 380px viewport (James 2026-07-02). */
  .filter-bar{gap:10px 12px}
  .filter-bar__item{flex:1 1 0; min-width:0}
  .filter-bar__item select, .filter-bar__item .search-select{max-width:none; width:100%; min-width:0}
  .filter-bar__reset{margin-left:0; flex:0 0 auto}
  .rcm-switch-row{margin:0; flex:1 1 100%; justify-content:flex-start}
  /* block header: title + sub + controls (ToggleGroups, CSV button) wrap instead
     of overflowing (mobile sweep: products/products-adoption/gaap/margin/
     drilldown/arr-composition, high+medium). */
  .block__head{flex-wrap:wrap; row-gap:6px}
  /* flex-shrink:0 (base rule) sizes the title to its max-content width
     regardless of white-space, so long titles ("Product Quality Quadrant:
     YoY vs. GDR vs. ARR/TTM RCM") overflowed the viewport instead of
     wrapping even after flex-wrap kicked in on the row — the item just never
     shrank enough to trigger a line break. flex-basis:100% forces it onto
     its own full-width line where white-space:normal can actually wrap it. */
  .block__title{white-space:normal; flex:1 1 100%; min-width:0}
  .block__sub{flex:1 1 100%; order:3; min-width:0}
  .block__spacer{flex:0 0 0; width:0}
  /* right-edge fade so a horizontally-scrollable table signals there's more to
     the right, even before the user discovers the scrollbar (mobile sweep:
     retention/customers/margin/drilldown, high+medium). Purely decorative —
     doesn't block scroll/tap since it's a background image, not an element. */
  .tbl-wrap{
    background:
      linear-gradient(to right, var(--card) 0%, rgba(255,255,255,0) 18px) left,
      linear-gradient(to left, var(--card) 0%, rgba(255,255,255,0) 18px) right;
    background-repeat:no-repeat; background-attachment:local, local;
    background-size:18px 100%, 18px 100%;
  }
  /* frozen first column truncation: table-layout:fixed force-equalizes every
     column to the same width, so a rollforward row label ("Contraction"),
     Channel/Band/Customer name, etc. gets ellipsis-truncated to ambiguity
     even though the table already scrolls horizontally at this width (mobile
     sweep: retention/customers/arr-composition, high). Switching to auto lets
     the label column size to its content while numeric columns stay tight;
     the table already has a min-width (cols×84px) forcing the same scroll
     behavior either way, so this doesn't change what's reachable, only what's
     legible before scrolling. GAAP/Margin component tables are already auto. */
  table.tbl{table-layout:auto}
  .tbl td:first-child, .tbl th:first-child{min-width:130px; white-space:nowrap}
  /* defensive reach for the Overview bridge waterfalls: if the rendered canvas
     ever exceeds the card at a true narrow viewport, scroll it rather than
     clip it. (Round-2 recheck note: most "right-edge clipped" reports turned
     out to be an Edge-headless capture artifact — it lays the page out at its
     ~470px window floor while cropping the PNG at 380px, chopping the
     380-470px band; live-DOM at a real 380px shows these contained. This rule
     costs nothing and guards the genuine case.) */
  .ov-bridge{overflow-x:auto}
  /* inert full-width elements (hidden InfoTip popovers, closed nav scrim) give
     body a phantom scrollWidth; clip so nothing can pan the page horizontally
     — real horizontal scrolling lives inside .tbl-wrap/chart containers. */
  html, body{overflow-x:clip}
}
/* narrow desktop: 5 columns would clip 30px values - cap at 3 */
@media (min-width: 881px) and (max-width: 1180px){
  .kpi-grid{grid-template-columns:repeat(3, minmax(0, 1fr))}
  .nl-rail{grid-template-columns:repeat(3, minmax(0, 1fr))}
  .kpi__value{font-size:27px}
}
@media (min-width: 881px){
  .topbar{padding:10px 40px; background:transparent; box-shadow:none; position:static}
}

/* ============================================================== Overview redesign
   Minimal/airy (James 2026-06-16): white surfaces, one accent (brand indigo) for
   line charts + sparklines, navy ink. Layout: a 2x2 grid of the four big charts
   on the left with a vertical KPI rail (Growth / Retention / TTM Revenue) on the
   right, then full-width Definitions. Real ECharts everywhere. */
/* charts ~20% narrower; the freed width goes to a wider KPI rail. align-items
   stretch + flex:1 clusters make the rail exactly match the chart-column height
   so the TTM card bottom lines up with the bridges (James 2026-06-17) */
.ov-layout{--card-h:432px; display:grid; grid-template-columns:minmax(0,1fr) 590px; gap:14px;
  align-items:start; margin-bottom:14px}
/* fixed, reasonable card height so charts fill tightly (no dead band) instead of
   being stretched to the tall rail height (James 2026-06-17 whitespace fix) */
.ov-charts{display:grid; grid-template-columns:repeat(2, minmax(0,1fr));
  grid-auto-rows:var(--card-h); gap:14px; min-height:0}
/* the four chart cards size by the 2x2 grid — neutralize legacy span classes */
.ov-charts > .block, .ov-charts > .ov-hero{grid-column:auto; min-height:0; height:100%}
.ov-charts > .block{display:flex; flex-direction:column}
.ov-charts > .block .block__body{flex:1; display:flex; flex-direction:column; min-height:0}
/* charts fill their cards so the 2x2 balances the KPI rail height (no dead space).
   James 2026-07-02: the WRAPPER div (this rule's target) was correctly stretching
   to fill the card (height:auto + flex:1 inside the flex-column .block__body),
   but the wrapper itself was display:block, so the nested inner .chart (the
   actual EChartsBase canvas div, same className, per the .chart>.chart rule)
   had no flex context to grow in and collapsed to min-height:150px — leaving
   dead white space below the drawn chart. display:flex here gives that inner
   div something to flex against, so .chart>.chart{height:100%} finally
   resolves against the wrapper's real (stretched) height. */
.ov-charts .ov-compare, .ov-charts .ov-bridge{
  height:auto; flex:1; min-height:190px; display:flex; flex-direction:column;
}
.ov-charts .ov-compare > .chart, .ov-charts .ov-bridge > .chart{flex:1; min-height:0}
.ov-charts .ov-blank{flex:1}
/* rail height = exactly two chart rows + the row gap, so the 3 flex:1 cards fill
   it and the TTM card bottom lines up with the bridges (James 2026-06-17) */
.ov-rail{display:flex; flex-direction:column; gap:14px; min-width:0;
  height:calc(var(--card-h) * 2 + 14px)}
.ov-rail > .ov-cluster{flex:1; min-height:0}

/* hero: STACKED — compact header row (Ending ARR left, Active Practices right)
   over a full-width blue trend, so the chart stays wide instead of a tall sliver
   (James 2026-06-17, critique P1 fix) */
.ov-hero{
  position:relative; overflow:hidden; border-radius:var(--radius); background:var(--card);
  box-shadow:var(--shadow); padding:18px 20px; color:var(--ink);
  display:flex; flex-direction:column; gap:10px;
  animation:reveal .45s cubic-bezier(.2,.7,.3,1) backwards;
}
/* twin heroes (James 2026-07-02, item 1): ARR + Active Practices as two equal
   headline blocks, divided by a vertical rule. Values colored to match the
   HeroTrend lines (blue ARR / pink practices) so the numbers above and the
   lines below read as one unit. */
.ov-hero__head{display:flex; align-items:flex-start; gap:26px; flex-wrap:wrap}
/* content-width (not equal-stretch) so the ARR block | divider | practices
   block pack to the left instead of the practices number drifting toward
   the middle/right (James 2026-07-02, batch 23 item 1). */
.ov-hero__headline{min-width:0; flex:0 1 auto; padding:0 2px}
.ov-hero__divider{align-self:stretch; flex:none; width:1px; border-left:1px solid var(--line); margin:2px 0}
.ov-hero__label{font-size:15px; font-weight:600; color:var(--slate); margin-top:8px}
.ov-hero__period{font-size:var(--fs-body); color:var(--faint); margin-top:2px}
/* hero value scoped to 34px, down from --fs-hero(40px) — James 2026-07-02, U2
   item 2: "numbers and text might be a bit too large". Var itself untouched
   (other --fs-hero consumers unaffected). */
.ov-hero__value{font-family:var(--font-head); font-size:34px; font-weight:600; color:var(--navy); letter-spacing:-.03em; line-height:1.02; margin-top:0}
.ov-hero__value--blue{color:var(--cyan)}
.ov-hero__value--pink{color:var(--pink)}
.ov-hero__yoy{font-size:var(--fs-label); margin-top:5px; font-weight:500}
.ov-hero__yoy.pos{color:var(--pos)} .ov-hero__yoy.neg{color:var(--neg)}
.ov-hero .ov-yoy-tag{color:var(--faint); font-weight:400; font-size:var(--fs-note)}
.ov-hero__chart{flex:1; min-height:0; min-width:0}
/* min-height 160->240 (James 2026-07-02, U2 item 1): the old 160px floor left
   a large empty band above the lines once the card grew tall enough to clear
   it; 240px gives the chart room to actually fill the card. */
.ov-hero__chart .chart{width:100%; height:100%; min-height:240px}

/* KPI rail cards (Growth / Retention / TTM Revenue). Single-line sparks don't
   need the old seasonal height — sized so the rail matches the chart column
   height (no dead space) (James 2026-06-17) */
.ov-cluster{
  background:var(--card); border-radius:var(--radius); box-shadow:var(--shadow);
  padding:10px 18px; min-width:0; display:flex; flex-direction:column;
  animation:reveal .45s cubic-bezier(.2,.7,.3,1) backwards;
}
.ov-cluster__label{font-size:var(--fs-label); font-weight:600; color:#14161D; letter-spacing:.01em;
  padding-bottom:8px; margin-bottom:2px; border-bottom:1px solid var(--hairline)}
/* tiles flex to fill the card height; metric hugs a narrow fixed column and the
   spark flexes to fill the rest, closing the gap between metric and spark */
.ov-tile{display:flex; align-items:center; gap:12px; flex:1; min-height:0; border-bottom:1px solid var(--hairline)}
.ov-tile:last-child{border-bottom:0}
.ov-tile__l{flex:none; width:118px; min-width:0}
.ov-tile__l .nm{font-size:var(--fs-label); color:var(--slate); font-weight:500; line-height:1.2}
.ov-tile__l .per{font-size:var(--fs-note); color:var(--faint); margin-top:1px}
.ov-tile__l .val{font-family:var(--font-head); font-size:var(--fs-title); font-weight:600; color:var(--navy); letter-spacing:-.02em; line-height:1.1; margin-top:1px}
.ov-tile__yoy{font-size:var(--fs-note); margin-top:1px}
.ov-tile__yoy.pos{color:var(--pos)} .ov-tile__yoy.neg{color:var(--neg)}
.ov-tile .ov-yoy-tag{color:var(--faint); font-size:var(--fs-micro)}
.ov-tile__spk{flex:1; min-width:0; align-self:stretch}
.ov-tile__spk .chart{width:100%; height:100%; min-height:46px}

/* a blanked chart card (e.g. RCM bridge under a non-RCM product filter) */
.ov-blank{display:flex; align-items:center; justify-content:center; text-align:center;
  color:var(--faint); font-size:var(--fs-label); line-height:1.5; min-height:200px; padding:0 28px; flex:1}
/* RCM: an inapplicable ARR view shows a single quiet centered dash (James 2026-06-19) */
.ov-blank--dash{font-size:var(--fs-hero); font-weight:300; color:var(--hairline)}

/* comparison header delta (on white) + the four chart heights (taller/airy) */
.ov-compare-delta{display:flex; flex-direction:column; align-items:flex-start; gap:0;
  margin-bottom:4px; line-height:1.2}
.ov-compare-delta__amt{font-family:var(--font-head); font-weight:700; font-size:var(--fs-stat);
  letter-spacing:-.01em; color:#14161D}
.ov-compare-delta .pos{color:var(--pos)} .ov-compare-delta .neg{color:var(--neg)}
.ov-compare-delta__pct{font-size:var(--fs-label); font-weight:500; margin-top:1px}
.ov-compare-delta .ov-yoy-tag{color:var(--faint); font-weight:400; font-size:var(--fs-note)}
/* .ov-compare bumped 158->190 (James 2026-07-02, item C3.5c: bars read short vs
   the whitespace above/below) — matches .ov-charts .ov-compare's own
   min-height:190px so the two rules agree instead of the flex override doing
   all the work silently. */
.ov-compare{height:190px} .ov-bridge{height:172px}

/* ≤1500: the 590px rail squeezes the charts (and wraps the hero header), so drop
   the rail below as a 3-up row */
@media (max-width:1500px){
  .ov-layout{grid-template-columns:1fr}
  .ov-rail{display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:14px; height:auto}
}
/* ≤880: charts stack one per row (each gets the full main width) */
@media (max-width:880px){
  .ov-charts{grid-template-columns:1fr}
}
/* ≤600: phone — hero text above its chart, rail cards stack */
@media (max-width:600px){
  .ov-rail{grid-template-columns:1fr}
  .ov-hero__value{font-size:32px}
  .ov-hero__chart .chart{height:160px}
  /* twin headline blocks wrap to a stacked layout at narrow widths — divider
     rotates from vertical to horizontal between the two (mobile sweep carried
     over from the old single-stat layout; same reasoning, new markup).
     James 2026-07-02. */
  .ov-hero__head{flex-wrap:wrap}
  .ov-hero__headline{flex:1 1 100%}
  .ov-hero__divider{
    width:auto; align-self:stretch; border-left:none;
    border-top:1px solid var(--hairline); margin:4px 0;
  }
}

/* "Include RCM" bright toggle — Customers tab (James 2026-06-19). Folds recurring
   TTM RCM into ARR across the tab; turquoise when on so it reads as a live overlay. */
.rcm-switch-row{display:flex; justify-content:flex-end; margin:-2px 0 10px}
.rcm-switch{display:inline-flex; align-items:center; gap:8px; cursor:pointer;
  font-size:var(--fs-label); font-weight:600; color:var(--slate); background:var(--card);
  border:1px solid var(--hairline); border-radius:999px; padding:5px 13px 5px 6px;
  transition:border-color .15s ease, background .15s ease, color .15s ease}
.rcm-switch:hover:not(:disabled){border-color:var(--cyan)}
.rcm-switch:disabled{opacity:.45; cursor:not-allowed}
.rcm-switch__track{position:relative; width:30px; height:16px; border-radius:999px;
  background:var(--hairline); transition:background .15s ease; flex:none}
.rcm-switch__thumb{position:absolute; top:2px; left:2px; width:12px; height:12px;
  border-radius:50%; background:#fff; box-shadow:0 1px 2px rgba(0,0,0,.25); transition:left .15s ease}
.rcm-switch.on{color:var(--navy); border-color:var(--turquoise,#32E6D7);
  background:linear-gradient(0deg, rgba(50,230,215,.14), rgba(50,230,215,.14)), var(--card)}
.rcm-switch.on .rcm-switch__track{background:var(--turquoise,#32E6D7)}
.rcm-switch.on .rcm-switch__thumb{left:16px}
