/* ==========================================================================
   game.css — everything under html.mode-game ONLY.

   Not one rule in this file applies otherwise, which is what keeps the plain
   site complete and JavaScript-free. In particular, hiding inactive regions
   happens here and nowhere else: a crawler or a JS-off visitor sees every
   region in normal flow.
   ========================================================================== */

html.mode-game {
  overflow: hidden;
  overscroll-behavior: none;
  height: 100%;
}

html.mode-game body {
  height: 100%;
  /* Plain mode keeps native scrolling untouched; only the game captures
     gestures, so only the game opts out of them. */
  touch-action: none;
}

/* `touch-action` is NOT an inherited property, so setting it on body leaves
   every descendant at `auto` — and a drag that starts on a card then scrolls
   the page sideways instead of moving the character. It has to be applied to
   the subtree. */
html.mode-game #content,
html.mode-game #content * {
  touch-action: none;
  overscroll-behavior: none;
  /* No text selection either: dragging across prose would otherwise begin a
     selection drag and hand the gesture to the browser. The reader panel is
     outside #content precisely so it keeps normal selectable text. */
  user-select: none;
  -webkit-user-select: none;
}

/* While a caret or selection exists anywhere, Chrome redoes selection work
   across the whole world on every frame it moves, which halves the frame
   rate. A click on the contents backdrop would otherwise leave a caret in
   #live. Only the reader keeps selectable text. */
html.mode-game body {
  user-select: none;
  -webkit-user-select: none;
}
html.mode-game .reader {
  user-select: text;
  -webkit-user-select: text;
}

/* And no native drag of any draggable content — images, inline SVG, links. */
html.mode-game #content img,
html.mode-game #content svg,
html.mode-game #content a {
  -webkit-user-drag: none;
}

/* While the character is held, text selection would turn the drag into a
   native text-drag, which also scrolls. */
html.mode-game.dragging,
html.mode-game.dragging * {
  user-select: none;
  -webkit-user-select: none;
  cursor: grabbing !important;
}

/* The camera is a single transform on this element and nothing else. */
html.mode-game #content {
  position: fixed;
  inset: 0;
  overflow: hidden;
}

/* --zoom and .zoomed are set by camera.js. Only applied below 1, because a
   transform also makes #content the containing block for fixed children. */
html.mode-game.zoomed #content {
  right: auto;
  bottom: auto;
  width: calc(100% / var(--zoom));
  height: calc(100% / var(--zoom));
  transform: scale(var(--zoom));
  transform-origin: 0 0;
}

html.mode-game #world {
  position: absolute;
  top: 0;
  left: 0;
  padding: 0;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

html.mode-game .region {
  position: absolute;
  left: 0;
  top: 0;
  width: auto;
  margin: 0;
  padding: 0;
  border: 0;
}

/* Regions never overlap in world space. Inactive ones are skipped entirely —
   `hidden` rather than `auto`, because `auto` would let a skipped card report
   a placeholder height and corrupt the collider cache. The map lists every
   region, so nothing becomes unreachable. */
html.mode-game .region:not([data-active]) {
  content-visibility: hidden;
}

html.mode-game .card {
  position: absolute;
  margin: 0;
  width: var(--card-w, 340px);
}

html.mode-game .card--side,
html.mode-game .card--skill {
  --card-w: 300px;
}

/* A gallery sign hung from the ceiling on two wires (its .art). */
html.mode-game .card--sign { --card-w: 240px; text-align: center; }
html.mode-game .card--sign h2 { margin: 0; letter-spacing: 0.25em; }
html.mode-game .card--sign h2::before,
html.mode-game .card--sign h2::after { content: none; }
html.mode-game .card--sign p { margin: 0.3em 0 0; color: var(--accent-ink); }
html.mode-game .card--sign > .art { font-weight: 400; color: var(--ink-faint); }

/* Canvas: viewport-fixed, outside #world, never transformed by CSS. The camera
   is applied in canvas space so the backing store stays an exact device-pixel
   grid at any DPR. */
