/* ============================================================
   HAPPY BIRTHDAY — a birthday film in four acts
   Vanilla + GSAP. One page, one continuous piece:
     1. a beating heart you open with a tap
     2. it falls, and bursts into a flood of rose
     3. a kinetic wish hinges up out of that colour
     4. a gold light blooms, and a tree grows into a heart
   One grain + vignette lens sits over all of it.
   ============================================================ */

:root{
  /* --- cream / off-white (Act 1 + the tree's dawn) --- */
  --paper-0:  #fffaf4;
  --paper-1:  #f7ece0;
  --paper-2:  #eddac6;

  /* --- wine-red flood (Acts 2–3) --- */
  --rose-deep: #63102c;
  --rose:      #93203f;
  --rose-lift: #b23a5c;
  --wine:      #2c0714;

  /* --- accents / ink --- */
  --gold-1:    #f0c274;
  --gold-2:    #d79a3f;
  --ink:       #5c1a2c;   /* wine ink              */
  --ink-soft:  #8a5566;   /* muted eyebrow / sub   */
  --cream:     #fffaf4;

  --ease-out:  cubic-bezier(.2,.7,.2,1);
}

*{ box-sizing:border-box; margin:0; padding:0; }
html, body{ height:100%; }

body{
  background:#12060c;                 /* seam colour under everything */
  overflow:hidden;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  cursor:auto;
}

/* Cross-device tap hardening — kills Android's gray flash overlay and
   iOS's long-press copy/share callout on every button in the experience
   (jar, envelope, camera controls, replay, music toggle, nav buttons). */
button{
  -webkit-tap-highlight-color:transparent;
  -webkit-touch-callout:none;
  -webkit-user-select:none;
  user-select:none;
}

.scene{ position:fixed; inset:0; overflow:hidden; }

/* Keep pull-to-refresh / edge-swipe-back from fighting the app's own
   vertical scroll on pages that need it (some Android browsers otherwise
   treat the scroll gesture as a page-refresh or back-navigation cue). */
.page{ overscroll-behavior:contain; }

.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;
}

/* ============================================================
   THE TREE CANVAS (Act 4) — z0, transparent until it plays
   ============================================================ */
.tree{
  position:absolute; inset:0;
  width:100%; height:100%;
  display:block;
  z-index:1;
}

/* ============================================================
   ACT 1 — THE INVITATION
   ============================================================ */
