/* ============================================================
 * Voice Agent Hero, verbatim from the user's voice-light.jsx
 * artifact (1272 lines). Adaptations:
 *   1. FUCHSIA = brand #ED4D86 (was #CE5188)
 *      FUCHSIA_DEEP = #B8336A (slightly darker brand)
 *      All hardcoded rgba(206, 81, 136, …) calls swapped to
 *      rgba(237, 77, 134, …) so the soft pinks match.
 *   2. CALL[i].t and dur replaced with real recorded audio
 *      durations (16-turn recording at /marketing/voice-hero-aria/turn-NN.wav).
 *   3. Audio playback wired into usePlayback, when sound is
 *      enabled, AudioContext schedules all 16 buffers anchored
 *      to the visual `time` clock; pause stops sources, replay
 *      resets, seek re-anchors. Visual remains identical.
 *   4. H1 / H2 / lead reflect the user's chosen copy
 *      ("The voice in front of every call." / italic
 *       "Fluent, available, everywhere." / "A true concierge
 *       agent that handles the calls your team can't…").
 *   5. window.VoiceHeroAria export so platform/voice.html
 *      keeps using the same name.
 * ============================================================ */

const { useState, useEffect, useRef, useMemo } = React;

const FUCHSIA = "#ED4D86";
const FUCHSIA_DEEP = "#B8336A";
const FUCHSIA_RGB = "237, 77, 134";
const PINK_SOFT = "#F5C8D8";
const INK = "#1a1a1a";
const DIM = "#6e6363";
const FAINT = "#a99fa3";
const RULE = "#e3ddcf";
const PAPER = "#fbfaf6";
const CREAM = "#f6f4ef";
const SURFACE = "#ffffff";
const SOFT_BG = `rgba(${FUCHSIA_RGB}, 0.05)`;
const BORDER_HOT = `rgba(${FUCHSIA_RGB}, 0.28)`;

/* ── audio source paths ── */
const AUDIO_PATHS = Array.from({ length: 16 }, (_, i) =>
  `/marketing/voice-hero-aria/turn-${String(i).padStart(2, "0")}.wav`
);

/* ────────────────────────── conversation script ──────────────────────────
   t / dur measured from the actual recorded WAV files (24kHz mono).
   ~0.4s gap between turns. CALL_END is the cumulative end. */
const CALL = [
  { t: 0.00,   spk: "agent", dur: 6.18,
    text: "Welcome to Olea Group. I'm Aria, your booking assistant. How can I help you today?" },
  { t: 6.58,   spk: "guest", dur: 4.05,
    text: "Hi, I'm planning my anniversary trip and I had a few questions before booking.",
    memory: [{ key: "Trip purpose", value: "Anniversary celebration", confidence: 0.97, delay: 1.4 }] },
  { t: 11.03,  spk: "agent", dur: 6.05,
    text: "Of course. I'd love to help you plan something special. What would you like to know?" },
  { t: 17.48,  spk: "guest", dur: 3.54,
    text: "My partner is vegan. Do any of your restaurants have a proper vegan menu?",
    memory: [{ key: "Dietary", value: "Partner is vegan", confidence: 0.99, delay: 1.0 }] },
  { t: 21.42,  spk: "agent", dur: 10.46,
    text: "Yes. At our Olea Cliffside property, the restaurant Verde serves a full vegan tasting menu, eight courses, refreshed seasonally. I can send it to you.",
    kb: { title: "Verde · vegan tasting menu", label: "F&B · Vegan menus", source: "verde_menu_q2.pdf", snippet: "8 courses · seasonal · €145pp", confidence: 0.94 } },
  { t: 32.28,  spk: "guest", dur: 2.56,
    text: "Perfect. And does the spa offer couples treatments?",
    memory: [{ key: "Travel party", value: "Couple · 2 adults", confidence: 0.96, delay: 0.8 }] },
  { t: 35.24,  spk: "agent", dur: 11.57,
    text: "It does. The Aphrodite Ritual is our signature couples experience, ninety minutes, with a private hammam, massage and champagne service.",
    kb: { title: "Aphrodite Ritual · 90 min", label: "Spa · Couples treatments", source: "spa_menu_2026.pdf", snippet: "Hammam · massage · champagne", confidence: 0.97 } },
  { t: 47.21,  spk: "guest", dur: 8.32,
    text: "That sounds wonderful. Actually, can you recommend an adult-only hotel in the Canary Islands? Gym, wellness, near the beach, with good restaurants around?",
    memory: [
      { key: "Region", value: "Canary Islands", confidence: 0.99, delay: 2.2 },
      { key: "Constraints", value: "Adult-only · wellness · beach · restaurants", confidence: 0.95, delay: 5.0 },
    ] },
  { t: 55.93,  spk: "agent", dur: 3.91,
    text: "I have three excellent matches in our group. Let me share them.",
    search: { q: "canary islands · adult-only · wellness · beach · 4★+" } },
  { t: 60.24,  spk: "agent", dur: 20.63,
    text: "Olea Cliffside in Tenerife, twelve-hundred-square-meter spa, fifty-meter infinity pool, two minutes from Playa del Duque. Zafira Marina on Lanzarote, on the beach itself with a yoga pavilion. And Sunset Beach in Fuerteventura, the most secluded of the three.",
    showHotels: true },
  { t: 81.27,  spk: "guest", dur: 4.70,
    text: "Olea Cliffside sounds amazing. Can you check availability for next weekend, two nights?",
    memory: [{ key: "Stay window", value: "May 16–18 · 2 nights · 2 adults", confidence: 0.98, delay: 2.2 }] },
  { t: 86.37,  spk: "agent", dur: 15.01,
    text: "Checking now. I have three rate options. A Sea View Suite at six-hundred-forty per night. A Cliff Suite with private terrace at eight-hundred-twenty. And the Aphrodite Suite, which includes the couples ritual, at eleven-hundred-eighty.",
    bookingQuery: { hotel: "Olea Cliffside", dates: "May 16–18 · 2 nights", guests: "2 adults" },
    showRates: true },
  { t: 101.78, spk: "guest", dur: 3.21,
    text: "Let's go with the Cliff Suite. Can you send me the details?",
    memory: [{ key: "Rate selected", value: "Cliff Suite · €820/night", confidence: 0.99, delay: 1.2 }] },
  { t: 105.39, spk: "agent", dur: 10.64,
    text: "I'll send everything to your email now, the booking link, vegan menu preview and spa availability. You'll have it in about thirty seconds.",
    composeEmail: true },
  { t: 116.43, spk: "guest", dur: 1.91,
    text: "Amazing. Thank you, Aria." },
  { t: 118.74, spk: "agent", dur: 3.77,
    text: "My pleasure. I'll be right here if you need to make any changes." },
];
const CALL_END = 122.51;

function deriveActions() {
  const out = [];
  CALL.forEach((u) => {
    if (u.memory) {
      u.memory.forEach(m => out.push({ t: u.t + (m.delay || 0.5), kind: "memory", key: m.key, value: m.value, confidence: m.confidence, dur: 1.4 }));
    }
    if (u.kb) out.push({ t: u.t - 0.4, kind: "kb", label: u.kb.label, source: u.kb.source, dur: 1.6 });
    if (u.search) out.push({ t: u.t + 0.6, kind: "search", q: u.search.q, dur: 4.0 });
    if (u.showHotels) {
      // scaled to 20.63s real audio (artifact: 12s → factors 1.72)
      out.push({ t: u.t + 0.4,  kind: "hotel", id: "olea",   dur: 0.4 });
      out.push({ t: u.t + 7.0,  kind: "hotel", id: "zafira", dur: 0.4 });
      out.push({ t: u.t + 14.0, kind: "hotel", id: "sunset", dur: 0.4 });
    }
    if (u.bookingQuery) out.push({ t: u.t + 0.4, kind: "bookq", ...u.bookingQuery, dur: 3.0 });
    if (u.showRates) {
      // scaled to 15.01s real audio (artifact: 13.6s → factor 1.10)
      out.push({ t: u.t + 4.6,  kind: "rate", idx: 0, dur: 0.4 });
      out.push({ t: u.t + 7.9,  kind: "rate", idx: 1, dur: 0.4 });
      out.push({ t: u.t + 11.4, kind: "rate", idx: 2, dur: 0.4 });
    }
    if (u.composeEmail) {
      // scaled to 10.64s real audio (artifact: 9.4s → factor 1.13)
      out.push({ t: u.t + 0.4, kind: "email", phase: "compose", dur: 4.0 });
      out.push({ t: u.t + 6.8, kind: "email", phase: "sent",    dur: 0.4 });
    }
  });
  out.sort((a, b) => a.t - b.t);
  return out;
}
const ACTIONS = deriveActions();

