/* Deep Sea Dive — minimalist storybook.

   Flat muted palette, paper grain, glassmorphic UI that inverts as the water
   darkens, and depth-driven cross-faded water gradient. */

:root {
  /* UI ink + paper. The scene is dark from the very first frame, so the UI
     starts light-on-dark and only cools slightly as the water goes black. */
  --ink: #e6e0d4;
  --ink-soft: #cec7b8;
  --paper: #efe9dd;
  /* The surface glass is a dark scrim, not a pale wash: the first thing a
     child sees is lit teal with light shafts over it, and pale-on-pale left
     the depth readout unreadable. main.js cross-fades this to a pale glass by
     330 m, once the water is dark enough to need one. */
  --glass: rgba(9, 24, 29, 0.34);
  --glass-line: rgba(228, 240, 240, 0.38);
  --shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --font: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
    Georgia, "Times New Roman", serif;
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  margin: 0;
  height: 100%;
  /* The scene is a fixed full-bleed stage; nothing scrolls natively. */
  overflow: hidden;
  background: #0a1a20;
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

body {
  /* Stop the browser claiming drags for back-swipe or pinch-zoom. */
  touch-action: none;
  overscroll-behavior: none;
  user-select: none;
  -webkit-user-select: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.grain-defs {
  position: absolute;
  width: 0;
  height: 0;
}

/* ------------------------------------------------------- depth gradient */

.water {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  /* Matches the deepest stop, so any pixel the column does not cover still
     reads as deep water rather than as a gap. */
  background: #020809;
}

/* The whole 1000 m of water as one gradient, twice the viewport tall, with
   the top of the column sitting at the surface and the middle at 1000 m.
   JS scrolls it upward as you dive, so the water is a place you move through
   rather than a colour that gets swapped underneath you. */
.water-column {
  position: absolute;
  left: 0;
  right: 0;
  /* height and transform are set from JS. --surface-h is the gradient's 0 m
     line, --floor is the 1000 m line; the space between them is the world. */
  top: 0;
  height: calc(var(--surface-h, 0px) + var(--floor, 100vh));
  background: linear-gradient(
    180deg,
    var(--c0) 0%,
    var(--c1) 8%,
    var(--c2) 16.7%,
    var(--c3) 26.7%,
    var(--c4) 36.7%,
    var(--c5) 46.7%,
    var(--c6) 56.7%,
    var(--c7) 62.7%,
    var(--c8) 66.7%,
    /* The last screen of the column is the run-out below the floor. It is
       already the floor colour, so it holds flat to the bottom edge. */
    var(--c8) 100%
  );
  will-change: transform;
}

/* ------------------------------------------------------------ depth haze */

/* One unbroken darkening, surface to floor. No stops that create an edge, no
   second element to align with the column -- the whole point is that there is
   nowhere for a seam to appear. It is also barely-there at the top and heavy
   at the bottom, because that is how light behaves underwater: it comes from
   above and leaves with distance. */
.water-depth {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* the light dying with depth */
    linear-gradient(
      180deg,
      rgba(4, 26, 30, 0) 0%,
      rgba(4, 22, 26, 0.1) 18%,
      rgba(3, 17, 20, 0.24) 38%,
      rgba(2, 11, 13, 0.42) 60%,
      rgba(1, 6, 8, 0.62) 82%,
      rgba(0, 3, 4, 0.78) 100%
    );
}

/* ------------------------------------------------------------ particulate */

/* Marine snow as two parallax layers. The far one is small, slow and faint;
   the near one is larger, faster and slightly brighter. Two layers is the
   minimum that reads as distance -- one layer is just noise on the lens, and
   three is indistinguishable from two. Both are radial-gradient tiles rather
   than images, so they cost no requests and scale to any screen. */
.water-particulate {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  /* The drift is the only thing here that costs anything, and an idle scene
     does not need it. main.js sets .still when the water stops moving and
     takes it off the moment a drag begins. */
  opacity: 1;
  transition: opacity 0.6s ease;
}

.water-particulate.still {
  opacity: 0.55;
}

/* Paused, not just dimmed: a paused animation costs the compositor nothing,
   which a dimmed running one does not. */
.water-particulate.still .mote-layer {
  animation-play-state: paused;
}

.mote-layer {
  position: absolute;
  inset: -20% -10%;
  background-repeat: repeat;
  /* Promoted to its own compositor layer: the drift then moves a texture
     rather than repainting a full-viewport gradient every frame. Without this
     the water scrolls, the motes repaint underneath it, and the whole stack
     is re-rasterised on each touchmove -- which is what hung the tablet. */
  will-change: transform;
  transform: translateZ(0);
  backface-visibility: hidden;
  /* Promote ONLY while the water is moving. A permanently promoted layer is
     also a permanently composited one, and on a tablet that costs memory
     whether or not anything is animating. */
  contain: paint;
}

.mote-far {
  background-image:
    radial-gradient(circle, rgba(226, 238, 234, 0.5) 0.6px, transparent 0.9px),
    radial-gradient(circle, rgba(226, 238, 234, 0.32) 0.5px, transparent 0.8px);
  background-size: 46px 52px, 71px 83px;
  background-position: 0 0, 23px 31px;
  animation: drift-far 71s linear infinite;
  opacity: 0.5;
}

.mote-near {
  background-image:
    radial-gradient(circle, rgba(236, 244, 240, 0.62) 1.1px, transparent 1.6px),
    radial-gradient(circle, rgba(236, 244, 240, 0.4) 0.9px, transparent 1.3px);
  background-size: 113px 127px, 167px 151px;
  background-position: 37px 19px, 71px 97px;
  animation: drift-near 41s linear infinite;
  opacity: 0.62;
}

/* Slow, and in opposite directions, so the two layers never line up into a
   visible pattern. */
@keyframes drift-far {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-46px, 312px, 0);
  }
}

