/* ============================================================
   Cohesion — themeable "Constellation" UI.
   Dark (night sky) is the default; `body.light` opts back into the
   original "Newspaper Minimalist" cream palette. All surface colors
   flow through custom properties so the two themes stay in sync.
   ============================================================ */

@font-face {
  font-family: 'Planets';
  src: url('./Planets.otf') format('opentype');
}

:root {
  /* ── page chrome ── */
  --paper: #050507;
  --card: #151720;
  --ink: #f1f2f6;
  --ink-60: rgba(241, 242, 246, 0.6);
  --ink-35: rgba(241, 242, 246, 0.35);
  --rule: rgba(255, 255, 255, 0.15);
  --sage: #8ba88e;
  --sage-deep: #5f7d64;
  --slate: #7a8b99;
  --terra: #c1785a;
  --purple: #a879c9;
  /* a warning, not a failure — reads as a question, so it must not wear
     --terra (which the UI uses for "this is broken") */
  --ochre: #c8a24a;
  --serif: "Newsreader", Georgia, "Times New Roman", serif;
  --sans: "Inter", "Noto Emoji", -apple-system, system-ui, sans-serif;
  --shadow: 0 4px 12px rgba(0, 0, 0, 0.05);

  /* ── the constellation board ── */
  /* one black everywhere: the same #050507 the masthead/page uses, so header,
     board and clue desk are a single continuous night sky */
  --board-bg: #050507;
  /* --board-shadow: inset 0 0 60px rgba(0, 0, 0, 0.9);
  /* stars spread evenly across the tile — one near every corner + edge + a few
     interior — so no surface (even the short masthead) shows a dead-black
     corner, yet no region collects extra light. Density is uniform, so header,
     board and clue desk read as the same black. */
  --board-stars:
    radial-gradient(1.5px 1.5px at 6% 8%, rgba(255, 255, 255, 0.7), transparent),
    radial-gradient(1px 1px at 94% 6%, rgba(255, 255, 255, 0.65), transparent),
    radial-gradient(1.5px 1.5px at 8% 92%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(1px 1px at 92% 94%, rgba(255, 255, 255, 0.7), transparent),
    radial-gradient(2px 2px at 50% 4%, rgba(255, 255, 255, 0.55), transparent),
    radial-gradient(1px 1px at 4% 48%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(1.5px 1.5px at 96% 50%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(1px 1px at 50% 95%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(2px 2px at 28% 30%, rgba(255, 255, 255, 0.85), transparent),
    radial-gradient(1px 1px at 72% 26%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(1.5px 1.5px at 30% 70%, rgba(255, 255, 255, 0.7), transparent),
    radial-gradient(1px 1px at 70% 72%, rgba(255, 255, 255, 0.8), transparent),
    radial-gradient(1px 1px at 52% 52%, rgba(255, 255, 255, 0.5), transparent);

  /* ── edges & badges ── */
  --edge-stroke: rgba(255, 255, 255, 0.15);
  --badge-bg: #1c1c24;
  --badge-border: rgba(255, 255, 255, 0.15);
  --badge-ink: rgba(255, 255, 255, 0.75);
  --badge-tech-ink: #e6c567;
  --badge-tech-bg: rgba(212, 168, 67, 0.2);
  /* verdict fills carry white ✓/✕ text — deepened from --sage/--terra so that
     white passes WCAG AA (5.7:1 and 6.1:1). Same in both themes. */
  --good-fill: #4f6e54;
  --bad-fill: #9c4a30;

  /* ── nodes ── */
  --node-bg: radial-gradient(circle at center, #1a1a24 0%, #0a0a0d 100%);
  --node-border: rgba(255, 255, 255, 0.25);
  --node-ink: rgba(255, 255, 255, 0.5);
  --glyph-dim: rgba(255, 255, 255, 0.3);
  --node-active: #fff;
  --node-active-border: rgba(255, 255, 255, 0.6);
  --node-armed-border: rgba(255, 255, 255, 0.7);
  --node-target-shadow: 0 0 12px rgba(255, 255, 255, 0.15);
  --node-armed-pulse-a: 0 0 8px rgba(255, 255, 255, 0.2);
  --node-armed-pulse-b: 0 0 18px rgba(255, 255, 255, 0.5);
  --node-fill-bg: #1c1c24;
  --node-fill-border: rgba(255, 255, 255, 0.3);
  --node-fill-shadow: 0 4px 12px rgba(0, 0, 0, 0.4), 0 0 8px rgba(255, 255, 255, 0.05);
  --node-correct-bg: color-mix(in srgb, var(--sage) 35%, #1c1c24);
  --node-correct-shadow: 0 0 20px color-mix(in srgb, var(--sage) 60%, transparent);
  --node-wrong-bg: color-mix(in srgb, var(--terra) 35%, #1c1c24);
  --node-wrong-shadow: 0 0 20px color-mix(in srgb, var(--terra) 60%, transparent);
  --node-expert-bg: color-mix(in srgb, #4285F4 35%, #1c1c24);
  --node-misplaced-bg: color-mix(in srgb, var(--ochre) 32%, #1c1c24);
  --node-name: #fff;
  --node-tag: rgba(255, 255, 255, 0.6);
}

/* ── light theme: the original cream newspaper look ── */
body.light {
  --paper: #faf9f4;
  --card: #fffefa;
  --ink: #1c1c1a;
  --ink-60: #6b6a62;
  --ink-35: #a3a196;
  --rule: #e3e0d3;
  --purple: #814da8;
  --ochre: #9a7212; /* darker on cream, to keep the contrast readable */
  --shadow: 0 4px 12px rgba(0, 0, 0, 0.05);

  --board-bg: transparent;
  --board-shadow: none;
  --board-stars: none;

  --edge-stroke: var(--ink-35);
  --badge-bg: var(--paper);
  --badge-border: var(--rule);
  --badge-ink: var(--ink-60);
  --badge-tech-ink: #7a6520;
  --badge-tech-bg: #fdf6e3;

  --node-bg: var(--paper);
  --node-border: var(--ink-35);
  --node-ink: var(--ink-35);
  --glyph-dim: var(--ink-35);
  --node-active: var(--slate);
  --node-active-border: var(--slate);
  --node-armed-border: var(--slate);
  --node-target-shadow: 0 0 0 3px color-mix(in srgb, var(--slate) 18%, transparent);
  --node-armed-pulse-a: 0 0 0 3px color-mix(in srgb, var(--slate) 22%, transparent);
  --node-armed-pulse-b: 0 0 0 7px color-mix(in srgb, var(--slate) 9%, transparent);
  --node-fill-bg: var(--tint, var(--card));
  --node-fill-border: var(--rule);
  --node-fill-shadow: var(--shadow);
  --node-correct-bg: color-mix(in srgb, var(--sage) 12%, var(--card));
  --node-correct-shadow: 0 0 0 3px color-mix(in srgb, var(--sage) 20%, transparent);
  --node-wrong-bg: color-mix(in srgb, var(--terra) 12%, var(--card));
  --node-wrong-shadow: 0 0 0 3px color-mix(in srgb, var(--terra) 20%, transparent);
  --node-expert-bg: color-mix(in srgb, #4285F4 12%, var(--card));
  --node-misplaced-bg: color-mix(in srgb, var(--ochre) 14%, var(--card));
  --node-name: var(--ink);
  --node-tag: var(--ink-60);
}

* { margin: 0; padding: 0; box-sizing: border-box; font-variant-emoji: text; }
html { height: 100%; }
body {
  min-height: 100%;
  overflow-x: hidden; /* the full-bleed sky backdrop is 100vw; don't let it scroll sideways */
  /* the whole page is one night sky: the star field lives on the body so it
     spans the full viewport width (no dead-black margins beside the 780px
     column on wide windows). The board paints its own opaque sky on top. */
  background-color: var(--paper);
  background-image: var(--board-stars);
  background-size: 250px 250px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.hidden { display: none !important; }

#custom-tooltip {
  position: fixed;
  z-index: 10000;
  pointer-events: none;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 500;
  padding: 4px 8px;
  border-radius: 4px;
  transform: translate(-50%, -100%);
  opacity: 0;
  transition: opacity 0.1s;
  white-space: nowrap;
  letter-spacing: 0.02em;
}
#custom-tooltip.visible {
  opacity: 1;
}

button { font-family: var(--sans); cursor: pointer; }
button:focus-visible, a:focus-visible { outline: 2px solid var(--slate); outline-offset: 2px; }

/* ===== masthead ===== */
#masthead {
  width: 100%;
  max-width: 780px;
  padding: 18px 20px 10px;
  text-align: center;
  /* transparent — the continuous star field on <body> shows through, so the
     header reads as the top of the same uninterrupted sky */
  background: transparent;
}
.mast-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 15px;
  color: var(--ink-60);
}
.mast-top a { color: var(--ink-60); text-decoration: none; letter-spacing: 0.04em; }
.mast-top a:hover { color: var(--ink); }
.mast-tools { display: inline-flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
/* labelled pills: an icon glyph + its name, comfortably sized and tappable */
#archive-btn, #help-btn, #settings-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 15px;
  background: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink-60);
  font-family: var(--sans);
  font-size: 15px; font-weight: 600; line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
#archive-btn:hover, #help-btn:hover, #settings-btn:hover {
  color: var(--ink);
  border-color: var(--ink-35);
  background: color-mix(in srgb, var(--ink) 6%, transparent);
}
.mast-tools .nav-ico { font-size: 18px; line-height: 1; }
/* Rules uses a "?" drawn inside a circle (currentColor, so it tracks the pill) */
.mast-tools .nav-qmark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  font-size: 12px; font-weight: 700;
  box-sizing: border-box;
}
.mast-title {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(34px, 8vw, 46px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-top: 1rem;
}
.mast-title a {
  color: inherit;
  text-decoration: none;
}
.mast-logo {
  height: clamp(48px, 11.2vw, 50px);
  width: auto;
  vertical-align: middle;
  filter: invert(1); /* make black text white for dark mode */
}
body.light .mast-logo {
  filter: none; /* keep it black in light mode */
}
.win-logo {
  display: block;
  height: clamp(40px, 10vw, 56px);
  width: auto;
  margin: 0 auto 4px;
  filter: invert(1); /* white in dark mode, matching the masthead */
}
body.light .win-logo {
  filter: none;
}
.mast-rule {
  border: none;
  border-top: 1px solid var(--ink);
  margin: 8px 0 3px;
  position: relative;
}
.mast-rule::after {
  content: "";
  display: block;
  border-top: 1px solid var(--ink);
  margin-top: 2px;
}
.mast-dateline {
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-60);
  padding-bottom: 6px;
  padding-top: 6px;
  border-bottom: 1px solid var(--rule);
}

/* Diagnostic marker: this board came from the rotation fallback, not the
   scheduler — i.e. nothing is scheduled for that date. */
.fallback-flag {
  display: inline-block;
  vertical-align: baseline;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--badge-tech-ink);
  background: var(--badge-tech-bg);
  border: 1px solid #d4a843;
  border-radius: 3px;
  padding: 1px 5px;
  white-space: nowrap;
}

main { width: 100%; max-width: 780px; padding: 0 20px 40px; flex: 1; }

/* ===== front page (overlay) ===== */
.front-sheet {
  background: var(--card); /* use card instead of paper so it stands out */
}
.front-kicker {
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--slate);
}
.front-title {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(30px, 6vw, 44px);
  line-height: 1.15;
  margin: 10px auto 12px;
  max-width: 520px;
}
.front-formation {
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--sage-deep);
  margin: -4px 0 12px;
}
.front-brief {
  font-family: var(--serif);
  font-style: italic;
  font-size: 18px;
  color: var(--ink-60);
  max-width: 440px;
  margin: 0 auto 20px;
}
.perk-note {
  font-size: 16px;
  color: var(--terra);
  max-width: 380px;
  margin: 0 auto 22px;
  min-height: 18px;
}
.perk-note:empty { margin: 0 auto; }

/* mode choice */
#mode-choice {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 480px;
  margin: 0 auto;
}
.mode-card {
  flex: 1 1 200px;
  max-width: 230px;
  text-align: left;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 14px;
  padding: 16px 18px;
  color: var(--ink);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.mode-card:hover { transform: translateY(-2px); border-color: var(--ink-35); }
.mode-name { font-family: var(--serif); font-size: 20px; font-weight: 500; }
.mode-desc { font-size: 16px; color: var(--ink-60); line-height: 1.45; }

/* back issues */
#back-issues {
  display: none;  
  margin-top: 30px;
  border-top: 1px solid var(--rule);
  padding-top: 16px;
}
#back-issues:empty { border: none; }
.bi-head {
  font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ink-60); margin-bottom: 10px;
}
.bi-list { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.bi-item {
  font-size: 16px;
  color: var(--ink);
  text-decoration: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 6px 13px;
  background: var(--card);
}
.bi-item:hover { border-color: var(--slate); }
.bi-item.solved { color: var(--sage-deep); }
.bi-item .bi-no { font-family: var(--serif); font-style: italic; margin-right: 4px; }
.front-mark { width: 108px; height: 70px; margin: 6px auto 20px; display: block; }
.front-mark line { stroke: var(--ink); stroke-width: 1.4; }
.front-mark circle { fill: var(--paper); stroke: var(--ink); stroke-width: 1.4; }
.front-mark .tinted { fill: var(--sage); }
.btn {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  border: none;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: 14px 36px;
  box-shadow: var(--shadow);
  transition: transform 0.16s ease;
}
.btn:hover { transform: translateY(-1px); }
.btn.quiet {
  background: none; color: var(--ink-60);
  border: 1px solid var(--rule); box-shadow: none;
  padding: 11px 26px;
}
.btn.quiet:hover { color: var(--ink); border-color: var(--ink-35); }
.front-foot { margin-top: 22px; font-size: 16px; color: var(--ink-60); }
.front-foot b { color: var(--ink); font-weight: 600; }

/* ===== board page ===== */
#play, #drawer { transition: filter 0.3s ease, opacity 0.3s ease; }
#play { padding-top: 12px; }
#play.blurred, #drawer.blurred { filter: blur(8px) saturate(0.8); opacity: 0.6; pointer-events: none; }


#board {
  position: relative;
  width: 100%;
  height: clamp(500px, 55vh, 700px);
  margin: 6px 0 4px;
  -webkit-user-select: none;
  user-select: none;
}
/* The night sky is painted on a full-bleed backdrop layer, not the board box
   itself, so it can span the whole viewport width and run off the bottom while
   the percentage-positioned nodes stay put. Its top is pinned to the board top
   (top: 0), so the top edge doesn't move. */
#board::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  height: max(100%, 100vh);
  background-color: var(--board-bg);
  background-image: var(--board-stars);
  background-size: 250px 250px;
  background-repeat: repeat;
  box-shadow: var(--board-shadow);
  z-index: -1;
  pointer-events: none;
}
#edge-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.edge-base, .edge-fuse, .edge-hit { fill: none; }
.edge-base { stroke: var(--edge-stroke); stroke-width: 1.2; }
/* A revealed edge carries a clue you can re-open — mark the line as "intel"
   in slate. Satisfaction (good/technical/bad, below) still overrides it. */
