:root {
  --directional-text: 0.9rem;
  --answer-icon-size: 1.125rem;
  --icon-button-padding: 7px;
  /* An icon button at rest — and its icon, wherever it's pictured. */
  --icon-button-opacity: 0.55;
  --bg: oklch(.918 .035 62);
  --card: oklch(.965 .018 64);
  --tile: oklch(.992 .006 75);
  --ink: oklch(.31 .026 50);
  --ink-strong: oklch(.15 .018 50);
  /* Keyboard focus on a control that sits on a light surface. */
  --focus-ring-ink-width: 2px;
  --focus-ring-ink: var(--focus-ring-ink-width) solid var(--ink);
  --focus-ring-ink-offset: 3px;
  /* How far the ring paints outside its element. */
  --focus-ring-ink-reach: calc(var(--focus-ring-ink-width) + var(--focus-ring-ink-offset));
  --muted: oklch(.745 .022 55);
  --accent: oklch(.575 .135 44);
  --accent-ink: oklch(.992 .012 75);
  --line: oklch(.85 .022 60);
  --label: oklch(.47 .028 50);
  --hairline: var(--line);
  /* The back-to-portal bar's warm near-white. */
  --portal-bar-bg: #f9f7f2;
  --bg-dark: #ab5945;
  /* Colour and font are the only app-specific parts of the toast's styling;
     keeping them in tokens leaves the rule itself free of local names. */
  --toast-bg: var(--bg-dark);
  --toast-fg: var(--tile);
  --toast-font: var(--font-sans);
  /* Shared font convention: serif for game content, sans for anything
     directional (instructions, controls, etc). */
  --font-serif: "Roboto Serif", "Iowan Old Style", "Palatino Linotype", Palatino, Cambria, Georgia, serif;
  --font-sans: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* The shared page column (shared-ui-patterns.md, "Page column"), and the
     left margin that centres it on the whole window: 100vw counts a desktop
     scroll bar, so the column stays put when the page starts or stops
     scrolling (seen in Forage when the portal's notice was dismissed), rather
     than jumping by half the bar's width. Capped at the space actually there,
     so a narrow window never pushes it under the scroll bar. Resolved where
     it's used, so 100% is that element's containing block. */
  --page-column: 24rem;
  --page-column-left: clamp(0px, calc((100vw - var(--page-column)) / 2), calc(100% - var(--page-column)));
}

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

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

html, body { overscroll-behavior-y: none; }

body {
  font-family: var(--font-sans);
  background: var(--bg);
}

/* Mirrors the prototype's outer wrapper: a min-height (not a forced height)
   so a short puzzle's background fills the screen without ever creating
   scrollable overflow — only a puzzle whose content is genuinely taller than
   the viewport scrolls, which is expected, not a bug.
   100vh resolves against the browser's LARGEST possible viewport (toolbar
   collapsed), not the shorter one actually visible while it's showing — that
   gap is exactly enough extra height to scroll the toolbar away even when the
   content itself fits. dvh tracks the real visible viewport; 100vh above is
   just the fallback for browsers that don't support dvh.
   flex-direction: column (not row) so BackToPortalBar can stack above
   .threads-board/.app--message as a sibling. Those two centre themselves on
   the whole window (--page-column-left), so they line up at the start. */
#root {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* Thin back-link to the portal landing page. See
   back-link-to-portal-design-spec.md for the full rationale. Rendered as a
   sibling of .threads-board/.app--message, above it, so it spans the full
   viewport width unconstrained by their own max-width/padding. */
.back-to-portal-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  width: 100%;
  /* 5px on top, not 7px: the line above takes those 2px, so the bar is no
     taller for it. */
  padding: 5px 0 7px;
  /* The bar spans only the space beside a desktop scroll bar, so centring its
     content in the bar would move it when the page starts or stops scrolling.
     Padding the left by the scroll bar's width (100vw counts it; 100% doesn't)
     centres the icon and caption on the whole window, like the page column. */
  padding-left: calc(100vw - 100%);
  background: var(--portal-bar-bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 0.95rem;
  text-decoration: none;
  /* The line above sets the near-white bar off from a phone browser's own
     controls. 2px, since 1px was too thin to see. */
  border-top: 2px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}

.back-to-portal-bar:focus { outline: none; }
.back-to-portal-bar:focus-visible {
  /* Unseen beside the box-shadow, but drawn where a contrast theme (forced
     colours) drops box-shadows. */
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px var(--portal-bar-bg), 0 0 0 4px var(--ink);
}

/* Full column width (not shrunk to the text), so --page-column-left centres
   it; the text is centred inside. */
.app--message {
  width: 100%;
  max-width: var(--page-column);
  margin-left: var(--page-column-left);
  padding: 1.5rem 1rem 3rem;
  font-family: var(--font-sans);
  color: var(--label);
  text-align: center;
}

.threads-board {
  width: 100%;
  max-width: var(--page-column);
  margin-left: var(--page-column-left);
  /* Bottom padding trimmed from the design's 3rem — that much was leaving
     puzzles with a long word (which need the shrink lever) taller than the
     visible viewport on phones whose browser chrome is showing. */
  padding: 12px 1rem 1.5rem;
}

.threads-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.wordmark {
  font-family: var(--font-serif);
  font-size: 1.1rem;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.wordmark-btn {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  cursor: default;
}
.wordmark-btn:not(:disabled) {
  cursor: pointer;
}
.wordmark-btn:focus { outline: none; }
.wordmark-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink);
  border-radius: 2px;
}

