/* charts.css -- Everything a figure needs: the chart frame, cross-chart linking, the entrance choreography, the table view every chart must have, and the one hero number.
 *
 * Part 5 of 9. These files were one style.css and are still one
 * stylesheet: equal-specificity rules resolve by document order, so the
 * <link> tags in every page must stay in this order and nothing may move
 * between files without checking what it lands after.
 *   tokens.css -> layout.css -> type.css -> prose.css -> charts.css -> sources.css -> paper.css -> interactive.css -> home.css
 *
 * See site/README.md for the rules that govern all of them. */

/* --- Charts -------------------------------------------------------------- */

figure.chart { margin: var(--space-5) 0; }

.chart-title {
  font: 600 var(--size-3)/var(--lh-snug) var(--font-display);
  margin: 0 0 var(--space-1);
}

.chart-sub {
  font: 400 var(--size-2)/var(--lh-body) var(--font-display);
  color: var(--text-secondary);
  margin: 0 0 var(--space-3);
}

/* The drawing shares its edges with the prose, the title, the subtitle and the
 * legend -- the whole figure is one column with everything else on the page. The
 * SVG scales to whatever that column is (viewBox plus width:100%), so this needs
 * no width of its own beyond the block default. */
.chart-canvas {
  position: relative;
  width: 100%;
}
.chart-canvas svg { display: block; width: 100%; height: auto; overflow: visible; }

.axis-line { stroke: var(--border); stroke-width: 1; }
.grid-line { stroke: var(--grid); stroke-width: 1; }
.axis-label,
.tick-label {
  font: 400 var(--size-1)/var(--lh-tight) var(--font-display);
  fill: var(--text-muted);
}
.axis-label { font-weight: 500; fill: var(--text-secondary); }
/* One row of a category axis picked out from its neighbors -- the sampled
 * parcel among the eight around it. Weight and ink, not color: the hues on
 * these charts are spoken for by the series, and a colored row label would
 * claim to be one of them. */
.tick-label--emphasis { font-weight: 700; fill: var(--text-primary); }
.point-label {
  font: 500 var(--size-1)/var(--lh-tight) var(--font-display);
  fill: var(--text-secondary);
}
/* A plant's name beside its own cores on the ordination. The halo is the
 * surface color, so the letters stay readable across hulls and spokes without
 * fencing off a box of the plot. */