.edge-base.revealed { stroke: var(--purple); stroke-width: 1.5; }
.edge-base.bad { stroke: var(--terra); stroke-width: 1.6; }
.edge-base.good { stroke: var(--sage-deep); stroke-width: 1.6; }
.edge-base.technical { stroke: var(--sage); stroke-width: 1.6; }
.edge-fuse {
  stroke: var(--sage-deep);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  opacity: 0;
}
.edge-fuse.lit {
  opacity: 1;
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.55s cubic-bezier(0.3, 0.8, 0.4, 1);
}
/* wide invisible grab target: tap the line to swap the two athletes on it */
.edge-hit { stroke: transparent; stroke-width: 20; pointer-events: stroke; cursor: pointer; }
.edge-base.hover { stroke: var(--slate); stroke-width: 2; }

/* connect-spark: a twinkle of starlight where two nodes lock in */
.spark {
  position: absolute;
  width: 12px; height: 12px;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 3;
  color: var(--sage-deep);
  background: radial-gradient(circle, color-mix(in srgb, var(--sage) 70%, transparent) 0%, transparent 62%);
  animation: sparkGlow 0.72s ease-out forwards;
}
.spark::before {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(currentColor, currentColor) center / 1.5px 100% no-repeat,
    linear-gradient(currentColor, currentColor) center / 100% 1.5px no-repeat;
  animation: sparkStar 0.72s ease-out forwards;
}
@keyframes sparkGlow {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.2); }
  30% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(2.6); }
}
@keyframes sparkStar {
  0% { opacity: 0; transform: scale(0.2) rotate(0deg); }
  40% { opacity: 0.9; }
  100% { opacity: 0; transform: scale(1.7) rotate(40deg); }
}

/* victory: the same night sky the player solved on — same black, same wide
   star field — the stars just "come alive". The twinkle layer is full-bleed
   (mirrors #board::before) so the living stars span the whole viewport width,
   not just the 780px board column. */
.starfield {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  height: max(100%, 100vh);
  pointer-events: none;
  z-index: 0;
}
.star {
  position: absolute;
  width: var(--sz, 2px); height: var(--sz, 2px);
  border-radius: 50%;
  background: var(--sage-deep);
  box-shadow: 0 0 5px color-mix(in srgb, var(--sage) 65%, transparent);
  transform: translate(-50%, -50%) scale(0);
  animation: twinkle var(--d, 1.6s) ease-in-out var(--delay, 0s) infinite;
}
@keyframes twinkle {
  0%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
  50% { opacity: 0.85; transform: translate(-50%, -50%) scale(1); }
}

/* modifier badge floats just off the edge (positioned in JS) so the base
   line reads through unbroken; paper backing keeps it legible over anything */
.edge-badge {
  position: absolute;
  transform: translate(-50%, -50%);
  background: var(--badge-bg);
  border: 1px solid var(--badge-border);
  border-radius: 8px; /* Rounded square instead of pill */
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--badge-ink);
  padding: 3px 9px;
  white-space: pre-wrap;
  max-width: 140px;
  text-align: center;
  pointer-events: auto;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: color 0.02s, border-color 0.1s, background 0.1s;
}
/* Revealed: a slate-tinted, bordered chip that reads as a re-openable clue.
   The satisfaction states (good/technical/bad) below override the colours. */
.edge-badge.revealed {
  padding: 4px 12px;
  border-color: color-mix(in srgb, var(--purple) 60%, var(--badge-border));
  background: color-mix(in srgb, var(--purple) 14%, var(--badge-bg));
}
.badge-reveal {
  font-family: var(--serif);
  font-style: italic;
  font-size: 11.5px;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 500;
  color: var(--purple);
  margin-top: -1px;
  /* Just the opening line on the grid; the rest lives in the zoom. */
  max-width: 118px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--purple) 45%, transparent);
  text-underline-offset: 2px;
}
/* Once judged, the chip takes its verdict colour — keep the clue line legible
   against it rather than slate-on-green. */
