/* Categorix — chart-surface roles and palette.
   Palette is the validated reference set, used unmodified in both modes. */
:root {
  color-scheme: light;
  --surface-1: #fcfcfb;
  --plane: #f9f9f7;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --text-muted: #898781;
  --grid: #e1e0d9;
  --axis: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10);
  --series-1: #2a78d6;
  --series-2: #1baf7a;
  --series-3: #eda100;
  --series-4: #008300;
  --series-5: #4a3aa7;
  --series-6: #e34948;
  --series-7: #e87ba4;
  --series-8: #eb6834;
  --good: #0ca30c;
  --warning: #fab219;
  --serious: #ec835a;
  --critical: #d03b3b;
  --success-text: #006300;
  /* Sequential ramp for the velocity heatmap: one hue, stepped light -> dark,
     so the lightest step recedes toward the surface and reads as "near zero".
     Each step carries its own ink colour so labels stay legible on the fill. */
  --heat-0: #f2f1ee; --heat-0-ink: #52514e;
  --heat-1: #cde2fb; --heat-1-ink: #0b0b0b;
  --heat-2: #9ec5f4; --heat-2-ink: #0b0b0b;
  --heat-3: #3987e5; --heat-3-ink: #ffffff;
  --heat-4: #256abf; --heat-4-ink: #ffffff;
  --heat-5: #104281; --heat-5-ink: #ffffff;
  /* Choropleth ramp: the theme's own blue scale, so the map reads as part of
     the UI rather than beside it. Steps were chosen by search over that scale
     for perfectly even lightness rungs (L* 78/67/56/45/34, gaps 11/11/11/11) --
     even rungs are what carry a single-hue sequential ramp, since every band
     shares a hue and only lightness encodes magnitude.

     Adjacent dE sits at 12, the floor. That is expected for one hue and is fine
     here: the >=12 separation target exists for categorical palettes, where hue
     alone must distinguish series. Ink is chosen per step because the state
     labels sit directly on the fill -- the plain --heat-* blues fail that at
     3.6:1, which is why the map does not simply reuse them. */
  --map-0: #e8e6e1; --map-0-ink: #52514e;
  --map-1: #9ec5f4; --map-1-ink: #0b0b0b;
  --map-2: #6da7ec; --map-2-ink: #0b0b0b;
  --map-3: #3987e5; --map-3-ink: #0b0b0b;
  --map-4: #256abf; --map-4-ink: #ffffff;
  --map-5: #184f95; --map-5-ink: #ffffff;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --surface-1: #1a1a19;
    --plane: #0d0d0d;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted: #898781;
    --grid: #2c2c2a;
    --axis: #383835;
    --border: rgba(255, 255, 255, 0.10);
    --series-1: #3987e5;
    --series-2: #199e70;
    --series-3: #c98500;
    --series-4: #008300;
    --series-5: #9085e9;
    --series-6: #e66767;
    --series-7: #d55181;
    --series-8: #d95926;
    --success-text: #0ca30c;
    /* Same hue, stepped for the dark surface: low steps sit near the surface,
       high steps lift off it. Not an inversion of the light ramp. */
    --heat-0: #232322; --heat-0-ink: #898781;
    --heat-1: #12314f; --heat-1-ink: #c3c2b7;
    --heat-2: #184f95; --heat-2-ink: #ffffff;
    --heat-3: #2a78d6; --heat-3-ink: #ffffff;
    --heat-4: #5598e7; --heat-4-ink: #0b0b0b;
    --heat-5: #9ec5f4; --heat-5-ink: #0b0b0b;
    /* The same hue journey walked upward: on a dark surface the low band has to
       sit near the surface and the high band lift off it, so brightness still
       tracks magnitude. Each step is chosen against #1a1a19 rather than being a
       flip of the light ramp: the same five steps in mirror order, rungs
       34/45/56/67/78. */
    --map-0: #262523; --map-0-ink: #898781;
    --map-1: #184f95; --map-1-ink: #ffffff;
    --map-2: #256abf; --map-2-ink: #ffffff;
    --map-3: #3987e5; --map-3-ink: #0b0b0b;
    --map-4: #6da7ec; --map-4-ink: #0b0b0b;
    --map-5: #9ec5f4; --map-5-ink: #0b0b0b;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --surface-1: #1a1a19;
  --plane: #0d0d0d;
  --text-primary: #ffffff;
  --text-secondary: #c3c2b7;
  --text-muted: #898781;
  --grid: #2c2c2a;
  --axis: #383835;
  --border: rgba(255, 255, 255, 0.10);
  --series-1: #3987e5;
  --series-2: #199e70;
  --series-3: #c98500;
  --series-4: #008300;
  --series-5: #9085e9;
  --series-6: #e66767;
  --series-7: #d55181;
  --series-8: #d95926;
  --success-text: #0ca30c;
  --heat-0: #232322; --heat-0-ink: #898781;
  --heat-1: #12314f; --heat-1-ink: #c3c2b7;
  --heat-2: #184f95; --heat-2-ink: #ffffff;
  --heat-3: #2a78d6; --heat-3-ink: #ffffff;
  --heat-4: #5598e7; --heat-4-ink: #0b0b0b;
  --heat-5: #9ec5f4; --heat-5-ink: #0b0b0b;
  --map-0: #262523; --map-0-ink: #898781;
  --map-1: #184f95; --map-1-ink: #ffffff;
  --map-2: #256abf; --map-2-ink: #ffffff;
  --map-3: #3987e5; --map-3-ink: #0b0b0b;
  --map-4: #6da7ec; --map-4-ink: #0b0b0b;
  --map-5: #9ec5f4; --map-5-ink: #0b0b0b;
}

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--plane);
  color: var(--text-primary);
  font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}
