/* ===========================================================================
   Design tokens — §1 ui-brief.md. No colour outside this list.
   =========================================================================== */
:root {
  color-scheme: light dark;
  --control-line: 18px;

  --bg:        #f4f6fb;
  --surface:   #ffffff;
  --surface-2: #f8fafc;
  --border:    #e6eaf2;
  --text:      #0f172a;
  --muted:     #64748b;
  --track:     #eef1f6;

  --brand:      #2563eb;
  --brand-soft: #e8efff;
  --link:       var(--brand);

  --ok:     #16a34a;  --ok-soft:     #e7f7ed;
  --warn:   #f59e0b;  --warn-soft:   #fef3c7;
  --bad:    #dc2626;  --bad-soft:    #fdecec;
  --violet: #7c3aed;  --violet-soft: #f1e9ff;
  --cyan:   #0891b2;  --cyan-soft:   #e0f5fa;

  --ok-text:  #15803d;
  --bad-text: var(--bad);

  --sidebar:        #0f1b34;
  --sidebar-2:      #16233f;
  --sidebar-text:   #c3cddf;
  --sidebar-muted:  #7b8aa5;
  /* Dark sidebar on a light page separates itself; on a dark page it does not,
     so the edge becomes a real line only in dark mode. */
  --sidebar-border: transparent;

  --radius: 12px;
  --shadow: 0 1px 2px rgba(15,23,42,.05), 0 1px 3px rgba(15,23,42,.04);

  --scroll-thumb:       #c9d2e1;
  --scroll-thumb-hover: #a6b3c9;
  --sidebar-scroll-thumb:       #2b3a58;
  --sidebar-scroll-thumb-hover: #3b4d72;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0b1220;  --surface: #121a2b;  --surface-2: #172136;
    --border: #24314b;  --text: #e6ebf5;  --muted: #94a3b8;  --track: #1e293b;
    --brand-soft: #1c2b52;
    --link: var(--text);
    --ok-text: var(--ok);  --bad-text: #f87171;
    --ok-soft: #12301f;  --warn-soft: #382b10;  --bad-soft: #3a1717;
    --violet-soft: #271b45;  --cyan-soft: #10303a;
    --sidebar: #0a1122;  --sidebar-2: #131d33;  --sidebar-border: #24314b;
    --scroll-thumb: #2f3d59;  --scroll-thumb-hover: #415275;
    --sidebar-scroll-thumb: #24314b;  --sidebar-scroll-thumb-hover: #33456a;
    --shadow: none;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0b1220;  --surface: #121a2b;  --surface-2: #172136;
  --border: #24314b;  --text: #e6ebf5;  --muted: #94a3b8;  --track: #1e293b;
  --brand-soft: #1c2b52;
  --link: var(--text);
  --ok-text: var(--ok);  --bad-text: #f87171;
  --ok-soft: #12301f;  --warn-soft: #382b10;  --bad-soft: #3a1717;
  --violet-soft: #271b45;  --cyan-soft: #10303a;
  --sidebar: #0a1122;  --sidebar-2: #131d33;  --sidebar-border: #24314b;
  --scroll-thumb: #2f3d59;  --scroll-thumb-hover: #415275;
  --sidebar-scroll-thumb: #24314b;  --sidebar-scroll-thumb-hover: #33456a;
  --shadow: none;
}
:root[data-theme="light"] { color-scheme: light; }

/* ===========================================================================
   Reset + typography — §2
   =========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

/* The document is a fixed-size frame; scrolling happens in .main. Without this
   the browser paints its own page scrollbar over the app — unthemed, and in an
   embedded deployment it belongs to the host page where no rule here reaches it. */
