/* ============================================================================
   eplankton — charts.css
   ----------------------------------------------------------------------------
   Owns everything under `.chart` (the frame produced by chartFrame(), CONTRACT
   §6.6 / §9 "Chart frame") plus every SVG mark class the chart modules emit.

   Tuned for the light console: the plot ground is white (--surface-1), the frame
   is a hairline card with no resting shadow, titles are sentence case, and grid
   and axes are the lightest thing on the plot — never darker than the data.
   Shadows exist on the tooltip only.

   Two conventions the chart modules rely on — nothing else is assumed:

   1. COLOUR OWNERSHIP.
      Marks that are inherently multi-colour never get a `fill` from this file,
      because a CSS declaration always beats an SVG presentation attribute:
        .fp-cell .hex .cell .sunburst-arc .alluvial-link .alluvial-node
        .ridge-path
      For those, the module sets the colour (attribute or inline style) and this
      file supplies only structure — the surface-coloured gap stroke, widths,
      opacity, cursor, transitions.
      Marks that are inherently single-colour take their colour from the local
      custom property `--mark-color` (default `var(--series-1)`), so a module can
      recolour a whole group with one declaration:
        g.style.setProperty('--mark-color', 'var(--series-3)')
      (not g.setAttribute('style', …) — style-src-attr 'self' blocks it, see
      EPL-472.) An inline declaration set this way also wins over this file,
      always.

   2. VISIBILITY HOOKS.
      .chart-tooltip is hidden by default; it shows on `.on` / `.show` /
      `.visible` / `[data-show="true"]` / an inline `display`.
      .chart-table shows when the frame carries [data-view="table"].
      Busy state: `.chart.busy` or `.chart[aria-busy="true"]`.

   Tokens only — no colour literal appears below.
   ========================================================================== */

/* ============================================================================
   1. Frame
   ========================================================================== */

.chart {
  --mark-color: var(--series-1);
  --chart-pad-x: var(--s4);
  --chart-pad-y: var(--s3);

  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}

/* the frame is a card-like surface but flush: no inner shadow, no lift */
.chart + .chart { margin-top: var(--s4); }

.chart-head {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  min-width: 0;
  padding: var(--chart-pad-y) var(--chart-pad-x);
  border-bottom: 1px solid var(--line);
}

.chart-title {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14px;                  /* sentence case, never uppercase */
  line-height: 1.2;
  letter-spacing: -.01em;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* the sub gives way first: a truncated title is unreadable, a truncated
   subtitle is merely shorter */
.chart-sub {
  flex: 1 1 0;                      /* basis 0: the sub lives on the leftovers */
  min-width: 0;
  font-family: var(--font-ui);
  font-size: 11.5px;
  line-height: 1.2;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chart-actions {
  flex: 0 0 auto;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--s1);
  align-self: center;
}

/* bare buttons emitted by chartFrame; anything carrying .btn keeps its own look */
.chart-actions button:not(.btn),
.chart-actions .chart-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s1);
  min-width: 26px;
  height: 24px;
  padding: 0 var(--s2);
  background: var(--surface-1);
  border: 1px solid var(--line-control);
  border-radius: var(--r-sm);
  font-family: var(--font-ui);
  font-size: var(--fs-eyebrow);
  line-height: 1;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease),
              background-color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease);
}

.chart-actions button:not(.btn):hover,
.chart-actions .chart-action:hover {
  color: var(--text-primary);
  background: var(--surface-hover);
  border-color: var(--line-strong);
}

.chart-actions button:not(.btn).on,
.chart-actions button:not(.btn)[aria-pressed="true"],
.chart-actions .chart-action.on {
  color: var(--accent-text);
  border-color: var(--line-control);
  background: var(--accent-wash);
}

.chart-actions button:not(.btn):focus-visible,
.chart-actions .chart-action:focus-visible,
.chart-legend .legend-item:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

.chart-actions svg { width: 13px; height: 13px; display: block; fill: none;
  stroke: currentColor; stroke-width: 1.5px; stroke-linecap: round;
  stroke-linejoin: round; }

/* --- body ---------------------------------------------------------------- */