html.mode-game #stage {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 30;
  width: 100%;
  height: 100%;
  pointer-events: none;
  will-change: transform;
}

/* Touch controls are real buttons, not canvas-drawn, so they stay focusable
   and accessible and cost nothing to render. */
html.mode-game .touch-controls {
  display: flex;
  position: fixed;
  z-index: 50;
  left: 0;
  right: 0;
  bottom: 0;
  justify-content: space-between;
  align-items: flex-end;
  padding: 0.8rem;
  padding-bottom: max(0.8rem, env(safe-area-inset-bottom));
  padding-left: max(0.8rem, env(safe-area-inset-left));
  padding-right: max(0.8rem, env(safe-area-inset-right));
  pointer-events: none;
}

html.mode-game .touch-controls[hidden] { display: none; }

html.mode-game .touch-left,
html.mode-game .touch-right {
  display: grid;
  gap: 0.4rem;
  pointer-events: auto;
}
html.mode-game .touch-left { grid-template-columns: repeat(3, auto); }
html.mode-game .touch-right { justify-items: end; }
.touch-btn--up { grid-column: 2; }
.touch-btn:is([data-key=left], [data-key=down], [data-key=right]) { grid-row: 2; }

.touch-btn {
  width: 4.2rem;
  height: 4.2rem;
  font-family: var(--mono);
  font-size: 1.2rem;
  font-weight: 700;
  background: color-mix(in srgb, var(--paper) 85%, transparent);
  border: solid;
  border-width: 16px 10px;
  border-image: var(--ascii-box) 16 10 round;
  color: var(--ink);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.touch-btn:active { background: color-mix(in srgb, var(--ink) 15%, var(--paper)); }
.touch-btn--jump { width: 5.4rem; font-size: 1rem; }
.touch-btn--use { height: 3.4rem; font-size: 0.9rem; }

@media (max-width: 30rem), (max-height: 30rem) {
  .touch-btn { width: 3.5rem; height: 3.5rem; font-size: 1rem; }
  .touch-btn--jump { width: 4.6rem; font-size: 0.9rem; }
  .touch-btn--use { height: 3rem; font-size: 0.85rem; }
}

/* Coarse pointers only — a mouse user gets the cursor affordance instead. */
@media (pointer: fine) {
  html.mode-game .touch-controls { display: none !important; }
}

/* The chrome stays for the mode toggle and contents, but compact: the game
   view is inset below it, so every pixel it takes is a pixel of sky lost. */
html.mode-game .chrome {
  padding-top: max(0.35rem, env(safe-area-inset-top));
  padding-bottom: 0.35rem;
  background: var(--paper);
  backdrop-filter: none;
}
html.mode-game .brand { font-size: var(--step-0); }

/* One row, not plain mode's wrapped one: a second row of header is sky lost. */
@media (max-width: 34rem) {
  html.mode-game .chrome { flex-wrap: nowrap; gap: 0.5rem; }
  html.mode-game .chrome-actions { width: auto; gap: 0.25rem; }
  html.mode-game .chrome-actions .btn { flex: none; font-size: 12px; }
  html.mode-game .brand { font-size: 14px; white-space: nowrap; }
}

/* Debug overlay (backtick). Deliberately plain — this is a tuning tool. */
.debug {
  position: fixed;
  z-index: 90;
  top: 0.6rem;
  left: 0.6rem;
  padding: 0.6rem 0.8rem;
  min-width: 22rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.5;
  color: var(--debug-ink);
  background: var(--debug-bg);
  border-radius: 6px;
  pointer-events: none;
}
.debug[hidden] { display: none; }
.debug div { display: flex; gap: 0.75rem; }
.debug b { flex: 0 0 6.5rem; color: var(--debug-label); font-weight: 600; }
.debug span { white-space: pre-wrap; }

/* ASCII world ------------------------------------------------------------ */

/* The cell size is measured from `.ascii` by js/game/ascii.js, and LINE
   there must match the line-height here. */
.ascii {
  font-family: var(--mono);
  font-size: 14px;
  line-height: 16px;
  font-weight: 400;
}

html.mode-game body {
  background: var(--paper);
}

html.mode-game .ascii-sky {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  color: var(--ink-faint);
  pointer-events: none;
  will-change: transform;
}
/* Inline backgrounds only cover the glyph box, so line-height must not
   exceed the font's own height or the solid runs leave gaps. */
.ascii-sky .sky-solid     { background: var(--paper); }
.ascii-sky .sky-star      { color: color-mix(in srgb, var(--ink-faint) 60%, var(--paper)); }
.ascii-sky .sky-mtn       { color: color-mix(in srgb, var(--ink-faint) 60%, var(--paper)); }
.ascii-sky .sky-mtn-fill  { color: color-mix(in srgb, var(--ink-faint) 40%, var(--paper)); }
.ascii-sky .sky-snow      { color: color-mix(in srgb, var(--ink-faint) 45%, var(--paper)); }
.ascii-sky .sky-cloud     { color: color-mix(in srgb, var(--sky-cloud) 65%, var(--paper)); }
.ascii-sky .sky-fog       { color: color-mix(in srgb, var(--sky-fog) 50%, var(--paper)); }
.ascii-sky .sky-hill      { color: var(--ink-faint); }
.ascii-sky .sky-hill-fill { color: color-mix(in srgb, var(--sky-hill) 70%, var(--paper)); }
.ascii-sky .sky-tree      { color: var(--sky-tree); }

/* The span keeps its own character, transparent, so the column width is
   exact; the glyph that shows is the ::before, which js/game/ascii.js swaps
   through data-lit. */
.ascii-sky .sky-twinkle { position: relative; color: transparent; }
.ascii-sky .sky-twinkle::before {
  position: absolute;
  left: 0;
  top: 0;
  color: color-mix(in srgb, var(--ink-faint) 60%, var(--paper));
}
.ascii-sky .sky-twinkle[data-lit="1"]::before { color: color-mix(in srgb, var(--ink-faint) 80%, var(--ink-soft)); }
.ascii-sky .sky-twinkle[data-lit="2"]::before { color: var(--ink-soft); }
.ascii-sky .sky-twinkle--dot::before  { content: "."; }
.ascii-sky .sky-twinkle--plus::before { content: "+"; }
.ascii-sky .sky-twinkle--star::before { content: "*"; }
.ascii-sky .sky-twinkle--dot[data-lit="2"]::before  { content: ","; }
.ascii-sky .sky-twinkle--plus[data-lit="2"]::before { content: "*"; }
.ascii-sky .sky-twinkle--star[data-lit="2"]::before { content: "+"; }

html.mode-game .ascii-sky--birds { width: 0; height: 0; }

html.mode-game .ascii-sky--sun { width: 0; height: 0; }
.sky-sun {
  position: absolute;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--sun);
  font-weight: 700;
  white-space: pre;
  text-align: left;
  cursor: pointer;
  pointer-events: auto;
}
.sky-sun:hover { color: var(--interact); }
.sky-sun-moon,
:root[data-theme="dark"] .sky-sun-sun { display: none; }
:root[data-theme="dark"] .sky-sun-moon { display: inline; }
html.mode-game:is([data-underground], [data-museum]) .sky-sun { visibility: hidden; }
.flock {
  position: absolute;
  animation: bird-fly 60s linear infinite;
}
.bird {
  position: absolute;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: pre;
}
.bird::before { content: "\\v/"; }
.ascii-sky--birds[data-flap] .bird::before { content: "—v—"; }
@keyframes bird-fly {
  0%   { transform: translate(0, 0); opacity: 0; }
  5%   { opacity: 1; }
  30%  { transform: translate(480px, -24px); }
  60%  { transform: translate(960px, 10px); }
  95%  { opacity: 1; }
  100% { transform: translate(1600px, -8px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .flock { animation: none; }
}

/* Opaque, so the sky never shows through the gaps between characters. */
html.mode-game .ascii-terrain {
  position: absolute;
  margin: 0;
  overflow: hidden;
  white-space: pre;
  color: color-mix(in srgb, var(--ink-soft) 60%, var(--paper));
  background: var(--paper);
  pointer-events: none;
}
/* Underground, everything in the world is shaded grey to sit in the cave
   (the sky layers are outside #world, so the cave keeps its own colours). */
html.mode-game {
  --cave-tunnel: color-mix(in srgb, var(--stone) 7%, var(--paper));
  --cave-ground: color-mix(in srgb, var(--stone) 6%, var(--paper));
}
html.mode-game[data-underground] #world { --paper: var(--cave-tunnel); }
html.mode-game[data-underground] :where(.ascii-terrain),
html.mode-game[data-underground] .mover { background: var(--cave-ground); }
html.mode-game .ascii-terrain::first-line { color: var(--ink); font-weight: 700; }
html.mode-game .ascii-terrain--ledge { color: var(--platform); background: transparent; }
html.mode-game .ascii-terrain--ledge::first-line { color: var(--platform); }
html.mode-game .ascii-terrain--wall { color: var(--ink); }

/* The museum's floor, walls and ceiling, in the hallway's colours. */
html.mode-game .ascii-terrain--gallery-floor {
  color: color-mix(in srgb, var(--mu-taupe) 35%, var(--paper));
  background: color-mix(in srgb, var(--mu-cream) 70%, var(--paper));
}
html.mode-game .ascii-terrain--gallery-floor::first-line { color: var(--mu-brown); }
html.mode-game .ascii-terrain--gallery-ceiling {
  color: color-mix(in srgb, var(--mu-tan) 60%, var(--paper));
  background: color-mix(in srgb, var(--mu-beige) 55%, var(--paper));
}
html.mode-game .ascii-terrain--gallery-ceiling::first-line { color: color-mix(in srgb, var(--mu-tan) 60%, var(--paper)); font-weight: 400; }
html.mode-game .ascii-terrain--gallery-wall {
  color: color-mix(in srgb, var(--mu-taupe) 80%, var(--paper));
  background: color-mix(in srgb, var(--mu-linen) 70%, var(--paper));
}
html.mode-game .ascii-terrain--gallery-wall::first-line { color: var(--mu-brown); }

html.mode-game .ascii-water {
  position: absolute;
  margin: 0;
  overflow: hidden;
  white-space: pre;
  color: color-mix(in srgb, var(--water) 45%, var(--paper));
  background: color-mix(in srgb, var(--water) 8%, var(--paper));
  pointer-events: none;
}
html.mode-game .ascii-spikes {
  position: absolute;
  margin: 0;
  overflow: hidden;
  white-space: pre;
  color: var(--spike);
  font-weight: 700;
  pointer-events: none;
}
html.mode-game .ascii-water .water-surface { color: var(--water); font-weight: 700; }

/* Cards: the exact ASCII frame replaces the border image. Its characters
   are set per card as --frame by js/game/level.js. */
html.mode-game .card {
  padding: 16px 2ch;
  line-height: 1.45;
  border: 0;
  border-image: none;
}

html.mode-game .card::before {
  content: var(--frame, "");
  position: absolute;
  inset: 0;
  overflow: hidden;
  white-space: pre;
  font: 400 14px/var(--frame-line, 16px) var(--mono);
  letter-spacing: var(--frame-spacing, 0);
  color: var(--ink-soft);
  pointer-events: none;
}


/* Time machines and the trip between them -------------------------------- */

html.mode-game .time-machine {
  position: absolute;
  margin: 0;
  padding: 0;
  white-space: pre;
  text-align: left;
  color: var(--ink);
  background: var(--paper);
  border: 0;
  cursor: pointer;
}
html.mode-game .time-machine:hover { color: var(--interact); }
.time-machine-screen { color: var(--accent); font-weight: 700; }

html.mode-game .warp {
  position: fixed;
  inset: 0;
  z-index: 35;
  margin: 0;
  overflow: hidden;
  white-space: pre;
  color: var(--accent-ink);
  background: var(--paper);
  pointer-events: none;
  opacity: 0;
}
html.mode-game .warp[hidden] { display: none; }
/* 900ms is WARP_MS in js/game/index.js; the swap happens at the midpoint. */
html.mode-game .warp.is-on { animation: warp 900ms ease-in-out both; }

@keyframes warp {
  0%   { opacity: 0; transform: translateX(0); }
  35%, 65% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-12ch); }
}

