/* The three puzzle boards. */

.puzzle {
  display: grid;
  gap: var(--space-5);
}
.puzzle__stage { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.puzzle__meta { gap: var(--space-2); }
.puzzle__controls { gap: var(--space-2); }

.feedback {
  margin: 0;
  min-height: 1.5em;
  font-size: var(--step--1);
  font-weight: 550;
}
.feedback.is-ok { color: var(--ok); }
.feedback.is-bad { color: var(--danger); }

/* Wider than a phone: board beside its controls. */
@media (min-width: 860px) {
  .puzzle--robot { grid-template-columns: 1fr 300px; align-items: start; }
  .puzzle--logic { grid-template-columns: 300px 1fr; align-items: start; }
}

/* ==================================================================== *
 * Circuits
 * ==================================================================== */

.board {
  position: relative;
  display: grid;
  grid-template-rows: repeat(var(--rows), 1fr);

  /* The gap and padding are variables because the robot has to translate by
     exactly one cell PLUS one gap. Hardcoding 3px in two places is how the
     robot drifts out of alignment on wide boards. */
  --gap: 3px;
  --pad: 3px;

  gap: var(--gap);
  padding: var(--pad);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);

  width: 100%;
  max-width: min(100%, calc(var(--cols) * 68px));
  aspect-ratio: var(--cols) / var(--rows);
}

.board__row { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: var(--gap); }

.board__cell {
  position: relative;
  border-radius: 4px;
  background: var(--tile);
  display: grid;
  place-items: center;
  transition: background var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.board__cell.is-wall { background: var(--tile-wall); }
.board__cell.is-goal { background: var(--gold-soft); box-shadow: inset 0 0 0 2px var(--gold); }

/* The wall the robot just hit, so the failure points at itself. */
.board__cell.is-crash {
  background: var(--danger);
  box-shadow: 0 0 0 3px var(--danger), 0 0 18px color-mix(in srgb, var(--danger) 60%, transparent);
  animation: crashPulse 0.5s var(--ease) 3;
}
@keyframes crashPulse { 50% { opacity: 0.55; } }
@media (prefers-reduced-motion: reduce) {
  .board__cell.is-crash { animation: none; }
}

.chip {
  width: 38%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 10px color-mix(in srgb, var(--gold) 55%, transparent);
  transition: opacity var(--normal) var(--ease), transform var(--normal) var(--ease);
}
.board__cell.is-collected .chip { opacity: 0; transform: scale(0.2); }

.goal-ring {
  width: 54%;
  aspect-ratio: 1;
  border: 2px dashed var(--gold);
  border-radius: 50%;
}

/* --- The robot -------------------------------------------------------- *
 * Two nested elements on purpose: the outer one only ever translates, the
 * inner one only ever rotates. Combining both on one element makes the
 * movement animation swing through the rotation, which reads as the robot
 * sliding sideways while spinning.
 * --------------------------------------------------------------------- */

.robot {
  position: absolute;
  top: var(--pad);
  left: var(--pad);

  /* Exactly one cell. */
  width: calc((100% - 2 * var(--pad) - (var(--cols) - 1) * var(--gap)) / var(--cols));
  height: calc((100% - 2 * var(--pad) - (var(--rows) - 1) * var(--gap)) / var(--rows));

  /* The transform is set from JS as a fully resolved value, NOT with var()
     inside it. Chrome will not re-resolve a transform that depends on an
     unregistered custom property while a transition on that transform is
     active: the element simply stays where it was. With `transition: none` the
     same declaration computes correctly, which is what makes it so confusing.
     The transition stays here; only the value comes from script. */
  transition: transform var(--normal) var(--ease);

  display: grid;
  place-items: center;
  pointer-events: none;
  z-index: 2;
}

.robot__craft {
  position: relative;
  width: 84%;
  height: 84%;
  display: grid;
  place-items: center;
  /* Set from JS for the same reason as the translate above. */
  transition: transform var(--normal) var(--ease);
}

.robot__body {
  width: 74%;
  height: 74%;
  border-radius: 26%;
  background: var(--robot);
  box-shadow: 0 0 12px color-mix(in srgb, var(--robot) 45%, transparent);
}

/* A solid arrowhead at the leading edge. This is the direction indicator —
   it must be unmistakable at a glance, not a subtle dot. */
.robot__nose {
  position: absolute;
  top: -2%;
  left: 50%;
  translate: -50% 0;
  width: 0;
  height: 0;
  border-left: 0.22em solid transparent;
  border-right: 0.22em solid transparent;
  border-bottom: 0.26em solid var(--robot);
  font-size: min(3.4vw, 22px);
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--robot) 60%, transparent));
}

