/* Programming Language Atlas — documentation-first design system */

:root {
  --fg: #111318;
  --muted: #59616d;
  --line: #252a31;
  --line-soft: #d7dce2;

  --bg: #f6f7f9;
  --surface: #ffffff;
  --surface-alt: #f0f3f6;

  /* The categorical scale. These six carry classification, never rank, so
     they are spaced by lightness as well as hue: under a red-blind or
     green-blind simulation hue alone collapses, and check.py asserts a
     minimum perceptual separation between every pair in both schemes. */
  --accent: #1556ac;
  --visited: #4a3f9e;
  --success: #186e3b;
  --warning: #936000;
  --secondary: #4f2f76;
  --info: #09565c;
  --unknown: #5f666f;

  --surface-hover: #fbfcfe;
  --flag: #fff6d9;
  --on-mark: #ffffff;

  --max-width: 1180px;

  color-scheme: light dark;
}

/* ---------- Dark scheme ----------
   Tokens only: no rule below this block is scheme-aware, and no markup
   changes. Scoped to `screen` so a page printed from a dark-themed browser
   still puts dark ink on white paper.

   Surfaces are near-black rather than black and the foreground is dimmed off
   pure white: #fff on #000 is over-contrast and haloes on OLED. Every pair
   here is asserted at WCAG AA by check.py, in both schemes. */

@media screen and (prefers-color-scheme: dark) {
  :root {
    --fg: #e4e8ee;
    --muted: #a7b0bc;
    --line: #6f7987;
    --line-soft: #2b323b;

    --bg: #12151a;
    --surface: #171b21;
    --surface-alt: #1d222a;

    --accent: #6eb4f4;
    --visited: #b49af0;
    --success: #64da7e;
    --warning: #ffc151;
    --secondary: #cc84e5;
    --info: #3ca0a0;
    --unknown: #adbac5;

    --surface-hover: #232a33;
    --flag: #3d3a1a;
    --on-mark: #12151a;
  }
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* Anchor targets clear the heading rule when jumped to from a table of contents. */
:target { scroll-margin-top: 12px; }
h1, h2, h3, section, tr { scroll-margin-top: 12px; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 8px 12px;
  font-size: 14px;
  z-index: 10;
}

.skip-link:focus {
  left: 8px;
  top: 8px;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums lining-nums;
  overflow-wrap: break-word;
}

.wrap {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 16px;
}

/* ---------- Header / navigation ---------- */

.site-header {
  background: var(--surface-alt);
  border-bottom: 1px solid var(--line);
  padding-top: 24px;
  text-align: center;
}

.site-title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.site-title a { color: var(--fg); text-decoration: none; }
.site-title a:visited { color: var(--fg); }

.site-tagline {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--muted);
}

.site-nav {
  margin-top: 12px;
  font-size: 14px;
  border-top: 1px solid var(--line-soft);
  padding: 8px 0;
}

.site-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  justify-content: center;
}

/* Language articles are a second list, not a third wrapping of the topic row.
   Adding C/Rust/Swift references to one ul made a 19-item ribbon that wrapped
   through Concurrency and looked like overflow rather than structure. */
.site-nav ul + ul {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line-soft);
}

.site-nav a {
  color: var(--accent);
  text-decoration: none;
  display: inline-block;
  padding: 2px 0;
}
.site-nav a:hover { text-decoration: underline; }
.site-nav a:visited { color: var(--visited); }
.site-nav a[aria-current="page"] {
  color: var(--fg);
  font-weight: 600;
  text-decoration: none;
}

/* ---------- Links ---------- */

a { color: var(--accent); }
a:visited { color: var(--visited); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---------- Typography ---------- */

main { padding-bottom: 24px; }

h1 {
  font-size: 1.7rem;
  line-height: 1.25;
  margin: 20px 0 6px;
}

h2 {
  font-size: 1.2rem;
  margin: 35px 0 12px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--line-soft);
}

h3 {
  font-size: 1rem;
  margin: 20px 0 8px;
}

h4 {
  font-size: 0.9rem;
  margin: 16px 0 6px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

p { margin: 0 0 12px; }

.lede {
  text-align: center;
  max-width: 780px;
  margin: 0 auto 16px;
  color: var(--muted);
  font-size: 14.5px;
}

.section-note {
  font-size: 13px;
  color: var(--muted);
  margin: -4px 0 12px;
}

code, kbd, samp, .mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.92em;
}

