/* ═══════════════════════════════════════════════════════════════════
   CHARTS.CSS · Data-visualization tokens
   ───────────────────────────────────────────────────────────────────
   Canonical chart layer. Until this file existed, every chart on
   baseforce.io hand-rolled its own chrome and its own palette, and
   the documented --chart-* tokens lived only in private/loam/assets/
   — a path no production page loads.

   THIS FILE IS TOKENS ONLY. Chart chrome (axis, grid, tooltip,
   legend, table-view twin) lands here next; do not add page-local
   chart CSS in the meantime.

   Load order:
     1. tokens.css
     2. theme-[name].css
     3. vivid.css
     4. charts.css        (this file)
     5. components.css

   ───────────────────────────────────────────────────────────────────
   WHY THIS FILE EXISTS — the accent ramp is not a categorical palette

   --accent … --accent-5 was documented as "4-5 colors: categorical
   charts where each series needs to be distinguishable." It is not.
   Measured (OKLab ΔE ×100, Machado-Oliveira-Fernandes 2009 @ 1.0):

     slate day    all 5 below the 0.10 chroma floor (max 0.063)
                  worst adjacent pair ΔE 14.1 normal vision  (floor 15)
     slate night  worst adjacent pair ΔE  7.1 normal vision
                  --accent and --accent-2 are the SAME COLOUR to a
                  full-colour reader, before colourblindness applies

   Night is authored as the default theme on every page, so this was
   the shipping case.

   The ramp is not broken — it is mis-cast. Slate 1→3 validates clean
   as an ORDINAL ramp in both modes (monotone L, ΔL gaps, single hue,
   light end clears surface). It is a sequential ramp that was being
   asked to carry identity.

   So: identity work moves to --cat-*, below, seeded from the vivid
   hues and snapped to passing. --vivid keeps its punctuation role
   (hero number, CTA fill, live dot) at its current values — it is
   NOT re-stepped, because darkening --vivid-2 for chart compliance
   would have degraded every hero number on night.
   ═══════════════════════════════════════════════════════════════════ */


/* ─── Categorical · identity (which series) ──────────────────────────
   THREE SLOTS. THAT IS THE CAP, and it is a measured limit, not a
   stylistic preference: a 4th hue cannot be seated on this palette
   without dropping a pair under the ΔE 15 normal-vision gate.

   Past three series: fold the tail into "Other", facet into small
   multiples, or use a table. Never generate a 4th hue.

   Assign in fixed order, 1 → 2 → 3, never cycled. Colour follows the
   entity, never its rank — a filter that changes the series count
   must not repaint the survivors.

   Validated (--pairs all, the harder test, so these hold for scatter
   and bubble as well as bars and lines):
     day    vs --surface-raised #FCFBF7
            CVD ΔE 10.8 · normal-vision ΔE 16.6 · contrast 4.65/3.31/5.63
     night  vs --surface-raised #292927
            CVD ΔE 10.3 · normal-vision ΔE 16.2 · contrast 4.93/3.31/5.12
   ─────────────────────────────────────────────────────────────────── */

/* DEFAULT — the slate slots, so --cat-* is never undefined under a
   theme that has not been done yet. A theme without its own block
   renders correct, validated, colour-blind-safe charts that are simply
   off its own hue family; that degrades honestly, where an empty token
   would collapse every series to gray.

   THE OTHER FIVE THEMES ARE NOT DONE, and that is deliberate, not an
   oversight. Snapping each theme's --vivid triple to passing produces
   mechanically valid but off-register colour — moss night lands on
   #EC0C36, rust night on #F93E8F, in a system whose register is
   "the forest floor after rain" and "barren cold stone". Honey night
   has NO passing triple on its vivid seed at all. Picking hues that
   both clear the gates and stay in the family is a design decision per
   theme, so it wants a design session and a DS-D ruling, not a script.
   Until then every theme borrows slate's. Only slate ships in
   production today (every page loads theme-slate.css). */
:root {
  --cat-1:  #1A78B0;
  --cat-2:  #B0832A;
  --cat-3:  #2E7243;
}

[data-accent="slate"] {
  --cat-1:  #1A78B0;   /* kingfisher — as --vivid */
  --cat-2:  #B0832A;   /* lake gold  — as --vivid-2 */
  --cat-3:  #2E7243;   /* reed       — --vivid-3 lifted to the chroma floor
                          (C 0.081 -> 0.102; L and hue held) */
}

/* Night default, same reasoning. */
[data-theme="night"] {
  --cat-1:  #3DA0CD;
  --cat-2:  #94740F;
  --cat-3:  #49AC67;
}

