/* Chrono Adventure — watercolor storybook UI chrome.
   Placeholder gradients stand in for backgrounds/portraits until the
   art pass lands; every image tag degrades gracefully on 404. */

:root {
  --ink: #2b2138;
  --paper: #f7ecd9;
  --paper-dark: #e8d9bb;
  --paper-line: #c9b790;
  --accent-warm: #d97f4c;
  --accent-cool: #4c7fd9;
  --accent-1672: #8a5a2b;
  --gold: #c9a227;
  --danger: #a33b3b;
  --good: #3b8a5a;
  font-size: 16px;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  display: flex; align-items: center; justify-content: center;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  /* Near-black behind the scene so any sliver the blurred backdrop doesn't
     reach reads as a deliberate cinematic frame, never a purple void. */
  background: #0a0710;
  color: var(--ink);
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
}
/* Mobile browsers resize their address-bar chrome in and out of the layout
   viewport, so a plain 100vh is taller than what's actually visible and
   bottom-anchored UI (the dialogue box, trigger buttons) gets cut off
   under the chrome. --vh is set from JS (window.innerHeight, see main.js)
   as a fallback for browsers without dvh support; dvh wins wherever it's
   understood. */
#app {
  position: relative; width: 100vw; overflow: hidden;
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  /* Scenes are 3:2 (1.5). Cap the play area's aspect so wide/landscape screens
     letterbox with side bars instead of cover-cropping ~25% off the top and
     bottom (which hid ceiling hotspots like the gallery camera). Height stays
     full so overlays keep their room; only the width is capped. */
  max-width: calc(100vh * 1.6);
  max-width: calc(var(--vh, 1vh) * 160);
  max-width: calc(100dvh * 1.6);
  margin: 0 auto;
}

/* THE ROTATE GATE — a 3:2 landscape game cannot be readable in a phone's tall
   portrait band, so on PHONES held in portrait (min viewport side < 560px) a
   friendly full-screen prompt asks for landscape. main.js toggles
   `body.needs-rotate` on resize/orientation change; tablets and desktops are
   never gated. This replaced both the shrink-to-fit scaling (unreadable) and the
   dismissible pill (players ended up stuck in a tiny UI). */
.rotate-notice {
  position: fixed; inset: 0; z-index: 200;
  display: none; flex-direction: column; align-items: center; justify-content: center;
  gap: 1.1rem; padding: 2rem; text-align: center;
  background: radial-gradient(circle at 50% 40%, #322747 0%, #17102a 75%);
  color: #f3e7cf;
}
body.needs-rotate .rotate-notice { display: flex; }
.rotate-ico {
  width: clamp(96px, 30vw, 150px); height: auto; color: var(--gold);
  animation: rotate-hint 2.6s ease-in-out infinite;
}
.rotate-notice p { margin: 0; max-width: 22rem; font-size: 1.15rem; font-weight: 700; line-height: 1.45; }
@keyframes rotate-hint {
  0%, 55%, 100% { transform: rotate(0deg); }
  75%, 92% { transform: rotate(-90deg); }
}
@media (prefers-reduced-motion: reduce) { .rotate-ico { animation: none; } }
img { max-width: 100%; display: block; }
button { font-family: inherit; cursor: pointer; }

/* ---------- buttons ---------- */
.btn {
  background: url('../assets/img/ui_button_plaque.webp') center/100% 100% no-repeat;
  border: none;
  border-radius: 0;
  padding: 0.75em 1.8em;
  font-size: 1rem;
  font-weight: 700;
  color: #3a2a12;
  text-shadow: 0 1px 1px rgba(255,255,255,0.35);
  filter: drop-shadow(0 3px 3px rgba(0,0,0,0.35));
  transition: transform 0.08s ease, filter 0.08s ease;
}
.btn:hover:not(:disabled) { transform: translateY(-2px) scale(1.03); filter: drop-shadow(0 5px 5px rgba(0,0,0,0.4)) brightness(1.04); }
.btn:active:not(:disabled) { transform: translateY(1px) scale(0.99); filter: drop-shadow(0 2px 2px rgba(0,0,0,0.3)); }
.btn:disabled { opacity: 0.5; cursor: default; }
.btn-primary {
  background-image: url('../assets/img/ui_button_plaque_primary.webp');
  color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}
/* The plaque art's brass/wood frame occupies ~12% of the stretched width per
   side, so small buttons need generous side padding or long labels (hint
   buttons with coin costs) print over the ornament. nowrap keeps the label
   one line so the plaque never grows tall and thin. */
.btn-sm { padding: 0.5em 1.9em; font-size: 0.85rem; white-space: nowrap; }

/* inline icon assets (replace emoji) */
.ui-icon { height: 1.3em; width: auto; vertical-align: -0.3em; display: inline-block; }
.ui-icon.icon-inline { height: 1.1em; vertical-align: -0.2em; }
.btn-arrow-back .ui-icon, .btn-arrow-next .ui-icon { height: 0.9em; }
.btn-arrow-back { display: inline-flex; align-items: center; gap: 0.35em; flex-direction: row-reverse; }
.btn-arrow-next { display: inline-flex; align-items: center; gap: 0.35em; }

/* ---------- title screen ---------- */
.title-screen {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 1.5rem; text-align: center; padding: 4vh 2rem 8vh;
}
/* Two layers split FROM the single agreed-on title_hero illustration
   (see tools/gen_title_layers.py) — far: the full canal/sky/clock scene
   with the sisters inpainted out; near: just the sisters, cut to alpha —
   both the same 1536x1024 canvas, so they stay perfectly aligned and
   together reconstruct the original at rest. Drifting them at slightly
   different speeds sells a parallax panning depth illusion. */
.title-parallax {
  position: absolute; inset: 0; z-index: -1; overflow: hidden;
  background: #10141f;
}
.title-layer {
  position: absolute; inset: -5% -14%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  will-change: transform;
}
.title-layer-far {
  background-image: url('../assets/img/title_bg_far.webp');
  animation: title-pan-far 20s linear infinite alternate;
}
.title-layer-near {
  background-image: url('../assets/img/title_fg_near.webp');
  animation: title-pan-near 20s linear infinite alternate;
}
.title-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(20,15,26,0.18) 0%, rgba(20,15,26,0.08) 45%, rgba(20,15,26,0.62) 100%);
}
/* Default motion for every parallax scene (title, intro, cinematic): a slow
   horizontal drift PLUS a gentle ken-burns zoom-in. The zoom is what sells
   "alive" without lifting any grounded prop off its surface — vertical
   translation is what made foreground cutouts read as floating, so it's
   reserved for deliberate lift-off scenes (see .intro-parallax.vertical). */
@keyframes title-pan-far {
  from { transform: translateX(-6%) scale(1.03); }
  to { transform: translateX(6%) scale(1.08); }
}
@keyframes title-pan-near {
  from { transform: translateX(-11%) scale(1.05); }
  to { transform: translateX(11%) scale(1.11); }
}
/* ---------- introduction sequence ---------- */
/* Nine short scenes shown once before Chapter 1 (New Game only). Real two-
   layer parallax, same technique and same two-speed pattern as the title
   screen (see tools/gen_intro_layers.py): a far background plate with the
   near-field subject inpainted OUT, and a foreground cutout with ONLY that
   subject, transparent everywhere else, aligned to the same canvas — the
   two layers drift at different speeds to sell real depth. Most scenes pan
   horizontally; a `vertical` modifier (see ui.js's INTRO_SCENES `axis`
   field) swaps to a vertical drift for portrait-framed compositions (a
   tall workbench, a tall cabinet, a tall display case). */
.intro-screen {
  position: absolute; inset: 0; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  padding: 4vh 2rem 8vh;
}
.intro-parallax { position: absolute; inset: 0; z-index: -1; overflow: hidden; background: #10141f; }
.intro-layer {
  position: absolute; inset: -5% -14%;
  background-repeat: no-repeat; background-position: center; background-size: cover;
  will-change: transform;
}
.intro-layer-far { animation: title-pan-far 20s linear infinite alternate; }
.intro-layer-near { z-index: 1; animation: title-pan-near 20s linear infinite alternate; }
.intro-parallax.vertical .intro-layer { inset: -14% -5%; }
.intro-parallax.vertical .intro-layer-far { animation-name: title-pan-far-v; }
/* The vertical variant is now RESERVED for deliberate lift-off / landing
   moments (something rising or descending, e.g. levitation). Here the near
   subject genuinely rises up out of frame while zooming — the lift IS the
   point, so it doesn't read as an accidental float. Do not use axis:'v' for
   ordinary grounded scenes; use the default horizontal pan + zoom instead. */
.intro-parallax.vertical .intro-layer-near {
  animation-name: intro-fg-rise-v;
  transform-origin: 50% 80%;
}
.intro-mist {
  position: absolute; inset: -5% -14%; z-index: 2;
  background: radial-gradient(ellipse at 50% 100%, rgba(20,15,26,0.05) 0%, rgba(20,15,26,0.4) 70%, rgba(20,15,26,0.75) 100%);
  pointer-events: none;
}
@keyframes title-pan-far-v {
  from { transform: translateY(-6%); }
  to { transform: translateY(6%); }
}
@keyframes intro-fg-rise-v {
  from { transform: translateY(5%) scale(1.02); }
  to { transform: translateY(-7%) scale(1.09); }
}
.intro-caption {
  background: rgba(20,15,26,0.85); color: var(--paper);
  border: 1px solid var(--gold); border-radius: 12px;
  padding: 1.2em 1.6em; max-width: 42rem; text-align: center;
  font-size: 1.15rem; line-height: 1.5;
  /* Long narration on a short screen must never clip: as a flex item with
     min-height:0 the card shrinks to the space the band actually has and the
     text SCROLLS inside (thin themed scrollbar); ui.js walks it down gently
     (auto-scroll) until the player touches it. Full text always reachable. */
  min-height: 0; overflow-y: auto;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  scrollbar-width: thin; scrollbar-color: rgba(243,231,207,0.45) transparent;
}
.intro-caption::-webkit-scrollbar { width: 7px; }
.intro-caption::-webkit-scrollbar-thumb { background: rgba(243,231,207,0.45); border-radius: 999px; }
.intro-caption::-webkit-scrollbar-track { background: transparent; }
/* On the intro only, keep the caption's top clear of the Skip pill. */
.intro-screen .intro-caption { margin-top: calc(2vh + 3.4rem); }

/* ---------- cinematic insert (mid-chapter parallax beat) ---------- */
/* Same full-screen 2-3 layer parallax as the intro sequence, dropped into
   a chapter at a strategic story moment instead of only playing once at
   the very start — see dialogue.js's `cinematic` beat type. */
.cinematic-screen {
  position: absolute; inset: 0; cursor: pointer; z-index: 30;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  padding: 4vh 2rem 8vh;
}
.cinematic-caption .dialogue-name { text-align: center; }
.cinematic-caption.narrator { font-style: italic; }
.intro-skip {
  position: absolute; top: 2vh; right: 2vh; z-index: 5;
  background: rgba(20,15,26,0.7); color: var(--paper); border: 1px solid var(--paper);
  border-radius: 999px; padding: 0.5em 1.2em; font-weight: 600;
}
.intro-dots { display: flex; gap: 0.5rem; margin-top: 1rem; }
.intro-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: rgba(255,255,255,0.3); }
.intro-dot.active { background: var(--gold); }

/* Illustrated calligraphy wordmark (a real painted asset, not a font) —
   one image per language since the subtitle line is translated. */
.title-logo {
  position: absolute; top: 6vh; left: 50%; transform: translateX(-50%);
  /* Sized against BOTH axes: width for desktops, max-height for short
     viewports — the old 80%-width logo covered nearly the whole screen on
     phone-landscape. object-fit keeps the art proportional inside. */
  width: min(60%, 30rem); height: auto;
  max-height: 32vh; object-fit: contain; object-position: top center;
  filter: drop-shadow(0 6px 16px rgba(0,0,0,0.55));
}
.title-buttons { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; }

/* Language row INSIDE the settings panel (the title screen uses the pill
   selector below instead). */
.lang-row { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
.lang-btn {
  background: var(--paper-dark); border: 2px solid var(--ink); border-radius: 8px;
  padding: 0.4em 0.9em; font-weight: 600;
}
.lang-btn.active { background: var(--gold); }

/* Language selector: one quiet pill; tapping it opens a small list that
   unfolds UPWARD (the pill sits near the bottom edge). Replaced the old
   three-button row, which read as clutter under the main menu. */
.lang-select { position: relative; }
.lang-current {
  background: rgba(20,15,26,0.55); color: var(--paper);
  border: 1px solid rgba(255,255,255,0.35); border-radius: 999px;
  padding: 0.35em 1.1em; font-weight: 600; font-size: 0.95em;
  backdrop-filter: blur(4px); cursor: pointer;
}
.lang-current:hover { border-color: var(--gold); }
.lang-caret { display: inline-block; margin-left: 0.35em; font-size: 0.8em; opacity: 0.8; }
.lang-select.open .lang-caret { transform: rotate(180deg); }
.lang-menu[hidden] { display: none; } /* display:flex below would defeat [hidden] */
.lang-menu {
  position: absolute; bottom: calc(100% + 0.45rem); left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 0.2rem;
  background: rgba(20,15,26,0.94); border: 1px solid rgba(255,255,255,0.25);
  border-radius: 12px; padding: 0.35rem; min-width: 10.5rem;
  box-shadow: 0 8px 24px rgba(0,0,0,0.5); z-index: 30;
}
.lang-option {
  display: flex; align-items: center; gap: 0.5em;
  background: none; border: 0; border-radius: 8px; color: var(--paper);
  padding: 0.45em 0.8em; font-weight: 600; text-align: left; cursor: pointer;
}
.lang-option:hover { background: rgba(255,255,255,0.12); }
.lang-option.active { color: var(--gold); }
.lang-option.active::after { content: '✓'; margin-left: auto; }

/* ---------- HUD ---------- */
.hud {
  position: absolute; top: 0; left: 0; right: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.6rem 1rem; pointer-events: none;
}
.hud-left { display: flex; gap: 0.5rem; pointer-events: auto; }
.trunk-btn {
  pointer-events: auto;
  background: var(--paper); border: 2px solid var(--ink);
  border-radius: 50%; width: 3.2rem; height: 3.2rem; box-shadow: 0 3px 0 var(--ink);
  display: flex; align-items: center; justify-content: center;
}
.trunk-btn .ui-icon { height: 1.9rem; vertical-align: 0; }
/* the Evidence button bumps + flashes gold when a flying artifact "lands" in it */
.trunk-btn.evidence-collect { animation: evidence-land 0.7s cubic-bezier(.3,1.4,.5,1); }
@keyframes evidence-land {
  0%   { transform: scale(1); box-shadow: 0 3px 0 var(--ink); }
  30%  { transform: scale(1.28); box-shadow: 0 3px 0 var(--ink), 0 0 0 6px rgba(255,196,92,0.55), 0 0 22px 6px rgba(255,196,92,0.7); }
  100% { transform: scale(1); box-shadow: 0 3px 0 var(--ink); }
}

/* Evidence flourish — an artifact card that pops at centre and flies into the
   Evidence button (js/main.js evidenceFlourish drives it with the Web Animations
   API, so position/scale are set there; this is just the look). On <body>, above
   everything, non-interactive. */
.evidence-flyer {
  position: fixed; z-index: 90; pointer-events: none;
  will-change: transform, opacity;
}
.evidence-flyer-card {
  display: flex; flex-direction: column; align-items: center; gap: 0.35rem;
  padding: 0.7rem 0.8rem 0.6rem; width: min(46vw, 172px);
  background: linear-gradient(180deg, rgba(38,26,52,0.97), rgba(26,17,38,0.97));
  border: 2px solid var(--gold); border-radius: 14px;
  box-shadow: 0 12px 34px rgba(0,0,0,0.55), 0 0 0 3px rgba(255,196,92,0.18), inset 0 0 18px rgba(255,208,120,0.12);
}
.evidence-flyer-img {
  width: min(24vw, 96px); height: min(24vw, 96px); object-fit: contain;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,0.55));
}
.evidence-flyer-emoji { font-size: min(20vw, 74px); line-height: 1; }
.evidence-flyer-cap {
  display: flex; flex-direction: column; align-items: center; gap: 0.1rem;
  font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--gold); font-weight: 800; text-align: center;
}
.evidence-flyer-name {
  text-transform: none; letter-spacing: 0; font-size: 0.86rem; font-weight: 700;
  color: #f6ecd6;
}
.mute-btn {
  pointer-events: auto;
  background: var(--paper); border: 2px solid var(--ink);
  border-radius: 50%; width: 3.2rem; height: 3.2rem; box-shadow: 0 3px 0 var(--ink);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.4rem; line-height: 1;
}
.mute-btn-title { position: absolute; top: 2vh; right: 2vh; z-index: 5; }
.hud-stats { display: flex; gap: 0.6rem; pointer-events: auto; }
.hud-stat {
  background: rgba(43,33,56,0.85); color: var(--paper);
  border: 1px solid var(--gold); border-radius: 999px; padding: 0.3em 0.8em;
  font-weight: 600; font-size: 0.9rem; white-space: nowrap;
}

/* ---------- stage / hotspots ---------- */
/* The scene layer is locked to the art's true 3:2 aspect and letterboxed to the
   largest 3:2 box that fits the viewport, in ANY orientation. This is CRITICAL:
   hotspots + artifact sprites are positioned in %-of-.stage and hit-tested
   against .stage's rect, so .stage must be exactly the same shape as the
   1536×1024 (3:2) background — the image fills it with no crop, so a hotspot at
   35%,66% of the art is at 35%,66% of the frame on every device. The letterbox
   margin is never empty: `.stage-backdrop` paints a blurred, dimmed, zoomed copy
   of the same scene behind the frame, so the art appears to bleed off the edges
   (cinematic vignette) rather than sitting in bars. */
/* THE GAME SCREEN — responsive contract (tools/test_layout.html guards this):
   `.screen` is the largest 3:2 box that fits the viewport, in any orientation —
   the scene letterboxes CONTAIN-style, so the WHOLE scene (every hotspot, clue
   and character) is always fully on-screen. Never cover-crop it: a clue that can
   slide off-screen is a game-breaker (v1.36 taught us). In landscape the box
   fills nearly the entire display — that IS the full-screen experience — and the
   blurred backdrop fills the slim margins. The UI is NEVER scaled by a transform
   to fit: dialogue, HUD, puzzle overlays and the 3D examine are viewport-sized
   with normal readable text (phones in portrait get the rotate gate instead). */
.screen {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(150dvh, 100vw);
  height: min(100dvh, 66.6667vw);
  overflow: hidden;
  z-index: 1;
}
.stage {
  position: absolute; inset: 0;
  overflow: hidden;
}
/* Blurred scene extension that fills the letterbox margin edge-to-edge. Fixed to
   the viewport so it covers the whole screen regardless of #app's aspect cap;
   sits behind everything (the framed scene, HUD, dialogue). */
.stage-backdrop {
  position: fixed; inset: 0; z-index: 0; overflow: hidden;
  background: #0a0710; pointer-events: none;
}
.stage-backdrop img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.14);
  filter: blur(30px) brightness(0.5) saturate(1.08);
}
.bg-fallback {
  position: absolute; inset: 0;
  background: linear-gradient(160deg, #4c6a8a 0%, #6d8fae 40%, #d9b98c 100%);
}
.stage.era-past .bg-fallback {
  background: linear-gradient(160deg, #5a3a24 0%, #8a5a2b 45%, #c9a227 100%);
}
.bg-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Subtle version stamp on the title screen. */
.title-version {
  position: absolute; right: 0.8rem; bottom: 0.6rem;
  font-size: 0.72rem; letter-spacing: 0.04em; opacity: 0.45;
  color: #f2e9d8; text-shadow: 0 1px 2px rgba(0,0,0,0.7); pointer-events: none;
}
/* "New version available" banner (bottom-center), tap to reload. */
.update-banner {
  position: fixed; left: 50%; bottom: 1rem; transform: translateX(-50%);
  z-index: 999; cursor: pointer;
  background: linear-gradient(180deg, rgba(58,44,74,0.96), rgba(38,28,52,0.97));
  border: 1px solid rgba(201,162,39,0.85); border-radius: 999px;
  padding: 0.55em 1.3em; font-size: 0.9rem; font-weight: 700;
  color: #f7efdd; text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  box-shadow: 0 4px 16px rgba(0,0,0,0.5);
  animation: placard-in 0.35s ease-out both;
}
.update-banner:hover { filter: brightness(1.1); }

.hotspot-layer { position: absolute; inset: 0; }

/* Investigating a room: the system pointer is hidden and a magnifying glass
   element follows the pointer instead (see .touch-magnifier), so searching the
   scene reads as searching. Buttons keep a normal pointer so they're clearly
   clickable. */
.stage.investigating { cursor: none; }
.stage.investigating .explore-trigger-btn,
.stage.investigating .travel-btn { cursor: pointer; }

/* The magnifying glass that follows the pointer (mouse or finger) while
   investigating. main.js positions it and toggles .active as the pointer
   enters/leaves the scene. Non-interactive so it never eats a tap. */
.touch-magnifier {
  position: absolute; width: 96px; height: 96px; margin: -48px 0 0 -48px;
  background: url('../assets/img/cursor_magnifier.png') center/contain no-repeat;
  pointer-events: none; opacity: 0; z-index: 18;
  transition: opacity 0.12s ease, filter 0.18s ease;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,0.55));
}
.touch-magnifier.active { opacity: 1; }
/* Over a hidden clue. Two cues, so it reads WITH or WITHOUT haptics and stays
   visible even when a fingertip covers the lens: a soft warm glow on the glass
   itself, plus a gold "sonar" ring that expands OUT past where a finger sits.
   Both live on the cursor, never on the scene, so the clue's exact spot is
   never marked — you still have to sweep to find it. Clear, but not a
   giveaway. (A haptic tick also fires on entry where supported — main.js.) */
.touch-magnifier.over {
  filter: drop-shadow(0 0 16px rgba(246,208,128,0.78)) drop-shadow(0 3px 5px rgba(0,0,0,0.55));
}
.touch-magnifier::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 84px; height: 84px; margin: -42px 0 0 -42px;
  border-radius: 50%; border: 3px solid rgba(246,208,128,0.85);
  opacity: 0; pointer-events: none;
}
.touch-magnifier.over::after { animation: mag-ping 1.05s ease-out infinite; }
@keyframes mag-ping {
  0%   { transform: scale(0.5); opacity: 0; }
  28%  { opacity: 0.8; }
  100% { transform: scale(2); opacity: 0; }
}

/* Clipart prop painted onto the scene at a discovery hotspot (e.g. the muddy
   footprint). Non-interactive itself — the invisible hotspot over it takes
   the tap; both vanish together once the evidence is collected. */
.artifact-sprite {
  position: absolute; object-fit: contain; pointer-events: none; z-index: 4;
  /* Soft grounding shadow only — no glow, so the prop blends into the scene
     rather than reading as a floating sticker. The pulsing hotspot box is
     what signals "interactable". */
  filter: drop-shadow(0 3px 5px rgba(0,0,0,0.4));
}
/* A flat mark (footprint) laid into the floor plane, so it recedes in the
   scene's perspective instead of facing the camera head-on. */
.artifact-flat {
  transform: perspective(520px) rotateX(58deg) rotate(-7deg);
  transform-origin: center 80%;
  /* A stain absorbs light — multiply blends the mud INTO the floor (darkening
     it) instead of sitting on top as a bright sticker. Dropped brightness +
     opacity so it reads as a faint mark, not a spotlight. */
  mix-blend-mode: multiply;
  filter: brightness(0.8) drop-shadow(0 1px 2px rgba(0,0,0,0.25));
  opacity: 0.72;
}
/* Hotspots are INVISIBLE — no outline or glow that would give away where to
   look. The player sweeps the magnifying glass and releases to investigate a
   spot; hit-testing is done by coordinates in main.js, not by these boxes. */