// reasoning icons (no emoji)
function ActionIcon({ kind, color }) {
  const c = color;
  if (kind === "kb") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M5 4a2 2 0 0 1 2-2h12v18H7a2 2 0 0 0-2 2z"/><path d="M9 7h7M9 11h7M9 15h5"/></svg>
  );
  if (kind === "memory") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3a4 4 0 0 1 4 4v3a4 4 0 0 0 1.5 3.1L18 14H6l.5-.9A4 4 0 0 0 8 10V7a4 4 0 0 1 4-4z"/><path d="M9.5 17a2.5 2.5 0 0 0 5 0"/></svg>
  );
  if (kind === "search") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.5-4.5"/></svg>
  );
  if (kind === "hotel") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinejoin="round"><polygon points="12,3 21,12 12,21 3,12"/></svg>
  );
  if (kind === "bookq") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill={c} stroke="none"><polygon points="6,4 20,12 6,20"/></svg>
  );
  if (kind === "rate") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7" fill={c}/></svg>
  );
  if (kind === "email") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg>
  );
  if (kind === "emailSent") return (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="4,12 10,18 20,6"/></svg>
  );
  return null;
}

function describeAction(a) {
  if (a.kind === "memory") return {
    icon: "memory", label: "Memory saved", color: "#8B5BC8",
    title: a.value, detail: `Key · ${a.key}`,
    confidence: a.confidence,
  };
  if (a.kind === "kb") return {
    icon: "kb", label: "Found in knowledge base", color: FUCHSIA,
    title: a.title || a.label,
    detail: a.snippet ? `${a.source} · ${a.snippet}` : a.source,
    confidence: a.confidence,
  };
  if (a.kind === "search") return {
    icon: "search", label: "Property search", color: FUCHSIA_DEEP,
    title: a.q, detail: "26 properties scanned · 3 strong matches",
  };
  if (a.kind === "hotel") return {
    icon: "hotel", label: "Match found", color: FUCHSIA,
    title: HOTELS[a.id].name, detail: `${HOTELS[a.id].loc} · ${HOTELS[a.id].tag}`,
  };
  if (a.kind === "bookq") return {
    icon: "bookq", label: "Booking engine query", color: INK,
    title: a.hotel, detail: `${a.dates} · ${a.guests}`,
  };
  if (a.kind === "rate") return {
    icon: "rate", label: "Rate received", color: FUCHSIA,
    title: RATES[a.idx].name, detail: `€${RATES[a.idx].price}/night · ${RATES[a.idx].desc.split(" · ")[0]}`,
  };
  if (a.kind === "email") return {
    icon: a.phase === "sent" ? "emailSent" : "email",
    label: a.phase === "sent" ? "Email delivered" : "Composing email",
    color: a.phase === "sent" ? "#2f7d6b" : FUCHSIA,
    title: "guest@anniversary.com",
    detail: a.phase === "sent" ? "Sent · 0.4s · 3 attachments" : "Cliff Suite · vegan menu · spa booking window",
  };
  return { icon: "kb", label: "Event", color: DIM, title: "" };
}

function ListeningPlaceholder({ time }) {
  return (
    <div style={{
      padding: "22px 14px", background: `rgba(${FUCHSIA_RGB}, 0.04)`,
      border: `1px dashed ${BORDER_HOT}`, borderRadius: 10,
      display: "flex", flexDirection: "column", gap: 10, alignItems: "center",
    }}>
      <div style={{ display: "flex", gap: 4, alignItems: "center", height: 22 }}>
        {[0,1,2,3,4,5,6].map(i => {
          const v = Math.sin(time * 4 + i * 0.55) * 0.5 + 0.5;
          return <span key={i} style={{
            width: 2, height: 4 + v * 18, background: FUCHSIA,
            borderRadius: 1, opacity: 0.4 + v * 0.6,
          }} />;
        })}
      </div>
      <span style={{
        fontFamily: "'JetBrains Mono', ui-monospace, monospace",
        fontSize: 10, color: FUCHSIA, letterSpacing: "0.18em",
        textTransform: "uppercase", fontWeight: 600,
      }}>Listening for intent</span>
      <span style={{
        fontFamily: "'Source Serif 4', Georgia, serif",
        fontSize: 12.5, color: DIM, fontStyle: "italic", textAlign: "center",
        lineHeight: 1.4, maxWidth: 200,
      }}>Memories form here as the guest speaks.</span>
    </div>
  );
}

const HOTELS = {
  olea:   { name: "Olea Cliffside",  loc: "Tenerife",     stars: "★★★★★", price: 640, tag: "Best match" },
  zafira: { name: "Zafira Marina",   loc: "Lanzarote",    stars: "★★★★★", price: 510, tag: "On the beach" },
  sunset: { name: "Sunset Beach",    loc: "Fuerteventura", stars: "★★★★",  price: 460, tag: "Most secluded" },
};
const RATES = [
  { name: "Sea View Suite",  desc: "King bed · 48m² · sea-facing balcony", price: 640 },
  { name: "Cliff Suite",     desc: "King bed · 64m² · private terrace · plunge pool", price: 820, recommended: true },
  { name: "Aphrodite Suite", desc: "Includes Aphrodite Ritual for two · 86m² · butler", price: 1180 },
];

/* ── Audio playback hook (visual time clock + AudioContext scheduler) ── */
function useAudioBuffers() {
  const [buffers, setBuffers] = useState({});
  const audioCtxRef = useRef(null);
  const requestedRef = useRef(false);

  function ensure() {
    if (audioCtxRef.current) return audioCtxRef.current;
    try {
      audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)();
      if (!requestedRef.current) {
        requestedRef.current = true;
        AUDIO_PATHS.forEach((p, i) => {
          fetch(p).then(r => r.arrayBuffer())
            .then(ab => audioCtxRef.current.decodeAudioData(ab))
            .then(buf => setBuffers(b => ({ ...b, [i]: buf })))
            .catch(() => {});
        });
      }
    } catch(_) {}
    return audioCtxRef.current;
  }

  return { buffers, audioCtxRef, ensure };
}

