@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Archivo:wght@400;600&family=Oswald:wght@500&display=swap');

/* Real Futura Condensed Medium for the wake-up subtitle — Oswald above
   stays in the font stack as a fallback in case this file ever goes
   missing, but this is the actual font now. */
@font-face {
  font-family: 'Futura Condensed Medium';
  src: url('fonts/FuturaCondensedMedium.otf') format('opentype');
  font-weight: 500;
  font-style: normal;
}

/* The on-screen-display font from a VCR — the same lineage as the no-signal
   blue the loading screen sits on. font-display: block so the byline never
   flashes in a fallback face first; it's one short line, so waiting on the
   font is better than showing the wrong one. */
@font-face {
  font-family: 'VCR OSD Mono';
  src: url('fonts/VCR_OSD_MONO_1.001.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  background: #0d0b10;
  font-family: var(--ui-font, 'Archivo', sans-serif);
  color: #f2ece2;
  /* Nothing on this page is meant to be copy-selected — it's a 3D scene
     with UI text laid over it, not a document. Without this, dragging to
     look around on a touchscreen can catch the subtitle text underneath
     your finger and pop the native text-selection UI instead of just
     turning the camera. #wake-subtitle already had its own user-select:none,
     but that alone wasn't enough on every mobile browser — setting it here
     on html/body closes the gap site-wide. */
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

#scene {
  position: fixed;
  inset: 0;
  display: block;
  outline: none;
  cursor: grab;
  /* Without this, a tap/drag on the canvas can get read by the browser as
     a pull-to-refresh or pinch-zoom gesture instead of our own click/drag
     handling — on mobile that was reloading the whole page the instant you
     tapped a canvas. The mobile control buttons already had this; the main
     scene canvas itself never did. */
  touch-action: none;
}
#scene:active { cursor: grabbing; }
#scene.hovering,
#scene.hovering-seat { cursor: pointer; }

/* The pixel cursor, in the room as well as the menu — a cursor image can't be
   styled (image-rendering does nothing to it), so the chunkiness is baked
   into the PNG at a 16-cell grid. Pointer devices only: a touchscreen has no
   cursor, and coarse pointers get the OS default. */
@media (hover: hover) and (pointer: fine) {
  html, body, #scene {
    cursor: url("/menu/cursors/logo-default-px.png") 0 0, auto;
  }
  #scene.hovering,
  #scene.hovering-seat,
  #scene.hovering-station,
  button, a, .desk-piece {
    cursor: url("/menu/cursors/logo-pointer-px.png") 0 0, pointer;
  }
}

/* The wordmark face: the real Game Boy 1989 face with every glyph rebuilt as
   squares on a 16-per-em grid — genuinely pixelated outlines, not a filtered
   image, so it stays selectable text and scales cleanly. GameBoy.woff2 next to
   it is the untouched smooth cut; swap the two src lines to go back. */
@font-face {
  font-family: "GameBoy";
  src: url("/menu/fonts/GameBoyPx.woff2") format("woff2"),
       url("/menu/fonts/GameBoyPx.woff") format("woff");
  font-display: block;   /* one short line — better to wait than flash a fallback */
}

/* The byline's own face — the NES Mario type, which is a bitmap face to begin
   with, so it needs no pixelating pass of its own. Subset to the letters and
   digits the line actually uses. */
@font-face {
  font-family: "MarioNES";
  src: url("/menu/fonts/MarioNES.woff2") format("woff2"),
       url("/menu/fonts/MarioNES.woff") format("woff");
  font-display: block;
}

/* Kept for the compass, which still sets in it. */
@font-face {
  font-family: "Pix32 Cond";
  src: url("/menu/fonts/Pix32-Cond.woff2") format("woff2"),
       url("/menu/fonts/Pix32-Cond.woff") format("woff");
  font-display: block;
}
@font-face {
  font-family: "Pix32";
  src: url("/menu/fonts/Pix32.woff2") format("woff2"),
       url("/menu/fonts/Pix32.woff") format("woff");
  font-display: block;
}

/* ---------- loading screen: the Game Boy boot ----------
   Traced off the reference frame by frame, because the first attempt guessed
   and got it wrong in both respects:

   MOTION. The letters do not drop straight down. They swoop in from off the
   RIGHT, enormous and piled over each other, then arc down and left, shrinking
   as they go and overshooting slightly before they settle. Each one starts a
   beat after the one before, so the leading letters are already placed while
   the trailing ones are still large and behind them.

   COLOUR. Not one colour per letter. The whole word runs a single continuous
   hue cycle as it travels — magenta, red, orange, yellow, green, cyan — and
   arrives blue. That's why in the reference every letter on screen is a
   different colour at any instant but they're all clearly the same sweep.

   2026 sits in the Nintendo(R) slot: on screen before anything moves, magenta
   throughout, and deliberately outside the hue cycle. */

