/* Montserrat, self-hosted (variable weight, Latin subset). */
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../assets/fonts/montserrat-normal-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Montserrat";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("../assets/fonts/montserrat-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --font: "Montserrat", system-ui, sans-serif;
}

* {
  box-sizing: border-box;
}

button,
input,
select {
  font-family: inherit;
}

html,
body {
  font-family: var(--font);
  height: 100%;
  margin: 0;
  background: black;
  overflow: hidden;
  /* It's all clicking and dragging — rapid clicks shouldn't select text. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.stage {
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.scene {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 48px;
}

/* Entrance-only: applied on mount, removed once it finishes so later
   drags/moves (which don't recreate the DOM node) never replay it. */
.drop-in {
  animation: drop-in 0.6s ease-out both;
  animation-delay: var(--enter-delay, 0s);
}

@keyframes drop-in {
  from {
    transform: translate(-50%, -50%) translateY(-90vh);
    opacity: 0;
  }
  to {
    transform: translate(-50%, -50%) translateY(0);
    opacity: 1;
  }
}

/* Entrance-only, same removal contract as .drop-in — for things that
   should just appear (a small pop), not fall from off-screen. */
.pop-in {
  animation: pop-in 0.25s ease-out both;
  animation-delay: var(--enter-delay, 0s);
}

@keyframes pop-in {
  from {
    transform: translate(-50%, -50%) scale(0.7);
    opacity: 0;
  }
  to {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
  }
}

/* Every spawned thing (engine/sprite.js): a fixed-position box centred
   on its left/top — no card/shadow framing, so each just sits on the page. */
.sprite {
  position: fixed;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  transform: translate(-50%, -50%);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  z-index: 10;
  transition: transform 0.2s ease, opacity 0.5s ease;
}

/* The ground/plant sit beneath everything else that can be dragged
   over them (tools, threats, ...), regardless of DOM/creation order. */
.sprite.ground-level {
  z-index: 1;
}

/* interactions/threat.js fades a retired threat out via this class and
   waits on its transitionend. */
.sprite.retiring {
  opacity: 0;
  pointer-events: none;
}

.sprite-img {
  width: 100%;
  height: auto;
  object-fit: contain;
  display: block;
  /* So swapping `src` (e.g. a character's closed/open pose, which isn't
     pixel-identical in aspect ratio) grows/shrinks smoothly instead of
     jumping a size on the very next paint. */
  transition: width 0.3s ease, height 0.3s ease;
}

/* Holds height constant instead — the item's own state changes (not
   this container) are what should visibly resize it, symmetrically in
   both directions, via the transition above. */
.sprite--fixed-height {
  width: auto;
}

.sprite--fixed-height .sprite-img {
  width: auto;
  height: 100%;
}

/* Added by interactions/drag.js while an item can be dragged. */
.sprite.draggable {
  cursor: grab;
}

/* interactions/threat.js tags anything chaseable with this. */
.sprite.threat {
  cursor: pointer;
}

.sprite.clickable {
  cursor: pointer;
}

/* The bears' bowls are always in front of the bears (whose layer rises as they walk
   down the floor, up to about 100), so a bowl can never end up hidden behind one
   — a sleeping bear included — and can always be picked up. A dragged one is higher still. */
.sprite.bowl {
  z-index: 500;
}

.sprite.dragging {
  cursor: grabbing;
  z-index: 1000;
  transform: translate(-50%, -50%) scale(1.05);
}

.sprite.locked {
  cursor: default;
  pointer-events: none;
}

/* A used tool (seeds/watering-can/sun) snapping back to its origin
   instead of disappearing — left/top only transition here, never on
   the base .sprite, or every drag frame would lag behind the
   pointer instead of tracking it 1:1. */
.sprite.returning {
  transition: left 0.5s cubic-bezier(0.34, 1.56, 0.64, 1), top 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
    transform 0.2s ease, opacity 0.5s ease;
}

.threat-hint {
  position: fixed;
  transform: translate(-50%, -100%);
  background: rgba(20, 20, 20, 0.85);
  color: #fff;
  font-family: var(--font);
  font-size: 0.75rem;
  padding: 4px 10px;
  border-radius: 6px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 2000;
}

.caption {
  position: fixed;
  left: 50%;
  bottom: 40px;
  transform: translateX(-50%);
  max-width: min(80vw, 560px);
  text-align: center;
  background: rgba(20, 20, 20, 0.85);
  color: #fff;
  padding: 10px 18px;
  border-radius: 10px;
  font-family: var(--font);
  font-size: 1rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
  z-index: 5200; /* above the loading screen (5000), below the audio controls (5500) */
}

.caption.visible {
  opacity: 1;
}

.scene-exit {
  opacity: 0;
  transition: opacity 0.5s ease;
}

.loading-screen {
  position: fixed;
  inset: 0;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 5000;
}

.loading-bar-track {
  width: 240px;
  height: 4px;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 2px;
  overflow: hidden;
}

.loading-bar {
  height: 100%;
  width: 0%;
  background: #fff;
  animation: loading-bar-fill 1.2s ease-in-out forwards;
}

@keyframes loading-bar-fill {
  from {
    width: 0%;
  }
  to {
    width: 100%;
  }
}

.retry-popup-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 6000;
}

.retry-popup {
  background: #fff;
  border-radius: 12px;
  padding: 32px 40px;
  text-align: center;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
  font-family: var(--font);
}

.retry-popup p {
  margin: 0 0 20px;
  font-size: 1.2rem;
  color: #1a1a1a;
}

.retry-popup-button {
  padding: 10px 24px;
  border: none;
  border-radius: 8px;
  background: #1a1a1a;
  color: #fff;
  font-size: 1rem;
  cursor: pointer;
}

.retry-popup-button:hover {
  background: #333;
}

.tv-frame {
  position: relative;
  width: min(70vw, 560px);
  padding: 28px 28px 64px;
  border-radius: 32px;
  background: linear-gradient(160deg, #6b5a4a, #3f342a);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), inset 0 0 0 4px #2a221a;
}

.tv-screen {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 18px;
  overflow: hidden;
  background: #000;
  box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.8), inset 0 0 6px 3px #000;
  animation: tv-flicker 6s infinite;
}

.tv-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}

.tv-scanlines {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.25) 0px,
    rgba(0, 0, 0, 0.25) 1px,
    transparent 2px,
    transparent 3px
  );
  mix-blend-mode: multiply;
}

.tv-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 60px 20px rgba(0, 0, 0, 0.6);
  border-radius: 18px;
}