.chart-body {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
  padding: var(--chart-pad-y) var(--chart-pad-x);
}

.chart-body > svg,
.chart-svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
  overflow: hidden;                 /* never widens the page */
  -webkit-tap-highlight-color: transparent;
}

.chart-svg text,
.chart-body > svg text { user-select: none; -webkit-user-select: none; }

/* wide charts scroll inside their own wrapper, never the page */
.chart .scroll-x {
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}
.chart .scroll-x > svg,
.chart .scroll-x > .chart-svg {
  width: auto;
  max-width: none;
}
.chart .scroll-x::-webkit-scrollbar { height: 8px; }
.chart .scroll-x::-webkit-scrollbar-track { background: transparent; }
.chart .scroll-x::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: var(--r-full);
}

.chart-note {
  padding: 0 var(--chart-pad-x) var(--chart-pad-y);
  font-family: var(--font-ui);
  font-size: var(--fs-micro);
  line-height: 1.5;
  color: var(--text-muted);
}

.chart.dense {
  --chart-pad-x: var(--s3);
  --chart-pad-y: var(--s2);
}
.chart.dense .chart-title { font-size: 13px; }

/* ============================================================================
   2. Axes, grid, generic marks
   ========================================================================== */

.axis text,
text.axis {
  font-family: var(--font-ui);
  font-size: 10.5px;
  fill: var(--text-muted);
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
}

.axis path,
.axis line {
  stroke: var(--axis);
  stroke-width: 1px;
  fill: none;
  shape-rendering: crispEdges;
}

.grid-line,
.axis line.grid-line,
.axis .grid-line line {
  stroke: var(--grid);
  stroke-width: 1px;
  fill: none;
  vector-effect: non-scaling-stroke;
  shape-rendering: crispEdges;
}

/* the one uppercase that survives: a mono eyebrow, and it stays recessive */
.axis-label,
text.axis-label {
  font-family: var(--font-ui);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  fill: var(--text-muted);
}

/* text-transform:uppercase maps U+00B5 to a capital Greek Mu — "µm" would
   render "ΜM". Charts with a micron axis label opt out with this class. */
.axis-label.unit-verbatim {
  text-transform: none;
  letter-spacing: .06em;
}

/* an axis label's unit in its own tspan keeps its case, like a table column's */
.axis-label .unit {
  text-transform: none;
  letter-spacing: .06em;
}

.mark {
  vector-effect: non-scaling-stroke;
  transition: opacity var(--dur-1) var(--ease),
              fill-opacity var(--dur-1) var(--ease);
}

.mark.dim { opacity: .22; }
/* a group's hatch over its bar (engine.groupHatchPattern) dims with it */
.mark-hatch.dim { opacity: .22; }

.hit {
  fill: transparent;
  stroke: none;
  pointer-events: all;
  cursor: crosshair;
}

.crosshair,
line.crosshair,
.crosshair line {
  stroke: var(--text-muted);
  stroke-width: 1px;
  stroke-dasharray: 3 3;
  fill: none;
  pointer-events: none;
  shape-rendering: crispEdges;
  vector-effect: non-scaling-stroke;
}

/* ============================================================================
   3. Tooltip
   ========================================================================== */

.chart-tooltip {
  display: none;
  position: absolute;
  z-index: 6;
  top: 0;
  left: 0;
  /* Room for a value that says what it is per ("0.0059 mm³ / L of seawater")
     beside its label, so neither is cut (EPL-2334). */
  max-width: 320px;
  padding: var(--s2) var(--s3);
  background: var(--surface-1);     /* white card over white chart; the shadow
                                       and the --line-strong edge lift it */
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3);
  font-family: var(--font-ui);
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  animation: ep-tip-in 60ms linear both;
}

/* Dark is the secondary theme, not an inversion: there the card ground already
   IS --surface-1, so a --surface-1 tooltip vanishes into the chart it floats
   over and a black shadow lifts nothing. Overlays step UP on dark. */
:root[data-theme="dark"] .chart-tooltip { background: var(--surface-3); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .chart-tooltip {
    background: var(--surface-3);
  }
}