.wrap { max-width: 1440px; margin: 0 auto; padding: 0 24px; }

/* ---- banded sections ----
   Each analytical theme gets its own full-bleed horizontal band, alternating
   surface so the deck reads as distinct chapters rather than one long scroll. */
.band { padding: 34px 0 38px; }
.band.alt { background: var(--surface-1); border-block: 1px solid var(--border); }
.sec-head { margin-bottom: 18px; max-width: 900px; }
.sec-head h2 {
  margin: 0 0 4px; font-size: 19px; font-weight: 650; letter-spacing: -0.01em;
}
.sec-head p { margin: 0; font-size: 13px; color: var(--text-secondary); }
.band.alt .card { background: var(--plane); }

/* ---- header ---- */
header.top {
  position: sticky; top: 0; z-index: 30;
  background: var(--plane); border-bottom: 1px solid var(--border);
  padding: 14px 0 0;
}
.topbar { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }

/* ---- tab navigation ---- */
.tabs {
  display: flex; gap: 2px; margin-top: 14px;
  border-bottom: 1px solid var(--border);
  /* Setting overflow on one axis makes the other compute to `auto`, not
     `visible`. The buttons' -1px bottom margin (which tucks the active
     underline over this border) then overflows by 1px and raises a vertical
     scrollbar on the right of the tab strip. Pad that pixel back and pin the
     vertical axis so only horizontal scrolling is ever offered. */
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 1px;
}
.tabs button {
  appearance: none; background: none; border: none; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 550;
  color: var(--text-secondary); padding: 9px 15px 10px;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  white-space: nowrap; border-radius: 6px 6px 0 0;
}
.tabs button:hover { color: var(--text-primary); background: var(--surface-1); }
.tabs button[aria-selected="true"] {
  color: var(--series-1); border-bottom-color: var(--series-1);
}
.tabpanel[hidden] { display: none; }
.window-note { font-size: 12px; color: var(--text-muted); white-space: nowrap; }
.window-note.bad { color: var(--critical); font-weight: 600; }

/* Filters are staged, not live: the Apply button is the only thing that
   triggers a query, and it signals that there are unapplied edits. */
button.apply {
  background: var(--surface-1); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 7px;
  padding: 7px 18px; font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer; white-space: nowrap;
}
button.apply:disabled { opacity: .45; cursor: default; }
button.apply.primed {
  background: var(--series-1); border-color: var(--series-1); color: #fff;
}
button.apply.primed:hover { filter: brightness(1.08); }

/* ---- loading feedback ----
   Panels can take tens of seconds against a 2-core database, so the page has to
   keep saying "still working" or it reads as broken. Three layers: a progress
   bar driven by how many panels have actually returned, a count in the pill,
   and shimmer placeholders where content has not arrived yet. */
