﻿/* ===========================================================================
   Malia's birthday guestbook — tropical island stage

   HOW TO USE THIS FILE
   --------------------
   Everything below "THEME TOKENS" is the personality of the site: change the
   colours, fonts, radii, and the decorative rules in the THEME LAYER at the
   bottom. Everything in the FUNCTIONAL LAYER (form, cards, progress bar,
   focus rings, reduced motion, responsive rules) is what keeps the guestbook
   usable and accessible — restyle it, but don't delete it.

   Non-negotiables when re-theming:
     * :focus-visible must stay clearly visible against its background.
     * Body text must keep at least 4.5:1 contrast (3:1 for large headings).
     * overflow-x: hidden on <body> plus max-width:100% on media — no
       horizontal scrollbar at 320px.
     * The prefers-reduced-motion block at the bottom must survive.
   =========================================================================== */

/* ============================ THEME TOKENS ============================ */
:root {
  --primary: #0E7C86;
  --secondary: #FF7D6B;
  --accent: #F26AA7;

  --surface: #FFF7EC;      /* page background            */
  --card: #FFFFFF;            /* raised cards and the form  */
  --ink: #233A3E;              /* body text                  */
  --ink-soft: #5B6E72;    /* secondary text             */
  --on-primary: #FFFFFF;/* text on primary surfaces   */

  --font-display: "Fredoka", "Segoe UI Rounded", system-ui, sans-serif;
  --font-body: "Nunito", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-accent: "Caveat", "Segoe Script", cursive;

  --radius: 22px;
  --shadow: 0 10px 30px rgb(0 0 0 / 0.16);
  --wrap: 68rem;
}

/* ========================== FUNCTIONAL LAYER ========================== */

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ink);
  background: var(--surface);
  overflow-x: hidden;          /* belt for decorative overhangs */
}

img, svg, video { max-width: 100%; }
img, video { height: auto; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.15;
  margin: 0 0 0.5em;
  text-wrap: balance;
}

a { color: var(--primary); }

.wrap {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(1.1rem, 4vw, 2rem);
}

/* Keyboard access -------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: 0.75rem;
  top: -4rem;
  z-index: 50;
  background: var(--ink);
  color: var(--surface);
  padding: 0.6rem 1rem;
  border-radius: 0 0 10px 10px;
  text-decoration: none;
  font-weight: 700;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 0; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ------------------------- STAGE (3D island hero) ------------------------- */
/* Layout only — the visual dressing lives in the THEME LAYER below.

   .stage is the whole opening block; .stage-view is the island's viewport.
   On desktop the two are the same box (stage-view fills the stage) and the
   sound board is an absolute overlay on top. On phones the stage becomes a
   plain column: island viewport first, sound board dock second — see the
   "ISLAND FIRST, BOARD BELOW" block further down. */
.stage {
  position: relative;
  overflow: hidden;
  height: min(88vh, 900px);
  min-height: 560px;
}

.stage-view {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* The dock is inert on desktop: its children lay out against .stage exactly
   as they did when they were direct children of it. */
.board-dock { display: contents; }

.stage-sky,
.stage-canvas,
.stage-fallback {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.stage-canvas {
  display: block;
  cursor: grab;
  /* Horizontal drags spin the island; vertical touches keep scrolling the
     page. This line is what stops the 3D scene from ever trapping scroll. */
  touch-action: pan-y;
}
.stage-canvas.grabbing { cursor: grabbing; }

/* The hidden attribute must always win, even over explicit display rules. */
[hidden] { display: none !important; }

/* When WebGL is unavailable the titles sit over the painted illustration,
   which is busy — give them a soft card so they stay readable. */
.stage.no-webgl .stage-titles {
  background: rgb(255 247 236 / 0.82);
  border-radius: 22px;
  padding: 1.1rem 1.4rem 1.3rem;
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.18);
}

.stage-fallback picture, .stage-fallback img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center bottom;
}

.eyebrow {
  margin: 0 0 0.9rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-size: 0.82rem;
}

.hero-sub {
  margin: 0 auto;
  max-width: 34rem;
  font-size: clamp(1.05rem, 2.4vw, 1.3rem);
  font-weight: 700;
}

/* Portrait / family photo */
.portrait {
  position: relative;
  display: inline-block;
  margin: 0;
  max-width: min(460px, 92vw);
  background: var(--card);
  padding: 14px 14px 10px;
  border-radius: 8px;
  box-shadow: var(--shadow);
}
.portrait-photo { display: block; width: 100%; border-radius: 4px; }
.portrait-caption {
  font-family: var(--font-accent);
  font-size: 1.5rem;
  color: var(--ink-soft);
  margin: 0.35rem 0 0.1rem;
}

.age-badge {
  position: absolute;
  top: -22px;
  right: -18px;
  display: grid;
  place-items: center;
  width: clamp(64px, 15vw, 92px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-primary);
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1.4rem, 4vw, 2rem);
  box-shadow: var(--shadow);
}