@keyframes drift-near {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(74px, -388px, 0);
  }
}

/* ------------------------------------------------------------- rays */

.rays {
  position: fixed;
  inset: 0;
  z-index: 1;
  /* No mix-blend-mode. Six full-viewport layers inside a screen blend mean the
     compositor re-blends the entire framebuffer on every scroll frame; on a
     tablet that is the hang. The beams are already pale and low-alpha over a
     dark ground, so they read as light without it. */
  pointer-events: none;
  /* One layer for all six beams, promoted once, so the sway is a texture
     transform rather than six re-composites. */
  will-change: opacity, transform;
  contain: paint;
  /* JS scales this down to 0 by ~200m. */
  opacity: 1;
  will-change: opacity;
}

/* The sea surface itself, seen from below.

   A flat gradient is what made the water read as a background colour rather
   than a place. This is the one thing overhead that tells you which way is up:
   a bright rippling ceiling that all the shafts come down from, and which
   recedes as you descend. It is the highest-value piece of atmosphere in the
   scene, so it gets its own layer. */
.surface {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 1;
  will-change: opacity;
}

.surface-caustic {
  position: absolute;
  top: -6%;
  left: -10%;
  width: 120%;
  height: 34%;
  /* Overlapping bands: two crossing wave sets make a net of light, which is
     what a caustic actually is. One gradient band just reads as a pale bar. */
  background:
    repeating-linear-gradient(
      101deg,
      rgba(196, 232, 230, 0.13) 0px,
      rgba(196, 232, 230, 0) 19px,
      rgba(206, 238, 236, 0.1) 47px,
      rgba(196, 232, 230, 0) 63px,
      rgba(200, 234, 234, 0.07) 91px,
      rgba(196, 232, 230, 0) 127px
    ),
    repeating-linear-gradient(
      83deg,
      rgba(178, 220, 220, 0.1) 0px,
      rgba(178, 220, 220, 0) 27px,
      rgba(190, 228, 228, 0.08) 58px,
      rgba(178, 220, 220, 0) 89px,
      rgba(184, 224, 224, 0.06) 131px,
      rgba(178, 220, 220, 0) 168px
    );
  /* The mask is what stops this reading as wallpaper. Without it the bands
     ran to a hard horizontal edge and the whole ceiling looked like a
     diamond-quilted bedsheet hung over the scene. Fading them out downward
     puts the brightest light directly overhead, where the sun is. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 26%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 26%, transparent 100%);
  filter: blur(3px);
  animation: surface-drift 19s ease-in-out infinite alternate;
  opacity: 0.7;
}

.surface-glow {
  position: absolute;
  top: -8%;
  left: 0;
  width: 100%;
  height: 30%;
  background: radial-gradient(
    ellipse 70% 100% at 50% 0%,
    rgba(190, 228, 228, 0.3) 0%,
    rgba(170, 214, 216, 0.12) 45%,
    rgba(160, 206, 210, 0) 72%
  );
  filter: blur(6px);
}

@keyframes surface-drift {
  from {
    transform: translate3d(-1.5%, 0, 0) scale(1);
  }
  to {
    transform: translate3d(1.5%, 0.6%, 0) scale(1.03);
  }
}

/* A light shaft, painted rather than blurred.

   These were a 7px blur on a flat gradient, which is why they read as
   vertical smears -- a video compression artefact, not light in water. A beam
   in water is bright along its axis and feathered at both edges, so the
   feather has to be built into the gradient itself:

     - across: transparent at the edges, opaque down the middle
     - down:   brightest at the surface, gone by two-thirds of the way down

   No filter at all. A soft gradient costs nothing and, unlike a blur, does
   not bleed the beam sideways as it leans. */
.ray {
  position: absolute;
  top: -14%;
  left: var(--x);
  width: var(--w);
  height: 128%;
  background:
    /* down the beam: in at the surface, out well before the bottom.
       These are pale-on-pale over deep water, so normal compositing reads the
       same as screen here -- and normal compositing does not force the whole
       viewport through a blend on every frame, which is the difference between
       smooth and stuck on a tablet. */
    linear-gradient(
      180deg,
      rgba(206, 230, 226, var(--o)) 0%,
      rgba(198, 224, 221, calc(var(--o) * 0.5)) 22%,
      rgba(186, 214, 212, calc(var(--o) * 0.16)) 44%,
      rgba(178, 208, 208, 0) 66%
    );
  /* across the beam: feathered on both sides, so it has a soft core rather
     than a hard column edge */
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent 0%,
    rgba(0, 0, 0, 0.35) 22%,
    #000 50%,
    rgba(0, 0, 0, 0.35) 78%,
    transparent 100%
  );
  mask-image: linear-gradient(
    90deg,
    transparent 0%,
    rgba(0, 0, 0, 0.35) 22%,
    #000 50%,
    rgba(0, 0, 0, 0.35) 78%,
    transparent 100%
  );
  transform-origin: 50% 0%;
  /* Leaned, because light through moving water is never vertical. */
  transform: skewX(-7deg);
  animation: sway var(--d) ease-in-out infinite alternate;
}