.edge-badge.good .badge-reveal,
.edge-badge.technical .badge-reveal,
.edge-badge.bad .badge-reveal {
  color: inherit;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
}
.edge-badge.good { color: #fff; background: var(--good-fill); border-color: var(--good-fill); }
.edge-badge.good .badge-label::before { content: "✓ "; }
.edge-badge.technical { color: var(--badge-tech-ink); background: var(--badge-tech-bg); border-color: #d4a843; }
.edge-badge.technical .badge-label::before { content: "~ "; }
.edge-badge.bad { color: #fff; background: var(--bad-fill); border-color: var(--bad-fill); animation: badgeShake 0.4s ease; }
.edge-badge.bad .badge-label::before { content: "✕ "; }
.edge-base.bad { stroke-dasharray: 4 3; }
@keyframes badgeShake {
  0%, 100% { transform: translate(-50%, -50%); }
  25% { transform: translate(calc(-50% - 3px), -50%); }
  75% { transform: translate(calc(-50% + 3px), -50%); }
}

/* nodes */
.node {
  position: absolute;
  transform: translate(-50%, -50%);
  width: var(--node-size, clamp(80px, 15vw, 100px));
  aspect-ratio: 1;
  border-radius: 50%;
  border: 1.5px dashed var(--node-border);
  background: var(--node-bg);
  color: var(--node-ink);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 7px 8px;
  font-family: var(--sans);
  transition: border-color 0.1s, background 0.1s, box-shadow 0.1s;
  touch-action: none;
}
.node .node-letter {
  font-family: 'Planets', var(--serif);
  font-size: 17px;
  font-style: italic;
}
/* swappable planet symbols — a webfont so
   they render the same everywhere, never as tofu boxes */
.glyph {
  font-family: 'Planets', var(--serif);
  font-style: normal;
  font-weight: normal;
  font-feature-settings: normal;
}
.glyph[data-label="A"], .node .node-letter.glyph[data-label="A"] { color: #ff8b94; }
.glyph[data-label="B"], .node .node-letter.glyph[data-label="B"] { color: #ca3fb1ff; }
.glyph[data-label="C"], .node .node-letter.glyph[data-label="C"] { color: #ffd3b6; }
.glyph[data-label="D"], .node .node-letter.glyph[data-label="D"] { color: #dcedc1; }
.glyph[data-label="E"], .node .node-letter.glyph[data-label="E"] { color: #a8e6cf; }
.glyph[data-label="F"], .node .node-letter.glyph[data-label="F"] { color: #a4b3f9; }
.glyph[data-label="G"], .node .node-letter.glyph[data-label="G"] { color: #c4a1ff; }
.glyph[data-label="H"], .node .node-letter.glyph[data-label="H"] { color: #ffb6c1; }
.node .node-letter.glyph { font-size: 46px; line-height: 1; color: var(--glyph-dim); font-style: normal; }
.node.armed .node-letter.glyph, .node.targetable .node-letter.glyph { color: var(--node-active); }
.node.targetable { border-color: var(--node-active-border); color: var(--node-active); box-shadow: var(--node-target-shadow); }
/* node-first: an armed empty node pulses while it waits for a name */
.node.armed {
  border-style: solid;
  border-color: var(--node-armed-border);
  color: var(--node-active);
  animation: armedPulse 1.5s ease-in-out infinite;
}
@keyframes armedPulse {
  0%, 100% { box-shadow: var(--node-armed-pulse-a); }
  50% { box-shadow: var(--node-armed-pulse-b); }
}
.node.filled {
  border-style: solid;
  border-color: var(--node-fill-border);
  background: var(--node-fill-bg);
  color: var(--node-name);
  box-shadow: var(--node-fill-shadow);
}
.node.filled.correct {
  border-color: var(--sage);
  background: var(--node-correct-bg);
  box-shadow: var(--node-correct-shadow);
}
.node.filled.wrong {
  border-color: var(--terra);
  background: var(--node-wrong-bg);
  box-shadow: var(--node-wrong-shadow);
}
/* Placed but never submitted: the board is deliberately silent about it. */
.node.filled.unjudged {
  border-color: #4285F4;
  background: var(--node-expert-bg);
  box-shadow: 0 0 12px color-mix(in srgb, #4285F4 60%, transparent);
}
/* Right name, wrong planet — it's in the solution, just not here. Ochre, not
   terra: this is a question to answer, not a placement that's broken. */
.node.filled.misplaced {
  border-color: var(--ochre);
  background: var(--node-misplaced-bg);
  box-shadow: 0 0 14px color-mix(in srgb, var(--ochre) 45%, transparent);
}
/* A correct planet is settled: it can't be dragged, dropped on, or taken back. */
.node.locked { cursor: default; }
.node.locked::after {
  content: "🔒";
  position: absolute;
  bottom: -7px; left: 50%;
  transform: translateX(-50%);
  font-size: 10px;
  line-height: 1;
  opacity: 0.75;
}
/* the flash as a submit's verdict lands on each planet in turn */
.node.judged { animation: verdictFlash 0.45s ease; }
@keyframes verdictFlash {
  0% { transform: translate(-50%, -50%) scale(1); }
  45% { transform: translate(-50%, -50%) scale(1.09); }
  100% { transform: translate(-50%, -50%) scale(1); }
}
.node .node-letter.glyph.small-glyph {
  position: absolute;
  top: -8px;
  right: -8px;
  font-size: 14px;
  line-height: 1;
  background: var(--badge-bg);
  border: 1px solid var(--badge-border);
  border-radius: 50%;
  box-shadow: var(--shadow);
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.node.filled.correct .node-letter.glyph.small-glyph {
  border-color: var(--sage);
}
.node .node-name { font-weight: 600; font-size: 12.5px; line-height: 1.25; text-align: center; color: var(--node-name); }
.node .node-tag {
  font-size: 9.5px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--node-tag);
}
.node .node-scout-tag {
  position: absolute;
  bottom: -8px;
  background: var(--badge-bg);
  border: 1px solid var(--slate);
  color: var(--slate);
  border-radius: 999px;
  font-size: 9px;
  padding: 1px 6px;
  letter-spacing: 0.05em;
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  text-transform: uppercase;
  white-space: nowrap;
}
.node.pop { animation: nodePop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes nodePop {
  0% { transform: translate(-50%, -50%) scale(1); }
  40% { transform: translate(-50%, -50%) scale(1.12); }
  100% { transform: translate(-50%, -50%) scale(1); }
}
.node.snap { animation: nodeSnap 0.38s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes nodeSnap {
  0% { transform: translate(-50%, -50%) scale(0.92); }
  55% { transform: translate(-50%, -50%) scale(1.06); }
  100% { transform: translate(-50%, -50%) scale(1); }
}

/* flying tile (bank → node) */
.flier {
  position: fixed;
  z-index: 60;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 12.5px;
  font-weight: 600;
  padding: 8px 16px;
  pointer-events: none;
  white-space: nowrap;
  transition: transform 0.38s cubic-bezier(0.34, 1.3, 0.5, 1), opacity 0.38s ease;
}

/* ===== player bank ===== */
#bank-label {
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-60);
  text-align: center;
  border-top: 1px solid var(--rule);
  padding-top: 12px;
  margin-top: 2px;
}
#bank {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  padding: 10px 0 4px;
}
.chip {
  border: 1px solid var(--rule);
  background: var(--card);
  border-radius: 50%;
  width: clamp(75px, 15vw, 90px);
  aspect-ratio: 1;
  padding: 4px;
  justify-content: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.2;
  box-shadow: var(--shadow);
  transition: opacity 0.1s, border-color 0.1s, box-shadow 0.1s, transform 0.1s;
}
.chip .chip-name { font-weight: 600; font-size: 12.5px; color: var(--ink); }
.chip .chip-tag { font-size: 9.5px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-60); }
.chip:hover { transform: translateY(-1px); }
.chip.selected {
  border-color: var(--slate);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--slate) 22%, transparent);
}
.chip.used { opacity: 0.32; pointer-events: none; }
/* a decoy struck by "Cut a Decoy": crossed out and out of play */
.chip.cut { opacity: 0.3; pointer-events: none; text-decoration: line-through; text-decoration-color: var(--terra); }
.chip.cut .chip-name { color: var(--ink-60); }
.chip.cutting { animation: cutStrike 0.5s ease; }
@keyframes cutStrike {
  0% { transform: scale(1); }
  40% { transform: scale(1.06) rotate(-1.5deg); border-color: var(--terra); }
  100% { transform: scale(1); }
}
/* while a node is armed, the whole bank reads as "pick one of these" */
#bank.picking .chip:not(.used) { border-color: var(--slate); }

/* ===== drag-and-drop ===== */
/* the chip fades while its ghost is in flight */
.chip.chip-dragging {
  opacity: 0.25;
  transform: scale(0.95);
  pointer-events: none;
}
/* floating ghost that follows the pointer */
.drag-ghost {
  position: fixed;
  z-index: 70;
  border: 1.5px solid var(--slate);
  background: var(--card);
  border-radius: 50%;
  width: clamp(75px, 15vw, 90px);
  aspect-ratio: 1;
  padding: 4px;
  justify-content: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.2;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18), 0 0 0 3px color-mix(in srgb, var(--slate) 22%, transparent);
  pointer-events: none;
  font-family: var(--sans);
  transform: scale(1.06);
  will-change: left, top;
}
.drag-ghost .chip-name { font-weight: 600; font-size: 12.5px; color: var(--ink); }
.drag-ghost .chip-tag { font-size: 9.5px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-60); }
/* the node glows when the ghost hovers near it */
.node.drop-hover {
  border-style: solid;
  border-color: var(--slate);
  transition: box-shadow 0.05s, border-color 0.05s;
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--slate) 24%, transparent),
              0 0 16px color-mix(in srgb, var(--slate) 12%, transparent);
}
.node.drop-hover .node-letter.glyph { color: var(--slate); }

/* ===== clue drawer ===== */
#drawer {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 100%;
  max-width: 560px;
  /* the clue desk wears the same night-sky motif as the board: a scatter of
     starlight over the exact same board-black, so the two surfaces read as one
     continuous sky (light mode keeps the cream card — see override below) */
 background: none;
  border: 1px solid var(--rule);
  border-bottom: none;
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.07);
  transition: transform 0.4s cubic-bezier(0.3, 1, 0.4, 1);
  transform: translateX(-50%) translateY(calc(100% - 46px));
  z-index: 40;
}
/* light mode: the board is transparent over cream, so the panel keeps its
   own card surface rather than going see-through */
body.light #drawer { background: none;}
#drawer.open { transform: translateX(-50%) translateY(0); }


#drawer-handle {
  width: 100%;
  background: none;
  border: none;
  padding: 13px 18px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
}
#drawer-handle::before {
  content: "";
  position: absolute;
  top: 6px; left: 50%;
  transform: translateX(-50%);
  width: 36px; height: 3px;
  border-radius: 3px;
  background: var(--rule);
}
#drawer-scroll {
  max-height: min(62vh, 480px);
  overflow-y: auto;
}
/* ===== candidates (the bank, docked in the clue desk) ===== */
#bank-panel {
  border-top: 1px solid var(--rule);
  margin-top: 6px;
  padding: 0 12px 16px;
}
#bank-heading {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-60);
  text-align: center;
  padding: 14px 0 2px;
}
/* Docked in the rail, the handle already says CANDIDATES — don't say it twice. */
#drawer-scroll #bank-heading { display: none; }
.clue-section { padding: 6px 0 2px; }
.clue-group + .clue-group { margin-top: 24px; }
.section-head {
  border-top: 1px solid var(--rule);
  padding-top: 12px;
  margin-bottom: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.section-name {
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 500;
}
.section-blurb { font-size: 16px; color: var(--ink-60); }
.section-done { font-size: 16px; color: var(--ink-35); font-style: italic; padding: 4px 0; }
.free-tag {
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--sage-deep); border: 1px solid var(--sage); border-radius: 999px;
  padding: 1px 6px; margin-left: 4px; vertical-align: middle;
}
.clue {
  padding: 8px 2px;
}
.clue-text { font-size: 16px; font-weight: normal; color: var(--ink); letter-spacing: 0.04em; }
.clue-text.masked {
  color: var(--ink-35);
  filter: blur(4px);
  user-select: none;
}
.clue-text.unfurl { animation: unfurl 0.6s ease both; }
@keyframes unfurl {
  from { filter: blur(6px); opacity: 0.3; }
  to { filter: blur(0); opacity: 1; }
}
.clue-actions { margin-top: 6px; display: flex; gap: 8px; align-items: center; }
.clue-btn {
  background: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: 12px;
  font-weight: normal;
  letter-spacing: 0.04em;
  padding: 6px 12px;
  color: var(--ink);
}
.clue-btn:hover, .clue-btn.lit { border-color: var(--terra); color: var(--terra); }
.clue-btn.confirm { border-color: var(--terra); background: var(--terra); color: #fff; }
.clue-cost { font-size: 11px; color: var(--terra); font-weight: 600; }

/* targeted-intel rows: the edge/node being probed, shown plain (it's
   the question), so only the answer arrives blurred-then-unfurled */
.clue-text.intel-label { color: var(--ink-60); font-size: 16px; }
.clue-text.intel-label b { color: var(--ink); }
.clue[data-hi-edge]:hover, .clue[data-hi-node]:hover, .clue[data-hi-edge].lit, .clue[data-hi-node].lit { background: color-mix(in srgb, var(--slate) 8%, transparent); border-radius: 8px; }

.intel-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 4px 2px;
}
.intel-grid .clue-btn {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* board echo when hovering an intel row */
.edge-badge.lit { color: var(--slate); border-color: var(--slate); background: color-mix(in srgb, var(--slate) 14%, var(--paper)); }
.node.lit { border-color: var(--slate); box-shadow: 0 0 0 3px color-mix(in srgb, var(--slate) 22%, transparent); }

/* ===== budget bars (clues left, submits right — one row, equal halves) ===== */
#top-bars {
  display: flex;
  align-items: stretch;
  gap: 8px;
  margin-bottom: 4px;
}
.budget-bar {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--card);
}
/* Once the game is over the submit half is gone; the clue half keeps the row. */
#print-bar.hidden { display: none; }
#clue-actions { display: flex; gap: 8px; }

