/* ==========================================================
   ACT 1: THE LAKE TRIP — STYLES & INTERACTIVE HUD
   Callie and Tay: A Story About Heat Stroke in Dogs
   ========================================================== */

.act1-container {
  position: relative;
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 30%, #FFFDFB 0%, var(--palette-peach-light) 65%, var(--palette-peach) 100%);
  padding: 1rem;
  box-sizing: border-box;
  overflow: hidden;
}

/* Main 16:9 Cinematic Viewport Card */
.act1-viewport-card {
  position: relative;
  width: min(94vw, 1300px);
  aspect-ratio: 16 / 9;
  max-height: 84vh;
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: var(--elev-card);
  border: 4px solid var(--palette-warm-white);
  background: #1E293B;
  user-select: none;
  transition: transform var(--dur-slow) var(--ease-out-expo), filter var(--dur-slow) var(--ease-out-expo);
}

/* Low Camera POV Perspective Effect */
.act1-viewport-card.low-cam {
  transform: perspective(1000px) rotateX(1deg);
}

/* POV Rise Animation (Beat 1H) */
.act1-viewport-card.pov-raised {
  /* No camera move here any more: the author found the tilt/lift/zoom at the end of the act
     unnecessary. Same transform as .low-cam so nothing shifts when the class swaps. */
  transform: perspective(1000px) rotateX(1deg);
}

/* Background Lake Scene Image */
.act1-scene-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
  filter: saturate(calc(100% - var(--act1-saturation-drop, 0%)));
  transition: filter 1.8s ease, transform 1.2s ease;
}


/* Overlays Container */
.act1-overlay-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 10;
}

/* ==========================================================
   PERSISTENT HUD (TOP BAR)
   ========================================================== */

.act1-hud-bar {
  position: absolute;
  top: 1.25rem;
  left: 1.5rem;
  right: 1.5rem;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: none;
  z-index: 40;
}

.act1-hud-group {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  pointer-events: auto;
}

.act1-hud-pill {
  background: var(--surface-hud);
  backdrop-filter: blur(10px);
  color: var(--text-inverse);
  border: 1.5px solid var(--hairline-light);
  font-family: var(--font-family-display);
  font-size: 0.88rem;
  font-weight: 700;
  height: 38px;
  padding: 0 1rem;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  box-shadow: var(--elev-3);
  box-sizing: border-box;
}

.act1-hud-pill.clock-pill {
  background: var(--surface-clock);
  border-color: var(--color-amber-tint);
  color: #FFEDD5;
  letter-spacing: 0.02em;
}

.act1-hud-pill.leads-pill {
  background: rgba(124, 45, 18, 0.9);
  border-color: #FB923C;
  color: var(--text-inverse);
  transition: background-color var(--dur-base) ease, border-color var(--dur-base) ease;
}

.act1-hud-pill.leads-pill.all-done {
  /* WCAG AA: at 0.92 alpha over a bright scene this composited to 4.34:1.
     Opaque --color-success is 5.02:1. */
  background: var(--color-success);
  border-color: var(--color-success-pale);
}

/* Prompt shown once all leads are worked, pointing at the canopy as the break trigger */
.act1-hud-pill.canopy-prompt-pill {
  /* WCAG AA: opaque for the same reason as .all-done above. */
  background: var(--color-success);
  border-color: var(--color-success-pale);
  color: var(--text-inverse);
  animation: pulsePill 2.4s infinite;
}

.act1-hud-pill.hints-dropped-pill {
  background: rgba(185, 28, 28, 0.95);
  border-color: var(--color-danger-pale);
  color: var(--text-inverse);
  font-weight: 800;
  letter-spacing: 0.04em;
  animation: pulsePill 2s infinite;
}

.hud-label-short {
  display: none;
}

.hud-label-full {
  display: inline;
}

@keyframes pulsePill {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.04); }
}

/* ==========================================================
   TAY'S BODY TEMPERATURE GAUGE
   The only HUD element that reports on HER rather than on the scene, so it is
   deliberately built differently from the neutral pills: taller, its own colour
   ramp, and it heats up visibly as the afternoon runs on.
   ========================================================== */

.act1-temp-gauge {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  height: 46px;
  padding: 0 0.9rem 0 0.75rem;
  border-radius: var(--radius-sm);
  box-sizing: border-box;
  background: linear-gradient(160deg, rgba(12, 20, 34, 0.96), rgba(24, 16, 14, 0.96));
  border: 2px solid var(--temp-accent, var(--temp-normal));
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35), 0 0 18px -4px var(--temp-glow, transparent);
  overflow: hidden;
  transition: border-color 0.6s ease, box-shadow 0.6s ease;
}


.temp-gauge-readout {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
}

.temp-gauge-value {
  font-family: var(--font-family-display);
  font-size: 1.05rem;
  font-weight: 900;
  letter-spacing: 0.01em;
  color: var(--temp-accent, var(--temp-normal));
  transition: color 0.6s ease;
}

.temp-gauge-label {
  font-family: var(--font-family-display);
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
  white-space: nowrap;
}

/* Thermometer fill along the bottom edge */
.temp-gauge-track {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 4px;
  background: rgba(255, 255, 255, 0.14);
}

.temp-gauge-fill {
  display: block;
  height: 100%;
  width: var(--temp-fill, 0%);
  background: linear-gradient(90deg, var(--temp-normal) 0%, var(--temp-elevated) 42%, var(--temp-danger) 68%, var(--temp-critical) 100%);
  background-size: 260px 100%;
  transition: width 1.2s var(--ease-out-expo);
}

/* Colour ramp by clinical stage */
.act1-temp-gauge.stage-normal   { --temp-accent: var(--temp-normal);   --temp-glow: rgba(52, 211, 153, 0.35); }
.act1-temp-gauge.stage-elevated { --temp-accent: var(--temp-elevated); --temp-glow: rgba(252, 211, 77, 0.45); }
.act1-temp-gauge.stage-danger   { --temp-accent: var(--temp-danger);   --temp-glow: rgba(249, 115, 22, 0.6); }

.act1-temp-gauge.stage-critical {
  --temp-accent: var(--temp-critical);
  --temp-glow: rgba(239, 68, 68, 0.75);
  animation: tempCritical 1.5s infinite ease-in-out;
}

.act1-temp-gauge.stage-critical .temp-gauge-label {
  color: var(--color-danger-pale);
}

@keyframes tempCritical {
  0%, 100% { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35), 0 0 14px -4px rgba(239, 68, 68, 0.6); }
  50% { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35), 0 0 26px 2px rgba(239, 68, 68, 0.9); }
}

@media (prefers-reduced-motion: reduce) {
  .act1-temp-gauge.stage-critical { animation: none; }
}

/* ==========================================================
   IN-SCENE INTERACTIVE OBJECTS — no floating pins/badges.
   Each lead is the drawn object itself: it glows to invite the click, lifts on
   hover/focus, reveals its name in a small tooltip, and shows a checkmark once
   visited. Positions are chosen so no two lead hit-areas overlap.
   ========================================================== */

.act1-interactable {
  position: absolute;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  pointer-events: auto;
}