/* Two eyes near the nose, so the "front" reads even without the arrow. */
.robot__eyes {
  position: absolute;
  top: 34%;
  left: 50%;
  translate: -50% 0;
  display: flex;
  gap: 22%;
  width: 46%;
  justify-content: space-between;
}
.robot__eyes i {
  display: block;
  width: 26%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--bg-deep);
}

/* Facing readout beside the board — the same information as text, because an
   arrow on a small tile is not enough on its own. */
.facing {
  font-variant-numeric: tabular-nums;
  text-transform: capitalize;
}
.facing__arrow {
  display: inline-block;
  transition: transform var(--normal) var(--ease);
}

/* --- Program panel ---------------------------------------------------- */

.program {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.program h3 { font-size: var(--step-0); }
.program__func { border-top: 1px solid var(--border); padding-top: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }

.program__list {
  list-style: none;
  margin: 0;
  padding: var(--space-2);
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  min-height: 52px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 260px;
  overflow-y: auto;
}
.program__empty { text-align: center; padding: var(--space-2); margin: 0; }

.program__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: 4px;
  background: var(--surface-raised);
  font-size: var(--step--1);
  margin: 0;
}
.program__num { color: var(--ink-faint); font-variant-numeric: tabular-nums; min-width: 1.4em; font-size: 11px; }
.program__glyph { color: var(--accent); font-weight: 700; min-width: 1.4em; text-align: center; }
.program__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.program__remove {
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  flex: none;
  border: none;
  background: transparent;
  color: var(--ink-faint);
  border-radius: 4px;
  cursor: pointer;
}
.program__remove:hover { background: var(--danger-soft); color: var(--danger); }
@media (pointer: coarse) { .program__remove { width: 36px; height: 36px; } }

.palette { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: var(--space-2); }

.palette__btn {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-min);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px dashed var(--border-strong);
  background: var(--surface-sunken);
  color: var(--ink);
  font: inherit;
  font-size: var(--step--1);
  cursor: pointer;
  text-align: left;
}
.palette__btn:hover:not(:disabled) { border-style: solid; border-color: var(--accent); background: var(--accent-soft); }
.palette__btn:disabled { opacity: 0.4; cursor: not-allowed; }
.palette__glyph { color: var(--accent); font-weight: 700; font-size: var(--step-0); min-width: 1.5em; text-align: center; }
.palette__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ==================================================================== *
 * Recall
 * ==================================================================== */

.memory__instruction { margin: 0; font-size: var(--step-1); font-weight: 600; }
.memory__status { margin: 0; min-height: 1.5em; color: var(--ink-muted); font-weight: 550; }