.progress {
  position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: transparent; overflow: hidden; pointer-events: none;
}
.progress > span {
  display: block; height: 100%; width: 0%;
  background: var(--series-1);
  transition: width .35s ease, opacity .3s ease;
}
/* Until the first panel lands there is no fraction to show, so the bar runs an
   indeterminate sweep rather than sitting at zero. */
body[data-busy="1"] .progress > span.indeterminate {
  width: 35%; animation: sweep 1.1s ease-in-out infinite;
}
@keyframes sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(340%); }
}

.busy { display: inline-flex; align-items: center; gap: 7px; }
.busy::before {
  content: ""; width: 11px; height: 11px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--series-1);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Stale content is dimmed via an explicit class toggled in setBusy(), not an
   attribute selector on <body>. Keep it readable — blanking the page on every
   refresh is more disorienting than a dimmed number. */
.is-stale { opacity: .45; transition: opacity .15s; }

.skeleton { display: flex; flex-direction: column; gap: 9px; padding: 4px 0; }
.skeleton i {
  display: block; height: 13px; border-radius: 5px;
  background: linear-gradient(90deg,
    var(--grid) 25%, var(--surface-1) 50%, var(--grid) 75%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}
.band.alt .skeleton i {
  background: linear-gradient(90deg,
    var(--grid) 25%, var(--plane) 50%, var(--grid) 75%);
  background-size: 400% 100%;
}
@keyframes shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton i, .progress > span.indeterminate, .busy::before { animation: none; }
}

/* ---- India choropleth ---- */
svg.india-map { width: 100%; height: auto; max-height: 620px; display: block; }
svg.india-map path { cursor: default; transition: filter .1s; }
/* Labels sit on top of the fill, so they carry a surface-coloured halo rather
   than relying on the fill underneath being a particular lightness. */
/* `.chart text` sets a muted fill for axis labels and outranks a bare
   `.map-label` on specificity, so these are scoped to the map's own svg. */
/* These sizes are in viewBox units and must match FS/LH in renderIndiaMap --
   the collision and gutter maths measure labels with those constants, so a
   mismatch makes the layout reason about a different size than it draws. */
svg.india-map text.map-label {
  font-size: 17px; font-weight: 650; fill: var(--text-primary);
  paint-order: stroke; stroke: var(--surface-1); stroke-width: 4px;
  stroke-linejoin: round; pointer-events: none;
}
.band.alt svg.india-map text.map-label { stroke: var(--plane); }
svg.india-map text.map-label tspan.map-label-val {
  font-size: 15px; fill: var(--text-secondary); font-weight: 600;
}
svg.india-map .map-leader {
  fill: none; stroke: var(--text-muted); stroke-width: 1.1px;
  vector-effect: non-scaling-stroke; pointer-events: none; opacity: .65;
}
svg.india-map { max-height: 760px; }

/* ---- login ----
   A tilting glass card over a drifting backdrop. The card keeps its contents on
   real 3D layers (preserve-3d + per-layer translateZ) so they separate as it
   turns, rather than the flat "fake tilt" of transforming one plane. All motion
   is transform/opacity only, so it stays on the compositor, and the whole thing
   collapses to a static card under prefers-reduced-motion. */
.login-page {
  display: grid; place-items: center; min-height: 100vh;
  overflow: hidden; position: relative;
}

/* Backdrop */
.aurora { position: fixed; inset: 0; overflow: hidden; z-index: 0; }
.aurora .orb {
  position: absolute; border-radius: 50%; filter: blur(70px);
  opacity: .5; will-change: transform;
}
.orb-1 {
  width: 46vmax; height: 46vmax; left: -12vmax; top: -14vmax;
  background: radial-gradient(circle at 40% 40%, var(--series-1), transparent 62%);
  animation: drift-a 26s ease-in-out infinite;
}
.orb-2 {
  width: 40vmax; height: 40vmax; right: -12vmax; top: 6vmax;
  background: radial-gradient(circle at 55% 45%, var(--series-5), transparent 62%);
  animation: drift-b 32s ease-in-out infinite;
}
.orb-3 {
  width: 38vmax; height: 38vmax; left: 26vmax; bottom: -18vmax;
  background: radial-gradient(circle at 50% 50%, var(--series-2), transparent 62%);
  animation: drift-c 29s ease-in-out infinite;
}
@keyframes drift-a {
  0%,100% { transform: translate3d(0,0,0) scale(1); }
  50%     { transform: translate3d(7vmax,5vmax,0) scale(1.12); }
}
@keyframes drift-b {
  0%,100% { transform: translate3d(0,0,0) scale(1.06); }
  50%     { transform: translate3d(-6vmax,8vmax,0) scale(1); }
}
@keyframes drift-c {
  0%,100% { transform: translate3d(0,0,0) scale(1); }
  50%     { transform: translate3d(-8vmax,-6vmax,0) scale(1.1); }
}