html, body { height: 100%; overflow: hidden; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { margin: 0; }
h1 { font-size: 22px; font-weight: 600; letter-spacing: -.02em; }
h2 { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; min-width: 0; }
p  { margin: 0; }
a  { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.tiny  { font-size: 10.5px; }
.mono  { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.nowrap { white-space: nowrap; }
.align-right { text-align: right; }
.icon { flex: none; }

:focus-visible, input:focus, select:focus, textarea:focus, button:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

/* Scrollbar — §8
   The page itself never scrolls (see .app / .main below), so every scrollbar on
   screen belongs to an element inside this stylesheet and takes these colours.
   A pill thumb floating on a transparent track, inset by its own border so it
   reads as part of the surface rather than a browser chrome bolted to the edge. */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-button { display: none; }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: var(--scroll-thumb);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: padding-box;
  transition: background-color .12s ease-out;
}
::-webkit-scrollbar-thumb:hover  { background-color: var(--scroll-thumb-hover); }
::-webkit-scrollbar-thumb:active { background-color: var(--brand); }

/* Inside panels a 12px bar is heavy next to 13px text — the same pill, narrower. */
.sidebar::-webkit-scrollbar,
.select-menu::-webkit-scrollbar,
.table-wrap::-webkit-scrollbar,
#detailWrap::-webkit-scrollbar { width: 9px; height: 9px; }
.sidebar::-webkit-scrollbar-thumb,
.select-menu::-webkit-scrollbar-thumb,
.table-wrap::-webkit-scrollbar-thumb,
#detailWrap::-webkit-scrollbar-thumb { border-width: 2px; }

.sidebar::-webkit-scrollbar-thumb { background-color: var(--sidebar-scroll-thumb); }
.sidebar::-webkit-scrollbar-thumb:hover { background-color: var(--sidebar-scroll-thumb-hover); }

/* Firefox has no ::-webkit-scrollbar; it gets the same two colours the other way. */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }
  .sidebar { scrollbar-color: var(--sidebar-scroll-thumb) transparent; }
}

/* ===========================================================================
   App shell — §3
   =========================================================================== */
.app {
  position: relative;
  display: grid;
  grid-template-columns: 236px 1fr;
  height: 100vh;
  height: 100dvh;   /* dvh keeps the shell honest when a mobile URL bar slides away */
  overflow: hidden;
  transition: opacity 120ms ease-out, grid-template-columns 160ms ease-out;
}
.app.is-entering { opacity: 0; }
.app.is-collapsed { grid-template-columns: 68px 1fr; }
@media (prefers-reduced-motion: reduce) { .app { transition: none; } }

