/* ============================================================================
   rohit.vision — VISUALISATION
   Charts, plots, heatmaps, network diagrams, equations, stat tiles, callouts.
   Depends on tokens.css + base.css.

   THE RULES THAT MATTER (violating these is a bug, not a style choice)
   1. ONE y-axis. Never two scales on one chart. Two measures of different
      magnitude -> two charts, small multiples, or index both to a common base.
   2. Colour follows the ENTITY, never its rank. Filtering a series out must
      not repaint the survivors. Bind series-N to the thing, once.
   3. Sequential = one hue light->dark. Diverging = two poles + GRAY midpoint.
      Never a rainbow. Never a hue at a diverging midpoint.
   4. A legend is present for >= 2 series (a single series needs none — the
      title names it). <= 4 series are also direct-labelled, so identity is
      never carried by colour alone.
   5. Text wears ink tokens, never the series colour. A colour chip beside a
      label carries identity; the label itself stays in ink.
   6. Status colours are reserved. They never appear as "series 4".
   ========================================================================= */

/* --------------------------------------------------------------------------
   FIGURE — the wrapper every chart and diagram sits in.
   ----------------------------------------------------------------------- */
.viz {
  background: var(--viz-surface);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  overflow: hidden;
}

.viz__head { display: flex; flex-direction: column; gap: var(--space-1); }

.viz__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* The title states the takeaway, not the variables. "Val loss plateaus at
   epoch 30" beats "Loss vs epoch" — the axes already say the second one. */
.viz__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  color: var(--ink);
}

.viz__sub { font-size: var(--text-base); color: var(--ink-secondary); }

/* The plot area scrolls horizontally on narrow screens; the page never does.
   overflow-y is pinned to `hidden` on purpose: `overflow-x: auto` alone makes
   the box a scroll container on BOTH axes, and then a chart inside a
   height-constrained parent (a slide) gets vertically CLIPPED instead of
   scaled — the lines appear to stop mid-plot. Height is capped rather than
   clipped, so the aspect ratio shrinks the chart to fit. */
.viz__plot { overflow-x: auto; overflow-y: hidden; min-width: 0; min-height: 0; }
.viz__plot > svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 100%;   /* fit a bounded parent; no-op when height is auto */
  margin-inline: auto;
  overflow: visible;
}

.viz__note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   SERIES — bind by slot. Use the attribute form in markup:
     <path class="viz-line" data-series="1" />
   so the same token drives stroke, fill and legend chip with no duplication.
   ----------------------------------------------------------------------- */
[data-series='1'] { --s: var(--series-1); }
[data-series='2'] { --s: var(--series-2); }
[data-series='3'] { --s: var(--series-3); }
[data-series='4'] { --s: var(--series-4); }
[data-series='5'] { --s: var(--series-5); }
[data-series='6'] { --s: var(--series-6); }
[data-series='7'] { --s: var(--series-7); }
[data-series='8'] { --s: var(--series-8); }
[data-series='other'] { --s: var(--series-other); }

/* Lines — 2px, round joins, no vertex dots unless the data is sparse. */
/* Every mark falls back to slot 1 if data-series is missing. Without the
   fallback a forgotten attribute makes the mark invisible rather than wrong,
   which is much harder to notice. */