/* -------------------------------- ABOUT -------------------------------- */
.about {
  background: var(--surface);
  text-align: center;
  padding: clamp(2.5rem, 6vw, 4rem) 0;
}
.about h2 { font-size: clamp(1.9rem, 5vw, 2.7rem); color: var(--primary); }

.lede {
  max-width: 40rem;
  margin: 0 auto 2.2rem;
  font-size: clamp(1.05rem, 2.2vw, 1.2rem);
  color: var(--ink-soft);
}

.pillars {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.8rem 1rem;
  padding: 0;
  margin: 0;
}
.pillars li {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  background: var(--card);
  border: 1.5px solid color-mix(in srgb, var(--primary) 25%, transparent);
  border-radius: 999px;
  padding: 0.55rem 1.15rem;
  font-weight: 700;
  color: var(--primary);
}

/* ------------------------------ GUESTBOOK ------------------------------ */
.guestbook {
  background: var(--primary);
  color: var(--on-primary);
  padding: clamp(2.8rem, 6vw, 4.5rem) 0;
}
.guestbook h2 {
  font-size: clamp(2rem, 5vw, 2.8rem);
  text-align: center;
  color: inherit;
}
.section-sub {
  text-align: center;
  max-width: 34rem;
  margin: 0 auto 2.4rem;
}

/* The signing form */
.signing-card {
  position: relative;
  overflow: hidden;
  background: var(--card);
  color: var(--ink);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  max-width: 40rem;
  margin: 0 auto 3.5rem;
  padding: clamp(1.3rem, 4vw, 2.2rem);
}
/* sunset ribbon along the top of the form */
.signing-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 7px;
  background: linear-gradient(90deg, var(--primary), var(--accent), var(--secondary), #e9b44c);
}

.field { margin-bottom: 1.2rem; }
.field label,
.file-label {
  display: block;
  font-weight: 800;
  margin-bottom: 0.35rem;
  color: var(--ink);
}
.file-label em { font-style: normal; font-weight: 600; color: var(--ink-soft); }

.field input[type="text"],
.field textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: #fff;
  border: 2px solid color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 10px;
  padding: 0.65rem 0.85rem;
  transition: border-color 0.15s ease;
}
.field input[type="text"]:focus-visible,
.field textarea:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--primary);
}
.field textarea { resize: vertical; min-height: 7.5rem; }
.field.invalid input,
.field.invalid textarea { border-color: #c0392b; }

.count { float: right; font-weight: 600; font-size: 0.8rem; color: var(--ink-soft); }
.field-err { margin: 0.35rem 0 0; color: #c0392b; font-weight: 700; font-size: 0.92rem; }
.field-hint { margin: 0.35rem 0 0; color: var(--ink-soft); font-size: 0.85rem; }

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; }
@media (max-width: 620px) { .field-row { grid-template-columns: 1fr; } }

/* Styled file pickers. The real input stays in the accessibility tree —
   it is visually hidden, NOT display:none, so it remains focusable and the
   <label> keeps working for screen readers and keyboards alike. */
.file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}
.file-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  background: color-mix(in srgb, var(--secondary) 18%, #fff);
  color: var(--ink);
  border: 2px dashed var(--primary);
  border-radius: 10px;
  padding: 0.6rem 1rem;
  font-weight: 700;
  max-width: 100%;
}
.file-btn svg { width: 20px; height: 20px; flex: none; }
.file-btn [data-file-text] {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.field-file:focus-within .file-btn { outline: 3px solid var(--accent); outline-offset: 2px; }

.file-clear {
  margin-top: 0.4rem;
  background: none;
  border: none;
  cursor: pointer;
  color: #c0392b;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 700;
  text-decoration: underline;
  padding: 0;
}

/* Honeypot — off-screen rather than display:none, which some bots skip. */
.hp { position: absolute; left: -9999px; top: -9999px; }

.submit-row {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  flex-wrap: wrap;
  margin-top: 1.4rem;
}

.btn-primary {
  font: inherit;
  font-weight: 800;
  font-size: 1.05rem;
  background: var(--accent);
  color: var(--on-primary);
  border: none;
  border-radius: 999px;
  padding: 0.75rem 1.6rem;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.btn-primary:hover { filter: brightness(0.92); transform: translateY(-1px); }
.btn-primary:active { transform: translateY(1px); }
.btn-primary[disabled] { opacity: 0.6; cursor: wait; transform: none; }

/* Upload progress */
.progress { flex: 1 1 180px; min-width: 160px; }
.progress-bar {
  height: 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 12%, transparent);
  overflow: hidden;
  position: relative;
}
.progress-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  width: var(--pct, 0%);
  background: linear-gradient(90deg, var(--secondary), var(--accent));
  border-radius: 999px;
  transition: width 0.2s ease;
}
.progress-text { font-size: 0.85rem; font-weight: 700; color: var(--ink-soft); }

