#sky { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; }
#show { position: relative; z-index: 10; }
body { margin: 0; overscroll-behavior-y: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/*
 * index.html opts into viewport-fit=cover so the fixed starfield paints under
 * the notch and the home indicator — the full-bleed sky is the whole point of
 * the piece. That means the fixed HUD layer has to inset itself, or the
 * parents' escape hatch renders underneath a Dynamic Island (~59px). env()
 * resolves to 0 on any device without cutouts, so max() keeps the ordinary
 * padding everywhere else.
 */
.hud {
  /* Position lives on the element as Tailwind utilities, so the HUD is still
     pinned top-right if this stylesheet ever fails to load. Only the inset
     correction is here. */
  padding-top: max(0.75rem, env(safe-area-inset-top));
  padding-right: max(0.75rem, env(safe-area-inset-right));
}

/*
 * The fixed HUD (#hud in index.html: SOUND ON + MISSION BRIEF) floats above
 * every screen at z-50, so any screen whose own content starts at the very
 * top of the viewport can render underneath it — confirmed at 375x812 with
 * the airlock's first line sitting directly under the buttons.
 *
 * --hud-height mirrors the HUD's own box exactly, term for term, rather than
 * guessing a pixel value that would silently drift the next time the HUD's
 * markup changes:
 *   - max(0.75rem, env(safe-area-inset-top))  the .hud rule above, verbatim
 *   - --hud-button-height (34px)               measured: the SOUND ON /
 *                                               MISSION BRIEF pill (text-xs,
 *                                               py-2, border) in
 *                                               sound-toggle.js / brief.js
 *   - 0.75rem                                  the HUD's own bottom padding
 *                                               (Tailwind's p-3), which
 *                                               show.css does not override
 * If sound-toggle.js or brief.js ever change that button's font size,
 * padding or border width, update --hud-button-height to match.
 */
:root {
  --hud-button-height: 34px;
  --hud-height: calc(max(0.75rem, env(safe-area-inset-top)) + var(--hud-button-height) + 0.75rem);
}

/*
 * Top padding for any screen whose first content must clear the fixed HUD,
 * plus a breathing gap below it. --hud-height is the exact, single count of
 * the top safe-area inset (see the block above) — this is the only place
 * that value is spent. The breathing gap itself is a clamp(), not a flat
 * pixel value: on a short viewport (a small phone, or a tall inset eating
 * into the available height) it settles at its 0.5rem floor; on anything
 * roomier it grows back toward its 0.75rem ceiling. 1.5vh keeps that
 * transition tied to the viewport actually being measured, rather than one
 * fixed pixel value tuned to a single device.
 */
.top-clear-hud {
  padding-top: calc(var(--hud-height) + clamp(0.5rem, 1.5vh, 0.75rem));
}

/*
 * Mirror of the HUD's own max(fixed, env()) pattern above, for content that
 * runs down to the bottom safe area (the home-indicator cutout) instead of
 * the top HUD. max(), not the inset alone, so a phone with no cutout still
 * keeps a full 1rem of ordinary bottom breathing room instead of collapsing
 * to 0 — same reasoning as .hud's own padding-top.
 */
.bottom-clear-safe {
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
}

/*
 * svh is Safari 15.4+ / Chrome 108+. On anything older the declaration is
 * dropped, every full-height screen collapses to content height, and
 * ScrollTrigger's start/end positions stop meaning anything — on exactly the
 * older phones GSAP was chosen for. 100vh is wrong by one toolbar; nothing.
 */
.screen {
  min-height: 100vh;
  min-height: 100svh;
}

/*
 * #brief-open ("Mission Brief") is the parents' escape hatch to the date,
 * address and pickup time. A gentle glow + scale pulse makes an adult's eye
 * find it without shouting over the show. brief.js drops the .brief-pulse
 * class the first time the dialog is opened, so the pulse is a discovery aid
 * only — quiet again for the rest of that visit.
 */
@keyframes brief-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(92, 225, 230, 0.5); transform: scale(1); }
  50%      { box-shadow: 0 0 0 8px rgba(92, 225, 230, 0); transform: scale(1.05); }
}

.brief-pulse {
  animation: brief-pulse 2.4s ease-in-out infinite;
}

/*
 * The blanket reduced-motion rule above (`*, *::before, *::after`) already
 * crushes every animation-duration to 0.01ms, which is enough for
 * transitions but leaves an *infinite* animation like this one looping so
 * fast it's an unreliable, browser-dependent flicker rather than a
 * guaranteed static state. This is the one element on the page whose whole
 * purpose is motion, so it gets its own explicit reduced-motion rule rather
 * than relying on that generic one: no animation at all, replaced by a
 * steady glow and a brighter border, so the button stays visually distinct
 * without being the one thing still moving for a guest who asked everything
 * to stop.
 */
@media (prefers-reduced-motion: reduce) {
  .brief-pulse {
    animation: none !important;
    border-color: #5ce1e6;
    box-shadow: 0 0 0 3px rgba(92, 225, 230, 0.45);
  }
}