.wordmark-tm {
  font-size: 0.45em;
  vertical-align: super;
  margin-left: 0.05em;
  font-weight: 400;
  letter-spacing: 0;
}

/* Instructions trigger — circled bold "i" */
.instructions-trigger {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.instructions-trigger:hover {
  border-color: var(--ink-strong);
  color: var(--ink-strong);
}
.instructions-trigger:focus { outline: none; }
.instructions-trigger:focus-visible {
  /* Unseen beside the box-shadow, but drawn where a contrast theme (forced
     colours) drops box-shadows. */
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink);
}

.eyebrow {
  font-family: var(--font-sans);
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--accent);
  text-align: center;
  margin-bottom: .25rem;
}

.step1__instruction {
  font-family: var(--font-sans);
  font-size: var(--directional-text);
  color: var(--label);
  text-align: center;
  margin-bottom: 1.125rem;
}

/* Word card */
.word-card {
  background: var(--card);
  border-radius: 1rem;
  padding: 9.4px .875rem 11px;
  margin-bottom: .5rem;
  transition: background 0.35s ease;
}

.word-card--solved { background: oklch(.940 .020 62); }
.word-card--shaking { animation: shake 0.4s ease; }
.word-card--solved .word-card__source { opacity: 0.52; }

/* Step 1 chrome fade — when .step1--leaving, everything disappears except the
   solution tiles so they can linger before the flying-overlay takes over. */
.step1--leaving .word-card { background: transparent; }

.word-card__source { transition: opacity 0.3s ease; }
.step1--leaving .word-card__source { opacity: 0; }

.word-card__answer-label { transition: opacity 0.2s ease; }
.step1--leaving .word-card__answer-label { opacity: 0; }

.step1__instruction { transition: opacity 0.25s ease; }
.step1--leaving .step1__instruction { opacity: 0; }

.word-card__source {
  position: relative;
  display: flex;
  gap: .25rem;
  justify-content: center;
  margin: 0 auto;
  /* Allow vertical page scroll to still start on this row; horizontal
     movement is claimed by the drag-select gesture instead of native panning. */
  touch-action: pan-y;
}

/* Grid (not flex) so the solution tiles center on the card regardless of the
   label's width — the label and the trailing spacer form two equal columns
   that balance around the centered middle column. */
.word-card__answer {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: .625rem;
  margin-top: .5rem;
}