function usePlayback({ audioCtxRef, buffers, soundOn }) {
  const [time, setTime] = useState(0);
  const [playing, setPlaying] = useState(true);
  const lastRef = useRef(performance.now());
  const sourcesRef = useRef([]);
  const seekTokenRef = useRef(0);
  const [seekToken, setSeekToken] = useState(0);

  /* RAF visual clock */
  useEffect(() => {
    let raf;
    const loop = (now) => {
      const dt = (now - lastRef.current) / 1000;
      lastRef.current = now;
      if (playing) {
        setTime(t => {
          const nt = t + dt;
          if (nt >= CALL_END) return 0;
          return nt;
        });
      }
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame((n) => { lastRef.current = n; loop(n); });
    return () => cancelAnimationFrame(raf);
  }, [playing]);

  /* Audio scheduling: when sound on + playing, schedule all 16 turns
     starting from current `time`. Re-runs on play/pause/sound/seek. */
  useEffect(() => {
    // stop any existing sources
    sourcesRef.current.forEach(s => { try { s.onended = null; s.stop(0); s.disconnect(); } catch(_){} });
    sourcesRef.current = [];

    const ctx = audioCtxRef.current;
    if (!soundOn || !playing || !ctx) return;
    if (ctx.state === "suspended") ctx.resume().catch(() => {});

    const ctxNow = ctx.currentTime + 0.05;
    const baseCtx = ctxNow - time; // ctx time for visual t=0

    CALL.forEach((u, i) => {
      const buf = buffers[i];
      if (!buf) return;
      const startCtx = baseCtx + u.t;
      const endCtx   = startCtx + buf.duration;
      if (endCtx <= ctx.currentTime) return; // entirely past

      const offsetIntoBuffer = Math.max(0, ctx.currentTime - startCtx);
      const realStart        = Math.max(ctx.currentTime, startCtx);
      const src = ctx.createBufferSource();
      src.buffer = buf;
      src.connect(ctx.destination);
      try { src.start(realStart, offsetIntoBuffer); } catch(_) {}
      sourcesRef.current.push(src);
    });

    return () => {
      sourcesRef.current.forEach(s => { try { s.onended = null; s.stop(0); s.disconnect(); } catch(_){} });
      sourcesRef.current = [];
    };
  }, [playing, soundOn, seekToken, audioCtxRef, buffers]);

  function setTimeAndReseek(t) {
    setTime(t);
    seekTokenRef.current += 1;
    setSeekToken(seekTokenRef.current);
  }
  function replay() {
    setTimeAndReseek(0);
    setPlaying(true);
  }

  return { time, setTime: setTimeAndReseek, playing, setPlaying, replay };
}

function currentSpeaker(time) {
  for (let i = 0; i < CALL.length; i++) {
    const u = CALL[i];
    if (time >= u.t && time < u.t + u.dur) return { utter: u, idx: i };
  }
  return null;
}

/* ────────────────────────── 2D pulse · soft pink halos + mic core ────────────────────────── */
function VoicePulse({ time, speakerKind, amp, playing, onToggle }) {
  const isAgent = speakerKind === "agent";
  const isGuest = speakerKind === "guest";
  const isLive  = isAgent || isGuest;

  const HALOS = [
    { base: 76,  op: 0.32 },
    { base: 110, op: 0.22 },
    { base: 144, op: 0.14 },
    { base: 178, op: 0.08 },
    { base: 212, op: 0.05 },
  ];
  const SONAR = 3;
  const SONAR_PERIOD = 2.6;
  const SONAR_MIN = 50;
  const SONAR_MAX = 220;

  const coreScale = 1 + (isLive ? amp * 0.06 + Math.sin(time * 6) * 0.03 : 0);

  return (
    <div style={{
      position: "relative", width: 460, height: 460,
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <svg width="460" height="460" viewBox="0 0 460 460" style={{ position: "absolute", inset: 0 }}>
        {HALOS.map((h, i) => {
          const breath = Math.sin(time * 1.3 + i * 0.6) * 0.5 + 0.5;
          const r = h.base + breath * (isLive ? 8 : 3);
          const op = h.op * (isLive ? 0.85 + breath * 0.3 : 0.55 + breath * 0.15);
          return (
            <circle key={i} cx="230" cy="230" r={r}
              fill={FUCHSIA} fillOpacity={op}
            />
          );
        })}

        {isLive && Array.from({ length: SONAR }).map((_, i) => {
          const phase = ((time / SONAR_PERIOD) + i / SONAR) % 1;
          const r = SONAR_MIN + phase * (SONAR_MAX - SONAR_MIN);
          const fadeIn = Math.min(1, phase / 0.08);
          const fadeOut = (1 - phase) * (1 - phase);
          const op = 0.55 * fadeIn * fadeOut;
          return (
            <circle key={i} cx="230" cy="230" r={r}
              fill="none" stroke={FUCHSIA} strokeOpacity={op}
              strokeWidth={1.2 - phase * 0.5}
            />
          );
        })}
      </svg>

      <button onClick={onToggle} aria-label={playing ? "Pause call" : "Start call"}
        style={{
          position: "relative", zIndex: 2,
          width: 92, height: 92, borderRadius: "50%",
          background: FUCHSIA,
          border: "none",
          cursor: "pointer",
          display: "flex", alignItems: "center", justifyContent: "center",
          transform: `scale(${coreScale})`,
          transition: "transform 0.15s ease, background 0.2s ease",
          boxShadow: `0 14px 32px -10px ${FUCHSIA}AA, 0 0 0 1px ${FUCHSIA_DEEP}30`,
        }}
        onMouseEnter={e => e.currentTarget.style.background = FUCHSIA_DEEP}
        onMouseLeave={e => e.currentTarget.style.background = FUCHSIA}
      >
        {playing ? (
          <svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
            <rect x="9" y="3" width="6" height="12" rx="3" fill="#fff" stroke="none" />
            <path d="M5 11a7 7 0 0 0 14 0" />
            <line x1="12" y1="18" x2="12" y2="22" />
          </svg>
        ) : (
          <svg width="30" height="30" viewBox="0 0 24 24" fill="#fff">
            <polygon points="6,4 20,12 6,20" />
          </svg>
        )}
      </button>

      <div style={{
        position: "absolute", bottom: 36,
        display: "flex", alignItems: "center", gap: 8,
        fontFamily: "'Source Serif 4', Georgia, serif",
        fontSize: 15, fontStyle: "italic",
        color: isLive ? INK : DIM,
      }}>
        <span style={{
          width: 6, height: 6, borderRadius: "50%",
          background: isLive ? FUCHSIA : FAINT,
          animation: isLive ? "vha-dot 1.2s ease-in-out infinite" : "none",
        }} />
        {!playing
          ? "Call paused"
          : isAgent ? "Aria is speaking…"
          : isGuest ? "Listening to caller…"
          : "Standing by…"}
      </div>
    </div>
  );
}

/* ────────────────────────── transcript ────────────────────────── */
function Transcript({ time }) {
  const visibleLines = CALL.map((u, i) => {
    if (time < u.t) return { u, i, words: [], state: "future" };
    const words = u.text.split(" ");
    const elapsed = time - u.t;
    const frac = Math.min(1, elapsed / u.dur);
    const n = Math.ceil(frac * words.length);
    const state = time < u.t + u.dur ? "current" : "past";
    return { u, i, words: words.slice(0, n), totalWords: words, state };
  }).filter(l => l.state !== "future");
  /* Reverse for column-reverse layout: newest bubble is rendered first in DOM
     and pinned to the BOTTOM of the container, so the actively-spoken turn is
     always visible. Older bubbles overflow at the TOP and the mask fades them. */
  const last3 = visibleLines.slice(-4).slice().reverse();

  return (
    <div style={{
      display: "flex", flexDirection: "column-reverse", gap: 14,
      maxHeight: 420, overflow: "hidden",
      maskImage: "linear-gradient(to top, #000 65%, transparent 100%)",
      WebkitMaskImage: "linear-gradient(to top, #000 65%, transparent 100%)",
    }}>
      {last3.map((l, idx) => {
        const isAgent = l.u.spk === "agent";
        const isCur = l.state === "current";
        const align = isAgent ? "flex-start" : "flex-end";
        return (
          <div key={l.i} style={{ display: "flex", flexDirection: "column", alignItems: align }}>
            <div style={{
              display: "flex", alignItems: "center", gap: 8, marginBottom: 5,
              flexDirection: isAgent ? "row" : "row-reverse",
            }}>
              <span style={{
                width: 6, height: 6, borderRadius: "50%",
                background: isAgent ? FUCHSIA : INK,
                opacity: isCur ? 1 : 0.4,
                animation: isCur ? "vha-dot 1.4s ease-in-out infinite" : "none",
              }} />
              <span style={{
                fontFamily: "'JetBrains Mono', ui-monospace, monospace",
                fontSize: 9.5, color: isAgent ? FUCHSIA : INK,
                opacity: isCur ? 1 : 0.55,
                letterSpacing: "0.18em", textTransform: "uppercase", fontWeight: 500,
              }}>{isAgent ? "Aria" : "Guest"}</span>
              <span style={{
                fontFamily: "'JetBrains Mono', ui-monospace, monospace",
                fontSize: 9.5, color: FAINT,
                fontVariantNumeric: "tabular-nums",
              }}>
                {fmt(l.u.t)}
              </span>
            </div>
            <div style={{
              maxWidth: "78%",
              padding: "13px 18px",
              borderRadius: 14,
              borderTopLeftRadius:  isAgent ? 4 : 14,
              borderTopRightRadius: isAgent ? 14 : 4,
              background: isAgent
                ? `linear-gradient(135deg, rgba(${FUCHSIA_RGB}, 0.08), rgba(${FUCHSIA_RGB}, 0.02))`
                : "#fff",
              border: `1px solid ${isAgent ? `rgba(${FUCHSIA_RGB}, 0.22)` : RULE}`,
              fontFamily: "'Source Serif 4', Georgia, serif",
              fontSize: 18, lineHeight: 1.42,
              color: isCur ? INK : "rgba(26, 26, 26, 0.42)",
              fontStyle: isAgent ? "italic" : "normal",
              fontWeight: isAgent ? 400 : 400,
              transition: "color 0.4s ease",
              animation: idx === 0 ? "vha-fadein 0.35s ease" : "none",
              boxShadow: isCur ? "0 8px 22px -16px rgba(20,14,10,0.18)" : "none",
            }}>
              {l.words.map((w, k) => (
                <span key={k} style={{
                  display: "inline",
                  animation: isCur && k === l.words.length - 1 ? "vha-word 0.18s ease" : "none",
                }}>
                  {w}{k < l.words.length - 1 ? " " : ""}
                </span>
              ))}
              {isCur && l.words.length < l.totalWords.length && (
                <span style={{
                  display: "inline-block",
                  width: 7, height: 16,
                  marginLeft: 4,
                  background: isAgent ? FUCHSIA : INK,
                  verticalAlign: "middle",
                  animation: "vha-blink 0.7s steps(2) infinite",
                }} />
              )}
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* ────────────────────────── action log ────────────────────────── */
function ActionLog({ time }) {
  const all = ACTIONS.filter(a => time >= a.t);
  if (all.length === 0) return <ListeningPlaceholder time={time} />;
  const items = all.slice(-7);
  const MONO = "'JetBrains Mono', ui-monospace, monospace";
  const SERIF = "'Source Serif 4', Georgia, serif";

  return (
    <div style={{ position: "relative" }}>
      <div style={{
        position: "absolute", left: 17.5, top: 10, bottom: 10,
        width: 1, backgroundImage: `linear-gradient(${RULE} 60%, transparent 0)`,
        backgroundSize: "1px 5px",
      }} />
      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        {items.map((a, i) => {
          const isCurrent = time < a.t + (a.dur || 1.5) + 1.6 && time >= a.t;
          const m = describeAction(a);
          return (
            <div key={i + "_" + a.t} style={{
              position: "relative", paddingLeft: 36,
              animation: "vha-slidein 0.35s ease",
              opacity: isCurrent ? 1 : 0.7,
              transition: "opacity 0.4s ease",
            }}>
              <div style={{
                position: "absolute", left: 13, top: 13,
                width: 10, height: 10, borderRadius: "50%",
                background: isCurrent ? m.color : "#fff",
                border: `1.5px solid ${m.color}`,
                boxShadow: isCurrent ? `0 0 0 4px ${m.color}1F` : "none",
                transition: "background 0.3s ease, box-shadow 0.3s ease",
                zIndex: 1,
              }} />
              <div style={{
                background: "#fff",
                border: `1px solid ${isCurrent ? m.color + "55" : RULE}`,
                borderRadius: 9, padding: "9px 11px 10px",
                display: "flex", gap: 9, position: "relative",
                boxShadow: isCurrent ? `0 8px 18px -14px ${m.color}` : "none",
              }}>
                <div style={{
                  width: 22, height: 22, borderRadius: 6, flexShrink: 0,
                  background: m.color + "12",
                  border: `1px solid ${m.color}38`,
                  display: "flex", alignItems: "center", justifyContent: "center",
                }}>
                  <ActionIcon kind={m.icon} color={m.color} />
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                    <span style={{
                      fontFamily: MONO, fontSize: 9, color: m.color,
                      letterSpacing: "0.16em", textTransform: "uppercase", fontWeight: 600,
                      whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                    }}>{m.label}</span>
                    {m.confidence != null && (
                      <span style={{
                        fontFamily: MONO, fontSize: 8.5, color: FAINT,
                        letterSpacing: "0.04em", flexShrink: 0,
                      }}>{Math.round(m.confidence * 100)}%</span>
                    )}
                    <span style={{ flex: 1 }} />
                    <span style={{
                      fontFamily: MONO, fontSize: 9, color: FAINT,
                      fontVariantNumeric: "tabular-nums", flexShrink: 0,
                    }}>{fmt(a.t)}</span>
                  </div>
                  <div style={{
                    fontFamily: SERIF, fontSize: 13, color: INK,
                    fontWeight: 500, marginTop: 2, letterSpacing: "-0.005em",
                    lineHeight: 1.25,
                    whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                  }}>{m.title}</div>
                  {m.detail && (
                    <div style={{
                      fontFamily: MONO, fontSize: 10, color: DIM,
                      marginTop: 3, letterSpacing: "0.02em",
                      whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                    }}>{m.detail}</div>
                  )}
                </div>
                {isCurrent && (
                  <div style={{
                    position: "absolute", right: 9, bottom: 9,
                    display: "flex", gap: 2, alignItems: "flex-end",
                  }}>
                    {[0,1,2].map(k => (
                      <span key={k} style={{
                        width: 2, height: 8, background: m.color, borderRadius: 1,
                        animation: `vha-bar 0.9s ease-in-out ${k * 0.12}s infinite`,
                      }} />
                    ))}
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ────────────────────────── dynamic right panel ────────────────────────── */
function HotelCard({ id, time, revealAt }) {
  const h = HOTELS[id];
  if (time < revealAt) return null;
  const enter = Math.min(1, (time - revealAt) / 0.55);
  return (
    <div style={{
      background: "#fff",
      border: `1px solid ${BORDER_HOT}`,
      borderRadius: 10,
      padding: "12px 14px",
      display: "flex", alignItems: "center", gap: 12,
      opacity: enter,
      transform: `translateX(${(1 - enter) * 20}px)`,
      transition: "transform 0.5s ease",
      boxShadow: `0 10px 24px -18px ${FUCHSIA}`,
    }}>
      <div style={{
        width: 44, height: 44, borderRadius: 8, flexShrink: 0,
        background: `linear-gradient(135deg, ${FUCHSIA}, ${FUCHSIA_DEEP})`,
        display: "flex", alignItems: "center", justifyContent: "center",
        fontFamily: "'Source Serif 4', Georgia, serif",
        fontSize: 18, color: "#fff", fontStyle: "italic",
      }}>{h.name[0]}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
          <span style={{
            fontFamily: "'Source Serif 4', Georgia, serif",
            fontSize: 16, color: INK, fontWeight: 500, letterSpacing: "-0.005em",
          }}>{h.name}</span>
          <span style={{
            fontFamily: "'JetBrains Mono', ui-monospace, monospace",
            fontSize: 9, color: FUCHSIA, letterSpacing: "0.1em",
          }}>{h.stars}</span>
        </div>
        <div style={{
          display: "flex", alignItems: "center", gap: 8,
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 10.5, color: DIM, marginTop: 2, letterSpacing: "0.04em",
        }}>
          <span>{h.loc}</span>
          <span style={{ opacity: 0.4 }}>·</span>
          <span style={{ color: FUCHSIA }}>{h.tag}</span>
        </div>
      </div>
      <div style={{ textAlign: "right" }}>
        <div style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 14, color: INK, fontWeight: 600,
          fontVariantNumeric: "tabular-nums",
        }}>€{h.price}</div>
        <div style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 9.5, color: FAINT, letterSpacing: "0.06em",
        }}>FROM · NIGHT</div>
      </div>
    </div>
  );
}

function BookingEngineCheck({ time, qStart, qEnd }) {
  if (time < qStart || time > qEnd + 12) return null;
  const querying = time < qEnd;
  const elapsed = Math.min(1, (time - qStart) / (qEnd - qStart));
  return (
    <div style={{
      background: "#fff", border: `1px solid ${BORDER_HOT}`,
      borderRadius: 10,
      padding: "12px 14px",
      animation: "vha-fadein 0.4s ease",
    }}>
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "center",
        marginBottom: 10,
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{
            width: 7, height: 7, borderRadius: "50%",
            background: querying ? FUCHSIA : "#2f7d6b",
            animation: querying ? "vha-dot 0.9s ease-in-out infinite" : "none",
          }} />
          <span style={{
            fontFamily: "'JetBrains Mono', ui-monospace, monospace",
            fontSize: 10, color: querying ? FUCHSIA : "#2f7d6b",
            letterSpacing: "0.14em", textTransform: "uppercase", fontWeight: 600,
          }}>{querying ? "Querying booking engine" : "Live availability · 3 rates"}</span>
        </div>
        <span style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 9.5, color: DIM, letterSpacing: "0.06em",
        }}>OLEA · MAY 16–18</span>
      </div>
      <div style={{ height: 3, background: `${FUCHSIA}15`, borderRadius: 2, overflow: "hidden" }}>
        <div style={{
          height: "100%", width: `${elapsed * 100}%`,
          background: `linear-gradient(90deg, ${FUCHSIA}, ${FUCHSIA_DEEP})`,
          transition: "width 0.4s ease",
        }} />
      </div>
    </div>
  );
}

function RateCard({ rate, idx, time, showAt }) {
  if (time < showAt) return null;
  const enter = Math.min(1, (time - showAt) / 0.55);
  return (
    <div style={{
      background: rate.recommended
        ? `linear-gradient(135deg, rgba(${FUCHSIA_RGB}, 0.10), rgba(255, 255, 255, 1))`
        : "#fff",
      border: `1px solid ${rate.recommended ? `rgba(${FUCHSIA_RGB}, 0.5)` : RULE}`,
      borderRadius: 10,
      padding: "11px 14px",
      display: "flex", alignItems: "center", gap: 14,
      position: "relative",
      opacity: enter,
      transform: `translateY(${(1 - enter) * 12}px)`,
      transition: "transform 0.5s ease, opacity 0.5s ease",
      boxShadow: rate.recommended ? `0 14px 28px -16px ${FUCHSIA}` : "none",
    }}>
      {rate.recommended && (
        <span style={{
          position: "absolute", top: -8, right: 12,
          padding: "2px 8px", borderRadius: 999,
          background: FUCHSIA, color: "#fff",
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 8.5, letterSpacing: "0.12em", fontWeight: 600,
          boxShadow: `0 4px 10px ${FUCHSIA}66`,
        }}>SELECTED</span>
      )}
      <div style={{
        width: 28, textAlign: "center",
        fontFamily: "'JetBrains Mono', ui-monospace, monospace",
        fontSize: 10.5, color: FUCHSIA,
      }}>0{idx+1}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontFamily: "'Source Serif 4', Georgia, serif",
          fontSize: 14.5, color: INK, fontWeight: 500, letterSpacing: "-0.005em",
        }}>{rate.name}</div>
        <div style={{
          fontSize: 11, color: DIM, marginTop: 2,
          whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
        }}>{rate.desc}</div>
      </div>
      <div style={{ textAlign: "right" }}>
        <div style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 16, color: INK, fontWeight: 600,
          fontVariantNumeric: "tabular-nums",
        }}>€{rate.price}</div>
        <div style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 9, color: FAINT, letterSpacing: "0.08em",
        }}>PER NIGHT</div>
      </div>
    </div>
  );
}

function EmailCard({ time, composeStart, sentAt }) {
  if (time < composeStart) return null;
  const composing = time < sentAt;
  const enter = Math.min(1, (time - composeStart) / 0.5);
  return (
    <div style={{
      background: composing
        ? `linear-gradient(135deg, rgba(${FUCHSIA_RGB}, 0.06), #fff)`
        : "linear-gradient(135deg, rgba(47, 125, 107, 0.08), #fff)",
      border: `1px solid ${composing ? BORDER_HOT : "rgba(47, 125, 107, 0.4)"}`,
      borderRadius: 10,
      padding: "12px 14px",
      opacity: enter,
      transform: `translateY(${(1 - enter) * 12}px)`,
      transition: "transform 0.5s ease, background 0.4s ease, border 0.4s ease",
    }}>
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "center",
        marginBottom: 8,
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{
            width: 7, height: 7, borderRadius: "50%",
            background: composing ? FUCHSIA : "#2f7d6b",
            animation: composing ? "vha-dot 0.9s infinite" : "none",
          }} />
          <span style={{
            fontFamily: "'JetBrains Mono', ui-monospace, monospace",
            fontSize: 10, color: composing ? FUCHSIA : "#2f7d6b",
            letterSpacing: "0.14em", textTransform: "uppercase", fontWeight: 600,
          }}>{composing ? "Composing email" : "Email sent"}</span>
        </div>
        <span style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 9, color: DIM, letterSpacing: "0.06em",
        }}>{composing ? "DRAFT" : "DELIVERED · 0.4s"}</span>
      </div>
      <div style={{
        fontFamily: "'Source Serif 4', Georgia, serif",
        fontSize: 13.5, color: INK, fontWeight: 500, marginBottom: 6,
      }}>Olea Cliffside · Cliff Suite · May 16–18</div>
      <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
        {[
          "✓ Booking confirmation link",
          "✓ Verde · Vegan tasting menu",
          "✓ Aphrodite Ritual booking window",
        ].map((r, i) => (
          <div key={i} style={{
            fontFamily: "'JetBrains Mono', ui-monospace, monospace",
            fontSize: 10.5, color: composing ? DIM : "#444",
            letterSpacing: "0.04em",
          }}>{r}</div>
        ))}
      </div>
    </div>
  );
}

function DynamicPanel({ time }) {
  const hotelStart = CALL.find(u => u.showHotels);
  const ratesStart = CALL.find(u => u.showRates);
  const bookU = CALL.find(u => u.bookingQuery);
  const emailU = CALL.find(u => u.composeEmail);
  const showHotels = hotelStart && time >= hotelStart.t + 0.4;
  const showBooking = bookU && time >= bookU.t + 0.4;
  const showEmail = emailU && time >= emailU.t + 0.4;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
      <div style={{
        display: "flex", alignItems: "center", gap: 10, marginBottom: 4,
      }}>
        <span style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 10, color: FUCHSIA, letterSpacing: "0.18em",
          textTransform: "uppercase", fontWeight: 600,
        }}>Live agent workspace</span>
        <span style={{ flex: 1, height: 1, background: RULE }} />
      </div>
      {!showHotels && !showBooking && !showEmail && <WorkspaceIdle time={time} />}
      {showHotels && (
        <>
          <SectionLabel>Recommended properties</SectionLabel>
          <HotelCard id="olea"   time={time} revealAt={hotelStart.t + 0.4} />
          <HotelCard id="zafira" time={time} revealAt={hotelStart.t + 7.0} />
          <HotelCard id="sunset" time={time} revealAt={hotelStart.t + 14.0} />
        </>
      )}
      {showBooking && (
        <>
          <SectionLabel>Real-time availability</SectionLabel>
          <BookingEngineCheck time={time} qStart={bookU.t + 0.4} qEnd={bookU.t + 4.0} />
          {[0, 1, 2].map(i => (
            <RateCard key={i} rate={RATES[i]} idx={i} time={time}
              showAt={bookU.t + 4.6 + i * 3.3} />
          ))}
        </>
      )}
      {showEmail && (
        <>
          <SectionLabel>Confirmation</SectionLabel>
          <EmailCard time={time} composeStart={emailU.t + 0.4} sentAt={emailU.t + 6.8} />
        </>
      )}
    </div>
  );
}