.hero{
  position:absolute; inset:0;
  z-index:3;
  overflow:hidden;
}
/* the warm field the heart sits on */
.hero__bg{
  position:absolute; inset:0;
  background:
    radial-gradient(120% 88% at 50% 24%, #fff8f1 0%, var(--paper-0) 38%, var(--paper-1) 74%, var(--paper-2) 100%);
}
.hero__bg::after{               /* a soft warm pool of light behind the heart */
  content:""; position:absolute; left:50%; top:44%;
  width:min(78vw,720px); aspect-ratio:1; transform:translate(-50%,-50%);
  background:radial-gradient(circle, rgba(240,194,116,.45), rgba(147,32,63,.14) 46%, transparent 70%);
  filter:blur(6px);
}

/* drifting light motes (JS fills this with a few spans) */
.hero__motes{ position:absolute; inset:0; pointer-events:none; }
.mote{
  position:absolute; border-radius:50%;
  background:radial-gradient(circle, rgba(255,236,214,.9), rgba(255,206,180,.15) 55%, transparent 72%);
  will-change:transform, opacity;
}

.hero__eyebrow{
  position:absolute; top:13%; left:0; right:0;
  text-align:center;
  font-family:"Cormorant Garamond", Georgia, serif;
  font-style:italic; font-weight:600;
  font-size:clamp(15px, 3.6vw, 26px);
  letter-spacing:.06em;
  color:var(--ink);
  text-shadow:0 1px 10px rgba(255,248,241,.9);
  opacity:0;                          /* GSAP reveals */
}

/* ---- the target heart (GSAP animates .target on the hit → fall → burst) ---- */
.targetWrap{
  position:absolute; top:33%; left:0; right:0;
  display:flex; justify-content:center;
  transform:translateY(-50%);         /* centre at 33% — wrap is NOT animated */
  pointer-events:none;
}
.target{
  position:relative;
  width:clamp(120px, 27vw, 208px);
  aspect-ratio:100/92;
  will-change:transform;
  transform-origin:50% 60%;
}
.target__heart{ position:absolute; inset:0; display:block; }

/* radial bloom behind the heart — swells on the beat */
.heart__glow{
  position:absolute; left:50%; top:52%;
  width:230%; aspect-ratio:1; transform:translate(-50%,-50%) scale(1);
  border-radius:50%;
  background:radial-gradient(circle, rgba(147,32,63,.5), rgba(122,16,56,.18) 42%, transparent 68%);
  filter:blur(4px);
  pointer-events:none;
  will-change:transform, opacity;
}
.heart__svg{
  width:100%; height:100%; display:block;
  transform-origin:50% 60%;           /* the beat scales THIS, not the wrapper */
  filter:
    drop-shadow(0 10px 22px rgba(168,15,64,.34))
    drop-shadow(0 3px 6px rgba(120,10,48,.30));
  will-change:transform;
}

/* ---- the bow + arrow rig ----
   Placed lower-left and rotated by JS to aim at the heart, so the shot travels
   on a DIAGONAL. The whole rig (left/top + rotation around the grip) is set in
   JS from measured geometry; everything below is in the rig's own space. */
.archery{
  position:absolute; top:-9999px; left:-9999px; opacity:0;    /* JS sets left/top so the grip anchors */
  width:clamp(100px, 18vw, 168px);     /* smaller, daintier bow — delicate against the heart */
  cursor:grab;
  -webkit-tap-highlight-color:transparent;
  touch-action:none;                   /* we own the drag gesture */
  will-change:transform;
}
.archery:active{ cursor:grabbing; }
.archery:focus-visible{ outline:none; }
.archery:focus-visible .bow{ filter:drop-shadow(0 0 0 3px rgba(255,255,255,.85)) drop-shadow(0 10px 18px rgba(120,50,20,.4)); }
.bow{
  display:block; width:100%; height:auto;
  filter:drop-shadow(0 12px 18px rgba(90,40,15,.3));
  overflow:visible;
}

/* the aim line rides inside the rig, so it points straight at the heart */
.aim{
  position:absolute; left:50%; bottom:32%;
  width:2px; height:190%; margin-left:-1px;
  transform-origin:bottom center;
  background:linear-gradient(0deg, rgba(240,194,116,.6) 0%, rgba(147,32,63,.22) 52%, transparent 78%);
  opacity:0; pointer-events:none;
}

/* the arrow overlaps the bow at the nock and flies to the heart */
.arrow{
  position:absolute; left:0; right:0; margin-inline:auto;
  bottom:36%;                          /* JS re-anchors the tail to the nock */
  width:17.5%; height:auto;
  overflow:visible;
  will-change:transform;
  filter:drop-shadow(0 5px 7px rgba(90,40,15,.36));
}
.arrow__wings{ transform-box:fill-box; transform-origin:50% 90%; }
.arrow__wings .wingL,
.arrow__wings .wingR{ transform-box:fill-box; }
.wingL{ transform-origin:88% 60%; animation:flapL 2.6s ease-in-out infinite; }
.wingR{ transform-origin:12% 60%; animation:flapR 2.6s ease-in-out infinite; }
@keyframes flapL{ 0%,100%{ transform:rotate(0deg); } 50%{ transform:rotate(-11deg); } }
@keyframes flapR{ 0%,100%{ transform:rotate(0deg); } 50%{ transform:rotate(11deg); } }

/* ---- the draw / release hint ---- */
.hero__hint{
  position:absolute; bottom:8.5%; left:0; right:0;
  text-align:center;
  font-family:"Cormorant Garamond", Georgia, serif;
  font-weight:600; font-style:italic;
  font-size:clamp(13px, 3.1vw, 19px);
  letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink-soft);
  opacity:0;                          /* GSAP reveals */
}
.hero__ring{ display:none; }

/* ---- short-viewport landscape fix (e.g. phones rotated sideways) ----
   The eyebrow/target/hint are positioned with top:%/bottom:% of the hero's
   height. On tall screens that's plenty of room, but on short landscape
   viewports (phone rotated, height <= ~500px) the heart's clamped size no
   longer scales down enough relative to the section height, so the eyebrow
   text and heart glow can visually collide. Shrink the target and push the
   eyebrow/hint further to the edges so nothing overlaps. */
@media (max-height:500px) and (orientation:landscape){
  .hero__eyebrow{ top:4%; font-size:clamp(13px, 2.6vw, 20px); }
  .targetWrap{ top:50%; }
  .target{ width:clamp(96px, 16vw, 150px); }
  .archery{ width:clamp(80px, 13vw, 130px); }
  .hero__hint{ bottom:4%; }
}