:root {
  /* Tuned by hand in tune.html — these are her numbers, not derived ones. */
  --gb-step: 0.07s;   /* gap between one letter setting off and the next */
  --gb-start: 0.35s;   /* how long 2026 sits alone first */
  --gb-fly: 0.8s;      /* how long one letter takes to rise, crest and settle */

  /* Colour. --lead is where the FIRST letter enters the wheel (relative to the
     blue everything ends on), --step is how much further round each next
     letter starts, and --end is the shared finish: a multiple of 360 so it
     resolves back to blue. */
  --gb-hue-lead: 40deg;
  --gb-hue-step: 25deg;
  /* ONE pass round the wheel, not two.
     Every letter travels from its own entry angle to this shared end, so how
     far it goes is (end - entry). At 720 the first letter covered 680deg —
     nearly two full turns — and ran the whole purple/pink/red/yellow/blue
     sequence TWICE, which is what read as too colourful. At 360 it covers
     320deg: one pass, ending on blue.

     THE RULE while tuning: lead + 8 x step must stay BELOW this number, or the
     last letters get a negative distance and spin backwards through the wheel.
     40 + 8x25 = 240, comfortably under 360. */
  --gb-hue-end: 360deg;

  /* The other half of "less colourful": how saturated the letters are while
     they're travelling. They ramp back to full by the time they land, so the
     flight is muted and the finished wordmark still reads as solid blue. */
  --gb-sat: 0.65;

  /* The arc. It doesn't come straight up: it starts below and to the LEFT,
     swings up and to the RIGHT, and on the way round it comes toward you —
     that's the crest scale being the BIGGEST point, not the start. Then it
     falls back and away into its place. Round the front, down into the back. */
  --gb-from-x: -26vw;
  --gb-crest-x: 6vw;

  /* Final wordmark: heavier and tighter than the face sets it. */
  /* The pink orb that crosses the finished wordmark once. */
  --gb-orb-color: #ff6fe0;
  --gb-orb-size: 55%;    /* how wide the glow is, as a share of the word */
  --gb-orb-dur: 1.1s;
  --gb-orb-wait: 0.25s;  /* beat between the last letter landing and the pass */

  --gb-bold: 2px;        /* dilates the glyph without antialiasing it */
  --gb-track: -0.015em;  /* negative tracking = less kerned */
}

#loading-screen {
  position: fixed;
  inset: 0;
  z-index: 50;
  /* Warm cream rather than the old pink-white: the room's first frame is a
     yellow-lit bedroom, and cutting from pink to that read as a colour jump. */
  background: #fbf3da;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity .6s ease;
  overflow: hidden;      /* letters start well off-screen */
}
#loading-screen.hidden { opacity: 0; pointer-events: none; }
#loading-screen.hidden * { pointer-events: none; }

.loading-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.gb-word {
  position: relative;   /* the orb overlay hangs off this */
  display: flex;
  font-family: "GameBoy", "Trebuchet MS", sans-serif;
  font-size: clamp(2.2rem, 10vw, 5rem);
  line-height: 1;
  letter-spacing: var(--gb-track);
  /* Blue, because blue is where every letter ENDS. Each one's hue-rotate
     finishes on a whole number of turns, so the filter resolves to identity
     and what you're left with is this colour. See gb-hue below. */
  color: #2b2fd6;
}

.gb-word span {
  display: inline-block;
  /* Motion and hue on the same delay and the same duration: a letter starts
     travelling and starts changing colour together, and it finishes its cycle
     exactly as it lands. */
  animation:
    gb-wave var(--gb-fly) cubic-bezier(0.3, 0.15, 0.3, 1) both,
    gb-hue var(--gb-fly) linear both;
  animation-delay:
    calc(var(--gb-start) + var(--i) * var(--gb-step)),
    calc(var(--gb-start) + var(--i) * var(--gb-step));
  /* Where this letter ENTERS the colour wheel. Each one starts a step further
     round than the one before — first purple, next pink, and so on — then
     runs the whole wheel as it rises. */
  /* How far above its resting place this letter crests before dropping in —
     and THIS is the wave. Near nothing for the first letter, growing along the
     word, so the placed letters sit low on the left while the arriving ones
     ride higher and higher to the right. */
  --peak: calc(-1vh - var(--i) * 3.7vh);
  --h0: calc(var(--gb-hue-lead) + var(--i) * var(--gb-hue-step));
  /* Bolder by dilation rather than by faux-bold: offset copies of the glyph in
     its own colour thicken every stem by a whole pixel and leave the edges
     hard. Synthesised bold would smear a bitmap face sideways instead. */
  text-shadow:
    var(--gb-bold) 0 0 currentColor,
    0 var(--gb-bold) 0 currentColor,
    var(--gb-bold) var(--gb-bold) 0 currentColor;
  will-change: transform, opacity, filter;
}