.word-card__answer-label {
  font-family: var(--font-sans);
  font-size: .875rem;
  color: var(--label);
  white-space: nowrap;
  justify-self: end;
}

.word-card__solution {
  display: inline-flex;
  gap: .3125rem;
  justify-self: center;
}

/* Letter tiles — source (clickable) */
.source-tile {
  width: 1.95rem;
  height: 2.4rem;
  border-radius: 0;
  font-family: var(--font-serif);
  font-size: 1.35rem;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 1;
  border: 0;
  transition: all .14s ease;
  -webkit-user-select: none;
  user-select: none;
  /* A tap turns the tile to the accent as the finger lifts; the phone's own
     highlight (blue on Android) would only flash a colour from outside the
     palette first. */
  -webkit-tap-highlight-color: transparent;
}
/* Invisible hit-slop: extends the tappable area into half the gap on each
   side, without changing the painted tile size — fixes "fat finger"
   mis-taps between adjacent letters on touch devices. */
.source-tile::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(-1 * var(--hit-slop, 0px));
  right: calc(-1 * var(--hit-slop, 0px));
}
.source-tile[data-state="available"] {
  background: var(--tile);
  color: var(--ink);
  cursor: pointer;
}
.source-tile[data-state="selected"] {
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
}
.source-tile:disabled {
  cursor: default;
}
.source-tile:focus { outline: none; }
.source-tile:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink);
  border-radius: 2px;
}
/* Selected tiles sit on --accent, where the default --ink ring would have
   poor contrast — same fix as every other selected/dark-fill state in this
   app's sibling games: key the ring to the tile's own ink instead. */
.source-tile[data-state="selected"]:focus-visible {
  box-shadow: 0 0 0 2px var(--accent), 0 0 0 4px var(--accent-ink);
}
.source-tile[data-state="skipped"] {
  background: transparent;
  color: var(--muted);
  cursor: default;
}

/* Solution tiles — read-only, smaller than source tiles */
.solution-tile {
  width: 1.375rem;
  height: 1.6875rem;
  border-radius: 0;
  font-family: var(--font-serif);
  font-size: .9375rem;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .3s ease, color .3s ease;
}
.solution-tile[data-state="empty"] {
  border: 1.5px solid var(--line);
}
.solution-tile[data-state="filled"] {
  background: var(--tile);
  color: var(--ink);
}
.solution-tile[data-state="success"] {
  background: var(--accent);
  color: var(--accent-ink);
}
.solution-tile[data-state="solved"] {
  background: var(--tile);
  color: var(--ink);
}

/* Weave SVG */
.weave {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  width: 100%;
  height: 2rem;
  overflow: visible;
}
.weave--down {
  height: 100%;
}
.weave__line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .85;
}
.weave--down .weave__line {
  opacity: .7;
}
.weave__dot {
  fill: var(--accent);
}

/* Step 2 */
.step2__instruction {
  font-family: var(--font-sans);
  font-size: var(--directional-text);
  line-height: 1.4;
  color: var(--label);
  text-align: center;
  margin-bottom: 1.5625rem;
}
.step2__clue-text,
.step2__clue {
  font-family: var(--font-serif);
  font-size: 1.25rem;
}
.step2__clue-text {
  color: var(--ink);
}
.step2__clue {
  color: var(--accent);
  font-weight: 600;
}

.step2__answer-area {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: .625rem;
  margin-bottom: 1.5rem;
}

.step2__answer {
  display: flex;
  gap: .4375rem;
}