.sidebar {
  background: var(--sidebar);
  border-right: 1px solid var(--sidebar-border);
  color: var(--sidebar-text);
  padding: 18px 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  height: 100%;
  overflow-y: auto;
  min-width: 0;
}
.brand { display: flex; align-items: center; gap: 11px; min-width: 0; }
.brand-mark {
  width: 36px; height: 36px; border-radius: 10px; flex: none;
  background: linear-gradient(140deg, #3b82f6, #2563eb);
  color: #fff; display: grid; place-items: center;
}
.brand-name { font-size: 15px; font-weight: 600; color: #fff; line-height: 1.2; }
.brand-tag  { font-size: 11px; color: var(--sidebar-muted); }

/* Both hidden on desktop, where the rail toggle on the sidebar edge does this job.
   `.topbar` prefix is load-bearing: #btnMenu also carries .icon-button, whose
   `display: grid` sits later in this file at equal specificity and would win. */
.topbar .nav-open { display: none; }
.nav-close {
  display: none; margin-left: auto; flex: none;
  width: 36px; height: 36px; border-radius: 9px; padding: 0;
  border: 1px solid rgba(255,255,255,.14); background: transparent;
  color: var(--sidebar-text); place-items: center; cursor: pointer;
}
.nav-close:hover { background: var(--sidebar-2); color: #fff; }

.nav-scrim { position: fixed; inset: 0; z-index: 55; background: rgba(15,23,42,.55); }

.nav { display: flex; flex-direction: column; gap: 3px; }
.nav-heading {
  font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--sidebar-muted); padding: 12px 11px 5px;
}
.nav-link {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 11px; border-radius: 9px;
  font-size: 13.5px; color: var(--sidebar-text);
  text-decoration: none; cursor: pointer; border: 0; background: none;
  width: 100%; text-align: left; font-family: inherit;
}
.nav-link:hover { background: var(--sidebar-2); color: #fff; text-decoration: none; }
.nav-link.is-active { background: var(--brand); color: #fff; font-weight: 600; }

.sidebar-foot {
  margin-top: auto; padding-top: 14px;
  border-top: 1px solid rgba(255,255,255,.08);
  display: flex; flex-direction: column; gap: 10px;
}
.user-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.avatar {
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  background: var(--sidebar-2); color: #fff;
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
}
.user-meta { min-width: 0; }
.user-name  { display: block; font-size: 12.5px; color: #fff; font-weight: 600;
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-transform: capitalize; }
.user-email { display: block; font-size: 11px; color: var(--sidebar-muted);
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-version { font-size: 10.5px; color: var(--sidebar-muted); }

/* When collapsed the labels are clipped, not display:none — screen readers keep them. */
.app.is-collapsed .collapsible {
  position: absolute; width: 1px; height: 1px;
  clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
/* No align-items:center here: .sidebar is a flex column, and centering shrinks
   every child to its content width — .nav-link{width:100%} then resolves to just
   the icon and the active/hover pill nearly disappears. Keep the children full
   width and centre the contents of each item instead. */
.app.is-collapsed .sidebar { padding: 18px 10px 14px; }
.app.is-collapsed .brand { justify-content: center; }
.app.is-collapsed .nav { align-items: stretch; }
.app.is-collapsed .nav-link { justify-content: center; padding: 10px 0; }
.app.is-collapsed .nav-heading {
  height: 1px; padding: 0; margin: 10px 4px; background: rgba(255,255,255,.08); overflow: hidden;
}
.app.is-collapsed .user-row { justify-content: center; }

/* Collapse toggle — a circled arrow straddling the sidebar's edge, vertically
   level with the logo (sidebar padding-top 18px + half of the 36px mark = 36px).
   position:fixed rather than absolute: the sidebar owns a scroll container that
   would clip it, and the button must stay put while the page scrolls. */
.rail-toggle {
  position: fixed; top: 36px; left: 236px;
  transform: translate(-50%, -50%);
  width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  display: grid; place-items: center; cursor: pointer; padding: 0;
  z-index: 30;
  transition: left 160ms ease-out, color .1s, border-color .1s;
}
.rail-toggle:hover { color: var(--brand); border-color: var(--brand); }
.app.is-collapsed .rail-toggle { left: 68px; }
@media (prefers-reduced-motion: reduce) { .rail-toggle { transition: none; } }

/* min-height:0 lets .main shrink inside the fixed-height grid row instead of
   pushing the shell taller; .content below is what actually scrolls. */
.main { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }

.topbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 24px;
}
.topbar-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* 7 + 18 + 7 + 2px border = 34px, the same height as the icon buttons beside it. */
.topbar .select-trigger {
  min-width: 0; padding: 7px 10px; font-size: 13px; line-height: 18px;
  border-radius: 9px; background: var(--surface); gap: 7px;
}
.topbar .select-trigger .st-icon { display: grid; color: var(--muted); flex: none; }
.topbar .select-trigger .st-label { font-weight: 600; }
/* margin-inline:auto keeps the search centred now that nothing sits to its left. */
.search-shell { position: relative; flex: 1; max-width: 560px; min-width: 0; margin-inline: auto; }
.search-shell .search-icon {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--muted); pointer-events: none;
}
/* Specific enough to beat input[type=text] in §5.5: that rule sits later in the
   file at equal specificity, so it used to swallow the 36px padding-left and the
   icon ended up printed on top of the text. */
.search-shell input[type=text] {
  width: 100%; padding: 9px 12px 9px 36px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text);
  font-family: inherit; font-size: 13.5px; line-height: var(--control-line);
}

.icon-button {
  width: 34px; height: 34px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--muted); display: grid; place-items: center; cursor: pointer; flex: none;
}
.icon-button:hover { color: var(--brand); }
.icon-button.spin svg { animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .icon-button.spin svg { animation: none; } }

.chip-soft {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 11px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border);
  font-size: 12.5px; color: var(--muted); white-space: nowrap;
}
.chip-soft.is-stale { color: var(--warn); border-color: var(--warn); }
.chip-soft.is-error { color: var(--bad);  border-color: var(--bad); }

/* The one scrolling region of the app. Scrolling here rather than on .main
   keeps the bar under the topbar instead of running the full window height past
   it, and scroll-padding stops a section jumped to from the nav landing behind
   the topbar's lower edge. */
.content {
  padding: 22px 24px 32px;
  display: flex; flex-direction: column; gap: 18px; min-width: 0;
  flex: 1; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  scroll-padding-top: 12px;
  scrollbar-gutter: stable;
}
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.page-quote { font-style: italic; font-size: 13px; color: var(--muted); }

.section-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 4px; flex-wrap: wrap;
}
.section-head h2 { font-size: 15px; }