/* Up from the bottom, over the top, down into place.
   Every earlier attempt had these coming in from the side or straight down,
   which is why none of them looked right. They rise from below the screen,
   overshoot ABOVE where they belong, and settle back down — and each letter
   crests higher than the one before it (see --peak), so the word ripples into
   existence left to right instead of nine letters doing the same hop. */
@keyframes gb-wave {
  from { opacity: 0; transform: translate(var(--gb-from-x), 52vh) scale(2.2); }
  /* A one-percent fade: it doesn't ease in, it just appears — already well up
     from the bottom and on its way. */
  32%  { opacity: 0; }
  33%  { opacity: 1; }
  /* The front of the loop: furthest right, highest, and LARGEST — this is the
     point where it has swung closest to you. */
  60%  { transform: translate(var(--gb-crest-x), var(--peak)) scale(3.2); }
  to   { opacity: 1; transform: translate(0, 0) scale(1); }
}

/* The orb. A second copy of the word painted pink and shown only through a
   soft travelling window — so it isn't a light drawn ON TOP of the letters,
   it's the letters themselves going pink where it passes, which is what the
   reference does. Sits over the spans, ignores the pointer, runs once, and
   waits until every letter has landed and gone blue. */
.gb-word::after {
  content: attr(data-text);
  position: absolute;
  left: 0;
  top: 0;
  white-space: pre;
  letter-spacing: inherit;
  color: var(--gb-orb-color);
  /* Matches the dilation on the real letters, or the pink copy sits a pixel
     thin inside them and fringes at the edges. */
  text-shadow:
    var(--gb-bold) 0 0 var(--gb-orb-color),
    0 var(--gb-bold) 0 var(--gb-orb-color),
    var(--gb-bold) var(--gb-bold) 0 var(--gb-orb-color);
  pointer-events: none;
  -webkit-mask-image: radial-gradient(ellipse 50% 160% at 50% 50%,
    #000 0%, rgba(0,0,0,0.55) 42%, transparent 74%);
  mask-image: radial-gradient(ellipse 50% 160% at 50% 50%,
    #000 0%, rgba(0,0,0,0.55) 42%, transparent 74%);
  -webkit-mask-size: var(--gb-orb-size) 100%;
  mask-size: var(--gb-orb-size) 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  animation: gb-orb var(--gb-orb-dur) ease-in-out both;
  /* Starts once the last letter has settled: its delay, plus its flight, plus
     a beat. */
  animation-delay: calc(var(--gb-start) + 8 * var(--gb-step) + var(--gb-fly) + var(--gb-orb-wait));
}
/* A percentage mask-position is measured against (container - mask), not the
   container, so with a mask 55% wide, 170% only moves it 76% across and the
   orb was still sitting on the last two letters when it stopped. Clearing the
   word completely needs 100/45 = 222%, so it runs -140% to 240% and passes
   right off both ends. */
@keyframes gb-orb {
  from { visibility: hidden; -webkit-mask-position: -140% 50%; mask-position: -140% 50%; }
  1%   { visibility: visible; }
  to   { visibility: visible; -webkit-mask-position: 240% 50%; mask-position: 240% 50%; }
}
/* No mask support means no orb — better nothing than a solid pink word.
   Both spellings have to be tested: iOS Safari before 15.4 only ever had the
   -webkit- form, so testing the unprefixed property alone reported "no mask
   support" on exactly the phones that do support it, and hid the orb. */
@supports not ((mask-image: radial-gradient(#000, #000)) or
               (-webkit-mask-image: radial-gradient(#000, #000))) {
  .gb-word::after { display: none; }
}

/* Every letter travels to the SAME end angle, and that angle is a whole
   number of turns — so however far round it started, it resolves to zero
   rotation and lands on the base blue. Because the start differs per letter
   and the end doesn't, each one covers a different arc, and every arc is more
   than a full turn: at 1080deg even the last letter (starting furthest round)
   still goes the whole way round the wheel before it settles. */
@keyframes gb-hue {
  from { filter: hue-rotate(var(--h0)) saturate(var(--gb-sat)); }
  to   { filter: hue-rotate(var(--gb-hue-end)) saturate(1); }
}

.gb-sub {
  /* Pulled out of the centred stack and parked near the bottom of the screen,
     and no entrance of its own — it is simply already there when the page
     paints, the way the console's own byline is. */
  position: absolute;
  left: 0;
  right: 0;
  bottom: max(9vh, env(safe-area-inset-bottom, 0px));
  font-family: "MarioNES", "Pix32 Cond", "Futura Condensed", sans-serif;
  font-size: clamp(0.78rem, 2.5vw, 1.1rem);
  letter-spacing: 0.12em;
  text-indent: 0.12em;  /* offsets the trailing letter-space, so it stays centred */
  color: #16130f;
}

@media (prefers-reduced-motion: reduce) {
  .gb-word span { animation: none; }
  .gb-word::after { animation: none; opacity: 0; }
  .gb-word { animation: none; }
}

/* Kept in the DOM but normally empty: only ever written to if the model fails
   to load, so a failure still says so instead of leaving you on a blank page. */
.loading-sub {
  margin-top: 1em;
  font-family: 'VCR OSD Mono', 'Courier New', monospace;
  letter-spacing: .1em;
  font-size: .8rem;
  color: #8a2b2b;
}

/* ---------- intro overlay (start menu) ---------- */
#intro-overlay {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 40px;
  transition: opacity .4s ease, visibility .4s ease;
}
#intro-overlay.hidden { opacity: 0; visibility: hidden; pointer-events: none; }

.menu-question {
  font-family: var(--ui-font, 'Helvetica Neue', Helvetica, Arial, sans-serif);
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: 1.5rem;
  font-weight: 700;
  color: #f2ece2;
  text-align: center;
  padding: 0 24px;
}

.menu-options {
  display: flex;
  gap: 52px;
}

.menu-option {
  font-family: var(--ui-font, 'Helvetica Neue', Helvetica, Arial, sans-serif);
  text-transform: uppercase;
  letter-spacing: .1em;
  font-size: .9rem;
  font-weight: 600;
  background: none;
  border: none;
  border-bottom: 1px solid transparent;
  color: #f2ece2;
  opacity: .7;
  cursor: pointer;
  padding: 6px 2px;
  transition: opacity .15s ease, border-color .15s ease;
}
.menu-option:hover { opacity: 1; border-bottom-color: #ffce6b; }

.menu-note {
  font-family: var(--ui-font, 'Helvetica Neue', Helvetica, Arial, sans-serif);
  font-size: .78rem;
  letter-spacing: .04em;
  color: #f2ece2;
  opacity: .5;
  transition: opacity .3s ease;
}
.menu-note.hidden { opacity: 0; }

/* ---------- eyelids ----------
   Two solid black planes that fully cover the screen (meeting edge to
   edge). The camera snaps into the chair while they're still shut, and the
   "flat portfolio" / "get up" menu comes up right on top of this same
   black screen. Only once "get up" is clicked do they blink a few times (a
   quick partial parting via .blink, using this fast default transition)
   before the real reveal — at which point .slow-open swaps in the long
   cinematic transition below and .open takes them all the way, uncovering
   the seated view underneath. */
#eyelid-top, #eyelid-bottom {
  position: fixed;
  left: 0;
  right: 0;
  height: 50vh;
  background: #000;
  z-index: 40;
  transition: transform 0.22s ease;
}
#eyelid-top.slow-open, #eyelid-bottom.slow-open {
  transition: transform 1.2s cubic-bezier(0.65, 0, 0.35, 1);
}
#eyelid-top { top: 0; transform: translateY(0); }
#eyelid-bottom { bottom: 0; transform: translateY(0); }
#eyelid-top.blink { transform: translateY(-22%); }
#eyelid-bottom.blink { transform: translateY(22%); }
#eyelid-top.open { transform: translateY(-100%); }
#eyelid-bottom.open { transform: translateY(100%); }
#eyelid-top.done, #eyelid-bottom.done { pointer-events: none; }

/* ---------- wake-up subtitle (Skyrim-style, grey Futura-Condensed-ish) ---------- */
/* Oswald is the free stand-in for Futura Condensed Medium (that exact
   commercial font isn't available on a web font CDN) — same geometric,
   condensed, medium-weight look the reference style is going for. Shown
   once, right as the eyelids finish opening (see showWakeSubtitles in
   main.js), never replayed on repeat sit-downs. */
#wake-subtitle {
  position: fixed;
  left: 50%;
  bottom: 9%;
  transform: translateX(-50%);
  z-index: 42;
  font-family: var(--ui-font, 'Futura Condensed Medium', 'Oswald', Arial, sans-serif);
  font-weight: 500;
  font-size: 1.4rem;
  letter-spacing: .015em;
  color: #ffffff;
  text-shadow: 0 2px 5px rgba(0,0,0,0.48), 0 0 14px rgba(0,0,0,0.33);
  text-align: center;
  opacity: 0;
  transition: opacity .5s ease;
  pointer-events: none;
  max-width: 92vw;
  padding: 0 16px;
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  -webkit-touch-callout: none; /* iOS long-press menu (Copy/Look Up) on the text itself */
}
#wake-subtitle.show { opacity: 1; }
#wake-subtitle .wake-name { color: #9a9a9a; }

/* Every subtitle line (wake-up, stand-up, posters, vinyl, shirts) renders
   through this one element/rule, so this single breakpoint keeps them all
   at the exact same scaled-down size on phones — same timing as desktop,
   just smaller and narrower so long lines wrap instead of overflowing. */
@media (max-width: 700px) {
  #wake-subtitle {
    /* Full-width bar flush against the true bottom edge instead of a
       floating centered line — reads like a real subtitle track instead
       of text just sitting in the middle of the screen. The d-pad
       (#mobile-controls below) is pushed up just enough to clear this
       bar's height instead of guessing a fixed gap. */
    left: 0;
    right: 0;
    bottom: 0;
    transform: none;
    width: 100%;
    max-width: 100%;
    background: linear-gradient(to top, rgba(8,6,10,0.62), rgba(8,6,10,0));
    font-size: 1.05rem;
    padding: 16px 20px calc(14px + env(safe-area-inset-bottom, 0px));
    box-sizing: border-box;
  }
}

/* ---------- on-screen mobile move buttons ---------- */
/* Hidden by default (and on any device with a real mouse/trackpad — the
   "hover: none, pointer: coarse" pair below is the standard way to detect
   touch-primary screens) so desktop players never see a d-pad they don't
   need. Laid out as a 3x3 grid so w/a/s/d land in their natural compass
   positions without hand-placing four separate offsets. */
/* WASD d-pad, touch devices only.
   Position is the joystick's — low in the bottom-left corner, rather than the
   old 68px-up spot that put it awkwardly high on the screen.
   env(safe-area-inset-*) keeps it clear of the home indicator on a notched
   phone without hard-coding a gap on phones that don't have one. */
#mobile-controls {
  position: fixed;
  left: max(20px, env(safe-area-inset-left, 0px));
  bottom: max(20px, env(safe-area-inset-bottom, 0px));
  z-index: 20;
  display: none;
  grid-template-columns: repeat(3, 42px);
  grid-template-rows: repeat(3, 42px);
  gap: 6px;
  opacity: 0;
  transition: opacity .6s ease 1s;
  pointer-events: none;
  touch-action: none;
}
#mobile-controls.show { opacity: .85; pointer-events: auto; }
@media (hover: none) and (pointer: coarse) {
  #mobile-controls { display: grid; }
}
/* 8-bit d-pad.
   Square corners, a hard two-tone bevel instead of a hairline, and arrows
   drawn as actual pixel grids rather than font glyphs — a Unicode triangle is
   a smooth vector shape and would have stayed smooth no matter what else
   changed around it. */
.mc-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #17141c;
  border: 0;
  border-radius: 0;
  /* NES-style bevel: light from the top-left, shadow to the bottom-right,
     all hard steps — no gradients, no blur. */
  box-shadow:
    inset 3px 3px 0 0 rgba(255, 255, 255, 0.22),
    inset -3px -3px 0 0 rgba(0, 0, 0, 0.55),
    0 0 0 3px rgba(10, 8, 13, 0.75);
  color: #f2ece2;
  /* The label text stays in the DOM for screen readers; the visible arrow is
     drawn by ::before below. */
  font-size: 0;
  line-height: 1;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

/* A clean geometric triangle, clipped rather than built out of pixel cells.
   The stepped 1-3-5-7 grid version read as chunky against everything else on
   screen; a hard-edged solid triangle keeps the same flat, constructed feel
   without the staircase. The square bevelled key around it is what carries the
   8-bit character — the arrow just has to be a crisp shape.

   One shape, rotated for the other three, so all four stay identical. */
.mc-btn::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 12px;
  margin: -6px 0 0 -8px;
  background: currentColor;
  clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
  transform-origin: 50% 50%;
}
.mc-w::before { transform: rotate(0deg); }
.mc-d::before { transform: rotate(90deg); }
.mc-s::before { transform: rotate(180deg); }
.mc-a::before { transform: rotate(270deg); }

