:root {
  --zq-navy: #05030d;
  --zq-panel: rgba(18, 11, 45, 0.68);
  --zq-purple: #6c2fe2;
  --zq-indigo: #21105a;
  --zq-gold: #f1c76a;
  --zq-blue: #31d7ff;
  --zq-white: #f8f7ff;
  --zq-muted: rgba(248, 247, 255, 0.74);
  /* Height reserved for the touch pad. 0 on desktop, so every calc() below
     resolves to exactly the pre-pad values and desktop layout is unchanged. */
  --zq-pad-h: 0px;
  /* Width of each landscape rail. 110 not 125: at 812x375 the canvas needs
     562px, so 2x125 leaves zero margin while 2x110 leaves 30px. */
  --zq-rail-w: 110px;
}

/* Wider phones in landscape (iPhone Pro Max ~956x440) have ~148px of free
   space per side once the 3:2 canvas is placed, so the rail can grow without
   ever touching the canvas. 782-899px keeps the tighter 110px rail. */
@media (min-width: 900px) {
  :root { --zq-rail-w: 132px; }
}

* {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  min-width: 320px;
  height: 100%;
  min-height: 100%;
  margin: 0;
  overflow: hidden;
  background:
    radial-gradient(circle at 18% 12%, rgba(108, 47, 226, 0.38), transparent 26rem),
    radial-gradient(circle at 82% 16%, rgba(241, 199, 106, 0.18), transparent 22rem),
    linear-gradient(135deg, var(--zq-navy), var(--zq-indigo) 58%, var(--zq-navy));
  color: var(--zq-white);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

button,
a {
  font: inherit;
}

.zq-shell {
  position: relative;
  width: 100vw;
  /* vh first as the fallback; svh overrides it where supported so the
     small-viewport height wins instead of being clamped back up by min-height. */
  min-height: 100vh;
  min-height: 100svh;
  height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

.zq-shell::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05) 1px, transparent 1px, transparent 28px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.04) 1px, transparent 1px, transparent 28px);
  opacity: 0.5;
}

.zq-topbar {
  position: fixed;
  z-index: 4200;
  top: max(0.75rem, env(safe-area-inset-top));
  left: max(0.75rem, env(safe-area-inset-left));
  right: max(0.75rem, env(safe-area-inset-right));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: auto;
  min-height: 44px;
  pointer-events: none;
  transition: opacity 180ms ease;
}

.zq-topbar a,
.zq-topbar button,
.zq-topbar strong {
  border: 1px solid rgba(241, 199, 106, 0.24);
  border-radius: 8px;
  background: rgba(5, 3, 13, 0.64);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.26);
  min-width: 0;
  color: var(--zq-white);
  font-size: 0.78rem;
  font-weight: 900;
  letter-spacing: 0;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  pointer-events: auto;
}

.zq-topbar strong {
  color: var(--zq-gold);
  padding: 0.55rem 0.7rem;
  text-align: center;
}

.zq-topbar a {
  padding: 0.65rem 0.75rem;
}

.zq-topbar button {
  justify-self: end;
  cursor: pointer;
  min-height: 32px;
  padding: 0.55rem 0.7rem;
}

.zq-topbar button:hover,
.zq-topbar a:hover {
  color: var(--zq-gold);
}

.zq-game-container,
#game-container {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100vw;
  /* Same ordering as .zq-shell above: vh fallback, then svh. */
  min-height: 100vh;
  min-height: 100svh;
  height: 100svh;
  padding: 0;
  transition: padding 180ms ease;
}

#game-container canvas {
  display: block;
  width: auto !important;
  height: auto !important;
  max-width: 100vw;
  max-height: 100svh;
  margin: 0 !important;
  transition: max-height 180ms ease;
}

.zq-shell[data-zq-mode="playing"] .zq-game-container {
  padding-top: 3.3rem;
  padding-bottom: 0.4rem;
}

.zq-shell[data-zq-mode="playing"] #game-container canvas {
  max-height: calc(100svh - 3.7rem);
}

.zq-shell[data-zq-mode="playing"] .zq-topbar {
  opacity: 0.7;
}

.zq-shell[data-zq-mode="playing"] .zq-topbar:hover,
.zq-shell[data-zq-mode="playing"] .zq-topbar:focus-within {
  opacity: 1;
}

.zq-shell[data-zq-mode="playing"] .zq-topbar strong {
  opacity: 0;
  pointer-events: none;
}

.zq-shell[data-zq-mode="playing"] .zq-help {
  display: none;
}

.zq-loader {
  position: fixed;
  z-index: 3900;
  inset: 0;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.8rem;
  padding: 2rem;
  text-align: center;
  background:
    radial-gradient(circle at 50% 20%, rgba(108, 47, 226, 0.42), transparent 28rem),
    rgba(5, 3, 13, 0.92);
  transition: opacity 220ms ease, visibility 220ms ease;
}

.zq-loader.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.zq-loader-mark {
  display: flex;
  align-items: end;
  gap: 0.65rem;
  min-height: 76px;
}

.zq-loader-mark i {
  display: block;
  width: 42px;
  height: 64px;
  background: linear-gradient(135deg, var(--zq-gold), var(--zq-purple), var(--zq-blue));
  clip-path: polygon(50% 0, 100% 32%, 82% 100%, 18% 100%, 0 32%);
  animation: zqPulse 1s ease-in-out infinite alternate;
}

.zq-loader-mark i:nth-child(2) {
  animation-delay: 0.12s;
}

.zq-loader-mark i:nth-child(3) {
  animation-delay: 0.24s;
}

.zq-kicker {
  margin: 0;
  color: var(--zq-gold);
  font-size: 0.78rem;
  font-weight: 1000;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.zq-loader h1 {
  margin: 0;
  color: var(--zq-white);
  font-size: clamp(2.6rem, 9vw, 6.5rem);
  line-height: 0.9;
}

.zq-loader p {
  max-width: 520px;
  margin: 0;
  color: var(--zq-muted);
  font-weight: 800;
}

.zq-help {
  position: fixed;
  z-index: 4100;
  right: max(0.75rem, env(safe-area-inset-right));
  bottom: max(0.75rem, env(safe-area-inset-bottom));
  display: grid;
  grid-template-columns: auto auto;
  gap: 0.18rem 0.55rem;
  border: 1px solid rgba(241, 199, 106, 0.22);
  border-radius: 8px;
  background: var(--zq-panel);
  padding: 0.55rem 0.7rem;
  color: var(--zq-muted);
  font-size: 0.72rem;
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  transition: opacity 180ms ease;
  /* Purely informational — no interactive children. Without this it sits on
     the bottom-right ~3% of the canvas and swallows the pointerdown that
     advances the story and cutscene screens. The pad-device rule below hides
     it entirely; this is the fix for desktop, where no pad is injected. */
  pointer-events: none;
}

.zq-help strong {
  color: var(--zq-white);
}

.zq-help.is-dimmed {
  opacity: 0.34;
}

.zq-help-tip {
  grid-column: 1 / -1;
  max-width: 30ch;
  margin: 0.35rem 0 0;
  border-top: 1px solid rgba(241, 199, 106, 0.18);
  padding-top: 0.35rem;
  line-height: 1.35;
}

.zq-help-tip b {
  color: var(--zq-gold);
}

.zq-letter-option {
  display: flex !important;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
}

.zq-answer-letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  border: 2px solid #22c55e;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.34);
  color: #86efac;
  box-shadow: 0 0 12px rgba(34, 197, 94, 0.45);
  line-height: 1;
}