p code, li code, td code, th code {
  background: var(--surface-alt);
  border: 1px solid var(--line-soft);
  padding: 0 3px;
}

pre {
  margin: 0 0 12px;
  padding: 11px 13px;
  background: var(--surface);
  border: 1px solid var(--line);
  overflow-x: auto;
  font-size: 13px;
  line-height: 1.45;
}

pre code { background: none; border: 0; padding: 0; }

blockquote {
  margin: 0 0 12px;
  padding: 8px 13px;
  border-left: 3px solid var(--line-soft);
  color: var(--muted);
}

ul, ol { margin: 0 0 12px; padding-left: 22px; }
li { margin-bottom: 4px; }

hr { border: 0; border-top: 1px solid var(--line-soft); margin: 24px 0; }

/* ---------- Cards ---------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin: 0 0 12px;
}

.cards.cols-2 { grid-template-columns: repeat(2, 1fr); }
.cards.cols-3 { grid-template-columns: repeat(3, 1fr); }
.cards.cols-4 { grid-template-columns: repeat(4, 1fr); }
.cards.cols-5 { grid-template-columns: repeat(5, 1fr); }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 11px 13px;
}

.card h3 { margin: 0 0 6px; font-size: 0.95rem; }
.card p { margin: 0 0 6px; font-size: 13.5px; color: var(--muted); }
.card p:last-child, .card ul:last-child { margin-bottom: 0; }
.card ul { font-size: 13px; padding-left: 18px; color: var(--muted); }

a.card {
  display: block;
  text-decoration: none;
  color: var(--fg);
}
a.card:visited { color: var(--fg); }
a.card:hover, a.card:focus-visible {
  border-color: var(--accent);
  background: var(--surface-hover);
}
a.card h3 { color: var(--accent); }
a.card:hover h3 { text-decoration: underline; }

.card-meta {
  font-size: 12px;
  color: var(--muted);
  margin-top: 6px;
  display: block;
}

/* Statistic tiles */

.stats {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 12px;
  margin-bottom: 12px;
}

.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 11px 13px;
}

.stat .value { font-size: 1.25rem; font-weight: 700; display: block; }
.stat .label { font-size: 12px; color: var(--muted); display: block; margin-top: 2px; }

/* ---------- Status markers ---------- */

.tag {
  display: inline-block;
  font-size: 11.5px;
  line-height: 1.4;
  padding: 0 5px;
  border: 1px solid currentColor;
  color: var(--unknown);
  white-space: nowrap;
  vertical-align: baseline;
}

.tag-static   { color: var(--accent); }
.tag-dynamic  { color: var(--secondary); }
.tag-gc       { color: var(--success); }
.tag-manual   { color: var(--warning); }
.tag-ownership{ color: var(--info); }
.tag-warning  { color: var(--warning); }
.tag-unknown  { color: var(--unknown); }

.legend {
  font-size: 12.5px;
  color: var(--muted);
  margin: 8px 0 12px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
}

/* ---------- Tables ---------- */

.table-scroll {
  overflow: auto;
  border: 1px solid var(--line);
  background: var(--surface);
  max-height: 70vh;
  margin-bottom: 6px;
}

/* The container is a keyboard-operable scroll region, so it takes a focus ring. */
.table-scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

table {
  border-collapse: collapse;
  width: 100%;
  font-size: 13.5px;
  background: var(--surface);
}

/* Size to content so wide tables scroll horizontally instead of crushing their
   columns; min-width keeps short tables filling the panel. */
.table-scroll > table {
  width: auto;
  min-width: 100%;
}

th, td {
  padding: 6px 8px;
  border: 1px solid var(--line-soft);
  text-align: left;
  vertical-align: top;
}

thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface-alt);
  font-weight: 700;
  white-space: nowrap;
  /* Collapsed borders do not paint under a sticky cell; draw them as insets. */
  box-shadow: inset 0 -2px 0 var(--line), inset -1px 0 0 var(--line-soft);
}

tbody th[scope="row"] {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface);
  font-weight: 700;
  white-space: nowrap;
  box-shadow: inset -1px 0 0 var(--line), inset 0 -1px 0 var(--line-soft);
}

tbody tr.group-start th[scope="row"] {
  box-shadow: inset -1px 0 0 var(--line), inset 0 -1px 0 var(--line-soft),
              inset 0 2px 0 var(--line);
}

thead th:first-child {
  left: 0;
  z-index: 3;
  background: var(--surface-alt);
}