/* Pressed: the bevel inverts, so the key reads as pushed in. */
.mc-btn:active {
  background: #3a2f16;
  color: #ffce6b;
  box-shadow:
    inset 3px 3px 0 0 rgba(0, 0, 0, 0.55),
    inset -3px -3px 0 0 rgba(255, 255, 255, 0.18),
    0 0 0 3px rgba(10, 8, 13, 0.75);
}

.mc-w { grid-column: 2; grid-row: 1; }
.mc-a { grid-column: 1; grid-row: 2; }
.mc-s { grid-column: 2; grid-row: 2; }
.mc-d { grid-column: 3; grid-row: 2; }

/* ---------- on-screen mobile prev/next (vinyl crate, closet rack) ---------- */
/* Same touch-only gating as #mobile-controls above, but shown/hidden by JS
   (via the .show class) rather than always-on — only makes sense while
   you're actually focused on something browsable. */
#mobile-cycle-controls {
  position: fixed;
  right: 18px;
  /* matches #mobile-controls' new bottom offset, so both button clusters
     sit at the same height above the full-width subtitle bar */
  bottom: 68px;
  z-index: 20;
  display: none;
  gap: 14px;
  opacity: 0;
  transition: opacity .25s ease;
  pointer-events: none;
  touch-action: none;
}
#mobile-cycle-controls.show { opacity: .85; pointer-events: auto; }
@media (hover: none) and (pointer: coarse) {
  #mobile-cycle-controls { display: flex; }
}
.mc-cycle-btn {
  width: 54px;
  height: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10,8,13,0.55);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 12px;
  color: #f2ece2;
  font-size: 1.8rem;
  line-height: 1;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.mc-cycle-btn:active { background: rgba(255,206,107,0.3); }

