/* The birthday intro, from the prototype Tracy and the other session built.
   Ported rather than rewritten: the copy, the replies, the twelve steps and the
   reveal are exactly as they were made.

   Every rule is scoped under .intro-scope. The prototype owned a whole page —
   :root, body, body::after, body[data-theme] — and this has to sit over a site
   that already has all of those. The scope is the only change of substance;
   everything else is the original.

   Used by two things and duplicated by neither: /bday, which stays put and can
   be watched any time, and the first-visit overlay on /. */
.intro-scope {
  color-scheme: dark;
  font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-synthesis: none;
  --ink: #fffaf5;
  --muted: rgba(255, 250, 245, 0.7);
  --glass: rgba(255, 255, 255, 0.14);
  --glass-edge: rgba(255, 255, 255, 0.26);
  --base-a: #b51755;
  --base-b: #de5e57;
  --base-c: #f2ab70;
  --orb-a: #ff87a9;
  --orb-b: #ffd17c;
  --orb-c: #9f5cff;
}


.intro-scope {
  min-height: 100%;
}

.intro-scope {
  min-height: 100svh;
  margin: 0;
  overflow: hidden;
  color: var(--ink);
  background:
    radial-gradient(circle at 50% 120%, rgba(255, 255, 255, 0.16), transparent 42%),
    linear-gradient(145deg, var(--base-a), var(--base-b) 54%, var(--base-c));
  transition: background 1.1s ease;
}

.intro-scope::after {
  position: fixed;
  inset: 0;
  z-index: 1;
  content: "";
  pointer-events: none;
  opacity: 0.09;
  background-image:
    repeating-radial-gradient(circle at 30% 40%, transparent 0 2px, rgba(255, 255, 255, 0.18) 3px 4px),
    repeating-radial-gradient(circle at 70% 60%, transparent 0 3px, rgba(18, 8, 43, 0.18) 4px 5px);
  background-size: 11px 13px, 17px 19px;
  mix-blend-mode: soft-light;
}

.intro-scope[data-theme="1"] {
  --base-a: #67227d;
  --base-b: #bc3a78;
  --base-c: #ef895f;
  --orb-a: #ff7d9d;
  --orb-b: #ffbf69;
  --orb-c: #7f79ff;
}

.intro-scope[data-theme="2"] {
  --base-a: #34319b;
  --base-b: #6e3bb3;
  --base-c: #cf4f8c;
  --orb-a: #ff64aa;
  --orb-b: #a476ff;
  --orb-c: #54d7d0;
}

.intro-scope[data-theme="3"] {
  --base-a: #122b68;
  --base-b: #35339c;
  --base-c: #7b3fa4;
  --orb-a: #ba66ff;
  --orb-b: #43d3df;
  --orb-c: #ff5b9c;
}

.intro-scope[data-theme="4"] {
  --base-a: #071a36;
  --base-b: #12395f;
  --base-c: #36516e;
  --orb-a: #1d7eaa;
  --orb-b: #8f57d4;
  --orb-c: #f05a8c;
}

button {
  font: inherit;
}

.ambient {
  position: fixed;
  z-index: 0;
  width: min(64vw, 54rem);
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.52;
  transition: background-color 1.2s ease;
  animation: drift 14s ease-in-out infinite alternate;
}

.ambient--one {
  top: -32%;
  left: -12%;
  background: var(--orb-a);
}

.ambient--two {
  right: -18%;
  bottom: -38%;
  background: var(--orb-b);
  animation-delay: -5s;
  animation-duration: 18s;
}

.ambient--three {
  top: 38%;
  left: 48%;
  width: min(34vw, 27rem);
  background: var(--orb-c);
  opacity: 0.34;
  animation-delay: -9s;
  animation-duration: 16s;
}

.intro,
.reveal {
  position: relative;
  z-index: 2;
  min-height: 100svh;
}

/* THE MIDDLE ROW IS NAMED, not counted.
   `1fr auto 1fr` centres the conversation only while there are exactly three
   children: the eyebrow took row 1, the conversation row 2, the counter row 3.
   Removing the eyebrow on 11 August silently promoted the conversation into
   row 1 and it stopped centring vertically. Tracy: "the content is no longer
   centering vertically."

   Implicit placement is a join between a stylesheet and a child count, and
   nothing anywhere stated it. Placing the two survivors explicitly means the
   grid no longer cares how many children there are. */
.intro {
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  width: min(100% - 2rem, 72rem);
  margin: 0 auto;
  padding: clamp(1.5rem, 4vw, 3.5rem) 0;
}
.intro > .conversation { grid-row: 2; }
.intro > .position { grid-row: 3; }

