// ═══════════════════════════════════════════════════════════════
// ACT II — EXCHANGE (9 – 36s, 27 seconds)
// Four connection stories — slower pacing so viewers can absorb.
//
// Beat 1 (0 – 7s)   Enthusiast ↔ Trainer    — "Coach"
// Beat 2 (7 – 14s)  Institution ↔ Enthusiast — "Manage"
// Beat 3 (14 – 21s) Trainer ↔ Institution   — "Hire"
// Beat 4 (21 – 27s) All three shared feed    — "Belong"
// ═══════════════════════════════════════════════════════════════

function Scene_Exchange({ t }) {
  const secs = t * 27;
  const beat1T = Math.min(1, Math.max(0, secs / 7));
  const beat2T = Math.min(1, Math.max(0, (secs - 7) / 7));
  const beat3T = Math.min(1, Math.max(0, (secs - 14) / 7));
  const beat4T = Math.min(1, Math.max(0, (secs - 21) / 6));

  const players = {
    institution: { x: 160, y: 300, cx: 260, cy: 460, role: 'institution', name: 'Iron Peak',  tag: 'FITNESS CENTER', meta: '6 branches' },
    trainer:     { x: 830, y: 300, cx: 930, cy: 460, role: 'trainer',     name: 'Coach Ryan', tag: 'TRAINER',        meta: 'Strength · 8 yrs' },
    enthusiast:  { x: 1500, y: 300, cx: 1600, cy: 460, role: 'enthusiast', name: 'Samira',    tag: 'ENTHUSIAST',     meta: '21-day streak' },
  };

  const activeBeat =
    beat4T > 0 ? 4 :
    beat3T > 0 ? 3 :
    beat2T > 0 ? 2 : 1;

  // Highlighting logic — which cards are "lit" per beat
  const highlightMap = {
    1: { institution: false, trainer: true,  enthusiast: true },
    2: { institution: true,  trainer: false, enthusiast: true },
    3: { institution: true,  trainer: true,  enthusiast: false },
    4: { institution: true,  trainer: true,  enthusiast: true },
  };

  return (
    <SceneBG>
      {/* Player silhouettes — active ones pop, inactive fades way back */}
      {Object.values(players).map((p, i) => {
        const lit = highlightMap[activeBeat][p.role];
        // Beat 4 shows all 3 normally; pairwise beats pop active and hide inactive
        const isPairwise = activeBeat < 4;
        const baseOpacity = lit ? 1 : (isPairwise ? 0.08 : 0.45);
        const baseScale = lit && isPairwise ? 1.06 : 1;

        // ── Converge into network: last 12% of Exchange, silhouettes pull toward (960,540) and shrink to dots ──
        const conv = Math.max(0, Math.min(1, (t - 0.88) / 0.12));
        const convE = conv * conv * (3 - 2 * conv); // smoothstep
        const targetX = 960, targetY = 540;
        const dx = targetX - p.cx;
        const dy = targetY - p.cy;
        const pullX = dx * convE;
        const pullY = dy * convE;
        const convScale = 1 - convE * 0.88; // 1 → 0.12
        const convOpacity = 1 - convE; // fade out as they collapse

        const finalOpacity = baseOpacity * convOpacity;
        const finalScale = baseScale * convScale;
        const finalFilter = lit
          ? (convE > 0.2 ? `drop-shadow(0 0 ${20 + convE * 40}px #CCFF00) brightness(${1 + convE * 0.8})` : 'none')
          : (isPairwise ? 'grayscale(1) blur(1px)' : 'none');

        return (
          <div key={i} style={{
            position: 'absolute', left: 0, top: 0,
            transform: `translate(${pullX}px, ${pullY}px) scale(${finalScale})`,
            transformOrigin: `${p.cx}px ${p.cy}px`,
            transition: conv > 0 ? 'none' : 'transform 400ms cubic-bezier(.2,.7,.2,1), opacity 400ms',
            opacity: finalOpacity,
            filter: finalFilter,
          }}>
            <PlayerCard
              x={p.x} y={p.y}
              role={p.role}
              name={p.name}
              tag={p.tag}
              meta={p.meta}
              active={true}
              highlighted={lit}
              opacity={1}
            />
          </div>
        );
      })}

      {/* Converge-into-network core bloom — bright flash at the merge point as silhouettes collide */}
      {t > 0.88 && (() => {
        const conv = Math.max(0, Math.min(1, (t - 0.88) / 0.12));
        const convE = conv * conv * (3 - 2 * conv);
        return (
          <div style={{
            position: 'absolute',
            left: 960 - 300, top: 540 - 300,
            width: 600, height: 600,
            background: `radial-gradient(circle, rgba(204,255,0, ${0.9 * convE}) 0%, rgba(204,255,0, ${0.4 * convE}) 20%, rgba(204,255,0, ${0.08 * convE}) 50%, transparent 75%)`,
            filter: 'blur(12px)',
            zIndex: 8,
            pointerEvents: 'none',
          }}/>
        );
      })()}

      {/* Convergence streaks — thin volt lines from each silhouette into the merge point */}
      {t > 0.88 && (() => {
        const conv = Math.max(0, Math.min(1, (t - 0.88) / 0.12));
        const convE = conv * conv * (3 - 2 * conv);
        return (
          <svg style={{ position: 'absolute', inset: 0, width: 1920, height: 1080, pointerEvents: 'none', zIndex: 7 }}>
            {Object.values(players).map((p, i) => {
              // Shorten the line as silhouettes collapse into center
              const startX = p.cx + (960 - p.cx) * convE * 0.5;
              const startY = p.cy + (540 - p.cy) * convE * 0.5;
              return (
                <line key={i}
                  x1={startX} y1={startY}
                  x2={960} y2={540}
                  stroke="#CCFF00"
                  strokeWidth={1 + convE * 3}
                  strokeOpacity={0.3 + convE * 0.6}
                  style={{ filter: `drop-shadow(0 0 ${4 + convE * 12}px #CCFF00)` }}
                />
              );
            })}
          </svg>
        );
      })()}

      {/* Bold active connection line between the two lit players in pairwise beats */}
      {activeBeat < 4 && (
        <ActiveConnection
          players={players}
          highlightMap={highlightMap[activeBeat]}
          pulse={secs}
        />
      )}

      {/* Ambient background lines */}
      <AmbientLinks players={players} time={secs}/>

      {/* ── Beats ── (fade out during convergence) */}
      <div style={{
        opacity: 1 - Math.max(0, Math.min(1, (t - 0.88) / 0.12)),
        transition: 'none',
      }}>
        {beat1T > 0 && beat2T < 0.05 && <BeatCoach t={beat1T} players={players}/>}
        {beat2T > 0 && beat3T < 0.05 && <BeatManage t={beat2T} players={players}/>}
        {beat3T > 0 && beat4T < 0.05 && <BeatHire t={beat3T} players={players}/>}
        {beat4T > 0 && <BeatCommunity t={beat4T} players={players}/>}
      </div>

      {/* Act label + dynamic beat headline */}
      <div style={{
        position: 'absolute',
        left: 96, bottom: 160,
        opacity: 1 - Math.max(0, Math.min(1, (t - 0.88) / 0.12)),
      }}>
        <div style={{
          fontFamily: FONT_MONO,
          fontSize: 13,
          color: BRAND.volt,
          letterSpacing: '0.3em',
          fontWeight: 700,
          marginBottom: 14,
          textTransform: 'uppercase',
        }}>
          <span style={{ display: 'inline-block', width: 40, height: 2, background: BRAND.volt, verticalAlign: 'middle', marginRight: 14 }}/>
          Act II · The Exchange
        </div>
        <BeatHeadline beat1T={beat1T} beat2T={beat2T} beat3T={beat3T} beat4T={beat4T}/>
      </div>

    </SceneBG>
  );
}