/* ---------- lightbox ---------- */
#lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity .3s ease, visibility .3s ease;
}
#lightbox.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
#lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8,6,10,0.82);
  backdrop-filter: blur(4px);
}
#lightbox-card {
  position: relative;
  z-index: 2;
  display: flex;
  gap: 28px;
  align-items: center;
  max-width: 90vw;
  max-height: 86vh;
  padding: 20px;
}
#lightbox-media {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #1a1520;
  border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
  padding: 14px;
}
#lightbox-canvas {
  width: min(52vw, 420px);
  height: min(52vw, 420px);
  border-radius: 4px;
  display: block;
}
#lightbox-info {
  width: 260px;
  text-align: left;
}
#lightbox-kicker {
  font-size: .72rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #ffce6b;
  margin-bottom: 6px;
  opacity: .9;
}
#lightbox-title {
  font-family: var(--ui-font, 'Space Grotesk', sans-serif);
  font-size: 1.4rem;
  margin: 0 0 10px;
}
#lightbox-desc {
  font-size: .88rem;
  line-height: 1.55;
  opacity: .75;
  margin: 0;
}
#lightbox-close, #lightbox-prev, #lightbox-next {
  position: absolute;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.15);
  color: #f2ece2;
  cursor: pointer;
  border-radius: 50%;
  width: 40px;
  height: 40px;
  font-size: 1.4rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .15s ease;
}
#lightbox-close:hover, #lightbox-prev:hover, #lightbox-next:hover {
  background: rgba(255,206,107,0.25);
}
#lightbox-close { top: -6px; right: -6px; }
#lightbox-prev { left: -50px; top: 50%; transform: translateY(-50%); }
#lightbox-next { right: -50px; top: 50%; transform: translateY(-50%); }

