*{box-sizing:border-box;margin:0;padding:0}
:root{
  --bg:#1a1a2e;--surface:#16213e;--surface2:#0f3460;
  --accent:#e94560;--accent2:#f5a623;--text:#eaeaea;
  --muted:#8888aa;--ok:#27ae60;--err:#e74c3c;
  --cell:46px;--gap:4px;--cell-font:26px;--letter-shift:-5px;
  /* Modern monospace stack — crisp teleprinter look on every device. Each OS
     resolves to its own good mono (SF Mono, Roboto Mono, Consolas…) instead of
     the clunky Courier fallback. Used for tickers, keys, stamps. */
  --mono:ui-monospace,'SF Mono','Roboto Mono','DejaVu Sans Mono',Menlo,Consolas,monospace;
}
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;
  min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:12px;
  padding-bottom:calc(var(--kb-height, 160px) + 16px);}
/* Tiled title — full-size REAL playing tiles (.cell.inp steel / .cell.sol gold),
   so the 8 QODEWORD tiles line up exactly with the grid below: the title reads as
   the top row of the same board. Starts scrambled+silver, locks letters into
   code(QODEWORD) + green as words solve, all gold on level complete. */
h1#titleTiles{display:flex;gap:var(--gap);justify-content:center;margin-bottom:10px;line-height:1;}
h1#titleTiles .tl{
  transition:background .45s ease,color .45s ease,box-shadow .45s ease,transform .35s ease;}
/* The title now uses the same BULB lampboard as the splash + grid (.bulb-g/.bulb-r
   light the inset circle; .sol = gold). The steel tile stays neutral. */
.sub{color:var(--muted);font-size:.8rem;margin-bottom:12px;}
/* Control keys above the viewport — one row of small typewriter keys in code
   form (H[n] C[n] CL NX NG). Same key language as the main keyboard: black disc,
   gold ring, mono. Press strikes down + gold glow. */
.ctrlkeys{display:flex;gap:8px;flex-wrap:nowrap;justify-content:center;align-items:center;
  margin-bottom:12px;width:100%;max-width:460px;}