.form-status { min-height: 1.4em; margin: 0.9rem 0 0; font-weight: 800; }
.form-status.ok { color: #1f6b4a; }
.form-status.err { color: #c0392b; }

/* Guestbook entries */
.entries-title { font-size: clamp(1.5rem, 4vw, 2rem); text-align: center; margin-bottom: 1.6rem; }
.entries-status { text-align: center; margin-bottom: 1.2rem; font-weight: 700; }
.entries-status .retry {
  background: none;
  border: 2px solid currentColor;
  color: inherit;
  font: inherit;
  font-weight: 800;
  border-radius: 999px;
  padding: 0.35rem 1rem;
  cursor: pointer;
  margin-left: 0.6rem;
}

.entries {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 1.6rem;
  align-items: start;
}

.entry-card {
  background: var(--card);
  color: var(--ink);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.15rem 1.2rem 1rem;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
/* postcards pinned to a board: a playful alternating tilt */
.entry-card:nth-child(odd) { transform: rotate(-0.7deg); }
.entry-card:nth-child(even) { transform: rotate(0.6deg); }
.entry-card:hover {
  transform: rotate(0deg) translateY(-4px);
  box-shadow: 0 16px 40px rgb(0 0 0 / 0.24);
}

.entry-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.8rem;
  margin-bottom: 0.5rem;
}
.entry-name {
  font-family: var(--font-accent);
  font-size: 1.7rem;
  line-height: 1.05;
  color: var(--primary);
  margin: 0;
  overflow-wrap: anywhere;   /* a very long single "word" can't blow the grid */
}
.entry-date {
  flex: none;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.entry-msg {
  margin: 0 0 0.8rem;
  white-space: pre-line;     /* honour the line breaks a guest typed */
  overflow-wrap: anywhere;
}

.entry-media { margin: 0 0 0.8rem; }
.entry-media img {
  display: block;
  width: 100%;
  border-radius: 8px;
}
.entry-media video {
  display: block;
  width: 100%;
  border-radius: 8px;
  background: #000;
}
.media-fallback {
  font-size: 0.9rem;
  color: var(--ink-soft);
  background: color-mix(in srgb, var(--secondary) 15%, transparent);
  border-radius: 8px;
  padding: 0.6rem 0.8rem;
}

.entry-foot { display: flex; justify-content: flex-end; }

.react-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font: inherit;
  font-weight: 800;
  font-size: 0.95rem;
  background: color-mix(in srgb, var(--secondary) 18%, #fff);
  color: var(--ink);
  border: 2px solid var(--primary);
  border-radius: 999px;
  padding: 0.3rem 0.9rem;
  cursor: pointer;
  transition: transform 0.12s ease, background 0.12s ease;
}
.react-icon { font-size: 1.2rem; line-height: 1; }
.react-btn:hover:not([disabled]) { transform: scale(1.04); }
.react-btn[disabled] {
  cursor: default;
  background: var(--accent);
  color: var(--on-primary);
  border-color: var(--accent);
}
.react-count { min-width: 1ch; }

@keyframes react-pop { 40% { transform: scale(1.3) rotate(-8deg); } }
.react-btn.pop .react-icon { animation: react-pop 0.45s ease; }

.empty-state {
  grid-column: 1 / -1;
  text-align: center;
  border: 3px dashed color-mix(in srgb, currentColor 40%, transparent);
  border-radius: 14px;
  padding: 2.6rem 1.5rem;
}
.empty-state p {
  margin: 0;
  font-family: var(--font-accent);
  font-size: clamp(1.4rem, 4vw, 1.9rem);
}

/* -------------------------------- FOOTER -------------------------------- */
.footer {
  background: var(--secondary);
  color: var(--ink);
  text-align: center;
  padding: 3rem 1.25rem 2.4rem;
}
.footer-big {
  font-family: var(--font-accent);
  font-size: clamp(2rem, 6vw, 2.8rem);
  margin: 0 0 0.2rem;
}
.footer-line { margin: 0; font-weight: 800; font-size: 1.1rem; }
.footer-fine { margin: 1.6rem 0 0; font-size: 0.85rem; font-weight: 600; opacity: 0.75; }

/* ---------------------- CELEBRATION (post-submit) ---------------------- */
#celebration-root {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 60;
}
.confetti-bit {
  position: absolute;
  top: -4vh;
  border-radius: 3px;
  animation: confetti-fall var(--dur, 2.8s) ease-in forwards;
  transform: rotate(var(--rot, 0deg));
}
@keyframes confetti-fall {
  to { transform: translateY(110vh) rotate(calc(var(--rot, 0deg) + 300deg)); opacity: 0.9; }
}

/* ------------------------ MOTION & REDUCED MOTION ------------------------ */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.6s ease, transform 0.6s ease;
  }
  .reveal.in { opacity: 1; transform: none; }
}

/* Everything decorative stops here when the visitor asks for less motion.
   main.js also skips the confetti and the reaction "pop". */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------ SMALL SCREENS ------------------------------ */