.clue-btn {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 13px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.12s ease;
}
.clue-btn .clue-ico { font-size: 14px; line-height: 1; }
/* :not(.armed) so hovering an armed clue can't repaint its border back to a
   resting colour — the armed state has to survive the cursor sitting on it. */
.clue-btn:hover:not(:disabled):not(.armed) { border-color: var(--ink-35); transform: translateY(-1px); }
.clue-btn:disabled { opacity: 0.3; cursor: default; pointer-events: none; }
/* An armed clue is holding the board's next tap — say so loudly. */
.clue-btn.armed {
  border-color: var(--slate);
  background: color-mix(in srgb, var(--slate) 18%, transparent);
  color: var(--slate);
  animation: cluePulse 1.6s ease-in-out infinite;
}
@keyframes cluePulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--slate) 40%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--slate) 0%, transparent); }
}

/* Each budget bar leads with a small eyebrow title on its own line (top-left),
   so it's obvious which half is clues and which is the submit. flex-basis:100%
   forces the wrap; the actions and the charge stack flow on the line below. */
.bar-title {
  flex: 0 0 100%;
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-60);
}
/* The charge stack, captioned. margin-left:auto pins the group to the bar's
   right edge even when the bar wraps. */
.charge-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  margin-left: auto;
}
.charge-label {
  font-family: var(--sans);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-60);
}
/* Both budgets read as a stack of planet glyphs (the Planets-font "Æ"), not a
   number — draining top to bottom. */
.charge-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.charge-glyph {
  font-family: 'Planets', var(--serif);
  font-style: normal;
  font-weight: normal;
  font-size: 26px;
  line-height: 1;
  color: var(--sage);
  text-shadow: 0 0 8px color-mix(in srgb, var(--sage) 45%, transparent);
  transition: color 0.3s ease, opacity 0.3s ease, text-shadow 0.3s ease;
}
.charge-glyph.spent {
  color: var(--ink-35);
  opacity: 0.5;
  text-shadow: none;
}
/* Attempt glyphs read as a second, separate budget — slate, not sage. */
.charge-glyph.attempt-glyph {
  color: var(--slate);
  text-shadow: 0 0 8px color-mix(in srgb, var(--slate) 45%, transparent);
}
.charge-glyph.attempt-glyph.spent {
  color: var(--ink-35);
  text-shadow: none;
}
/* One attempt left — the last glyph burns. */
#attempt-charges.last-chance .charge-glyph:not(.spent) {
  color: var(--terra);
  text-shadow: 0 0 10px color-mix(in srgb, var(--terra) 55%, transparent);
}

/* The confirm bar holds its slot whether or not it's showing, so arming Decoy
   fades a bar in rather than pushing the whole board down a row. Its height is
   fixed for the same reason — the one line of copy never wraps to two. */

/* While a clue is armed the board dims and only legal targets stay lit — and
   each legal target gets a dashed ring circling it, so the board itself says
   "tap one of these" and no instruction line has to. */
#board.arming .node:not(.clue-target) { opacity: 0.42; }
#board.arming .node.clue-target {
  border-color: var(--slate);
  cursor: pointer;
}
#board.arming .node.clue-target::before {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: 50%;
  border: 2px dashed var(--slate);
  opacity: 0.85;
  pointer-events: none;
  animation: targetOrbit 5s linear infinite;
}
@keyframes targetOrbit { to { transform: rotate(360deg); } }

/* .hintable is the JS-set truth of "a Hint can still do something here" —
   green (settled) and purple (already read) connections drop out of it, so the
   arming highlight passes them by. */
#board.arming .edge-badge { opacity: 0.35; pointer-events: none; }
#board.arming-edges .edge-badge.hintable {
  opacity: 1;
  pointer-events: auto;
  border-color: var(--slate);
  cursor: pointer;
}
/* The badge isn't round, so it gets marching ants rather than a spin: a dashed
   ring drawn as a gradient border, scrolling around the chip. */
#board.arming-edges .edge-badge.hintable::after {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: 12px;
  padding: 2px;
  background:
    repeating-linear-gradient(90deg, var(--slate) 0 7px, transparent 7px 14px);
  background-size: 14px 100%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  animation: targetAnts 0.7s linear infinite;
}
@keyframes targetAnts { to { background-position: 14px 0; } }

/* And the line itself: the dashes crawl along the connection toward the badge. */
#board.arming-edges .edge-base.hintable {
  stroke: var(--slate);
  stroke-width: 2;
  stroke-dasharray: 0.03 0.02;
  animation: edgeAnts 1.4s linear infinite;
}

@keyframes edgeAnts { to { stroke-dashoffset: -0.05; } }

#board.arming-edges .edge-hit.hintable { cursor: pointer; }

@media (prefers-reduced-motion: reduce) {
  #board.arming .node.clue-target::before,
  #board.arming-edges .edge-badge.hintable::after,
  #board.arming-edges .edge-base.hintable { animation: none; }
}