.chart-tooltip.on,
.chart-tooltip.show,
.chart-tooltip.visible,
.chart-tooltip[data-show="true"],
.chart-tooltip[data-open="true"] { display: block; }

.chart-tooltip[hidden] { display: none !important; }

@keyframes ep-tip-in { from { opacity: 0; } to { opacity: 1; } }

.chart-tooltip .tip-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-small);
  line-height: 1.25;
  letter-spacing: -.01em;
  color: var(--text-primary);
  margin-bottom: var(--s1);
}

.chart-tooltip .tip-sub {
  color: var(--text-muted);
  font-size: var(--fs-micro);
  margin-bottom: var(--s1);
}

/* a taxon path or an object id runs longer than the row it sits in */
.chart-tooltip .tip-sub.wrap {
  white-space: normal;
  word-break: break-word;
}

.chart-tooltip .tip-sub.tight { margin: 4px 0 0; }

.chart-tooltip .tip-title .rank-note { opacity: .7; }

.chart-tooltip .tip-thumb {
  display: block;
  width: 88px;
  height: 88px;
  /* The whole specimen on its imaging ground, as in the grid: the
     --specimen-bg exception noted at the top of components.css. A thumbnail
     is no longer padded square in the file. */
  --specimen-bg: #fff;
  object-fit: contain;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--specimen-bg);
  margin: 2px 0 6px;
}

/* components/thumbfit.js: a small thumbnail padded to the scale of its
   square. 44px is half the box; a percentage would be of the tooltip. */
.chart-tooltip .tip-thumb[data-thumb-fill] {
  padding: calc((1 - var(--thumb-fill, 1)) * 44px);
}

/* a morphospace cell's tooltip: a few of its objects in a row (EPL-3040) */
.chart-tooltip .tip-thumbs {
  display: flex;
  gap: 4px;
  margin: 2px 0 6px;
}

.chart-tooltip .tip-thumbs .tip-thumb {
  box-sizing: border-box;
  width: 52px;
  height: 52px;
  margin: 0;
}

/* thumbfit.js's padding, at the scale of this smaller box (half of 52px) */
.chart-tooltip .tip-thumbs .tip-thumb[data-thumb-fill] {
  padding: calc((1 - var(--thumb-fill, 1)) * 26px);
}

/* swatch + label + value, value right-aligned and tabular */
.chart-tooltip .row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  padding: 1px 0;
}

.chart-tooltip .row .swatch {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: currentColor;
}

.chart-tooltip .row .label,
.chart-tooltip .row > span:nth-child(2) {
  color: var(--text-secondary);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chart-tooltip .row .val,
.chart-tooltip .row .value,
.chart-tooltip .row > span:last-child {
  text-align: right;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.chart-tooltip .row.total {
  margin-top: var(--s1);
  padding-top: var(--s1);
  border-top: 1px solid var(--line);
}

/* ============================================================================
   4. Legend
   ========================================================================== */

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s1) var(--s4);
  padding: 0 var(--chart-pad-x) var(--chart-pad-y);
}

.chart-legend .legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  padding: 2px 4px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  font-family: var(--font-ui);
  font-size: var(--fs-micro);
  line-height: 1.2;
  color: var(--text-secondary);
  cursor: pointer;
  text-align: left;
  transition: opacity var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease);
  /* A legend value can be a whole mark — the compare chart's slope carries
     its cutoff weakness (EPL-986): 177 characters on S033_4. components.css's
     shared .legend-item says nowrap, which here cut that sentence off the
     card's edge with no ellipsis (PR #411 QA, EPL-992). The item takes at
     most the row and its text wraps at spaces; the swatch and name stay on
     the first line. */
  max-width: 100%;
  white-space: normal;
  flex-wrap: wrap;
}

.chart-legend .legend-item > span { min-width: 0; }

