/* WEFT Phase A — state machine layout (tokens.css required) */

*, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  padding: 0;
  font-family: var(--font);
  font-size: var(--text-base);
  line-height: 1.45;
  color: var(--ink);
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  background:
    radial-gradient(120% 80% at 15% -10%, color-mix(in oklch, var(--word-1) 22%, transparent), transparent 60%),
    radial-gradient(120% 80% at 90% 0%, color-mix(in oklch, var(--word-4) 20%, transparent), transparent 55%),
    var(--bg);
  background-attachment: fixed;
}

.app {
  max-width: var(--app-max);
  margin: 0 auto;
  min-height: 100dvh;
  padding:
    max(var(--space-4), env(safe-area-inset-top))
    max(var(--space-4), env(safe-area-inset-right))
    max(var(--space-6), env(safe-area-inset-bottom))
    max(var(--space-4), env(safe-area-inset-left));
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

[data-ui-state="playing"] .solve-panel { display: none !important; }

[data-ui-state="solved"] .play-shell { display: none !important; }

/* Setup owns the whole screen: no board, no win panel, no in-game chrome. */
[data-ui-state="setup"] .play-shell,
[data-ui-state="setup"] .solve-panel,
[data-ui-state="setup"] .play-action-bar,
[data-ui-state="setup"] .play-header-center { display: none !important; }
[data-ui-state="setup"] .setup-panel { display: block; }
.setup-panel[hidden] { display: none !important; }

.setup-panel {
  display: none;
  margin: auto 0;
}

.setup-title {
  font-size: var(--text-xl);
  margin: 0 0 var(--space-2);
}

.setup-sub {
  color: var(--text-muted);
  font-size: var(--text-sm);
  margin: 0 0 var(--space-6);
}

.level-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-3);
}

.level-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-4);
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line-ui);
  border-radius: var(--radius-md);
  color: var(--text);
  cursor: pointer;
  font: inherit;
  min-height: var(--touch-min);
  transition: border-color var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}

.level-card:hover:not(:disabled) {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.level-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.level-card[aria-current="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.level-card:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.level-badge {
  font-family: var(--mono);
  font-size: var(--text-xs);
  font-weight: 600;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--accent-soft);
  color: var(--accent);
}

.level-name { font-weight: 600; }

.level-desc,
.level-count {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.level-count { font-variant-numeric: tabular-nums; }

/* Tappable: this is the fastest route to the level selector. */
.chip-level {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--touch-min);
  padding: var(--space-1) var(--space-2);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}

.chip-level:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.chip-level:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.chip-level-label {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.75;
}

.chip-level-value {
  font-family: var(--mono);
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.chip-level[hidden] { display: none !important; }

.play-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--header-h);
  padding: 0 var(--space-2);
  flex-shrink: 0;
}

/* The 0.28em tracking and the uppercase transform were tuned for a four-letter acronym. Applied
   to "Wortweg" they produced a ~150px wordmark that overran the timer and pushed the level chip
   under the buttons at 390px. A name is not an acronym: mixed case, normal tracking. */