/* ===========================================================================
   Grid — §4
   =========================================================================== */
/* min(Npx, 100%) chứ không phải Npx trần: khi container hẹp hơn ngưỡng min,
   minmax() vẫn giữ nguyên track ở Npx và đẩy cả trang cuộn ngang. Trên màn 390px
   thì .grid-2 tạo ra card 420px — đúng bằng số ở đây. */
/* .kpis is capped at 6 per row. 12 primary KPIs then read as two even rows and
   the 6 secondary ones as one, instead of the 7 or 8 a wide screen used to fit
   — at which point the second row was a ragged remainder and the eye had no
   column to follow down.

   Still auto-fit, still no manual breakpoint (§4): the track minimum is
   whichever is LARGER of 178px and the width six columns would each get. Wide
   container -> the six-column share wins and nothing more fits. Narrow -> 178px
   wins and it behaves exactly as before. The outer min(100%, …) is the original
   guard against a container narrower than one card pushing the page sideways.

   The 3px is slack. Six tracks at exactly (100% - 5*gap)/6 come to precisely
   100%, and sub-pixel rounding can drop that to five columns; shaving the
   minimum keeps the sixth in without ever letting a seventh fit.

   Changing the cap means changing two numbers: the multiplier on the gap
   (cols - 1) and the divisor (cols). */
.kpis   { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(min(100%, max(178px, (100% - 5 * 14px - 3px) / 6)), 1fr)); align-items: stretch; }
.grid-4 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr)); align-items: stretch; }
.grid-3 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr)); align-items: stretch; }
.grid-2 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); align-items: stretch; }

/* ===========================================================================
   Card — §5.1
   =========================================================================== */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  display: flex; flex-direction: column; min-width: 0;
}
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 13px 16px; border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.card-body { padding: 14px 16px; flex: 1; min-width: 0; }
.card-body.tight { padding: 0; }
.card-body.tight th, .card-body.tight td { padding: 8px 12px; font-size: 12.5px; }
.card-icon { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 8px; flex: none; }
.link-action { font-size: 12.5px; font-weight: 600; cursor: pointer; background: none; border: 0;
               color: var(--link); font-family: inherit; padding: 0; }

.tone-blue   { background: var(--brand-soft);  color: var(--brand); }
.tone-green  { background: var(--ok-soft);     color: var(--ok); }
.tone-red    { background: var(--bad-soft);    color: var(--bad); }
.tone-amber  { background: var(--warn-soft);   color: var(--warn); }
.tone-violet { background: var(--violet-soft); color: var(--violet); }
.tone-cyan   { background: var(--cyan-soft);   color: var(--cyan); }

/* ===========================================================================
   KPI — §5.2
   =========================================================================== */
.kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 16px;
  min-width: 0;
}
.kpi-top   { display: flex; align-items: center; gap: 10px; min-width: 0; }
.kpi-icon  { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; flex: none; }
.kpi-label { font-size: 12.5px; color: var(--muted); font-weight: 600;
             white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kpi-value { font-size: 27px; font-weight: 700; letter-spacing: -.03em; margin-top: 10px; }
.kpi-hint  { font-size: 12px; margin-top: 2px; min-height: 18px; }
.kpi-hint.ok   { color: var(--ok-text); }
.kpi-hint.bad  { color: var(--bad-text); }
.kpi-hint.warn { color: var(--warn); }
.kpi-spark { margin-top: 8px; height: 30px; }

/* ===========================================================================
   Table — §5.3
   =========================================================================== */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td {
  text-align: left; padding: 9px 16px;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
thead th {
  color: var(--muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
  background: var(--surface-2); position: sticky; top: 0; z-index: 3;
}
thead th.sortable { cursor: pointer; user-select: none; }
thead th.sortable:hover { color: var(--text); }
thead th.is-sorted { color: var(--text); }
thead th .caret { font-size: 9px; margin-left: 3px; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--surface-2); }
tfoot td {
  background: var(--surface-2); font-weight: 700; border-top: 1px solid var(--border);
  border-bottom: 0; position: sticky; bottom: 0; z-index: 3;
}
td.wrap { white-space: normal; max-width: 320px; overflow-wrap: anywhere; }
.row-index { color: var(--muted); width: 28px; }
.num { font-variant-numeric: tabular-nums; }

/* Heat cells for CR / ROAS — the fill tracks the value, but the number is always there.

   z-index:0 on the cell itself is load-bearing, not decoration: with only
   position:relative (z-index:auto) the cell creates no stacking context, so
   .heat-v{z-index:1} is hoisted into the same context as thead th{z-index:3}.
   Same context means DOM order decides, tbody comes after thead, and the CR/ROAS
   numbers paint straight over the column headers while the table scrolls. */
.heat { position: relative; z-index: 0; }
.heat .heat-bg { position: absolute; inset: 2px; border-radius: 5px; z-index: 0; }
.heat .heat-v  { position: relative; z-index: 1; }

/* ===========================================================================
   Chip / pill / dot — §5.4
   =========================================================================== */
.chip { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px; font-weight: 700; }
.chip-up   { background: var(--ok-soft);  color: var(--ok); }
.chip-down { background: var(--bad-soft); color: var(--bad); }
.chip-flat { background: var(--track);    color: var(--muted); }

.pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 600; }
.pill-ok { background: var(--ok-soft); color: var(--ok); }
.pill-bad { background: var(--bad-soft); color: var(--bad); }
.pill-warn { background: var(--warn-soft); color: #92400e; }
.pill-neutral { background: var(--track); color: var(--muted); }

.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex: none; }
.dot-ok { background: var(--ok); } .dot-warn { background: var(--warn); } .dot-bad { background: var(--bad); }

.cell-chip {
  display: inline-block; padding: 1px 6px; border-radius: 6px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 12.5px; overflow-wrap: anywhere;
}

/* ===========================================================================
   Buttons & inputs — §5.5
   =========================================================================== */
.btn {
  border: 1px solid transparent; border-radius: 9px; padding: 10px 14px;
  font-size: 14px; font-weight: 600; line-height: var(--control-line);
  background: var(--brand); color: #fff; cursor: pointer; white-space: nowrap;
  font-family: inherit;
}
.btn:hover { filter: brightness(1.05); }
.btn:disabled { opacity: .6; cursor: not-allowed; }
.btn-ghost { background: var(--surface); color: var(--text); border-color: var(--border); font-weight: 500; }
.btn-sm { padding: 5px 10px; font-size: 12.5px; line-height: 16px; border-radius: 7px; }

input[type=text], input[type=date], input[type=number], select {
  width: 100%; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface-2); color: var(--text);
  font-family: inherit; font-size: 14px; line-height: var(--control-line);
  /* Ellipsis applies to the placeholder too, so a long hint ends in "…" instead
     of being chopped mid-word. The full text lives in title= for the hover. */
  text-overflow: ellipsis;
}
input[type=date] { min-width: 150px; }

.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.toolbar > .btn, .toolbar > .select-shell { flex: none; }

/* The filter row spans the full width instead of leaving a ragged gap on the
   right: equal columns, as many as fit at 150px, each stretching to 1fr.

   auto-FIT, not auto-fill. auto-fill keeps every track it can make room for, so
   7 filters in a row wide enough for 8 left an empty 8th column — the gap on the
   right. auto-fit drops a track only when it is empty across the WHOLE grid,
   which happens exactly when there are more columns than filters; a filter left
   alone on a second row still sits in an occupied track and keeps its width. */
#filterBar {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
}
#filterBar > * { min-width: 0; }
#filterBar .select-trigger, #filterBar input[type=text] { min-width: 0; }

.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field-label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 600; }

/* ===========================================================================
   Custom select — §5.6
   =========================================================================== */
/* A bare <input> is intrinsically ~20 characters wide, so a free-text filter came
   out wider than the 150px dropdowns next to it. size=1 in the markup kills that
   intrinsic width and lets min-width line them up exactly. */
.select-shell { position: relative; min-width: 0; }
.select-shell > input[type=text] { min-width: 150px; }
.select-trigger {
  display: flex; align-items: center; gap: 8px; justify-content: space-between;
  width: 100%; min-width: 150px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface-2); color: var(--text);
  font-family: inherit; font-size: 14px; line-height: var(--control-line);
  cursor: pointer; text-align: left;
}
.select-trigger.is-open { border-color: var(--brand); }
/* Compact variant for dropdowns that sit beside .btn-sm controls. */
.select-shell.is-compact .select-trigger {
  min-width: 108px; padding: 5px 10px; font-size: 12.5px;
  line-height: 16px; border-radius: 7px; gap: 6px;
}
.select-trigger .st-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-trigger .st-count {
  font-size: 11.5px; font-weight: 700; color: var(--brand);
  background: var(--brand-soft); border-radius: 999px; padding: 0 7px; flex: none;
}
.select-trigger .st-caret { color: var(--muted); flex: none; }