.act1-interactable:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-on-dark);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.interactable-img,
.canopy-shade-layer,
.canopy-frame-layer {
  width: 100%;
  height: 100%;
  /* contain, not cover: these are objects with real silhouettes, and cropping one
     would cut a leg or a wheel off rather than just trimming background. */
  object-fit: contain;
  display: block;
}

.interactable-art {
  display: block;
  width: 100%;
  height: 100%;
  transition: transform var(--dur-fast) var(--ease-pop);
}

.interactable-art svg {
  width: 100%;
  height: 100%;
  display: block;
}

.act1-interactable:hover .interactable-art,
.act1-interactable:focus-visible .interactable-art {
  transform: scale(1.04);
}


.prop-ground-shadow {
  position: absolute;
  background: radial-gradient(ellipse at center, rgba(24, 36, 24, 0.45) 0%, rgba(24, 36, 24, 0.2) 60%, transparent 100%);
  border-radius: 50%;
  pointer-events: none;
  z-index: -1;
}

.interactable-cooler .prop-ground-shadow {
  bottom: -2%;
  left: -8%;
  width: 95%;
  height: 20%;
}

.interactable-dock .prop-ground-shadow {
  bottom: 0%;
  left: -4%;
  width: 90%;
  height: 25%;
}

.interactable-bowl .prop-ground-shadow {
  bottom: -6%;
  left: -12%;
  width: 110%;
  height: 35%;
}

.prop-affordance-ring {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 80%;
  aspect-ratio: 1 / 0.3;
  border: 2px solid var(--color-food-glow);
  border-radius: 50%;
  opacity: 0.8;
  animation: propRingPulse 2.6s infinite ease-in-out;
  pointer-events: none;
}

.interactable-bowl .prop-affordance-ring {
  width: 140%;
  bottom: -15%;
}

.interactable-cooler .prop-affordance-ring {
  width: 100%;
}

/* A long, thin dock: ringing the whole walkway made an oval floating on the water. The ring
   sits around the near-end landing instead — on the sand, where Tay steps on. */
.interactable-dock .prop-affordance-ring {
  width: 30%;
  left: 84%;
  bottom: 9%;
}

/* The lake gets the same amber ring as the other leads, lying flat on the water around the
   ripple point, so it reads as clickable in the same language. The art box clips (the
   ripples), so this ring is sized inside it. */
.interactable-lake .prop-affordance-ring.lake-ring {
  bottom: 12%;
  width: 46%;
  aspect-ratio: 1 / 0.3;
  border-width: 2.5px;
}

@keyframes propRingPulse {
  0%, 100% { transform: translateX(-50%) scale(0.95); opacity: 0.4; }
  50% { transform: translateX(-50%) scale(1.05); opacity: 0.8; }
}

.act1-interactable:hover .prop-affordance-ring,
.act1-interactable:focus-visible .prop-affordance-ring {
  animation-play-state: paused;
  opacity: 1;
}

.act1-interactable.visited .prop-affordance-ring {
  animation: none;
  opacity: 0; /* Ring goes quiet when visited */
}

@media (prefers-reduced-motion: reduce) {
  .prop-affordance-ring {
    animation: none;
    opacity: 0.6;
  }
}

/* Name tooltip — replaces the old always-on floating label pill; only shows on hover/focus */
.interactable-tooltip {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translate(-50%, 6px);
  margin-bottom: 8px;
  background: var(--surface-tooltip);
  backdrop-filter: blur(6px);
  color: var(--text-inverse);
  font-family: var(--font-family-display);
  font-size: 0.78rem;
  font-weight: 700;
  padding: 0.3rem 0.75rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(255, 255, 255, 0.2);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 20;
}

.act1-interactable:hover .interactable-tooltip,
.act1-interactable:focus-visible .interactable-tooltip {
  opacity: 1;
  transform: translate(-50%, 0);
}

.interactable-check {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 20px;
  height: 20px;
  /* WCAG AA: white glyph on #10B981 was 2.54:1. --color-success-strong is
     5.48:1, and still clears 3:1 against its own white ring. */
  background: var(--color-success-strong);
  color: var(--text-inverse);
  border: 2px solid var(--palette-warm-white);
  border-radius: 50%;
  font-size: 0.68rem;
  font-weight: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
  z-index: 21;
}

/* The scene layer is a full-stage overlay. It must be explicitly positioned: as a static
   block it sat AFTER the full-height background image in normal flow, i.e. below the
   stage, so any child that lost its absolute positioning got laid out down there and
   clipped away by the card's overflow:hidden. */
.act1-scene-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Its own stacking context. The props sort by ground line with z-indexes up to ~90;
     without this they competed with the speech layer (30) and the HUD/nav bars (40), and
     drew straight over the case-file modal and the HUD buttons. */
  z-index: 10;
  isolation: isolate;
}

/* Pop-Up Shade Canopy. Scenery during the hunt; the break trigger once all four leads
   are worked. Its cast shadow is a separate layer so the sun can move it independently. */
.lake-scene-canopy {
  position: absolute;
  top: 27%;
  left: 60%;
  width: 35%;
  height: 53.5%;
  pointer-events: none;
  z-index: 11;
  filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.25));
}

.canopy-frame-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* The shade is a separate 900x400 plate (docs/act1-art-prompts.md §9) rather than part of
   the canopy art, because the sun moves it independently as the act runs on.

   It cannot share the frame layer's box. The canopy's own art fills that box top to bottom
   with the legs landing on its lower edge, whereas the shade is a flat quad lying on the
   ground: it has to be centred on the feet line, not on the canopy, and it is wider than
   the structure because it falls away to the left. Hence its own geometry below — anchored
   to the bottom of the canopy box and overhanging it on both sides. */
.canopy-shade-layer {
  position: absolute;
  left: -40%;
  bottom: -38%;
  width: 150%;
  height: auto;
  display: block;
  transform: translateX(var(--act1-shade-shift, 0%)) scaleY(var(--act1-shade-scale-y, 1));
  /* Anchored at the feet: as the shade shortens through the afternoon it collapses toward
     the legs, the way a real shadow does, rather than toward its own centre. */
  transform-origin: 50% 92%;
  transition: transform 2.5s ease-in-out;
}

/* Armed state: the canopy becomes clickable and invites the rest beat. */
.lake-scene-canopy.canopy-armed {
  pointer-events: auto;
  cursor: pointer;
  animation: canopyInvite 2.6s infinite ease-in-out;
}

@keyframes canopyInvite {
  0%, 100% { filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.25)) drop-shadow(0 0 0 rgba(125, 211, 252, 0)); }
  50% { filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.25)) drop-shadow(0 0 18px rgba(125, 211, 252, 0.75)); }
}

.lake-scene-canopy.canopy-armed:hover,
.lake-scene-canopy.canopy-armed:focus-visible {
  animation-play-state: paused;
  filter: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.3)) drop-shadow(0 0 24px rgba(125, 211, 252, 0.9));
}

/* The canopy is the break trigger and is keyboard-operable (role="button"
   tabindex="0"), so it needs a real ring. It previously set `outline: none`,
   which left keyboard users with only a glow — and glow alone is not a visible
   focus indicator. */