/* Perspective floor: a grid receding to the horizon, which is what sells depth
   behind the card. */
.aurora .floor {
  position: absolute; left: -50%; right: -50%; bottom: -18vh; height: 62vh;
  background-image:
    linear-gradient(to right, var(--border) 1px, transparent 1px),
    linear-gradient(to bottom, var(--border) 1px, transparent 1px);
  background-size: 62px 62px;
  transform: perspective(340px) rotateX(62deg);
  transform-origin: 50% 100%;
  mask-image: linear-gradient(to bottom, transparent, #000 42%, transparent 96%);
  opacity: .85;
  animation: floor-run 7s linear infinite;
}
@keyframes floor-run { to { background-position: 0 62px, 0 62px; } }

/* Rising motes — a light nod to transactions flowing through the pipeline. */
.spark-field { position: absolute; inset: 0; }
.spark-field i {
  position: absolute; left: var(--x); bottom: -8px;
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--series-1); opacity: 0;
  animation: rise var(--t) linear var(--d) infinite;
}
@keyframes rise {
  0%   { transform: translateY(0) scale(.6); opacity: 0; }
  12%  { opacity: .75; }
  85%  { opacity: .35; }
  100% { transform: translateY(-104vh) scale(1.3); opacity: 0; }
}

/* Stage + card */
.login-stage {
  position: relative; z-index: 1;
  width: 100%; max-width: 400px; padding: 24px;
  perspective: 1100px;
}
.login-card {
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
  transition: transform .5s cubic-bezier(.22,.61,.36,1), box-shadow .4s;
  background: color-mix(in srgb, var(--surface-1) 82%, transparent);
  backdrop-filter: blur(18px) saturate(1.3);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid color-mix(in srgb, var(--text-primary) 12%, transparent);
  border-radius: 18px; padding: 30px 28px 28px;
  display: flex; flex-direction: column; gap: 16px;
  box-shadow: 0 26px 70px -28px rgba(0,0,0,.55), 0 2px 10px -4px rgba(0,0,0,.3);
  animation: card-in .7s cubic-bezier(.2,.7,.3,1) both;
}
/* While the pointer is over it, follow instantly; the slow transition is only
   for the glide back to rest. */
.login-card.tilting { transition: transform .08s linear, box-shadow .4s; }
@keyframes card-in {
  from { opacity: 0; transform: translateY(26px) rotateX(12deg) scale(.96); }
  to   { opacity: 1; transform: translateY(0) rotateX(0) scale(1); }
}

/* Specular sweep that tracks the cursor. */
.login-card .sheen {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: radial-gradient(420px circle at var(--mx,50%) var(--my,50%),
              color-mix(in srgb, var(--series-1) 20%, transparent), transparent 62%);
  opacity: 0; transition: opacity .35s;
}
.login-card.tilting .sheen { opacity: 1; }

/* Depth layers: pushing these toward the viewer is what makes the tilt read as
   an object rather than a picture of one. */
.lc-layer { transform-style: preserve-3d; }
.lc-brand  { transform: translateZ(46px); }
.lc-fields {
  transform: translateZ(22px);
  display: flex; flex-direction: column; gap: 14px;
}

.mark { margin-bottom: 10px; }
.mark svg { display: block; overflow: visible; }
.mark .m-bar { fill: var(--series-1); transform-origin: 50% 100%; }
.mark .m-bar:nth-child(1) { animation: bar-pulse 2.8s ease-in-out infinite; }
.mark .m-bar:nth-child(2) { animation: bar-pulse 2.8s ease-in-out .18s infinite; }
.mark .m-bar:nth-child(3) { animation: bar-pulse 2.8s ease-in-out .36s infinite; }
.mark .m-dot {
  fill: var(--series-2);
  animation: dot-pulse 2.8s ease-in-out .5s infinite;
}
@keyframes bar-pulse {
  0%,100% { transform: scaleY(1); opacity: .92; }
  50%     { transform: scaleY(1.14); opacity: 1; }
}
@keyframes dot-pulse {
  0%,100% { transform: scale(1); opacity: .9; }
  50%     { transform: scale(1.3); opacity: 1; }
}