function WorkspaceIdle({ time }) {
  const channels = [
    { icon: "◆", label: "Property catalog",   sub: "3,400 rooms · 12 brands" },
    { icon: "☰", label: "Knowledge base",    sub: "F&B · spa · policies" },
    { icon: "▶", label: "Booking engine",    sub: "live rates · availability" },
    { icon: "✎", label: "Email & messaging", sub: "24 templates ready" },
  ];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: 4 }}>
      <div style={{
        fontFamily: "'Source Serif 4', Georgia, serif",
        fontSize: 15, color: DIM, fontStyle: "italic",
        lineHeight: 1.45, marginBottom: 6,
      }}>
        Aria's workspace will fill in as the conversation unfolds, hotels,
        rates, and the email she sends to your guest.
      </div>
      {channels.map((c, i) => {
        const phase = (Math.sin(time * 1.6 + i * 0.9) * 0.5 + 0.5);
        const active = ((Math.floor(time * 0.8) % channels.length) === i);
        return (
          <div key={i} style={{
            display: "flex", alignItems: "center", gap: 12,
            padding: "10px 12px",
            background: "#fff",
            border: `1px solid ${active ? BORDER_HOT : RULE}`,
            borderRadius: 10,
            position: "relative", overflow: "hidden",
            transition: "border 0.4s ease",
          }}>
            <div style={{
              position: "absolute", inset: 0,
              background: `linear-gradient(110deg, transparent 30%, ${FUCHSIA}14 50%, transparent 70%)`,
              transform: `translateX(${(((time * 80) + i * 90) % 260) - 130}%)`,
              pointerEvents: "none",
            }} />
            <div style={{
              width: 28, height: 28, borderRadius: 7,
              background: active ? FUCHSIA : `${FUCHSIA}18`,
              color: active ? "#fff" : FUCHSIA,
              border: `1px solid ${active ? FUCHSIA : `${FUCHSIA}40`}`,
              display: "flex", alignItems: "center", justifyContent: "center",
              fontFamily: "'JetBrains Mono', ui-monospace, monospace",
              fontSize: 12, fontWeight: 600,
              transition: "background 0.4s ease, color 0.4s ease",
              flexShrink: 0,
            }}>{c.icon}</div>
            <div style={{ flex: 1, minWidth: 0, position: "relative" }}>
              <div style={{
                fontFamily: "'JetBrains Mono', ui-monospace, monospace",
                fontSize: 10, color: active ? FUCHSIA : INK,
                letterSpacing: "0.14em", textTransform: "uppercase",
                fontWeight: 600, marginBottom: 3,
                transition: "color 0.4s ease",
              }}>{c.label}</div>
              <div style={{
                fontSize: 11.5, color: DIM,
              }}>{c.sub}</div>
            </div>
            {active && (
              <div style={{ display: "flex", alignItems: "center", gap: 3 }}>
                {[0, 1, 2].map(k => (
                  <span key={k} style={{
                    width: 2, height: 10, background: FUCHSIA, borderRadius: 1,
                    animation: `vha-bar 0.9s ease-in-out ${k * 0.12}s infinite`,
                  }} />
                ))}
              </div>
            )}
          </div>
        );
      })}
      <div style={{
        marginTop: 4,
        padding: "10px 12px",
        border: `1px dashed ${BORDER_HOT}`,
        borderRadius: 10,
        display: "flex", alignItems: "center", gap: 10,
        background: `rgba(${FUCHSIA_RGB}, 0.04)`,
      }}>
        <span style={{
          width: 6, height: 6, borderRadius: "50%", background: FUCHSIA,
          animation: "vha-dot 1.2s ease-in-out infinite",
        }} />
        <span style={{
          fontFamily: "'JetBrains Mono', ui-monospace, monospace",
          fontSize: 10, color: FUCHSIA, letterSpacing: "0.14em",
          textTransform: "uppercase", fontWeight: 600,
        }}>Standing by, waiting on caller</span>
      </div>
    </div>
  );
}

