
  /* ── typed-invitation intro overlay ───────────────────
     A blank #FAFAFA sheet with a blinking caret types a personal note,
     selects it, retypes the formal invitation line, then dissolves to
     reveal the live hero. */
  body.intro-active{ cursor: default; height: 100%; overflow: hidden; }  /* hold the page still while the note types */

  #intro{
    position: fixed; inset: 0; z-index: 5;
    display: flex; align-items: center; justify-content: center;
    padding: 0 28px;
    background: var(--paper);
    transition: opacity .75s ease;
    -webkit-user-select: none; user-select: none;   /* the typed note can't be highlighted as it types */
    -webkit-touch-callout: none;                     /* no iOS long-press selection menu */
  }
  body.revealed #intro{ opacity: 0; pointer-events: none; }

  #introLine{
    font-family: 'LF Serif', 'Fraunces', Georgia, 'Times New Roman', serif;
    font-optical-sizing: auto;
    font-weight: 380;
    font-size: clamp(40px, 9vw, 100px);
    letter-spacing: -0.01em;
    line-height: 1.18;
    color: var(--ink);
    text-align: center;
    white-space: pre-line;           /* honour the explicit "\n" the typer inserts after "Welcome" so it always sits on its own row, the rest below — on phone AND laptop */
    overflow-wrap: break-word;       /* wrap only between words — never split a word mid-glyph */
    word-break: keep-all;
    transform: translateY(-2vh);
    transition: filter .7s ease, transform .7s ease;
  }
  body.revealed #introLine{ filter: blur(20px); transform: translateY(-2vh) scale(1.04); }
  #caret{
    /* sized to the line box (1em, baseline-aligned) so it can never inflate the
       line or get pushed onto a line of its own at a wrap boundary — it stays
       welded beside the last typed glyph. */
    display: inline-block;
    width: 1px; height: 1em;
    margin-left: .09em;
    vertical-align: -0.12em;
    background: var(--ink);
    animation: caretBlink 1s ease 0s infinite;
  }
  #caret.solid{ animation: none; opacity: 1; }
  @keyframes caretBlink{ 0%,40%{ opacity: 0; } 49%,100%{ opacity: 1; } }

  /* ── hero entrance (plays as the intro dissolves) ────── */
  .nav{
    opacity: 0; transform: translateY(12px); filter: blur(7px);
    transition: opacity .85s ease, transform .9s cubic-bezier(.2,.7,.2,1), filter .9s ease;
  }
  body.revealed .nav  { opacity: 1; transform: none; filter: blur(0); transition-delay: .50s; }

  /* ── ambient-audio toggle ─────────────────────────────────
     Figma "Frame 4": a flat #F2F2F2 pill (1px #EBEBEB stroke) that morphs
     between "playing" (speaker + waves) and "muted" (speaker + slash). On the
     hero it sits centred along the bottom; the instant the page starts to
     scroll it glides into the bottom-right corner. No hover lift / shadow. */
  .audio-dock{
    position: fixed;
    --m:  clamp(18px, 2.4vw, 30px);   /* resting corner margin */
    /* lift the pill clear of the home-indicator / rounded corners on notched
       phones; env() is 0 elsewhere so the desktop corner is unchanged. The
       centring math below reads --mr so the centred hero state never jumps. */
    --mr: max(var(--m), env(safe-area-inset-right));
    --mb: max(var(--m), env(safe-area-inset-bottom));
    --bw: 49.5px;                     /* pill width  (kept in sync with .audio-toggle) */
    --bh: 36px;                       /* pill height */
    right: var(--mr);
    bottom: var(--mb);
    z-index: 4;
    opacity: 0;
    filter: blur(7px);
    /* hero state: shift left from the corner to the viewport centre */
    transform: translateX(calc(var(--mr) + var(--bw) / 2 - 50vw));
    transition: opacity .85s ease, filter .9s ease,
                transform .7s cubic-bezier(.2,.7,.2,1);
  }
  /* reveal keeps the centred offset — only fade + focus animate (delayed) */
  body.revealed .audio-dock{ opacity: 1; filter: blur(0); transition-delay: .6s, .6s, 0s; }
  /* scrolling away from the hero glides the pill into its resting corner */
  body.scrolled .audio-dock{ transform: translateX(0); }

  .audio-toggle{
    position: relative;
    display: grid;
    place-items: center;
    width: var(--bw);               /* 1.375 : 1 — Figma pill ratio, scaled to 75% */
    height: var(--bh);
    padding: 0;
    border: 1px solid #EBEBEB;      /* hairline stroke, no shadow */
    border-radius: 999px;           /* fully rounded (Figma: 499.5px) */
    background: #F2F2F2;             /* Figma background */
    cursor: pointer;
    pointer-events: auto;
    -webkit-tap-highlight-color: transparent;
  }
  /* hover is deliberately inert — no lift / offset, no shadow change */
  .audio-toggle:focus-visible{ outline: 2px solid rgba(11,11,12,.45); outline-offset: 3px; }
  /* touch: a quick press response (no hover to lean on), and an invisible hit
     expander so the small Figma pill is still a comfortable ≥44px tap target */
  .audio-toggle{ transition: transform .18s ease; }
  .audio-toggle:active{ transform: scale(.9); }
  @media (pointer: coarse){
    .audio-toggle::after{ content:""; position:absolute; inset:-7px; }
  }

  /* the two morphing icon layers, stacked dead-centre */
  .audio-toggle .ic{
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    transition: opacity .5s ease, filter .5s ease, transform .6s cubic-bezier(.2,.7,.2,1);
    /* no permanent will-change: the .5s transition promotes the layer transiently on toggle */
  }
  .audio-toggle .ic svg{ display: block; height: 17px; width: auto; }

  /* default = sound ON: waves sharp, slash blurred away */
  .audio-toggle .ic-on { opacity: 1; filter: blur(0);   transform: scale(1); }
  .audio-toggle .ic-off{ opacity: 0; filter: blur(7px); transform: scale(.55) rotate(-12deg); }
  /* muted = sound OFF: slash sharp, waves blurred away */
  .audio-toggle.is-muted .ic-on { opacity: 0; filter: blur(7px); transform: scale(.55) rotate(12deg); }
  .audio-toggle.is-muted .ic-off{ opacity: 1; filter: blur(0);   transform: scale(1); }

  /* reduced motion / no-JS: skip straight to the hero */
  body.instant #intro{ display: none; }
  body.instant #gl{ opacity: 1 !important; filter: none !important; transition: none !important; }
  body.instant .nav{
    opacity: 1 !important; transform: none !important; filter: none !important; transition: none !important;
  }
  /* keep the dock's placement transform (centred / corner); just kill the motion */
  body.instant .audio-dock{
    opacity: 1 !important; filter: none !important; transition: none !important;
  }

  /* honour reduced-motion: swap the icons instantly instead of morphing */
  @media (prefers-reduced-motion: reduce){
    .audio-toggle .ic{ transition: opacity .12s ease; filter: none !important; }
    .audio-toggle .ic-off,
    .audio-toggle.is-muted .ic-on{ transform: none; }
    .audio-toggle .ic-on,
    .audio-toggle.is-muted .ic-off{ transform: none; }
  }

  @media (max-width: 560px){
    .nav{ gap:8px; }
    .audio-dock{ --bw: 45px; --bh: 33px; }    /* keeps the centring math + pill in sync */
    .audio-toggle .ic svg{ height: 16px; }
  }