.lake-scene-canopy.canopy-armed:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-on-dark);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.canopy-tooltip {
  position: absolute;
  top: 6%;
  left: 50%;
  transform: translate(-50%, -6px);
  background: var(--surface-tooltip);
  backdrop-filter: blur(6px);
  color: var(--text-inverse);
  font-family: var(--font-family-display);
  font-size: 0.78rem;
  font-weight: 700;
  padding: 0.3rem 0.75rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  border: 1px solid var(--hairline-light);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.lake-scene-canopy.canopy-armed:hover .canopy-tooltip,
.lake-scene-canopy.canopy-armed:focus-visible .canopy-tooltip {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Beats 1D/1E push in on the canopy so the nap and shade drift read at close range. */
.act1-viewport-card.canopy-focus .act1-scene-layer,
.act1-viewport-card.canopy-focus .act1-scene-img {
  transform: scale(1.85);
  transform-origin: 77% 62%;
  transition: transform 1.6s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Tay, asleep on the grass under the canopy in lateral recumbency */
.lake-scene-tay {
  position: absolute;
  top: 64%; /* clear of the nav bar's primary button */
  left: 71%; /* head clear of the canopy's front leg in the canopy-focus camera */
  width: 16%;
  aspect-ratio: 1376 / 768;
  pointer-events: none;
  z-index: 13;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.28));
}

/* One-shot entrance, applied only on the render that first places her */
.lake-scene-tay.tay-entering {
  animation: taySettle 0.9s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes taySettle {
  from { opacity: 0; transform: translateY(-8px) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.tay-lateral-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
}

.tay-lateral-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

/* Labored breathing: abdominal and thoracic heaving anchored along the dorsal spine */
.tay-labored-abdomen {
  transform-origin: 766px 240px;
  transform-box: view-box;
  animation: tayLaboredHeave 0.72s infinite ease-in-out;
}

/* In drift beat (Beat 1E), breathing becomes faster and shallower as hyperthermia worsens */
.lake-scene-tay.tay-drifting .tay-labored-abdomen {
  animation-duration: 0.52s;
}

@keyframes tayLaboredHeave {
  0%, 100% {
    transform: scale(1, 1);
  }
  35% {
    transform: scale(1.025, 1.075) translateY(2px);
  }
  65% {
    transform: scale(1.015, 1.04) translateY(1px);
  }
}

/* ==========================================================
   TAY STANDING & TROTTING AVATAR IN THE LAKE HUB
   ========================================================== */

.act1-avatar-tay {
  position: absolute;
  width: 14%;
  aspect-ratio: 1200 / 896;
  min-width: 80px;
  pointer-events: none;
  z-index: 15;
  /* Smooth travel across screen and smooth depth perspective scaling */
  transition: top 3.2s cubic-bezier(0.35, 0, 0.25, 1),
              left 3.2s cubic-bezier(0.35, 0, 0.25, 1),
              scale 3.2s cubic-bezier(0.35, 0, 0.25, 1);
  scale: var(--tay-scale, 1);
}

/* Natural raw PNG image faces RIGHT. Anchor at nose (86.5% 73.2%) */
.act1-avatar-tay.facing-right {
  transform-origin: 86.5% 73.2%;
  translate: -86.5% -73.2%;
}

.act1-avatar-tay.facing-right .act1-avatar-tay-sprite {
  transform: none;
}

/* When moving or facing LEFT: anchor at flipped nose (13.5% 73.2%), flip instantly with zero paper-flip animation */
.act1-avatar-tay.facing-left {
  transform-origin: 13.5% 73.2%;
  translate: -13.5% -73.2%;
}

.act1-avatar-tay.facing-left .act1-avatar-tay-sprite {
  transform: scaleX(-1);
}

.act1-avatar-tay-sprite {
  position: relative;
  width: 100%;
  height: 100%;
}

/* Soft contact shadow beneath Tay's paws */
.act1-avatar-tay .tay-ground-shadow {
  position: absolute;
  bottom: 6%;
  left: 20%;
  width: 58%;
  height: 15%;
  background: radial-gradient(ellipse at center, rgba(30, 41, 30, 0.38) 0%, rgba(30, 41, 30, 0.18) 60%, transparent 100%);
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
}

.act1-avatar-tay .tay-avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.22));
  z-index: 2;
}

/* Ground sniffing walking sprite hidden while standing */
.act1-avatar-tay .tay-sniffing-img {
  display: none;
}

/* While walking: switch to sniffing pose and apply trotting motion */
.act1-avatar-tay.is-walking .tay-standing-img {
  display: none;
}

.act1-avatar-tay.is-walking .tay-sniffing-img {
  display: block;
  animation: taySniffTrot 0.36s infinite alternate ease-in-out;
}

.act1-avatar-tay.is-walking .tay-ground-shadow {
  animation: tayShadowTrot 0.36s infinite alternate ease-in-out;
}

/* Pure vertical trot bobbing without any angular rotation */
@keyframes taySniffTrot {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-3.5px);
  }
}

@keyframes tayShadowTrot {
  0% {
    transform: scale(1);
    opacity: 0.38;
  }
  100% {
    transform: scale(0.92);
    opacity: 0.26;
  }
}

/* Idle breathing pacing accelerates with exhaustion stage */
.act1-avatar-tay:not(.is-walking) .tay-standing-img {
  transform-origin: 50% 85%;
  animation: tayStandingBreathe 2.4s infinite ease-in-out;
}

.act1-avatar-tay.stage-2:not(.is-walking) .tay-standing-img {
  animation-duration: 1.8s;
}

.act1-avatar-tay.stage-3:not(.is-walking) .tay-standing-img {
  animation-duration: 1.2s;
}

.act1-avatar-tay.stage-4:not(.is-walking) .tay-standing-img {
  animation-duration: 0.75s;
}

@keyframes tayStandingBreathe {
  0%, 100% {
    transform: scale(1, 1);
  }
  50% {
    transform: scale(1.008, 1.018);
  }
}

/* 1. The Cooler ("The Vault") — out in open sun, clear of the tree's root flare and
   the fallen log. The script is explicit that it sits in full sun with no shade near it. */
.interactable-cooler {
  top: 60%;
  left: 39%;
  width: 7.4%;
  height: 13%;
}

/* 2. The Dock ("High Ground") — drawn broadside, running along the shore rather than
   out from it. The box is no longer rotated: the shoreline's -9° is built into the art's
   own long edges, which keeps the check badge and the hover lift on the same axes as
   every other interactable. Short and wide because we see the dock's length, not its
   depth — height dropped from 22% to 8.5% when it turned side-on. */
.interactable-dock {
  /* 28% wide, anchored on the near (lower-right) end where the deck meets the sand at
     ~28.2% / 61.5% of the card. The art is generated by docs/lake-prop-dock.py; if its
     near-end corner moves in the SVG, recompute these four from it. The depth sort in
     syncSceneState() uses that same 61.5% ground line. */
  top: 43.2%;
  left: 2.3%;
  width: 28%;
  height: 21.7%;
}

.interactable-dock:hover .interactable-art,
.interactable-dock:focus-visible .interactable-art {
  transform: scale(1.06) translateY(-3px);
}

/* The deck art is an inset trapezoid, so the default top-right corner badge floats well
   clear of it and reads as unrelated. Pull the tick onto the deck itself. */