[data-accent="slate"][data-theme="night"] {
  --cat-1:  #3DA0CD;   /* dawn water */
  --cat-2:  #94740F;   /* lamplight, brought into the dark lightness band.
                          Yellow is intrinsically light: any gold pushed to
                          L <= 0.67 reads as ochre. That is correct for a
                          chart FILL on a dark surface — bright saturated
                          marks glare — and is why --vivid-2 (#ECC458), which
                          is display TYPE, is deliberately not this value. */
  --cat-3:  #49AC67;   /* reed moon */
}


/* ─── Ordinal / sequential · magnitude and position ──────────────────
   One hue, light -> dark. This is what the accent ramp is actually for,
   and it is the SAFE DEFAULT — reach for it unless the data's job is
   specifically identity or polarity.

   Use for: funnel stages, size tiers, age bands, cohort buckets, any
   ordered scale, and any single-series magnitude comparison (one
   series -> one colour for every bar; never colour nominal bars by
   their value).

   Validated as an ordinal ramp, both modes: PASS monotone lightness,
   PASS adjacent ΔL >= 0.06, PASS light-end contrast, PASS single hue
   (spread 11 deg day / 10 deg night).
   ─────────────────────────────────────────────────────────────────── */

:root {
  --seq-1:  var(--accent);     /* lightest in night, darkest in day */
  --seq-2:  var(--accent-2);
  --seq-3:  var(--accent-3);
}


/* ─── The chart ground ───────────────────────────────────────────────
   Charts mount on --surface-raised. Not a stylistic preference:
   --cat-2 measures 2.58:1 on --surface-flat and 2.91:1 on
   --surface-page, both under the 3:1 mark minimum, and no gold exists
   that clears 3:1 on --surface-flat while staying separable from
   --cat-3 (the nearest candidate collapses to ΔE 5.9). The ground
   moves, not the ink.
   ─────────────────────────────────────────────────────────────────── */

.chart-surface {
  background: var(--surface-raised);
}


/* ─── Reserved ───────────────────────────────────────────────────────
   Status tokens (--status-live / -attention / -error) are NEVER a
   series colour, and --cat-* is never a status. A series wears status
   tokens only when the series MEANS good/bad (delta bars, pass/fail,
   risk tiers) — and then it ships with an icon or label, never colour
   alone. Diverging (above/below a baseline) is --status-live against
   --status-error with a neutral --text-tertiary midpoint.
   ─────────────────────────────────────────────────────────────────── */


/* ═══════════════════════════════════════════════════════════════════
   CHROME
   Ported from private/loam/assets/charts.css, which was well-built but
   unreachable — no production page loads that path, so every --chart-*
   token it declared resolved to nothing on baseforce.io.
   Corrections made in the port are marked CORRECTED.
   ═══════════════════════════════════════════════════════════════════ */


/* ─── Chart container ────────────────────────────────────────────────
   A chart lives inside .chart. Use .card when it needs surface
   separation; .chart alone when flush.

   Any fixed height MUST include the x-axis band — plot height plus
   axis labels — or the card gets a nested vertical scrollbar and the
   axis is what gets cut. Prefer letting the container grow.
   ─────────────────────────────────────────────────────────────────── */

/* Chrome tokens live at :root, not on .chart, because chart libraries
   read them with getComputedStyle(document.documentElement) — see
   chart-helpers.js chartTokens(). Declared on .chart they would
   resolve to empty strings there. .chart may still override locally. */
:root {
  --chart-axis-stroke:     var(--border-rest);
  --chart-axis-text:       var(--text-tertiary);
  --chart-gridline:        var(--surface-divider);
  --chart-gridline-major:  var(--border-rest);
  --chart-tick:            var(--text-tertiary);

  --chart-tooltip-bg:      var(--surface-elevated);
  --chart-tooltip-border:  var(--border-rest);
  --chart-tooltip-text:    var(--text-primary);

  --chart-pos:             var(--status-live);      /* positive delta */
  --chart-neg:             var(--status-error);     /* negative delta */
  --chart-zero:            var(--text-tertiary);    /* exactly zero */

  /* CORRECTED: was --text-muted (2.6:1), under the 3:1 mark minimum.
     A forecast line is a MARK, not display type. Dashing carries the
     "projected" meaning; the stroke still has to be visible. */
  --chart-projected:       var(--text-tertiary);

  /* De-emphasis — the gray that "Other" and every non-hero series wear
     in an emphasis chart. Not a categorical slot. */
  --chart-muted:           var(--text-tertiary);

  /* Mark specs. Fixed across every chart; read them, do not retype them. */
  --chart-line-w:          2px;      /* round join + cap */
  --chart-marker-r:        4px;      /* >= 8px diameter */
  --chart-ring-w:          2px;      /* surface ring on dots/end-markers */
  --chart-gap:             2px;      /* surface gap between touching fills */
  --chart-bar-max:         24px;     /* cap thickness; leftover band is air */
  --chart-bar-radius:      4px;      /* data-end only; square at baseline */
  --chart-hit-min:         24px;     /* minimum pointer/focus target */
  --chart-area-alpha:      0.10;     /* area wash, never a saturated block */
}

.chart {
  width: 100%;
  position: relative;
  font-family: var(--font-sans);
  font-size: var(--size-tiny);
  color: var(--text-secondary);
  background: var(--surface-raised);   /* the chart ground — see above */
}

.chart-canvas { width: 100%; display: block; }

.chart--sm .chart-canvas { height: 180px; }
.chart--md .chart-canvas { height: 280px; }
.chart--lg .chart-canvas { height: 380px; }
.chart--xl .chart-canvas { height: 480px; }

.chart-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.chart-title {
  font-size: var(--size-label);
  letter-spacing: var(--track-uppercase);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-3);
}