.hotspot { position: absolute; border-radius: 12px; }
.hotspot-hotcoin.collected { pointer-events: none; }

/* Lem's investigation "thought" — a brief, non-blocking italic caption (a
   thought in her head) shown when a search turns up nothing. Auto-fades. */
.lem-thought {
  position: absolute; left: 50%; bottom: 16%; transform: translateX(-50%);
  z-index: 24; pointer-events: none;
  max-width: 80%; padding: 0.7em 1.4em;
  text-align: center; font-style: italic; font-size: 1.05rem;
  color: #efe7d6; text-shadow: 0 2px 5px rgba(0,0,0,0.85);
  background: radial-gradient(ellipse at center, rgba(18,13,26,0.72), rgba(18,13,26,0));
  animation: lem-thought-in 0.3s ease-out both;
}
@keyframes lem-thought-in {
  from { opacity: 0; transform: translateX(-50%) translateY(6px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
.fx-layer { position: absolute; inset: 0; pointer-events: none; z-index: 15; }

/* ---------- ambient scene FX (the world breathes) ----------
   Declarative per-scene effects from SCENES[id].fx — light rays, dust
   motes, candle/lantern/torch/monitor flicker, drizzle, embers, a rare
   sparkle, fog. All percent-positioned inside the same letterboxed box as
   the hotspots, all pointer-transparent, all cheap (opacity/transform
   only). Calm by design: peak opacities stay low. */
.ambient-layer { position: absolute; inset: 0; pointer-events: none; z-index: 6; overflow: hidden; }

/* light rays: a tilted container of 3 soft stripes, breathing slowly */
.afx-rays {
  position: absolute; height: 120%; mix-blend-mode: screen;
  transform: rotate(var(--tilt, 15deg)); transform-origin: top center;
  /* fade in over the first few % so the shaft emerges AT the painted window /
     opening (the box top is placed on the light source) with no hard edge */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 7%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 7%);
}
.afx-rays i {
  position: absolute; top: 0; bottom: 0; display: block;
  background: linear-gradient(to bottom, rgba(255, 236, 190, 0.30), rgba(255, 236, 190, 0.05) 65%, rgba(255, 236, 190, 0) 92%);
  animation: afx-ray-breathe 9s ease-in-out infinite;
}
.afx-rays i:nth-child(1) { left: 6%; width: 22%; }
.afx-rays i:nth-child(2) { left: 40%; width: 14%; animation-delay: -3s; animation-duration: 11s; }
.afx-rays i:nth-child(3) { left: 66%; width: 26%; animation-delay: -6s; animation-duration: 13s; }
.afx-rays.afx-cool i { background: linear-gradient(to bottom, rgba(205, 228, 255, 0.26), rgba(205, 228, 255, 0.04) 65%, rgba(205, 228, 255, 0) 92%); }
@keyframes afx-ray-breathe {
  0%, 100% { opacity: 0.35; transform: translateX(0); }
  50% { opacity: 0.8; transform: translateX(1.2%); }
}

/* dust motes drifting inside the light */
.afx-mote {
  position: absolute; display: block; width: 3px; height: 3px; border-radius: 50%;
  background: rgba(255, 244, 210, 0.8); filter: blur(0.5px); opacity: 0;
  animation: afx-mote-drift var(--dur, 14s) linear infinite; animation-delay: var(--delay, 0s);
}
@keyframes afx-mote-drift {
  0% { transform: translate(0, 0); opacity: 0; }
  12% { opacity: 0.55; }
  85% { opacity: 0.3; }
  100% { transform: translate(var(--dx, -18px), var(--dy, 44px)); opacity: 0; }
}

/* fire-lit glow that flickers: candle / lantern / torch / fire / monitor */
.afx-flicker {
  position: absolute; border-radius: 50%; mix-blend-mode: screen; transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(255, 190, 96, 0.34) 0%, rgba(255, 160, 60, 0.12) 45%, rgba(255, 140, 40, 0) 70%);
  animation: afx-flicker var(--dur, 2.6s) steps(24, end) infinite;
}
.afx-flicker.afx-monitor { background: radial-gradient(circle, rgba(140, 200, 255, 0.22) 0%, rgba(120, 180, 255, 0.08) 45%, rgba(120, 180, 255, 0) 70%); animation-duration: 4.5s; }
.afx-flicker.afx-fire { background: radial-gradient(circle, rgba(255, 150, 60, 0.42) 0%, rgba(255, 110, 40, 0.16) 50%, rgba(255, 90, 30, 0) 72%); animation-duration: 1.7s; }
@keyframes afx-flicker {
  0%, 100% { opacity: 0.82; } 7% { opacity: 0.68; } 13% { opacity: 0.9; }
  22% { opacity: 0.74; } 34% { opacity: 0.95; } 41% { opacity: 0.7; }
  53% { opacity: 0.88; } 66% { opacity: 0.76; } 78% { opacity: 0.92; } 90% { opacity: 0.72; }
}

/* soft drizzle: two offset line rasters slipping down */
.afx-drizzle { position: absolute; inset: -6% 0 0 0; opacity: 0.12; }
.afx-drizzle::before, .afx-drizzle::after {
  content: ''; position: absolute; inset: 0;
  background: repeating-linear-gradient(78deg, rgba(220, 232, 245, 0) 0 7px, rgba(220, 232, 245, 0.5) 7px 8px, rgba(220, 232, 245, 0) 8px 16px);
  background-size: 120px 120px; animation: afx-rain 0.85s linear infinite;
}
.afx-drizzle::after { background-size: 90px 90px; opacity: 0.6; animation-duration: 0.6s; }
@keyframes afx-rain { from { background-position: 0 0; } to { background-position: -18px 120px; } }

/* embers rising from a blaze */
.afx-ember {
  position: absolute; display: block; width: 4px; height: 4px; border-radius: 50%;
  background: rgba(255, 160, 70, 0.95); box-shadow: 0 0 6px rgba(255, 120, 40, 0.8); opacity: 0;
  animation: afx-ember-rise var(--dur, 5s) ease-out infinite; animation-delay: var(--delay, 0s);
}
@keyframes afx-ember-rise {
  0% { transform: translate(0, 0) scale(1); opacity: 0; }
  10% { opacity: 0.9; }
  100% { transform: translate(var(--dx, 10px), -46vh) scale(0.4); opacity: 0; }
}

/* a rare, tiny glint (finale dome, precious things) */
.afx-sparkle {
  position: absolute; width: 14px; height: 14px; transform: translate(-50%, -50%); opacity: 0;
  background:
    radial-gradient(circle, rgba(255, 255, 235, 0.95) 0%, rgba(255, 250, 220, 0) 38%),
    linear-gradient(rgba(255, 255, 240, 0.9), rgba(255, 255, 240, 0.9)) 50% 0 / 1.5px 100% no-repeat,
    linear-gradient(rgba(255, 255, 240, 0.9), rgba(255, 255, 240, 0.9)) 0 50% / 100% 1.5px no-repeat;
  animation: afx-sparkle 9s ease-in-out infinite; animation-delay: var(--delay, 0s);
}
@keyframes afx-sparkle {
  0%, 92%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.4) rotate(0deg); }
  95% { opacity: 0.9; transform: translate(-50%, -50%) scale(1) rotate(45deg); }
  98% { opacity: 0; transform: translate(-50%, -50%) scale(0.5) rotate(80deg); }
}

/* slow fog band */
.afx-fog {
  position: absolute; left: -20%; width: 140%; border-radius: 50%;
  background: radial-gradient(ellipse, rgba(226, 232, 240, 0.14) 0%, rgba(226, 232, 240, 0) 70%);
  filter: blur(6px); animation: afx-fog-drift 26s ease-in-out infinite alternate;
}
@keyframes afx-fog-drift { from { transform: translateX(-4%); } to { transform: translateX(4%); } }

/* quiet lens flare pinned on a painted practical light — a warm core, one
   thin anamorphic streak and two faint ghost dots trailing off along the
   lens axis. Everything scales from --fs so one class fits lamp or lantern. */
.afx-flare {
  position: absolute; width: 0; height: 0; mix-blend-mode: screen;
  font-size: clamp(0.55rem, 1.5vmin, 1.05rem);
  animation: afx-flare-breathe var(--dur, 8s) ease-in-out infinite;
}
.afx-flare b { position: absolute; left: 0; top: 0; display: block; transform: translate(-50%, -50%); }
.afx-flare-core {
  width: calc(4.6em * var(--fs, 1)); height: calc(4.6em * var(--fs, 1)); border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 240, 205, 0.5) 0%, rgba(255, 225, 170, 0.16) 32%, rgba(255, 225, 170, 0) 68%);
}
.afx-flare-streak {
  width: calc(13em * var(--fs, 1)); height: 2px; border-radius: 2px;
  background: linear-gradient(to right, rgba(255, 235, 195, 0) 0%, rgba(255, 240, 210, 0.55) 50%, rgba(255, 235, 195, 0) 100%);
  filter: blur(0.6px);
}
.afx-flare-ghost { border-radius: 50%; background: radial-gradient(circle, rgba(255, 236, 200, 0.22) 0%, rgba(255, 236, 200, 0) 70%); }
.afx-flare-ghost.afx-g1 { width: calc(1.5em * var(--fs, 1)); height: calc(1.5em * var(--fs, 1)); left: calc(3.4em * var(--fs, 1)); top: calc(2.1em * var(--fs, 1)); }
.afx-flare-ghost.afx-g2 { width: calc(0.9em * var(--fs, 1)); height: calc(0.9em * var(--fs, 1)); left: calc(5.6em * var(--fs, 1)); top: calc(3.5em * var(--fs, 1)); }
@keyframes afx-flare-breathe {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 0.9; }
}

/* reduce-motion: freeze the motion, keep the still light (rays/glows/flares
   stay as fixed soft light; motes, rain, embers, sparkles, fog vanish) */
@media (prefers-reduced-motion: reduce) {
  .ambient-layer .afx-rays i, .ambient-layer .afx-flicker { animation: none; opacity: 0.6; }
  .ambient-layer .afx-flare { animation: none; opacity: 0.6; }
  .ambient-layer .afx-mote, .ambient-layer .afx-ember, .ambient-layer .afx-sparkle,
  .ambient-layer .afx-drizzle, .ambient-layer .afx-fog { display: none; }
}
html.reduce-motion .ambient-layer .afx-rays i, html.reduce-motion .ambient-layer .afx-flicker { animation: none; opacity: 0.6; }
html.reduce-motion .ambient-layer .afx-flare { animation: none; opacity: 0.6; }
html.reduce-motion .ambient-layer .afx-mote, html.reduce-motion .ambient-layer .afx-ember,
html.reduce-motion .ambient-layer .afx-sparkle, html.reduce-motion .ambient-layer .afx-drizzle,
html.reduce-motion .ambient-layer .afx-fog { display: none; }

/* Phoenix-Wright-style labeled action button — the one way forward out of
   an explore scene, read as a clear instruction instead of a highlighted
   square the player has to hunt for. */
.explore-trigger-btn {
  position: absolute; left: 50%; bottom: 4vh; transform: translateX(-50%);
  z-index: 10;
  background: url('../assets/img/ui_button_plaque_primary.webp') center/100% 100% no-repeat;
  border: none; border-radius: 0;
  padding: 0.85em 2em;
  font-size: 1.05rem; font-weight: 700;
  color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.6);
  filter: drop-shadow(0 3px 3px rgba(0,0,0,0.35));
  display: inline-flex; align-items: center; gap: 0.5em;
  animation: trigger-btn-pulse 2.2s ease-in-out infinite;
}
.explore-trigger-btn:hover { filter: drop-shadow(0 5px 5px rgba(0,0,0,0.4)) brightness(1.06); }
.explore-trigger-btn:active { transform: translateX(-50%) translateY(1px) scale(0.99); }
@keyframes trigger-btn-pulse {
  0%, 100% { filter: drop-shadow(0 3px 3px rgba(0,0,0,0.35)) brightness(1); }
  50% { filter: drop-shadow(0 3px 6px rgba(201,162,39,0.55)) brightness(1.08); }
}

/* Phoenix-Wright "Move" buttons — one per connected room the player can
   wander back to. Stacked top-left so they never collide with the primary
   forward trigger (bottom-center) or the HUD (top strip). */
.travel-bar {
  position: absolute; left: 2%; top: 16%;
  z-index: 10;
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.5em;
  max-width: 46%;
}
.travel-btn {
  background: linear-gradient(180deg, rgba(58,44,74,0.92), rgba(38,28,52,0.94));
  border: 1px solid rgba(201,162,39,0.55); border-radius: 10px;
  padding: 0.5em 1em;
  font-size: 0.92rem; font-weight: 600;
  color: #f2e9d8; text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 0.45em;
  box-shadow: 0 2px 6px rgba(0,0,0,0.35);
  transition: transform 0.08s ease, filter 0.12s ease, border-color 0.12s ease;
}
.travel-btn:hover { filter: brightness(1.12); border-color: rgba(201,162,39,0.95); }
.travel-btn:active { transform: translateY(1px) scale(0.99); }
.travel-btn .ui-icon { width: 0.85em; height: 0.85em; opacity: 0.85; }

/* Phoenix-Wright location placard: a brief location + date/time card on
   arrival. Non-interactive — taps pass straight through to the scene. */