.viz-line {
  fill: none;
  stroke: var(--s, var(--series-1));
  stroke-width: var(--viz-mark);
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* A forecast / extrapolated segment is dashed — the dash IS the semantics. */
.viz-line--forecast { stroke-dasharray: 4 4; opacity: 0.85; }

/* Area fill under a line. Only ever for ONE series; two translucent areas
   stack into a third colour that means nothing. */
.viz-area { fill: var(--viz-fill); stroke: none; }

/* Bars — rounded only on the data end, anchored to the baseline, plus a 2px
   surface gap so adjacent and stacked segments read as separate marks. */
.viz-bar {
  fill: var(--s, var(--series-1));
  rx: var(--viz-radius);
  stroke: var(--viz-surface);
  stroke-width: var(--viz-gap);
}
/* In a STACK only the topmost segment carries the rounded data end. SVG rx
   rounds all four corners, so leaving it on every segment turns the stack into
   a column of pills and breaks the "rounded at the data end only" rule.
   Lower segments take --stack (or --flat, same thing, clearer name at a
   call site). */
.viz-bar--flat,
.viz-bar--stack { rx: 0; }

/* Dots — 8px minimum so they survive at reading size and on a projector.
   A surface ring keeps overlapping points readable. */
.viz-dot {
  fill: var(--s, var(--series-1));
  r: calc(var(--viz-dot) / 2);
  stroke: var(--viz-surface);
  stroke-width: var(--viz-gap);
}

/* --------------------------------------------------------------------------
   AXES & GRID — recessive. The grid is a hairline the eye can ignore; the
   baseline is one step stronger because it carries the zero.
   ----------------------------------------------------------------------- */
.viz-grid line, .viz-grid path { stroke: var(--viz-grid); stroke-width: 1px; }
.viz-axis line, .viz-axis path { stroke: var(--viz-axis); stroke-width: 1px; }

.viz-tick {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  fill: var(--viz-label);
  font-variant-numeric: tabular-nums;
}

.viz-axis-title {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  fill: var(--ink-muted);
}

/* A zero / threshold reference rule — dashed, muted, labelled. */
.viz-rule { stroke: var(--ink-muted); stroke-width: 1px; stroke-dasharray: 2 3; }

/* Direct label on the end of a line — the preferred alternative to a legend
   when there are <= 4 series. Ink, not series colour (rule 5). */
.viz-label-direct {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  fill: var(--ink-secondary);
}

/* --------------------------------------------------------------------------
   LEGEND — present whenever there are >= 2 series. One row, above or below
   the plot, never a floating box over the data.
   ----------------------------------------------------------------------- */
.viz__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: center;
}

.viz__legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--ink-secondary);   /* ink, never --s */
}

.viz__legend-chip {
  width: 10px;
  height: 10px;
  flex: none;
  border-radius: var(--radius-tight);
  background: var(--s, var(--series-1));
}
/* Line-type series get a bar chip; dashed series show the dash in the chip. */
.viz__legend-chip--line { height: 2px; width: 14px; border-radius: 0; }
.viz__legend-chip--dashed {
  height: 0;
  width: 14px;
  background: none;
  border-top: 2px dashed var(--s, var(--series-1));
}

/* --------------------------------------------------------------------------
   HOVER — an HTML/SVG chart is interactive by default. Crosshair + tooltip on
   line/area; per-mark tooltip on bar/dot/cell. Hit targets exceed the mark.
   ----------------------------------------------------------------------- */
.viz-hit { fill: transparent; cursor: crosshair; }
.viz-crosshair { stroke: var(--ink-muted); stroke-width: 1px; stroke-dasharray: 2 2; }

.viz-tooltip {
  position: absolute;
  z-index: var(--z-raised);
  min-width: 8rem;
  background: var(--surface-raised);
  border: var(--border-width) solid var(--border-control);
  border-radius: var(--radius);
  box-shadow: var(--shadow-popover);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.viz-tooltip[data-open='true'] { opacity: 1; }

.viz-tooltip__key {
  font-family: var(--font-mono);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}
.viz-tooltip__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  color: var(--ink-secondary);
}
.viz-tooltip__val {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* Emphasis on hover: raise the hovered series, recede the rest. Opacity —
   never a hue change, which would break entity-colour binding. */
.viz--focus .viz-line,
.viz--focus .viz-bar,
.viz--focus .viz-dot { opacity: 0.35; transition: opacity var(--dur-fast) var(--ease); }
.viz--focus [data-focus='true'] { opacity: 1; }

/* --------------------------------------------------------------------------
   STAT TILE — for a single headline number. When the answer is one figure,
   this is the right form and a chart is the wrong one.
   ----------------------------------------------------------------------- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-4);
}

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
}

.stat__label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Proportional figures for a standalone number; tabular only in columns. */
.stat__value {
  font-family: var(--font-mono);
  font-size: var(--text-3xl);
  font-weight: var(--weight-medium);
  line-height: var(--leading-none);
  color: var(--ink);
}
.stat__unit {
  font-size: var(--text-md);
  font-weight: var(--weight-regular);
  color: var(--ink-secondary);
}

/* Delta ships with an arrow glyph AND a sign — never colour alone. */
.stat__delta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-muted);
}
.stat__delta--up   { color: var(--status-good); }
.stat__delta--down { color: var(--status-critical); }
/* For loss curves, DOWN is good. Use the neutral modifier and let the label
   say so, rather than inverting the colour semantics. */
.stat__delta--neutral { color: var(--ink-muted); }

/* Sparkline — inline, no axes, no labels. Trend only. */
.sparkline { display: block; width: 100%; height: 2rem; overflow: visible; }

