/* report_v2/style.css */

/* Design pass, Task 7 (spec §5): one type scale and one spacing scale instead of the six-plus
   ad-hoc pixel sizes the file had accumulated task by task. Every size below is one of these —
   a literal px value elsewhere in this file is either a hairline (1px border), an icon-scale
   dimension (chart heights, the swatch/dash in the legend), or a value ECharts itself takes
   (font sizes passed into chart option objects, which cannot read a CSS custom property). */
:root {
  --bg:#f5f5f0; --card:#fff; --ink:#23231f; --muted:#7a7a71; --line:#e4e4dc;
  --blue:#1a56b0; --green:#2e7d2e; --amber:#b06000; --red:#c0392b; --grey:#dcdcd6;
  /* Type scale: 12 / 13 / 15 / 18 / 24 / 32 */
  --fs-1:12px; --fs-2:13px; --fs-3:15px; --fs-4:18px; --fs-5:24px; --fs-6:32px;
  /* Spacing scale: 4 / 8 / 12 / 16 / 24 / 32 */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:24px; --sp-6:32px;
  --radius-sm:6px; --radius-md:10px;
  --ctrl-h:30px; /* every filter/chart-control input shares this so a row of mixed
                    select/input/button elements lines up instead of jittering by a few px */
  --topbar-h:104px; /* fallback before app.js measures the real height (see its
                       syncTopbarHeight()) — used so a table's sticky header tucks under the
                       header bar instead of being painted over by it */
}
* { box-sizing: border-box; }
body { margin:0; background:var(--bg); color:var(--ink); font:var(--fs-2)/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; }
h3 { font-size:var(--fs-3); }

/* ---- Header: brand line, tabs, filters --------------------------------------------------- */
.top { position:sticky; top:0; z-index:10; background:var(--bg); border-bottom:1px solid var(--line); padding:var(--sp-3) var(--sp-4) var(--sp-2); }
.brandline { display:flex; gap:var(--sp-3); align-items:baseline; flex-wrap:wrap; }
.muted { color:var(--muted); font-size:var(--fs-1); }