.interactable-dock .interactable-check {
  /* Rides a bollard a third of the way along the deck — not the box's top-right corner (the
     box is mostly transparent sky), and not the landing, where Tay stands and would hide it. */
  top: 41%;
  right: 33%;
}

/* 3. Water Bowl ("The Water One") — open grass, clear of everything else */
.interactable-bowl {
  top: 74%;
  left: 47%;
  width: 5.5%;
  height: 5.6%;
}

/* 4. The Lake ("The Biggest Bowl") — the open water left of the dock. No drawn icon,
   so the affordance is the water's own motion: wavefronts that run parallel to the
   shoreline and travel across the water toward the beach. */
/* Open water between the dock's far end and the near shore; it sat on the far shoreline.
   Kept right of the dock's hit box (which ends at 30.3%). */
.interactable-lake {
  top: 48%;
  left: 30.5%;
  width: 20%;
  height: 8%;
}

/* The lake has no drawn object of its own. It used to advertise itself as a tinted,
   hard-bordered rectangle of water — which is a raw hit target, not art, and on a painted
   plate it read as unfinished. Act 2's pattern is the right one: the affordance is a
   discrete ring token sitting ON the art, and the art itself is left alone. The water's
   own motion (the shore-parallel ripples below) carries the rest of the invitation. */
.interactable-lake .interactable-art {
  position: relative;
  overflow: hidden;
  /* No drop-shadow or pulse: there is no object here to lift off the plate. */
  filter: none;
  animation: none;
}

/* The ring token, centred in the water patch. Mirrors .act2-hotspot so the two acts
   teach the learner the same "this is clickable" vocabulary. */
/* The lake's affordance is its own water, not a chip laid on top of it. A dark UI disc
   read as a foreign object once the scenery became rendered art — so instead, rings
   spread from a point on the surface the way a drop does. It is unmistakably animated,
   which is what says "clickable", while still being a thing water actually does. */
.lake-touch-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.lake-touch-ring {
  fill: none;
  stroke: rgba(255, 255, 255, 0.95);
  stroke-width: 2.4;
  vector-effect: non-scaling-stroke;
  transform-box: view-box;
  transform-origin: 50px 50px;
  opacity: 0;
  animation: lakeTouchRing 3.2s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
}

.ring-2 { animation-delay: 1.06s; }
.ring-3 { animation-delay: 2.12s; }

@keyframes lakeTouchRing {
  0%   { transform: scale(0.1);  opacity: 0; }
  12%  { opacity: 0.7; }
  100% { transform: scale(1);    opacity: 0; }
}

/* Hover/focus: the same gesture, just more insistent. */
.interactable-lake:hover .lake-touch-ring,
.interactable-lake:focus-visible .lake-touch-ring {
  animation-duration: 1.9s;
  stroke-width: 1.8;
}

/* Worked: the corner check badge is the status signal, so the water goes quiet. */
.interactable-lake.visited .lake-touch-ring {
  animation: none;
  opacity: 0;
}

.interactable-lake:hover .interactable-art,
.interactable-lake:focus-visible .interactable-art {
  /* Water shouldn't lift or pop the way a solid object does. */
  transform: none;
  filter: none;
}

/* The lake's tooltip is its main "this is a thing" signal, so it stays visible. */
.interactable-lake .interactable-tooltip {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Scene-wide water surface. Sits under everything else in the scene layer and takes no
   clicks — the lake's hit area is still .interactable-lake, this is only its motion.
   The clip path is the water's measured outline (see the JS comment on the layer), so
   ripples run the full width of the lake and stop exactly at both shores. */
.act1-water-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  clip-path: polygon(
    -2% 48.5%, 57.2% 48.5%,
    57.2% 50.6%, 50% 51.7%, 39.8% 54%, 29.5% 58.1%,
    19.3% 61.3%, 9% 63.3%, -2% 65.6%
  );
}

.act1-water-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

/* Each wave is the same shore-traced path, released out near the far bank and sliding
   shoreward. The layer's clip-path is what stops them at the water's edge. The lake
   narrows toward the tree, so a single travel distance reads as correct perspective:
   the wave crosses the wide near water slowly and the thin far water in a moment. */
.lake-wave {
  opacity: 0;
  animation: lakeShoreWave 5.4s linear infinite;
}

.lake-wave-2 { animation-delay: 1.35s; }
.lake-wave-3 { animation-delay: 2.7s; }
.lake-wave-4 { animation-delay: 4.05s; }

@keyframes lakeShoreWave {
  0% { transform: translateY(-15px) scaleX(0.94); opacity: 0; }
  22% { opacity: 0.32; }
  75% { opacity: 0.18; }
  100% { transform: translateY(2px) scaleX(1.04); opacity: 0; }
}

/* Hovering the lake quickens its water. The waves are no longer inside the button, so
   the relationship has to be expressed from the card down. */
.act1-viewport-card:has(.interactable-lake:hover) .lake-wave,
.act1-viewport-card:has(.interactable-lake:focus-visible) .lake-wave {
  animation-duration: 2.9s;
}

@media (prefers-reduced-motion: reduce) {
  /* Still water, but still four wavefronts: with the animation off they would all stack
     on the traced shoreline and read as one hard line drawn on the sand. Spread them
     back out across the water so the lake keeps its texture without moving. */
  .lake-wave { animation: none; opacity: 0.2; }
  .lake-wave-1 { transform: translateY(-12px) scaleX(0.95); }
  .lake-wave-2 { transform: translateY(-8px) scaleX(0.97); }
  .lake-wave-3 { transform: translateY(-4px) scaleX(0.99); }
  .lake-wave-4 { transform: translateY(0); }
}

/* ==========================================================
   BEAT 1A: TAY'S MISSION CARD (FIND REAL FOOD, NOT KIBBLE)
   ========================================================== */

.act1-mission-card {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(90%, 620px);
  max-height: min(85vh, 520px);
  overflow-y: auto;
  background: var(--surface-card-dark);
  border: 3px solid var(--color-amber);
  border-radius: var(--radius-lg);
  padding: 1.35rem 1.6rem;
  box-shadow: 
    var(--elev-modal),
    0 0 30px rgba(245, 158, 11, 0.3);
  color: var(--text-on-dark);
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  z-index: 50;
  pointer-events: auto;
}

.mission-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1.5px solid rgba(245, 158, 11, 0.35);
  padding-bottom: 0.55rem;
  margin-bottom: 0.75rem;
}

.mission-card-badge {
  background: var(--color-amber-deep);
  color: var(--color-amber-pale);
  font-family: var(--font-family-display);
  font-size: 0.75rem;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-xs);
}

.mission-card-title {
  font-family: var(--font-family-display);
  font-size: 1.35rem;
  font-weight: 800;
  color: var(--text-inverse);
  margin: 0;
}

.mission-card-body {
  font-family: var(--font-family-body);
  font-size: 0.98rem;
  line-height: 1.48;
  color: var(--text-on-dark-muted);
  margin-bottom: 0.85rem;
}

.mission-leads-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin-top: 0.75rem;
  background: rgba(255, 255, 255, 0.05);
  padding: 0.75rem;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.mission-lead-item {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-amber-tint);
}

