:root {
  --bg: #0b0b0d;
  --surface: #16161a;
  --surface-2: #1f1f25;
  --border: #33333d;
  --text: #f2f2f4;
  --text-dim: #a0a0ad;
  --accent: #ffc93c;          /* marquee yellow */
  --accent-dim: #8a6c1f;
  --repertory: #ffc93c;
  --featured: #6ec7e8;
  --ghost: #33333d;
  --focus: #ffffff;
  --radius: 6px;
  --font-display: "Haettenschweiler", "Arial Narrow", Impact, sans-serif;
  --font-body: ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  line-height: 1.5;
}

a { color: var(--accent); }

.site-header {
  padding: 2.5rem 1.5rem 1.5rem;
  border-bottom: 3px solid var(--accent);
}
.site-header h1 {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 8vw, 5rem);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0;
}
.tagline { color: var(--text-dim); margin: 0.25rem 0 0; }

.error {
  margin: 1.5rem;
  padding: 1rem;
  border: 1px solid #c0392b;
  border-radius: var(--radius);
  background: #2a1210;
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  align-items: flex-end;
  padding: 1.25rem 1.5rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
}
.filter { display: flex; flex-direction: column; gap: 0.35rem; border: 0; padding: 0; margin: 0; }
.filter label, .filter legend { font-size: 0.8rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.06em; }
.filter .value { color: var(--accent); font-variant-numeric: tabular-nums; }
/* Two-thumb range: two native range inputs stacked on one painted track.
   The inputs keep their own keyboard handling and ARIA; only their visuals
   are suppressed. pointer-events is off on the input box and back on for the
   thumb, so the lower input is still grabbable through the upper one's track. */
.range-dual { position: relative; width: 11rem; max-width: 100%; height: 1.5rem; }
.range-rail, .range-fill {
  position: absolute; top: 50%; transform: translateY(-50%);
  height: 4px; border-radius: 2px; pointer-events: none;
}
.range-rail { left: 0; right: 0; background: var(--surface-2); border: 1px solid var(--border); }
.range-fill { background: var(--accent); }

.range-dual input[type="range"] {
  position: absolute; left: 0; top: 0;
  width: 100%; height: 100%; margin: 0;
  -webkit-appearance: none; appearance: none;
  background: transparent; pointer-events: none;
}
.range-dual input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; pointer-events: auto;
  width: 14px; height: 14px; border-radius: 50%; cursor: grab;
  background: var(--accent); border: 2px solid var(--surface);
}
.range-dual input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 14px; height: 14px; border-radius: 50%; cursor: grab;
  background: var(--accent); border: 2px solid var(--surface);
}
.range-dual input[type="range"]::-moz-range-track { background: transparent; border: 0; }

/* The input box covers the whole control, so the shared :focus-visible outline
   would draw a rectangle around both thumbs. Ring the focused thumb instead. */
.range-dual input[type="range"]:focus-visible { outline: none; }
.range-dual input[type="range"]:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--focus); outline-offset: 2px;
}
.range-dual input[type="range"]:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--focus); outline-offset: 2px;
}

input[type="range"] { accent-color: var(--accent); }
input[type="search"] {
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0.45rem 0.6rem; min-width: 12rem;
}
#role-filters { display: flex; flex-direction: column; }
.chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.chip { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.6rem;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; font-size: 0.85rem; cursor: pointer; }
.chip input { accent-color: var(--accent); }
.chip:has(input:checked) { border-color: var(--accent); color: var(--accent); }
.reset { background: var(--surface-2); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 0.45rem 0.9rem; cursor: pointer; }
.result-count { color: var(--text-dim); font-size: 0.85rem; margin: 0; width: 100%; }