.ck-key{flex:0 0 auto;width:46px;height:46px;padding:0;     /* equal w/h → circle */
  display:inline-flex;align-items:center;justify-content:center;
  -webkit-tap-highlight-color:transparent;-webkit-appearance:none;appearance:none;outline:none;
  -webkit-user-select:none;user-select:none;
  background:radial-gradient(circle at 50% 28%, #4a4a4a 0%, #1c1c1c 48%, #050505 100%);
  border:2px solid #c9a23a;border-radius:50%;
  color:#f5f5f5;font-family:var(--mono);font-weight:700;font-size:.9rem;
  letter-spacing:.02em;text-shadow:0 1px 2px rgba(0,0,0,.9);
  box-shadow:0 0 0 1px #8a6d22, 0 3px 0 #2a2218, 0 5px 6px rgba(0,0,0,.55),
    inset 0 2px 3px rgba(255,255,255,.16);
  cursor:pointer;transition:transform .06s ease, box-shadow .06s ease, border-color .1s;}
.ck-key:active{
  transform:translateY(3px);border-color:#ffd96a;color:#fff;
  box-shadow:0 0 0 1px #c9a23a, 0 0 0 #2a2218, 0 2px 3px rgba(0,0,0,.5),
    inset 0 2px 3px rgba(255,255,255,.15), 0 0 12px rgba(245,200,90,.55);}
.ck-key:focus,.ck-key:focus-visible{outline:none;}
.ck-key.spent{opacity:.45;}                 /* hints exhausted */
.ck-key .kn{color:#ffd96a;margin-left:1px;}  /* the [n] count, brass */
@media(max-width:430px){
  .ctrlkeys{gap:8px;}
  .ck-key{width:42px;height:42px;font-size:.84rem;}
}
@media(max-width:360px){
  .ctrlkeys{gap:6px;}
  .ck-key{width:38px;height:38px;font-size:.78rem;}
}
/* Intercepted-signal box — matches the typewriter keys: black panel with a brass
   bezel (same border/edge/sheen recipe as .kk / .ck-key). */
.clue-box{
  background:radial-gradient(ellipse at 50% 0%, #2a2a2a 0%, #141414 55%, #080808 100%);
  border:2px solid #c9a23a;border-radius:10px;
  padding:8px 14px;width:100%;max-width:460px;margin-bottom:12px;overflow:hidden;
  box-shadow:
    0 0 0 1px #8a6d22,                       /* darker brass edge under the bezel */
    0 4px 8px rgba(0,0,0,.55),
    inset 0 2px 4px rgba(255,255,255,.08),   /* top sheen */
    inset 0 0 18px rgba(127,209,127,.06);}   /* faint green screen glow */
.clue-box .cl{font-size:.55rem;color:#c9a23a;text-transform:uppercase;letter-spacing:.18em;
  margin-bottom:4px;}
/* Score strip — thin black/brass readout under the board (same bezel recipe). */
.score-strip{
  display:flex;flex-wrap:nowrap;align-items:center;justify-content:center;gap:10px;
  width:100%;max-width:min(92vw,460px);margin:0 auto 12px;padding:5px 10px;
  background:radial-gradient(ellipse at 50% 0%, #2a2a2a 0%, #141414 55%, #080808 100%);
  border:2px solid #c9a23a;border-radius:8px;
  box-shadow:0 0 0 1px #8a6d22, 0 3px 6px rgba(0,0,0,.5),
    inset 0 1px 3px rgba(255,255,255,.07);
  font-family:var(--mono);white-space:nowrap;}
.ss-cell{display:flex;flex-direction:column;align-items:center;line-height:1.1;flex:1;min-width:0;}
.ss-lbl{font-size:.46rem;letter-spacing:.1em;color:#c9a23a;text-transform:uppercase;white-space:nowrap;}
.ss-val{font-size:.9rem;font-weight:700;white-space:nowrap;}
/* The round "fix" reads like a green map reference (phosphor, like the clue ticker). */
.ss-val.ss-fix{color:#7fd17f;letter-spacing:.06em;text-shadow:0 0 6px rgba(127,209,127,.45),0 1px 1px #000;}
/* The lifetime total is the brass/gold tally. */
.ss-val.ss-tot{color:#f0d98a;text-shadow:0 0 5px rgba(240,217,138,.45),0 1px 1px #000;}
.ss-sep{width:1px;align-self:stretch;background:linear-gradient(#8a6d22,transparent);opacity:.6;}
/* Single-clue ticker: the active clue slides in from the right (decrypting off the
   wire), unscrambles, and settles. On solve, the next clue slides in over it. Fixed
   at exactly 2 lines tall — JS word-wraps the text so words never split mid-line,
   and clips overflow so the box can never grow past the viewport.
   Styled like the splash teleprinter — green Courier with a phosphor glow.
   The slide-in is a CSS keyframe (added per clue via JS) so it fires reliably —
   class-toggle transitions can be coalesced into one frame and never animate. */
.ticker2{font-family:var(--mono);color:#7fd17f;letter-spacing:.03em;
  text-shadow:0 0 6px rgba(127,209,127,.35);font-size:.76rem;font-weight:600;
  line-height:1.25;
  /* Exactly 3 lines tall (1.25 × 3 = 3.75em) with tight line-spacing. Font sized
     so even the longest clue (~112 chars) fits 3 lines on a narrow phone, so the
     box height is consistent and nothing ever truncates. */
  height:calc(3.75em + .15em);min-height:calc(3.75em + .15em);
  overflow:hidden;}
/* Inner span carries the slide transform on its own compositor layer, so the
   per-frame decode-flicker repaints don't thrash the slide (no jump/jank). */
#ticker2Text{display:block;white-space:pre-wrap;will-change:transform;}
.ticker2 .tlabel{color:#5a8f5a;letter-spacing:.06em;margin-right:.4em;}
.ticker2.anim #ticker2Text{animation:clueSlideIn 2.2s cubic-bezier(.33,0,.2,1);}
/* Scroll in from the right EDGE of the box, decelerate, and stop at the start.
   100% = the element's own width, so it begins fully off to the right. Slow enough
   to read the line travelling across — a gentle ease-out, not a blink. */
@keyframes clueSlideIn{
  from{opacity:.2;transform:translateX(100%);}
  20%{opacity:1;}
  to{opacity:1;transform:translateX(0);}
}
.code-hint{display:none;margin-top:8px;padding-top:8px;border-top:1px dashed var(--surface2);
  font-size:.85rem;font-style:italic;color:#c39bd3;}
.code-hint.show{display:block;}
/* Map-style viewport: a fixed-size window onto the board. Small grids (e.g.
   8×8 four-letter) fit entirely and look unchanged; larger grids (10×10+)
   overflow and can be panned/scrolled, so cells keep a comfortable tap size
   instead of shrinking to fit. See 5-LETTER-SCOPE.md §3 / THEME.md. */
#gridvp{
  max-width:min(92vw, 460px);
  /* max-height is set precisely in JS (updateViewport) from the board's actual
     on-screen position to the top of the fixed keyboard. A loose CSS ceiling
     stays as a fallback before JS runs. */
  max-height:460px;
  overflow:auto;
  margin:0 auto 12px;
  /* Brass bezel — same recipe as .clue-box / the typewriter keys, so the board
     window reads as part of the same machine. */
  border:2px solid #c9a23a;border-radius:10px;
  box-shadow:
    0 0 0 1px #8a6d22,                       /* darker brass edge under the bezel */
    0 4px 8px rgba(0,0,0,.55),
    inset 0 2px 4px rgba(255,255,255,.08);   /* top sheen */
  -webkit-overflow-scrolling:touch;       /* momentum scroll on iOS */
  touch-action:pan-x pan-y;               /* allow two-axis panning */
  overscroll-behavior:contain;
  cursor:grab;
  scrollbar-width:none;                    /* Firefox */
  -ms-overflow-style:none;                 /* old Edge */
}
#gridvp::-webkit-scrollbar{width:0;height:0;display:none;}  /* WebKit/Blink */
#gridvp.panning{cursor:grabbing;}
#gridvp.fits{overflow:hidden;cursor:default;}  /* board fits — no pan needed */
/* The board sits ON the map: #grid is backed by the picked map (cover, sized to
   the whole grid), blank cells are transparent so the map shows through, and only
   the letter tiles (.inp/.sol) are opaque — like markers placed on a chart.
   Map class is set on <body> (body.mapN) in JS; covers all four variants. */
#grid{display:flex;flex-direction:column;gap:var(--gap);align-items:center;
  width:max-content;margin:0 auto;padding:6px;}
/* The map is a FIXED chart on the viewport WINDOW (not the grid), so it always
   shows the whole map at a consistent size; the board pans OVER it like markers on
   a chart. The default scroll-container behaviour pins the background to the
   window box (it does NOT scroll with the panned grid). Map class on <body> in JS. */
/* Anchored bottom-right (like the splash) so the TOP SECRET stamp + © coin baked into
   that corner stay visible behind the board instead of being cropped on narrow/mobile
   viewports. Cache-buster matches the splash (?v=7) so play loads the new stamped maps. */
#gridvp{background:#14100a right bottom / cover no-repeat;}
body.map1 #gridvp{background-image:url('../assets/QodeMap.jpg?v=9');}
body.map2 #gridvp{background-image:url('../assets/QodeMapBlue.jpg?v=9');}
body.map3 #gridvp{background-image:url('../assets/QodeMapSat.jpg?v=9');}
body.map4 #gridvp{background-image:url('../assets/QodeMapNeg.jpg?v=9');}
.grow{display:flex;gap:var(--gap);flex:0 0 auto;flex-wrap:nowrap;}
.cell{flex:0 0 auto;width:var(--cell);height:var(--cell);border-radius:7px;
  display:flex;align-items:center;justify-content:center;
  font-family:'Arial Black',Arial,sans-serif;
  font-size:var(--cell-font);font-weight:900;text-transform:uppercase;line-height:1;
  padding-top:var(--letter-shift);
  position:relative;user-select:none;transition:background .15s,transform .1s,box-shadow .15s;}
.cell.blk{background:transparent;border-radius:3px;}   /* blank → see the map through */
/* The typed letter sits above the bulb (::before, z-index:1). */
.clt{position:relative;z-index:2;line-height:1;}
.cell.inp,.cell.inp.hi,.cell.inp.cur{
  color:#f0f0f0;
  -webkit-text-stroke:2px rgba(0,0,0,0.9);
  paint-order:stroke fill;
  text-shadow:0 1px 2px rgba(0,0,0,.8);
}
.cell.sol{
  color:#fff8dc;
  -webkit-text-stroke:2px rgba(60,30,0,0.85);
  paint-order:stroke fill;
  text-shadow:0 1px 2px rgba(40,20,0,.8);
}

/* Brushed BLACK metal tile (matches the keyboard's black keys). The steel inset
   lens lives in ::before; the letter + brass highlight sit above it. */
.cell.inp{
  background:
    repeating-linear-gradient(90deg,
      transparent 0px, transparent 2px,
      rgba(255,255,255,.025) 2px, rgba(255,255,255,.025) 3px
    ),
    linear-gradient(170deg,#3a3a3a 0%,#202020 28%,#444 48%,#161616 70%,#2a2a2a 100%);
  border:none;
  /* Plain black tile — NO gold bezel by default IN THE GRID. The bezel marks the
     currently-selected word's cells (.hi/.cur). Display tiles (title + splash)
     always wear the bezel — see the rule below. */
  box-shadow:
    0 4px 10px rgba(0,0,0,.7),
    0 1px 3px rgba(0,0,0,.5),
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 -1px 0 rgba(0,0,0,.55);
  cursor:pointer;
}
/* DISPLAY tiles (game title row + splash QODEWORD/START) always show the gold
   bezel — they aren't selectable, so they wear it permanently. Excluded once
   solved (.sol/.gold) so the solved tile shows the canonical brushed-gold look. */
#titleTiles .tl.cell.inp:not(.sol), #splash .cell.inp:not(.gold):not(.sol){
  box-shadow:
    inset 0 0 0 2px #e8c040,
    inset 0 0 0 3px #a07810,
    0 4px 10px rgba(0,0,0,.7),
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 -1px 0 rgba(0,0,0,.55);
}
/* Inset STAINLESS-STEEL disc, RECESSED into the brushed-black tile — full contrast.
   Recess read comes from the shadow direction: the tile rim casts a dark shadow
   DOWN over the disc's top-inner edge, with a light catch at the bottom-inner edge
   (light from above into a hole). Flat steel sheen, not a dome. Lights green/red/
   gold when the lampboard responds (see .bulb-g/.bulb-r). */
.cell.inp::before,.cell.sol::before{
  content:'';
  position:absolute;
  inset:16%;
  border-radius:50%;
  /* Clean grey metal disc — smooth top-down sheen (dark top / lighter bottom reads
     as recessed), no cross-grain texture. */
  background:linear-gradient(180deg,#5e5e5e 0%,#7a7a7a 45%,#8e8e8e 75%,#9c9c9c 100%);
  box-shadow:
    inset 0 3px 5px rgba(0,0,0,.7),            /* tile rim shadow cast INTO the hole (top) */
    inset 0 -2px 3px rgba(255,255,255,.45),    /* light catch on the lower inner wall */
    0 1px 0 rgba(255,255,255,.12);             /* tiny lip highlight below the rim */
  pointer-events:none;
  z-index:1;
  transition:background .2s ease, box-shadow .2s ease;
}
/* Lampboard: the inset circle illuminates like a bulb. Green = right letter,
   right cell. Red = right letter, wrong cell. (Letters not in the answer leave
   the bulb dark — no class.) The steel square itself stays neutral. */
/* Real-bulb look: DEEP saturated glass (darker lens), a bright pinpoint core, and
   a soft glow that spills just past the rim — like light leaking off the lamp —
   without flooding the whole steel square. */
.cell.inp.bulb-g::before{
  background:radial-gradient(circle at 50% 40%, #bdffd6 0%, #2fd874 28%, #119c4a 58%, #07642f 100%);
  box-shadow:
    inset 0 1px 3px rgba(255,255,255,.55),
    inset 0 -2px 4px rgba(0,70,28,.6),
    inset 0 0 7px rgba(150,255,190,.75),     /* bright pinpoint core */
    0 0 7px 2px rgba(40,230,120,.85),        /* rim */
    0 0 15px 5px rgba(40,220,115,.4);        /* soft spill past the circle */
}
.cell.inp.bulb-r::before{
  /* Red is pushed harder than green: the eye sees green as brighter at equal
     intensity, so red needs a stronger core + wider spill to look as luminous. */
  background:radial-gradient(circle at 50% 40%, #ffd2da 0%, #fb4259 28%, #cc1a34 58%, #8a0e20 100%);
  box-shadow:
    inset 0 1px 3px rgba(255,255,255,.55),
    inset 0 -2px 4px rgba(80,0,8,.6),
    inset 0 0 8px rgba(255,170,185,.85),     /* brighter pinpoint core */
    0 0 8px 2px rgba(255,70,100,.95),        /* stronger rim */
    0 0 19px 7px rgba(250,60,85,.5);         /* wider, stronger spill */
}
/* Selection cues are now NEUTRAL (steel/brass), so red & green are reserved for
   the lampboard meaning. .hi = belongs to selected word; .cur = cursor cell. */
/* SELECTED word's cells get the brass bezel (inset ring, same as the keys) —
   only the active clue is framed in brass; everything else is plain black. */
.cell.inp.hi{
  background:
    repeating-linear-gradient(90deg,
      transparent 0px, transparent 2px,
      rgba(255,255,255,.025) 2px, rgba(255,255,255,.025) 3px
    ),
    linear-gradient(170deg,#444 0%,#262626 28%,#4e4e4e 48%,#1c1c1c 70%,#323232 100%);
  box-shadow:
    inset 0 0 0 2px #e8c040,                   /* GOLD bezel ring (matches solved) */
    inset 0 0 0 3px #a07810,                   /* darker gold edge under it */
    0 4px 10px rgba(0,0,0,.7),
    inset 0 1px 0 rgba(255,255,255,.18),
    inset 0 -1px 0 rgba(0,0,0,.55);
}
.cell.inp.cur{
  background:
    repeating-linear-gradient(90deg,
      transparent 0px, transparent 2px,
      rgba(255,255,255,.03) 2px, rgba(255,255,255,.03) 3px
    ),
    linear-gradient(170deg,#4e4e4e 0%,#2c2c2c 28%,#585858 48%,#222 70%,#3a3a3a 100%);
  transform:scale(1.07);
  box-shadow:
    inset 0 0 0 2px #ffe07a,                   /* brighter gold bezel for the cursor cell */
    inset 0 0 0 3px #d4a830,
    0 6px 14px rgba(0,0,0,.75),
    inset 0 1px 0 rgba(255,255,255,.22),
    inset 0 -1px 0 rgba(0,0,0,.6),
    0 0 8px 2px rgba(232,192,64,.55);
}

/* gold brushed steel — the completed/solved colour. The selection bezel matches
   this gold (see .hi/.cur). */
.cell.sol{
  background:
    repeating-linear-gradient(90deg,
      transparent 0px, transparent 2px,
      rgba(255,220,50,.04) 2px, rgba(255,220,50,.04) 3px
    ),
    linear-gradient(170deg,#d4a830 0%,#a07810 25%,#e8c040 45%,#886008 65%,#c09020 100%);
  border:none;
  box-shadow:
    0 4px 10px rgba(0,0,0,.65),
    0 1px 3px rgba(0,0,0,.4),
    inset 0 1px 0 rgba(255,240,100,.35),
    inset 0 -1px 0 rgba(60,30,0,.5);
}
/* On completion the lens turns BLACK (like a normal black tile / typewriter key
   disc) set in the gold square — gold ring + black disc mirrors the keys. */
.cell.sol::before{
  background:radial-gradient(circle at 50% 32%, #2a2a2a 0%, #111 50%, #000 100%);
  box-shadow:
    inset 0 2px 4px rgba(0,0,0,.8),
    inset 0 -1px 2px rgba(255,220,120,.25),
    0 0 0 1px rgba(60,40,0,.6),
    0 1px 1px rgba(255,220,120,.2);
}
.cell.bad{background:var(--err);color:#fff;}
@keyframes pop{0%{transform:scale(1)}50%{transform:scale(1.18)}100%{transform:scale(1)}}
@keyframes shk{0%,100%{transform:translateX(0)}30%{transform:translateX(-4px)}70%{transform:translateX(4px)}}
.cell.sol{animation:pop .3s ease;}
.cell.bad{animation:shk .3s ease;}
.cnum{position:absolute;top:5px;left:6px;font-size:.46rem;color:#e8c040;font-weight:700;z-index:3;
  -webkit-text-stroke:0;text-shadow:0 1px 1px rgba(0,0,0,.9);}  /* gold; inset clear of the bezel */
.btn{padding:9px 18px;border:none;border-radius:9px;font-size:.85rem;font-weight:700;
  cursor:pointer;text-transform:uppercase;letter-spacing:.05em;transition:transform .1s;}
.btn:active{transform:scale(.95);}
.btn-p{background:var(--accent);color:#fff;}
.btn-s{background:var(--surface2);color:var(--text);}
.btn-h{background:#3498db;color:#fff;}
.btn-c{background:linear-gradient(135deg,#9b59b6,#7d3c98);color:#fff;}
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none;}
.btn:disabled:active{transform:none;}
/* ── Vintage typewriter keyboard ──────────────────────────────────────────────
   Round ivory keytops in nickel rings on a dark machine bed — the Enigma is, at
   heart, a typewriter. Keys sit raised; pressing one stamps it down and lights it
   gold (a struck typebar). See THEME.md → typography/machine chrome. */
#kb{display:flex;flex-direction:column;gap:7px;align-items:center;
  position:fixed;bottom:0;left:0;right:0;padding:12px 10px calc(env(safe-area-inset-bottom, 10px) + 10px);
  z-index:10;
  /* Crackle-black machine bed with a brushed sheen and a brass top rail. */
  background:
    linear-gradient(180deg,#3a3026 0,#2a2118 2px,transparent 3px),
    repeating-linear-gradient(90deg, rgba(255,255,255,.012) 0 2px, transparent 2px 4px),
    linear-gradient(180deg,#22201d 0%,#161412 100%);
  border-top:2px solid #5a4a2e;
  box-shadow:inset 0 2px 6px rgba(0,0,0,.6), 0 -3px 10px rgba(0,0,0,.5);
  transition:padding .2s ease;}
/* Minimise tab — a small brass chevron on the keyboard's brass rail. Desktop only
   (hidden on touch, where the on-screen keyboard is the primary input). */
#kbToggle{display:none;position:absolute;top:-13px;right:14px;width:38px;height:18px;
  align-items:center;justify-content:center;
  background:linear-gradient(180deg,#3a3026,#22201d);border:1px solid #5a4a2e;
  border-bottom:none;border-radius:6px 6px 0 0;
  color:#e8c040;font-size:.7rem;line-height:1;cursor:pointer;
  -webkit-appearance:none;appearance:none;outline:none;
  box-shadow:0 -2px 5px rgba(0,0,0,.4);}
@media(hover:hover) and (pointer:fine){#kbToggle{display:flex;}}  /* desktop/mouse only */
/* Collapsed: hide the key rows, keep just the brass rail + tab to restore. */
#kb.kbmin{padding:6px 10px;}
#kb.kbmin .kbr{display:none;}
.kbr{display:flex;gap:7px;justify-content:center;}
/* Each row is slightly inset like the staggered banks of a typewriter. */
.kbr:nth-child(2){padding-left:14px;}
.kbr:nth-child(3){padding-left:28px;}
.kk{flex:0 0 auto;width:40px;height:40px;          /* equal w/h → perfect circle */
  display:flex;align-items:center;justify-content:center;
  -webkit-tap-highlight-color:transparent;          /* kill mobile green box */
  -webkit-appearance:none;appearance:none;outline:none;
  -webkit-touch-callout:none;user-select:none;-webkit-user-select:none;
  /* Classic typewriter key: glossy BLACK disc with a WHITE letter, ringed by a
     GOLD/brass collar. A subtle top highlight gives the glass keytop sheen. */
  background:radial-gradient(circle at 50% 32%, #4a4a4a 0%, #1c1c1c 45%, #050505 100%);
  border:2px solid #c9a23a;border-radius:50%;
  color:#f5f5f5;font-family:var(--mono);font-weight:700;font-size:1rem;
  text-shadow:0 1px 2px rgba(0,0,0,.9);
  box-shadow:
    0 0 0 1px #8a6d22,          /* darker brass edge under the ring */
    0 3px 0 #2a2218,            /* the key's metal stem (struck height) */
    0 5px 6px rgba(0,0,0,.6),
    inset 0 2px 3px rgba(255,255,255,.18);   /* glass sheen */
  cursor:pointer;transition:transform .06s ease, box-shadow .06s ease, border-color .1s;}
.kk:active{
  transform:translateY(3px);                    /* the key strikes down */
  border-color:#ffd96a;
  color:#fff;
  box-shadow:
    0 0 0 1px #c9a23a,
    0 0 0 #2a2218,
    0 2px 3px rgba(0,0,0,.5),
    inset 0 2px 3px rgba(255,255,255,.15),
    0 0 12px rgba(245,200,90,.55);            /* gold strike glow */
}
.kk:focus,.kk:focus-visible{outline:none;}     /* no focus box after tap */
/* JS-driven strike (added on .struck by strikeKey) — plays the full DOWN-then-UP
   travel so a PHYSICAL keypress animates the matching on-screen key like a real
   mechanical keystroke. CSS :active only holds while pressed; this completes the
   spring-back, and works when there's no pointer press at all. */
.kk.struck{animation:keyStrike .17s ease-out;}
@keyframes keyStrike{
  0%{transform:translateY(0);}
  35%{transform:translateY(3px);                 /* strike down */
    border-color:#ffd96a;color:#fff;
    box-shadow:0 0 0 1px #c9a23a,0 0 0 #2a2218,0 2px 3px rgba(0,0,0,.5),
      inset 0 2px 3px rgba(255,255,255,.15),0 0 12px rgba(245,200,90,.6);}
  100%{transform:translateY(0);}                 /* spring back up */
}
.kk.wide{width:52px;border-radius:21px;font-size:.72rem;}  /* Backspace / Enter bars */
/* Shrink keys + gaps on narrow phones so a 10-key row always fits the width.
   Keys stay SQUARE (width === height) so they render as true circles. */
@media(max-width:430px){
  #kb{gap:6px;padding:10px 4px calc(env(safe-area-inset-bottom, 8px) + 8px);}
  .kbr{gap:4px;}
  .kbr:nth-child(2){padding-left:8px;}
  /* Row 3 (Backspace … Enter) has wide keys at both ends — no extra indent, it
     already fills the width; keep it flush so Enter never spills off the edge. */
  .kbr:nth-child(3){padding-left:0;}
  .kk{width:32px;height:32px;font-size:.86rem;}
  .kk.wide{width:42px;font-size:.62rem;}
}
@media(max-width:360px){
  .kbr{gap:3px;}
  .kbr:nth-child(2){padding-left:6px;}
  .kk{width:29px;height:29px;font-size:.8rem;}
  .kk.wide{width:38px;}
}
@media(pointer:coarse),(max-width:768px){:root{--cell:40px;--cell-font:22px;--letter-shift:1px}}
@media(max-width:420px){:root{--cell:36px;--gap:3px;--cell-font:20px;--letter-shift:1px}}
#ov{display:none;position:fixed;inset:0;background:rgba(4,6,7,.92);z-index:99;
  align-items:center;justify-content:center;padding:16px;}
#ov.show{display:flex;}

/* ── BIOS / decoder boot screen (tier intro) ────────────────────────────────
   A green-phosphor terminal inside the game's signature black panel + brass
   bezel (same recipe as .clue-box / the typewriter keys). Lines self-test
   (GO / DOWN) on a delay, then the player presses to begin. Blocks play. */
/* BIOS uses the same tall brass bezel (.report-frame/.report-window) as the report;
   only the inner content (lines + begin key) is BIOS-specific. */
#bios{display:none;position:fixed;inset:0;z-index:150;
  background:rgba(4,6,7,.92);align-items:center;justify-content:center;padding:16px;}
#bios.show{display:flex;}
.bios-lines{font-size:4.4cqw;line-height:1.7;letter-spacing:.06em;white-space:pre;
  width:100%;text-align:center;flex:0 1 auto;min-height:0;overflow:hidden;
  text-shadow:0 0 5px rgba(127,209,127,.4);color:#7fd17f;}
/* Readout block: centred as a unit (inline-block) but lines stay left-aligned so the
   dotted GO/DOWN columns line up. Small gap below before the QODE key. */
.bios-readout{display:inline-block;text-align:left;margin:0 auto 1.4em;}
.bios-readout .go{display:block;color:#7fd17f;}     /* phosphor green — system up */
.bios-readout .down{display:block;color:#ff5e72;font-weight:800;text-shadow:0 0 8px rgba(251,66,89,.8);}
/* Header + divider are block-level + centred so they sit mid-window regardless of
   length; the GO/DOWN readout lines stay inline (preserving their dotted columns). */
.bios-lines .hdr{display:block;text-align:center;color:#c9a23a;text-shadow:none;letter-spacing:.1em;}
.bios-lines .rule{display:block;text-align:center;color:#5a6b5a;text-shadow:none;}
/* Begin button — black panel with brass trim, matching every other frame/key.
   No pulsing. */
/* BIOS window centres its content vertically; the button never shrinks/clips. */
#bios .report-window{justify-content:center;gap:2cqw;}
.bios-begin{display:none;flex:0 0 auto;font-family:var(--mono);font-weight:700;font-size:3.4cqw;
  letter-spacing:.12em;color:#f0d98a;width:100%;
  background:radial-gradient(ellipse at 50% 0%, #2a2a2a 0%, #141414 60%, #080808 100%);
  border:2px solid #c9a23a;border-radius:7px;padding:3cqw 2cqw;cursor:pointer;
  box-shadow:0 0 0 1px #8a6d22, 0 3px 6px rgba(0,0,0,.5),
    inset 0 1px 2px rgba(255,255,255,.08);}
.bios-begin.ready{display:block;}
.bios-begin:hover{filter:brightness(1.15);}
/* ── Intercept report (completed popup) ─────────────────────────────────────
   A teleprinter printout in the same black-panel/brass-bezel terminal as the
   BIOS boot screen — the round's FIX coordinate, banked points, station total. */
/* The tall portrait bezel frames the report. Aspect-locked to the PNG (1536:2752),
   sized to the largest that fits the screen height (and width). The report content
   sits in the central window (measured insets: ~26% sides, ~24% top/bottom). */
.report-frame{
  position:relative;
  aspect-ratio:1536/2752;
  /* Fill the screen height (bigger on desktop), but never wider than the screen. */
  height:min(96vh, 900px);
  max-width:96vw;
  background:url('../assets/VPBezelTall.png') center/contain no-repeat;
  font-family:var(--mono);
  container-type:inline-size;                  /* fonts below scale to frame WIDTH (cqw) */
}
.report-window{
  position:absolute;left:26%;top:23.6%;right:26%;bottom:24.9%;
  display:flex;flex-direction:column;
  /* Recessed black screen behind the brass window, with a faint green glow. */
  background:radial-gradient(ellipse at 50% 0%, #1c1c1c 0%, #0d0d0d 60%, #060606 100%);
  border-radius:3px;box-shadow:inset 0 0 16px rgba(127,209,127,.06),inset 0 2px 6px rgba(0,0,0,.7);
  /* Padding scales with the frame so text never touches the brass window edge. */
  padding:3cqw 3cqw;overflow:hidden;}
.report-hdr{color:#c9a23a;letter-spacing:.06em;font-size:2.7cqw;font-weight:700;
  text-align:center;margin-bottom:2cqw;}
/* Lines wrap (no fixed monospace columns) so long values never overflow the narrow
   window; font scales with frame width and is sized so the whole report fits — no scroll. */
.report-lines{font-size:3cqw;line-height:1.35;letter-spacing:.01em;
  white-space:normal;text-align:left;margin:0 0 2cqw;color:#7fd17f;flex:1;
  text-shadow:0 0 4px rgba(127,209,127,.4);}
.r-row{display:flex;justify-content:space-between;align-items:baseline;gap:2cqw;margin:.15em 0;}
.r-row .lbl{color:#8aa890;flex:0 0 auto;}         /* dim label */
.r-row .val{color:#cfeccf;text-align:right;}      /* bright value */
.r-row .fix{color:#f5d97a;text-align:right;text-shadow:0 0 6px rgba(245,217,122,.5);}  /* brass headline coord */
.r-row .bank{color:#7fd17f;text-align:right;}
.r-row .total{color:#f5d97a;text-align:right;font-weight:700;}
.r-rule{height:1px;background:repeating-linear-gradient(90deg,#5a6b5a 0 5px,transparent 5px 9px);
  margin:.4em 0;opacity:.7;}
.r-verdict{text-align:center;margin:.4em 0 0;font-weight:700;line-height:1.25;}
.r-verdict.verdict-good{color:#7fd17f;text-shadow:0 0 7px rgba(127,209,127,.6);}
.r-verdict.verdict-mid{color:#f5a623;}
.r-verdict.verdict-poor{color:#fb4259;text-shadow:0 0 5px rgba(251,66,89,.4);}
.report-actions{display:flex;flex-direction:column;gap:1.6cqw;align-items:center;}
/* Report buttons — brass keys, same family as the typewriter keys. */
.report-key{font-family:var(--mono);font-weight:700;font-size:2.6cqw;letter-spacing:.03em;
  color:#eee;background:linear-gradient(180deg, #3a3a3a 0%, #222 100%);
  border:2px solid #8a6d22;border-radius:6px;padding:2cqw 2cqw;cursor:pointer;width:100%;
  box-shadow:0 2px 5px rgba(0,0,0,.5), inset 0 1px 2px rgba(255,255,255,.1);}
.report-key:hover{filter:brightness(1.12);}
.report-key-p{color:#1a1305;
  background:linear-gradient(180deg, #f0cf6a 0%, #d9af3e 50%, #b88a25 100%);
  border-color:#c9a23a;box-shadow:0 0 0 1px #8a6d22, 0 3px 6px rgba(0,0,0,.5),
    inset 0 1px 2px rgba(255,255,255,.4);}
/* Dev-only zone parked below the viewport (stage nav + code toggle). Visually
   set apart with a dashed rule + low opacity so it never reads as game UI. */
.dev-zone{width:100%;max-width:460px;margin:6px auto 10px;padding-top:8px;
  border-top:1px dashed var(--surface2);opacity:.8;}
.dev-bar{display:flex;justify-content:center;margin:0 0 4px;}
.btn-xs{padding:3px 10px;font-size:.6rem;opacity:.7;}
.btn-xs:hover{opacity:1;}
.level-nav{display:flex;align-items:center;justify-content:center;gap:8px;margin:4px 0 6px;}
#levelDisplay{color:var(--accent2);font-size:.85rem;font-weight:700;
  background:var(--surface2);border-radius:8px;padding:4px 12px;min-width:160px;
  text-align:center;letter-spacing:.03em;}
#levelHint{color:var(--muted);font-size:.75rem;margin:0 0 4px;text-align:center;
  font-style:italic;min-height:1em;}

/* ── Splash: wordless tutorial (scramble → green/red → gold). Shown once on
   load before the first puzzle; dismissed by tap. Mirrors the real per-word
   feedback rhythm. See THEME.md → Splash screen / PROGRESSION.md §4b. ── */
/* Splash is a full-screen cover that the PAGE can scroll (position:absolute, not
   fixed) — so on short/mobile viewports you simply scroll down to the START tiles
   rather than them being clipped. min-height fills the screen; it grows taller and
   the page scrolls when the content needs more room. */
/* Splash background = one of four real Bletchley/Enigma map images, chosen at
   RANDOM each load (JS adds .map1–.map4 in initSplash). A dark vignette overlay
   (::before) keeps the tiles/text legible; a "TOP SECRET" stamp (::after) sits
   bottom-right. See assets/QodeMap*.png. */
#splash{position:fixed;inset:0;z-index:200;overflow:hidden;
  background:#2a2118 right bottom / cover no-repeat;   /* image set by .map1–.map4 */
  /* Anchored bottom-right so the TOP SECRET stamp + © coin (baked into that corner of
     each map) are never cropped by `cover`; trimming falls on the top/left ocean. */
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:22px;padding:64px 16px 48px;
  transition:opacity .5s ease;}
#splash.map1{background-image:url('../assets/QodeMap.jpg?v=9');}      /* aged parchment + corner TOP SECRET stamp */
#splash.map2{background-image:url('../assets/QodeMapBlue.jpg?v=9');}  /* blueprint */
#splash.map3{background-image:url('../assets/QodeMapSat.jpg?v=9');}   /* satellite grey */
#splash.map4{background-image:url('../assets/QodeMapNeg.jpg?v=9');}   /* negative film */
/* Vignette + slight darken so the steel tiles and text stay legible on the map. */
#splash::before{content:'';position:absolute;inset:0;z-index:0;pointer-events:none;
  background:
    radial-gradient(ellipse 70% 55% at 50% 46%, rgba(20,14,6,.28) 0%, rgba(20,14,6,.5) 100%),
    linear-gradient(180deg, rgba(20,14,6,.35) 0%, transparent 22%, transparent 85%, rgba(20,14,6,.28) 100%);}
#splash > *{position:relative;z-index:1;}   /* content above the map */
/* The "TOP SECRET" rubber stamp is baked into the map images (see assets/QodeMap*.png),
   sitting bottom-right. The source-image watermark (Gemini sparkle/diamond) lands in the
   very corner, just right of the stamp — so we cover it with a small "© Qodeword" badge.
   This is our branding AND it hides the watermark on every map variant at once. */
/* The "TOP SECRET" stamp AND the "© QODEWORD" branding badge are baked into the map
   images (see assets/QodeMap*.png), bottom-right. The badge sits over the source-image
   watermark (the white sparkle/diamond) to mask it — baked in (not CSS) so it tracks
   the `cover`-cropped image on every screen instead of drifting off the watermark. */
#splash.hide{opacity:0;pointer-events:none;}
/* While the splash is up, lock page scroll behind the fixed cover. */
body.splash-open{overflow:hidden;}
/* Ticker-tape intercept header — types out like a teleprinter off the wire.
   Monospace, with a blinking caret. The "signal coming through" before the
   decode. (Themed font comes later — THEME.md typography.) */
/* Splash intercept line: same motion as the in-game clue ticker — the line
   scrolls in from the right, halts, and decodes (flicker locks L→R) in place. */
/* Intercept line — green phosphor (matches the in-game ticker), with a dark
   shadow + green glow so it stays legible over the busy map backgrounds. */
#ticker{position:absolute;top:max(22px,env(safe-area-inset-top,22px));
  left:0;right:0;width:100%;overflow:hidden;text-align:center;
  font-family:var(--mono);font-size:.95rem;letter-spacing:.1em;font-weight:700;
  color:#7fd17f;opacity:.95;white-space:nowrap;min-height:1.1em;
  text-shadow:0 1px 3px rgba(0,0,0,.9),0 0 8px rgba(0,0,0,.7),0 0 10px rgba(127,209,127,.5);}
#tickerText.anim{display:inline-block;animation:clueSlideIn 2.2s cubic-bezier(.33,0,.2,1);}
@media(max-width:520px){#ticker{font-size:.8rem;letter-spacing:.05em;}}
#splash .tiles{display:flex;gap:var(--gap);}
/* Nudge the QODEWORD row up slightly so it clears Bletchley Park on the map
   below. translateY (not margin) so the flex gap to START stays unchanged. */
#splashTiles{transform:translateY(-34px);}
#splash .cell{width:54px;height:54px;font-size:30px;cursor:default;
  transition:background .4s ease,color .4s ease,box-shadow .45s ease,transform .35s ease;}
@media(max-width:430px){#splash .cell{width:40px;height:40px;font-size:22px;}}
/* Splash feedback now lights the BULB (the inset ::before circle), matching the
   in-game lampboard — the steel tile stays neutral; only the lamp glows. The
   splash letter is set as plain textContent, so raise it above the bulb here. */
#splash .cell{position:relative;}
/* Splash bulbs — same real-bulb look: deep glass, pinpoint core, soft outer spill
   (a touch stronger than in-game since it's the showcase). */
#splash .cell.wrong::before{      /* wrong — red bulb (pushed harder than green) */
  background:radial-gradient(circle at 50% 38%, #ffd2da 0%, #fb4259 28%, #cc1a34 58%, #8a0e20 100%);
  box-shadow:inset 0 1px 3px rgba(255,255,255,.55),inset 0 -2px 4px rgba(80,0,8,.6),
    inset 0 0 9px rgba(255,170,185,.85),0 0 9px 2px rgba(255,70,100,1),0 0 22px 8px rgba(250,60,85,.55);}
#splash .cell.correct::before{    /* right place — green bulb */
  background:radial-gradient(circle at 50% 38%, #bdffd6 0%, #2fd874 28%, #119c4a 58%, #07642f 100%);
  box-shadow:inset 0 1px 3px rgba(255,255,255,.55),inset 0 -2px 4px rgba(0,70,28,.6),
    inset 0 0 8px rgba(150,255,190,.8),0 0 8px 2px rgba(40,230,120,.9),0 0 18px 6px rgba(40,220,115,.48);}
/* Letter stays readable over a lit bulb (it's in a .clt span, z-index above). */
#splash .cell.wrong, #splash .cell.correct{color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.85);}
/* Gold (solved) still ignites the whole tile — the "all gold" celebration. */
/* Splash "gold" must match the in-game solved tile (.cell.sol) exactly — same
   brushed-gold gradient, shadows and recessed gold ::before. (A gentle lift is the
   only extra, as the splash finish flourish.) */
#splash .cell.gold{
  background:
    repeating-linear-gradient(90deg,
      transparent 0px, transparent 2px,
      rgba(255,220,50,.04) 2px, rgba(255,220,50,.04) 3px
    ),
    linear-gradient(170deg,#d4a830 0%,#a07810 25%,#e8c040 45%,#886008 65%,#c09020 100%);
  color:#fff8dc;-webkit-text-stroke:2px rgba(60,30,0,0.85);paint-order:stroke fill;
  text-shadow:0 1px 2px rgba(40,20,0,.8);
  box-shadow:
    0 4px 10px rgba(0,0,0,.65),
    0 1px 3px rgba(0,0,0,.4),
    inset 0 1px 0 rgba(255,240,100,.35),
    inset 0 -1px 0 rgba(60,30,0,.5);
  transform:translateY(-2px) scale(1.04);}
#splash .cell.gold::before{      /* black disc in the gold square — matches in-game */
  background:radial-gradient(circle at 50% 32%, #2a2a2a 0%, #111 50%, #000 100%);
  box-shadow:
    inset 0 2px 4px rgba(0,0,0,.8),
    inset 0 -1px 2px rgba(255,220,120,.25),
    0 0 0 1px rgba(60,40,0,.6),
    0 1px 1px rgba(255,220,120,.2);}
#splash .sub{font-family:var(--mono);letter-spacing:.35em;font-size:.7rem;text-transform:uppercase;
  font-weight:700;color:#7fd17f;
  text-shadow:0 1px 3px rgba(0,0,0,.9),0 0 8px rgba(127,209,127,.4);
  opacity:0;transition:opacity .8s;}
#splash.done .sub{opacity:1;}
/* Start control: 5 silver TILES (not a text button) — anagram of START that
   flickers red/green, rearranges to START, and ignites gold on press. Same tile
   language as the QODEWORD row above. Hidden until the splash finishes (.done).
   In NORMAL FLOW (with a top margin) so it's always reachable — on short/mobile
   viewports the splash scrolls rather than clipping the START off-screen. */
#startTiles{margin-top:18px;opacity:0;pointer-events:none;cursor:pointer;
  transition:opacity .6s ease;}
#startTiles .cell{width:38px;height:38px;font-size:22px;cursor:pointer;}
#splash.done #startTiles{opacity:1;pointer-events:auto;animation:pulse 1.9s ease-in-out infinite;}
#startTiles.pressed{animation:none;}
@keyframes pulse{50%{opacity:.5;}}
/* Custom install button — black panel + brass trim, matching the game's buttons
   (same recipe as the BIOS QODE / report keys). Only shown when installable. */
.install-btn{margin-top:16px;font-family:var(--mono);font-weight:700;font-size:.8rem;
  letter-spacing:.12em;color:#f0d98a;
  background:radial-gradient(ellipse at 50% 0%, #2a2a2a 0%, #141414 60%, #080808 100%);
  border:2px solid #c9a23a;border-radius:8px;padding:10px 20px;cursor:pointer;
  box-shadow:0 0 0 1px #8a6d22, 0 3px 6px rgba(0,0,0,.5), inset 0 1px 2px rgba(255,255,255,.08);}
.install-btn:hover{filter:brightness(1.15);}
.install-btn[hidden]{display:none;}
