:root {
  /* AEW palette sampled from the official shop logo — see PROJECT_SPEC.md */
  --bg: #0a0a0a;
  --panel: #141414;
  --text: #f5f5f0;
  --text-dim: #8a8a8a;
  --gold: #97843d;
  --gold-bright: #d9ca90;
  --border: #2a2a2a;
  --node-fill: #1a1a1a;
  /* Lifted surface for the desktop lightbox: --panel over a 72%-black
     backdrop is nearly the same value as the dimmed page behind it, so the
     dialog reads as a smudge rather than a card. */
  --panel-raised: #1e1e1e;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: 'Georgia', 'Iowan Old Style', serif;
}

a { color: var(--gold-bright); }
a:hover { color: var(--gold); }

/* Stacked and centred rather than title-left / links-right. On a wide
   screen the space-between version parked the support links ~1100px from
   everything else, in the top-right corner where site navigation normally
   lives and where eyes learn to skip — the two things the project is
   actually asking of a visitor shouldn't read as utility chrome. */
.site-header, .site-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 16px;
  padding: 22px 32px;
  border-bottom: 1px solid var(--border);
}
.site-footer {
  border-bottom: none;
  border-top: 1px solid var(--border);
  margin-top: 32px;
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 12.5px;
  color: var(--text-dim);
}

.posted-section {
  padding: 20px 32px;
  border-bottom: 1px solid var(--border);
  text-align: center;
}
.posted-section h2 {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-weight: 700;
  letter-spacing: 0.03em;
  font-size: 13px;
  text-transform: uppercase;
  color: var(--gold-bright);
  margin: 0 0 4px;
}
.posted-hint {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 12px;
  color: var(--text-dim);
  margin: 0 0 12px;
}
/* Same 1000px column as the chart below, so the two halves of the page line
   up instead of the cards running wall-to-wall past a centred graph. */
.posted-list {
  display: flex;
  flex-direction: column;
  gap: 18px;
  max-width: 1000px;
  margin: 0 auto;
}

/* One band per generation, mirroring the layers of the graph — and centred
   for the same reason, since the tree below is centred too. A short band
   (two or three people) sits as a centred cluster rather than hugging the
   left edge under a full-width one.

   Cards flex rather than sitting in fixed grid tracks: that's what lets a
   part-full row centre itself. They wrap when a band is too wide, which at
   phone widths means one per line. */
.posted-generation {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

/* An index entry, not a container: clicking one focuses the graph and opens
   the detail, exactly as clicking that person's node does. */
.posted-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Grow to share the row, but never so wide that two people in a band
     stretch into a pair of banners. */
  flex: 1 1 200px;
  max-width: 260px;
  min-height: 44px;
  padding: 12px 16px;
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 13px;
  text-align: left;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.posted-card:hover { color: var(--gold-bright); border-color: var(--gold); }
.posted-card.selected { border-color: var(--gold); color: var(--gold-bright); }

/* Play triangle in CSS borders rather than a glyph like "▶" — a "▾" here
   previously rendered as a stray dash in fonts lacking coverage. */
.posted-card-play {
  flex: none;
  width: 0;
  height: 0;
  border-left: 7px solid currentColor;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  opacity: 0.55;
  transition: opacity 0.15s ease;
}
.posted-card:hover .posted-card-play,
.posted-card.selected .posted-card-play { opacity: 1; }

.title-block h1 {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-weight: 700;
  letter-spacing: 0.03em;
  font-size: 22px;
  text-transform: uppercase;
  color: var(--gold-bright);
  margin: 0 0 6px;
}
.title-block .sub {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  color: var(--text-dim);
  font-size: 13px;
  max-width: 640px;
  line-height: 1.5;
  margin: 0 auto;
}

.support-links {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
.support-link {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  text-decoration: none;
  color: var(--gold);
  border: 1px solid var(--gold);
  border-radius: 999px;
  padding: 8px 16px;
  transition: all 0.15s ease;
}
.support-link:hover {
  color: var(--bg);
  background: var(--gold-bright);
  border-color: var(--gold-bright);
}
.github-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.github-badge svg {
  fill: currentColor;
}

main {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 28px 16px 8px;
}

.toggle {
  display: flex;
  gap: 2px;
  margin-bottom: 18px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
}
.toggle button {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  border: none;
  background: transparent;
  color: var(--text-dim);
  padding: 8px 18px;
  border-radius: 999px;
  cursor: pointer;
  transition: all 0.15s ease;
}
.toggle button.active { background: var(--gold); color: var(--bg); }

.legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px 24px;
  margin-bottom: 14px;
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 12px;
  color: var(--text-dim);
}
.legend-item { display: flex; align-items: center; gap: 6px; }
.swatch { width: 20px; height: 0; display: inline-block; border-top: 2px solid; }
.swatch.primary { border-color: var(--border); }
.node-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
  background: var(--node-fill);
  border: 1.5px solid var(--gold);
}
.node-dot.pending { border-style: dashed; opacity: 0.55; }