.login-card .brand { font-size: 27px; letter-spacing: -.02em; }
.login-sub { margin: 4px 0 0; font-size: 13px; color: var(--text-secondary); }

.login-field { display: flex; flex-direction: column; gap: 6px; }
.login-field span {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-muted); font-weight: 600;
}
.login-field input {
  background: color-mix(in srgb, var(--plane) 70%, transparent);
  color: var(--text-primary);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 11px 12px; font: inherit; font-size: 14px;
  transition: border-color .16s, box-shadow .16s, background .16s;
}
.login-field input:hover { border-color: var(--axis); }
.login-field input:focus {
  outline: none; border-color: var(--series-1);
  background: var(--plane);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--series-1) 24%, transparent);
}

.login-btn {
  position: relative; overflow: hidden;
  margin-top: 6px; color: #fff;
  background: linear-gradient(120deg, var(--series-1), var(--series-5));
  border: none; border-radius: 10px;
  padding: 12px 14px; font: inherit; font-size: 14px; font-weight: 650;
  cursor: pointer; transition: transform .12s, box-shadow .2s, filter .2s;
  box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--series-1) 90%, transparent);
}
.login-btn:hover { filter: brightness(1.07); box-shadow: 0 14px 30px -12px color-mix(in srgb, var(--series-1) 95%, transparent); }
.login-btn:active { transform: translateY(1px); }
.login-btn:disabled { cursor: default; filter: saturate(.7); }
/* Light travelling across the button while the request is in flight. */
.login-btn.working::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.34), transparent);
  animation: btn-scan 1.1s linear infinite;
}
@keyframes btn-scan { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

.login-error {
  font-size: 13px; color: var(--critical); font-weight: 550;
  border: 1px solid var(--border); border-left: 3px solid var(--critical);
  border-radius: 9px; padding: 9px 11px;
  background: color-mix(in srgb, var(--critical) 10%, transparent);
}
.login-error[hidden] { display: none; }

.login-card.shake { animation: shake .42s cubic-bezier(.36,.07,.19,.97); }
@keyframes shake {
  10%,90% { transform: translateX(-2px); }
  20%,80% { transform: translateX(4px); }
  30%,50%,70% { transform: translateX(-7px); }
  40%,60% { transform: translateX(7px); }
}
.login-card.accepted {
  animation: accepted .42s cubic-bezier(.3,.8,.4,1) forwards;
}
@keyframes accepted {
  to { transform: translateY(-8px) scale(1.02); opacity: 0; filter: blur(3px); }
}

.login-foot {
  margin: 18px 0 0; text-align: center; font-size: 12px; color: var(--text-muted);
}

@media (prefers-reduced-motion: reduce) {
  .orb, .floor, .spark-field i, .mark .m-bar, .mark .m-dot,
  .login-btn.working::after { animation: none; }
  .login-card { animation: none; transition: none; transform: none; }
  .login-card.shake, .login-card.accepted { animation: none; }
  .spark-field { display: none; }
}

.signout {
  background: none; border: none; cursor: pointer; font: inherit;
  font-size: 12px; color: var(--text-muted); padding: 0; text-decoration: underline;
}
.signout:hover { color: var(--text-primary); }
.whoami { font-size: 12px; color: var(--text-secondary); white-space: nowrap; }

.method-notes p {
  margin: 0 0 10px; font-size: 13px; color: var(--text-secondary);
  line-height: 1.6;
}
.method-notes p:last-child { margin-bottom: 0; }
.method-notes strong { color: var(--text-primary); }
.brand { font-size: 20px; font-weight: 650; letter-spacing: -0.01em; }
.brand span { color: var(--series-1); }
.tagline { color: var(--text-secondary); font-size: 13px; }
.spacer { flex: 1; }
button.ghost {
  background: var(--surface-1); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 12px; cursor: pointer; font: inherit; font-size: 13px;
}
button.ghost:hover { color: var(--text-primary); }

/* The theme toggle carries a single glyph, so it is squared off rather than
   left as a wide text pill. The accessible name lives in aria-label. */
#theme-toggle {
  width: 34px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; line-height: 1;
  transition: border-color .14s, transform .14s;
}
#theme-toggle:hover { border-color: var(--axis); transform: translateY(-1px); }
#theme-toggle:active { transform: translateY(0); }