.select-menu {
  position: fixed; z-index: 60;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 9px; box-shadow: 0 14px 34px rgba(15,23,42,.18);
  max-height: 300px; overflow-y: auto; padding: 4px; min-width: 200px;
}
.select-menu .sm-search { padding: 4px; position: sticky; top: -4px; background: var(--surface); }
.select-menu .sm-search input { padding: 7px 10px; font-size: 13px; }
.select-menu .sm-actions {
  display: flex; gap: 8px; padding: 4px 6px 6px; border-bottom: 1px solid var(--border); margin-bottom: 4px;
}
.select-option {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 10px; border-radius: 7px; font-size: 13.5px; cursor: pointer;
}
.select-option:hover, .select-option.is-active { background: var(--surface-2); }
.select-option.is-selected { color: var(--brand); font-weight: 600; }
.select-option .so-check { width: 14px; flex: none; }
.select-option .so-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.select-option .so-count { font-size: 11px; color: var(--muted); flex: none; }

/* ===========================================================================
   Charts — §6
   =========================================================================== */
.chart { width: 100%; display: block; overflow: visible; }
.donut-wrap { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.donut-wrap svg { flex: none; }
.legend { display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 140px; font-size: 12.5px; }
.legend-row { display: flex; align-items: center; gap: 8px; }
.legend-row .dot { width: 9px; height: 9px; }
.legend-label { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.legend-value { margin-left: auto; font-weight: 600; }
.legend-pct { width: 52px; text-align: right; color: var(--muted); }

.series-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-bottom: 8px; font-size: 12px; }
.series-item { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
.series-item[data-series] { cursor: pointer; }   /* only the toggleable ones */
.series-item .swatch { width: 12px; height: 3px; border-radius: 2px; flex: none; }
.series-item.is-muted { opacity: .35; }

.bars { display: flex; align-items: flex-end; gap: 10px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
.bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0; }
.bar-value { font-size: 13px; font-weight: 700; }
.bar-track { width: 100%; max-width: 58px; background: var(--track); border-radius: 8px;
             display: flex; align-items: flex-end; height: var(--bar-height, 120px); }
.bar-fill  { width: 100%; border-radius: 8px 8px 0 0; min-height: 3px; }
.bar-label { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; max-width: 100%; }


.funnel { width: 100%; }
.funnel svg { width: 100%; height: auto; display: block; }

/* Time range popover: presets above, calendar below, one control overall.
   Taller than the default 300px: at 300 the Apply button fell below the menu's
   own scroll line, so the primary action was invisible until you scrolled. */
.tr-menu { padding-bottom: 8px; max-height: min(520px, 88vh); width: 300px; }
.tr-custom { border-top: 1px solid var(--border); margin-top: 4px; padding: 0 6px; }
.tr-foot { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.tr-summary { flex: 1; min-width: 0; }
.tr-apply { flex: none; }

/* -------------------------------- calendar -------------------------------- */
.cal { user-select: none; }
.cal-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 2px 0 6px; }
.cal-title { font-size: 13px; font-weight: 600; }
.cal-nav {
  width: 26px; height: 26px; border-radius: 7px; padding: 0;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--muted); display: grid; place-items: center; cursor: pointer;
}
.cal-nav:hover { color: var(--brand); border-color: var(--brand); }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-dow span {
  text-align: center; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); padding-bottom: 4px;
}
.cal-day {
  aspect-ratio: 1; min-height: 30px; border: 0; padding: 0;
  background: transparent; color: var(--text);
  font: inherit; font-size: 12.5px; border-radius: 7px;
  cursor: pointer; display: grid; place-items: center;
}
.cal-day:hover:not(:disabled) { background: var(--surface-2); }
.cal-day.is-out { color: var(--muted); opacity: .5; }
.cal-day:disabled { color: var(--muted); opacity: .3; cursor: not-allowed; }
/* Today is marked by an outline, so it survives being inside a selected range. */
.cal-day.is-today { box-shadow: inset 0 0 0 1px var(--border); }
.cal-day.is-in { background: var(--brand-soft); border-radius: 0; }
.cal-day.is-start, .cal-day.is-end {
  background: var(--brand); color: #fff; font-weight: 600; opacity: 1;
}
.cal-day.is-start { border-radius: 7px 0 0 7px; }
.cal-day.is-end   { border-radius: 0 7px 7px 0; }
.cal-day.is-start.is-end { border-radius: 7px; }
.co-group { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
            color: var(--muted); padding: 8px 4px 5px; }