.mission-card-footer {
  display: flex;
  justify-content: flex-end;
  border-top: 1px solid var(--hairline-light-soft);
  padding-top: 0.75rem;
  margin-top: 0.5rem;
}

/* ==========================================================
   POP-UP VIEWPORT MODAL (TAY'S POV CLOSE-UPS)
   ========================================================== */

.act1-pov-viewport-modal {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: var(--surface-scrim);
  backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 45;
  pointer-events: auto;
  padding: 1rem;
  box-sizing: border-box;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.pov-viewport-card {
  position: relative;
  width: min(94vw, 980px);
  aspect-ratio: 16 / 9;
  max-height: min(82vh, 580px);
  background: var(--surface-card-darkest);
  border: 3px solid var(--color-amber);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 
    0 24px 60px rgba(0, 0, 0, 0.85),
    0 0 30px rgba(245, 158, 11, 0.35);
  display: flex;
  flex-direction: column;
}

.pov-viewport-header {
  position: absolute;
  top: 0.85rem;
  left: 1.1rem;
  right: 1.1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  z-index: 30;
  pointer-events: none;
}

.pov-tag-badge {
  background: var(--surface-chip-dark);
  backdrop-filter: blur(8px);
  color: var(--color-amber-tint);
  border: 1px solid var(--temp-danger);
  font-family: var(--font-family-display);
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.3rem 0.8rem;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  gap: 0.4rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

.pov-close-btn {
  background: var(--surface-chip-dark);
  backdrop-filter: blur(8px);
  color: var(--text-inverse);
  border: 1px solid rgba(255, 255, 255, 0.3);
  font-family: var(--font-family-display);
  font-size: 0.85rem;
  font-weight: 700;
  padding: 0.3rem 0.85rem;
  border-radius: var(--radius-pill);
  cursor: pointer;
  pointer-events: auto;
  transition: background-color var(--dur-fast) ease,
              border-color var(--dur-fast) ease,
              transform var(--dur-fast) ease;
}

.pov-close-btn:hover,
.pov-close-btn:focus-visible {
  background: var(--color-danger);
  border-color: var(--color-danger-bright);
  transform: translateY(-1px);
}

.pov-close-btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-on-dark);
  outline-offset: var(--focus-ring-offset);
}

.pov-scene-illustration {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* An <img> now, not an <svg> with preserveAspectRatio="slice" — object-fit carries
     the same crop behaviour across the card's 16:9 at every width. */
  object-fit: cover;
  overflow: hidden;
  z-index: 10;
}

.pov-overlay-speech {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 25;
}

/* ==========================================================
   POV DIALOGUE — the whole exchange on one screen
   ========================================================== */

/* One beat at a time; the learner advances it. Tay speaks from the lower left, Callie
   from the upper right, so an exchange alternates across the frame. */
.pov-dialogue-bubble {
  top: 14%;
  left: 5%;
  max-width: min(440px, 46%);
  animation: bubblePop 0.3s var(--ease-back) both;
}

.pov-callie-bubble {
  top: 12%;
  right: 5%;
  left: auto;
  max-width: min(400px, 44%);
  animation: bubblePop 0.3s var(--ease-back) both;
}

/* Callie's words take the bubble standard (§6.3.1) from their .speech-bubble-text parent —
   never their own size or weight. A 1.02rem/700 override here made Callie visibly smaller and
   heavier than Tay and Dana in the same scene (Acts 1 and 2, incl. the symptom cards). */
.callie-dialogue {
  font: inherit;
  color: var(--palette-brown-dark);
}

/* The line where she gives up on the lead and moves on — same bubble, dimmer and
   italic so it reads as her trailing off rather than a new thought. */
.pov-dialogue-bubble.is-exit-line .tay-sub-dialogue {
  font-style: italic;
  opacity: 0.9;
}






/* ==========================================================
   TRUTH STAMP — the flat third voice.
   One metric, one sentence. It arrives after Tay has had her say, so the
   contradiction lands in a single glance instead of a card to read through.
   ========================================================== */




.truth-stamp-metric {
  flex-shrink: 0;
  background: var(--color-amber-deep);
  color: var(--color-amber-pale);
  font-family: var(--font-family-display);
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-amber-tint-2);
  white-space: nowrap;
}



@media (prefers-reduced-motion: reduce) {
  .pov-dialogue-bubble,
  .pov-callie-bubble {
    animation-duration: 0.01s;
    animation-delay: 0s;
  }
}


/* Heat haze / shimmering animation for the Dock POV */
.heat-haze-layer {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    0deg,
    rgba(255, 255, 255, 0) 0px,
    rgba(255, 200, 100, 0.08) 10px,
    rgba(255, 255, 255, 0) 20px
  );
  animation: heatHaze 2s infinite linear;
  pointer-events: none;
  mix-blend-mode: overlay;
}

@keyframes heatHaze {
  0% { transform: translateY(0) scaleY(1); opacity: 0.4; }
  50% { transform: translateY(-8px) scaleY(1.02); opacity: 0.8; }
  100% { transform: translateY(-16px) scaleY(1); opacity: 0.4; }
}

/* ==========================================================
   SPEECH BUBBLES IN ACT 1
   ========================================================== */

.act1-speech-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 30;
}

/* ==========================================================
   ACT 1 INTRO SPEECH BUBBLES & CLICK-THROUGH AFFORDANCES
   ========================================================== */

/* Cursor affordance during cold open dialogue */
#act1-card.is-cold-open-active {
  cursor: pointer;
}

#act1-card.is-cold-open-active .act1-hud-bar,
#act1-card.is-cold-open-active .act1-nav-bar,
#act1-card.is-cold-open-active .act1-mission-card {
  cursor: default;
}

/* Callie off-screen — every line she speaks from outside the frame in Act 1 (cold open,
   alarm, POV rise). Her normal bubble (§6.3: white, brown-dark ink, teal label), pinned to
   the right edge, with the tail leaving the RIGHT side toward where she is standing.
   Tail height is overridable with --stem-top, like --stem-left/--stem-right elsewhere. */
.speech-bubble.callie-bubble.act1-callie-offscreen-bubble {
  top: 20%;
  right: calc(22px + 1.5rem); /* the tail's length, plus a gutter from the card edge */
  left: auto;
  max-width: var(--bubble-max-w, 320px);
}

.speech-bubble.callie-bubble.act1-callie-offscreen-bubble::before {
  bottom: auto;
  left: auto;
  top: var(--stem-top, 46%);
  right: -22px;
  border-top: 8px solid transparent;
  border-bottom: 14px solid transparent;
  border-left: 22px solid var(--palette-brown-dark);
  border-right: none;
}

.speech-bubble.callie-bubble.act1-callie-offscreen-bubble::after {
  bottom: auto;
  left: auto;
  top: calc(var(--stem-top, 46%) + 3px);
  right: -17px;
  border-top: 6px solid transparent;
  border-bottom: 10.5px solid transparent;
  border-left: 18px solid var(--palette-warm-white);
  border-right: none;
}

/* Subtle click-through guidance at the bottom of cold open dialogue */
/* Cold Open Nudge Pill in Navigation Bar */
.act1-hud-pill.act1-nudge-pill {
  background: var(--surface-hud);
  border-color: var(--color-amber-tint);
  font-weight: 600;
  font-size: 0.82rem;
  color: var(--text-on-dark);
  letter-spacing: 0.02em;
  animation: pulsePill 2.5s infinite;
}