.zq-answer-text {
  min-width: 0;
}

@keyframes zqPulse {
  from {
    transform: translateY(0);
    filter: brightness(0.9);
  }

  to {
    transform: translateY(-10px);
    filter: brightness(1.18);
  }
}

/* ---------------------------------------------------------------------------
   Touch control pad.

   ONE GATE. The bridge decides whether a pad exists; when it injects, it adds
   .zq-has-pad to <html> and sets --zq-pad-h in JS. Every rule below is scoped
   to that class. There is deliberately no media query: two gates that could
   disagree is exactly what shipped the overflow last time.
   --------------------------------------------------------------------------- */

/* --- layout: canvas gets exactly its own height, the pad gets the rest --- */

html.zq-has-pad .zq-game-container,
html.zq-has-pad #game-container {
  inset: 0 0 auto 0;
  /* BOTH, or min-height: 100svh clamps the container back to full height. */
  min-height: calc(100svh - var(--zq-pad-h));
  height: calc(100svh - var(--zq-pad-h));
  align-items: flex-start;
}

/* Beats Phaser's inline autoCenter margin so the canvas anchors to the top. */
html.zq-has-pad #game-container canvas {
  margin-top: 0 !important;
  max-height: calc(100svh - var(--zq-pad-h));
}

/* The playing-mode padding would reintroduce dead space under the canvas. */
html.zq-has-pad .zq-shell[data-zq-mode="playing"] .zq-game-container {
  padding-top: 0;
  padding-bottom: 0;
}

html.zq-has-pad .zq-shell[data-zq-mode="playing"] #game-container canvas {
  max-height: calc(100svh - var(--zq-pad-h));
}

/* The keyboard legend is meaningless on a touch device, and at its current
   height it sat on top of the canvas and swallowed the taps that advance the
   title and story screens. The pad documents the controls instead. */
html.zq-has-pad .zq-help {
  display: none;
}

/* --- the pad itself --- */

html.zq-has-pad .zq-pad {
  position: fixed;
  z-index: 4300;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  height: var(--zq-pad-h);
  border-top: 1px solid rgba(241, 199, 106, 0.2);
  background: linear-gradient(180deg, rgba(5, 3, 13, 0.88), rgba(5, 3, 13, 0.98));
  padding:
    0.45rem
    max(0.75rem, env(safe-area-inset-right))
    max(1.1rem, env(safe-area-inset-bottom))
    max(0.75rem, env(safe-area-inset-left));
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

/* touch-action / user-select are scoped to the buttons, never the page. */
html.zq-has-pad .zq-pad button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(241, 199, 106, 0.3);
  border-radius: 14px;
  background: rgba(33, 16, 90, 0.72);
  color: var(--zq-white);
  font-weight: 900;
  line-height: 1;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

html.zq-has-pad .zq-pad button.is-pressed {
  border-color: var(--zq-gold);
  background: var(--zq-purple);
  transform: translateY(1px);
}

/* row 1 — pause, top-right, out of the thumb sweep */
html.zq-has-pad .zq-pad-top {
  display: flex;
  flex: 0 0 auto;
  justify-content: flex-end;
}

html.zq-has-pad .zq-pad-pause {
  width: 58px;
  height: 34px;
  font-size: 0.64rem;
  letter-spacing: 0.04em;
  opacity: 0.8;
}

/* row 2 — the thumb zone. Takes the slack; contents sit at its bottom. */
html.zq-has-pad .zq-pad-thumbs {
  display: flex;
  flex: 1 1 auto;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.5rem;
  min-height: 0;
}

html.zq-has-pad .zq-pad-dpad {
  display: grid;
  /* Jump moved to the right thumb - see .zq-pad-jump. A d-pad that owns
     jump forces one thumb to do move AND jump, which is why running jumps
     were impossible on a phone. */
  grid-template-columns: repeat(2, clamp(88px, 29vw, 112px));
  grid-template-areas: "left right";
  gap: 0.4rem;
}

/* Sized by the smaller of width and height so short/landscape viewports
   shrink the keys instead of overflowing the pad. */
html.zq-has-pad .zq-pad-dpad button {
  height: clamp(44px, min(19vw, 9.5vh), 78px);
  font-size: 1.35rem;
}

html.zq-has-pad .zq-pad-dpad [data-zq-key="ArrowUp"] {
  grid-area: up;
  justify-self: center;
  width: clamp(88px, 29vw, 112px);
}

html.zq-has-pad .zq-pad-dpad [data-zq-key="ArrowLeft"] { grid-area: left; }
html.zq-has-pad .zq-pad-dpad [data-zq-key="ArrowRight"] { grid-area: right; }

/* actions stack vertically — side by side would not fit beside the d-pad */
html.zq-has-pad .zq-pad-actions {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

html.zq-has-pad .zq-pad-actions button {
  width: clamp(56px, min(24vw, 12vh), 90px);
  height: clamp(56px, min(24vw, 12vh), 90px);
  border-radius: 50%;
  font-size: 1.7rem;
}

html.zq-has-pad .zq-pad-actions [data-zq-key="KeyX"] {
  border-color: rgba(49, 215, 255, 0.5);
  background: rgba(49, 215, 255, 0.18);
}

/* rows 3 and 4 — ability select. Flexible slots so they can never overflow. */
html.zq-has-pad .zq-pad-abilities,
html.zq-has-pad .zq-pad-secrets {
  display: flex;
  flex: 0 0 auto;
  gap: 0.3rem;
  width: 100%;
}

html.zq-has-pad .zq-pad-abilities[hidden],
html.zq-has-pad .zq-pad-secrets[hidden] {
  display: none;
}

html.zq-has-pad .zq-pad-abilities button,
html.zq-has-pad .zq-pad-secrets button {
  flex: 1 1 0;
  min-width: 0;
  height: clamp(32px, 5.5vh, 44px);
  border-radius: 10px;
  font-size: 0.82rem;
}

html.zq-has-pad .zq-pad-secrets button {
  max-width: 88px;
  border-color: rgba(49, 215, 255, 0.45);
  background: rgba(49, 215, 255, 0.14);
}

/* ---------------------------------------------------------------------------
   Short landscape (a phone turned sideways). The bottom-bar layout squeezes
   the canvas to 180x120, which is unplayable, and the flanking layout does not
   exist yet. Ask for portrait instead of shipping that. Gated on HEIGHT, not
   orientation alone, so a landscape touchscreen laptop is unaffected.
   --------------------------------------------------------------------------- */

html.zq-landscape-block .zq-pad {
  display: none;
}

/* Canvas goes back to full height so a dismissed notice leaves a big game. */
html.zq-landscape-block .zq-game-container,
html.zq-landscape-block #game-container {
  inset: 0;
  min-height: 100svh;
  height: 100svh;
  align-items: center;
}

html.zq-landscape-block #game-container canvas {
  margin-top: revert !important;
  max-height: 100svh;
}

.zq-rotate-notice {
  display: none;
}