.memory__pad {
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  gap: var(--space-3);
  width: 100%;
  max-width: min(100%, 420px);
  aspect-ratio: 1;
}
.memory__pad.is-wrong { animation: shake 0.4s var(--ease); }

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-7px); }
  40% { transform: translateX(7px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .memory__pad.is-wrong { animation: none; outline: 3px solid var(--danger); outline-offset: 4px; }
}

.memory__tile {
  border: 2px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  color: var(--ink-faint);
  cursor: pointer;
  display: grid;
  place-items: center;
  font: inherit;
  font-size: var(--step-2);
  font-weight: 700;
  transition: background var(--fast) var(--ease), transform var(--fast) var(--ease), color var(--fast) var(--ease);
}
.memory__tile:disabled { cursor: default; }

/* Each tile keeps its own hue, but the number is what identifies it. */
.memory__tile[data-hue="1"] { border-color: var(--hue-1); }
.memory__tile[data-hue="2"] { border-color: var(--hue-2); }
.memory__tile[data-hue="3"] { border-color: var(--hue-3); }
.memory__tile[data-hue="4"] { border-color: var(--hue-4); }
.memory__tile[data-hue="5"] { border-color: var(--hue-5); }
.memory__tile[data-hue="6"] { border-color: var(--hue-6); }

.memory__tile.is-lit[data-hue="1"] { background: var(--hue-1); }
.memory__tile.is-lit[data-hue="2"] { background: var(--hue-2); }
.memory__tile.is-lit[data-hue="3"] { background: var(--hue-3); }
.memory__tile.is-lit[data-hue="4"] { background: var(--hue-4); }
.memory__tile.is-lit[data-hue="5"] { background: var(--hue-5); }
.memory__tile.is-lit[data-hue="6"] { background: var(--hue-6); }
.memory__tile.is-lit { color: var(--bg-deep); transform: scale(1.04); }

.memory__tile:not(:disabled):active { transform: scale(0.97); }

/* ==================================================================== *
 * Deduction
 * ==================================================================== */

.logic__panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.logic__panel h3 { font-size: var(--step-0); }
.logic__clues { margin: 0; padding-left: var(--space-5); font-size: var(--step--1); }
.logic__clues li { margin-top: var(--space-2); }

.logic__grids {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}
.logic__gridlabel { margin: 0 0 var(--space-2); text-transform: uppercase; letter-spacing: 0.06em; font-size: 11px; font-weight: 650; }

.logic__grid { border-collapse: separate; border-spacing: 3px; width: 100%; }
.logic__corner { width: 34px; }
.logic__head {
  font-size: var(--step--1);
  font-weight: 650;
  color: var(--ink-muted);
  text-align: center;
  padding: 2px;
}
.logic__head--row { text-align: right; padding-right: var(--space-2); white-space: nowrap; }

.logic__cell {
  width: 100%;
  aspect-ratio: 1;
  min-width: 34px;
  min-height: 34px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: var(--surface-sunken);
  color: var(--ink);
  font: inherit;
  font-size: var(--step-0);
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.logic__cell:hover { border-color: var(--accent); }
.logic__cell.is-yes { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
.logic__cell.is-no { background: var(--surface); color: var(--ink-faint); }

@media (pointer: coarse) {
  .logic__cell { min-width: var(--touch-min); min-height: var(--touch-min); }
}

/* ------------------------------------------------------------------ *
 * Pathways
 * ------------------------------------------------------------------ */

.paths__hint {
  color: var(--ink-muted);
  font-size: var(--step--1);
  max-width: var(--measure);
}

.paths__grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  gap: var(--space-1);
  width: 100%;
  /* Square cells whatever the aspect of the level, so a segment's arms always
     meet its neighbours' at the same place on the edge. */
  max-width: min(100%, calc(var(--cols) * 76px));
  margin-inline: auto;
}

.paths__gap { aspect-ratio: 1; }

.paths__cell {
  aspect-ratio: 1;
  min-width: 0;
  display: grid;
  place-items: center;
  padding: 0;
  border: 2px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  color: var(--ink-faint);
  cursor: pointer;
  transition: color var(--fast) var(--ease), background var(--fast) var(--ease),
              border-color var(--fast) var(--ease);
}

.paths__cell svg { width: 86%; height: 86%; }

/* Live segments are the signal, so they carry the accent. Colour is never the
   only cue: the label says "powered", and a node that is live also fills in. */
.paths__cell.is-live {
  color: var(--accent);
  border-color: var(--accent-soft);
  background: var(--surface-raised);
}

/* The source and the nodes are the cells that matter, so they read as
   something other than pipe even before anything is powered. */
.paths__cell[data-role='source'],
.paths__cell[data-role='node'] {
  border-color: var(--border-strong);
  border-style: dashed;
}

.paths__cell[data-role='source']::after,
.paths__cell[data-role='node']::after {
  content: '';
  position: absolute;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 2px solid currentColor;
}

.paths__cell[data-role='source'] { position: relative; }
.paths__cell[data-role='node'] { position: relative; }

.paths__cell[data-role='source']::after { background: var(--accent); border-color: var(--accent); }
.paths__cell[data-role='node'].is-live::after { background: currentColor; }

/* An arm pointing at nothing. Marked with a dotted edge as well as a colour,
   since "which end is leaking" is the one thing the player has to see. */
.paths__cell.is-leaking {
  border-color: var(--warn);
  border-style: dotted;
}

.paths__status {
  color: var(--ink-muted);
  font-size: var(--step--1);
  min-height: 1.5em;
}

@media (pointer: coarse) {
  .paths__cell { min-height: var(--touch-min); }
}

/* ------------------------------------------------------------------ *
 * Sequence
 * ------------------------------------------------------------------ */

.patterns__prompt,
.patterns__question {
  color: var(--ink-muted);
  font-size: var(--step--1);
  max-width: var(--measure);
}

.patterns__question { font-weight: 600; color: var(--ink); }

.patterns__strip,
.patterns__choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.patterns__term,
.patterns__choice {
  display: grid;
  place-items: center;
  min-width: 68px;
  min-height: 68px;
  padding: var(--space-2);
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunken);
}

.patterns__term--unknown {
  border-style: dashed;
  border-color: var(--border-strong);
}

.patterns__qmark {
  font-size: var(--step-2);
  font-weight: 700;
  color: var(--ink-faint);
}

.patterns__choice {
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}

.patterns__choice:hover:not(:disabled) { border-color: var(--accent); }
.patterns__choice:disabled { cursor: default; opacity: 0.45; }

/* Right and wrong are never signalled by colour alone: the status line says
   which it was, and a wrong option is also dimmed and disabled. */
.patterns__choice.is-right { border-color: var(--ok); background: var(--ok-soft); }
.patterns__choice.is-wrong { border-color: var(--danger); background: var(--danger-soft); }

/* The figures themselves. Shapes are drawn in CSS rather than loaded, so they
   inherit the theme's ink colour and need no asset. */
.patterns__figure {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 4px;
  max-width: 52px;
}

.patterns__shape {
  width: 16px;
  height: 16px;
  background: var(--ink);
}

.patterns__shape[data-shape='circle'] { border-radius: 50%; }
.patterns__shape[data-shape='square'] { border-radius: 2px; }
.patterns__shape[data-shape='triangle'] { clip-path: polygon(50% 0%, 100% 100%, 0% 100%); }
.patterns__shape[data-shape='diamond'] { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }

/* An outlined shape is the same silhouette with the middle removed, so it
   stays readable at this size rather than becoming a thin wire. */
.patterns__shape[data-shape='circle'][data-fill='outline'],
.patterns__shape[data-shape='square'][data-fill='outline'] {
  background: none;
  border: 3px solid var(--ink);
}
.patterns__shape[data-shape='triangle'][data-fill='outline'],
.patterns__shape[data-shape='diamond'][data-fill='outline'] {
  /* clip-path cannot leave a hollow centre, so the cut-out is a second clipped
     layer painted in the surface colour and inset from the edge. */
  position: relative;
}
.patterns__shape[data-shape='triangle'][data-fill='outline']::after,
.patterns__shape[data-shape='diamond'][data-fill='outline']::after {
  content: '';
  position: absolute;
  inset: 4px;
  background: var(--surface-sunken);
}
.patterns__shape[data-shape='triangle'][data-fill='outline']::after {
  clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
.patterns__shape[data-shape='diamond'][data-fill='outline']::after {
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}

.patterns__status {
  color: var(--ink-muted);
  font-size: var(--step--1);
  min-height: 1.5em;
}

@media (pointer: coarse) {
  .patterns__choice { min-width: var(--touch-min); min-height: var(--touch-min); }
}

/* ------------------------------------------------------------------ *
 * Reckoning
 * ------------------------------------------------------------------ */

.numbers__prompt {
  color: var(--ink-muted);
  font-size: var(--step--1);
  max-width: var(--measure);
}

.numbers__target {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-5);
  border: 2px solid var(--accent-soft);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  align-self: center;
}

.numbers__targetValue {
  font-size: var(--step-4);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--accent);
}