/* --------------------------------------------------------------------------
   HEATMAP / MATRIX — attention maps, confusion matrices, correlation.
   Cells carry a 1px surface gap so the grid reads without gridlines.
   Sequential for magnitude; diverging for signed values.
   ----------------------------------------------------------------------- */
.matrix { border-collapse: separate; border-spacing: 1px; font-size: var(--text-xs); }
.matrix th {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  color: var(--ink-muted);
  text-transform: none;
  letter-spacing: 0;
  border: none;
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}
.matrix td {
  padding: var(--space-2);
  border: none;
  text-align: center;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  border-radius: var(--radius-tight);
  background: var(--cell, var(--surface-inset));
  color: var(--ink);
}

/* Sequential bins — bind with data-bin="1".."7" */
[data-bin='1'] { --cell: var(--seq-7); }   /* near zero — recedes to surface */
[data-bin='2'] { --cell: var(--seq-6); }
[data-bin='3'] { --cell: var(--seq-5); }
[data-bin='4'] { --cell: var(--seq-4); }
[data-bin='5'] { --cell: var(--seq-3); }
[data-bin='6'] { --cell: var(--seq-2); }
[data-bin='7'] { --cell: var(--seq-1); }   /* max */

/* Diverging bins — bind with data-div="-2".."2". Gray midpoint. */
[data-div='-2'] { --cell: var(--div-neg-2); }
[data-div='-1'] { --cell: var(--div-neg-1); }
[data-div='0']  { --cell: var(--div-mid); }
[data-div='1']  { --cell: var(--div-pos-1); }
[data-div='2']  { --cell: var(--div-pos-2); }

/* Cell labels flip ink at bin 4, and use PURE white / PURE black rather than
   --ink / --ink-inverse. Measured against each ramp step:
     bins 1–3 on #ffffff   5.02 · 7.13 · 9.11
     bins 4–7 on #000000   6.37 · 9.22 · 12.05 · 14.96
   The softer --ink (#e8e8e8) fails on bin 3 (4.09) and --ink-inverse fails on
   bin 4 (2.69 the other way) — bin 3/4 is the crossover, and only the pure
   endpoints clear 4.5:1 on every step.

   These selectors are qualified with `.matrix td` deliberately: a bare
   [data-bin] attribute selector (0,1,0) LOSES to `.matrix td` (0,1,1) and the
   flip silently never applies, leaving labels at 1.15:1 on the light steps. */
.matrix td[data-bin='1'],
.matrix td[data-bin='2'],
.matrix td[data-bin='3'] { color: var(--n-0); }
.matrix td[data-bin='4'],
.matrix td[data-bin='5'],
.matrix td[data-bin='6'],
.matrix td[data-bin='7'] { color: var(--n-1000); }
/* Diverging: the poles are dark enough for white at every step; the gray
   midpoint takes white too (--n-600 #444444 -> 8.9:1). */
.matrix td[data-div] { color: var(--n-0); }

/* Colour scale legend — a continuous ramp with min/mid/max ticks only. */
.viz__scale { display: flex; align-items: center; gap: var(--space-2); }
.viz__scale-ramp {
  height: 8px;
  flex: 1;
  min-width: 6rem;
  border-radius: var(--radius-pill);
  background: linear-gradient(to right,
    var(--seq-7), var(--seq-5), var(--seq-3), var(--seq-1));
}
.viz__scale-ramp--div {
  background: linear-gradient(to right,
    var(--div-neg-2), var(--div-neg-1), var(--div-mid),
    var(--div-pos-1), var(--div-pos-2));
}
.viz__scale-tick {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   NETWORK DIAGRAM — layers, nodes, edges. Fills are hue-tinted near-blacks
   so the diagram reads as part of the page rather than a pasted graphic.
   ----------------------------------------------------------------------- */
/* Columns STRETCH to the tallest layer so every label lands on the same
   baseline; the nodes then centre inside the remaining height. With
   align-items:center the labels drift by layer depth — a 4-node column and a
   1-node column put their labels in different places. */
.net { display: flex; align-items: stretch; justify-content: center; gap: var(--space-7); }
.net__layer { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); }
.net__nodes {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3);
}

.net__layer-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}

.net__node {
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-round);
  border: var(--border-width) solid var(--node-border);
  background: var(--node-hidden);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-secondary);
}
.net__node--input  { background: var(--node-input);  border-color: var(--series-1); }
.net__node--output { background: var(--node-output); border-color: var(--series-8); }

.net__edge { stroke: var(--node-border); stroke-width: 1px; fill: none; }
.net__edge--active { stroke: var(--series-1); stroke-width: var(--viz-mark); }