/* The cat ---------------------------------------------------------------- */

html.mode-game .cat {
  position: absolute;
  margin: 0;
  padding: 0;
  white-space: pre;
  text-align: left;
  color: var(--cat);
  background: var(--paper);
  border: 0;
  cursor: pointer;
}
html.mode-game .cat:hover { color: var(--interact); }

html.mode-game .card[data-href] { cursor: pointer; }
html.mode-game .card[data-href]:hover::before { color: var(--interact); }

/* ASCII art stands on top of its card, outside the collider, so the art
   never changes how tall the obstacle is. */
html.mode-game .card:has(> .art) { contain: layout style; }
html.mode-game .card > .art {
  position: absolute;
  bottom: 100%;
  left: 50%;
  translate: -50% 0;
  width: max-content;
  margin: 0;
  white-space: pre;
  font-weight: 700;
  color: var(--ink-soft);
}
html.mode-game .card > .art--logo { color: var(--ink); }
html.mode-game .card > .art .art-solid { background: var(--paper); }
/* In the museum the wall behind is the hallway's beige at the layer's 55%
   opacity, which comes to about 30% of it over paper. */
html.mode-game .region[data-region="history"] .card > .art .art-solid {
  background: color-mix(in srgb, var(--mu-beige) 30%, var(--paper));
}
html.mode-game #work-now .art { color: var(--art-amplitude); }
html.mode-game #project-dossiers .art { color: var(--accent); }
html.mode-game #project-beyond20 .art { color: var(--art-beyond20); }
html.mode-game #project-deepreli .art { color: var(--water); }
html.mode-game #project-aoc .art { color: var(--art-aoc); }
html.mode-game #contact .art { color: var(--accent); }

