/* ==========================================================================
   Skumetrica — dashboard styles
   Chart series colours are the validated categorical palette (see dataviz);
   the gradients are decorative surfaces only and never encode a value.
   ========================================================================== */

:root {
  color-scheme: light;

  /* brand — sampled from the logo, not eyeballed */
  --brand:       #23466c;      /* logo navy */
  --brand-deep:  #1b3557;      /* darkened navy — the sidebar plane */
  --accent:      #f5851a;      /* logo orange */

  /* planes */
  --page:        #f4f3f7;
  --surface:     #ffffff;
  --surface-2:   #fafaf9;
  --ink:         #1b3557;      /* sidebar plane — --brand-deep, kept literal
                                  because charts.js reads this one back */
  --ink-2:       #234064;

  /* text */
  --text:        #0b0b0b;
  --text-2:      #52514e;
  --muted:       #898781;
  --on-ink:      #ffffff;
  /* Navy is a lighter plane than the near-black it replaced, so the same .62
     that gave nav labels 7.5:1 there gives 5.8:1 here. .70 restores 6.9:1.
     Both pass; this keeps the sidebar reading as it did before. */
  --on-ink-2:    rgba(255,255,255,.70);

  /* chrome */
  --grid:        #e8e7e1;
  --axis:        #d8d7d0;
  --border:      rgba(11,11,11,.09);
  --shadow:      0 1px 2px rgba(16,15,25,.04), 0 8px 24px rgba(16,15,25,.06);
  --shadow-lift: 0 2px 6px rgba(16,15,25,.07), 0 16px 40px rgba(16,15,25,.10);

  /* categorical series — validated, fixed order, never cycled.
     s1/s2 are re-based on the brand; s1 is a lightened --brand because the
     navy itself is too dark to read as a line on a white card. s3–s5 keep
     their validated values so the palette's separation survives. */
  --s1: #2f6fb0;  /* brand blue   */
  --s2: #d9700f;  /* brand orange, darkened — --accent itself is 2.5:1 on a
                     white card, under the 3:1 a line needs. This is 3.4:1. */
  --s3: #1baf7a;  /* aqua    */
  --s4: #eda100;  /* yellow  */
  --s5: #e87ba4;  /* magenta */

  /* status — reserved, never used as a series */
  --good:     #0ca30c;
  --warning:  #fab219;
  --serious:  #ec835a;
  --critical: #d03b3b;
  --up:       #006300;
  --down:     #c0322f;

  /* Decorative gradients (stat cards only). Four steps across the brand
     range; the orange tile starts on --accent exactly. The two dark steps
     carry white text, the two bright ones carry --on-tile navy — which is
     the logo's own pairing, and the only way a bright orange tile reaches
     4.5:1. Worst stop of each: 7.4, 5.2, 7.0, 4.8:1. */
  --g-navy:   linear-gradient(135deg, #2e5788 0%, #1b3557 100%);
  --g-blue:   linear-gradient(135deg, #3a6fa8 0%, #264f7d 100%);
  --g-amber:  linear-gradient(135deg, #fcb84a 0%, #f2a018 100%);
  --g-orange: linear-gradient(135deg, #f5851a 0%, #e37514 100%);
  --on-tile:  #16283f;

  --r-sm: 8px;  --r: 14px;  --r-lg: 20px;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --page: #0d0d0d;  --surface: #1a1a19;  --surface-2: #222221;
    --ink: #16283f;   --ink-2: #1e3653;
    --text: #ffffff;  --text-2: #c3c2b7;   --muted: #898781;
    --grid: #2c2c2a;  --axis: #383835;     --border: rgba(255,255,255,.10);
    --shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.3);
    --shadow-lift: 0 2px 6px rgba(0,0,0,.5), 0 16px 40px rgba(0,0,0,.45);
    --s1: #4a8ac9; --s2: #e8770f; --s3: #199e70; --s4: #c98500; --s5: #d55181;
    --up: #0ca30c;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --page: #0d0d0d;  --surface: #1a1a19;  --surface-2: #222221;
  --ink: #16283f;   --ink-2: #1e3653;
  --text: #ffffff;  --text-2: #c3c2b7;   --muted: #898781;
  --grid: #2c2c2a;  --axis: #383835;     --border: rgba(255,255,255,.10);
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.3);
  --shadow-lift: 0 2px 6px rgba(0,0,0,.5), 0 16px 40px rgba(0,0,0,.45);
  --s1: #4a8ac9; --s2: #e8770f; --s3: #199e70; --s4: #c98500; --s5: #d55181;
  --up: #0ca30c;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--sans);
  background: var(--page);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
/* --s1, not --accent: a focus ring needs 3:1 against the page, and the logo
   orange manages only 2.5:1 on white. --s1 is brand blue and themes with it. */
:focus-visible { outline: 2px solid var(--s1); outline-offset: 2px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ------------------------------------------------------------------ shell */
.shell { display: grid; grid-template-columns: 236px minmax(0, 1fr); min-height: 100vh; }

.sidebar {
  background: var(--ink);
  color: var(--on-ink);
  padding: 22px 0 28px;
  position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column;
  overflow-y: auto;
}
.brand { display: flex; align-items: center; gap: 11px; padding: 0 22px 26px; }
/* The wordmark carries the name, so no text sits beside it. object-fit keeps
   it from being squashed if the flex row ever gets narrower than the mark:
   height is pinned, so max-width alone would distort it rather than scale it. */
.brand-logo {
  height: 26px; width: auto; max-width: 100%;
  display: block; object-fit: contain; object-position: left center;
}

/* The sidebar is a dark plane in both themes, so it always takes the
   dark-background mark. Elsewhere — the sign-in page — both variants are in
   the markup and one is hidden, following the same theme signals as the
   tokens above: the media query is the default, an explicit data-theme wins. */
.logo-on-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .logo-on-light { display: none; }
  :root:where(:not([data-theme="light"])) .logo-on-dark  { display: block; }
}
:root[data-theme="dark"]  .logo-on-light { display: none; }
:root[data-theme="dark"]  .logo-on-dark  { display: block; }
:root[data-theme="light"] .logo-on-light { display: block; }
:root[data-theme="light"] .logo-on-dark  { display: none; }

/* The theme toggle shows the theme it switches TO, on the same signals as the
   logo above — the media query is the default, an explicit data-theme wins. A
   moon while already dark says nothing about what pressing it does. */
.theme-toggle .ico-sun { display: none; }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .theme-toggle .ico-moon { display: none; }
  :root:where(:not([data-theme="light"])) .theme-toggle .ico-sun  { display: block; }
}
:root[data-theme="dark"]  .theme-toggle .ico-moon { display: none; }
:root[data-theme="dark"]  .theme-toggle .ico-sun  { display: block; }
:root[data-theme="light"] .theme-toggle .ico-moon { display: block; }
:root[data-theme="light"] .theme-toggle .ico-sun  { display: none; }

.nav { display: flex; flex-direction: column; gap: 1px; padding: 0 12px; }
.nav-label {
  font-size: 10px; letter-spacing: .13em; text-transform: uppercase;
  color: rgba(255,255,255,.34); font-weight: 650;
  padding: 16px 10px 7px;
}
.nav a {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; border-radius: 9px;
  color: var(--on-ink-2); font-size: 13.5px; font-weight: 500;
  transition: background .15s, color .15s;
}
.nav a:hover { background: rgba(255,255,255,.06); color: #fff; }
.nav a.active { background: rgba(255,255,255,.10); color: #fff; font-weight: 600; }
/* An icon takes the colour of whatever it sits in.
   This used to be `color: rgba(255,255,255,.55)` on the bare .ico selector —
   written for the sidebar, which is dark, but applying everywhere. In the
   topbar, which is not, that painted white icons onto a white button: the theme
   toggle rendered completely blank and Export CSV lost its download arrow. The
   sidebar keeps its own colour; everything else inherits currentColor, which the
   SVGs already stroke with, so it follows the theme without another rule. */
.ico { width: 17px; height: 17px; flex: none; }
.nav .ico { color: rgba(255,255,255,.55); }
.nav a.active .ico { color: var(--accent); }
.nav a:hover .ico { color: #fff; }

.side-foot { margin-top: auto; padding: 18px 22px 0; }
.side-note {
  font-size: 11px; color: rgba(255,255,255,.42); line-height: 1.55;
  border-top: 1px solid rgba(255,255,255,.09); padding-top: 14px;
}

/* ------------------------------------------------------------------- main */
.main { min-width: 0; display: flex; flex-direction: column; }

.topbar {
  display: flex; align-items: center; gap: 18px;
  padding: 18px 30px; background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 40;
}
.page-title { font-size: 18px; font-weight: 650; letter-spacing: -.02em; margin: 0; }
.page-sub { font-size: 12.5px; color: var(--muted); margin-top: 1px; }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.content { padding: 24px 30px 60px; display: flex; flex-direction: column; gap: 20px; }

/* -------------------------------------------------------- profile dropdown */
/* Native <details>/<summary>, the same choice as the collapsible brief below
   — a menu that needs JavaScript to open is a menu that is sometimes shut.
   The trigger keeps looking like the ghost buttons either side of it so the
   topbar still reads as one row rather than one control standing out from
   the rest. */
.profile-menu { position: relative; }
.profile-menu > summary {
  cursor: pointer; list-style: none; user-select: none;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 12px; border-radius: 10px; border: 1px solid transparent;
}
.profile-menu > summary::-webkit-details-marker { display: none; }
.profile-menu > summary::after { content: '▾'; font-size: 9px; color: var(--muted); }
.profile-menu:hover > summary,
.profile-menu > summary:focus-visible { background: var(--surface-2); }
.profile-menu[open] > summary {
  background: var(--surface-2); border-color: var(--border);
}
.profile-menu[open] > summary::after { transform: rotate(180deg); }
/* The panel's `display` is scoped to [open], and that scoping is the whole
   point of the rule.

   A closed <details> hides its non-summary children through the user agent, and
   an author declaration for `display` beats a user-agent one for the same
   property whatever its specificity. So an unscoped `display: flex` here risks
   overriding the closed state outright and leaving the menu permanently open —
   a floating panel under the tenant name on every page of the app.

   The two disclosures this file already had, .brief-more and details.table-view,
   both set `display` only on the summary and its markers and never on the
   content. This follows them. */
.profile-menu-panel {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 50;
  min-width: 200px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lift);
  padding: 6px;
  /* The topbar only wraps below 900px — .topbar-actions is hidden for print
     and nowhere else — so this panel is reachable on a phone, anchored to the
     right edge of a trigger that may itself have wrapped. Without a ceiling a
     200px minimum plus a long email pushes it off the side of the screen. */
  max-width: calc(100vw - 32px);
}
.profile-menu[open] > .profile-menu-panel { display: flex; flex-direction: column; }
.profile-menu-account {
  padding: 8px 10px 9px; margin-bottom: 4px; border-bottom: 1px solid var(--border);
}
.profile-menu-name {
  font-size: 13px; font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.profile-menu-email {
  font-size: 11.5px; color: var(--muted); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.profile-menu-item {
  font-size: 13px; font-weight: 550; color: var(--text);
  padding: 8px 10px; border-radius: 8px;
}
.profile-menu-item:hover { background: var(--surface-2); }

/* --------------------------------------------------------------- controls */
.filters {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  max-width: 100%; min-width: 0;
}
.seg {
  display: inline-flex; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 3px; gap: 2px; box-shadow: var(--shadow);
  /* Scroll within itself on narrow screens — the page body must never scroll. */
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }
.seg a, .seg button {
  font: inherit; font-size: 12.5px; font-weight: 550;
  padding: 6px 12px; border-radius: 7px; border: 0; background: none;
  color: var(--text-2); cursor: pointer; white-space: nowrap;
  transition: background .15s, color .15s;
}
.seg a:hover, .seg button:hover { background: var(--surface-2); color: var(--text); }
.seg a.on, .seg button.on { background: var(--ink); color: #fff; }

.select {
  font: inherit; font-size: 12.5px; font-weight: 550; color: var(--text);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 7px 11px; box-shadow: var(--shadow); cursor: pointer;
}
.btn {
  font: inherit; font-size: 12.5px; font-weight: 600;
  /* inline-flex so an icon sits on the text's centre line rather than its
     baseline, which left the download arrow riding low once it became visible. */
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 15px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer;
  box-shadow: var(--shadow); transition: transform .12s, box-shadow .12s;
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-lift); }
/* Navy, not orange: white on --accent is 2.4:1. Orange stays on surfaces that
   carry no text — the focus ring, the active nav icon, the tiles. */
.btn-primary { background: var(--brand); color: #fff; border-color: transparent; }
.btn-primary:hover { background: #2c5581; }
.btn-ghost { background: none; box-shadow: none; }

/* ------------------------------------------------------------------ cards */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
  padding: 20px 22px; min-width: 0;
}
.card-head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 16px; flex-wrap: wrap; }
.card-title { font-size: 14.5px; font-weight: 650; letter-spacing: -.01em; margin: 0; }
.card-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
.card-tools { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.grid { display: grid; gap: 18px; }
.g-4 { grid-template-columns: repeat(4, minmax(0,1fr)); }
.g-3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.g-2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.g-main { grid-template-columns: minmax(0,2.15fr) minmax(0,1fr); }
.g-side { grid-template-columns: minmax(0,1fr) minmax(0,1.55fr); }

/* -------------------------------------------------------------- stat tile */
.kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 18px; }
/* Five across for pages carrying a metric and its counterpart — the two
   conversion rates only read as a pair when they sit side by side. */
.kpis.k5 { grid-template-columns: repeat(5, minmax(0,1fr)); }
/* Three across, for a page whose figures come in threes — certain, probable and
   what is about to expire only read as one thought when they share a row. Added
   after the Recovery page shipped with a `kpi-row` class nobody had written, so
   its three cards stacked vertically down the page and stopped being a
   comparison at all. */
.kpis.k3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.kpis.k2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.kpi { position: relative; }
.kpi-label {
  font-size: 11.5px; font-weight: 600; color: var(--muted);
  letter-spacing: .02em;
}
.kpi-value {
  font-size: 27px; font-weight: 680; letter-spacing: -.035em;
  margin-top: 5px; line-height: 1.1;
}
.kpi-foot { display: flex; align-items: center; gap: 8px; margin-top: 9px; flex-wrap: wrap; }
.delta {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 12px; font-weight: 650; padding: 2px 7px; border-radius: 999px;
}
.delta.up   { color: var(--up);   background: color-mix(in srgb, var(--up) 11%, transparent); }
.delta.down { color: var(--down); background: color-mix(in srgb, var(--down) 11%, transparent); }
.delta.flat { color: var(--muted); background: var(--surface-2); }
.kpi-note { font-size: 11.5px; color: var(--muted); }
.kpi-spark { position: absolute; right: 18px; top: 18px; opacity: .9; }

/* ------------------------------------------------------- gradient tiles */
.tiles { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 18px; }
.tile {
  border-radius: var(--r-lg); padding: 18px 20px 16px; color: #fff;
  position: relative; overflow: hidden; min-height: 132px;
  display: flex; flex-direction: column;
  box-shadow: 0 8px 26px rgba(16,15,25,.16);
}
.tile.navy   { background: var(--g-navy); }
.tile.blue   { background: var(--g-blue); }
.tile.amber  { background: var(--g-amber); color: var(--on-tile); }
.tile.orange { background: var(--g-orange); color: var(--on-tile); }
/* Label and note were dimmed to .93/.88, which cost roughly a third of the
   contrast and put every tile under 4.5:1 — the brightest was 1.8:1. Size and
   weight already carry the hierarchy, so they stay fully opaque. */
.tile-label { font-size: 12.5px; font-weight: 600; }
.tile-value {
  font-size: 30px; font-weight: 700; letter-spacing: -.04em;
  margin-top: 4px; line-height: 1.05;
}
.tile-foot {
  margin-top: auto; display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
}
.tile-note { font-size: 11.5px; font-weight: 500; }
.tile-art { opacity: .95; }
.tile::after {
  content: ''; position: absolute; inset: auto -30px -60px auto;
  width: 170px; height: 170px; border-radius: 50%;
  background: rgba(255,255,255,.10); pointer-events: none;
}

/* A figure that is negative because the thing went backwards, not because it is
   a cost. Used in the lift table, where a product that sold less while being
   advertised has to read differently from one that simply spent money. */
.tbl td.neg { color: var(--critical); }

/* ------------------------------------------------------------------ funnel */
/* Impressions, clicks and orders are three magnitudes about three orders of
   magnitude apart. Drawn as bars that is one bar and two invisible slivers, so
   the steps are written out and the rate between them — the part actually worth
   reading — sits in the gap. */
.funnel {
  display: flex; align-items: stretch; gap: 8px; margin: 4px 0 18px;
}
.funnel-step {
  flex: 1 1 0; min-width: 0; padding: 14px 16px;
  border: 1px solid var(--grid); border-radius: var(--r);
  background: var(--surface-2);
}
.funnel-value {
  font-size: 24px; font-weight: 700; letter-spacing: -.03em; line-height: 1.1;
}
.funnel-label {
  font-size: 12.5px; font-weight: 600; color: var(--text-2); margin-top: 3px;
}
.funnel-note { font-size: 11.5px; color: var(--muted); margin-top: 5px; min-height: 15px; }
.funnel-arrow {
  align-self: center; color: var(--muted); font-size: 17px; flex: none; padding: 0 2px;
}
@media (max-width: 640px) {
  .funnel { flex-direction: column; }
  .funnel-arrow { transform: rotate(90deg); }
}

/* ------------------------------------------------------------------ chart */
.chart { width: 100%; position: relative; }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; }
.legend { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 12px; }
.legend-item {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--text-2); font-weight: 550;
}
.legend-key { width: 11px; height: 11px; border-radius: 3px; flex: none; }
.legend-key.line { height: 3px; border-radius: 2px; width: 15px; }

.tooltip {
  position: absolute; pointer-events: none; z-index: 60;
  background: var(--ink); color: #fff; border-radius: 10px;
  padding: 9px 11px; font-size: 12px; line-height: 1.45;
  box-shadow: 0 8px 26px rgba(0,0,0,.28);
  opacity: 0; transition: opacity .1s; min-width: 132px;
  transform: translate(-50%, -100%);
}
.tooltip.on { opacity: 1; }
.tt-title { font-weight: 650; margin-bottom: 5px; font-size: 11.5px; opacity: .82; }
.tt-row { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.tt-row + .tt-row { margin-top: 3px; }
.tt-key { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.tt-val { margin-left: auto; font-weight: 650; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ table */
.tbl-wrap { overflow-x: auto; margin: 0 -22px -20px; padding: 0 22px 4px; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
table.tbl th {
  text-align: left; font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); font-weight: 650; padding: 0 14px 9px 0; white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
table.tbl td {
  padding: 11px 14px 11px 0; border-bottom: 1px solid var(--border);
  vertical-align: middle; color: var(--text-2);
}
table.tbl tr:last-child td { border-bottom: 0; }
table.tbl td.num, table.tbl th.num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 0; }
table.tbl td.strong { color: var(--text); font-weight: 600; }
/* A cell whose number is the reason to act today. The same amber the warning
   pill uses, at the weight that survives being one figure in a column of them.
   Colour is never the only carrier: every use of this also states the urgency in
   words beside it. */
table.tbl td.warn { color: #8a6100; font-weight: 650; }
table.tbl tbody tr:hover td { background: var(--surface-2); }
/* Rows that are context rather than work — a destination no shipment can reach.
   Opacity rather than a fixed grey, so it recedes by the same amount against
   either theme's surface and the pill keeps its own contrast. Kept above the
   3:1 floor for non-text, and the row's meaning is also stated in words: colour
   is never the only carrier. */
table.tbl tbody tr.muted-row td { opacity: .62; }
table.tbl tbody tr.muted-row:hover td { opacity: 1; }

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 650; padding: 3px 9px; border-radius: 999px;
  background: var(--surface-2); color: var(--text-2); white-space: nowrap;
}
.pill.good     { background: color-mix(in srgb, var(--good) 13%, transparent);     color: var(--up); }
.pill.warning  { background: color-mix(in srgb, var(--warning) 20%, transparent);  color: #8a6100; }
.pill.critical { background: color-mix(in srgb, var(--critical) 13%, transparent); color: var(--critical); }
.pill.neutral  { background: color-mix(in srgb, var(--s1) 11%, transparent);       color: var(--s1); }

.thumb {
  width: 40px; height: 40px; border-radius: 9px; object-fit: cover;
  background: var(--surface-2); border: 1px solid var(--border); flex: none;
}
.prod { display: flex; align-items: center; gap: 12px; min-width: 220px; }
.prod-name {
  font-weight: 600; color: var(--text); font-size: 13px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; max-width: 320px; line-height: 1.35;
}
.prod-meta { font-size: 11.5px; color: var(--muted); margin-top: 1px; }

/* --------------------------------------------------------------- meters */
.meter { height: 6px; border-radius: 999px; background: var(--grid); overflow: hidden; min-width: 60px; }
.meter > span { display: block; height: 100%; border-radius: 999px; background: var(--s1); }
.meter.good > span     { background: var(--good); }
.meter.warning > span  { background: var(--warning); }
.meter.critical > span { background: var(--critical); }

/* ---------------------------------------------------------------- banner */
.banner {
  display: flex; align-items: center; gap: 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--s1);
  border-radius: var(--r); padding: 13px 18px; box-shadow: var(--shadow);
  font-size: 13px; color: var(--text-2);
}
.banner.warn { border-left-color: var(--warning); }
.banner strong { color: var(--text); }
/* Links inherit colour and lose their underline globally, which is right in a
   nav and wrong in a sentence — a way out of a warning has to look like one. */
.banner a, .hint a { color: var(--s1); text-decoration: underline; }
.banner .btn { margin-left: auto; }
.spin {
  width: 15px; height: 15px; flex: none; border-radius: 50%;
  border: 2px solid var(--grid); border-top-color: var(--s1);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.empty { text-align: center; padding: 54px 20px; color: var(--muted); }
.empty h3 { color: var(--text); font-size: 15px; margin: 0 0 6px; font-weight: 650; }
.empty p { margin: 0 auto; max-width: 430px; font-size: 13px; line-height: 1.6; }

/* ---------------------------------------------------------------- misc */
.row { display: flex; align-items: center; gap: 10px; }
.between { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.muted { color: var(--muted); }
/* A form that exists only to carry one button, so the button sits in the cell
   rather than claiming a line of its own. */
form.inline { display: inline; }
.tnum { font-variant-numeric: tabular-nums; }
.mono-note { font-size: 11.5px; color: var(--muted); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
details.table-view { margin-top: 14px; }
details.table-view > summary {
  font-size: 12px; color: var(--muted); cursor: pointer; font-weight: 600;
  list-style: none; display: inline-flex; align-items: center; gap: 6px;
}
details.table-view > summary::-webkit-details-marker { display: none; }
details.table-view > summary::before { content: '▸'; font-size: 10px; }
details.table-view[open] > summary::before { content: '▾'; }
details.table-view .tbl-wrap { margin-top: 10px; max-height: 320px; overflow-y: auto; }

.hint {
  font-size: 12px; color: var(--muted); background: var(--surface-2);
  border-radius: var(--r-sm); padding: 9px 12px; line-height: 1.5;
}

/* --------------------------------------------------------------- briefing */
/* The written read on the account. Set apart from the cards below it — a thin
   accent rule rather than a different colour, because it is a different KIND of
   thing on the page (someone's reading of the figures, not the figures) and the
   reader should be able to tell at a glance without it shouting. */
.brief { margin-bottom: 20px; border-top: 3px solid var(--claude); }

/* The mark and the name, wherever Claude's own work is shown. Coral is
   Anthropic's, not this application's palette, and that is the point: the
   reader should be able to tell whose reading they are looking at. */
:root { --claude: #d97757; }
.claude-mark { flex: none; color: var(--claude); }
.claude-lockup {
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  color: var(--claude); font-weight: 650;
}
.brief .card-title { display: flex; align-items: center; gap: 9px; }

/* Everything past the summary, folded away. The report is long by design and
   the headline plus the summary is the whole of it for most mornings, so the
   rest opens on request rather than pushing the figures below the fold. Native
   <details>, so it works with JavaScript off and is keyboard-operable without
   anything being written to make it so. */
.brief-more { margin-top: 18px; }
.brief-more > summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center;
  gap: 7px; font-size: 12.5px; font-weight: 620; color: var(--claude);
  border: 1px solid var(--border); border-radius: 999px; padding: 6px 14px;
  user-select: none;
}
.brief-more > summary::-webkit-details-marker { display: none; }
.brief-more > summary:hover { background: var(--surface-2); }
.brief-more > summary::after {
  content: '▾'; font-size: 10px; transition: transform .15s ease;
}
.brief-more[open] > summary::after { transform: rotate(180deg); }
.brief-more[open] > summary { margin-bottom: 4px; }
.brief-more > summary .open-word { display: inline; }
.brief-more[open] > summary .open-word { display: none; }
.brief-more > summary .shut-word { display: none; }
.brief-more[open] > summary .shut-word { display: inline; }
.brief-headline {
  font-size: 18px; font-weight: 640; letter-spacing: -.02em; line-height: 1.35;
  margin: 2px 0 0; max-width: 76ch;
}
.brief-summary {
  font-size: 13.5px; color: var(--text-2); line-height: 1.6;
  margin: 9px 0 0; max-width: 82ch;
}
.brief-cols { display: grid; grid-template-columns: minmax(0,1.25fr) minmax(0,1fr); gap: 26px; margin-top: 20px; }
.brief-col { min-width: 0; }
.brief-h {
  font-size: 11.5px; font-weight: 650; color: var(--muted); letter-spacing: .04em;
  text-transform: uppercase; margin: 0 0 10px;
}
.brief-col .brief-h + * { margin-top: 0; }
.brief-col > .brief-h:not(:first-child) { margin-top: 22px; }

.brief-list, .brief-todo, .brief-questions { list-style: none; margin: 0; padding: 0; }
.brief-item, .brief-task {
  border-left: 2px solid var(--border); padding: 0 0 0 13px; margin-bottom: 16px;
}
.brief-item.sev-critical { border-left-color: var(--critical); }
.brief-item.sev-watch    { border-left-color: var(--warning); }
.brief-item.sev-good     { border-left-color: var(--good); }
/* Priority reads as urgency, so only the first rank is coloured — three shades
   of "important" is the same as none. */
.brief-task.p1 { border-left-color: var(--accent); }
.brief-item-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; flex-wrap: wrap; }
.brief-title { font-size: 13.5px; font-weight: 640; line-height: 1.4; }
.brief-detail { font-size: 12.5px; color: var(--text-2); line-height: 1.55; margin-top: 4px; }
.brief-meta { font-size: 11.5px; color: var(--muted); margin-top: 5px; }

.brief-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.brief-chip {
  font-size: 11.5px; color: var(--text-2); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 999px; padding: 3px 9px;
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.brief-chip b { font-weight: 680; color: var(--text); }

.brief-watch { display: flex; flex-direction: column; gap: 10px; }
.brief-watch-row {
  display: grid; grid-template-columns: auto minmax(0,1fr); column-gap: 10px;
  align-items: baseline;
}
.brief-watch-val { font-size: 17px; font-weight: 660; letter-spacing: -.03em; }
.brief-watch-lab { font-size: 12.5px; font-weight: 600; min-width: 0; }
.brief-watch-why { grid-column: 2; font-size: 11.5px; color: var(--muted); line-height: 1.45; }

.brief-questions li {
  font-size: 12.5px; color: var(--text-2); line-height: 1.5; margin-bottom: 7px;
  padding-left: 15px; position: relative;
}
.brief-questions li::before { content: '?'; position: absolute; left: 0; color: var(--muted); font-weight: 650; }

.brief-foot {
  font-size: 11.5px; color: var(--muted); line-height: 1.55;
  border-top: 1px solid var(--border); margin-top: 20px; padding-top: 12px;
}
.brief-note {
  font-size: 12px; color: var(--muted); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 9px 12px; margin-bottom: 20px;
}

/* ------------------------------------------------------------ responsive */
@media (max-width: 1280px) {
  .g-main, .g-side { grid-template-columns: minmax(0,1fr); }
  /* .kpis.k5 is listed explicitly: it outranks a bare .kpis selector, so
     without it the five-column row would survive every breakpoint below. */
  .kpis, .kpis.k5, .kpis.k3, .kpis.k2, .tiles { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .g-4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .brief-cols { grid-template-columns: minmax(0,1fr); gap: 22px; }
}
@media (max-width: 900px) {
  .shell { grid-template-columns: 1fr; }
  .sidebar {
    position: static; height: auto; flex-direction: row; align-items: center;
    overflow-x: auto; padding: 12px 16px; gap: 14px;
  }
  /* flex: none — the sidebar is a scrolling row here, and without it the nav
     squeezes the mark instead of scrolling past it. */
  .brand { padding: 0 8px 0 0; flex: none; }
  .brand-logo { height: 20px; }
  .side-foot, .nav-label { display: none; }
  .nav { flex-direction: row; padding: 0; gap: 4px; }
  .nav a span:not(.ico) { display: none; }
  .nav a { padding: 9px 11px; }
  .content, .topbar { padding-left: 16px; padding-right: 16px; }
  .g-3, .g-2, .g-4, .kpis, .kpis.k5, .kpis.k3, .kpis.k2, .tiles { grid-template-columns: minmax(0,1fr); }
  .topbar { flex-wrap: wrap; }
}
@media print {
  .sidebar, .filters, .topbar-actions { display: none; }
  .card { box-shadow: none; border: 1px solid #ddd; break-inside: avoid; }
}

/* ------------------------------------------------------------ plain pages
   Pages reached without signing in: the unsubscribe landing, and anything else
   that has to work for someone who has no session and no reason to make one.
   They carry no sidebar and no chrome, so they need their own centred column
   rather than the app frame's.

   .btn-quiet and .small were being used by recovery.php before they existed
   here, which is why its dismiss control has been rendering as an unstyled
   button. Defined once, for both. */
body.plain {
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 var(--font, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif);
  margin: 0;
}
body.plain .narrow {
  max-width: 560px;
  margin: 0 auto;
  padding: 56px 22px 72px;
}
body.plain h1 { font-size: 19px; letter-spacing: -.01em; margin: 0 0 26px; }
body.plain h2 { font-size: 17px; margin: 0 0 12px; }
body.plain p  { margin: 0 0 14px; }
body.plain form { margin: 18px 0; }

/* A control that does something small and reversible — dismiss, restore. It
   should not compete with the page's real action, so it reads as text that
   happens to be clickable. */
.btn-quiet {
  font: inherit; font-size: 12.5px;
  padding: 5px 9px; border-radius: 8px;
  border: 1px solid transparent; background: none;
  color: var(--muted); cursor: pointer;
}
.btn-quiet:hover { border-color: var(--border); color: var(--text); }

.small { font-size: 12.5px; }

/* A choice in a settings form — a radio or checkbox with its label beside it.
   Written when the alert preferences landed; the recovery and unsubscribe
   pages had already shipped using classes nobody had defined, so this one is
   defined with its markup rather than after it. */
.opt {
  display: flex; align-items: baseline; gap: 9px;
  padding: 6px 0; cursor: pointer; font-size: 14px;
}
.opt input { margin: 0; flex: 0 0 auto; accent-color: var(--brand); }

/* --------------------------------------------------------------- Ask Claude
   The question form and the history of past ones. .select already styles a
   bordered field with the app's own radius and shadow; a textarea only needs
   its cursor and width corrected — "pointer" reads as a dropdown, and nothing
   else here sets a width, so it would otherwise hug its own content. */
.ask-question {
  display: block; width: 100%; resize: vertical; cursor: text;
  font-family: inherit; line-height: 1.5; min-height: 64px;
}
.ask-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 12px;
}

/* One past question per <details>, newest first. Sharing the shape .brief-more
   already established for "fold away everything past the headline" rather
   than inventing a second disclosure pattern for the same idea. */
.ask-item { padding: 14px 18px; }
.ask-item + .ask-item { margin-top: 10px; }
.ask-item > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center;
  justify-content: space-between; gap: 12px; font-size: 13.5px; font-weight: 620;
}
.ask-item > summary::-webkit-details-marker { display: none; }
.ask-item > summary::before {
  content: '▸'; font-size: 10px; color: var(--muted); margin-right: 9px;
  transition: transform .15s ease;
}
.ask-item[open] > summary::before { transform: rotate(90deg); }
.ask-q { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ask-meta { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
.ask-body { margin-top: 14px; }
.ask-headline {
  font-size: 15.5px; font-weight: 640; letter-spacing: -.01em;
  line-height: 1.4; margin: 0 0 8px;
}
.ask-body p { font-size: 13.5px; line-height: 1.6; margin: 0 0 10px; }
.ask-body p:last-child { margin-bottom: 0; }

/* One surviving chart nomination per box, stacked on narrow histories and
   side by side once there is room for two — AskAnswer::MAX_CHARTS caps this
   at four, never so many the grid gets cramped. */
.ask-charts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px; margin: 4px 0 12px;
}
.ask-chart { min-width: 0; }
.ask-chart-title { font-size: 12.5px; font-weight: 620; margin: 0 0 8px; color: var(--text-2); }
.ask-chart-mount { min-height: 230px; }
.opt:hover { color: var(--text); }

/* Suggested changes on an Ask answer.
   The two values are the loudest thing in the block on purpose: they are what
   is being agreed to, and the reasoning under them is only why. A left border
   coloured by state so a page of history reads at a glance — proposed is the
   only one carrying buttons. */
.ask-actions { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 14px; }
.ask-actions-head { margin: 0 0 4px; font-size: 14px; }
.ask-action {
  margin-top: 10px; padding: 12px 14px;
  border: 1px solid var(--border); border-left: 3px solid var(--muted);
  border-radius: 8px; background: var(--card-alt, transparent);
}
.ask-action-proposed { border-left-color: var(--accent); }
.ask-action-applied  { border-left-color: var(--good); }
.ask-action-failed,
.ask-action-stale    { border-left-color: var(--warning); }
.ask-action-what { font-weight: 600; }
.ask-action-values { margin-top: 6px; font-size: 18px; }
.ask-action-was    { color: var(--muted); }
.ask-action-arrow  { margin: 0 8px; color: var(--muted); }
.ask-action-why    { margin-top: 8px; font-size: 13px; line-height: 1.5; }
.ask-action-buttons { margin-top: 12px; }
.ask-action-buttons form { display: flex; gap: 8px; }
.ask-action-state {
  margin-top: 10px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}
.ask-action-state form { display: inline; }

/* The wait on an Ask question. Hidden until the form is submitted; the `hidden`
   attribute does the hiding, so with no JavaScript it never appears and the
   form behaves exactly as it did before. */
.ask-working {
  display: flex; gap: 12px; align-items: flex-start;
  margin-top: 14px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 8px;
}
.ask-working[hidden] { display: none; }