.chart-legend .legend-key {
  margin-right: -2px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.chart-legend .legend-item:hover,
.chart-legend .legend-item:focus { color: var(--text-primary); }

/* hovering / focusing one item dims the others */
.chart-legend:hover .legend-item:not(:hover),
.chart-legend:focus-within .legend-item:not(:focus):not(:hover) { opacity: .34; }

.chart-legend .legend-item[aria-pressed="false"] { opacity: .34; }

.chart-legend .swatch {
  flex: none;
  display: inline-block;
  background: currentColor;         /* module sets `color` (or `background`) */
}

.chart-legend .swatch.line {
  width: 12px;
  height: 2px;
  border-radius: 1px;
}

.chart-legend .swatch.dot {
  width: 9px;
  height: 9px;
  border-radius: var(--r-full);
}

.chart-legend .swatch.square {
  width: 9px;
  height: 9px;
  border-radius: 2px;
}

.chart-legend .swatch.area {
  position: relative;
  width: 12px;
  height: 9px;
  border-radius: 2px;
  background: color-mix(in srgb, currentColor 55%, transparent);
  border-top: 2px solid currentColor;
  box-sizing: border-box;
}

/* ============================================================================
   5. Table fallback
   ========================================================================== */

.chart-table {
  display: none;
  max-height: 320px;
  overflow: auto;
  overscroll-behavior: contain;
  margin: 0 var(--chart-pad-x) var(--chart-pad-y);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-1);
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

.chart[data-view="table"] .chart-table { display: block; }
.chart[data-view="table"] .chart-body,
.chart[data-view="table"] .chart-legend { display: none; }

.chart-table table,
table.chart-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
}

.chart-table th,
.chart-table td {
  padding: 3px var(--s3);
  font-family: var(--font-ui);
  font-size: var(--fs-micro);
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  border-bottom: 1px solid var(--line-faint);
}

.chart-table th {
  background: var(--surface-2);
  color: var(--text-muted);
  font-weight: 400;
  text-align: left;
}

/* Only the column headers are eyebrows, pinned while the table scrolls.
   The row headers in the first column are data (a size bin in µm, a taxon
   name) and print as written: uppercase would turn "µm" into "ΜM", which
   reads as millimetres, and would rename every taxon. */
.chart-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  border-bottom: 1px solid var(--line);
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  font-size: var(--fs-eyebrow);
}

/* a column's unit (`columns[].unit`) keeps its case, like .unit-verbatim */
.chart-table thead th .unit {
  text-transform: none;
  letter-spacing: .06em;
}

.chart-table td { color: var(--text-primary); }
.chart-table tbody tr:last-child th,
.chart-table tbody tr:last-child td { border-bottom: 0; }
.chart-table tbody tr:hover td { background: var(--surface-2); }

/* first column labels, every other column is a number */
.chart-table th + th,
.chart-table td + td { text-align: right; }
.chart-table [data-align="left"] { text-align: left; }
.chart-table [data-align="right"] { text-align: right; }

.chart-table::-webkit-scrollbar { width: 8px; height: 8px; }
.chart-table::-webkit-scrollbar-track { background: transparent; }
.chart-table::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: var(--r-full);
}

/* ============================================================================
   6. Empty & busy states
   ========================================================================== */

/* on the light ground the empty state is a quiet sunken panel, not a void */
.chart-empty {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  padding: var(--s6) var(--s4);
  background: var(--surface-2);
  font-family: var(--font-ui);
  font-size: 11.5px;
  line-height: 1.6;
  letter-spacing: .02em;
  text-align: center;
  color: var(--text-muted);
  pointer-events: none;
}

.chart-body:has(> .chart-empty) { min-height: 148px; }

/* A grade withheld on purpose (quality gauge): the message is a block in
   flow above the checks that did run and the flags, not an overlay hiding
   them; the arc's empty plot is dropped so the message sits where it was. */
.chart-empty.chart-empty--inflow {
  position: static;
  min-height: 148px;
  border-radius: var(--r-md);
  margin-bottom: var(--s3);
}
.chart-body:has(> .chart-empty--inflow) > svg { display: none; }

.chart.busy .chart-body,
.chart[aria-busy="true"] .chart-body {
  opacity: .6;                      /* on white, .55 washed the marks out */
  transition: opacity var(--dur-2) var(--ease);
}

/* the sweep rides the top edge; 2px so it is legible against white */
.chart.busy::after,
.chart[aria-busy="true"]::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 34%;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: ep-chart-sweep 1.6s var(--ease) infinite;
  pointer-events: none;
  z-index: 4;
}