html.zq-landscape-block:not(.zq-rotate-dismissed) .zq-rotate-notice {
  position: fixed;
  z-index: 4400;
  inset: 0;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.9rem;
  padding: 1.5rem;
  text-align: center;
  background: rgba(5, 3, 13, 0.92);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.zq-rotate-notice p {
  max-width: 26ch;
  margin: 0;
  color: var(--zq-white);
  font-size: 1.1rem;
  font-weight: 900;
  line-height: 1.35;
}

.zq-rotate-notice small {
  color: var(--zq-muted);
}

.zq-rotate-notice button {
  border: 1px solid rgba(241, 199, 106, 0.4);
  border-radius: 12px;
  background: rgba(33, 16, 90, 0.8);
  padding: 0.65rem 1.1rem;
  color: var(--zq-white);
  font-weight: 800;
  cursor: pointer;
  touch-action: manipulation;
}

/* World-map button. Injected only on touch devices, and only useful on the
   title screen — the world map is a level selector, not an in-game action. */
.zq-worldmap-btn {
  display: none;
}

/* Title screen and during play. In-game it is the only route to the level
   selector on a touch device, since there is no M key there. */
html.zq-has-pad body[data-zq-mode="title"] .zq-worldmap-btn,
html.zq-has-pad body[data-zq-mode="playing"] .zq-worldmap-btn {
  display: block;
}

/* Smaller and dimmer in-game so it is hard to hit by accident. */
html.zq-has-pad body[data-zq-mode="playing"] .zq-worldmap-btn {
  padding: 0.35rem 0.7rem;
  font-size: 0.62rem;
  opacity: 0.72;
}

.zq-worldmap-btn {
  position: fixed;
  z-index: 4350;
  border: 2px solid var(--zq-blue);
  border-radius: 12px;
  background: rgba(33, 16, 90, 0.92);
  padding: 0.65rem 0.9rem;
  color: var(--zq-blue);
  font-size: 0.78rem;
  font-weight: 900;
  letter-spacing: 0.05em;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

/* PORTRAIT: sits in the pad's top row, opposite PAUSE — never over the canvas.
   Previously it was centred over the canvas and covered the game's own
   "PRESS M FOR WORLD MAP" text. */
html.zq-has-pad .zq-worldmap-btn {
  top: calc(100svh - var(--zq-pad-h) + 0.45rem);
  left: max(0.75rem, env(safe-area-inset-left));
}

/* DS LANDSCAPE: top-left, inside the left rail, opposite PAUSE. */
html.zq-landscape-ds .zq-worldmap-btn {
  top: max(0.5rem, env(safe-area-inset-top));
  bottom: auto;
  left: max(0.4rem, env(safe-area-inset-left));
  width: var(--zq-rail-w);
  padding: 0.5rem 0.2rem;
  font-size: 0.6rem;
  text-align: center;
}

/* ---------------------------------------------------------------------------
   Landscape "DS" layout — controls flank the canvas in two fixed rails and
   consume ZERO vertical space, so the canvas keeps the full viewport height.

   Same DOM as portrait. `display: contents` on .zq-pad-thumbs promotes the
   d-pad and actions to grid items so they can sit in opposite rails without
   rebuilding the pad — no rebuild means no orphaned buttons on rotate.
   --------------------------------------------------------------------------- */

html.zq-landscape-ds .zq-game-container,
html.zq-landscape-ds #game-container {
  inset: 0;
  min-height: 100svh;
  height: 100svh;
  align-items: center;
  /* The rail is 110px, but .zq-pad carries its own horizontal padding, so the
     rail CONTENT ends slightly past 110. Reserve the extra or the outermost
     buttons bleed over the canvas edge on wide viewports. */
  padding: 0 calc(var(--zq-rail-w) + 0.6rem);
}

html.zq-landscape-ds #game-container canvas {
  margin-top: 0 !important;
  max-height: 100svh;
  max-width: calc(100vw - (2 * (var(--zq-rail-w) + 0.6rem)));
}

html.zq-landscape-ds .zq-help {
  display: none;
}

/* The pad becomes a full-viewport grid whose middle column is empty, so taps
   in the centre reach the canvas. Only the controls themselves take pointers. */
html.zq-landscape-ds .zq-pad {
  inset: 0;
  height: auto;
  display: grid;
  grid-template-columns: var(--zq-rail-w) 1fr var(--zq-rail-w);
  grid-template-rows: auto 1fr auto auto;
  /* .zq-pad-thumbs spans the full width and pushes the d-pad and actions to
     opposite ends with space-between. The middle is empty and the pad is
     pointer-events:none, so taps still reach the canvas. This deliberately
     avoids `display: contents`, which WebKit (all iOS browsers, Chrome
     included) has historically mishandled on grid items. */
  grid-template-areas:
    "top     top    top"
    "thumbs  thumbs thumbs"
    "abil    .      ."
    "secrets .      .";
  gap: 0.4rem;
  border-top: 0;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  padding: max(0.5rem, env(safe-area-inset-top)) max(0.4rem, env(safe-area-inset-right))
           max(0.5rem, env(safe-area-inset-bottom)) max(0.4rem, env(safe-area-inset-left));
  pointer-events: none;
}

html.zq-landscape-ds .zq-pad-thumbs {
  grid-area: thumbs;
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0;
  min-height: 0;
  pointer-events: none;
}

html.zq-landscape-ds .zq-pad-dpad {
  align-self: end;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.25rem;
  width: var(--zq-rail-w);
  flex: 0 0 var(--zq-rail-w);
  pointer-events: auto;
}

/* Keys were 53x43 on a 844x390 viewport — measurably squished. Vertical space
   in the rail is not the constraint (the rail is 390 tall and holds ~120px of
   d-pad), so the clamp is raised until the keys are thumb-sized. */
html.zq-landscape-ds .zq-pad-dpad button {
  width: 100%;
  height: clamp(46px, 15vh, 66px);
  font-size: 1.3rem;
}

html.zq-landscape-ds .zq-pad-dpad [data-zq-key="ArrowUp"] {
  width: 78%;
}

html.zq-landscape-ds .zq-pad-top {
  grid-area: top;
  justify-content: flex-end;
  align-self: start;
  pointer-events: none;
}

html.zq-landscape-ds .zq-pad-top > * {
  width: var(--zq-rail-w);
  pointer-events: auto;
}

html.zq-landscape-ds .zq-pad-pause {
  /* .zq-pad-top now spans all three columns, so 100% would stretch this
     across the canvas. Pin it to the rail width instead. */
  width: var(--zq-rail-w);
  height: 28px;
  font-size: 0.58rem;
}

html.zq-landscape-ds .zq-pad-actions {
  align-self: end;
  align-items: center;
  flex-direction: column;
  gap: 0.4rem;
  width: var(--zq-rail-w);
  flex: 0 0 var(--zq-rail-w);
  pointer-events: auto;
}

html.zq-landscape-ds .zq-pad-actions button {
  width: clamp(62px, 17vh, 86px);
  height: clamp(62px, 17vh, 86px);
  font-size: 1.6rem;
}

/* 1-7 as a 4 + 3 grid under the d-pad */
html.zq-landscape-ds .zq-pad-abilities {
  grid-area: abil;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.22rem;
  width: 100%;
  align-self: end;
  pointer-events: auto;
}

/* secrets 8/9/0/- as 2 x 2 under the actions */
html.zq-landscape-ds .zq-pad-secrets {
  grid-area: secrets;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.22rem;
  width: 100%;
  align-self: end;
  pointer-events: auto;
}

html.zq-landscape-ds .zq-pad-abilities[hidden],
html.zq-landscape-ds .zq-pad-secrets[hidden] {
  display: none;
}