function SectionLabel({ children }) {
  return (
    <div style={{
      fontFamily: "'JetBrains Mono', ui-monospace, monospace",
      fontSize: 9.5, color: FAINT, letterSpacing: "0.16em",
      textTransform: "uppercase", marginTop: 6, marginBottom: 2,
      animation: "vha-fadein 0.3s ease",
    }}>{children}</div>
  );
}

function fmt(t) {
  const m = Math.floor(t / 60);
  const s = Math.floor(t % 60);
  return `${m}:${String(s).padStart(2, "0")}`;
}

function MiniWave({ time, amp }) {
  const bars = 32;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 2, height: 22 }}>
      {Array.from({ length: bars }).map((_, i) => {
        const phase = i / bars;
        const v = (Math.sin(phase * Math.PI * 6 + time * 8) * 0.5 + 0.5) * amp + (1 - amp) * 0.18;
        return (
          <span key={i} style={{
            width: 2, borderRadius: 1,
            height: 4 + v * 18,
            background: FUCHSIA,
            opacity: 0.5 + v * 0.5,
            transition: "height 0.05s linear",
          }} />
        );
      })}
    </div>
  );
}

/* ────────────────────────── main ────────────────────────── */
function VoiceHeroAria() {
  const { buffers, audioCtxRef, ensure } = useAudioBuffers();
  const [soundOn, setSoundOn] = useState(false);
  const [hasInteracted, setHasInteracted] = useState(false);
  const { time, setTime, playing, setPlaying, replay } = usePlayback({
    audioCtxRef, buffers, soundOn,
  });
  const cur = currentSpeaker(time);
  const speakerKind = cur ? cur.utter.spk : "idle";
  const amp = cur ? 1 : 0.15;

  /* First user gesture anywhere on the demo enables sound by default.
     Subsequent clicks on the sound pill toggle mute. */
  function arm() {
    if (!hasInteracted) {
      setHasInteracted(true);
      ensure();
      setSoundOn(true);
      return true;
    }
    return false;
  }
  function handlePauseToggle() {
    arm();
    setPlaying(p => !p);
  }
  function handleReplay() {
    arm();
    replay();
  }
  function handleScrubSeek(t) {
    arm();
    setTime(t);
  }
  function handleSoundToggle() {
    /* If this is the first interaction, the click just enables sound (the
       button was labelled "Play with sound"). After that it's a mute toggle. */
    if (arm()) return;
    setSoundOn(prev => !prev);
  }

  return (
    <section data-screen-label="Voice Agent" style={{
      position: "relative",
      background: CREAM,
      color: INK,
      fontFamily: "'Inter', system-ui, sans-serif",
      overflow: "hidden",
      padding: "60px 32px 32px",
    }}>
      {/* keyframes */}
      <style>{`
        @keyframes vha-fadein  { from { opacity:0; transform: translateY(6px);} to { opacity:1; transform: translateY(0);} }
        @keyframes vha-slidein { from { opacity:0; transform: translateX(-10px);} to { opacity:1; transform: translateX(0);} }
        @keyframes vha-dot     { 0%,100% { opacity:1; transform: scale(1);} 50% { opacity:.55; transform: scale(1.2);} }
        @keyframes vha-blink   { 0%,50% { opacity:1;} 51%,100% { opacity:0;} }
        @keyframes vha-bar     { 0%,100% { transform: scaleY(0.4);} 50% { transform: scaleY(1);} }
        @keyframes vha-word    { from { opacity:0; filter: blur(4px);} to { opacity:1; filter: blur(0);} }
        .vha-benefits { grid-template-columns: repeat(4, minmax(0, 1fr)); }
        @media (max-width: 860px) { .vha-benefits { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
        @media (max-width: 480px) { .vha-benefits { grid-template-columns: minmax(0, 1fr); } }
        .vha-main-grid { grid-template-columns: 260px 1fr 360px; }
        /* Deliberately NOT stacked. Stacking made each panel a 342px column
           while its content still needs ~917px, so the reasoning cards were
           clipped mid-card. The three panels are one composition, so the whole
           grid is wrapped in <ScaleFrame> and shrunk instead — same treatment
           as every other artifact, and as memory's hero diagram. */
      `}</style>

      {/* soft pink ambient */}
      <div style={{
        position: "absolute", top: -160, left: "30%", width: 800, height: 600,
        background: `radial-gradient(ellipse, rgba(${FUCHSIA_RGB}, 0.10) 0%, transparent 60%)`,
        filter: "blur(40px)", pointerEvents: "none",
      }} />

      {/* HEADER */}
      <div style={{
        position: "relative", maxWidth: 1320, margin: "0 auto 36px", textAlign: "center",
      }}>
        <div style={{
          display: "inline-flex", alignItems: "center", gap: 10, marginBottom: 18,
        }}>
          <span style={{ width: 16, height: 1.5, background: FUCHSIA }} />
          <span style={{
            fontFamily: "'JetBrains Mono', ui-monospace, monospace",
            fontSize: 11, color: FUCHSIA, letterSpacing: "0.22em",
            textTransform: "uppercase", fontWeight: 500,
          }}>Voice Agent</span>
        </div>
        <h1 className="h-display" style={{ maxWidth: 920, marginInline: "auto" }}>
          The voice in front of every call.{" "}
          <span className="italic-accent" style={{ display: "block" }}>
            Fluent, available, everywhere.
          </span>
        </h1>
        <p style={{
          fontSize: 15, color: DIM, lineHeight: 1.65,
          maxWidth: 620, margin: "20px auto 0",
        }}>
          A true concierge agent that handles the calls your team can't,
          answering questions, recommending properties, checking real
          availability and generating bookings.
        </p>
      </div>

      {/* MAIN GRID */}
      <ScaleFrame nativeWidth={1320}>
      <div className="vha-main-grid" style={{
        position: "relative", maxWidth: 1320, margin: "0 auto",
        display: "grid",
        gap: 24,
        alignItems: "start",
      }}>
        {/* LEFT */}
        <div style={{
          background: "rgba(255,255,255,0.6)",
          border: `1px solid ${RULE}`,
          borderRadius: 14, padding: 18,
          backdropFilter: "blur(8px)",
          WebkitBackdropFilter: "blur(8px)",
          minHeight: 540,
        }}>
          <div style={{
            display: "flex", alignItems: "center", gap: 8,
            paddingBottom: 12, borderBottom: `1px solid ${RULE}`,
            marginBottom: 14,
          }}>
            <span style={{
              fontFamily: "'JetBrains Mono', ui-monospace, monospace",
              fontSize: 10, color: FUCHSIA, letterSpacing: "0.18em",
              textTransform: "uppercase", fontWeight: 600,
            }}>Agent reasoning</span>
            <span style={{ flex: 1 }} />
            <span style={{
              fontFamily: "'JetBrains Mono', ui-monospace, monospace",
              fontSize: 10, color: FAINT, letterSpacing: "0.1em",
            }}>{ACTIONS.filter(a => time >= a.t).length}/{ACTIONS.length}</span>
          </div>
          <ActionLog time={time} />
        </div>

        {/* CENTER */}
        <div style={{
          display: "flex", flexDirection: "column", alignItems: "center", gap: 24,
        }}>
          <VoicePulse time={time} speakerKind={speakerKind} amp={amp}
            playing={playing} onToggle={handlePauseToggle} />
          <div style={{
            display: "flex", justifyContent: "center", gap: 8, flexWrap: "wrap",
          }}>
            <Pill label="Sentiment" value="Positive" color="#2f7d6b" />
            <Pill label="Intent" value={inferIntent(time)} color={FUCHSIA} />
            <Pill label="Language" value="English" color={INK} />
            <Pill label="Confidence" value="98.4%" color={FUCHSIA} />
          </div>
          <div style={{ width: "100%", maxWidth: 660, marginTop: 8 }}>
            <Transcript time={time} />
          </div>
        </div>

        {/* RIGHT */}
        <div style={{
          background: "rgba(255,255,255,0.6)",
          border: `1px solid ${RULE}`,
          borderRadius: 14, padding: 18,
          backdropFilter: "blur(8px)",
          WebkitBackdropFilter: "blur(8px)",
          minHeight: 540,
        }}>
          <DynamicPanel time={time} />
        </div>
      </div>
      </ScaleFrame>

      {/* FOOTER */}
      <ScaleFrame nativeWidth={1320} style={{ maxWidth: 1320, margin: "0 auto" }}>
      <div style={{
        position: "relative", maxWidth: 1320, margin: "32px auto 0",
        background: "rgba(255,255,255,0.7)",
        border: `1px solid ${RULE}`,
        borderRadius: 14, padding: "14px 20px",
        backdropFilter: "blur(8px)",
        WebkitBackdropFilter: "blur(8px)",
        display: "grid",
        gridTemplateColumns: "auto 1fr auto",
        gap: 24,
        alignItems: "center",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
          <button onClick={handlePauseToggle}
            onMouseEnter={e => { if (playing) e.currentTarget.style.background = '#000'; else e.currentTarget.style.background = FUCHSIA_DEEP; }}
            onMouseLeave={e => { e.currentTarget.style.background = playing ? INK : FUCHSIA; }}
            style={{
              display: "inline-flex", alignItems: "center", gap: 9,
              height: 40, padding: "0 18px 0 14px",
              borderRadius: 999,
              background: playing ? INK : FUCHSIA,
              border: "none", cursor: "pointer",
              color: "#fff",
              fontFamily: "'Inter', system-ui, sans-serif",
              fontSize: 13, fontWeight: 600, letterSpacing: "-0.005em",
              boxShadow: playing
                ? "0 8px 18px -10px rgba(0,0,0,0.5)"
                : `0 10px 22px -10px ${FUCHSIA}AA`,
              transition: "background 0.2s ease, transform 0.15s ease",
            }}>
            {playing ? (
              <>
                <svg width="14" height="14" viewBox="0 0 14 14" fill="#fff"><rect x="2" y="2" width="3.5" height="10" rx="0.6" /><rect x="8.5" y="2" width="3.5" height="10" rx="0.6" /></svg>
                <span>Pause call</span>
              </>
            ) : (
              <>
                <svg width="14" height="14" viewBox="0 0 14 14" fill="#fff"><polygon points="3,2 12,7 3,12" /></svg>
                <span>Resume call</span>
              </>
            )}
          </button>

          <button onClick={handleReplay}
            aria-label="Replay from start"
            title="Replay from start"
            style={{
              width: 32, height: 32, borderRadius: "50%",
              background: "#fff", color: FUCHSIA,
              border: `1px solid ${FUCHSIA}55`,
              cursor: "pointer", display: "inline-flex",
              alignItems: "center", justifyContent: "center",
              transition: "background 0.2s, color 0.2s",
            }}
            onMouseEnter={e => { e.currentTarget.style.background = FUCHSIA; e.currentTarget.style.color = "#fff"; }}
            onMouseLeave={e => { e.currentTarget.style.background = "#fff"; e.currentTarget.style.color = FUCHSIA; }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <path d="M3 12a9 9 0 1 0 3-6.7L3 8"/>
              <path d="M3 3v5h5"/>
            </svg>
          </button>

          <div style={{
            fontFamily: "'JetBrains Mono', ui-monospace, monospace",
            fontSize: 13, color: INK,
            fontVariantNumeric: "tabular-nums", letterSpacing: "0.04em",
          }}>{fmt(time)} <span style={{ color: FAINT }}>/ {fmt(CALL_END)}</span></div>
          <MiniWave time={time} amp={amp} />
        </div>

        <div style={{ position: "relative", height: 30 }}>
          <div style={{
            position: "absolute", top: 13, left: 0, right: 0, height: 3,
            background: `${FUCHSIA}15`, borderRadius: 2,
          }} />
          <div style={{
            position: "absolute", top: 13, left: 0,
            width: `${(time / CALL_END) * 100}%`, height: 3,
            background: `linear-gradient(90deg, ${FUCHSIA}, ${FUCHSIA_DEEP})`,
            borderRadius: 2,
          }} />
          {CALL.map((u, i) => {
            const left = (u.t / CALL_END) * 100;
            return (
              <span key={i} title={u.text.slice(0, 40)} style={{
                position: "absolute", top: 11, left: `${left}%`,
                width: 2, height: 7,
                background: u.spk === "agent" ? FUCHSIA : INK,
                opacity: time >= u.t ? 1 : 0.35,
                transform: "translateX(-1px)",
                borderRadius: 1,
              }} />
            );
          })}
          {ACTIONS.filter(a => ["search", "bookq", "email"].includes(a.kind)).map((a, i) => {
            const left = (a.t / CALL_END) * 100;
            return (
              <span key={"a"+i} style={{
                position: "absolute", top: 4, left: `${left}%`,
                width: 6, height: 6, borderRadius: "50%",
                background: FUCHSIA, transform: "translateX(-3px)",
                opacity: time >= a.t ? 1 : 0.35,
              }} />
            );
          })}
          <div onClick={(e) => {
            const r = e.currentTarget.getBoundingClientRect();
            const f = (e.clientX - r.left) / r.width;
            handleScrubSeek(Math.max(0, Math.min(CALL_END - 0.01, f * CALL_END)));
          }} style={{
            position: "absolute", inset: 0, cursor: "pointer",
          }} />
          <div style={{
            position: "absolute",
            left: `${(time / CALL_END) * 100}%`,
            top: 7,
            width: 14, height: 14, borderRadius: "50%",
            background: "#fff", border: `2px solid ${FUCHSIA}`,
            transform: "translateX(-7px)",
            boxShadow: `0 2px 6px ${FUCHSIA}66`, pointerEvents: "none",
          }} />
        </div>

        <div style={{ display: "flex", gap: 16, alignItems: "center" }}>
          <button onClick={handleSoundToggle}
            aria-label={!hasInteracted ? "Play with sound" : soundOn ? "Mute" : "Unmute"}
            style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "5px 12px", borderRadius: 999,
              background: soundOn ? FUCHSIA : "#fff",
              color: soundOn ? "#fff" : FUCHSIA,
              border: `1px solid ${soundOn ? FUCHSIA : `${FUCHSIA}40`}`,
              cursor: "pointer",
              fontFamily: "'Inter', system-ui, sans-serif",
              fontSize: 11, fontWeight: 500,
              transition: "background 0.2s, color 0.2s, border 0.2s",
            }}>
            {soundOn ? (
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none"><path d="M3 9v6h4l5 4V5L7 9H3Z" fill="currentColor"/><path d="M16 9.5c1.2.8 1.2 4.2 0 5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>
            ) : (
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none"><path d="M3 9v6h4l5 4V5L7 9H3Z" fill="currentColor"/><path d="M16 9l5 6M21 9l-5 6" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>
            )}
            <span>{!hasInteracted ? "Play with sound" : soundOn ? "Sound on" : "Muted"}</span>
          </button>
          <Stat label="LATENCY" value="190ms" color="#2f7d6b" />
          <Stat label="HANDOFF" value="0%" color={FUCHSIA} />
        </div>
      </div>
      </ScaleFrame>

      {/* benefits strip */}
      <div className="vha-benefits" style={{
        position: "relative", maxWidth: 1320, margin: "20px auto 0",
        display: "grid", gap: 12,
      }}>
        {[
          { lbl: "Picks up in", v: "0.8s", sub: "every call, every hour" },
          { lbl: "Languages", v: "27+", sub: "fluent in real time" },
          { lbl: "Call center relief", v: "70%", sub: "questions & objections solved" },
          { lbl: "Direct bookings closed", v: "30%", sub: "routed straight to email" },
        ].map((s, i) => (
          <div key={i} style={{
            background: "rgba(255,255,255,0.7)",
            border: `1px solid ${RULE}`,
            borderRadius: 10, padding: "14px 16px",
            backdropFilter: "blur(8px)",
            WebkitBackdropFilter: "blur(8px)",
          }}>
            <div style={{
              fontFamily: "'JetBrains Mono', ui-monospace, monospace",
              fontSize: 9.5, color: FAINT, letterSpacing: "0.16em",
              textTransform: "uppercase", marginBottom: 4,
            }}>{s.lbl}</div>
            <div style={{
              fontFamily: "'Instrument Serif', 'Source Serif 4', Georgia, serif",
              fontSize: 28, color: INK, fontWeight: 400,
              letterSpacing: "-0.02em", lineHeight: 1,
            }}>
              <span style={{ color: FUCHSIA, fontStyle: "italic" }}>{s.v}</span>
            </div>
            <div style={{ fontSize: 11.5, color: DIM, marginTop: 4 }}>{s.sub}</div>
          </div>
        ))}
      </div>
    </section>
  );
}

function Pill({ label, value, color }) {
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      padding: "5px 11px", borderRadius: 999,
      background: "#fff",
      border: `1px solid ${RULE}`,
      fontFamily: "'JetBrains Mono', ui-monospace, monospace",
      fontSize: 10, fontWeight: 500,
    }}>
      <span style={{
        width: 5, height: 5, borderRadius: "50%", background: color,
      }} />
      <span style={{ color: FAINT, letterSpacing: "0.1em" }}>{label}</span>
      <span style={{ color: INK, letterSpacing: "0.04em" }}>{value}</span>
    </span>
  );
}

function Stat({ label, value, color }) {
  return (
    <div style={{ textAlign: "right" }}>
      <div style={{
        fontFamily: "'JetBrains Mono', ui-monospace, monospace",
        fontSize: 9, color: FAINT, letterSpacing: "0.14em",
        textTransform: "uppercase",
      }}>{label}</div>
      <div style={{
        fontFamily: "'JetBrains Mono', ui-monospace, monospace",
        fontSize: 13, color, fontWeight: 600,
        fontVariantNumeric: "tabular-nums",
      }}>{value}</div>
    </div>
  );
}

function inferIntent(time) {
  if (time < 11)  return "Greeting";
  if (time < 21)  return "F&B inquiry";
  if (time < 35)  return "Spa inquiry";
  if (time < 60)  return "Recommendation";
  if (time < 86)  return "Availability";
  if (time < 116) return "Booking";
  return "Closing";
}

window.VoiceHeroAria = VoiceHeroAria;