// Bright animated line connecting the two lit players in a pairwise beat
function ActiveConnection({ players, highlightMap, pulse }) {
  const lit = Object.values(players).filter(p => highlightMap[p.role]);
  if (lit.length !== 2) return null;
  const [a, b] = lit;
  const cx = (a.cx + b.cx) / 2;
  const cy = (a.cy + b.cy) / 2 + 10;
  // Quadratic path from a to b via cx,cy
  const dx = b.cx - a.cx;
  const dy = b.cy - a.cy;
  const len = Math.hypot(dx, dy);
  const angle = Math.atan2(dy, dx) * 180 / Math.PI;
  return (
    <svg style={{ position: 'absolute', inset: 0, width: 1920, height: 1080, pointerEvents: 'none', zIndex: 5 }}>
      <defs>
        <linearGradient id="conn-grad" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0" stopColor="#CCFF00" stopOpacity="0.9"/>
          <stop offset="0.5" stopColor="#CCFF00" stopOpacity="1"/>
          <stop offset="1" stopColor="#CCFF00" stopOpacity="0.9"/>
        </linearGradient>
      </defs>
      <path
        d={`M ${a.cx} ${a.cy} Q ${cx} ${cy} ${b.cx} ${b.cy}`}
        stroke="url(#conn-grad)"
        strokeWidth="2.5"
        fill="none"
        opacity="0.75"
      />
      <path
        d={`M ${a.cx} ${a.cy} Q ${cx} ${cy} ${b.cx} ${b.cy}`}
        stroke="#CCFF00"
        strokeWidth="1"
        fill="none"
        strokeDasharray="6 10"
        strokeDashoffset={-pulse * 30}
        opacity="0.8"
      />
    </svg>
  );
}

