.screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  animation: screen-enter 0.24s var(--ease) both;
}

@keyframes screen-enter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---------- buttons ---------- */
.btn {
  font-size: 15px;
  font-weight: 600;
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  letter-spacing: -0.01em;
  transition: transform 0.1s var(--ease), background 0.15s var(--ease), border-color 0.15s var(--ease),
    box-shadow 0.15s var(--ease);
}
.btn:active {
  transform: scale(0.975);
}
.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.22) inset, 0 8px 22px -12px var(--accent);
}
.btn-ghost {
  background: var(--surface-3);
  color: var(--text);
  border: 1px solid var(--line);
}
.btn-lg {
  font-size: 16.5px;
  padding: 16px 28px;
  min-height: 54px;
  border-radius: var(--radius);
}
.btn-arrow {
  transition: transform 0.18s var(--ease);
}

/* Round icon buttons in headers. Neutral until touched, so a bar with three of
   them doesn't read as three competing calls to action. */
.icon-btn {
  width: 44px;
  height: 44px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 19px;
  line-height: 1;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  background: var(--surface-2);
  border: 1px solid var(--line);
  transition: background 0.15s var(--ease), color 0.15s var(--ease), transform 0.1s var(--ease);
}
.icon-btn:active {
  transform: scale(0.94);
}

@media (hover: hover) {
  .btn-primary:hover {
    filter: brightness(1.06);
  }
  .btn-primary:hover .btn-arrow {
    transform: translateX(3px);
  }
  .btn-ghost:hover {
    background: var(--surface-hover);
    border-color: var(--line-strong);
  }
  .icon-btn:hover {
    background: var(--surface-hover);
    color: var(--text);
  }
}

/* ---------- title screen ---------- */
.screen-title {
  align-items: center;
  /* Centred by an auto margin on the card rather than by the line itself, so a
     window too short for the poster — a phone on its side — scrolls to the rest
     of it instead of hiding the top off the edge. */
  justify-content: flex-start;
  overflow-y: auto;
  padding: 24px calc(20px + var(--safe-right)) calc(24px + var(--safe-bottom)) calc(20px + var(--safe-left));
  background:
    radial-gradient(ellipse 70% 50% at 15% 0%, rgba(56, 217, 201, 0.13), transparent 70%),
    radial-gradient(ellipse 60% 50% at 90% 100%, rgba(255, 200, 87, 0.08), transparent 70%),
    var(--board-bg);
}
/* A faint grid behind the title, the same one the board is drawn on. */
.screen-title::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: linear-gradient(rgba(255, 255, 255, 0.028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.028) 1px, transparent 1px);
  background-size: 44px 44px;
  mask-image: radial-gradient(ellipse 80% 70% at 50% 40%, #000 20%, transparent 75%);
  pointer-events: none;
}
.title-card {
  position: relative;
  margin-block: auto;
  width: 100%;
  max-width: 540px;
  min-width: 0;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
.title-logo {
  display: block;
  width: 96px;
  height: auto;
  object-fit: contain;
  filter: drop-shadow(7px 8px 0 rgba(255, 79, 139, .82)) drop-shadow(0 18px 22px rgba(49, 215, 244, .24));
  transform: rotate(-4deg);
}
.screen-title h1 {
  font-size: clamp(34px, 9vw, 52px);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.04;
  background: linear-gradient(180deg, #ffffff 10%, #a9c2ce 130%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.screen-title h1 span { color: var(--accent); -webkit-text-fill-color: var(--accent); }
/* The two ways off this screen, stacked and the same width: start the game, or
   sign in first. One column so the pair keeps its shape whatever either label
   says — the poster's rule about a solid fill still decides which is which. */
.title-actions {
  width: 100%;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}
.title-actions > .btn { width: 100%; }
.title-stats {
  display: flex;
  gap: 10px;
  width: 100%;
  max-width: 380px;
}
.title-profile {
  width: 100%;
  max-width: 460px;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px;
  text-align: left;
  background: linear-gradient(135deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2);
}
.level-orb {
  width: 58px;
  height: 58px;
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--accent);
  background: var(--accent-soft);
  border: 2px solid var(--accent-line);
}
.level-orb span { font-size: 9px; font-weight: 800; letter-spacing: .1em; }
.level-orb strong { font-size: 23px; line-height: 1; }
.title-profile-main { flex: 1; min-width: 0; }
.title-profile-line { display: flex; justify-content: space-between; gap: 10px; margin-bottom: 7px; }
.title-profile-line strong { font-size: 13px; }
.title-profile-line span { color: var(--text-faint); font-size: 11px; }
.xp-meter { height: 7px; overflow: hidden; border-radius: var(--pill); background: var(--surface-3); }
.xp-meter span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--accent), #81f0b3); box-shadow: 0 0 12px rgba(56,217,201,.35); }
.title-mini-stats { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 9px; color: var(--text-muted); font-size: 10.5px; }
.title-mini-stats span { display: inline-flex; align-items: center; gap: 4px; }
.title-mini-stats .ui-icon { width: 12px; height: 12px; color: var(--warm); }
.title-mini-stats span:nth-child(2) .ui-icon { color: #ff7e61; }
.title-mini-stats span:nth-child(3) .ui-icon { color: var(--accent); }
.title-mini-stats b { color: var(--text); }

/* Localized title copy occupies the original English geometry: a box holds its
   tuned size so switching language does not shift the artwork around it, but
   only ever grows from there — never clips. Every width here is a floor, not a
   cap, because a string that does not fit (a longer Hebrew label, or the arcade
   theme's heavier button text) has to be given room rather than spilled out
   past the edge of its own box. */
.screen-title h1 { min-width: 260px; }
#start-btn { min-width: 224px; }
#start-label { flex: 1 1 auto; min-width: 0; line-height: 19px; white-space: nowrap; }
.title-mini-stats > span:nth-child(1) { flex: 0 0 auto; min-width: 55px; }
.title-mini-stats > span:nth-child(2) { flex: 0 0 auto; min-width: 82px; }
.title-mini-stats > span:nth-child(3) { flex: 0 0 auto; min-width: 60px; }
#title-stars { flex: 0 0 auto; min-width: 39px; }
#title-streak { flex: 0 0 auto; min-width: 66px; }
#title-badges { flex: 0 0 auto; min-width: 44px; }
.title-stat {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 8px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.title-stat strong {
  font-size: 20px;
  font-weight: 650;
  letter-spacing: -0.02em;
}
.title-stat span {
  font-size: 11.5px;
  color: var(--text-faint);
  letter-spacing: 0.02em;
}

/* ---------- lesson list ---------- */
.app-header {
  display: flex;
  align-items: center;
  gap: 12px;
  /* The right pad clears the corner the fixed language switch owns, so the rank
     box ends up beside it rather than under it. */
  padding: calc(12px + var(--safe-top)) calc(14px + var(--corner-lane) + var(--safe-right)) 12px calc(14px + var(--safe-left));
  border-bottom: 1px solid var(--line);
  background: var(--surface-1);
  flex: none;
}
.app-header-title {
  flex: 1;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.app-header-title h2 {
  font-size: 17px;
}
.app-header-sub {
  font-size: 12px;
  color: var(--text-faint);
}
/* The header is what the player menu hangs off, on both screens that host it. */
.app-header {
  position: relative;
}
.header-spacer {
  width: 40px;
  flex: none;
}
.header-rank {
  width: 44px;
  height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-sm);
}
.header-rank span { font-size: 8px; font-weight: 800; letter-spacing: .08em; line-height: 1; }
.header-rank strong { font-size: 17px; line-height: 1.15; }
.lessons-scroll {
  flex: 1;
  position: relative;
  overflow-y: auto;
  padding: 10px calc(16px + var(--safe-right)) calc(36px + var(--safe-bottom)) calc(16px + var(--safe-left));
  max-width: 760px;
  margin: 0 auto;
  width: 100%;
}

/* ---------- the mission board ----------
   The map is a development board: pads for missions, etched buses between
   them, silkscreen for the few things that have to be written down. Anything
   that would have been a sentence is a title/aria-label instead. */
/* The mission board is still a board — traces, pads, silkscreen — but it is
   built out of the title screen's colours rather than a set of its own: the
   same navy ground, the same faint blue grid with its two stray vias, and the
   same four inks. Which of them a thing wears follows the poster's own rule:
   solid fills and hard offset shadows are for the things you press, everything
   else is quiet. */
.screen-lessons {
  /* Two materials, and they are not the same thing. The chrome — panels,
     plating, outlines — is the poster's blue. The conductor is copper: the
     buses etched across the ground, the pins on every chip, and the run that
     daisy-chains one mission to the next. */
  --etch: var(--play-blue);
  --etch-dim: #2a4c78;
  --copper: #b6813f;
  --run-dead: rgba(182, 129, 63, 0.5);
  --run-live: var(--accent);
  --silk: var(--text);
  --mask: var(--board-bg);
  background-color: var(--mask);
  background-image:
    radial-gradient(circle at 18% 22%, rgba(255, 79, 139, 0.18) 0 2px, transparent 3px),
    radial-gradient(circle at 82% 72%, rgba(49, 215, 244, 0.2) 0 2px, transparent 3px),
    linear-gradient(rgba(91, 114, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(91, 114, 255, 0.045) 1px, transparent 1px);
  background-size: 160px 160px, 190px 190px, 36px 36px, 36px 36px;
}
/* The etching, behind everything the screen has to say. Copper, because that
   is what a board's buses are made of and the ground being the poster's navy
   does not change the metal running across it. */
.board-traces-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.board-traces {
  display: block;
}
.board-traces-lines path {
  fill: none;
  stroke: var(--copper);
  stroke-width: 1.6;
  stroke-linecap: round;
  opacity: 0.18;
}
.board-traces-vias circle {
  fill: none;
  stroke: var(--copper);
  stroke-width: 2.4;
  opacity: 0.22;
}
/* Everything else on this screen sits on top of it. */
.screen-lessons > .board-header,
.screen-lessons > .lessons-scroll {
  position: relative;
  z-index: 1;
}
/* And the header sits over the missions rather than beside them, because the
   player menu hangs out of its bottom edge: level with the board, the chips —
   drawn after it — would come up through the panel. */
.screen-lessons > .board-header {
  z-index: 2;
}
.board-header {
  background: rgba(9, 20, 42, 0.92);
  border-bottom: 2px solid var(--etch-dim);
}
.board-silk {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-size: 0;
}
.silk-mark {
  font-family: var(--font-technical);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--silk);
  opacity: 0.72;
}
.silk-count {
  font-family: var(--font-technical);
  font-size: 12px;
  font-weight: 700;
  color: var(--warm);
}

/* The instrument band: the level, the meter under it, and four running totals.
   No panel round any of it. A card with a border is a way of saying "these
   things belong together" that costs a rectangle and a line — spacing says the
   same thing for nothing, and it lets the board's own copper carry on behind
   the numbers instead of being blanked out in three places. */
.board-readout {
  display: flex;
  flex-direction: column;
  gap: 13px;
  margin-top: 18px;
  padding: 0;
  border: 0;
  background: none;
}
.readout-level {
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
}
.readout-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
/* The largest thing said on this screen after the missions themselves. */
.readout-rank {
  font-family: var(--font-technical);
  font-size: 27px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--accent);
}
.readout-xp {
  font-family: var(--font-technical);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--silk);
}
/* The one line drawn on the band, because it is the only one carrying a value.
   A hairline the full width of the board, with the head of the fill lit. */
.readout-level .xp-meter {
  height: 3px;
  border: 0;
  border-radius: var(--pill);
  background: rgba(133, 176, 255, 0.13);
}
.readout-level .xp-meter span {
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
}
/* Mark over figure, the way an instrument face is laid out — and the figure is
   the size of something worth reading rather than a caption. */
.readout-gauges {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 10px 18px;
}
.gauge {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  /* Columns of a fixed width rather than four things spaced apart: figures of
     different lengths otherwise leave gaps of different sizes, and a row of
     readings that does not line up reads as an accident. */
  min-width: 58px;
  gap: 3px;
  font-family: var(--font-technical);
  font-size: 21px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--text);
}
.gauge b { display: inline-flex; align-items: baseline; gap: 4px; }
.gauge .ui-icon { width: 13px; height: 13px; color: var(--warm); }
.gauge-credits .ui-icon { color: var(--warm); }
.gauge-streak .ui-icon { color: #ff7e61; }
.gauge-badges .ui-icon { color: var(--accent); }
.gauge-bump {
  font-style: normal;
  font-size: 10px;
  font-weight: 800;
  color: var(--accent);
}

/* The day's button and the badge LEDs share the line under the band. */
.board-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* Both left-aligned rather than pushed to the two edges: the day's button
     shrinks to a line of small type once it is claimed, and a row that
     re-anchors itself when that happens reads as two different layouts. */
  gap: 14px 20px;
  margin-top: 20px;
}

/* Today's challenge: the one thing on this screen you press, so by the
   poster's rule it is the one thing with a fill and a hard shadow under it. */
.board-daily {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 9px 16px 10px;
  border-radius: var(--pill);
  color: #241b00;
  background: var(--warm);
  border: 2px solid #fff2a0;
  box-shadow: 0 3px 0 #8e6700;
  text-align: left;
}
.board-daily .daily-mark { display: inline-flex; }
.board-daily .daily-mark .ui-icon { width: 16px; height: 16px; }
.board-daily .daily-word {
  color: inherit;
  opacity: 1;
  font-size: 10px;
  letter-spacing: 0.16em;
}
.daily-pay {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-technical);
  font-size: 13px;
  font-weight: 800;
  color: #4a3600;
}
.daily-pay .ui-icon { width: 14px; height: 14px; }
.daily-pay i { width: 4px; }
.board-daily:active { transform: translateY(3px); box-shadow: 0 0 0 #8e6700; }
/* Claimed: there is nothing left to press, so it stops looking like a button. */
.board-daily.claimed {
  padding-inline: 0;
  color: var(--text-faint);
  background: none;
  border-color: transparent;
  box-shadow: none;
}
.board-daily.claimed:active { transform: none; }
.board-daily.claimed .daily-mark .ui-icon { color: var(--accent); }

/* Badges, as the row of indicator LEDs along the board's edge — which is what
   they finally look like now there is no card drawn round them. */
.board-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  padding: 0;
  border: 0;
  background: none;
}
.badge-led {
  display: grid;
  place-items: center;
  width: 27px;
  height: 27px;
  color: rgba(133, 176, 255, 0.32);
  border: 1px solid rgba(133, 176, 255, 0.2);
  border-radius: 50%;
  background: none;
}
.badge-led .ui-icon { width: 14px; height: 14px; }
.badge-led.earned {
  color: #241b00;
  background: var(--warm);
  border-color: #fff2a0;
  box-shadow: 0 0 12px rgba(255, 220, 77, 0.35);
}

/* A stage is a zone of the board, marked out in silkscreen. */
.board-zone {
  position: relative;
  /* Over the chain, which is drawn into the scroll box behind everything. */
  z-index: 1;
  margin-top: 22px;
  padding: 26px 12px 14px;
  border: 1px dashed rgba(133, 176, 255, 0.24);
  border-radius: var(--radius-sm);
}
.zone-silk {
  position: absolute;
  top: -8px;
  left: 12px;
  right: 12px;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 6px;
  background: var(--mask);
}
.zone-meter {
  flex: 1;
  height: 3px;
  border-radius: var(--pill);
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}
.zone-meter span { display: block; height: 100%; background: var(--accent); }

/* A project inside a stage gets its own run of pads under its name. */
.zone-group-mark {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 16px 0 8px;
}
.zone-group-mark::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--etch-dim);
}
.zone-body > .zone-pads + .zone-group-mark { margin-top: 22px; }

/* The missions, as chips on one long run.
 *
 * Each is an IC: a body with pins down both sides, dropped onto the board a
 * little above or below its neighbour the way a router actually places parts.
 * What wires them together is not here — see `mission-chain.js`, which routes a
 * single polyline through every chip on the board from where they actually
 * ended up, because nothing written in CSS survives a grid that re-wraps, a
 * stage header between two rows, and a reading direction that reverses. */
.zone-pads {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: 18px 14px;
  align-items: start;
  --stagger: 14px;
}
.chip-pad {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  height: 90px;
  padding-top: 8px;
  border: 0;
  background: none;
}
/* Every other chip is set down a little lower, so a row reads as parts placed
   along a track rather than as a row of tiles. */
.chip-pad:nth-child(even) { margin-top: var(--stagger); }