.play-header h1 {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  white-space: nowrap;
  flex-shrink: 0;
  background: linear-gradient(90deg, var(--word-1), var(--word-4), var(--word-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* The theme and menu buttons travel together as ONE flex item. Dropping a second button
   straight into .play-header would have made it a fourth child of a space-between row, which
   spreads the wordmark and chips apart instead of keeping the controls in the corner. */
.play-header-tools {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}

.play-header-center {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  justify-content: center;
  /* A flex item defaults to min-width:auto, so this refused to shrink below its content and
     pushed the gear button off the right edge at 320px — the menu became unreachable. */
  min-width: 0;
}

/* The header carries a wordmark, a timer, a streak, a level chip and two buttons. Below ~480px
   that is more than fits, and .play-header-center has min-width:0 so the surplus does not push
   anything — it silently OVERLAPS, which is how "Wortweg" ended up printed on top of the timer.
   The breakpoint was 379px, which left a 390px phone broken. Two things go:
     - the streak, which a player can lose without losing anything needed mid-puzzle;
     - the word "Niveau", because the chip's own value already reads B1 and its aria-label
       still announces the full "Niveau ändern (B1)". */
@media (max-width: 479px) {
  .chip-streak { display: none; }
  .chip-level-label { display: none; }
}

.timer {
  font-family: var(--mono);
  font-size: var(--text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  min-width: 52px;
  text-align: center;
}

/* Buttons, chips, progress — see components.css */

.visually-hidden-tools {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}

.play-shell {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.play-layout {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  flex: 1;
}

@media (min-width: 1024px) {
  .play-layout {
    display: grid;
    grid-template-columns: 1fr var(--rail-w);
    grid-template-rows: 1fr;
    align-items: start;
    gap: var(--space-6);
  }
}

.board-column {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

.board-caption {
  margin: 0;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
}

.board-caption #puzzleName {
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.02em;
}

/* One pill per fact. Size and level are the two the player scans for, so they carry
   weight; everything else stays quiet so it does not compete with the board. */
.puzzle-tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1);
}

.puzzle-tag {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  color: var(--text-muted);
  background: var(--surface-2, var(--surface));
}

.puzzle-tag--size {
  font-family: var(--mono);
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--text);
  border-color: var(--line-ui);
  background: transparent;
}

.puzzle-tag--level {
  font-family: var(--mono);
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--accent);
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}

.grid-wrap {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
}

/* Board generation runs on the visitor's own device and blocks the main thread — measured
   at 130-500ms on desktop, several times that on a phone. The status line is
   screen-reader-only, so without this the player stares at an empty board and assumes the
   game is broken. Absolutely positioned so it never enters resizeGrid()'s measurement of
   .grid-wrap.

   The 120ms delay is what stops a fast board from flashing an overlay nobody can read. It
   has to live here rather than in a setTimeout, because generation freezes the main thread:
   a JS timer could only fire once generation is already over. Opacity animations run on the
   compositor thread, which keeps ticking while JS is blocked. */
.board-loader {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  background: color-mix(in oklch, var(--bg) 72%, transparent);
  border-radius: var(--radius-md);
  opacity: 0;
  animation: board-loader-in var(--duration-fast) var(--ease-out) 120ms forwards;
}

.board-loader[hidden] {
  display: none;
}

.board-loader-text {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.board-loader-dots {
  display: flex;
  gap: var(--space-2);
}

.board-loader-dots i {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  animation: board-loader-pulse 1s var(--ease-out) infinite;
}

.board-loader-dots i:nth-child(2) { animation-delay: 0.15s; }
.board-loader-dots i:nth-child(3) { animation-delay: 0.3s; }

@keyframes board-loader-in {
  to { opacity: 1; }
}

@keyframes board-loader-pulse {
  0%, 100% { opacity: 0.25; transform: scale(0.8); }
  50%      { opacity: 1;    transform: scale(1); }
}

/* The tokens zero out --duration-* under reduced motion, but a keyframe animation ignores
   those; it needs its own opt-out or it keeps pulsing. The overlay itself keeps its
   animation — its duration is already 0ms here, so it just snaps in after the delay. */
@media (prefers-reduced-motion: reduce) {
  .board-loader-dots i {
    animation: none;
    opacity: 0.6;
  }
}

.board {
  position: relative;
  touch-action: none;
  width: 100%;
  max-width: 100%;
}

.grid {
  --grid-gap: 6px;
  display: grid;
  gap: var(--grid-gap);
  user-select: none;
  touch-action: none;
  grid-template-columns: repeat(var(--cols, 5), var(--tile-size, 60px));
  grid-template-rows: repeat(var(--rows, 5), var(--tile-size, 60px));
  position: relative;
  z-index: 1;
  margin: 0 auto;
  width: fit-content;
}

.line-svg {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  overflow: visible;
}

.cell {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--tile-border);
  border-radius: var(--radius-sm);
  font-family: var(--mono);
  /* Scales with the tile. The old 24px floor was taller than a small tile's inner box, so a
     shrunk cell would have clipped its own letter — including Ä/Ö/Ü, whose umlaut sits above
     the cap height and is the first thing to get cut. */
  font-size: max(15px, calc(var(--tile-size, 60px) * 0.42));
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  position: relative;
  touch-action: none;
  z-index: 1;
}

.cell.wall {
  background: transparent;
  border: none;
  pointer-events: none;
  visibility: hidden;
}

.cell.selected {
  background: var(--accent-path);
  border-color: var(--accent);
  border-width: 2px;
  color: var(--ink);
  z-index: 3;
}

.cell.selected.first { font-weight: 700; }

.cell.locked {
  background: var(--surface);
  cursor: default;
  border-width: 2px;
  z-index: 2;
}

.cell.locked.c1 { border-color: var(--w1); color: color-mix(in srgb, var(--w1) 80%, var(--ink)); }
.cell.locked.c2 { border-color: var(--w2); color: color-mix(in srgb, var(--w2) 80%, var(--ink)); }
.cell.locked.c3 { border-color: var(--w3); color: color-mix(in srgb, var(--w3) 80%, var(--ink)); }
.cell.locked.c4 { border-color: var(--w4); color: color-mix(in srgb, var(--w4) 80%, var(--ink)); }
.cell.locked.c5 { border-color: var(--w5); color: color-mix(in srgb, var(--w5) 80%, var(--ink)); }

.cell.locked::after { display: none; }

.progress-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: 0 var(--space-2);
}