/* ============================================================
   ACT 2 — THE FLOOD (the burst circle)
   ============================================================ */
.flood{
  position:absolute; left:50%; top:50%;
  width:140px; height:140px; margin:-70px 0 0 -70px;
  border-radius:50%;
  background:radial-gradient(circle at 40% 34%, var(--rose-lift), var(--rose) 46%, var(--rose-deep) 100%);
  z-index:5;
  opacity:0; transform:scale(.001);
  will-change:transform, opacity;
  pointer-events:none;
}

/* ============================================================
   ACT 3 — THE WISH FIELD (rose world + kinetic type)
   ============================================================ */
.field{
  position:absolute; inset:0;
  z-index:4;                          /* below the flood; revealed once it covers */
  opacity:0;
  /* purely decorative — must never intercept the tap on the heart beneath it.
     An opacity:0 element is still hit-testable, so without this the invisible
     field (z4, above the hero z3) swallows every Act-1 tap. */
  pointer-events:none;
  overflow:hidden;
  background:
    radial-gradient(120% 100% at 50% 8%, #a8284a 0%, var(--rose) 42%, var(--rose-deep) 78%, var(--wine) 100%);
  color:#fff;
}
.blob{
  position:absolute; border-radius:50%;
  filter:blur(46px); opacity:0;
  mix-blend-mode:screen;
  will-change:transform, opacity;
}
.blob--1{ width:58vmax; height:58vmax; left:-18vmax; top:-16vmax;
  background:radial-gradient(circle, rgba(178,58,92,.85), transparent 62%); }
.blob--2{ width:46vmax; height:46vmax; right:-14vmax; top:22vmax;
  background:radial-gradient(circle, rgba(147,32,63,.7), transparent 64%); }
.blob--3{ width:52vmax; height:52vmax; left:24vmax; bottom:-22vmax;
  background:radial-gradient(circle, rgba(99,16,44,.75), transparent 66%); }

/* faint grid — a hair of parallax gives the flood depth, not flatness */
.fgrid{
  position:absolute; inset:-6%;
  background-image:
    linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
  background-size:clamp(38px,7vw,74px) clamp(38px,7vw,74px);
  will-change:transform;
}
.fvignette{
  position:absolute; inset:0;
  background:radial-gradient(128% 96% at 50% 44%, transparent 50%, rgba(70,6,28,.5) 100%);
}

/* the camera — everything on the flood rides a slow continuous push */
.camera{
  position:absolute; inset:0;
  display:flex; flex-direction:column; justify-content:center; align-items:center;
  gap:clamp(4px,1.2vh,12px);
  padding:0 6vw; text-align:center;
  will-change:transform;
}
.kEyebrow{
  font-family:"Cormorant Garamond", Georgia, serif;
  font-style:italic; font-weight:500;
  font-size:clamp(15px, 3.6vw, 27px);
  letter-spacing:.08em;
  color:#f6dfc2;
  opacity:0; transform:translateY(10px);
  margin-bottom:clamp(2px,1vh,10px);
}

.headline{ font-family:"Fraunces", Georgia, serif; line-height:.92; }
.hl__line{ display:block; }
/* the mask each headline word hinges out of. Padded to clear ascenders +
   descenders so glyphs never peek past the clip edge when parked. */
.mask{
  display:inline-block; overflow:hidden;
  padding:.14em .06em;
  margin:-.14em -.06em;
}
.hl__word{
  display:inline-block;
  font-weight:900;
  font-size:clamp(52px, 15vw, 168px);
  letter-spacing:-.02em;
  color:#fff6f1;
  text-shadow:0 6px 34px rgba(60,4,24,.34), 0 2px 3px rgba(90,8,36,.42);
}
/* "Anniversary" is longer than the old word — scale it down so it never
   outgrows the frame on narrow screens */
.hl__word--long{ font-size:clamp(34px, 10.6vw, 118px); letter-spacing:-.01em; }
/* each glyph hinges up on its own bottom edge, under its own perspective */
.hl__ch{ display:inline-block; will-change:transform; }

/* gold hand-drawn underline under the second word */
.uline{
  width:clamp(150px, 40vw, 380px); height:auto;
  margin-top:clamp(4px,1.4vh,14px);
  color:var(--gold-1);
  filter:drop-shadow(0 2px 8px rgba(255,180,90,.5));
}
.kSub{
  margin-top:clamp(12px,2.4vh,26px);
  font-family:"Cormorant Garamond", Georgia, serif;
  font-weight:500; font-style:italic;
  font-size:clamp(14px, 3.4vw, 24px);
  letter-spacing:.05em;
  color:#f6e2d8;
  opacity:0; transform:translateY(10px);
}

/* cinema bars — closed = black, they open as the wish lands */
.bar{
  position:absolute; left:0; right:0; height:16vh;
  background:#12040b; z-index:3; will-change:transform; pointer-events:none;
}
.bar--top{ top:0; }
.bar--bot{ bottom:0; }

/* ============================================================
   THE HANDOFF BLOOM (rose wish → dawn of the tree)
   ============================================================ */
.bloom{
  position:absolute; left:50%; top:50%;
  width:60px; height:60px; margin:-30px 0 0 -30px;
  border-radius:50%;
  background:radial-gradient(circle, #fff 0%, #fff2d6 30%, rgba(255,214,150,.85) 55%, rgba(255,190,150,0) 74%);
  z-index:8; opacity:0; transform:scale(.001);
  will-change:transform, opacity; pointer-events:none;
}

/* ============================================================
   ACT 4 — THE HAND-LETTERED WISH (over the tree)
   ============================================================ */
.wish{
  position:absolute;
  left:5.5%; bottom:10%;
  max-width:46%;
  z-index:6;
  pointer-events:none;
  color:var(--ink);
  /* hard gate: wish__hero keeps opacity:1 (its clip-path drives the write-on),
     so without this its clipped sliver + drop-shadow would leak into Acts 1–3. */
  visibility:hidden;
}
@media (max-width: 600px){
  .wish{
    left: 5%;
    bottom: max(72px, 9vh);
    max-width: 82%;
  }
}
.wish.is-in{ visibility:visible; }
.wish::before{
  content:""; position:absolute; inset:-18% -24% -24% -16%;
  background:radial-gradient(84% 98% at 34% 52%, rgba(255,250,243,.94), rgba(255,250,243,.6) 48%, rgba(255,250,243,0) 80%);
  z-index:-1; opacity:0; transition:opacity 1s ease;
}
.wish.is-in::before{ opacity:1; }
.wish__eyebrow{
  font-family:"Cormorant Garamond", Georgia, serif;
  font-style:italic; font-weight:600;
  font-size:clamp(13px, 3.2vw, 24px);
  letter-spacing:.05em; color:#7a2c40;
  text-shadow:0 1px 2px #fff8f1, 0 0 8px rgba(255,248,241,.95);
}
.wish__heroWrap{ position:relative; display:inline-block; }
.wish__heroWrap::before{
  content:""; position:absolute; left:-12%; right:-12%; top:-26%; bottom:-22%;
  background:radial-gradient(60% 70% at 46% 52%, rgba(255,214,168,.5), rgba(255,150,150,.2) 52%, transparent 74%);
  filter:blur(14px); z-index:-1; opacity:0; pointer-events:none;
}
.wish__heroWrap::after{
  content:"\2726";
  position:absolute; right:-1%; top:-14%;
  font-size:clamp(15px, 3.6vw, 32px);
  color:#ffe1a0; text-shadow:0 0 12px rgba(255,205,120,.95);
  opacity:0; pointer-events:none;
}
.wish__hero{
  font-family:"Great Vibes", cursive; font-weight:400;
  font-size:clamp(40px, 9.6vw, 86px);
  line-height:.98; margin:-.02em 0 .06em;
  /* one consistent rose, top-lit — every letter reads the same (no per-glyph
     colour shift, which is what made the old rose→gold version look messy).
     Deep enough to hold up against the tree's bright bloom behind it. */
  background:linear-gradient(180deg, #b23a5c 0%, #93203f 50%, #63102c 100%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
  /* a crisp cream edge for legibility over the tree + one soft drop */
  filter:
    drop-shadow(0 1px 1px rgba(255,252,248,.98))
    drop-shadow(0 2px 6px rgba(130,18,55,.45));
  clip-path:inset(0 100% -28% -10%);
  transform-origin:8% 60%;             /* breathe from the start of the line */
}
.wish__rule{
  display:block; width:clamp(46px, 13vw, 110px); height:2px;
  margin:.12em 0 .48em;
  background:linear-gradient(90deg, var(--gold-2), rgba(232,162,61,0));
  box-shadow:0 0 8px rgba(255,247,240,.9);
  transform-origin:left center;
}
.wish__sub{
  font-family:"Cormorant Garamond", Georgia, serif;
  font-weight:600; font-size:clamp(12px, 2.9vw, 20px);
  letter-spacing:.03em; color:var(--ink);
  text-shadow:0 1px 2px #fff8f1, 0 0 8px rgba(255,248,241,.98);
}
.wish__eyebrow, .wish__rule, .wish__sub{
  opacity:0; transform:translateY(12px); filter:blur(5px);
  transition:opacity .8s ease, transform .9s var(--ease-out), filter .8s ease;
}
.wish__rule{ transform:translateY(12px) scaleX(.3); }
.wish.is-in .wish__eyebrow,
.wish.is-in .wish__sub{ opacity:1; transform:translateY(0); filter:blur(0); }
.wish.is-in .wish__rule{ opacity:1; transform:translateY(0) scaleX(1); }
.wish.is-in .wish__rule{ transition-delay:1.35s; }
.wish.is-in .wish__sub{  transition-delay:1.55s; }
.wish.is-in .wish__hero{
  animation:write 1.2s cubic-bezier(.62,.04,.24,1) .35s forwards,
            wishBreath 4s ease-in-out 1.9s infinite;
}
/* a gentle living breath — transform only, so it can never wash the fill out */
@keyframes wishBreath{ 0%,100%{ transform:scale(1); } 50%{ transform:scale(1.02); } }
.wish.is-in .wish__heroWrap::before{ animation:hlin .8s ease .3s forwards, hlpulse 3.4s ease-in-out 1.7s infinite; }
.wish.is-in .wish__heroWrap::after{ animation:twk 2.6s ease-in-out 1.6s infinite; }
@keyframes write{ from{ clip-path:inset(0 100% -28% -10%); } to{ clip-path:inset(0 -10% -28% -10%); } }
@keyframes sheen{ 0%,18%{ background-position:0% 50%; } 60%,100%{ background-position:100% 50%; } }
@keyframes hlin{ from{ opacity:0; transform:scale(.86); } to{ opacity:.92; transform:scale(1); } }
@keyframes hlpulse{ 0%,100%{ opacity:.72; } 50%{ opacity:1; } }
@keyframes twk{ 0%,100%{ opacity:0; transform:scale(.5) rotate(-12deg); } 45%{ opacity:1; transform:scale(1.05) rotate(16deg); } }

/* ============================================================
   REPLAY
   ============================================================ */
.replay{
  position:absolute; right:clamp(16px,4vw,32px); bottom:clamp(16px,4vh,32px); z-index:7;
  display:inline-flex; flex-direction:row-reverse; align-items:center; gap:8px;
  padding:12px 22px; border-radius:999px;
  font:600 13px/1 "Cormorant Garamond", Georgia, serif; letter-spacing:.16em; text-transform:uppercase;
  color:#fff6f1; background:linear-gradient(135deg, #7a1638, #4c0c22);
  border:1px solid rgba(255,223,180,.35);
  box-shadow:0 10px 26px -10px rgba(60,4,20,.55);
  cursor:pointer; opacity:0; transform:translateY(8px);
  transition:opacity .5s ease, transform .5s var(--ease-out), box-shadow .3s ease;
}
.replay.is-shown{ opacity:1; transform:translateY(0); }
.replay:hover{ box-shadow:0 12px 30px -8px rgba(60,4,20,.7); }
.replay svg{ opacity:.9; }

/* ============================================================
   THE LENS — grain + vignette over every act
   ============================================================ */
.grade{ position:absolute; inset:0; z-index:9; pointer-events:none; }
.grade__vignette{
  position:absolute; inset:0;
  background:radial-gradient(125% 95% at 50% 42%, transparent 54%, rgba(60,18,30,.14) 84%, rgba(40,12,22,.3) 100%);
  mix-blend-mode:multiply;
}
.grade__grain{
  position:absolute; inset:0;
  background:url('../textures/grain.png') repeat;
  background-size:300px; opacity:.05; mix-blend-mode:overlay;
}

/* ============================================================
   REDUCED MOTION — intentionally NOT honoured. The film IS the
   surprise (see the note in birthday.js), so we always play the
   full animation. This block used to force `.hero{display:none}`
   whenever the OS/browser reported prefers-reduced-motion: reduce
   (very common on Android — battery saver, some phone skins'
   "remove animations" setting, etc). That silently hid the whole
   intro forever with `!important`, with no JS error at all, which
   was the actual cause of "black screen, only the music button and
   watermark show" on mobile. Left here, disabled, as a paper trail.
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  /* intentionally empty */
}