.tabs { display:flex; gap:var(--sp-1); margin:var(--sp-2) 0; flex-wrap:wrap; }
.tabs a { padding:5px 11px; border-radius:var(--radius-sm); color:var(--ink); text-decoration:none; font-size:var(--fs-2); white-space:nowrap; }
.tabs a.on { background:var(--ink); color:#fff; }
.tabs a.v1 { color:var(--muted); margin-left:auto; }

/* Design pass, Task 7: on a phone ten tabs + the v1 link wrapped into a ragged multi-row block.
   Nav becomes one horizontally-scrollable row instead — every tab stays reachable, none of them
   compete with the page's own vertical scroll, and nothing here widens the page itself (the
   nav's own overflow-x is contained to the nav, never the document — see the .page-scroll-guard
   note at the bottom of this file for how that's verified). */
@media (max-width:640px) {
  .tabs { flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch; padding-bottom:2px; }
  .tabs a { flex:0 0 auto; }
  .tabs a.v1 { margin-left:var(--sp-2); }
}

/* Filters: three groups — [range + dates] [comparison + alignment + label]
   [brands, channels, status, attribution] — sharing one row on desktop, one column behind a
   toggle on a phone (Task 7(d)). */
.filters { font-size:var(--fs-2); }
.filters select, .filters input, .filters button {
  font:inherit; padding:0 var(--sp-2); height:var(--ctrl-h); border:1px solid var(--line);
  border-radius:var(--radius-sm); background:#fff;
}
.filters input[type=search] { padding:0 var(--sp-2); }
.fbody { display:flex; gap:var(--sp-4); flex-wrap:wrap; align-items:center; }
.fgroup { display:flex; gap:var(--sp-2); flex-wrap:wrap; align-items:center; }
.filtertoggle { display:none; }
@media (max-width:640px) {
  .filtertoggle {
    display:flex; width:100%; justify-content:space-between; align-items:center; gap:var(--sp-2);
    background:#fff; text-align:left; margin-bottom:var(--sp-2);
  }
  .filtertoggle .muted { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:60vw; }
  .fbody { flex-direction:column; align-items:stretch; }
  .fbody.collapsed { display:none; }
  .fgroup { flex-direction:column; align-items:stretch; }
}

.multi { position:relative; }
.multi > button { text-align:left; }
.multi .panel { position:absolute; top:110%; left:0; background:#fff; border:1px solid var(--line); border-radius:8px; padding:var(--sp-2); min-width:190px; box-shadow:0 6px 18px rgba(0,0,0,.12); z-index:20; }
.multi .panel label { display:block; padding:2px 0; white-space:nowrap; }
.daterange { display:inline-flex; align-items:center; gap:4px; }
.daterange input { height:var(--ctrl-h); }
.cmplabel { white-space:nowrap; }

/* ---- Chart controls (metric/grain selector, shortcut chips) ------------------------------ */
.chartctl { display:flex; gap:var(--sp-2); flex-wrap:wrap; align-items:center; margin-bottom:var(--sp-2); }
.chartctl select, .chartctl button { height:var(--ctrl-h); }
.shortcuts { display:flex; gap:6px; flex-wrap:wrap; }
.chip { font-size:var(--fs-1); padding:3px 9px; border:1px solid var(--line); border-radius:14px; background:#fff; cursor:pointer; }
.chip:hover { background:var(--bg); }

/* Shared chart legend (Task 7(c)): one static HTML legend above a grid of compact tiles
   (small multiples, Overview's brand mini tiles) instead of an ECharts legend crowding every
   120-160px card — charts.js's legendBarHtml()/seriesLegend() build this from the exact same
   series list a tile itself draws, so the colors always match. */
.chartlegend { display:flex; gap:var(--sp-4); flex-wrap:wrap; align-items:center; margin:0 0 var(--sp-2); font-size:var(--fs-1); color:var(--muted); }
.chartlegend .litem { display:inline-flex; align-items:center; gap:5px; }
.chartlegend .sw { width:12px; height:12px; border-radius:2px; display:inline-block; }
.chartlegend .sw.line { height:2px; border-radius:0; border-top:2px solid; background:none !important; }

/* Explore split + 2-4 metrics: one small combo chart per group instead of one overlaid chart
   (controller ruling, 2026-09-18 — 8 overlaid lines in 2 colors were not legible). 1 column by
   default (phone), 2 at tablet, 3 at desktop. */
.smgrid { display:grid; grid-template-columns:1fr; gap:var(--sp-3); margin-bottom:4px; }
@media (min-width:641px) { .smgrid { grid-template-columns:repeat(2,1fr); } }
@media (min-width:1200px) { .smgrid { grid-template-columns:repeat(3,1fr); } }
.sm-card { background:var(--card); border:1px solid var(--line); border-radius:var(--radius-md); padding:var(--sp-2) var(--sp-3); }
.sm-card h4 { margin:0 0 4px; font-size:var(--fs-1); font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sm-card .chart { height:160px; }

/* ---- Page / cards -------------------------------------------------------------------------- */
.view { max-width:1280px; margin:0 auto; padding:var(--sp-4); }
.card { background:var(--card); border:1px solid var(--line); border-radius:var(--radius-md); padding:var(--sp-3) var(--sp-4); margin:0 0 var(--sp-4); }
.card h3 { margin:0 0 4px; font-size:var(--fs-3); }
.note { color:var(--muted); font-size:var(--fs-1); margin:0 0 var(--sp-2); }
/* Campaign names ("What changed", "Who owns the gap") are long underscore-joined strings with no
   natural break point — at 390px one alone can be wider than the card, which the browser will
   NOT wrap on its own (an underscore isn't a break opportunity) and widens the whole page instead
   of just that line. overflow-wrap:anywhere forces a break only when there's nowhere else to
   break; it never touches the numeric `white-space:nowrap` table cells, which opt out of wrapping
   entirely on purpose (they scroll inside .scroll instead — see the .grid2 rule above). */
.card p, .card li, .kpi .l, .kpi .muted { overflow-wrap:anywhere; }

/* KPI / totals cards (spec §5): value large, one reference line under it (plan / floor / prior
   period) — the same three-line shape (label, value, reference) everywhere a card appears. */
.kpis { display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:var(--sp-2); margin-bottom:var(--sp-4); }
.kpi { background:var(--card); border:1px solid var(--line); border-radius:var(--radius-md); padding:var(--sp-2) var(--sp-3); }
.kpi .l { font-size:var(--fs-1); color:var(--muted); }
.kpi .v { font-size:var(--fs-5); font-weight:600; font-variant-numeric:tabular-nums; line-height:1.25; }
/* The verdict line (Overview's "are we on pace" headline, Task 7): the single largest, boldest
   line on the page — everything else on Overview reads as detail beneath it. */
.verdict { font-size:var(--fs-4); font-weight:600; margin:0 0 4px; }
/* The Funnel card's own decomposition sentence (spec §2.1) — bold within ITS card, but never the
   page-level headline .verdict is (2026-09-20 fix: the two collided on Overview, the one tab that
   renders both, so a page could carry two ".verdict" lines and anything counting on there being
   exactly one page verdict silently broke). */
.fn-verdict { font-size:var(--fs-3); font-weight:600; margin:8px 0 4px; }

/* Colour with meaning only (spec §5): reserved for state-vs-reference (a delta, a pace verdict).
   Everything else stays --ink/--muted. Chart series keep their own palette (charts.js PALETTE),
   untouched by this rule. */
.up { color:var(--green); } .down { color:var(--red); } .flat { color:var(--muted); }
.warn { color:var(--amber); }
.bd { display:inline-block; font-size:10.5px; font-weight:500; color:var(--muted); background:var(--bg); border:1px solid var(--line); border-radius:4px; padding:1px 6px; margin-left:4px; white-space:nowrap; }

/* Elapsed-vs-achieved pacing (Carlos, 2026-09-20): a track with two markers — the achieved-%
   FILL and a vertical TICK at elapsed-% ("today"). Coloured only when behind beyond the stated
   tolerance (pacing.js's paceGapState) — same "colour with meaning only" rule as .up/.down above. */
.pace-metric { margin:0 0 var(--sp-3); }
.pace-metric:last-child { margin-bottom:0; }
.pace-metric-l { font-size:var(--fs-1); color:var(--muted); margin-bottom:2px; }
.pacebar { position:relative; height:10px; background:var(--grey); border-radius:5px; margin:2px 0 4px; }
.pacebar-fill { position:absolute; top:0; left:0; height:100%; min-width:2px; border-radius:5px; background:var(--blue); transition:width .2s; }
.pacebar-fill.behind { background:var(--red); }
.pacebar-mark { position:absolute; top:-3px; width:2px; height:16px; background:var(--ink); }
.pace-line { margin:0; font-size:var(--fs-1); }

.chart { width:100%; height:300px; }
.chart.sm { height:120px; }
.grid2 { display:grid; grid-template-columns:repeat(auto-fit,minmax(420px,1fr)); gap:var(--sp-4); }
.multiples { display:grid; grid-template-columns:repeat(auto-fill,minmax(250px,1fr)); gap:var(--sp-2); }
/* A grid item's default automatic minimum width is its content's min-content size — for a card
   whose content is a wide `white-space:nowrap` table, that min-content can be 900px+, which
   silently widens the WHOLE grid track (and the page) even though the table's own `.scroll`
   wrapper already scrolls horizontally on its own. min-width:0 tells the grid item to defer to
   that inner scroll instead of protecting the table's full width (verified: this is what
   actually widened the page at 390px on the Search NB/Search Brand "Top keywords" cards, not
   the grid or the table's own overflow rule, which were both already correct in isolation). */
.grid2 > .card, .smgrid > .sm-card, .multiples > div { min-width:0; }

/* ---- Tables --------------------------------------------------------------------------------
   Dense in the operator views (Explore, Campaign, the channel tabs), one shared look everywhere:
   tabular numbers so a column of money/ratio values lines up on the decimal point, right-aligned
   numeric cells, a sticky header row that tucks under the fixed top bar (--topbar-h, measured by
   app.js) rather than being painted over by it. */
.scroll { overflow-x:auto; }
table { border-collapse:collapse; width:100%; font-size:var(--fs-1); font-variant-numeric:tabular-nums; }
th, td { padding:6px var(--sp-2); border-bottom:1px solid #efefe8; text-align:right; white-space:nowrap; }
th { color:var(--muted); font-weight:600; cursor:pointer; }
th:first-child, td:first-child { text-align:left; }
/* Sticky header row (Task 6/7) — opt-in via `.stickytable`, not every table. A sticky element is
   clamped inside its own containing block: on a SHORT table (Channel mix, 4 rows) once the reader
   scrolls the table mostly out of view, the stuck header has nowhere left to go but the table's
   own bottom edge, where it visually overlaps the last data row (verified in a real browser, not
   just a screenshot artifact — the underlying DOM/data is correct). That only actually matters
   once a table is long enough to be worth pinning a header on in the first place — Explore's own
   table (dozens of campaigns) is the case this was built for. */
/* 2026-09-20: `top:var(--topbar-h)` pinned the header to the PAGE scroll, and a sticky element is
   clamped inside its own containing block — so once the table scrolled mostly past, the header sat
   on top of the last data rows (Carlos hit exactly this in Explore's grouped view: the column
   titles rendered across the Nervalley/Rejuvapaw rows). The fix is to give the table its own
   scrollport: the header then sticks to the top of THAT box and can never travel over a row. */
.scroll:has(> .stickytable) { max-height:78vh; overflow:auto; }
.stickytable th { position:sticky; top:0; background:#fff; z-index:2; box-shadow:0 1px 0 var(--line); }
.stickytable th:first-child { z-index:4; }
tr.g1 td:first-child { font-weight:600; } tr.g2 td:first-child { padding-left:22px; } tr.g3 td:first-child { padding-left:40px; }
td.name { max-width:340px; overflow:hidden; text-overflow:ellipsis; }
a.link { color:var(--blue); text-decoration:none; cursor:pointer; }
ul.changed { margin:0; padding-left:18px; } ul.changed li { margin:3px 0; }
.error { position:fixed; bottom:var(--sp-3); left:var(--sp-3); right:var(--sp-3); background:var(--red); color:#fff; padding:10px 14px; border-radius:var(--radius-md); }

/* A wide table scrolls INSIDE its own card (never the page) with its first column pinned, so a
   long row of numeric columns stays legible on a 390px phone without losing which campaign/
   brand/keyword each row is. Kept for every width, not phone-only — it costs nothing on a table
   that never needs to scroll horizontally in the first place. */
.scroll table th:first-child, .scroll table td:first-child { position:sticky; left:0; z-index:3; background:var(--card); }
.scroll table th:first-child { background:#fff; }

@media (max-width:640px) {
  .grid2 { grid-template-columns:1fr; }
  .tabs a.v1 { margin-left:0; }
  .view { padding:var(--sp-3); }
}

/* Long prose cells (e.g. action gates) wrap instead of widening the table. */
td.wrapcell { white-space: normal; min-width: 16rem; max-width: 32rem; }