.eyebrow {
  align-self: start;
  justify-self: center;
  margin: 0;
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.conversation {
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}

.copy {
  max-width: 19ch;
  min-height: 2.6em;
  margin: 0;
  font-size: clamp(2.25rem, 7.4vw, 6.6rem);
  font-weight: 580;
  letter-spacing: -0.055em;
  line-height: 0.98;
  text-wrap: balance;
  text-shadow: 0 0.08em 0.7em rgba(28, 8, 40, 0.16);
}

.reply {
  max-width: min(38rem, 90vw);
  margin-top: clamp(2.5rem, 7vh, 5rem);
  padding: 0.9rem 1.35rem 0.98rem;
  color: var(--ink);
  font-size: clamp(0.98rem, 2vw, 1.12rem);
  font-weight: 620;
  line-height: 1.3;
  background: var(--glass);
  border: 1px solid var(--glass-edge);
  border-radius: 999px;
  box-shadow:
    0 1rem 3rem rgba(24, 10, 48, 0.17),
    inset 0 1px rgba(255, 255, 255, 0.16);
  backdrop-filter: blur(18px) saturate(120%);
  -webkit-backdrop-filter: blur(18px) saturate(120%);
  cursor: pointer;
  transition: transform 180ms ease, background-color 180ms ease, box-shadow 180ms ease;
}

.reply:hover {
  background: rgba(255, 255, 255, 0.2);
  box-shadow:
    0 1.25rem 3.2rem rgba(24, 10, 48, 0.22),
    inset 0 1px rgba(255, 255, 255, 0.2);
  transform: translateY(-2px);
}

.reply:active {
  transform: translateY(0) scale(0.985);
}

.reply:focus-visible,
.restart:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.92);
  outline-offset: 5px;
}

.reply--enthusiastic {
  padding-inline: clamp(1.4rem, 5vw, 3rem);
  background: rgba(255, 255, 255, 0.21);
  box-shadow:
    0 1.3rem 4rem rgba(31, 7, 58, 0.25),
    0 0 0 0.45rem rgba(255, 255, 255, 0.055);
}

.position {
  align-self: end;
  justify-self: center;
  margin: 0;
  color: rgba(255, 255, 255, 0.55);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
}

.is-leaving .copy,
.is-leaving .reply {
  animation: leave 300ms ease both;
}

.is-entering .copy {
  animation: arrive 600ms cubic-bezier(0.2, 0.75, 0.22, 1) both;
}

.is-entering .reply {
  animation: arrive 600ms 130ms cubic-bezier(0.2, 0.75, 0.22, 1) both;
}
/* AN ANIMATION BEATS A DECLARATION, which is the whole of this bug.
   Every new screen runs `arrive` on the reply. A CSS animation wins over the
   plain `opacity: 0` in .reply--waiting, so the button faded IN, the animation
   finished, .reply--waiting then took effect and it vanished, and the reading
   timer brought it back. Tracy, twice: "the button appears at the start, then
   vanishes, then appears at the timing point."

   The first attempt at this removed the transition, which was a real fault but
   not this one. The button is held by a class and animated by a keyframe, and
   the two were arguing with the keyframe winning. So the arrival animation
   simply does not run while the button is being held back: it gets its own
   fade in when the timer releases it. */
.is-entering .reply--waiting {
  animation: none;
}

.reveal {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  visibility: hidden;
  opacity: 0;
  background: #061426;
  transition: opacity 900ms ease, visibility 0s 900ms;
}

.reveal::before {
  position: absolute;
  inset: 0;
  content: "";
  background:
    radial-gradient(circle at 50% 45%, rgba(90, 210, 221, 0.14), transparent 30%),
    linear-gradient(155deg, rgba(35, 75, 115, 0.2), transparent 48%);
}

.reveal.is-visible {
  visibility: visible;
  opacity: 1;
  transition-delay: 0s;
}

.reveal__glow {
  position: absolute;
  width: min(72vw, 46rem);
  aspect-ratio: 1.7;
  border: 1px solid rgba(130, 227, 232, 0.18);
  border-radius: 1.5rem;
  background: rgba(31, 120, 147, 0.09);
  box-shadow:
    0 0 8rem rgba(46, 189, 202, 0.14),
    inset 0 0 6rem rgba(119, 67, 191, 0.08);
  transform: perspective(800px) rotateX(3deg);
}

.reveal__content {
  position: relative;
  text-align: center;
}

.reveal__for {
  margin: 0 0 1.1rem;
  color: #92dce3;
  font-size: 0.72rem;
  font-weight: 680;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

.reveal h1 {
  margin: 0;
  font-size: clamp(3rem, 10vw, 8.5rem);
  font-weight: 540;
  letter-spacing: -0.065em;
  line-height: 0.9;
  text-wrap: balance;
}

.reveal__line {
  margin: 1.6rem 0 0;
  color: rgba(255, 255, 255, 0.64);
  font-size: clamp(1rem, 2vw, 1.2rem);
}

.restart {
  margin-top: 3.2rem;
  padding: 0.78rem 1.1rem;
  color: rgba(255, 255, 255, 0.62);
  background: transparent;
  border: 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.22);
  cursor: pointer;
}