/* ==========================================================
   MODAL ENTRANCE ANIMATIONS
   (The inline "truth stamp" cards were removed from Act 1 — the risk factors are
   now revealed only in the Beat 1G Case File. Their styles went with them; the
   shared entrance keyframes below are still used by the remaining cards.)
   ========================================================== */

@keyframes stampSlam {
  0% {
    opacity: 0;
    transform: translateX(-50%) translateY(20px) scale(0.95);
  }
  100% {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1);
  }
}

/* For elements centered on BOTH axes (top:50%; left:50%; transform: translate(-50%,-50%)).
   stampSlam's translateX(-50%)-only end frame silently drops the vertical centering once
   the animation's forwards fill-mode locks it in — this variant preserves both. */
@keyframes stampSlamCentered {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.95);
  }
  100% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
}

/* Still used by the Case File header's timestamp badge */
.stamp-temp-badge {
  background: var(--color-amber-deep);
  color: var(--color-amber-pale);
  font-family: var(--font-family-display);
  font-size: 0.85rem;
  font-weight: 800;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-amber-tint-2);
}

/* ==========================================================
   BOTTOM NAVIGATION & ACTION CONTROLS
   ========================================================== */

.act1-nav-bar {
  position: absolute;
  bottom: 1.25rem;
  left: 1.5rem;
  right: 1.5rem;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: none;
  z-index: 40;
}

.act1-hud-btn {
  background: var(--surface-hud-btn);
  backdrop-filter: blur(8px);
  color: var(--text-inverse);
  border: 1.5px solid var(--hairline-light);
  font-family: var(--font-family-display);
  font-size: 0.92rem;
  font-weight: 700;
  height: 40px;
  padding: 0 1.2rem;
  border-radius: var(--radius-pill);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  box-sizing: border-box;
  transition: background-color var(--dur-fast) var(--ease-out-expo),
              border-color var(--dur-fast) var(--ease-out-expo),
              opacity var(--dur-fast) var(--ease-out-expo),
              transform var(--dur-fast) var(--ease-out-expo);
  box-shadow: var(--elev-2);
  pointer-events: auto;
}

.act1-hud-btn:hover:not(:disabled) {
  background: var(--surface-hud-btn-hover);
  transform: translateY(-2px);
  border-color: var(--hairline-light-strong);
}

.act1-hud-btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-on-dark);
  outline-offset: var(--focus-ring-offset);
  background: var(--surface-hud-btn-hover);
  border-color: var(--hairline-light-strong);
}

.act1-hud-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.act1-hud-btn.btn-action-primary {
  background: var(--color-heat);
  color: var(--text-inverse);
  border: 2px solid var(--color-amber-tint);
  font-weight: 800;
  box-shadow: 0 6px 20px rgba(194, 65, 12, 0.4);
}

.act1-hud-btn.btn-action-primary:hover:not(:disabled) {
  background: var(--color-heat-hover);
  border-color: var(--palette-warm-white);
  transform: translateY(-2px);
}

.act1-hud-btn.btn-action-primary:focus-visible {
  background: var(--color-heat-hover);
  border-color: var(--palette-warm-white);
}

.act1-hud-btn.btn-action-primary.pulse-btn {
  animation: pulseStartBtn 1.8s infinite;
}

/* ==========================================================
   TERMINAL BEATS OVERLAYS
   ========================================================== */

/* Visual Escalation System — the sun creeps onto Tay's spot as the clock advances.
   Opacity is driven continuously by --act1-drift-opacity (see getEscalation() in JS),
   not just during the dedicated Shade Drift beat. */
.shade-drift-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle at 62% 68%, rgba(255, 240, 190, 0.4) 0%, rgba(255, 205, 120, 0.18) 45%, transparent 78%);
  mix-blend-mode: overlay;
  opacity: var(--act1-drift-opacity, 0);
  transition: opacity 2.5s ease-in-out;
  z-index: 15;
}

.shade-drift-timelapse-banner {
  position: absolute;
  top: 4.5rem;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(15, 23, 42, 0.9);
  backdrop-filter: blur(8px);
  color: var(--color-amber-tint);
  border: 1.5px solid var(--temp-danger);
  padding: 0.65rem 1.4rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-family-display);
  font-size: 0.95rem;
  font-weight: 700;
  box-shadow: var(--elev-4);
  z-index: 35;
  animation: pulsePill 2s infinite;
}

/* Beat 1F: Foil Crinkle Effect / Alarm */
.foil-crinkle-banner {
  position: absolute;
  bottom: 5.5rem;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(10px);
  border: 2px solid var(--color-heat-bright);
  color: var(--palette-brown-dark);
  padding: 0.85rem 1.4rem;
  border-radius: var(--radius-md);
  font-family: var(--font-family-display);
  font-size: 1rem;
  font-weight: 700;
  text-align: center;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
  max-width: 500px;
  z-index: 35;
}

.foil-crinkle-sound {
  display: block;
  font-size: 0.82rem;
  color: var(--color-heat);
  font-style: italic;
  margin-top: 0.25rem;
}

/* ==========================================================
   BEAT 1G: CASE FILE CARD MODAL
   ========================================================== */

.case-file-card {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(92%, 840px);
  max-height: 94%;
  background: var(--surface-card-dark);
  border: 2.5px solid var(--color-info);
  border-radius: var(--radius-lg);
  padding: clamp(0.75rem, 1.6vw, 1.25rem) clamp(1rem, 2.2vw, 1.6rem);
  box-shadow: 
    0 20px 50px rgba(0, 0, 0, 0.8),
    0 0 25px rgba(56, 189, 248, 0.25);
  color: var(--text-on-dark);
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  overflow-y: auto;
  pointer-events: auto;
  z-index: 50;
}

/* Custom sleek scrollbar for case-file card */
.case-file-card::-webkit-scrollbar {
  width: 6px;
}
.case-file-card::-webkit-scrollbar-track {
  background: rgba(15, 23, 42, 0.6);
  border-radius: var(--radius-pill);
}
.case-file-card::-webkit-scrollbar-thumb {
  background: var(--color-info-mid);
  border-radius: var(--radius-pill);
}

.case-file-header {
  border-bottom: 1.5px solid rgba(56, 189, 248, 0.35);
  padding-bottom: 0.45rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  flex-shrink: 0;
}

.case-file-title-group {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-wrap: wrap;
}

.case-file-badge {
  /* WCAG AA: white on #0284C7 was 4.10:1. --color-info-strong is 5.93:1.
     #0284C7 is retained above for the non-text rules and scrollbar. */
  background: var(--color-info-strong);
  color: var(--text-inverse);
  font-family: var(--font-family-display);
  font-size: 0.72rem;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}

.case-file-title {
  font-family: var(--font-family-display);
  font-size: clamp(1rem, 1.45vw, 1.25rem);
  font-weight: 800;
  color: var(--text-inverse);
  margin: 0;
  line-height: 1.2;
}