@keyframes tv-flicker {
  0%,
  96%,
  100% {
    filter: brightness(1);
  }
  97% {
    filter: brightness(1.15);
  }
  98% {
    filter: brightness(0.9);
  }
}

.tv-channel-label {
  position: absolute;
  top: 10px;
  left: 14px;
  color: #d8f5d8;
  font-family: "Courier New", monospace;
  font-size: 0.9rem;
  text-shadow: 0 0 4px rgba(0, 255, 0, 0.6);
  opacity: 0.85;
  pointer-events: none;
}

.tv-dial {
  position: absolute;
  bottom: 8px;
  left: 24px;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #8a7a63, #3a2f24);
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);
  cursor: grab;
  touch-action: none;
}

/* The idle nudge from scene 1: a pulsing glow around the dial. */
.tv-dial.hint-glow {
  animation: dial-glow 1.2s ease-in-out infinite;
}

@keyframes dial-glow {
  0%, 100% {
    box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5), 0 0 0 0 rgba(143, 224, 148, 0);
  }
  50% {
    box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5), 0 0 0 7px rgba(143, 224, 148, 0.35), 0 0 24px 10px rgba(143, 224, 148, 0.85);
  }
}

.tv-dial.locked {
  cursor: default;
}

.tv-dial:active {
  cursor: grabbing;
}

.tv-dial-notch {
  position: absolute;
  top: 4px;
  left: 50%;
  width: 4px;
  height: 16px;
  background: #f0e6d2;
  border-radius: 2px;
  transform: translateX(-50%);
}

.glitch-static-patch {
  position: fixed;
  pointer-events: none;
  z-index: 4500;
  opacity: 0.55;
  image-rendering: pixelated;
  mix-blend-mode: overlay;
}

.glitch-shutter {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  pointer-events: none;
  z-index: 4600;
  opacity: 0.45;
  image-rendering: pixelated;
  mix-blend-mode: overlay;
  animation: glitch-shutter-sweep var(--shutter-duration, 500ms) linear forwards;
}

@keyframes glitch-shutter-sweep {
  from {
    transform: translateY(-10vh);
  }
  to {
    transform: translateY(110vh);
  }
}

@keyframes glitch-blip {
  0% {
    transform: translateX(0);
    filter: none;
    opacity: 1;
  }
  10% {
    transform: translateX(-4px);
    filter: hue-rotate(30deg) brightness(1.3) drop-shadow(2px 0 rgba(255, 0, 60, 0.8)) drop-shadow(-2px 0 rgba(0, 220, 255, 0.8));
    opacity: 0.6;
  }
  20% {
    transform: translateX(3px);
    filter: drop-shadow(-2px 0 rgba(255, 0, 60, 0.8)) drop-shadow(2px 0 rgba(0, 220, 255, 0.8));
    opacity: 1;
  }
  30% {
    transform: translateX(0);
    filter: none;
    opacity: 0.25;
  }
  40% {
    opacity: 1;
  }
  100% {
    transform: translateX(0);
    filter: none;
    opacity: 1;
  }
}

.glitch-blip {
  animation: glitch-blip 0.35s steps(2, end);
}

/* ---- engine/effects.js ------------------------------------------- */

@keyframes shake {
  0%, 100% { transform: translate(-50%, -50%) rotate(0); }
  10% { transform: translate(-50%, -50%) translate(-14px, 4px) rotate(-8deg); }
  20% { transform: translate(-50%, -50%) translate(12px, -6px) rotate(7deg); }
  30% { transform: translate(-50%, -50%) translate(-12px, 2px) rotate(-9deg); }
  40% { transform: translate(-50%, -50%) translate(14px, -4px) rotate(8deg); }
  50% { transform: translate(-50%, -50%) translate(-10px, 6px) rotate(-6deg); }
  60% { transform: translate(-50%, -50%) translate(10px, -2px) rotate(6deg); }
  70% { transform: translate(-50%, -50%) translate(-6px, 2px) rotate(-4deg); }
  85% { transform: translate(-50%, -50%) translate(4px, 0) rotate(2deg); }
}

.shake {
  animation: shake 0.6s ease-in-out;
}

.speech {
  position: fixed;
  transform: translate(-50%, calc(-100% - 10px));
  background: #fff;
  color: #1a1a1a;
  font-family: var(--font);
  font-weight: 700;
  padding: 8px 14px;
  border-radius: 14px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 3000;
  animation: pop-speech 0.18s ease-out both;
}

.speech--shout {
  font-size: 1.6rem;
  color: #c62828;
}

@keyframes pop-speech {
  from { opacity: 0; transform: translate(-50%, calc(-100% - 10px)) scale(0.6); }
}

.burst-particle {
  position: fixed;
  border-radius: 50%;
  pointer-events: none;
  z-index: 3500;
  transform: translate(-50%, -50%);
  animation: burst-out 0.9s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}

@keyframes burst-out {
  to {
    transform: translate(-50%, -50%) translate(var(--dx), var(--dy)) scale(0.3);
    opacity: 0;
  }
}

.screen-flash {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 4800;
  animation: screen-flash 0.6s ease-out forwards;
}

@keyframes screen-flash {
  from { opacity: 0.95; }
  to { opacity: 0; }
}

/* ---- shared story beats ------------------------------------------ */

/* "Click me": a slow pulse + glow on whatever the player should touch. */
.sprite.beckon {
  animation: beckon 1.4s ease-in-out infinite;
}

@keyframes beckon {
  0%, 100% { transform: translate(-50%, -50%) scale(1); filter: drop-shadow(0 0 0 transparent); }
  50% { transform: translate(-50%, -50%) scale(1.07); filter: drop-shadow(0 0 18px rgba(143, 224, 148, 0.8)); }
}

/* Something worth picking up. */
.sprite.glint .sprite-img {
  animation: glint 1.8s ease-in-out infinite;
}

@keyframes glint {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(255, 230, 128, 0.4)); }
  50% { filter: drop-shadow(0 0 14px rgba(255, 230, 128, 1)); }
}

.sprite.fade-out {
  opacity: 0;
  pointer-events: none;
  transition: opacity 1s ease;
}

/* ---- signal ------------------------------------------------------ */

.tv-arrive {
  animation: tv-arrive 0.5s ease-out both;
}

@keyframes tv-arrive {
  from { transform: scale(0.6); opacity: 0; }
}

/* ---- cabin ------------------------------------------------------- */

.fall-away {
  animation: fall-away 0.8s ease-in forwards;
}