@keyframes ep-chart-sweep {
  from { transform: translateX(-110%); }
  to   { transform: translateX(310%); }
}

/* ============================================================================
   7. Mark helpers — one block per chart family. Tokens only.
   ========================================================================== */

/* --- fingerprint (charts/fingerprint.js) — renders at 28px and at 640px ---- */
.fp-cell {
  stroke: var(--surface-1);
  stroke-width: 2px;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  transition: opacity var(--dur-1) var(--ease),
              fill-opacity var(--dur-1) var(--ease);
}
/* a classified cell nobody measured (fingerprint.js decision 7): off the
   confidence ramp, outlined in its own hue; selection still wins below */
.fp-cell.unmeasured {
  stroke: var(--fp-unmeasured, currentColor);
  stroke-dasharray: 3 2;
}
.fp-cell.sel,
.fp-cell.on { stroke: var(--accent); stroke-width: 2px; }
.fp-cell.dim { opacity: .22; }

.fp-ring-label,
text.fp-ring-label {
  font-family: var(--font-ui);
  font-size: 9.5px;
  letter-spacing: .06em;
  fill: var(--text-muted);
  text-anchor: middle;
  paint-order: stroke fill;
  stroke: var(--surface-1);
  stroke-width: 2.5px;
  stroke-linejoin: round;
  pointer-events: none;
}

/* guide rings: --line vanished on white, --axis is the recessive hairline */
.fp-axis,
.fp-ring {
  fill: none;
  stroke: var(--axis);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}
/* the spoke between the living and the non-living sectors (fingerprint.js decision 9) */
.fp-side {
  stroke: var(--text-muted);
  stroke-dasharray: 3 3;
}

/* --- morphospace hexbin (charts/morphospace.js) --------------------------- */
.hex {
  stroke: var(--surface-1);
  stroke-width: .75px;
  vector-effect: non-scaling-stroke;
  shape-rendering: geometricPrecision;
  transition: opacity var(--dur-1) var(--ease);
}