.case-file-table {
  width: 100%;
  border-collapse: collapse;
  margin: clamp(0.4rem, 1vw, 0.75rem) 0;
  flex-grow: 1;
}

.case-file-table th {
  text-align: left;
  font-family: var(--font-family-display);
  font-size: clamp(0.72rem, 0.95vw, 0.8rem);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-on-dark-dim);
  padding: 0.35rem 0.6rem;
  border-bottom: 1px solid var(--hairline-light-soft);
}

.case-file-table td {
  padding: clamp(0.35rem, 0.8vw, 0.55rem) 0.6rem;
  font-family: var(--font-family-body);
  font-size: clamp(0.82rem, 1.1vw, 0.94rem);
  line-height: 1.35;
  color: var(--text-on-dark-muted);
  border-bottom: 1px solid var(--hairline-light-faint);
}

.case-file-table td.tay-term {
  font-family: var(--font-family-display);
  font-weight: 700;
  color: var(--color-amber-tint);
  white-space: nowrap;
}

/* Middle column: the plain object behind Tay's name. A row header (th scope="row") for screen
   readers, styled as body-weight text rather than the uppercase column-header treatment. */
.case-file-table th.object-term {
  font-family: var(--font-family-display);
  font-size: clamp(0.82rem, 1.1vw, 0.94rem);
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-on-dark);
  white-space: nowrap;
  padding: clamp(0.35rem, 0.8vw, 0.55rem) 0.6rem;
  border-bottom: 1px solid var(--hairline-light-faint);
}

/* Lake Row highlighted / set apart per script */
.case-file-table tr.lake-row {
  background: rgba(14, 165, 233, 0.12);
  border-top: 1.5px solid var(--color-info-mid);
  border-bottom: 1.5px solid var(--color-info-mid);
}

.case-file-table tr.lake-row td.tay-term {
  color: var(--color-info);
}

.case-file-table tr.lake-row td.reality-term {
  color: var(--color-info-pale);
  font-weight: 600;
}

.case-file-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  border-top: 1px solid var(--hairline-light-soft);
  padding-top: 0.55rem;
  margin-top: 0.25rem;
  flex-shrink: 0;
}

.case-file-footer .act1-hud-btn {
  height: 36px;
  font-size: 0.88rem;
  padding: 0 1.1rem;
}

/* ==========================================================
   ACT 2 PLACEHOLDER MODAL
   ========================================================== */

.act2-placeholder-modal {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(90%, 540px);
  background: var(--palette-warm-white);
  border-radius: var(--radius-lg);
  padding: 2rem 2.2rem;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
  border: 3px solid var(--palette-teal-dark);
  text-align: center;
  z-index: 60;
}

.act2-placeholder-title {
  font-family: var(--font-family-display);
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--palette-brown-dark);
  margin-bottom: 0.75rem;
}

.act2-placeholder-text {
  font-family: var(--font-family-body);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--palette-text-muted);
  margin-bottom: 1.5rem;
}

/* ==========================================================
   REDUCED MOTION — FULL COVERAGE
   Policy (docs/design-language.md §5.3):
     1. Every looping/decorative animation is switched off.
     2. Entrances are SHORTENED, never removed — an entrance whose final
        keyframe carries the element's centring transform (stampSlam,
        stampSlamCentered) would drop that centring and jump if
        the animation were set to `none`.
     3. Continuous escalation transitions (shade drift, saturation drain)
        become instant rather than disappearing — they carry meaning.
   ========================================================== */

@media (prefers-reduced-motion: reduce) {
  /* 1. Looping invitations and alarms — off. The affordance survives via the
     static glow, the tooltip, and the checkmark. */
  .act1-hud-pill.canopy-prompt-pill,
  .act1-hud-pill.hints-dropped-pill,
  .shade-drift-timelapse-banner,
  .act1-hud-btn.btn-action-primary.pulse-btn,
  .lake-scene-canopy.canopy-armed,
  .heat-haze-layer,
  .tay-labored-abdomen,
  .act1-avatar-tay .tay-standing-img,
  .act1-avatar-tay.is-walking .tay-sniffing-img,
  .act1-avatar-tay.is-walking .tay-ground-shadow {
    animation: none;
  }

  .act1-avatar-tay {
    transition-duration: 0.01s;
  }

  /* Interactables keep their resting glow but stop pulsing. */
  .interactable-art { animation: none; }

  /* The rings are the affordance, so they stay — they just hold still. Frozen at three
     different radii rather than all at the centre, so the water still reads as disturbed. */
  .lake-touch-ring { animation: none; opacity: 0.4; }
  .lake-touch-ring.ring-1 { transform: scale(0.35); }
  .lake-touch-ring.ring-2 { transform: scale(0.62); }
  .lake-touch-ring.ring-3 { transform: scale(0.9); }

  /* 2. Entrances — shortened, not removed. */
  .act1-mission-card,
  .case-file-card,
  .act2-placeholder-modal,
  .foil-crinkle-banner,
  .act1-pov-viewport-modal,
  .lake-scene-tay.tay-entering {
    animation-duration: 0.01s;
    animation-delay: 0s;
  }

  /* 3. Escalation transitions stay, but resolve immediately. */
  .act1-scene-img,
  .canopy-shade-layer,
  .shade-drift-overlay,
  .temp-gauge-fill,
  .act1-viewport-card,
  .act1-viewport-card.canopy-focus .act1-scene-layer,
  .act1-viewport-card.canopy-focus .act1-scene-img {
    transition-duration: 0.01s;
  }

  /* 4. Hover/focus lifts flatten; the glow still changes so the affordance
     remains, and focus rings are unaffected. */
  .act1-interactable:hover .interactable-art,
  .act1-interactable:focus-visible .interactable-art,
  .interactable-dock:hover .interactable-art,
  .interactable-dock:focus-visible .interactable-art,
  .act1-hud-btn:hover:not(:disabled),
  .act1-hud-btn.btn-action-primary:hover:not(:disabled),
  .pov-close-btn:hover,
  .pov-close-btn:focus-visible {
    transform: none;
  }

  .interactable-tooltip,
  .canopy-tooltip {
    transition-duration: 0.01s;
  }
}

/* ==========================================================
   RESPONSIVE ADJUSTMENTS
   ========================================================== */