.numbers__pool,
.numbers__ops {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}

.numbers__tile,
.numbers__op {
  min-width: var(--touch-min);
  min-height: var(--touch-min);
  padding: 0 var(--space-3);
  border: 2px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  color: var(--ink);
  font: inherit;
  font-size: var(--step-1);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}

/* Selection is shown by a filled swatch and by aria-pressed, never by colour
   alone — a screen reader gets the same signal the eye does. */
.numbers__tile.is-selected,
.numbers__op.is-selected {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}

.numbers__tile:disabled,
.numbers__op:disabled { opacity: 0.4; cursor: default; }

.numbers__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-muted);
}

.numbers__status {
  color: var(--ink-muted);
  font-size: var(--step--1);
  min-height: 1.5em;
}

/* ------------------------------------------------------------------ *
 * Cadence
 * ------------------------------------------------------------------ */

.melody__prompt,
.melody__instruction {
  color: var(--ink-muted);
  font-size: var(--step--1);
  max-width: var(--measure);
}

.melody__instruction { color: var(--ink); font-weight: 600; }

/* The staff. Each bar sits at its note's own height, so the phrase has a
   visible shape and not only an audible one — that is what makes the puzzle
   playable without hearing it. */
.melody__staff {
  list-style: none;
  margin: 0;
  padding: var(--space-2);
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  min-height: 108px;
  border-radius: var(--radius);
  background: var(--surface-sunken);
}