/* Below the ground the sky fades out and the rock fades in. The marker is
   set by js/game/index.js from the character's depth.
   A faded-out layer is also hidden once the fade ends, so the browser can
   drop its texture: the layers are thousands of px across, and iOS Safari
   runs out of memory holding all of them. */
html.mode-game .ascii-sky { transition: opacity 0.6s ease, visibility 0s; }
html.mode-game :is(.ascii-sky--cave, .ascii-sky--museum),
html.mode-game[data-underground] .ascii-sky:not(.ascii-sky--cave),
html.mode-game[data-museum] .ascii-sky:not(.ascii-sky--museum) {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s 0.6s;
}
html.mode-game[data-underground] .ascii-sky--cave { opacity: 1; visibility: visible; transition: opacity 0.6s ease, visibility 0s; }
html.mode-game[data-museum] .ascii-sky--museum { opacity: 0.55; visibility: visible; transition: opacity 0.6s ease, visibility 0s; }

/* The hallway behind the past: a warm wall, gilt frames, a velvet rope.
   Anything drawn on the wall takes the wall's colour behind it, or each
   glyph shows as a paper box. */
.ascii-sky--museum { --mu-wall: color-mix(in srgb, var(--mu-beige) 55%, var(--paper)); }
.ascii-sky .mu-wall         { color: color-mix(in srgb, var(--mu-tan) 35%, var(--paper)); background: var(--mu-wall); }
.ascii-sky :is(.mu-lamp, .mu-light, .mu-plaque, .mu-rope, .mu-brass) { background: var(--mu-wall); }
.ascii-sky .mu-floor        { color: color-mix(in srgb, var(--mu-taupe) 35%, var(--paper)); background: color-mix(in srgb, var(--mu-cream) 70%, var(--paper)); }
.ascii-sky .mu-ceiling      { color: color-mix(in srgb, var(--mu-tan) 40%, var(--paper)); }
.ascii-sky .mu-trim         { color: color-mix(in srgb, var(--mu-brown) 70%, var(--paper)); background: color-mix(in srgb, var(--mu-beige) 55%, var(--paper)); }
.ascii-sky .mu-pillar       { color: color-mix(in srgb, var(--mu-taupe) 80%, var(--paper)); }
.ascii-sky .mu-pillar-fill  { color: color-mix(in srgb, var(--mu-taupe) 40%, var(--paper)); }
.ascii-sky .mu-frame        { color: var(--mu-gold); font-weight: 700; }
.ascii-sky .mu-canvas       { background: var(--paper); }
.ascii-sky .mu-art          { color: var(--ink-soft); }
.ascii-sky .mu-plaque       { color: var(--mu-plaque); }
.ascii-sky .mu-lamp         { color: var(--ink-soft); }
.ascii-sky .mu-light        { color: var(--mu-light); }
.ascii-sky .mu-brass        { color: var(--mu-gold); font-weight: 700; }
.ascii-sky .mu-rope         { color: var(--mu-rope); font-weight: 700; }
/* Dark rock, with the tunnels carved out of it in a lighter grey. Solid
   cells are the carved ones, apart from stones and gems set in the rock. */