html.zq-landscape-ds .zq-pad-abilities button,
html.zq-landscape-ds .zq-pad-secrets button {
  width: 100%;
  max-width: none;
  height: clamp(26px, 8vh, 34px);
  border-radius: 8px;
  font-size: 0.68rem;
}

@media (max-width: 760px) {
  .zq-topbar {
    top: 0.5rem;
  }

  .zq-topbar strong {
    display: none;
  }

  .zq-help {
    left: 0.75rem;
    right: 0.75rem;
    grid-template-columns: 1fr 1fr;
    text-align: center;
  }
}


/* ---------------------------------------------------------------------------
   In-app browser hint. Shown only inside Messenger/Instagram/Facebook/Line
   webviews, which are portrait-locked and can never reach the DS rail layout.
   Sits above the game but below the pause/world-map controls.
   --------------------------------------------------------------------------- */
.zq-webview-hint {
  position: fixed;
  z-index: 4300;
  left: 0;
  right: 0;
  top: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: calc(0.5rem + env(safe-area-inset-top)) 0.75rem 0.5rem;
  background: rgba(18, 11, 45, 0.96);
  border-bottom: 2px solid rgba(49, 215, 255, 0.55);
  color: #e8f6ff;
  font-size: 0.82rem;
  line-height: 1.35;
  text-align: left;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}

.zq-webview-hint span {
  flex: 1 1 auto;
}

.zq-webview-hint strong {
  color: #31d7ff;
  white-space: nowrap;
}

.zq-webview-hint button {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid rgba(232, 246, 255, 0.35);
  background: rgba(255, 255, 255, 0.08);
  color: #e8f6ff;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}


/* ---------------------------------------------------------------------------
   Thumb ergonomics fix. Jump lives with the action buttons on the RIGHT so the
   left thumb only ever steers; ability selection moves under the d-pad on the
   LEFT so the right thumb only ever fires. Reported on device: running jumps
   were impossible because one thumb owned both move and jump.
   --------------------------------------------------------------------------- */
html.zq-has-pad .zq-pad-jump {
  border-color: rgba(241, 199, 106, 0.55);
  background: rgba(241, 199, 106, 0.18);
}

/* The shell topbar sits on top of both rails in landscape - "Bonzentu Arcade"
   over the left rail, Fullscreen colliding with PAUSE on the right. The pad
   carries its own FULL button instead. */
html.zq-landscape-ds .zq-topbar {
  display: none;
}

html.zq-landscape-ds .zq-pad-top {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 0.3rem;
}

html.zq-landscape-ds .zq-pad-fs {
  width: calc(var(--zq-rail-w) * 0.42);
  height: 28px;
  font-size: 0.55rem;
  pointer-events: auto;
}

html.zq-landscape-ds .zq-pad-pause {
  width: calc(var(--zq-rail-w) * 0.55);
}

/* Both ability rows stack on the left rail, under the d-pad. */
html.zq-landscape-ds .zq-pad-abilities,
html.zq-landscape-ds .zq-pad-secrets {
  width: var(--zq-rail-w);
  justify-self: start;
}

/* CONTINUE - story, victory and game-over advance on Enter, and until now the
   pad had no Enter at all, so touch players had to guess that tapping the
   canvas worked. Bottom-centre of the canvas, only where advance is accepted. */
.zq-pad-continue {
  display: none;
}

html.zq-has-pad body[data-zq-mode="story"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="victory"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="game-over"] .zq-pad-continue {
  position: fixed;
  z-index: 4350;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--zq-pad-h) + max(0.6rem, env(safe-area-inset-bottom)) + 0.4rem);
  display: block;
  width: auto;
  min-width: 168px;
  height: 46px;
  padding: 0 1.1rem;
  border-radius: 10px;
  border: 2px solid rgba(49, 215, 255, 0.6);
  background: rgba(18, 11, 45, 0.95);
  color: #e8f6ff;
  font-size: 0.85rem;
  font-weight: 900;
  letter-spacing: 0.04em;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
  pointer-events: auto;
}

/* In landscape the pad has no reserved height, so anchor to the viewport. */
html.zq-landscape-ds body[data-zq-mode="story"] .zq-pad-continue,
html.zq-landscape-ds body[data-zq-mode="victory"] .zq-pad-continue,
html.zq-landscape-ds body[data-zq-mode="game-over"] .zq-pad-continue {
  bottom: max(0.7rem, env(safe-area-inset-bottom));
}


/* The right rail now carries THREE buttons (jump, attack, use) inside the 1fr
   thumbs row, and that row shrank once both ability rows took the bottom of the
   grid. At 812x375 the row is ~184px, so 3 x 62px + gaps overflowed upward into
   PAUSE. Sized to fit the row instead of the rail. */
html.zq-landscape-ds .zq-pad-actions {
  align-self: end;
  gap: 0.35rem;
}

html.zq-landscape-ds .zq-pad-actions button {
  width: clamp(46px, 14vh, 58px);
  height: clamp(46px, 14vh, 58px);
  font-size: 1.4rem;
}

/* With jump gone, left/right can take the freed height. */
html.zq-landscape-ds .zq-pad-dpad button {
  height: clamp(52px, 17vh, 72px);
}


/* PAUSE and FULL were 28px tall, under the 44px minimum touch target. The
   thumbs row has ~14px of slack at 812x375 after the action buttons were
   resized, so this is affordable. */
html.zq-landscape-ds .zq-pad-pause,
html.zq-landscape-ds .zq-pad-fs {
  height: 38px;
}


/* ---------------------------------------------------------------------------
   CONTINUE, corrected.

   It was centred over the canvas, which put it straight on top of the game's
   own bottom-of-screen text - the title screen's "PRESS M FOR WORLD MAP" and
   the story screen's SKIP button. The earlier check asserted WHERE the button
   was and never asked what was underneath it.

   New rule: CONTINUE never overlaps the canvas. In landscape it lives in the
   right rail under USE; in portrait it sits in the pad strip below the canvas.
   That makes the failure impossible rather than merely unlikely.
   --------------------------------------------------------------------------- */
html.zq-has-pad body[data-zq-mode="story"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="victory"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="game-over"] .zq-pad-continue {
  left: auto;
  right: max(0.6rem, env(safe-area-inset-right));
  transform: none;
  bottom: calc(var(--zq-pad-h) - 56px);
  min-width: 0;
  width: auto;
}

/* Landscape: right rail, under USE, clear of the canvas edge at x=125..688. */
html.zq-landscape-ds body[data-zq-mode="story"] .zq-pad-continue,
html.zq-landscape-ds body[data-zq-mode="victory"] .zq-pad-continue,
html.zq-landscape-ds body[data-zq-mode="game-over"] .zq-pad-continue {
  left: auto;
  right: max(0.4rem, env(safe-area-inset-right));
  bottom: max(0.7rem, env(safe-area-inset-bottom));
  transform: none;
  width: var(--zq-rail-w);
  min-width: 0;
  height: 52px;
  padding: 0 0.3rem;
  font-size: 0.68rem;
}

/* Belt and braces: never on the title screen, which has its own instructions. */
html.zq-has-pad body[data-zq-mode="title"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="playing"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="world-map"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="paused"] .zq-pad-continue,
html.zq-has-pad body[data-zq-mode="puzzle"] .zq-pad-continue {
  display: none;
}

/* Ability digits were 25x30. The left rail had 170px of headroom, so 1-7 goes
   from a 4-column to a 3-column grid and both rows gain height: 34x44 instead
   of 25x30, which is 2.5x the touch area and meets the 44px minimum on the
   axis that matters most for a thumb coming from below. */