/* Count on the left, percentage on the right — the percentage is the number people
   actually read, so it gets the accent colour and the heavier weight. */
.progress-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.progress-label {
  font-family: var(--mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.progress-pct {
  font-family: var(--mono);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  transition: color var(--duration-fast) var(--ease-out);
}

.progress-wrap[data-complete="true"] .progress-pct { color: var(--ok, var(--accent)); }

.word-rail {
  background: var(--surface);
  border: 1px solid var(--line-ui);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}

.word-rail-title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Word list is ALWAYS a vertical crossword-style list (every viewport). */
.word-slot-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

@media (min-width: 1024px) {
  .word-rail {
    position: sticky;
    top: var(--space-4);
    max-height: calc(100dvh - var(--header-h) - var(--space-8));
    overflow-y: auto;
  }
}

/* The rail is the ONLY place that tells a player how many words to look for and how long each
   one is — and on a phone it sat under the board, the progress bar and the status line, i.e.
   off-screen until you scrolled. You would start dragging with no idea what you were hunting.
   Above the board it is the first thing you read; a wrapping row is what makes that affordable
   in vertical space, since a column of six slots costs ~280px the board needs. */
@media (max-width: 1023px) {
  .word-rail {
    order: -1;
    padding: var(--space-3);
  }

  .word-rail-title { margin-bottom: var(--space-2); }

  .word-slot-list {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .word-slot-row { min-width: 0; }

  /* An expanded slot takes the whole row back, or the lesson would be squeezed into the width
     of one chip. */
  .word-slot-row:has(.word-slot-detail) { flex: 1 0 100%; }

  .word-slot {
    grid-template-columns: 6px auto minmax(0, auto) auto;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
  }

  /* Unsolved, the row of dots says exactly what the number beside it already says, and it is
     what makes a 12-letter chip too wide to share a line with anything. Keep the number.
     The tick column is empty until the word is found, so it is dropped from the track list too
     — an empty grid cell still costs its gap, which is what left the chips half air. */
  .word-slot:not(.done) .slot-text,
  .word-slot:not(.done) .slot-check { display: none; }

  .word-slot:not(.done) { grid-template-columns: 6px auto; }

  .slot-len { min-width: 20px; padding: 2px var(--space-1); }
}

.word-slot-row {
  display: flex;
  flex-direction: column;
}

.word-slot {
  display: grid;
  grid-template-columns: 6px 26px 1fr auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-ui);
  border-left: 4px solid var(--slot-color, var(--line-ui));
  background: var(--surface-raised);
  transition: background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}

/* A solved slot is a <button>, so it needs the browser's button defaults undone; an unsolved
   one is a <div> and has nothing to reveal. */
button.word-slot {
  width: 100%;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

button.word-slot[aria-expanded="true"] {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.word-slot-detail {
  padding: var(--space-3) var(--space-4) var(--space-4);
  border: 1px solid color-mix(in oklch, var(--slot-color, var(--line-ui)) 45%, var(--line-ui));
  border-top: none;
  border-left: 4px solid var(--slot-color, var(--line-ui));
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: color-mix(in oklch, var(--slot-color, var(--surface)) 8%, var(--surface));
}

.word-slot-detail.c1 { --slot-color: var(--w1); }
.word-slot-detail.c2 { --slot-color: var(--w2); }
.word-slot-detail.c3 { --slot-color: var(--w3); }
.word-slot-detail.c4 { --slot-color: var(--w4); }
.word-slot-detail.c5 { --slot-color: var(--w5); }
.word-slot-detail.c6 { --slot-color: var(--w6); }

.word-slot-detail .lesson-word { font-size: var(--text-base); }

/* Word identity colour, shown ALWAYS (colourful even before solving) */
.word-slot.c1 { --slot-color: var(--w1); }
.word-slot.c2 { --slot-color: var(--w2); }
.word-slot.c3 { --slot-color: var(--w3); }
.word-slot.c4 { --slot-color: var(--w4); }
.word-slot.c5 { --slot-color: var(--w5); }
.word-slot.c6 { --slot-color: var(--w6); }

.word-slot.done {
  border-color: color-mix(in oklch, var(--slot-color) 45%, var(--line-ui));
  border-left-color: var(--slot-color);
  background: color-mix(in oklch, var(--slot-color) 14%, var(--surface));
}

.slot-dot {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--slot-color);
}
.word-slot:not(.done) .slot-dot { opacity: 0.5; }

.slot-len {
  font-family: var(--mono);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--slot-color);
  text-align: center;
  background: color-mix(in oklch, var(--slot-color) 16%, transparent);
  border-radius: var(--radius-xs);
  padding: 2px 0;
}

.slot-text {
  font-family: var(--mono);
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: 0.16em;
  color: var(--text-muted);
  line-height: 1.3;
}

.word-slot.done .slot-text {
  color: var(--text);
  letter-spacing: 0.08em;
}

.slot-translation {
  display: block;
  font-family: var(--font);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--slot-color);
  margin-top: 3px;
}

.slot-check {
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--slot-color);
}