// ── Beat 1 — "Coach" ─────────────────────────────
// Enthusiast books a demo with trainer; trainer accepts; booking confirmed.
function BeatCoach({ t, players }) {
  const fly1 = Math.min(1, t / 0.32);
  const showRequest = Math.min(1, Math.max(0, (t - 0.28) / 0.18));
  const fly2 = Math.min(1, Math.max(0, (t - 0.52) / 0.2));
  const bothBooked = Math.min(1, Math.max(0, (t - 0.7) / 0.25));
  const from = players.enthusiast;
  const to = players.trainer;

  return (
    <>
      <DataPacket
        fromX={from.cx} fromY={from.cy}
        toX={to.cx} toY={to.cy}
        progress={fly1 < 1 ? fly1 : 0}
        label="→" curve={-0.2}
      />
      <DataPacket
        fromX={to.cx} fromY={to.cy}
        toX={from.cx} toY={from.cy}
        progress={fly2 < 1 ? fly2 : 0}
        label="✓" curve={0.2}
      />
      {showRequest > 0 && (
        <MiniCard
          x={to.cx - 110} y={to.cy - 40}
          opacity={showRequest}
          slideUp={(1 - showRequest) * 20}
          tag="● NEW DEMO REQUEST"
          title="Samira K."
          sub="Strength · Beginner"
          cta={t > 0.55 ? '✓ ACCEPTED' : 'TAP TO REVIEW'}
          ctaActive={t > 0.55}
        />
      )}
      {bothBooked > 0 && (
        <MiniCard
          x={from.cx - 110} y={from.cy - 40}
          opacity={bothBooked}
          slideUp={(1 - bothBooked) * 20}
          tag="✓ BOOKED"
          title="Coach Ryan"
          sub="Free demo · Sat 10 AM"
          footer="Live with trainer"
          active
        />
      )}
    </>
  );
}