@keyframes drift {
  from { transform: translate3d(-3%, -2%, 0) scale(0.94); }
  to { transform: translate3d(5%, 4%, 0) scale(1.08); }
}

@keyframes arrive {
  from { opacity: 0; transform: translateY(16px); filter: blur(5px); }
  to { opacity: 1; transform: translateY(0); filter: blur(0); }
}

@keyframes leave {
  from { opacity: 1; transform: translateY(0); filter: blur(0); }
  to { opacity: 0; transform: translateY(-12px); filter: blur(4px); }
}

@media (max-width: 38rem) {
  .intro {
    width: min(100% - 2.4rem, 72rem);
  }

  .copy {
    max-width: 16ch;
  }

  .reply {
    margin-top: 2.7rem;
  }

  .ambient {
    filter: blur(48px);
  }

  .reveal__glow {
    width: 92vw;
    aspect-ratio: 1.2;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

/* ═══ THE ENDING: BLACK, THE WORDMARK, THEN THE SITE ══════════════════════
   Tracy, 11 August: "the curtain thing is not working. How about fade to
   black, fade up the WATCH logo, then fade to the site."

   The curtain was two halves carrying the scope's own gradient, so the split
   stayed invisible until it moved — which is also why it hid the whole sequence
   when it sat there unmoved, and why it never read as theatre when it did move.
   Two gradient panels sliding apart on a gradient background is a wipe nobody
   can see the point of.

   This is three plain beats instead, and each one is a thing a reader already
   understands: the light goes out, the name of the thing appears, the thing
   itself is behind it. It also has no moving parts to get wrong.

   The last fade is not here: `onDone` adds `.going` to the gate, which carries
   its own `transition: opacity .9s` in index.html, so the site is revealed by
   the overlay it was always behind. */
.intro-scope .blackout {
  position: fixed;
  inset: 0;
  z-index: 8;
  display: grid;
  place-items: center;
  background: #05030a;
  opacity: 0;
  pointer-events: none;
  transition: opacity 700ms ease;
}
.intro-scope.is-ending .blackout { opacity: 1; }

.intro-scope .blackout .mark {
  font-family: Inter, ui-sans-serif, -apple-system, sans-serif;
  font-size: clamp(2rem, 7vw, 4.4rem);
  font-weight: 800;
  /* Matched to the site's wordmark. It was tracked out at 0.3em, which is a
     different mark from the one on the front page, and the black screen is the
     moment the two are meant to be recognised as the same thing. */
  letter-spacing: -0.05em;
  color: #fffaf5;
  opacity: 0;
  transform: scale(0.97);
  white-space: nowrap;
  transition: opacity 800ms ease, transform 2600ms ease-out;
}
/* A very slow swell while it holds, so the wordmark is never quite static —
   the house rule, and the reason the hold does not feel like a stall. */
/* The joining word, gold and lowercase, exactly as on the site. */
.intro-scope .blackout .mark i {
  color: #f2ab70;
  font-style: normal;
  font-weight: 600;
  font-size: 0.72em;
  letter-spacing: -0.02em;
  text-transform: lowercase;
  /* Tucked under the T, matching the site. See .wordmark .to in index.html. */
  margin-left: -0.2em;
}
.intro-scope.is-ending.mark-up .blackout .mark {
  opacity: 1;
  transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .intro-scope .blackout,
  .intro-scope .blackout .mark { transition-duration: 300ms; }
  /* A replace-all put a second copy of the joining-word rule in here on
     11 August. It was harmless and identical, and it is gone. */
  .intro-scope.is-ending.mark-up .blackout .mark { transform: none; }
}

/* THE HELD STATE ITSELF, re-added after 0.99.0 deleted it.
   That release replaced everything from the curtain comment to the end of this
   file, and this rule had been appended after the curtain block, so it went
   with it. What survived was the comment explaining it and the `animation:
   none` override that depends on it — so the button was still disabled by the
   script and no longer hidden by the stylesheet. Tracy: "they now immediately
   appear but are not clickable until the timer is up."

   A slice to end-of-file is a delete of everything somebody appended later.

   Not hidden with display, so the button keeps its space and nothing jumps when
   it becomes live. `transition: none` on the way in: only arriving is animated,
   or every new screen shows the button and fades it out in full view. */
.reply--waiting {
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .reply--waiting { transform: none; }
}