.answer-tile {
  width: 1.875rem;
  height: 2.25rem;
  border-radius: 0;
  font-family: var(--font-serif);
  font-size: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.answer-tile[data-state="empty"] {
  border: 1.5px solid var(--line);
}
.answer-tile[data-state="filled"] {
  background: var(--accent);
  color: var(--accent-ink);
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  18%       { transform: translateX(-7px); }
  36%       { transform: translateX(7px); }
  54%       { transform: translateX(-4px); }
  72%       { transform: translateX(4px); }
}

.step2__threads {
  margin: 0 auto;
  /* The swipe-to-select gesture is itself vertical (row to row), unlike Step
     1's horizontal drag, so it can't coexist with native vertical scroll the
     way pan-y does there — claim the gesture outright. */
  touch-action: none;
}

.step2__threads--shaking {
  animation: shake 0.4s ease;
}

.step2__threads--fading {
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}

@keyframes answer-celebrate {
  0%   { transform: scale(1);   animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }
  50%  { transform: scale(1.6); animation-timing-function: ease-out; }
  100% { transform: scale(1); }
}

.step2__answer--celebrating {
  animation: answer-celebrate 0.8s;
}

@keyframes step2-actions-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.step2__solved-area {
  margin-top: 1.875rem;
  margin-bottom: 2.5rem;
  text-align: center;
}

.step2__solved {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 500;
  color: var(--ink);
}

.step2__hint-tally {
  font-family: var(--font-sans);
  font-size: .875rem;
  color: var(--label);
  margin-top: .375rem;
}

.step2__share {
  display: flex;
  justify-content: center;
  margin-top: 2rem;
  animation: step2-actions-in 0.2s ease-out;
}

.share-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 18px;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 1.5px solid var(--accent);
  border-radius: 8px;
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 0.15s;
}
@media (hover: hover) {
  .share-button:hover { opacity: 1; }
}
.share-button:focus { outline: none; }
.share-button:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--focus-ring-ink-offset);
}

.share-modal-text {
  display: block;
  width: 240px;
  max-width: 100%;
  padding: 8px 10px;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
}

.step2__cross-promo {
  display: flex;
  flex-direction: column;
  align-items: center;
  animation: step2-actions-in 0.2s ease-out;
}

/* A notice from the portal (design-for-notices.md, Part 3, Look), at the top
   of the cross-promo block, so it arrives with that block's entrance. A card
   in the tiles' near-white on the page's peach, so it stands out from the
   page and doesn't read as a third game logo. */
.notice {
  position: relative;
  width: 100%;
  margin-top: 36px;
  background: var(--tile);
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}

/* Centred like the rest of the screen, with equal margins: the ✕ sits in the
   corner, over the padding, not in a column that would push the text over.
   A fixed line height, so a line holding the ↗ (drawn from a fallback font
   with taller metrics) isn't spaced further from its neighbour. */
.notice-text {
  padding: 12px 32px;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--ink);
  text-align: center;
}

/* A small caps line above the text, in the link's colour: a label for whoever
   is previewing, not part of the message. */
.notice-preview {
  padding: 10px 32px 0;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  text-align: center;
}

.notice-preview + .notice-text {
  padding-top: 2px;
}

/* Never breaks inside: the line may break only in the space before it. */
.notice-link {
  font-weight: 500;
  color: var(--accent);
  text-decoration: underline;
  white-space: nowrap;
}

/* Still a 44px target, since it sits near the link, where a slip gives the
   wrong answer; the glyph is drawn in the target's corner so it reads as the
   card's own close control. */
.notice-dismiss {
  position: absolute;
  top: 0;
  right: 0;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding: 8px 10px 0 0;
  font-size: 0.875rem;
  color: var(--label);
  background: none;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}
.notice-dismiss:hover { color: var(--ink); }
.notice-dismiss:focus { outline: none; }
.notice-dismiss:focus-visible {
  box-shadow: 0 0 0 2px var(--tile), 0 0 0 4px var(--ink);
}

.step2__cross-promo-caption {
  width: 100%;
  margin-top: 36px;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--label);
  text-align: center;
}

/* Each link is only as wide as it needs to be, not the row's full width — so
   a short tagline wraps within that narrower box and stays centered under
   its icon. One link's width matches its icon's width exactly, so it sits
   flush with the row's outer edge; the other link is wider than its icon
   (see its own comment below), so it sits a bit inset from the edge.
   `justify-content: space-between` anchors both to the row's outer edges
   rather than centering them within the full row width. */
