/* viz.css — the SVG system.
 *
 * ONE AXIS TREATMENT ACROSS EVERY CHART. The spine, the week grid, the year
 * grid, the line chart and the sparkline are one instrument — a shared time
 * axis — so they get one hairline weight, one tick voice and one cell corner.
 * They used to invent their own grid weight, tick colour and radius each.
 *
 * THE COLOUR RULE: a chart may use --ink at varying opacity, the --data-1..5
 * ramp, and the three semantic colours. It may NOT introduce a hue. Category is
 * encoded by position, by label, or by the deterministic per-person hash —
 * never by a palette.
 *
 * THE PALETTE, as tokens (tokens.css, "data-viz"): --viz-series (ink),
 * --viz-series-2 (ink-3, dashed), --viz-emph (the accent, for the ONE mark a
 * sentence is about), --data-1..5 (sequential magnitude), --viz-now (live),
 * --viz-hatch (not measured), --viz-grid / --viz-axis (hairlines), and
 * --viz-font for every tick. A new chart inherits all of it by using these
 * classes; it never writes a colour literal.
 */
.viz-emph { stroke: var(--viz-emph); fill: var(--viz-emph); }

/* --------------------------------------------------- shared axis voice */

.viz-grid,
.spine-grid-line,
.line-grid,
.week-tick {
  stroke: var(--viz-grid);
  stroke-width: var(--viz-grid-w);
}

.viz-axis,
.spine-axis,
.week-axis {
  stroke: var(--viz-axis);
  stroke-width: var(--viz-axis-w);
}

/* Anything measured is set in the mono face, including axis labels. */
.viz-tick,
.spine-tick,
.line-tick,
.week-tick-label,
.year-label {
  fill: var(--ink-3);
  font-family: var(--viz-font);
  font-size: var(--viz-tick-size);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

/* Wide content scrolls inside its own container, always. */
.spine-scroll, .week-scroll, .year-wrap { overflow-x: auto; scrollbar-gutter: stable; }

/* -------------------------------------------------------- the time spine
 *
 * free4talk.me draws presence bars with no axis at all: you can see THAT
 * someone was there in three bursts, never WHEN. The axis is the differentiator,
 * so it gets the care. Drawn at measured pixel width rather than a scaled
 * viewBox, so labels stay legible instead of shrinking. */

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

/* The lane is a RULE, not a slab. A filled track reads as "this slot is a
 * container"; a rule reads as "this is the axis for this person", which is what
 * it actually is. */
.spine-lane-rule { stroke: var(--viz-grid); stroke-width: var(--viz-grid-w); }
.spine-lane-label { fill: var(--ink-2); font-size: var(--t-2xs); }

.spine-bar { cursor: default; transform-origin: left center; }
/* An approximate boundary is drawn as texture, never as a shorter bar. */
.spine-bar-low { stroke: var(--rule); stroke-dasharray: 2 2; stroke-width: 1; }

/* "Now" is the one live element in the whole system, and it is --live because
 * --live means observed and current. */
.spine-now-line {
  stroke: var(--viz-now);
  stroke-width: var(--viz-now-w);
  stroke-dasharray: 3 3;
}
.spine-now-label {
  fill: var(--viz-now);
  font-family: var(--font-mono);
  font-size: var(--t-3xs);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
}

.spine-empty {
  padding: var(--s-6);
  text-align: center;
  color: var(--ink-3);
  font-size: var(--t-sm);
  border: var(--edge-soft);
  border-radius: var(--r-2);
}

/* ----------------------------------------------------------- line chart */

.line-chart { display: grid; gap: var(--s-3); }
.line-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--s-3);
}
.line-plot { min-width: 0; }
.line-svg { display: block; width: 100%; height: auto; }

.line-path {
  fill: none;
  stroke: var(--viz-series);
  stroke-width: var(--viz-series-w);
  stroke-linejoin: round;
  stroke-linecap: round;
}
.line-area { fill: var(--data-1); stroke: none; }
.line-dot { fill: var(--viz-series); }

/* Two series, distinguished by weight and dash rather than by hue, because a
 * hue here would collide with the semantic contract. */
.series-b .line-path { stroke: var(--viz-series-2); stroke-width: 1.25; stroke-dasharray: 4 3; }
.series-b .line-dot  { fill: var(--viz-series-2); }
.series-b.line-area  { fill: none; }

.line-overlay { fill: transparent; cursor: crosshair; }
.line-cursor-line { stroke: var(--rule); stroke-width: 1; }
.line-cursor-dot { fill: var(--bg); stroke: var(--viz-series); stroke-width: 1.5; }

.line-caption { color: var(--ink-3); font-size: var(--t-xs); }

/* ------------------------------------------------------------- readout
 *
 * The hover/focus sentence strip. A keyboard walk fills it exactly as a pointer
 * does — no information on this site is hover-only. */