html.zq-landscape-ds .zq-pad-abilities {
  grid-template-columns: repeat(3, 1fr);
  gap: 0.28rem;
}

html.zq-landscape-ds .zq-pad-secrets {
  gap: 0.28rem;
}

html.zq-landscape-ds .zq-pad-abilities button,
html.zq-landscape-ds .zq-pad-secrets button {
  height: clamp(34px, 12vh, 44px);
  font-size: 0.8rem;
}


/* ---------------------------------------------------------------------------
   DS rails: independent columns, not shared grid rows.

   The grid model coupled the two rails - abil/secrets defined ROW heights for
   the whole grid, so every time the left rail grew, the 1fr thumbs row shrank
   and the right rail's action buttons overflowed upward into PAUSE. That bug
   appeared twice. Each rail is now positioned independently, so left-rail
   changes cannot move right-rail controls at all.
   --------------------------------------------------------------------------- */
html.zq-landscape-ds .zq-pad-thumbs {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}

html.zq-landscape-ds .zq-pad-dpad,
html.zq-landscape-ds .zq-pad-actions,
html.zq-landscape-ds .zq-pad-abilities,
html.zq-landscape-ds .zq-pad-secrets {
  position: absolute;
  pointer-events: auto;
}

/* Left rail, stacked from the bottom: secrets, abilities, d-pad. */
html.zq-landscape-ds .zq-pad-secrets {
  left: max(0.4rem, env(safe-area-inset-left));
  bottom: max(0.5rem, env(safe-area-inset-bottom));
}

html.zq-landscape-ds .zq-pad-abilities {
  left: max(0.4rem, env(safe-area-inset-left));
  bottom: calc(max(0.5rem, env(safe-area-inset-bottom)) + 97px);
}

html.zq-landscape-ds .zq-pad-dpad {
  left: max(0.4rem, env(safe-area-inset-left));
  bottom: calc(max(0.5rem, env(safe-area-inset-bottom)) + 244px);
}

/* Right rail, bottom-anchored and independent of anything on the left. */
html.zq-landscape-ds .zq-pad-actions {
  right: max(0.4rem, env(safe-area-inset-right));
  bottom: max(0.5rem, env(safe-area-inset-bottom));
}

/* During story, victory and game-over the gameplay controls do nothing, so
   they are hidden and CONTINUE gets the rail to itself. This also removes any
   chance of CONTINUE colliding with the action buttons. */
html.zq-landscape-ds body[data-zq-mode="story"] .zq-pad-thumbs,
html.zq-landscape-ds body[data-zq-mode="story"] .zq-pad-abilities,
html.zq-landscape-ds body[data-zq-mode="story"] .zq-pad-secrets,
html.zq-landscape-ds body[data-zq-mode="victory"] .zq-pad-thumbs,
html.zq-landscape-ds body[data-zq-mode="victory"] .zq-pad-abilities,
html.zq-landscape-ds body[data-zq-mode="victory"] .zq-pad-secrets,
html.zq-landscape-ds body[data-zq-mode="game-over"] .zq-pad-thumbs,
html.zq-landscape-ds body[data-zq-mode="game-over"] .zq-pad-abilities,
html.zq-landscape-ds body[data-zq-mode="game-over"] .zq-pad-secrets {
  display: none;
}

html.zq-landscape-ds body[data-zq-mode="story"] .zq-pad-continue,
html.zq-landscape-ds body[data-zq-mode="victory"] .zq-pad-continue,
html.zq-landscape-ds body[data-zq-mode="game-over"] .zq-pad-continue {
  height: 72px;
  font-size: 0.78rem;
}


/* ---------------------------------------------------------------------------
   CORRECTION. The previous block pinned the d-pad 244px above the bottom to
   reserve room for the ability rows - but those rows are [hidden] until the
   player actually owns an ability, which is most of the early game. With them
   hidden the d-pad floated near the TOP of the rail with 270px of dead space
   under it, i.e. movement was out of thumb reach.

   Fix: the d-pad anchors to the BOTTOM and everything else stacks UPWARD from
   it. A hidden row then costs nothing, because nothing below it depends on its
   height. The d-pad is also the control that most deserves the thumb-closest
   position.
   --------------------------------------------------------------------------- */
html.zq-landscape-ds .zq-pad-dpad {
  bottom: max(0.5rem, env(safe-area-inset-bottom));
}

html.zq-landscape-ds .zq-pad-abilities {
  bottom: calc(max(0.5rem, env(safe-area-inset-bottom)) + 78px);
}

html.zq-landscape-ds .zq-pad-secrets {
  bottom: calc(max(0.5rem, env(safe-area-inset-bottom)) + 225px);
}

/* Portrait: the action column grew to three buttons while the d-pad dropped to
   one row, so the two thumb clusters no longer lined up - the d-pad sat at the
   bottom while the actions ran 200px higher. Bottom-align both, and size the
   actions so three fit without towering over the d-pad. */
html.zq-has-pad .zq-pad-thumbs {
  align-items: flex-end;
}

html.zq-has-pad .zq-pad-actions button {
  width: clamp(54px, min(20vw, 10vh), 76px);
  height: clamp(54px, min(20vw, 10vh), 76px);
}

html.zq-has-pad .zq-pad-actions {
  gap: 0.5rem;
}


/* ---------------------------------------------------------------------------
   REAL viewport height.

   Everything above sizes against 100svh. On iOS that resolves against the
   LAYOUT viewport, which does not shrink for Safari's toolbars or its landscape
   tab strip - so the game was laid out for more height than the browser had
   actually left, and the top of it ran under the chrome.

   --zq-vh is published by the bridge from visualViewport.height, which is the
   genuinely visible area, and is re-published on visualViewport resize/scroll.
   The fallback keeps the old behaviour if the script has not run yet.
   --------------------------------------------------------------------------- */
:root {
  --zq-vh: 100svh;
}

.zq-shell {
  min-height: var(--zq-vh);
  height: var(--zq-vh);
}

.zq-game-container,
#game-container {
  min-height: var(--zq-vh);
  height: var(--zq-vh);
}

#game-container canvas {
  max-height: var(--zq-vh);
}

.zq-shell[data-zq-mode="playing"] #game-container canvas {
  max-height: calc(var(--zq-vh) - 3.7rem);
}

html.zq-has-pad .zq-shell[data-zq-mode="playing"] .zq-game-container,
html.zq-has-pad #game-container {
  min-height: calc(var(--zq-vh) - var(--zq-pad-h));
  height: calc(var(--zq-vh) - var(--zq-pad-h));
}

html.zq-has-pad #game-container canvas {
  max-height: calc(var(--zq-vh) - var(--zq-pad-h));
}

html.zq-landscape-ds .zq-game-container,
html.zq-landscape-ds #game-container {
  min-height: var(--zq-vh);
  height: var(--zq-vh);
}

html.zq-landscape-ds #game-container canvas {
  max-height: var(--zq-vh);
}

html.zq-landscape-block .zq-game-container,
html.zq-landscape-block #game-container {
  min-height: var(--zq-vh);
  height: var(--zq-vh);
}

html.zq-landscape-block #game-container canvas {
  max-height: var(--zq-vh);
}

/* The pad is fixed to the bottom of the VISIBLE area, not the layout viewport. */
html.zq-has-pad .zq-pad {
  bottom: 0;
}