.step2__cross-promo-row {
  width: 100%;
  display: flex;
  justify-content: space-between;
  margin-top: 12px;
}

.step2__cross-promo-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  text-decoration: none;
}
.step2__cross-promo-link:focus { outline: none; }
.step2__cross-promo-link:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--focus-ring-ink-offset);
  border-radius: 2px;
}

/* One wordmark is a single row of tiles; the other is a staircase of the
   same tile unit across three rows. Sizing both icons to the same height
   would make the single-row icon's tiles dwarf the staircase icon's, so
   instead each is sized so the staircase icon's middle "U N G" row of tiles
   (278/908 of its image height) renders at the same height as the
   single-row icon's tile row (277/317 of its image height) — chosen at a
   32px height for the single-row icon, which is why the staircase icon ends
   up taller overall (91px) despite matching tile size.
   The single-row icon has no such middle row — its one row of tiles sits
   near the very top of its image (starting ~2px in) — so top-aligning both
   links (the row's default behavior) leaves the single-row icon's tiles
   level with the staircase icon's *top* tile, not its "U N G" row. Nudging
   the single-row link down by the "U N G" row's own offset (~31px, minus
   the single-row icon's ~2px of built-in top padding) brings the two tile
   rows level. */
.step2__cross-promo-link--forage { width: 108px; margin-top: 29px; }
/* 140px (not the 71px icon width) so "Find the 5 words that form a ladder"
   wraps across ~2 lines instead of 4 — the icon, narrower than this box,
   centers within it via .step2__cross-promo-link's align-items: center, same
   as the tagline below it. The pair sits a bit inset from the row's right
   edge as a result, but reads as one centered unit instead of the icon
   hugging the edge while the wider tagline sits off-center under it. */
.step2__cross-promo-link--rungs { width: 140px; }

.step2__cross-promo-icon {
  display: block;
}
.step2__cross-promo-icon--forage { width: 108px; height: 32px; }
.step2__cross-promo-icon--rungs { width: 71px; height: 91px; }

.step2__cross-promo-tagline {
  width: 100%;
  margin: 0;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  color: var(--label);
}

@keyframes toast-fadein {
  from { opacity: 0; transform: translate(-50%, calc(-50% - 10px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}

.toast {
  position: fixed;
  top: var(--toast-top, 1.25rem);
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 200;
  background: var(--toast-bg);
  color: var(--toast-fg);
  padding: 0.625rem 1.25rem;
  border-radius: 0.5rem;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2);
  font-family: var(--toast-font);
  font-size: 0.9rem;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  opacity: 1;
  animation: toast-fadein 0.3s ease-out;
  transition: opacity 0.5s ease;
}

.toast--leaving {
  opacity: 0;
  pointer-events: none;
}

.step2__row {
  height: 2.75rem;
  margin-bottom: .625rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .375rem;
  position: relative;
  z-index: 1;
}

.meta-tile {
  width: 1.875rem;
  height: 2.125rem;
  border-radius: 0;
  font-family: var(--font-serif);
  font-size: 1.125rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  -webkit-user-select: none;
  user-select: none;
  /* As on .source-tile: no phone tap highlight. */
  -webkit-tap-highlight-color: transparent;
  position: relative;
}
/* Invisible hit-slop: extends the tappable area into half the gap on each
   side, without changing the painted tile size — fixes "fat finger"
   mis-taps between adjacent letters on touch devices. */
.meta-tile::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(-1 * var(--hit-slop, 0px));
  right: calc(-1 * var(--hit-slop, 0px));
}
.meta-tile[data-state="available"] {
  background: var(--tile);
  color: var(--ink);
  cursor: pointer;
}
.meta-tile[data-state="selected"] {
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  animation: tile-select .14s ease;
}
.meta-tile:focus { outline: none; }
.meta-tile:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink);
  border-radius: 2px;
}
.meta-tile[data-state="selected"]:focus-visible {
  box-shadow: 0 0 0 2px var(--accent), 0 0 0 4px var(--accent-ink);
}

