:root {
  --bg: #0e1116;
  --bg2: #161b25;
  --ink: #eef2f8;
  --dim: #7d879b;
  --line: #242b39;
  --accent: #ffb703;
  --accent2: #4cc9f0;
  --green: #4d9e57;
  --amber: #c9a227;
  --tile: #1b2230;
  --tile-line: #2f394b;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background:
    radial-gradient(900px 620px at 15% -12%, #1a2230 0%, transparent 62%),
    radial-gradient(760px 520px at 100% 4%, #17222c 0%, transparent 58%),
    var(--bg);
  color: var(--ink);
  font-family: "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
  -webkit-text-size-adjust: 100%;
  /* nothing here scrolls, so a rubber-band drag is only ever the board
     sliding out from under a tap */
  overscroll-behavior: none;
}

h1, h2 { margin: 0; }

/* A fixed height, not a minimum. With `min-height` the page simply grew past
   the viewport as the ladder did, so nothing was ever forced to shrink and
   `overflow: hidden` clipped the keyboard instead of the board adapting. */
/* The height comes from JavaScript, with 100vh as the floor.
 *
 * This used to be `height:100vh; height:100svh`. Browsers that understand svh
 * get the small viewport — the height with the address bar showing — which is
 * what the board should be sized against. Browsers that do not fall back to
 * 100vh, and on Android 100vh is the LARGE viewport, the height with the
 * chrome hidden. The page is then about 105px taller than what you can see,
 * body is overflow:hidden, and the bottom is simply gone.
 *
 * The bottom is the keyboard. Measured on three Android profiles: clipped by
 * 105px on all of them, and completely unreachable on two. svh shipped in
 * Chrome 108, and Android WebView on older and budget devices runs well
 * behind that — so this was invisible to every browser I had been testing in
 * and broken on the ones a real player reported.
 *
 * --app-height defaults to 100vh, which every browser parses, and JS replaces
 * it with the measured innerHeight. Deliberately NOT recomputed as the chrome
 * slides in and out: the tiles are sized to the height available, so tracking
 * that would resize the board and move every letter under a moving finger,
 * which is the reason svh was chosen over dvh in the first place. */
body.game-ladder {
  display: flex;
  flex-direction: column;
  height: var(--app-height, 100vh);
  overflow: hidden;
}

/* ================================================================= topbar */

.topbar {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  padding: 12px 20px;
  border-bottom: 1px solid var(--line);
  flex: none;
}

/* The tagline reads as a subtitle to the mark, so it sits beneath the lockup
   rather than trailing off its baseline. */
.brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
}

.brand { display: flex; align-items: center; }

.brand h1 {
  font-size: clamp(16px, 2.3vw, 20px);
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--ink);
  line-height: 1;
  white-space: nowrap;
}


.stats { display: flex; gap: 7px; margin-left: auto; }

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 66px;
  padding: 4px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--bg2);
}

.stat.wide { min-width: 104px; }
.stat-label { font-size: 9px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--dim); }
.stat-value { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat-value.sm { font-size: 13px; letter-spacing: 0.4px; }

.controls { display: flex; align-items: center; gap: 8px; }

.btn {
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg2);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, transform 0.1s;
}

.btn:hover { border-color: #3d4a61; background: #1d2431; }
.btn:active { transform: translateY(1px); }
.btn.ghost {
  display: inline-grid;
  place-items: center;
  width: 34px;
  padding: 8px 0;
  font-weight: 700;
}

.btn.ghost svg { width: 15px; height: 15px; fill: currentColor; display: block; }
.btn.small { font-size: 12px; padding: 6px 12px; }
.btn.small em { font-style: normal; color: var(--dim); font-size: 10px; }

.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #1a1405;
  font-weight: 700;
}