.interaction-hint {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 11px;
  color: var(--text-dim);
  margin: 0 0 10px;
}

.chart-wrap {
  position: relative;
  width: 100%;
  max-width: 1000px;
  overflow: hidden;
}
svg#chart {
  width: 100%;
  height: 60vh;
  min-height: 320px;
  max-height: 600px;
  background: var(--panel);
  border-radius: 8px;
  border: 1px solid var(--border);
  display: block;
  touch-action: none;
  cursor: grab;
}
svg#chart:active { cursor: grabbing; }

/* Every control that acts on the chart, gathered in one corner: the narrow
   zoom squares stacked, with the wide Reset pill underneath as the base of
   the column rather than a lone overhang at the top. */
.chart-overlay-controls {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}

.reset-view,
.chart-toggle {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 12px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.reset-view:hover,
.chart-toggle:hover { color: var(--gold-bright); border-color: var(--gold); }

/* A toggle rather than a one-shot, so it holds its state visibly: gold fill
   while on, the same "this is active" language the Tree/DAG switch uses.
   aria-pressed carries it for assistive tech and drives the styling too,
   so there's no second source of truth to fall out of sync. */
.chart-toggle[aria-pressed="true"] {
  color: var(--bg);
  background: var(--gold);
  border-color: var(--gold);
}
.chart-toggle[aria-pressed="true"]:hover {
  color: var(--bg);
  background: var(--gold-bright);
  border-color: var(--gold-bright);
}

/* Full wording sits fine across a 1000px chart; on a phone it ran to 58% of
   the chart's width and crowded the graph it controls. aria-label carries
   the full phrase either way. */
.label-narrow { display: none; }

.zoom-controls {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.zoom-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  color: var(--text-dim);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.zoom-btn:hover:not(:disabled) { color: var(--gold-bright); border-color: var(--gold); }
.zoom-btn:disabled { opacity: 0.3; cursor: default; }
/* Inline SVG rather than "+" / "−" glyphs — the same font-coverage trap that
   turned a "▾" into a stray dash earlier in the chart controls. */
.zoom-btn svg {
  display: block;
  width: 12px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (pointer: coarse) {
  .reset-view,
  .chart-toggle { min-height: 44px; padding: 6px 16px; }
  .zoom-btn { width: 44px; height: 44px; }
  .zoom-btn svg { width: 15px; height: 15px; }
}

.node-label {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 11px;
  fill: var(--text);
  pointer-events: none;
}
.edge.primary, .edge.secondary { fill: none; stroke: var(--border); stroke-width: 1.5; }
.node circle {
  fill: var(--node-fill);
  stroke: var(--gold);
  stroke-width: 1.4;
  cursor: pointer;
}
.node:hover circle { stroke: var(--gold-bright); }
.node.root circle { fill: #221d0f; stroke: var(--gold-bright); stroke-width: 2.5; }

/* Awaiting their video: dashed ring + dimmed, not a color change — stays
   readable in grayscale/print and doesn't read as a red "incomplete" flag,
   matching the no-callout-shaming tone in PROJECT_SPEC.md. */
.node.pending circle { stroke-dasharray: 3 2; opacity: 0.55; }
.node.pending .node-label { opacity: 0.6; }
.node.pending:hover circle { opacity: 0.85; }

.chart-background { cursor: default; }

/* Focused on one person: the layout itself is recomputed to include only
   their neighborhood (see app.js isNodeVisible), so this just highlights
   which node was clicked — there's nothing else rendered left to dim. */
.node.selected circle {
  stroke: var(--gold-bright);
  stroke-width: 3;
  filter: drop-shadow(0 0 6px rgba(217, 202, 144, 0.65));
}

/* Once focused, the node becomes the play control for that person: filled
   gold with the chart's own ground showing through as the triangle, so it
   reads as cut out rather than drawn on. Clicking it opens their video —
   see the click handler in drawNodes.

   Not for anyone still awaiting theirs: they keep the plain focus ring
   (their circle is the dashed "pending" treatment), since a play mark would
   promise a video that isn't up. */
.node.selected:not(.pending) circle {
  fill: var(--gold);
}
.node-play {
  fill: var(--panel);
  opacity: 0;
  transition: opacity 0.15s ease;
}
.node.selected:not(.pending) .node-play { opacity: 1; }

.caption {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  font-size: 11.5px;
  color: var(--text-dim);
  margin-top: 10px;
  max-width: 640px;
  text-align: center;
  line-height: 1.5;
}

/* Mobile-first: an ordinary block that sits below the map (the page is
   reordered to put the map first — see the max-width block below), so on a
   phone the map is never covered. The desktop treatment is a centered
   lightbox, further down. */
.detail-panel {
  position: relative;
  background: var(--panel);
  padding: 16px;
  margin: 4px 16px 0;
  border: 1px solid var(--gold);
  border-radius: 10px;
  font-family: 'Helvetica Neue', Arial, sans-serif;
}
.detail-panel.hidden { display: none; }

.detail-backdrop { display: none; }

/* Desktop: a centered lightbox. The old 340px right-hand rail rendered a
   16:9 embed at ~300px wide inside a ~900px-tall column — the container's
   shape was the opposite of the video's, wasting most of its height. A
   centered dialog gives the video the page's width instead. */
@media (min-width: 900px) {
  .detail-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 40;
    background: rgba(0, 0, 0, 0.72);
    transition: opacity 0.18s ease;
  }
  .detail-backdrop.hidden {
    opacity: 0;
    pointer-events: none;
  }

  .detail-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 41;
    width: min(900px, calc(100vw - 96px));
    max-height: calc(100vh - 72px);
    overflow-y: auto;
    margin: 0;
    padding: 28px 32px 32px;
    background: var(--panel-raised);
    border: 1px solid var(--gold);
    border-radius: 12px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.75);
    transform: translate(-50%, -50%);
    animation: detail-pop 0.18s ease;
  }
}

@keyframes detail-pop {
  from { opacity: 0; transform: translate(-50%, -47%); }
}
@media (prefers-reduced-motion: reduce) {
  .detail-panel { animation: none; }
  .detail-backdrop { transition: none; }
}

/* No overlay works on a narrow screen: a full-height side panel blanks the
   map, and a bottom sheet still covers it (at ~820x1180 a 55vh sheet lands
   exactly on top of the chart). Hence the in-flow default above — the map
   is never covered and stays pannable while the detail is open. A lightbox
   would have the same problem, so it's desktop-only.

   This query also reorders the page so the map comes first. Twelve cards ran
   the map ~1020px down an 844px-tall phone screen, putting the thing the
   site exists to show a full swipe below the fold. */
@media (max-width: 899px) {
  body {
    display: flex;
    flex-direction: column;
    /* Video embeds finish loading a beat after the detail opens and can add
       several hundred px to an in-flow block. Scroll anchoring reacts by
       scrolling down by exactly that much to hold the content below it
       still — which yanks the map off the top of the screen right after
       we've deliberately scrolled it into view. */
    overflow-anchor: none;
  }
  .site-header { order: 0; }
  main { order: 1; }
  .detail-panel { order: 2; }
  .posted-section { order: 3; }
  .site-footer { order: 4; }

  .posted-section {
    border-bottom: none;
    border-top: 1px solid var(--border);
    /* Match main's gutter so the cards line up with the map above them —
       the section's wider 32px gutter left them visibly inset. */
    padding-left: 16px;
    padding-right: 16px;
  }

  /* One card per row down here, so the desktop cap that stops a two-person
     band stretching into banners just makes them look narrow instead. */
  .posted-card { max-width: none; }

  .label-wide { display: none; }
  .label-narrow { display: inline; }

  /* A phone-shaped box is much taller than the graph needs once it's zoomed
     for legibility (see MIN_LEGIBLE_SCALE in app.js) — the extra height is
     dead space above and below the tree, and it pushes the detail and the
     card list further down. */
  svg#chart {
    height: 50vh;
    min-height: 280px;
  }
}
/* 44px box around a 20px glyph — the only way to dismiss the detail, so it
   needs a real touch target rather than the ~24px the glyph alone gives. */