.play-action-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--line-ui);
  border-radius: var(--radius-lg);
}

.play-action-bar .btn-next { margin-left: auto; }
.play-action-bar .btn-ghost.primary-accent { display: none; }

.action-divider {
  width: 1px;
  height: 24px;
  background: var(--line-ui);
}

/* The game's only text feedback surface: wrong word, word locked, hint applied, cleared.
   It reserves its line height so text arriving never shifts the board underneath it, and it
   never auto-dismisses — a timer that erases the reason a word was rejected is worse than
   leaving the message up, and the next interaction replaces it anyway. */
.status {
  min-height: calc(var(--text-sm) * 1.5);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-muted);
  text-align: center;
}

.status.error {
  color: var(--danger);
  border-color: color-mix(in oklch, var(--danger) 35%, transparent);
  background: color-mix(in oklch, var(--danger) 10%, transparent);
}

.status.success,
.status.win {
  color: var(--accent);
  border-color: color-mix(in oklch, var(--accent) 35%, transparent);
  background: var(--accent-soft);
}

.status-live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}

.solve-panel {
  margin-top: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line-ui);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  text-align: center;
}

.solve-panel[hidden] { display: none !important; }

.solve-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
  font-weight: 600;
}

.solve-time {
  margin: 0 0 var(--space-1);
  font-family: var(--mono);
  font-size: 32px;
  font-weight: 600;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.solve-sub {
  margin: 0 0 var(--space-5);
  font-size: var(--text-sm);
  color: var(--muted);
}

.solve-recap {
  margin: 0 0 var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line-ui);
  text-align: left;
}