/* The chain itself, behind every chip and in front of the etching. */
.mission-chain-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.mission-chain path {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.chain-dead { stroke: var(--run-dead); stroke-width: 2; }
.chain-live {
  stroke: var(--run-live);
  stroke-width: 2;
  opacity: 0.8;
  filter: drop-shadow(0 0 4px rgba(49, 215, 244, 0.4));
}
/* The current itself: one short bright dash, sent down the live length on a
   loop by the screen that drew the path — the dash has to know how long the
   run is, and only the thing that measured it does. */
.chain-flow {
  stroke: #dffaff;
  stroke-width: 3;
  filter: drop-shadow(0 0 7px var(--run-live));
}
/* The chip's body. Narrower than it is tall, the way a small DIP package is,
   with the pin-1 dot in the corner the silkscreen would mark. */
.pad-face {
  position: relative;
  display: grid;
  place-items: center;
  width: 44px;
  height: 46px;
  color: var(--silk);
  background:
    radial-gradient(circle at 6px 6px, rgba(255, 255, 255, 0.3) 0 1.4px, transparent 2px),
    linear-gradient(160deg, #16294a, #0d1a30);
  border: 1px solid var(--etch-dim);
  border-radius: 6px;
  /* A part sitting on the board rather than printed on it, the way the title
     screen's mark and its level orb do: a hard offset, no blur. */
  box-shadow: 0 2px 0 #060f22;
  transition: transform 0.12s var(--ease), border-color 0.15s var(--ease),
    box-shadow 0.15s var(--ease), background 0.15s var(--ease), color 0.15s var(--ease);
}
/* Three pins a side, the middle one level with the run so the track meets
   metal rather than the gap between two of them. */
.pad-face::before,
.pad-face::after {
  content: '';
  position: absolute;
  top: 8px;
  bottom: 8px;
  width: 5px;
  background: repeating-linear-gradient(180deg, var(--copper) 0 4px, transparent 4px 13px);
}
.pad-face::before { inset-inline-start: -5px; }
.pad-face::after { inset-inline-end: -5px; }
.pad-face .ui-icon { width: 22px; height: 22px; }
/* Given a height rather than left to its line box: the run is positioned from
   the top of the cell, so what stands above the chip has to be a known size. */
.pad-refdes {
  height: 12px;
  font-family: var(--font-technical);
  font-size: 11px;
  line-height: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--silk);
  opacity: 0.82;
}
.pad-leds { display: flex; gap: 4px; }
.pad-led {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.09);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}
.pad-led.lit {
  background: var(--warm);
  box-shadow: 0 0 7px rgba(255, 220, 77, 0.7);
}
/* Cleared: the current has reached this chip and it is lit. Edged rather than
   filled — a stage most of the way through would otherwise be a wall of solid
   colour with the one thing left to do lost in it — and it brightens as the
   surge passes, on the same clock as the run that feeds it. */
.chip-pad.is-done .pad-face {
  color: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 2px 0 #061426;
  /* --flare is how far into a lap the pulse reaches *this* chip, written on by
     the chain once it knows the distance. So a chip lights when the current
     gets to it rather than on a clock of its own. */
  animation: chip-flare var(--chain-lap, 3s) var(--ease) infinite;
  animation-delay: var(--flare, 0s);
}
@keyframes chip-flare {
  0% { box-shadow: 0 2px 0 #061426, 0 0 15px rgba(49, 215, 244, 0.6); }
  26%, 100% { box-shadow: 0 2px 0 #061426, 0 0 0 rgba(49, 215, 244, 0); }
}
/* Where the current stops: the title screen's own mark, pink shadow and all —
   the loudest thing on the board, and it flares as the surge arrives. */
.chip-pad.is-next .pad-face {
  color: #fff;
  background: var(--play-blue);
  border-color: #91a0ff;
  box-shadow: 3px 3px 0 var(--play-pink);
  animation: pad-arrive var(--chain-lap, 3s) var(--ease) infinite;
  animation-delay: var(--flare, 0s);
}
@keyframes pad-arrive {
  0% { box-shadow: 3px 3px 0 var(--play-pink), 0 0 0 6px rgba(91, 114, 255, 0.26); }
  26%, 100% { box-shadow: 3px 3px 0 var(--play-pink), 0 0 0 0 rgba(91, 114, 255, 0); }
}
/* Not yet reached: dead copper, and a body only outlined onto the board. */
.chip-pad.is-locked { opacity: 0.58; }
.chip-pad.is-locked .pad-face { border-style: dashed; box-shadow: none; }
.chip-pad.is-locked .pad-face::before,
.chip-pad.is-locked .pad-face::after {
  background: repeating-linear-gradient(180deg, var(--run-dead) 0 4px, transparent 4px 13px);
}
/* Pressed, a part goes down onto the board and its shadow goes with it. */
.chip-pad:not(:disabled):active .pad-face { transform: translateY(2px); box-shadow: 0 0 0 #060f22; }
.chip-pad.is-next:not(:disabled):active .pad-face { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--play-pink); }
@media (hover: hover) {
  .chip-pad:not(:disabled):hover .pad-face { border-color: var(--silk); transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .chip-pad .pad-face { animation: none; }
}

/* What a clear is worth is three chips: the mark for finishing a mission is a
   part, on a screen made of parts. A seated chip is lit through its body and
   warm at the pins; an empty socket is the outline on its own. */
.rating-chips { display: inline-flex; gap: 2px; flex: none; }
.rating-chip { color: var(--surface-hover); }
.rating-chip.earned { color: var(--warm); filter: drop-shadow(0 0 4px rgba(255,200,87,.18)); }
.rating-chip.earned .ui-icon rect { fill: currentColor; fill-opacity: .16; }
.rating-chip .ui-icon { width: 12px; height: 12px; stroke-width: 1.4; }

/* ---------- game: header ---------- */
.screen-game {
  background: var(--board-bg);
}
.game-hud {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  /* Same corner, same clearance — otherwise the switch sits on the hint button. */
  /* No corner lane reserved here: during a mission the language switch is not
     in the corner at all, it is down in the controls with everything else. */
  padding: calc(8px + var(--safe-top)) calc(12px + var(--safe-right)) 11px calc(12px + var(--safe-left));
  border-bottom: 1px solid var(--line);
  background: var(--surface-1);
  flex: none;
}
.hud-goal {
  flex: 1;
  min-width: 0;
}
.hud-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}
.hud-chip {
  flex: none;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: var(--radius-xs);
  padding: 3px 7px;
}
.hud-title {
  font-size: 15px;
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.hud-run {
  flex: none;
  font-size: 9.5px;
  font-weight: 700;
  color: var(--warm);
  border: 1px solid rgba(255, 200, 87, .25);
  background: var(--warm-soft);
  border-radius: var(--pill);
  padding: 3px 7px;
  white-space: nowrap;
}
.hud-run.lost { color: var(--text-faint); border-color: var(--line); background: var(--surface-2); }
@media (max-width: 560px) { .hud-run { display: none; } }
/* The goal doubles as the way back into the briefing, so it is a button — but
   it has to look like the line of text it is, not like another control. */
.hud-goal-text {
  display: block;
  width: 100%;
  text-align: left;
  margin-top: 2px;
  padding: 0;
  font-size: 13px;
  line-height: 1.35;
  color: var(--text-muted);
  border-bottom: 1px dashed transparent;
}
.hud-goal-text span {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
@media (hover: hover) {
  .hud-goal-text:hover {
    color: var(--text);
  }
}
.hud-steps {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 0;
  display: flex;
  gap: 3px;
  height: 3px;
}
.hud-step {
  flex: 1;
  border-radius: var(--pill);
  background: var(--surface-3);
  transition: background 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.hud-step.done {
  background: var(--accent);
  box-shadow: 0 0 10px rgba(56, 217, 201, 0.45);
}

/* ---------- game: stage ---------- */
.game-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.board-wrap {
  flex: 1;
  position: relative;
  min-width: 0;
  min-height: 0;
}
#board-canvas {
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

/* Anything the circuit has to say, docked under the board rather than laid
   over it: a fault to fix, and a note about what is being drawn. */
.stage-status {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 8px 10px 0;
}
.stage-status[hidden] {
  display: none;
}
.status-msg {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  font-size: 12.5px;
  line-height: 1.4;
  border-radius: var(--radius-sm);
  padding: 8px 11px;
}
.status-msg[hidden] {
  display: none;
}
.status-msg::before {
  flex: none;
  font-size: 12px;
  line-height: 1.45;
}
.status-alert {
  color: #ffd9da;
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
}
.status-info {
  color: var(--text-muted);
  background: var(--surface-1);
  border: 1px solid var(--line);
}

/* Wire colours, shown only while a wire is selected. */
.selection-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px 2px;
}
/* Opened by a class rather than by `hidden`: on the phone layout it fades and
   folds rather than vanishing, and `display: none` has nothing to animate. */
.selection-bar:not(.is-open) {
  display: none;
}
/* Colours are a wire's business. A part selected on the board brings out the
   bin and nothing else. */
.selection-bar:not(.has-colours) .selection-label,
.selection-bar:not(.has-colours) .wire-swatches {
  display: none;
}
.selection-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
  flex: none;
}
.wire-swatches {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 2px;
}
.wire-swatch {
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  background: var(--swatch);
  border: 2px solid transparent;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45);
  transition: transform 0.12s var(--ease), box-shadow 0.15s var(--ease);
}
.wire-swatch.chosen {
  box-shadow: 0 0 0 2px var(--board-bg), 0 0 0 4px var(--text);
  transform: scale(1.08);
}
/* Taking the wire out, next to the colours it could have been instead — the
   same handler as the delete circle in the fan, put where the wire is already
   being worked on so it costs no trip through the menu. */
.selection-delete {
  flex: none;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  transition: transform 0.12s var(--ease), background 0.15s var(--ease);
}
.selection-delete .ui-icon {
  width: 14px;
  height: 14px;
}
.selection-delete:active {
  transform: scale(0.9);
}

/* A mouse user expects the action to follow the selected object. This is a
   second route to the same delete command; the edge control stays in place
   for players who prefer a stable target and for touch layouts. */
.part-delete,
.part-delete[hidden] {
  display: none;
}
@media (hover: hover) and (pointer: fine) {
  .part-delete:not([hidden]) {
    position: absolute;
    /* Board-local action: above the canvas, below the stage-bar (3), meter
       (4), transient game chrome, and modal/menu layers. */
    z-index: 2;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transform: translate(-50%, -50%);
    color: var(--danger);
    background: rgba(6, 12, 22, 0.94);
    border: 1px solid var(--danger-line);
    border-radius: 50%;
    box-shadow: 0 5px 16px -6px rgba(0, 0, 0, 0.9);
    backdrop-filter: var(--g-blur);
    transition: transform 0.12s var(--ease), background 0.15s var(--ease);
  }
  .part-delete:not([hidden]):hover {
    background: var(--danger-soft);
  }
  .part-delete:not([hidden]):active {
    transform: translate(-50%, -50%) scale(0.9);
  }
  .part-delete .ui-icon {
    width: 17px;
    height: 17px;
  }
}

/* ---------- game: toolbar ---------- */
.stage-bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 7px calc(10px + var(--safe-right)) 7px calc(10px + var(--safe-left));
}
.tool-group {
  display: flex;
  gap: 0;
  padding: 2px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.tool-btn {
  min-width: 44px;
  height: 44px;
  padding: 0 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-muted);
  background: transparent;
  border-radius: var(--radius-sm);
  transition: background 0.14s var(--ease), color 0.14s var(--ease), transform 0.1s var(--ease);
}
.tool-btn:active:not(:disabled) {
  transform: scale(0.94);
}
@media (hover: hover) {
  .tool-btn:hover:not(:disabled) {
    background: var(--surface-3);
    color: var(--text);
  }
  .tool-danger:hover:not(:disabled) {
    background: var(--danger-soft);
    color: var(--danger);
  }
}
/* The flip sits on its own, since it is neither an edit nor a zoom. */
.tool-wide {
  background: var(--surface-1);
  border: 1px solid var(--line);
  padding: 0 12px;
  height: 44px;
  border-radius: var(--radius);
}
.tool-wide[aria-pressed='true'] {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
}

/* The three toolbar groups are tight on the narrowest phones, so everything
   loses a little padding. */
@media (max-width: 360px) {
  .stage-bar {
    gap: 6px;
    padding-left: calc(8px + var(--safe-left));
    padding-right: calc(8px + var(--safe-right));
  }
  .tool-btn {
    min-width: 42px;
    height: 44px;
    padding: 0 6px;
  }
  .tool-wide {
    height: 40px;
    padding: 0 9px;
  }
}

/* A borrowed language switch is laid out by whatever it was lent to, so the
   corner it owns has to be given up first: the offsets that pin it there
   outlive `position: static` and would otherwise put it back in the corner of
   its new host. */
.language-switch.is-hosted {
  position: static;
  inset: auto;
  width: auto;
  box-shadow: none;
}

/* What a part being carried out of the tray looks like on the way. Never a
   pointer target — it is under the finger, and the drop is decided by where
   the finger is, not by what the ghost happens to be over. */
.palette-ghost {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 40;
  width: 54px;
  height: 54px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--accent);
  background: rgba(6, 12, 22, 0.9);
  border: 1px solid var(--accent-line);
  box-shadow: 0 12px 30px -10px rgba(0, 0, 0, 0.8), 0 0 0 6px rgba(90, 220, 200, 0.08);
  pointer-events: none;
  animation: ghost-lift 0.16s var(--ease) both;
}
.palette-ghost .ui-icon {
  width: 24px;
  height: 24px;
}
/* Crossing onto the workbench hands the preview to the real canvas renderer,
   which draws the component itself at the position where it would land. */
.palette-ghost.is-over-canvas {
  visibility: hidden;
}
@keyframes ghost-lift {
  from { opacity: 0.4; scale: 0.7; }
  to { opacity: 1; scale: 1; }
}

/* ---------- game: the dial ----------
   The controls at every viewport size. The same buttons that once occupied a
   desktop strip are laid over the board instead: flip in one top corner,
   fit-to-view in the other, and everything else folded into a single button on
   the bottom edge that fans them out on a tap. JS sets --dx/--dy per button
   (the fan has to spread the same half-circle over five buttons or seven) and
   --step, which is how far from the middle of the fan it is; the stylesheet
   does the rest. */
