/* ==========================================================================
   DEFUSAL
   A calm, chunky, two-sided device. The front carries the modules and the
   clock; the noisy business — wiring, payload, battery — lives on the back,
   where it says "bomb" without shouting over the puzzles.

   The whole case is laid out at a fixed design size and scaled to the
   viewport in one transform, so nothing ever reflows.
   ========================================================================== */

/* Every face the game uses is bundled. A font installed on the machine this
   was built on is not on the machine it is shown on — American Typewriter and
   Avenir, which the old stacks leaned on, do not exist on Windows at all, so
   half the players were reading Courier New — and there is no network to fall
   back to.

   Two families, not four. The segment face is the device talking; IBM Plex
   is everything else, one superfamily in four cuts that were drawn to sit
   together (OFL, fonts/plex/OFL.txt). That is the pattern in the games this
   borrows from: Keep Talking pairs one display face with one document face;
   Alien: Isolation runs its whole interface in one grotesque and keeps a
   second, diegetic face for the terminals. Latin subsets, about 260KB. */
@font-face {
  font-family: "Seven Segment";
  src: url("fonts/seven-segment.ttf") format("truetype");
  font-weight: normal; font-style: normal; font-display: block;
}
@font-face { font-family: "Plex Sans"; src: url("fonts/plex/ibm-plex-sans-latin-400-normal.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Sans"; src: url("fonts/plex/ibm-plex-sans-latin-600-normal.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Sans"; src: url("fonts/plex/ibm-plex-sans-latin-700-normal.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Sans Condensed"; src: url("fonts/plex/ibm-plex-sans-condensed-latin-500-normal.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Sans Condensed"; src: url("fonts/plex/ibm-plex-sans-condensed-latin-600-normal.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Sans Condensed"; src: url("fonts/plex/ibm-plex-sans-condensed-latin-700-normal.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Mono"; src: url("fonts/plex/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Mono"; src: url("fonts/plex/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Mono"; src: url("fonts/plex/ibm-plex-mono-latin-600-normal.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Serif"; src: url("fonts/plex/ibm-plex-serif-latin-400-normal.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Plex Serif"; src: url("fonts/plex/ibm-plex-serif-latin-400-italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "Plex Serif"; src: url("fonts/plex/ibm-plex-serif-latin-700-normal.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap; }

:root {
  --bg:      #070d11;
  --bg-2:    #16262f;
  --ink:     #1a2429;   /* outlines on the lit faces */
  /* Quiet, not faint. These sit on a moving sky, at 10 and 11px, in a face
     with thin strokes — the old #7c8f98 measured fine against a flat swatch
     and was genuinely hard to read against the backdrop. */
  --ink-2:   #a4b8c2;   /* quiet labels, still plainly readable */
  --ink-3:   #86a0ab;   /* the quietest thing on a screen */
  --ink-off: #56676e;   /* switched OFF, and meant to look it */
  --paper:   #eceff0;   /* module faces stay lit and legible */
  --soft:    #d7dedf;
  --case:    #2c383e;   /* the shell */
  --case-d:  #1a2429;
  --rim:     #6fd7e8;   /* the light that is not ours */
  --amber:   #ffc861;
  --red:     #d0574f;
  --blue:    #4180ae;
  --green:   #4f9d5d;
  --yellow:  #ddb63f;
  --violet:  #7a6bb0;
  --text:    #dbe9ef;   /* body copy, lifted off the old #cfdde2 */

  /* ---- two families, five roles ---------------------------------------
     The old set was four unrelated families — a seven-segment readout, a
     slab typewriter, a geometric sans and the system mono — and it looked
     like it: every screen had a join where one voice met another. Now the
     segment face is the device, and every other role is a cut of Plex.

       --seg     the device: the clock, a serial, the title, a verdict, a
                 device's name. Lit, and glows.
       --round   controls and labels: every key, button, tab and heading in
                 a panel. Plex Sans Condensed, set in capitals and tracked —
                 the printed legend on a piece of equipment.
       --prose   small print and interface sentences: blurbs, captions, a
                 note under a field. Plex Sans, drawn for screens.
       --doc     the manual's prose. Plex Serif — the manual is a document,
                 as it is in Keep Talking, and the serif is the one change
                 that says "paper" without leaving the family.
       --mono    figures that must line up: a table, a round code. Plex Mono.
     The name --round is kept so every rule that meant "the game's face"
     still does. -------------------------------------------------------- */
  --seg: "Seven Segment", "Plex Mono", ui-monospace, monospace;
  --round: "Plex Sans Condensed", "Plex Sans", "Arial Narrow", system-ui, sans-serif;
  --prose: "Plex Sans", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --doc: "Plex Serif", Georgia, "Times New Roman", serif;
  --mono: "Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* ---- one scale, used everywhere -------------------------------------
     The sizes used to be picked per rule: 10, 11, 12, 13, 15, 17, 19, 21,
     22, 25, 26 — eleven sizes, most of them a pixel apart, which is how a
     screen ends up with no hierarchy at all. Six steps, and a heading is
     obviously a heading. ------------------------------------------------- */
  --t-hero:  clamp(42px, 7.6vw, 88px);   /* the title, a verdict */
  --t-head:  clamp(21px, 2.9vw, 30px);   /* a screen heading, a device name */
  --t-sub:   17px;                       /* a heading inside a panel */
  --t-body:  15px;                       /* anything read in sentences */
  --t-label: 12px;                       /* a label on a control or a figure */
  --t-micro: 10px;                       /* the quietest thing that has words */

  /* and one set of tracking values, so a label is spaced like every other */
  --trk-hero:  .3em;
  --trk-head:  .12em;
  --trk-label: .18em;

  /* ---- one set of durations ------------------------------------------
     Everything moved too fast to be watched. A transition is not an
     interruption to be got out of the way — it is the thing that tells the
     player they went somewhere. These are the only numbers used. ---------- */
  --t-quick:  .22s;    /* a control answering a press */
  --t-mid:    .42s;    /* something small changing state */
  --t-slow:   .68s;    /* a screen arriving */
  --t-cut:    .92s;    /* the iris covering the glass */
  --ease:     cubic-bezier(.22, .9, .25, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-in:  cubic-bezier(.55, 0, .85, .35);
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0; height: 100%;
  color: var(--text);
  font-family: var(--round);
  font-size: 15px;
  overflow: hidden;
}

/* Only the root paints a ground. The body used to carry this same gradient,
   which put an opaque layer directly over the sky at z-index -1 — the whole
   backdrop was being painted behind it and was never really seen. */
html {
  background: radial-gradient(ellipse at 50% 12%, var(--bg-2) 0%, var(--bg) 62%, #04080a 100%);
}
body { background: transparent; }

[hidden] { display: none !important; }
.screen { position: fixed; inset: 0; }

/* ======================= menu / result ================================== */

.menu { display: grid; place-items: center; padding: 24px; overflow: auto; }
.sheet { width: min(auto, 100%); text-align: center; }

.title {
  margin: 0 0 22px;
  font-family: var(--seg);
  font-size: var(--t-hero);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: .1em; text-indent: .1em;
  color: #eaf7fb;
  /* the glow is the accent of whichever device is in front of you */
  text-shadow: 0 0 6px color-mix(in srgb, var(--accent, #6fd7e8) 85%, #fff),
               0 0 28px color-mix(in srgb, var(--accent, #6fd7e8) 70%, transparent),
               0 0 72px color-mix(in srgb, var(--accent, #6fd7e8) 45%, transparent);
  transition: text-shadow .6s ease, color .6s ease;
}
/* the unlit segments behind the lit ones, the way a real readout has them */
.title { position: relative; }

/* --- the campaign -------------------------------------------------------- */

.freeplay { margin-top: 22px; }
.progress { margin: 24px 0 6px; font-family: var(--prose); font-size: var(--t-label);
            font-weight: 700; letter-spacing: .14em; color: var(--ink-2);
            text-shadow: 0 1px 10px rgba(0,0,0,.8); }

.choices { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
.choices.two { grid-template-columns: repeat(2, 190px); justify-content: center; }

.key {
  display: grid; gap: 5px; justify-items: center;
  padding: 16px 10px;
  font-family: var(--round); color: var(--text);
  background: #17242b;
  border: 1px solid #2c4048;
  border-radius: 10px;
  cursor: pointer;
  transition: transform .12s ease, border-color .18s ease, background .18s ease;
}
.key b { font-size: 15px; font-weight: 400; letter-spacing: .12em;
          text-shadow: 0 0 16px rgba(160, 220, 240, .28), 0 1px 2px rgba(0,0,0,.6); }
.key:hover { transform: translateY(-2px); border-color: var(--rim); background: #1e2f38; }
.key:active { transform: none; }
.key.on { border-color: var(--rim); }

.practice {
  margin-top: 18px; padding: 16px;
  display: flex; gap: 20px; align-items: center; justify-content: center;
  flex-wrap: wrap;
  border: 1px solid #2c4048; border-radius: 10px;
  background: #17242b;
}
.field { display: inline-flex; align-items: center; gap: 9px; }
.field span { font-family: var(--prose); font-size: var(--t-micro); font-weight: 700;
               letter-spacing: var(--trk-label); color: var(--ink-2); }
.field input, .answer input {
  font-family: var(--round); font-size: 20px; text-align: center;
  width: 84px; padding: 8px;
  color: var(--ink); background: #fff;
  border: 2px solid var(--ink); border-radius: 7px;
  transition: box-shadow .15s ease;
}
.field input:focus, .answer input:focus {
  outline: none; box-shadow: 0 0 0 3px var(--rim);
}

/* No margin on the base class. A control that carries layout spacing cannot
   be put in a row with anything else without knocking it out of line, which
   is exactly what it did to the mode screen's BACK. */
.sound {
  font-family: var(--round); font-size: 12px;
  color: var(--ink-2); background: transparent;
  border: 1px solid #2c4048; border-radius: 8px;
  padding: 8px 15px; cursor: pointer; letter-spacing: .12em;
}
.sound { text-shadow: 0 1px 8px rgba(0,0,0,.7); }
.sound:hover { color: var(--text); border-color: var(--rim);
               box-shadow: 0 0 16px rgba(111,215,232,.22); }
.sound.off { color: var(--ink-off); }

.verdict {
  margin: 0 0 18px;
  font-family: var(--seg);
  font-size: var(--t-hero); line-height: 1.05;
  font-weight: 400;
  /* No text-indent. It was there to cancel the trailing letter-space, and
     with `text-align: center` that is already cancelled — the pair pushed the
     word off the middle of the screen by a third of a character. */
  letter-spacing: .1em;
  color: #eaf7fb;
  text-shadow: 0 0 6px rgba(255,255,255,.7), 0 0 30px rgba(111,215,232,.6),
               0 0 76px rgba(111,215,232,.35);
}
.verdict.good { color: #c8f7d5;
  text-shadow: 0 0 6px rgba(255,255,255,.7), 0 0 30px rgba(105,220,140,.7),
               0 0 78px rgba(105,220,140,.4); }
.verdict.bad  { color: #ffcdc5;
  text-shadow: 0 0 6px rgba(255,255,255,.7), 0 0 30px rgba(224,104,92,.7),
               0 0 78px rgba(224,104,92,.4); }
.cause { display: none; }   /* the verdict speaks for itself */
.stats { display: grid; gap: 8px; max-width: 380px; margin: 0 auto 26px; }
.stats .row {
  display: flex; justify-content: space-between; align-items: center;
  padding: 11px 16px;
  background: #17242b;
  border: 1px solid #2c4048; border-radius: 9px;
}
.stats .k { font-family: var(--prose); font-size: var(--t-label); font-weight: 700;
             letter-spacing: .1em; color: var(--ink-2); }
.stats .v { font-family: var(--mono); font-size: 19px; color: #eaf6fa; }
.seed { margin-top: 20px; font-family: var(--prose); font-size: var(--t-label);
         color: var(--ink-3); text-shadow: 0 1px 10px rgba(0,0,0,.8); }

/* ======================= the case ======================================== */

/* Named for itself, not "stage": a generic class here once collided with a
   menu button rule and made every mousedown scale the whole screen. */
.deck { overflow: hidden; perspective: 1700px; perspective-origin: 50% 46%; }

/* a scaled element that overflows the viewport cannot be centred by grid or
   flex alignment, so it is placed absolutely */
.bomb {
  width: 1696px; height: 1042px;   /* replaced per round in script */
  position: absolute; left: 50%; top: 50%;
  transform-origin: center center;
  transform-style: preserve-3d;
  transition: transform .16s ease-out;
}

/* two faces on one shell */
.flipper {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  transition: transform 1s cubic-bezier(.45,.05,.3,1);
}
.bomb { --turn: translateY(0px); }
.flipper { transform: var(--turn); }
.bomb.flipped .flipper { transform: var(--turn) rotateY(180deg); }
/* the player's drag turns the case at once; letting go settles it */
body.case-turning .flipper { transition: none; }
.bomb.posing .flipper { transition: transform .62s cubic-bezier(.3,.7,.2,1); }

/* six pieces of a real box: two faces and four rims, so turning it over
   shows the thickness instead of a flat card */
.face, .edge {
  position: absolute; left: 50%; top: 50%;
  backface-visibility: hidden;
}
.face, .face > .casing, .bays { transform-style: preserve-3d; }
.face > .casing, .bays, .bay, .panel, .scrim { backface-visibility: hidden; }
.face.back { }
.edge {
  pointer-events: none;      /* decoration; never eats a click */
  border: 3px solid #0e1519;
  border-radius: 6px;
  box-shadow: inset 0 0 30px rgba(0,0,0,.5);
  /* four leads run the length of each rim: visible only as the case turns */
  background:
    linear-gradient(90deg, transparent 0 16%, #d0574f 16% 22%, transparent 22% 36%,
      #ddb63f 36% 42%, transparent 42% 58%, #4180ae 58% 64%, transparent 64% 78%,
      #4f9d5d 78% 84%, transparent 84%),
    linear-gradient(90deg, #35444b, #26333a 60%, #1d282e);
}
.edge.tb {
  background:
    linear-gradient(180deg, transparent 0 16%, #d0574f 16% 22%, transparent 22% 36%,
      #ddb63f 36% 42%, transparent 42% 58%, #4180ae 58% 64%, transparent 64% 78%,
      #4f9d5d 78% 84%, transparent 84%),
    linear-gradient(180deg, #3a4a52, #222e34);
}

/* the reverse: a stamped plate where the clock would be */
.strip.plate-only { flex-direction: column; gap: 6px; padding: 16px 34px; }
.stamp { font-family: var(--seg); font-size: 30px; letter-spacing: .3em;
         text-indent: .3em; color: var(--rim); }
.plate-only .stamp { font-family: var(--mono); font-weight: 600; font-size: 26px; }
.stamp-sub { font-size: 11px; letter-spacing: .34em; color: var(--ink-off); }

.key.locked, .key.locked:hover {
  background: #101a1f; border-color: #1e2a30; color: #364750;
  cursor: default; transform: none;
}


.casing {
  position: absolute; inset: 0;
  border-radius: 28px;
  background:
    radial-gradient(120% 80% at 24% -12%, rgba(255,255,255,.16), transparent 58%),
    linear-gradient(180deg, rgba(255,255,255,.13) 0 10px, transparent 10px),
    linear-gradient(0deg, rgba(0,0,0,.3) 0 18px, transparent 18px),
    linear-gradient(162deg, #46575f 0%, var(--case) 46%, #17222a 100%);
  border: 3px solid #0b1115;
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.22),
    inset 2px 0 0 rgba(255,255,255,.07),
    inset -2px 0 0 rgba(0,0,0,.4),
    inset 0 -4px 0 rgba(0,0,0,.5),
    inset 0 0 0 1px rgba(111,215,232,.12),
    0 10px 0 var(--case-d), 0 15px 0 #0b1115,
    0 44px 76px rgba(0,0,0,.7);
}
.chassis { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.chassis svg { display: block; width: 100%; height: 100%; }

/* Deliberately outside the 3D tree. Hit-testing inside a rotated preserve-3d
   subtree is fragile, and this control has to work every time. */
.flip-btn {
  position: absolute; top: 22px; right: 26px; z-index: 80;
  width: 50px; height: 50px; padding: 0;
  display: grid; place-items: center;
  background: var(--paper); color: var(--ink);
  border: 3px solid var(--ink); border-radius: 12px;
  cursor: pointer; box-shadow: 0 4px 0 var(--ink);
}
.flip-btn svg { width: 20px; height: 20px; fill: none; stroke: currentColor;
                stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.flip-btn:hover { background: #fffdf7; }
.flip-btn:active { transform: scale(.94); }

/* the control strip: clock, drain bar, strikes, serial and counter, carried
   on the top rail of both faces so turning the case over costs nothing */
.panel {
  position: absolute; left: 0; right: 0; top: 0; height: 208px;
  display: grid; place-items: center;
  z-index: 45;                     /* stays lit above the focus scrim */
  pointer-events: none;
}
.panel .strip {
  pointer-events: auto;
  display: flex; align-items: center; gap: 30px;
  padding: 14px 26px;
  margin-top: 44px;
  background: linear-gradient(180deg, #22323b, #101c22);
  border: 2px solid #0b1115; border-radius: 16px;
  transform: translateZ(22px);
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.16),
    inset 0 0 0 1px rgba(111,215,232,.22),
    0 8px 14px rgba(0,0,0,.6), 0 18px 30px rgba(0,0,0,.45);
}
.panel .clock { display: flex; flex-direction: column; gap: 8px; }
.panel .gauges { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.panel .tail { display: flex; flex-direction: column; align-items: center; gap: 12px; }

.bays {
  position: absolute;
  top: 208px; right: 88px; bottom: 118px; left: 88px;
  display: grid;
  gap: 14px;   /* template set per round in script */
  padding: 10px;
  border-radius: 16px;
  background: linear-gradient(180deg, #12191e, #1a242b);
  box-shadow:
    inset 0 4px 10px rgba(0,0,0,.75),
    inset 0 -2px 0 rgba(255,255,255,.05),
    0 1px 0 rgba(255,255,255,.08);
}

.bay {
  position: relative;
  transition: transform .4s cubic-bezier(.2,.85,.25,1),
              border-color .35s ease, background .35s ease, opacity .3s ease;
  min-width: 0; min-height: 0;
  border-radius: 12px;
  background: linear-gradient(180deg, #f6f8f8 0%, var(--paper) 34%, #dfe5e6 100%);
  border: 2px solid #0b1115;
  transform: translateZ(14px);
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.95),
    inset 0 -3px 0 rgba(0,0,0,.14),
    0 5px 9px rgba(0,0,0,.5),
    0 12px 22px rgba(0,0,0,.4);
}

/* --- hardware bolted into a slot no module uses -------------------------- */
.bay.fitting {
  transform: translateZ(8px);
  background: linear-gradient(180deg, #3d4d56, #26333a);
  border-color: #162025;
  overflow: hidden;
  display: grid; place-items: center;
}
.bay.fitting .art { width: 100%; }

/* --- an unused slot ------------------------------------------------------ */
.bay.blank {
  transform: translateZ(4px);
  background: linear-gradient(180deg, #2e3d45, #1c262c);
  border-color: #162025;
  box-shadow: inset 0 2px 0 rgba(255,255,255,.06),
              inset 0 -2px 0 rgba(0,0,0,.25);
}

/* --- a module ------------------------------------------------------------ */
.bay.module {
  padding: 16px;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px;
  overflow: hidden;
}
.bay.module > * { flex: none; }

.led {
  position: absolute; top: 11px; right: 11px;
  width: 17px; height: 17px; border-radius: 50%;
  background: #ddd6c7;
  border: 3px solid var(--ink);
  transition: background .3s ease;
}
/* solved: the whole outline turns green */
.bay.solved { border-color: var(--green); background: #eef7ee; }
.bay.solved .led { background: var(--green); border-color: #a6e8b4;
  box-shadow: 0 0 12px 3px rgba(79,157,93,.85), inset 0 1px 0 rgba(255,255,255,.6); }
.bay.solved .led::after {
  content: ''; position: absolute; left: 3px; top: 0;
  width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2.5px 2.5px 0;
  transform: rotate(42deg);
}
.bay.solved .body { opacity: .4; pointer-events: none; }

/* press progress, always in the same corner of every bay */
.dots { position: absolute; left: 14px; bottom: 12px; display: flex; gap: 5px; }
.dots i {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--rim); border: 2.5px solid var(--ink);
  animation: pop .3s cubic-bezier(.2,1.5,.4,1);
}

.bay.striking::after {
  content: ''; position: absolute; inset: 0;
  background: rgba(208,87,79,.4);
  box-shadow: inset 0 0 0 6px var(--red);
  animation: flash .6s ease-out forwards; pointer-events: none;
}
@keyframes flash { from { opacity: 1; } to { opacity: 0; } }

.body { display: flex; flex-direction: column; align-items: center;
        justify-content: center; gap: 10px; width: 100%;
        transition: opacity .5s ease; }
.art { width: 100%; height: auto; display: block; }
/* wide modules lay their art beside their controls */
.split { display: flex; align-items: center; gap: 16px; width: 100%;
         justify-content: center; }
.split > .art { height: 248px; width: auto; flex: none; }
.side { display: flex; flex-direction: column; align-items: center;
        justify-content: center; gap: 14px; flex: none; }
.uc > .side input { width: 106px; }

/* --- the clock ------------------------------------------------------------ */
.lcd {
  position: relative; overflow: hidden;
  width: 320px; padding: 12px 0 14px;
  text-align: center;
  font-size: 76px; line-height: 1;
  letter-spacing: .03em;
  font-variant-numeric: tabular-nums;
  color: var(--amber); font-family: var(--seg);
  text-shadow: 0 0 18px rgba(255,200,97,.6);
  background: #060c0f;
  border: 2px solid #0e1519; border-radius: 10px;
}
.lcd.urgent { animation: pulse 1s steps(2, end) infinite; }
@keyframes pulse { 50% { color: #7a4a30; } }

.fuse {
  width: 320px; height: 26px;
  background: #0b1418;
  border: 2px solid #0e1519; border-radius: 11px;
  overflow: hidden;
}
.fuse i {
  display: block; height: 100%; width: 100%;
  background: var(--green);
  border-radius: 0 8px 8px 0;
  transition: width .25s linear, background .5s ease;
}
.fuse.warn i { background: var(--yellow); }
.fuse.danger i { background: var(--red); }

.strike-row { display: flex; gap: 16px; }
.strike-row i {
  width: 30px; height: 30px; border-radius: 50%;
  background: #0b1418; border: 2px solid #33454d;
}
.strike-row i.lit {
  background: var(--red);
  box-shadow: 0 0 14px 4px rgba(208,87,79,.8), inset 0 1px 0 rgba(255,255,255,.5);
  animation: ledPunch .45s cubic-bezier(.2,1.6,.4,1);
}

.plate {
  padding: 6px 18px;
  /* the serial is read character by character against the rules, and the
     segment face cannot tell G from 6, S from 5, B from 8 or Z from 2 */
  font-size: 26px; font-weight: 600; letter-spacing: .15em; text-indent: .15em;
  color: var(--rim); background: #0b1418; font-family: var(--mono);
  border: 2px solid #0e1519; border-radius: 7px;
}
.panel .count { font-size: 28px; color: #74c98c; font-family: var(--seg); }
.abort {
  font-family: var(--round); font-size: 12px;
  color: #ffe4e0; background: #b8443c;
  border: 2px solid #7d2a24; border-radius: 8px;
  text-shadow: 0 1px 0 rgba(0,0,0,.35);
  padding: 8px 18px; font-size: 14px; cursor: pointer;
  box-shadow: 0 3px 0 var(--ink);
}
.abort:hover { background: #cc4d44; }
.abort:active { transform: scale(.94); }

/* ======================= shared module widgets =========================== */

.answer { display: flex; gap: 10px; align-items: center; justify-content: center; }
.answer input { width: 92px; font-size: 22px; padding: 11px 8px; }
.answer input.narrow { width: 70px; }
.answer input:disabled { color: #35714a; background: #e7f2e7; }

.go-btn, .press-btn, .null-btn {
  font-family: var(--round); color: var(--ink);
  border: 3px solid var(--ink); border-radius: 11px;
  cursor: pointer; box-shadow: 0 4px 0 var(--ink);
  transition: transform .1s ease, box-shadow .1s ease, filter .15s ease;
}
.go-btn { font-size: 15px; padding: 12px 17px; background: #8fc79a; }
.press-btn { font-size: 15px; padding: 17px 19px; background: #e2d7bf; }
.null-btn { font-size: 24px; padding: 5px 17px; background: var(--paper); }
.go-btn:hover, .press-btn:hover, .null-btn:hover { filter: brightness(1.05); }
.go-btn:active, .press-btn:active, .null-btn:active {
  transform: translateY(3px); box-shadow: 0 1px 0 var(--ink);
}
.go-btn:disabled, .press-btn:disabled { opacity: .45; transform: none; }

/* opacity rather than filter: a filter on a group inside the 3D tree forces
   the whole panel to re-rasterise on every hover */
.hit { cursor: pointer; transition: opacity .12s ease; }
.hit:hover { opacity: .82; }
.hit:active { opacity: .62; }

/* sequences */
.strip {
  padding: 14px;
  background: #ece5d4;
  border: 3px solid var(--ink); border-radius: 14px;
}
.terms { display: flex; gap: 8px; justify-content: center; }
.terms div {
  width: 82px; height: 148px; line-height: 148px; text-align: center;
  font-size: 46px;
  color: var(--ink); background: var(--paper);
  border: 3px solid var(--ink); border-radius: 10px;
}
.terms div.gap { color: var(--blue); background: #dceaf3; border-style: dashed; }

/* rationality */
.readout {
  min-width: 186px; padding: 7px 20px; text-align: center;
  font-size: 40px; line-height: 1.15;
  color: var(--ink); background: #ece5d4;
  border: 3px solid var(--ink); border-radius: 12px;
}

/* venn */
.expr {
  font-size: 22px; letter-spacing: .04em;
  color: var(--ink); padding: 8px 15px;
  background: #dceaf3;
  border: 3px solid var(--ink); border-radius: 11px;
}
.venn-num.taken circle { fill: var(--green); stroke: var(--ink); }
.venn-num.taken text { fill: #fff; }
.venn-num circle, .venn-num text { transition: fill .25s ease, stroke .25s ease; }
.venn-num.taken { animation: pop .32s cubic-bezier(.2,1.4,.4,1); }

/* angles */
.swatch {
  width: 152px; height: 56px; border-radius: 12px;
  border: 3px solid var(--ink);
  background: #ccc;
  transition: background .3s ease;
}

/* unit conversions */
.uc { display: flex; gap: 12px; width: 100%; align-items: stretch; }
.uc-lamps { display: flex; flex-direction: column; justify-content: space-evenly;
            padding: 4px 0; }
.uc-lamps i {
  width: 21px; height: 21px; border-radius: 5px;
  background: #ddd6c7; border: 3px solid var(--ink);
}
.uc-lamps i.on { background: var(--rim); animation: lampOn .7s ease-out both;
  box-shadow: 0 0 11px 3px rgba(111,215,232,.7), inset 0 1px 0 rgba(255,255,255,.5); }
.uc-stack { flex: 1 1 auto; display: flex; flex-direction: column; gap: 6px;
            min-width: 0; }
.uc-pane {
  background: #e9eef0;
  border: 3px solid var(--ink); border-radius: 10px; overflow: hidden;
}
.uc-pane.dark { background: #8d959a; }
.uc-pane svg { width: 100%; height: auto; display: block; }

/* ======================= focus view ====================================== */

.zoom {
  position: absolute; top: 10px; left: 10px; z-index: 6;
  width: 26px; height: 26px; padding: 0;
  display: grid; place-items: center;
  background: var(--paper); border: 3px solid var(--ink); border-radius: 8px;
  color: var(--ink); cursor: pointer; opacity: .7;
  transition: opacity .15s ease, background .15s ease;
}
.zoom svg { width: 14px; height: 14px; fill: none; stroke: currentColor;
            stroke-width: 2.4; stroke-linecap: round; }
.bay:hover .zoom { opacity: 1; }
.zoom:hover { background: var(--rim); }
.bay.focused .zoom { opacity: 1; background: var(--rim); }
.zoom .out { display: none; }
.bay.focused .zoom .in { display: none; }
.bay.focused .zoom .out { display: block; }

.scrim {
  position: absolute; inset: 0; z-index: 40;
  background: rgba(30, 38, 42, .68);
  opacity: 0; pointer-events: none;
  transition: opacity .34s ease;
}
.casing.focusing .scrim { opacity: 1; pointer-events: auto; }

.bay.focused { z-index: 60; box-shadow: 0 18px 0 rgba(0,0,0,.2); }
.bay.ducked { z-index: 50; }

/* ======================= debug =========================================== */

.answer-tag { display: none; }
body.debug .answer-tag {
  display: block;
  position: absolute; left: 8px; right: 8px; bottom: 7px;
  padding: 3px 6px;
  font-size: 11px; line-height: 1.25; text-align: center;
  color: var(--ink); background: var(--rim);
  border: 2px solid var(--ink); border-radius: 7px;
  z-index: 5;
}

/* ==========================================================================
   MOTION — presentation only; state always changes synchronously.
   ========================================================================== */

@keyframes pop      { 0% { transform: scale(.4); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
@keyframes ledPunch { 0% { transform: scale(1); } 32% { transform: scale(1.45); } 100% { transform: scale(1); } }
@keyframes lampOn   { 0% { opacity: .3; } 18% { opacity: 1; } 26% { opacity: .4; } 44% { opacity: 1; }
                      56% { opacity: .6; } 100% { opacity: 1; } }

@keyframes screenIn  { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
@keyframes screenOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(1.03); } }
.screen.entering { animation: screenIn .38s cubic-bezier(.2,.85,.25,1) both; }
.screen.leaving  { animation: screenOut .28s ease-in both; pointer-events: none; }

.flash { position: fixed; inset: 0; z-index: 90; pointer-events: none; opacity: 0; }
@keyframes strikeWash { 0% { opacity: 0; } 12% { opacity: 1; } 100% { opacity: 0; } }
.flash.strike {
  background: radial-gradient(120% 90% at 50% 50%, transparent 36%, rgba(208,87,79,.6) 100%);
  animation: strikeWash .5s ease-out forwards;
}
@keyframes detonate {
  0% { opacity: 0; background: #fff; } 4% { opacity: 1; background: #fff; }
  22% { opacity: 1; background: #f0d08a; } 55% { opacity: .78; background: #c9603a; }
  100% { opacity: 0; background: #d8b39a; }
}
.flash.boom { animation: detonate 1.1s ease-out forwards; }
@keyframes safeWash { 0% { opacity: 0; } 18% { opacity: .85; } 100% { opacity: 0; } }
.flash.safe {
  background: radial-gradient(120% 90% at 50% 50%, rgba(79,157,93,.55) 0%, transparent 65%);
  animation: safeWash .8s ease-out forwards;
}

@keyframes titleIn { from { opacity: 0; transform: translateY(20px) scale(.9); }
                     to   { opacity: 1; transform: none; } }
.menu.entering .title { animation: titleIn .5s cubic-bezier(.2,1.1,.35,1) both; }

@keyframes riseIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.menu.entering .key { animation: riseIn .42s cubic-bezier(.2,1.1,.35,1) both; }
.menu.entering .key:nth-child(1) { animation-delay: .10s; }
.menu.entering .key:nth-child(2) { animation-delay: .16s; }
.menu.entering .key:nth-child(3) { animation-delay: .22s; }
.menu.entering .key:nth-child(4) { animation-delay: .28s; }
.menu.entering .key:nth-child(5) { animation-delay: .34s; }
.menu.entering .sound { animation: riseIn .5s ease-out .44s both; }

@keyframes panelDrop { from { opacity: 0; transform: translateY(-10px) scaleY(.9); }
                       to   { opacity: 1; transform: none; } }
.practice { transform-origin: top center; animation: panelDrop .26s cubic-bezier(.2,1.1,.35,1); }

@keyframes casingIn { from { opacity: 0; transform: translateY(32px) scale(.96); }
                      to   { opacity: 1; transform: none; } }
.casing.arming { animation: casingIn .55s cubic-bezier(.16,1.05,.3,1) both; }

/* the arming push: a long, slow close on the working view */
.bomb.arming-zoom { transition: transform 1.15s cubic-bezier(.42,.02,.18,1); }
/* nothing is touchable until the case is in frame */
.bomb.arming .bays, .bomb.arming .panel { pointer-events: none; }

@keyframes bayIn { 0% { opacity: 0; transform: scale(.82); }
                   60% { opacity: 1; transform: scale(1.03); }
                   100% { opacity: 1; transform: none; } }
@keyframes bayCharge { 0% { opacity: .7; } 70% { opacity: .2; } 100% { opacity: 0; } }
.casing.arming .bay { animation: bayIn .46s cubic-bezier(.2,1.2,.35,1) both;
                      animation-delay: var(--d, 0ms); }
.casing.arming .bay::before {
  content: ''; position: absolute; inset: 0; z-index: 4; pointer-events: none;
  background: radial-gradient(80% 60% at 50% 50%, rgba(255,255,255,.85), transparent 70%);
  animation: bayCharge .5s ease-out both;
  animation-delay: var(--d, 0ms);
}
.casing.arming .bay.solved::before { animation: bayCharge .5s ease-out both; }

@keyframes lcdBoot {
  0% { opacity: 0; } 8% { opacity: 1; } 13% { opacity: .1; } 21% { opacity: 1; }
  28% { opacity: .35; } 36% { opacity: 1; } 44% { opacity: .6; } 100% { opacity: 1; }
}
@keyframes stripOn {
  0%   { opacity: 0; transform: scale(.86); }
  60%  { opacity: 1; transform: scale(1.05); }
  100% { opacity: 1; transform: none; }
}
@keyframes stripFlash { 0% { opacity: .9; } 100% { opacity: 0; } }
.casing.arming .panel .strip {
  animation: stripOn .5s cubic-bezier(.2,1.35,.35,1) both;
  animation-delay: var(--panel-d, 900ms);
}
.casing.arming .panel .strip::after {
  content: ''; position: absolute; inset: -10px; border-radius: 26px;
  background: rgba(255,255,255,.9); pointer-events: none;
  animation: stripFlash .5s ease-out both;
  animation-delay: var(--panel-d, 900ms);
}
.panel .strip { position: relative; }
.casing.arming .lcd { animation: lcdBoot .8s ease-out both;
                      animation-delay: calc(var(--panel-d, 900ms) + 120ms); }

@keyframes digitTick { 0% { opacity: .55; transform: translateY(-2px); } 100% { opacity: 1; transform: none; } }
.digits { display: block; }
.digits.tick { animation: digitTick .16s ease-out; }

@keyframes critical { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
.deck.critical::after {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 2;
  background: radial-gradient(130% 100% at 50% 50%, transparent 42%, rgba(208,87,79,.24) 100%);
  animation: critical 1s ease-in-out infinite;
}

@keyframes casingShake {
  0%, 100% { transform: none; }
  12% { transform: translate(-9px, 3px) rotate(-.45deg); }
  26% { transform: translate(8px, -3px) rotate(.4deg); }
  40% { transform: translate(-6px, 2px) rotate(-.24deg); }
  56% { transform: translate(5px, -2px) rotate(.18deg); }
  74% { transform: translate(-3px, 1px); }
  88% { transform: translate(2px, 0); }
}
.casing.shake { animation: casingShake .55s ease-out; }

@keyframes solveSweep { from { transform: translateX(-115%); } to { transform: translateX(115%); } }
.bay.module.solved::before {
  content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background: linear-gradient(105deg, transparent 20%, rgba(79,157,93,.45) 50%, transparent 80%);
  animation: solveSweep .75s cubic-bezier(.3,.7,.4,1) forwards;
}

@keyframes verdictIn { from { opacity: 0; transform: scale(1.3); } to { opacity: 1; transform: none; } }
.menu.entering .verdict { animation: verdictIn .5s cubic-bezier(.2,1.2,.4,1) both; }
.menu.entering .cause { animation: riseIn .42s ease-out .2s both; }
.menu.entering .stats .row { animation: riseIn .42s ease-out both; }
.menu.entering .stats .row:nth-child(1) { animation-delay: .28s; }
.menu.entering .stats .row:nth-child(2) { animation-delay: .35s; }
.menu.entering .stats .row:nth-child(3) { animation-delay: .42s; }
.menu.entering .seed { animation: riseIn .5s ease-out .56s both; }

@keyframes dealIn { from { opacity: 0; transform: translate(-46px, 12px) rotate(-11deg); }
                    to   { opacity: 1; transform: none; } }
.deal { transform-box: fill-box; transform-origin: 50% 50%;
        animation: dealIn .5s cubic-bezier(.2,1.1,.3,1) both; }
.deal-2 { animation-delay: .11s; }

@keyframes tileIn { from { opacity: 0; transform: translateY(-14px) scale(.85); } to { opacity: 1; transform: none; } }
.terms div { animation: tileIn .38s cubic-bezier(.2,1.2,.4,1) both; }
.terms div:nth-child(1) { animation-delay: .05s; }
.terms div:nth-child(2) { animation-delay: .11s; }
.terms div:nth-child(3) { animation-delay: .17s; }
.terms div:nth-child(4) { animation-delay: .23s; }
.terms div:nth-child(5) { animation-delay: .29s; }

@keyframes readoutIn { from { opacity: 0; transform: scale(.9); } to { opacity: 1; transform: none; } }
.readout, .expr { animation: readoutIn .45s cubic-bezier(.2,1.2,.4,1) .1s both; }

/* was @media (prefers-reduced-motion) — now the REDUCE MOTION setting,
   which follows the system unless the player overrides it (js/settings.js) */
body.rm, body.rm *, body.rm *::before, body.rm *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }


/* ==========================================================================
   THE VOICE
   Anything the devices say uses one treatment, everywhere it appears.
   ========================================================================== */

.voice {
  font-family: var(--round);
  letter-spacing: .04em;
  color: #eaf4f8;
  text-shadow: 0 0 26px rgba(150, 210, 232, .40),
               0 2px 12px rgba(0, 0, 0, .85);
}

.premise {
  margin: -18px 0 30px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-3);
  text-shadow: none;
  max-width: 560px;
  margin-left: auto; margin-right: auto;
}

.key em { font-style: normal; font-size: 10px; color: #b3c2c9; }
.key i  { color: var(--ink-3); }

.tally { margin: 22px 0 0; font-family: var(--prose); font-size: 12px;
         color: var(--ink-3); }
.minor { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* The result screen's line of narration is gone — the verdict and the
   statistics are what the player came back for. */

.arming-line {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 70; margin: 0;
  font-size: 17px; color: #e6f0f4;
  padding: 10px 22px;
  background: rgba(18, 26, 30, .72);
  border-radius: 14px;
  pointer-events: none; opacity: 0;
}
.arming-line.on { animation: armingSay 3s ease both; }
@keyframes armingSay {
  0% { opacity: 0; transform: translateX(-50%) translateY(10px); }
  14% { opacity: 1; transform: translateX(-50%); }
  74% { opacity: 1; }
  100% { opacity: 0; }
}

/* ==========================================================================
   THE CUTSCENES
   The picture is one canvas (js/cine/); everything here is the frame round
   it — letterbox bars, grain, a vignette — and the few things that must be
   text: the line being spoken, title cards, the hold-to-skip ring, BEGIN,
   the credits.
   ========================================================================== */

.cine {
  position: fixed; inset: 0; z-index: 100;
  background: #000; overflow: hidden;
  cursor: pointer; user-select: none; -webkit-user-select: none;
  opacity: 0; transition: opacity .6s ease;
  touch-action: none;
}
.cine.open { opacity: 1; }
.cine-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* film grain and a vignette: what makes a painted frame read as a shot */
.cine-grain {
  position: absolute; inset: -50%; z-index: 2; pointer-events: none;
  opacity: .07; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  animation: cineGrain .5s steps(4) infinite;
}
@keyframes cineGrain { 0% { transform: translate(0, 0); } 25% { transform: translate(-3%, 2%); }
                       50% { transform: translate(2%, -3%); } 75% { transform: translate(-2%, -1%); } }
.cine-vig {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: radial-gradient(120% 90% at 50% 50%, transparent 55%, rgba(0,0,0,.62) 100%);
}

/* letterbox: the bars slide in once the scene is open */
.cine-bar {
  position: absolute; left: 0; right: 0; height: 0; z-index: 3;
  background: #000; transition: height 1.4s cubic-bezier(.6, 0, .2, 1);
}
.cine-bar.top { top: 0; } .cine-bar.bot { bottom: 0; }
.cine.open .cine-bar { height: 11.5vh; }

/* the line being spoken, revealed word by word as the voice says it */
.cine-sub {
  position: absolute; z-index: 4; left: 50%; transform: translateX(-50%);
  bottom: calc(11.5vh + 2.6vh); margin: 0;
  width: min(1100px, 86vw); text-align: center;
  font-family: var(--prose); font-weight: 400;
  font-size: calc(clamp(17px, 2.1vw, 30px) * var(--ui-text, 1));
  line-height: 1.38; letter-spacing: .01em; color: #f2f6f8;
  text-shadow: 0 2px 3px rgba(0,0,0,.95), 0 0 18px rgba(0,0,0,.8);
  opacity: 0; transition: opacity .45s ease;
}
.cine-sub.on { opacity: 1; }
.cine-sub .w { opacity: 0; transition: opacity .18s ease; }
.cine-sub .w.on { opacity: 1; }

/* a title card between acts, on black */
.cine-card {
  position: absolute; inset: 0; z-index: 5; display: grid; place-items: center;
  pointer-events: none; opacity: 0; transition: opacity 1s ease;
}
.cine-card.on { opacity: 1; }
.cine-card div { text-align: center; }
.cine-card small {
  display: block; margin-bottom: 14px;
  font-family: var(--round); font-weight: 600; letter-spacing: .5em; text-indent: .5em;
  font-size: calc(13px * var(--ui-text, 1)); color: var(--rim);
}
.cine-card b {
  font-family: var(--seg); font-weight: 400; letter-spacing: .18em; text-indent: .18em;
  font-size: calc(clamp(30px, 4.4vw, 60px) * var(--ui-text, 1)); color: #eaf7fb;
  text-shadow: 0 0 6px rgba(255,255,255,.55), 0 0 30px rgba(111,215,232,.55);
}

/* hold to skip: the ring fills while a press is held; it only appears once
   a press has begun */
.cine-skip {
  position: absolute; z-index: 6; right: 26px; bottom: calc(11.5vh + 18px);
  display: flex; align-items: center; gap: 10px;
  font-family: var(--round); font-weight: 600; font-size: 11px; letter-spacing: .26em;
  color: rgba(230,245,250,.8); opacity: 0; transition: opacity .2s ease;
  pointer-events: none;
}
.cine-skip.on { opacity: 1; }
.cine-skip svg { width: 30px; height: 30px; transform: rotate(-90deg); }
.cine-skip circle { fill: none; stroke-width: 3.2; }
.cine-skip .track { stroke: rgba(255,255,255,.18); }
.cine-skip .ring { stroke: var(--rim); stroke-dasharray: 100; stroke-dashoffset: 100;
                   stroke-linecap: round; filter: drop-shadow(0 0 4px rgba(111,215,232,.8)); }

/* the credits, rolling up over the last frame */
.cine-credits {
  position: absolute; inset: 11.5vh 0; z-index: 4; overflow: hidden;
  pointer-events: none; text-align: center;
}
.cine-credits .roll { position: absolute; left: 0; right: 0; top: 100%; }
.cine-credits.roll .roll { animation: cineRoll 16s linear both; }
@keyframes cineRoll { from { transform: translateY(0); } to { transform: translateY(-190%); } }
.cine-credits h2 { font-family: var(--seg); font-weight: 400; font-size: 54px;
                   letter-spacing: .2em; margin: 0 0 6px; color: #eaf7fb;
                   text-shadow: 0 0 26px rgba(111,215,232,.6); }
.cine-credits h3 { font-family: var(--round); font-weight: 600; font-size: 12px;
                   letter-spacing: .4em; color: var(--rim); margin: 44px 0 8px;
                   text-transform: uppercase; }
.cine-credits p { font-family: var(--prose); font-size: 18px; color: #d9e8ee; margin: 0; }
.cine-credits .role { color: var(--ink-2); font-size: 15px; }
.cine-credits .last { margin-top: 70px; font-family: var(--doc); font-style: italic; font-size: 22px; }

/* ==========================================================================
   Press feedback: every control answers the click.
   ========================================================================== */
.key, .go-btn, .press-btn, .null-btn, .sound, .abort,
.zoom, .flip-btn, .hit {
  transition: transform .09s cubic-bezier(.3,.9,.4,1), filter .12s ease,
              border-color .18s ease, background .18s ease, box-shadow .12s ease;
}
.key:active, .sound:active,
.zoom:active, .flip-btn:active { transform: scale(.94); }
.go-btn:active, .press-btn:active, .null-btn:active { transform: scale(.94); }
.key:active { filter: brightness(1.35); }
@keyframes tapFlash { from { box-shadow: 0 0 0 0 rgba(111,215,232,.55); }
                      to   { box-shadow: 0 0 0 14px rgba(111,215,232,0); } }
.key:active, .go-btn:active, .press-btn:active,
.null-btn:active { animation: tapFlash .35s ease-out; }

/* ==========================================================================
   DEVICE SELECT
   One page per device rather than a row of buttons: pick a stage and it is
   an event, with its own record in front of you.
   ========================================================================== */

.select { display: flex; align-items: center; justify-content: center; gap: 16px; }

.nav {
  flex: none; width: 52px; height: 52px;
  font-family: var(--round); font-size: 30px; line-height: 1;
  color: var(--text); background: #17242b;
  border: 1px solid #2c4048; border-radius: 50%;
  cursor: pointer;
}
.nav { text-shadow: 0 0 14px rgba(111,215,232,.3); }
.nav:hover:not(:disabled) { border-color: var(--rim); background: #1e2f38;
                            box-shadow: 0 0 18px rgba(111,215,232,.28); }
.nav:active:not(:disabled) { transform: scale(.9); }
.nav:disabled { opacity: .25; cursor: default; }

.viewport { width: min(470px, 82vw); overflow: hidden; }
.track { display: flex; transition: transform .42s cubic-bezier(.3,.85,.3,1); }
.page { flex: 0 0 100%; padding: 6px; }

.card {
  display: grid; gap: 13px; justify-items: center;
  padding: 24px 22px 20px;
  min-height: 396px;
  background: linear-gradient(180deg, #1b2a32, #131f26);
  border: 1px solid #2c4048; border-radius: 16px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07), 0 12px 30px rgba(0,0,0,.45);
  transition: border-color .3s ease, opacity .3s ease;
}
/* completed: the border alone says so, so each device keeps its own
   colour — TRIVIAL stays blue once it is answered */
.card.done { border-color: var(--green); box-shadow: 0 0 0 1px rgba(116,201,140,.3); }
.card.locked { opacity: .55; border-color: #1e2a30; }

.idx { font-family: var(--prose); font-size: var(--t-micro); font-weight: 700;
        letter-spacing: var(--trk-hero); text-indent: var(--trk-hero);
        color: var(--ink-3); }
.nm { font-family: var(--seg); font-weight: 400;
      font-size: var(--t-head); letter-spacing: .08em;
      color: #f2fbfd; line-height: 1.1;
      text-shadow: 0 0 5px color-mix(in srgb, var(--accent) 90%, #fff),
                   0 0 22px color-mix(in srgb, var(--accent) 75%, transparent),
                   0 0 54px color-mix(in srgb, var(--accent) 45%, transparent); }

.emblem-wrap { color: var(--rim); line-height: 0;
                filter: drop-shadow(0 0 12px color-mix(in srgb,
                        var(--accent, #6fd7e8) 55%, transparent)); }
.card.locked .emblem-wrap { color: #33454d; filter: none; }
.emblem { width: 108px; height: 108px; display: block; }

.facts { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 22px; width: 100%; }
.fact {
  display: flex; justify-content: space-between; align-items: baseline;
  border-bottom: 1px solid #22323a; padding-bottom: 5px;
}
.fact span { font-family: var(--prose); font-size: var(--t-micro); font-weight: 700;
              letter-spacing: var(--trk-label); color: var(--ink-2); }
.fact b { font-family: var(--mono); font-size: 15px; color: #eaf6fa;
          font-weight: 500; }
.card.locked .facts { visibility: hidden; }

.blurb { font-family: var(--prose); font-size: var(--t-label); color: var(--ink-2);
          max-width: 300px; line-height: 1.55; }
.engage { width: 100%; padding: 15px 10px; }
.engage b { font-size: 15px; letter-spacing: .16em;
             text-shadow: 0 0 18px color-mix(in srgb, var(--accent) 60%, transparent),
                          0 1px 2px rgba(0,0,0,.6); }
.engage:disabled { opacity: .4; cursor: default; }

.card .choices { grid-template-columns: 1fr 1fr; gap: 10px; width: 100%; }
.card .practice {
  margin: 0; flex-direction: column; gap: 14px; width: 100%;
  background: transparent; border: 0; padding: 0;
}
.card .practice .go { width: 100%; }

.sel-dots { display: flex; gap: 9px; justify-content: center; margin-top: 18px; }
.dot {
  width: 9px; height: 9px; padding: 0; border-radius: 50%;
  background: #24343c; border: 1px solid #33474f; cursor: pointer;
}
.dot.on { background: var(--rim); border-color: var(--rim);
          box-shadow: 0 0 10px rgba(111,215,232,.6); }
.dot.locked { background: #17222700; border-style: dashed; }

/* the opening's last beat: it hands you a device, not a menu. It sits over
   the picture, above the line, so nothing on screen moves when it appears. */
.cs-begin {
  position: absolute; z-index: 6;
  left: 50%; bottom: clamp(140px, 26vh, 250px); transform: translateX(-50%);
  font-family: var(--round); font-size: 15px; letter-spacing: .3em;
  text-indent: .3em; color: #06121a;
  background: var(--rim); border: 0; border-radius: 12px;
  padding: 16px 42px; cursor: pointer;
  box-shadow: 0 0 40px rgba(111,215,232,.55);
}
.cs-begin.in { animation: beginIn .6s cubic-bezier(.2,1.3,.4,1) both,
                          beginPulse 2.4s ease-in-out .6s infinite; }
@keyframes beginIn { from { opacity: 0; transform: translateX(-50%) scale(.8); }
                     to   { opacity: 1; transform: translateX(-50%) scale(1); } }
@keyframes beginPulse {
  0%, 100% { box-shadow: 0 0 34px rgba(111,215,232,.45); }
  50%      { box-shadow: 0 0 58px rgba(111,215,232,.8); }
}
.cs-begin:active { transform: translateX(-50%) scale(.94); }

/* ==========================================================================
   THE ROOM
   A procedural sky behind every screen: drifting colour, stars with depth,
   the curve of a world, and something crossing it very far away.
   ========================================================================== */

.backdrop { position: fixed; inset: 0; z-index: -1; overflow: hidden;
            pointer-events: none; contain: strict; }
.bd-svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* colour as plain gradients: no filter, so nothing re-blurs per frame */
.bd-cloud {
  position: absolute; border-radius: 50%;
  transform: translate(-50%, -50%);
  will-change: transform;
  /* painted from currentColor so a change of sky is one colour transition */
  background: radial-gradient(closest-side, currentColor 0%, rgba(0,0,0,0) 72%);
  transition: color 1.6s ease;
  animation-name: bdDrift; animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

/* one very slow pass of light over the whole sky */
.bd-sweep {
  position: absolute; inset: -30%;
  background: linear-gradient(102deg, rgba(0,0,0,0) 38%,
              rgba(150,205,225,.055) 50%, rgba(0,0,0,0) 62%);
  will-change: transform;
  animation: bdSweep 42s linear infinite;
}
@keyframes bdSweep {
  from { transform: translateX(-38%); } to { transform: translateX(38%); }
}
@keyframes bdDrift {
  0%, 100% { transform: translate(-50%, -50%) scale(1); }
  33%      { transform: translate(-46%, -54%) scale(1.12); }
  66%      { transform: translate(-55%, -46%) scale(.92); }
}

@keyframes bdTwinkle { 0%, 100% { opacity: .22; } 50% { opacity: 1; } }
.bd-star { animation: bdTwinkle 6s ease-in-out infinite; }

/* two layers, drifting at different speeds, so the sky has depth */
@keyframes bdPanA { from { transform: translateX(0); } to { transform: translateX(-46px); } }
@keyframes bdPanB { from { transform: translateX(0); } to { transform: translateX(-110px); } }
.bd-layer-0 { animation: bdPanA 150s linear infinite alternate; }
.bd-layer-1 { animation: bdPanB 110s linear infinite alternate; }

@keyframes bdRim { 0%, 100% { opacity: .28; } 50% { opacity: .62; } }
.bd-rim { animation: bdRim 11s ease-in-out infinite; }

@keyframes bdBlink { 0%, 100% { opacity: 1; } 50% { opacity: .15; } }
.bd-blink { animation: bdBlink 2.6s ease-in-out infinite; }

/* it takes four minutes to cross, and you are meant to half-miss it */
@keyframes bdCross {
  from { transform: translate(-140px, 190px) rotate(-4deg); opacity: 0; }
  8%   { opacity: .85; }
  92%  { opacity: .85; }
  to   { transform: translate(1740px, 96px) rotate(-4deg); opacity: 0; }
}
.bd-far { animation: bdCross 240s linear infinite; }

/* rare, quick, and gone: 1.1s of travel inside a 90s cycle */
@keyframes bdShoot {
  0%      { opacity: 0; transform: translate(0, 0) scale(.6); }
  0.6%    { opacity: .9; }
  1.2%    { opacity: 0; transform: translate(340px, 120px) scale(1); }
  100%    { opacity: 0; transform: translate(340px, 120px) scale(1); }
}
.bd-shoot { animation: bdShoot 90s linear infinite; }
.bd-shoot-1 { animation-duration: 126s; }

/* ==========================================================================
   Colour on the selector: each device carries its own accent, cool to hot.
   ========================================================================== */

.card { --accent: #6fd7e8; }
.emblem-wrap { color: var(--accent); }
.card.locked .emblem-wrap { color: #33454d; }
.card.next {
  border-color: var(--accent);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08),
              0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent),
              0 0 46px color-mix(in srgb, var(--accent) 22%, transparent),
              0 12px 30px rgba(0,0,0,.5);
}
.card .nm { color: var(--accent); }
.card.locked .nm { color: var(--ink-off); }
.engage {
  background: color-mix(in srgb, var(--accent) 22%, #16232a);
  border-color: color-mix(in srgb, var(--accent) 55%, #16232a);
  color: #eaf6f9;
}
.engage:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 34%, #16232a);
}
.dot.on { background: var(--dot, var(--rim)); border-color: var(--dot, var(--rim));
          box-shadow: 0 0 10px color-mix(in srgb, var(--dot, var(--rim)) 70%, transparent); }


/* While a device is on screen there is no sky at all. A bomb on a table in a
   room is a different place from a menu in orbit, and the star field behind
   the case read as decoration sitting on top of the fiction. The room is a
   single pool of light on a dark ground — one static gradient, no layers, no
   animation, so it costs less than the sky it replaces. */
body.playing .backdrop { display: none; }
body.playing::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(128% 96% at 50% 40%,
      #1e2a31 0%, #141e23 38%, #0c1317 68%, #070c0f 100%);
}
/* and a floor: the case sits on something rather than floating in a gradient */
body.playing::after {
  content: ''; position: fixed; z-index: -1; pointer-events: none;
  left: 0; right: 0; bottom: 0; height: 34%;
  background: linear-gradient(to bottom,
    rgba(4, 8, 10, 0) 0%, rgba(4, 8, 10, .55) 62%, rgba(3, 6, 8, .9) 100%);
}
/* the armed lamp on the case: opacity on two small nodes, nothing else */
@keyframes pilot { 0%, 100% { opacity: .18; transform: scale(1); }
                   50%      { opacity: .7;  transform: scale(2.1); } }
.pilot-halo { transform-box: fill-box; transform-origin: 50% 50%;
              animation: pilot 2.6s ease-in-out infinite; }
/* was @media (prefers-reduced-motion) — now the REDUCE MOTION setting,
   which follows the system unless the player overrides it (js/settings.js) */
body.rm .pilot-halo { animation: none; }


/* the menu's sky still holds its position rather than animating when hidden */
body.playing .backdrop *,
body.playing .backdrop { animation-play-state: paused !important; }
/* and drop the blending pass while the case is on screen: five screen-blended
   layers cost a composite each, and the sky is mostly covered anyway */
/* The opening used to hide the sky entirely, which is what made those scenes
   feel like they happened in a void. It now shows through a veil instead. */
body.cutscene-open .backdrop { display: block; }
/* ...but only the sky. The screens underneath must not show through the
   veil — the menu was legible behind the opening. */
body.cutscene-open .screen { visibility: hidden; }

/* A hidden screen still keeps its animations ticking in the style engine.
   Nothing paints, but there is no reason to keep computing them. */
.screen[hidden] *, .screen[hidden] { animation-play-state: paused !important; }
body.cutscene-open .backdrop .bd-cloud { opacity: .34 !important; }
.bomb { will-change: transform; }
/* the 3D case turns under a drag on anything that is not a control */
body.room3d.playing #screen-game { cursor: grab; }
body.case-turning, body.case-turning * { cursor: grabbing !important; }
.turn-hint {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%); z-index: 60; margin: 0;
  font-family: var(--round); font-weight: 700; font-size: 11px; letter-spacing: .28em; white-space: nowrap;
  color: #cfe0e7; background: rgba(6,10,13,.6); border: 1px solid #2b3d46; border-radius: 999px;
  padding: 9px 18px; pointer-events: none; animation: riseIn .5s ease-out 1.2s both;
}
/* squaring up to read a module, or settling back to rest on the desk */
.bomb.posing { transition: transform .62s cubic-bezier(.3,.7,.2,1); }

/* ==========================================================================
   WHAT IS CLICKABLE
   The case is scenery. Nothing in it takes a pointer unless it is genuinely a
   control, so a click on bare shell, a rail, a blank plate or the dead area
   of a module simply does not land on anything. Events still bubble up from
   the controls, so the per-bay sound hook is unaffected.
   ========================================================================== */

.bomb, .bomb * { pointer-events: none; }

/* the exceptions, and the whole list of them */
.bay.module .hit,      .bay.module .hit *,
.bay.module button,    .bay.module button *,
.bay.module input,
.bay.module canvas.m3d,          /* a 3D module finds its own parts in a click */
.zoom, .zoom *,
.panel .abort { pointer-events: auto; }

/* the scrim only takes clicks while a module is actually pulled forward;
   left always-on it swallows every click across the grid */
.casing.focusing .scrim { pointer-events: auto; }

/* The case is hardware, not prose. Without this a click-drag paints a text
   selection across every module at once, which reads as the whole bomb
   reacting to the click. */
.bomb { user-select: none; -webkit-user-select: none; }
.bomb input { user-select: text; -webkit-user-select: text; }

/* ==========================================================================
   ZOOM OUT, and the case losing power
   ========================================================================== */

/* sits under TURN, same treatment; outside the 3D tree like TURN, because a
   control that must always work has no business inside a rotated subtree */
.wide-btn { top: 82px; }
.wide-btn .w-in { display: none; }
.wide-btn.on .w-out { display: none; }
.wide-btn.on .w-in { display: block; }
.wide-btn.on { background: var(--rim); color: #06222b; border-color: var(--rim); }

/* The fade goes on the perspective container, never on .bomb: opacity or a
   filter on an element inside preserve-3d flattens the box mid-animation. */
@keyframes deckAbort { from { opacity: 1; } to { opacity: 0; } }
.deck.aborting { animation: deckAbort .58s ease-in forwards; }
.deck.aborting .digits { color: #223038; text-shadow: none; transition: color .18s ease; }
.deck.aborting .led { background: #223038; box-shadow: none; }

/* ==========================================================================
   TOUCH AND SMALL SCREENS
   The casing already scales to the viewport, so the work here is input
   ergonomics rather than layout: kill the tap delay and the double-tap zoom,
   stop the page rubber-banding under a drag, drop hover-only affordances on
   devices that cannot hover, and keep the chrome clear of notches.
   ========================================================================== */

html, body {
  overscroll-behavior: none;                 /* no pull-to-refresh mid-round */
  -webkit-text-size-adjust: 100%;            /* iOS must not reflow our type */
}

body {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  box-sizing: border-box;
}

button, .key, .bay, .bay *, .zoom, .answer input, .field input,
.cs-begin, .sound, [role="button"] {
  touch-action: manipulation;                /* removes the 300ms tap delay */
  -webkit-tap-highlight-color: transparent;
}

.bomb, .casing { touch-action: manipulation; }

/* A finger has no hover state. Anything that only appears on hover has to be
   permanently visible where hovering is impossible, or it cannot be found. */
@media (hover: none) {
  .zoom { opacity: 1 !important; }
  .bay:hover, .key:hover, button:hover { transform: none; }
}

/* Coarse pointers need a bigger target than a mouse does. 44px is the floor
   both Apple and Google publish, and the bays scale with the casing so this
   only has to lift the chrome controls. */
@media (pointer: coarse) {
  .key { min-height: 52px; }
  .zoom { min-width: 44px; min-height: 44px; }
  .sound, .cs-begin { min-height: 44px; padding-left: 18px; padding-right: 18px; }
  .answer input, .field input { min-height: 44px; }
}

/* Phone-sized viewports. The menu and result sheets are the only fixed-width
   pieces; the bomb itself is already a single scaled transform. */
@media (max-width: 820px) {
  .sheet { padding: 18px 14px; }
  .title { font-size: 40px; }
  .choices { gap: 10px; }
  .key i { font-size: 11px; }
  .stats { max-width: none; }
}

@media (max-width: 560px) {
  .title { font-size: 32px; }
  .choices.two { grid-template-columns: 1fr; }
  .blurb { max-width: none; }
}

/* Portrait on a handset. The casing is a wide object and squeezing it into a
   tall frame makes the gradations on the ruler and cylinder unreadable, which
   breaks Unit Conversions specifically. Ask for a turn rather than render
   something the Defuser cannot read. */
#rotate-hint { display: none; }

@media (max-width: 900px) and (orientation: portrait) {
  body.playing #rotate-hint,
  body.cutscene-open #rotate-hint {
    display: flex;
    position: fixed; inset: 0; z-index: 9000;
    align-items: center; justify-content: center;
    flex-direction: column; gap: 16px; text-align: center;
    padding: 32px;
    background: var(--bg, #0b1115);
  }
  body.playing .deck, body.cutscene-open .cine { filter: blur(2px); }
  #rotate-hint p { font-size: 16px; color: var(--ink-2, #9fb0b8); margin: 0; max-width: 320px; }
  #rotate-hint .rot-glyph {
    width: 76px; height: 48px; border: 3px solid currentColor;
    border-radius: 8px; color: var(--ink-2, #9fb0b8);
    animation: rotNudge 2.2s ease-in-out infinite;
  }
}

@keyframes rotNudge {
  0%, 45%   { transform: rotate(90deg) scale(.82); }
  60%, 100% { transform: rotate(0deg) scale(1); }
}

/* was @media (prefers-reduced-motion) — now the REDUCE MOTION setting,
   which follows the system unless the player overrides it (js/settings.js) */
body.rm #rotate-hint .rot-glyph { animation: none; transform: none; }


/* ==========================================================================
   MENU MOTION
   Nothing here is meant to be noticed on its own — the point is only that
   the screen is never completely still. Amplitudes are a few pixels and the
   cycles are long and mutually prime, so they never line up into a pulse.
   The blanket prefers-reduced-motion rule switches all of it off.
   ========================================================================== */

@keyframes mFloat  { 0%, 100% { transform: translateY(0); }
                     50%      { transform: translateY(-5px); } }
@keyframes mBreath { 0%, 100% { transform: scale(1) rotate(0deg); }
                     50%      { transform: scale(1.045) rotate(1.6deg); } }
@keyframes mSway   { 0%, 100% { transform: translateY(0) rotate(0deg); }
                     50%      { transform: translateY(-3px) rotate(-.35deg); } }
@keyframes mPulse  { 0%, 100% { opacity: .82; } 50% { opacity: 1; } }

/* transform and opacity only: animating text-shadow repaints the whole title
   every frame, and it cannot be handed to the compositor */
.title      { animation: mFloat 15s ease-in-out infinite; }
.viewport   { animation: mFloat 13s ease-in-out infinite; }
.nav        { animation: mPulse 7s ease-in-out infinite; }
.sel-dots   { animation: mFloat 19s ease-in-out infinite; }

/* Scoped to the page in view. Running these on all five cards meant the
   carousel dragged eight extra animations along with every slide. */
.card.focused-page .emblem-wrap { animation: mBreath 17s ease-in-out infinite; }
.card.focused-page .facts       { animation: mSway 21s ease-in-out infinite; }

/* the emblem already carries a transform on the group, so animate a wrapper */
.emblem-wrap { display: inline-block; transform-origin: 50% 50%; }

/* stagger them so the whole sheet never moves as one piece */
.viewport    { animation-delay: -3s; }
.card.focused-page .emblem-wrap { animation-delay: -7s; }
.card.focused-page .facts       { animation-delay: -11s; }
.sel-dots    { animation-delay: -5s; }

/* ==========================================================================
   THE MODE SCREEN
   Three keys along the bottom and, above them, a panel big enough to show
   what the mode looks like once a device is armed. The preview is the point:
   "two devices" is a shape, and a player picks a shape from a picture of it
   far more easily than from a sentence about it.
   ========================================================================== */

.mode-sheet { width: min(940px, 100%); }
.mode-heading {
  margin: 0 0 22px; font-family: var(--seg);
  font-size: var(--t-head); letter-spacing: .1em;
  color: #eaf7fb; font-weight: 400; line-height: 1.1;
  text-shadow: 0 0 5px rgba(255,255,255,.6), 0 0 26px rgba(111,215,232,.55),
               0 0 64px rgba(111,215,232,.3);
}

.mode-stage {
  /* The width is capped in viewport HEIGHT: the panel is as tall as the
     drawing makes it, so capping the width is how the drawing is kept from
     eating the screen — and it keeps the art filling the panel instead of
     floating in the middle of one. */
  position: relative; overflow: hidden;
  width: min(100%, 78vh); margin: 0 auto;
  border: 1px solid #2c4048; border-radius: 16px;
  background: linear-gradient(180deg, #1b2a32, #121d24);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07), 0 14px 34px rgba(0,0,0,.5);
}
.mode-track { display: flex; transition: transform .42s cubic-bezier(.3,.85,.3,1); }
.mode-panel {
  flex: 0 0 100%; padding: 14px 14px 18px;
  display: grid; gap: 6px; justify-items: center;
  color: var(--rim); opacity: .45;
  transition: opacity .32s ease;
}
.mode-panel.on { opacity: 1; }
.mode-art-wrap { width: 100%; line-height: 0; }
.mode-art { width: 100%; height: auto; display: block; }

/* the one extra choice a mode may carry */
.mode-opts { margin: 12px 0 16px; }
.mode-opt { display: flex; flex-direction: column; align-items: center; gap: 9px; }
.mode-opt-label { font-family: var(--prose); font-size: var(--t-micro); font-weight: 700;
                  letter-spacing: var(--trk-label); color: var(--ink-3);
                  margin-top: 4px; text-shadow: 0 1px 10px rgba(0,0,0,.8); }
.mode-opt-keys { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.opt-btn {
  font-family: var(--round); font-size: 12px; letter-spacing: .12em;
  color: var(--ink-2); background: #16222a;
  border: 2px solid #2b3d46; border-radius: 10px;
  padding: 10px 16px; min-height: 42px; cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.opt-btn:hover { color: var(--text); border-color: #486069; }
.opt-btn.on { color: #06121a; background: var(--rim); border-color: var(--rim); }

.mode-row {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px; margin-bottom: 16px;
}
.mode-key { display: grid; gap: 4px; padding: 14px 8px; }
.mode-key i { font-style: normal; font-family: var(--prose); font-size: var(--t-micro);
              font-weight: 700; letter-spacing: var(--trk-label); color: var(--ink-3); }
.mode-key.on { border-color: var(--rim); background: #1e2f38;
                box-shadow: 0 0 0 1px rgba(111,215,232,.35),
                            0 0 28px rgba(111,215,232,.22); }
.mode-key.on i { color: #a9e8f4; }

.mode-go { display: flex; gap: 10px; justify-content: center; align-items: center; }
.mode-go .key { min-width: 240px; padding: 0 22px; min-height: 56px;
                display: grid; place-items: center; }
/* the same height as the key beside it: two controls on one row that do not
   share a baseline read as a mistake */
.mode-go .sound { padding: 0 22px; min-height: 56px;
                  display: grid; place-items: center; }

/* ==========================================================================
   THE SEED PAGE
   OPEN PLAY dealt a new random device every time and gave the player no way
   back to one. This takes the code the result screen printed.
   ========================================================================== */

.seedbox { align-items: stretch; }
.field.wide { width: 100%; }
.field.wide input {
  flex: 1 1 auto; min-width: 0; width: 100%;
  font-family: var(--mono); letter-spacing: .18em; text-transform: uppercase;
}
.seed-note { margin: 0; font-family: var(--prose); font-size: var(--t-label);
              line-height: 1.5; color: var(--ink-2); }
.seed-note.bad { color: var(--red); }

.seed-code {
  /* a code is read back and typed in: the segment face draws S as 5, B as 8
     and O as 0, so it is set in the mono face instead */
  font-family: var(--mono); font-size: 20px; font-weight: 500;
  letter-spacing: .14em; color: var(--amber);
  text-shadow: 0 0 20px rgba(255,200,97,.35), 0 1px 8px rgba(0,0,0,.8);
  user-select: all; -webkit-user-select: all;
}

/* ==========================================================================
   THE MANUAL, INSIDE THE GAME
   Typeset for the screen it is on, not an embedded document. One mounted
   view, used as half of a Solo split and as the whole of a second screen in
   two-device mode. No search: the contents are how a module is found.
   ========================================================================== */

.manual-view {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  background: #0b1115;
}
.mv-bar {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 11px max(14px, env(safe-area-inset-left)) 11px 14px;
  padding-top: calc(11px + env(safe-area-inset-top, 0px));
  background: linear-gradient(180deg, #16232b, #101a20);
  border-bottom: 1px solid #2a3c45;
}
.mv-brand {
  font-family: var(--round); font-size: 12px; font-weight: 700;
  letter-spacing: .26em; color: #8fe0ef; white-space: nowrap;
  text-shadow: 0 0 18px rgba(111,215,232,.45);
}

.mv-slot { position: relative; flex: 1 1 auto; min-height: 0; }
.mv-paper {
  position: absolute; inset: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 0 0 64px;
  overscroll-behavior: contain;
}
.mv-sheet { max-width: 70ch; margin: 0 auto;
            padding: 22px max(18px, env(safe-area-inset-left)); }
.mv-sheet-title {
  margin: 0 0 10px; font-family: var(--round); font-size: var(--t-sub);
  font-weight: 700; letter-spacing: var(--trk-label); color: #eaf7fb;
  text-shadow: 0 0 26px rgba(111,215,232,.28), 0 2px 3px rgba(0,0,0,.6);
}
.mv-sheet-sub { margin: 0 0 18px; font-family: var(--prose); font-size: var(--t-label);
                line-height: 1.62; color: var(--ink-2); }
.mv-toc { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 6px; }
.mv-toc a {
  display: inline-block; padding: 8px 13px; min-height: 40px; line-height: 24px;
  border: 1px solid #33484f; border-radius: 999px;
  font-family: var(--prose); font-size: 13px; font-weight: 600;
  text-decoration: none; color: var(--text); background: #16242c;
}
.mv-toc a:hover { border-color: var(--rim); color: #bfeef8; }
.mv-toc a .dot { display: inline-block; width: 8px; height: 8px;
                 border-radius: 50%; margin-right: 7px; vertical-align: middle; }
.mv-missing { padding: 40px 20px; text-align: center; color: var(--red); }

/* ==========================================================================
   SOLO: THE SPLIT
   The device and the manual share the glass. Either half can take all of it,
   and the split turns. It starts vertical.
   ========================================================================== */

.play-manual {
  position: fixed; z-index: 60;
  border-left: 1px solid #22323a;
  background: #0b1115;
}

/* The divider's position, as a percentage of the screen along the axis the
   split runs. Both panes and the grip read the same number. */
:root { --split-at: 50%; }

/* side by side (the default) */
body.split-v.playing #screen-game { right: calc(100% - var(--split-at)); }
body.split-v .play-manual { top: 0; bottom: 0; right: 0;
                            left: var(--split-at);
                            border-left: 1px solid #22323a; border-top: 0; }

/* one above the other */
body.split-h.playing #screen-game { bottom: calc(100% - var(--split-at)); }
body.split-h .play-manual { left: 0; right: 0; bottom: 0;
                            top: var(--split-at);
                            border-top: 1px solid #22323a; border-left: 0; }

/* one half, all of it */
body.pane-bomb.playing #screen-game { inset: 0; }
body.pane-bomb .play-manual { display: none !important; }
body.pane-manual.playing #screen-game { visibility: hidden; }
body.pane-manual .play-manual { inset: 0; border: 0; }

/* ---- the grip ----------------------------------------------------------
   A wide invisible target over a hairline: the line wants to be 1px and the
   finger wants 20, so the line is drawn and the target is not. */
.split-grip {
  position: fixed; z-index: 92;
  touch-action: none;
  display: grid; place-items: center;
}
body.split-v .split-grip {
  top: 0; bottom: 0; left: var(--split-at);
  width: 22px; margin-left: -11px; cursor: col-resize;
}
body.split-h .split-grip {
  left: 0; right: 0; top: var(--split-at);
  height: 22px; margin-top: -11px; cursor: row-resize;
}
/* the seam, so the boundary reads as something you can take hold of */
.split-grip::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to right, transparent 45%,
              rgba(111,215,232,.16) 45%, rgba(111,215,232,.16) 55%,
              transparent 55%);
}
body.split-h .split-grip::before {
  background: linear-gradient(to bottom, transparent 45%,
              rgba(111,215,232,.16) 45%, rgba(111,215,232,.16) 55%,
              transparent 55%);
}
/* the handle itself: a short bar sitting on the seam */
.split-grip::after {
  content: ''; display: block; position: relative; border-radius: 3px;
  background: #6b8794;
  box-shadow: 0 0 0 1px rgba(8, 13, 16, .8);
  transition: background .15s ease, box-shadow .15s ease;
}
body.split-v .split-grip::after { width: 5px; height: 64px; }
body.split-h .split-grip::after { width: 64px; height: 5px; }
.split-grip:hover::after, .split-grip:focus-visible::after,
.split-grip.dragging::after {
  background: var(--rim);
  box-shadow: 0 0 14px color-mix(in srgb, var(--rim) 60%, transparent);
}
.split-grip:focus-visible { outline: none; }
/* while it is being dragged nothing else should take the pointer, and the
   case must not try to lean towards the cursor */
body.resizing, body.resizing * { cursor: inherit !important; user-select: none; }
body.resizing .bomb { transition: none; }

/* the case's own controls follow its pane, so they only need to stand clear
   of the layout bar at the bottom */
.layout-bar {
  position: fixed; z-index: 95;
  left: 50%; transform: translateX(-50%);
  bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  display: flex; gap: 6px; padding: 5px;
  background: rgba(8, 13, 16, .94);
  border: 1px solid #2b3d46; border-radius: 12px;
  box-shadow: 0 6px 20px rgba(0,0,0,.55);
}
.lay-btn {
  font-family: var(--round); font-size: 11px; letter-spacing: .12em;
  color: var(--ink-2); background: transparent;
  border: 1px solid transparent; border-radius: 9px;
  padding: 8px 12px; min-height: 38px; cursor: pointer;
}
.lay-btn:hover { color: var(--text); }
.lay-btn.on { color: #06121a; background: var(--rim); border-color: var(--rim); }
.lay-btn:disabled { opacity: .35; cursor: default; }
.lay-turn { display: grid; place-items: center; width: 42px; padding: 0; }
.lay-turn svg { width: 20px; height: 20px; fill: none; stroke: currentColor;
                stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lay-turn .lay-h { display: none; }
.lay-turn.horizontal .lay-v { display: none; }
.lay-turn.horizontal .lay-h { display: block; }

/* ==========================================================================
   TWO DEVICES: THE SCREEN THAT IS THE MANUAL
   ========================================================================== */

.reader { background: #0b1115; }
.reader-host { position: absolute; inset: 0 0 58px; }
.reader-back {
  position: absolute; z-index: 5;
  left: 50%; transform: translateX(-50%);
  bottom: calc(10px + env(safe-area-inset-bottom, 0px));
}
/* the manual screen is a document, not a round: no rotate nag, no sky */
body.reading #rotate-hint { display: none !important; }

/* ==========================================================================
   MANUAL CONTENT
   Shared by manual/index.html and by the manual mounted inside the game, so
   a rule looks the same wherever it is read. This is the longest prose in the
   project and the only text a player reads under time pressure, so it is set
   for reading: a humanist sans, a real measure, and headings that are a
   different size and weight rather than the same size in a different colour.
   ========================================================================== */

.mod { border-top: 1px solid #27383f; padding: 28px 0 12px;
       scroll-margin-top: 84px; }
.mod-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.mod h2 {
  margin: 0; font-family: var(--round); font-size: calc(var(--t-sub) * 1.3);
  font-weight: 700; letter-spacing: .04em; color: #f0fafd;
  text-shadow: 0 0 22px rgba(111,215,232,.22);
}
.badge {
  font-family: var(--prose); font-size: 10px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  border: 1px solid; border-radius: 6px; padding: 3px 9px;
}
.badge.easy   { color: #86dda1; border-color: #3e7554; }
.badge.medium { color: #f0c96a; border-color: #7d6631; }
.badge.hard   { color: #f0897e; border-color: #83473f; }
.tagline { margin: 7px 0 0; font-family: var(--doc); color: var(--ink-3);
           font-style: italic; font-size: var(--t-body); }

/* ---- the rules ---- */
.rules { margin-top: 10px; max-width: 64ch; font-family: var(--doc); }
.rules p { line-height: 1.68; margin: 14px 0; font-size: var(--t-body);
           color: var(--text); }
.rules p.h {
  font-family: var(--round); font-size: var(--t-sub); font-weight: 700;
  color: #bfe9f4; margin: 28px 0 4px; letter-spacing: .1em;
  text-transform: uppercase;
  text-shadow: 0 0 18px rgba(111,215,232,.25);
}
.rules p.attn {
  color: var(--amber); font-weight: 700; letter-spacing: .05em;
  margin: 26px 0 4px; text-transform: uppercase; font-size: 14px;
}
.rules li { line-height: 1.66; margin: 8px 0; font-size: var(--t-body);
            color: var(--text); }
.rules ul { padding-left: 22px; margin: 10px 0; }
.rules b { color: #ffffff; font-weight: 700; }

.tablewrap { overflow-x: auto; margin: 16px 0; -webkit-overflow-scrolling: touch; }
.rules table { border-collapse: collapse; font-family: var(--mono);
               font-size: 13px; min-width: 100%; }
.rules th, .rules td {
  border: 1px solid #33484f; padding: 8px 12px; text-align: left;
  white-space: nowrap; color: var(--text);
}
.rules thead th { background: #1b2a33; color: #d9eef5; font-weight: 700;
                  letter-spacing: .06em; }
.rules tbody tr:nth-child(even) { background: rgba(255,255,255,.028); }
.rules tr.subhead th { background: #1b2a33; color: #d9eef5; font-weight: 700;
                       letter-spacing: .06em; }

/* There is no search anywhere any more, so there is nothing to highlight and
   no module is ever hidden from the list. */

/* ==========================================================================
   TOUCH
   The case is one scaled transform, so a CSS min-height inside it is scaled
   too: 44px of stylesheet becomes about 21px of glass on a phone. Anything
   that must stay thumb-sized is therefore sized in case units large enough to
   survive the scale, and the rest is handled by making the whole bay a target.
   ========================================================================== */

@media (pointer: coarse) {
  /* the lens, at a size that survives the case scale */
  .zoom { width: 56px; height: 56px; border-radius: 12px; opacity: .92; }
  .zoom svg { width: 30px; height: 30px; stroke-width: 2.2; }
  /* the two case controls sit outside the transform, so plain px is honest */
  .flip-btn { width: 52px; height: 52px; }
}

/* The keyboard takes the bottom of the screen and the case is centred in the
   whole window, so without this it sits behind the keys. --kb-lift is set from
   visualViewport; it is 0 whenever no keyboard is up. */
:root { --kb-lift: 0px; }
body.kb-open .bomb { margin-top: var(--kb-lift); }
body.kb-open .flip-btn { opacity: .35; }

/* the pinned clock, for a focused module on a phone */
.focus-clock {
  position: fixed; z-index: 70;
  top: calc(8px + env(safe-area-inset-top, 0px));
  left: calc(8px + env(safe-area-inset-left, 0px));
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 13px;
  background: rgba(8, 13, 16, .92);
  border: 1px solid #2b3d46; border-radius: 10px;
  font-family: var(--mono); font-size: 20px; letter-spacing: .05em;
  color: var(--amber); pointer-events: none;
  box-shadow: 0 4px 14px rgba(0,0,0,.5);
}
.focus-clock.urgent { color: var(--red); border-color: #6e3b36; }
.focus-clock .fc-count { font-size: 13px; color: var(--ink-2); }

/* ==========================================================================
   THE OPENING ON A SHORT SCREEN
   A phone held sideways is about 390 tall. The frame, the caption and the
   BEGIN slot are stacked, so they have to be told to give ground — otherwise
   the column is taller than the glass and the caption falls off the bottom.
   These sit at the end of the file on purpose: they have to win against the
   coarse-pointer minimums above, which exist for fingers on a tall screen.
   ========================================================================== */

@media (max-height: 540px) {
  .cs-begin { padding: 10px 26px; min-height: 40px; font-size: 12px;
              bottom: 84px; }

  /* the mode screen, on the same short glass */
    .mode-heading { font-size: 17px; margin-bottom: 8px; }
  .mode-stage { width: min(100%, 52vh); }
    .mode-opts { margin: 8px 0 10px; }
  .mode-opt { gap: 5px; }
  .mode-row { margin-bottom: 10px; }
  .mode-key { padding: 9px 6px; }
  .mode-go .key { min-width: 170px; padding: 11px 18px; }
  .menu { padding: 4px 8px; }
  .mode-sheet { padding: 0; }

  /* the device selector and the result screen, on the same short glass. A
     phone held sideways is about 390 tall; the title alone was 40px of it and
     the card asked for 396 more, so ENGAGE was below the fold. */
  .sheet { padding: 4px 10px; }
  .title { font-size: 20px; margin: 0 0 3px; letter-spacing: .26em;
           text-indent: .26em; }
  .title::after { width: 120px; height: 1px; margin: 6px auto 0; }
  .card { min-height: 0; padding: 8px 14px 7px; gap: 5px; }
  .nm { font-size: 18px; }
  .emblem { width: 34px; height: 34px; }
  .emblem-wrap { line-height: 0; }
  .facts { gap: 1px 18px; }
  .fact { padding-bottom: 2px; }
  .fact span { font-size: 10px; letter-spacing: .1em; }
  .fact b { font-size: 13px; }
  .idx { font-size: 10px; }
  .blurb { font-size: 12px; }
  .progress { font-size: 11px; }
  .engage { padding: 9px 10px; }
  .engage b { font-size: 13px; }
  .sel-dots { margin-top: 5px; }
  .progress { margin: 5px 0 4px; }
  .minor { gap: 6px; }
  .sound { padding: 6px 11px; min-height: 32px; font-size: 11px; }
  .nav { width: 42px; height: 42px; font-size: 24px; }
  .blurb { font-size: 11px; }
  .card .practice, .seedbox { gap: 8px; }

  .verdict { font-size: 30px; margin: 0 0 8px; }
  .stats { gap: 5px; margin-bottom: 12px; }
  .stats .row { padding: 7px 14px; }
  .stats .v { font-size: 15px; }
  .choices.two { grid-template-columns: repeat(2, 150px); }
  .key { min-height: 44px; }
}

/* A phone held sideways is wide and short, which is the wrong shape for a
   column. The preview takes the left half and everything you press takes the
   right, so nothing has to be scrolled to be reached. */
@media (max-height: 540px) and (min-width: 660px) {
  .mode-sheet {
    display: grid; text-align: center;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    column-gap: 18px; align-items: center;
  }
  .mode-heading { grid-column: 1 / -1; grid-row: 1; margin-bottom: 6px; }
  .mode-stage { grid-column: 1; grid-row: 2 / span 3; width: 100%; }
  .mode-opts   { grid-column: 2; grid-row: 2; margin: 0 0 10px; }
  .mode-row    { grid-column: 2; grid-row: 3; grid-template-columns: 1fr;
                 gap: 6px; margin-bottom: 10px; }
  .mode-go     { grid-column: 2; grid-row: 4; }
  .mode-key { grid-template-columns: auto auto; justify-content: center;
              gap: 10px; align-items: baseline; padding: 8px 10px;
              min-height: 42px; }
  .mode-go .key { min-width: 0; width: 100%; padding: 11px 14px; }
}

/* ==========================================================================
   THE MOTION LAYER
   Transform and opacity, everywhere, without exception. Those two are handed
   to the compositor: they cost no layout and no repaint, so a phone can run
   all of this and still hold sixty while a clock ticks and eight modules sit
   on screen. Nothing below animates width, height, top, left, filter or
   box-shadow on a loop — where a glow has to move it is a pre-composited
   layer whose OPACITY changes, not a shadow being recalculated per frame.

   The blanket prefers-reduced-motion rule at the end of this file switches
   every one of them off.
   ========================================================================== */

/* ---- press ripples ------------------------------------------------------
   One node, added at the point pressed and removed when it finishes. The
   host only clips while a ripple is actually inside it, so no control is
   permanently given a stacking context it did not need. */
/* The host clips the ripple; it is only given a position if it does not
   already have one. Forcing `position: relative` on every control took the
   absolutely placed ones — SKIP, BEGIN — out of position the instant they
   were pressed, so the button jumped away from the cursor and the click
   landed on nothing. */
.fx-clip { overflow: hidden; }
.fx-anchor { position: relative; }
.fx-ripple {
  position: absolute; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side,
              rgba(160, 235, 252, .26), rgba(160, 235, 252, .07) 66%,
              rgba(160, 235, 252, 0));
  transform: scale(0); opacity: 0;
  animation: fxRipple .72s cubic-bezier(.2, .7, .3, 1) forwards;
  will-change: transform, opacity;
}
@keyframes fxRipple {
  from { transform: scale(0); opacity: .75; }
  60%  { opacity: .34; }
  to   { transform: scale(1); opacity: 0; }
}
/* on the lit controls the ripple has to be dark to be seen at all */
.go-btn .fx-ripple, .press-btn .fx-ripple, .null-btn .fx-ripple,
.opt-btn.on .fx-ripple, .lay-btn.on .fx-ripple, .cs-begin .fx-ripple {
  background: radial-gradient(closest-side,
              rgba(6, 18, 26, .38), rgba(6, 18, 26, .12) 70%,
              rgba(6, 18, 26, 0));
}

/* ---- the iris -----------------------------------------------------------
   The cut between two places. A disc, scaled — never a clip-path, which
   repaints its layer on every frame of the animation. */
.fx-iris {
  position: fixed; z-index: 400; border-radius: 50%;
  pointer-events: none; transform: scale(0);
  contain: strict;
}
/* while it is closing it swallows presses, so a second click cannot arm a
   second device behind it; while it opens it is already out of the way */
.fx-iris.close { pointer-events: auto; }
/* a lit rim, so the disc reads as a lens closing rather than as a blob. It is
   painted once when the node is sized, never animated. */
.fx-iris::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  box-shadow: inset 0 0 90px 30px rgba(255, 255, 255, .05);
}
.fx-iris.close { animation: fxIrisClose var(--t-cut) cubic-bezier(.45, 0, .7, .15) forwards; }
.fx-iris.open  { animation: fxIrisOpen calc(var(--t-cut) * 1.15) var(--ease-out) forwards; }
@keyframes fxIrisClose { from { transform: scale(0); } to { transform: scale(1); } }
@keyframes fxIrisOpen  { from { transform: scale(1); } to { transform: scale(0); } }

/* ---- reveal on scroll ---------------------------------------------------- */
.fx-reveal { opacity: 0; transform: translate3d(0, 14px, 0); }
.fx-reveal.fx-seen {
  opacity: 1; transform: none;
  transition: opacity .5s ease, transform .5s cubic-bezier(.2, .8, .3, 1);
}

/* ==========================================================================
   SCREENS
   The swap itself is hidden. Every page change happens behind a cover that
   opens from the control you pressed, closes over the glass, lets the screen
   change where nobody can see it, and opens again — so the screens themselves
   have nothing to hide and only have to settle into place. They used to slide
   past each other in full view, which put the one uninteresting part of a
   change of screen, the change, on show.
   ========================================================================== */

@keyframes scrSettle { from { opacity: 0; transform: scale(.985); }
                       to   { opacity: 1; transform: none; } }
@keyframes scrGone   { from { opacity: 1; } to { opacity: 0; } }

.screen.entering { z-index: 3; animation: scrSettle var(--t-mid) var(--ease-out) both; }
.screen.leaving  { z-index: 2; pointer-events: none;
                   animation: scrGone .16s linear both; }

/* The deck must never be scaled on its way in: it carries a perspective and a
   preserve-3d case, and a transform on the container flattens the box. */
#screen-game.entering, #screen-game.leaving { animation-name: scrGone !important; }
#screen-game.entering { animation-direction: reverse; }

/* ==========================================================================
   ARRIVALS
   A screen assembles rather than appearing. Every delay and duration here is
   roughly half again what it was: the whole point of a transition is that it
   is watched, and these were over before the eye had found them.
   ========================================================================== */

@keyframes fxRise { from { opacity: 0; transform: translate3d(0, 22px, 0); }
                    to   { opacity: 1; transform: none; } }
@keyframes fxRiseIn { from { opacity: 0; transform: translate3d(0, 34px, 0) scale(.95); }
                      to   { opacity: 1; transform: none; } }
@keyframes fxPop { from { opacity: 0; transform: scale(.78); }
                   62%  { opacity: 1; transform: scale(1.05); }
                   to   { opacity: 1; transform: scale(1); } }
@keyframes fxSlideL { from { opacity: 0; transform: translate3d(-30px, 0, 0); }
                      to   { opacity: 1; transform: none; } }
@keyframes fxWiden { from { transform: scaleX(0); opacity: 0; }
                     to   { transform: scaleX(1); opacity: 1; } }

/* the menu */
.menu.entering .title        { animation: fxRiseIn .9s var(--ease-out) both; }
.menu.entering .title::after { animation: fxWiden 1s var(--ease-out) .34s both; }
.menu.entering .select       { animation: fxRiseIn .8s var(--ease-out) .16s both; }
.menu.entering .sel-dots     { animation: fxRise .6s var(--ease) .42s both; }
.menu.entering .progress     { animation: fxRise .6s var(--ease) .5s both; }
.menu.entering .minor > *    { animation: fxRiseIn .66s var(--ease-out) both; }
.menu.entering .minor > *:nth-child(1) { animation-delay: .58s; }
.menu.entering .minor > *:nth-child(2) { animation-delay: .66s; }

/* the mode screen */
.menu.entering .mode-heading { animation: fxRiseIn .7s var(--ease-out) .13s both; }
.menu.entering .mode-stage   { animation: fxRiseIn .85s var(--ease-out) .22s both; }
.menu.entering .mode-opts    { animation: fxRise .62s var(--ease) .54s both; }
.menu.entering .mode-row .mode-key { animation: fxRiseIn .7s var(--ease-out) both; }
.menu.entering .mode-row .mode-key:nth-child(1) { animation-delay: .6s; }
.menu.entering .mode-row .mode-key:nth-child(2) { animation-delay: .7s; }
.menu.entering .mode-row .mode-key:nth-child(3) { animation-delay: .8s; }
.menu.entering .mode-go      { animation: fxRise .7s var(--ease-out) .92s both; }

/* settings */
.menu.entering .set-heading  { animation: fxRiseIn .7s var(--ease-out) .06s both; }
.menu.entering .set-row      { animation: fxSlideL .66s var(--ease-out) both; }
.menu.entering .set-row:nth-child(1) { animation-delay: .2s; }
.menu.entering .set-row:nth-child(2) { animation-delay: .3s; }
.menu.entering .set-row:nth-child(3) { animation-delay: .4s; }
.menu.entering .set-row:nth-child(4) { animation-delay: .48s; }
.menu.entering .set-row:nth-child(5) { animation-delay: .56s; }
.menu.entering .set-row:nth-child(6) { animation-delay: .64s; }

/* the result, read top to bottom */
@keyframes fxVerdict { from { opacity: 0; transform: scale(1.38); }
                       55%  { opacity: 1; }
                       to   { opacity: 1; transform: scale(1); } }
.menu.entering .verdict { animation: fxVerdict .95s var(--ease-out) both; }
.menu.entering .stats .row { animation: fxSlideL .7s var(--ease-out) both; }
.menu.entering .stats .row:nth-child(1) { animation-delay: .38s; }
.menu.entering .stats .row:nth-child(2) { animation-delay: .5s; }
.menu.entering .stats .row:nth-child(3) { animation-delay: .62s; }
.menu.entering .stats .row:nth-child(4) { animation-delay: .74s; }
.menu.entering .choices .key { animation: fxRiseIn .7s var(--ease-out) both; }
.menu.entering .choices .key:nth-child(1) { animation-delay: .86s; }
.menu.entering .choices .key:nth-child(2) { animation-delay: .95s; }
.menu.entering .choices .key:nth-child(3) { animation-delay: 1.04s; }
.menu.entering .code-plate { animation: fxRiseIn .8s var(--ease-out) 1.14s both; }

/* the manual, wherever it is mounted */
@keyframes fxSlideDown { from { opacity: 0; transform: translate3d(0, -100%, 0); }
                         to   { opacity: 1; transform: none; } }
.manual-view .mv-bar { animation: fxSlideDown .62s var(--ease-out) both; }
.mv-sheet-title { animation: fxSlideL .7s var(--ease-out) .14s both; }
.mv-sheet-sub   { animation: fxRise .7s var(--ease) .26s both; }
.mv-toc a { animation: fxPop .56s var(--ease-out) both;
            animation-delay: calc(.34s + var(--i, 0) * .055s); }

/* ---- a half of the split arriving ----------------------------------------
   The geometry still snaps — animating `inset` on two full-screen panes is
   layout work on every frame for the whole of both. What travels is a
   transform, and it travels from the side the half actually lives on. */
@keyframes fxFromRight { from { opacity: 0; transform: translate3d(14%, 0, 0); }
                         to   { opacity: 1; transform: none; } }
@keyframes fxFromLeft  { from { opacity: 0; transform: translate3d(-14%, 0, 0); }
                         to   { opacity: 1; transform: none; } }
@keyframes fxFromBelow { from { opacity: 0; transform: translate3d(0, 16%, 0); }
                         to   { opacity: 1; transform: none; } }
.fx-slide.from-right { animation: fxFromRight .58s var(--ease-out) both; }
.fx-slide.from-left  { animation: fxFromLeft .58s var(--ease-out) both; }
.fx-slide.from-below { animation: fxFromBelow .58s var(--ease-out) both; }

/* something worth noticing changed in place */
@keyframes fxFlash { 0%   { color: var(--rim); }
                     100% { color: inherit; } }
.fx-flash { animation: fxFlash .9s var(--ease) both; }

/* ==========================================================================
   HOVER
   Only where a pointer can actually hover. Each is a transform or an opacity
   on an element that already has its own layer while the transition runs.
   ========================================================================== */

@media (hover: hover) {
  .key, .sound, .nav, .opt-btn, .lay-btn, .mv-toc a, .dot, .flip-btn {
    transition: transform .18s cubic-bezier(.2, .9, .3, 1),
                background-color .18s ease, border-color .18s ease,
                color .18s ease, box-shadow .22s ease;
  }
  .key:hover:not(:disabled)   { transform: translateY(-3px); }
  .sound:hover                { transform: translateY(-2px); }
  .opt-btn:hover:not(.on)     { transform: translateY(-2px); }
  .lay-btn:hover:not(.on)     { transform: translateY(-1px); }
  .mv-toc a:hover             { transform: translateY(-2px); }
  .nav:hover:not(:disabled)   { transform: scale(1.09); }
  .dot:hover                  { transform: scale(1.35); }
  .flip-btn:hover             { transform: translateY(-2px); }
  .mode-key:hover:not(.on)    { transform: translateY(-4px); }

  /* No hover transform on a bay, and no transform transition on one. A bay
     lives inside the case's preserve-3d tree, which the cursor tilt already
     rewrites every frame; giving each of eight bays its own 3D transform to
     interpolate meant the whole tree re-composited as the pointer crossed it,
     and a focused bay — which carries an INLINE transform — fought the rule
     outright. The case answers the pointer by leaning, once, as a whole. */

  /* the card under the pointer lifts out of the carousel */
  .card { transition: transform .3s cubic-bezier(.2, .9, .3, 1),
                      border-color .3s ease, box-shadow .3s ease; }
  .page:hover .card:not(.locked) { transform: translateY(-4px); }
}

/* pressing always answers, pointer or not */
.key:active:not(:disabled), .sound:active, .opt-btn:active, .lay-btn:active,
.mode-key:active, .mv-toc a:active { transform: scale(.965); }

/* ==========================================================================
   THE CAROUSELS
   The track already slides. What was missing is that the page you are ON
   should look nearer than the ones you are not.
   ========================================================================== */

/* The card you are on sits nearer than the ones sliding past it. Scoped by
   the class the carousel already sets, not by :has(): a structural selector
   re-evaluated on every DOM change buys nothing here. */
.card:not(.focused-page) { transform: scale(.94); }
.mode-panel { transition: transform .42s cubic-bezier(.3,.85,.3,1), opacity .32s ease; }
.mode-panel:not(.on) { transform: scale(.9); }

/* the dots answer the page landing on them */
.dot { transition: transform .3s cubic-bezier(.2,1.4,.4,1), background-color .3s ease; }
.dot.on { transform: scale(1.5); }

/* ==========================================================================
   THE DEVICE
   ========================================================================== */

/* the case's own zoom, when the defuser pulls back or pushes in */
.bomb.zooming { transition: transform .52s cubic-bezier(.22, 1, .28, 1); }

/* a module coming forward, and the rest of the case falling away behind it */
/* The base `.bay` rule already carries the transform, border and background
   transitions the case is built around — a focused module coming forward, a
   solved bay fading green. Nothing here overrides them: an earlier pass set
   `transition: none` on unfocused bays and silently took the solve fade with
   it. */
.scrim { transition: opacity .32s ease; }

/* a solve: the bay flashes, then a ring leaves it */
@keyframes fxSolveRing {
  from { opacity: .85; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.14); }
}
.bay.module.solved::after {
  content: ''; position: absolute; inset: -3px; border-radius: 16px;
  border: 3px solid var(--green); pointer-events: none;
  animation: fxSolveRing .62s ease-out forwards;
}

/* a strike: the bay recoils */
/* Every frame keeps the bay's own translateZ(14px): a bay sits proud of the
   panel, and a keyframe that forgets that drops it flat for the length of the
   kick and pops it back out at the end. */
@keyframes fxStrikeKick {
  0%   { transform: translateZ(14px); }
  18%  { transform: translateZ(14px) translate3d(-7px, 2px, 0) rotate(-.5deg); }
  42%  { transform: translateZ(14px) translate3d(6px, -2px, 0) rotate(.45deg); }
  68%  { transform: translateZ(14px) translate3d(-3px, 1px, 0) rotate(-.2deg); }
  100% { transform: translateZ(14px); }
}
.bay.striking:not(.focused) {
  animation: fxStrikeKick .5s cubic-bezier(.36,.07,.19,.97) both;
}

/* ==========================================================================
   THE SPLIT
   The geometry snaps — animating `inset` on two full-screen panes is layout
   work on every frame, for the whole of both panes, and the case inside one
   of them has to be re-measured as it goes. The panes FADE instead: the same
   read, off the compositor.
   ========================================================================== */

@keyframes fxPaneIn { from { opacity: 0; transform: scale(.985); }
                      to   { opacity: 1; transform: none; } }
@keyframes fxFade   { from { opacity: 0; } to { opacity: 1; } }
.play-manual.fx-pane { animation: fxPaneIn .3s cubic-bezier(.2,.9,.3,1) both; }
/* The deck fades, never the case: opacity on an element inside preserve-3d
   flattens the box, and the case is mid-turn often enough to matter. */
#screen-game.fx-pane { animation: fxFade .3s ease both; }

@keyframes fxBarIn { from { opacity: 0; transform: translateX(-50%) translateY(16px); }
                     to   { opacity: 1; transform: translateX(-50%); } }
.layout-bar.fx-in { animation: fxBarIn .34s cubic-bezier(.2,1.15,.35,1) both; }
.split-grip.fx-in { animation: fxFade .4s ease .1s both; }

/* ==========================================================================
   OFF
   One rule, and none of the above happens. It sits last so it wins.
   ========================================================================== */

/* was @media (prefers-reduced-motion) — now the REDUCE MOTION setting,
   which follows the system unless the player overrides it (js/settings.js) */
body.rm .fx-ripple, body.rm .fx-iris { display: none !important; }
body.rm .fx-reveal { opacity: 1 !important; transform: none !important; }
body.rm .mode-panel, body.rm .dot, body.rm .card { transition: none !important; }
body.rm .card:not(.focused-page) { transform: none; }
body.rm .mode-panel:not(.on) { transform: none; }


/* ==========================================================================
   EMPHASIS
   There used to be four buttons of identical weight under the device card:
   MODE, SOUND, REPLAY INTRO, RESET PROGRESS. Nothing told the eye which of
   them mattered, and one of them wipes your progress. There are two controls
   here now — the one decision that changes how the game is played, and a
   door to everything that is housekeeping.
   ========================================================================== */

.minor { display: flex; gap: 12px; justify-content: center; align-items: stretch;
         margin-top: 6px; }

/* MODE: a statement of what the mode currently IS, not a button whose label
   you have to remember the meaning of */
.wide-key {
  display: grid; gap: 3px; justify-items: center;
  min-width: 240px; padding: 13px 26px;
}
.wide-key b { font-size: var(--t-label); letter-spacing: var(--trk-label); }
.wide-key i {
  font-style: normal; font-family: var(--prose); font-weight: 700;
  font-size: var(--t-micro); letter-spacing: .16em; color: var(--rim);
}

/* settings: present, reachable, and not competing */
.icon-key {
  flex: none; width: 52px; padding: 0;
  display: grid; place-items: center;
  font-family: var(--round); color: var(--ink-2);
  background: #16232a; border: 1px solid #2c4048; border-radius: 12px;
  cursor: pointer;
}
.icon-key svg { width: 21px; height: 21px; fill: none; stroke: currentColor;
                stroke-width: 1.9; stroke-linecap: round; }
.icon-key:hover { color: var(--rim); border-color: var(--rim); }

/* ==========================================================================
   SETTINGS
   ========================================================================== */

.set-sheet { width: min(560px, 100%); }
.set-heading {
  margin: 0 0 24px; font-family: var(--seg); font-weight: 400;
  font-size: var(--t-head); letter-spacing: .1em; color: #eaf7fb;
  line-height: 1.1;
  text-shadow: 0 0 5px rgba(255,255,255,.6), 0 0 26px rgba(111,215,232,.55),
               0 0 64px rgba(111,215,232,.3);
}
.set-list { display: grid; gap: 10px; margin-bottom: 26px; text-align: left; }
.set-row {
  display: flex; align-items: center; gap: 14px;
  padding: 13px 16px 13px 20px;
  background: linear-gradient(180deg, #1b2a32, #141f26);
  border: 1px solid #2c4048; border-radius: 13px;
}
.set-row > b {
  flex: 1 1 auto; min-width: 0; text-align: left;
  font-family: var(--round); font-weight: 700; font-size: var(--t-label);
  letter-spacing: var(--trk-label); color: #e7f3f6;
}
.set-val {
  flex: none; font-family: var(--seg); font-size: var(--t-sub);
  color: var(--ink-2); letter-spacing: .06em;
}
.set-row.danger { border-color: #4a2f2c; }
.set-row .opt-btn { flex: none; min-width: 84px; }
.opt-btn.bad { color: #f0a49b; border-color: #5e3833; }
.opt-btn.bad:hover { color: #fff; background: var(--red); border-color: var(--red); }

/* ==========================================================================
   THE CODE, ON THE RESULT SCREEN
   It was a label, a code and a button strung along one line under everything
   else, which is why it read as debris. It is a plate now: the thing the
   player is meant to take away from the round has an edge around it.
   ========================================================================== */

.result-sheet { width: min(460px, 100%); }

.code-plate {
  margin: 26px auto 0; max-width: 420px; text-align: left;
  padding: 14px 16px 13px;
  background: linear-gradient(180deg, #17242c, #111b21);
  border: 1px solid #2c4048; border-radius: 13px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
}
.code-head { display: flex; align-items: baseline; justify-content: space-between;
             gap: 10px; margin-bottom: 8px; }
.code-label {
  font-family: var(--prose); font-weight: 700; font-size: var(--t-micro);
  letter-spacing: var(--trk-label); color: var(--ink-3);
}
.code-detail {
  font-family: var(--mono); font-size: var(--t-micro); color: var(--ink-3);
  letter-spacing: .08em; white-space: nowrap;
}
.code-row { display: flex; align-items: center; gap: 12px; }
.code-row .seed-code { flex: 1 1 auto; min-width: 0; }
.code-row .opt-btn { flex: none; min-width: 78px; padding: 9px 12px;
                     min-height: 38px; }

/* a control that refuses: the panel shakes its head */
@keyframes fxShake {
  0%, 100% { transform: translate3d(0,0,0); }
  15% { transform: translate3d(-8px,0,0); }
  33% { transform: translate3d(7px,0,0); }
  52% { transform: translate3d(-5px,0,0); }
  71% { transform: translate3d(3px,0,0); }
  87% { transform: translate3d(-1px,0,0); }
}
.fx-shake { animation: fxShake .44s cubic-bezier(.36,.07,.19,.97) both; }
/* was @media (prefers-reduced-motion) — now the REDUCE MOTION setting,
   which follows the system unless the player overrides it (js/settings.js) */
body.rm .fx-shake { animation: none; }


/* ==========================================================================
   THE SEGMENTED CONTROL
   Two cells, one lit pill, and the pill is the only thing that moves. What was
   here before rebuilt both buttons on every press, so the labels changed width
   and every line under them jumped to match — choosing what a screen is looked
   like the page coming apart.
   ========================================================================== */

.seg {
  position: relative; display: grid;
  grid-template-columns: repeat(var(--n, 2), minmax(0, 1fr));
  gap: 0; padding: 4px;
  background: #121d24; border: 1px solid #2b3d46; border-radius: 12px;
  min-width: min(320px, 78vw);
}
.seg-pill {
  position: absolute; z-index: 0;
  top: 4px; bottom: 4px; left: 4px;
  width: calc((100% - 8px) / var(--n, 2));
  border-radius: 9px;
  background: var(--rim);
  box-shadow: 0 0 22px color-mix(in srgb, var(--rim) 45%, transparent);
  transition: transform var(--t-mid) var(--ease-out);
}
.seg-btn {
  position: relative; z-index: 1;
  font-family: var(--round); font-size: var(--t-label);
  letter-spacing: var(--trk-label);
  color: var(--ink-2); background: transparent; border: 0;
  padding: 11px 10px; min-height: 42px; cursor: pointer;
  transition: color var(--t-mid) var(--ease);
}
.seg-btn.on { color: #06121a; }
.seg-btn:hover:not(.on) { color: var(--text); }

/* was @media (prefers-reduced-motion) — now the REDUCE MOTION setting,
   which follows the system unless the player overrides it (js/settings.js) */
body.rm .seg-pill { transition: none; }


/* ---- level sliders -------------------------------------------------------
   A lit bar, the same cyan as every other thing that is ON, filled up to the
   thumb. The fill is --fill, set from JS as the value changes. */
.set-slide > b { flex: 0 0 96px; }
.slider {
  -webkit-appearance: none; appearance: none;
  flex: 1 1 auto; min-width: 0; height: 30px; margin: 0;
  background: transparent; cursor: pointer; --fill: 100%;
}
.slider:focus { outline: none; }
.slider::-webkit-slider-runnable-track {
  height: 8px; border-radius: 6px; border: 1px solid #2b3d46;
  background: linear-gradient(90deg, var(--rim) 0 var(--fill), #0e181d var(--fill) 100%);
  box-shadow: 0 0 12px rgba(111,215,232,.35), inset 0 1px 2px rgba(0,0,0,.5);
}
.slider::-moz-range-track {
  height: 8px; border-radius: 6px; border: 1px solid #2b3d46; background: #0e181d;
}
.slider::-moz-range-progress {
  height: 8px; border-radius: 6px; background: var(--rim);
  box-shadow: 0 0 12px rgba(111,215,232,.45);
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; margin-top: -8px; border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, #ffffff, #bfeff7 55%, #6fd7e8);
  border: 2px solid #06121a;
  box-shadow: 0 0 0 2px var(--rim), 0 0 16px rgba(111,215,232,.8);
  transition: transform var(--t-quick) var(--ease);
}
.slider::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; background: #dff8fc;
  border: 2px solid #06121a; box-shadow: 0 0 0 2px var(--rim), 0 0 16px rgba(111,215,232,.8);
}
.slider:hover::-webkit-slider-thumb, .slider:focus-visible::-webkit-slider-thumb {
  transform: scale(1.15);
}
.slider:focus-visible::-webkit-slider-runnable-track { border-color: var(--rim); }
/* sound OFF: the levels are kept, but they read as switched off */
.slider.muted { filter: grayscale(1) brightness(.6); }
.set-slide .set-val { min-width: 3ch; text-align: right; color: var(--rim);
  text-shadow: 0 0 10px rgba(111,215,232,.6); }
@media (max-width: 520px) {
  .set-slide > b { flex-basis: 70px; }
}

/* ==========================================================================
   LIGHT
   Everything on the case that is a light source now reads as one: a hot core,
   a tight bloom, and a wide spill onto whatever is around it. Before, most of
   them were flat colour with at best one soft shadow, so the clock and a
   painted sticker looked the same kind of thing.

   All of it is static box-shadow and text-shadow. Those rasterise once with
   the layer; a CSS filter or an animated shadow inside the 3D tree would be
   repainted with the whole case on every tilt and zoom (see "Cut the
   per-frame work" in the history).
   ========================================================================== */

/* the clock: segments burning through a smoked window */
.lcd {
  text-shadow:
    0 0 2px #fff3d6,
    0 0 10px rgba(255,200,97,.95),
    0 0 26px rgba(255,190,80,.7),
    0 0 52px rgba(255,170,60,.45);
  box-shadow:
    inset 0 0 26px rgba(255,190,80,.16),
    inset 0 0 0 1px rgba(255,200,97,.12),
    0 0 22px rgba(255,190,80,.22);
}
.lcd.urgent { text-shadow: 0 0 2px #ffe0da, 0 0 12px rgba(255,120,100,.9),
                           0 0 30px rgba(230,80,70,.65), 0 0 56px rgba(210,60,50,.4); }

/* the serial and the counter are readouts too */
.plate {
  text-shadow: 0 0 2px #e6fbff, 0 0 9px rgba(111,215,232,.9),
               0 0 22px rgba(111,215,232,.55);
  box-shadow: inset 0 0 18px rgba(111,215,232,.14), 0 0 16px rgba(111,215,232,.18);
}
.panel .count {
  text-shadow: 0 0 2px #e5ffe9, 0 0 9px rgba(116,201,140,.9),
               0 0 22px rgba(116,201,140,.5);
}

/* the drain bar is a lit tube, and its colour is the light it throws */
.fuse { box-shadow: 0 0 16px rgba(79,157,93,.45), inset 0 0 0 1px rgba(255,255,255,.04); }
.fuse.warn   { box-shadow: 0 0 16px rgba(221,182,63,.5); }
.fuse.danger { box-shadow: 0 0 18px rgba(208,87,79,.6); }
.fuse i { box-shadow: inset 0 2px 0 rgba(255,255,255,.35), inset 0 -3px 6px rgba(0,0,0,.25); }

/* strike lamps: dark glass until lit, then a hot core and a wide spill */
.strike-row i { box-shadow: inset 0 3px 6px rgba(0,0,0,.6), inset 0 -1px 0 rgba(255,255,255,.06); }
.strike-row i.lit {
  background: radial-gradient(circle at 42% 38%, #ffd9d3 0 14%, #ff6a5c 38%, var(--red) 70%);
  box-shadow: 0 0 6px 2px rgba(255,120,100,.95), 0 0 22px 8px rgba(208,87,79,.7),
              0 0 48px 14px rgba(208,87,79,.35), inset 0 1px 0 rgba(255,255,255,.5);
}

/* the ABORT key is backlit */
.abort { box-shadow: 0 3px 0 var(--ink), 0 0 16px rgba(208,87,79,.45); }

/* A module's face is a backlit panel, and its rim is where the light leaks:
   a thin cool edge and a soft spill onto the tray between the bays. */
.bay.module {
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.95),
    inset 0 -3px 0 rgba(0,0,0,.14),
    inset 0 0 22px rgba(190,236,245,.4),
    0 0 0 1.5px rgba(160,228,240,.5),
    0 0 14px rgba(140,220,236,.45),
    0 0 34px rgba(111,215,232,.22),
    0 5px 9px rgba(0,0,0,.5),
    0 12px 22px rgba(0,0,0,.4);
}
.bay.module.solved {
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.95),
    inset 0 0 26px rgba(120,210,140,.35),
    0 0 0 1px rgba(120,210,140,.55),
    0 0 26px rgba(79,157,93,.55),
    0 5px 9px rgba(0,0,0,.5);
}
/* the tray the modules sit in catches their light */
.bays {
  box-shadow:
    inset 0 4px 10px rgba(0,0,0,.75),
    inset 0 0 40px rgba(111,215,232,.08),
    inset 0 -2px 0 rgba(255,255,255,.05),
    0 1px 0 rgba(255,255,255,.08);
}

/* status lamp in each bay's corner: dim glass, then green and burning */
.led { box-shadow: inset 0 2px 3px rgba(0,0,0,.25); }
.bay.solved .led {
  background: radial-gradient(circle at 40% 35%, #e6ffe9 0 18%, #6ad07e 45%, var(--green) 80%);
  box-shadow: 0 0 5px 2px rgba(140,230,160,.95), 0 0 16px 6px rgba(79,157,93,.75),
              0 0 34px 10px rgba(79,157,93,.35), inset 0 1px 0 rgba(255,255,255,.6);
}

/* the conversion module's indicator lamps */
.uc-lamps i.on {
  background: radial-gradient(circle at 40% 35%, #effdff 0 16%, #9fe8f4 42%, var(--rim) 80%);
  box-shadow: 0 0 5px 2px rgba(170,238,248,.95), 0 0 16px 6px rgba(111,215,232,.7),
              0 0 32px 10px rgba(111,215,232,.3), inset 0 1px 0 rgba(255,255,255,.5);
}

/* press-progress pips light up as they are earned */
.dots i { box-shadow: 0 0 8px 2px rgba(111,215,232,.75), 0 0 18px 4px rgba(111,215,232,.3); }

/* ---- zoom -----------------------------------------------------------------
   The case zooms under the pointer and can be dragged once it is bigger than
   its pane; a finger pinch is handled in script, so the browser must not take
   the gesture for a page zoom. The manual keeps vertical scrolling native. */
#screen-game { touch-action: none; }
body.view-zoomed #screen-game { cursor: grab; }
body.view-panning #screen-game, body.view-panning #screen-game * { cursor: grabbing !important; }
body.view-panning .bomb { transition: none !important; }
.mv-paper { touch-action: pan-x pan-y; }

.mv-zoom { margin-left: auto; display: flex; align-items: center; gap: 4px; }
.mv-zbtn, .mv-zval {
  font-family: var(--round); font-weight: 700; color: #bfeef8;
  background: #0f1a20; border: 1px solid #2b3d46; border-radius: 8px;
  height: 30px; cursor: pointer;
  transition: color var(--t-quick) ease, border-color var(--t-quick) ease;
}
.mv-zbtn { width: 30px; font-size: 17px; line-height: 1; padding: 0; }
.mv-zval { min-width: 56px; padding: 0 8px; font-family: var(--mono);
           font-size: 12px; font-weight: 500; color: var(--ink-2); }
.mv-zbtn:hover:not(:disabled), .mv-zval:hover { border-color: var(--rim); color: #fff; }
.mv-zbtn:disabled { opacity: .35; cursor: default; }
@media (pointer: coarse) {
  .mv-zbtn { width: 40px; height: 40px; }
  .mv-zval { height: 40px; }
}

/* ==========================================================================
   PLAYER SETTINGS
   Every one of these is a class or a custom property set by js/settings.js;
   nothing below runs any script.
   ========================================================================== */

/* SCREEN SHAKE off: the case, a struck bay and the seed box stay still. The
   strike still flashes and still sounds — it is only the movement that goes. */
body.no-shake .casing.shake,
body.no-shake .bay.striking:not(.focused),
body.no-shake .fx-shake { animation: none !important; }

/* FLASHES: every full-screen wash, the red room under thirty seconds, a
   struck bay's red fill and the white pop as the strip powers up are scaled by
   --flash-k. `filter: opacity()` multiplies with the opacity each animation
   sets, so the animations themselves are untouched. */
.flash, .deck.critical::after, .bay.striking::after,
.casing.arming .panel .strip::after { filter: opacity(var(--flash-k, 1)); }
/* at zero, gone — and the clock stops blinking, holding a steady red instead */
body.no-flash .flash, body.no-flash .deck.critical::after,
body.no-flash .bay.striking::after,
body.no-flash .casing.arming .panel .strip::after { display: none; }
body.no-flash .lcd.urgent { animation: none; color: #ff8a70; }

/* TEXT SIZE: the menus, the opening's caption and the split's controls. CSS
   `zoom` re-lays the text out at the new size instead of stretching a
   picture of it. The case is a scaled drawing with its own zoom; the manual
   multiplies this into its own size in js/manual-view.js. */
.menu .sheet, .cs-begin, .layout-bar, .reader-back,
.mv-bar, .arming-line, .focus-clock { zoom: var(--ui-text, 1); }

/* COLOUR LABELS: a letter on every key whose colour is the rule. */
.cb-tag { display: none; pointer-events: none; }
body.cb .cb-tag { display: inline; }
.swatch { display: grid; place-items: center; }
body.cb .swatch-word {
  display: block; font-family: var(--round); font-weight: 700; font-size: 20px;
  letter-spacing: .12em; color: #fff;
  text-shadow: 0 0 3px #1a2429, 0 1px 2px #1a2429, 0 0 1px #1a2429;
}

/* ---- the settings screen's tabs and rows ------------------------------- */
.set-sheet { width: min(620px, 100%); }
.set-tabs { margin: 0 0 16px; }
.set-tabs .seg-btn { font-weight: 700; }
/* the pages are laid over one another in one grid cell, so the sheet is as
   tall as the tallest page and switching tabs never moves DONE */
.set-pages { display: grid; margin-bottom: 26px; }
.set-pages > .set-list { grid-area: 1 / 1; margin: 0; align-content: start; }
.set-pages > .set-list[hidden] { display: grid !important; visibility: hidden; }
.set-row { min-height: 64px; }
.set-lbl { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
           gap: 3px; text-align: left; }
.set-lbl b { font-family: var(--round); font-weight: 700; font-size: var(--t-label);
             letter-spacing: var(--trk-label); color: #e7f3f6; }
.set-lbl small { font-family: var(--prose); font-size: 11px; color: var(--ink-3);
                 line-height: 1.35; }
.set-slide .set-lbl { flex: 0 0 150px; }
.set-seg { flex: none; min-width: 200px; }
.set-seg .seg-btn { padding: 8px 10px; min-height: 36px; font-weight: 700; }
.set-row[hidden] { display: none !important; }
@media (max-width: 560px) {
  .set-slide .set-lbl { flex-basis: 100px; }
  .set-lbl small { display: none; }
  .set-seg { min-width: 150px; }
}

/* ==========================================================================
   THE ROOM (js/room3d.js)
   A WebGL layer behind the case: the desk it stands on, the wall, the lamp,
   the case's solid body and its shadow. The CSS case still draws the faces
   and takes every press; the room only matches it. With the room on, the
   CSS rims and the painted drop shadow give way to the real ones.
   ========================================================================== */
canvas.room3d { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; display: block;
                 pointer-events: none; }   /* scenery: it never takes a click */
body.room3d .edge { display: none; }
body.room3d .casing {
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.22),
    inset 2px 0 0 rgba(255,255,255,.07),
    inset -2px 0 0 rgba(0,0,0,.4),
    inset 0 -4px 0 rgba(0,0,0,.5),
    inset 0 0 0 1px rgba(111,215,232,.12);
}
/* The 3D case (test shot B): the face is a plain plate in the body's own
   colour, set into the solid frame the room draws round it. The painted
   chassis — screws, looms, stripes, a drawn handle — gives way to the real
   handle and feet, and the serial moves to the case's two sides, where a
   player turns the case to read it. */
body.room3d .chassis { display: none; }
body.room3d .casing {
  background: linear-gradient(180deg, #44555d 0%, #3a4a52 55%, #33424a 100%);
  border: 2px solid #1b2428; border-radius: 22px;
  box-shadow: inset 0 3px 0 rgba(255,255,255,.09), inset 0 -3px 0 rgba(0,0,0,.25);
}
body.room3d .panel .plate, body.room3d .strip.plate-only { display: none; }
body.room3d .panel .strip {
  background: linear-gradient(180deg, #121b20, #18232a);
  border: 2px solid #0b1115; border-radius: 18px;
  box-shadow: inset 0 6px 14px rgba(0,0,0,.55), 0 2px 0 rgba(255,255,255,.08);
}

/* the lamp's light on a CSS face: darker as it turns away, in one flat step */
.room-shade {
  position: absolute; inset: 0; z-index: 70; pointer-events: none;
  border-radius: inherit; background: #05080b; opacity: 0;
}

/* a module rebuilt in 3D (js/module3d.js): its canvas sits in the bay like
   any panel and takes the presses itself */
canvas.m3d { display: block; touch-action: manipulation; }
/* the 2D view a 3D module presents: still mounted, still the source of
   truth, shown only while the 3D view is being built over it */
.m3d-src { display: none !important; }
.m3d-pending > .m3d-src { display: block !important; position: absolute; left: 0; top: 0; right: 0; }
.m3d-pending > canvas.m3d { opacity: 0; }
canvas.m3d { transition: opacity .28s ease-out; }
.body.m3d-host { position: relative; }
/* the original text boxes, set into the module's 3D slots */
.m3d-layer { position: absolute; pointer-events: none; }
.bay.module .m3d-input {
  position: absolute; margin: 0; padding: 0 8px; box-sizing: border-box;
  background: transparent; border: 0; border-radius: 6px; outline: none;
  font-family: var(--seg); font-size: 34px; line-height: 1; text-align: center;
  color: rgb(255,200,97); caret-color: rgb(255,200,97);
  text-shadow: 0 0 10px rgba(255,190,80,.7);
  pointer-events: auto;
}
.bay.module .m3d-input:focus { box-shadow: 0 0 0 2px rgba(111,215,232,.7), 0 0 16px rgba(111,215,232,.45); }
.bay.module .m3d-input:disabled { color: #5fae74; text-shadow: none; background: transparent; }

/* ==========================================================================
   THE SHELL (js/shell.js, js/title3d.js): splash, title, main menu
   ========================================================================== */
.shell-bg { position: fixed; inset: 0; z-index: -1; background: #0d1217; }
.shell-bg canvas.title3d { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* over the 3D desk the 2D sky steps aside */
body.shell-3d .backdrop { visibility: hidden; }

/* the splash: the name, lit, on black */
.splash {
  position: fixed; inset: 0; z-index: 200; background: #000;
  display: grid; place-items: center; cursor: pointer;
}
.splash-mark { text-align: center; opacity: 0; transform: scale(.97); }
.splash.on .splash-mark { animation: splashIn 2.2s var(--ease-out) both; }
.splash.off { animation: splashOut .7s ease both; }
@keyframes splashIn { 0% { opacity: 0; transform: scale(.97); } 30% { opacity: 1; transform: none; }
                      78% { opacity: 1; } 100% { opacity: 1; } }
@keyframes splashOut { to { opacity: 0; } }
.splash-mark b {
  display: block; font-family: var(--seg); font-weight: 400;
  font-size: clamp(36px, 7vw, 92px); letter-spacing: .14em; text-indent: .14em; color: #eaf7fb;
  text-shadow: 0 0 6px rgba(255,255,255,.6), 0 0 30px rgba(111,215,232,.6), 0 0 80px rgba(111,215,232,.3);
}
.splash-mark small {
  display: block; margin-top: 18px; font-family: var(--round); font-weight: 600;
  font-size: 12px; letter-spacing: .5em; text-indent: .5em; color: var(--ink-2);
}

/* the title: the name over the desk, and the one instruction */
.title-screen { display: grid; grid-template-rows: 1fr auto; padding: 8vh 6vw 9vh; cursor: pointer; }
.title-block { align-self: start; text-align: center; }
.title-logo {
  margin: 0; font-family: var(--seg); font-weight: 400;
  font-size: clamp(40px, 8vw, 110px); letter-spacing: .12em; text-indent: .12em; color: #f2fbfd;
  text-shadow: 0 0 6px rgba(255,255,255,.55), 0 0 34px rgba(111,215,232,.55), 0 0 90px rgba(111,215,232,.3),
               0 4px 24px rgba(0,0,0,.6);
}
.title-tag {
  margin: 14px 0 0; font-family: var(--round); font-weight: 600; font-size: calc(13px * var(--ui-text, 1));
  letter-spacing: .32em; text-indent: .32em; color: #d6e7ee; text-shadow: 0 2px 10px rgba(0,0,0,.9);
}
.press-any {
  margin: 0; text-align: center; font-family: var(--round); font-weight: 700;
  font-size: calc(15px * var(--ui-text, 1)); letter-spacing: .5em; text-indent: .5em; color: #ffd27a;
  text-shadow: 0 0 14px rgba(255,190,80,.7), 0 2px 8px rgba(0,0,0,.9);
  animation: pressAny 2.2s ease-in-out infinite;
}
@keyframes pressAny { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
.shell-version { position: absolute; right: 18px; bottom: 14px; font-family: var(--mono);
                 font-size: 11px; color: rgba(220,235,240,.45); }

/* the main menu: a column on the left, the desk on the right */
.main-screen { display: flex; align-items: center; padding: 0 0 0 clamp(20px, 7vw, 120px); }
.main-panel {
  width: min(440px, 88vw); padding: 34px 30px 26px;
  background: linear-gradient(90deg, rgba(5,9,12,.82), rgba(5,9,12,.55));
  border-left: 3px solid var(--rim); border-radius: 0 16px 16px 0;
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
  zoom: var(--ui-text, 1);
}
.main-logo {
  margin: 0 0 22px; font-family: var(--seg); font-weight: 400; font-size: 34px;
  letter-spacing: .12em; color: #eaf7fb; text-shadow: 0 0 22px rgba(111,215,232,.5);
}
.main-menu { display: grid; gap: 6px; }
.mm-item {
  display: grid; text-align: left; gap: 3px;
  padding: 12px 18px 12px 22px; border: 0; border-radius: 10px; cursor: pointer;
  background: transparent; color: #cfe0e7; position: relative;
  transition: background var(--t-quick) ease, color var(--t-quick) ease, transform var(--t-quick) var(--ease-out);
}
.mm-item b { font-family: var(--round); font-weight: 700; font-size: 22px; letter-spacing: .14em; }
.mm-item i { font-style: normal; font-family: var(--round); font-weight: 600; font-size: 11px;
             letter-spacing: .22em; color: var(--ink-3); }
.mm-item::before {
  content: ''; position: absolute; left: 6px; top: 14px; bottom: 14px; width: 4px; border-radius: 2px;
  background: var(--rim); opacity: 0; transform: scaleY(.3);
  transition: opacity var(--t-quick) ease, transform var(--t-quick) var(--ease-out);
  box-shadow: 0 0 12px rgba(111,215,232,.8);
}
.mm-item.sel { background: rgba(111,215,232,.1); color: #ffffff; transform: translateX(6px); }
.mm-item.sel::before { opacity: 1; transform: none; }
.mm-item.sel i { color: #9fe3ef; }
.mm-item.locked b { color: var(--ink-off); }
.main-hint { margin: 18px 0 0 22px; font-family: var(--round); font-weight: 600; font-size: 11px;
             letter-spacing: .26em; color: var(--ink-3); }
@media (pointer: coarse) { .main-hint { display: none; } }

/* back to the main menu, from the device carousel */
.corner-back {
  position: absolute; top: 18px; left: 20px; z-index: 5;
  font-family: var(--round); font-weight: 700; font-size: 12px; letter-spacing: .2em;
  color: var(--ink-2); background: rgba(6,10,13,.55); border: 1px solid #2b3d46;
  border-radius: 10px; padding: 10px 16px; cursor: pointer;
}
.corner-back:hover { color: #fff; border-color: var(--rim); }

/* the desktop window: no title bar, a drag strip along the top, and the
   corner controls kept clear of the window's own buttons */
#drag-strip { display: none; }
body.desktop #drag-strip { display: block; position: fixed; top: 0; left: 0; right: 0; height: 30px;
  z-index: 2147483000; -webkit-app-region: drag; }
body.desktop button, body.desktop input, body.desktop select, body.desktop a,
body.desktop label, body.desktop canvas.m3d, body.desktop .bay, body.desktop .card,
body.desktop .mm-item { -webkit-app-region: no-drag; }
body.desktop-mac .corner-back { left: 92px; }
/* the case's own buttons share .flip-btn, so each is moved down by name */
body.desktop-win .flip-btn { top: 46px; }
body.desktop-win .wide-btn { top: 106px; }

.mm-item:focus-visible { outline: 2px solid var(--rim); outline-offset: 2px; }

/* ======================= the briefing ===================================== */
/* Between a campaign device's scene and the device: which one, what it holds,
   the best so far. Tinted by the device's own accent, as its carousel card is. */
.brief-sheet {
  width: min(560px, 92vw); padding: 30px 34px 24px; text-align: center;
  background: linear-gradient(180deg, rgba(10,18,23,.92), rgba(6,11,14,.92));
  border: 1px solid #2c4048; border-top: 3px solid var(--accent, var(--rim));
  border-radius: 16px; box-shadow: 0 30px 80px rgba(0,0,0,.55);
  zoom: var(--ui-text, 1);
}
.brief-head { display: flex; justify-content: space-between; font-family: var(--round);
  font-weight: 700; font-size: 11px; letter-spacing: .3em; color: var(--ink-2); }
.brief-head #brief-idx { color: var(--accent, var(--rim)); }
.brief-emblem { width: 96px; height: 96px; margin: 18px auto 6px; color: var(--accent, var(--rim));
  filter: drop-shadow(0 0 14px color-mix(in srgb, var(--accent, #6fd7e8) 55%, transparent)); }
.brief-emblem svg { width: 100%; height: 100%; }
.brief-name { margin: 0; font-family: var(--seg); font-weight: 400;
  font-size: clamp(30px, 5.4vw, 48px); letter-spacing: .12em; text-indent: .12em; color: #f2fbfd;
  text-shadow: 0 0 6px rgba(255,255,255,.5), 0 0 28px color-mix(in srgb, var(--accent, #6fd7e8) 60%, transparent); }
.brief-line { margin: 12px auto 20px; max-width: 40ch; font-family: var(--prose); font-size: 15px;
  line-height: 1.45; color: #cfe0e7; }
.brief-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 0 0 22px; }
.brief-facts div { display: flex; justify-content: space-between; align-items: center;
  padding: 10px 14px; background: #17242b; border: 1px solid #2c4048; border-radius: 9px; }
.brief-facts span { font-family: var(--prose); font-size: var(--t-label); font-weight: 700;
  letter-spacing: .1em; color: var(--ink-2); }
.brief-facts b { font-family: var(--mono); font-weight: 400; font-size: 17px; color: #eaf6fa; }
.brief-hint { margin: 14px 0 0; font-family: var(--round); font-weight: 600; font-size: 10px;
  letter-spacing: .3em; color: var(--ink-3); }
@media (max-width: 520px) { .brief-facts { grid-template-columns: 1fr; } }

/* ======================= the rank ======================================== */
/* The letter is stamped on once the figures have counted up. */
.rank-stamp { display: grid; justify-items: center; gap: 6px; margin: -6px 0 18px;
  opacity: 0; }
.rank-stamp[hidden] { display: none; }
.rank-stamp b {
  display: grid; place-items: center; width: 92px; height: 92px;
  /* not the segment face: its S is a 5 */
  font-family: var(--round); font-weight: 700; font-size: 60px; line-height: 1;
  border: 4px solid currentColor; border-radius: 14px; transform: rotate(-8deg);
  text-shadow: 0 0 18px currentColor; box-shadow: 0 0 26px -6px currentColor, inset 0 0 18px -8px currentColor;
}
.rank-stamp .rank-s { color: #ffd27a; }
.rank-stamp .rank-a { color: #8fe6f2; }
.rank-stamp .rank-b { color: #a6e3b4; }
.rank-stamp .rank-c { color: #b9c4c9; }
.rank-stamp span { font-family: var(--round); font-weight: 700; font-size: 11px;
  letter-spacing: .28em; color: var(--ink-2); }
.rank-stamp em { font-style: normal; font-family: var(--round); font-weight: 700; font-size: 11px;
  letter-spacing: .3em; color: #ffd27a; text-shadow: 0 0 12px rgba(255,190,80,.6); }
.rank-stamp.on { opacity: 1; }
.rank-stamp.on b { animation: rankStamp .42s cubic-bezier(.2,1.5,.4,1) both; }
.rank-stamp.on span, .rank-stamp.on em { animation: riseIn .4s ease-out .25s both; }
@keyframes rankStamp {
  0%   { transform: scale(2.6) rotate(-20deg); opacity: 0; }
  60%  { opacity: 1; }
  100% { transform: scale(1) rotate(-8deg); opacity: 1; }
}
.rm .rank-stamp.on b { animation: none; }