/* Pin fixed chrome to the VISIBLE viewport, not the layout viewport. Without
   these insets the pad's bottom row sits under Safari's toolbar and is simply
   unreachable - which is the "cuts off where the browser is" report. */
:root {
  --zq-vv-top: 0px;
  --zq-vv-bottom: 0px;
}

html.zq-has-pad .zq-pad {
  bottom: var(--zq-vv-bottom);
}

html.zq-landscape-ds .zq-pad {
  inset: auto 0 var(--zq-vv-bottom) 0;
  top: var(--zq-vv-top);
  height: auto;
}

html.zq-landscape-ds .zq-game-container,
html.zq-landscape-ds #game-container,
html.zq-landscape-block .zq-game-container,
html.zq-landscape-block #game-container {
  top: var(--zq-vv-top);
  bottom: var(--zq-vv-bottom);
  inset-inline: 0;
  height: var(--zq-vh);
}

.zq-topbar {
  top: calc(var(--zq-vv-top) + max(0.75rem, env(safe-area-inset-top)));
}

.zq-webview-hint {
  top: var(--zq-vv-top);
}


/* ---------------------------------------------------------------------------
   HUD LEGIBILITY

   The HUD lives in a DOM layer that the bridge scales to match the canvas, so
   its px sizes shrink with the canvas: on a 375-wide phone the scale is 0.326
   and the 10px controls legend renders at 3.3px, the 8px charge counters at
   2.6px. Unreadable rather than merely small.

   --zq-hud-k is 1/canvasScale (clamped 1..3.2), so calc(Npx * var(--zq-hud-k))
   is N PHYSICAL pixels at any canvas size. Desktop has k=1 and is unchanged.
   Sizes are raised slightly above the originals because the originals were
   already tight for a child.
   --------------------------------------------------------------------------- */
:root {
  --zq-hud-k: 1;
}

#ui-container #health-text,
#ui-container #lives-text {
  font-size: calc(15px * var(--zq-hud-k)) !important;
}

#ui-container #level-text {
  font-size: calc(17px * var(--zq-hud-k)) !important;
}

#ui-container #ability-name {
  font-size: calc(14px * var(--zq-hud-k)) !important;
}

#ui-container #ability-charges {
  font-size: calc(12px * var(--zq-hud-k)) !important;
}

#ui-container #active-ability-icon {
  font-size: calc(26px * var(--zq-hud-k)) !important;
}

#ui-container #ability-controls-hint {
  font-size: calc(10px * var(--zq-hud-k)) !important;
}

/* Ability and secret slots: the glyph and its little key number. 8px was the
   worst offender in the whole HUD. */
#ui-container #abilities-bar div,
#ui-container #easter-egg-bar div {
  font-size: calc(11px * var(--zq-hud-k)) !important;
}

#ui-container #lives-hearts span {
  font-size: calc(16px * var(--zq-hud-k)) !important;
}

/* Health bar is geometry rather than type, but it is thin on a phone. */
#ui-container #health-fill {
  min-height: calc(6px * var(--zq-hud-k));
}


/* ---------------------------------------------------------------------------
   TABERNACLE ENTRY + CONTROLS REFERENCE

   The room shipped as the fourth chip in a row of four on the world map -
   two screens deep, same size and colour as its neighbours. The owner could
   not find it. It now gets a first-class button beside WORLD MAP, and its own
   promoted row on the map itself.

   The controls card lives in the shell because it has to describe the TOUCH
   layouts, which the game itself knows nothing about, and because it must be
   reachable from title, pause and mid-level alike.
   --------------------------------------------------------------------------- */
.zq-tabernacle-btn,
.zq-controls-btn {
  position: fixed;
  z-index: 4350;
  display: none;
  border: 2px solid rgba(34, 197, 94, 0.55);
  border-radius: 10px;
  background: rgba(18, 11, 45, 0.94);
  color: #e8f6ff;
  font-size: 0.72rem;
  font-weight: 900;
  letter-spacing: 0.04em;
  padding: 0.5rem 0.7rem;
  cursor: pointer;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  pointer-events: auto;
}

.zq-controls-btn {
  border-color: rgba(241, 199, 106, 0.55);
}

/* Shown wherever the world-map button is shown, stacked beneath it. */
html.zq-has-pad body[data-zq-mode="title"] .zq-tabernacle-btn,
html.zq-has-pad body[data-zq-mode="title"] .zq-controls-btn,
html.zq-has-pad body[data-zq-mode="paused"] .zq-controls-btn {
  display: block;
  left: max(0.6rem, env(safe-area-inset-left));
}

html.zq-has-pad body[data-zq-mode="title"] .zq-tabernacle-btn {
  top: calc(var(--zq-vv-top) + 3.1rem);
}

html.zq-has-pad body[data-zq-mode="title"] .zq-controls-btn,
html.zq-has-pad body[data-zq-mode="paused"] .zq-controls-btn {
  top: calc(var(--zq-vv-top) + 5.6rem);
}

/* Desktop has no pad, so these still need to exist there. */
html:not(.zq-has-pad) body[data-zq-mode="title"] .zq-controls-btn,
html:not(.zq-has-pad) body[data-zq-mode="paused"] .zq-controls-btn {
  display: block;
  left: max(0.75rem, env(safe-area-inset-left));
  bottom: max(0.75rem, env(safe-area-inset-bottom));
}

.zq-controls {
  position: fixed;
  z-index: 4500;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgba(3, 2, 10, 0.86);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.zq-controls[hidden] {
  display: none;
}

.zq-controls-card {
  width: min(560px, 96vw);
  max-height: min(86vh, var(--zq-vh));
  overflow-y: auto;
  border: 3px solid rgba(241, 199, 106, 0.5);
  border-radius: 12px;
  background: rgba(18, 11, 45, 0.98);
  padding: 1rem 1.1rem 1.2rem;
  color: var(--zq-white);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}

.zq-controls-card h2 {
  margin: 0 0 0.7rem;
  color: var(--zq-gold);
  font-size: 1.05rem;
  letter-spacing: 0.08em;
}

.zq-controls-card table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.78rem;
}

.zq-controls-card th {
  text-align: left;
  color: var(--zq-blue);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  padding: 0.3rem 0.4rem;
  border-bottom: 1px solid rgba(248, 247, 255, 0.18);
}

.zq-controls-card td {
  padding: 0.34rem 0.4rem;
  border-bottom: 1px solid rgba(248, 247, 255, 0.07);
  vertical-align: top;
}

.zq-controls-card td:first-child {
  color: var(--zq-gold);
  white-space: nowrap;
}

.zq-controls-note {
  margin: 0.8rem 0 0.9rem;
  color: var(--zq-muted);
  font-size: 0.72rem;
  line-height: 1.45;
}

.zq-controls-card button {
  width: 100%;
  min-height: 46px;
  border: 2px solid rgba(49, 215, 255, 0.55);
  border-radius: 10px;
  background: rgba(49, 215, 255, 0.14);
  color: #e8f6ff;
  font-size: 0.82rem;
  font-weight: 900;
  cursor: pointer;
}