.dial-only {
  display: none;
}
.stage {
  position: relative;
}
  /* Out of the column and over the board, so the canvas gets the whole stage.
     The layer itself is transparent to touch — only the buttons on it are not. */
  .stage-bar {
    position: absolute;
    inset: 0;
    padding: calc(10px + var(--safe-top)) calc(10px + var(--safe-right)) calc(12px + var(--safe-bottom)) calc(10px + var(--safe-left));
    display: block;
    pointer-events: none;
    z-index: 3;
  }
  .stage-bar .tool-group,
  /* Borrowed from the tray. Its own box takes no part in the dial's layout —
     the buttons inside it are what gets placed on the ring. */
  .stage-bar .palette-items {
    display: contents;
  }

  /* The name of the thing, written over the circle rather than on it — a
     46px disc has room for an icon or a word, not both. Drawn from an
     attribute so it costs no element inside the button, which matters for the
     language switch: its own face is set as text content, and a child would be
     wiped every time the language changed. Never a tap target. */
  .stage-bar .tool-btn:not(#flip-btn):not(#zoom-reset)::before {
    content: attr(data-dial-label);
    position: absolute;
    bottom: calc(100% + 4px);
    left: 50%;
    transform: translateX(calc(-50% + var(--lx, 0px)));
    padding: 1px 5px;
    border-radius: 6px;
    background: rgba(6, 12, 22, 0.78);
    max-width: 70px;
    font-family: var(--g-mono, ui-monospace, monospace);
    font-size: 8.5px;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: 0.02em;
    text-align: center;
    color: var(--g-muted, #9fadc0);
    text-shadow: 0 1px 3px rgba(4, 8, 14, 0.9), 0 0 6px rgba(4, 8, 14, 0.8);
    pointer-events: none;
  }

  /* A page has opened, so the root's own buttons fold back into the middle
     rather than vanishing — the ring stays put and its contents change. The
     five ids in the :not()s are what makes this outrank the .dial-open rule
     below; specificity counts ids first, whichever way round they are written. */
  .dial-page .tool-btn:not(#flip-btn):not(#zoom-reset):not(#dial-back):not(#world-btn):not(#pinmap-btn) {
    transform: translate(0, 0) scale(0.4);
    opacity: 0;
    pointer-events: none;
  }
  .dial-only {
    display: inline-flex;
  }
  .dial-only[hidden] {
    display: none;
  }

  /* Every control on this layer is a circle. */
  .stage-bar .tool-btn {
    position: absolute;
    width: 46px;
    height: 46px;
    min-width: 0;
    padding: 0;
    border-radius: 50%;
    background: var(--surface-1);
    border: 1px solid var(--line);
    box-shadow: var(--shadow-2);
    pointer-events: auto;
  }

  /* The two that are always there, in the corners they belong to. */
  #flip-btn {
    top: calc(10px + var(--safe-top));
    left: calc(10px + var(--safe-left));
  }
  #zoom-reset {
    top: calc(10px + var(--safe-top));
    right: calc(10px + var(--safe-right));
  }

  /* And the third: the way into the world controls, on the lessons that have
     any. It is not in the fan, because the weather is worked *while* watching
     what it does to the board — a control you have to open a menu to reach is
     a control you stop reaching for. Halfway down the right edge, clear of
     both top corners and of the fan at the bottom, and clear of the strand
     colours down the other side.

     Same edge in both languages, deliberately: like the selection bar, this is
     reached for mid-thought, and the two corner buttons swap sides in Hebrew
     precisely because they are not. */
  #world-btn {
    top: 50%;
    right: calc(10px + var(--safe-right));
    margin-top: -23px;
  }
  /* The pin map's bubble, on the same edge and reached for the same way: with
     the board already in mind, so it keeps the right in both languages. Above
     the world button where there is one, and in its place where there is not —
     the ring below it measures its own arc against wherever this lands. */
  #pinmap-btn {
    top: 50%;
    right: calc(10px + var(--safe-right));
    margin-top: -23px;
  }
  .stage-bar.has-world #pinmap-btn {
    margin-top: -145px;
  }
  #pinmap-btn[aria-expanded='true'] {
    color: var(--accent-ink);
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: 0 0 0 6px var(--accent-soft), var(--shadow-2);
  }

  /* Its ring is out, and it is the middle of that ring rather than a button
     beside it — so it lights in the ring's own colour, the way the dial's own
     button turns into the cross that shuts it. */
  #world-btn[aria-expanded='true'] {
    color: var(--world-ink);
    background: var(--world);
    border-color: var(--world);
    box-shadow: 0 0 0 6px var(--world-soft), var(--shadow-2);
  }

  /* The pin map itself: a card hung off its bubble on the same edge, and
     reference rather than a dialog — a tap on the board leaves it open, the
     way the world ring stays out while its weather is being watched. */
  .pinmap-panel {
    position: absolute;
    z-index: 4;
    top: calc(66px + var(--safe-top));
    right: calc(66px + var(--safe-right));
    width: min(268px, calc(100vw - 96px));
    max-height: calc(100% - 150px);
    overflow-y: auto;
    padding: 10px 12px 11px;
    color: var(--g-text, #eef2f8);
    background: var(--g-ink, rgba(6, 12, 22, 0.94));
    border: 1px solid var(--g-line-strong, rgba(255, 255, 255, 0.17));
    border-radius: 13px;
    box-shadow: var(--shadow-3);
    backdrop-filter: var(--g-blur);
    pointer-events: auto;
  }
  .pinmap-panel[hidden] { display: none; }

  /* The button the rest come out of. */
  .dial-fab {
    position: absolute;
    left: 50%;
    bottom: calc(12px + var(--safe-bottom));
    width: 56px;
    height: 56px;
    margin-left: -28px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--bg);
    background: var(--accent);
    box-shadow: var(--shadow-3);
    pointer-events: auto;
    /* Above the sheet, not under it: whatever is open, the button that closes
       it again is the one already under the thumb. */
    z-index: 5;
    transition: transform 0.26s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s var(--ease);
  }
  .dial-fab:active {
    transform: scale(0.92);
  }

  /* Said once, in the only place it can be pointed at: over the button the
     tray is behind. It is a label on a button rather than a dialog, because
     the answer is one tap away and a dialog would be in front of it. The
     button breathes underneath so the eye goes to the button, not the pill. */
  .dial-coach {
    position: absolute;
    left: 50%;
    bottom: calc(80px + var(--safe-bottom));
    transform: translateX(-50%);
    z-index: 5;
    padding: 7px 12px;
    border-radius: var(--pill);
    background: var(--accent);
    color: var(--accent-ink, #04121a);
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
    box-shadow: var(--shadow-3);
    pointer-events: none;
    animation: coach-in .3s var(--ease) both;
  }
  .dial-coach::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    margin-left: -6px;
    border: 6px solid transparent;
    border-top-color: var(--accent);
    border-bottom: 0;
  }
  @keyframes coach-in {
    from { opacity: 0; transform: translate(-50%, 6px); }
    to { opacity: 1; transform: translate(-50%, 0); }
  }
  /* A slow breath under the pill: enough to find with the corner of an eye,
     not enough to hurry anybody. */
  /* The breath is a ring on a pseudo-element that only ever changes
     transform and opacity, so the compositor plays it without repainting.
     Its first draft animated the button's own box-shadow spread, which is a
     repaint of the button every frame; over a canvas that repaints too, a
     Galaxy A8 measured 18 fps on mission 1 with it and 37 without. */
  .stage-bar.coach-parts .dial-fab::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: 0 0 0 6px rgba(56, 217, 201, .5);
    pointer-events: none;
    animation: coach-breathe 1.9s var(--ease) infinite;
  }
  @keyframes coach-breathe {
    0%, 100% { transform: scale(1); opacity: .9; }
    55% { transform: scale(1.3); opacity: 0; }
  }
  @media (prefers-reduced-motion: reduce) {
    .dial-coach { animation: none; }
    .stage-bar.coach-parts .dial-fab::after { animation: none; }
  }
  .dial-fab-glyph {
    display: inline-flex;
    transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  /* Plus turns into a cross. One glyph, one rotation — no icon swap to go
     wrong halfway through the turn. */
  .dial-open .dial-fab {
    color: var(--accent);
    background: var(--g-ink, rgba(6, 12, 22, 0.92));
    border: 1px solid var(--g-line-strong, rgba(255, 255, 255, 0.17));
    backdrop-filter: var(--g-blur);
  }
  .dial-open .dial-fab-glyph {
    transform: rotate(135deg);
  }

  /* The fan. Closed, every button is folded into the middle of the one that
     opens them, at nothing, and inert. */
  .stage-bar .tool-btn:not(#flip-btn):not(#zoom-reset):not(#world-btn):not(#pinmap-btn) {
    left: 50%;
    bottom: calc(12px + var(--safe-bottom));
    margin-left: -23px;
    margin-bottom: 5px;
    transform: translate(0, 0) scale(0.4);
    opacity: 0;
    pointer-events: none;
    transition:
      transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1),
      opacity 0.18s var(--ease);
    transition-delay: calc(var(--step, 0) * 22ms);
  }
  .dial-open .tool-btn:not(#flip-btn):not(#zoom-reset):not(#world-btn):not(#pinmap-btn) {
    transform: translate(var(--dx, 0px), var(--dy, 0px)) scale(1);
    opacity: 1;
    pointer-events: auto;
  }
  /* Opening runs from the middle of the fan outwards, closing from the outside
     back in, so the two halves of the gesture mirror each other. */
  .dial-open .tool-btn:not(#flip-btn):not(#zoom-reset):not(#world-btn):not(#pinmap-btn) {
    transition-delay: calc(var(--step, 0) * 26ms);
  }
  .stage-bar .tool-btn:disabled {
    opacity: 0;
  }

  /* The rail does not come out on this layout at all. Both of the things it
     holds — the tray and the world controls — are pages of the dial now, laid
     out as circles over the board, so there is nothing left for a sheet to
     slide up and no strip of screen it has to be given. It is still where
     those controls live between visits, which is why it is hidden rather than
     taken apart. */
  /* (Hidden by the .screen-game rule further down: a bare class here loses to
     the rail's own later declarations, and a media query buys no specificity.) */

  /* Whatever the circuit has to say floats over the top of the board rather
     than taking a strip under it — a message lasts a few seconds and the
     board lasts the whole mission. A sentence read through a ring of circles
     is not read at all, so while the root fan is out the note stands down; a
     page of the dial leaves it be, because the world controls are worked
     precisely to watch what it says. */
  .stage-status {
    position: absolute;
    z-index: 3;
    top: calc(10px + var(--safe-top));
    left: calc(66px + var(--safe-left));
    right: calc(66px + var(--safe-right));
    padding: 0;
    pointer-events: none;
  }
  .status-msg {
    background: var(--g-ink, rgba(6, 12, 22, 0.92));
    backdrop-filter: var(--g-blur);
    box-shadow: var(--shadow-2);
    font-size: 11.5px;
    padding: 7px 10px;
  }
  .stage:has(.stage-bar.dial-open:not(.dial-page)) .stage-status,
  .stage:has(.stage-bar.dial-open:not(.dial-page)) .selection-bar {
    opacity: 0.12;
  }
  .stage-status,
  .selection-bar {
    transition: opacity 0.2s var(--ease);
  }

  /* The strand's colour, as a line of bubbles down the edge of the screen.
     It used to be a strip along the bottom, which cost the board a row of its
     height for as long as a wire was selected and put the swatches directly
     under the fan they share the corner with. Down the side they cost nothing
     and sit where the board is emptiest; where the screen is too short for
     one column — a phone held sideways — they wrap into a second. */
  /* Kept on the page whether a wire is selected or not, so that picking one up
     and letting it go are both movements rather than a substitution. Written
     with the same :not() the rule it is overriding uses, because that one
     carries two classes and a media query buys no specificity. */
  .selection-bar,
  .selection-bar:not(.is-open) {
    position: absolute;
    z-index: 3;
    top: calc(66px + var(--safe-top));
    bottom: calc(74px + var(--safe-bottom));
    left: calc(10px + var(--safe-left));
    right: auto;
    display: flex;
    flex-flow: column wrap;
    align-items: flex-start;
    align-content: flex-start;
    gap: 9px;
    padding: 0;
    pointer-events: none;
  }
  /* Dealt out from the edge one after another, and gathered back in the same
     order. `--step` is written on each button where it is built, because the
     stagger has to survive the line wrapping into a second column. */
  .selection-label,
  .wire-swatch,
  .selection-delete {
    transform: translateX(calc(var(--slide, -1) * 16px)) scale(0.4);
    opacity: 0;
    transition:
      transform 0.26s cubic-bezier(0.34, 1.4, 0.64, 1),
      opacity 0.16s var(--ease),
      box-shadow 0.15s var(--ease);
    transition-delay: calc(var(--step, 0) * 22ms);
  }
  .selection-bar.is-open .selection-label,
  .selection-bar.is-open .wire-swatch,
  .selection-bar.is-open .selection-delete {
    transform: translateX(0) scale(1);
    opacity: 1;
  }
  .selection-bar.is-open .wire-swatch.chosen {
    transform: translateX(0) scale(1.12);
  }
  .selection-bar:not(.is-open) .wire-swatch,
  .selection-bar:not(.is-open) .selection-delete {
    pointer-events: none;
  }
  .selection-label {
    width: 44px;
    font-size: 9.5px;
    letter-spacing: 0.1em;
    text-align: center;
    text-shadow: 0 1px 3px rgba(4, 8, 14, 0.9);
  }
  .wire-swatches {
    display: contents;
  }
  .wire-swatch {
    width: 44px;
    height: 44px;
    pointer-events: auto;
    border: 2px solid var(--g-line-strong, rgba(255, 255, 255, 0.17));
    box-shadow: 0 4px 12px -4px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(0, 0, 0, 0.5);
  }
  .selection-bar.is-open .wire-swatch.chosen {
    box-shadow: 0 0 0 2px rgba(6, 12, 22, 0.92), 0 0 0 4px var(--text);
  }
  /* Last down the line, and the only one that is not a colour — a full-sized
     circle like the rest, so it is as easy to hit and as hard to hit by
     accident. */
  .selection-delete {
    width: 44px;
    height: 44px;
    pointer-events: auto;
    background: rgba(6, 12, 22, 0.92);
    border: 1px solid var(--danger-line);
    backdrop-filter: var(--g-blur);
    box-shadow: 0 4px 12px -4px rgba(0, 0, 0, 0.8);
  }
  .selection-delete .ui-icon {
    width: 16px;
    height: 16px;
  }
  .selection-bar.is-open .selection-delete:active {
    transform: translateX(0) scale(0.9);
  }
@media (prefers-reduced-motion: reduce) {
  .dial-fab,
  .dial-fab-glyph,
  .stage-bar .tool-btn,
  .stage-bar .palette-item,
  .stage-bar .env-bubble,
  .selection-label,
  .wire-swatch,
  .selection-delete,
  .game-rail {
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }
}

/* ---------- game: rail ---------- */
.game-rail {
  flex: none;
  display: flex;
  flex-direction: column;
  background: var(--surface-1);
  border-top: 1px solid var(--line);
}
/* A lesson with no world controls and no tray gets no rail at all, rather than
   an empty strip with a border on it. */
.game-rail:empty,
.game-rail:not(:has(.rail-section:not([hidden]))) {
  display: none;
}
.rail-section + .rail-section {
  border-top: 1px solid var(--line-soft);
}
.rail-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 8px calc(12px + var(--safe-right)) 4px calc(12px + var(--safe-left));
  text-align: left;
}
.rail-title {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  flex: none;
}
.rail-note {
  font-size: 11px;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- game: world controls ---------- */
.env-controls {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 2px calc(12px + var(--safe-right)) 8px calc(12px + var(--safe-left));
}
.env-control {
  flex: none;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 10px 7px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.env-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.env-body {
  display: flex;
  align-items: center;
  gap: 8px;
}
.env-value {
  font-size: 12px;
  color: var(--text);
  min-width: 46px;
  text-align: right;
}
.env-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-weight: 600;
  padding: 6px 11px;
  min-height: 32px;
  border-radius: var(--pill);
  background: var(--surface-3);
  border: 1px solid var(--line);
  color: var(--text-muted);
  white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.env-toggle-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-faint);
  transition: background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.env-toggle.on {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
}
.env-toggle.on .env-toggle-dot {
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}
/* Which card is being held to the reader. One button per card rather than a
   dropdown: the same three buttons are the three circles on the dial, and a
   native select was the last widget here that could not become one. */
.env-choice {
  display: flex;
  gap: 4px;
}
.env-opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  padding: 6px 10px;
  min-height: 32px;
  border-radius: var(--pill);
  background: var(--surface-3);
  border: 1px solid var(--line);
  color: var(--text-muted);
  white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.env-opt.chosen {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
}
.env-opt .ui-icon {
  width: 14px;
  height: 14px;
}
/* The glyph a control wears on the dial, where the word is written over the
   circle instead of on it. In the rail the row's own label already says what
   the control is, so only the two that were always icons keep theirs. */
.env-bubble-face {
  display: none;
  align-items: center;
  justify-content: center;
}
.env-opt .env-bubble-face,
.env-pad .env-bubble-face {
  display: inline-flex;
}
/* The dial's face for a slider. A range input measures against its own track,
   and on the ring that track is 46 pixels wide — so the knob is a second face
   for the one value, and it exists only where the ring does. */
.env-knob {
  display: none;
}
.env-buttons {
  display: flex;
  gap: 4px;
}
.env-pad {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-xs);
  background: var(--surface-3);
  border: 1px solid var(--line);
  color: var(--text-muted);
  font-size: 13px;
  touch-action: none;
  transition: background 0.1s var(--ease), color 0.1s var(--ease), transform 0.1s var(--ease);
}
.env-pad.on {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
  transform: scale(0.94);
}

/* A slider that reads as a level: the part below the handle is filled. */
.env-control input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  width: 116px;
  height: 22px;
  background: transparent;
  cursor: pointer;
}
.env-control input[type='range']::-webkit-slider-runnable-track {
  height: 5px;
  border-radius: var(--pill);
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--fill, 0%),
    var(--surface-hover) var(--fill, 0%) 100%
  );
}
.env-control input[type='range']::-moz-range-track {
  height: 5px;
  border-radius: var(--pill);
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--fill, 0%),
    var(--surface-hover) var(--fill, 0%) 100%
  );
}
.env-control input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  margin-top: -5.5px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--surface-1);
  box-shadow: var(--shadow-1);
}
.env-control input[type='range']::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--surface-1);
  box-shadow: var(--shadow-1);
}