@media (max-width: 720px) {
  #lightbox-card { flex-direction: column; max-height: 92vh; overflow-y: auto; }
  #lightbox-canvas { width: min(80vw, 320px); height: min(80vw, 320px); }
  #lightbox-info { width: min(80vw, 320px); text-align: center; }
  #lightbox-prev { left: 6px; }
  #lightbox-next { right: 6px; }
}

/* ---------- crate flippers ----------
   Shown only while a record is pulled out of the crate. Sits low and centred,
   clear of the d-pad in the bottom-left and the MENU button top-right. */
#vinyl-nav {
  position: fixed;
  left: 50%;
  bottom: max(6vh, env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%) translateY(8px);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 1.1rem;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .28s ease, transform .28s ease, visibility 0s linear .28s;
}
#vinyl-nav.show {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
  transition: opacity .28s ease, transform .28s ease, visibility 0s;
}
#vinyl-nav button {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
  color: #fff;
  background: rgba(8, 8, 6, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.34);
  backdrop-filter: blur(3px);
  transition: background .18s ease, transform .18s ease;
}
#vinyl-nav button:hover,
#vinyl-nav button:focus-visible {
  background: rgba(8, 8, 6, 0.68);
  transform: scale(1.06);
}
#vinyl-nav button:active { transform: scale(0.96); }
#vinyl-nav svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: square;
  stroke-linejoin: miter;
}
#vinyl-count {
  min-width: 4.5ch;
  text-align: center;
  font-family: 'VCR OSD Mono', 'Courier New', monospace;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.75);
  user-select: none;
}
/* Bigger tap targets on a phone, and lifted clear of the d-pad. */
@media (max-width: 860px) {
  #vinyl-nav {
    bottom: max(11vh, env(safe-area-inset-bottom, 0px));
    gap: 1.4rem;
  }
  #vinyl-nav button { width: 52px; height: 52px; }
  #vinyl-nav svg { width: 24px; height: 24px; }
}