@keyframes sway {
  from {
    transform: skewX(-7deg) rotate(-1.2deg) scaleY(0.97);
  }
  to {
    transform: skewX(-4deg) rotate(1.2deg) scaleY(1.03);
  }
}

/* ------------------------------------------------------------- stage */

.stage {
  position: fixed;
  inset: 0;
  z-index: 2;
  touch-action: none;
}

#sea {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

/* Paper grain: a single fixed noise tile over the whole scene. */
.grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  mix-blend-mode: overlay;
}

/* ------------------------------------------------------------- glass */

.glass {
  background: var(--glass);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  border: 1px solid var(--glass-line);
  box-shadow: var(--shadow);
  color: var(--ink);
}

.hud {
  position: fixed;
  inset: 0;
  z-index: 4;
  pointer-events: none;
}

/* ------------------------------------------------------------- gauge */

.gauge {
  position: absolute;
  top: max(16px, env(safe-area-inset-top));
  left: 16px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.gauge-track {
  position: relative;
  width: 2px;
  /* A ribbon, not a panel. The old gauge was a 158 x 354 px sheet of glass
     with a backdrop blur, which covered a quarter of a tablet and turned every
     animal that swam behind it into a grey smear. The scale is a line; only
     the moving parts get a surface. */
  height: min(30vh, 208px);
  margin: 64px 0;
  border-radius: 999px;
  background: currentColor;
  opacity: 0.3;
}

/* The sea surface, pinned to the top so the scale has an anchor. */
.gauge-surface {
  position: absolute;
  left: -5px;
  right: -5px;
  top: -1px;
  height: 1px;
  background: currentColor;
  opacity: 0.75;
}

.gauge-marker {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, -50%);
  will-change: transform;
}

.gauge-readout {
  position: absolute;
  /* Clear of the diver, which sits at 6-21px. */
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: baseline;
  gap: 1px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  border: 1px solid var(--glass-line);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* A soft dark halo so the number survives passing over a light shaft. The
     pill is 60 x 24 px, so a blur here never smears the scene. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}

/* A tiny diver riding the scale: depth becomes a place, not a number. */
.gauge-diver {
  position: absolute;
  /* The track is 2px wide, so a percentage offset lands on top of it. Sit the
     diver just to the right of the track instead, ahead of the readout. */
  left: 5px;
  top: 50%;
  width: 13px;
  height: 13px;
  transform: translateY(-50%);
  color: var(--ink);
  opacity: 0.9;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}

.gauge-diver svg {
  width: 100%;
  height: 100%;
  fill: currentColor;
  stroke: none;
}

.gauge-readout strong {
  font-size: 0.98rem;
  font-weight: 500;
  letter-spacing: 0;
}

.gauge-readout em {
  font-style: normal;
  font-size: 0.68rem;
  color: var(--ink-soft);
}