.location-placard {
  position: absolute; left: 50%; top: 12%; transform: translateX(-50%);
  z-index: 22; pointer-events: none;
  min-width: 44%; max-width: 88%;
  padding: 0.6em 1.6em;
  text-align: center;
  background: linear-gradient(180deg, rgba(18,13,26,0.97), rgba(9,6,15,0.98));
  border-top: 2px solid rgba(201,162,39,0.9);
  border-bottom: 2px solid rgba(201,162,39,0.9);
  box-shadow: 0 6px 26px rgba(0,0,0,0.7);
  backdrop-filter: blur(3px);
  animation: placard-in 0.35s ease-out both;
}
.location-placard .placard-location {
  font-size: 1.25rem; font-weight: 800; letter-spacing: 0.02em;
  color: #f7efdd; text-shadow: 0 2px 4px rgba(0,0,0,0.7);
}
.location-placard .placard-date {
  margin-top: 0.15em;
  font-size: 0.9rem; font-weight: 500; letter-spacing: 0.03em;
  color: #c9a227;
}
@keyframes placard-in {
  from { opacity: 0; transform: translateX(-50%) translateY(-8px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
/* Fades OUT (never pops) once the hold is over; dialogue is held back until
   this finishes, so a speaker never overlaps the placard. */
.location-placard.fade-out { animation: placard-out 0.6s ease-in both; }
@keyframes placard-out {
  from { opacity: 1; transform: translateX(-50%) translateY(0); }
  to   { opacity: 0; transform: translateX(-50%) translateY(-8px); }
}
@media (max-width: 640px) {
  .travel-bar { top: 14%; max-width: 60%; }
  .travel-btn { font-size: 0.82rem; padding: 0.42em 0.8em; }
  .location-placard .placard-location { font-size: 1.05rem; }
  .location-placard .placard-date { font-size: 0.8rem; }
}

/* ---------- dialogue box ----------
   Ace Attorney composition: the character fills most of the stage, and a
   wide, centered textbox crosses over their lower body near the bottom —
   the box sits ABOVE the portrait in stacking order, not beside it. */
/* Framed to the exact same 3:2 letterbox as .stage, so the character portraits
   and the textbox live INSIDE the scene frame and can never break out into the
   blurred backdrop margin. */
#dialogue-slot {
  position: absolute; inset: 0;
  z-index: 25; pointer-events: none;
}
.dialogue-wrap {
  position: absolute; inset: 0;
  pointer-events: auto; cursor: pointer;
  animation: portrait-fade-in 0.28s ease-out;
}
@keyframes portrait-fade-in {
  from { transform: translateY(14px); }
  to { transform: none; }
}
.portrait {
  position: absolute; bottom: 0; z-index: 1;
  /* Relative to the framed slot (not the viewport) so the character scales with
     the letterbox and its feet sit on the frame's bottom edge — never spilling
     past the scene into the backdrop. */
  max-height: 100%;
  filter: drop-shadow(0 10px 14px rgba(0,0,0,0.45));
}
.portrait-left { left: 3%; }
.portrait-right { right: 3%; }
.portrait-center { left: 50%; transform: translateX(-50%); }
.dialogue-box {
  position: absolute; z-index: 2; left: 50%; bottom: 1.2rem;
  transform: translateX(-50%);
  /* Percent, NOT vw: the play area (#app) is letterboxed narrower than the
     viewport in landscape and clips its overflow, so a vw-wide box spills past
     #app and the left of the left-aligned text gets cut off. % keeps it inside. */
  width: min(94%, 68rem);
  background: var(--paper); border: 3px solid var(--ink); border-radius: 16px;
  padding: 1rem 1.4rem; box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  min-height: 6rem;
  /* Safety cap for short viewports (landscape phones, small tablets) —
     long lines + a multi-choice list can otherwise grow taller than the
     visible area; scroll inside the box instead of clipping under the
     screen edge. */
  max-height: min(52vh, 52dvh);
  overflow-y: auto;
}
.narrator .dialogue-box { font-style: italic; text-align: center; background: rgba(247,236,217,0.92); }
.dialogue-name { font-weight: 800; color: var(--accent-warm); margin-bottom: 0.2em; }
.dialogue-text { font-size: 1.08rem; line-height: 1.4; white-space: pre-line; }
.tap-indicator { text-align: right; font-size: 0.8rem; opacity: 0.6; margin-top: 0.3em; }
.dialogue-choices { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.6rem; }
.choice-btn { text-align: left; }

/* ---------- toasts ---------- */
.toast-box {
  position: absolute; top: 4.2rem; left: 50%; transform: translateX(-50%); z-index: 30;
  display: flex; flex-direction: column; gap: 0.3rem; align-items: center;
  pointer-events: none;
}
.toast {
  background: rgba(43,33,56,0.92); color: var(--paper); border: 1px solid var(--gold);
  border-radius: 999px; padding: 0.35em 1em; font-weight: 600; font-size: 0.9rem;
  animation: toast-in 0.2s ease-out;
}
@keyframes toast-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ---------- overlays (puzzle / confrontation / trunk / evidence) ---------- */
/* Full-viewport modals (fixed, so they escape #app's aspect cap): the whole
   display dims and the panel uses normal, readable text at every screen size —
   overlays are NEVER shrunk by a transform to fit a frame. Tall content scrolls
   inside the panel; the hint/Submit row stays pinned at its bottom. */
.overlay {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(20,15,26,0.78);
  display: flex;
  padding: max(0.9rem, env(safe-area-inset-top, 0px)) max(0.9rem, env(safe-area-inset-right, 0px))
           max(0.9rem, env(safe-area-inset-bottom, 0px)) max(0.9rem, env(safe-area-inset-left, 0px));
  overflow-y: auto;
}
.overlay-panel {
  background: var(--paper); border: 3px solid var(--ink); border-radius: 18px;
  /* margin:auto (NOT flex centering): auto margins centre the panel when it is
     smaller than the screen, yet let it scroll from the very top when taller —
     flex align/justify-center clips the top of overflowing content. */
  margin: auto;
  max-width: 56rem; width: 100%; max-height: 100%; overflow-y: auto;
  padding: 1.4rem 1.6rem; box-shadow: 0 10px 0 rgba(0,0,0,0.35);
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
}
.overlay-title { font-size: 1.3rem; font-weight: 800; margin-bottom: 0.4rem; display: flex; align-items: center; gap: 0.6rem; }
.picarat-badge { font-size: 0.85rem; background: var(--gold); border-radius: 999px; padding: 0.1em 0.7em; }
.overlay-prompt { margin-bottom: 0.8rem; opacity: 0.85; }
/* The hint/Submit row is pinned to the bottom of the (scrollable) panel, so it
   is ALWAYS visible on short/landscape screens no matter how tall the puzzle
   body is. Bleeds to the panel edges with a paper backing so scrolling content
   passes cleanly underneath. */
.overlay-actions {
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
  position: sticky; bottom: 0; z-index: 6;
  margin: 0.9rem -1.6rem -1.4rem; padding: 0.7rem 1.6rem 1rem;
  background: var(--paper); border-top: 2px solid rgba(43,33,56,0.12);
  box-shadow: 0 -10px 16px -10px rgba(0,0,0,0.35);
}
/* Scroll affordance: a soft paper fade floating above the pinned row, so puzzle
   content visibly slips underneath it — an always-honest "there's more, scroll"
   cue that costs nothing when the panel fits. */
.overlay-actions::before {
  content: ''; position: absolute; left: 0; right: 0; top: -20px; height: 20px;
  background: linear-gradient(to bottom, rgba(247,236,217,0), var(--paper));
  pointer-events: none;
}
/* Tidy, unobtrusive scrollbars for the panel (and the logic grid's sideways pane). */
.overlay-panel, .logic-grid-scroll { scrollbar-width: thin; scrollbar-color: rgba(43,33,56,0.35) transparent; }
.overlay-panel::-webkit-scrollbar, .logic-grid-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.overlay-panel::-webkit-scrollbar-thumb, .logic-grid-scroll::-webkit-scrollbar-thumb {
  background: rgba(43,33,56,0.35); border-radius: 999px; border: 2px solid var(--paper);
}
.overlay-panel::-webkit-scrollbar-track, .logic-grid-scroll::-webkit-scrollbar-track { background: transparent; }

/* sequence puzzle — a row of draggable photo cards. Drag to reorder from
   earliest (left) to latest (right); the number badge is the live position. */
.seq-scale {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  margin-bottom: 0.55rem; font-size: 0.82rem; font-weight: 700; color: var(--accent-warm);
}
.seq-scale-end { display: inline-flex; align-items: center; gap: 0.3em; white-space: nowrap; }
.seq-scale-hint { font-weight: 500; opacity: 0.72; font-style: italic; text-align: center; }
.seq-scale .ui-icon { width: 0.8em; height: 0.8em; opacity: 0.8; }
.seq-row {
  display: flex; flex-wrap: wrap; gap: 0.55rem; justify-content: center;
  padding: 0.7rem 0.5rem; border-radius: 12px; background: rgba(0,0,0,0.06);
  /* pan-y: a vertical swipe still scrolls the panel (to reach cards + the pinned
     Submit row); a horizontal drag reorders the cards. */
  touch-action: pan-y;
}
.seq-card {
  position: relative; background: var(--paper-dark); border: 2px solid var(--ink);
  border-radius: 10px; padding: 0.45em; font-size: 0.9rem; width: 8.6rem;
  cursor: grab; user-select: none; -webkit-user-select: none; touch-action: pan-y;
  text-align: center;
  transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.seq-card:active { cursor: grabbing; }
.seq-card.dragging {
  cursor: grabbing; z-index: 20; border-color: var(--gold);
  box-shadow: 0 12px 26px rgba(0,0,0,0.55); opacity: 0.96;
  transition: none;  /* track the finger instantly; only the OTHER cards animate */
}
.seq-num {
  position: absolute; top: -0.6em; left: -0.6em; z-index: 2;
  min-width: 1.5em; height: 1.5em; line-height: 1.4em; padding: 0 0.2em;
  background: var(--gold); color: #241a0b; border: 2px solid var(--ink);
  border-radius: 999px; font-weight: 800; font-size: 0.8rem; text-align: center;
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.seq-grip { display: block; color: rgba(0,0,0,0.32); font-size: 0.85rem; line-height: 1; margin-bottom: 0.1em; letter-spacing: 0.12em; }
.seq-card.has-img { width: 9rem; }
.puzzle-img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 6px;
  border: 1px solid rgba(0,0,0,0.35); box-shadow: 0 1px 4px rgba(0,0,0,0.4); display: block;
}
.seq-card-cap { display: block; margin-top: 0.3em; font-size: 0.72rem; line-height: 1.25; color: var(--ink); }

/* sensor puzzle — a "physical" mechanic (blow into the mic / tilt / shake).
   The target image sits under a grimy dust layer that clears as progress
   fills; a guaranteed on-screen puff button keeps it solvable everywhere. */
.sensor-overlay .overlay-panel { text-align: center; }
.sensor-stage {
  position: relative; width: min(74%, 22rem); margin: 0.6rem auto 0; aspect-ratio: 1 / 1;
  border-radius: 12px; overflow: hidden; border: 3px solid var(--ink);
  box-shadow: 0 6px 0 rgba(0,0,0,0.3); background: #2a2018;
}
.sensor-img { width: 100%; height: 100%; object-fit: cover; display: block; transition: filter 0.1s linear, transform 0.12s ease; }
.sensor-dust {
  position: absolute; inset: 0; transition: opacity 0.12s linear;
  backdrop-filter: blur(2px) sepia(0.35);
  background:
    radial-gradient(circle at 22% 28%, rgba(120,98,64,0.85), transparent 42%),
    radial-gradient(circle at 72% 62%, rgba(96,78,50,0.82), transparent 46%),
    radial-gradient(circle at 46% 82%, rgba(112,90,58,0.82), transparent 42%),
    radial-gradient(circle at 84% 22%, rgba(104,84,54,0.82), transparent 40%),
    radial-gradient(circle at 12% 66%, rgba(90,72,46,0.82), transparent 40%),
    linear-gradient(160deg, rgba(78,62,42,0.92), rgba(56,45,30,0.95));
}
/* 'align' mechanic: a slitted plate the player rotates (gyro / drag) over the
   scene. Oversized + centred so rotation never exposes the stage corners; it
   fades out as the meter fills to reveal the hidden mark beneath. */
.sensor-slits {
  position: absolute; left: -50%; top: -50%; width: 200%; height: 200%;
  transform-origin: center center; pointer-events: none;
  transition: opacity 0.15s linear;
  background: repeating-linear-gradient(0deg,
    rgba(24,17,10,0.95) 0 8px, rgba(0,0,0,0) 8px 16px);
}
.sensor-stage.blowing { animation: sensor-rattle 0.28s ease-in-out infinite; }
@keyframes sensor-rattle {
  0%, 100% { transform: translate(0, 0); }
  25% { transform: translate(-1.5px, 1px); }
  75% { transform: translate(1.5px, -1px); }
}
/* A shake puzzle rattles harder and faster than a gentle blow. */
.sensor-stage.sensor-shake.blowing { animation: sensor-rattle-hard 0.1s linear infinite; }
@keyframes sensor-rattle-hard {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  20% { transform: translate(-3px, 2px) rotate(-0.5deg); }
  40% { transform: translate(3px, -2px) rotate(0.5deg); }
  60% { transform: translate(-2px, 3px) rotate(-0.4deg); }
  80% { transform: translate(2px, -3px) rotate(0.4deg); }
}
@media (prefers-reduced-motion: reduce) { .sensor-stage.sensor-shake.blowing { animation: none; } }
.sensor-reveal-cap {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 0.5em 0.4em;
  font-family: Georgia, serif; font-weight: 800; letter-spacing: 0.08em; font-size: 1.05rem;
  color: #fff8e6; text-shadow: 0 2px 4px rgba(0,0,0,0.8);
  background: linear-gradient(transparent, rgba(0,0,0,0.8));
  opacity: 0; transition: opacity 0.45s ease; pointer-events: none;
}
.sensor-reveal-cap.show { opacity: 1; }
.sensor-progress {
  width: min(74%, 22rem); height: 0.75rem; margin: 0.7rem auto 0.35rem;
  background: rgba(0,0,0,0.15); border: 1px solid rgba(0,0,0,0.25);
  border-radius: 999px; overflow: hidden;
}
.sensor-progress-fill {
  height: 100%; width: 0; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-warm), var(--gold));
  transition: width 0.08s linear;
}
.sensor-instruction { font-size: 0.92rem; opacity: 0.85; margin: 0.3rem 0 0.7rem; }
.sensor-controls { display: flex; gap: 0.6rem; justify-content: center; flex-wrap: wrap; }
.sensor-puff { touch-action: none; user-select: none; -webkit-user-select: none; font-size: 1.05rem; padding: 0.6em 1.4em; }
.sensor-puff:active { transform: translateY(2px) scale(0.98); }
/* The fallback stays hidden until the player looks stuck (main.js revealPuff),
   then fades in gently so it reads as a helpful hint, not a normal control. */
.sensor-puff.is-hidden { display: none; }
.sensor-controls { min-height: 3rem; align-items: center; }
.sensor-puff:not(.is-hidden) { animation: puff-reveal 0.4s ease-out; }
@keyframes puff-reveal { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .sensor-stage.blowing { animation: none; } }

/* sliding-tile puzzle — N×N windows onto one image, tap beside the blank to
   slide. Auto-completes when every piece is home. */
.sliding-overlay .overlay-panel { text-align: center; }
.slide-grid {
  display: grid; gap: 4px; width: min(82%, 21rem); margin: 0.8rem auto 0;
  aspect-ratio: 1 / 1; padding: 6px; border-radius: 12px;
  background: rgba(0,0,0,0.18); border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
}
.slide-tile {
  aspect-ratio: 1 / 1; border-radius: 4px; cursor: pointer;
  background-repeat: no-repeat; background-color: #2a2018;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.3);
  transition: transform 0.07s ease;
}
.slide-tile:not(.slide-blank):active { transform: scale(0.96); }
.slide-blank { background: transparent; cursor: default; box-shadow: none; }

/* placement puzzle — a top-down vault plan with compass-positioned zones; tap a
   barrel from the tray, tap a zone to set it down. */
.placement-overlay .overlay-panel { text-align: center; }
.vault-plan {
  display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.4rem;
  width: min(96%, 30rem); margin: 0.6rem auto 0.8rem; padding: 0.5rem;
  border-radius: 14px; border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  background: radial-gradient(circle at 50% 50%, #6b5a44, #3a2f22 78%);
  background-size: cover; background-position: center;
}
.vault-core { grid-column: 2; grid-row: 2; display: flex; align-items: center; justify-content: center; font-size: 1.8rem; }
.vault-zone {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 0.25rem;
  padding: 0.3rem; border: 2px dashed rgba(255,240,210,0.45); border-radius: 8px;
  background: rgba(20,14,8,0.42); cursor: pointer; transition: border-color 0.12s, background 0.12s;
}
.vault-zone:hover { background: rgba(20,14,8,0.3); }
.vault-zone.filled { border-style: solid; border-color: var(--gold); background: rgba(20,14,8,0.25); }
.vault-n { grid-column: 2; grid-row: 1; }
.vault-w { grid-column: 1; grid-row: 2; }
.vault-e { grid-column: 3; grid-row: 2; }
.vault-s { grid-column: 2; grid-row: 3; }
.vault-zone-label { font-size: 0.6rem; line-height: 1.18; color: #fff3dd; text-shadow: 0 1px 2px rgba(0,0,0,0.8); }
.vault-slot {
  width: 2.4rem; height: 2.4rem; border-radius: 50%;
  border: 2px dashed rgba(255,255,255,0.35); background: rgba(0,0,0,0.15);
}
.barrel {
  display: flex; flex-direction: column; align-items: center; gap: 0.1rem;
  cursor: pointer; max-width: 8rem; padding: 0.1rem; border-radius: 8px;
}
.barrel-img { width: 3rem; height: auto; display: block; filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5)); transition: transform 0.1s, filter 0.1s; }
.barrel.selected .barrel-img { transform: scale(1.12) translateY(-2px); filter: drop-shadow(0 0 7px var(--gold)); }
.vault-zone .barrel-img { width: 2.7rem; }
.barrel-cap { font-size: 0.56rem; line-height: 1.15; color: var(--ink); text-align: center; }
.vault-zone .barrel-cap { color: #fff3dd; text-shadow: 0 1px 2px rgba(0,0,0,0.8); }
.barrel-tray {
  display: flex; flex-wrap: wrap; gap: 0.6rem; justify-content: center; min-height: 3.5rem; align-items: flex-start;
  padding: 0.6rem; background: rgba(0,0,0,0.06); border-radius: 10px;
}
.barrel-tray-empty { opacity: 0.6; font-style: italic; align-self: center; }

/* routing puzzle — rotate lead channel tiles on a sunken stone basin so canal
   water floods from the inlet (source) to the sump (drain). Pipes are inline
   SVG: they turn on tap and glow blue where the water has reached. */
.routing-overlay .overlay-panel { text-align: center; }
.pipe-basin {
  display: grid; gap: 3px; width: min(94%, 25rem); margin: 0.7rem auto 0.9rem;
  aspect-ratio: 1 / 1; padding: 0.5rem; border-radius: 14px;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  background-color: #43372a;
  background-image: radial-gradient(circle at 50% 45%, #5b4a37, #2e251a 85%);
  background-size: cover; background-position: center;
}
.pipe-cell { position: relative; aspect-ratio: 1 / 1; border-radius: 6px; background: rgba(15,10,6,0.28); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.35); }
.pipe-play { cursor: pointer; }
.pipe-play:active .pipe-svg { transform: rotate(calc(var(--rot,0) * 90deg)) scale(0.9); }
.pipe-fixed { cursor: default; }
.pipe-svg {
  width: 100%; height: 100%; display: block;
  transform: rotate(calc(var(--rot,0) * 90deg)); transform-origin: 50% 50%;
  transition: transform 0.18s ease;
}
.pipe-casing { stroke: #241a11; stroke-width: 27; stroke-linecap: round; fill: none; }
.pipe-rim { stroke: #9a6836; stroke-width: 16; stroke-linecap: round; fill: none; }
.pipe-hub { fill: #7d5230; }
.pipe-water { stroke: #5cc4f2; stroke-width: 8; stroke-linecap: round; fill: none; opacity: 0; transition: opacity 0.2s ease; }
.pipe-water-hub { stroke: none; fill: #5cc4f2; }
.wet .pipe-water { opacity: 1; stroke-dasharray: 13 11; animation: waterflow 0.7s linear infinite; }
.wet .pipe-water-hub { stroke-dasharray: none; animation: none; }
@keyframes waterflow { to { stroke-dashoffset: -24; } }
.pipe-source { fill: #2f7cab; stroke: #d6f0ff; stroke-width: 3; }
.pipe-source-eye { fill: #cbecff; }
.pipe-drain { fill: #1c140d; stroke: #000; stroke-width: 2; }
.pipe-drain-grate { stroke: #c08a55; stroke-width: 3; fill: none; stroke-linecap: round; }
.wet .pipe-drain { fill: #123043; }
.pipe-rock {
  position: absolute; inset: 11%; border-radius: 22% 34% 26% 30%;
  background: radial-gradient(circle at 38% 32%, #6a6157, #3a342c 80%);
  box-shadow: inset 0 -3px 5px rgba(0,0,0,0.5), inset 0 2px 3px rgba(255,255,255,0.12);
}

/* carillon puzzle — bronze bells hanging in a belfry frame. "Listen" plays the
   alarm peal (each bell swings + sounds); the player rings it back in order. */
.carillon-overlay .overlay-panel { text-align: center; }
.belfry {
  position: relative; width: min(96%, 30rem); margin: 0.7rem auto 0.8rem;
  aspect-ratio: 3 / 2; border-radius: 14px; overflow: hidden;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  background-color: #2c2115; background-size: cover; background-position: center top;
}
.bell-row {
  position: absolute; inset: 0; display: flex; justify-content: space-around;
  align-items: flex-start; padding: 9% 4% 0;
}
.carillon-bell {
  background: none; border: none; padding: 0; cursor: pointer;
  display: inline-flex; align-items: flex-start; justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.carillon-bell-img {
  width: calc(var(--bell, 1) * 4.7rem); height: auto; display: block;
  transform-origin: 50% 8%; transition: filter 0.15s, transform 0.1s;
  filter: drop-shadow(0 3px 4px rgba(0,0,0,0.55));
}
.carillon-bell:hover .carillon-bell-img { transform: scale(1.04); }
.carillon-bell.swing .carillon-bell-img {
  animation: bellswing 0.66s ease;
  filter: drop-shadow(0 0 12px var(--gold)) drop-shadow(0 3px 4px rgba(0,0,0,0.5));
}
@keyframes bellswing {
  0% { transform: rotate(0); } 18% { transform: rotate(12deg); }
  44% { transform: rotate(-9deg); } 68% { transform: rotate(6deg); }
  86% { transform: rotate(-3deg); } 100% { transform: rotate(0); }
}
.carillon-controls { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; margin-top: 0.2rem; }
.carillon-listen.playing { opacity: 0.75; animation: listenpulse 0.64s ease-in-out infinite; }
@keyframes listenpulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
.carillon-dots { display: flex; gap: 0.5rem; }
.carillon-dot {
  width: 0.85rem; height: 0.85rem; border-radius: 50%;
  background: rgba(0,0,0,0.18); border: 2px solid var(--ink); transition: background 0.15s, box-shadow 0.15s;
}
.carillon-dot.lit { background: var(--gold); box-shadow: 0 0 8px var(--gold); }

/* trace puzzle — drag one unbroken line through the vault waypoints in order to
   carve the escape route. The glowing line + lit nodes are updated live by
   main.js during the drag; auto-solves when the whole route is traced. */
.trace-overlay .overlay-panel { text-align: center; }
.trace-stage {
  position: relative; width: min(94%, 26rem); margin: 0.7rem auto 0.9rem;
  aspect-ratio: 1 / 1; border-radius: 14px; overflow: hidden; cursor: crosshair;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  background-color: #2a231b; background-size: cover; background-position: center;
  touch-action: none; -webkit-user-select: none; user-select: none;
}
.trace-svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
.trace-line {
  fill: none; stroke: #ffcf72; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
  filter: drop-shadow(0 0 2px rgba(255,190,90,0.9));
}
.trace-nodes { position: absolute; inset: 0; pointer-events: none; }
.trace-node {
  position: absolute; width: 1.4rem; height: 1.4rem; transform: translate(-50%, -50%);
  border-radius: 50%; border: 2px solid rgba(255,240,210,0.75); background: rgba(18,12,7,0.5);
  box-shadow: 0 0 5px rgba(0,0,0,0.6); transition: background 0.12s, box-shadow 0.12s;
}
.trace-node.lit { background: var(--gold); border-color: #fff3d0; box-shadow: 0 0 13px var(--gold); }
.trace-start { border-color: #93e0a0; width: 1.6rem; height: 1.6rem; }
.trace-start:not(.lit) { animation: tracepulse 1.1s ease-in-out infinite; }
.trace-end { border-color: #ff9f6e; width: 1.7rem; height: 1.7rem; }
@keyframes tracepulse {
  0%, 100% { box-shadow: 0 0 5px rgba(147,224,160,0.6); }
  50% { box-shadow: 0 0 15px rgba(147,224,160,0.95); }
}

/* lock puzzle — a side-view canal lock. Work the two sluices (raise/lower the
   chamber) and the two gates (each only opens level with its own canal) to walk
   the barge from the high canal down to the low. Water, gates and barge animate
   in place; main.js drives the state and auto-solves on the barge's exit. */
.lock-overlay .overlay-panel { text-align: center; }
.lock-stage {
  position: relative; width: min(96%, 32rem); margin: 0.7rem auto 0.7rem;
  aspect-ratio: 4 / 3; border-radius: 14px; overflow: hidden;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  background-color: #7d868c; background-size: cover; background-position: center bottom;
}
.lock-stage.nudge { animation: lock-nudge 0.32s ease; }
@keyframes lock-nudge { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-7px); } 75% { transform: translateX(7px); } }
.lock-water {
  position: absolute; bottom: 0;
  /* translucent so the painted stone chamber shows through — reads as water
     filling the lock rather than a flat blue block */
  background: linear-gradient(to bottom, rgba(88,150,190,0.44), rgba(30,74,112,0.66));
  border-top: 3px solid rgba(214,238,250,0.85);
  box-shadow: inset 0 6px 10px -6px rgba(255,255,255,0.5);
}
.lock-water-high { left: 0; width: 24%; height: 62%; }
.lock-water-low { right: 0; width: 24%; height: 30%; }
.lock-water-chamber { left: 24%; width: 52%; transition: height 0.85s ease; }
.lock-water-chamber.high { height: 62%; }
.lock-water-chamber.low { height: 30%; }
.lock-gate {
  position: absolute; bottom: 0; width: 3.2%; height: 66%; z-index: 3;
  background: linear-gradient(to right, #3c2a18, #6b4a2c 45%, #2e2013);
  border: 1px solid #241a10; border-radius: 2px 2px 0 0;
  box-shadow: 0 0 4px rgba(0,0,0,0.5); transform-origin: bottom center;
  transition: transform 0.5s ease;
}
.lock-gate-upper { left: 22.4%; }
.lock-gate-lower { left: 74.4%; }
.lock-gate.open { transform: translateY(108%); }
.lock-barge {
  position: absolute; width: 21%; height: auto; z-index: 4;
  filter: drop-shadow(0 3px 3px rgba(0,0,0,0.4));
  transition: left 0.85s ease, bottom 0.85s ease;
}
.lock-barge.barge-high { left: 2%; bottom: 60%; }
.lock-barge.barge-chamber.high { left: 40%; bottom: 60%; }
.lock-barge.barge-chamber.low { left: 40%; bottom: 28%; }
.lock-barge.barge-low { left: 77%; bottom: 28%; }
.lock-glint {
  position: absolute; left: 47%; bottom: 7%; width: 1.5rem; height: 1.5rem; z-index: 2;
  border-radius: 50%; opacity: 0; pointer-events: none;
  background: radial-gradient(circle, #fff6cf 0%, var(--gold) 45%, rgba(201,162,39,0) 72%);
}
.lock-glint.show { opacity: 1; animation: glintpulse 1s ease-in-out infinite; }
@keyframes glintpulse { 0%,100% { transform: scale(0.85); opacity: 0.75; } 50% { transform: scale(1.15); opacity: 1; } }
.lock-controls { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin: 0.2rem 0 0.3rem; }

/* plant puzzle — drag the sapling on a canalside cross-section to the one spot
   where it will thrive for centuries; it glows in the sweet spot and, on a good
   drop, takes root and grows into the landmark tree (main.js). */
.plant-overlay .overlay-panel { text-align: center; }
.plant-stage {
  position: relative; width: min(96%, 30rem); margin: 0.7rem auto 0.8rem;
  aspect-ratio: 4 / 3; border-radius: 14px; overflow: hidden;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  background-color: #6f7d5a; background-size: cover; background-position: center;
  touch-action: none; -webkit-user-select: none; user-select: none;
}
.plant-sapling {
  position: absolute; width: 9%; height: auto; z-index: 3; cursor: grab;
  transform: translateX(-50%); transform-origin: 50% 100%;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.45));
}
.plant-sapling.dragging { cursor: grabbing; transition: none; transform: translateX(-50%) scale(1.08); }
.plant-sapling.springing { transition: left 0.4s ease, bottom 0.4s ease; }
.plant-sapling.valid { filter: drop-shadow(0 0 10px #8ff08a) drop-shadow(0 0 4px #8ff08a); }
.plant-tree {
  position: absolute; width: 30%; height: auto; z-index: 2; pointer-events: none;
  transform: translateX(-50%) scale(0.05); transform-origin: 50% 100%;
  opacity: 0; filter: drop-shadow(0 3px 4px rgba(0,0,0,0.4));
}
.plant-tree.grown { transform: translateX(-50%) scale(1); opacity: 1; transition: transform 0.9s cubic-bezier(0.34,1.3,0.5,1), opacity 0.6s ease; }

/* balance puzzle — a hoist beam with the bell on one arm; load stones onto the
   cradle on the other until the beam levels and the bell rises silently. The
   beam tilts by --tilt (set from the weight difference); read the tilt, not
   numbers. main.js re-tilts on each tap and auto-solves at balance. */
.balance-overlay .overlay-panel { text-align: center; }
.balance-stage {
  position: relative; width: min(96%, 32rem); margin: 0.7rem auto 0.5rem;
  aspect-ratio: 3 / 2; border-radius: 14px; overflow: hidden;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 rgba(0,0,0,0.3);
  background-color: #2c2115; background-size: cover; background-position: center top;
}
.balance-post { position: absolute; left: 50%; top: 30%; transform: translateX(-50%); width: 0.55rem; height: 62%; background: linear-gradient(to right, #3c2a18, #6b4a2c, #3c2a18); border-radius: 2px; }
.balance-fulcrum {
  position: absolute; left: 50%; top: 27%; transform: translateX(-50%);
  width: 0; height: 0; border-left: 1rem solid transparent; border-right: 1rem solid transparent;
  border-bottom: 1.3rem solid #6b4a2c; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5));
}
.balance-beam {
  position: absolute; left: 12%; right: 12%; top: 30%; height: 0.75rem; z-index: 3;
  background: linear-gradient(to bottom, #8a5f34, #4a3320); border: 1px solid #2e2013; border-radius: 4px;
  transform-origin: 50% 50%; transform: rotate(var(--tilt, 0deg));
  transition: transform 0.5s cubic-bezier(0.3,0.9,0.4,1); box-shadow: 0 2px 3px rgba(0,0,0,0.4);
}
.balance-beam.level { box-shadow: 0 0 12px var(--gold), 0 2px 3px rgba(0,0,0,0.4); }
.balance-arm { position: absolute; top: 90%; display: flex; flex-direction: column; align-items: center; }
.balance-arm-left { left: 2%; }
.balance-arm-right { right: 2%; }
.balance-rope { width: 3px; height: 1.5rem; background: #4a3320; }
.balance-bell { width: 4.2rem; height: auto; filter: drop-shadow(0 3px 3px rgba(0,0,0,0.45)); }
.balance-cradle {
  min-width: 5.5rem; min-height: 2.2rem; display: flex; flex-wrap: wrap; gap: 0.25rem;
  align-items: center; justify-content: center; padding: 0.3rem 0.4rem;
  background: linear-gradient(to bottom, rgba(60,42,24,0.4), rgba(40,28,16,0.75));
  border: 2px solid #4a3320; border-radius: 0 0 12px 12px; border-top: none;
}
.balance-stone {
  width: var(--sz, 2rem); height: var(--sz, 2rem); border-radius: 50%; padding: 0; border: 1px solid #201812;
  background: radial-gradient(circle at 38% 32%, #8b8377, #4c453b 78%);
  box-shadow: inset 0 -2px 3px rgba(0,0,0,0.5), inset 0 2px 2px rgba(255,255,255,0.14); cursor: pointer;
  transition: transform 0.08s;
}
.balance-stone:active { transform: scale(0.92); }
.balance-cradle .balance-stone { cursor: pointer; }
.balance-tray {
  display: flex; flex-wrap: wrap; gap: 0.55rem; justify-content: center; align-items: center;
  min-height: 3rem; padding: 0.5rem; background: rgba(0,0,0,0.06); border-radius: 10px; margin-bottom: 0.3rem;
}

/* matching puzzle */
/* ---------- logic grid (deduction matrix) — illustrated dossier board ---------- */
.logic-wrap { display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-start; }
.logic-clues {
  flex: 1 1 14rem; min-width: 12rem; margin: 0; padding-left: 1.3em;
  display: flex; flex-direction: column; gap: 0.4rem;
  font-size: 0.88rem; line-height: 1.3; color: var(--ink);
}
.logic-clues li { background: var(--paper-dark); border-radius: 8px; padding: 0.4em 0.6em; }
/* min-width:0 lets this flex item shrink below the grid's natural width so the
   grid SCROLLS inside the panel instead of forcing the whole panel wider than
   the screen. */
.logic-grid-scroll { flex: 1 1 auto; min-width: 0; max-width: 100%; overflow-x: auto; padding-bottom: 4px; }
.logic-grid {
  border-collapse: separate; border-spacing: 0; margin: 0 auto;
  background: #efe2c6; border: 2px solid var(--ink); border-radius: 10px; overflow: hidden;
}
.logic-grid th, .logic-grid td { border: 1px solid rgba(43,33,56,0.22); padding: 0; }
.logic-grid .logic-corner { border: none; background: transparent; }

/* Column header = a small illustrated gate emblem + a compact caption underneath
   (no rotated text, so long gate names can never spill out of the frame). */
.logic-colh {
  width: 5.4rem; vertical-align: bottom; padding: 0.35rem 0.15rem 0.3rem;
  background: rgba(201,183,144,0.4);
}
.logic-emblem {
  width: 3rem; height: 3rem; object-fit: contain; display: block; margin: 0 auto 0.15rem;
  border-radius: 6px; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.25));
}
.logic-colh-cap {
  display: block; font-size: 0.64rem; font-weight: 700; line-height: 1.05;
  color: var(--ink); overflow-wrap: anywhere;
}
/* Row header = a round portrait "mugshot" + the keeper's name on one line. */
.logic-rowh {
  text-align: left; padding: 0.2rem 0.55rem; background: rgba(201,183,144,0.4); white-space: nowrap;
}
.logic-face {
  width: 2.4rem; height: 2.4rem; object-fit: cover; border-radius: 50%;
  vertical-align: middle; margin-right: 0.45rem; border: 2px solid var(--paper);
  box-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.logic-rowh-cap { font-size: 0.8rem; font-weight: 700; color: var(--ink); vertical-align: middle; }

.logic-cell {
  width: 3rem; height: 3rem; margin: 0; padding: 0; cursor: pointer;
  background: rgba(247,236,217,0.55); border: none; border-radius: 0;
  font-size: 1.5rem; font-weight: 900; line-height: 1; color: var(--ink);
  display: flex; align-items: center; justify-content: center; transition: background 0.12s;
}
.logic-cell:hover { background: #fffbef; }
.logic-cell.mark-no { color: var(--danger); }
.logic-cell.mark-yes { color: var(--good); background: rgba(59,138,90,0.2); }

/* ---------- decant / measuring puzzle: glass workshop vessels ---------- */
/* Three unmarked glass vessels standing on a workbench shelf; the liquid (amber
   clock-oil) is a live CSS fill so it rises and falls as you pour between them.
   --decant-liquid can be overridden per puzzle for a different fluid. */
.decant-overlay { --decant-liquid: #d68a2e; }
.decant-overlay .overlay-panel { text-align: center; }
.decant-stage {
  display: flex; justify-content: center; align-items: flex-end; gap: clamp(0.7rem, 3vw, 1.8rem);
  margin: 0.9rem auto 0.4rem; padding: 0 0.4rem 0.7rem;
  border-bottom: 4px solid #6b4f2e; max-width: 30rem;
}
.decant-vessel {
  display: flex; flex-direction: column; align-items: center; gap: 0.35rem;
  cursor: pointer; -webkit-tap-highlight-color: transparent; user-select: none;
}
/* the glass body — rounded foot, faint blue-green glass tint, top rim */
.decant-jar {
  position: relative; width: 3.4rem; min-height: 3rem;
  border: 3px solid rgba(60,52,44,0.85); border-top-width: 4px;
  border-radius: 0.5rem 0.5rem 1.1rem 1.1rem; overflow: hidden;
  background: linear-gradient(105deg, rgba(255,255,255,0.28), rgba(200,224,224,0.10) 40%, rgba(120,150,150,0.16));
  box-shadow: inset 0 0 8px rgba(255,255,255,0.28), 0 4px 0 rgba(0,0,0,0.28);
  transition: box-shadow 0.15s, transform 0.1s;
}
/* the liquid: fills from the bottom, glossy surface line at the top edge */
.decant-fill {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: linear-gradient(180deg, color-mix(in srgb, var(--decant-liquid) 72%, #fff) 0 0.28rem, var(--decant-liquid) 0.28rem);
  border-top: 2px solid rgba(255,255,255,0.55);
  transition: height 0.28s cubic-bezier(.4,1.3,.5,1);
}
.decant-level {
  position: absolute; left: 0; right: 0; bottom: 0.25rem; z-index: 2;
  text-align: center; font-weight: 900; font-size: 1.05rem;
  color: #2a1c0c; text-shadow: 0 1px 0 rgba(255,255,255,0.5);
}
.decant-cap {
  font-size: 0.72rem; font-weight: 800; color: var(--ink);
  background: var(--paper-dark); border: 1.5px solid var(--ink); border-radius: 999px;
  padding: 0.05rem 0.55rem; letter-spacing: 0.02em;
}
.decant-vessel.selected .decant-jar {
  box-shadow: 0 0 0 3px var(--gold), inset 0 0 8px rgba(255,255,255,0.3), 0 4px 0 rgba(0,0,0,0.28);
  transform: translateY(-3px);
}
.decant-vessel.target-hit .decant-jar {
  box-shadow: 0 0 0 3px var(--good), inset 0 0 10px rgba(120,220,150,0.4), 0 4px 0 rgba(0,0,0,0.28);
}
.decant-tools { margin: 0.2rem 0 0.1rem; }

/* ---------- eraflip: the time-flip puzzle board ---------- */
/* One painted board, two faces (1672 / today). The SVG layer carries the
   interactive pits and the procedurally grown future; a brass pocket-watch
   button flips eras with the warp sfx. */
.eraflip-stage { display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-start; }
.eraflip-board {
  position: relative; flex: 1 1 24rem; min-width: min(100%, 20rem); aspect-ratio: 3 / 2;
  border: 3px solid var(--ink); border-radius: 14px; overflow: hidden;
  background-size: cover; background-position: center;
  box-shadow: 0 8px 0 rgba(0,0,0,0.3);
}
.eraflip-board.era-b-past { filter: sepia(0.18) saturate(1.05); }
.eraflip-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* painted tree/sapling sprites, anchored by their base to the chosen pit.
   The color grade (matched to the painted-in reference tree: olive-brown
   canopy, darker twilight values, more contrast) + the elliptical contact
   shadow seat the sprite into the scene's lighting. */
.eraflip-tree {
  position: absolute; width: auto; max-width: none; pointer-events: none;
  transform-origin: 50% 100%;
  filter: sepia(0.35) hue-rotate(15deg) brightness(0.78) contrast(1.15) saturate(1.1)
          drop-shadow(0 3px 5px rgba(12,12,20,0.55));
}
.eraflip-tree-shadow {
  position: absolute; height: 4.5%; transform: translateX(-50%);
  pointer-events: none; border-radius: 50%;
  background: radial-gradient(ellipse at center, rgba(10,10,18,0.5) 0%, rgba(10,10,18,0) 68%);
}
/* the date-stone sprite is generated from its own present-day scene reference —
   no tree grade, just a soft seat-in shadow */
.eraflip-stone-sprite { filter: drop-shadow(0 2px 4px rgba(12,12,20,0.45)); }

/* ---------- warmap (four_fronts): council map, front pins, convoy tokens ---- */
.warmap-board { cursor: default; }
.warmap-front { cursor: pointer; }
.warmap-front .front-halo { fill: rgba(178,58,44,0.12); stroke: #b23a2c; stroke-width: 0.7; stroke-dasharray: 2.4 1.6; }
.warmap-front .front-dot { fill: #b23a2c; }
.warmap-front.armed .front-halo {
  fill: rgba(232,181,58,0.2); stroke: var(--gold); stroke-dasharray: none; stroke-width: 1.1;
}
.warmap-front.armed .front-halo { animation: warmap-pulse 1.1s ease-in-out infinite; }
@keyframes warmap-pulse { 0%,100% { stroke-width: 1.1; } 50% { stroke-width: 1.9; } }
/* threat arrows (chrome): faded-red war-ink strokes from every front pin
   toward the city marker — drawn in code so they always point at Amsterdam */
.eraflip-svg .threat { fill: none; stroke: rgba(178,58,44,0.5); stroke-width: 1.2; stroke-linecap: round; }
.eraflip-svg .threat-under { fill: none; stroke: rgba(178,58,44,0.16); stroke-width: 2.8; stroke-linecap: round; }
.eraflip-svg .threat-head { fill: rgba(178,58,44,0.6); }
.eraflip-svg .city-dot { fill: #b23a2c; }
.eraflip-svg .city-ring { fill: none; stroke: rgba(178,58,44,0.45); stroke-width: 0.55; }
.warmap-token {
  position: absolute; width: 9%; transform: translate(-50%, -50%);
  cursor: pointer; filter: drop-shadow(0 2px 3px rgba(12,12,20,0.5));
}
/* front captions: HTML chips (self-sizing in every language — never SVG text) */
.warmap-cap {
  position: absolute; transform: translateX(-50%);
  background: rgba(24,16,34,0.82); color: #f6ecd6; border: 1px solid rgba(178,58,44,0.8);
  border-radius: 999px; padding: 0.1em 0.6em;
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.05em; white-space: nowrap;
  pointer-events: none;
}
.warmap-tray { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-top: 0.6rem; }
.warmap-tray-token {
  display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
  width: 30%; min-width: 76px; padding: 0.35rem 0.2rem;
  background: rgba(243,231,207,0.65); border: 1.5px solid rgba(120,94,60,0.55);
  border-radius: 10px; cursor: pointer; font: inherit;
}
.warmap-tray-token img { width: 74%; aspect-ratio: 1; object-fit: contain; }
.warmap-tray-token span { font-size: 0.78rem; font-weight: 700; color: #3c3226; text-align: center; }
.warmap-tray-token.chosen { border-color: var(--gold); background: rgba(232,181,58,0.28); box-shadow: 0 0 0 2px rgba(232,181,58,0.35); }
.warmap-tray-empty { font-style: italic; opacity: 0.75; font-size: 0.85rem; padding: 0.3rem; }

/* ---------- escape (press_gang): junctions, patrols, player ---------- */
.escape-board { max-width: 720px; margin: 0 auto; }
.escape-node { cursor: default; }
.escape-node.can { cursor: pointer; }
.escape-node .esc-halo { fill: transparent; stroke: transparent; }
.escape-node.can .esc-halo { fill: rgba(232,181,58,0.14); stroke: var(--gold); stroke-width: 0.6; stroke-dasharray: 2 1.4; }
.escape-node .esc-dot { fill: rgba(246,236,214,0.85); stroke: rgba(24,16,34,0.6); stroke-width: 0.4; }
.escape-node.is-exit .esc-dot { fill: #8fe6a6; }
.esc-exit-glow { fill: rgba(143,230,166,0.25); }
.esc-edge { stroke: rgba(246,236,214,0.35); stroke-width: 0.7; stroke-dasharray: 1.6 1.6; }
.esc-patrol { fill: #b23a2c; stroke: #f6ecd6; stroke-width: 0.5; }
.esc-player { fill: var(--gold); stroke: #3a2a12; stroke-width: 0.5; }
.esc-player-ring { fill: none; stroke: rgba(232,181,58,0.7); stroke-width: 0.7; }
.escape-board .eraflip-goals { z-index: 2; }
.eraflip-era-tag {
  position: absolute; top: 0.55rem; left: 0.6rem;
  background: rgba(24,16,34,0.85); color: var(--gold); border: 1.5px solid var(--gold);
  border-radius: 999px; padding: 0.15em 0.75em; font-weight: 800; font-size: 0.85rem;
  letter-spacing: 0.06em;
}
.eraflip-flip {
  position: absolute; right: 0.6rem; bottom: 0.6rem;
  display: inline-flex; align-items: center; gap: 0.45em;
  background: linear-gradient(180deg, #e9c25e, #b8862f); color: #2a1c0c;
  border: 2px solid #6b4c17; border-radius: 999px; padding: 0.4em 0.9em;
  font-weight: 800; font-size: 0.9rem; cursor: pointer;
  box-shadow: 0 4px 0 rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.5);
}
.eraflip-flip:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(0,0,0,0.35); }
.eraflip-flip-face { font-size: 1.15em; }
/* pits on the 1672 face */
.eraflip-pit { cursor: pointer; }
.eraflip-pit .pit-earth { fill: #4a3626; stroke: #2c2138; stroke-width: 0.5; }
.eraflip-pit .pit-halo { fill: rgba(232,181,58,0.16); stroke: var(--gold); stroke-width: 0.7; stroke-dasharray: 2.4 1.6; }
.eraflip-pit.chosen .pit-halo { fill: rgba(232,181,58,0.34); stroke-width: 1.1; stroke-dasharray: none; }
.eraflip-pit .pit-cap { fill: #f6ecd6; font-size: 5.4px; font-weight: 800; text-anchor: middle; paint-order: stroke; stroke: rgba(24,16,34,0.85); stroke-width: 1.6px; }
.eraflip-svg .stake { stroke: #a9834a; stroke-width: 1.1; stroke-dasharray: 2 1.2; }
/* canal sluice-board gauges (chrome): timber frame + removable slats.
   Tap to cycle closed → half → open; the side-rail chips mirror the state. */
.eraflip-gauge .gauge-frame { fill: rgba(24,16,34,0.55); stroke: #a9834a; stroke-width: 0.8; }
.eraflip-gauge .gauge-slat { fill: rgba(24,16,34,0.25); stroke: rgba(169,131,74,0.45); stroke-width: 0.35; }
.eraflip-gauge .gauge-slat.in { fill: #a9834a; stroke: #6d5330; }
.eraflip-gauge.unset .gauge-frame { stroke-dasharray: 2 1.4; }
.eraflip-svg .board-cap { fill: #f6ecd6; font-size: 4.6px; font-weight: 700; paint-order: stroke; stroke: rgba(24,16,34,0.8); stroke-width: 1.4px; }
.eraflip-svg .board-cap.mid { text-anchor: middle; }
.eraflip-svg .board-cap.dim { opacity: 0.55; font-size: 3.6px; font-style: italic; }
/* ledger_ink (Ch8): handwriting strips + ink-state grading. Every strip is
   normalized to ONE mid-brown base (#6b4a26) so these filters can grade both
   ways — brightness() down for blacks, opacity up for ghosts. Fresh inks in
   1672 are deliberately near-indistinguishable; the AGED states differ. */
.ledger-strip { position: absolute; transform: translate(-50%, -50%); pointer-events: none; max-width: none; }
.ledger-strip.ink-unset { opacity: 0.13; filter: saturate(0.35) brightness(1.4); }             /* washed by the flood */
.era-b-past .ledger-strip.ink-gall { filter: brightness(0.4) hue-rotate(215deg) saturate(0.4); } /* fresh gall: blue-black */
.era-b-past .ledger-strip.ink-carbon { filter: brightness(0.22) saturate(0.2); }               /* fresh lampblack */
.era-b-past .ledger-strip.ink-bister { filter: brightness(0.55) saturate(1.15); }              /* fresh bister: dark warm */
.era-b-present .ledger-strip.ink-gall { filter: none; }                                        /* aged gall: warm brown */
.era-b-present .ledger-strip.ink-carbon { filter: brightness(0.3) saturate(0.5); }             /* lampblack never fades */
.era-b-present .ledger-strip.ink-bister { opacity: 0.3; filter: brightness(1.45) saturate(0.6); } /* bister: a ghost */
.ledger-strip.ink-forged { filter: brightness(0.18) saturate(0.3); }                           /* the line time forgot */
.ledger-strip.ink-forged.glare { animation: forged-glare 1.6s ease-in-out infinite; }
@keyframes forged-glare {
  0%, 100% { filter: brightness(0.18) saturate(0.3) drop-shadow(0 0 2px rgba(216,58,58,0)); }
  50% { filter: brightness(0.18) saturate(0.3) drop-shadow(0 0 5px rgba(216,58,58,0.9)); }
}
/* ink-pot pits: tap to cycle gall → lampblack → bister */
.eraflip-inkpot .pot-body { fill: rgba(24,16,34,0.62); stroke: #a9834a; stroke-width: 0.5; }
.eraflip-inkpot.unset .pot-body { stroke-dasharray: 1.6 1.2; }
/* IMMERSIVE puzzle presentation (war map): the board art fills the frame
   like the 3D examine — no popup card. Backdrop is the keeper's hut,
   blurred and darkened; orders/tray/actions float as parchment chrome. */
.puzzle-overlay.immersive {
  position: fixed; inset: 0; z-index: 40; display: flex; padding: 0;
  align-items: stretch; background: #14101e;
}
.immersive .imm-backdrop {
  position: absolute; inset: -2rem; background-size: cover; background-position: center;
  filter: blur(9px) brightness(0.5) saturate(0.85);
}
.immersive .imm-title {
  position: absolute; top: 0.6rem; left: 0.9rem; z-index: 3;
  color: #f6ecd6; font-weight: 800; font-size: clamp(1rem, 2.2vw, 1.35rem);
  text-shadow: 0 2px 6px rgba(0,0,0,0.8);
}
.immersive .imm-stage {
  position: relative; z-index: 2; flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; justify-content: center; padding: 2.6rem 0.4rem 0.8rem 0.8rem;
}
.immersive .imm-board {
  position: relative; aspect-ratio: 3 / 2; max-width: 100%; max-height: 100%;
  width: auto; height: 100%;
  border: 3px solid rgba(24,16,34,0.8); border-radius: 10px; overflow: hidden;
  background-size: cover; background-position: center;
  box-shadow: 0 22px 60px rgba(0,0,0,0.65);
}
.immersive .imm-rail {
  position: relative; z-index: 2; flex: 0 0 clamp(15rem, 30vw, 22rem);
  display: flex; flex-direction: column; gap: 0.5rem;
  padding: 2.6rem 0.8rem 0.8rem 0.4rem; overflow-y: auto;
}
.immersive .imm-prompt {
  background: rgba(246,236,214,0.92); border-radius: 8px; padding: 0.5rem 0.65rem;
  font-size: clamp(0.72rem, 1.5vw, 0.88rem); line-height: 1.35;
}
.immersive .imm-clues {
  margin: 0; background: rgba(246,236,214,0.92); border-radius: 8px;
  padding: 0.5rem 0.65rem 0.5rem 1.6rem; font-size: clamp(0.7rem, 1.45vw, 0.85rem);
  max-height: 34vh; overflow-y: auto;
}
.immersive .imm-tray { background: rgba(24,16,34,0.55); border-radius: 10px; padding: 0.45rem; }
.immersive .imm-tray .warmap-tray-empty { color: #e8d9ae; font-style: italic; opacity: 0.9; }
.immersive .imm-hints { margin: 0; }
.immersive .imm-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: auto; }
/* the drag ghost that follows the pointer */
.warmap-drag-ghost {
  position: fixed; z-index: 99; width: clamp(3rem, 8vw, 4.5rem); pointer-events: none;
  transform: translate(-50%, -60%); opacity: 0.9;
  filter: drop-shadow(0 6px 10px rgba(0,0,0,0.55));
}
.warmap-overlay.dragging .warmap-front .front-halo { fill: rgba(232,181,58,0.3); stroke-width: 1.2; }

/* going train (gear_train): brass wheels on the engraved plate. Every gear is
   its own square SVG so circles stay round on the 3:2 plate; the .gt-spin
   group turns at the wheel's TRUE computed speed (inline animation-duration)
   the moment the train reaches it. */
.gt-gear { position: absolute; transform: translate(-50%, -50%); aspect-ratio: 1; z-index: 2; cursor: grab; }
.gt-gear.fixed { cursor: default; }
.gt-gear svg { width: 100%; height: 100%; display: block; overflow: visible; }
.gt-gear.chosen { filter: drop-shadow(0 0 8px rgba(232,181,58,0.9)); }
.gt-spin { transform-origin: 50px 50px; }
.gt-spin.turning { animation: gt-turn linear infinite; }
.gt-spin.turning.widdershins { animation-direction: reverse; }
@keyframes gt-turn { to { transform: rotate(360deg); } }
.gt-body.gt-wheel { fill: url(#gtBrassW); stroke: #4a3512; stroke-width: 1.1; }
.gt-body.gt-pinion { fill: url(#gtBrassP); stroke: #46320f; stroke-width: 1.0; }
.gt-body.gt-escwheel { fill: url(#gtBrassP); stroke: #46320f; stroke-width: 1.0; }
.gt-depth { fill: #241905; opacity: 0.85; stroke: none; }
.gt-engrave { fill: none; stroke: #6b4d1d; stroke-width: 0.9; opacity: 0.85; }
.gt-hubring { fill: none; stroke: #5d4419; stroke-width: 1.6; }
.gt-boss { fill: url(#gtBrassP); stroke: #3f2d0e; stroke-width: 0.8; }
.gt-slot { fill: none; stroke: #2f2109; stroke-width: 1.1; stroke-linecap: round; }
.gt-plaque { fill: #e6c672; stroke: #4a3512; stroke-width: 0.7; }
.gt-plaqueline { fill: none; stroke: #6e5220; stroke-width: 0.7; stroke-linecap: round; }
.gt-glint { fill: none; stroke: rgba(255,246,214,0.5); stroke-width: 1.4; stroke-linecap: round; }
.gt-gear.good .gt-body.gt-escwheel { fill: #a9c77c; }
.gt-gear.bad .gt-body.gt-escwheel { fill: #d98b6e; }
.gt-num {
  fill: #2b1d0c; font-size: 13px; font-weight: 800; text-anchor: middle;
  paint-order: stroke; stroke: #f0e2bd; stroke-width: 3px;
}
.gt-arbor {
  position: absolute; transform: translate(-50%, -50%); width: 11%; aspect-ratio: 1;
  border-radius: 50%; border: 3px dashed transparent; background: transparent;
  cursor: pointer; z-index: 1; padding: 0;
}
.gt-arbor.armed {
  border-color: rgba(232,181,58,0.95); background: rgba(232,181,58,0.12);
  animation: gt-arbor-pulse 1.1s ease-in-out infinite;
}
.geartrain-overlay.dragging .gt-arbor { border-color: rgba(232,181,58,0.85); }
@keyframes gt-arbor-pulse { 50% { box-shadow: 0 0 0 6px rgba(232,181,58,0.18); } }
.gt-meter {
  display: flex; align-items: baseline; gap: 0.5rem; color: #e8d9ae;
  background: rgba(24,16,34,0.75); border-radius: 10px; padding: 0.45rem 0.65rem;
}
.gt-meter-big { font-size: clamp(1.2rem, 2.6vw, 1.6rem); font-weight: 900; }
.gt-meter.good .gt-meter-big { color: #8fe08f; }
.gt-meter.bad .gt-meter-big { color: #ff9a7a; }
.gt-meter-label { flex: 1; font-size: clamp(0.62rem, 1.3vw, 0.74rem); opacity: 0.85; }
.gt-meter-target { font-size: clamp(0.72rem, 1.5vw, 0.85rem); font-weight: 700; opacity: 0.9; white-space: nowrap; }
.gt-offbeat {
  font-size: clamp(0.66rem, 1.4vw, 0.78rem); color: #ffd9c9;
  background: rgba(120,40,20,0.5); border-radius: 8px; padding: 0.35rem 0.55rem;
}
.gt-tray { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.gt-tray-token {
  width: clamp(3.1rem, 7vw, 4.1rem); padding: 0.25rem; cursor: grab;
  display: flex; flex-direction: column; align-items: center; gap: 0.1rem;
  background: rgba(246,236,214,0.14); border: 2px solid rgba(246,236,214,0.25); border-radius: 10px;
}
.gt-tray-token svg { width: 100%; aspect-ratio: 1; display: block; }
.gt-tray-token.chosen { border-color: #e8b53a; box-shadow: 0 0 0 2px rgba(232,181,58,0.5); }
.gt-token-nums { color: #f6ecd6; font-weight: 800; font-size: clamp(0.68rem, 1.5vw, 0.82rem); }
.gt-token-nums em { font-style: normal; opacity: 0.6; }
.gt-legend { color: #e8d9ae; font-size: clamp(0.6rem, 1.25vw, 0.7rem); opacity: 0.85; padding: 0 0.2rem; }
.gt-drag-ghost { width: clamp(3.4rem, 9vw, 5rem); aspect-ratio: 1; }
.gt-drag-ghost svg { width: 100%; height: 100%; }

/* candle blow-out (mechanic 'flame'): a live flame licks over the lantern
   art and gutters down as the player blows; the whole stage sinks into
   darkness with it (driven per-frame from main.js updateSensorDom). */
.sensor-candleflame {
  /* NOTE: the stage itself carries class "sensor-flame" (its mechanic name),
     so the live flame element uses this distinct class */
  position: absolute; width: 7%; aspect-ratio: 0.55; z-index: 2;
  transform: translate(-50%, -100%); transform-origin: 50% 100%;
  pointer-events: none;
  background: radial-gradient(ellipse 50% 62% at 50% 78%, #fff8d8 0%, #ffd873 34%, #ff9b2e 58%, rgba(214,90,24,0.55) 76%, transparent 88%);
  border-radius: 50% 50% 42% 42% / 68% 68% 30% 30%;
  filter: blur(0.6px) drop-shadow(0 0 14px rgba(255,170,60,0.85));
  animation: flame-flicker 0.42s ease-in-out infinite;
}
@keyframes flame-flicker {
  0%, 100% { scale: 1 1; rotate: -1.6deg; }
  30% { scale: 1.06 0.94; rotate: 1.8deg; }
  65% { scale: 0.95 1.05; rotate: -0.8deg; }
}
.sensor-dark {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background: radial-gradient(circle at 50% 45%, rgba(4,3,8,0.86) 0%, #04030a 100%);
  opacity: 0;
}

/* chronicle: collected real-history pages in the trunk */
.chron-count { font-weight: 800; margin-bottom: 0.5rem; }
.chron-list { display: flex; flex-direction: column; gap: 0.6rem; max-height: 52vh; overflow-y: auto; }
.chron-card {
  background: #f6ecd6; border: 2px solid var(--ink, #241a33); border-radius: 10px;
  padding: 0.6rem 0.8rem; box-shadow: 0 3px 0 rgba(0,0,0,0.2);
}
.chron-card.locked { opacity: 0.55; border-style: dashed; }
.chron-title { font-weight: 800; margin-bottom: 0.25rem; }
.chron-text { font-size: 0.88rem; line-height: 1.45; white-space: pre-line; }

/* hum-the-bell (mechanic 'hum'): resonance rings ripple out of the stones
   while a steady note holds; container opacity driven per-frame. */
.sensor-rings { position: absolute; inset: 0; z-index: 2; pointer-events: none; opacity: 0; }
.sensor-rings i {
  position: absolute; left: 50%; top: 55%; width: 26%; aspect-ratio: 1;
  margin: -13% 0 0 -13%; border-radius: 50%;
  border: 3px solid rgba(255, 216, 130, 0.85);
  box-shadow: 0 0 12px rgba(255, 190, 80, 0.35), inset 0 0 12px rgba(255, 190, 80, 0.25);
  animation: hum-ring 2.1s linear infinite;
}
.sensor-rings i:nth-child(2) { animation-delay: 0.7s; }
.sensor-rings i:nth-child(3) { animation-delay: 1.4s; }
@keyframes hum-ring {
  from { transform: scale(0.3); opacity: 0.9; }
  to { transform: scale(2.7); opacity: 0; }
}

/* ball maze (counterweight_channel): live pieces over the drain-plate art.
   The board is 3:2 so widths are set in %-of-board-width with aspect-ratio
   keeping the circles round. The ball is moved per-frame from main.js. */
.ballmaze-board { touch-action: none; cursor: grab; }
.bm-ball {
  position: absolute; transform: translate(-50%, -50%); aspect-ratio: 1;
  border-radius: 50%; z-index: 3; pointer-events: none;
  background: radial-gradient(circle at 34% 30%, #ffe9b0, #c99b3f 45%, #6e4f1a 85%);
  box-shadow: 0 3px 6px rgba(0,0,0,0.55), inset 0 -2px 4px rgba(0,0,0,0.3);
}
.bm-hole {
  /* the hole itself is painted in the plate art — this only deepens it */
  position: absolute; transform: translate(-50%, -50%); aspect-ratio: 1;
  border-radius: 50%; z-index: 1; pointer-events: none;
  background: radial-gradient(circle, rgba(5,3,10,0.6) 45%, rgba(5,3,10,0.3) 70%, transparent 100%);
}
.bm-goal {
  position: absolute; transform: translate(-50%, -50%); aspect-ratio: 1;
  border-radius: 50%; z-index: 2; pointer-events: none;
  border: 3px solid rgba(232,181,58,0.85);
  background: radial-gradient(circle, rgba(20,12,30,0.9) 45%, rgba(232,181,58,0.25) 70%, transparent 100%);
  animation: bm-goal-pulse 1.6s ease-in-out infinite;
}
.bm-goal.done { border-color: #8fe08f; animation: none; }
@keyframes bm-goal-pulse { 50% { box-shadow: 0 0 14px 4px rgba(232,181,58,0.4); } }
.bm-tip {
  color: #e8d9ae; font-size: clamp(0.66rem, 1.4vw, 0.78rem);
  background: rgba(24,16,34,0.75); border-radius: 8px; padding: 0.4rem 0.6rem;
  transition: background 0.25s;
}
.bm-tip.fell { background: rgba(120,40,20,0.8); color: #ffd9c9; }

/* closeup layer: zoomed prop art (a notice, an inscription) shown above the
   scene while its dialogue lines play. Sits UNDER the dialogue popup
   (z-index) so the reading lines stay visible over the scrim. */
/* The zoom lives in the UPPER region so it NEVER covers the dialogue box: the
   layer stops ~32% above the bottom (where the dialogue sits) and centres the
   framed image within the remaining space. The scrim still dims the whole
   screen and stays tap-through so the dialogue underneath keeps advancing; only
   the ✕ is interactive. */
.closeup-layer {
  position: fixed; top: 0; left: 0; right: 0; bottom: 32%;
  z-index: 60; display: flex; align-items: center; justify-content: center;
  pointer-events: none; padding: 3.2rem 1.2rem 0;
}
.closeup-layer .closeup-scrim { position: fixed; inset: 0; z-index: -1; background: rgba(12, 8, 20, 0.72); }
.closeup-frame { position: relative; display: inline-flex; pointer-events: none; max-height: 100%; }
.closeup-layer .closeup-img {
  position: relative; display: block; max-width: min(82vw, 30rem); max-height: min(58vh, 100%);
  border: 4px solid var(--ink, #241a33); border-radius: 10px;
  box-shadow: 0 18px 50px rgba(0,0,0,0.6);
  animation: closeup-in 0.35s ease-out;
}
.closeup-close {
  position: absolute; top: -0.7rem; right: -0.7rem; pointer-events: auto;
  width: 2.5rem; height: 2.5rem; border-radius: 50%;
  background: var(--paper); color: var(--ink); border: 2px solid var(--ink);
  font-size: 1.05rem; font-weight: 900; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 3px 0 var(--ink), 0 6px 16px rgba(0,0,0,0.55);
}
.closeup-close:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--ink), 0 3px 10px rgba(0,0,0,0.5); }
@keyframes closeup-in { from { transform: scale(0.6); opacity: 0; } to { transform: scale(1); opacity: 1; } }
/* brick_message (Ch5): the scratched cog-and-arrow mark. Normalized to pale
   chalk (#d8cfc0) so filters grade it: fresh incision in wet 1672 brick,
   crisp survivor under the eave today, or a rain-worn ghost. */
.brick-mark { position: absolute; transform: translate(-50%, -50%); pointer-events: none; max-width: none; }
.brick-mark.fresh { opacity: 0.95; filter: drop-shadow(0.5px 1px 0.5px rgba(24,16,34,0.55)); }
.brick-mark.crisp { opacity: 0.92; filter: brightness(0.72) sepia(0.3) contrast(1.1) drop-shadow(0.5px 1px 0.5px rgba(24,16,34,0.5)); }
.brick-mark.faint { opacity: 0.3; filter: brightness(0.95) sepia(0.3); }
/* wardmaze (archive_labyrinth, Ch8): file drawers + ward plaques.
   A drawer only arms (glows, accepts the tap) when the player stands at its
   junction; a wrongly opened one keeps a red X so the sting is remembered. */
.wardmaze-drawer { cursor: pointer; }
.wardmaze-drawer .drawer-halo { fill: transparent; }
.wardmaze-drawer .drawer-face { fill: rgba(96,66,38,0.9); stroke: #3c2c1c; stroke-width: 0.5; }
.wardmaze-drawer .drawer-pull { fill: #d8b25e; stroke: #6d5330; stroke-width: 0.3; }
.wardmaze-drawer.armed .drawer-halo { fill: rgba(232,181,58,0.28); stroke: var(--gold); stroke-width: 0.8; animation: drawer-armed 1.1s ease-in-out infinite; }
.wardmaze-drawer.opened { cursor: default; opacity: 0.75; }
.wardmaze-drawer.opened .drawer-x { stroke: #d83a3a; stroke-width: 0.9; fill: none; }
@keyframes drawer-armed { 0%, 100% { stroke-opacity: 0.45; } 50% { stroke-opacity: 1; } }
.wardmaze-plaque {
  position: absolute; transform: translate(-15%, -50%);
  background: rgba(24,16,34,0.86); color: #e8d9ae;
  border: 1.5px solid #a9834a; border-radius: 4px;
  font-size: clamp(0.5rem, 1.4vw, 0.72rem); font-weight: 700; letter-spacing: 0.02em;
  padding: 0.14em 0.5em; white-space: nowrap; pointer-events: none;
}
/* goal badges on the present face — HTML chips so any label length fits */
.eraflip-goals {
  position: absolute; top: 0.55rem; right: 0.6rem;
  display: flex; gap: 0.4rem; flex-wrap: wrap; justify-content: flex-end;
  max-width: 70%; pointer-events: none;
}
/* a single long chip (the "nothing chosen yet" note) must wrap INSIDE the
   container instead of running under the era tag on the board's other side */
.eraflip-goals .goal { max-width: 100%; }
.eraflip-goals .goal {
  display: inline-flex; align-items: center; gap: 0.35em;
  background: rgba(24,16,34,0.88); color: #f6ecd6;
  border: 1.5px solid; border-radius: 999px;
  padding: 0.14em 0.7em; font-size: 0.8rem; font-weight: 700; white-space: nowrap;
}
.eraflip-goals .goal.ok { border-color: #6fbf8a; }
.eraflip-goals .goal.ok b { color: #8fe6a6; }
.eraflip-goals .goal.bad { border-color: #c96b6b; }
.eraflip-goals .goal.bad b { color: #e89a9a; }
.eraflip-goals .goal.none { border-color: rgba(243,231,207,0.4); font-style: italic; }
/* the choice side-rail */
.eraflip-side { flex: 1 1 13rem; min-width: 12rem; display: flex; flex-direction: column; gap: 0.7rem; }
.eraflip-clues { flex: initial; }
.eraflip-group { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.eraflip-group-cap { flex-basis: 100%; font-size: 0.78rem; font-weight: 800; opacity: 0.75; text-transform: uppercase; letter-spacing: 0.05em; }
.eraflip-chip {
  background: var(--paper-dark); border: 2px solid var(--ink); border-radius: 999px;
  padding: 0.32em 0.85em; font-weight: 700; font-size: 0.9rem; cursor: pointer;
}
.eraflip-chip.chosen { background: var(--gold); box-shadow: inset 0 0 0 2px rgba(255,255,255,0.5); }
.eraflip-chip:disabled { opacity: 0.55; cursor: default; }

/* ---------- 3D evidence examination (js/examine3d.js) ---------- */
/* A full-viewport mode: the WebGL canvas fills the whole display (3D content has
   no fixed aspect, so nothing can crop off), and the HUD (title / prompt / reveal
   callouts / done) uses normal readable text with safe-area padding. HUD is
   click-through except the Done button so drags reach the model. */
.examine-layer {
  position: fixed; inset: 0;
  overflow: hidden; z-index: 60;
  background: radial-gradient(circle at 50% 42%, #2c2440 0%, #171021 80%);
}
.examine-layer[hidden] { display: none; }
/* atmospheric backdrop image (set per-examination by main.js) + a vignette scrim
   so the edges darken and the object + HUD text pop against it. */
.examine-bg {
  position: absolute; inset: 0; background-size: cover; background-position: center;
  filter: brightness(0.62) saturate(0.96) blur(1px); transform: scale(1.05);
}
.examine-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(circle at 50% 40%, rgba(18,12,26,0) 0%, rgba(12,8,18,0.5) 68%, rgba(6,4,12,0.86) 100%);
}
.examine-stage { position: absolute; inset: 0; }
.examine-stage canvas { width: 100%; height: 100%; display: block; }
/* aim loupe — the magnifier-style hint the object gives when a hidden detail is
   turned toward you. It follows that detail's screen position (set by main.js)
   and pulses when it's well in the light, so tapping there reveals it. It never
   marks a clue until you've actually caught it in the light. pointer-events:none
   so the tap passes through to the WebGL canvas (examine3d handles the hit). */
.examine-reticle {
  position: absolute; left: 50%; top: 50%;
  width: min(23vmin, 108px); height: min(23vmin, 108px);
  pointer-events: none; opacity: 0; z-index: 2;
  transform: translate(-50%, -50%) scale(1.3);
  transition: opacity 0.18s ease-out, transform 0.18s ease-out;
}
.examine-reticle::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid rgba(255,214,138,0.92);
  background: radial-gradient(circle, rgba(255,214,138,0.14) 0%, rgba(255,214,138,0) 64%);
  box-shadow: 0 0 20px 5px rgba(255,196,92,0.34), inset 0 0 16px rgba(255,208,120,0.26);
}
.examine-reticle::after { /* cross-hair ticks — reads as a loupe, not a target */
  content: ''; position: absolute; inset: 34%; border-radius: 50%;
  border: 1px solid rgba(255,224,160,0.55);
}
.examine-reticle.active::before { animation: reticle-pulse 1.15s ease-in-out infinite; }
@keyframes reticle-pulse {
  0%, 100% { transform: scale(0.93); opacity: 0.82; }
  50%      { transform: scale(1.07); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .examine-reticle.active::before { animation: none; }
}
.examine-hud {
  position: absolute; inset: 0; pointer-events: none; display: flex; flex-direction: column;
  padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)
           env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
}
.examine-titlebar { display: flex; align-items: center; gap: 0.55rem; padding: 0.7rem 1rem 0; }
.examine-title { font-weight: 800; font-size: 1.15rem; color: #f4e7cf; text-shadow: 0 2px 6px rgba(0,0,0,0.75); }
.examine-prompt { padding: 0.15rem 1rem 0; max-width: 34rem; font-size: 0.9rem; line-height: 1.35; color: #e8dcc4; text-shadow: 0 1px 4px rgba(0,0,0,0.75); }
.examine-found { margin-top: auto; padding: 0 1rem; display: flex; flex-direction: column; gap: 0.4rem; }
.examine-callout {
  align-self: flex-start; max-width: 92%;
  background: rgba(24,16,34,0.92); border: 2px solid var(--gold); border-radius: 12px;
  padding: 0.5rem 0.8rem; color: #f6ecd6; font-size: 0.88rem; line-height: 1.35;
  box-shadow: 0 6px 20px rgba(0,0,0,0.5); animation: callout-in 0.28s ease-out;
}
@keyframes callout-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.examine-done { pointer-events: auto; margin: 0.6rem 1rem 1rem; align-self: center; }
.examine-done[hidden] { display: none; }

/* MATCHING (drag-to-align): the two columns are visually distinct — the LEFT
   column is fixed parchment "who" cards, the RIGHT column is loose guest-book
   slips you drag up and down until each sits on its partner's row. Every card
   has the SAME height so a row reads as a pair at a glance. */
.match-draghint { font-size: 0.85rem; opacity: 0.8; margin-bottom: 0.55rem; font-style: italic; }
.match-board { display: grid; grid-template-columns: 1fr 1fr; gap: 0 1.4rem; align-items: start; }
.match-col { display: flex; flex-direction: column; gap: 0.55rem; min-width: 0; }
.match-item {
  display: flex; align-items: center; gap: 0.55em;
  height: 5.6rem; overflow: hidden;
  border-radius: 10px; padding: 0.4em 0.7em;
  font-size: 0.84rem; line-height: 1.25; position: relative;
}
/* who: fixed parchment cards, gold spine, calm */
.match-left {
  background: var(--paper-dark); border: 2px solid var(--ink);
  border-left: 6px solid var(--gold, #c9a227);
}
/* guest-book slips: cooler tint, dashed edge, grip — these clearly move */
.match-right {
  background: rgba(96, 118, 148, 0.14); border: 2px dashed var(--accent-cool, #4a6a8a);
  cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none;
}
.match-right:active { cursor: grabbing; }
.match-right.dragging {
  z-index: 30; box-shadow: 0 10px 26px rgba(0,0,0,0.4);
  border-style: solid; background: rgba(96, 118, 148, 0.28); opacity: 0.95;
}
.match-right .seq-grip { flex: 0 0 auto; opacity: 0.55; font-size: 1.05rem; }
.match-num {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  background: var(--ink); color: var(--paper, #f3ead6); border-radius: 999px;
  width: 1.5em; height: 1.5em; font-size: 0.72rem; font-weight: 700;
}
.match-right .match-num { background: var(--accent-cool, #4a6a8a); }
.match-item .puzzle-img {
  width: 3.2rem; height: 3.2rem; aspect-ratio: auto; flex: 0 0 auto; object-fit: cover;
}
.match-item .match-cap {
  flex: 1; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}

.hints-panel { margin-top: 0.8rem; display: flex; flex-direction: column; gap: 0.3rem; }
.hint-text { background: rgba(201,162,39,0.18); border-left: 4px solid var(--gold); padding: 0.4em 0.7em; font-size: 0.9rem; }
.puzzle-feedback { font-weight: 700; margin-top: 0.5rem; min-height: 1.4em; }
.puzzle-feedback.correct { color: var(--good); }
.puzzle-feedback.wrong { color: var(--danger); animation: shake 0.3s; }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  75% { transform: translateX(6px); }
}

/* confrontation */
.confrontation-overlay { flex-direction: column; }
/* The debate fills the viewport, but its composition (witness + dialogue box)
   stays no wider than the 3:2 scene band, centred — so the framing matches the
   scene behind it on ultrawide screens. Centred with auto margins (NOT a
   translate) because .dialogue-wrap's fade-in animation ends at transform:none. */
.confrontation-overlay .dialogue-wrap {
  left: 0; right: 0; margin: 0 auto; width: min(100vw, 150dvh);
}
.conf-topbar { position: absolute; top: 1rem; left: 1.5rem; right: 1.5rem; display: flex; justify-content: space-between; align-items: center; z-index: 30; }
.conf-counter { font-weight: 700; font-size: 0.95rem; color: var(--paper); background: rgba(20,15,26,0.55); border: 1px solid var(--gold); border-radius: 999px; padding: 0.15em 0.7em; }
.conf-strikes { font-size: 1.3rem; letter-spacing: 0.2em; }
.conf-box { border-color: var(--danger); }
.conf-feedback { margin-top: 0.5rem; font-weight: 700; color: var(--danger); }
.conf-actions {
  display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: flex-end; margin-top: 0.8rem;
}
/* Press affordance: a pulsing gold "?" badge marks a statement worth pressing
   that you haven't pressed yet — teaches players the verb is meaningful. */
.conf-press-btn { position: relative; }
.conf-press-hot { animation: conf-press-pulse 1.6s ease-out infinite; }
.conf-press-badge {
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: 0.4em; width: 1.2em; height: 1.2em; border-radius: 50%;
  background: var(--gold); color: #1a1420; font-weight: 800; font-size: 0.85em;
}
@keyframes conf-press-pulse {
  0% { box-shadow: 0 0 0 0 rgba(201,162,39,0.55); }
  70% { box-shadow: 0 0 0 0.7rem rgba(201,162,39,0); }
  100% { box-shadow: 0 0 0 0 rgba(201,162,39,0); }
}
html.reduce-motion .conf-press-hot { animation: none; }

/* ---------- confrontation title card + first-time tutorial ---------- */
.conf-splash {
  position: fixed; inset: 0; z-index: 44; overflow: hidden; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 50% 45%, rgba(60,40,10,0.85), rgba(12,9,16,0.96) 70%);
}
.conf-splash-card { text-align: center; padding: 2rem 1rem; animation: conf-splash-in 0.5s cubic-bezier(.2,.8,.3,1.25) both; }
.conf-splash-rule { height: 3px; width: 72%; margin: 0.8rem auto; background: linear-gradient(90deg, transparent, var(--gold), transparent); }
.conf-splash-title {
  font-size: clamp(2rem, 6.5vw, 3.8rem); font-weight: 900; letter-spacing: 0.06em;
  color: var(--paper); text-transform: uppercase;
  text-shadow: 0 3px 0 rgba(0,0,0,0.6), 0 0 2rem rgba(201,162,39,0.6);
}
.conf-splash-sub { margin-top: 0.5rem; font-size: clamp(0.95rem, 3vw, 1.4rem); color: var(--gold); font-weight: 700; }
.conf-splash-tap { margin-top: 1.4rem; font-size: 0.9rem; color: var(--paper); animation: tap-blink 1.4s ease-in-out infinite; }
@keyframes conf-splash-in { from { transform: scale(0.72) rotate(-3deg); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes tap-blink { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.9; } }
html.reduce-motion .conf-splash-card { animation: none; }
html.reduce-motion .conf-splash-tap { animation: none; opacity: 0.7; }
.conf-tut {
  position: fixed; inset: 0; z-index: 44; padding: 1.2rem; cursor: pointer;
  display: flex; align-items: center; justify-content: center; background: rgba(12,9,16,0.86);
}
.conf-tut-card {
  max-width: 34rem; background: var(--paper); color: var(--ink);
  border: 3px solid var(--gold); border-radius: 16px; padding: 1.4rem 1.6rem;
  box-shadow: 0 1rem 3rem rgba(0,0,0,0.5); cursor: default;
}
.conf-tut-title { font-size: 1.4rem; font-weight: 800; margin-bottom: 0.8rem; text-align: center; }
.conf-tut-list { margin: 0 0 1rem 1.1rem; padding: 0; }
.conf-tut-list li { margin-bottom: 0.7rem; line-height: 1.45; }
.conf-tut-card .btn { display: block; margin: 0 auto; }

/* evidence */
.evidence-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0.7rem; margin: 0.8rem 0; }
.evidence-card {
  background: var(--paper-dark); border: 2px solid var(--ink); border-radius: 10px;
  padding: 0.6em; text-align: center; cursor: pointer;
}
.evidence-card.static { cursor: default; text-align: left; }
.evidence-card img { height: 4rem; object-fit: contain; margin: 0 auto 0.4em; }
.evidence-name { font-weight: 700; font-size: 0.85rem; }
.evidence-desc { font-size: 0.8rem; opacity: 0.8; margin-top: 0.3em; }

/* trunk */
.tab-row { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.8rem; }
.tab-btn.active { background: var(--gold); }
.trunk-content { min-height: 8rem; margin-bottom: 1rem; }
.journal-list { padding-left: 1.2rem; display: flex; flex-direction: column; gap: 0.5rem; }

/* chapter end — a full-viewport modal (NOT the .screen letterbox: it must own the
   whole display, and combining it with .screen's centering transform + 3:2 height
   shoved it off the corner on non-3:2 displays, e.g. an unfolded Galaxy Fold).
   overflow-y:auto + safe-area padding so it never clips on short/tall screens. */
.chapter-end {
  position: fixed; inset: 0; z-index: 70; display: flex; flex-direction: column; align-items: center;
  justify-content: center; color: var(--paper); text-align: center; overflow-y: auto;
  /* the game's deep-plum backdrop with a warm gilt glow behind the card + a vignette */
  background:
    radial-gradient(90% 70% at 50% 42%, rgba(201,162,39,0.16) 0%, rgba(201,162,39,0) 60%),
    radial-gradient(130% 120% at 50% 30%, #322445 0%, #241a33 55%, #17101f 100%);
  padding: max(2rem, env(safe-area-inset-top)) max(1.2rem, env(safe-area-inset-right))
           max(2rem, env(safe-area-inset-bottom)) max(1.2rem, env(safe-area-inset-left));
}
/* auto margins: centred when it fits, scrolls (never clips) on a short cover screen */
.chapter-end > :first-child { margin-top: auto; }
.chapter-end > :last-child { margin-bottom: auto; }
/* the "chapter complete" certificate — gilded, parchment-dark, in the game's style */
.chapter-end-card {
  position: relative; width: min(92vw, 30rem);
  padding: clamp(2.4rem, 6vw, 3rem) clamp(1.4rem, 5vw, 2.6rem) clamp(1.6rem, 4vw, 2rem);
  background:
    radial-gradient(120% 90% at 50% 0%, #3a2c4d 0%, #2b2138 58%, #221a2e 100%);
  border: 1px solid rgba(201,162,39,0.55);
  border-radius: 16px;
  box-shadow: 0 22px 70px rgba(0,0,0,0.62), inset 0 0 0 1px rgba(201,162,39,0.14),
              inset 0 0 46px rgba(0,0,0,0.4);
  display: flex; flex-direction: column; align-items: center; gap: 0.9rem;
  animation: chapter-end-in 0.5s cubic-bezier(.2,.9,.3,1) both;
}
@keyframes chapter-end-in { from { opacity: 0; transform: translateY(14px) scale(0.97); } to { opacity: 1; transform: none; } }
/* wax-seal / medallion crowning the card with the case-file emblem */
.chapter-end-seal {
  position: absolute; top: 0; left: 50%; transform: translate(-50%, -52%);
  width: 3.6rem; height: 3.6rem; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 50% 35%, #4a3a24, #2b2138);
  border: 2px solid var(--gold);
  box-shadow: 0 6px 18px rgba(0,0,0,0.55), 0 0 22px rgba(201,162,39,0.35), inset 0 0 12px rgba(201,162,39,0.25);
}
.chapter-end-seal .ui-icon { height: 1.9rem; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.6)); }
.chapter-end-rule {
  height: 2px; width: 78%;
  background: linear-gradient(90deg, transparent, rgba(201,162,39,0.9), transparent);
}
.chapter-end-title {
  font-family: 'Cinzel', Georgia, serif; font-weight: 800; letter-spacing: 0.03em;
  font-size: clamp(1.6rem, 6vw, 2.3rem); line-height: 1.1;
  color: #f0d98a; text-shadow: 0 2px 8px rgba(0,0,0,0.6), 0 0 18px rgba(201,162,39,0.22);
  margin: 0.2rem 0 0;
}
.chapter-end-cliff {
  font-family: Georgia, serif; font-style: italic; font-size: clamp(1rem, 3.4vw, 1.2rem);
  color: var(--gold); margin: 0;
}
.chapter-end.is-finale .chapter-end-cliff { font-style: normal; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
.chapter-end-stats { display: flex; flex-direction: column; gap: 0.45rem; margin: 0.1rem 0 0.3rem; }
.chapter-end-stat {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  font-size: clamp(0.9rem, 3vw, 1.02rem); color: #ecdfc4;
}
.chapter-end-stat .ui-icon { height: 1.15rem; }
.chapter-end-actions { display: flex; flex-direction: column; align-items: center; gap: 0.7rem; margin-top: 0.7rem; width: 100%; }
.chapter-end-actions .btn { min-width: 15rem; max-width: 100%; }

/* Camera-shake juice on the stage: a light jolt for a discovery, a hard one
   for a shock. A slight scale keeps the stage edges covered while it moves. */
/* No `both` fill: after the shake the element reverts to no transform, so #app
   never keeps a residual transform (which would re-anchor fixed descendants). */
.fx-shake { animation: fx-shake 0.42s cubic-bezier(.36,.07,.19,.97); }
.fx-shake-hard { animation: fx-shake-hard 0.6s cubic-bezier(.36,.07,.19,.97); }
@keyframes fx-shake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  20%, 80% { transform: scale(1.015) translate3d(2px, -1px, 0); }
  30%, 50%, 70% { transform: scale(1.015) translate3d(-5px, 2px, 0); }
  40%, 60% { transform: scale(1.015) translate3d(5px, -2px, 0); }
}
@keyframes fx-shake-hard {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  20%, 80% { transform: scale(1.025) translate3d(5px, -3px, 0) rotate(0.5deg); }
  30%, 50%, 70% { transform: scale(1.025) translate3d(-10px, 3px, 0) rotate(-0.7deg); }
  40%, 60% { transform: scale(1.025) translate3d(10px, -3px, 0) rotate(0.7deg); }
}

/* The "aha" moment (discovery polish): a soft golden bloom breathing over
   the whole frame + a barely-there camera ease on #app. Calm on purpose —
   the discovery language is warmth, never a jolt. No fill on either
   animation, so no residual transform/opacity is left behind. */
#aha-bloom {
  position: fixed; inset: 0; z-index: 55; pointer-events: none; opacity: 0;
  background:
    radial-gradient(ellipse 62% 55% at 50% 44%, rgba(255, 216, 128, 0.17) 0%, rgba(255, 216, 128, 0.04) 55%, rgba(255, 216, 128, 0) 72%),
    radial-gradient(ellipse 130% 120% at 50% 50%, rgba(0, 0, 0, 0) 62%, rgba(255, 190, 90, 0.13) 100%);
}
#aha-bloom.on { animation: aha-bloom 1.05s ease-out; }
@keyframes aha-bloom {
  0% { opacity: 0; }
  22% { opacity: 1; }
  100% { opacity: 0; }
}
.fx-aha { animation: fx-aha 0.9s cubic-bezier(0.22, 0.9, 0.35, 1); }
@keyframes fx-aha {
  0% { transform: scale(1); }
  35% { transform: scale(1.015); }
  100% { transform: scale(1); }
}

/* Foreground depth layer: a close-to-camera slice in soft focus, drifting
   a few pixels against the room over a long period — the parallax says
   "you could almost hide behind this". Never intercepts a tap. */
.scene-fg {
  position: absolute; top: -2%; bottom: -2%; pointer-events: none; z-index: 8;
  /* soft focus ONLY — never darken or desaturate the painted scene behind:
     the layer adds depth at the frame edge, it must not "dirty" the art */
  filter: blur(1.8px);
}
.scene-fg img { width: 100%; height: 100%; object-fit: cover; }
.scene-fg-l { animation: scene-fg-drift-l 17s ease-in-out infinite alternate; }
.scene-fg-r { animation: scene-fg-drift-r 17s ease-in-out infinite alternate; }
.scene-fg-t { top: -2%; left: -1.5%; right: -1.5%; bottom: auto; animation: scene-fg-drift-l 19s ease-in-out infinite alternate; }
.scene-fg-t img { object-fit: cover; object-position: top; }
@keyframes scene-fg-drift-l { from { transform: translateX(-0.6%) scale(1.04); } to { transform: translateX(0.7%) scale(1.04); } }
@keyframes scene-fg-drift-r { from { transform: translateX(0.6%) scale(1.04); } to { transform: translateX(-0.7%) scale(1.04); } }
@media (prefers-reduced-motion: reduce) { .scene-fg-l, .scene-fg-r { animation: none; transform: scale(1.04); } }
html.reduce-motion .scene-fg-l, html.reduce-motion .scene-fg-r { animation: none; transform: scale(1.04); }

/* The clockwork mouse's cameo: a rare scurry along the floor line during
   quiet exploration. Tappable while visible (the one pointer-active child
   of the ambient layer). */
.mouse-cameo {
  position: absolute; width: 6.5%; z-index: 7; cursor: pointer;
  pointer-events: auto; filter: drop-shadow(0 2px 3px rgba(0,0,0,0.4));
}
.mouse-cameo.ltr { left: -8%; animation: cameo-ltr 3.6s linear forwards; }
.mouse-cameo.rtl { right: -8%; animation: cameo-rtl 3.6s linear forwards; }
@keyframes cameo-ltr {
  0% { transform: translateX(0) translateY(0); }
  25% { transform: translateX(160%) translateY(-2px); }
  50% { transform: translateX(300%) translateY(0); }
  75% { transform: translateX(440%) translateY(-2px); }
  100% { transform: translateX(600%) translateY(0); }
}
@keyframes cameo-rtl {
  0% { transform: scaleX(-1) translateX(0) translateY(0); }
  25% { transform: scaleX(-1) translateX(160%) translateY(-2px); }
  50% { transform: scaleX(-1) translateX(300%) translateY(0); }
  75% { transform: scaleX(-1) translateX(440%) translateY(-2px); }
  100% { transform: scaleX(-1) translateX(600%) translateY(0); }
}
@media (prefers-reduced-motion: reduce) { .mouse-cameo { display: none; } }
html.reduce-motion .mouse-cameo { display: none; }

/* the secret carillon: five faint bell-glints along the title skyline */
.title-bells { position: absolute; inset: 0; pointer-events: none; }
.title-bell {
  position: absolute; width: 16px; height: 16px; border: 0; border-radius: 50%;
  pointer-events: auto; cursor: pointer; opacity: 0.3; padding: 0;
  background: radial-gradient(circle, rgba(255, 224, 150, 0.65) 0%, rgba(255, 224, 150, 0) 70%);
}
.title-bell:hover, .title-bell:active { opacity: 0.9; }

/* the case web: notebook entries pinned to the board, joined by red thread */
.caseweb { position: relative; width: 100%; }
.cw-thread { position: absolute; inset: 0; width: 100%; height: 100%; }
.cw-thread path { fill: none; stroke: #b3312f; stroke-width: 1.6; vector-effect: non-scaling-stroke; opacity: 0.85; }
.cw-card {
  position: absolute; width: 47%; padding: 0.55em 0.8em 0.6em; font-size: 0.86em; line-height: 1.35;
  background: var(--paper, #f3e9d4); color: #2c2118; border-radius: 3px;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35);
}
.cw-pin {
  position: absolute; top: -5px; left: 50%; width: 10px; height: 10px; border-radius: 50%;
  transform: translateX(-50%);
  background: radial-gradient(circle at 35% 30%, #e46a67, #8f1f1d 70%);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* scene transition: a fresh room dissolves in with the gentlest settle —
   no cut, no pop. The placard rides on top as before. */
.stage-enter { animation: stage-enter 0.42s ease-out; }
@keyframes stage-enter {
  0% { opacity: 0.05; transform: scale(1.012); }
  100% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .stage-enter { animation: none; } }
html.reduce-motion .stage-enter { animation: none; }
@media (prefers-reduced-motion: reduce) {
  /* Stop non-essential, continuous motion for players who ask for it: the
     screen shake, the title + intro parallax drift, the magnifier's sonar
     ring, and the forward-trigger attention pulse. Puzzle-feedback pulses
     (water flow, carillon listen, trace/lock hints) are deliberately kept —
     they carry gameplay meaning. The magnifier keeps its static "over" glow,
     so the proximity cue still reads without the ring's motion. */
  .fx-shake, .fx-shake-hard,
  .fx-aha,
  .title-layer-far, .title-layer-near,
  .intro-layer-far, .intro-layer-near,
  .touch-magnifier.over::after,
  .explore-trigger-btn { animation: none; }
  /* the aha bloom keeps a single soft opacity breath — it is the discovery
     cue itself — but the camera ease above is dropped */
}

/* fx */
.fx-splash {
  position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: Georgia, serif; font-weight: 900; font-size: clamp(2.5rem, 10vw, 7rem);
  color: #fff; -webkit-text-stroke: 3px var(--danger); text-shadow: 0 0 30px rgba(0,0,0,0.6);
  background: rgba(0,0,0,0.15); animation: objection-pop 1.4s ease-out forwards;
}
@keyframes objection-pop {
  0% { transform: scale(0.4) rotate(-8deg); opacity: 0; }
  15% { transform: scale(1.15) rotate(2deg); opacity: 1; }
  25% { transform: scale(1) rotate(0deg); }
  80% { opacity: 1; }
  100% { opacity: 0; }
}
/* ---------- time-travel warp (fx: era_flash) ---------- */
/* A clock face is dialed wildly while a shockwave ripple bursts and the whole
   screen washes toward the era we're jumping INTO (gold = to 1672, blue = to
   present). Every element self-centres via translate(-50%,-50%) folded into
   its keyframes so the animated scale/rotate never fights the centring. */
.fx-time-warp { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 45; }
.fx-time-warp .warp-wash { position: absolute; inset: -12%; opacity: 0; animation: warp-wash 2.4s ease-in-out forwards; }
.fx-time-warp.to-past .warp-wash { background: radial-gradient(circle at 50% 50%, rgba(255,214,120,0.95), rgba(201,140,39,0.6) 45%, rgba(50,26,8,0.9) 100%); }
.fx-time-warp.to-present .warp-wash { background: radial-gradient(circle at 50% 50%, rgba(150,205,255,0.95), rgba(56,116,196,0.6) 45%, rgba(10,18,36,0.9) 100%); }
.warp-streaks {
  position: absolute; left: 50%; top: 50%; width: 260%; aspect-ratio: 1; opacity: 0;
  background: repeating-conic-gradient(from 0deg, rgba(255,255,255,0) 0deg, rgba(255,255,255,0.13) 1deg, rgba(255,255,255,0) 2.6deg);
  animation: warp-streaks 2.4s ease-in forwards;
}
.warp-ripple {
  position: absolute; left: 50%; top: 50%; width: 16%; aspect-ratio: 1; border-radius: 50%;
  border: 0.4rem solid rgba(255,255,255,0.85); opacity: 0; animation: warp-ripple 1.5s ease-out forwards;
}
.warp-ripple-2 { animation-delay: 0.5s; }
.warp-clock {
  position: absolute; left: 50%; top: 50%; width: 44%; max-width: 34rem; aspect-ratio: 1; opacity: 0;
  animation: warp-clock 2.4s cubic-bezier(.45,0,.55,1) forwards;
}
.warp-clock-svg { width: 100%; height: 100%; overflow: visible; filter: drop-shadow(0 0 1.2rem rgba(255,240,200,0.7)); }
.warp-gear { fill: none; stroke: rgba(255,240,200,0.9); stroke-width: 5; stroke-dasharray: 5 9; }
.warp-face { fill: rgba(20,15,26,0.35); stroke: rgba(255,240,200,0.95); stroke-width: 3; }
.warp-ticks line { stroke: rgba(255,240,200,0.9); stroke-width: 3; }
.warp-hand { stroke: #fff; stroke-linecap: round; transform-origin: 100px 100px; }
.warp-hand-h { stroke-width: 6; animation: warp-spin 2.4s linear forwards; }
.warp-hand-m { stroke-width: 4; animation: warp-spin-fast 2.4s linear forwards; }
.warp-hub { fill: #fff; }
.fx-time-warp.to-present .warp-clock-svg { filter: drop-shadow(0 0 1.2rem rgba(200,230,255,0.7)); }
.fx-time-warp.to-present .warp-gear,
.fx-time-warp.to-present .warp-face,
.fx-time-warp.to-present .warp-ticks line { stroke: rgba(205,232,255,0.95); }
@keyframes warp-wash { 0% { opacity: 0; } 35% { opacity: 1; } 72% { opacity: 0.85; } 100% { opacity: 0; } }
@keyframes warp-streaks {
  0% { opacity: 0; transform: translate(-50%,-50%) rotate(0deg) scale(0.5); }
  40% { opacity: 0.85; }
  100% { opacity: 0; transform: translate(-50%,-50%) rotate(150deg) scale(1.15); }
}
@keyframes warp-ripple {
  0% { opacity: 0; transform: translate(-50%,-50%) scale(0.2); }
  14% { opacity: 0.9; }
  100% { opacity: 0; transform: translate(-50%,-50%) scale(11); }
}
@keyframes warp-clock {
  0% { opacity: 0; transform: translate(-50%,-50%) scale(0.15) rotate(0deg); }
  22% { opacity: 1; transform: translate(-50%,-50%) scale(1) rotate(80deg); }
  68% { opacity: 1; transform: translate(-50%,-50%) scale(1.35) rotate(300deg); }
  100% { opacity: 0; transform: translate(-50%,-50%) scale(3.4) rotate(520deg); }
}
@keyframes warp-spin { from { transform: rotate(0deg); } to { transform: rotate(1080deg); } }
@keyframes warp-spin-fast { from { transform: rotate(0deg); } to { transform: rotate(2520deg); } }
/* Reduce-motion: skip the spin/ripple, keep only the calm colour wash. */
html.reduce-motion .warp-streaks,
html.reduce-motion .warp-ripple,
html.reduce-motion .warp-clock { display: none; }

/* ---------- small screens ---------- */
@media (max-width: 700px) {
  /* NOTE: do NOT cap .portrait in vh here — the portrait is sized relative to
     the 3:2 letterbox frame (#dialogue-slot), not the viewport. A vh cap on a
     tall phone is larger than the frame band and the character spills up out of
     the letterbox into the blurred backdrop. The base `max-height: 100%` keeps
     her feet on the frame's bottom edge and her head inside it, on every device. */
  .dialogue-wrap { padding: 0 0.6rem 0.8rem; }
  .match-board { gap: 0 0.6rem; }
  .match-item { height: 6.2rem; font-size: 0.74rem; padding: 0.35em 0.5em; }
  .match-item .puzzle-img { width: 2.4rem; height: 2.4rem; }
  .hud-stat { font-size: 0.75rem; padding: 0.3em 0.6em; }
  .hud-stats { gap: 0.35rem; }
}
@media (max-width: 420px) {
  .hud-stat { font-size: 0.68rem; padding: 0.25em 0.5em; }
}
/* Short / landscape viewports (e.g. a tablet in landscape): compact the puzzle
   overlay so the whole thing — cards AND the pinned Submit/hint row — fits. */
@media (max-height: 620px), (orientation: landscape) and (max-height: 860px) {
  .overlay-panel { padding: 1rem 1.15rem; }
  .overlay-title { font-size: 1.1rem; }
  .overlay-prompt { margin-bottom: 0.5rem; font-size: 0.9rem; }
  .seq-scale { margin-bottom: 0.35rem; }
  .seq-card { width: 6.9rem; padding: 0.35em; font-size: 0.8rem; }
  .seq-card.has-img { width: 7.1rem; }
  .seq-card .puzzle-img { aspect-ratio: 4 / 3; }
  .seq-card-cap { font-size: 0.6rem; margin-top: 0.2em; }
  .seq-row { gap: 0.4rem; padding: 0.45rem 0.4rem; }
  .overlay-actions { margin-top: 0.6rem; }
  .btn-sm { padding: 0.4em 1.4em; font-size: 0.82rem; }
}

/* ---------- concept-art gallery (picarat reward) — plates laid on an art-book ---------- */
.gallery-screen {
  position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden;
  color: #f3e4c2; background: #170f07;
}
.gallery-topbar { display: flex; align-items: center; gap: 1rem; padding: 0.7rem 1rem 0.5rem; z-index: 2; }
.gallery-topbar h2 {
  flex: 1; text-align: center; margin: 0; min-width: 8rem;
  font-family: Georgia, 'Times New Roman', serif; font-size: 1.4rem; font-weight: 800;
  color: #f6e6c0; text-shadow: 0 2px 6px rgba(0,0,0,0.85);
}
.gallery-topbar .gallery-count {
  font-size: 0.86rem; font-weight: 700; display: inline-flex; align-items: center; gap: 0.3em;
  white-space: nowrap; background: rgba(0,0,0,0.45); padding: 0.3em 0.7em; border-radius: 999px;
}
.gallery-count:empty { display: none; }
.gallery-back { display: inline-flex; align-items: center; gap: 0.3em; }
.gallery-stage { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; padding: 0 0.6rem 0.7rem; }
/* the open book — the concept art is laid onto its pages */
.gallery-book {
  position: relative; aspect-ratio: 3 / 2; height: 100%; max-width: 100%; margin: 0 auto;
  background: url('../assets/img/gallery/gallery_bg.webp') center/cover no-repeat;
  border-radius: 6px; box-shadow: 0 12px 44px rgba(0,0,0,0.65);
}
/* the two facing pages, split by the central gutter */
.gallery-book .gallery-grid {
  position: absolute; top: 12%; bottom: 22.5%; left: 22%; right: 18%;
  display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr 1fr;
  column-gap: 10%; row-gap: 2.8%;
}
.gallery-card { position: relative; display: flex; flex-direction: column; align-items: center; min-height: 0; }
.gallery-plate {
  position: relative; width: 100%; flex: 1 1 auto; min-height: 0; overflow: hidden; border-radius: 2px;
  border: 4px solid transparent; border-image: linear-gradient(145deg, #e6c477, #7a5a24 42%, #f4dca0 55%, #6a4a1c) 1;
  box-shadow: 0 3px 9px rgba(0,0,0,0.5);
}
.gallery-plate img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.gallery-card.unlocked { cursor: pointer; }
.gallery-card.unlocked .gallery-plate { transition: transform 0.14s ease, box-shadow 0.14s ease; }
.gallery-card.unlocked:hover .gallery-plate, .gallery-card.unlocked:active .gallery-plate { transform: translateY(-2px); box-shadow: 0 8px 16px rgba(0,0,0,0.6); }
/* caption is INK on the parchment page */
.gallery-card-title {
  margin-top: 0.22rem; font-family: Georgia, serif; font-size: clamp(0.55rem, 1.6vh, 0.85rem);
  font-weight: 700; color: #3d2c17; text-align: center; line-height: 1.12; max-width: 100%;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.gallery-card.locked .gallery-plate {
  display: flex; align-items: center; justify-content: center;
  background: repeating-linear-gradient(45deg, rgba(58,42,26,0.32) 0 9px, rgba(44,31,19,0.32) 9px 18px);
}
.gallery-lock-seal { font-size: clamp(1rem, 3.4vh, 1.9rem); opacity: 0.78; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.7)); }
.gallery-card.locked .gallery-card-title { color: #5a4325; }
.gallery-cost { display: inline-flex; align-items: center; justify-content: center; gap: 0.3em; }
.gallery-cost .ui-icon { width: 1em; height: 1em; }
/* full view: the plate held up over the open book */
.gallery-full .gallery-stage { flex-direction: column; }
.gallery-full-frame { margin: 0; max-width: 58rem; width: 100%; display: flex; flex-direction: column; align-items: center; }
.gallery-full-frame img {
  max-width: 100%; max-height: 62vh; object-fit: contain; border-radius: 3px;
  border: 10px solid transparent; border-image: linear-gradient(145deg, #e6c477, #7a5a24 42%, #f4dca0 55%, #6a4a1c) 1;
  box-shadow: 0 16px 46px rgba(0,0,0,0.7);
}
.gallery-full-frame figcaption { text-align: center; margin-top: 0.9rem; max-width: 42rem; }
.gallery-full-frame figcaption p { margin: 0; line-height: 1.55; color: #f3e4c2; text-shadow: 0 1px 3px rgba(0,0,0,0.9); }

/* second stacked round title button (settings gear under the mute button) */
.mute-btn-title2 { top: calc(2vh + 3.7rem); }

/* ---------- settings / accessibility modal ---------- */
.settings-modal {
  position: fixed; inset: 0; z-index: 10000; display: flex; align-items: center; justify-content: center;
  background: rgba(15,10,20,0.72); padding: 1.2rem;
}
.settings-panel {
  background: var(--paper); color: var(--ink); border: 3px solid var(--ink); border-radius: 16px;
  width: min(92vw, 30rem); max-height: 92dvh; overflow-y: auto; padding: 1.1rem 1.3rem 1.3rem;
  box-shadow: 0 12px 0 rgba(0,0,0,0.35);
}
.settings-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.4rem; }
.settings-head h2 { flex: 1; margin: 0; font-size: 1.4rem; color: var(--accent-warm); }
.settings-close {
  background: var(--paper); border: 2px solid var(--ink); border-radius: 50%; width: 2.2rem; height: 2.2rem;
  font-size: 1rem; line-height: 1; cursor: pointer; flex: 0 0 auto;
}
.set-row { display: flex; align-items: center; gap: 0.8rem; padding: 0.6rem 0; border-top: 1px solid rgba(43,33,56,0.12); }
.set-row > span { flex: 0 0 38%; font-weight: 700; }
.set-row input[type="range"] { flex: 1; accent-color: var(--accent-warm); height: 1.7rem; }
.set-ts-row { flex: 1; display: flex; gap: 0.4rem; }
.set-ts { min-width: 2.6rem; }
.set-ts.active { background: var(--gold); border-color: var(--ink); }
.set-toggle { cursor: pointer; }
.set-toggle input[type="checkbox"] { width: 1.4rem; height: 1.4rem; accent-color: var(--accent-warm); }

/* in-game reduce-motion override — mirrors the prefers-reduced-motion query */
html.reduce-motion .fx-shake, html.reduce-motion .fx-shake-hard,
html.reduce-motion .fx-aha,
html.reduce-motion .title-layer-far, html.reduce-motion .title-layer-near,
html.reduce-motion .intro-layer-far, html.reduce-motion .intro-layer-near,
html.reduce-motion .touch-magnifier.over::after,
html.reduce-motion .explore-trigger-btn,
html.reduce-motion .rotate-ico,
html.reduce-motion .tip-glass { animation: none !important; }

/* ---------- first-time "sweep to search" coach mark ---------- */
.investigate-tip {
  position: fixed; left: 50%; bottom: 16%; transform: translateX(-50%); z-index: 50;
  width: min(90vw, 27rem); pointer-events: auto; cursor: pointer;
}
.tip-card {
  background: rgba(20,14,26,0.93); color: var(--paper); border: 2px solid var(--gold);
  border-radius: 14px; padding: 0.9rem 1.2rem 1rem; text-align: center;
  box-shadow: 0 10px 26px rgba(0,0,0,0.55); animation: tip-in 0.3s ease-out both;
}
.tip-glass {
  width: 44px; height: 44px; margin: 0 auto 0.5rem;
  background: url('../assets/img/cursor_magnifier.png') center/contain no-repeat;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
  animation: tip-sweep 1.9s ease-in-out infinite;
}
.tip-card p { margin: 0 0 0.7rem; line-height: 1.4; font-size: 1rem; }
@keyframes tip-sweep { 0%, 100% { transform: translateX(-30px) rotate(-7deg); } 50% { transform: translateX(30px) rotate(7deg); } }
@keyframes tip-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* ---------- PWA install invite (title-screen overlay) ---------- */
.install-invite{
  position:fixed; inset:0; z-index:1200;
  display:flex; align-items:center; justify-content:center;
  padding:5vmin; background:rgba(12,9,18,.72); backdrop-filter:blur(3px);
  animation:installFade .25s ease-out;
}
@keyframes installFade{from{opacity:0}to{opacity:1}}
.install-card{
  max-width:min(560px,92vw); text-align:center;
  padding:clamp(18px,3.4vmin,30px) clamp(20px,4vmin,38px);
  color:#f3e9d6;
  background:
    radial-gradient(120% 140% at 50% 0%, #3a2c4d 0%, #2b2138 60%, #221a2e 100%);
  border:1px solid rgba(212,175,95,.55);
  border-radius:14px;
  box-shadow:0 18px 60px rgba(0,0,0,.6), inset 0 0 40px rgba(0,0,0,.35);
}
.install-rule{
  height:2px; margin:2px auto 0; width:70%;
  background:linear-gradient(90deg,transparent,rgba(212,175,95,.85),transparent);
}
.install-title{
  font-family:'Cinzel',Georgia,serif; letter-spacing:.02em;
  font-size:clamp(1.15rem,3.2vmin,1.6rem); color:#e9c877;
  margin:14px 0 10px; text-shadow:0 1px 2px rgba(0,0,0,.5);
}
.install-body{
  font-size:clamp(.92rem,2.2vmin,1.02rem); line-height:1.5; opacity:.95;
  margin:0 auto 12px; max-width:44ch;
}
.install-size{
  font-size:clamp(.82rem,1.9vmin,.92rem); line-height:1.45;
  color:#d4af5f; opacity:.95; margin:0 auto 16px; max-width:42ch;
}
.install-actions{ display:flex; gap:12px; justify-content:center; flex-wrap:wrap; margin:6px 0 12px; }
.install-actions .btn{ min-width:120px; }

<<<<<<< HEAD
/* ============ DEMO build: title ribbon + coming-soon end card ============ */
/* A gilded corner ribbon over the title art — unmistakable but not shouting. */
.title-demo-badge {
  position: absolute; top: 1.1rem; left: -2.6rem; z-index: 6;
  transform: rotate(-45deg); transform-origin: center;
  width: 9.5rem; text-align: center; padding: 0.28rem 0;
  background: linear-gradient(180deg, #d4af37, #a67c1b);
  color: #241a10; font-weight: 800; font-size: 0.82rem; letter-spacing: 0.14em;
  text-transform: uppercase;
  box-shadow: 0 3px 10px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,240,200,0.6);
  pointer-events: none;
}
/* The coming-soon card: chapter-end language, key-art crowned. */
.demo-end-card { width: min(94vw, 34rem); padding-top: 1.1rem; gap: 0.7rem; }
.demo-end-art {
  width: calc(100% + 2 * clamp(1.4rem, 5vw, 2.6rem)); margin: 0 calc(-1 * clamp(1.4rem, 5vw, 2.6rem));
  margin-top: 0; border-radius: 16px 16px 0 0; overflow: hidden;
  border-bottom: 1px solid rgba(201,162,39,0.55);
  box-shadow: inset 0 -18px 30px rgba(0,0,0,0.45);
}
.demo-end-art img { display: block; width: 100%; height: clamp(9rem, 26vh, 15rem); object-fit: cover; }
.demo-end-kicker {
  margin-top: 0.4rem;
  font-size: 0.78rem; font-weight: 800; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--gold, #c9a227); opacity: 0.92;
}
.demo-end-body { margin: 0; text-align: center; line-height: 1.5; font-size: 0.98rem; opacity: 0.94; }
.demo-end-coming { margin: 0; text-align: center; font-weight: 700; color: var(--gold, #c9a227); }
@media (max-height: 560px) {
  .demo-end-art img { height: 7rem; }
  .demo-end-body { font-size: 0.88rem; }
=======
/* ============ SPRINT 1: full-screen puzzle chrome ============ */
/* The puzzle takes the WHOLE play screen: no beige card, no letterbox gutter.
   Dark atelier backdrop, slim title strip, tap-to-expand prompt, boards get
   the full width, actions float at the bottom thumb zone. */
.puzzle-overlay { padding: 0; background: rgba(12, 9, 18, 0.92); backdrop-filter: blur(6px); }
.puzzle-overlay .overlay-panel {
  max-width: none; width: 100%; height: 100%; max-height: 100%;
  margin: 0; border: none; border-radius: 0; box-shadow: none;
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(58, 44, 77, 0.55) 0%, rgba(24, 18, 34, 0.92) 62%, rgba(16, 12, 24, 0.97) 100%);
  color: #f0e8d8;
  display: flex; flex-direction: column;
  padding: max(0.7rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right))
           max(0.8rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
  animation: puzzle-in 0.38s cubic-bezier(.2,.9,.3,1) both;
}
@keyframes puzzle-in { from { opacity: 0; transform: scale(1.04); } to { opacity: 1; transform: none; } }
.puzzle-overlay .overlay-title {
  flex: 0 0 auto; font-size: 1.05rem; letter-spacing: 0.02em;
  color: var(--gold, #c9a227); margin-bottom: 0.15rem;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}
/* prompt: two-line clamp, tap anywhere on it to expand/collapse */
.puzzle-overlay .overlay-prompt {
  flex: 0 0 auto; font-size: 0.88rem; opacity: 0.82; margin-bottom: 0.55rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; cursor: pointer;
}
.puzzle-overlay .overlay-prompt.expanded { -webkit-line-clamp: unset; }
/* the board region grows into all remaining space and centres its content.
   :where() keeps this at (0,2,0) specificity so a board that needs its own
   inner layout (logic-wrap, eraflip-stage) can restore it with a later rule
   instead of fighting the :not() chain. */
.puzzle-overlay .overlay-panel > :where(*:not(.overlay-title):not(.overlay-prompt):not(.overlay-actions):not(.hints-panel):not(.puzzle-feedback)) {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; justify-content: center;
}
/* floating bottom action bar: hints + confirm in the thumb zone */
.puzzle-overlay .overlay-actions {
  flex: 0 0 auto; position: sticky; bottom: 0; z-index: 5;
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; justify-content: center;
  padding: 0.55rem 0.4rem calc(0.4rem + env(safe-area-inset-bottom, 0px));
  margin: 0.4rem -0.4rem 0; border-radius: 14px 14px 0 0;
  background: linear-gradient(180deg, rgba(16,12,24,0) 0%, rgba(16,12,24,0.85) 30%);
}
.puzzle-overlay .overlay-actions .btn { min-height: 2.9rem; }
.puzzle-overlay .hints-panel { flex: 0 0 auto; }
.puzzle-overlay .hint-text { background: rgba(201,162,39,0.14); color: #f0e8d8; }
.puzzle-overlay .puzzle-feedback { flex: 0 0 auto; text-align: center; }
/* light-paper widgets inside the dark room keep their own ink text */
.puzzle-overlay .match-left, .puzzle-overlay .seq-card { color: var(--ink, #2b2138); }
@media (max-height: 560px) {
  .puzzle-overlay .overlay-title { font-size: 0.92rem; }
  .puzzle-overlay .overlay-prompt { -webkit-line-clamp: 1; font-size: 0.8rem; }
>>>>>>> 33a401f (v1.84.12: PUZZLE-UX SPRINT 1 — full-screen touch chrome for all 30 puzzles)
}

/* Sprint 3: the 3D lock chamber fills the board region */
.lock3d-mount { flex: 1 1 auto; min-height: 42vh; border-radius: 12px; overflow: hidden; }
.lock3d-mount:empty { display: none; }

/* ============ SPRINT 4: bespoke-2D wave (demo-facing) ============ */

/* ---- cam_timeline: the security video wall. The six stills hang as CRT
   monitors in a dark control room — bezels, scanlines, curved glass, tape
   labels. Same seq-card drag underneath. ---- */
.crt-desk {
  border-radius: 14px; padding: 0.85rem 0.7rem 1rem;
  background:
    radial-gradient(130% 100% at 50% -10%, #313941 0%, #171c21 52%, #0a0d11 100%);
  box-shadow: inset 0 0 46px rgba(0,0,0,0.6), 0 4px 16px rgba(0,0,0,0.4);
}
.crt-desk .seq-scale {
  color: #ffb454; font-family: 'Courier New', ui-monospace, monospace;
  text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.78rem;
  text-shadow: 0 0 7px rgba(255, 160, 50, 0.55);
  padding: 0 0.3rem;
}
.crt-desk .seq-scale-hint { font-style: normal; opacity: 0.62; }
.seq-row--crt { background: transparent; padding: 0.5rem 0.2rem; gap: 0.8rem; }
.puzzle-overlay .seq-card.crt-monitor {
  width: 10.2rem; padding: 0.42em 0.42em 0.38em; color: #cfd6dd;
  background: linear-gradient(180deg, #40454c, #24282d 70%, #1b1e23);
  border: 1px solid #05070a; border-radius: 10px;
  box-shadow: 0 7px 16px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.09);
}
.crt-monitor .seq-num {
  background: #ffb454; color: #201302; border-color: #05070a;
  box-shadow: 0 0 9px rgba(255, 160, 50, 0.55);
}
.crt-screen {
  position: relative; border-radius: 7px / 9px; overflow: hidden; background: #000;
  box-shadow: inset 0 0 14px rgba(0,0,0,0.95);
}
.crt-screen .puzzle-img {
  border: none; border-radius: 0; box-shadow: none; aspect-ratio: 4 / 3;
  filter: saturate(0.5) contrast(1.14) brightness(0.96);
}
.crt-scan {
  position: absolute; inset: 0; pointer-events: none;
  background:
    repeating-linear-gradient(0deg, rgba(0,0,0,0.24) 0 1px, transparent 1px 3px),
    linear-gradient(180deg, rgba(90,255,170,0.05), rgba(90,255,170,0.03));
}
.crt-scan::after {          /* the slow rolling refresh band */
  content: ''; position: absolute; left: 0; right: 0; height: 22%;
  background: linear-gradient(180deg, transparent, rgba(200,255,220,0.16), transparent);
  animation: crt-roll 4.6s linear infinite;
}
@keyframes crt-roll { from { top: -25%; } to { top: 105%; } }
.crt-glass {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(115% 92% at 50% 42%, transparent 56%, rgba(0,0,0,0.4) 100%),
    linear-gradient(112deg, rgba(255,255,255,0.11) 0%, transparent 26%);
}
.crt-monitor .seq-card-cap {
  color: #9fe8b8; font-family: 'Courier New', ui-monospace, monospace;
  background: #0d1114; border-radius: 4px; padding: 0.22em 0.3em; margin-top: 0.34em;
  text-shadow: 0 0 5px rgba(60, 220, 130, 0.5); min-height: 2.3em;
}
.crt-monitor.dragging {
  border-color: #05070a;
  box-shadow: 0 16px 32px rgba(0,0,0,0.65), 0 0 20px rgba(120,255,170,0.28);
}
.crt-monitor.dragging .crt-scan { animation: crt-jitter 0.12s steps(2) infinite; }
@keyframes crt-jitter { 50% { transform: translateY(1px); opacity: 0.82; } }
.crt-monitor.just-dropped { animation: crt-degauss 0.4s ease-out; }
@keyframes crt-degauss {
  0% { transform: scale(1.035, 0.965); }
  45% { transform: scale(0.982, 1.02); }
  100% { transform: none; }
}
@media (max-width: 640px), (max-height: 560px) {
  .puzzle-overlay .seq-card.crt-monitor { width: 7.6rem; }
  .seq-row--crt { gap: 0.55rem; }
  .crt-monitor .seq-card-cap { font-size: 0.58rem; }
}

/* ---- logicgrid: the quartermaster's chalk slate. Slate in a timber frame,
   chalk grid lines, marks that scratch on with a dust pop, clues as pinned
   paper scraps. Reused by refugee_billets (Ch2) and merchant_ledgers (Ch4). */
.logicgrid-overlay .logic-wrap {
  border-radius: 6px; padding: 1rem 1.1rem; position: relative;
  background:
    radial-gradient(120% 90% at 68% 82%, rgba(255,255,255,0.05) 0%, transparent 42%),
    repeating-linear-gradient(104deg, rgba(255,255,255,0.022) 0 2px, transparent 2px 8px),
    linear-gradient(158deg, #313a3d 0%, #272f33 48%, #222a2e 100%);
  border: 9px solid #0000;
  border-image: linear-gradient(134deg, #6d4b2e 0%, #8f6d46 38%, #5c3f27 72%, #7a5a39 100%) 1;
  box-shadow: inset 0 0 44px rgba(0,0,0,0.5), 0 5px 18px rgba(0,0,0,0.45);
}
/* restore the side-by-side board layout inside the full-height region (the
   Sprint-1 rule above turns direct children into flex COLUMNS; with wrap that
   pushed the grid/side-rail into a cut-off second column) */
.logicgrid-overlay .logic-wrap { flex-direction: row; align-items: center; align-content: center; }
.eraflip-overlay .eraflip-stage,
.wardmaze-overlay .eraflip-stage { flex-direction: row; align-items: center; align-content: center; }
.logicgrid-overlay .logic-clues { background: transparent; color: #f2eee2; }
.logicgrid-overlay .logic-clues li {
  position: relative; border-radius: 2px; color: var(--ink, #2b2138);
  background: linear-gradient(180deg, #f2e7cc, #e7d9b6);
  box-shadow: 0 3px 9px rgba(0,0,0,0.5);
  transform: rotate(-1.1deg); margin-top: 0.5em;
}
.logicgrid-overlay .logic-clues li:nth-child(2n) { transform: rotate(0.9deg); }
.logicgrid-overlay .logic-clues li::before {   /* the pin */
  content: ''; position: absolute; top: -5px; left: 50%; margin-left: -5px;
  width: 10px; height: 10px; border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #e88, #8b1e1e 72%);
  box-shadow: 0 2px 3px rgba(0,0,0,0.55);
}
.logicgrid-overlay .logic-grid {
  background: rgba(255,255,255,0.045);
  border: 2px solid rgba(246, 243, 234, 0.5);
}
.logicgrid-overlay .logic-grid th, .logicgrid-overlay .logic-grid td {
  border: 1.6px solid rgba(246, 243, 234, 0.34);
}
.logicgrid-overlay .logic-grid .logic-corner { border: none; background: transparent; }
.logicgrid-overlay .logic-colh, .logicgrid-overlay .logic-rowh { background: rgba(255,255,255,0.055); }
.logicgrid-overlay .logic-colh-cap, .logicgrid-overlay .logic-rowh-cap {
  color: #f2eee2; text-shadow: 0 0 3px rgba(255,255,255,0.25);
}
.logicgrid-overlay .logic-face, .logicgrid-overlay .logic-emblem {
  border: 2px solid rgba(246,243,234,0.55); border-radius: 3px;
  background: #d9d2c0; box-shadow: 0 2px 7px rgba(0,0,0,0.5);
}
.logicgrid-overlay .logic-cell { background: transparent; color: #f6f3ea; }
.logicgrid-overlay .logic-cell:hover { background: rgba(255,255,255,0.07); }
.logicgrid-overlay .logic-cell.mark-no {
  color: #efe9da; text-shadow: 0 0 2px rgba(255,255,255,0.45);
}
.logicgrid-overlay .logic-cell.mark-yes {
  color: #ffe9a8; background: rgba(255, 233, 168, 0.13);
  text-shadow: 0 0 4px rgba(255, 216, 110, 0.55);
}
.logic-cell.just-chalked { animation: chalk-pop 0.32s ease-out; }
@keyframes chalk-pop {           /* the mark lands + a puff of chalk dust */
  0% { transform: scale(1.45); opacity: 0.25; box-shadow: 0 0 0 0 rgba(255,255,255,0.4); }
  60% { opacity: 1; }
  100% { transform: none; box-shadow: 0 0 16px 12px rgba(255,255,255,0); }
}

/* ---- canal_depth: the water answers the sluice boards. Flow streams run
   under each gauge on the 1672 face (three when open, a trickle at half,
   still when closed) and the era flip is a liquid wipe. ---- */
.gauge-flow .flow-line {
  fill: none; stroke: rgba(206, 234, 246, 0.85); stroke-width: 1.35;
  stroke-linecap: round; stroke-dasharray: 2.4 2.8;
  animation: canal-flow 0.85s linear infinite;
}
@keyframes canal-flow { to { stroke-dashoffset: -10.4; } }
.gauge-flow .flow-foam { fill: rgba(220, 242, 250, 0.5); animation: foam-pulse 1.6s ease-in-out infinite; }
@keyframes foam-pulse { 50% { opacity: 0.45; transform: scale(1.12); } }
.gauge-flow.flow-half .flow-line { stroke-width: 0.9; animation-duration: 1.5s; opacity: 0.75; }
.gauge-flow.flow-half .flow-line:nth-child(1), .gauge-flow.flow-half .flow-line:nth-child(3) { display: none; }
.gauge-flow.flow-half .flow-foam { opacity: 0.4; }
.gauge-flow.flow-closed .flow-line, .gauge-flow.flow-unset .flow-line,
.gauge-flow.flow-closed .flow-foam, .gauge-flow.flow-unset .flow-foam { display: none; }
.gauge-flow { pointer-events: none; }
.eraflip-gauge.just-set { animation: gauge-pop 0.3s ease-out; transform-box: fill-box; transform-origin: center; }
@keyframes gauge-pop { 35% { transform: scale(1.14); } }

/* the liquid era wipe: a water sheet with a foam crest that floods up over
   the old era ('in'), then lifts off the new one ('out') */
.era-wipe {
  position: absolute; inset: 0; z-index: 6; overflow: hidden;
  pointer-events: none; border-radius: inherit;
}
.era-wipe-water {
  position: absolute; left: -4%; right: -4%; top: 0; height: 134%;
  background: linear-gradient(180deg,
    rgba(168, 219, 235, 0.93) 0%, rgba(52, 110, 148, 0.96) 38%, rgba(14, 44, 72, 0.98) 100%);
}
.era-wipe-water::before {        /* leading foam crest (top edge) */
  content: ''; position: absolute; left: 0; right: 0; top: -1.3rem; height: 1.4rem;
  background-image: radial-gradient(circle at 0.7rem 1.4rem, rgba(226, 244, 252, 0.95) 0.66rem, transparent 0.72rem);
  background-size: 1.4rem 1.4rem; background-repeat: repeat-x;
}
.era-wipe-water::after {         /* trailing droplets (bottom edge) */
  content: ''; position: absolute; left: 0; right: 0; bottom: -1.1rem; height: 1.2rem;
  background-image: radial-gradient(circle at 0.6rem 0, rgba(20, 52, 82, 0.95) 0.5rem, transparent 0.56rem);
  background-size: 1.7rem 1.2rem; background-repeat: repeat-x;
}
/* translate %s are relative to the sheet's OWN height (134% of the board):
   78% = crest just under the bottom edge, -4% = board fully underwater,
   -138% = sheet fully lifted past the top. */
.era-wipe-in .era-wipe-water { transform: translateY(78%); animation: era-wipe-in 0.42s cubic-bezier(.3,.1,.4,1) forwards; }
@keyframes era-wipe-in { to { transform: translateY(-4%); } }
.era-wipe-out .era-wipe-water { transform: translateY(-4%); animation: era-wipe-out 0.55s cubic-bezier(.5,0,.65,1) forwards; }
@keyframes era-wipe-out { to { transform: translateY(-138%); } }

/* ============ SPRINT 5: machines & mass ============ */
/* AAA mechanism doctrine (Uncharted/Tomb Raider): everything has DETENTS and
   WEIGHT — mechanisms land in notches with a clunk and an overshoot settle,
   and the room answers back. main.js plays the motion (WAAPI) that each
   re-render would otherwise teleport. */

/* FIX (Sprint-1 regression, same family as the logic-grid one): the board-
   region catch-all forces every direct child into a flex COLUMN, which
   flattened the display:grid boards (compass vault, slab grid, pipe basin)
   into single-file strips and stacked the row trays/vessel racks. Restore
   each board's intrinsic layout; flex:0 keeps grids from stretching past
   their aspect-ratio. */
.puzzle-overlay .overlay-panel > .vault-plan,
.puzzle-overlay .overlay-panel > .slide-grid,
.puzzle-overlay .overlay-panel > .pipe-basin { display: grid; flex: 0 1 auto; }
.puzzle-overlay .overlay-panel > .decant-stage { flex-direction: row; align-items: flex-end; flex: 0 1 auto; }
.puzzle-overlay .overlay-panel > .barrel-tray,
.puzzle-overlay .overlay-panel > .balance-tray { flex-direction: row; flex-wrap: wrap; align-items: center; flex: 0 1 auto; }

/* ---- carillon (#19): the belfry becomes a rig — headstock beam, ropes,
   bells that ring visible sound-rings sized by their pitch ---- */
.carillon-overlay .belfry::before {      /* the oak headstock beam */
  content: ''; position: absolute; left: 0; right: 0; top: 6.5%; height: 7%;
  background: linear-gradient(180deg, #5a4025, #3a2917 60%, #2a1d0f);
  box-shadow: 0 3px 8px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.12);
  z-index: 0;
}
.carillon-overlay .belfry::after {       /* dusk vignette + candle grade */
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(90% 80% at 50% 42%, transparent 46%, rgba(8,4,0,0.5) 100%),
    linear-gradient(180deg, rgba(255,180,80,0.06), transparent 45%);
}
.carillon-bell { position: relative; z-index: 1; }
.carillon-bell::before {                 /* the rope from beam to canon */
  content: ''; position: absolute; left: 50%; top: -8%; bottom: 78%;
  width: 3px; margin-left: -1.5px; border-radius: 2px;
  background: repeating-linear-gradient(180deg, #9a7b4d 0 3px, #7a5f39 3px 6px);
  box-shadow: 0 0 2px rgba(0,0,0,0.5);
}
.carillon-bell.swing::before { animation: rope-sway 0.66s ease; transform-origin: top center; }
@keyframes rope-sway { 25% { transform: rotate(3deg); } 60% { transform: rotate(-2deg); } 100% { transform: none; } }
.bell-ring {                             /* the visible peal */
  position: absolute; left: 50%; bottom: 6%; pointer-events: none; z-index: 2;
  width: calc(var(--ring, 1) * 2.2rem); height: calc(var(--ring, 1) * 1.05rem);
  margin-left: calc(var(--ring, 1) * -1.1rem);
  border: 2px solid rgba(255, 220, 140, 0.75); border-radius: 50%;
  opacity: 0; transform: scale(0.3);
  animation: bell-ring-out calc(0.55s + var(--ring, 1) * 0.5s) ease-out forwards;
}
@keyframes bell-ring-out {
  0% { opacity: 0.9; transform: scale(0.3); border-width: 3px; }
  100% { opacity: 0; transform: scale(3.1); border-width: 1px; }
}

/* ---- catacomb slabs (#23): stone that grinds in torchlight ---- */
.sliding-overlay .slide-grid {
  position: relative; gap: 5px; padding: 9px; border-radius: 10px;
  background: linear-gradient(160deg, #201812 0%, #171210 55%, #12100e 100%);
  border: 3px solid #0a0705;
  box-shadow: inset 0 0 26px rgba(0,0,0,0.75), 0 6px 18px rgba(0,0,0,0.5);
}
.sliding-overlay .slide-grid::after {    /* the torch dances over the wall */
  content: ''; position: absolute; inset: -6px; pointer-events: none; border-radius: 10px;
  background: radial-gradient(70% 60% at 66% 30%, rgba(255,150,50,0.13), transparent 70%);
  animation: torch-dance 3.2s ease-in-out infinite;
  mix-blend-mode: screen;
}
@keyframes torch-dance {
  0%, 100% { opacity: 0.8; transform: translate(0, 0); }
  32% { opacity: 1; transform: translate(-8px, 5px); }
  58% { opacity: 0.65; transform: translate(5px, -3px); }
  80% { opacity: 0.95; transform: translate(-3px, 2px); }
}
.sliding-overlay .slide-tile {
  position: relative; border-radius: 3px;
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,0.09), inset 0 -3px 6px rgba(0,0,0,0.55),
    inset 2px 0 3px rgba(0,0,0,0.25), 0 2px 5px rgba(0,0,0,0.55);
}
.sliding-overlay .slide-blank {
  background: radial-gradient(80% 80% at 50% 55%, #0c0908, #060404); box-shadow: inset 0 4px 12px rgba(0,0,0,0.9);
}
.stone-dust {                            /* kicked up where the slab stood */
  position: absolute; left: 18%; right: 18%; bottom: 8%; height: 34%;
  pointer-events: none; border-radius: 50%;
  background: radial-gradient(60% 100% at 50% 100%, rgba(190,170,140,0.5), transparent 70%);
  filter: blur(2px); opacity: 0;
  animation: dust-kick 0.5s ease-out forwards;
}
@keyframes dust-kick {
  0% { opacity: 0.85; transform: translateY(0) scale(0.6); }
  100% { opacity: 0; transform: translateY(-46%) scale(1.5); }
}

/* ---- placements (#9 witness rings, #27 water barrels): heft + answer ---- */
.placement-overlay .barrel {
  transition: transform 0.14s ease, filter 0.14s ease;
}
.placement-overlay .barrel.selected {
  transform: scale(1.1) rotate(-2.5deg);
  filter: drop-shadow(0 10px 12px rgba(0,0,0,0.55)) brightness(1.08);
}
.placement-overlay .vault-zone { transition: box-shadow 0.2s ease; }
.placement-overlay .vault-zone.armed:not(.filled) .vault-slot {
  animation: slot-beckon 1.1s ease-in-out infinite;
}
@keyframes slot-beckon {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232,181,58,0.0); }
  50% { box-shadow: 0 0 0 5px rgba(232,181,58,0.28); }
}
.placement-overlay .barrel.just-placed { animation: token-land 0.32s cubic-bezier(.34,1.56,.64,1); }
@keyframes token-land { 0% { transform: scale(1.18); } 100% { transform: none; } }
.water-ripple {                          /* the standing water answers */
  position: absolute; left: 50%; top: 62%; width: 3.2rem; height: 1.25rem;
  margin: -0.62rem 0 0 -1.6rem; pointer-events: none;
  border: 2px solid rgba(160, 210, 235, 0.65); border-radius: 50%;
  animation: ripple-out 0.85s ease-out forwards;
}
@keyframes ripple-out {
  0% { opacity: 0.9; transform: scale(0.35); }
  100% { opacity: 0; transform: scale(2.6); }
}
/* the flooded cellar floor carries a faint standing-water sheen */
.pz-water_barrels .vault-plan::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background: linear-gradient(115deg, transparent 30%, rgba(150,200,230,0.10) 46%, transparent 60%);
  background-size: 240% 100%;
  animation: water-sheen 6.5s ease-in-out infinite;
}
@keyframes water-sheen { 0%, 100% { background-position: 10% 0; } 50% { background-position: 85% 0; } }

/* ---- decant (#14): brass vessels, living oil ---- */
.decant-overlay .decant-jar {
  background: linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.03) 30%, rgba(0,0,0,0.16));
  border: 2px solid #6d5330; border-bottom-width: 4px; border-radius: 6px 6px 9px 9px;
  box-shadow: inset 0 0 10px rgba(0,0,0,0.35), 0 4px 10px rgba(0,0,0,0.4), inset 2px 0 4px rgba(255,255,255,0.10);
  position: relative; overflow: hidden;
}
.decant-overlay .decant-fill {
  background: linear-gradient(180deg, #d9a441, #a86f1e 70%, #8a5a15);
  box-shadow: inset 0 3px 5px rgba(255,235,170,0.4);
  position: relative;
}
.decant-overlay .decant-fill::after {    /* the oil surface never sits dead still */
  content: ''; position: absolute; left: -12%; right: -12%; top: -0.3rem; height: 0.6rem;
  background: radial-gradient(50% 100% at 50% 50%, rgba(255,232,170,0.65), rgba(217,164,65,0.25) 70%, transparent);
  border-radius: 50%;
  animation: oil-bob 3.4s ease-in-out infinite;
}
@keyframes oil-bob {
  0%, 100% { transform: translateY(0) scaleY(1); }
  50% { transform: translateY(0.08rem) scaleY(0.82); }
}
.decant-overlay .decant-vessel { transform-origin: 50% 92%; }

/* ---- routing (#12): the spring answers ---- */
.routing-overlay .pipe-source { animation: source-pulse 2.4s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes source-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.72; } }
.routing-overlay .pipe-basin { position: relative; }
.routing-overlay .pipe-basin::after {    /* faint caustic light off the water */
  content: ''; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background: linear-gradient(100deg, transparent 38%, rgba(120,190,230,0.06) 50%, transparent 62%);
  background-size: 300% 100%;
  animation: water-sheen 7s ease-in-out infinite;
}

/* ---- balance (#20): mass at rest is still mass ---- */
.balance-overlay .balance-beam { transition: filter 0.3s ease; }
.balance-overlay .balance-beam.level { filter: drop-shadow(0 0 10px rgba(120,220,150,0.45)); }
.balance-overlay .balance-bell {      /* the great bell hangs, never dead still */
  transform-origin: 50% -12%;
  animation: pendulum 4.6s ease-in-out infinite;
}
@keyframes pendulum { 0%, 100% { transform: rotate(1.4deg); } 50% { transform: rotate(-1.4deg); } }

/* ---- idle life: nothing on a board sits perfectly still ---- */
.carillon-bell-img { animation: bell-idle 5.4s ease-in-out infinite; }
.carillon-bell:nth-child(2n) .carillon-bell-img { animation-delay: -1.7s; }
.carillon-bell:nth-child(3n) .carillon-bell-img { animation-delay: -3.1s; }
@keyframes bell-idle { 0%, 100% { transform: rotate(0.7deg); } 50% { transform: rotate(-0.7deg); } }
.pz-water_barrels .vault-zone .barrel:not(.just-placed) {
  animation: barrel-bob 3.8s ease-in-out infinite;
}
.pz-water_barrels .vault-zone:nth-child(2n) .barrel:not(.just-placed) { animation-delay: -1.9s; }
@keyframes barrel-bob {
  0%, 100% { transform: translateY(0) rotate(0.4deg); }
  50% { transform: translateY(2px) rotate(-0.4deg); }
}

/* Sprint 7: the 3D gear bench fills the immersive stage (flat plate hides) */
.geartrain-overlay .gears3d-mount { flex: 1 1 auto; align-self: stretch; min-height: 42vh; border-radius: 10px; overflow: hidden; }
.geartrain-overlay .gears3d-mount:empty { display: none; }
/* Sprint 9: the 3D war table, same treatment */
.warmap-overlay .war3d-mount { flex: 1 1 auto; align-self: stretch; min-height: 42vh; border-radius: 10px; overflow: hidden; }
.warmap-overlay .war3d-mount:empty { display: none; }

/* ============ SPRINT 6: shadows & fire (coverage → 30/30) ============ */

/* ---- escape boards (#7 press_gang, #26 archive_lockdown): the patrols
   carry LANTERNS — their gaze is a flickering cone of light on the ground */
.escape-overlay .lantern-cone {
  fill: rgba(255, 158, 70, 0.17);
  animation: lantern-flicker 1.7s ease-in-out infinite;
}
.escape-overlay .lantern-pool {
  fill: rgba(255, 175, 85, 0.12);
  animation: lantern-flicker 2.3s ease-in-out infinite reverse;
}
@keyframes lantern-flicker { 0%, 100% { opacity: 1; } 42% { opacity: 0.72; } 68% { opacity: 0.9; } }
/* night vignette on both; the burning archive glows from below instead */
.escape-overlay .escape-board::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: radial-gradient(95% 88% at 50% 44%, transparent 52%, rgba(4, 6, 14, 0.42) 100%);
}
.pz-archive_lockdown .escape-board::after {
  background:
    linear-gradient(0deg, rgba(255, 96, 20, 0.16), transparent 42%),
    radial-gradient(95% 88% at 50% 40%, transparent 50%, rgba(20, 4, 2, 0.45) 100%);
  animation: fire-breathe 2.6s ease-in-out infinite;
}
@keyframes fire-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.78; } }
.escape-board.caught-flash { animation: caught-wash 0.55s ease-out; }
@keyframes caught-wash {
  0% { box-shadow: inset 0 0 0 200px rgba(200, 30, 20, 0.35); }
  100% { box-shadow: inset 0 0 0 200px rgba(200, 30, 20, 0); }
}

/* ---- wardmaze (#25): the sisters carry the only lantern in the stacks ---- */
.wardmaze-overlay .ward-dark {
  fill: rgba(5, 3, 10, 0.72); pointer-events: none;
  animation: ward-breathe 3.4s ease-in-out infinite;
}
@keyframes ward-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.9; } }
.wardmaze-overlay .esc-player-ring { animation: lantern-flicker 2s ease-in-out infinite; }
.wardmaze-drawer.armed .drawer-face { animation: drawer-beckon 1.1s ease-in-out infinite; }
@keyframes drawer-beckon {
  0%, 100% { filter: none; }
  50% { filter: drop-shadow(0 0 3px rgba(232, 181, 58, 0.9)); }
}

/* ---- smoke_arrows (#29): the traced route burns as an ember trail ---- */
.trace-overlay .trace-line {
  stroke: #ffb254;
  filter: drop-shadow(0 0 2.5px rgba(255, 130, 30, 0.9)) drop-shadow(0 0 7px rgba(255, 90, 10, 0.45));
  stroke-dasharray: 1.6 1.1;
  animation: ember-crawl 0.7s linear infinite;
}
@keyframes ember-crawl { to { stroke-dashoffset: -2.7; } }
.trace-overlay .trace-node.lit {
  animation: ember-node 1.3s ease-in-out infinite;
  filter: drop-shadow(0 0 4px rgba(255, 120, 30, 0.85));
}
@keyframes ember-node { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }

/* ---- sensor garnishes: the working moment answers back harder ---- */
/* blow (#13): the dust STREAMS while breath is on it */
.sensor-blow.blowing .sensor-dust {
  animation: dust-stream 0.5s linear infinite;
}
@keyframes dust-stream { to { background-position: 2.2rem -0.5rem; } }
/* shake (#17): the whole frame judders and rust flecks sift down */
.sensor-shake.blowing .sensor-img { animation: judder 0.16s steps(2) infinite; }
@keyframes judder { 50% { margin-left: 3px; margin-top: -2px; } }
.sensor-shake.blowing::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image:
    radial-gradient(circle at 22% 8%, rgba(150, 90, 40, 0.75) 1.2px, transparent 1.8px),
    radial-gradient(circle at 64% 4%, rgba(120, 70, 30, 0.7) 1px, transparent 1.6px),
    radial-gradient(circle at 86% 12%, rgba(160, 100, 45, 0.6) 1.4px, transparent 2px);
  background-size: 100% 46%, 100% 61%, 100% 37%;
  animation: rust-sift 0.6s linear infinite;
}
@keyframes rust-sift { to { background-position: 0 46%, 0 61%, 0 37%; } }
/* hum (#22): the rim blurs with resonance while the note holds */
.sensor-hum.blowing .sensor-img { filter: blur(0.7px) brightness(1.05); }
/* align (#10): light through the vellum as the slits line up */
.sensor-align.blowing .sensor-slits { box-shadow: inset 0 0 34px rgba(255, 236, 170, 0.28); }
/* win garnishes (.sensor-won set by onSensorSolved, no re-render in the way) */
.sensor-flame.sensor-won::after {           /* the blown candle lets go a wisp */
  content: ''; position: absolute; left: 50%; top: 40%; width: 0.5rem; height: 2.6rem;
  border-radius: 50%; pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(200, 200, 210, 0.5) 40%, rgba(160, 160, 175, 0.25));
  filter: blur(2px);
  animation: smoke-wisp 2.2s ease-out forwards;
}
@keyframes smoke-wisp {
  0% { transform: translate(-50%, 0) scaleY(0.4); opacity: 0; }
  25% { opacity: 0.9; }
  100% { transform: translate(-30%, -130%) scaleY(1.4) rotate(14deg); opacity: 0; }
}
.sensor-tilt.sensor-won::after {            /* the righted pillar catches the sun */
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 34%, rgba(255, 238, 180, 0.5) 48%, transparent 62%);
  background-size: 280% 100%; background-position: 120% 0;
  animation: sunbeam-pass 1.6s ease-out forwards;
}
@keyframes sunbeam-pass { to { background-position: -60% 0; } }
.sensor-align.sensor-won .sensor-reveal-cap { text-shadow: 0 0 14px rgba(255, 236, 170, 0.9); }

/* ---- eraflip kind garnishes ---- */
/* tree (#8): the grown future sheds drifting leaves */
.eraflip-k-tree.era-b-present::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image:
    radial-gradient(ellipse 3px 2px at 18% 12%, rgba(150, 160, 80, 0.55) 40%, transparent 60%),
    radial-gradient(ellipse 2.6px 1.8px at 58% 4%, rgba(130, 145, 70, 0.5) 40%, transparent 60%),
    radial-gradient(ellipse 3px 2px at 82% 18%, rgba(160, 170, 95, 0.45) 40%, transparent 60%);
  background-size: 100% 130%, 100% 160%, 100% 145%;
  animation: leaf-drift 9s linear infinite;
}
@keyframes leaf-drift { to { background-position: 6% 130%, -4% 160%, 5% 145%; } }
/* brick (#16): a lantern rakes slowly across the 1672 wall — shadows walk */
.eraflip-k-brick.era-b-past::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 30%, rgba(255, 208, 130, 0.14) 46%, rgba(60, 30, 10, 0.12) 60%, transparent 74%);
  background-size: 260% 100%;
  animation: water-sheen 8.5s ease-in-out infinite;
}
/* ledger (#24): the fresh 1672 ink still sits WET on the page */
.eraflip-k-ledger.era-b-past::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.09) 50%, transparent 60%);
  background-size: 300% 100%;
  animation: water-sheen 6s ease-in-out infinite;
}


/* ============ POLISH SPRINT 10: diegesis & ritual ============ */
/* every puzzle happens IN the scene: the live room, blurred, behind the board */
.puzzle-overlay { overflow: hidden; }
.puzzle-overlay::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  background-image: var(--scene-bg); background-size: cover; background-position: center;
  filter: blur(10px) brightness(0.42) saturate(0.88);
  transform: scale(1.07);
}
.puzzle-overlay .overlay-panel {
  position: relative; z-index: 1;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(26, 19, 36, 0.35) 0%, rgba(16, 12, 22, 0.6) 62%, rgba(10, 8, 16, 0.78) 100%);
}
/* immersive boards keep their own painted backdrop above the scene blur */
.puzzle-overlay .imm-backdrop { z-index: 0; }

/* the entry plate: an engraved brass nameplate stamps on, then withdraws */
.puzzle-plate {
  position: absolute; left: 50%; top: 17%; transform: translateX(-50%);
  z-index: 9; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
  padding: 0.75rem 1.7rem; border-radius: 8px;
  color: #f6e8c4; font-weight: 800; letter-spacing: 0.04em; text-align: center;
  background: linear-gradient(180deg, #7a5f34, #4c3a1c);
  border: 2px solid #2c2010;
  box-shadow: 0 10px 30px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.25), inset 0 -2px 5px rgba(0,0,0,0.4);
  text-shadow: 0 1px 2px rgba(0,0,0,0.7);
  animation: plate-stamp 2s cubic-bezier(.2,.9,.3,1) forwards;
}
.puzzle-plate .pp-pica { font-size: 0.85em; color: #ffd76a; }
@keyframes plate-stamp {
  0% { opacity: 0; transform: translateX(-50%) scale(1.55); }
  12% { opacity: 1; transform: translateX(-50%) scale(1); }
  80% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-50%) scale(0.98) translateY(-9px); }
}

/* the win: a red wax seal presses the answer shut, coins arc to the counter */
.win-seal {
  position: absolute; left: 50%; top: 40%; width: 7rem; height: 7rem;
  margin: -3.5rem 0 0 -3.5rem; z-index: 9; pointer-events: none; border-radius: 50%;
  background: radial-gradient(circle at 36% 30%, #b8352a, #7a1f16 62%, #5c150e);
  box-shadow: 0 6px 18px rgba(0,0,0,0.55), inset 0 2px 6px rgba(255,120,90,0.5), inset 0 -4px 10px rgba(0,0,0,0.45);
  display: flex; align-items: center; justify-content: center;
  animation: seal-stamp 0.5s cubic-bezier(.25,1.4,.4,1);
}
.win-seal::before {
  content: ''; position: absolute; inset: 9%; border-radius: 50%;
  border: 2px solid rgba(255, 180, 150, 0.4);
}
.win-seal i {
  font-style: normal; font-weight: 900; font-size: 2.3rem; letter-spacing: 0.05em;
  color: #ffd9c4; text-shadow: 0 -1px 1px rgba(0,0,0,0.6), 0 1px 1px rgba(255,200,170,0.35);
}
@keyframes seal-stamp {
  0% { transform: scale(2.4) rotate(-14deg); opacity: 0; }
  60% { transform: scale(0.94) rotate(2deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); }
}
.pica-coin {
  position: absolute; left: 50%; top: 42%; width: 1.15rem; height: 1.15rem;
  border-radius: 50%; z-index: 9; pointer-events: none; opacity: 0;
  background: radial-gradient(circle at 35% 30%, #ffe9a0, #d8a832 55%, #8a6414);
  border: 1px solid #6b4c12;
  animation: coin-arc 0.9s cubic-bezier(.3,.4,.6,1) forwards;
  animation-delay: calc(var(--i) * 0.09s);
}
@keyframes coin-arc {
  0% { opacity: 1; transform: translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(calc(9rem + var(--i) * 2.2rem), calc(-13rem - var(--i) * 1.2rem)) scale(0.5); }
}

/* material honesty on the small surfaces: paper fibre on cards, brass bezel
   on the sensor instruments */
.puzzle-overlay .seq-card:not(.crt-monitor), .puzzle-overlay .match-item {
  background-image:
    repeating-linear-gradient(98deg, rgba(120, 90, 40, 0.05) 0 2px, transparent 2px 6px),
    repeating-linear-gradient(4deg, rgba(90, 70, 30, 0.035) 0 1px, transparent 1px 5px),
    linear-gradient(180deg, #f4ead2, #e9dcba);
}
.sensor-stage {
  border-color: #6b4c17;
  box-shadow: 0 6px 0 rgba(0,0,0,0.3), inset 0 0 0 3px rgba(201, 162, 39, 0.35), inset 0 0 24px rgba(0,0,0,0.35);
}


/* ============ POLISH SPRINT 11: period type + painted materials ============ */
/* IM Fell English — a true 17th-century English/Dutch print face, bundled
   (no CDN). Display voice only: titles, placards, plates, chapter cards.
   Body text keeps the readable modern face. */
@font-face {
  font-family: 'IM Fell';
  src: url('../assets/fonts/imfell-normal.woff2') format('woff2');
  font-style: normal; font-weight: 400; font-display: swap;
}
@font-face {
  font-family: 'IM Fell';
  src: url('../assets/fonts/imfell-italic.woff2') format('woff2');
  font-style: italic; font-weight: 400; font-display: swap;
}
.overlay-title, .imm-title, .puzzle-plate .pp-title,
.location-placard, .chapter-end-title, .conf-splash-title,
.title-version, .demo-ribbon, .gt-meter-big {
  font-family: 'IM Fell', Georgia, serif;
  letter-spacing: 0.02em;
}
.puzzle-overlay .overlay-title { font-size: 1.2rem; font-weight: 400; }
.puzzle-plate .pp-title { font-weight: 400; font-size: 1.15em; }
/* painted parchment on the paper surfaces (texture lands with the asset) */
.puzzle-overlay .logic-clues li, .puzzle-overlay .hint-text,
.puzzle-overlay .seq-card:not(.crt-monitor), .puzzle-overlay .match-item {
  background-image: url('../assets/img/ui_parchment.webp');
  background-size: 340px;
  background-blend-mode: multiply;
  background-color: #f0e5c8;
}
.logicgrid-overlay .logic-clues li { background-color: #eee0bd; }
/* oiled oak on the chrome bars: the action bar becomes furniture */
.puzzle-overlay .overlay-actions {
  background-image: linear-gradient(180deg, rgba(16,12,24,0.15), rgba(16,12,24,0.55)), url('../assets/img/ui_oakbrass.webp');
  background-size: auto, 420px;
  border-top: 2px solid rgba(201, 162, 39, 0.4);
  border-radius: 10px 10px 0 0;
}
.puzzle-plate {
  background-image: linear-gradient(180deg, rgba(122,95,52,0.75), rgba(76,58,28,0.85)), url('../assets/img/ui_oakbrass.webp');
  background-size: auto, 300px;
}