:root {
  /* Height of the caption footer on touch. The d-pad is lifted by exactly
     this, so the two stack instead of overlapping — see the touch block at the
     end of the toefu-line section. Declared on the root because both elements
     need it and they are siblings, not nested. */
  --room-footer: 2.5rem;
}

/* ---------------------------------------------------------------- toefu line
   The caption strip along the bottom of explore mode: TOEFU: "…". One line at
   a time, said on arrival and on every clickable thing — see js/lines.js,
   which holds all the copy.

   No panel or scrim behind it. The room underneath is bright and busy, so
   legibility comes from a hard double shadow on the type itself, the same way
   the compass labels hold up over the live view. A bar would read as HUD; this
   reads as subtitles, which is the intent.

   pointer-events: none throughout — it spans the full width and sits low, so
   without this it would swallow taps meant for the d-pad or the room. */
.toefu-line {
  position: fixed;
  left: 0;
  right: 0;
  bottom: max(1.6rem, env(safe-area-inset-bottom, 0px));
  /* Above the station and pickup overlays (40) so it can caption them, below
     the pause menu (50), which hides it outright. */
  z-index: 45;
  display: block;
  padding: 0 clamp(1rem, 4vw, 3rem);
  pointer-events: none;
  color: #ffffff;
  font-family: "Pix32 Cond", "Pix32", "Futura Condensed", sans-serif;
  font-size: clamp(0.72rem, 1.5vw, 0.95rem);
  letter-spacing: 0.06em;
  line-height: 1.3;
  text-align: center;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.85), 0 0 10px rgba(0, 0, 0, 0.7);
  -webkit-font-smoothing: none;
  font-synthesis: none;
  opacity: 0;
  transform: translateY(0.4rem);
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.toefu-line.is-on {
  opacity: 1;
  transform: translateY(0);
}
.toefu-line-name {
  margin-right: 0.45em;
  font-weight: 700;
  letter-spacing: 0.12em;
}
/* Wraps rather than truncating. These lines are written to be read, and an
   ellipsis would cut one off mid-sentence on a phone. The measure lives on the
   block below rather than on the quote itself, so the name stays inline with
   the first row instead of being pushed above it. */
.toefu-line-inner {
  max-width: min(96vw, 62rem);
  margin: 0 auto;
}

/* On touch the line becomes a footer: pinned to the very bottom edge, full
   width, with the d-pad stacked directly on top of it. Lifting the pad by
   exactly the footer's own height is what keeps the two from ever crossing —
   the pad is 3x42px plus gaps in the bottom-left, so any value that merely
   "clears" it by guesswork lands the line in the middle of the arrows, which
   is what it was doing. */
@media (hover: none) and (pointer: coarse) {
  .toefu-line {
    bottom: 0;
    min-height: var(--room-footer);
    padding: 0.35rem 0.9rem calc(0.35rem + env(safe-area-inset-bottom, 0px));
  }
  #mobile-controls {
    bottom: calc(max(20px, env(safe-area-inset-bottom, 0px)) + var(--room-footer));
  }
  /* The prompt sits above the pad rather than in the footer, so the footer
     stays one line of type and nothing has to move to make room. */
  .toefu-ask {
    bottom: calc(var(--room-footer) + 0.5rem + env(safe-area-inset-bottom, 0px));
  }
  /* Re-stated at the same specificity as the desktop rule below, which would
     otherwise lift the line off the bottom edge when a question is up. */
  body:has(.toefu-ask.is-on) .toefu-line { bottom: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .toefu-line { transition: none; transform: none; }
}