.gauge-zone {
  /* Floats at the top of the ribbon, beside the sea-surface mark, instead of
     sitting in the middle of a panel and dictating its width. */
  align-self: flex-start;
  margin-top: 34px;
  margin-left: 10px;
  font-size: 0.8rem;
  line-height: 1.25;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.72;
  white-space: nowrap;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

.discovered {
  position: absolute;
  top: max(16px, env(safe-area-inset-top));
  right: 16px;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

.slash {
  color: var(--ink-soft);
  margin: 0 1px;
}

/* ------------------------------------------------------- zone banner */

.zone-banner {
  position: absolute;
  left: 50%;
  top: 24%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  color: var(--ink);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.25);
}

.zone-banner[data-open="true"] {
  opacity: 1;
}

.zone-banner-name {
  font-size: 1.5rem;
  letter-spacing: 0.01em;
}

.zone-banner-depth {
  font-size: 0.82rem;
  color: var(--ink-soft);
}

/* ---------------------------------------------------------- name card */

.name-card {
  position: fixed;
  left: 50%;
  bottom: max(30px, env(safe-area-inset-bottom));
  z-index: 5;
  width: min(86vw, 420px);
  padding: 16px 22px 18px;
  border-radius: 20px;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-50%) translateY(14px);
  transition:
    opacity 0.4s var(--ease),
    transform 0.4s var(--ease);
}

.name-card[data-open="true"] {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.name-card-name {
  display: block;
  font-size: 1.45rem;
  letter-spacing: 0.01em;
}

.name-card-fact {
  display: block;
  margin-top: 5px;
  font-size: 0.92rem;
  line-height: 1.4;
  color: var(--ink-soft);
}

/* ------------------------------------------------------------- sound */

/* ---------------------------------------------------------------- coach */

/* Wordless coaching for a first-time player. Two icons, no reading required,
   and it retires itself the moment the child does either thing. */
.coach {
  position: fixed;
  left: 50%;
  bottom: max(26px, env(safe-area-inset-bottom));
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 14px;
  /* Keep clear of the sound button in the bottom-right corner, and of the
     name card that shares this row. */
  margin-right: 72px;
  padding: 12px 20px;
  border-radius: 999px;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-50%) translateY(12px);
  transition:
    opacity 0.6s var(--ease),
    transform 0.6s var(--ease);
}

.coach[data-open="true"] {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.coach-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

.coach-icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.9;
  /* A slow breath, so the invitation reads as alive rather than as a label. */
  animation: coach-breathe 3.4s ease-in-out infinite;
}

.coach-item:last-child .coach-icon {
  animation-delay: 1.7s;
}

.coach-text {
  font-size: 0.86rem;
  color: var(--ink);
  white-space: nowrap;
}

.coach-sep {
  width: 1px;
  height: 20px;
  background: currentColor;
  opacity: 0.25;
}

@keyframes coach-breathe {
  0%,
  100% {
    transform: translateY(0);
    opacity: 0.75;
  }
  50% {
    transform: translateY(-3px);
    opacity: 1;
  }
}

.sound {
  position: fixed;
  right: 16px;
  bottom: max(20px, env(safe-area-inset-bottom));
  z-index: 5;
  width: 46px;
  height: 46px;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  cursor: pointer;
  color: var(--ink);
}

.sound svg {
  width: 22px;
  height: 22px;
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.sound .cross {
  display: none;
  fill: none;
}

.sound[aria-pressed="false"] .wave {
  display: none;
}

.sound[aria-pressed="false"] .cross {
  display: block;
}

/* --------------------------------------------------------------- gate */

.gate {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  background: rgba(24, 23, 21, 0.78);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: opacity 0.6s var(--ease);
}

.gate[data-open="false"] {
  opacity: 0;
  pointer-events: none;
}

.gate-inner {
  text-align: center;
  padding: 24px;
  color: #e6e0d4;
}

.gate-title {
  margin: 0 0 8px;
  font-size: clamp(2rem, 8vw, 3.2rem);
  font-weight: 400;
  letter-spacing: 0.01em;
}

.gate-sub {
  margin: 0 0 26px;
  max-width: 26ch;
  font-size: 1rem;
  line-height: 1.5;
  color: #cec7b8;
}

.gate-btn {
  appearance: none;
  border: 1px solid rgba(230, 224, 212, 0.35);
  background: #efe9dd;
  color: #1c1c1a;
  font: inherit;
  font-size: 1.06rem;
  letter-spacing: 0.02em;
  padding: 13px 34px;
  border-radius: 999px;
  cursor: pointer;
  transition:
    transform 0.25s var(--ease),
    box-shadow 0.25s var(--ease);
}

.gate-btn:active {
  transform: scale(0.97);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}

@media (prefers-reduced-motion: reduce) {
  /* Every moving thing, not a hand-picked two. The mote layers were added
     later and never made it into this list, so a tablet user asking for
     reduced motion still got two full-viewport animations running. */
  .ray,
  .mote-far,
  .mote-near,
  .surface-caustic,
  .surface-glow,
  .coach-icon {
    animation: none !important;
  }
  .zone-banner,
  .name-card,
  .coach,
  .gate,
  .gate-btn {
    transition-duration: 0.01ms;
  }
}
