Bday

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#171335">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>%F0%9F%8E%82</text></svg>">
<title>A Birthday, Just for You 🎂</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,wght@0,400;0,600;0,700;1,500&family=Quicksand:wght@500;600;700&family=Caveat:wght@500;600&display=swap" rel="stylesheet">
<style>
  :root{
    --ink:#171335;
    --ink-deep:#0c0a1f;
    --ink-light:#2a2359;
    --gold:#f5b942;
    --gold-soft:#ffe1a8;
    --blush:#ff6f91;
    --blush-deep:#e14f74;
    --sage:#8fd0b4;
    --cream:#fbf1e1;
    --font-display:'Fraunces', serif;
    --font-body:'Quicksand', sans-serif;
    --font-script:'Caveat', cursive;
  }

  *{ box-sizing:border-box; }
  html,body{ margin:0; padding:0; }
  body{
    font-family:var(--font-body);
    background: radial-gradient(ellipse at top, var(--ink-light) 0%, var(--ink) 45%, var(--ink-deep) 100%);
    color:var(--cream);
    min-height:100vh; min-height:100svh;
    overflow-x:hidden;
  }
  button{ font-family:inherit; }
  button:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; border-radius:8px; }
  p{ margin:0; }

  noscript div{
    position:fixed; inset:0; z-index:9999; display:flex; align-items:center; justify-content:center;
    background:var(--ink); color:var(--cream); font-family:var(--font-body); text-align:center; padding:40px;
  }

  /* ---------- ambient background ---------- */
  .bg-lights{ position:fixed; inset:0; overflow:hidden; z-index:0; pointer-events:none; }
  .orb{ position:absolute; border-radius:50%; opacity:.5; animation: drift 16s ease-in-out infinite; }
  .orb--1{ width:200px;height:200px; left:6%; top:10%; background:radial-gradient(circle, var(--gold) 0%, transparent 70%); animation-duration:17s; }
  .orb--2{ width:150px;height:150px; right:8%; top:18%; background:radial-gradient(circle, var(--blush) 0%, transparent 70%); animation-duration:20s; animation-delay:-4s;}
  .orb--3{ width:240px;height:240px; left:16%; bottom:8%; background:radial-gradient(circle, var(--sage) 0%, transparent 70%); animation-duration:23s; animation-delay:-9s;}
  .orb--4{ width:130px;height:130px; right:14%; bottom:14%; background:radial-gradient(circle, var(--gold-soft) 0%, transparent 70%); animation-duration:18s; animation-delay:-2s;}
  .orb--5{ width:100px;height:100px; left:46%; top:4%; background:radial-gradient(circle, var(--blush-deep) 0%, transparent 70%); animation-duration:14s; animation-delay:-6s;}
  .orb--6{ width:170px;height:170px; right:36%; bottom:2%; background:radial-gradient(circle, var(--gold) 0%, transparent 70%); animation-duration:21s; animation-delay:-12s;}
  @keyframes drift{
    0%,100%{ transform:translate(0,0) scale(1); }
    50%{ transform:translate(24px,-34px) scale(1.12); }
  }

  /* ---------- fixed chrome ---------- */
  .mute-btn{
    position:fixed; top:16px; right:16px; z-index:50;
    width:42px;height:42px;border-radius:50%;
    background:rgba(23,19,53,.55); border:1px solid rgba(251,241,225,.2);
    backdrop-filter:blur(6px); color:var(--cream);
    display:flex; align-items:center; justify-content:center; cursor:pointer;
  }
  .progress{
    position:fixed; top:22px; left:50%; transform:translateX(-50%);
    display:flex; gap:8px; z-index:50; opacity:0; transition:opacity .4s ease;
  }
  .dot{ width:7px;height:7px;border-radius:50%; background:rgba(251,241,225,.25); transition:all .3s ease; }
  .dot.is-active{ background:var(--gold); transform:scale(1.4); }
  .dot.is-done{ background:var(--blush); }

  /* ---------- scene shell ---------- */
  #app{ position:relative; z-index:1; min-height:100vh; min-height:100svh; overflow:hidden; }
  .scene{
    position:absolute; inset:0;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    text-align:center; padding:100px 22px 60px;
    opacity:0; pointer-events:none; transition:opacity .55s ease;
  }
  .scene.active{ opacity:1; pointer-events:auto; }

  .eyebrow{
    font-family:var(--font-body); font-weight:700; text-transform:uppercase;
    letter-spacing:.22em; font-size:.72rem; color:var(--gold-soft); margin:0 0 14px;
  }

  .btn{
    font-family:var(--font-body); font-weight:700; font-size:1rem; letter-spacing:.02em;
    padding:.9em 1.7em; border-radius:999px; border:none; cursor:pointer;
    display:inline-flex; align-items:center; gap:.5em;
    transition:transform .2s ease, box-shadow .2s ease, background .2s ease;
  }
  .btn--primary{
    background:linear-gradient(135deg, var(--gold-soft), var(--gold)); color:var(--ink-deep);
    box-shadow:0 8px 24px rgba(245,185,66,.35);
  }
  .btn--primary:hover{ transform:translateY(-2px); box-shadow:0 12px 30px rgba(245,185,66,.45); }
  .btn--ghost{
    background:rgba(251,241,225,.08); color:var(--cream); border:1.5px solid rgba(251,241,225,.35);
  }
  .btn--ghost:hover{ background:rgba(251,241,225,.16); }

  /* ---------- cover ---------- */
  .cover-card{ max-width:520px; }
  .cover-title{
    font-family:var(--font-display); font-weight:600; font-size:clamp(2.1rem,7vw,3.4rem);
    line-height:1.12; color:var(--cream); margin:0 0 18px;
  }
  .cover-title span{ color:var(--gold); font-style:italic; }
  .cover-sub{ font-size:1.05rem; color:rgba(251,241,225,.75); line-height:1.65; margin-bottom:36px; }

  /* ---------- hero ---------- */
  .hero-title{
    font-family:var(--font-display); font-weight:600; font-size:clamp(2.1rem,7vw,3.4rem);
    color:var(--cream); margin:0 0 18px;
  }
  .hero-title span{ color:var(--gold); font-style:italic; }
  .hero-sub{ font-size:1.05rem; color:rgba(251,241,225,.75); line-height:1.65; max-width:460px; margin:0 auto 36px; }

  /* ---------- shared scene head ---------- */
  .scene-head{ margin-bottom:30px; max-width:480px; }
  .scene-title{ font-family:var(--font-display); font-weight:600; font-size:clamp(1.6rem,5.5vw,2.3rem); color:var(--cream); margin:0 0 8px; }
  .scene-sub{ font-size:.98rem; color:rgba(251,241,225,.7); margin:0; }

  /* ---------- balloons ---------- */
  .balloon-field{ display:flex; flex-wrap:wrap; justify-content:center; gap:16px 20px; max-width:520px; margin-bottom:22px; }
  .balloon{ background:none; border:none; padding:0; cursor:pointer; display:flex; flex-direction:column; align-items:center; }
  .balloon:not(.popped){ animation:floaty var(--sway-dur,4s) ease-in-out infinite; animation-delay:var(--float-delay,0s); }
  .balloon-svg{ width:58px; height:auto; filter:drop-shadow(0 8px 14px rgba(12,10,31,.45)); }
  .balloon-string{ width:2px; height:42px; background:rgba(251,241,225,.3); margin-top:-2px; }
  .balloon.popped{ animation:pop .35s ease-out forwards; pointer-events:none; }
  @keyframes floaty{
    0%,100%{ transform:translateY(0) rotate(-3deg); }
    50%{ transform:translateY(-14px) rotate(3deg); }
  }
  @keyframes pop{
    0%{ transform:scale(1); opacity:1; }
    55%{ transform:scale(1.25); opacity:1; }
    100%{ transform:scale(0); opacity:0; }
  }
  .counter{ font-weight:700; color:var(--gold-soft); letter-spacing:.05em; }

  /* ---------- candles / cake ---------- */
  .cake-wrap{ display:flex; flex-direction:column; align-items:center; margin:10px 0 20px; }
  .candles{ display:flex; gap:14px; margin-bottom:-6px; position:relative; z-index:2; }
  .candle{ position:relative; width:12px; background:none; border:none; cursor:pointer; padding:26px 2px 0; }
  .candle-stick{ display:block; width:8px; height:34px; margin:0 auto; border-radius:2px;
    background:repeating-linear-gradient(180deg, var(--cream) 0 6px, var(--blush) 6px 12px); }
  .wick{ position:absolute; top:20px; left:50%; transform:translateX(-50%); width:2px;height:6px; background:#4a3a1a; }
  .flame{
    position:absolute; top:-1px; left:50%; transform:translateX(-50%); width:13px;height:19px;
    background:radial-gradient(circle at 50% 65%, #fff6d8 0%, var(--gold) 55%, #e8791a 100%);
    border-radius:50% 50% 50% 50%/60% 60% 40% 40%;
    filter:drop-shadow(0 0 6px rgba(245,185,66,.85));
    animation:flicker 1.5s ease-in-out infinite; animation-delay:var(--flicker-delay,0s);
    transform-origin:bottom center;
  }
  @keyframes flicker{
    0%,100%{ transform:translateX(-50%) scaleY(1) scaleX(1) rotate(0deg); }
    25%{ transform:translateX(-50%) scaleY(1.08) scaleX(.95) rotate(-3deg); }
    50%{ transform:translateX(-50%) scaleY(.95) scaleX(1.05) rotate(2deg); }
    75%{ transform:translateX(-50%) scaleY(1.05) scaleX(.97) rotate(-2deg); }
  }
  .candle.out .flame{ opacity:0; transform:translateX(-50%) translateY(4px) scale(.3); transition:opacity .25s ease, transform .25s ease; animation:none; }
  .cake-body{ display:flex; flex-direction:column; align-items:center; }
  .cake-tier--top{ width:150px;height:54px; border-radius:14px 14px 6px 6px; background:linear-gradient(180deg,var(--gold-soft),var(--gold)); box-shadow:inset 0 -8px 0 rgba(255,255,255,.15); }
  .cake-tier--bottom{ width:230px;height:74px; margin-top:-8px; border-radius:16px 16px 8px 8px; background:linear-gradient(180deg,#ff9fb6,var(--blush-deep)); box-shadow:inset 0 -10px 0 rgba(255,255,255,.12); }
  .cake-plate{ width:270px;height:14px;border-radius:50%; background:var(--cream); box-shadow:0 6px 18px rgba(12,10,31,.5); margin-top:2px; }
  .wish-msg{ margin-top:18px; font-family:var(--font-display); font-style:italic; font-size:1.15rem; color:var(--gold-soft); opacity:0; transform:translateY(8px); transition:opacity .5s ease, transform .5s ease; }
  .wish-msg.show{ opacity:1; transform:translateY(0); }

  /* ---------- cut the cake ---------- */
  .cake-round{ position:relative; width:180px;height:180px; border:none; padding:0; cursor:pointer; background:none; margin-bottom:26px; }
  .cake-half{
    position:absolute; inset:0; border-radius:50%;
    background:radial-gradient(circle at 35% 30%, #ffdca0, var(--gold) 45%, var(--blush-deep) 100%);
    box-shadow:0 10px 30px rgba(12,10,31,.5);
    transition:transform .5s cubic-bezier(.34,1.56,.64,1);
  }
  .cake-half--left{ clip-path:inset(0 50% 0 0); }
  .cake-half--right{ clip-path:inset(0 0 0 50%); }
  .cake-round.cut .cake-half--left{ transform:translateX(-46px) rotate(-10deg); }
  .cake-round.cut .cake-half--right{ transform:translateX(46px) rotate(10deg); }
  .knife{ position:absolute; top:-14px; right:-8px; font-size:2.1rem; transform:rotate(20deg); transition:transform .4s ease; }
  .cake-round.cut .knife{ transform:rotate(75deg) translateY(14px); }
  .fortune-note{
    max-width:360px; background:rgba(251,241,225,.06); border:1px solid rgba(251,241,225,.18);
    border-radius:20px; padding:26px 24px; opacity:0; transform:translateY(14px) scale(.96);
    transition:opacity .5s ease, transform .5s ease; pointer-events:none;
  }
  .fortune-note.show{ opacity:1; transform:translateY(0) scale(1); pointer-events:auto; }
  .fortune-label{ text-transform:uppercase; letter-spacing:.18em; font-size:.7rem; color:var(--gold-soft); margin:0 0 10px; font-weight:700; }
  .fortune-text{ font-family:var(--font-display); font-style:italic; font-size:1.25rem; color:var(--cream); margin:0 0 20px; }

  /* ---------- letter ---------- */
  .letter-card{
    position:relative; background:linear-gradient(160deg, var(--cream), #f4e3c8); color:#3a2c14;
    border-radius:18px; padding:42px 36px; max-width:440px; text-align:left;
    box-shadow:0 24px 60px rgba(12,10,31,.55); margin-bottom:28px;
  }
  .letter-card::before{
    content:''; position:absolute; inset:0; border-radius:18px;
    box-shadow:0 0 60px 20px rgba(245,185,66,.25); z-index:-1;
  }
  .letter-line, .letter-sign{
    font-family:var(--font-script); font-size:1.55rem; line-height:1.5; margin:0 0 14px;
    opacity:0; transform:translateY(14px); animation:lineIn .6s ease forwards;
    animation-delay:calc(var(--i,0) * .18s);
  }
  .letter-greeting{ font-size:1.85rem; margin-bottom:18px; }
  .letter-sign{ margin-top:14px; margin-bottom:0; }
  @keyframes lineIn{ to{ opacity:1; transform:translateY(0); } }

  /* ---------- finale ---------- */
  .marquee{ font-family:var(--font-display); font-weight:700; font-size:clamp(2rem,8vw,3.6rem); color:var(--cream); margin:0 0 14px; }
  .marquee-name{ display:block; color:var(--gold); font-style:italic; animation:glow 2.2s ease-in-out infinite; }
  @keyframes glow{
    0%,100%{ text-shadow:0 0 18px rgba(245,185,66,.5); }
    50%{ text-shadow:0 0 34px rgba(245,185,66,.85); }
  }
  .finale-sub{ color:rgba(251,241,225,.75); margin-bottom:32px; font-size:1.05rem; max-width:420px; }

  /* ---------- particles ---------- */
  .confetti-piece{ position:fixed; width:8px;height:8px; border-radius:2px; pointer-events:none; z-index:500; animation:confettiFly .9s ease-out forwards; }
  @keyframes confettiFly{ to{ transform:translate(var(--dx),var(--dy)) rotate(var(--rot)); opacity:0; } }
  .confetti-fall{ position:fixed; top:-20px; width:8px;height:8px; border-radius:2px; pointer-events:none; z-index:500; animation:confettiFall 2.8s linear forwards; }
  @keyframes confettiFall{ to{ transform:translate(var(--drift,0px),110vh) rotate(340deg); opacity:.15; } }
  .smoke-puff{ position:fixed; width:14px;height:14px; border-radius:50%; background:rgba(255,255,255,.55); pointer-events:none; z-index:400; animation:smokeRise 1s ease-out forwards; }
  @keyframes smokeRise{ to{ transform:translateY(-40px) scale(2); opacity:0; } }

  /* ---------- responsive ---------- */
  @media (max-width:480px){
    .scene{ padding:88px 16px 50px; }
    .balloon-svg{ width:48px; }
    .cake-tier--bottom{ width:196px; }
    .cake-tier--top{ width:128px; }
    .cake-round{ width:150px;height:150px; }
    .letter-card{ padding:30px 22px; }
    .letter-line, .letter-sign{ font-size:1.35rem; }
    .letter-greeting{ font-size:1.6rem; }
  }

  @media (prefers-reduced-motion: reduce){
    *, *::before, *::after{
      animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important;
    }
  }
</style>
</head>
<body>

<noscript><div>This little celebration needs JavaScript turned on to unwrap properly 💌</div></noscript>

<div class="bg-lights" aria-hidden="true">
  <span class="orb orb--1"></span>
  <span class="orb orb--2"></span>
  <span class="orb orb--3"></span>
  <span class="orb orb--4"></span>
  <span class="orb orb--5"></span>
  <span class="orb orb--6"></span>
</div>

<button class="mute-btn" id="muteBtn" aria-label="Mute sound" aria-pressed="false"></button>
<div class="progress" id="progress" aria-hidden="true">
  <span class="dot" data-dot="2"></span>
  <span class="dot" data-dot="3"></span>
  <span class="dot" data-dot="4"></span>
  <span class="dot" data-dot="5"></span>
</div>

<main id="app">

  <section class="scene scene--cover active" data-scene="0">
    <div class="cover-card">
      <p class="eyebrow">admit one</p>
      <h1 class="cover-title">A Birthday, Just for <span id="coverName">Maya</span></h1>
      <p class="cover-sub" id="coverSub">No crowds. No small talk. Just you, six candles, and everyone who loves you.</p>
      <button class="btn btn--primary" id="openBtn"><span aria-hidden="true">🎁</span> Open your gift</button>
    </div>
  </section>

  <section class="scene scene--hero" data-scene="1">
    <div>
      <h2 class="hero-title">Happy Birthday, <span id="heroName">Maya</span>!</h2>
      <p class="hero-sub" id="heroSub">I built you a tiny party — just balloons, candlelight, and a few things I've been wanting to say.</p>
      <button class="btn btn--primary" data-next>Let's go ↓</button>
    </div>
  </section>

  <section class="scene scene--balloons" data-scene="2">
    <div class="scene-head">
      <p class="eyebrow">step one</p>
      <h2 class="scene-title">Pop every balloon</h2>
      <p class="scene-sub">each one's got something good inside</p>
    </div>
    <div class="balloon-field" id="balloonField"></div>
    <p class="counter" id="balloonCounter">0 of 6 popped</p>
  </section>

  <section class="scene scene--candles" data-scene="3">
    <div class="scene-head">
      <p class="eyebrow">step two</p>
      <h2 class="scene-title">Make a wish</h2>
      <p class="scene-sub">blow out every candle — tap each one</p>
    </div>
    <div class="cake-wrap">
      <div class="candles" id="candleField"></div>
      <div class="cake-body">
        <div class="cake-tier cake-tier--top"></div>
        <div class="cake-tier cake-tier--bottom"></div>
      </div>
      <div class="cake-plate"></div>
    </div>
    <p class="wish-msg" id="wishMsg">🌙 wish made. it's already coming true.</p>
  </section>

  <section class="scene scene--cake" data-scene="4">
    <div class="scene-head">
      <p class="eyebrow">step three</p>
      <h2 class="scene-title">Cut the first slice</h2>
      <p class="scene-sub">go on, it's tradition</p>
    </div>
    <button class="cake-round" id="cakeCutBtn" aria-label="Cut the cake">
      <span class="cake-half cake-half--left"></span>
      <span class="cake-half cake-half--right"></span>
      <span class="knife" aria-hidden="true">🔪</span>
    </button>
    <div class="fortune-note" id="fortuneNote">
      <p class="fortune-label">your fortune</p>
      <p class="fortune-text" id="fortuneText">This is going to be your best year yet.</p>
      <button class="btn btn--ghost" data-next>Continue →</button>
    </div>
  </section>

  <section class="scene scene--letter" data-scene="5">
    <p class="eyebrow">one more thing</p>
    <div class="letter-card" id="letterCard"></div>
    <button class="btn btn--primary" data-next>Keep going →</button>
  </section>

  <section class="scene scene--finale" data-scene="6">
    <h2 class="marquee">Happy Birthday<span class="marquee-name" id="finaleName">Maya</span>🎉</h2>
    <p class="finale-sub" id="finaleSub">This gift doesn't expire. Neither does how much you're loved.</p>
    <button class="btn btn--primary" id="replayBtn">Replay ↻</button>
  </section>

</main>

<script>
  /* ============ PERSONALIZE ME ============ */
  const CONFIG = {
    name: "Maya",
    fromName: "Me",
    ticketLine: "No crowds. No small talk. Just you, six candles, and everyone who loves you.",
    heroLine: "I built you a tiny party — just balloons, candlelight, and a few things I've been wanting to say.",
    fortune: "This is going to be your best year yet.",
    finaleLine: "This gift doesn't expire. Neither does how much you're loved.",
    balloonCount: 6,
    candleCount: 6,
    letter: [
      "Maya,",
      "I don't say this enough, so I'm saying it here, where you can't interrupt me: I'm really glad you exist.",
      "This year, I hope you laugh more than you plan for, rest more than you feel guilty about, and get exactly the birthday you deserve — today, and every day after.",
      "Here's to another year of you being exactly, wonderfully yourself."
    ]
  };
  /* ========================================= */

  const BALLOON_COLORS = ['#F5B942','#FF6F91','#8FD0B4','#FFE1A8','#E14F74','#FBF1E1'];
  const CONFETTI_COLORS = BALLOON_COLORS;

  const ICON_SOUND = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/></svg>';
  const ICON_MUTED = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>';

  const scenes = Array.from(document.querySelectorAll('.scene'));
  const progress = document.getElementById('progress');
  const dots = Array.from(document.querySelectorAll('.dot'));
  const muteBtn = document.getElementById('muteBtn');
  const coverName = document.getElementById('coverName');
  const coverSub = document.getElementById('coverSub');
  const openBtn = document.getElementById('openBtn');
  const heroName = document.getElementById('heroName');
  const heroSub = document.getElementById('heroSub');
  const balloonField = document.getElementById('balloonField');
  const balloonCounter = document.getElementById('balloonCounter');
  const candleField = document.getElementById('candleField');
  const wishMsg = document.getElementById('wishMsg');
  const cakeCutBtn = document.getElementById('cakeCutBtn');
  const fortuneNote = document.getElementById('fortuneNote');
  const fortuneText = document.getElementById('fortuneText');
  const letterCard = document.getElementById('letterCard');
  const finaleName = document.getElementById('finaleName');
  const finaleSub = document.getElementById('finaleSub');
  const replayBtn = document.getElementById('replayBtn');

  let currentScene = 0;
  let poppedCount = 0;
  let blownCount = 0;
  let cakeCutDone = false;
  let muted = false;
  let audioCtx = null;
  let finaleInterval = null;

  function escapeHTML(str){
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
  }

  /* ---------- audio ---------- */
  function ensureAudio(){
    if(!audioCtx){
      try{ audioCtx = new (window.AudioContext||window.webkitAudioContext)(); }
      catch(e){ audioCtx = null; }
    }
    if(audioCtx && audioCtx.state === 'suspended'){ audioCtx.resume(); }
  }
  function tone(freq, start, dur, type, peak){
    if(!audioCtx) return;
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.type = type || 'sine';
    osc.frequency.setValueAtTime(freq, audioCtx.currentTime + start);
    gain.gain.setValueAtTime(0, audioCtx.currentTime + start);
    gain.gain.linearRampToValueAtTime(peak || 0.15, audioCtx.currentTime + start + 0.02);
    gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + start + dur);
    osc.connect(gain).connect(audioCtx.destination);
    osc.start(audioCtx.currentTime + start);
    osc.stop(audioCtx.currentTime + start + dur + 0.05);
  }
  function playTone(name){
    if(muted) return;
    ensureAudio();
    if(!audioCtx) return;
    if(name==='pop'){ tone(880,0,.12,'triangle',0.16); tone(1320,.04,.1,'triangle',0.1); }
    else if(name==='blow'){ tone(300,0,.25,'sine',0.14); tone(180,.05,.3,'sine',0.1); }
    else if(name==='cut'){ tone(500,0,.08,'square',0.07); tone(700,.06,.08,'square',0.05); }
    else if(name==='chime'){ tone(523.25,0,.5,'sine',0.15); tone(659.25,.12,.5,'sine',0.13); tone(783.99,.24,.6,'sine',0.13); }
    else if(name==='open'){ tone(440,0,.15,'sine',0.12); tone(660,.08,.2,'sine',0.1); }
  }

  /* ---------- particles ---------- */
  function burstConfetti(x, y, count){
    for(let i=0;i<(count||10);i++){
      const p = document.createElement('span');
      p.className = 'confetti-piece';
      const angle = Math.random()*Math.PI*2;
      const dist = 36 + Math.random()*70;
      const dx = Math.cos(angle)*dist;
      const dy = Math.sin(angle)*dist - 26;
      p.style.setProperty('--dx', dx+'px');
      p.style.setProperty('--dy', dy+'px');
      p.style.setProperty('--rot', (Math.random()*360)+'deg');
      p.style.left = x+'px';
      p.style.top = y+'px';
      p.style.background = CONFETTI_COLORS[Math.floor(Math.random()*CONFETTI_COLORS.length)];
      document.body.appendChild(p);
      p.addEventListener('animationend', function(){ p.remove(); });
    }
  }
  function spawnFallingConfetti(x){
    const p = document.createElement('span');
    p.className = 'confetti-fall';
    p.style.left = x+'px';
    p.style.background = CONFETTI_COLORS[Math.floor(Math.random()*CONFETTI_COLORS.length)];
    p.style.setProperty('--drift', (Math.random()*60-30)+'px');
    document.body.appendChild(p);
    p.addEventListener('animationend', function(){ p.remove(); });
  }
  function spawnSmoke(x, y){
    const s = document.createElement('span');
    s.className = 'smoke-puff';
    s.style.left = (x-7)+'px';
    s.style.top = y+'px';
    document.body.appendChild(s);
    s.addEventListener('animationend', function(){ s.remove(); });
  }

  /* ---------- build interactive scenes ---------- */
  function buildBalloons(){
    balloonField.innerHTML = '';
    poppedCount = 0;
    for(let i=0;i<CONFIG.balloonCount;i++){
      const color = BALLOON_COLORS[i % BALLOON_COLORS.length];
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'balloon';
      btn.setAttribute('aria-label', 'Pop balloon '+(i+1));
      btn.style.setProperty('--float-delay', (i*0.35)+'s');
      btn.style.setProperty('--sway-dur', (3.6 + (i%3)*0.5)+'s');
      btn.innerHTML =
        '<svg class="balloon-svg" viewBox="0 0 60 68" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">'+
          '<ellipse cx="30" cy="28" rx="26" ry="30" fill="'+color+'"/>'+
          '<ellipse cx="20" cy="15" rx="6" ry="9" fill="rgba(255,255,255,.28)"/>'+
          '<path d="M25 56 L35 56 L30 65 Z" fill="'+color+'"/>'+
        '</svg>'+
        '<span class="balloon-string" aria-hidden="true"></span>';
      btn.addEventListener('click', function(){ popBalloon(btn); }, { once:true });
      balloonField.appendChild(btn);
    }
    balloonCounter.textContent = '0 of '+CONFIG.balloonCount+' popped';
  }
  function popBalloon(btn){
    if(btn.classList.contains('popped')) return;
    btn.classList.add('popped');
    const rect = btn.getBoundingClientRect();
    burstConfetti(rect.left+rect.width/2, rect.top+rect.height/2, 10);
    playTone('pop');
    poppedCount++;
    balloonCounter.textContent = poppedCount+' of '+CONFIG.balloonCount+' popped';
    if(poppedCount >= CONFIG.balloonCount){
      setTimeout(function(){ goToScene(3); }, 900);
    }
  }

  function buildCandles(){
    candleField.innerHTML = '';
    blownCount = 0;
    wishMsg.classList.remove('show');
    for(let i=0;i<CONFIG.candleCount;i++){
      const c = document.createElement('button');
      c.type = 'button';
      c.className = 'candle';
      c.setAttribute('aria-label', 'Blow out candle '+(i+1));
      c.style.setProperty('--flicker-delay', (i*0.22)+'s');
      c.innerHTML = '<span class="flame"></span><span class="wick"></span><span class="candle-stick"></span>';
      c.addEventListener('click', function(){ blowCandle(c); }, { once:true });
      candleField.appendChild(c);
    }
  }
  function blowCandle(c){
    if(c.classList.contains('out')) return;
    c.classList.add('out');
    const rect = c.getBoundingClientRect();
    spawnSmoke(rect.left+rect.width/2, rect.top);
    playTone('blow');
    blownCount++;
    if(blownCount >= CONFIG.candleCount){
      setTimeout(function(){
        wishMsg.classList.add('show');
        playTone('chime');
      }, 300);
      setTimeout(function(){ goToScene(4); }, 2200);
    }
  }

  function buildLetter(){
    let html = '';
    CONFIG.letter.forEach(function(line, i){
      const cls = i===0 ? 'letter-line letter-greeting' : 'letter-line';
      html += '<p class="'+cls+'" style="--i:'+i+'">'+escapeHTML(line)+'</p>';
    });
    html += '<p class="letter-sign" style="--i:'+CONFIG.letter.length+'">With love,<br>'+escapeHTML(CONFIG.fromName)+'</p>';
    letterCard.innerHTML = html;
  }
  function restartLetterAnim(){
    const els = letterCard.querySelectorAll('.letter-line, .letter-sign');
    els.forEach(function(el){
      el.style.animation = 'none';
      void el.offsetWidth;
      el.style.animation = '';
    });
  }

  cakeCutBtn.addEventListener('click', function(){
    if(cakeCutDone) return;
    cakeCutDone = true;
    cakeCutBtn.classList.add('cut');
    playTone('cut');
    setTimeout(function(){ fortuneNote.classList.add('show'); }, 350);
  });

  /* ---------- finale confetti ---------- */
  function startFinaleConfetti(){
    stopFinaleConfetti();
    finaleInterval = setInterval(function(){
      spawnFallingConfetti(Math.random()*window.innerWidth);
    }, 220);
  }
  function stopFinaleConfetti(){
    if(finaleInterval){ clearInterval(finaleInterval); finaleInterval = null; }
  }

  /* ---------- navigation ---------- */
  function updateProgress(n){
    progress.style.opacity = (n>=2 && n<=5) ? '1' : '0';
    dots.forEach(function(d){
      const val = parseInt(d.dataset.dot, 10);
      d.classList.toggle('is-active', val===n);
      d.classList.toggle('is-done', val<n);
    });
  }
  function goToScene(n){
    scenes.forEach(function(s){ s.classList.remove('active'); });
    scenes[n].classList.add('active');
    currentScene = n;
    updateProgress(n);
    if(n===5){ restartLetterAnim(); }
    if(n===6){ startFinaleConfetti(); } else { stopFinaleConfetti(); }
    window.scrollTo(0,0);
  }

  function applyConfig(){
    document.title = 'A Birthday, Just for '+CONFIG.name+' 🎂';
    coverName.textContent = CONFIG.name;
    coverSub.textContent = CONFIG.ticketLine;
    heroName.textContent = CONFIG.name;
    heroSub.textContent = CONFIG.heroLine;
    fortuneText.textContent = CONFIG.fortune;
    finaleName.textContent = CONFIG.name;
    finaleSub.textContent = CONFIG.finaleLine;
  }

  function resetExperience(){
    buildBalloons();
    buildCandles();
    cakeCutDone = false;
    cakeCutBtn.classList.remove('cut');
    fortuneNote.classList.remove('show');
    goToScene(0);
  }

  muteBtn.addEventListener('click', function(){
    muted = !muted;
    muteBtn.setAttribute('aria-pressed', String(muted));
    muteBtn.innerHTML = muted ? ICON_MUTED : ICON_SOUND;
  });

  openBtn.addEventListener('click', function(){
    playTone('open');
    goToScene(1);
  });

  document.querySelectorAll('[data-next]').forEach(function(btn){
    btn.addEventListener('click', function(){ goToScene(currentScene+1); });
  });

  replayBtn.addEventListener('click', resetExperience);

  /* ---------- init ---------- */
  muteBtn.innerHTML = ICON_SOUND;
  applyConfig();
  buildLetter();
  buildBalloons();
  buildCandles();
  goToScene(0);
</script>
</body>
</html>