html.mode-game .ascii-sky--cave {
  background: color-mix(in srgb, var(--rock) 15%, var(--paper));
}
.ascii-sky--cave .sky-solid { background: var(--cave-tunnel); }
.ascii-sky--cave :is(.sky-stone, .sky-gem) { background: none; }
.ascii-sky--cave .sky-glow-1 { background: color-mix(in srgb, var(--glow) 18%, var(--cave-tunnel)); }
.ascii-sky--cave :is(.sky-glow-2, .sky-flame, .sky-torch) { background: color-mix(in srgb, var(--glow) 36%, var(--cave-tunnel)); }
.ascii-sky .sky-torch { color: var(--torch); font-weight: 700; }
.ascii-sky .sky-flame { color: var(--flame); font-weight: 700; animation: flicker 0.9s steps(3) infinite alternate; }
@keyframes flicker {
  from { color: var(--flame); }
  to   { color: var(--flame-hot); }
}
@media (prefers-reduced-motion: reduce) { .ascii-sky .sky-flame { animation: none; } }
.ascii-sky .sky-rock  { color: color-mix(in srgb, var(--rock-ink) 28%, var(--paper)); }
.ascii-sky .sky-stone { color: color-mix(in srgb, var(--rock-ink) 40%, var(--paper)); }