// ── Beat 2 — "Manage" ─────────────────────────────
// Institution manages its members: check-in, member card, progress tracking.
function BeatManage({ t, players }) {
  // Narrative:
  //  0.00 – 0.25   Member card flies institution → enthusiast (they belong)
  //  0.20 – 0.50   Enthusiast check-in pings back to institution dashboard
  //  0.45 – 0.75   Dashboard card appears at institution showing member progress
  //  0.70 – 1.00   Rolling member roster bloom around institution
  const cardIssue = Math.min(1, t / 0.3);
  const checkIn = Math.min(1, Math.max(0, (t - 0.22) / 0.3));
  const dashboard = Math.min(1, Math.max(0, (t - 0.48) / 0.25));
  const roster = Math.min(1, Math.max(0, (t - 0.68) / 0.3));

  return (
    <>
      {/* Member card flying from institution to enthusiast */}
      <DataPacket
        fromX={players.institution.cx} fromY={players.institution.cy}
        toX={players.enthusiast.cx} toY={players.enthusiast.cy}
        progress={cardIssue < 1 ? cardIssue : 0}
        label="⎈" curve={-0.18} size={26}
      />
      {/* Check-in ping back */}
      <DataPacket
        fromX={players.enthusiast.cx} fromY={players.enthusiast.cy + 40}
        toX={players.institution.cx} toY={players.institution.cy + 40}
        progress={checkIn < 1 ? checkIn : 0}
        label="◉" curve={0.18} size={20}
      />

      {/* Member card landed on enthusiast */}
      {cardIssue > 0.85 && (
        <MiniCard
          x={players.enthusiast.cx - 110} y={players.enthusiast.cy + 180}
          opacity={Math.min(1, (cardIssue - 0.85) / 0.15) * (1 - Math.max(0, (t - 0.95) / 0.05))}
          slideUp={0}
          tag="● MEMBER · IRON PEAK"
          title="Samira K. · #IP-0442"
          sub="Active · since Mar 2026"
          cta="ALL 6 BRANCHES"
          ctaActive
        />
      )}

      {/* Dashboard card at institution */}
      {dashboard > 0 && (
        <div style={{
          position: 'absolute',
          left: players.institution.cx - 140,
          top: players.institution.cy + 180,
          opacity: dashboard,
          transform: `translateY(${(1 - dashboard) * 20}px)`,
          width: 280,
          padding: 14,
          background: 'rgba(20,20,20,0.96)',
          border: `1px solid ${BRAND.volt}`,
          borderRadius: 12,
          fontFamily: FONT_SANS,
          boxShadow: '0 20px 50px rgba(0,0,0,0.5), 0 0 30px rgba(204,255,0,0.2)',
          zIndex: 20,
        }}>
          <div style={{ fontSize: 9, color: BRAND.volt, letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
            ● TODAY · INDIRANAGAR
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 18, marginBottom: 10 }}>
            <MiniStat label="CHECK-INS" value="247"/>
            <MiniStat label="MEMBERS" value="1,289"/>
            <MiniStat label="ACTIVE NOW" value="64"/>
          </div>
          <div style={{ height: 1, background: 'rgba(255,255,255,0.08)', margin: '8px 0' }}/>
          <div style={{ fontSize: 11, color: BRAND.textSecondary, display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{ width: 6, height: 6, borderRadius: '50%', background: BRAND.volt, boxShadow: `0 0 6px ${BRAND.volt}` }}/>
            Samira K. checked in · 2m ago
          </div>
        </div>
      )}

      {/* Roster bloom — small avatar dots around institution */}
      {roster > 0 && [...Array(9)].map((_, i) => {
        const angle = (i / 9) * Math.PI * 2;
        const dist = 140 + (i % 3) * 22;
        const delay = i * 0.05;
        const p = Math.max(0, Math.min(1, (roster - delay) / 0.4));
        if (p <= 0) return null;
        return (
          <div key={i} style={{
            position: 'absolute',
            left: players.institution.cx + Math.cos(angle) * dist * p - 10,
            top: players.institution.cy + Math.sin(angle) * dist * p - 10 - 40,
            width: 20, height: 20, borderRadius: '50%',
            background: i % 3 === 0 ? BRAND.volt : '#fff',
            opacity: 0.75 * p,
            boxShadow: i % 3 === 0 ? `0 0 10px ${BRAND.volt}` : 'none',
          }}/>
        );
      })}
    </>
  );
}

// ── Beat 3 — "Hire" ─────────────────────────────
// Institution posts a job. Trainer applies. Gets hired.
function BeatHire({ t, players }) {
  //  0.00 – 0.30   Institution broadcasts a job (pulse rings)
  //  0.25 – 0.55   Trainer's inbox shows "OPEN JOB"
  //  0.45 – 0.75   Trainer applies — packet flies back to institution
  //  0.70 – 1.00   Institution sends back "HIRED" + Trainer gets "Iron Peak Coach" badge
  const broadcast = Math.min(1, t / 0.3);
  const listingAtTrainer = Math.min(1, Math.max(0, (t - 0.25) / 0.18));
  const apply = Math.min(1, Math.max(0, (t - 0.5) / 0.25));
  const hired = Math.min(1, Math.max(0, (t - 0.72) / 0.28));
  const badge = Math.min(1, Math.max(0, (t - 0.8) / 0.2));

  return (
    <>
      {/* Broadcast rings */}
      {[0, 0.12, 0.24].map((d, i) => {
        const w = Math.max(0, broadcast - d);
        if (w <= 0 || w >= 1) return null;
        return (
          <div key={i} style={{
            position: 'absolute',
            left: players.institution.cx - 250,
            top: players.institution.cy - 250,
            width: 500, height: 500,
            borderRadius: '50%',
            border: `2px solid ${BRAND.volt}`,
            opacity: (1 - w) * 0.6,
            transform: `scale(${0.1 + w * 1.5})`,
            pointerEvents: 'none',
          }}/>
        );
      })}

      {/* Job packet → trainer */}
      <DataPacket
        fromX={players.institution.cx} fromY={players.institution.cy}
        toX={players.trainer.cx} toY={players.trainer.cy}
        progress={broadcast < 1 && broadcast > 0.15 ? (broadcast - 0.15) / 0.85 : 0}
        label="⚡" curve={0.15} size={26}
      />

      {/* Job listing at trainer */}
      {listingAtTrainer > 0 && apply < 0.9 && (
        <MiniCard
          x={players.trainer.cx - 110} y={players.trainer.cy - 40}
          opacity={listingAtTrainer * (1 - Math.max(0, (apply - 0.8) / 0.2))}
          slideUp={(1 - listingAtTrainer) * 16}
          tag="⚡ OPEN JOB"
          title="Lead Strength Coach"
          sub="Iron Peak · Indiranagar"
          cta={apply > 0.3 ? '✓ APPLIED' : 'APPLY'}
          ctaActive={apply > 0.3}
        />
      )}

      {/* Application flies back */}
      <DataPacket
        fromX={players.trainer.cx} fromY={players.trainer.cy + 40}
        toX={players.institution.cx} toY={players.institution.cy + 40}
        progress={apply < 1 && apply > 0.15 ? apply : 0}
        label="↩" curve={-0.18} size={22}
      />

      {/* Hired confirmation — fires from institution */}
      <DataPacket
        fromX={players.institution.cx} fromY={players.institution.cy}
        toX={players.trainer.cx} toY={players.trainer.cy}
        progress={hired < 1 ? hired : 0}
        label="✓" curve={0.15} size={28}
      />

      {/* Institution shortlist panel */}
      {apply > 0.25 && (
        <div style={{
          position: 'absolute',
          left: players.institution.cx - 140,
          top: players.institution.cy + 180,
          opacity: Math.min(1, (apply - 0.25) / 0.25) * (1 - Math.max(0, (t - 0.95) / 0.05)),
          width: 280,
          padding: 14,
          background: 'rgba(20,20,20,0.96)',
          border: `1px solid ${hired > 0.3 ? BRAND.volt : BRAND.glassBorderStrong}`,
          borderRadius: 12,
          fontFamily: FONT_SANS,
          boxShadow: '0 20px 50px rgba(0,0,0,0.5)',
          zIndex: 20,
        }}>
          <div style={{ fontSize: 9, color: BRAND.volt, letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
            ● 14 APPLICATIONS
          </div>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 10px', background: hired > 0.3 ? 'rgba(204,255,0,0.08)' : 'rgba(255,255,255,0.04)', borderRadius: 6, border: hired > 0.3 ? `1px solid ${BRAND.volt}` : '1px solid transparent', transition: 'all 300ms' }}>
            <div>
              <div style={{ fontSize: 12, color: '#fff', fontWeight: 700 }}>Coach Ryan</div>
              <div style={{ fontSize: 10, color: BRAND.textSecondary }}>Strength · 8 yrs · ★ 4.9</div>
            </div>
            <div style={{
              fontSize: 9, padding: '4px 8px', borderRadius: 4, fontWeight: 800,
              background: hired > 0.3 ? BRAND.volt : 'rgba(255,255,255,0.08)',
              color: hired > 0.3 ? '#0a0a0a' : '#fff',
              letterSpacing: '0.1em',
            }}>
              {hired > 0.3 ? 'HIRED' : 'REVIEW'}
            </div>
          </div>
        </div>
      )}

      {/* Badge on trainer */}
      {badge > 0 && (
        <div style={{
          position: 'absolute',
          left: players.trainer.cx - 105,
          top: players.trainer.cy + 200,
          opacity: badge,
          transform: `scale(${0.6 + badge * 0.4})`,
          padding: '8px 18px',
          background: BRAND.volt,
          color: '#0a0a0a',
          borderRadius: 20,
          fontFamily: FONT_MONO,
          fontSize: 11,
          fontWeight: 800,
          letterSpacing: '0.18em',
          boxShadow: `0 0 30px ${BRAND.voltGlow}`,
          zIndex: 20,
          whiteSpace: 'nowrap',
        }}>
          ⚡ IRON PEAK · COACH
        </div>
      )}
    </>
  );
}

// ── Beat 4 — "Community" ─────────────────────────
// All three roles share a social feed: challenges, cheers, leaderboard.
// Visual: a giant shared stream runs through the middle with posts from
// each player; cheers pulse between them.
function BeatCommunity({ t, players }) {
  // Build a rolling feed of 5 shared posts that reveal sequentially.
  const posts = [
    { t: 0.00, from: 'enthusiast', name: 'Samira',    text: 'Hit 21-day streak 🔥',            kind: 'post', tag: 'ENTHUSIAST' },
    { t: 0.18, from: 'trainer',    name: 'Coach Ryan', text: 'Challenge · 30-day full body', kind: 'challenge', tag: 'TRAINER' },
    { t: 0.36, from: 'institution', name: 'Iron Peak', text: 'Weekly leaderboard · top 10',  kind: 'board', tag: 'FITNESS CENTER' },
    { t: 0.55, from: 'enthusiast', name: 'Aisha R.',   text: 'Joined challenge',               kind: 'post', tag: 'ENTHUSIAST' },
    { t: 0.72, from: 'trainer',    name: 'Coach Priya', text: 'Live demo · Fri 7 PM',          kind: 'live', tag: 'TRAINER' },
  ];

  // Cheer pulses between the three silhouettes
  const cheers = [
    { at: 0.12, from: 'enthusiast', to: 'trainer' },
    { at: 0.30, from: 'trainer',    to: 'enthusiast' },
    { at: 0.48, from: 'institution', to: 'enthusiast' },
    { at: 0.62, from: 'enthusiast', to: 'institution' },
    { at: 0.78, from: 'trainer', to: 'institution' },
  ];

  return (
    <>
      {/* Feed stream — moved to the TOP, above silhouettes, spread across width */}
      <div style={{
        position: 'absolute',
        left: 96, right: 96,
        top: 140, height: 120,
        zIndex: 15,
      }}>
        <div style={{
          position: 'absolute', left: 0, top: 0,
          fontFamily: FONT_MONO, fontSize: 12, fontWeight: 700,
          color: BRAND.volt, letterSpacing: '0.24em',
          display: 'flex', alignItems: 'center', gap: 10,
        }}>
          <div style={{ width: 8, height: 8, borderRadius: '50%', background: BRAND.volt, boxShadow: `0 0 10px ${BRAND.volt}` }}/>
          SHARED FEED · LIVE
          <span style={{ color: BRAND.textMeta, marginLeft: 8, letterSpacing: '0.2em' }}>· CHALLENGES · CHEERS · LEADERBOARD</span>
        </div>
        {/* Posts row — evenly distributed strip */}
        <div style={{
          position: 'absolute', left: 0, right: 0, top: 32,
          display: 'flex', alignItems: 'stretch', gap: 16,
        }}>
          {posts.map((p, i) => {
            const reveal = Math.max(0, Math.min(1, (t - p.t) / 0.15));
            if (reveal <= 0) return null;
            const color = p.from === 'institution' ? '#CCFF00' : p.from === 'trainer' ? '#D9FF33' : '#fff';
            return (
              <div key={i} style={{
                flex: 1,
                padding: '10px 14px',
                background: 'rgba(20,20,20,0.92)',
                border: `1px solid ${BRAND.glassBorderStrong}`,
                borderLeft: `3px solid ${color}`,
                borderRadius: 10,
                opacity: reveal,
                transform: `translateY(${(1 - reveal) * 14}px)`,
                fontFamily: FONT_SANS,
                backdropFilter: 'blur(8px)',
              }}>
                <div style={{ fontSize: 9, color, letterSpacing: '0.22em', fontWeight: 700, marginBottom: 4 }}>
                  {p.tag}
                </div>
                <div style={{ fontSize: 13, color: '#fff', fontWeight: 700, marginBottom: 2, letterSpacing: '-0.01em' }}>{p.name}</div>
                <div style={{ fontSize: 11, color: BRAND.textSecondary }}>{p.text}</div>
              </div>
            );
          })}
        </div>
      </div>

      {/* Cheer pulses flying between silhouettes */}
      {cheers.map((c, i) => {
        const local = (t - c.at) / 0.15;
        if (local <= 0 || local >= 1) return null;
        const from = players[c.from];
        const to = players[c.to];
        return (
          <DataPacket
            key={i}
            fromX={from.cx} fromY={from.cy - 40}
            toX={to.cx} toY={to.cy - 40}
            progress={local}
            label="♥"
            curve={i % 2 === 0 ? 0.2 : -0.2}
            size={20}
          />
        );
      })}

      {/* Leaderboard mini — positioned top-right area, below feed */}
      {t > 0.4 && (
        <div style={{
          position: 'absolute',
          right: 96,
          top: 310,
          opacity: Math.min(1, (t - 0.4) / 0.2),
          width: 300,
          padding: 14,
          background: 'rgba(20,20,20,0.96)',
          border: `1px solid ${BRAND.volt}`,
          borderRadius: 12,
          fontFamily: FONT_SANS,
          boxShadow: '0 0 30px rgba(204,255,0,0.2)',
          zIndex: 20,
        }}>
          <div style={{ fontSize: 9, color: BRAND.volt, letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
            ● 30-DAY CHALLENGE · TOP 3
          </div>
          {['Samira K.', 'Aisha R.', 'Karthik S.'].map((n, i) => (
            <div key={i} style={{
              display: 'flex', alignItems: 'center', gap: 10, padding: '5px 0',
              fontSize: 12, color: i === 0 ? '#fff' : BRAND.textSecondary,
              borderBottom: i < 2 ? '1px solid rgba(255,255,255,0.04)' : 'none',
            }}>
              <span style={{ fontFamily: FONT_MONO, fontWeight: 800, color: i === 0 ? BRAND.volt : BRAND.textTertiary, width: 20 }}>
                #{i + 1}
              </span>
              <span style={{ fontWeight: 600, flex: 1 }}>{n}</span>
              <span style={{ fontFamily: FONT_MONO, color: i === 0 ? BRAND.volt : BRAND.textTertiary }}>
                {[12400, 11280, 9945][i].toLocaleString()}
              </span>
            </div>
          ))}
        </div>
      )}
    </>
  );
}

// ── Helpers ───────────────────────────────────────
function MiniCard({ x, y, opacity = 1, slideUp = 0, tag, title, sub, cta, ctaActive, footer, active }) {
  return (
    <div style={{
      position: 'absolute', left: x, top: y,
      opacity,
      transform: `translateY(${slideUp}px)`,
      width: 220, padding: 14,
      background: 'rgba(20,20,20,0.94)',
      border: `1px solid ${active || ctaActive ? BRAND.volt : BRAND.glassBorderStrong}`,
      borderRadius: 12,
      backdropFilter: 'blur(10px)',
      boxShadow: `0 20px 50px rgba(0,0,0,0.5), 0 0 30px ${active || ctaActive ? 'rgba(204,255,0,0.3)' : 'transparent'}`,
      fontFamily: FONT_SANS,
      zIndex: 20,
    }}>
      <div style={{ fontSize: 9, color: BRAND.volt, letterSpacing: '0.2em', fontWeight: 700, marginBottom: 6 }}>
        {tag}
      </div>
      <div style={{ fontSize: 14, color: '#fff', fontWeight: 700, marginBottom: 2 }}>{title}</div>
      <div style={{ fontSize: 11, color: BRAND.textSecondary, marginBottom: cta || footer ? 10 : 0 }}>{sub}</div>
      {cta && (
        <div style={{
          fontSize: 10, padding: '6px 10px', borderRadius: 6, fontWeight: 700, textAlign: 'center',
          background: ctaActive ? BRAND.volt : 'rgba(255,255,255,0.08)',
          color: ctaActive ? '#0a0a0a' : '#fff',
          transition: 'all 300ms',
          letterSpacing: '0.05em',
        }}>
          {cta}
        </div>
      )}
      {footer && (
        <div style={{
          display: 'flex', alignItems: 'center', gap: 6,
          fontSize: 10, color: BRAND.volt, fontWeight: 600,
        }}>
          <div style={{ width: 6, height: 6, borderRadius: '50%', background: BRAND.volt, boxShadow: `0 0 6px ${BRAND.volt}` }}/>
          {footer}
        </div>
      )}
    </div>
  );
}

function MiniStat({ label, value }) {
  return (
    <div>
      <div style={{ fontFamily: FONT_DISPLAY, fontSize: 22, fontWeight: 800, color: '#fff', lineHeight: 1 }}>{value}</div>
      <div style={{ fontFamily: FONT_MONO, fontSize: 9, color: BRAND.textTertiary, letterSpacing: '0.2em', marginTop: 3 }}>{label}</div>
    </div>
  );
}

// Ambient static line between the 3 silhouettes
function AmbientLinks({ players, time }) {
  const nodes = [players.institution, players.trainer, players.enthusiast];
  return (
    <svg style={{ position: 'absolute', inset: 0, pointerEvents: 'none', overflow: 'visible' }} viewBox="0 0 1920 1080">
      {[[0,1],[1,2],[0,2]].map(([a,b], i) => {
        const A = nodes[a], B = nodes[b];
        return (
          <line key={i}
            x1={A.cx} y1={A.cy} x2={B.cx} y2={B.cy}
            stroke="#CCFF00"
            strokeOpacity="0.1"
            strokeWidth="1"
            strokeDasharray="3 6"
          />
        );
      })}
    </svg>
  );
}

// Dynamic beat headline + subtitle, crossfades between beats
function BeatHeadline({ beat1T, beat2T, beat3T, beat4T }) {
  const beats = [
    { title: 'Coach.',    subtitle: 'Find them. Book them. Train with them.' },
    { title: 'Fitness Centers.',   subtitle: 'Every member. Every branch. One view.' },
    { title: 'Hire.',     subtitle: 'Top trainers, shortlisted in minutes.' },
    { title: 'Community.',   subtitle: 'Challenges, cheers, a feed you share.' },
  ];
  const ts = [beat1T, beat2T, beat3T, beat4T];
  return (
    <div style={{ position: 'relative', width: 1100, minHeight: 180 }}>
      {beats.map((b, i) => {
        const tVal = ts[i];
        const fadeIn = Math.min(1, Math.max(0, (tVal - 0.02) / 0.15));
        const fadeOut = Math.min(1, Math.max(0, (0.97 - tVal) / 0.15));
        const visible = tVal > 0.02 && tVal < 0.97;
        const op = visible ? fadeIn * fadeOut : 0;
        return (
          <div key={i} style={{
            position: 'absolute', inset: 0,
            opacity: op,
            transform: `translateY(${(1 - fadeIn) * 20}px)`,
          }}>
            <div style={{
              fontFamily: FONT_DISPLAY,
              fontSize: 96, fontWeight: 900,
              color: BRAND.volt,
              letterSpacing: '-0.04em', lineHeight: 0.9,
              textShadow: `0 0 50px ${BRAND.voltGlow}`,
            }}>{b.title}</div>
            <div style={{
              fontFamily: FONT_DISPLAY,
              fontSize: 28, fontWeight: 500,
              color: 'rgba(255,255,255,0.82)',
              letterSpacing: '-0.01em',
              marginTop: 10,
            }}>{b.subtitle}</div>
          </div>
        );
      })}
    </div>
  );
}

// Narrative live-log — changed to pure relational events (no payouts)
function LiveLog({ secs }) {
  const lines = [
    { t: 1.5,  text: 'samira.k → ryan.coach · demo.book',           tag: 'req' },
    { t: 3.0,  text: 'ryan.coach ← demo.accepted · sat 10:00',      tag: 'ok' },
    { t: 6.0,  text: 'ironpeak → samira.k · member.issued',         tag: 'req' },
    { t: 7.8,  text: 'samira.k → ironpeak · check-in',              tag: 'ok' },
    { t: 9.5,  text: 'ironpeak.dashboard · 247 today · 1,289 total', tag: 'ok' },
    { t: 11.5, text: 'ironpeak → jobs.broadcast · lead.strength',   tag: 'req' },
    { t: 13.5, text: 'ryan.coach → application.submit',             tag: 'req' },
    { t: 15.2, text: 'ironpeak ← shortlist · hired ryan.coach',     tag: 'ok' },
    { t: 17.5, text: 'feed.live · samira.k · streak 21 days',       tag: 'req' },
    { t: 19.0, text: 'feed.live · challenge · 30-day full body',    tag: 'ok' },
    { t: 20.2, text: 'leaderboard.update · 12,400 pts',             tag: 'ok' },
  ];
  const visible = lines.filter(l => secs >= l.t).slice(-5);
  return (
    <div style={{
      position: 'absolute',
      right: 96, bottom: 160,
      width: 420,
      fontFamily: FONT_MONO,
    }}>
      <div style={{ fontSize: 11, color: BRAND.textMeta, letterSpacing: '0.22em', fontWeight: 700, marginBottom: 14 }}>
        ● LIVE · PLATFORM LOG
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {visible.map((l) => {
          const age = secs - l.t;
          const opacity = Math.max(0.25, 1 - age * 0.12);
          return (
            <div key={l.t} style={{
              display: 'flex', alignItems: 'center', gap: 10,
              fontSize: 12, opacity,
            }}>
              <div style={{ width: 6, height: 6, borderRadius: '50%', background: l.tag === 'ok' ? BRAND.volt : 'rgba(255,255,255,0.5)' }}/>
              <div style={{ color: '#fff' }}>{l.text}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

Object.assign(window, { Scene_Exchange });