.solve-recap[hidden] { display: none !important; }

.solve-recap-title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: center;
}

.solve-recap-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The recap is the only screen whose job is reading, so it is generously spaced and the
   list never scrolls independently — a nested scrollbar would hide the vocabulary that is
   the entire point of the screen. The page scrolls; the list does not. */
.solve-recap-item {
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid color-mix(in oklch, var(--slot-color, var(--line-ui)) 45%, var(--line-ui));
  border-left: 4px solid var(--slot-color, var(--line-ui));
  background: color-mix(in oklch, var(--slot-color, var(--surface)) 14%, var(--surface));
}

.solve-recap-item.c1 { --slot-color: var(--w1); }
.solve-recap-item.c2 { --slot-color: var(--w2); }
.solve-recap-item.c3 { --slot-color: var(--w3); }
.solve-recap-item.c4 { --slot-color: var(--w4); }
.solve-recap-item.c5 { --slot-color: var(--w5); }
.solve-recap-item.c6 { --slot-color: var(--w6); }

/* —— One word's lesson: gender, headword, plural, meaning, sentence ——
   Emitted by lessonHtml() and used by BOTH the win screen and the expanded word rail, so
   the two can never drift into showing different amounts about the same word. Every line
   is absent whenever its data is, which for the legacy list is most of them. */
.lesson-head {
  margin: 0;
  text-wrap: pretty;
}

.lesson-article {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--slot-color, var(--accent));
}

.lesson-word {
  font-family: var(--mono);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text);
}

.lesson-plural {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.lesson-gloss {
  margin: var(--space-1) 0 0;
  font-size: var(--text-base);
  color: var(--text-muted);
}

.lesson-example {
  margin: var(--space-3) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid color-mix(in oklch, var(--slot-color, var(--line-ui)) 30%, transparent);
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--text);
  text-wrap: pretty;
}

/* The point of the sentence is seeing the word behave in a clause, so the word is marked
   inside it — in its own board colour, tying it back to the path that was drawn. */
.lesson-example strong {
  font-weight: 700;
  color: var(--slot-color, var(--accent));
}

.lesson-example-en {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  font-style: italic;
  color: var(--text-muted);
}

.solve-recap-total {
  margin: var(--space-4) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  text-align: center;
}

.solve-recap-total[hidden] { display: none !important; }

/* —— "Was this puzzle good?" ——
   One tap and it is gone. The answer is only worth collecting because it leaves for
   analytics with the level and size attached: "boards are boring" is not actionable,
   "7x7 A1 boards are boring" points straight at a content gap. */
.solve-rating {
  margin: 0 0 var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line-ui);
}

.solve-rating-q {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.solve-rating-stars {
  display: flex;
  justify-content: center;
  gap: var(--space-1);
}

.rating-star {
  border: none;
  background: transparent;
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  min-width: var(--touch-min);
  min-height: var(--touch-min);
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--line-ui);
  transition: color var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}

/* Hovering a star lights every star up to it, so the control reads as a 1-5 scale rather
   than five separate buttons. `:has` walks left from the hovered star to its siblings. */
.solve-rating-stars:hover .rating-star { color: var(--accent); }
.solve-rating-stars .rating-star:hover ~ .rating-star { color: var(--line-ui); }
.rating-star:hover { transform: scale(1.15); }
.rating-star:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.solve-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.solve-actions-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}

.gear-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: transparent;
}
.gear-backdrop.hidden { display: none; }