/* ---------------------------------------------------------------------------
   THE CHROME SHELF — added 2026-08-15
   ---------------------------------------------------------------------------
   Measured on an iPhone 14 in portrait before this change:
     - canvas 390x260, i.e. 31% of the screen
     - THREE chrome buttons drawn ON TOP of that canvas (topbar fullscreen,
       TABERNACLE, CONTROLS), each 32-38px tall against a 44px minimum, all
       in the top-left corner - the worst place on the screen for a thumb
     - the pad below was 584px tall with ~280px of dead space, because
       .zq-pad-thumbs takes all the slack and pins its contents to the bottom

   The dead space is now the shelf. Chrome moved off the canvas and down into
   the thumb zone, where there is room for 46px targets.

   The canvas is NOT bigger afterwards and cannot be: it is aspect-locked at
   3:2 under Scale.FIT, so 390 CSS px wide can only ever be 260 tall. Reclaiming
   pad space moves dead space, it does not convert it into canvas. Raising the
   31% needs an aspect-ratio decision, which is the owner's.
   --------------------------------------------------------------------------- */

html.zq-has-pad .zq-pad-top      { order: 0; }
html.zq-has-pad .zq-pad-chrome   { order: 1; }
html.zq-has-pad .zq-pad-thumbs   { order: 2; }
html.zq-has-pad .zq-pad-continue { order: 3; }
html.zq-has-pad .zq-pad-abilities{ order: 4; }
html.zq-has-pad .zq-pad-secrets  { order: 5; }

html.zq-has-pad .zq-pad-chrome {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: stretch;
}

/* Static beats the fixed positioning these buttons use on desktop, so top/left
   stop applying and they simply flow inside the shelf. */
html.zq-has-pad .zq-pad-chrome > button {
  position: static;
  display: none;
  flex: 1 1 8rem;
  min-height: 46px;          /* 44px minimum, plus 2px of margin for error */
  min-width: 5.5rem;
  top: auto;
  left: auto;
  bottom: auto;
  margin: 0;
  padding: 0.5rem 0.6rem;
  font-size: 0.72rem;
  font-weight: 900;
  letter-spacing: 0.04em;
  border-radius: 12px;
  align-items: center;
  justify-content: center;
}

/* Which chrome is offered depends on where the player is. */
html.zq-has-pad body[data-zq-mode="title"] .zq-pad-chrome > [data-zq-start],
html.zq-has-pad body[data-zq-mode="title"] .zq-pad-chrome > [data-zq-worldmap],
html.zq-has-pad body[data-zq-mode="title"] .zq-pad-chrome > [data-zq-tabernacle],
html.zq-has-pad body[data-zq-mode="title"] .zq-pad-chrome > .zq-controls-btn,
html.zq-has-pad body[data-zq-mode="playing"] .zq-pad-chrome > [data-zq-worldmap],
html.zq-has-pad body[data-zq-mode="playing"] .zq-pad-chrome > .zq-controls-btn,
html.zq-has-pad body[data-zq-mode="paused"] .zq-pad-chrome > .zq-controls-btn {
  display: inline-flex;
}

/* START is the primary action on the title screen and should read as such. */
html.zq-has-pad .zq-pad-chrome > [data-zq-start] {
  flex: 1 1 100%;
  min-height: 54px;
  font-size: 0.9rem;
  border: 2px solid rgba(241, 199, 106, 0.85);
  background: rgba(90, 60, 12, 0.92);
  color: #ffe9a8;
}

.zq-start-btn {
  position: fixed;
  display: none;
  z-index: 4350;
  border: 2px solid rgba(241, 199, 106, 0.55);
  border-radius: 10px;
  background: rgba(18, 11, 45, 0.94);
  color: #ffe9a8;
  cursor: pointer;
  pointer-events: auto;
}

/* The topbar's Fullscreen button overlapped the canvas in portrait and is
   redundant once the pad exists - the pad's FULL button reuses its handler. */
html.zq-has-pad .zq-topbar button[data-zq-fullscreen] {
  display: none;
}

/* Push the shelf down into the thumb arc.
   First pass put it directly under the pause row at y=371, which measured as
   REGRIP - reachable only by shifting grip. Letting the thumbs row size to its
   contents and giving the shelf `margin-top: auto` collapses the slack ABOVE
   the shelf instead of above the thumbs, dropping the shelf ~130px into reach.
   Scoped away from the landscape rails, which lay the pad out entirely
   differently and must not inherit this. */
html.zq-has-pad:not(.zq-landscape-ds):not(.zq-landscape-block) .zq-pad-thumbs {
  flex: 0 0 auto;
}
html.zq-has-pad:not(.zq-landscape-ds):not(.zq-landscape-block) .zq-pad-chrome {
  margin-top: auto;
}

/* ---------------------------------------------------------------------------
   DISMISSED BLOCK MODE — a game you can actually control
   ---------------------------------------------------------------------------
   `html.zq-landscape-block .zq-pad { display: none }` above is correct while
   the rotate notice is up. It was NOT correct after "Continue anyway": the
   notice hid, the pad stayed hidden, --zq-pad-h stayed 0, and the player was
   left in landscape with ZERO controls. Measured cold at 736x414 (iPhone 8
   Plus), 667x375 (SE) and 1024x700 (touch laptop, aspect 1.46): 0 of 23
   buttons visible both before and after dismissing.

   Block mode fires on EITHER trigger in landscapeMode():
     w / h < 1.5   (canvas is width-limited)
     w < 782       (too narrow for the DS rails)

   Once dismissed, the pad comes back in its portrait layout - cramped in
   landscape, but playable, which beats unplayable.
   --------------------------------------------------------------------------- */

html.zq-landscape-block.zq-rotate-dismissed .zq-pad {
  display: flex;
}

/* Give the canvas back only the space the pad is actually using. */
html.zq-landscape-block.zq-rotate-dismissed .zq-game-container,
html.zq-landscape-block.zq-rotate-dismissed #game-container {
  inset: 0 0 auto 0;
  min-height: calc(100svh - var(--zq-pad-h));
  height: calc(100svh - var(--zq-pad-h));
  align-items: flex-start;
}

html.zq-landscape-block.zq-rotate-dismissed #game-container canvas {
  margin-top: 0 !important;
  max-height: calc(100svh - var(--zq-pad-h));
}

/* The chrome shelf and thumb-row rules were scoped away from every landscape
   class. Dismissed block mode uses the PORTRAIT layout, so it needs them back
   or the shelf sits at the top of the pad out of thumb reach. */
html.zq-landscape-block.zq-rotate-dismissed .zq-pad-thumbs {
  flex: 0 0 auto;
}
html.zq-landscape-block.zq-rotate-dismissed .zq-pad-chrome {
  margin-top: auto;
}

/* ---------------------------------------------------------------------------
   CHROME SHELF — ONE STACKING RULE, ONE SIZING RULE, ALL MODES
   ---------------------------------------------------------------------------
   Diagnosed 2026-08-17 by cold-loading ten viewports and CLICKING, not measuring.

   THE BUG: the WORLD MAP button rendered at a correct size and position in
   ds-landscape, desktop-touch and desktop-big, and was NOT CLICKABLE in any of
   them. elementFromPoint at its centre returned `.zq-game-container` - the
   canvas container was painting on top of it.

   THE CAUSE WAS MINE. The portrait chrome shelf set
   `.zq-pad-chrome > button { position: static }`. A static child of the fixed
   pad does not establish stacking above a positioned sibling, so the game
   container covered it. The rails (.zq-pad-dpad, .zq-pad-actions) were
   unaffected because they are position:absolute - which is why the d-pad kept
   working and only the chrome died. The rule was scoped to `html.zq-has-pad`,
   so it silently applied in landscape too.

   SECOND SYMPTOM, SAME CAUSE: in a column rail the shelf stretched to fill -
   110x317 at 844x390, 132x727 at 1920x1080 - so its buttons grew to 175px and
   245px tall and swallowed the d-pad. That is the "CONTROLS overlaps Move
   left/right" defect, and it is the same block of CSS.

   THE RULE: chrome buttons are positioned and above the canvas in EVERY mode,
   and never stretch. Sizing differs by layout; stacking does not.
   --------------------------------------------------------------------------- */