/* a clicked cell (EPL-3040): it draws no brush rectangle, so its edge is the mark */
.hex-picked {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

.taxon-ellipse {
  fill: none;
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

.outlier-pin {
  fill: none;
  stroke: var(--text-secondary);
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
  cursor: pointer;
}

/* --- size spectrum (charts/sizespectrum.js) ------------------------------- */
.nbss-fit {
  fill: none;
  stroke: var(--text-secondary);
  stroke-width: 2px;
  stroke-dasharray: 6 4;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

.nbss-point {
  fill: var(--mark-color);
  stroke: var(--surface-1);
  stroke-width: 2px;                /* surface ring on overlapping marks */
  vector-effect: non-scaling-stroke;
  transition: opacity var(--dur-1) var(--ease);
}

/* --- taxonomy (charts/taxonomy.js) ---------------------------------------- */
.sunburst-arc {
  stroke: var(--surface-1);
  stroke-width: 2px;
  stroke-linejoin: round;
  cursor: pointer;
  transition: opacity var(--dur-1) var(--ease);
}
.sunburst-arc:hover { opacity: .82; }
.sunburst-arc.dim { opacity: .22; }

circle.sunburst-center,
path.sunburst-center,
rect.sunburst-center {
  fill: var(--surface-1);
  stroke: var(--line-strong);       /* --line disappeared into the white hole */
  stroke-width: 1px;
  cursor: pointer;
}

text.sunburst-center {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-lg);
  letter-spacing: var(--track-display);
  text-anchor: middle;
  fill: var(--text-primary);
  stroke: none;
  pointer-events: none;
}

.treemap-cell {
  stroke: var(--surface-1);
  stroke-width: 2px;
  cursor: pointer;
  shape-rendering: crispEdges;
}

/* --- flow / alluvial (charts/flow.js) ------------------------------------- */
.alluvial-link {
  fill-opacity: .38;
  stroke: none;
  transition: fill-opacity var(--dur-1) var(--ease);
  cursor: pointer;
}
.alluvial-link:hover,
.alluvial-link.on { fill-opacity: .62; }
.alluvial-link.dim { fill-opacity: .12; }

.alluvial-node {
  stroke: var(--surface-1);
  stroke-width: 1px;
  cursor: pointer;
  transition: opacity var(--dur-1) var(--ease);
}

.alluvial-label,
text.alluvial-label {
  font-family: var(--font-ui);
  font-size: var(--fs-eyebrow);
  fill: var(--text-secondary);
  pointer-events: none;
}

/* --- ridgeline (charts/ridgeline.js) -------------------------------------- */
/* colour (fill + stroke) comes from the module; .55 fill + 2px top edge here */
.ridge-path {
  fill-opacity: .55;
  stroke-width: 2px;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  transition: fill-opacity var(--dur-1) var(--ease);
}
.ridge-path.dim { fill-opacity: .16; opacity: .5; }

.ridge-label,
text.ridge-label {
  font-family: var(--font-ui);
  font-size: var(--fs-eyebrow);
  fill: var(--text-secondary);
  pointer-events: none;
}

.ridge-base {
  stroke: var(--axis);
  stroke-width: 1px;
  fill: none;
  shape-rendering: crispEdges;
}

/* --- diversity radar (charts/diversity.js) -------------------------------- */
.radar-grid {
  fill: none;
  stroke: var(--grid);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
  shape-rendering: geometricPrecision;
  pointer-events: none;
}
.radar-grid.spoke { stroke: var(--axis); }

.radar-area {
  fill: var(--mark-color);
  fill-opacity: .22;
  stroke: var(--mark-color);
  stroke-width: 2px;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.radar-point {
  fill: var(--mark-color);
  stroke: var(--surface-1);
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

/* --- heatmap (charts/heatmap.js) ------------------------------------------ */
.cell {
  stroke: var(--surface-1);
  stroke-width: 1px;                /* the 1px surface gap between cells */
  shape-rendering: crispEdges;
  cursor: pointer;
  transition: opacity var(--dur-1) var(--ease);
}
.cell.sel {
  stroke: var(--accent);
  stroke-width: 2px;
  shape-rendering: geometricPrecision;
}
.cell.dim { opacity: .3; }

.reference-line {
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 1.5px;
  stroke-dasharray: 4 4;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* --- quality gauge (charts/quality.js) ------------------------------------ */
.gauge-track {
  fill: none;
  stroke: var(--surface-3);         /* a visible unfilled arc on white */
  stroke-width: 10px;
  stroke-linecap: round;
}

.gauge-fill {
  fill: none;
  stroke: var(--mark-color);
  stroke-width: 10px;
  stroke-linecap: round;
  transition: stroke-dashoffset var(--dur-3) var(--ease),
              stroke var(--dur-2) var(--ease);
}

.gauge-value,
text.gauge-value {
  font-family: var(--font-display); /* hero number — matches .stat-value */
  font-weight: 600;
  font-size: var(--ds-md);
  letter-spacing: var(--track-display);
  font-variant-numeric: tabular-nums;
  text-anchor: middle;
  fill: var(--text-primary);
}

/* --- sparklines (charts/sparkline.js) — often live outside a .chart frame -- */
.spark-line {
  fill: none;
  stroke: var(--mark-color, var(--series-1));
  stroke-width: 1.5px;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.spark-area {
  fill: var(--mark-color, var(--series-1));
  fill-opacity: .16;
  stroke: none;
}

.spark-bar {
  fill: var(--mark-color, var(--series-1));
  rx: 1px;
  shape-rendering: crispEdges;
}
.spark-bar.dim { opacity: .3; }

/* --- ocean map (charts/map.js) -------------------------------------------- */
.map-graticule {
  fill: none;
  stroke: var(--grid);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* --surface-3 land on a --surface-2 ocean is only one step of contrast, so the
   coastline carries the shape: a full 1px of --line-strong, non-scaling so it
   stays a hairline through the map's own zoom. Without it: a blank rectangle. */
.map-land {
  fill: var(--surface-3);
  stroke: var(--line-strong);
  stroke-width: 1px;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* EPL-960: the LOD coastline (levels 1-2) draws fill and edge as separate
   paths per tile, expanded-bbox fills overlapping at tile seams, so each
   needs only one half of .map-land's look rather than both. */
.map-land-fill {
  fill: var(--surface-3);
  stroke: none;
  pointer-events: none;
}
.map-lake-fill {
  /* The ocean's own fill: inland water is drawn as water. Nonzero, as the
     server reads the lakes: some lake records overlap, and an island winds
     against its lake. */
  fill: var(--surface-2);
  fill-rule: nonzero;
  stroke: none;
  pointer-events: none;
}
.map-land-edge {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 1px;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

.map-pin {
  fill: var(--mark-color);
  stroke: var(--surface-1);
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
  cursor: pointer;
  transition: opacity var(--dur-1) var(--ease),
              stroke var(--dur-1) var(--ease);
}
.map-pin.on {
  stroke: var(--accent);
  stroke-width: 2.5px;
}
.map-pin.dim { opacity: .28; }

/* --- run progress stream (charts/progress.js) ----------------------------- */
.run-stream-bar {
  fill: var(--mark-color);
  rx: 1px;
  shape-rendering: crispEdges;
  transition: opacity var(--dur-1) var(--ease);
}

.stage-tick {
  fill: none;
  stroke: var(--axis);
  stroke-width: 1px;
  stroke-dasharray: 3 3;
  shape-rendering: crispEdges;
  pointer-events: none;
}

text.stage-tick {
  stroke: none;
  fill: var(--text-muted);
  font-family: var(--font-ui);
  font-size: 9.5px;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
}

/* ============================================================================
   8. Responsive
   ========================================================================== */

@media (max-width: 640px) {
  .chart { --chart-pad-x: var(--s3); }
  .chart-head { flex-wrap: wrap; row-gap: var(--s1); }
  .chart-sub { flex-basis: 100%; }
  .chart-actions { margin-left: auto; }
  .chart-tooltip { max-width: 220px; }
  .chart-table { max-height: 260px; }
}

/* ============================================================================
   9. Motion, forced colours, print
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .chart,
  .chart *,
  .chart *::before,
  .chart *::after,
  .chart-tooltip,
  .fp-cell,
  .hex,
  .cell,
  .mark,
  .spark-line,
  .spark-area,
  .spark-bar,
  .map-pin,
  .gauge-fill,
  .ridge-path,
  .sunburst-arc,
  .alluvial-link {
    transition: none !important;
    animation: none !important;
  }
  .chart.busy::after,
  .chart[aria-busy="true"]::after {
    width: 100%;
    opacity: .45;
    transform: none;
  }
}

@media (forced-colors: active) {
  .chart {
    border-color: currentColor;
  }
  .chart-head,
  .chart-table th { border-bottom-color: currentColor; }

  .mark,
  .fp-cell,
  .hex,
  .cell,
  .sunburst-arc,
  .treemap-cell,
  .alluvial-link,
  .alluvial-node,
  .ridge-path,
  .radar-area,
  .radar-point,
  .nbss-point,
  .spark-bar,
  .spark-area,
  .map-land,
  .map-land-fill,
  .map-lake-fill,
  .map-land-edge,
  .map-pin,
  .run-stream-bar {
    stroke: currentColor;
    stroke-width: 1px;
  }

  .chart-tooltip {
    border-color: currentColor;
    box-shadow: none;
  }
  .chart-legend .swatch { border: 1px solid currentColor; }
  .chart.busy::after,
  .chart[aria-busy="true"]::after { background: currentColor; }
}

@media print {
  .chart {
    break-inside: avoid;
    page-break-inside: avoid;
    box-shadow: none;
  }
  .chart-actions,
  .chart-tooltip,
  .crosshair { display: none !important; }
  .chart.busy::after,
  .chart[aria-busy="true"]::after { display: none; }
  .chart-table { max-height: none; overflow: visible; }
}

/* The map draws a full Robinson graticule whose outer meridian sits a hair
   outside the viewBox; without an explicit clip the SVG root inherits
   `overflow: visible` from the reset and pushes the page 2px wide. */
.chart-svg { overflow: hidden; }