/* ---------- game: parts tray ---------- */
.palette[hidden] {
  display: none;
}
.palette-chevron {
  margin-left: auto;
  font-size: 10px;
  color: var(--text-faint);
  transition: transform 0.2s var(--ease);
}
.palette.collapsed .palette-chevron {
  transform: rotate(-90deg);
}
.palette.collapsed .palette-items {
  display: none;
}
.palette-items {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 2px calc(12px + var(--safe-right)) calc(8px + var(--safe-bottom)) calc(12px + var(--safe-left));
}
.palette-item {
  flex: none;
  /* Sideways is the tray scrolling; any other direction is a part being taken
     out of it, and that one is ours to handle. */
  touch-action: pan-x;
  width: 74px;
  min-height: 60px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 7px 6px;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease), transform 0.1s var(--ease);
}
.palette-item:active:not(:disabled) {
  transform: scale(0.96);
}
@media (hover: hover) {
  .palette-item:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-3);
  }
}
.palette-item.armed {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 1px var(--accent-line), 0 8px 20px -14px var(--accent);
}
.palette-item.disabled {
  opacity: 0.4;
}
.palette-item-icon {
  font-size: 18px;
  line-height: 1;
}
.palette-item-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 0;
  width: 100%;
}
.palette-item-label {
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  line-height: 1.2;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.palette-item-count {
  font-size: 10px;
  color: var(--text-faint);
}
.palette-item.disabled .palette-item-count {
  color: var(--danger);
}
/* What fitting one costs: free while the lesson is still handing them out,
   its price once you are replacing something you burnt. */
.palette-item-cost {
  font-size: 10px;
  font-weight: 700;
  color: var(--warm);
}
.palette-item-cost.is-free {
  font-weight: 600;
  color: var(--text-faint);
}
.palette-item.unaffordable .palette-item-cost {
  color: var(--danger);
}
/* The balance, in the tray's own header — the only place the price of a
   replacement is ever weighed against it. */
.palette-credits {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
}
.palette-credits svg {
  width: 13px;
  height: 13px;
}
.counter-bump {
  display: block;
  font-style: normal;
  font-size: 9.5px;
  font-weight: 700;
  color: var(--accent);
}

@media (min-width: 900px) and (min-height: 560px) {
  .hud-goal-text span {
    -webkit-line-clamp: 2;
  }
}

/* ---------- modal ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(4, 8, 12, 0.62);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
  z-index: 10;
  opacity: 0;
  transition: opacity 0.17s var(--ease);
}
.modal-overlay.is-open {
  opacity: 1;
}
.modal-overlay[hidden] {
  display: none;
}
.modal-card {
  position: relative;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-bottom: none;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: 22px 20px calc(20px + var(--safe-bottom));
  width: 100%;
  max-height: 84vh;
  overflow-y: auto;
  box-shadow: var(--shadow-3);
  transform: translateY(14px);
  transition: transform 0.2s var(--ease);
}
.modal-overlay.is-open .modal-card {
  transform: translateY(0);
}
/* A hairline of colour along the top edge says what kind of news this is
   without a coloured card shouting it. */
.modal-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--accent);
  opacity: 0.5;
}
.modal-card.tone-win::before {
  background: linear-gradient(90deg, var(--accent), var(--warm));
  opacity: 1;
}
.modal-card.tone-danger::before {
  background: var(--danger);
  opacity: 1;
}
.modal-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 34px;
  height: 34px;
  font-size: 14px;
  background: transparent;
  border-color: transparent;
}
.modal-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 7px;
}
.tone-danger .modal-eyebrow {
  color: var(--danger);
}
.modal-card h2 {
  font-size: 21px;
  margin-bottom: 12px;
  padding-right: 34px;
}
.modal-card p {
  color: var(--text-muted);
  font-size: 14.5px;
  line-height: 1.62;
  margin-bottom: 14px;
}
.modal-card p strong {
  color: var(--text);
  font-weight: 650;
}
.modal-goal {
  padding: 11px 13px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 14px !important;
}
.modal-goal strong {
  display: block;
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent) !important;
  margin-bottom: 3px;
}
/* "Don't show this again" under a note the player has just read. A label, so
   the words are as much of the target as the box is. */
.modal-check {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 4px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: 13.5px;
  cursor: pointer;
}
.modal-check input {
  width: 18px;
  height: 18px;
  flex: none;
  accent-color: var(--accent);
  cursor: pointer;
}

.modal-aside {
  font-size: 13px !important;
  color: var(--text-faint) !important;
}
.modal-facts {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
}
.modal-facts > div {
  flex: 1;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.modal-facts dt {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 3px;
}
.modal-facts dd {
  font-size: 14px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.modal-hint-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
  list-style: none;
  counter-reset: hint;
}
.modal-hint-item {
  position: relative;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 11px 13px 11px 40px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  counter-increment: hint;
}
.modal-hint-item::before {
  content: counter(hint);
  position: absolute;
  left: 12px;
  top: 11px;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.modal-actions[hidden] {
  display: none;
}
.modal-actions .btn {
  flex: 1;
  min-width: 140px;
}

/* ---------- walkthrough ----------
   The demonstration dialog. It is the same card as every other dialog, with a
   bench in the middle of it: the canvas keeps a fixed shape so the picture does
   not jump between steps, and the words above and below it stay short enough
   that both are readable without scrolling on a phone. */
.tutorial-card {
  display: flex;
  flex-direction: column;
}
.tutorial-lead {
  margin-bottom: 12px !important;
}
.tutorial-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  max-height: 46vh;
  margin-bottom: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  /* Matched by hand to COLORS.boardBg in src/engine/theme.js, so the frame and
     what the renderer paints inside it are the same ground. */
  background: #070B12;
}
.tutorial-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}
/* The same hairline of segments the header uses for a mission's checkpoints,
   so "how far along am I" looks the same wherever it is being asked. */
.tutorial-steps {
  display: flex;
  gap: 4px;
  margin-bottom: 11px;
}
.tutorial-step {
  flex: 1;
  height: 3px;
  border-radius: 2px;
  background: var(--surface-2);
  transition: background 0.2s var(--ease);
}
.tutorial-step.done {
  background: var(--accent);
}
.tutorial-caption {
  min-height: 3.2em;
  color: var(--text) !important;
}
.modal-new {
  padding: 10px 12px;
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
  color: var(--text) !important;
  font-size: 13.5px !important;
}
/* Two icon buttons in the header's right-hand corner rather than one. The
   corner lane the language switch reserves is unchanged — this group sits
   inside it, not beside it. */