.readout {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-3);
  min-height: 1.5em;
  font-size: var(--t-xs);
}
.readout-time {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  font-weight: 600;
}
.readout-val { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.readout-dim { color: var(--ink-3); }

/* -------------------------------------------------------------- legend */

.legend {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s-3);
  font-size: var(--t-2xs);
  color: var(--ink-2);
}
.legend-key { display: inline-flex; align-items: center; gap: var(--s-1); }
.legend-swatch {
  flex: none;
  width: 14px; height: 10px;
  border-radius: var(--r-1);
  background: var(--data-4);
}
.legend-key[data-swatch='fade'] .legend-swatch {
  background: linear-gradient(90deg, var(--data-4) 0%, var(--data-4) 60%, transparent 100%);
}
.legend-key[data-swatch='hatch'] .legend-swatch {
  background: repeating-linear-gradient(
    45deg, transparent 0 2px, var(--viz-hatch) 2px 3px
  );
  box-shadow: inset 0 0 0 1px var(--rule-soft);
}
.legend-key[data-swatch='dash'] .legend-swatch {
  background: none;
  border: 1px dashed var(--rule);
}
.legend-key[data-tone='live'] .legend-swatch { background: var(--live); }
.legend-key[data-tone='warn'] .legend-swatch { background: var(--warn); }
.legend-key[data-tone='void'] .legend-swatch { background: var(--void); }

/* ====================================================== the week grid
 *
 * THE THREE CELL STATES ARE LOAD-BEARING, NOT DECORATIVE:
 *   .is-seen       data ink, opacity carries the rate
 *   .is-empty      flat surface — a MEASURED zero
 *   .is-unwatched  hatching — no measurement exists
 *
 * Drawing the third like the second reports OUR OWN DOWNTIME as this person's
 * absence, which is the same class of lie as showing a truncated session as a
 * short visit. They must stay visually distinct in both themes.
 * ================================================================= */

.week { display: grid; gap: var(--s-3); }
.week-svg { display: block; width: 100%; min-width: 534px; height: auto; }

.week-cell { stroke: var(--bg); stroke-width: 0.5; }
.week-cell.is-seen { fill: var(--data-5); }
.week-cell.is-empty { fill: var(--surface-2); }
.week-cell.is-unwatched { stroke: var(--rule-soft); }

.week-hatch-bg { fill: var(--surface); }
.week-hatch-line { stroke: var(--viz-hatch); stroke-width: 1.4; opacity: 0.45; }

.week-day-label { fill: var(--ink-2); font-size: var(--t-2xs); }
/* Outlines the cells the sentence above is about, so the claim and the chart
 * cannot disagree. */
.week-window { fill: none; stroke: var(--ink); stroke-width: 1.25; }

.week-tznote { color: var(--ink-3); font-size: var(--t-2xs); }

/* ====================================================== the year grid */

.year { display: grid; gap: var(--s-3); }
.year-svg { display: block; height: auto; }

.year-cell { stroke: none; }
.year-cell.lv-0 { fill: var(--surface-2); }
.year-cell.lv-1 { fill: var(--data-1); }
.year-cell.lv-2 { fill: var(--data-2); }
.year-cell.lv-3 { fill: var(--data-3); }
.year-cell.lv-4 { fill: var(--data-4); }
.year-cell.lv-5 { fill: var(--data-5); }
/* Not collected must not read as "nothing happened" — it is our gap, not
 * theirs. Same argument as .week-cell.is-unwatched, different chart. */
.year-cell.is-uncollected {
  fill: none;
  stroke: var(--rule);
  stroke-dasharray: 2 2;
}

.year-legend {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-1);
  font-size: var(--t-2xs);
  color: var(--ink-3);
}
.year-legend .legend-gap { margin-inline: var(--s-1); }
.year-key {
  width: 12px; height: 12px;
  border-radius: var(--r-1);
  background: var(--surface-2);
}
.year-key.lv-1 { background: var(--data-1); }
.year-key.lv-2 { background: var(--data-2); }
.year-key.lv-3 { background: var(--data-3); }
.year-key.lv-4 { background: var(--data-4); }
.year-key.lv-5 { background: var(--data-5); }
.year-key.is-uncollected {
  background: none;
  border: 1px dashed var(--rule);
}

/* ---------------------------------------------------------- sparkline
 *
 * No axis, no labels: one path, an optional min/max band, and a <title>
 * carrying first, last and peak. It is a shape in a sentence, not a chart. */
.spark { display: inline-block; vertical-align: middle; overflow: visible; }
.spark-band { fill: var(--data-1); stroke: none; }
.spark-path {
  fill: none;
  stroke: var(--ink-2);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.spark[data-tone='live'] .spark-path { stroke: var(--live); }
.spark[data-tone='warn'] .spark-path { stroke: var(--warn); }
.spark-end { fill: var(--ink); }

/* ---------------------------------------------------------- bar track
 *
 * The HTML (not SVG) bar row: one visit per bar on a shared span. A visit with
 * no observed end FADES rather than stopping at a hard edge — the same rule the
 * SVG spine follows, for the same reason: a squared-off end asserts a departure
 * that was never seen. */
.bartrack { position: relative; height: 18px; min-width: 0; }
.bartrack-rule {
  position: absolute; left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--viz-grid);
}
.bartrack-bar {
  position: absolute; top: 50%;
  height: var(--viz-bar-h);
  transform: translateY(-50%);
  border-radius: var(--r-1);
}

/* The axis row and the bar rows MUST share one grid-template-columns. `ch`
 * resolves against each element's own font, so an axis set at a different size
 * slides every tick off the bar it labels. This is why the shared template is
 * declared once, here, and both rows just reference it. */
.sheet-grid {
  display: grid;
  grid-template-columns: minmax(6rem, 11rem) minmax(0, 1fr) 5rem;
  column-gap: var(--s-3);
  align-items: center;
}
.sheet-axis { color: var(--ink-3); font-size: var(--t-3xs); }
.sheet-axis-ticks { display: flex; justify-content: space-between; }