/* --------------------------------------------------------------------------
   EQUATION BLOCK — a display equation is a figure. It gets a well, a number,
   and optional syntax colouring for the variables it introduces.
   ----------------------------------------------------------------------- */
.eq {
  position: relative;
  background: var(--surface-inset);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  box-shadow: inset 0 0 40px var(--eq-glow);
  padding: var(--space-5) var(--space-6);
  overflow-x: auto;
}
.eq__num {
  position: absolute;
  right: var(--space-4);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-muted);
}
.eq-var  { color: var(--eq-var); }
.eq-op   { color: var(--eq-op); }
.eq-num  { color: var(--eq-num); }
.eq-func { color: var(--eq-func); }

/* --------------------------------------------------------------------------
   CALLOUT — status-coloured, and ALWAYS icon + label. The colour is the
   third cue, never the first.
   ----------------------------------------------------------------------- */
.callout {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4);
  border-left: 2px solid var(--c, var(--status-info));
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--c-bg, var(--status-info-bg));
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}
.callout__icon { flex: none; color: var(--c, var(--status-info)); line-height: var(--leading-normal); }
.callout__body { min-width: 0; }
.callout__title {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--c, var(--status-info));
  margin-bottom: var(--space-1);
}

.callout--tip      { --c: var(--status-good);     --c-bg: var(--status-good-bg); }
.callout--note     { --c: var(--status-info);     --c-bg: var(--status-info-bg); }
.callout--warning  { --c: var(--status-warning);  --c-bg: var(--status-warning-bg); }
.callout--danger   { --c: var(--status-critical); --c-bg: var(--status-critical-bg); }
.callout--question { --c: var(--status-note);     --c-bg: var(--status-note-bg); }

/* --------------------------------------------------------------------------
   BADGE — a small reserved-colour chip. Text is 12px (the floor), never less.
   ----------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.1rem var(--space-2);
  border: var(--border-width) solid var(--c, var(--border-control));
  border-radius: var(--radius-pill);
  background: var(--c-bg, transparent);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--c, var(--ink-secondary));
  white-space: nowrap;
}
.badge--good     { --c: var(--status-good);     --c-bg: var(--status-good-bg); }
.badge--info     { --c: var(--status-info);     --c-bg: var(--status-info-bg); }
.badge--warning  { --c: var(--status-warning);  --c-bg: var(--status-warning-bg); }
.badge--critical { --c: var(--status-critical); --c-bg: var(--status-critical-bg); }

/* --------------------------------------------------------------------------
   TABLE VIEW — every chart can be read as a table. This is the relief for
   the contrast WARN case and the honest fallback for screen readers.
   ----------------------------------------------------------------------- */
.viz__table[hidden] { display: none; }
.viz__toggle {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-muted);
  border-bottom: var(--border-width) dotted var(--border-control);
  transition: color var(--dur-fast) var(--ease);
}
.viz__toggle:hover { color: var(--ink-accent); }

/* --------------------------------------------------------------------------
   TEXTURE — the accessibility channel. One directional fill at 45 deg and
   its 135 deg mirror. Triggered by print, forced-colors, or an explicit
   opt-in. NEVER decorative and never on by default.
   ----------------------------------------------------------------------- */
.viz[data-texture='on'] .viz-bar[data-series='2'],
.viz[data-texture='on'] .viz-area[data-series='2'] { fill: url(#hatch-45); }
.viz[data-texture='on'] .viz-bar[data-series='3'],
.viz[data-texture='on'] .viz-area[data-series='3'] { fill: url(#hatch-135); }

@media (forced-colors: active) {
  .viz-line, .viz-bar, .viz-dot { forced-color-adjust: none; }
  .viz { border-color: CanvasText; }
}

/* In print, marks keep their hue (the palette is validated on white) but the
   surface gap has to follow the white surface or bars fuse together. */
@media print {
  .viz { break-inside: avoid; border-color: var(--border-strong); }
  .viz-bar, .viz-dot { stroke: #ffffff; }
  .viz__toggle, .viz-tooltip { display: none; }
  .viz__table[hidden] { display: table; }   /* the table IS the print form */
}

/* --------------------------------------------------------------------------
   RESPONSIVE
   ----------------------------------------------------------------------- */
@media (max-width: 768px) {
  .viz { padding: var(--space-4); }
  .net { gap: var(--space-5); }
  .stat__value { font-size: var(--text-2xl); }
}