/* Connections only answer a click when a Hint is armed, when they've already
   been read, or once the game is over — so they only offer a pointer then. */
.edge-hit, .edge-badge { cursor: default; }
.edge-badge.revealed, .edge-hit.revealed,
body.ended .edge-badge, body.ended .edge-hit { cursor: pointer; }

/* ===== submit bar =====
   #print-btn wears .clue-btn, so it inherits the pill shape and type. Only the
   states differ: a full board glows sage instead of arming slate. */
#print-btn:disabled { opacity: 0.45; }
#print-btn.ready {
  /* border-color: var(--sage); */
  /* color: var(--sage-deep); */
  color: black;
  background: white;
  border-color:white;
  /* background: var(--sage-deep); */
  /* box-shadow: 0 0 16px color-mix(in srgb, var(--sage) 34%, transparent); */
}
/* #print-btn.ready:hover { background: color-mix(in srgb, var(--sage) 24%, transparent); } */


/* ===== overlays (help / victory) ===== */
.veil {
  position: fixed;
  inset: 0;
  background: rgba(28, 28, 26, 0.34);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 8vh 20px 20px;
  z-index: 80;
  overflow-y: auto;
}
.sheet {
  background: var(--paper);
  border-radius: 14px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.18);
  max-width: 460px;
  width: 100%;
  padding: 30px 30px 26px;
  text-align: center;
  animation: sheetDrop 0.5s cubic-bezier(0.3, 1.2, 0.4, 1) both;
}
@keyframes sheetDrop {
  from { transform: translateY(-28px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
.sheet h2 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 30px;
  margin-bottom: 4px;
}
.sheet .sheet-sub { font-family: var(--serif); font-style: italic; color: var(--ink-60); margin-bottom: 18px; }
.sheet .rule { border: none; border-top: 1px solid var(--rule); margin: 16px 0; }
.howto-list p b { color: var(--ink); font-weight: 600; }

.legend-box {
  margin-top: 16px;
  padding: 12px;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 8px;
  font-size: 13px;
}
.legend-item { margin-bottom: 4px; color: var(--ink-60); }
.legend-item:last-child { margin-bottom: 0; }
.legend-icon { font-family: var(--sans); font-weight: bold; width: 16px; display: inline-block; text-align: center; }

/* ===== how-to-play: the bite-sized tutorial slider =====
   One rule per screen; each illustration is built from the real board classes
   (.node, .chip, .edge-badge, .clue-btn, .charge-glyph) so it renders exactly
   like the live game and re-themes with the page. */
.tut-sheet { position: relative; padding-top: 26px; }
.tut-x {
  position: absolute;
  top: 12px; right: 14px;
  width: 28px; height: 28px;
  border: none; background: transparent;
  color: var(--ink-60);
  font-size: 16px; line-height: 1;
  border-radius: 50%;
}
.tut-x:hover { color: var(--ink); background: var(--card); }
.tut-kicker {
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-60);
  margin-bottom: 16px;
}

.tut-viewport { overflow: hidden; width: 100%; }
.tut-track {
  display: flex;
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}
.tut-slide { flex: 0 0 100%; min-width: 0; padding: 0 4px; }

/* ---- the persistent example board: one complete mini-board, spotlit per step ----
   Night-sky backdrop like the real board (re-themes light/dark). Nothing inside
   reacts to the pointer — it's a diagram. Per-step state (.glow / .is-filled /
   .is-slot / verdicts / .demo-place / .demo-reveal / bank .used) is toggled from
   setBoardStep(). The spotlight glow is purple, always on, pulsing between a
   half-strength and full-strength halo, and tints its target ~10% purple. */
.tut-board {
  --tb: 58px;               /* node + flyer diameter-ish */
  --placedur: 6s;
  --revealdur: 7s;          /* ~5s of sequence, then a 2s pause before it loops */
  position: relative;
  width: 100%;
  margin-bottom: 16px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background-color: var(--board-bg);
  background-image: var(--board-stars);
  background-size: 250px 250px;
  overflow: hidden;
}
.tut-board * { pointer-events: none; }
.tut-stage { position: relative; height: 172px; }

/* ---- purple spotlight glows (shared, always-on 50%→100% pulse) ---- */
@keyframes tbNodeGlow {
  0%, 100% { box-shadow: 0 0 10px 1px color-mix(in srgb, var(--purple) 50%, transparent); }
  50% { box-shadow: 0 0 24px 6px color-mix(in srgb, var(--purple) 90%, transparent); }
}
@keyframes tbBadgeGlow {
  0%, 100% { box-shadow: 0 0 9px 0 color-mix(in srgb, var(--purple) 50%, transparent); transform: translate(-50%,-50%) scale(1.03); }
  50% { box-shadow: 0 0 18px 3px color-mix(in srgb, var(--purple) 90%, transparent); transform: translate(-50%,-50%) scale(1.07); }
}
@keyframes tbEdgeGlow {
  0%, 100% { stroke-width: 2.4; filter: drop-shadow(0 0 3px var(--purple)); }
  50% { stroke-width: 3.4; filter: drop-shadow(0 0 8px var(--purple)); }
}
@keyframes tbPillGlow {
  0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--purple) 30%, transparent), 0 0 8px color-mix(in srgb, var(--purple) 40%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--purple) 14%, transparent), 0 0 18px color-mix(in srgb, var(--purple) 70%, transparent); }
}

/* connections (SVG) */
.tut-edges { position: absolute; inset: 0; width: 100%; height: 100%; }
.tut-edge {
  stroke: var(--node-border); stroke-width: 1.6; fill: none;
  vector-effect: non-scaling-stroke;
  transition: stroke 0.2s, stroke-width 0.2s;
}
.tut-edge.glow { stroke: var(--purple); animation: tbEdgeGlow 1.5s ease-in-out infinite; }

/* connection badges */
.tut-badge {
  position: absolute;
  transform: translate(-50%, -50%);
  z-index: 3;
  padding: 3px 8px;
  border: 1px solid var(--badge-border);
  border-radius: 8px;
  background: var(--badge-bg);
  color: var(--badge-ink);
  font-size: 9px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  white-space: nowrap;
  transition: box-shadow 0.2s, border-color 0.2s, color 0.2s, background 0.2s;
}
.tut-badge.glow {
  z-index: 4;
  border-color: var(--purple); color: var(--ink);
  background: color-mix(in srgb, var(--purple) 14%, var(--badge-bg));
  animation: tbBadgeGlow 1.5s ease-in-out infinite;
}

/* nodes (planets) */
.tut-node {
  position: absolute;
  transform: translate(-50%, -50%);
  width: var(--tb); aspect-ratio: 1;
  border-radius: 50%;
  border: 1.5px dashed var(--node-border);
  background: var(--node-bg);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 1px; padding: 5px;
  transition: box-shadow 0.25s, border-color 0.25s, background 0.25s, transform 0.25s;
}
.tut-glyph { font-family: 'Planets', var(--serif); font-style: normal; font-size: 28px; line-height: 1; }
.tut-name {
  font-weight: 600; font-size: 11px; line-height: 1.05;
  color: var(--node-name); text-align: center;
  opacity: 0; transition: opacity 0.25s;
}
.tut-lock {
  position: absolute; bottom: -7px; left: 50%;
  transform: translateX(-50%);
  font-size: 10px; line-height: 1;
  opacity: 0; transition: opacity 0.25s;
}

/* named layout (a placed node, or a waiting slot): glyph shrinks to a corner */
.tut-node.is-filled, .tut-node.is-slot {
  border-style: solid;
  border-color: var(--node-fill-border);
  background: var(--node-fill-bg);
  box-shadow: var(--node-fill-shadow);
}
.tut-node.is-filled .tut-glyph, .tut-node.is-slot .tut-glyph {
  position: absolute; top: -6px; right: -6px;
  font-size: 12px; width: 19px; height: 19px;
  display: flex; align-items: center; justify-content: center;
  background: var(--badge-bg);
  border: 1px solid var(--badge-border);
  border-radius: 50%;
}
.tut-node.is-filled .tut-name { opacity: 1; }

/* purple spotlight on a node (+ ~10% purple fill) */
.tut-node.glow {
  border-style: solid; border-color: var(--purple);
  background: color-mix(in srgb, var(--purple) 12%, var(--card));
  animation: tbNodeGlow 1.5s ease-in-out infinite;
}

/* Colours step: verdicts */
.tut-node.v-correct { border-color: var(--sage); background: var(--node-correct-bg); box-shadow: var(--node-correct-shadow); }
.tut-node.v-misplaced { border-color: var(--ochre); background: var(--node-misplaced-bg); box-shadow: 0 0 14px color-mix(in srgb, var(--ochre) 45%, transparent); }
.tut-node.v-decoy { border-color: var(--terra); background: var(--node-wrong-bg); box-shadow: var(--node-wrong-shadow); }
.tut-node.v-correct .tut-lock { opacity: 0.8; }