/* An open item preview puts a card at the bottom of the screen. The line sits
   just above it rather than behind it — the height comes from stations.js,
   which measures the card after layout because its stats row wraps differently
   per item. More specific than the touch rule above, so it wins on a phone
   too. */
body.has-pickup .toefu-line {
  bottom: calc(
    max(3vh, env(safe-area-inset-bottom, 0px)) + var(--pickup-card-h, 7rem) + 0.9rem
  );
}

/* ---------------------------------------------------------------- room HUD
   The corner pickup toast, the yes/no prompt under the line, and the joint's
   screen effect. All of it belongs to explore mode and all of it is removed
   again when it's done — nothing here persists. */

/* Skyrim's pickup notice: small, top-left, gone in a couple of seconds. */
.toefu-toast {
  position: fixed;
  top: max(1rem, env(safe-area-inset-top, 0px));
  left: max(1rem, env(safe-area-inset-left, 0px));
  z-index: 46;
  padding: 0.4rem 0.75rem;
  pointer-events: none;
  background: rgba(12, 12, 16, 0.72);
  color: #ffffff;
  font-family: "Pix32 Cond", "Pix32", "Futura Condensed", sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  -webkit-font-smoothing: none;
  font-synthesis: none;
  opacity: 0;
  transform: translateY(-0.3rem);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.toefu-toast.is-on { opacity: 1; transform: translateY(0); }

/* Sits directly under the caption line, so the question and the answer read as
   one thing. Same bottom maths as .toefu-line, offset by its own height. */
.toefu-ask {
  position: fixed;
  left: 0;
  right: 0;
  bottom: max(1.6rem, env(safe-area-inset-bottom, 0px));
  z-index: 46;
  display: flex;
  justify-content: center;
  gap: 0.6rem;
  padding: 0 1rem;
  opacity: 0;
  transform: translateY(0.4rem);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.toefu-ask.is-on { opacity: 1; transform: translateY(0); }
.toefu-ask button {
  padding: 0.45rem 1.1rem;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 999px;
  background: rgba(12, 12, 16, 0.6);
  color: #ffffff;
  font-family: "Pix32 Cond", "Pix32", "Futura Condensed", sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  -webkit-font-smoothing: none;
  transition: background 0.18s ease, border-color 0.18s ease;
}
.toefu-ask button:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: #ffffff;
}
/* With a question up, the line moves off the very bottom to make room. */
body:has(.toefu-ask.is-on) .toefu-line { bottom: max(4.4rem, calc(env(safe-area-inset-bottom, 0px) + 2.8rem)); }

/* ---- the joint --------------------------------------------------------
   Warmth and drift, not a filter stack: the canvas gets a gentle saturate and
   a hair of blur (both compositor-side, so the render loop is untouched), and
   one overlay carries the colour wash and the motes. It all comes off after a
   few seconds — see runHighEffect in js/lines.js. */
body.is-high #scene {
  filter: saturate(1.45) contrast(0.94) blur(0.4px);
  transition: filter 1.1s ease;
}
#scene { transition: filter 1.4s ease; }

.high-fx {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s ease;
  background:
    radial-gradient(ellipse at 22% 28%, rgba(255, 120, 200, 0.16), transparent 55%),
    radial-gradient(ellipse at 78% 66%, rgba(120, 220, 255, 0.16), transparent 55%),
    radial-gradient(ellipse at 50% 90%, rgba(255, 220, 120, 0.13), transparent 60%);
  animation: high-drift 9s ease-in-out infinite alternate;
}
.high-fx.is-on { opacity: 1; }
@keyframes high-drift {
  from { background-position: 0% 0%, 0% 0%, 0% 0%; }
  to   { background-position: 6% -4%, -5% 5%, 3% -3%; }
}
/* Motes. Square, like everything else here — a round blurred dot would be the
   one soft-edged thing on the screen. */
.high-fx i {
  position: absolute;
  width: var(--s, 4px);
  height: var(--s, 4px);
  background: #fff;
  box-shadow: 0 0 8px rgba(255, 255, 255, 0.9);
  opacity: 0;
  animation: high-mote var(--d, 3s) ease-in-out var(--o, 0s) infinite;
}
@keyframes high-mote {
  0%, 100% { opacity: 0; transform: translateY(0); }
  50% { opacity: 0.85; transform: translateY(-14px); }
}

@media (prefers-reduced-motion: reduce) {
  .high-fx { animation: none; }
  .high-fx i { animation: none; opacity: 0.5; }
  .toefu-toast, .toefu-ask { transition: none; }
}