@keyframes fall-away {
  20% { transform: translate(-50%, -50%) translateY(-10px) rotate(-15deg); }
  to { transform: translate(-50%, -50%) translateY(140px) rotate(60deg); opacity: 0; }
}

.scene-cabin .sprite[data-item-id="cabin"].clickable:hover {
  filter: drop-shadow(0 0 16px rgba(255, 179, 71, 0.7));
}

/* ---- bears ------------------------------------------------------- */

.flicker .sprite-img {
  animation: flicker 0.35s steps(2) infinite alternate;
  filter: drop-shadow(0 0 22px rgba(255, 140, 0, 0.9));
}

@keyframes flicker {
  from { transform: scale(1, 1); }
  to { transform: scale(1.06, 0.94); }
}

.sprite.sleeping {
  transform: translate(-50%, -50%) translateY(30%) rotate(-82deg);
  transition: transform 1.2s ease-in-out;
}

.zzz {
  position: absolute;
  top: 0;
  right: -10px;
  color: #cfe3ff;
  font: 700 1.4rem var(--font);
  transform: rotate(82deg);
  animation: zzz 2.4s ease-in-out infinite;
  pointer-events: none;
}

@keyframes zzz {
  0% { opacity: 0; translate: 0 0; }
  30% { opacity: 1; }
  100% { opacity: 0; translate: 40px -10px; }
}

/* A table drawn from elements (story/props/table.js). Everything is a
   percentage of its 440:214 box, so it scales with its width. */
.sprite.table {
  aspect-ratio: 440 / 214;
  display: block;
}

.sprite.table::after {
  content: "";
  position: absolute;
  left: 2%;
  right: 2%;
  bottom: -3%;
  height: 8%;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.3);
  filter: blur(6px);
  z-index: -1;
}

.table-part {
  position: absolute;
}

/* Painted on the front band; they belong to the table, not the bowls. */
.table-label {
  position: absolute;
  top: 11%;
  transform: translateX(-50%);
  color: #fff;
  font-family: var(--font);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  white-space: pre-line;
  width: max-content; /* not squeezed by the table's edge, whichever slot it's in */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
  pointer-events: none;
}

.table-top {
  left: 0;
  top: 0;
  width: 100%;
  height: 7.5%;
  background: linear-gradient(to bottom, #d19a5f, #b9824a);
  clip-path: polygon(7.7% 0, 92.3% 0, 100% 100%, 0 100%);
}

/* The dark front the bowls' labels are read against. */
.table-front {
  left: 0;
  top: 7.5%;
  width: 100%;
  height: 29%;
  background:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.07), transparent 40%),
    repeating-linear-gradient(to right, transparent 0 46px, rgba(0, 0, 0, 0.12) 46px 48px),
    #6b4423;
  border: 3px solid #4a2d15;
  box-sizing: border-box;
}