.cluster-label {
  font: 600 var(--size-1)/var(--lh-tight) var(--font-display);
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.value-label {
  font: 600 var(--size-1)/var(--lh-tight) var(--font-display);
  fill: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.mean-bar { stroke: var(--text-primary); stroke-width: 2.5; stroke-linecap: round; }

/* An annotation is the chart saying what it shows. It outranks the axis
 * furniture and sits below the marks: primary ink, so it is read before a tick
 * label, but no color of its own -- a colored note would claim to be one of
 * the series. The leader is hairline and muted, because it carries no
 * information beyond "this text is about that mark". */
.annotation__text {
  font: 500 var(--size-1)/var(--lh-snug) var(--font-display);
  fill: var(--text-primary);
}
.annotation__leader {
  stroke: var(--text-muted);
  stroke-width: 1;
  fill: none;
}

/* A 2px ring in the surface color so overlapping dots stay countable. */
.mark {
  /* Ink outline on every plotted mark (2026-08-18; it was a paper halo), so
   * points hold their edge against boxes, bars and each other. */
  stroke: var(--text-primary);
  stroke-width: 1.25;
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.16s ease, stroke-width 0.16s ease;
}
.mark:hover { stroke-width: 2.25; }

/* --- Cross-chart linking -------------------------------------------------- */

/* Hovering one soil core dims the other twelve everywhere on the page. Dimming
 * rather than hiding: the muted marks still show where the rest of the data
 * sits, so the highlighted core is read in context instead of in isolation. */
.mark.is-muted { opacity: 0.16; }
.mark.is-linked {
  stroke: var(--text-primary);
  stroke-width: 2.5;
}

/* Bars grow out of the baseline they are measured from; a bar that fades in at
 * full height says nothing about where its zero is. */
.mark--bar { transform-origin: bottom; }

/* The same rule for a bar measured along the x axis. `bottom` leaves the x
 * origin at the center, so a horizontal bar animating in with scaleX grows out
 * of its own middle in both directions -- away from the very baseline the
 * length is read against. */
.mark--bar-h { transform-origin: left; }

/* --- Entrance choreography ------------------------------------------------- */

/* The sequences themselves live in charts.js, because each one is specific to
 * the analysis its chart shows. CSS owns exactly one thing here: keeping the
 * marks out of sight until the chart is reached, so the reader sees the sequence
 * from its beginning rather than catching the end of it.
 *
 * This is the one rule on the page that can hide data, which is why it is
 * written as narrowly as possible: `animateOnEnter` carries a timeout that adds
 * `is-visible` whether or not the observer ever fires, every animation is
 * defined as a temporary displacement from the drawn state, and the whole block
 * is skipped for a reader who asked for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .chart:not(.is-visible) .mark,
  .chart:not(.is-visible) .anim { opacity: 0; }

  /* There were two more rules here, animating a `section::before` accent tab
   * into place as the reader arrived at each section. The tab itself was deleted
   * some time ago -- nothing has given that pseudo-element a `content` since --
   * so both rules have been animating a box that does not exist. Removed rather
   * than left inert: an entrance sequence that appears to choreograph something
   * invisible is worse than no comment at all for the next reader of this file. */
}

/* Hit targets larger than the marks they serve. */
.hit { fill: transparent; cursor: pointer; }

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  font: 400 var(--size-1)/var(--lh-snug) var(--font-display);
  color: var(--text-secondary);
}
/* Swatches line up with the *first line* of their label, not with the middle of
 * whatever box the label happens to need.
 *
 * `align-items: center` reads as the obvious choice and is right only while
 * every entry is one line. Give one entry a long label -- "Nitrogen-fixers, the
 * proposed explanation" -- and it wraps while its neighbor does not; centring
 * then leaves the two swatches ten pixels apart, and a legend whose marks sit at
 * two different heights reads as a rendering fault rather than as a legend.
 *
 * So the row does not stretch its entries, each entry aligns to its own top, and
 * the swatch is nudged down by roughly the gap between a cap height and a line
 * box -- so it sits on the first line optically, rather than flush with the
 * ascender where it would look a shade high. */
.legend { align-items: flex-start; }
.legend li { display: flex; align-items: flex-start; gap: var(--space-2); }
.legend svg { width: 0.85rem; height: 0.85rem; flex: none; margin-top: 0.22em; }

/* A legend entry is also a filter: pointing at one dims every mark that is not
 * that series, which is the only way to read a stacked bar's third tier across
 * five columns. Dimming, never hiding -- the rest of the data stays on screen at
 * low contrast so the highlighted series is read in its context. */
.legend li[data-series] { cursor: pointer; border-radius: var(--radius); transition: color 0.15s; }
.legend li[data-series]:hover,
.legend li[data-series]:focus-visible { color: var(--text-primary); outline: none; }
.legend li[data-series]:focus-visible { box-shadow: 0 0 0 2px var(--accent); }

.tooltip {
  position: absolute;
  pointer-events: none;
  z-index: 5;
  min-width: 7rem;
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-primary);
  font: 400 var(--size-1)/var(--lh-snug) var(--font-display);
  box-shadow: var(--shadow-soft);
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 0.1s, transform 0.12s cubic-bezier(0.22, 0.68, 0.3, 1);
}
.tooltip[data-visible="true"] { opacity: 1; transform: none; }
.tooltip strong { display: block; font-size: var(--size-2); }
.tooltip span { color: var(--text-secondary); }

/* --- The table view, which every chart must have ------------------------- */

/* The figure a link just opened. The reader followed a URL that pointed here
 * and would otherwise arrive at a page of charts with nothing saying which. */
.chart:target .chart-title { color: var(--accent); }
.chart { scroll-margin-top: calc(var(--topbar-h) + 1.5rem); }

/* The one control under a chart. It used to sit in a row of four; the spacing
 * that row carried is folded in here so removing the other three did not pull
 * the button up against the plot. */