/* The column picker reuses .select-menu; this is only its checkbox row. */
.col-option { display: flex; align-items: center; gap: 9px; padding: 7px 10px;
              border-radius: 7px; font-size: 13.5px; cursor: pointer; }
.col-option:hover { background: var(--surface-2); }
.col-option.is-on { color: var(--brand); font-weight: 600; }
.col-option .co-check { width: 14px; flex: none; }

/* Line chart tooltip — floats like the select menu, same surface and shadow. */
.chart-tip {
  position: fixed; z-index: 70; pointer-events: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 9px; box-shadow: 0 14px 34px rgba(15,23,42,.18);
  padding: 9px 11px; font-size: 12.5px; min-width: 150px; max-width: 260px;
}
.chart-tip .tip-title { font-weight: 700; margin-bottom: 5px; }
.chart-tip .tip-row { display: flex; align-items: center; gap: 8px; }
.chart-tip .tip-row .swatch { width: 12px; height: 3px; border-radius: 2px; flex: none; }
.chart-tip .tip-row .tip-label { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chart-tip .tip-row .tip-val { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }

/* Heatmap */
.heatmap { width: 100%; border-collapse: separate; border-spacing: 2px; font-size: 12px; }
.heatmap th { background: transparent; position: static; text-transform: none;
              letter-spacing: 0; font-size: 11px; padding: 4px 6px; }
.heatmap td { border: 0; padding: 0; }
.heatmap .hm-cell {
  border-radius: 6px; padding: 7px 8px; text-align: right;
  font-variant-numeric: tabular-nums; min-width: 62px;
}

/* ===========================================================================
   States — §8
   =========================================================================== */
.state {
  padding: 26px 16px; text-align: center; color: var(--muted); font-size: 13px;
  display: flex; flex-direction: column; gap: 8px; align-items: center;
}
.state-error { color: var(--bad); }
/* The boot screen lives outside .app, so it needs its own scroll: body no
   longer provides one and the start-up error screen is a long read. */
#boot { height: 100%; overflow-y: auto; justify-content: center; }
.skeleton {
  background: linear-gradient(90deg, var(--track) 25%, var(--surface-2) 37%, var(--track) 63%);
  background-size: 400% 100%; animation: shimmer 1.3s ease-in-out infinite;
  border-radius: 6px;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }

.banner {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 14px; border-radius: var(--radius);
  border: 1px solid var(--warn); background: var(--warn-soft); color: #92400e; font-size: 13px;
}
.banner.is-error { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); }

.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px;
         padding: 10px 16px; border-top: 1px solid var(--border); font-size: 12.5px;
         color: var(--muted); flex-wrap: wrap; }
.pager-controls { display: flex; align-items: center; gap: 8px; }

/* ===========================================================================
   Responsive — §3
   =========================================================================== */