/* ---- place demo: three name chips fly from the bank onto the three nodes ---- */
.tut-fly {
  position: absolute;
  transform: translate(-50%, -50%);
  translate: 0 0;
  width: var(--tb); aspect-ratio: 1;
  border-radius: 50%;
  border: 1px solid var(--purple);
  background: color-mix(in srgb, var(--purple) 16%, var(--card));
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 4px;
  font-size: 10.5px; font-weight: 600; line-height: 1.05;
  box-shadow: 0 4px 12px rgba(0,0,0,0.35), 0 0 14px color-mix(in srgb, var(--purple) 55%, transparent);
  opacity: 0; z-index: 6;
}
.tut-board.demo-place .fly-a { animation: flyA var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .fly-b { animation: flyB var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .fly-c { animation: flyC var(--placedur) ease-in-out infinite; }
@keyframes flyA { 0% { translate: 6px 66px; opacity: 0; } 3% { opacity: 1; } 18% { translate: 0 0; opacity: 1; } 22% { translate: 0 0; opacity: 0; } 100% { opacity: 0; } }
@keyframes flyB { 0%, 30% { translate: -6px 40px; opacity: 0; } 33% { opacity: 1; } 47% { translate: 0 0; opacity: 1; } 51% { opacity: 0; } 100% { opacity: 0; } }
@keyframes flyC { 0%, 58% { translate: 6px 40px; opacity: 0; } 61% { opacity: 1; } 75% { translate: 0 0; opacity: 1; } 79% { opacity: 0; } 100% { opacity: 0; } }

/* each node's name fades in as its chip lands, holds, then resets for the loop */
.tut-board.demo-place .node-a .tut-name { animation: nameA var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .node-b .tut-name { animation: nameB var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .node-c .tut-name { animation: nameC var(--placedur) ease-in-out infinite; }
@keyframes nameA { 0%, 18% { opacity: 0; } 21%, 96% { opacity: 1; } 100% { opacity: 0; } }
@keyframes nameB { 0%, 47% { opacity: 0; } 50%, 96% { opacity: 1; } 100% { opacity: 0; } }
@keyframes nameC { 0%, 75% { opacity: 0; } 78%, 96% { opacity: 1; } 100% { opacity: 0; } }
/* a small green catch-flash as each name lands */
.tut-board.demo-place .node-a { animation: catchA var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .node-b { animation: catchB var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .node-c { animation: catchC var(--placedur) ease-in-out infinite; }
@keyframes catchA { 0%, 17% { box-shadow: var(--node-fill-shadow); transform: translate(-50%,-50%) scale(1); } 21% { box-shadow: 0 0 18px 3px color-mix(in srgb, var(--sage) 70%, transparent); transform: translate(-50%,-50%) scale(1.09); } 30%, 100% { box-shadow: var(--node-fill-shadow); transform: translate(-50%,-50%) scale(1); } }
@keyframes catchB { 0%, 46% { box-shadow: var(--node-fill-shadow); transform: translate(-50%,-50%) scale(1); } 50% { box-shadow: 0 0 18px 3px color-mix(in srgb, var(--sage) 70%, transparent); transform: translate(-50%,-50%) scale(1.09); } 59%, 100% { box-shadow: var(--node-fill-shadow); transform: translate(-50%,-50%) scale(1); } }
@keyframes catchC { 0%, 74% { box-shadow: var(--node-fill-shadow); transform: translate(-50%,-50%) scale(1); } 78% { box-shadow: 0 0 18px 3px color-mix(in srgb, var(--sage) 70%, transparent); transform: translate(-50%,-50%) scale(1.09); } 87%, 100% { box-shadow: var(--node-fill-shadow); transform: translate(-50%,-50%) scale(1); } }

/* ---- reveal demo (clues step): a choreographed loop —
   Reveal is selected and snaps to a STATIC focus state (no pulsing); then node A
   glows while still empty; then "Apple" is placed — the planet minimises to the
   corner like a filled node, the name fades in, and the bank chip is spent.
   Everything resets and rests (no glow) for ~2s before repeating.
   node A stays a plain empty node; these keyframes drive the whole reveal. */
.tut-board.demo-reveal .pill-reveal { animation: revealBtn var(--revealdur) ease-in-out infinite; }
@keyframes revealBtn {
  /* rest, then a quick press as it's clicked… */
  0%, 6% { border-color: var(--rule); color: var(--ink); background: transparent; box-shadow: none; transform: scale(1); }
  10% { transform: scale(0.93); }
  13% { transform: scale(1); }
  /* …then hold a static focus state (identical stops = no pulsing) */
  14%, 66% { border-color: var(--purple); color: var(--purple); background: color-mix(in srgb, var(--purple) 14%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--purple) 30%, transparent); transform: scale(1); }
  /* reset for the pause */
  72%, 100% { border-color: var(--rule); color: var(--ink); background: transparent; box-shadow: none; transform: scale(1); }
}
/* node A: empty (dashed) → glow while empty → filled + glow → reset */
.tut-board.demo-reveal .node-a { animation: revealNode var(--revealdur) ease-in-out infinite; }
@keyframes revealNode {
  0%, 26% { border-style: dashed; border-color: var(--node-border); background: var(--node-bg); box-shadow: none; }
  32%, 38% { border-style: dashed; border-color: var(--node-active-border); background: var(--node-bg); box-shadow: 0 0 22px 4px color-mix(in srgb, var(--purple) 80%, transparent); }
  44%, 66% { border-style: solid; border-color: var(--purple); background: color-mix(in srgb, var(--purple) 12%, var(--card)); box-shadow: 0 0 24px 6px color-mix(in srgb, var(--purple) 90%, transparent); }
  72%, 100% { border-style: dashed; border-color: var(--node-border); background: var(--node-bg); box-shadow: none; }
}
/* node A's planet: big and centred (board-empty) → minimised to the corner badge
   the moment it's placed, exactly like a filled node in play */
.tut-board.demo-reveal .node-a .tut-glyph {
  position: absolute;
  transform: translate(-50%, -50%);
  box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: 50%;
  animation: revealGlyph var(--revealdur) ease-in-out infinite;
}
@keyframes revealGlyph {
  0%, 38% { top: 50%; left: 50%; font-size: 28px; width: auto; height: auto; background: transparent; border-color: transparent; }
  44%, 66% { top: 3.5px; left: calc(var(--tb) - 15.5px); font-size: 12px; width: 19px; height: 19px; background: var(--badge-bg); border-color: var(--badge-border); }
  72%, 100% { top: 50%; left: 50%; font-size: 28px; width: auto; height: auto; background: transparent; border-color: transparent; }
}
.tut-board.demo-reveal .node-a .tut-name { animation: revealName var(--revealdur) ease-in-out infinite; }
@keyframes revealName { 0%, 40% { opacity: 0; } 46%, 66% { opacity: 1; } 72%, 100% { opacity: 0; } }
/* the placed name is spent from the bank */
.tut-board.demo-reveal .opt-apple { animation: revealUseApple var(--revealdur) ease-in-out infinite; }
@keyframes revealUseApple { 0%, 42% { opacity: 1; } 48%, 66% { opacity: 0.3; } 72%, 100% { opacity: 1; } }

/* ---- bank + controls tray, inside the board box ---- */
.tut-tray {
  border-top: 1px solid var(--rule);
  min-height: 104px;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 10px;
}
.tut-bank { display: flex; flex-wrap: wrap; gap: 9px; justify-content: center; }
/* the name options are circular chips, same shape the live bank uses */
.tut-opt {
  width: 66px; aspect-ratio: 1;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--card);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 4px;
  font-size: 10.5px; font-weight: 600; line-height: 1.05;
  box-shadow: var(--shadow);
  transition: opacity 0.3s;
}
.tut-opt.used { opacity: 0.3; }
/* bank chips spend (dim) in sequence as their name is placed during the demo */
.tut-board.demo-place .opt-apple { animation: useA var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .opt-cherry { animation: useB var(--placedur) ease-in-out infinite; }
.tut-board.demo-place .opt-lemon { animation: useC var(--placedur) ease-in-out infinite; }
@keyframes useA { 0%, 18% { opacity: 1; } 22%, 96% { opacity: 0.3; } 100% { opacity: 1; } }
@keyframes useB { 0%, 47% { opacity: 1; } 51%, 96% { opacity: 0.3; } 100% { opacity: 1; } }
@keyframes useC { 0%, 75% { opacity: 1; } 79%, 96% { opacity: 0.3; } 100% { opacity: 1; } }

.tut-controls { display: flex; align-items: center; justify-content: center; }
.tut-controls.show-submit, .tut-controls.show-clues { margin-top: 10px; }
.tut-controls .pill-submit, .tut-controls .pill-clues { display: none; }
.tut-controls.show-submit .pill-submit { display: inline-flex; }
.tut-controls.show-clues .pill-clues { display: flex; }
.tut-clue-row { gap: 8px; flex-wrap: wrap; justify-content: center; }
.tut-pill { cursor: default; }
.tut-pill.glow {
  border-color: var(--purple);
  background: color-mix(in srgb, var(--purple) 12%, transparent);
  color: var(--purple);
  animation: tbPillGlow 1.5s ease-in-out infinite;
}
/* Submit keeps the game's green "ready" look, with the same pulsing glow */
.tut-pill.pill-submit.glow {
  border-color: var(--sage);
  color: var(--sage-deep);
  background: color-mix(in srgb, var(--sage) 16%, transparent);
  animation: tbSubmitGlow 1.5s ease-in-out infinite;
}
@keyframes tbSubmitGlow {
  0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sage) 36%, transparent), 0 0 10px color-mix(in srgb, var(--sage) 45%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--sage) 16%, transparent), 0 0 22px color-mix(in srgb, var(--sage) 80%, transparent); }
}

.tut-title { font-family: var(--serif); font-weight: 500; font-size: 23px; margin-bottom: 6px; }
.tut-text {
  font-size: 16px; line-height: 1.5;
  color: var(--ink-60);
  max-width: 38ch;
  margin: 0 auto;
  min-height: 4.4em;
}

.tut-dots { display: flex; gap: 7px; justify-content: center; margin-top: 18px; }
.tut-dot {
  width: 7px; height: 7px; padding: 0;
  border: none; border-radius: 50%;
  background: var(--ink-35);
  opacity: 0.55;
  transition: transform 0.15s ease, opacity 0.15s ease, background 0.15s ease;
}
.tut-dot.active { opacity: 1; background: var(--slate); transform: scale(1.3); }

.tut-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  margin-top: 20px;
}
.tut-nav .btn { min-width: 92px; margin: 0; }
.tut-back:disabled { opacity: 0.3; cursor: default; }
.tut-count { font-size: 12px; color: var(--ink-60); letter-spacing: 0.04em; }