@keyframes tile-select {
  from {
    background: var(--tile);
    color: var(--ink);
  }
}

/* ── Dialogs ───────────────────────────────────────────────────────────────
   Three roles, one vocabulary — see components/dialogRoles.ts, which is what
   decides who wears which. Rungs and Forage carry the same class names and
   the same --modal-* token names, so a look-and-feel change is the same edit
   in each game.

     .modal-panel                    sheet   — instructions
     .modal-panel--confirm           confirm — restart
     .modal-panel--info              info    — hint, share fallback

   The base rules only ever read tokens; a role modifier rebinds those tokens
   rather than restating the properties. So retheming a role means editing
   values in one block, and a new property added to the base rule is picked up
   by every role at once instead of needing to be repeated per modifier. */
:root {
  --modal-backdrop-bg: rgba(0, 0, 0, 0.5);
  --modal-backdrop-z: 50;
  --modal-backdrop-pad: 0;
  --modal-backdrop-animation: modal-backdrop-in 0.15s ease-out;

  --modal-panel-bg: #fff;
  --modal-panel-fg: inherit;
  --modal-panel-radius: 0.75rem;
  --modal-panel-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
  --modal-panel-width: 100%;
  --modal-panel-max-width: 24rem;
  --modal-panel-margin: 0 1rem;
  --modal-panel-pad: 1.5rem;
  /* Tall dialogs stop here and scroll inside .modal-body. 100% is the
     backdrop, which is fixed to the visible screen — no 100vh problem. */
  --modal-panel-max-height: calc(100% - 2rem);
  --modal-panel-animation: modal-panel-in 0.15s ease-out;

  --modal-close-inset: 9px;
  --modal-close-pad: 0;
  --modal-close-color: #6b7280;
  --modal-close-hover-color: #374151;
  --modal-close-opacity: 1;
  --modal-close-hover-opacity: 1;
  /* The focus ring sits just outside the sheet's ✕ glyph. */
  --modal-close-ring-offset: 2px;

  /* The info ✕'s box, and how far down the text must start to clear it. */
  --modal-close-info-inset: 3px;
  --modal-close-info-pad: 6px;
  --modal-close-info-icon: 20px;
  --modal-close-info-clearance: calc(var(--modal-close-info-inset) + 2 * var(--modal-close-info-pad) + var(--modal-close-info-icon) + 4px);

  --confirm-message-fg: #374151;
  --confirm-message-font-size: 0.95rem;
  --confirm-message-line-height: 1.55;
  --confirm-message-margin-bottom: 1.25rem;
  --confirm-btn-fg: var(--ink);
  --confirm-btn-border: 2px solid var(--ink);
  --confirm-btn-bg: transparent;
  /* The hover tint is the button's own ink at 8%, derived rather than written
     out: the literal it replaced was rgba(26, 26, 26, .08) — Forage's #1a1a1a
     ink, copied across verbatim, which never matched this game's palette. */
  --confirm-btn-hover-bg: color-mix(in srgb, var(--ink) 8%, transparent);
  --confirm-btn-radius: 6px;
  --confirm-btn-pad: 0.45rem 1.1rem;
  --confirm-btn-font-size: var(--directional-text);
  --confirm-btn-weight: 400;
}

@keyframes modal-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes modal-panel-in {
  from { opacity: 0; transform: scale(0.9); }
  to { opacity: 1; transform: scale(1); }
}

/* For a player who asks for less motion, a panel fades in without growing. */
@media (prefers-reduced-motion: reduce) {
  @keyframes modal-panel-in {
    from { opacity: 0; }
  }
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--modal-backdrop-z);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--modal-backdrop-pad);
  background: var(--modal-backdrop-bg);
  animation: var(--modal-backdrop-animation);
}