/* Said out loud when the renderer draws something other than what was
   asked for — a pie above the 3-slice cap becomes a bar. Silently
   substituting a form is worse than substituting one and saying so. */
.chart-demoted {
  font-size: var(--size-tiny);
  color: var(--text-tertiary);
  font-style: italic;
  line-height: 1.5;
  margin: 0 0 var(--space-3);
}


/* ─── Legend ─────────────────────────────────────────────────────────
   PRESENT WHENEVER THERE ARE TWO OR MORE SERIES. Identity is never
   carried by colour alone. A SINGLE series gets no legend box — one
   swatch just restates the title and costs space.

   The swatch mirrors the mark: rect for bars and areas, line for lines.
   ─────────────────────────────────────────────────────────────────── */

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-3);
  font-size: var(--size-tiny);
  color: var(--text-secondary);   /* text wears a TEXT token, never the series colour */
}

.chart-legend--compact { gap: var(--space-3); font-size: var(--size-label); }

.chart-legend--right {
  flex-direction: column;
  gap: var(--space-2);
  margin-top: 0;
  margin-left: var(--space-4);
  min-width: 120px;
}

.legend-item { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }

.legend-swatch { width: 10px; height: 10px; border-radius: var(--radius-sm); flex-shrink: 0; }
.legend-swatch--line   { height: var(--chart-line-w); width: 14px; border-radius: 0; }
.legend-swatch--dashed {
  height: var(--chart-line-w); width: 14px; border-radius: 0;
  background: repeating-linear-gradient(90deg, currentColor 0, currentColor 3px, transparent 3px, transparent 6px);
}


/* ─── Tooltip ────────────────────────────────────────────────────────
   Tooltips ENHANCE, they never gate: every value a tooltip shows is
   also reachable without it, via a direct label or the table view.
   Same content on keyboard focus as on hover.

   Values lead, labels follow — the reader already has the series and
   wants the number, so the value is the high-contrast element.
   ─────────────────────────────────────────────────────────────────── */

.chart-tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--chart-tooltip-bg);
  border: 0.5px solid var(--chart-tooltip-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  font-size: var(--size-tiny);
  color: var(--chart-tooltip-text);
  min-width: 120px;
  max-width: 240px;
  z-index: 50;
  opacity: 0;
  transition: opacity var(--motion-short) var(--ease);
}

.chart-tooltip[data-visible="true"] { opacity: 1; }

.chart-tooltip-title {
  font-size: var(--size-label);
  letter-spacing: var(--track-uppercase);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--space-1);
  font-style: italic;
}

.chart-tooltip-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); padding: 2px 0;
}
.chart-tooltip-row-label { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-secondary); }
.chart-tooltip-row-value { font-variant-numeric: tabular-nums; font-weight: var(--weight-medium); color: var(--text-primary); }

/* Line key, not a filled box — at tooltip density a box is data-weight
   ink doing a label's job. */
.chart-tooltip-key { width: 10px; height: var(--chart-line-w); border-radius: 0; flex-shrink: 0; }


/* ─── Table view — the accessibility twin ────────────────────────────
   EVERY chart has one. It is what makes a tooltip legal (values stay
   reachable without hovering) and what makes a sub-3:1 fill legal.

   Default is visually hidden but screen-reader reachable. Add
   [data-table-view="true"] on the .chart to reveal it for everyone —
   that is the toggle, and it is also the print and forced-colors path.
   ─────────────────────────────────────────────────────────────────── */