.table-part.leg {
  top: 36%;
  width: 6.8%;
  height: 62%;
  background: linear-gradient(to right, #6a4526, #523318);
  border: 2px solid #3f2713;
  border-radius: 3px;
  box-sizing: border-box;
}

.table-part.leg-l { left: 3.2%; }
.table-part.leg-r { right: 3.2%; }

.table-part.leg-back {
  top: 36%;
  width: 4.1%;
  height: 52%;
  background: #3f2713;
}

.table-part.leg-back-l { left: 15.9%; }
.table-part.leg-back-r { right: 15.9%; }

/* A bear: emoji head over a stick-figure body (story/props/bear.js). */
.sprite.bear {
  gap: 0;
}

.bear-head {
  position: relative;
  width: 100%;
}

/* Each bear's own shade (--tone, set per bear): papa the darkest, baby the lightest. */
.sprite.bear {
  --tone: brightness(1) saturate(1);
}

.bear-head .sprite-img {
  filter: var(--tone);
}

.bear-body {
  width: 64%;
  margin-top: -14%; /* tucks the neck up under the chin */
}

.bear-stick {
  display: block;
  width: 100%;
  overflow: visible;
  /* A dark outline so the pale body reads on the wall and the floor alike. */
  filter: drop-shadow(0 0 1px #2a1608) drop-shadow(0 0 1px #2a1608) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}

/* Arms and legs hinge at the shoulder and hip (SVG user units). */
.bear-stick .arm { transform-origin: 50px 20px; }
.bear-stick .leg { transform-origin: 50px 66px; }

.bear.walking .leg-l,
.bear.walking .arm-r {
  animation: bear-swing 0.55s ease-in-out infinite alternate;
}

.bear.walking .leg-r,
.bear.walking .arm-l {
  animation: bear-swing 0.55s ease-in-out infinite alternate-reverse;
}

.bear.walking .bear-head {
  animation: bear-bob 0.275s ease-in-out infinite alternate;
}

@keyframes bear-swing {
  from { transform: rotate(24deg); }
  to { transform: rotate(-24deg); }
}

@keyframes bear-bob {
  to { transform: translateY(-4px); }
}

.glowing .bear-stick {
  filter: drop-shadow(0 0 1px #2a1608) drop-shadow(0 0 12px rgba(255, 230, 128, 0.9));
}

.bear-eye {
  position: absolute;
  top: 57%;
  width: 10%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #fffbe0;
  opacity: 0;
  transform: translate(-50%, -50%);
  transition: opacity 0.5s ease, box-shadow 0.5s ease;
  pointer-events: none;
}

.bear-eye--left { left: 39%; }
.bear-eye--right { left: 61%; }

.glowing .bear-eye {
  opacity: 1;
  box-shadow: 0 0 10px 4px #ffe680, 0 0 30px 10px rgba(255, 200, 60, 0.8);
}

.glowing .sprite-img {
  filter: var(--tone, none) drop-shadow(0 0 20px rgba(255, 230, 128, 0.9));
}

.float-up {
  animation: float-up 2.6s ease-in forwards;
}

@keyframes float-up {
  0% { transform: translate(-50%, -50%); }
  40% { transform: translate(-50%, -50%) translate(-6px, -90px) rotate(-6deg); }
  70% { transform: translate(-50%, -50%) translate(6px, -170px) rotate(6deg) scale(1.15); }
  85% { transform: translate(-50%, -50%) translate(-4px, -200px) scale(1.35); }
  100% { transform: translate(-50%, -50%) translate(0, -210px) scale(1.6); }
}

.eaten {
  animation: eaten 0.6s ease-in forwards;
}

@keyframes eaten {
  to { transform: translate(-50%, -50%) scale(0.2); opacity: 0; }
}

/* ---- egg --------------------------------------------------------- */

.burying {
  animation: burying 0.7s ease-in forwards;
}

@keyframes burying {
  to { transform: translate(-50%, -50%) translateY(26px) scale(0.6); opacity: 0; }
}

.sprite.soaked .sprite-img {
  filter: sepia(0.7) brightness(0.8) saturate(1.4);
}

.sprite.heating .sprite-img {
  animation: heating 0.5s ease-in-out infinite alternate;
}

@keyframes heating {
  from { filter: drop-shadow(0 0 4px rgba(255, 160, 0, 0.5)); }
  to { filter: drop-shadow(0 0 22px rgba(255, 120, 0, 1)) brightness(1.1); }
}

.sprite.hot .sprite-img {
  filter: drop-shadow(0 0 18px rgba(255, 70, 0, 0.95)) sepia(0.3) saturate(1.6);
}

.sinking {
  animation: sinking 0.9s ease-in forwards;
}

@keyframes sinking {
  to { transform: translate(-50%, -50%) translateY(50px) scale(0.5) rotate(10deg); opacity: 0; }
}

.ringing {
  transform-origin: 50% 10%;
  animation: ringing 2.7s ease-in-out;
}

@keyframes ringing {
  0%, 100% { transform: translate(-50%, -50%) rotate(0); }
  10%, 43%, 76% { transform: translate(-50%, -50%) rotate(24deg); }
  26%, 59%, 92% { transform: translate(-50%, -50%) rotate(-24deg); }
}

/* ---- outlier ----------------------------------------------------- */

.sprite.fly-away {
  transition: left 1.2s ease-in, top 1.2s ease-in;
}

.sprite.chosen {
  transform: translate(-50%, -50%) scale(1.45); /* keep in step with CHOSEN_SCALE in 05-outlier.js */
  transition: left 1.2s ease-in-out, top 1.2s ease-in-out, transform 1.2s ease-in-out;
}

.emerge {
  animation: emerge 1.6s cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}

@keyframes emerge {
  from { transform: translate(-50%, -50%) scale(0.15); opacity: 0; }
  30% { opacity: 1; }
  to {
    transform: translate(-50%, -50%) translateY(-190px) scale(1) rotate(-4deg);
    filter: drop-shadow(0 0 22px rgba(255, 240, 180, 0.9));
  }
}

/* ---- audio controls (story/overlays/audio-controls.js) ------------ */

.mute-btn,
.mixer-btn {
  position: fixed;
  top: 16px;
  right: 16px;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  border-radius: 10px;
  background: rgba(20, 20, 20, 0.6);
  color: #fff;
  cursor: pointer;
  z-index: 5500;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.mute-btn:hover,
.mixer-btn:hover {
  transform: scale(1.08);
  background: rgba(40, 40, 40, 0.75);
}

.mute-btn.muted {
  color: #ff8a80;
}

.mute-btn:focus-visible,
.mixer-btn:focus-visible,
.mixer-mute:focus-visible,
.mixer-slider:focus-visible {
  outline: 2px solid #8fe094;
  outline-offset: 2px;
}

/* ---- engine/narration.js: narrateWords() -------------------------- */

.kinetic-text {
  position: fixed;
  inset: 0;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  gap: 0.15em 0.3em;
  padding: 0 min(14vw, 180px);
  color: #fff;
  font: 600 clamp(1.5rem, 3.4vw, 2.9rem) / 1.35 var(--font);
  text-align: center;
  pointer-events: none;
  z-index: 20;
}

/* Hidden words still take up their space, so lines never reflow as they arrive. */
.kinetic-text .word {
  opacity: 0;
  transform: translateY(14px) scale(0.96);
  filter: blur(6px);
  transition: opacity 0.35s ease-out, transform 0.35s ease-out, filter 0.35s ease-out;
}

.kinetic-text .word.visible {
  opacity: 1;
  transform: none;
  filter: none;
}

/* ---- finale: story/props/party.js, effects.js fireworks + rain ---- */

.rocket {
  position: fixed;
  width: 5px;
  height: 26px;
  border-radius: 3px;
  background: linear-gradient(to top, transparent, #ffd54a 60%, #fff);
  box-shadow: 0 0 8px 2px rgba(255, 213, 74, 0.8);
  transform: translateX(-50%);
  pointer-events: none;
  z-index: 3400;
}

.fall-item {
  position: fixed;
  top: -4rem;
  pointer-events: none;
  z-index: 3300;
  animation: fall-item 4s linear forwards;
}

@keyframes fall-item {
  to { transform: translate(var(--sway), 110vh) rotate(var(--spin)); }
}

/* Coloured spotlights sweeping around, behind everything. */
.party-lights {
  position: fixed;
  left: 50%;
  top: 50%;
  width: 220vmax;
  height: 220vmax;
  margin: -110vmax 0 0 -110vmax;
  background: repeating-conic-gradient(from 0deg, rgba(255, 23, 68, 0.28) 0 12deg, transparent 12deg 30deg, rgba(0, 176, 255, 0.28) 30deg 42deg, transparent 42deg 60deg, rgba(255, 234, 0, 0.26) 60deg 72deg, transparent 72deg 90deg, rgba(213, 0, 249, 0.28) 90deg 102deg, transparent 102deg 120deg);
  mix-blend-mode: screen;
  pointer-events: none;
  z-index: 0;
  animation: party-lights-spin 14s linear infinite, party-lights-hue 3s linear infinite;
}

@keyframes party-lights-spin { to { transform: rotate(360deg); } }
@keyframes party-lights-hue { to { filter: hue-rotate(360deg); } }

.disco-ball {
  position: fixed;
  top: 0;
  z-index: 30;
  pointer-events: none;
  transform-origin: 50% 0;
  animation: disco-swing 2.4s ease-in-out infinite alternate;
}

.disco-ball--left { left: 10vw; }
.disco-ball--right { right: 10vw; animation-delay: -1.2s; }

.disco-string {
  width: 2px;
  height: 14vh;
  margin: 0 auto;
  background: #aaa;
}

.disco-globe {
  font-size: clamp(3rem, 7vw, 5.5rem);
  line-height: 1;
  margin-top: -4px;
  filter: drop-shadow(0 0 18px rgba(255, 255, 255, 0.9));
  animation: disco-spin 1.6s steps(8) infinite;
}

@keyframes disco-swing {
  from { transform: rotate(-9deg); }
  to { transform: rotate(9deg); }
}

@keyframes disco-spin {
  50% { filter: drop-shadow(0 0 30px #ff4081) brightness(1.3); }
}

.party-title {
  position: fixed;
  left: 50%;
  bottom: 11vh;
  transform: translateX(-50%);
  white-space: nowrap;
  font: 900 clamp(2.4rem, 7.5vw, 6rem) / 1 var(--font);
  letter-spacing: 0.02em;
  background: linear-gradient(90deg, #ff1744, #ff9100, #ffea00, #00e676, #00b0ff, #d500f9, #ff1744);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 4px 0 rgba(0, 0, 0, 0.6)) drop-shadow(0 0 18px rgba(255, 255, 255, 0.5));
  pointer-events: none;
  z-index: 200;
  animation: party-title-slide 1.4s linear infinite, party-title-pulse 0.5s ease-in-out infinite alternate;
}

@keyframes party-title-slide { to { background-position: -200% 0; } }
@keyframes party-title-pulse {
  from { transform: translateX(-50%) scale(1) rotate(-1.5deg); }
  to { transform: translateX(-50%) scale(1.07) rotate(1.5deg); }
}

.party-subtitle {
  position: fixed;
  left: 50%;
  bottom: 8vh;
  transform: translateX(-50%);
  color: #fff;
  font: italic 600 clamp(0.9rem, 1.8vw, 1.3rem) var(--font);
  text-shadow: 0 0 10px #000;
  pointer-events: none;
  z-index: 200;
}

/* Dancers: every move keeps the sprite's centring translate (and the
   star's --s scale), or they'd jump off their spot. */
.sprite.dancing {
  animation-duration: var(--dur, 0.6s);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

.sprite.dance-bounce { animation-name: dance-bounce; }
.sprite.dance-sway { animation-name: dance-sway; }
.sprite.dance-hop { animation-name: dance-hop; }

@keyframes dance-bounce {
  0%, 100% { transform: translate(-50%, -50%) scale(var(--s, 1), var(--s, 1)); }
  50% { transform: translate(-50%, -50%) translateY(-34px) scale(calc(var(--s, 1) * 0.94), calc(var(--s, 1) * 1.08)); }
}

@keyframes dance-sway {
  0%, 100% { transform: translate(-50%, -50%) rotate(-12deg) scale(var(--s, 1)); }
  50% { transform: translate(-50%, -50%) translateY(-14px) rotate(12deg) scale(var(--s, 1)); }
}

@keyframes dance-hop {
  0% { transform: translate(-50%, -50%) scale(var(--s, 1)) scaleX(1); }
  25% { transform: translate(-50%, -50%) translate(-18px, -30px) rotate(-8deg) scale(var(--s, 1)); }
  50% { transform: translate(-50%, -50%) scale(var(--s, 1)) scaleX(-1); }
  75% { transform: translate(-50%, -50%) translate(18px, -30px) rotate(8deg) scale(var(--s, 1)) scaleX(-1); }
  100% { transform: translate(-50%, -50%) scale(var(--s, 1)) scaleX(1); }
}

.party-hat {
  position: absolute;
  left: 50%;
  top: 14%;
  transform: translate(-50%, -75%) rotate(-10deg);
  font-size: 2.3rem;
  line-height: 1;
  pointer-events: none;
}

/* ---- debug: engine/debug.js, engine/debug-panel.js --------------- */

/* Fast-forwarding to a ?step= target: everything finishes at once. */
body.fast-forward *,
body.fast-forward *::before,
body.fast-forward *::after {
  animation-duration: 1ms !important;
  animation-delay: 0s !important;
  transition-duration: 1ms !important;
  transition-delay: 0s !important;
}

.debug-panel {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 9000;
  font: 12px / 1.4 ui-monospace, Menlo, monospace;
  color: #e6e6e6;
}

.debug-toggle {
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 10px;
  background: rgba(20, 20, 20, 0.7);
  font-size: 18px;
  cursor: pointer;
  opacity: 0.55;
}

.debug-toggle:hover,
.debug-panel.open .debug-toggle {
  opacity: 1;
}

.debug-body {
  display: none;
  position: absolute;
  left: 0;
  bottom: 44px;
  width: 330px;
  padding: 12px;
  border-radius: 12px;
  background: rgba(15, 15, 18, 0.94);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.6);
}

.debug-panel.open .debug-body {
  display: block;
}

.debug-status {
  margin-bottom: 10px;
  padding: 6px 8px;
  border-radius: 6px;
  background: #1f2a1f;
  color: #8fe094;
}

.debug-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
}

.debug-row label {
  width: 44px;
  color: #999;
}

.debug-row select {
  flex: 1;
  min-width: 0;
  padding: 4px;
  border-radius: 6px;
  border: 1px solid #444;
  background: #222;
  color: inherit;
  font: inherit;
}

.debug-row button {
  flex: 1;
  padding: 5px 4px;
  border: none;
  border-radius: 6px;
  background: #34343c;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.debug-row button:hover {
  background: #4a4a55;
}

.debug-hint {
  color: #777;
  font-size: 11px;
}

.mixer-btn {
  right: 68px;
  opacity: 0.85;
}

.mixer-btn.open {
  opacity: 1;
  background: rgba(60, 60, 70, 0.85);
}

.mixer-panel {
  position: fixed;
  top: 68px;
  right: 16px;
  width: min(300px, calc(100vw - 32px));
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(20, 20, 24, 0.92);
  color: #fff;
  font: 0.85rem var(--font);
  z-index: 5500;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
  transition: opacity 0.2s ease;
}

.mixer-panel[hidden] {
  display: none;
}

/* Everything is muted from the main button: the rows still work, but read as inactive. */
.mixer-panel.all-muted .mixer-row {
  opacity: 0.45;
}

.mixer-row {
  display: grid;
  grid-template-columns: 28px 1fr;
  grid-template-areas: "mute label" "mute slider";
  align-items: center;
  column-gap: 10px;
  row-gap: 4px;
}

.mixer-row + .mixer-row {
  margin-top: 12px;
}

.mixer-row.muted .mixer-slider,
.mixer-row.muted .mixer-label {
  opacity: 0.5;
}

.mixer-mute {
  grid-area: mute;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mixer-mute:hover {
  background: rgba(255, 255, 255, 0.2);
}

.mixer-mute[aria-pressed="true"] {
  color: #ff8a80;
}

.mixer-label {
  grid-area: label;
}

.mixer-percent {
  grid-area: label;
  justify-self: end;
  color: #aaa;
  font-variant-numeric: tabular-nums;
}

.mixer-slider {
  grid-area: slider;
  width: 100%;
  height: 20px;
  margin: 0;
  background: transparent;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.mixer-slider::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(to right, #8fe094 var(--fill, 100%), rgba(255, 255, 255, 0.2) var(--fill, 100%));
}

.mixer-slider::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.2);
}

.mixer-slider::-moz-range-progress {
  height: 6px;
  border-radius: 3px;
  background: #8fe094;
}

.mixer-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin-top: -5px;
  border-radius: 50%;
  border: none;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

.mixer-slider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border: none;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

/* ---- scene backgrounds -------------------------------------------- *
 * One per scene, drawn in CSS (gradients and a couple of shapes) so
 * there's nothing to load. They sit on the scene's root, so they fade
 * out with it. Pseudo-elements are the ground/floor, under every sprite
 * (sprites are z-index 1 and up). Kinetic and caption text sit above.   */

.scene-signal,
.scene-cabin,
.scene-bears,
.scene-egg,
.scene-outlier {
  overflow: hidden;
}

.scene-signal::before,
.scene-cabin::before,
.scene-cabin::after,
.scene-bears::before,
.scene-outlier::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  z-index: 0;
  pointer-events: none;
}

/* 1. The signal: a deep-blue night with the beacon's glow and a few stars. */
.scene-signal {
  background:
    radial-gradient(1.6px 1.6px at 12% 16%, #fff 50%, transparent 52%),
    radial-gradient(1.2px 1.2px at 27% 62%, #cfe8ff 50%, transparent 52%),
    radial-gradient(1.8px 1.8px at 41% 9%, #fff 50%, transparent 52%),
    radial-gradient(1.2px 1.2px at 63% 22%, #ffe9c4 50%, transparent 52%),
    radial-gradient(1.6px 1.6px at 78% 70%, #fff 50%, transparent 52%),
    radial-gradient(1.2px 1.2px at 88% 14%, #cfe8ff 50%, transparent 52%),
    radial-gradient(1.4px 1.4px at 7% 82%, #fff 50%, transparent 52%),
    radial-gradient(1.4px 1.4px at 92% 48%, #fff 50%, transparent 52%),
    radial-gradient(ellipse 55% 60% at 50% 46%, rgba(110, 230, 180, 0.28), transparent 70%),
    linear-gradient(to bottom, #12304f, #1e4d7a 55%, #163a5f);
}

/* 2. The cabin: dusk over a forest clearing, with a moon and rolling hills. */
.scene-cabin {
  background:
    radial-gradient(circle at 84% 15%, #fff8d9 0 26px, rgba(255, 244, 190, 0.35) 28px, transparent 90px),
    radial-gradient(1.6px 1.6px at 14% 12%, #fff 50%, transparent 52%),
    radial-gradient(1.4px 1.4px at 31% 22%, #fff 50%, transparent 52%),
    radial-gradient(1.6px 1.6px at 52% 9%, #fff 50%, transparent 52%),
    radial-gradient(1.2px 1.2px at 68% 26%, #fff 50%, transparent 52%),
    linear-gradient(to bottom, #25356b 0%, #5a5190 38%, #c46f8b 62%, #f4a259 72%);
}

.scene-cabin::before {
  bottom: 0;
  height: 40%;
  background: radial-gradient(ellipse 60% 70% at 25% 100%, #3a8a52 0 55%, transparent 56%),
    radial-gradient(ellipse 70% 80% at 80% 100%, #2f7a47 0 55%, transparent 56%);
}

.scene-cabin::after {
  bottom: 0;
  height: 32%;
  background: linear-gradient(to bottom, #4c9a4f, #2d6a3c);
  border-radius: 55% 45% 0 0 / 22px 22px 0 0;
}

/* 3. Inside the cabin: dusty-teal planked walls, a warm firelit glow, a wooden floor. */
.scene-bears {
  background:
    radial-gradient(ellipse 40% 50% at 38% 52%, rgba(255, 168, 70, 0.4), transparent 70%),
    repeating-linear-gradient(to right, transparent 0 94px, rgba(0, 0, 0, 0.2) 94px 96px),
    linear-gradient(to bottom, #3b6a66, #2f5652);
}

.scene-bears::before {
  bottom: 0;
  height: 31%;
  background:
    repeating-linear-gradient(to right, transparent 0 150px, rgba(0, 0, 0, 0.22) 150px 152px),
    linear-gradient(to bottom, rgba(0, 0, 0, 0.35) 0 5px, transparent 5px),
    linear-gradient(to bottom, #9a6a3a, #6b4426);
}

/* 4. The egg: a terracotta room on the right, a tiled bathroom on the left
   (the .egg-* pieces are added by the scene) with a doorway between them. */
.scene-egg {
  background: linear-gradient(to bottom, #a4614a, #874837);
}

.egg-room-floor,
.egg-bath-wall,
.egg-bath-floor,
.egg-garden {
  position: absolute;
  z-index: 0;
  pointer-events: none;
}

/* The main room's floor, between the doorway and the garden. */
.egg-room-floor {
  left: 55%;
  right: 13%;
  bottom: 0;
  height: 36%;
  background:
    linear-gradient(to bottom, rgba(0, 0, 0, 0.35) 0 4px, transparent 4px),
    repeating-linear-gradient(to right, transparent 0 130px, rgba(0, 0, 0, 0.25) 130px 132px),
    linear-gradient(#7a5438, #4d3322);
}

.egg-bath-wall {
  left: 0;
  width: 55%;
  top: 0;
  bottom: 36%;
  background:
    repeating-linear-gradient(to right, rgba(255, 255, 255, 0.35) 0 2px, transparent 2px 56px),
    repeating-linear-gradient(to bottom, rgba(255, 255, 255, 0.35) 0 2px, transparent 2px 56px),
    linear-gradient(#69b7c4, #4c97a8);
}

.egg-bath-floor {
  left: 0;
  width: 55%;
  bottom: 0;
  height: 36%;
  background:
    linear-gradient(to bottom, rgba(0, 0, 0, 0.3) 0 4px, transparent 4px),
    conic-gradient(#3d8f98 25%, #2c6f7c 0 50%, #3d8f98 0 75%, #2c6f7c 0) 0 0 / 96px 96px;
}

/* Outside: sky, a sun, a fence and grass, past a wooden frame. */
.egg-garden {
  left: 87%;
  right: 0;
  top: 0;
  bottom: 0;
  box-sizing: border-box;
  border-left: 16px solid #3a2616;
  background:
    radial-gradient(circle at 72% 16%, #fff7c2 0 26px, rgba(255, 247, 194, 0.4) 28px, transparent 80px),
    linear-gradient(to bottom, #86cdf2 0, #c9ecfb 64%, #5fae5a 64%, #3f8444 100%);
  overflow: hidden;
}

.egg-garden::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 36%;
  height: 64px;
  background:
    linear-gradient(to bottom, transparent 16%, #eadfc4 16% 28%, transparent 28% 60%, #eadfc4 60% 72%, transparent 72%),
    repeating-linear-gradient(to right, #eadfc4 0 12px, transparent 12px 32px);
}

/* The arch between the bathroom and the main room: wide and low, sitting on
   the floor line, with the bell hanging in it. The same
   arch (.doorway) is the way out of the bears' room, where it's on the left. */
.doorway {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  width: 190px;
  height: 34%;
  transform: translateX(-50%);
  box-sizing: border-box;
  border: 14px solid #3a2616;
  border-bottom: 0;
  border-radius: 999px 999px 0 0; /* a semicircle at the top, whatever the width */
  background: linear-gradient(#0f171b, #22333b);
  box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.6);
  transition: box-shadow 1.2s ease;
}

/* The egg scene's two: the bathroom's (where the bell hangs, and the way out) and
   the kitchen's (the way we arrive). */
.egg-door-bath,
.egg-door-kitchen {
  bottom: 36%;
  width: 170px;
}

.egg-door-bath {
  left: 46.9%;
}

.egg-door-kitchen {
  left: 61.7%;
}

/* In the bears' room it's to the right of the fireplace; their floor starts 69% down. */
.bears-doorway {
  left: 46%;
  bottom: 31%;
  width: 210px;
}

/* Lit: the dark inside warms up to a glow, and spills out into the room. */
.doorway::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 999px 999px 0 0;
  background: radial-gradient(ellipse at 50% 70%, #fffbe8 0%, #ffd27a 38%, #ff9d2e 85%);
  opacity: 0;
  transition: opacity 1.2s ease;
}

.doorway.lit::after {
  opacity: 1;
}

.doorway.lit {
  box-shadow: inset 0 0 40px rgba(255, 150, 40, 0.5), 0 0 70px 24px rgba(255, 170, 60, 0.65);
}

/* 5. The outlier: a slate-blue stage, a spotlight, a lit floor. */
.scene-outlier {
  background:
    radial-gradient(ellipse 50% 70% at 50% 48%, rgba(255, 255, 255, 0.2), transparent 70%),
    linear-gradient(to bottom, #3b4a68, #2a3550);
}

.scene-outlier::before {
  bottom: 0;
  height: 30%;
  background:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.14) 0 3px, transparent 3px),
    linear-gradient(to bottom, #56648a, #3a4768);
}

/* Words must read on any of the above. */
.kinetic-text {
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.7);
}

/* engine/curtain.js: the cover that carries across a scene change. */
.curtain {
  position: fixed;
  inset: 0;
  z-index: 4900;
  pointer-events: none;
}

/* ---- bathroom fixtures (story/props/bathroom.js) ------------------- *
 * Drawn from elements; every size is a percentage of the box, so they
 * scale with the width they're spawned at.                             */

.sprite.toilet {
  aspect-ratio: 100 / 150;
  display: block;
}

.sprite.sink {
  aspect-ratio: 100 / 140;
  display: block;
}

.sprite.mirror {
  aspect-ratio: 80 / 100;
  display: block;
  box-sizing: border-box;
  background: linear-gradient(135deg, #dff3ff, #8fc7e3 55%, #dff3ff);
  border: 5px solid #c8a96a;
  border-radius: 50% 50% 8px 8px / 32% 32% 8px 8px;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
}

.bath-part {
  position: absolute;
  box-sizing: border-box;
}

.toilet-tank {
  left: 20%;
  top: 0;
  width: 60%;
  height: 26%;
  background: linear-gradient(#fff, #e1e7ea);
  border: 2px solid #b4bec3;
  border-radius: 8px 8px 4px 4px;
}

.toilet-handle {
  left: 64%;
  top: 6%;
  width: 14%;
  height: 4%;
  background: linear-gradient(#e8eef0, #9aa5aa);
  border-radius: 3px;
}

.toilet-pedestal {
  left: 26%;
  top: 62%;
  width: 48%;
  height: 38%;
  background: linear-gradient(to right, #eef2f4, #cdd5d9);
  border: 2px solid #b4bec3;
  border-radius: 4px 4px 10px 10px;
}

.toilet-bowl {
  left: 4%;
  top: 28%;
  width: 92%;
  height: 38%;
  background: linear-gradient(#fff, #dde4e7);
  border: 2px solid #b4bec3;
  border-radius: 50% 50% 46% 46% / 40% 40% 60% 60%;
}

.toilet-water {
  left: 14%;
  top: 16%;
  width: 72%;
  height: 50%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, #a9e0ff, #4aa7dd);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.25);
}

/* The flush: the water spins away down the bowl and refills, over the
   length of the recording (FLUSH_MS in bathroom.js), with a little shudder. */
.toilet.flushing {
  animation: toilet-shudder 0.45s ease-in-out 2;
}

.toilet.flushing .toilet-water {
  background: conic-gradient(#c8ecff, #3e9bd6, #c8ecff, #3e9bd6, #c8ecff);
  animation: toilet-swirl 4.8s ease-in-out;
}

@keyframes toilet-swirl {
  0% { transform: scale(1) rotate(0); opacity: 1; }
  15% { transform: scale(1) rotate(200deg); }
  50% { transform: scale(0.55) rotate(1100deg); }
  62% { transform: scale(0.12) rotate(1500deg); opacity: 0.55; }
  74% { transform: scale(0.12) rotate(1560deg); opacity: 0.55; }
  100% { transform: scale(1) rotate(1800deg); opacity: 1; }
}

@keyframes toilet-shudder {
  0%, 100% { transform: translate(-50%, -50%); }
  25% { transform: translate(-50%, -50%) translateX(-3px) rotate(-0.6deg); }
  75% { transform: translate(-50%, -50%) translateX(3px) rotate(0.6deg); }
}

.sink-pedestal {
  left: 36%;
  top: 22%;
  width: 28%;
  height: 78%;
  background: linear-gradient(to right, #eef2f4, #cdd5d9);
  border: 2px solid #b4bec3;
  border-radius: 4px 4px 8px 8px;
}

.sink-basin {
  left: 0;
  top: 0;
  width: 100%;
  height: 26%;
  background: linear-gradient(#fff, #dde4e7);
  border: 2px solid #b4bec3;
  border-radius: 6px 6px 50% 50% / 6px 6px 100% 100%;
}

.sink-tap {
  left: 45%;
  top: -9%;
  width: 10%;
  height: 11%;
  background: linear-gradient(to right, #e8eef0, #9aa5aa);
  border-radius: 4px 4px 0 0;
}

.sprite.toilet.clickable:hover {
  filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.7));
}

/* A dark veil behind the words, so they read against any room. */
.kinetic-text {
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.3));
  animation: kinetic-veil 0.6s ease-out both;
}

@keyframes kinetic-veil {
  from { opacity: 0; }
}

/* The frozen end of the TV zoom, laid over the window while the next scene
   mounts underneath (see intoTheTV in 01-signal.js). */
.tv-handoff {
  overflow: hidden;
  background: #000;
}

.tv-handoff canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  image-rendering: pixelated;
}

/* The screen's own scanlines and vignette, at the size they have once zoomed. */
.tv-handoff-scanlines,
.tv-handoff-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.tv-handoff-scanlines {
  background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0.25) 0 2.5px, transparent 2.5px 7.5px);
  mix-blend-mode: multiply;
}

.tv-handoff-vignette {
  box-shadow: inset 0 0 150px 50px rgba(0, 0, 0, 0.6);
}

/* Words shown over a full-screen curtain (the black after the doorway). */
.kinetic-text.over-curtain {
  z-index: 5100;
}

/* The TV the party shrinks into at the end (story/props/tv-outro.js): centred as in scene 1,
   over the same blue night. It's left over the window as a curtain for scene 1 to dissolve. */
.tv-outro {
  display: flex;
  align-items: center;
  justify-content: center;
}

.tv-outro-backdrop {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  opacity: 0;
}

/* The party scene's backdrop, extended to fill the TV's 4:3 screen when the party shrinks into
   it (story/props/tv-outro.js sets --tv-extra, the extra px above and below). */
.scene-outlier.tv-extended {
  overflow: visible;
}

.scene-outlier.tv-extended::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(-1 * var(--tv-extra));
  height: var(--tv-extra);
  background: #3b4a68; /* the wall's top colour */
  z-index: -1; /* behind the light rays, which sweep right across it */
  pointer-events: none;
}

.scene-outlier.tv-extended::before {
  bottom: calc(-1 * var(--tv-extra));
  height: calc(30% + var(--tv-extra));
}

/* ---- story/score.js: a "+N" floating up from where points were won ---- */

.score-pop {
  position: fixed;
  z-index: 900;
  transform: translate(-50%, -50%);
  pointer-events: none;
  font-weight: 800;
  font-size: 0.95rem;
  color: #ffe680;
  text-shadow: 0 0 8px rgba(255, 200, 60, 0.8), 0 2px 0 rgba(0, 0, 0, 0.6);
  animation: score-pop 1.6s ease-out forwards;
}

@keyframes score-pop {
  0% { opacity: 0; transform: translate(-50%, -30%) scale(0.6); }
  15% { opacity: 1; transform: translate(-50%, -60%) scale(1.15); }
  100% { opacity: 0; transform: translate(-50%, -260%) scale(1); }
}

.score-hud {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 1000;
  padding: 6px 10px;
  border-radius: 8px;
  background: rgba(10, 14, 26, 0.55);
  color: #ffe680;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.score-pop--loss {
  color: #ff8a80;
  text-shadow: 0 0 8px rgba(255, 70, 70, 0.7), 0 2px 0 rgba(0, 0, 0, 0.6);
}

/* An egg found in a nest glows as it floats up off the screen (scenes/02-cabin.js). */
.glowing-egg {
  filter: drop-shadow(0 0 22px rgba(255, 240, 160, 1)) drop-shadow(0 0 40px rgba(255, 220, 100, 0.8));
}

/* The hint bird's speech (story/props/hint-bird.js): a few words, so it wraps. */
.speech--hint {
  white-space: normal;
  max-width: 300px;
  text-align: center;
  line-height: 1.35;
}

/* Hearts floating up around what was hiding in the bush (scenes/04-egg.js). */
.love-heart {
  position: absolute;
  z-index: 40;
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: love-float var(--ms, 2200ms) ease-out forwards;
}

@keyframes love-float {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.4); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(-50% + var(--drift, 0px)), calc(-50% - var(--rise, 90px))) scale(1.1); }
}

.love-speck {
  border-radius: 50%;
  background: #e53935;
  box-shadow: 0 0 6px rgba(229, 57, 53, 0.8);
}

/* The party's way on: skips the rest of the party and shrinks it into the TV. */
.party-score-button {
  position: fixed;
  left: 50%;
  bottom: 2vh;
  transform: translateX(-50%);
  z-index: 250;
  padding: 10px 22px;
  border: 0;
  border-radius: 999px;
  background: #ffea00;
  color: #1a1a1a;
  font: 800 1rem var(--font);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.45), 0 0 18px rgba(255, 234, 0, 0.6);
  animation: party-button-pulse 0.9s ease-in-out infinite alternate;
}

.party-score-button:hover {
  background: #fff176;
}

@keyframes party-button-pulse {
  from { transform: translateX(-50%) scale(1); }
  to { transform: translateX(-50%) scale(1.06); }
}

/* The party's fireworks and confetti, in one layer (story/props/party.js) so they fade together. */
.party-fx {
  position: absolute;
  inset: 0;
  z-index: 3300;
  pointer-events: none;
}

/* The word reels on the TV, in the strip beside the dial (story/props/word-reels.js). */
.tv-words {
  position: absolute;
  left: 92px;
  right: 24px;
  bottom: 9px;
  display: flex;
  gap: 8px;
}

.tv-word {
  flex: 1;
  height: 40px;
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: 8px;
  background: #1c1710;
  color: #8fe094;
  font: 700 clamp(0.75rem, 2.2vw, 1.05rem) "Courier New", monospace;
  letter-spacing: 0.04em;
  text-shadow: 0 0 6px rgba(80, 255, 100, 0.6);
  box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.8), 0 1px 0 rgba(255, 255, 255, 0.12);
  cursor: pointer;
}

.tv-word:hover:not(:disabled) {
  background: #2a2318;
}

.tv-word:disabled {
  cursor: default;
}

.tv-word-reel {
  display: block;
  will-change: transform;
}

.tv-word-face {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 40px;
}

.tv-words.solved .tv-word {
  color: #d8ffd8;
  box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.8), 0 0 14px rgba(143, 224, 148, 0.8);
  cursor: default;
}

/* Before they're played with the buttons are dark: no words showing. */
.tv-word-face {
  transition: color 0.35s, text-shadow 0.35s;
}

.tv-words.dark .tv-word-face {
  color: transparent;
  text-shadow: none;
}