.modal-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  max-height: var(--modal-panel-max-height);
  background: var(--modal-panel-bg);
  color: var(--modal-panel-fg);
  border-radius: var(--modal-panel-radius);
  box-shadow: var(--modal-panel-shadow);
  max-width: var(--modal-panel-max-width);
  width: var(--modal-panel-width);
  margin: var(--modal-panel-margin);
  padding: var(--modal-panel-pad);
  font-family: var(--font-sans);
  font-size: var(--directional-text);
  animation: var(--modal-panel-animation);
}

/* The content scrolls; the panel doesn't. Deliberately not positioned, so
   the ✕ (absolute, measured from the panel) stays on the panel frame
   instead of scrolling away with the text.
   A scroller clips what paints past its edges, so it pads by a focus ring's
   reach — or the ring on a button at its edge is cut off — and gives the
   room back as a negative margin so the content doesn't move. */
.modal-body {
  overflow-y: auto;
  min-height: 0;
  overscroll-behavior: contain;
  padding: var(--focus-ring-ink-reach);
  margin: calc(-1 * var(--focus-ring-ink-reach));
}

/* confirm — a question and two buttons, narrower than a sheet */
.modal-panel--confirm {
  --modal-panel-max-width: 18rem;
}

/* info — a short statement that hugs its content, on a lighter backdrop */
.modal-backdrop--info {
  --modal-backdrop-bg: rgba(0, 0, 0, 0.4);
  --modal-backdrop-z: 100;
  --modal-backdrop-pad: 16px;
  --modal-backdrop-animation: none;
}

.modal-panel--info {
  --modal-panel-bg: var(--tile);
  --modal-panel-fg: var(--ink);
  --modal-panel-radius: 8px;
  --modal-panel-shadow: none;
  --modal-panel-width: fit-content;
  --modal-panel-max-width: none;
  --modal-panel-margin: 0;
  /* Its text is centred and runs the full width, so it starts below the ✕
     rather than running under it. */
  --modal-panel-pad: var(--modal-close-info-clearance) 32px 28px;
  --modal-panel-animation: none;
}

.modal-close {
  position: absolute;
  top: var(--modal-close-inset);
  right: var(--modal-close-inset);
  background: transparent;
  border: 0;
  padding: var(--modal-close-pad);
  color: var(--modal-close-color);
  opacity: var(--modal-close-opacity);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.15s ease, opacity 0.15s ease;
}

.modal-close:focus { outline: none; }
/* Full strength: the info ✕ is dimmed at rest, and so would its ring be. */
.modal-close:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--modal-close-ring-offset);
  opacity: 1;
}

.modal-close--info {
  --modal-close-inset: var(--modal-close-info-inset);
  --modal-close-pad: var(--modal-close-info-pad);
  --modal-close-color: var(--ink);
  --modal-close-hover-color: var(--ink);
  --modal-close-opacity: 0.6;
  /* Inside the ✕'s box, so the ring reaches no nearer the text than it. */
  --modal-close-ring-offset: calc(-1 * var(--focus-ring-ink-width));
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}

/* Sized here rather than by XIcon's own attributes, from the token the
   panel's clearance above is worked out from. */
.modal-close--info svg {
  width: var(--modal-close-info-icon);
  height: var(--modal-close-info-icon);
}

@media (hover: hover) {
  .modal-close:hover {
    color: var(--modal-close-hover-color);
    opacity: var(--modal-close-hover-opacity);
  }
  .modal-close--info:hover {
    --modal-close-hover-opacity: 1;
  }
}

.confirm-message {
  font-family: var(--font-sans);
  font-size: var(--confirm-message-font-size);
  color: var(--confirm-message-fg);
  line-height: var(--confirm-message-line-height);
  margin-bottom: var(--confirm-message-margin-bottom);
  text-align: center;
}

.confirm-buttons {
  display: flex;
  gap: 0.625rem;
  justify-content: center;
}