.detail-close {
  position: absolute;
  top: 6px;
  right: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 20px;
  cursor: pointer;
  line-height: 1;
}
.detail-close:hover { color: var(--text); }
#detail-name {
  font-size: 18px;
  color: var(--gold-bright);
  margin: 8px 0 4px;
}
.detail-label {
  font-size: 12.5px;
  color: var(--text-dim);
  margin: 0 0 12px;
}
.detail-label.hidden { display: none; }
.detail-relations {
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-dim);
  margin-bottom: 4px;
}
.detail-relations-label { color: var(--text-dim); }
.detail-relation-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: 12px;
  color: var(--gold-bright);
  text-decoration: underline;
  cursor: pointer;
}
.detail-relation-link:hover { color: var(--gold); }

.detail-embed-block {
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

/* Click-to-load stand-in for players that start on their own — currently
   Reddit's. Deliberately not full player height: with no thumbnail to show,
   a 480px empty box reads as a broken embed. */
.detail-embed-facade {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  min-height: 132px;
  padding: 20px;
  font-family: inherit;
  font-size: 13px;
  color: var(--text-dim);
  background: var(--node-fill);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.detail-embed-facade:hover {
  color: var(--gold-bright);
  border-color: var(--gold);
}
.detail-embed-facade-play {
  flex: none;
  width: 0;
  height: 0;
  border-left: 15px solid currentColor;
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
}

/* Source switcher, shown only when someone posted the same video to more
   than one platform (see buildSourcePicker). */
.detail-sources-block {
  padding: 12px 0 0;
  border-top: 1px solid var(--border);
}
.detail-sources {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.detail-source {
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  min-height: 38px;
  padding: 8px 16px;
  cursor: pointer;
  transition: all 0.15s ease;
}
.detail-source:hover { color: var(--gold-bright); border-color: var(--gold); }
/* Has to sit after the base rule, not up with the other coarse-pointer
   overrides — a media query adds no specificity, so from further up the
   file the 38px default would simply win. */
@media (pointer: coarse) {
  .detail-source { min-height: 44px; }
}
.detail-source.active {
  color: var(--bg);
  background: var(--gold);
  border-color: var(--gold);
}
/* The switcher already draws the rule above it. */
.detail-source-mount .detail-embed-block {
  border-top: none;
  padding-top: 4px;
}
.detail-embed {
  max-width: 100%;
  overflow-x: auto;
  margin-bottom: 6px;
}
.detail-embed-youtube {
  overflow: hidden;
  border-radius: 6px;
}
.detail-embed-youtube iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  display: block;
}
.detail-link {
  display: block;
  font-size: 13px;
  padding: 8px 0;
}
.detail-embed-block .detail-link { padding: 0; }
.detail-link-fallback {
  font-size: 11.5px;
  color: var(--text-dim);
}
.detail-awaiting {
  font-size: 13px;
  color: var(--text-dim);
  font-style: italic;
}