.hud-help {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

/* The card opens on a short. Nothing here falls: lightning is struck across the
   top of it, a flash goes off where it struck, and the spatter dies on its way
   down. It is over inside a second, because a short that carries on is a fault
   — and it clears before the chips seat, so the two read as a strike and then
   the hardware rather than one busy moment.

   Every bolt is a real stroke drawn on: `pathLength` normalises each path to
   100 units so one dash rule strikes them all, whatever their length, and the
   leader runs down the path before the return stroke lights it. What is aimed
   where comes in on the element from `shortCircuitBurst()`; this is only how
   it burns. */
.win-short { position: absolute; inset: 0 0 auto; height: 118px; overflow: hidden; pointer-events: none; }
.short-sky { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.short-strike {
  fill: none;
  /* A lit channel is white however hot the air around it: the ink is the halo,
     not the stroke, which is what keeps a bolt from reading as a bent wire in
     the game's own two colours. Mitred, because lightning turns corners. */
  stroke: #fff;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: miter;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  opacity: 0;
  filter: drop-shadow(0 0 2px #fff) drop-shadow(0 0 5px var(--ink)) drop-shadow(0 0 15px var(--ink));
  animation: bolt-strike .62s linear both;
}
/* The flash-over across the top edge is the heaviest and the first out; the
   branches are thinner than the bolts they leave. */
.short-strike.is-arc { stroke-width: 2.1; animation-duration: .46s; }
.short-strike.is-branch { stroke-width: 1.1; animation-duration: .5s; }
/* A leader steps down, the return stroke lights the whole channel, and what
   follows is flicker — lightning does not fade up, and it does not fade out
   evenly either. */
@keyframes bolt-strike {
  0% { opacity: .9; stroke-dashoffset: 100; }
  7% { opacity: 1; stroke-dashoffset: 42; }
  13% { opacity: 1; stroke-dashoffset: 0; }
  21% { opacity: .12; }
  28% { opacity: 1; }
  38% { opacity: .2; }
  49% { opacity: .85; }
  62% { opacity: .1; }
  72% { opacity: .55; }
  100% { opacity: 0; stroke-dashoffset: 0; }
}
/* The flash the strike throws on the card behind it, brightest on the frames
   the channel is lit. */
.short-flash {
  position: absolute; inset: -46px -10% auto; height: 150px;
  background: radial-gradient(44% 48% at 50% 44%, rgba(255,255,255,.92), rgba(93,224,230,.5) 28%, rgba(255,200,87,.18) 50%, transparent 70%);
  opacity: 0;
  animation: short-flash .58s linear both;
}
@keyframes short-flash {
  0%, 3% { opacity: 0; }
  6% { opacity: .95; }
  13% { opacity: .08; }
  19% { opacity: .72; }
  26% { opacity: .04; }
  38% { opacity: .32; }
  100% { opacity: 0; }
}
.short-spark {
  position: absolute; width: 2px; height: 7px; margin: -3.5px 0 0 -1px; border-radius: 1px;
  background: var(--ink); box-shadow: 0 0 7px var(--ink); opacity: 0;
  animation: short-spark .72s var(--ease) both;
}
/* Thrown clear, then given up to gravity — a spark is brightest the moment it
   leaves and dark long before it lands. */
@keyframes short-spark {
  0% { opacity: 0; transform: rotate(var(--spin)) scaleY(.5); }
  12% { opacity: 1; transform: translate(calc(var(--dx) * .45), calc(var(--dy) * .3)) rotate(var(--spin)); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--spin)) scaleY(.7); }
}
@media (prefers-reduced-motion: reduce) { .win-short { display: none; } }
.win-rating { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 6px 0 16px; }
.win-chips { gap: 9px; margin-bottom: 8px; }
.win-chips .rating-chip .ui-icon { width: 31px; height: 31px; stroke-width: 1.5; }
/* Earned chips are seated one after the other rather than all at once, the way
   the mission chain's pulse reaches one chip and then the next. */
.win-chips .rating-chip.earned { animation: chip-seat .34s var(--ease) both; animation-delay: calc(.1s + var(--seat, 0) * .13s); }
@keyframes chip-seat { from { opacity: 0; transform: translateY(-7px) scale(.8); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .win-chips .rating-chip.earned { animation: none; } }
.win-rating strong { font-size: 17px; }
.win-rating > span:last-child { color: var(--text-faint); font-size: 11.5px; margin-top: 2px; }
.win-rewards { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 14px; }
.win-rewards > div { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 1px 8px; padding: 11px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm); }
.win-rewards .ui-icon { grid-row: span 2; width: 22px; height: 22px; color: var(--accent); }
.win-rewards > div:nth-child(2) .ui-icon { color: var(--warm); }
.win-rewards span { color: var(--text-faint); font-size: 10px; }
.win-rewards strong { font-size: 16px; }
.win-achievements { display: flex; flex-direction: column; gap: 5px; padding: 10px 12px; margin-bottom: 14px; color: var(--warm); background: var(--warm-soft); border: 1px solid rgba(255,200,87,.25); border-radius: var(--radius-sm); }
.win-achievements > span { color: var(--text-faint); font-size: 9.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.win-achievements strong { display: flex; align-items: center; gap: 7px; font-size: 13px; }
.win-achievements .ui-icon { width: 17px; height: 17px; }

@media (min-width: 600px) {
  .modal-overlay {
    align-items: center;
    padding: 24px;
  }
  .modal-card {
    width: min(460px, 100%);
    border-radius: var(--radius-lg);
    border-bottom: 1px solid var(--line);
    padding: 26px 26px 24px;
    max-height: 82vh;
    transform: translateY(0) scale(0.97);
  }
  .modal-card::before {
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }
  .modal-overlay.is-open .modal-card {
    transform: scale(1);
  }
}

/* ---------- maker arcade art direction ----------
   The shell borrows from the thing being taught: solder-mask colour, plated
   pads and PCB traces. The trace through the mission list is the one strong
   visual idea; everything else stays chunky, direct and easy to read. */
.btn-primary {
  background: var(--warm);
  color: #241b00;
  border: 2px solid #fff2a0;
  box-shadow: 0 6px 0 #9a6f00, 0 12px 28px -12px rgba(255, 220, 77, .7);
  font-weight: 800;
}
.btn-primary:active {
  transform: translateY(4px);
  box-shadow: 0 2px 0 #9a6f00, 0 8px 18px -12px rgba(255, 220, 77, .6);
}
.btn-ghost {
  border-width: 2px;
}
.icon-btn {
  border-width: 2px;
  color: var(--text);
}

.screen-title {
  background-color: var(--board-bg);
  background-image:
    radial-gradient(circle at 18% 22%, rgba(255, 79, 139, .18) 0 2px, transparent 3px),
    radial-gradient(circle at 82% 72%, rgba(49, 215, 244, .2) 0 2px, transparent 3px),
    linear-gradient(rgba(91, 114, 255, .045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(91, 114, 255, .045) 1px, transparent 1px);
  background-size: 160px 160px, 190px 190px, 36px 36px, 36px 36px;
}
.screen-title::before {
  inset: 8% 7%;
  opacity: .65;
  background:
    linear-gradient(90deg, transparent 0 11%, var(--play-blue) 11% 11.35%, transparent 11.35% 88%, var(--play-pink) 88% 88.35%, transparent 88.35%),
    linear-gradient(0deg, transparent 0 18%, var(--play-pink) 18% 18.35%, transparent 18.35% 81%, var(--accent) 81% 81.35%, transparent 81.35%);
  mask-image: linear-gradient(to bottom, transparent, #000 24%, #000 76%, transparent);
}
.title-card {
  max-width: 620px;
  gap: 17px;
}
.title-logo {
  width: 112px;
  filter: drop-shadow(8px 9px 0 rgba(255, 79, 139, .82)) drop-shadow(0 22px 28px rgba(91, 114, 255, .5));
  transform: rotate(-5deg);
}
.screen-title h1 {
  font-size: clamp(44px, 10vw, 72px);
  line-height: .9;
  letter-spacing: -.065em;
  color: var(--text);
  background: none;
  -webkit-text-fill-color: currentColor;
  text-shadow: 0 5px 0 #1f3152;
}
.screen-title h1 span {
  display: inline-block;
  color: var(--accent);
  -webkit-text-fill-color: var(--accent);
  text-shadow: 0 5px 0 #075e75;
}
/* The name is the poster. Tracked in a little, because a wordmark of eight
   capitals wants to read as one object — and back out again in Hebrew, where
   the letters carry niqqud that crowding would sit on top of. The line box is
   given room under the baseline for the same reason. */
.wordmark { line-height: 1.02; letter-spacing: -.03em; padding-bottom: .06em; }
/* Hebrew sets the same mark in four letters, so it is given the size that
   makes it weigh the same on the poster — and a glow in place of the poster's
   hard offset, because an offset shadow repeats the niqqud five pixels lower
   and turns two vowel marks into six dots. The glow is the same neon the rest
   of the board is lit with, so the name still belongs to the screen. */
:root[data-lang='he'] .wordmark { letter-spacing: 0; font-size: clamp(52px, 12vw, 86px); }
:root[data-lang='he'] .screen-title h1 span { text-shadow: 0 0 22px rgba(49, 215, 244, .55), 0 0 6px rgba(49, 215, 244, .4); }
/* Struck like a sign coming on: two flickers and then it holds. A name that
   kept flickering would be a fault light. */
.wordmark span { animation: wordmark-strike .62s steps(1, end) both; }
@keyframes wordmark-strike {
  0%, 12% { opacity: .12; }
  18%, 30% { opacity: 1; }
  36%, 44% { opacity: .18; }
  50% { opacity: 1; }
  100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .wordmark span { animation: none; } }
.title-profile {
  max-width: 500px;
  padding: 4px 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}
.level-orb {
  width: 64px;
  height: 64px;
  border-radius: 18px;
  color: #fff;
  background: var(--play-pink);
  border: 2px solid #ff9abd;
  box-shadow: 5px 5px 0 #8d2048;
  transform: rotate(-3deg);
}
.level-orb span { color: #ffd3e2; }
.level-orb strong { font-size: 25px; }
.title-profile-line strong { color: var(--text); font-size: 14px; }
.title-profile-line span { color: var(--text-muted); }
.xp-meter { height: 10px; background: #142847; border: 2px solid #2a4c78; }
.xp-meter span { background: var(--accent); box-shadow: none; }
.title-mini-stats { font-family: var(--font-technical); font-size: 10px; font-weight: 700; }
.btn-lg { min-height: 58px; padding-inline: 30px; border-radius: 15px; }
.btn-arrow .ui-icon { width: 19px; height: 19px; }

.board-surface { background: transparent; }
.board-header {
  box-shadow: 0 4px 0 rgba(91, 114, 255, .2);
}
/* Everything in this header is a plated part on the board, and what plating
   looks like here is the title screen's badge: navy fill, a bright slate edge,
   and a hard shadow under it. */
.header-rank,
.board-header .icon-btn {
  color: var(--text);
  background: #1a2640;
  border: 2px solid #46597d;
  border-radius: 12px;
  box-shadow: 0 3px 0 rgba(3, 8, 20, .9);
}
/* Except the rank, which is the level orb off the poster, shrunk to fit a
   header — the one pink thing on the board, as it is the one pink thing
   there. */
.header-rank {
  color: #fff;
  background: var(--play-pink);
  border-color: #ff9abd;
  box-shadow: 0 3px 0 #8d2048;
}
.header-rank span { color: #ffd3e2; }
/* No corner to keep clear: on this screen the language switch is inside the
   player menu rather than fixed in the top corner, so the header takes the lane
   back and the rank box sits where the edge is. */
body[data-screen='lessonSelect'] {
  --corner-lane: 0px;
}
.board-header .icon-btn {
  color: var(--text-muted);
}
@media (hover: hover) {
  .board-header .icon-btn:hover {
    color: var(--warm);
    background: #223154;
  }
}
.board-header .icon-btn:active {
  transform: translateY(3px);
  box-shadow: 0 0 0 rgba(3, 8, 20, .9);
}



/* ---------- the game screen ----------
   The bench the board sits on, built the way a screen is built now: one dark
   ground with surfaces made of light laid over it, hairlines instead of
   outlines, glass where a panel covers the board, and depth from soft ambient
   shadow rather than a slab offset underneath everything. Scoped to the game
   screen — the title and the mission board keep their own art direction. */
.screen-game,
/* The player menu is glass wherever it is opened — the mission board hosts the
   same component — so it carries these itself rather than inheriting them from
   the screen behind it, which on the board would leave it lettered in the
   poster's ink and backed by nothing. */
.player-menu {
  --g-ink: rgba(6, 12, 22, 0.74);
  --g-line: rgba(255, 255, 255, 0.09);
  --g-line-strong: rgba(255, 255, 255, 0.17);
  --g-fill: rgba(255, 255, 255, 0.04);
  --g-fill-2: rgba(255, 255, 255, 0.07);
  --g-fill-3: rgba(255, 255, 255, 0.11);
  --g-blur: saturate(150%) blur(16px);
  --g-mono: var(--font-technical);
  --g-text: #eef2f8;
  --g-muted: #9fadc0;
  --g-faint: #6c7c92;
}
.screen-game {
  background: #070b12;
}

/* Header: the lesson, and the two things that are about the lesson rather than
   the board. Hairline under it, nothing else. */
.screen-game .game-hud {
  padding-block: 9px;
  background: var(--g-ink);
  backdrop-filter: var(--g-blur);
  border-bottom: 1px solid var(--g-line);
  box-shadow: none;
}
.screen-game .hud-chip {
  padding: 3px 8px;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-xs);
  font-family: var(--g-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.04em;
}
.screen-game .hud-title { font-size: 15px; font-weight: 600; letter-spacing: -0.015em; }
.screen-game .hud-run {
  padding: 3px 9px;
  color: var(--g-faint);
  background: var(--g-fill);
  border: 1px solid var(--g-line);
  border-radius: var(--pill);
  font-size: 10.5px;
  font-weight: 500;
}
.screen-game .hud-run.lost { color: var(--danger); border-color: var(--danger-line); background: transparent; }
.screen-game .hud-goal-text { color: var(--g-muted); font-size: 12.5px; font-weight: 400; }
.screen-game .hud-step { background: var(--g-fill-2); }
.screen-game .hud-step.done { background: var(--accent); box-shadow: 0 0 8px -1px var(--accent); }
.screen-game .icon-btn {
  width: 38px;
  height: 38px;
  border-radius: var(--radius-sm);
  color: var(--g-muted);
  background: var(--g-fill);
  border: 1px solid var(--g-line);
}
.screen-game .icon-btn .ui-icon { width: 17px; height: 17px; }

/* Toolbar: three groups of hairline controls floating on the board's glass. */
.screen-game .stage-bar {
  background: var(--g-ink);
  backdrop-filter: var(--g-blur);
  border-top: 1px solid var(--g-line);
}
.screen-game .tool-group,
.screen-game .tool-wide {
  background: var(--g-fill);
  border: 1px solid var(--g-line);
  border-radius: var(--radius-sm);
}
.screen-game .tool-btn { min-width: 40px; height: 40px; color: var(--g-muted); }
.screen-game .tool-btn .ui-icon { width: 17px; height: 17px; }
.screen-game .tool-wide { height: 40px; }
.screen-game .tool-wide[aria-pressed='true'] {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}

/* Worn as a dial, the same elements need every one of the strip's rules taken
   back off them — and this has to sit after those rules rather than beside
   them, because a media query buys no specificity. */
.screen-game .stage-bar {
    background: none;
    backdrop-filter: none;
    border-top: none;
  }
  .screen-game .tool-group {
    background: none;
    border: none;
  }
  /* 46px, not the strip's 40: on a phone these are thumb targets, and the
     circle is all there is to aim at. */
  .screen-game .tool-btn,
  .screen-game .tool-wide {
    width: 46px;
    height: 46px;
    min-width: 46px;
    padding: 0;
    border-radius: 50%;
    color: var(--g-text);
    background: var(--g-ink);
    border: 1px solid var(--g-line-strong);
    backdrop-filter: var(--g-blur);
  }
  .screen-game .tool-btn .ui-icon {
    width: 19px;
    height: 19px;
  }
  .screen-game .tool-wide[aria-pressed='true'] {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent-line);
  }
  .screen-game .tool-danger:not(:disabled) {
    color: var(--danger);
  }

  /* Nothing to undo yet is a different thing from a button you have not pressed,
     and it has to look it: the glass drains out of the disc, the hairline goes
     dashed, and the name over it fades with it. A circle you can press is solid.
     The two ids are not decoration — the rule that opens the fan carries them
     through its :not()s, so anything meaning to override it has to as well, and
     for want of that these buttons looked exactly like the live ones. */
  .screen-game .dial-open .tool-btn:not(#flip-btn):not(#zoom-reset):disabled,
  .screen-game .stage-bar[data-dial-page='parts'] .palette-item:disabled {
    opacity: 0.5;
    color: var(--g-faint);
    background: transparent;
    border-style: dashed;
    border-color: var(--g-line);
    backdrop-filter: none;
    box-shadow: none;
  }
  .screen-game .stage-bar .tool-btn:disabled::before,
  .screen-game .stage-bar .palette-item:disabled .palette-item-label {
    color: var(--g-faint);
    background: none;
  }
  /* An empty slot is worth saying plainly rather than as a dim zero. */
  .screen-game .stage-bar .palette-item:disabled .palette-item-count {
    color: var(--g-faint);
    background: transparent;
    border-style: dashed;
    border-color: var(--g-line);
  }
/* Rail: the panels beside the board, on the same glass as the bars. */
.screen-game .game-rail {
  background: var(--g-ink);
  backdrop-filter: var(--g-blur);
  border-top: 1px solid var(--g-line);
}
.screen-game .rail-section + .rail-section { border-top: 1px solid var(--g-line); }
.screen-game .rail-head { padding-top: 10px; padding-bottom: 6px; }
.screen-game .rail-title {
  color: var(--g-faint);
  font-family: var(--g-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
}
.screen-game .rail-note { color: var(--g-muted); font-size: 12px; }

/* The world controls. They set the weather for a sensor to read — a supporting
   act, not the subject — so each one is a single line of label, control and
   value rather than a card of its own. */
.screen-game .env-controls {
  display: flex;
  /* Explicitly a row: the desktop rail lays these out as a column, and a
     wrapping column turns "flex-basis" into a height, which is what made two
     sliders take up half the rail. */
  flex-direction: row;
  flex-wrap: wrap;
  /* Packed to the top: the rail stretches this section to fill the column, and
     wrapped flex lines would otherwise share out all of that height between
     two sliders. */
  align-content: flex-start;
  gap: 4px 16px;
  overflow: visible;
  padding-bottom: 10px;
}
.screen-game .env-control {
  flex: 1 1 190px;
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(72px, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 0;
  background: none;
  border: 0;
  min-height: 30px;
}
.screen-game .env-label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--g-faint);
}
.screen-game .env-body { min-width: 0; }
.screen-game .env-body > input[type='range'] { flex: 1; min-width: 0; }
.screen-game .env-value {
  min-width: 0;
  color: var(--g-text);
  font-family: var(--g-mono);
  font-size: 11.5px;
}
/* A control with no value to show — a toggle, a set of pads — spans the two
   columns the slider would have used, so it is not squeezed into a third. */
.screen-game .env-control:has(.env-toggle) .env-body,
.screen-game .env-control:has(.env-buttons) .env-body,
.screen-game .env-control:has(.env-choice) .env-body {
  grid-column: 2 / -1;
  justify-content: flex-end;
}
.screen-game .env-toggle {
  min-height: 28px;
  padding: 4px 10px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--g-muted);
  background: var(--g-fill);
  border: 1px solid var(--g-line);
}
.screen-game .env-toggle.on {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.screen-game .env-toggle.on .env-toggle-dot { background: var(--accent); box-shadow: 0 0 7px var(--accent); }
/* Three cards is more than fits across the rail beside a label, so they wrap
   and the row takes the width to wrap in. */
.screen-game .env-control:has(.env-choice) {
  flex-basis: 100%;
}
.screen-game .env-choice {
  flex-wrap: wrap;
  justify-content: flex-end;
  min-width: 0;
}
.screen-game .env-opt {
  min-height: 28px;
  padding: 4px 9px;
  font-size: 11.5px;
  font-weight: 500;
}
.screen-game .env-opt,
.screen-game .env-pad {
  color: var(--g-text);
  background: var(--g-fill);
  border: 1px solid var(--g-line);
}
.screen-game .env-opt.chosen {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.screen-game .env-pad .ui-icon { width: 17px; height: 17px; margin: auto; }

/* The parts tray. */
.screen-game .palette-item {
  background: var(--g-fill);
  border: 1px solid var(--g-line);
  border-radius: var(--radius-sm);
}
.screen-game .palette-item-icon {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: 8px;
}
.screen-game .palette-item-icon .ui-icon { width: 17px; height: 17px; }
.screen-game .palette-item-label { font-weight: 560; }
.screen-game .palette-item-count,
.screen-game .palette-item-cost { font-family: var(--g-mono); font-size: 11px; font-weight: 500; }
.screen-game .palette-item.armed {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  box-shadow: 0 0 0 1px var(--accent-line), 0 16px 32px -22px var(--accent);
}
.screen-game .palette-item.armed .palette-item-icon { color: var(--accent-ink); background: var(--accent); }
.screen-game .palette-credits { color: var(--g-muted); font-family: var(--g-mono); font-weight: 500; }

/* The tray's buttons while the dial has them: the same buttons, laid out on a
   ring. After the rules above rather than beside them, for the same reason as
   the tool buttons — a media query buys no specificity. */
.screen-game .stage-bar .palette-item {
    /* On the ring there is nothing to scroll, so every direction is a drag. */
    touch-action: none;
    position: absolute;
    left: 50%;
    bottom: calc(12px + var(--safe-bottom));
    width: 46px;
    height: 46px;
    min-height: 0;
    margin-left: -23px;
    margin-bottom: 5px;
    padding: 0;
    flex-direction: row;
    gap: 0;
    border-radius: 50%;
    background: var(--g-ink);
    border: 1px solid var(--g-line-strong);
    backdrop-filter: var(--g-blur);
    pointer-events: auto;
    /* Folded into the middle until the page it belongs to is the one showing. */
    transform: translate(0, 0) scale(0.4);
    opacity: 0;
    transition:
      transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1),
      opacity 0.18s var(--ease);
    transition-delay: calc(var(--step, 0) * 26ms);
  }
  /* Named page rather than "some page": a group being turned away from is still
     on the layer while it folds, and `.dial-page` alone would hold it open
     until the moment it was taken off — which is a group vanishing, not a
     group leaving. */
  .screen-game .stage-bar[data-dial-page='parts'] .palette-item {
    transform: translate(var(--dx, 0px), var(--dy, 0px)) scale(1);
    opacity: 1;
  }
  /* A press must not throw the circle back to the middle, so the nudge is
     applied on top of where the ring put it. */
  .screen-game .stage-bar[data-dial-page='parts'] .palette-item:active:not(:disabled) {
    transform: translate(var(--dx, 0px), var(--dy, 0px)) scale(0.94);
  }
  .screen-game .stage-bar[data-dial-page='parts'] .palette-item:disabled {
    opacity: 0.35;
  }
  /* Icon and a count; the name and the price are still read aloud, and are
     still written out in full in the rail. */
  .screen-game .stage-bar .palette-item-icon {
    width: auto;
    height: auto;
    background: none;
    border-radius: 0;
    color: var(--g-text);
  }
  .screen-game .stage-bar .palette-item.armed .palette-item-icon {
    color: var(--accent-ink);
    background: none;
  }
  .screen-game .stage-bar .palette-item-icon .ui-icon {
    width: 21px;
    height: 21px;
  }
  /* The part's own name, in the same place as the captions over the tools —
     it is already on the button, so it only has to move off the disc. */
  .screen-game .stage-bar .palette-item-label {
    position: absolute;
    bottom: calc(100% + 4px);
    left: 50%;
    transform: translateX(calc(-50% + var(--lx, 0px)));
    padding: 1px 5px;
    border-radius: 6px;
    background: rgba(6, 12, 22, 0.78);
    /* In the rail the name has a row to itself and is cut with an ellipsis if
       it runs long; over a circle it has two short lines instead, because
       "Range Find…" is not a name. */
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    max-width: 70px;
    font-family: var(--g-mono);
    font-size: 10px;
    font-weight: 600;
    line-height: 1.15;
    text-align: center;
    color: var(--g-muted);
    text-shadow: 0 1px 3px rgba(4, 8, 14, 0.9), 0 0 6px rgba(4, 8, 14, 0.8);
    pointer-events: none;
  }
  /* What fitting one costs, under the circle — but only once it costs
     something. While the lesson's own stock lasts there is no number worth
     printing, and a row of "free" under every part is a row of noise. */
  .screen-game .stage-bar .palette-item-cost {
    position: absolute;
    top: calc(100% + 4px);
    left: 50%;
    transform: translateX(calc(-50% + var(--lx, 0px)));
    padding: 1px 5px;
    border-radius: 6px;
    background: rgba(6, 12, 22, 0.78);
    font-family: var(--g-mono);
    font-size: 9.5px;
    font-weight: 700;
    line-height: 1.15;
    white-space: nowrap;
    color: var(--warm);
    text-shadow: 0 1px 3px rgba(4, 8, 14, 0.9);
    pointer-events: none;
  }
  /* Free, or nothing to fit at all: either way there is no figure, and an
     empty plate is still a plate. */
  .screen-game .stage-bar .palette-item-cost.is-free,
  .screen-game .stage-bar .palette-item:disabled .palette-item-cost.is-free,
  .screen-game .stage-bar .palette-item-cost:empty {
    display: none;
  }
  /* More than the balance will cover: the figure says so before the tap does. */
  .screen-game .stage-bar .palette-item.unaffordable .palette-item-cost {
    color: var(--danger);
  }
  .screen-game .stage-bar .palette-item-text {
    display: contents;
  }
  /* "3 left" does not fit on a circle, so the badge is drawn from the bare
     figure the tray also records; the sentence itself is still what a screen
     reader gets. */
  .screen-game .stage-bar .palette-item-count {
    position: absolute;
    right: -3px;
    bottom: -3px;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    font-size: 0;
    color: var(--g-text);
    background: var(--surface-3);
    border: 1px solid var(--g-line-strong);
  }
  .screen-game .stage-bar .palette-item-count::after {
    content: attr(data-count);
    font-family: var(--g-mono);
    font-size: 10px;
    font-weight: 600;
  }

  /* The rail itself never comes out here. Both of the things it holds are
     pages of the dial now, so nothing has to slide up over the board and no
     strip of screen has to be set aside for it — it is only still in the
     document because it is where those controls live between visits. */
  .screen-game .game-rail {
    display: none;
  }

  /* The world ring. Same circles and the same rules as the fan below — a name
     over the disc, a reading under it — but folded into #world-btn rather than
     into the dial, because that is the button they come out of. Every one of
     them starts life exactly on top of it and is dealt out along the arc from
     there; JS writes --dx/--dy per circle, since how far the arc reaches
     depends on how many controls the lesson has and how much room the window
     left between the corner buttons and the bottom of the board.

     They are the rail's own buttons — a switch with two faces is two switches
     that can disagree — restyled for the ring they are standing on, and the
     ring is light green throughout: the world is not one of the tools. */
  .screen-game .stage-bar .env-bubble {
    position: absolute;
    top: 50%;
    right: calc(10px + var(--safe-right));
    left: auto;
    bottom: auto;
    width: 46px;
    height: 46px;
    min-height: 0;
    margin: -23px 0 0;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0;
    border-radius: 50%;
    color: var(--world);
    background: var(--g-ink);
    border: 1px solid var(--world-line);
    backdrop-filter: var(--g-blur);
    box-shadow: var(--shadow-2);
    /* Nothing on the ring scrolls, so every direction is the knob's to drag. */
    touch-action: none;
    pointer-events: auto;
    transform: translate(0, 0) scale(0.4);
    opacity: 0;
    transition:
      transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1),
      opacity 0.18s var(--ease),
      background 0.15s var(--ease),
      color 0.15s var(--ease),
      border-color 0.15s var(--ease);
    transition-delay: calc(var(--step, 0) * 26ms);
  }
  .screen-game .stage-bar.world-open .env-bubble {
    transform: translate(var(--dx, 0px), var(--dy, 0px)) scale(1);
    opacity: 1;
  }
  /* A folded circle is invisible but it is still a circle, and for the length
     of the fold it is sitting exactly on top of the button that opens the ring
     — which would eat the second press of a quick open-and-shut. */
  .screen-game .stage-bar:not(.world-open) .env-bubble {
    pointer-events: none;
  }
  /* Held down, or switched on, or the card being held to the reader. */
  .screen-game .stage-bar .env-bubble.on,
  .screen-game .stage-bar .env-bubble.chosen,
  .screen-game .stage-bar .env-bubble.dragging {
    color: var(--world-ink);
    background: var(--world);
    border-color: var(--world);
  }
  /* A pad is *held*, so it gives under the thumb; a switch that is simply on
     stays the size it was. */
  .screen-game .stage-bar.world-open .env-pad.on {
    transform: translate(var(--dx, 0px), var(--dy, 0px)) scale(0.94);
  }
  .screen-game .stage-bar .env-bubble .env-bubble-face {
    display: inline-flex;
  }
  .screen-game .stage-bar .env-bubble .ui-icon {
    width: 21px;
    height: 21px;
  }
  /* The pill's furniture has no place on a disc: the whole circle lights, so
     the little lamp is redundant, and the option's name has moved over it. */
  .screen-game .stage-bar .env-toggle-dot,
  .screen-game .stage-bar .env-opt-text {
    display: none;
  }
  /* What it is, over the circle — the same plate the tools and the parts wear.
     Never a tap target. */
  .screen-game .stage-bar .env-bubble::before {
    content: attr(data-dial-label);
    position: absolute;
    bottom: calc(100% + 4px);
    left: 50%;
    transform: translateX(calc(-50% + var(--lx, 0px)));
    padding: 1px 5px;
    border-radius: 6px;
    background: rgba(6, 12, 22, 0.78);
    max-width: 70px;
    font-family: var(--g-mono);
    font-size: 8.5px;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: 0.02em;
    text-align: center;
    white-space: normal;
    color: var(--world);
    text-shadow: 0 1px 3px rgba(4, 8, 14, 0.9), 0 0 6px rgba(4, 8, 14, 0.8);
    pointer-events: none;
  }
  /* What it currently says, under it — "raining", "120cm". A control whose
     state is the circle itself has nothing to write here. */
  .screen-game .stage-bar .env-bubble-value {
    position: absolute;
    top: calc(100% + 4px);
    left: 50%;
    transform: translateX(calc(-50% + var(--lx, 0px)));
    padding: 1px 5px;
    border-radius: 6px;
    background: rgba(6, 12, 22, 0.78);
    font-family: var(--g-mono);
    font-size: 9.5px;
    font-weight: 700;
    line-height: 1.15;
    white-space: nowrap;
    color: var(--world);
    text-shadow: 0 1px 3px rgba(4, 8, 14, 0.9);
    pointer-events: none;
  }
  .screen-game .stage-bar .env-bubble-value:empty {
    display: none;
  }
  /* A knob reads as a level the way the slider it replaces does: the rim fills
     round as the value climbs. */
  .screen-game .stage-bar .env-knob::after {
    content: '';
    position: absolute;
    inset: -4px;
    border-radius: 50%;
    padding: 2px;
    background: conic-gradient(
      from 180deg,
      var(--world) 0 calc(var(--fill, 0) * 1%),
      rgba(255, 255, 255, 0.14) 0
    );
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    pointer-events: none;
  }
/* The message strip under the board. */
.screen-game .status-msg { border-width: 1px; font-size: 12px; }
.screen-game .status-msg::before { content: none; }
.screen-game .status-msg > .ui-icon { width: 16px; height: 16px; margin-top: 1px; }
.screen-game .status-alert > .ui-icon { color: var(--danger); }
.screen-game .status-info > .ui-icon { color: var(--accent); }

/* The bought answer: a parts list and a wiring table, laid out to be read off
   while wiring rather than admired. */
.solution-head {
  margin: 16px 0 7px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.solution-head:first-child { margin-top: 4px; }
.solution-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
  list-style: none;
  font-size: 13px;
  color: var(--text-muted);
}
.solution-list em { color: var(--text-faint); font-style: normal; font-size: 11.5px; }
.solution-wiring {
  display: flex;
  flex-direction: column;
  gap: 3px;
  list-style: none;
  counter-reset: wire;
}
.solution-wiring li {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: var(--radius-xs);
  font-family: var(--font-technical);
  font-size: 11.5px;
  color: var(--text);
}
.solution-wiring li > span:last-child { text-align: right; }
.solution-wiring .ui-icon { width: 15px; height: 15px; color: var(--accent); }

/* Modals only ever open over the board, so they are part of this too. */
.screen-game .tutorial-stage { border-color: var(--g-line); }
.screen-game .tutorial-step { background: var(--g-fill-2); }
.modal-card {
  background: rgba(13, 19, 30, 0.88);
  backdrop-filter: saturate(150%) blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: var(--shadow-3), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.modal-card::before { height: 1px; background: rgba(255, 255, 255, 0.14); opacity: 1; }
.tone-win::before { height: 2px; background: linear-gradient(90deg, transparent, var(--accent), transparent); }
.tone-danger::before { height: 2px; background: linear-gradient(90deg, transparent, var(--danger), transparent); }
.modal-eyebrow {
  color: var(--text-faint);
  font-family: var(--font-technical);
  font-weight: 500;
  letter-spacing: 0.12em;
}
.modal-card h3 { font-weight: 620; letter-spacing: -0.02em; }
.win-rewards > div { background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.09); }
.daily-bonus {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: -4px 0 14px;
  padding: 11px 13px;
  color: var(--warm);
  background: var(--warm-soft);
  border: 1px solid rgba(255, 200, 87, 0.24);
  border-radius: var(--radius-sm);
}
.daily-bonus span { display: flex; align-items: center; gap: 7px; font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }
.daily-bonus .ui-icon { width: 17px; height: 17px; }
.daily-bonus strong { font-size: 13px; font-weight: 620; white-space: nowrap; }

/* ---------- the account screen ----------
   One Google action, dressed as the mission board is: a plated card on the
   solder-mask ground. It borrows the board's palette rather than inventing a
   third look, because signing in is a doorway and not somewhere to linger. */
.screen-account {
  background-color: var(--board-bg);
  color: var(--text);
}
.account-scroll {
  flex: 1;
  overflow-y: auto;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 22px calc(18px + var(--safe-right)) calc(32px + var(--safe-bottom)) calc(18px + var(--safe-left));
}
.account-card {
  width: 100%;
  max-width: 380px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 22px;
  background: rgba(16, 29, 56, 0.86);
  border: 2px solid #46597d;
  border-radius: 16px;
  box-shadow: 0 4px 0 rgba(3, 8, 20, 0.9);
}
.account-google-mark {
  width: 58px;
  height: 58px;
  margin: 0 auto;
  display: grid;
  place-items: center;
  color: #4285f4;
  background: #fff;
  border-radius: 50%;
  font-family: Arial, sans-serif;
  font-size: 30px;
  font-weight: 800;
  box-shadow: 0 3px 0 rgba(3, 8, 20, 0.9);
}
.account-intro {
  max-width: 30ch;
  margin-inline: auto;
  text-align: center;
}
.account-google {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.account-google-button-mark {
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  color: #4285f4;
  background: #fff;
  border-radius: 50%;
  font-family: Arial, sans-serif;
  font-size: 15px;
  font-weight: 800;
}
/* Retained form primitives also serve the provider controls and the signed-in
   profile card; the retired username form itself is no longer rendered. */
.account-tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.account-tab {
  min-height: 40px;
  padding: 8px;
  border-radius: 9px;
  color: var(--text-muted);
  font-size: 13.5px;
  font-weight: 700;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.account-tab.is-active {
  color: var(--warm);
  background: rgba(91, 114, 255, 0.2);
  box-shadow: inset 0 0 0 1px #46597d;
}

.account-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.account-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* An author `display` beats the UA stylesheet's `[hidden] { display: none }`,
   so without this the address field stays on screen when signing in. */
.account-field[hidden] {
  display: none;
}
.account-label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* Credentials are typed left-to-right whatever the interface language is, so
   the inputs are pinned LTR — a password field that reflows mid-word is a
   password nobody can check before submitting. */
.account-card input {
  min-height: 46px;
  padding: 11px 13px;
  color: var(--text);
  background: rgba(0, 0, 0, 0.34);
  border: 2px solid var(--line-strong);
  border-radius: 11px;
  font-family: inherit;
  font-size: 16px; /* under 16px, iOS Safari zooms the page on focus */
  letter-spacing: 0.01em;
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.account-card input:focus {
  outline: none;
  border-color: var(--warm);
  box-shadow: 0 0 0 3px rgba(255, 200, 87, 0.16);
}
/* The second way in. The divider is the only line drawn inside the card, and
   it is there so the form reads as an alternative to the button above it
   rather than a second step after it. */
.account-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text-faint);
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.account-divider::before,
.account-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line-strong);
}
.account-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.account-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.account-field-label {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.account-field input {
  width: 100%;
  box-sizing: border-box;
  direction: ltr;
  text-align: left;
}
.account-links {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.account-link {
  padding: 4px 0;
  background: none;
  border: 0;
  color: var(--warm);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.account-link:disabled { opacity: 0.5; cursor: default; }
.account-error.is-notice {
  color: #38d9c9;
  background: rgba(56, 217, 201, 0.1);
  border-color: rgba(56, 217, 201, 0.35);
}
.account-hint {
  font-size: 11.5px;
  color: var(--text-faint);
}
.account-error {
  margin: 0;
  padding: 10px 12px;
  color: var(--danger);
  background: var(--danger-soft, rgba(255, 90, 90, 0.1));
  border: 1px solid var(--danger-line, rgba(255, 90, 90, 0.35));
  border-radius: 10px;
  font-size: 13px;
  font-weight: 600;
}
.account-submit {
  width: 100%;
  margin-top: 2px;
}
.account-note {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-faint);
}
.account-warning {
  padding-top: 2px;
  border-top: 1px dashed var(--line);
  padding-block-start: 12px;
}
.account-skip {
  color: var(--text-muted);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  min-height: 32px;
}

.account-avatar {
  width: 52px;
  height: 52px;
  margin: 0 auto;
  display: grid;
  place-items: center;
  color: var(--board-bg);
  background: var(--accent);
  border-radius: 50%;
}
.account-avatar .ui-icon { width: 26px; height: 26px; }
.account-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.account-eyebrow {
  margin: 0;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  text-align: center;
}
.account-name {
  margin: 0;
  font-size: 22px;
  font-weight: 800;
  text-align: center;
  color: var(--warm);
  /* A username is the one string on this screen the player chose, so it is
     shown exactly as typed rather than reflowed by the interface language. */
  direction: ltr;
  unicode-bidi: isolate;
}

/* The way into all of the above, on the title screen: the start button's twin,
   under it and the same size. Outlined rather than filled, because the poster
   only ever has one solid call to action on it and that one is the game. A
   label here can be a good deal longer than "Start first mission", so it wraps
   rather than pushing the pair wider than the column. */
.title-account {
  color: var(--text);
  background: rgba(0, 0, 0, 0.28);
  border: 2px solid var(--line-strong);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.25;
  white-space: normal;
  text-wrap: balance;
}
.title-account.is-signed-in { color: var(--accent); border-color: var(--accent-line); }
.title-account .ui-icon { width: 17px; height: 17px; }

@media (max-width: 639px) {
  .screen-title { padding-top: calc(28px + var(--safe-top)); }
  .title-logo { width: 88px; filter: drop-shadow(6px 7px 0 rgba(255, 79, 139, .82)) drop-shadow(0 16px 20px rgba(91, 114, 255, .42)); }
  .screen-title h1 { font-size: clamp(42px, 14vw, 60px); }
  .daily-bonus { align-items: flex-start; flex-direction: column; gap: 4px; }
  .account-card { padding: 18px; gap: 14px; }
  .screen-game .env-control { flex-basis: 150px; }
  .zone-pads { grid-template-columns: repeat(auto-fill, minmax(68px, 1fr)); gap: 16px 10px; }
  .board-readout { gap: 11px; }
  .readout-gauges { gap: 9px 14px; }
  .gauge { font-size: 19px; min-width: 52px; }
}

/* ---------- Hebrew: the layout turns round ----------

   The document itself stays LTR — every coordinate the canvas, the dial and
   the router work in is measured from the left, and mirroring the document
   would move all of them. What turns round is the chrome: the rows a reader
   scans, and the corners the two controls that frame every screen sit in.
   Anything not listed here is either symmetric or is the board, which has no
   side. */

.hud-wallet {
  display: flex;
  align-items: center;
  gap: 5px;
}
.hud-wallet > span {
  display: grid;
  grid-template-columns: auto auto;
  align-items: center;
  gap: 0 4px;
  min-height: 38px;
  padding: 4px 8px;
  border: 1px solid var(--g-line, var(--line));
  border-radius: var(--radius-sm);
  background: var(--g-fill, var(--surface-2));
}
.hud-wallet .ui-icon { grid-row: 1 / span 2; width: 15px; height: 15px; color: var(--accent); }
.hud-wallet > span + span .ui-icon { color: var(--warm); }
.hud-wallet b { font: 650 12px/1 var(--font-technical); color: var(--text); }
.hud-wallet small { font-size: 8px; line-height: 1; color: var(--text-faint); }

.circuit-meter {
  position: absolute;
  z-index: 4;
  top: 18px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 18px;
  width: auto;
  min-height: 0;
  padding: 0;
  overflow: visible;
  color: var(--g-text, var(--text));
  background: none;
  border: 0;
  border-radius: 0;
  backdrop-filter: none;
}
.circuit-meter::before {
  content: '';
  position: absolute;
  z-index: -1;
  left: 24px;
  right: 24px;
  top: 50%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(193, 126, 68, .5) 12%, rgba(193, 126, 68, .5) 88%, transparent);
}
.meter-reading {
  --meter-color: var(--accent);
  position: relative;
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  min-width: 58px;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--meter-color) 55%, transparent);
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 42%, color-mix(in srgb, var(--meter-color) 13%, transparent), transparent 62%),
    rgba(6, 12, 22, .9);
  box-shadow:
    inset 0 0 0 4px rgba(255, 255, 255, .025),
    0 0 18px -11px var(--meter-color);
  backdrop-filter: var(--g-blur, blur(12px));
}
.meter-voltage { --meter-color: #66a9ff; }
.meter-current { --meter-color: #ffe16b; }
.meter-resistance { --meter-color: #d79258; }
.meter-reading::after {
  content: '';
  position: absolute;
  right: 2px;
  bottom: 5px;
  width: 6px;
  height: 6px;
  border: 1px solid var(--meter-color);
  border-radius: 50%;
  background: #070b12;
}
.meter-reading small {
  position: absolute;
  bottom: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 100px;
  padding: 1px 5px;
  overflow: hidden;
  color: color-mix(in srgb, var(--meter-color) 75%, white);
  background: rgba(6, 12, 22, .78);
  border-radius: 6px;
  font: 600 8px/1.15 var(--g-mono, var(--font-technical));
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meter-reading strong { color: var(--g-text, var(--text)); font: 650 12px/1 var(--g-mono, var(--font-technical)); }
.meter-reading em {
  position: absolute;
  top: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  min-height: 9px;
  color: var(--meter-color);
  font: normal 7.5px/1 var(--g-mono, var(--font-technical));
}

/* Circuit advice is an annunciator light, not a card over the work. The light
   stays at the board edge; its sentence opens only when deliberately hovered
   or focused. */
.stage-status {
  position: absolute;
  z-index: 8;
  top: auto;
  right: calc(12px + var(--safe-right));
  bottom: calc(12px + var(--safe-bottom));
  left: auto;
  width: auto;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 7px;
  transform: none;
  pointer-events: none;
}
.status-msg {
  position: relative;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin: 0;
  padding: 0;
  color: var(--accent);
  background: rgba(7, 13, 22, .88);
  border: 1px solid var(--g-line-strong, var(--line));
  border-radius: 50%;
  box-shadow: 0 5px 16px -9px rgba(0, 0, 0, .95);
  backdrop-filter: var(--g-blur, blur(12px));
  pointer-events: auto;
}
.status-msg > span {
  position: absolute;
  right: calc(100% + 8px);
  bottom: 0;
  width: max-content;
  max-width: min(300px, calc(100vw - 76px));
  padding: 7px 10px;
  color: var(--text-muted);
  background: rgba(7, 13, 22, .96);
  border: 1px solid var(--g-line-strong, var(--line));
  border-radius: 12px 12px 3px 12px;
  box-shadow: 0 8px 22px -15px rgba(0, 0, 0, .95);
  font-size: 11px;
  line-height: 1.35;
  text-align: start;
  opacity: 0;
  visibility: hidden;
  transform: translateX(6px) scale(.98);
  transform-origin: right bottom;
  transition: opacity .14s var(--ease), transform .14s var(--ease), visibility 0s linear .14s;
  pointer-events: none;
}
.status-msg:hover > span,
.status-msg:focus > span,
.status-msg:focus-visible > span {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}
.status-msg:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}
.status-msg > .ui-icon {
  width: 17px !important;
  height: 17px !important;
  margin: 0 !important;
  padding: 0;
  border: 0;
}
.status-alert { color: #ff6b72; border-color: color-mix(in srgb, var(--danger) 58%, transparent); }
.status-alert > span { color: #ffd7d9; border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }
.status-alert > .ui-icon { color: currentColor !important; background: none; }
.status-info > .ui-icon { color: var(--accent) !important; }

/* The three seconds a finished circuit has to hold, counted over the middle of
   the board. Outlined and never filled: the poster's rule is that a solid fill
   is for the things you press, and this is the one moment the player is
   actually watching the circuit work — a filled numeral this size would be a
   lid on it. It takes no space in the layout and no pointer events, so nothing
   below it moves or stops working while it runs. */
.completion-countdown {
  position: absolute;
  z-index: 5;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.completion-countdown[hidden] { display: none; }

/* The strike. Same channels the win card throws — the shape, the ink and the
   flicker are all shared — but struck over the whole board and aimed at the
   numeral instead of at a card, and harder every second: `--surge` runs 1, 2, 3
   as the count runs 3, 2, 1, so the last one before the box is the one that
   lands. Both layers sit under the numeral, which is the thing being struck. */
.countdown-strikes {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.countdown-sky { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
/* Struck across a whole board rather than the head of a card, so the channel
   needs the weight to carry — and a little more of it each second. */
.completion-countdown .short-strike {
  stroke-width: calc(1.9px + var(--surge, 1) * .25px);
  animation-duration: .72s;
}
.countdown-flash {
  position: absolute;
  inset: -10%;
  background: radial-gradient(58% 54% at 50% 50%, rgba(255, 255, 255, .96), rgba(93, 224, 230, .55) 30%, rgba(255, 200, 87, .2) 52%, transparent 72%);
  opacity: 0;
  animation: countdown-flash .52s linear both;
}
/* Lightning does not fade up and it does not fade out evenly. The return stroke
   is the first spike, and what follows is flicker. */
@keyframes countdown-flash {
  0% { opacity: 0; }
  4% { opacity: calc(.42 + var(--surge, 1) * .16); }
  10% { opacity: .04; }
  16% { opacity: calc(.3 + var(--surge, 1) * .13); }
  24% { opacity: .02; }
  36% { opacity: calc(.12 + var(--surge, 1) * .07); }
  100% { opacity: 0; }
}
.countdown-number {
  font-family: var(--font-technical);
  font-size: clamp(110px, 30vmin, 260px);
  font-weight: 700;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 3px var(--accent);
  /* Browsers without text-stroke get a legible numeral rather than a blank
     one: the stroke above wins wherever it is understood. */
  text-shadow: 0 0 46px rgba(56, 217, 201, .5), 0 10px 34px rgba(0, 0, 0, .65);
  animation: countdown-tick 1s var(--ease) both;
}
@keyframes countdown-tick {
  0% { opacity: 0; transform: scale(1.55); }
  16% { opacity: 1; transform: scale(1); }
  72% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(.88); }
}

.reward-preview {
  margin: 4px 0 14px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.reward-preview > strong { display: block; margin-bottom: 8px; color: var(--text); font-size: 12px; }
.reward-preview > div { display: grid; gap: 6px; }
.reward-preview > div > span { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 9px; }
.reward-preview b { display: inline-flex; align-items: center; gap: 4px; color: var(--text-muted); font-size: 11px; font-weight: 600; }
.reward-preview b .ui-icon { width: 13px; height: 13px; color: var(--accent); }
.reward-preview b + b .ui-icon { color: var(--warm); }
.reward-preview small { display: block; margin-top: 8px; color: var(--text-faint); font-size: 10.5px; line-height: 1.4; }
.reward-chips { display: inline-flex; gap: 2px; }
.reward-chips .rating-chip { color: var(--warm); }

.concept-glossary { display: grid; gap: 8px; }
.concept-glossary > div { padding: 10px 12px; border-inline-start: 2px solid var(--accent); background: var(--surface-2); }
.concept-glossary dt { margin-bottom: 3px; color: var(--text); font-size: 13px; font-weight: 700; }
.concept-glossary dd { color: var(--text-muted); font-size: 12.5px; line-height: 1.5; }

.gauge-label { display: inline-flex; align-items: center; gap: 4px; color: var(--etch-dim); font: 700 8px/1 var(--font-technical); letter-spacing: .06em; text-transform: uppercase; }
.gauge .gauge-label .ui-icon { width: 11px; height: 11px; }

@media (max-width: 720px) {
  .hud-wallet small { display: none; }
  .hud-wallet > span { display: flex; min-height: 38px; padding-inline: 6px; }
}
@media (max-width: 560px) {
  .hud-help { gap: 3px; }
  .hud-help > #glossary-btn { display: none; }
  .hud-wallet { min-height: 38px; gap: 5px; padding: 0 6px; border: 1px solid var(--g-line, var(--line)); border-radius: var(--radius-sm); background: var(--g-fill, var(--surface-2)); }
  .hud-wallet > span { min-height: 36px; padding: 0; border: 0; background: transparent; }
  .circuit-meter { top: 17px; width: auto; gap: 12px; }
  .meter-reading { width: 50px; height: 50px; min-width: 50px; }
  .meter-reading small { max-width: 76px; font-size: 7px; }
  .meter-reading strong { font-size: 10px; }
  .countdown-number { font-size: clamp(88px, 26vmin, 180px); }
  .status-msg { width: 36px; height: 36px; }
  .reward-preview > div > span { grid-template-columns: 1fr auto; }
  .reward-preview b + b { grid-column: 2; }
}
@media (min-width: 600px) and (max-height: 560px) {
  .modal-card:has(.reward-preview) { width: min(760px, 100%); max-height: calc(100vh - 20px); padding: 16px 20px; }
  .modal-content:has(.reward-preview) { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 18px; }
  .modal-content:has(.reward-preview) > .modal-eyebrow,
  .modal-content:has(.reward-preview) > h2 { grid-column: 1 / -1; margin-bottom: 0; }
  .modal-content:has(.reward-preview) > p { grid-column: 1; margin-bottom: 0; }
  .modal-content:has(.reward-preview) > .reward-preview { grid-column: 2; grid-row: 3 / span 3; margin: 0; align-self: stretch; }
  .modal-card:has(.reward-preview) .modal-actions { margin-top: 10px; }
}
@media (prefers-reduced-motion: reduce) {
  /* Each numeral still has to appear and go again — it is the countdown, not
     decoration — so what is dropped is the movement, not the change. The sky is
     decoration and goes entirely, the way the win card's does. */
  .countdown-strikes { display: none; }
  .countdown-number { animation: countdown-hold 1s steps(1, end) both; }
  @keyframes countdown-hold {
    0%, 88% { opacity: 1; transform: none; }
    100% { opacity: 0; transform: none; }
  }
}

/* Player terminal ---------------------------------------------------------
   The game menu drops from the identity light in the HUD. It is a compact
   instrument panel: navigation first, then language pads, then account state.
   It does not become a second modal over the workbench. */
.screen-game .game-hud { z-index: 20; }
.player-menu-trigger[aria-expanded='true'] {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.player-menu {
  position: absolute;
  z-index: 22;
  top: calc(100% + 8px);
  right: calc(12px + var(--safe-right));
  width: min(330px, calc(100vw - 24px));
  max-height: calc(100vh - 82px - var(--safe-bottom));
  overflow-y: auto;
  padding: 12px;
  color: var(--g-text);
  background: rgba(7, 12, 21, .97);
  border: 1px solid var(--g-line-strong);
  border-radius: 15px;
  box-shadow: 0 22px 60px -24px rgba(0, 0, 0, .95);
  backdrop-filter: var(--g-blur);
}
.player-menu[hidden] { display: none; }
.player-menu-id {
  display: grid;
  grid-template-columns: 42px 1fr;
  align-items: center;
  gap: 10px;
  padding: 2px 2px 11px;
  border-bottom: 1px solid var(--g-line);
}
.player-menu-avatar {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  overflow: hidden;
  color: var(--accent);
  border: 1px solid var(--accent-line);
  border-radius: 50%;
  background: var(--accent-soft);
}
.player-menu-avatar img { width: 100%; height: 100%; object-fit: cover; }
.player-menu-avatar .ui-icon { width: 18px; height: 18px; }
.player-menu-id div { min-width: 0; display: grid; gap: 2px; }
.player-menu-id small,
.player-menu-audio > small {
  color: var(--g-faint);
  font: 600 9px/1 var(--g-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
}
.player-menu-id strong { overflow: hidden; color: var(--g-text); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
.player-menu-id em { color: var(--g-muted); font: normal 10px/1.2 var(--g-mono); }
.player-menu-balance { display: flex; gap: 8px; padding: 9px 2px; }
.player-menu-balance span { display: inline-flex; align-items: center; gap: 4px; color: var(--g-muted); font: 600 10px/1 var(--g-mono); }
.player-menu-balance .ui-icon { width: 13px; height: 13px; color: var(--accent); }
.player-menu-balance span + span .ui-icon { color: var(--warm); }
.player-menu-balance i { font-style: normal; }
.player-menu-links { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.player-menu-links button,
.player-menu-account {
  min-height: 54px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 7px;
  color: var(--g-muted);
  background: var(--g-fill);
  border: 1px solid var(--g-line);
  border-radius: 10px;
  font-size: 10px;
}
.player-menu-links button:hover { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }
.player-menu-links .ui-icon,
.player-menu-account .ui-icon { width: 17px; height: 17px; }
.player-menu-audio {
  display: grid;
  gap: 7px;
  margin-top: 10px;
  padding: 10px 2px;
  border-top: 1px solid var(--g-line);
}
.audio-control-row {
  display: grid;
  grid-template-columns: 34px 1fr;
  align-items: center;
  gap: 9px;
}
.audio-channel-toggle {
  position: relative;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: 9px;
}
.audio-channel-toggle .ui-icon { width: 17px; height: 17px; }
.audio-channel-toggle.is-muted { color: var(--g-faint); background: var(--g-fill); border-color: var(--g-line); }
.audio-channel-toggle.is-muted::after {
  content: '';
  position: absolute;
  width: 21px;
  height: 1px;
  background: var(--danger);
  transform: rotate(-45deg);
}
.audio-control-row label { min-width: 0; display: grid; grid-template-columns: 62px 1fr; align-items: center; gap: 8px; }
.audio-control-row label span { color: var(--g-muted); font-size: 10px; }
.audio-control-row input[type='range'] {
  appearance: none;
  width: 100%;
  height: 20px;
  margin: 0;
  background: transparent;
  cursor: pointer;
}
/* The fill is painted by hand, so it has a side of its own and does not follow
   the row's direction the way the native thumb does. In Hebrew the thumb runs
   from the right and this gradient went on starting at the left, which drew the
   quiet end of the slider filled in and the loud end grey. See the mirroring
   block at the end of this file for the other half of the rule. */
.audio-control-row input[type='range']::-webkit-slider-runnable-track {
  height: 4px;
  background: linear-gradient(to var(--audio-fill-end, right), var(--accent) var(--audio-level), var(--g-line-strong) var(--audio-level));
  border-radius: 0;
}
.audio-control-row input[type='range']::-webkit-slider-thumb {
  appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -5px;
  background: var(--accent);
  border: 2px solid #071421;
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--accent-line), 0 0 10px rgba(47, 215, 242, .22);
}
.audio-control-row input[type='range']::-moz-range-track { height: 4px; background: var(--g-line-strong); }
.audio-control-row input[type='range']::-moz-range-progress { height: 4px; background: var(--accent); }
.audio-control-row input[type='range']::-moz-range-thumb {
  width: 12px;
  height: 12px;
  background: var(--accent);
  border: 2px solid #071421;
  border-radius: 50%;
}
.player-menu-main[hidden],
.player-language-submenu[hidden] { display: none; }
.player-menu-language {
  width: 100%;
  min-height: 46px;
  display: grid;
  grid-template-columns: 24px 1fr auto 16px;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 8px 4px;
  color: var(--g-muted);
  background: transparent;
  border: 0;
  border-top: 1px solid var(--g-line);
  text-align: start;
}
.player-menu-language:hover { color: var(--accent); }
.player-menu-language .ui-icon:first-child { width: 18px; height: 18px; color: var(--accent); }
.player-menu-language .ui-icon:last-child { width: 14px; height: 14px; color: var(--g-faint); }
.player-menu-language span { font-size: 11px; }
.player-menu-language strong { color: var(--g-text); font: 650 10px/1 var(--g-mono); }
.player-language-submenu { min-height: 280px; animation: language-channel-in .16s ease-out both; }
.player-language-submenu > header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 0 11px;
  border-bottom: 1px solid var(--g-line);
}
.player-language-submenu > header button {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: 9px;
}
.player-language-submenu > header .ui-icon { width: 17px; height: 17px; }
.player-language-submenu > header h2 { color: var(--g-text); font-size: 14px; }
.player-language-submenu .player-language-slot { padding-top: 10px; }
@keyframes language-channel-in {
  from { opacity: 0; transform: translateX(10px); }
  to { opacity: 1; transform: translateX(0); }
}
.player-menu .language-switch.is-hosted { position: static; width: 100%; height: auto; }
.player-menu .language-switch.is-hosted .language-options { grid-template-columns: 1fr; gap: 5px; }
.player-menu .language-switch.is-hosted .language-options button {
  min-width: 0;
  min-height: 44px;
  padding: 0 12px;
  color: var(--g-muted);
  background: var(--g-fill);
  border: 1px solid var(--g-line);
  border-radius: 9px;
  font: 600 11px/1 var(--g-mono);
  text-align: start;
}
.player-menu .language-switch.is-hosted .language-options button::after { content: ''; float: inline-end; }
.player-menu .language-switch.is-hosted .language-options button.is-current::after { content: '✓'; }
.player-menu .language-switch.is-hosted .language-options button.is-current {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}
/* The pin map's contents. Read like an instrument face: the pin in mono at a
   fixed width so six of them line up as a column, and what the sketch does with
   it beside. A free pin is dimmed rather than left out — "GPIO5 is free" is
   half of what a player comes here to find out. */
.pinmap-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--g-line, rgba(255, 255, 255, 0.09));
}
.pinmap-head h2 {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--g-muted, #9fadc0);
}
.pinmap-close {
  width: 26px;
  height: 26px;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--g-muted, #9fadc0);
  border-radius: 8px;
}
.pinmap-close .ui-icon { width: 15px; height: 15px; }
.pinmap-rows {
  list-style: none;
  margin: 0;
  padding: 8px 0 0;
  display: grid;
  gap: 5px;
}
.pinmap-row {
  display: grid;
  grid-template-columns: 52px 1fr;
  align-items: baseline;
  gap: 8px;
  font-size: 11.5px;
}
.pinmap-pin {
  font: 700 11px/1.3 var(--g-mono, ui-monospace, monospace);
  color: var(--accent);
  letter-spacing: 0.04em;
}
.pinmap-row.is-power .pinmap-pin { color: var(--warm); }
.pinmap-row.is-free { opacity: 0.45; }
.pinmap-job { color: var(--g-text, #eef2f8); }
.pinmap-row.is-free .pinmap-job { color: var(--g-muted, #9fadc0); }
/* A pin whose job is the other board. It carries no new colour — a link is
   still a GPIO doing a job, and the card already spends its three inks on
   accent for a pin, warm for a rail and dim for a free one. What it gets is
   weight, because on a twin-node map it is the row that has to be read. */
.pinmap-row.is-link .pinmap-job { color: var(--g-text, #eef2f8); font-weight: 600; }
/* One heading per board, and only where there is more than one to tell apart —
   a single-board map is given no node letter and so grows no title bar. */
.pinmap-node {
  margin: 13px 0 0;
  font: 700 10px/1.3 var(--g-mono, ui-monospace, monospace);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--g-muted, #9fadc0);
}
.pinmap-node:first-of-type { margin-top: 6px; }
.pinmap-note {
  margin: 9px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--g-line, rgba(255, 255, 255, 0.09));
  color: var(--g-muted, #9fadc0);
  font-size: 10.5px;
  line-height: 1.45;
}

.player-menu-account { width: 100%; min-height: 38px; margin-top: 2px; flex-direction: row; color: var(--danger); background: transparent; border-color: var(--danger-line); }
@media (max-width: 460px) {
  .screen-game .hud-wallet { display: none; }
  .player-menu { right: calc(8px + var(--safe-right)); width: min(320px, calc(100vw - 16px)); }
}
@media (prefers-reduced-motion: reduce) {
  .player-language-submenu { animation: none; }
}

/* Profile: a campaign dossier etched into one board, with trophy sockets
   instead of dashboard cards. Earned hardware lights; locked hardware remains
   a readable silhouette so progress never turns into a mystery checklist. */
.screen-profile {
  --profile-board: #0d2442;
  --profile-edge: #47658d;
  --profile-copper: #d18a4b;
  --profile-silk: #e9f1ff;
  color: var(--text);
  background:
    radial-gradient(circle at 14% 16%, rgba(49, 215, 244, .08), transparent 28%),
    linear-gradient(145deg, #071425, #0a1c34 62%, #071323);
}
.profile-header { background: rgba(7, 18, 34, .92); border-color: rgba(91, 128, 176, .32); }
.profile-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 24px max(18px, calc((100vw - 1040px) / 2)) 42px;
}
.profile-identity {
  position: relative;
  display: grid;
  grid-template-columns: 88px 1fr minmax(170px, 260px);
  align-items: center;
  gap: 18px;
  padding: 20px;
  color: var(--profile-silk);
  background: var(--profile-board);
  border: 2px solid var(--profile-edge);
  border-radius: 18px;
  box-shadow: 0 5px 0 #040b14, inset 0 0 0 1px rgba(255,255,255,.04);
}
.profile-identity::after {
  content: '';
  position: absolute;
  right: 18px;
  bottom: -7px;
  width: 58%;
  height: 2px;
  background: var(--profile-copper);
  box-shadow: -16px 5px 0 -1px var(--profile-copper), 12px 10px 0 -1px var(--profile-copper);
  opacity: .72;
}
.profile-avatar {
  width: 82px;
  height: 82px;
  display: grid;
  place-items: center;
  overflow: hidden;
  color: #061321;
  background: var(--accent);
  border: 5px solid #071425;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--accent), 0 0 22px rgba(56,217,201,.24);
}
.profile-avatar img { width: 100%; height: 100%; object-fit: cover; }
.profile-avatar .ui-icon { width: 32px; height: 32px; }
.profile-callout { min-width: 0; }
.profile-kicker { margin-bottom: 5px; color: var(--accent); font: 700 10px/1 var(--font-technical); letter-spacing: .14em; text-transform: uppercase; }
.profile-callout h1 { overflow: hidden; font-size: clamp(23px, 4vw, 36px); line-height: 1.05; text-overflow: ellipsis; white-space: nowrap; }
.profile-rank { margin-top: 7px; color: #aabbd2; font-size: 12px; }
.profile-rank b { color: var(--warm); }
.profile-xp { display: grid; gap: 7px; }
.profile-xp > span { display: flex; align-items: baseline; justify-content: space-between; color: var(--accent); }
.profile-xp b { font-size: 22px; }
.profile-xp i { color: #8ea3bf; font: normal 10px/1 var(--font-technical); }
.profile-xp > div { height: 8px; overflow: hidden; border: 1px solid #48617f; border-radius: 5px; background: #071425; }
.profile-xp > div span { display: block; height: 100%; background: linear-gradient(90deg, var(--accent), #75f2d5); }
.profile-readouts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  margin: 24px 0;
  border-block: 1px solid rgba(209,138,75,.5);
}
.profile-readouts > div {
  min-height: 88px;
  display: grid;
  grid-template-columns: auto 1fr;
  align-content: center;
  gap: 3px 9px;
  padding: 14px;
  border-inline-end: 1px solid rgba(209,138,75,.28);
}
.profile-readouts > div:last-child { border-inline-end: 0; }
.profile-readouts .ui-icon { grid-row: 1 / span 3; align-self: center; width: 24px; height: 24px; color: var(--profile-copper); }
.profile-readouts b { color: var(--profile-silk); font-size: 19px; }
.profile-readouts span { color: #91a4be; font-size: 10px; text-transform: uppercase; letter-spacing: .08em; }
.profile-readouts small { color: #657b98; font-size: 9px; }
.trophy-case { padding: 20px; background: rgba(9, 29, 53, .74); border: 1px solid rgba(71,101,141,.72); border-radius: 18px; }
.trophy-case-head { display: flex; align-items: end; justify-content: space-between; gap: 16px; padding-bottom: 14px; border-bottom: 1px solid rgba(209,138,75,.36); }
.trophy-case-head p { margin-bottom: 4px; color: var(--profile-copper); font: 650 10px/1 var(--font-technical); letter-spacing: .12em; text-transform: uppercase; }
.trophy-case-head h2 { color: var(--profile-silk); font-size: 22px; }
.trophy-count { color: var(--warm); font-size: 18px; }
.trophy-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px 28px; }
.profile-trophy { display: grid; grid-template-columns: 58px 1fr; align-items: center; gap: 13px; min-height: 104px; padding: 14px 4px; border-bottom: 1px solid rgba(71,101,141,.34); }
.trophy-socket { width: 54px; height: 54px; display: grid; place-items: center; color: #627792; border: 1px dashed #536986; border-radius: 50%; background: #07182b; }
.trophy-socket .ui-icon { width: 24px; height: 24px; }
.profile-trophy.is-earned .trophy-socket { color: #071425; background: var(--warm); border: 3px solid #071425; box-shadow: 0 0 0 1px var(--warm), 0 0 18px rgba(255,200,87,.2); }
.profile-trophy h3 { color: var(--profile-silk); font-size: 13px; }
.profile-trophy p { margin-top: 4px; color: #8fa3be; font-size: 11px; line-height: 1.35; }
.profile-trophy small { display: inline-block; margin-top: 6px; color: #657b98; font: 650 9px/1 var(--font-technical); letter-spacing: .09em; text-transform: uppercase; }
.profile-trophy.is-earned small { color: var(--warm); }
.profile-trophy.is-locked h3 { color: #71849e; }
.profile-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 18px; }
.profile-actions .btn { display: inline-flex; align-items: center; gap: 8px; }
.profile-actions .ui-icon { width: 17px; height: 17px; }
@media (max-width: 720px) {
  .profile-scroll { padding: 14px 12px 32px; }
  .profile-identity { grid-template-columns: 66px 1fr; padding: 15px; }
  .profile-avatar { width: 62px; height: 62px; }
  .profile-xp { grid-column: 1 / -1; }
  .profile-readouts { grid-template-columns: repeat(2, 1fr); }
  .profile-readouts > div:nth-child(2) { border-inline-end: 0; }
  .trophy-grid { grid-template-columns: 1fr; }
  .profile-actions { flex-direction: column; }
  .profile-actions .btn { justify-content: center; }
}

:root:is([data-lang='he'], [data-lang='ar']) .language-switch:not(.is-hosted) {
  right: auto;
  left: calc(var(--corner-inset) + var(--safe-left));
}

/* The account card's two links: create on the right, where the row starts. */
:root:is([data-lang='he'], [data-lang='ar']) .account-links {
  flex-direction: row-reverse;
}

/* The two headers: back to the right, where the eye starts. */
:root:is([data-lang='he'], [data-lang='ar']) .app-header {
  flex-direction: row-reverse;
  padding-right: calc(14px + var(--safe-right));
  padding-left: calc(14px + var(--corner-lane) + var(--safe-left));
}
:root:is([data-lang='he'], [data-lang='ar']) .game-hud,
:root:is([data-lang='he'], [data-lang='ar']) .hud-line,
:root:is([data-lang='he'], [data-lang='ar']) .hud-help {
  flex-direction: row-reverse;
}
:root:is([data-lang='he'], [data-lang='ar']) .hud-wallet,
:root:is([data-lang='he'], [data-lang='ar']) .player-menu-audio,
:root:is([data-lang='he'], [data-lang='ar']) .player-menu-language,
:root:is([data-lang='he'], [data-lang='ar']) .player-language-submenu,
:root:is([data-lang='he'], [data-lang='ar']) .reward-preview > div > span {
  direction: rtl;
}
:root:is([data-lang='he'], [data-lang='ar']) .player-menu-language .ui-icon:last-child,
:root:is([data-lang='he'], [data-lang='ar']) .player-language-submenu > header .ui-icon {
  transform: scaleX(-1);
}
:root:is([data-lang='he'], [data-lang='ar']) .meter-reading,
:root:is([data-lang='he'], [data-lang='ar']) .concept-glossary > div {
  direction: rtl;
  text-align: right;
}

/* The instrument strip and the silkscreen rows read right to left. */
:root:is([data-lang='he'], [data-lang='ar']) .board-daily,
:root:is([data-lang='he'], [data-lang='ar']) .daily-pay,
:root:is([data-lang='he'], [data-lang='ar']) .board-badges,
:root:is([data-lang='he'], [data-lang='ar']) .board-strip,
:root:is([data-lang='he'], [data-lang='ar']) .zone-group-mark,
:root:is([data-lang='he'], [data-lang='ar']) .readout-head,
:root:is([data-lang='he'], [data-lang='ar']) .readout-gauges,
:root:is([data-lang='he'], [data-lang='ar']) .zone-silk,
:root:is([data-lang='he'], [data-lang='ar']) .daily-bonus,
:root:is([data-lang='he'], [data-lang='ar']) .daily-bonus span,
:root:is([data-lang='he'], [data-lang='ar']) .rail-head,
:root:is([data-lang='he'], [data-lang='ar']) .status-msg,
:root:is([data-lang='he'], [data-lang='ar']) .modal-check,
:root:is([data-lang='he'], [data-lang='ar']) .modal-actions {
  flex-direction: row-reverse;
}

/* A stack has no side to reverse; what turns it round is the end it lines up
   against. */
:root:is([data-lang='he'], [data-lang='ar']) .gauge {
  align-items: flex-end;
}

/* A meter fills from the side the reading starts on. */
:root:is([data-lang='he'], [data-lang='ar']) .xp-meter,
:root:is([data-lang='he'], [data-lang='ar']) .zone-meter,
:root:is([data-lang='he'], [data-lang='ar']) .hud-steps,
:root:is([data-lang='he'], [data-lang='ar']) .tutorial-steps {
  transform: scaleX(-1);
}
:root:is([data-lang='he'], [data-lang='ar']) .hud-steps,
:root:is([data-lang='he'], [data-lang='ar']) .tutorial-steps {
  flex-direction: row-reverse;
  transform: none;
}

/* The menu hangs off the button that opens it, and that button changes corner
   with the header. Left where it was, the panel would open on the far side of
   the screen from the thing that was pressed. */
:root:is([data-lang='he'], [data-lang='ar']) .player-menu {
  right: auto;
  left: calc(12px + var(--safe-left));
}

/* The pin map reads right to left with everything else: a grid follows the
   direction it is given, so the pin ends up at the start of its own row rather
   than stranded on the far side of what it names. The pin names themselves
   stay latin and upright — GPIO1 is GPIO1 in every language, and the bubble it
   opens from keeps the right edge in both, like the world button beside it. */
:root:is([data-lang='he'], [data-lang='ar']) .pinmap-panel {
  direction: rtl;
}
:root:is([data-lang='he'], [data-lang='ar']) .pinmap-pin {
  direction: ltr;
  text-align: end;
}

/* A volume slider reads from the side its own row does: the native control
   turns round with `direction`, and its hand-painted fill has to be told. */
:root:is([data-lang='he'], [data-lang='ar']) .audio-control-row input[type='range'] {
  --audio-fill-end: left;
}

/* Mission 01 is the top right pad, and the campaign runs away from it. */
:root:is([data-lang='he'], [data-lang='ar']) .zone-pads {
  direction: rtl;
}
:root:is([data-lang='he'], [data-lang='ar']) .board-daily {
  text-align: right;
}

/* The dialog's close button, and the room its title leaves for it. */
:root:is([data-lang='he'], [data-lang='ar']) .modal-close {
  right: auto;
  left: 12px;
}
:root:is([data-lang='he'], [data-lang='ar']) .modal-card h2 {
  padding-right: 0;
  padding-left: 34px;
}

/* The answer's wiring table: from and to swap ends, so the arrow points the
   way the sentence is read. */
:root:is([data-lang='he'], [data-lang='ar']) .solution-wiring li {
  direction: rtl;
}
:root:is([data-lang='he'], [data-lang='ar']) .solution-wiring li > span:last-child {
  text-align: left;
}
:root:is([data-lang='he'], [data-lang='ar']) .solution-wiring .ui-icon {
  transform: scaleX(-1);
}

/* On the dial the two corner buttons swap corners with everything else. */
:root:is([data-lang='he'], [data-lang='ar']) #flip-btn {
    left: auto;
    right: calc(10px + var(--safe-right));
  }
  :root:is([data-lang='he'], [data-lang='ar']) #zoom-reset {
    right: auto;
    left: calc(10px + var(--safe-left));
  }
  /* #world-btn is the one that does not, and there is no rule for it here on
     purpose: it is chrome you reach for with the board already in mind, like
     the strand colours below, so it keeps the right edge in both languages. */
  /* The line of strand colours and the bin are the one row of chrome that does
     not turn round in Hebrew, and does not follow the selection across the
     board either. They are reached for mid-gesture — a wire in one hand, the
     colour in the other — and a control that is somewhere else each time it
     opens is a control you have to look for. So: same edge, both languages,
     whatever is selected and wherever it sits. */

/* ---------- glass is a laptop material ----------
   Every pane of chrome over the board is frosted: backdrop-filter on the
   header, the instrument strip, the dial, the pill, the modal and its card.
   Under each of them the canvas repaints sixty times a second, and a blur
   over something that changes has to be recomputed every frame it changes.
   A laptop GPU does not notice. A 2018 mid-range phone (Galaxy A8, Mali-G71)
   measured 8 fps with the intro sheet open and 16 with it closed — and 37
   the moment the blur was gone, with the canvas at full resolution and the
   JS at seven milliseconds a frame. Resolution was tried and changed
   nothing; it was the glass.

   So on touch devices the glass is flat. The !important is deliberate: the
   blur is set in twenty rules at every specificity, some inside media
   queries of their own, and what is being asserted here is a fact about the
   hardware rather than a design preference that one of them should be able
   to win back. The ink is made a touch more opaque so the board does not
   read sharp through an un-frosted pane. */
@media (pointer: coarse) {
  body[data-screen='game'] .screen-game *,
  body[data-screen='game'] .modal-overlay,
  body[data-screen='game'] .modal-card {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  body[data-screen='game'] .screen-game {
    --g-ink: rgba(6, 12, 22, 0.9);
  }
}