@media (max-width: 640px) {
  .age-badge { top: -14px; right: -8px; }
  .entry-head { flex-direction: column; align-items: flex-start; gap: 0.2rem; }
}

/* ===============================================================
   THEME LAYER — Malia's tropical island stage.
   The 3D island opening, the light-and-sound console, the spotlight
   photo, the playbill card, and the finale all live here.
   =============================================================== */

/* ---- Wavy section transitions ------------------------------------------- */
/* Each divider floats over the bottom of the section above it and paints
   the next section's colour as a two-layer rolling wave. Zero net height:
   the following section starts exactly where it would have anyway. */
.wave-sep {
  position: relative;
  height: 64px;
  margin-top: -64px;
  z-index: 3;
  pointer-events: none;
  line-height: 0;
}
.wave-sep svg {
  display: block;
  width: 100%;
  height: 100%;
}
.wave-sep .w1 { fill: var(--to); opacity: 0.45; }
.wave-sep .w2 { fill: var(--to); }
/* Gold-crested variant for the stage → console junction */
.wave-sep.wave-gold .w1 { fill: #e9b44c; opacity: 0.9; }
@media (max-width: 640px) {
  .wave-sep { height: 40px; margin-top: -40px; }
}

/* ---- Squiggly heading underline ----------------------------------------- */
.spotlight-photo h2::after,
.about h2::after,
.playbill h2::after,
.guestbook h2::after,
.finale h2::after {
  content: "";
  display: block;
  width: 112px;
  height: 10px;
  margin: 0.55rem auto 0;
  background: currentColor;
  opacity: 0.55;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 10'%3E%3Cpath d='M1 5 Q 10 0 19 5 T 37 5 T 55 5 T 73 5 T 91 5 T 109 5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 10'%3E%3Cpath d='M1 5 Q 10 0 19 5 T 37 5 T 55 5 T 73 5 T 91 5 T 109 5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* ---- Stage dressing ---------------------------------------------------- */
/* Painted stand-in that matches the 3D scene's sky while it loads. */
.stage-sky {
  background: linear-gradient(180deg, #d989b8 0%, #eda0a2 34%, #f7bb8e 52%, #ffd98a 60%, #2aa8a0 60.3%, #1b8f8f 100%);
}

.stage-titles {
  position: absolute;
  top: clamp(1.6rem, 5vh, 3.2rem);
  left: 50%;
  transform: translateX(-50%);
  width: min(92vw, 46rem);
  text-align: center;
  color: #5c1622;
  pointer-events: none;
  z-index: 2;
  /* --recede is written by island.js as the visitor scrolls away. */
  opacity: calc(1 - var(--recede, 0) * 1.35);
}
.stage-titles .eyebrow { color: #7e2f3c; }
/* The 3D marquee carries the big birthday billing — the HTML heading rides
   above it, smaller, so the two never fight. */
.stage-titles h1 {
  font-size: clamp(1.6rem, 3.6vw, 2.6rem);
  margin-bottom: 0.15em;
  color: #6d1b29;
  text-shadow: 0 2px 0 rgb(255 255 255 / 0.45), 0 6px 24px rgb(255 200 143 / 0.55);
}
.stage-titles .hero-sub {
  color: #7e2f3c;
  font-size: clamp(0.95rem, 1.8vw, 1.1rem);
}
@media (max-width: 640px) {
  .stage-titles .hero-sub { display: none; }   /* the marquee says it all */
}

.drag-hint {
  position: absolute;
  left: 50%;
  bottom: clamp(3.4rem, 8vh, 4.6rem);
  transform: translateX(-50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0.5rem 1.05rem;
  border-radius: 999px;
  background: rgb(35 58 62 / 0.72);
  color: #fff7ec;
  font-weight: 700;
  font-size: 0.92rem;
  white-space: nowrap;
  max-width: 94vw;
  pointer-events: none;
  transition: opacity 0.6s ease;
  opacity: calc(1 - var(--recede, 0) * 2);
}
.drag-hint svg { width: 20px; height: 20px; flex: none; }
.drag-hint.gone { opacity: 0; }

.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 0.85rem;
  transform: translateX(-50%);
  z-index: 4;
  display: grid;
  justify-items: center;
  color: #fff7ec;
  text-shadow: 0 1px 4px rgb(20 60 60 / 0.6);
  text-decoration: none;
  font-weight: 800;
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: calc(1 - var(--recede, 0) * 2);
}
.scroll-cue svg { width: 26px; height: 26px; }
@media (prefers-reduced-motion: no-preference) {
  .scroll-cue svg { animation: cue-bob 1.8s ease-in-out infinite; }
  @keyframes cue-bob { 50% { transform: translateY(5px); } }
}

/* ---- The sound board (HTML controls on the 3D board) --------------------- */
/* board-layer + board-cam implement a minimal CSS3D projection driven by
   island-embed.js: the overlay is designed at 640×360px and mapped onto the
   3D board face, so the pads and dials read as built into the board. On
   phones and without WebGL the layer switches to .board-flat and docks at
   the bottom of the stage instead. */
.board-layer {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
}
.board-cam {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}
.board-ui {
  position: absolute;
  left: 0;
  top: 0;
  width: 640px;
  height: 360px;
  padding: 12px 16px 16px;
  pointer-events: auto;
  backface-visibility: hidden;
  display: grid;
  grid-template-rows: 34px 1fr;
  gap: 8px;
  font-family: var(--font-body);
}

.board-screen {
  justify-self: center;
  margin: 0;
  padding: 4px 22px;
  border-radius: 8px;
  background: #071c1e;
  color: #3df0dc;
  font-family: "Segoe UI", monospace;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.2em;
  text-shadow: 0 0 8px rgb(61 240 220 / 0.7);
  border: 1px solid rgb(61 240 220 / 0.25);
}

.board-main {
  display: grid;
  grid-template-columns: 158px 1fr;
  gap: 14px;
  min-height: 0;
}

/* Rotary dials: a real range input (transparent, full-hit-area) drives a
   rotating dial face. Pointer drag, touch, and arrow keys all work. */
.board-dials {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: 10px;
}
.dial {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 4px;
  border-radius: 14px;
  background: rgb(0 0 0 / 0.32);
  border: 1px solid rgb(255 217 138 / 0.25);
}
.dial-label {
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #ffd98a;
}
.dial-face {
  position: relative;
  width: 74px;
  height: 74px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 32% 28%, #5a5c6e, #26272f 68%);
  border: 3px solid #16171d;
  box-shadow: 0 4px 10px rgb(0 0 0 / 0.5), inset 0 1px 2px rgb(255 255 255 / 0.25);
  transform: rotate(var(--rot, 0deg));
}
.dial-face i {
  position: absolute;
  left: 50%;
  top: 6px;
  width: 6px;
  height: 20px;
  margin-left: -3px;
  border-radius: 3px;
  background: #ffd98a;
  box-shadow: 0 0 6px rgb(255 217 138 / 0.8);
}
.dial-color .dial-face {
  background:
    conic-gradient(#ff5d5d, #ffd257, #6bdc7a, #4fc9df, #7d7dff, #e46bdc, #ff5d5d);
}
.dial-color .dial-face::after {
  content: "";
  position: absolute;
  inset: 12px;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #4a4c5e, #26272f 70%);
}
.dial-color .dial-face i { z-index: 1; }
.dial input[type="range"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: grab;
  touch-action: none;
}
.dial input[type="range"]:active { cursor: grabbing; }
.dial:focus-within {
  outline: 3px solid #ffd98a;
  outline-offset: 2px;
}

/* The eight numbered pads */
.board-pads {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: 1fr 1fr;
  gap: 10px;
}
.pad {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 1px;
  padding: 4px 2px;
  font-family: var(--font-body);
  font-weight: 800;
  color: #fff7ec;
  background: linear-gradient(180deg, #e4589b, #c23a7e);
  border: none;
  border-bottom: 5px solid rgb(0 0 0 / 0.35);
  border-radius: 12px;
  cursor: pointer;
  transition: transform 0.1s ease, filter 0.1s ease;
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.35);
  touch-action: manipulation;
}
.pad:nth-child(2), .pad:nth-child(6) { background: linear-gradient(180deg, #9a6bdc, #7847b8); }
.pad:nth-child(3), .pad:nth-child(7) { background: linear-gradient(180deg, #3fb9c9, #2691a1); }
.pad:nth-child(4), .pad:nth-child(8) { background: linear-gradient(180deg, #f0b04a, #d18f2a); }
.pad:hover { filter: brightness(1.08); }
.pad:active { transform: translateY(2px); border-bottom-width: 3px; }
.pad:focus-visible { outline: 3px solid #ffd98a; outline-offset: 2px; }
.pad[disabled] { opacity: 0.4; cursor: not-allowed; }
.pad-num {
  font-family: var(--font-display);
  font-size: 22px;
  line-height: 1;
}
.pad-name { font-size: 11px; letter-spacing: 0.02em; }

@media (prefers-reduced-motion: no-preference) {
  .pad.hit { animation: pad-hit 0.32s ease; }
  @keyframes pad-hit {
    30% { transform: translateY(2px) scale(0.96); filter: brightness(1.35); }
  }
}

/* Flat mode: the same markup, laid out as a finger-sized panel instead of a
   CSS3D projection. Two hosts use it — the phone dock below the island (see
   "ISLAND FIRST, BOARD BELOW"), and the desktop no-WebGL fallback, where it
   still docks over the bottom of the painted illustration. */
.board-layer.board-flat {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 8px 8px;
}
.board-layer.board-flat .board-cam { position: static; transform: none !important; }
.board-layer.board-flat .board-ui {
  position: static;
  transform: none !important;
  width: min(560px, 100%);
  height: auto;
  border-radius: 18px;
  background: linear-gradient(180deg, rgb(30 32 42 / 0.92), rgb(22 23 31 / 0.94));
  border: 1px solid rgb(255 217 138 / 0.3);
  box-shadow: 0 12px 34px rgb(0 0 0 / 0.4);
  grid-template-rows: 30px 1fr;
}
.board-layer.board-flat .board-main { grid-template-columns: 132px 1fr; }
.board-layer.board-flat .pad { min-height: 62px; }
.board-layer.board-flat .dial-face { width: 62px; height: 62px; }
@media (max-width: 480px) {
  .board-layer.board-flat .board-main { grid-template-columns: 1fr; }
  .board-layer.board-flat .board-dials { grid-template-rows: none; grid-template-columns: 1fr 1fr; }
  .board-layer.board-flat .pad-name { font-size: 10px; }
}

/* Without WebGL there are no island lights to control — pads stay, dials go. */
.stage.no-webgl .board-dials { display: none; }
.stage.no-webgl .board-main { grid-template-columns: 1fr; }

/* =========================================================================
   ISLAND FIRST, BOARD BELOW — the phone / touch composition.

   Phones are the primary experience here, so the island gets a viewport of
   its own that nothing is allowed to sit on top of, and the sound board
   becomes an ordinary block in document flow underneath it. No negative
   margins, no absolute or fixed positioning over the canvas, no fixed top
   offsets on what follows: page height is just the sum of its parts.

   The query matches island-embed.js's FLAT test exactly, so the JS-side
   board mode and the CSS-side layout can never disagree (that mismatch is
   what let the board float over the island in the first place).
   ========================================================================= */
@media (max-width: 900px), (pointer: coarse) {
  .stage {
    display: flex;
    flex-direction: column;
    height: auto;
    min-height: 0;
    overflow: visible;
  }

  /* The island's own space. Sized from the viewport WIDTH, because that is
     what decides how big the island can actually be drawn — height beyond
     that just buys sky and water. Tall enough to feel like the hero, capped
     so it never becomes an empty blue field. */
  .stage-view {
    position: relative;
    inset: auto;
    width: 100%;
    height: clamp(400px, 128vw, 660px);
    height: clamp(400px, min(128vw, 82svh), 660px);
  }

  /* The 3D marquee already carries the billing, and in this composition the
     titles have the sky above the island to themselves — one line less keeps
     them clear of the marquee on every phone and small tablet. */
  .stage-titles .hero-sub { display: none; }

  /* The dock stops being invisible and becomes a real section: deeper water
     under the island, then the board. */
  .board-dock {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(0.9rem, 3vw, 1.3rem);
    /* Picks up the water at the foot of the canvas (~#01625e) and takes it
       deeper, so the band reads as ocean continuing rather than a slab that
       happens to be a different colour. */
    background: linear-gradient(180deg, #04635f 0%, #095760 48%, #0c4854 100%);
    padding: clamp(1.5rem, 5vw, 2.3rem) 0 clamp(3.6rem, 9vw, 4.8rem);
    /* a soft shelf of shadow marks where the island ends */
    box-shadow: inset 0 16px 26px -16px rgb(4 32 36 / 0.75);
  }

  /* Normal flow — the rules that used to lift the board over the canvas are
     all switched off here. */
  .board-layer.board-flat {
    position: static;
    inset: auto;
    width: 100%;
    align-items: stretch;
    overflow: visible;
    padding: 0 clamp(0.7rem, 4vw, 1.4rem);
  }
  .board-layer.board-flat .board-ui {
    padding: 14px 14px 16px;
    gap: 10px;
    /* auto, not a fixed 30px: the screen legend is allowed to be two lines
       on a 320px phone instead of spilling over the dials below it. */
    grid-template-rows: auto 1fr;
  }
  .board-layer.board-flat .pad { min-height: 66px; }

  /* Stage chrome stays inside the island viewport, stacked in the strip of
     water below the island. The hint is offset from the cue rather than from
     the viewport, so the two can never land on top of each other. */
  .scroll-cue { bottom: 0.6rem; }
  .drag-hint { bottom: calc(0.6rem + 46px); }

  /* Mute rides with the sound controls instead of floating on the island. */
  .board-dock .stage-mute {
    position: static;
    padding: 0.6rem 1.2rem;
    min-height: 44px;
    font-size: 0.9rem;
  }
}

/* Small tablets: the same composition, just a wider board. No cliff at
   768px — the rules above already own everything up to 900px. */
@media (min-width: 601px) and (max-width: 900px), (pointer: coarse) and (min-width: 601px) {
  .board-layer.board-flat .board-ui { width: min(620px, 100%); }
}

/* Narrow phones: everything that sits over the island shrinks so it never
   crowds the marquee, and the board's legend fits on one line. */
@media (max-width: 400px) {
  .stage-titles .eyebrow {
    font-size: 0.66rem;
    letter-spacing: 0.08em;
    margin-bottom: 0.5rem;
  }
  .stage-titles h1 { font-size: 1.32rem; }
  .drag-hint {
    font-size: 0.76rem;
    padding: 0.4rem 0.75rem;
    gap: 0.35rem;
  }
  .drag-hint svg { width: 16px; height: 16px; }
  .board-layer.board-flat .board-screen {
    font-size: 12px;
    letter-spacing: 0.1em;
    padding: 4px 12px;
  }
}

/* Small round mute toggle in the stage corner */
.stage-mute {
  position: absolute;
  right: clamp(0.8rem, 2.5vw, 1.6rem);
  bottom: clamp(0.8rem, 2.5vw, 1.6rem);
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5rem 0.95rem;
  border: none;
  border-radius: 999px;
  background: rgb(35 58 62 / 0.78);
  color: #ffe9c2;
  font: inherit;
  font-weight: 800;
  font-size: 0.85rem;
  cursor: pointer;
}
.stage-mute svg { width: 20px; height: 20px; }
.stage-mute:hover { background: rgb(35 58 62 / 0.95); }
.stage-mute:focus-visible { outline: 3px solid #ffd98a; outline-offset: 2px; }
.stage-mute.is-muted { background: rgb(110 58 58 / 0.85); }
.stage-mute.is-muted svg { opacity: 0.55; }

/* ---- Spotlight photo section ------------------------------------------- */
.spotlight-photo {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 50% 0%, #8e2438 0%, #6d1b29 45%, #4a1220 100%);
  color: #fff3d6;
  text-align: center;
  padding: clamp(3rem, 7vw, 5rem) 0 clamp(3.4rem, 7vw, 5.5rem);
}
.spotlight-photo h2 {
  color: #ffd98a;
  font-size: clamp(1.9rem, 5vw, 2.7rem);
  margin-bottom: 1.6em;
}

/* Two soft light beams crossing down onto the photo */
.beam {
  position: absolute;
  top: -12%;
  width: clamp(180px, 30vw, 380px);
  height: 130%;
  background: linear-gradient(180deg, rgb(255 233 176 / 0.32), rgb(255 233 176 / 0) 80%);
  clip-path: polygon(42% 0, 58% 0, 100% 100%, 0% 100%);
  pointer-events: none;
}
.beam-left { left: 4%; transform: rotate(14deg); }
.beam-right { right: 4%; transform: rotate(-14deg); }

/* Marquee-style frame drawn around the existing portrait card */
.spotlight-photo .portrait {
  background: #fdf3df;
  padding: 22px 22px 14px;
  border-radius: 14px;
  box-shadow: 0 24px 60px rgb(0 0 0 / 0.45);
}
.marquee-frame {
  position: absolute;
  inset: 8px;
  border-radius: 10px;
  border: 3px solid #e9b44c;
  pointer-events: none;
  /* marquee bulbs: little glowing dots marching around the frame */
  background:
    radial-gradient(circle 4px at 10px 10px, #ffd98a 4px, transparent 5px);
}
.marquee-frame::before {
  content: "";
  position: absolute;
  inset: -14px;
  border-radius: 14px;
  /* Marquee bulbs marching around all four edges of the frame */
  background-image:
    repeating-linear-gradient(90deg, #ffd98a 0 6px, transparent 6px 34px),
    repeating-linear-gradient(90deg, #ffd98a 0 6px, transparent 6px 34px),
    repeating-linear-gradient(180deg, #ffd98a 0 6px, transparent 6px 34px),
    repeating-linear-gradient(180deg, #ffd98a 0 6px, transparent 6px 34px);
  background-size: 100% 6px, 100% 6px, 6px 100%, 6px 100%;
  background-position: 14px 0, 14px 100%, 0 14px, 100% 14px;
  background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
  filter: drop-shadow(0 0 6px rgb(255 217 138 / 0.8));
  opacity: 0.9;
}
.spotlight-photo .portrait-caption { color: #7e5a2e; }

/* ---- About: sandy interlude -------------------------------------------- */
.about {
  background:
    radial-gradient(90% 60% at 20% 8%, rgb(242 106 167 / 0.06), transparent),
    radial-gradient(80% 55% at 85% 90%, rgb(14 124 134 / 0.07), transparent),
    linear-gradient(180deg, #fff7ec 0%, #fdeed3 100%);
}
.pillars li svg { width: 20px; height: 20px; flex: none; }
/* pastel chips, each with its own colour and a playful tilt */
.pillars li:nth-child(1) {
  background: linear-gradient(180deg, #e5f7f0, #d2efe4);
  border-color: rgb(14 124 134 / 0.35);
  color: #0b6b74;
  transform: rotate(-1.2deg);
}
.pillars li:nth-child(2) {
  background: linear-gradient(180deg, #fdeaf3, #f9d8e8);
  border-color: rgb(217 68 122 / 0.35);
  color: #b83d6d;
  transform: rotate(0.9deg);
}
.pillars li:nth-child(3) {
  background: linear-gradient(180deg, #fff3dc, #fbe6c2);
  border-color: rgb(216 164 74 / 0.45);
  color: #9a6a1c;
  transform: rotate(-0.7deg);
}

/* ---- Playbill (last year's card) ---------------------------------------- */
.playbill {
  background: linear-gradient(180deg, #fdeed3, #f9e0bb);
  text-align: center;
  padding: clamp(2.6rem, 6vw, 4.2rem) 0;
}
.playbill h2 { color: #a4453a; font-size: clamp(1.9rem, 5vw, 2.6rem); }
.playbill .section-sub { color: var(--ink-soft); }

.playbill-frame {
  position: relative;
  display: inline-block;
  margin: 0;
  max-width: min(430px, 90vw);
  background: #fffdf6;
  padding: 16px 16px 12px;
  border: 3px solid #e9b44c;
  outline: 3px solid #a4453a;
  outline-offset: 6px;
  border-radius: 6px;
  box-shadow: 0 18px 44px rgb(122 62 30 / 0.28);
  transform: rotate(-1.2deg);
}
.playbill-frame img { display: block; width: 100%; border-radius: 3px; }
.playbill-curtain {
  position: absolute;
  top: -16px;
  left: 50%;
  transform: translateX(-50%);
  width: 74%;
  height: 26px;
  border-radius: 0 0 16px 16px;
  background:
    repeating-linear-gradient(90deg, #b3283c 0 14px, #8e2438 14px 28px);
  box-shadow: 0 4px 10px rgb(0 0 0 / 0.25);
}
.playbill-caption {
  font-family: var(--font-accent);
  font-size: 1.45rem;
  color: #7e5a2e;
  margin: 0.45rem 0 0.05rem;
}

/* ---- Guestbook dressing -------------------------------------------------- */
.guestbook {
  background:
    linear-gradient(180deg, #0e7c86 0%, #0a616c 100%);
}
/* Custom reaction artwork (set in main.js) */
.react-icon img {
  width: 26px;
  height: 26px;
  display: block;
  border-radius: 50%;
}

/* ---- Finale -------------------------------------------------------------- */
.finale {
  background:
    linear-gradient(180deg, #4a1220 0%, #6d1b29 40%, #a4453a 100%);
  color: #fff3d6;
  text-align: center;
  padding: clamp(2.8rem, 6vw, 4.4rem) 0;
}
.finale h2 { color: #ffd98a; font-size: clamp(1.8rem, 4.6vw, 2.5rem); }
.finale .section-sub { color: #f7d9c4; margin-bottom: 1.8rem; }

.finale-bulbs {
  display: flex;
  justify-content: center;
  gap: clamp(0.9rem, 4vw, 1.8rem);
  margin-bottom: 1.6rem;
}
.finale-bulbs i {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ffd98a;
  box-shadow: 0 0 12px 3px rgb(255 217 138 / 0.75);
}
@media (prefers-reduced-motion: no-preference) {
  .finale-bulbs i { animation: bulb-twinkle 2.2s ease-in-out infinite; }
  .finale-bulbs i:nth-child(2n) { animation-delay: 0.55s; }
  .finale-bulbs i:nth-child(3n) { animation-delay: 1.1s; }
  @keyframes bulb-twinkle { 50% { opacity: 0.45; box-shadow: 0 0 4px 1px rgb(255 217 138 / 0.4); } }
}

.song-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.song-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font: inherit;
  font-weight: 800;
  font-size: 1.08rem;
  color: #4a1220;
  background: linear-gradient(180deg, #ffd98a, #e9b44c);
  border: none;
  border-bottom: 5px solid rgb(0 0 0 / 0.3);
  border-radius: 999px;
  padding: 0.85rem 1.7rem;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.song-btn:hover { filter: brightness(1.06); transform: translateY(-1px); }
.song-btn:active { transform: translateY(2px); border-bottom-width: 3px; }
.song-btn:focus-visible { outline: 3px solid #fff3d6; outline-offset: 3px; }
.song-btn svg { width: 22px; height: 22px; }
.song-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
.song-btn.is-playing { background: linear-gradient(180deg, #f5a8c9, #d9447a); color: #fff7ec; }
.song-status { font-family: var(--font-accent); font-size: 1.4rem; color: #ffd98a; min-height: 1.2em; }

/* ---- Footer -------------------------------------------------------------- */
.footer {
  background: linear-gradient(180deg, #a4453a, #8e3a30);
  color: #fff3d6;
}
.footer-big { color: #ffd98a; }

/* ---- Small screens ------------------------------------------------------- */
/* The stage's own height is set in the "ISLAND FIRST, BOARD BELOW" block —
   it is driven by the island viewport plus the board, not a viewport slice. */
@media (max-width: 640px) {
  .beam { display: none; }
  .marquee-frame::before { inset: -10px; }
}