:where(a, button, input, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

main { padding: 0 1.5rem 3rem; }
.chart-block { margin-top: 2.5rem; }
.chart-block h2 {
  font-family: var(--font-display); text-transform: uppercase;
  font-size: 1.6rem; letter-spacing: 0.03em; margin: 0 0 0.25rem;
}
.chart-note { color: var(--text-dim); font-size: 0.85rem; margin: 0 0 1rem; }
.timeline-scroll { max-height: 70vh; overflow-y: auto; overflow-x: hidden;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
#timeline, #castsize, .timeline-axis { display: block; width: 100%; }

/* The year axis is a separate SVG so it can stick to the top of
   .timeline-scroll while the rows scroll under it. It needs an opaque
   background — the bars would otherwise show through as they pass beneath. */
.timeline-axis {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  /* content-box, against the global border-box rule, and load-bearing.
     renderTimeline sets height="22" to match the viewBox's 22 units. Under
     border-box the 1px border eats into that, leaving a 21.2px content box
     for a 22-unit viewBox — so preserveAspectRatio's default (xMidYMid meet)
     scaled the axis to 21.2/22 = 96.4% and centred the leftover 29px of
     width, while the borderless plot SVG beside it scaled to 98.8% and
     filled. Two centred coordinate systems at different scales agree only at
     their midpoint: the crosshair's year tick sat 14.6px right of the rule at
     the left edge, dead-on in the middle, and 14.6px left of it at the right.
     Keeping the border outside the content box makes the axis's height ratio
     exactly 1, so it takes the same width-derived scale the plot does. */
  box-sizing: content-box;
}

/* Phones and small tablets, where the desktop layout has the timeline
   scrolling in a pane nobody can reach.

   position: sticky resolves against the nearest scrollport ancestor, and
   .timeline-scroll's own `overflow-y: auto` makes it one — so the year axis
   sticks to the top of that pane, never to the screen. Scrolling the page
   (which is what you do when you arrive at the top and swipe up) slides the
   pane and its axis away together while the pane's own 2787px of rows sit
   untouched at scrollTop 0. Measured at 390x844: by the time the chart was
   under the thumb, 84px of it were on screen and the axis was 54px above the
   top of the window.

   The sticky filter bar is the other half of it. At 390px it is 452px tall —
   54% of the viewport, 62% at 320px — so even an axis that did stick to the
   window would have had a third of a screen to stick to. It scrolls away
   with the page here and the chart gets the whole viewport.

   Both rules are lifts, not workarounds: with no nested scrollport the rows
   scroll with the page like the rest of the document, and the axis — the
   same rule, unchanged — now sticks to the window because the window is what
   scrolls it. Above this width the pane is right and stays. */
@media (max-width: 48rem) {
  .filters { position: static; }
  .timeline-scroll {
    max-height: none;
    overflow: visible;
    /* The rounded corners went with the clipping: overflow:visible lets the
       axis SVG's opaque background square off the top two. */
    border-radius: 0;
  }
}

.timeline-row:hover .bar-segment, .timeline-row:focus-visible .bar-segment { fill: #fff; }
/* Follows the pointer down the rows. Deliberately faint: it has to read as
   "you are here" across a 172-row chart without competing with the bars. */
.row-band { fill: #ffffff; opacity: 0.07; pointer-events: none; }
.timeline-label { fill: var(--text-dim); font-size: 11px; }
.axis-line { stroke: var(--border); }
.axis-text { fill: var(--text-dim); font-size: 11px; }
.crosshair { stroke: var(--accent); stroke-width: 1; opacity: 0.7; pointer-events: none; }
.crosshair-label {
  fill: var(--accent); font-size: 11px; font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* Hover readout on the cast-size chart. It sits inside the SVG it reports on,
   so it moves with the crosshair without any page-space bookkeeping — unlike
   .hovercard, which is a page element and has to convert coordinates. */
.chart-readout { pointer-events: none; }
.readout-box {
  fill: var(--surface-2); stroke: var(--accent); stroke-width: 1;
}
.readout-text { fill: var(--text-dim); font-size: 11px; }
.readout-year {
  fill: var(--accent); font-weight: 600; font-variant-numeric: tabular-nums;
}
.era-band { fill: #ffffff; opacity: 0.025; }

/* Five-season moving average over the season totals. Dashed so it reads as a
   derived line rather than another series — it is the one mark on the chart
   that no single season put there. fill:none is not optional on a polyline:
   the default fill would close the path back along the axis and flood the
   plot. pointer-events:none keeps it out of the crosshair's way. */
.trendline {
  fill: none;
  stroke: var(--focus);
  stroke-width: 1.5;
  stroke-dasharray: 6 4;
  stroke-linejoin: round;
  opacity: 0.7;
  pointer-events: none;
}

/* contain: paint is load-bearing, not decoration. The table is ~715px wide
   (nine nowrap columns) and overflow-x: auto does scroll it internally, but
   in Chrome the table's content width still propagates into the document's
   scrollable overflow rect -- at a 320px viewport the whole PAGE scrolled
   ~390px sideways, dragging the header, filters, and footer off-screen.
   contain: paint confines the box's overflow to itself, which is what the
   scroll container already means. Verified: page scrollWidth drops from 696
   to 305, the table still scrolls internally, and the thead stays sticky. */
.table-scroll { overflow-x: auto; contain: paint;
  border: 1px solid var(--border); border-radius: var(--radius); }
table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
th, td { padding: 0.5rem 0.7rem; text-align: left; border-bottom: 1px solid var(--border); white-space: nowrap; }
thead th { background: var(--surface-2); position: sticky; top: 0; }
thead button { background: none; border: 0; color: var(--text); font: inherit;
  font-weight: 600; cursor: pointer; padding: 0; }
thead th[aria-sort="ascending"] button::after { content: " ▲"; color: var(--accent); }
thead th[aria-sort="descending"] button::after { content: " ▼"; color: var(--accent); }
tbody tr:hover, tbody tr:focus-visible { background: var(--surface-2); }
/* The abbreviated column head keeps its full name in the title attribute for
   screen readers and hover; the UA's dotted underline is the hint that it is
   there, toned down to suit the dark surface. */
th abbr[title] { text-decoration: underline dotted var(--border); cursor: help; }
.check { color: var(--accent); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.hovercard {
  position: absolute; z-index: 50; width: 15rem; padding: 0.75rem;
  background: var(--surface-2); border: 1px solid var(--accent);
  border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  pointer-events: none; font-size: 0.85rem;
}
.hovercard img, .hovercard .avatar {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--radius); margin-bottom: 0.5rem; display: block;
}
.hovercard .avatar {
  display: grid; place-items: center; background: var(--surface);
  font-family: var(--font-display); font-size: 3rem; color: var(--accent-dim);
}
.hovercard h3 { margin: 0 0 0.2rem; font-size: 1rem; }
.hovercard dl { margin: 0.4rem 0 0; color: var(--text-dim); }
/* dd carries a 40px start margin by default, which would indent the card's
   two detail lines away from the heading above them. The terms themselves
   are .sr-only (see buildDetails). */
.hovercard dd { margin: 0; }
.hovercard .credit { margin: 0.5rem 0 0; font-size: 0.7rem; color: var(--text-dim); }

/* Credits page. auto-fill rather than auto-fit: with ~130 entries the track
   count is never the constraint, and auto-fit would stretch a lone entry in
   the last row across the whole width.

   min(19rem, 100%), not a bare 19rem: minmax's lower bound is a floor the
   track will not go below, so on a 320px phone a 304px minimum plus main's
   48px of padding overflowed the viewport by 60px and scrolled the whole
   page sideways. Wrapping it in min() lets the single column fall back to
   the container's width when the container is the narrower of the two. */
.credit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr));
  gap: 1rem;
}
.credit-entry {
  display: flex;
  gap: 0.75rem;
  padding: 0.75rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* flex-basis fixed alongside width/height so a photo that 404s cannot let
   the text column reflow into the gap the thumbnail was holding. */
.credit-entry .credit-thumb {
  flex: 0 0 64px;
  width: 64px; height: 64px;
  object-fit: cover;
  border-radius: var(--radius);
  margin: 0;
}
.credit-entry .avatar {
  display: grid; place-items: center; background: var(--surface-2);
  font-family: var(--font-display); font-size: 1.5rem; color: var(--accent-dim);
}
/* min-width: 0 undoes the flex default of `auto`, which refuses to shrink an
   item below its content's min-content width. These credits are free text
   from a wiki template and one of them (Victoria Jackson's) has a bare URL
   in the author field — a single unbreakable token wide enough to push the
   card 60px past a 320px viewport and scroll the whole page sideways.
   overflow-wrap then lets that token break rather than simply overflow. */
.credit-body { min-width: 0; }
.credit-name, .credit-line { overflow-wrap: break-word; }
.credit-name { margin: 0 0 0.15rem; font-weight: 600; }
.credit-line { margin: 0; font-size: 0.8rem; color: var(--text-dim); }
.uncredited { color: var(--text-dim); font-size: 0.85rem; line-height: 1.8; }

.site-footer {
  padding: 2rem 1.5rem; border-top: 1px solid var(--border);
  color: var(--text-dim); font-size: 0.8rem;
}
.site-footer p { margin: 0 0 0.5rem; }

@media (prefers-reduced-motion: no-preference) {
  .bar-segment { transition: fill 120ms ease; }
}