.confirm-btn {
  padding: var(--confirm-btn-pad);
  border-radius: var(--confirm-btn-radius);
  font-family: var(--font-sans);
  font-size: var(--confirm-btn-font-size);
  font-weight: var(--confirm-btn-weight);
  cursor: pointer;
  border: var(--confirm-btn-border);
  background: var(--confirm-btn-bg);
  color: var(--confirm-btn-fg);
  transition: background 0.15s;
}

.confirm-btn:focus { outline: none; }
.confirm-btn:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--focus-ring-ink-offset);
}

@media (hover: hover) {
  .confirm-btn:hover {
    background: var(--confirm-btn-hover-bg);
  }
}

.instructions-title {
  font-size: 1.125rem;
  font-weight: 600;
  color: #111827;
  margin-bottom: 0.75rem;
}

.instructions-tm {
  font-weight: 400;
}

.instructions-body {
  font-size: var(--directional-text);
  color: #374151;
  line-height: 1.625;
}
.instructions-body p + p {
  margin-top: 0.75rem;
}
.instructions-body .word-card {
  margin-top: 0.75rem;
}

.instructions-body .a11y-notes {
  margin-top: 0.75rem;
}
.instructions-body .a11y-notes summary {
  cursor: pointer;
  font-weight: 600;
}
.instructions-body .a11y-notes dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.25rem 0.75rem;
  margin: 0.5rem 0 0;
}
.instructions-body .a11y-notes dt {
  font-weight: 600;
}
.instructions-body .a11y-notes dd {
  margin: 0;
}
/* A flex box of fixed height, so a key's label — a word or a drawn arrow —
   is centred by layout rather than by wherever its font puts it. */
.instructions-body .a11y-notes kbd {
  font: inherit;
  font-size: 0.8em;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.5em;
  vertical-align: middle;
  padding: 0 0.3em;
  border: 1px solid currentColor;
  border-radius: 3px;
  white-space: nowrap;
}
.instructions-body .a11y-notes kbd svg {
  width: 1em;
  height: 1em;
}
/* The Clear button's ✕, pictured in the line as it looks on the board.
   Lowered from the baseline just enough to centre its ink on the capitals'
   height — centred on the lowercase letters (middle), it sat on the baseline
   and looked dropped beside the comma after it. */
.instructions-body .a11y-notes .clear-icon {
  display: inline-flex;
  vertical-align: -0.2em;
  color: var(--ink);
  opacity: var(--icon-button-opacity);
}
/* The strokes span only 14 of the icon's 24 units, so 5 units (.234em) either
   side are blank — which, on top of the word spaces, left a gap before the
   comma after the ✕. Pulled in by 3.5 units (.164em), keeping about .07em:
   what a letter keeps beside its ink. */
.instructions-body .a11y-notes .clear-icon svg {
  width: 1.125em;
  height: 1.125em;
  margin-inline: -0.164em;
}

.instructions-footer {
  margin-top: 1rem;
  font-size: 0.75rem;
  color: #6b7280;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.instructions-feedback {
  color: #2563eb;
  text-decoration: underline;
}
.instructions-feedback:hover {
  color: #1e40af;
}

/* Flying tile overlay — solution-tile groups that travel from Step 1 positions
   to Step 2 row positions during the stage transition. */
.flying-tile-group {
  display: inline-flex;
  gap: 0.3125rem;
  transition: transform 450ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Hint button */
.icon-button {
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--icon-button-padding);
  width: calc(var(--answer-icon-size) + 2 * var(--icon-button-padding));
  height: calc(var(--answer-icon-size) + 2 * var(--icon-button-padding));
  box-sizing: border-box;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: var(--icon-button-opacity);
  transition: opacity 0.15s;
  -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
  .icon-button:hover { opacity: 1; }
}

.answer-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
  justify-self: start;
}

.hint-glyph {
  font-family: var(--font-sans);
  font-size: var(--answer-icon-size);
  font-weight: 700;
}

.clear-button svg {
  width: var(--answer-icon-size);
  height: var(--answer-icon-size);
}

.clear-button--placeholder,
.hint-button--placeholder {
  visibility: hidden;
}

.hint-content {
  text-align: center;
}