@media (max-width: 768px) {
  .act1-container {
    padding: 0.5rem;
    height: 100vh;
    justify-content: center;
  }

  .act1-viewport-card {
    width: 100%;
    aspect-ratio: 4 / 3;
    max-height: 86vh;
    border-radius: var(--radius-lg);
  }

  /* Top HUD Bar */
  .act1-hud-bar {
    top: 0.6rem;
    left: 0.6rem;
    right: 0.6rem;
    height: auto;
    min-height: 36px;
    gap: 0.35rem;
  }

  .interactable-lake .interactable-tooltip {
    left: 50%;
    transform: translate(-50%, 6px);
  }
  .act1-interactable.interactable-lake:hover .interactable-tooltip,
  .act1-interactable.interactable-lake:focus-visible .interactable-tooltip {
    transform: translate(0, 0);
  }

  .act1-hud-group {
    gap: 0.35rem;
  }

  .act1-hud-btn {
    height: 34px;
    min-width: 36px;
    padding: 0 0.65rem;
    font-size: 0.78rem;
    touch-action: manipulation;
  }

  .act1-hud-pill {
    height: 32px;
    padding: 0 0.55rem;
    font-size: 0.76rem;
    gap: 0.3rem;
  }

  .hud-label-full {
    display: none;
  }

  .hud-label-short {
    display: inline;
  }

  .act1-temp-gauge {
    height: 34px;
    padding: 0 0.55rem;
    gap: 0.35rem;
  }


  .temp-gauge-value {
    font-size: 0.88rem;
  }

  .temp-gauge-label {
    font-size: 0.52rem;
  }

  /* Interactables & Touch Hit Areas (WCAG touch targets >= 44x44px) */
  .act1-interactable {
    touch-action: manipulation;
  }

  .act1-interactable::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    min-width: 44px;
    min-height: 44px;
    width: 120%;
    height: 120%;
    pointer-events: auto;
  }

  .interactable-check {
    width: 18px;
    height: 18px;
    font-size: 0.62rem;
    top: -4px;
    right: -4px;
  }

  /* Speech bubbles in Lake scene */
  .speech-bubble {
    --bubble-max-w: min(90vw, 340px);
    max-width: min(90vw, 340px) !important;
    padding: 0.85rem 1.1rem;
    border-radius: var(--radius-lg);
  }

  .speech-bubble-text {
    font-size: clamp(0.92rem, 3.2vw, 1.05rem);
  }

  /* Bottom nav bar */
  .act1-nav-bar {
    bottom: max(0.6rem, env(safe-area-inset-bottom, 0.6rem));
    left: 0.6rem;
    right: 0.6rem;
    height: 44px;
  }

  /* POV Viewport Modal */
  .act1-pov-viewport-modal {
    padding: 0.4rem;
  }

  .pov-viewport-card {
    width: 96%;
    max-height: 94%;
    padding: 0.75rem 0.9rem;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .pov-viewport-header {
    margin-bottom: 0.4rem;
    padding-bottom: 0.35rem;
  }

  .pov-tag-badge {
    font-size: 0.72rem;
    padding: 0.25rem 0.65rem;
  }

  .pov-close-btn {
    height: 32px;
    padding: 0 0.7rem;
    font-size: 0.76rem;
  }

  .pov-scene-illustration {
    height: 36%;
    max-height: 160px;
    flex-shrink: 0;
  }

  .pov-overlay-speech {
    flex: 1;
    overflow-y: auto;
    padding: 0.4rem 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
  }

  .pov-dialogue-bubble,
  .pov-callie-bubble {
    position: relative;
    top: auto;
    left: auto;
    max-width: 100% !important;
    padding: 0.75rem 1rem;
  }


  .truth-stamp-metric {
    font-size: 0.72rem;
  }


  /* Mission Card Modal */
  .act1-mission-card {
    width: 96%;
    max-height: 92%;
    padding: 0.9rem 1rem;
    overflow-y: auto;
  }

  .mission-card-title {
    font-size: 1.18rem;
    margin-bottom: 0.4rem;
  }

  .mission-card-body {
    font-size: 0.88rem;
  }

  .mission-leads-grid {
    grid-template-columns: 1fr 1fr;
    gap: 0.4rem;
  }

  .mission-lead-item {
    padding: 0.45rem 0.6rem;
    font-size: 0.8rem;
  }

  /* Case File Card Modal */
  .case-file-card {
    width: 96%;
    max-height: 94%;
    padding: 0.75rem 0.85rem;
    overflow-y: auto;
  }

  .case-file-title {
    font-size: 1.05rem;
  }

  .case-file-badge {
    font-size: 0.68rem;
  }

  .case-file-table th {
    font-size: 0.68rem;
    padding: 0.3rem 0.45rem;
  }

  .case-file-table td {
    font-size: 0.76rem;
    padding: 0.35rem 0.45rem;
  }
}

/* Extra small devices (<= 480px) */
@media (max-width: 480px) {
  .act1-container {
    padding: 0.25rem;
  }

  .act1-hud-bar {
    top: 0.4rem;
    left: 0.4rem;
    right: 0.4rem;
  }

  .act1-hud-btn {
    height: 32px;
    padding: 0 0.5rem;
    font-size: 0.74rem;
  }

  .act1-hud-pill {
    height: 30px;
    padding: 0 0.45rem;
    font-size: 0.72rem;
  }

  .act1-temp-gauge {
    height: 32px;
    padding: 0 0.45rem;
  }

  .mission-leads-grid {
    grid-template-columns: 1fr;
  }

  .case-file-table th,
  .case-file-table td {
    padding: 0.25rem 0.35rem;
    font-size: 0.72rem;
  }
}

@media (max-height: 700px) {
  .case-file-card {
    max-height: 96%;
    padding: 0.5rem 0.8rem;
  }
  .case-file-header {
    padding-bottom: 0.3rem;
  }
  .case-file-table {
    margin: 0.3rem 0;
  }
  .case-file-table th {
    font-size: 0.7rem;
    padding: 0.2rem 0.4rem;
  }
  .case-file-table td {
    font-size: 0.78rem;
    padding: 0.25rem 0.4rem;
  }
  .case-file-footer {
    padding-top: 0.35rem;
  }
}

/* The mission card is the last cold-open step, so it keys on the line, not the beat. */
.act1-mission-card.is-new-line {
  animation: stampSlamCentered var(--dur-base) var(--ease-out-expo) forwards;
}

.is-entering .act1-pov-viewport-modal {
  animation: fadeIn 0.25s ease-out forwards;
}

.is-entering .foil-crinkle-banner {
  animation: stampSlam 0.3s ease-out;
}

.is-entering .case-file-card {
  animation: stampSlamCentered var(--dur-base) var(--ease-out-expo) forwards;
}

.is-entering .act2-placeholder-modal {
  animation: stampSlam 0.3s ease-out;
}

#screen-act1 .speech-bubble:not(.is-new-line) { animation: none; }

/* Leads are only live in the hub (see syncSceneState). In every other beat the props stay in
   the scene as scenery: no invitation ring, no touch ripples, no tooltip, no hover lift, no
   pointer. Their clicks still bubble, so "click anywhere" in the cold open keeps working. */
.act1-viewport-card:not(.leads-live) .act1-interactable {
  cursor: inherit;
}
.act1-viewport-card:not(.leads-live) .prop-affordance-ring,
.act1-viewport-card:not(.leads-live) .lake-touch-ring,
.act1-viewport-card:not(.leads-live) .interactable-tooltip {
  animation: none;
  opacity: 0;
}
.act1-viewport-card:not(.leads-live) .act1-interactable:hover .interactable-art {
  transform: none;
}

.act1-interactable {
  isolation: isolate;
}
.interactable-art {
  position: relative;
  z-index: 1;
}
.ring-back {
  z-index: 0;
}
.ring-front {
  z-index: 2;
  clip-path: inset(50% 0 0 0);
}

/* The case file (recap) zooms the painted background out, but the scene props stay at the
   hub's scale, so they sat wrong against the plate. Behind the case file the scene is
   backdrop only: the props layer (leads, canopy, Tay) is hidden while the card is up. */
#screen-act1:has(.case-file-card) .act1-scene-layer {
  visibility: hidden;
}