.melody__bar {
  width: 22px;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--accent);
  /* 8 pitches over the staff's usable height. */
  height: calc(var(--pitch) * 11%);
}

/* Eight pads cannot each be 44px wide inside a 320px viewport, and shrinking
   them below that is the wrong trade: the pads are the only control here. So
   the keyboard keeps its targets and scrolls inside itself on narrow screens,
   which also keeps the left-to-right pitch order a scale depends on. The page
   itself still never scrolls sideways. */
.melody__keyboard {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(44px, 1fr);
  gap: var(--space-1);
  width: 100%;
  align-items: end;
  overflow-x: auto;
  padding-bottom: var(--space-1);
}

.melody__keyboard.is-wrong { animation: shake 0.4s var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .melody__keyboard.is-wrong { animation: none; outline: 3px solid var(--danger); outline-offset: 4px; }
}

/* Pads rise left to right. The height, the number and the tone all say the
   same thing, so no two pads are told apart by pitch alone. */
.melody__pad {
  display: grid;
  align-content: end;
  justify-items: center;
  padding-bottom: var(--space-2);
  min-height: var(--touch-min);
  height: calc(46px + var(--pitch) * 7px);
  border: 2px solid var(--border-strong);
  border-radius: var(--radius-sm) var(--radius-sm) var(--radius) var(--radius);
  background: var(--surface-sunken);
  color: var(--ink-muted);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease),
              border-color var(--fast) var(--ease);
}

.melody__pad:disabled { cursor: default; }

.melody__pad.is-lit {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Where a transposed phrase has to start. Dashed as well as tinted, so the
   instruction above and the pad agree without relying on colour. */
.melody__pad.is-target {
  border-color: var(--gold);
  border-style: dashed;
  background: var(--gold-soft);
}

.melody__status {
  color: var(--ink-muted);
  font-size: var(--step--1);
  min-height: 1.5em;
}