.ascii-sky .sky-tunnel { color: color-mix(in srgb, var(--tunnel-ink) 80%, var(--paper)); }
.ascii-sky .sky-wood   { color: color-mix(in srgb, var(--wood) 50%, var(--paper)); }
.ascii-sky .sky-gem    { font-weight: 700; }
.ascii-sky .sky-gem--red   { color: var(--gem-red); }
.ascii-sky .sky-gem--blue  { color: var(--gem-blue); }
.ascii-sky .sky-gem--green { color: var(--gem-green); }
.ascii-sky .sky-tunnel-dark { color: color-mix(in srgb, var(--tunnel-dark) 45%, var(--paper)); }

/* Skill platforms and the telescope -------------------------------------- */

html.mode-game .mover {
  position: absolute;
  margin: 0;
  white-space: pre;
  color: var(--ink);
  background: var(--paper);
  pointer-events: none;
  will-change: transform;
}
html.mode-game .mover::first-line { color: var(--accent); font-weight: 700; }

html.mode-game .telescope {
  position: absolute;
  margin: 0;
  padding: 0;
  white-space: pre;
  text-align: left;
  color: var(--ink);
  background: none;
  border: 0;
  font-weight: 700;
  cursor: pointer;
}
html.mode-game .telescope:hover { color: var(--interact); }
.telescope-brass { color: var(--brass); }

html.mode-game .chain {
  position: absolute;
  margin: 0;
  white-space: pre;
  color: color-mix(in srgb, var(--ink-faint) 55%, var(--paper));
  pointer-events: none;
}