.table-toggle {
  display: inline-block;
  margin-top: var(--space-3);
  font: 500 var(--size-2)/var(--lh-body) var(--font-display);
  color: var(--accent);
  background: none;
  border: 1px solid color-mix(in oklab, var(--accent) calc(35% * var(--tint-strength)), var(--tint-base));
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-3);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.table-toggle:hover {
  background: color-mix(in oklab, var(--accent) calc(10% * var(--tint-strength)), var(--tint-base));
  color: var(--text-primary);
}
.table-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.table-dialog {
  /* Sized to its content up to a ceiling, rather than a fixed panel: a
   * three-row table should not open a window built for thirteen. */
  width: min(34rem, calc(100vw - 2.5rem));
  max-height: min(80vh, 40rem);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-primary);
  box-shadow: var(--shadow-lift);
  overflow: hidden;

  /* The panel rises into place instead of appearing. `allow-discrete` is what
   * makes that possible on a <dialog>: display and the top layer are discrete
   * properties, and without it the element is simply gone before any transition
   * can run on the way out. Under reduced motion the blanket `transition: none`
   * at the foot of this file takes it straight to the open state. */
  opacity: 0;
  transform: translateY(10px) scale(0.985);
  transition:
    opacity 0.2s ease,
    transform 0.2s cubic-bezier(0.22, 0.68, 0.3, 1),
    overlay 0.2s allow-discrete,
    display 0.2s allow-discrete;
}
.table-dialog[open] { opacity: 1; transform: none; }
@starting-style {
  .table-dialog[open] { opacity: 0; transform: translateY(10px) scale(0.985); }
}
.table-dialog::backdrop {
  background: var(--scrim);
  opacity: 0;
  transition: opacity 0.2s, overlay 0.2s allow-discrete, display 0.2s allow-discrete;
}
.table-dialog[open]::backdrop { opacity: 1; }
@starting-style {
  .table-dialog[open]::backdrop { opacity: 0; }
}

.table-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--surface);
}
.table-dialog__head h3 { margin: 0; }
.table-dialog__head button {
  font: 400 var(--size-4)/var(--lh-tight) var(--font-display);
  color: var(--text-muted);
  background: none;
  border: 0;
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
}
.table-dialog__head button:hover { color: var(--text-primary); background: var(--surface-raised); }

.table-dialog table {
  width: 100%;
  border-collapse: collapse;
  font: 400 var(--size-2)/var(--lh-body) var(--font-display);
  font-variant-numeric: tabular-nums;
}
.table-dialog th, .table-dialog td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.table-dialog th {
  font-weight: 600;
  color: var(--text-secondary);
  position: sticky;
  top: 0;
  background: var(--surface);
}
.table-dialog .num { text-align: right; }
.table-dialog tbody tr:hover { background: var(--surface-raised); }

/* The table scrolls inside the dialog, both ways: long tables vertically, wide
 * ones horizontally. Neither ever moves the page underneath. */
.table-wrap { overflow: auto; max-height: calc(min(80vh, 40rem) - 3.6rem); }

/* --- The one hero number ------------------------------------------------- */

/* At the top of the scale, which is where a page's title is -- because on the
 * page this leads, this ratio *is* the title. It used to be set at up to 5.5rem,
 * near 99px, which is not a display figure but a hero banner: two and a half
 * times the h1 it sat under, and the one thing on the page that had been sized
 * by how much room there was rather than by what it had to say. */
.hero-stat .ratio {
  font: 700 var(--size-6)/var(--lh-tight) var(--font-display);
  letter-spacing: -0.035em;
  color: var(--accent);
  display: block;
}
.hero-stat .ratio-caption {
  font: 400 var(--size-2)/var(--lh-body) var(--font-display);
  color: var(--text-primary);
  margin: var(--space-1) 0 var(--space-4);
}

footer {
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
  font: 400 var(--size-2)/var(--lh-body) var(--font-display);
  color: var(--text-muted);
}
.meta { font-size: var(--size-1); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
  /* `*` does not match pseudo-elements, so the cross-fade between pages has to
   * be stood down by name. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
  ::backdrop { transition: none !important; }
}