/* Row headers wrap on narrow screens rather than forcing a very wide column. */
@media (max-width: 720px) {
  tbody th[scope="row"] { white-space: normal; min-width: 108px; }
}

tbody tr.group-start th, tbody tr.group-start td {
  border-top: 2px solid var(--line);
}

/* Group label spanning a long matrix, so rows read in labelled blocks. */
th.rowgroup {
  position: sticky;
  left: 0;
  background: var(--surface-alt);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  white-space: nowrap;
}

/* Numeric columns align right whether the cell is a data cell or the row's
   own header, so a size column reads as a column of numbers either way. */
td.num, th.num { text-align: right; }

caption, .table-caption {
  caption-side: bottom;
  text-align: left;
  font-size: 12.5px;
  color: var(--muted);
  padding: 6px 0 0;
  margin-bottom: 12px;
}

/* Infobox (article pages) */

.infobox {
  float: right;
  width: 330px;
  margin: 4px 0 16px 20px;
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 13px;
}

.infobox .infobox-title {
  background: var(--surface-alt);
  border-bottom: 2px solid var(--line);
  padding: 6px 8px;
  font-weight: 700;
  text-align: center;
  font-size: 14px;
}

.infobox table { font-size: 13px; }
.infobox th, .infobox td { border: 0; border-bottom: 1px solid var(--line-soft); }
.infobox th[scope="row"] { position: static; width: 40%; background: var(--surface); }
.infobox .subhead td {
  background: var(--surface-alt);
  text-align: center;
  font-weight: 700;
  border-top: 1px solid var(--line-soft);
}

/* ---------- Figures / media ---------- */

.figure {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 11px 13px;
  margin: 0 0 12px;
}

.figure .frame {
  aspect-ratio: 16 / 9;
  background: var(--surface-alt);
  border: 1px solid var(--line-soft);
  display: block;
  width: 100%;
  overflow: hidden;
}

.figure .frame.ratio-4-3 { aspect-ratio: 4 / 3; }
.figure .frame img, .figure .frame video, .figure .frame svg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.figure figcaption {
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 8px;
}

.media-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}

/* ---------- Section permalinks / back to top ---------- */

.anchor {
  font-weight: 400;
  font-size: 0.8em;
  margin-left: 6px;
  color: var(--muted);
  text-decoration: none;
  opacity: 0;
}

h2:hover .anchor, h2:focus-within .anchor,
h3:hover .anchor, h3:focus-within .anchor,
.anchor:focus-visible { opacity: 1; }
.anchor:hover { color: var(--accent); }

/* Touch devices have no hover, so the marker stays visible there. */
@media (hover: none) {
  .anchor { opacity: 0.6; }
}

.to-top {
  display: block;
  text-align: right;
  font-size: 12.5px;
  margin: -6px 0 0;
}

/* ---------- Table of contents ---------- */

.toc {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 11px 13px;
  margin: 0 0 16px;
  font-size: 13.5px;
}

.toc h2 {
  font-size: 0.95rem;
  margin: 0 0 8px;
  border: 0;
  padding: 0;
}

.toc h2 .anchor { display: none; }

.toc ol {
  margin: 0;
  padding-left: 20px;
  columns: 2;
  column-gap: 24px;
}

.toc li { break-inside: avoid; }

.toc ol ol {
  columns: 1;
  padding-left: 18px;
  margin: 2px 0 4px;
  list-style-type: lower-alpha;
  color: var(--muted);
}

/* ---------- Source / reference notes ---------- */

.sources {
  font-size: 13px;
  color: var(--muted);
}

.sources ol { padding-left: 20px; }

.callout {
  border: 1px solid var(--line);
  border-left: 4px solid var(--warning);
  background: var(--surface);
  padding: 11px 13px;
  font-size: 13.5px;
  margin: 0 0 12px;
}

.callout.info { border-left-color: var(--info); }

/* ---------- Footer ---------- */

.site-footer {
  margin-top: 48px;
  border-top: 1px solid var(--line-soft);
  padding: 16px 0 32px;
  font-size: 12.5px;
  color: var(--muted);
}

.site-footer p { margin: 0 0 6px; }

/* ---------- Responsive ---------- */