@media (max-width: 980px) {
  .app, .app.is-collapsed { grid-template-columns: 1fr; }

  /* The sidebar becomes an off-canvas drawer: it keeps its normal vertical
     shape and simply lives outside the viewport until asked for. Because it is
     position:fixed it leaves the grid, so .main gets the whole width. */
  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0;
    width: min(280px, 82vw); height: 100vh; z-index: 60;
    transform: translateX(-100%);
    transition: transform 180ms ease-out;
    border-right: 1px solid var(--sidebar-border); border-bottom: 0;
    padding: 16px 14px 14px; gap: 16px;
    box-shadow: 0 0 40px rgba(15,23,42,.45);
  }
  .app.is-nav-open .sidebar { transform: translateX(0); }

  .topbar .nav-open { display: grid; }
  .nav-close { display: grid; }
  .rail-toggle { display: none; }
  .nav-heading { display: none; }
  .content { padding: 16px; }
  .topbar { padding: 10px 16px; flex-wrap: wrap; }
  .search-shell { max-width: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

/* While the drawer is open the page behind it must not scroll away under it.
   The page is already locked; .main is the part that can still move. */
html.nav-locked .content { overflow: hidden; }
@media (max-width: 560px) {
  .kpis { grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); }
  .kpi-value { font-size: 22px; }
  .page-quote { display: none; }

  /* Two filters per row, always.
     The flex toolbar sizes each control by its own min-width (150px), so two of
     them plus the gap need 310px while a card at 360px viewport only offers 294
     — they dropped to one per row and the filter block got very tall. A 2-column
     grid states the intent directly; min-width:0 is what lets the controls
     shrink below that 150px floor. */
  #filterBar { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  #filterBar > * { min-width: 0; }
  #filterBar .select-trigger,
  #filterBar input[type=text] { min-width: 0; }

  /* Hamburger and search share the first row; the time range and the icon
     buttons wrap together onto the second. */
  .search-shell { flex: 1 1 200px; }
}

/* ===========================================================================
   Sign-in page
   Its own screen rather than a dialog over the dashboard: there is nothing
   behind it to see yet, and a blurred shell full of empty cards reads as a
   broken page rather than a locked one.
   =========================================================================== */
.signin {
  min-height: 100vh; display: grid; place-items: center;
  padding: 24px 16px; background: var(--bg);
  /* `body` is overflow:hidden for the dashboard shell's sake, so a card taller
     than the viewport — the blocked page on a phone — would otherwise have its
     buttons cut off with no way to reach them. */
  height: 100%; overflow-y: auto;
}
.signin-card {
  width: 100%; max-width: 420px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 28px 26px 22px;
}
.signin-brand { display: flex; align-items: center; gap: 11px; margin-bottom: 20px; }
.signin-brand .brand-mark { flex: none; }
.signin-card h1 { font-size: 19px; line-height: 26px; margin: 0 0 6px; }
.signin-card p { margin: 0 0 14px; }
.signin-list { margin: 0 0 18px; padding-left: 18px; }
.signin-list li { margin-bottom: 5px; }

/* Google's own mark keeps its colours; everything else follows the theme. */
.btn-google {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; background: var(--surface); color: var(--text);
  border: 1px solid var(--border); text-decoration: none;
}
.btn-google:hover { background: var(--surface-2); filter: none; }
.btn-google svg { flex: none; }

.signin-note {
  margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--border);
}
.signin-banner {
  border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; margin-bottom: 16px; font-size: 12.5px;
  overflow-wrap: anywhere;
}
.signin-banner.is-error { border-color: var(--bad); color: var(--bad); }
.signin-banner.is-ok { background: var(--surface-2); color: var(--muted); }

/* ===========================================================================
   Access control page (/admin)
   ===========================================================================
   Its own narrow column rather than the dashboard shell: there is no sidebar,
   no toolbar and nothing to filter here, and reusing .app would have meant
   carrying all three for a page with two tables on it. */
/* height + overflow, not just padding: `html, body` are height:100% with
   overflow:hidden so the dashboard shell can own its own scrolling, which means
   a long document here is clipped rather than scrolled unless it scrolls itself. */
.admin-page {
  height: 100%;
  overflow-y: auto;
  padding: 24px 16px 48px;
  display: flex; flex-direction: column; gap: 16px;
}
.admin-page > * { width: 100%; max-width: 980px; margin-inline: auto; }
.admin-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.admin-head h1 { font-size: 20px; margin: 0 0 4px; }
.admin-head p { margin: 0; max-width: 62ch; }

.admin-status { display: flex; gap: 28px; flex-wrap: wrap; }

.admin-row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }

/* The dashboard has no bare <input> style — every control there is a custom
   select — so these are the only inputs in the app and own their own rule.
   size=1 in the markup kills the ~20 character intrinsic width so flex and the
   table cells can size them. */
.admin-input {
  width: 100%; min-width: 0;
  padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-2); color: var(--text);
  font-family: inherit; font-size: 13px;
}
.admin-input:focus { outline: none; border-color: var(--brand); }
.admin-row .admin-input { flex: 1; }

.admin-actions { display: flex; gap: 8px; }

@media (max-width: 760px) {
  .admin-status { gap: 14px; }
}