.gear-menu {
  position: fixed;
  top: calc(var(--header-h) + var(--space-6));
  right: max(var(--space-4), env(safe-area-inset-right));
  z-index: 95;
  min-width: 220px;
  background: var(--surface-elevated);
  border: 1px solid var(--line-ui);
  border-radius: var(--radius-lg);
  padding: var(--space-2);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.gear-menu.hidden { display: none; }

/* —— First-visit rules card —— */
.intro-backdrop {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(var(--space-4), env(safe-area-inset-top))
           max(var(--space-4), env(safe-area-inset-right))
           max(var(--space-4), env(safe-area-inset-bottom))
           max(var(--space-4), env(safe-area-inset-left));
  background: oklch(0% 0 0 / 0.66);
}
.intro-backdrop.hidden { display: none; }

.intro-card {
  width: min(420px, 100%);
  /* The card must scroll rather than push its button off a short screen: at 320x568 in
     landscape there is not enough height for the diagram AND four rules AND the button. */
  max-height: 100%;
  overflow-y: auto;
  background: var(--surface-elevated);
  border: 1px solid var(--line-ui);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-raised);
  text-align: center;
}

.intro-title {
  margin: 0 0 var(--space-4);
  font-size: var(--text-xl);
}

.intro-demo { display: flex; justify-content: center; margin-bottom: var(--space-4); }
.intro-demo svg { width: 132px; height: 132px; }
.intro-demo-empty rect { fill: var(--surface); stroke: var(--tile-border); stroke-width: 1; }
.intro-demo-used rect { fill: none; stroke: var(--word-1); stroke-width: 2; }
.intro-demo-thread {
  fill: none;
  stroke: var(--word-1);
  stroke-width: 12;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.34;
}
.intro-demo-letter text {
  fill: var(--ink);
  font-family: var(--mono);
  font-size: 19px;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
}

.intro-rules {
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
  text-align: left;
  display: grid;
  gap: var(--space-3);
}
.intro-rules li {
  position: relative;
  padding-left: var(--space-5);
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--text-muted);
}
.intro-rules li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--word-1);
}

.intro-start {
  width: 100%;
  min-height: 48px;
  font: inherit;
  font-weight: 600;
  color: var(--bg);
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.intro-start:hover { filter: brightness(1.08); }

.gear-menu button {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: var(--text-sm);
  padding: var(--space-3);
  min-height: 44px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.gear-menu button:hover { background: var(--tile-hover); }
.gear-menu hr {
  border: none;
  border-top: 1px solid var(--line-ui);
  margin: var(--space-1) 0;
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
}
.modal-backdrop.hidden { display: none; }

.modal {
  background: var(--surface);
  padding: var(--space-6) var(--space-5);
  max-width: 480px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  position: relative;
  border: 1px solid var(--line-ui);
  border-radius: var(--radius-lg);
}

.modal h2 {
  margin: 0 0 var(--space-4);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.modal .close-btn {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  background: transparent;
  border: none;
  font-size: 18px;
  color: var(--faint);
  cursor: pointer;
}

.modal .add-row { display: flex; border: 1px solid var(--line-ui); margin-bottom: var(--space-4); }
.modal input[type=text], .modal textarea, .share-box input {
  flex: 1;
  padding: var(--space-3);
  border: none;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
}
.modal .add-row button, .modal .row button, .share-box button {
  font: inherit;
  font-size: var(--text-xs);
  padding: var(--space-3) var(--space-4);
  border: none;
  background: var(--accent);
  color: var(--selected-ink);
  cursor: pointer;
}
.modal .note { font-size: var(--text-xs); color: var(--faint); margin-top: var(--space-4); }
.modal .word-list { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.modal .word-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--line-ui);
  font-size: var(--text-xs);
  font-family: var(--mono);
}
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--space-5); }
.stats-grid > div {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-sm);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line-ui);
}
.share-box { display: flex; border: 1px solid var(--line-ui); margin: var(--space-3) 0; }

.leaderboard-row, .history-row {
  display: flex;
  justify-content: space-between;
  padding: var(--space-2) 0;
  font-size: var(--text-xs);
  border-bottom: 1px solid var(--line-ui);
}