/* Filter changes hit ClickHouse, so acknowledge the click immediately rather
   than leaving the old numbers on screen looking like nothing happened. */
.busy {
  font-size: 12px; color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 999px;
  padding: 4px 11px; background: var(--surface-1);
}
.busy[hidden] { display: none; }

/* ---- banner ---- */
.banner {
  display: flex; gap: 12px; align-items: flex-start;
  border: 1px solid var(--border); border-left: 3px solid var(--critical);
  background: var(--surface-1); border-radius: 10px;
  padding: 12px 16px; margin-bottom: 16px;
}
.banner.warn { border-left-color: var(--warning); }
.banner .icon { font-size: 16px; line-height: 1.4; }
.banner h4 { margin: 0 0 3px; font-size: 13px; font-weight: 650; }
.banner p { margin: 0; font-size: 13px; color: var(--text-secondary); }

/* ---- filters ---- */
.filters {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px 14px; margin-bottom: 20px;
}
.field { display: flex; flex-direction: column; gap: 4px; }
.field label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-muted); font-weight: 600;
}
.field select, .field input {
  background: var(--plane); color: var(--text-primary);
  border: 1px solid var(--border); border-radius: 7px;
  padding: 7px 10px; font: inherit; font-size: 13px; min-width: 130px;
  /* Inherit the page's light/dark so the native calendar popup and the
     option list are drawn in the matching scheme rather than a white default. */
  color-scheme: inherit;
  transition: border-color .12s, box-shadow .12s;
}
.field select:hover, .field input:hover { border-color: var(--axis); }

/* A visible focus ring: the browser default is inconsistent across engines and
   nearly invisible against these surfaces. */
.field select:focus-visible, .field input:focus-visible {
  outline: none;
  border-color: var(--series-1);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--series-1) 22%, transparent);
}

/* Replace the native arrow, which differs per browser and sits oddly against a
   custom-height control. Two rules because a background-image cannot pick up
   currentColor — the chevron is inked per theme. */
.field select {
  appearance: none; -webkit-appearance: none;
  padding-right: 30px; cursor: pointer;
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 10px 6px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%2352514e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .field select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23c3c2b7' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}
:root[data-theme="dark"] .field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23c3c2b7' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ---- cards & grid ---- */
.grid { display: grid; gap: 16px; }
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .g2, .g4 { grid-template-columns: 1fr; } }

.card {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 12px; padding: 16px 18px; min-width: 0;
}
.card h3 {
  margin: 0 0 2px; font-size: 14px; font-weight: 650;
}
.card .sub { margin: 0 0 14px; font-size: 12px; color: var(--text-muted); }

/* ---- stat tiles ---- */
.tile .val {
  font-size: 26px; font-weight: 650; letter-spacing: -0.02em;
  margin: 2px 0 0;
}
.tile .lbl {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-muted); font-weight: 600;
}
.tile .note { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
.tile .note.bad { color: var(--critical); }
.tile .note.ok { color: var(--success-text); }

/* ---- section head ---- */
h2.section {
  font-size: 12px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-muted); font-weight: 650;
  margin: 28px 0 12px; padding-bottom: 6px;
  border-bottom: 1px solid var(--grid);
}

/* ---- legend ---- */
.legend { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 10px; }
.legend .item {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-secondary);
}
.legend .swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }

/* ---- tables ---- */
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th {
  text-align: left; font-size: 11px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-muted); font-weight: 600;
  padding: 6px 10px 6px 0; border-bottom: 1px solid var(--grid);
}
td {
  padding: 8px 10px 8px 0; border-bottom: 1px solid var(--grid);
  color: var(--text-secondary);
}
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 0; }
td.key { color: var(--text-primary); font-weight: 550; }
tbody tr:last-child td { border-bottom: none; }