/* THE ACTUAL CAUSE - pointer-events, not stacking.
   In the landscape rails `.zq-pad` is pointer-events:none so taps fall through
   to the canvas, and each rail re-enables `pointer-events:auto` on ITSELF. The
   chrome shelf was added later and never got that treatment, so it inherited
   `none`: the buttons rendered at the right size in the right place and
   silently swallowed nothing. elementFromPoint returned .zq-game-container,
   which reads exactly like a z-index problem and is not one.

   The d-pad kept working throughout because .zq-pad-dpad sets pointer-events
   explicitly. That difference is the whole bug.

   One rule, every mode: chrome is interactive wherever it is visible. */
html.zq-has-pad .zq-pad-chrome,
html.zq-has-pad .zq-pad-chrome > button {
  pointer-events: auto;
}

html.zq-has-pad .zq-pad-chrome > button {
  position: relative;
  z-index: 2;
}

/* Sizing in the landscape rails: hug content, never stretch to fill. */
html.zq-landscape-ds .zq-pad-chrome,
html.zq-landscape-block:not(.zq-rotate-dismissed) .zq-pad-chrome {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  flex: 0 0 auto;
  gap: 0.35rem;
}

html.zq-landscape-ds .zq-pad-chrome > button {
  flex: 0 0 auto;      /* the fix for 175px and 245px tall buttons */
  min-height: 46px;
  max-height: 56px;
  font-size: 0.62rem;
  padding: 0.35rem 0.4rem;
}

/* The rail column must not run into the d-pad below it. */
html.zq-landscape-ds .zq-pad-chrome {
  max-height: 58%;
  overflow: visible;
}

/* The chrome shelf must never reach the action column.
   Measured across five viewports: CONTROLS is the widest chrome row and runs
   full width, while JUMP / ATTACK / USE sit in a column pinned to the right
   edge. The two boxes overlapped in every portrait-family mode -
   portrait 76x40px, webview 73x37px, tablet 76x40px, block-736 54x18px,
   desktop-1024 70x34px - always the same pair, always the same cause.
   Reserving the right-hand strip is one rule instead of five nudges. */
html.zq-has-pad:not(.zq-landscape-ds) .zq-pad-chrome {
  max-width: calc(100% - 6.5rem);
}

/* Keep the shelf to two rows: START, then the three secondary buttons together.
   Reserving the action column narrowed the shelf to ~286px at 390 wide, and an
   8rem flex-basis made CONTROLS wrap onto a third row. That pushed the pad's
   content 34px past the bottom of a 730px webview - the d-pad and USE were
   clipped off-screen. A 5rem basis fits three across in the narrowed shelf. */
html.zq-has-pad:not(.zq-landscape-ds) .zq-pad-chrome > button {
  flex: 1 1 5rem;
  min-width: 4.5rem;
}

/* ---------------------------------------------------------------------------
   CLEARING THE UNDER-44px DEBT REGISTER
   ---------------------------------------------------------------------------
   Three controls have sat below the WCAG 2.5.5 / Apple HIG 44px minimum since
   the pad shipped: PAUSE (58x34), the pad FULL button (40x34) and the desktop
   topbar Fullscreen (86x32). They were logged as accepted debt with a note to
   shrink the list, never grow it. This clears it.

   PAUSE stays visually quiet - it is deliberately de-emphasised so it is not
   mis-tapped mid-play - but quiet is a matter of contrast and opacity, not of
   being too small to hit. It keeps its low opacity and small type and simply
   meets the target.

   Scoped to the portrait/block pad and the desktop topbar. The DS rails size
   these against --zq-rail-w and are left alone, since the rail geometry was
   verified on a real device and is not worth disturbing for a control that
   already clears 44px there.
   --------------------------------------------------------------------------- */

html.zq-has-pad:not(.zq-landscape-ds) .zq-pad-pause {
  min-width: 58px;
  width: auto;
  height: 44px;
  padding: 0 0.6rem;
}

html.zq-has-pad:not(.zq-landscape-ds) .zq-pad-fs {
  min-width: 44px;
  width: auto;
  height: 44px;
  padding: 0 0.5rem;
}

/* Desktop has no pad, so this is the only fullscreen control there. */
.zq-topbar button {
  min-height: 44px;
}

/* The DS rails too. First pass scoped these out on the reasoning that the rail
   geometry was verified on a real device and was not worth disturbing - but
   38px is still under the minimum, and "verified" is not "exempt". The rails
   have room: the top row sits at y=8 in a 390px-tall landscape and the thumb
   rails do not begin until y=139. Six pixels is affordable. */
html.zq-landscape-ds .zq-pad-fs,
html.zq-landscape-ds .zq-pad-pause {
  height: 44px;
  min-width: 44px;
}

/* ---------------------------------------------------------------------------
   PUZZLE ANSWER KEYS — A/B/C/D in the pad
   ---------------------------------------------------------------------------
   The owner asked for these "like you would with the arrows and all that", i.e.
   living in the pad in the pad's own visual language, not as text tapped inside
   a panel.

   THE TAP DIAGNOSIS THAT PRECEDED THIS, so the same mistake is not repeated:
   the pad does NOT eat taps on the puzzle options. Measured on a 375x644 touch
   profile with the pad present - elementFromPoint at each option's centre
   returned the option itself, all four, hitIsSelf true. What it also returned
   was their size: 87x19px. Nineteen pixels tall, against the 44px minimum this
   project's own control audit enforces on every other control. The options were
   hittable in principle and far too small in practice, which is a different
   defect from the one that was assumed and needs a different fix - hence both
   rules below.
   --------------------------------------------------------------------------- */

/* The pad row. Matches the ability digits' shape so it reads as part of the pad. */
/* `display: grid` beats the UA stylesheet's `[hidden] { display: none }`, so
   without this rule the row is ALWAYS laid out and the pad grows by ~60px even
   when no puzzle is open. The deploy gate caught exactly that: Move left, Move
   right and Use ability were clipped outside the viewport in the webview
   profile. An author display rule must always be paired with its [hidden] guard. */
.zq-pad-answers[hidden] {
  display: none !important;
}

.zq-pad-answers {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.4rem;
  margin-top: 0.4rem;
  pointer-events: auto;
}

.zq-pad-answers > button {
  position: relative;
  z-index: 2;
  min-height: 52px;
  min-width: 52px;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #86efac;
  background: rgba(12, 24, 16, 0.92);
  border: 2px solid #22c55e;
  border-radius: 12px;
  box-shadow: 0 0 12px rgba(34, 197, 94, 0.35);
  pointer-events: auto;
}

.zq-pad-answers > button:active {
  transform: scale(0.94);
  background: rgba(34, 197, 94, 0.28);
}

/* The on-screen options themselves were 87x19. Give them a real touch target so
   direct tapping keeps working - the pad is an ADDITIONAL path, not a
   replacement, and desktop still answers by clicking these. */
.puzzle-option,
.zq-letter-option {
  min-height: 44px;
}