@media (prefers-reduced-motion: reduce) {
  .tut-track { transition: none; }
  .tut-node, .tut-edge, .tut-badge, .tut-pill, .tut-fly, .tut-opt,
  .tut-board.demo-place .tut-name, .tut-board.demo-place .tut-node,
  .tut-board.demo-reveal .pill-reveal, .tut-board.demo-reveal .node-a,
  .tut-board.demo-reveal .node-a .tut-glyph,
  .tut-board.demo-reveal .node-a .tut-name { animation: none !important; }
  .tut-fly { display: none; }                          /* no fly-in */
  .tut-board.demo-place .tut-name { opacity: 1; }      /* show placed names */
  .tut-board.demo-place .opt-apple,
  .tut-board.demo-place .opt-cherry,
  .tut-board.demo-place .opt-lemon { opacity: 0.3; }   /* show them spent */
  /* clues step: show the end state statically — Reveal focused, Apple placed */
  .tut-board.demo-reveal .pill-reveal {
    border-color: var(--purple); color: var(--purple);
    background: color-mix(in srgb, var(--purple) 14%, transparent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--purple) 30%, transparent);
  }
  .tut-board.demo-reveal .node-a {
    border-style: solid; border-color: var(--purple);
    background: color-mix(in srgb, var(--purple) 12%, var(--card));
  }
  .tut-board.demo-reveal .node-a .tut-glyph {
    position: absolute; top: 3.5px; left: calc(var(--tb) - 15.5px);
    transform: translate(-50%, -50%);
    font-size: 12px; width: 19px; height: 19px;
    background: var(--badge-bg); border: 1px solid var(--badge-border); border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
  }
  .tut-board.demo-reveal .node-a .tut-name { opacity: 1; }
  .tut-board.demo-reveal .opt-apple { opacity: 0.3; }
}

/* Settings Modal */
.settings-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 16px 0;
}
.setting-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 8px;
}
.setting-text {
  display: flex;
  flex-direction: column;
}
.setting-text b { font-weight: 600; color: var(--ink); }
.setting-text span { font-size: 16px; color: var(--ink-60); }
.setting-select {
  padding: 6px 10px;
  border-radius: 6px;
  border: 1px solid var(--ink-35);
  font-family: var(--sans);
  font-size: 16px;
  background: var(--paper);
  color: var(--ink);
}

/* Clue picker: charge state as 16px dots (green = available, grey = used) */
.clue-charge-dots { display: flex; justify-content: center; gap: 8px; margin: 16px 0 8px; }
.clue-dot { width: 16px; height: 16px; border-radius: 50%; flex: 0 0 auto; }
.clue-dot.avail { background: var(--sage); box-shadow: 0 0 6px color-mix(in srgb, var(--sage) 45%, transparent); }
.clue-dot.used { background: var(--ink-35); }
.clue-remaining { font-size: 16px; color: var(--ink-60); margin-bottom: 4px; }

/* Clue picker: the three clues as full-width choice cards */
.clue-options { display: flex; flex-direction: column; gap: 10px; margin: 18px 0; text-align: left; }
.clue-option {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 14px 16px;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 10px;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.12s ease;
}
.clue-option:hover:not(:disabled) {
  border-color: var(--slate);
  background: color-mix(in srgb, var(--slate) 6%, var(--card));
  transform: translateY(-1px);
}
.clue-option:disabled { opacity: 0.4; cursor: default; }
.clue-opt-ico { flex: 0 0 auto; width: 24px; text-align: center; font-size: 20px; line-height: 1; }
.clue-opt-text { display: flex; flex-direction: column; gap: 2px; }
.clue-opt-text b { font-weight: 600; color: var(--ink); }
.clue-opt-text span { font-size: 16px; color: var(--ink-60); line-height: 1.4; }

/* Clue picker: the decoy confirmation, shown in place of the clue list */
.clue-confirm-q { font-family: var(--serif); font-size: 20px; margin-top: 18px; }
.clue-confirm-note { font-size: 16px; color: var(--ink-60); margin-top: 6px; }

.actions { display: flex; flex-direction: column; gap: 10px; margin-top: 24px; }
.howto-list { text-align: left; font-size: 14px; display: flex; flex-direction: column; gap: 12px; }
.howto-list b { font-weight: 600; }
.howto-list .n {
  font-family: var(--serif); font-style: italic;
  color: var(--slate); margin-right: 8px;
}
.score-big {
  font-family: var(--serif);
  font-size: 54px;
  font-weight: 500;
  line-height: 1;
}
.score-tier { font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--sage-deep); font-weight: 600; margin-top: 6px; }
.score-tier.tier-bad { color: var(--terra); }
.result-stats { font-size: 16px; color: var(--ink-60); margin-top: 10px; }
.win-ribbon {
  display: inline-block;
  font-size: 11px; font-weight: 600; letter-spacing: 0.22em;
  color: #fff; background: var(--ink);
  border-radius: 999px; padding: 5px 14px;
  margin-bottom: 12px;
}
.win-story {
  font-family: var(--serif);
  font-size: 16px; line-height: 1.55;
  color: var(--ink-60);
  text-align: left;
  border-left: 2px solid var(--sage);
  padding-left: 14px;
  margin: 16px 2px 2px;
}
/* the shareable constellation — pure emoji grid, monospace so it
   previews here the way it renders in a feed */
.constellation {
  font-family: "SF Mono", "Menlo", "Consolas", "Noto Emoji", monospace;
  font-size: 15px;
  line-height: 1.05;
  letter-spacing: 0;
  margin: 14px auto 4px;
  text-align: center;
  white-space: pre;
  overflow-x: auto;
  font-variant-emoji: emoji;
}
/* ===== connection zoom (a revealed edge, blown up legible) ===== */
.veil { align-items: center; }
.zoom-sheet {
  max-width: 520px;
  padding: 40px 34px 30px;
}
.zoom-planets {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-bottom: 6px;
}
.zoom-planet {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  flex: 0 1 auto;
  min-width: 0;
}
.zoom-glyph { font-size: 62px; line-height: 1; }
.zoom-name {
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  text-align: center;
  line-height: 1.2;
}
.zoom-link {
  flex: 0 0 auto;
  width: 54px;
  height: 0;
  align-self: center;
  margin-bottom: 26px; /* ride up level with the glyphs, above the names */
  border-top: 2px dashed var(--ink-35);
}
.zoom-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: 18px;
}
.zoom-text {
  font-family: var(--serif);
  font-size: clamp(22px, 5.5vw, 30px);
  line-height: 1.35;
  color: var(--ink);
  margin-bottom: 26px;
}

.sheet .actions { display: flex; flex-direction: column; gap: 10px; align-items: center; margin-top: 16px; }
.toast { font-size: 16px; color: var(--sage-deep); font-weight: 600; margin-top: 8px; }
.countdown-line { font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-60); margin-top: 16px; }

/* ===== wide screens: dock the Clue Desk as a right-side rail ===== */
@media (min-width: 1040px) {
  /* Wider game on desktop, laid out as two columns — the board (75%) and the
     candidates column (25%) — with the clue/submit bars spanning the full width
     beneath. The masthead stays centred across the top at the same width. */
  body.playing main, body.playing #masthead { max-width: 1000px; }
  body.playing #play {
    display: grid;
    grid-template-columns: 3fr 1fr;
    grid-template-areas:
      "board candidates"
      "bars  bars";
    column-gap: 22px;
    row-gap: 14px;
    align-items: stretch;
  }
  /* Pin the board height on desktop so the whole layout (masthead + the two
     columns + the bars) fits the viewport: fill what's left after ~300px of
     masthead/bars/padding, floored and capped so it never gets cramped or huge.
     The candidates column stretches to match via the grid's align-items. */
  body.playing #board {
    grid-area: board;
    margin: 0;
    height: clamp(440px, calc(100vh - 300px), 760px);
  }
  body.playing #top-bars { grid-area: bars; }

  /* the candidates drawer is now an in-flow column, not a fixed rail */
  body.playing #drawer,
  body.playing #drawer.open {
    grid-area: candidates;
    position: static;
    inset: auto;
    transform: none;
    width: auto; max-width: none;
    z-index: auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
    border: 1px solid var(--rule);
    /* border-radius: 12px; */
    box-shadow: none;
  }
  body.playing #drawer-handle { cursor: default; padding: 14px 16px; }
  body.playing #drawer-handle::before { display: none; } /* no drag pill in column mode */
  body.playing #drawer-scroll {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow-y: auto;
  }

  /* endgame: the board reclaims the full width and the candidates fold back to a
     collapsible bottom-sheet tab, so the finished constellation gets the stage */
  body.ended #play { display: block; }
  body.ended #drawer,
  body.ended #drawer.open {
    position: fixed;
    grid-area: auto;
    left: 50%; right: auto; top: auto; bottom: 0;
    width: 100%; max-width: 560px;
    z-index: 40;
    border: 1px solid var(--rule); border-bottom: none;
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.07);
    transform: translateX(-50%) translateY(calc(100% - 46px));
  }
  body.ended #drawer.open { transform: translateX(-50%) translateY(0); }
  body.ended #drawer-handle { cursor: pointer; padding: 13px 18px; }
  body.ended #drawer-handle::before { display: block; }
  body.ended #drawer-scroll { max-height: min(46vh, 340px); flex: none; }

  /* --- desktop budget bars: tighten the CLUES and SUBMIT halves ---
     These overrides live inside the wide breakpoint only; the phone shell
     (≤1039px) keeps its stacked eyebrow-title layout untouched. */

  /* CLUES: drop the "Clues Left" caption and float the charge planets up
     beside the "Clues" header, laid out in a horizontal row. The clue
     buttons wrap to their own line beneath (flex-basis:100%). */
  body.playing #clue-bar { justify-content: flex-start; }
  body.playing #clue-bar .bar-title { flex: 0 0 auto; order: 0; }
  body.playing #clue-bar .charge-group {
    order: 1;
    flex-direction: row;
    align-items: center;
    margin-left: 10px;
  }
  body.playing #clue-bar .charge-label { display: none; }
  body.playing #clue-bar .charge-col { flex-direction: row; }
  body.playing #clue-bar #clue-actions { order: 2; flex: 1 1 100%; }

  /* SUBMIT: no eyebrow title at all; charge planets lie in a horizontal row. */
  body.playing #print-bar .bar-title { display: none; }
  body.playing #print-bar .charge-col { flex-direction: row; }

  #top-bars {
    justify-content: center;
    gap: 1.5rem;
    margin-top: 3rem;
  }
  body.playing #clue-bar,
  body.playing #print-bar {
    /* width: 30%; */
  }
  .clue-btn, .print-btn {
    padding: 1.2rem 2.4rem;
    border: 1px solid #fff;
  }
}