/* ---- chart bits ---- */
.chart { width: 100%; display: block; overflow: visible; }
.chart text { font: 11px system-ui, -apple-system, sans-serif; fill: var(--text-muted); }
.chart text.value { fill: var(--text-secondary); font-variant-numeric: tabular-nums; }
.chart text.label { fill: var(--text-primary); }
.empty {
  color: var(--text-muted); font-size: 13px; padding: 22px 0;
  text-align: center; border: 1px dashed var(--grid); border-radius: 8px;
}
.tooltip {
  position: fixed; pointer-events: none; z-index: 50;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px; font-size: 12px;
  box-shadow: 0 6px 20px rgba(0,0,0,.16); opacity: 0; transition: opacity .1s;
  color: var(--text-primary); max-width: 260px;
}
.tooltip .t-row { display: flex; align-items: center; gap: 7px; margin-top: 3px; }
.tooltip .t-row .swatch { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.tooltip .t-row .v { margin-left: auto; font-variant-numeric: tabular-nums; }
.tooltip .t-head { font-weight: 650; }

footer.foot {
  padding: 20px 0 40px; border-top: 1px solid var(--grid);
  color: var(--text-muted); font-size: 12px;
}

/* ---- rail mix cross-tab ----
   One percentage column plus its own bar per rail, rather than a single
   stacked bar with the number inside the fill. Text on a coloured fill would
   have to clear contrast against eight different series colours in two themes;
   this keeps every number on the surface where it is always legible. */
.rail-grid { min-width: 660px; }
.rail-row, .rail-head {
  display: grid; grid-template-columns: 150px repeat(4, 1fr) 86px;
  gap: 12px; align-items: center;
}
.rail-row { padding: 8px 0; }
.rail-row + .rail-row { border-top: 1px solid var(--grid); }
.rail-head {
  padding-bottom: 7px; border-bottom: 1px solid var(--axis); margin-bottom: 2px;
}
.rail-head span {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-muted);
}
.rail-head span.num, .rail-total { text-align: right; }
.rail-name {
  font-size: 13px; font-weight: 550; color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rail-cell { display: flex; align-items: center; gap: 7px; min-width: 0; }
.rail-pct {
  width: 36px; flex: none; text-align: right; font-size: 12px;
  font-variant-numeric: tabular-nums; color: var(--text-secondary);
}
.rail-track {
  flex: 1; height: 8px; border-radius: 4px; background: var(--grid);
  min-width: 0;
}
.rail-fill { display: block; height: 100%; border-radius: 4px; }
.rail-total {
  font-size: 12px; color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.rail-zero { color: var(--text-muted); opacity: .5; }

/* ---- proportional blocks (wallet composition) ---- */
.blocks { display: flex; flex-wrap: wrap; gap: 6px; }
.block {
  border-radius: 8px; padding: 12px 14px; min-width: 0;
  display: flex; flex-direction: column; justify-content: space-between;
  overflow: hidden;
}
.block .b-name {
  font-size: 12px; font-weight: 650; letter-spacing: .01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.block .b-val { font-size: 17px; font-weight: 650; margin-top: 6px; }
.block .b-pct { font-size: 11px; opacity: .85; }

/* ---- heatmap ---- */
.heat-scroll { overflow-x: auto; padding-bottom: 4px; }
table.heat { border-collapse: separate; border-spacing: 2px; width: auto; }
table.heat th {
  font-size: 10px; font-weight: 600; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .04em;
  padding: 0 4px 4px; border: none; white-space: nowrap; text-align: center;
}
table.heat th.rowhead {
  text-align: left; position: sticky; left: 0; z-index: 2;
  background: var(--surface-1); padding-right: 10px;
  color: var(--text-primary); font-size: 11px; text-transform: none;
  letter-spacing: 0;
}
.band.alt table.heat th.rowhead { background: var(--plane); }
table.heat td {
  padding: 0; border: none; min-width: 52px; height: 30px;
  text-align: center; font-size: 10.5px; font-variant-numeric: tabular-nums;
  border-radius: 4px; white-space: nowrap;
}

/* ---- insights ---- */
.insight-card { border-left: 3px solid var(--series-1); }
ul.insights { margin: 0; padding: 0; list-style: none; }
ul.insights li {
  padding: 9px 0; border-top: 1px solid var(--grid);
  font-size: 13.5px; color: var(--text-secondary); line-height: 1.55;
}
ul.insights li:first-child { border-top: none; }
ul.insights strong { color: var(--text-primary); font-weight: 650; }
ul.insights .tag {
  display: inline-block; font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  padding: 2px 7px; border-radius: 999px; margin-right: 8px;
  border: 1px solid var(--border); color: var(--text-secondary);
  vertical-align: 1px;
}