.btn.primary:hover { background: #ffc333; border-color: #ffc333; }
.btn.ok { background: var(--green); border-color: var(--green); color: #eafaec; }

/* ================================================================== board */

.ladder-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  padding: 0 16px;
}

.board {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
}

/* ================================================================== moves */

/* The allowance, drawn. Ten pips, a gap after the fifth: everything left of
   the gap is the shortest route that exists, everything right of it is
   margin. That one gap replaces both a number and a sentence of rules. */
.moves {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  /* the same 330 the rungs use, so the run of pips lines up with the board
     rather than floating in a narrower column of its own */
  max-width: 330px;
  flex: none;
}

.pips { display: flex; align-items: center; gap: 4px; flex: 1; }

.pip {
  flex: 1 1 0;
  height: 6px;
  min-width: 0;
  border-radius: 3px;
  background: #222a38;
  box-shadow: inset 0 0 0 1px #2c364a;
  transition: background 0.18s ease, box-shadow 0.18s ease;
}

/* the gap that says "five" without writing it */
.pip.after-par { margin-left: 9px; }

.pip.used { background: var(--green); box-shadow: inset 0 0 0 1px var(--green); }
.pip.used.over { background: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }

.moves-count {
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
  color: var(--dim);
  min-width: 38px;
  text-align: right;
}

.moves-count.tight { color: var(--accent); }

/* Balances the count on the other side of the pips. One colour, always: the
   readout reports and does not recommend. */
.ways {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: #5c6679;
  min-width: 62px;
  white-space: nowrap;
}

/* The readout carries no colour at all now. Green meant "fewer ways out",
   which reads as "good move", and the cyan lit up two-exit words, which are
   the dead ends. Both endorsed moves that leave the five-step route — the
   greenest one does so 93% of the time. See renderWays(). */

/* The rungs are the whole game, so they take all the vertical room going and
   shrink to fit rather than scrolling out of view.

   Ten moves means the ladder can reach twelve rows, which is far more than a
   phone has height for — and with the body set to `overflow: hidden`, rows
   that don't fit don't scroll, they shove the keyboard off the screen. So the
   tile size is measured against the room actually available (fitLadder) and
   handed back through --tile-size; --rung-w follows so tiles stay
   square instead of stretching into letterboxes. */
.ladder {
  --tile-size: 58px;
  --rung-w: 330px;
  display: flex;
  /* Hugs its rungs, so the pips sit just above the board instead of being
     shoved to the top of a tall screen. fitLadder measures the room from the
     board rather than from this box, so nothing here depends on its height —
     which also means sizing the tiles cannot change what they were sized
     against. */
  flex: 0 1 auto;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(3px, 0.9vh, 7px);
  width: 100%;
  min-height: 0;
}

.rung {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(3px, 0.8vw, 6px);
  width: 100%;
  max-width: var(--rung-w, 330px);
  flex: 0 0 auto;
  min-height: 0;
}

.gl {
  font-style: normal;
  display: block;
  /* Pin the line box: line-height:normal resolves differently per browser
     (Safari adds the font's line gap), which is what pushed the letters high
     there. A fixed box plus the measured nudge below is deterministic. */
  line-height: 1;
  transform: translate(var(--dx, 0), var(--dy, 0));
}

.lt {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  min-height: 0;
  /* A tile is a control, not prose. Without this, dragging across the board
     selects letters and a long press raises the iOS text callout — both of
     which shove the thing you were aiming at. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* no double-tap zoom: aiming at two tiles in quick succession is normal
     play, and having the second tap scale the board is not */
  touch-action: manipulation;
  max-height: var(--tile-size, 58px);
  border: 2px solid var(--tile-line);
  border-radius: 7px;
  /* tied to the tile, so a ladder squeezed onto a small screen shrinks its
     letters with it instead of overflowing them */
  font-size: clamp(13px, calc(var(--tile-size, 58px) * 0.46), 26px);
  font-weight: 800;
  background: var(--tile);
  color: var(--ink);
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

/* endpoints anchor the puzzle */
.rung.start .lt { background: #243043; border-color: #3a4a63; }
.rung.end .lt { background: transparent; border-color: var(--accent); color: var(--accent); }

.rung.played .lt { background: #1f2a38; border-color: #34425a; }

/* Green means the letter agrees with the target in that position — the same
   thing green means in every other word game. It is NOT "the letter I just
   changed"; that reading made players think a letter was locked in when the
   target did not contain it at all. */
/* Scoped to .rung so it outranks `.rung.played .lt`, which sets its own
   background — an unscoped `.lt.match` loses the specificity race and paints
   nothing at all. */
.rung .lt.match { background: var(--green); border-color: var(--green); color: #f0fbf1; }

/* The swapped letter gets a quiet dot instead of a colour, so tracing your
   route never competes with reading your progress. */
.rung .lt.changed { position: relative; }
.rung .lt.changed::after {
  content: '';
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 4px; height: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.45;
}

.rung.landed .lt { animation: land 0.34s cubic-bezier(.2,1.4,.4,1) backwards; }
.rung.landed .lt:nth-child(2) { animation-delay: 0.03s; }
.rung.landed .lt:nth-child(3) { animation-delay: 0.06s; }
.rung.landed .lt:nth-child(4) { animation-delay: 0.09s; }
.rung.landed .lt:nth-child(5) { animation-delay: 0.12s; }

@keyframes land { from { transform: translateY(-9px) scale(0.9); opacity: 0; } }

/* win flourish: a quick lift across the solved word, left to right */
.rung.solved .lt { animation: solved 0.5s cubic-bezier(.2,1.5,.35,1) backwards; }
.rung.solved .lt:nth-child(2) { animation-delay: 0.06s; }
.rung.solved .lt:nth-child(3) { animation-delay: 0.12s; }
.rung.solved .lt:nth-child(4) { animation-delay: 0.18s; }
.rung.solved .lt:nth-child(5) { animation-delay: 0.24s; }

@keyframes solved {
  0%   { transform: translateY(0) scale(1); }
  40%  { transform: translateY(-10px) scale(1.06); }
  100% { transform: translateY(0) scale(1); }
}

/* The draft row is prefilled with the word you're on, so it must not read as
   another played rung. Carried-over letters recede; only your edit and the
   aimed cell come forward. */
.rung.typing { margin-top: 3px; }

.rung.typing .lt {
  background: transparent;
  border-style: dashed;
  border-color: #333e52;
  color: #67718a;
  cursor: pointer;
}

.rung.typing .lt.changed {
  border-style: solid;
  border-color: var(--accent);
  background: rgba(255, 183, 3, 0.10);
  color: var(--accent);
}

.rung.typing .lt.caret {
  border-style: solid;
  border-color: var(--accent2);
  color: var(--ink);
  box-shadow: 0 0 0 3px rgba(76, 201, 240, 0.25);
}

/* green means the same thing on every row */
.rung.typing .lt.match {
  border-style: solid;
  background: var(--green);
  border-color: var(--green);
  color: #f0fbf1;
}

@keyframes tap { from { transform: scale(0.86); } }

.rung.typing.bounce { animation: bounce 0.36s ease; }

@keyframes bounce {
  20% { transform: translateX(-7px); }
  45% { transform: translateX(6px); }
  70% { transform: translateX(-3px); }
}

.msg {
  margin: 0;
  min-height: 18px;
  font-size: 12.5px;
  letter-spacing: 0.4px;
  color: #d99a9a;
  text-align: center;
}

.msg.good { color: #86c98e; }

/* The board shows the rule but not the mechanism, and the message line is
   already where a player looks for feedback — so it carries the instruction
   until they have played their first word, then gets out of the way. */
.msg.coach { color: var(--dim); }

/* ================================================================ keypad */

/* A physical keyboard makes the on-screen one redundant clutter — and unlike
   Wordle's, it carries no letter-state information to justify the space. Show
   it only where there is nothing to type with. */
.pad { flex: none; padding-bottom: max(10px, env(safe-area-inset-bottom)); }
.keys { display: none; }

@media (hover: none) and (pointer: coarse) {
  .keys { display: flex; }
}

/* nothing left to type once the puzzle is done */
body.done .keys { display: none; }

.pad-tools { display: flex; justify-content: center; gap: 8px; margin-bottom: 10px; }

.keys { flex-direction: column; gap: 6px; }
.krow { display: flex; justify-content: center; gap: 5px; }

.key {
  font-family: inherit;
  flex: 1 1 auto;
  max-width: 42px;
  height: clamp(40px, 6.4vh, 52px);
  border: 0;
  border-radius: 7px;
  background: #2b3444;
  color: var(--ink);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.12s, transform 0.08s;
  touch-action: manipulation;
}

/* Lit the moment there is a word to play. Typing letters and watching nothing
   happen is what made people think the game was broken; this points at the
   key that does something about it. */
.key.ready { background: var(--accent); color: #1a1405; }
.key.ready:hover { background: #ffc333; }

.key:hover { background: #364256; }
.key:active { transform: translateY(1px); background: #435268; }
/* Enter commits a move you cannot take back, so it should not be the same
   size as a letter. Grow rather than cap: on a phone the letters are uncapped
   and a max-width alone left Enter exactly as wide as Q.

   Basis 0 rather than auto, so Enter and Delete match each other: on an auto
   basis the label "ENTER" is simply wider than "⌫" and the two came out
   65px and 39px. */
.key.wide { flex: 1.6 1 0; max-width: 74px; font-size: 11.5px; letter-spacing: 0.6px; }

/* ================================================================ overlay */

.overlay {
  position: fixed;
  inset: 0;
  background: rgba(6, 8, 12, 0.8);
  backdrop-filter: blur(5px);
  display: grid;
  /* Centred vertically, stretched horizontally. `place-items: center` sized
     the modal to its own content, so on a 390px phone it came out 318px wide
     and everything inside inherited that — the graph was rendering at 0.95
     scale, below its own drawing size, because of a rule about alignment. */
  align-items: center;
  justify-items: stretch;
  grid-template-columns: minmax(0, 1fr);
  z-index: 100;
  padding: 18px;
  animation: fade 0.2s ease;
}

/* author display:grid would otherwise beat the UA rule for [hidden] */
.overlay[hidden] { display: none; }

@keyframes fade { from { opacity: 0; } }

.modal {
  background: linear-gradient(180deg, #1a212d 0%, #141a24 100%);
  border: 1px solid #2f3a4d;
  border-radius: 18px;
  padding: 28px 30px;
  max-width: 520px;
  width: 100%;
  margin-inline: auto;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.62);
  animation: pop 0.3s cubic-bezier(.2,1.2,.4,1);
}

@keyframes pop { from { transform: scale(0.9); opacity: 0; } }

/* The Play button has to be on screen the moment the rules are.
   .modal caps at 90vh and scrolls, so on a 375x812 phone the sheet ran to
   957px and the only way to start the game was to scroll 190px past the rules
   to find it. A first-time player should never have to go looking for the way
   in. Pinned to the bottom of the modal's own scrollport, with the sheet
   sliding under it. */
.modal.help .btn.primary {
  position: sticky;
  bottom: -28px;                 /* cancels the modal's own bottom padding */
  width: 100%;
  margin-top: 18px;
  padding-block: 12px;
  font-size: 15px;
  box-shadow: 0 -14px 18px -8px #151c27;
}

.modal h2 { font-size: 26px; letter-spacing: 0.5px; margin-bottom: 6px; }
.modal .verdict { font-size: 13.5px; color: var(--dim); margin: 0 0 4px; line-height: 1.55; }

.chain {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 18px 0 4px;
}

.chain span {
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 1.2px;
  padding: 5px 8px;
  border-radius: 6px;
  background: #232c3b;
  color: var(--ink);
}

.chain i { color: var(--dim); font-style: normal; font-size: 12px; }
.chain.small span { font-size: 11px; padding: 4px 6px; background: transparent; color: #97a2b8; }

/* The result headline carries the score itself, because the score is what
   gets quoted. The word beside it is flavour; the number is the message. */
.result-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}

.result-head b {
  font-size: 30px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
  color: var(--accent);
}

/* ============================================================== histogram */

/* Moves per finished puzzle. A play count says you turned up; a shape says
   how you play, and that is the part worth screenshotting. */
.hist { display: flex; flex-direction: column; gap: 4px; margin: 4px 0 18px; }

.hist-row { display: flex; align-items: center; gap: 9px; }

.hist-label {
  width: 16px;
  flex: none;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--dim);
  text-align: right;
}

.hist-bar {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  min-width: 22px;
  padding: 2px 7px;
  border-radius: 4px;
  background: #2b3444;
  font-size: 11px;
  font-weight: 700;
  color: #b9c3d6;
}

/* today's result, so the bar you just moved is the one you look at */
.hist-row.now .hist-bar { background: var(--green); color: #f0fbf1; }
.hist-row.now .hist-label { color: var(--ink); }

.hist-cap { margin: 14px 0 6px; font-size: 11px; letter-spacing: 0.3px; color: var(--dim); }

/* ================================================================= rarity */

/* Sits under the score, never in it. The number stays X/10 — this is a
   separate thing to be good at, for the players who have stopped finding the
   first one hard. */
.rarity {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 12px 0 0;
}

.rarity span {
  font-size: 9.5px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--dim);
}

.rarity b { font-size: 14px; font-weight: 700; color: #b9c3d6; }
.rarity.won b { color: var(--accent); }

/* A day with nothing to win. Stated rather than silently omitted, but quiet,
   so it reads as a fact about the puzzle and not as something you missed. */
.no-medal {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--dim);
}

.no-medal b { color: #98a2b6; font-weight: 700; }

/* A run ending is the one thing on this screen worth a colour it does not
   otherwise use. Quiet enough not to read as an error, loud enough that it is
   not missed — the loss is the motivator. */
.no-medal.ended b { color: #d99a6c; }

/* The medal leads the line. It is the one part a person can read at a glance
   and the one part that survives being screenshotted into a group chat. */
.rarity u {
  text-decoration: none;
  font-size: 17px;
  line-height: 1;
}

/* the count the place was made of */
.rarity i {
  font-style: normal;
  font-size: 10.5px;
  letter-spacing: 0.3px;
  color: var(--dim);
}

/* ================================================================ lattice */

/* Every five-step route at once, stacked by distance from the start. The
   shape is the point: puzzles fan out and funnel back, and seeing the eleven
   routes you did not take is the moment this game can offer and a ladder
   built on a yes/no dictionary cannot. */
/* The running medal count. One line, sitting with the medal it just added to,
   so a result reads as a deposit rather than a compliment. */
.tally {
  display: flex;
  align-items: center;
  gap: 12px;
  /* Room on both sides. The countdown panel below carries a border and a
     tint, and with nothing between them the medals read as its caption
     rather than as a result of their own. */
  margin: 16px 0 18px;
}

.tally-label {
  font-size: 9.5px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--dim);
}

.tally-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #b9c3d6;
}

.tally-item u { text-decoration: none; font-size: 15px; line-height: 1; }

/* An empty slot stays visible and goes quiet. A gap you can see is the whole
   reason to go after the one you have not got. */
.tally-item.none { color: #4d566a; }
.tally-item.none u { filter: grayscale(1); opacity: 0.45; }

.tally-item.lit { color: var(--accent); }

.tally-new {
  font-style: normal;
  font-size: 10.5px;
  color: var(--green);
  font-weight: 700;
}

/* pushed to the far end, so it reads as a separate fact rather than a fourth
   medal */
.tally-streak {
  margin-left: auto;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #b9c3d6;
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}

.tally-streak i {
  font-style: normal;
  font-size: 9.5px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--dim);
}

.lat-box { margin: 16px 0 18px; }

/* One row per step, always. Wrapping a wide layer onto a second line makes it
   read as two steps, which destroys the only thing this drawing is for — the
   widest layer runs to twelve words, so instead the whole block scrolls
   sideways as a unit and the rows stay in register. */
.lattice {
  padding: 6px 4px 2px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.18);
}

.lattice svg { display: block; width: 100%; height: auto; }

/* Texture, context, route. A dense day should still look dense — twenty-five
   ways through is the point — but the line you took has to survive it. */
.lat-edge { stroke: #262f3f; stroke-width: 0.8; }
.lat-edge.near { stroke: #36425a; stroke-width: 1; }
.lat-edge.walked { stroke: #8f9db4; stroke-width: 2.2; }

/* Nodes are sized by how many words are one letter away and banded by the
   same measure, so a thin part of the graph looks thin. d0 is a quiet corner,
   d2 a hub — the same ways-out count the rarity ranking is built on.

   The class names here were b0..b2 once and this comment outlived them by
   several renames, which is how a dead `.lat-step.b0` lookup went unnoticed:
   the ghost class was still spelled out in prose a few lines away.

   The same tiles the board is made of, shrunk. Fill deepens with degree, so
   a hub reads as solid and a quiet corner recedes — size and weight carry it,
   never hue, because every hue in this game already means something. */
.lat-node { fill: #1f2a38; stroke: #34425a; stroke-width: 1; }
.lat-node.d0 { fill: #1a2331; stroke: #2a3445; }
.lat-node.d1 { fill: #222e3e; stroke: #34425a; }
.lat-node.d2 { fill: #2b3a4e; stroke: #3f4f6b; }
/* Nodes are no longer dimmed for being outside the everyday list. That faded
   half the drawing on the basis of an internal split the player never agreed
   to and cannot see the rules of — and an uncommon word is a useful move, not
   a lesser one. Size and fill still track how connected a word is, which is a
   fact about the board rather than a verdict on the vocabulary. */

/* the same quiet dot the board uses to mark a swapped letter */
.lat-mark { fill: var(--accent); opacity: 0.75; }

/* the steps actually taken, spelled out and banded the same way */
.lat-step rect { fill: #1f2a38; stroke: #34425a; stroke-width: 1; }
.lat-step text {
  font-family: inherit;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.3px;
  fill: #dfe6f2;
}

/* start and target, styled exactly as their rows on the board */
.lat-step.post rect { fill: #243043; stroke: #3a4a63; }
.lat-step.goal rect { fill: transparent; stroke: var(--accent); }
.lat-step.goal text { fill: var(--accent); }

/* The ways-out count beside each step you took.
 *
   This replaces three metal tiers. The tiers banded each word's degree and
   showed it as a coloured dot, which asked the reader to hold a second scale
   in their head — and the one line that explained it sat behind a selector no
   code path could produce, so it never rendered once. Players got gold,
   silver and bronze with no definition anywhere on the screen.

   The number is the thing itself: how many words are one letter away, which
   is the counter that was on screen throughout the round. The four of them
   add up to the total printed under the score.

   Deliberately quiet — it is a reading, not a result, and the eye should
   land on the word first. */
.lat-ways {
  font-family: inherit;
  font-size: 7.5px;
  font-weight: 700;
  fill: #6f7b91;
  /* No text-anchor here. A CSS declaration beats an SVG presentation
     attribute, so this silently killed the text-anchor="end" that readingEl
     emits for left-placed labels: they moved left but still ran rightward,
     straight through the word. Two of four annotations overlapped their chip
     by up to 46px. The SVG default is already `start`. */
  font-variant-numeric: tabular-nums;
  /* Punched out of the edges behind it. The numbers sit just outside the
     chips, which is exactly where the busiest tangle of edges runs, and a
     line through a digit turns an 11 into something unreadable. Painting the
     stroke first lays a halo of the panel colour under each glyph. */
  stroke: #0f141c;
  stroke-width: 2.5px;
  paint-order: stroke fill;
}

.lat-note {
  margin: 8px 0 0;
  text-align: center;
  font-size: 11px;
  color: var(--dim);
}

.modal .scoreline { display: flex; gap: 24px; margin: 18px 0 14px; flex-wrap: wrap; }
.modal .scoreline div { display: flex; flex-direction: column; gap: 3px; }
.modal .scoreline span:first-child { font-size: 9.5px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--dim); }
.modal .scoreline span:last-child { font-size: 23px; font-weight: 700; font-variant-numeric: tabular-nums; }

/* miniature ladder inside the help sheet — the rule is easier shown than told */
.demo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  margin: 18px 0 8px;
}

.demo .rung { max-width: 232px; gap: 5px; }
.demo .lt { max-height: 42px; font-size: 18px; border-width: 2px; border-radius: 6px; }
.demo-cap { margin: 0 0 4px; text-align: center; font-size: 11.5px; color: var(--dim); }

.countdown {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 4px;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(255, 183, 3, 0.05);
}

.countdown span { font-size: 10px; letter-spacing: 1.6px; text-transform: uppercase; color: var(--dim); }
.countdown b { font-size: 20px; font-variant-numeric: tabular-nums; color: var(--accent); }

.best { margin-bottom: 18px; }
.best summary { font-size: 12px; color: var(--dim); cursor: pointer; letter-spacing: 0.4px; }
.best summary:hover { color: var(--ink); }

/* After a miss the route is shown rather than offered — someone who ran out
   of moves wants to see where it went, not click to find out. */
.best-label {
  margin: 18px 0 0;
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--dim);
}

.modal ol { padding-left: 20px; margin: 14px 0; line-height: 1.7; font-size: 14px; color: #ccd4e4; }
.modal ol b { color: var(--accent); }
.modal .fineprint { font-size: 12px; color: var(--dim); line-height: 1.6; border-top: 1px solid var(--line); padding-top: 14px; }
.modal .fineprint em { color: #97a2b8; font-style: normal; }

.modal-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.wide-btn { width: 100%; margin-bottom: 10px; }

/* Direct post targets. Secondary to the main Share button — on mobile the
   native sheet is better than any of these, so they sit quietly underneath. */
.targets {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.targets-label {
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--dim);
  margin-right: 2px;
}

.target {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--bg2);
  color: #97a2b8;
  transition: color 0.15s, border-color 0.15s, background 0.15s, transform 0.1s;
}

.target svg { width: 16px; height: 16px; fill: currentColor; }
.target:hover { color: var(--ink); border-color: #3d4a61; background: #1d2431; }
.target:active { transform: translateY(1px); }

/* on the board it is an unobtrusive row, not a section */
.targets-board {
  justify-content: center;
  border-top: 0;
  padding-top: 0;
  margin: 10px 0 0;
}
.modal-actions .btn { flex: 1 1 auto; min-width: 128px; }

/* ============================================================ responsive */

@media (max-width: 620px) {
  .topbar { gap: 8px; padding: 9px 12px; }
  .brand h1 { font-size: 15px; letter-spacing: 1px; }
  .stat { min-width: 0; padding: 3px 10px; }
  .stat.wide { min-width: 0; }
  .stat-value { font-size: 16px; }
  .ladder-main { padding: 0 10px; max-width: 460px; }
  .modal { padding: 22px 18px; }
  .modal-actions .btn { min-width: 0; }
  .key { max-width: none; }
}

/* ======================================================== accessibility */

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

/* Visible focus everywhere, but only for keyboard users. */
:focus-visible {
  outline: 3px solid var(--accent2);
  outline-offset: 2px;
}

.modal:focus { outline: none; }

/* Respect the OS setting. The tile animations are decorative; the game reads
   identically without them, so drop them rather than shortening them. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .rung.landed .lt { animation: none !important; }
}

/* Fingers need ~44px; a mouse does not. The share button is the most
   important control in the game on the device where sharing actually
   happens, and it was 28px tall. */
@media (hover: none) and (pointer: coarse) {
  .btn.small { min-height: 44px; padding: 10px 16px; font-size: 13px; }
  .target { width: 44px; height: 44px; }
  .target svg { width: 19px; height: 19px; }
  .btn.ghost { width: 44px; min-height: 44px; }
  .modal-actions .btn { min-height: 46px; }
  .targets { gap: 10px; }
}

/* ================================================================= install */

.modal .installline {
  margin: 12px 0 0;
  text-align: center;
  font-size: 12.5px;
  color: var(--dim);
  line-height: 1.5;
}
.modal .installline.hint b { color: #aab4c6; font-weight: 700; }

/* Safari's own share glyph, inline in the sentence. Naming the icon in words
   was what failed: this screen already has a button called "Share result"
   directly above, so "tap Share" pointed at the wrong control. A picture of
   the thing cannot be confused with a button that has no picture. */
.ios-share {
  width: 1em;
  height: 1em;
  vertical-align: -0.16em;
  fill: #aab4c6;
  margin: 0 1px;
}

.linkbtn {
  font-family: inherit;
  background: none;
  border: 0;
  padding: 6px 4px;
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.linkbtn:hover { color: #ffc333; }
.linkbtn:disabled { color: var(--dim); text-decoration: none; cursor: default; }

/* ============================================================ content pages

   how-to-play.html and 404.html. Everything here is scoped to `body.page` or
   to class names the game does not use, so the board cannot be reached from
   this section. Note that the fixed-height, overflow-hidden layout belongs to
   `body.game-ladder` alone — a content page needs to scroll, and inherits only
   the background, the colours and the typeface. */

body.page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.home {
  color: var(--ink);
  text-decoration: none;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.4px;
}
.home:hover { color: var(--accent); }

a.btn { text-decoration: none; display: inline-block; }

.prose {
  flex: 1 1 auto;
  width: 100%;
  max-width: 680px;
  margin: 0 auto;
  padding: 28px 20px 60px;
  line-height: 1.7;
  font-size: 16px;
  color: #ccd4e4;
}
.prose.narrow { max-width: 460px; }

.prose h1 {
  font-size: clamp(26px, 6vw, 34px);
  line-height: 1.25;
  letter-spacing: 0.3px;
  margin: 0 0 14px;
  color: var(--ink);
}
.prose h2 {
  font-size: clamp(18px, 4.4vw, 21px);
  margin: 38px 0 10px;
  color: var(--ink);
  letter-spacing: 0.2px;
}
.prose h3 {
  font-size: 16px;
  margin: 26px 0 4px;
  color: var(--ink);
}
.prose p { margin: 0 0 14px; }
.prose .lede { font-size: 17.5px; color: #dfe6f2; }
.prose b { color: var(--ink); }
.prose ol, .prose ul { padding-left: 22px; margin: 0 0 16px; }
.prose li { margin-bottom: 8px; }
.prose a { color: var(--accent); }
.prose .cta { margin-top: 34px; }
.prose .cta .btn { font-size: 15px; padding: 11px 22px; }

/* The example ladder. Bigger than the one on the help sheet, because here it
   is the thing being read rather than a reminder beside the rules. */
.demo-lg {
  gap: 5px;
  margin: 18px 0 22px;
}
.demo-lg .rung { max-width: 268px; }
.demo-lg .lt {
  max-height: 48px;
  font-size: 20px;
}

.pagefoot {
  flex: none;
  border-top: 1px solid var(--line);
  padding: 18px 20px;
  text-align: center;
  font-size: 12.5px;
  color: var(--dim);
}
.pagefoot p { margin: 0; }