@media (max-width: 1100px) {
  .cards.cols-4 { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 900px) {
  .cards.cols-3, .cards.cols-4, .cards.cols-5 { grid-template-columns: repeat(2, 1fr); }
  .stats { grid-template-columns: repeat(3, 1fr); }
  .infobox { float: none; width: auto; margin: 0 0 16px; }
}

@media (max-width: 720px) {
  .media-row { grid-template-columns: 1fr; }
  .site-nav { font-size: 13px; }
  .site-nav ul { justify-content: flex-start; gap: 2px 12px; }
  .site-nav a { padding: 6px 2px; }
  .toc ol { columns: 1; }
  /* Same contract as .table-scroll: a figure that carries structure is read
     by scrolling, not by shrinking its type below a readable size. */
  .figure .frame.ratio-auto {
    overflow-x: auto;
    overflow-y: hidden;
  }
  .figure .frame.ratio-auto svg {
    width: 40rem;
    min-width: 40rem;
    max-width: none;
    height: auto;
  }
}

@media (max-width: 520px) {
  .cards, .cards.cols-2, .cards.cols-3, .cards.cols-4, .cards.cols-5 {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 390px) {
  .wrap { padding: 0 12px; }
  .stats { grid-template-columns: 1fr; }
  table { font-size: 13px; }
}


/* ---------- Print ---------- */

@media print {
  :root { --bg: #fff; }
  body { font-size: 10.5pt; }
  .site-nav, .skip-link, .to-top, .anchor { display: none; }
  .site-header { border-bottom: 1pt solid var(--line); text-align: left; }
  .wrap { max-width: none; padding: 0; }
  /* Release the scroll and height clamps so tables and figures print in full. */
  .table-scroll { overflow: visible; max-height: none; }
  .figure .frame.ratio-auto { overflow: visible; }
  .figure .frame.ratio-auto svg { width: 100%; min-width: 0; }
  thead th, tbody th[scope="row"] { position: static; box-shadow: none; }
  thead th { border-bottom: 2px solid var(--line); }
  tbody th[scope="row"] { border-right: 1px solid var(--line); }
  thead { display: table-header-group; }
  tr, .card, .figure, pre, .callout { break-inside: avoid; }
  h2, h3 { break-after: avoid; }
  a { color: inherit; text-decoration: underline; }
  /* Expose destinations that are lost once the page is on paper. */
  main a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: var(--muted); }
  .site-footer { margin-top: 24px; }
}

/* ==========================================================================
   Motion system
   Three durations and two easings, applied only to state changes and to
   figure reveals. Everything below is opt-in: with reduced motion requested,
   no rule in this file animates.
   ========================================================================== */

:root {
  --dur-fast: 120ms;
  --dur-base: 220ms;
  --dur-slow: 480ms;
  --dur-draw: 1100ms;
  --ease: cubic-bezier(0.2, 0.6, 0.2, 1);
  --ease-out: cubic-bezier(0, 0.7, 0.3, 1);
}

@media (prefers-reduced-motion: no-preference) {
  a.card,
  .site-nav a,
  .anchor,
  .swatch,
  .chip {
    transition: background-color var(--dur-base) var(--ease),
                border-color var(--dur-base) var(--ease),
                color var(--dur-base) var(--ease),
                opacity var(--dur-fast) var(--ease);
  }

  a.card { transition: background-color var(--dur-base) var(--ease),
                       border-color var(--dur-base) var(--ease),
                       transform var(--dur-base) var(--ease); }
  a.card:hover { transform: translateY(-1px); }

  /* A permalinked destination announces itself once, then settles. */
  :target { animation: flag var(--dur-slow) var(--ease-out); }
}

@keyframes flag {
  from { background: var(--flag); }
  to   { background: transparent; }
}

/* ==========================================================================
   Visualisation primitives
   Charts and diagrams are inline SVG styled from the same tokens as the rest
   of the page, so they inherit the palette, the print rules, and the motion
   preference.
   ========================================================================== */

.figure .frame.ratio-auto {
  aspect-ratio: auto;
  padding: 4px;
}

/* Must beat `.figure .frame svg { height: 100%; object-fit: cover }`. Cover
   centre-crops a min-width drawing on a 390px screen, which is how the C map
   lost its layer labels on the left. */
.figure .frame.ratio-auto svg {
  height: auto;
  object-fit: contain;
  object-position: left top;
}

.viz {
  display: block;
  width: 100%;
  font-family: system-ui, sans-serif;
  overflow: visible;
}

.viz text { fill: var(--fg); font-size: 13px; }
.viz .lbl { fill: var(--muted); font-size: 12px; }
.viz .lbl-sm { fill: var(--muted); font-size: 10.5px; }
.viz .val { font-size: 12px; font-weight: 700; }
.viz .ttl { font-size: 13px; font-weight: 700; }
/* Formula cells in a matrix figure. Monospace so that the parenthesised
   operand forms line up column to column, which is the whole point of laying
   a specification table out as a grid. */
.viz .mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
}

.viz .axis { stroke: var(--line); stroke-width: 1; fill: none; }
.viz .grid { stroke: var(--line-soft); stroke-width: 1; fill: none; }
.viz .grid-dash { stroke: var(--line-soft); stroke-width: 1; fill: none; stroke-dasharray: 3 3; }

.viz .node { fill: var(--surface); stroke: var(--line); stroke-width: 1; }
.viz .node-alt { fill: var(--surface-alt); stroke: var(--line); stroke-width: 1; }
/* The subject of a lineage diagram: the one node that is not a link. */
.viz .node.accent { fill: var(--surface-alt); stroke: var(--accent); stroke-width: 2; }

/* Linked nodes. The hit area is the whole box, and the hover state has to be
   legible without colour, since colour here already means classification. */
.viz a rect { cursor: pointer; }
.viz a:hover rect { fill: var(--surface-hover); stroke: var(--accent); stroke-width: 2; }
.viz a:hover text { fill: var(--accent); }
.viz a:focus-visible rect { outline: 2px solid var(--accent); outline-offset: 2px; }
.viz .flow { stroke: var(--line); stroke-width: 1.5; fill: none; }
.viz .flow-soft { stroke: var(--line-soft); stroke-width: 1.5; fill: none; }

/* Series colours reuse the classification palette, so a bar and a status tag
   for the same axis value always agree. Colour never encodes magnitude. */
.viz .s-static    { fill: var(--accent);    stroke: var(--accent); }
.viz .s-dynamic   { fill: var(--secondary); stroke: var(--secondary); }
.viz .s-gc        { fill: var(--success);   stroke: var(--success); }
.viz .s-manual    { fill: var(--warning);   stroke: var(--warning); }
.viz .s-ownership { fill: var(--info);      stroke: var(--info); }
.viz .s-neutral   { fill: var(--unknown);   stroke: var(--unknown); }
.viz .stroke-only { fill: none; }

/* Ink for text drawn on top of a categorical fill rather than on the page. */
.viz .on-mark { fill: var(--on-mark); }

/* Primitives for the hand-drawn diagrams: the same tokens the generated
   figures use, so both kinds of figure follow the scheme together. */
.viz .canvas { fill: var(--surface-alt); }
.viz .box { fill: var(--surface-alt); stroke: var(--line-soft); stroke-width: 1; }
.viz .dim { fill: var(--muted); }
.viz .t-info { fill: var(--info); }

/* ---------- Chart motion ----------
   Bars grow, lines draw, points settle. Each figure animates once when it
   first scrolls into view where scroll-driven animation is supported, and
   otherwise on load. Nothing animates on a repeat scroll past. */

@media (prefers-reduced-motion: no-preference) {
  .viz .grow {
    transform-box: fill-box;
    transform-origin: left center;
    animation: grow-x var(--dur-slow) var(--ease-out) both;
  }

  .viz .grow-up {
    transform-box: fill-box;
    transform-origin: bottom center;
    animation: grow-y var(--dur-slow) var(--ease-out) both;
  }

  /* Paths carry pathLength="1000", so one dash value fits every line. */
  .viz .draw {
    stroke-dasharray: 1000;
    stroke-dashoffset: 1000;
    animation: draw var(--dur-draw) var(--ease-out) both;
  }

  .viz .fade { animation: fade var(--dur-slow) var(--ease-out) both; }

  /* A cache line arrives as one unit. Its cells rise together and share one
     delay, because a stagger would say the opposite of what the figure is
     for. Rise and fade are one keyframe list: two `animation` shorthands on
     the same element replace each other rather than composing. */
  .viz .lift { animation: lift var(--dur-slow) var(--ease-out) both; }

  /* Dashes travel along a pipeline edge to show direction of movement. */
  .viz .stream {
    stroke-dasharray: 6 5;
    animation: stream 1.4s linear infinite;
  }

  .viz .spin {
    transform-box: fill-box;
    transform-origin: center;
    animation: spin 14s linear infinite;
  }

  @supports (animation-timeline: view()) {
    .viz .grow, .viz .grow-up, .viz .draw, .viz .fade, .viz .lift {
      animation-timeline: view();
      animation-range: entry 10% cover 32%;
    }
  }
}

@keyframes grow-x   { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes grow-y   { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes draw     { to { stroke-dashoffset: 0; } }
@keyframes fade     { from { opacity: 0; } to { opacity: 1; } }
@keyframes lift     { from { opacity: 0; transform: translateY(14px); }
                      to   { opacity: 1; transform: translateY(0); } }
@keyframes stream   { to { stroke-dashoffset: -22; } }
@keyframes spin     { to { transform: rotate(360deg); } }

/* Staggered entry, so a series reads left to right rather than all at once. */
.viz .d1 { animation-delay: 60ms; }
.viz .d2 { animation-delay: 120ms; }
.viz .d3 { animation-delay: 180ms; }
.viz .d4 { animation-delay: 240ms; }
.viz .d5 { animation-delay: 300ms; }
.viz .d6 { animation-delay: 360ms; }
.viz .d7 { animation-delay: 420ms; }
.viz .d8 { animation-delay: 480ms; }

/* ---------- Chart interaction ----------
   Pointer and keyboard focus raise one mark out of the set. The value label
   is present in the markup at all times for assistive technology, and only
   its opacity changes. */

.viz .mark { cursor: default; }
.viz .mark .hit { fill: transparent; }
.viz .mark .peek { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  .viz .mark .peek { transition: opacity var(--dur-fast) var(--ease); }
  .viz .mark rect, .viz .mark circle { transition: opacity var(--dur-fast) var(--ease); }
}

.viz .mark:hover .peek,
.viz .mark:focus-visible .peek { opacity: 1; }
.viz .mark:hover rect, .viz .mark:hover circle { opacity: 0.82; }
.viz .mark:focus-visible { outline: none; }
.viz .mark:focus-visible .node,
.viz .mark:focus-visible rect,
.viz .mark:focus-visible circle { stroke: var(--fg); stroke-width: 2; }

@media print {
  .viz .grow, .viz .grow-up, .viz .draw, .viz .fade, .viz .lift,
  .viz .stream, .viz .spin { animation: none; }
  .viz .draw { stroke-dashoffset: 0; stroke-dasharray: none; }
  .viz .peek { opacity: 1; }
}

/* ---------- Visual catalogue entries ---------- */

.viz-entry {
  display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 12px;
  border: 1px solid var(--line);
  background: var(--surface);
  padding: 11px 13px;
  margin-bottom: 12px;
}

.viz-entry > div { min-width: 0; }
.viz-entry h3 { margin: 0 0 6px; }
.viz-entry .figure { border: 0; padding: 0; margin: 0; }
.viz-entry .frame { background: var(--surface); }
.viz-entry p { font-size: 13.5px; color: var(--muted); }

.blueprint {
  font-size: 11.5px;
  line-height: 1.35;
  background: var(--surface-alt);
  border: 1px solid var(--line-soft);
  color: var(--muted);
  padding: 8px 10px;
  margin: 0;
}

.use-when {
  font-size: 12.5px;
  border-left: 3px solid var(--line-soft);
  padding-left: 9px;
  margin: 0 0 8px;
}

.use-when strong { color: var(--fg); }

/* Motion specimens: a row of squares that replay the system's easings. */
.motion-demo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.motion-demo figure {
  margin: 0;
  border: 1px solid var(--line);
  background: var(--surface);
  padding: 11px 13px;
}

.motion-demo .track {
  height: 26px;
  background: var(--surface-alt);
  border: 1px solid var(--line-soft);
  position: relative;
  overflow: hidden;
}

.motion-demo .puck {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  background: var(--accent);
}

@media (prefers-reduced-motion: no-preference) {
  .motion-demo .puck { animation: slide 2.6s infinite; }
  .motion-demo .e-linear .puck { animation-timing-function: linear; }
  .motion-demo .e-base   .puck { animation-timing-function: var(--ease); }
  .motion-demo .e-out    .puck { animation-timing-function: var(--ease-out); }
}

@keyframes slide {
  0%, 12%   { transform: translateX(0); }
  50%, 62%  { transform: translateX(calc(100cqw - 24px)); }
  100%      { transform: translateX(0); }
}

.motion-demo .track { container-type: inline-size; }
.motion-demo figcaption { font-size: 12px; color: var(--muted); margin-top: 6px; }

@media (max-width: 900px) {
  .viz-entry { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------- site index */

/* Alphabet jump bar. Letters with no entries stay in place as inert text so
   the bar keeps a stable shape rather than reflowing as content changes. */
.jump-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  margin: 16px 0 24px;
  border: 1px solid var(--line);
  background: var(--line-soft);
}
.jump-bar a,
.jump-bar span {
  flex: 1 0 auto;
  min-width: 30px;
  padding: 7px 4px;
  background: var(--surface);
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-decoration: none;
}
.jump-bar a:hover { background: var(--surface-alt); }
.jump-bar a:visited { color: var(--accent); }
.jump-bar span { color: var(--unknown); font-weight: 400; }

kbd {
  padding: 1px 5px;
  border: 1px solid var(--line-soft);
  background: var(--surface-alt);
  font: inherit;
  font-size: 0.92em;
  white-space: nowrap;
}

/* ------------------------------------------------------- CSS-only faceting */

/* The index table filters with no JavaScript at all. Each row carries its
   classification as data attributes; each facet is a radio input; a :has()
   rule on the section hides the rows that do not match.

   The dimensions compose without a combinatorial explosion because each rule
   hides independently -- a row has to survive every active rule to remain
   visible, so three dimensions cost three rules rather than the product of
   their options. */

/* Hidden by default and revealed only where :has() works, so a browser
   without it shows the complete unfiltered table rather than controls that
   silently do nothing. */
.facets { display: none; }

@supports selector(:has(*)) {
  .facets {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
    margin: 16px 0;
    padding: 12px;
    border: 1px solid var(--line-soft);
    background: var(--surface-alt);
  }
  .facet-help,
  .facet-reset { grid-column: 1 / -1; margin: 0; color: var(--muted); font-size: 13px; }

  .facet { border: 0; margin: 0; padding: 0; }
  .facet legend {
    padding: 0 0 5px;
    font-weight: 700;
    font-size: 13px;
  }
  /* The input stays in the accessibility tree and keeps native keyboard and
     screen-reader behaviour; only its default box is replaced by the label. */
  .facet input {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .facet label {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    padding: 3px 7px;
    border: 1px solid transparent;
    font-size: 13px;
    cursor: pointer;
  }
  .facet label:hover { background: var(--surface-hover); }
  .facet input:checked + label {
    border-color: var(--line);
    background: var(--surface);
    font-weight: 700;
  }
  /* Focus lands on the visually hidden input, so the ring has to be drawn on
     the label that stands in for it. */
  .facet input:focus-visible + label { outline: 2px solid var(--accent); outline-offset: 1px; }
  .facet-n { color: var(--muted); font-variant-numeric: tabular-nums; font-weight: 400; }

  /* One rule per option. Selecting "Any" matches nothing here, so no rows are
     hidden and the table returns to its full state. */
  .filterable:has(#fam-c:checked)        tbody tr:not([data-fam="c"]),
  .filterable:has(#fam-ml:checked)       tbody tr:not([data-fam="ml"]),
  .filterable:has(#fam-lisp:checked)     tbody tr:not([data-fam="lisp"]),
  .filterable:has(#fam-dynamic:checked)  tbody tr:not([data-fam="dynamic"]),
  .filterable:has(#fam-systems:checked)  tbody tr:not([data-fam="systems"]),
  .filterable:has(#fam-jvm:checked)      tbody tr:not([data-fam="jvm"]),
  .filterable:has(#fam-array:checked)    tbody tr:not([data-fam="array"]),
  .filterable:has(#fam-logic:checked)    tbody tr:not([data-fam="logic"]),
  .filterable:has(#typ-static:checked)   tbody tr:not([data-typ="static"]),
  .filterable:has(#typ-dynamic:checked)  tbody tr:not([data-typ="dynamic"]),
  .filterable:has(#mem-gc:checked)       tbody tr:not([data-mem="gc"]),
  .filterable:has(#mem-manual:checked)   tbody tr:not([data-mem="manual"]),
  .filterable:has(#mem-ownership:checked) tbody tr:not([data-mem="ownership"]),
  .filterable:has(#mem-unknown:checked)  tbody tr:not([data-mem="unknown"]) {
    display: none;
  }
}

/* Filtering is a screen affordance. Print gets the complete table. */
@media print {
  .facets { display: none; }
  .filterable tbody tr { display: table-row !important; }
}