/* Elements that only appear in the phone app-shell header */
.mob-only { display: none; }


/* ============================================================
   PHONE APP-SHELL (≤1039px): the play view becomes a fixed-height
   column, top to bottom — an icon-only header, a board that flex-fills
   the middle with auto-shrinking planets (JS-set --node-size), the
   candidates tray, and the Clues/Submit pair as the bottom action row.
   Everything is on screen at once; the page never scrolls.

   The clue flow is the wide layout's, thumb-sized: tap Clues, pick a
   type in the sheet, then tap its target on the board. The picker rises
   from the bottom edge instead of dropping in from the top.
   ============================================================ */
@media (max-width: 1039px) {
  /* Connection labels shrink with the board. The cap is the real constraint:
     a chip wider than ~a third of a phone board leaves the relaxation in
     game.js no room to separate the planets it sits between. */
  .edge-badge {
    font-size: 9px;
    padding: 3px 7px;
    max-width: min(106px, 30vw);
    line-height: 1.25;
  }
  .badge-reveal { font-size: 10px; max-width: 92px; }

  /* --- shell frame: lock the play screen to the viewport --- */
  body.playing {
    height: 100vh; /* fallback for browsers without dvh */
    height: 100dvh;
    overflow: hidden;
  }
  body.playing #masthead {
    padding: max(6px, env(safe-area-inset-top)) 12px 4px;
    flex: none;
  }
  /* collapse the tall newspaper masthead down to just its tool row */
  body.playing .mast-title,
  body.playing .mast-rule,
  body.playing .mast-dateline { display: none; }
  /* One row, always — a wrapped header steals a whole band of board and the
     planets end up tucked behind it. The tools go icon-only and the back link
     becomes a bare chevron, which is what a phone header wants anyway. */
  body.playing .mast-top { gap: 8px; }
  body.playing .mast-tools { flex-wrap: nowrap; gap: 6px; }
  body.playing #archive-btn,
  body.playing #help-btn,
  body.playing #settings-btn {
    width: 36px; height: 36px;
    padding: 0;
    gap: 0;
    justify-content: center;
    flex: 0 0 auto;
  }
  body.playing .nav-label { display: none; }
  body.playing .mast-tools .nav-ico { font-size: 17px; }
  body.playing .mast-back { font-size: 22px; line-height: 1; padding: 0 8px 0 0; }
  body.playing .mast-back-label { display: none; }

  body.playing main {
    padding: 0 12px;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
  body.playing #play {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1;
    padding-top: 2px;
  }

  /* --- board flex-fills the space between header and tray --- */
  body.playing #board {
    height: auto;
    flex: 1 1 auto;
    min-height: 0;
    margin: 2px 0;
  }
  /* planets: JS sets --node-size; scale the inner glyph/text to match */
  body.playing .node {
    padding: 5px 6px;
    gap: 0;
  }
  body.playing .node .node-letter.glyph {
    font-size: calc(var(--node-size, 84px) * 0.5);
  }
  body.playing .node .node-name { font-size: 11.5px; }
  body.playing .node .node-letter.glyph.small-glyph {
    width: 18px; height: 18px;
    font-size: 11px;
    top: -5px; right: -5px;
  }
  body.playing .node.compact { padding: 3px 4px; }
  body.playing .node.compact .node-name { font-size: 10px; line-height: 1.15; }
  body.playing .node.compact .node-tag { display: none; }
  body.playing .node .node-scout-tag { font-size: 8px; padding: 1px 5px; bottom: -6px; }

  /* --- players tray: wrapped, shrink-to-fit, all chips visible ---
     game.js re-homes #bank-panel here (below the board) on the phone shell, so
     strip its rail chrome and the "Candidates" heading — the tray is just chips.
     Chips stay small enough to fit five across, so a nine-name pool costs the
     board two rows of height instead of three. */
  body.playing #bank-label { display: none; }
  body.playing #bank-panel {
    flex: none;
    border: none;
    margin: 0;
    padding: 0;
  }
  body.playing #bank-heading { display: none; }
  body.playing #bank {
    flex: none;
    gap: 6px;
    padding: 8px 0 4px;
    max-height: 30dvh;
    overflow-y: auto;
    border-top: 1px solid var(--rule);
  }
  body.playing #bank .chip { width: clamp(52px, 16.5vw, 68px); padding: 3px 2px; }
  body.playing #bank .chip .chip-name { font-size: 10px; line-height: 1.15; }
  body.playing #bank .chip .chip-tag { font-size: 8.5px; }
  /* A circle this small can't hold "Antetokounmpo" on one line — let a word
     that genuinely doesn't fit break rather than spill past the rim.
     break-word, not anywhere: `anywhere` also shrinks the min-content width,
     which splits names like "Blackhawks" that had room all along. The explicit
     width is what makes it bite — centred flex children shrink-wrap their text,
     so without it the line box is as wide as the word and nothing ever
     "overflows" to break. */
  body.playing #bank .chip .chip-name,
  body.playing .node .node-name {
    width: 100%;
    text-align: center;
    overflow-wrap: break-word;
  }

  /* --- the action row: the desktop's two pills, sized for a thumb ---
     Same pair, same order, same meaning as the wide layout — Clues opens the
     picker, Submit judges the board. They just go full-width and lose the
     "Submit" eyebrow, which on a phone only collided with the button under it. */
  body.playing #top-bars {
    flex: none;
    gap: 10px;
    align-items: stretch;
    margin: 0;
    padding: 8px 0 max(8px, env(safe-area-inset-bottom));
    border-top: 1px solid var(--rule);
  }
  body.playing #clue-bar,
  body.playing #print-bar {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
  }
  body.playing #print-bar .bar-title { display: none; }
  body.playing #clue-actions { flex: 1 1 auto; display: flex; }
  body.playing #top-bars .clue-btn {
    flex: 1 1 auto;
    justify-content: center;
    min-height: 48px;
    padding: 12px 14px;
    font-size: 15px;
    letter-spacing: 0.06em;
  }
  body.playing #top-bars .clue-btn .clue-name { display: inline; }
  body.playing #top-bars .clue-btn .clue-ico { font-size: 16px; }
  /* Once the game is over the submit half is gone. Centre what's left rather
     than stretching it — the same single pill the wide layout ends on, not a
     full-width bar with nothing to do. */
  body.ended #top-bars { justify-content: center; }
  body.ended #clue-bar { flex: 0 1 auto; }

  /* --- the clue picker: a bottom sheet, not a floating card ---
     Same three-step flow as the desktop (open Clues → pick a type → tap its
     target on the board); it just rises from the thumb instead of dropping
     from the top, and the board stays visible above it. */
  #clue-veil { align-items: flex-end; padding: 0; overflow: hidden; }
  #clue-veil .clue-sheet {
    max-width: none;
    width: 100%;
    max-height: 86dvh;
    overflow-y: auto;
    border-radius: 20px 20px 0 0;
    padding: 22px 18px max(18px, env(safe-area-inset-bottom));
    animation: sheetRise 0.32s cubic-bezier(0.25, 1, 0.4, 1) both;
  }
  #clue-veil .clue-sheet h2 { font-size: 24px; }
  .clue-charge-dots { margin: 12px 0 6px; }
  .clue-options { gap: 8px; margin: 14px 0; }
  /* Align the glyph with the clue's name, not the middle of a wrapped
     paragraph — on a phone these cards run two and three lines deep. */
  .clue-option { align-items: flex-start; padding: 13px 14px; }
  .clue-opt-ico { margin-top: 1px; }
  .clue-opt-text b { font-size: 16px; }
  .clue-opt-text span { font-size: 14px; }
  @keyframes sheetRise {
    from { transform: translateY(18px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
  }

  /* The candidates rail has no job on the phone shell: game.js placeBank()
     re-homes #bank-panel out of it and into the tray under the board, leaving
     an empty panel parked off the bottom edge whose only opener (its own
     handle) rides off-screen with it. Take it out of the layout entirely so it
     can't collect a tab stop the player can never see. (Its close button wears
     .mob-only, which therefore has nothing left to reveal at this width.) */
  #drawer { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