.chart-table {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.chart[data-table-view="true"] .chart-table {
  position: static; width: auto; height: auto;
  margin: var(--space-4) 0 0; overflow-x: auto;
  clip: auto; clip-path: none; white-space: normal;
}

.chart[data-table-view="true"] .chart-canvas-wrap { display: none; }

.chart-table table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.chart-table caption {
  caption-side: top; text-align: left;
  font-size: var(--size-label); letter-spacing: var(--track-uppercase);
  text-transform: uppercase; color: var(--text-tertiary);
  font-style: italic; padding-bottom: var(--space-2);
}
.chart-table th {
  text-align: left; padding: var(--space-2) var(--space-3);
  font-size: var(--size-label); font-weight: var(--weight-medium);
  color: var(--text-tertiary); letter-spacing: var(--track-uppercase);
  text-transform: uppercase; border-bottom: 1px solid var(--border-rest);
}
.chart-table td {
  padding: var(--space-2) var(--space-3); font-size: var(--size-small);
  color: var(--text-primary); border-bottom: 0.5px solid var(--border-rest);
}
.chart-table td.num, .chart-table th.num { text-align: right; }

.chart-table-toggle {
  background: none; border: 0.5px solid var(--border-rest);
  border-radius: var(--radius-sm); cursor: pointer;
  font-family: var(--font-sans); font-size: var(--size-label);
  letter-spacing: var(--track-uppercase); text-transform: uppercase;
  color: var(--text-tertiary); padding: 2px var(--space-2);
}
.chart-table-toggle:hover { color: var(--text-primary); border-color: var(--border-hover); }


/* ─── Delta indicators ─────────────────────────────────────────────── */

.chart-delta {
  display: inline-flex; align-items: center; gap: var(--space-1);
  font-size: var(--size-tiny); font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}
.chart-delta--pos  { color: var(--chart-pos); }
.chart-delta--neg  { color: var(--chart-neg); }
.chart-delta--zero { color: var(--chart-zero); }


/* ─── SVG axis / grid ────────────────────────────────────────────────
   Gridlines and axis rules are SOLID hairlines, one step off the
   surface. Never dashed: dashing reads as "projection" or "threshold"
   when it is just a grid, and it collides with the dashed forecast
   line. Dashing is reserved for a series that IS projected, and for a
   cutoff/threshold rule.

   Horizontal gridlines only on time-domain charts; four or five max.
   ─────────────────────────────────────────────────────────────────── */

.chart-axis-label {
  font-size: var(--size-label); fill: var(--chart-axis-text);
  letter-spacing: var(--track-uppercase); text-transform: uppercase; font-style: italic;
}
.chart-tick-label {
  font-size: var(--size-tiny); fill: var(--chart-tick);
  font-variant-numeric: tabular-nums;   /* tabular ONLY in columns and ticks */
}
.chart-axis-line   { stroke: var(--chart-axis-stroke); stroke-width: 0.5; fill: none; }
.chart-gridline    { stroke: var(--chart-gridline); stroke-width: 0.5; fill: none; }
.chart-gridline--major { stroke: var(--chart-gridline-major); }

/* The zero baseline on any chart that can cross zero is slightly
   heavier — it anchors the crossing point. */
.chart-baseline { stroke: var(--chart-zero); stroke-width: 1; fill: none; }

/* A projected/forecast series, and a cutoff rule. Dashing is the
   secondary encoding; it is NOT accompanied by an opacity drop. */
.chart-line--projected { stroke-dasharray: 4 4; }
.chart-cutoff { stroke: var(--chart-muted); stroke-width: 1; stroke-dasharray: 3 3; fill: none; }

/* Transparent hit target. Sized to --chart-hit-min, not to the painted
   mark — an 8px dot is a pinpoint nobody lands on. */
.chart-hit { fill: transparent; cursor: pointer; }
.chart-hit:focus-visible { outline: 1px solid var(--border-selected); outline-offset: 1px; }


/* ─── Empty state ────────────────────────────────────────────────────
   Preserves the container footprint so nothing shifts when data lands.
   Says which of the three things happened — loading, failed, or
   genuinely empty — never "No data" for all three (DS-D175).
   ─────────────────────────────────────────────────────────────────── */

.chart-empty {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 180px;
  color: var(--text-tertiary); font-size: var(--size-small); font-style: italic;
  background: var(--surface-flat); border-radius: var(--radius-md);
}


/* ─── Refetch ────────────────────────────────────────────────────────
   Hold the previous render at reduced opacity. No skeleton flash, no
   layout jump.
   ─────────────────────────────────────────────────────────────────── */

.chart[data-refetching="true"] .chart-canvas-wrap {
  opacity: 0.45;
  transition: opacity var(--motion-short) var(--ease);
}


/* ─── Forced colors / print ──────────────────────────────────────────
   Where hue stops carrying identity, the table view is the answer.
   ─────────────────────────────────────────────────────────────────── */

@media print {
  .chart .chart-canvas-wrap { display: none; }
  .chart .chart-table {
    position: static; width: auto; height: auto; margin: 0;
    clip: auto; clip-path: none; white-space: normal;
  }
  .chart-table-toggle { display: none; }
}


/* ═══════════════════════════════════════════════════════════════════
   End of charts.css
   ═══════════════════════════════════════════════════════════════════ */
