/* T-AV — Cinematic SVG visuals for each of the 11 home-page beats.
   Each visual receives `progress` (0..1) representing how far through
   the beat's pin we are, and animates accordingly. */

const { useRef, useState, useEffect } = React;

/* Helper: clamp + map.
   clamp() doubles as a defense-in-depth guard: a non-finite input
   (NaN / undefined — e.g. a degenerate `progress` prop) resolves to `lo`
   instead of poisoning every derived SVG attribute. The root cause of a
   NaN `progress` is fixed at source in pages/Home.jsx's __scrollFlush;
   this keeps the visuals robust to any future caller too. */
const clamp = (v, lo = 0, hi = 1) => (Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : lo);
const mix = (a, b, t) => a + (b - a) * t;

/* ===== Beat 1 — Empty venue with typewriter intake form.
   The form is scroll-driven via the `progress` prop:
   - 0 → 0.65: 8 event-type rows type themselves in one at a time
                (EXHIBITIONS → LAUNCH → SPORTS → CULTURAL → GOVERNMENT
                 → CORPORATE → PRIVATE → CONCERT)
   - 0.65 → 0.70: stamp lands on CONCERT row — [ ] flips to [×],
                  white halo flashes around the box (overshoot scale,
                  decaying radius), label brightens to bold white,
                  ◀ SELECTED tag slides in from the right.
   - 0.70 → 1.00: locked state holds.
   Render is pure on `progress` — reverse scroll un-types cleanly.
   Cursor blinks at 2Hz via a separate setInterval.
   Reduced-motion shows the locked state regardless of scroll. */
function BeatEmpty({ progress }) {
  const ROWS = ['EXHIBITIONS', 'LAUNCH', 'SPORTS', 'CULTURAL', 'GOVERNMENT', 'CORPORATE', 'PRIVATE', 'CONCERT'];
  const ROW_Y = [184, 212, 240, 268, 296, 324, 352, 380];
  const CONCERT_IDX = ROWS.length - 1;
  const STAMP_AT = 0.65;
  const FLASH_WIDTH = 0.05;
  const SCAN_END = STAMP_AT;
  const PREFIX = '[ ] ';
  const STAMPED_PREFIX = '[×] ';
  const CHAR_WIDTH = 11.5;

  const wrapperRef = useRef(null);

  const [reduced, setReduced] = useState(false);
  const [blink, setBlink] = useState(true);

  useEffect(() => {
    if (typeof window === 'undefined' || !window.matchMedia) return;
    const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
    setReduced(mql.matches);
    const handler = (e) => setReduced(e.matches);
    mql.addEventListener('change', handler);
    return () => mql.removeEventListener('change', handler);
  }, []);

  useEffect(() => {
    if (reduced) return;
    if (typeof IntersectionObserver === 'undefined') {
      const id = setInterval(() => setBlink((b) => !b), 500);
      return () => clearInterval(id);
    }
    const el = wrapperRef.current;
    if (!el) return;
    let intervalId = null;
    const start = () => {
      if (intervalId !== null) return;
      intervalId = setInterval(() => setBlink((b) => !b), 500);
    };
    const stop = () => {
      if (intervalId === null) return;
      clearInterval(intervalId);
      intervalId = null;
    };
    const io = new IntersectionObserver((entries) => {
      const e = entries[entries.length - 1];
      if (e.isIntersecting) start(); else stop();
    }, { threshold: 0 });
    io.observe(el);
    return () => {
      io.disconnect();
      stop();
    };
  }, [reduced]);

  const t = clamp(progress);
  const stamped = reduced || t >= STAMP_AT;
  const flashI = reduced ? 0 : (stamped ? (1 - Math.min(1, (t - STAMP_AT) / FLASH_WIDTH)) : 0);
  const scanT = reduced ? 1 : Math.min(1, t / SCAN_END);
  const exact = scanT * ROWS.length;
  const currentRow = Math.min(ROWS.length - 1, Math.floor(exact));
  const within = exact - currentRow;

  const rowTexts = ROWS.map((label, i) => {
    const prefix = (stamped && i === CONCERT_IDX) ? STAMPED_PREFIX : PREFIX;
    const fullText = prefix + label;
    let visibleChars;
    if (stamped) visibleChars = fullText.length;
    else if (i < currentRow) visibleChars = fullText.length;
    else if (i === currentRow) visibleChars = Math.floor(within * fullText.length);
    else visibleChars = 0;
    return { fullText, visibleChars };
  });

  const currentText = rowTexts[currentRow];
  const cursorX = 232 + currentText.visibleChars * CHAR_WIDTH;
  const cursorY = ROW_Y[currentRow] - 13;
  let cursorOpacity;
  if (stamped) cursorOpacity = 0;
  else if (t < 0.005) cursorOpacity = 0.85;
  else cursorOpacity = blink ? 0.85 : 0.2;

  const barY = stamped ? (ROW_Y[CONCERT_IDX] + 6) : (ROW_Y[currentRow] + 6);
  const barOpacity = (t < 0.005 && !stamped) ? 0 : (stamped ? (0.4 + flashI * 0.4) : 0.4);

  const haloR = 8 + flashI * 28;
  const haloOpacity = flashI * 0.75;
  const selectedOpacity = stamped ? 1 : 0;
  const selectedSlideX = stamped ? (1 - Math.min(1, (t - STAMP_AT) / 0.04)) * 18 : 18;

  let signalText;
  if (stamped) signalText = 'SIGNAL · LOCKED';
  else if (t < 0.005) signalText = 'SIGNAL · AWAITING';
  else signalText = 'SIGNAL · CAPTURING · ' + (currentRow + 1) + '/' + ROWS.length;

  const dust = Array.from({ length: 14 }, (_, i) => i);

  return (
    <svg ref={wrapperRef} viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <defs>
        <radialGradient id="empty-floor" cx="50%" cy="80%" r="60%">
          <stop offset="0%" stopColor="#161616" />
          <stop offset="100%" stopColor="#050507" />
        </radialGradient>
        <radialGradient id="empty-halo" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#f5f5f5" stopOpacity="0.85" />
          <stop offset="60%" stopColor="#3E8C84" stopOpacity="0.35" />
          <stop offset="100%" stopColor="#3E8C84" stopOpacity="0" />
        </radialGradient>
      </defs>
      <rect width="800" height="500" fill="url(#empty-floor)" />
      {/* Floor perspective — opacity halved to read behind the form panel */}
      {Array.from({ length: 8 }, (_, i) => {
        const y = 320 + i * 22;
        const inset = i * 36;
        return <line key={i} x1={inset} y1={y} x2={800 - inset} y2={y}
                     stroke="#3d3a39" strokeOpacity={(0.4 - i * 0.04) * 0.5} strokeWidth="1" strokeDasharray="3 6" />;
      })}
      {/* Drifting dust — unchanged */}
      {dust.map(i => {
        const x = (i * 67 + progress * 80) % 800;
        const y = 200 + Math.sin(i * 1.3 + progress * 6) * 80 + i * 6;
        return <circle key={i} cx={x} cy={y} r="1.2" fill="#6DACB5"
                       fillOpacity={0.3 + Math.sin(i + progress * 4) * 0.2} />;
      })}
      {/* Scan line — unchanged */}
      <line x1="0" y1={250 - progress * 60} x2="800" y2={250 - progress * 60}
            stroke="#3E8C84" strokeOpacity="0.18" strokeWidth="1" />
      {/* Corner status labels */}
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">{'VENUE · DAY 0 · INTAKE FORM'}</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">{signalText}</text>
      {/* Form panel (semi-transparent so dust + floor lines read through) */}
      <rect x="200" y="80" width="400" height="380" fill="#0a0a0a" fillOpacity="0.72" stroke="#2a2a2c" strokeWidth="1.2" />
      <rect x="200" y="80" width="400" height="32" fill="#15191b" stroke="#2a2a2c" strokeWidth="1.2" />
      <text x="216" y="101" fontFamily="ui-monospace, monospace" fontSize="10"
            fill="#8b949e" letterSpacing="2.4">{'/// EVENT BRIEF · INTAKE'}</text>
      <text x="584" y="101" fontFamily="ui-monospace, monospace" fontSize="10"
            fill="#8b949e" letterSpacing="2.4" textAnchor="end">FORM-001</text>
      <text x="216" y="138" fontFamily="ui-monospace, monospace" fontSize="10"
            fill="#8b949e" letterSpacing="2.4">{'EVENT TYPE · SELECT ONE'}</text>
      <line x1="216" y1="146" x2="584" y2="146" stroke="#2a2a2c" strokeWidth="0.6" />
      {/* Halo behind the CONCERT [×] (only visible during flash window) */}
      <circle cx="244" cy={ROW_Y[CONCERT_IDX] - 4} r={haloR} fill="url(#empty-halo)" opacity={haloOpacity} />
      {/* Underline bar (follows current row during scan, parks under CONCERT after stamp) */}
      <rect x="216" y={barY} width="368" height="2" fill="#3E8C84" fillOpacity={barOpacity} rx="1"
            style={{ transition: 'y 200ms ease, fill-opacity 200ms ease' }} />
      {/* Form rows — text content varies per render */}
      {rowTexts.map((row, i) => {
        const isStampedConcert = stamped && i === CONCERT_IDX;
        const isCurrent = !stamped && i === currentRow && row.visibleChars > 0;
        const textStyle = isStampedConcert
          ? { transform: 'scale(' + (1 + flashI * 0.6) + ')', transformBox: 'fill-box', transformOrigin: 'center', transition: 'fill 200ms ease' }
          : { transition: 'fill 200ms ease' };
        return (
          <text key={i} x="232" y={ROW_Y[i]}
                fontFamily="ui-monospace, monospace" fontSize="13"
                fill={isStampedConcert ? '#f5f5f5' : (isCurrent ? '#cfd2d4' : '#8b949e')}
                fontWeight={isStampedConcert ? 700 : 400}
                letterSpacing="2"
                style={textStyle}>
            {row.fullText.slice(0, row.visibleChars)}
          </text>
        );
      })}
      {/* Blinking cursor */}
      <rect x={cursorX} y={cursorY} width="9" height="14" fill="#3E8C84" opacity={cursorOpacity} />
      {/* SELECTED tag (slides in from the right after stamp) */}
      <text x="568" y={ROW_Y[CONCERT_IDX]}
            fontFamily="ui-monospace, monospace" fontSize="10"
            fill="#3E8C84" letterSpacing="2.4" textAnchor="end"
            opacity={selectedOpacity}
            style={{ transform: 'translateX(' + selectedSlideX + 'px)', transition: 'opacity 200ms ease' }}>
        {'◀ SELECTED'}
      </text>
    </svg>
  );
}

/* ===== Beat 2 — Floor plan grid + stage rectangle drawing in ===== */
function BeatFloorPlan({ progress }) {
  const t = clamp(progress * 1.4);
  const stagePerim = 2 * (240 + 130);
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="800" height="500" fill="#0a0a0a" />
      {/* Grid */}
      {Array.from({ length: 25 }, (_, i) => (
        <line key={'v'+i} x1={i * 32} y1={0} x2={i * 32} y2={500}
              stroke="#3d3a39" strokeOpacity={0.18 + Math.min(0.3, t * 0.4)} strokeWidth="0.5" />
      ))}
      {Array.from({ length: 16 }, (_, i) => (
        <line key={'h'+i} x1={0} y1={i * 32} x2={800} y2={i * 32}
              stroke="#3d3a39" strokeOpacity={0.18 + Math.min(0.3, t * 0.4)} strokeWidth="0.5" />
      ))}
      {/* Stage rect drawing in */}
      <rect x="280" y="180" width="240" height="130"
            fill="none" stroke="#3E8C84" strokeWidth="2"
            strokeDasharray={stagePerim}
            strokeDashoffset={stagePerim * (1 - t)} />
      {/* Audience zone */}
      <rect x="200" y="340" width="400" height="130"
            fill="none" stroke="#6DACB5" strokeOpacity={t * 0.5}
            strokeWidth="1" strokeDasharray="4 6" />
      {/* Labels */}
      <text x="400" y="248" textAnchor="middle"
            fontFamily="ui-monospace, monospace" fontSize="13"
            fill="#3E8C84" opacity={t}>STAGE</text>
      <text x="400" y="412" textAnchor="middle"
            fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" opacity={clamp((t - 0.3) * 2)} letterSpacing="2">AUDIENCE · 1,400 PAX</text>
      {/* FOH marker */}
      <g opacity={clamp((t - 0.5) * 2)}>
        <rect x="380" y="450" width="40" height="14" fill="none" stroke="#3E8C84" strokeWidth="1" />
        <text x="400" y="460" textAnchor="middle"
              fontFamily="ui-monospace, monospace" fontSize="9"
              fill="#6DACB5" letterSpacing="1.5">FOH</text>
      </g>
      {/* Dimensions */}
      <g opacity={clamp((t - 0.4) * 2)} stroke="#8b949e" strokeWidth="0.5">
        <line x1="280" y1="160" x2="520" y2="160" />
        <line x1="280" y1="155" x2="280" y2="165" />
        <line x1="520" y1="155" x2="520" y2="165" />
      </g>
      <text x="400" y="148" textAnchor="middle" fontFamily="ui-monospace, monospace"
            fontSize="10" fill="#8b949e" opacity={clamp((t - 0.4) * 2)}>24.0 m</text>
      {/* Header */}
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">FLOOR PLAN · v1 · SCALE 1:50</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">VECTORWORKS</text>
    </svg>
  );
}

/* ===== Beat 3 — Trusses fly in ===== */
function BeatTrusses({ progress }) {
  const t = clamp(progress * 1.2);
  const truss = (x, y, w) => {
    const segs = Math.floor(w / 28);
    return (
      <g>
        <line x1={x} y1={y} x2={x + w} y2={y} stroke="#3E8C84" strokeWidth="1.5" />
        <line x1={x} y1={y + 12} x2={x + w} y2={y + 12} stroke="#3E8C84" strokeWidth="1.5" />
        {Array.from({ length: segs }, (_, i) => (
          <g key={i}>
            <line x1={x + i * 28} y1={y} x2={x + (i + 1) * 28} y2={y + 12} stroke="#3E8C84" strokeWidth="0.8" />
            <line x1={x + (i + 1) * 28} y1={y} x2={x + i * 28} y2={y + 12} stroke="#3E8C84" strokeWidth="0.8" />
            <line x1={x + i * 28} y1={y} x2={x + i * 28} y2={y + 12} stroke="#3E8C84" strokeWidth="0.6" strokeOpacity="0.7" />
          </g>
        ))}
      </g>
    );
  };
  // Truss flies down from top
  const topY = mix(-30, 100, t);
  const sideY = mix(-30, 220, clamp((t - 0.3) * 1.5));
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="800" height="500" fill="#0a0a0a" />
      {/* Vertical motor lines */}
      {[150, 400, 650].map((x, i) => (
        <line key={i} x1={x} y1={0} x2={x} y2={topY}
              stroke="#3d3a39" strokeWidth="0.8" strokeDasharray="2 4" />
      ))}
      {/* Top truss */}
      {truss(150, topY, 500)}
      {/* Side trusses */}
      {sideY > 0 && truss(150, sideY, 200)}
      {sideY > 0 && truss(450, sideY, 200)}
      {/* Floor */}
      <line x1="0" y1="420" x2="800" y2="420" stroke="#3d3a39" strokeWidth="1" />
      {/* Headers */}
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">RIG PLOT · 24m × 12m · SWL 250kg</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">FLY · {Math.round(t * 100)}%</text>
      {/* Hook icons */}
      {[150, 400, 650].map((x, i) => (
        <circle key={i} cx={x} cy={topY} r="3" fill="#3E8C84" opacity={t} />
      ))}
    </svg>
  );
}

/* ===== Beat 4 — Speakers stack with SPL meter ===== */
function BeatAudio({ progress }) {
  const t = clamp(progress * 1.2);
  const stackCount = Math.min(6, Math.floor(t * 8));
  const speaker = (x, y, i) => (
    <g key={i}>
      <path d={`M ${x} ${y} L ${x + 60} ${y - 6} L ${x + 60} ${y + 22} L ${x} ${y + 28} Z`}
            fill="none" stroke="#3E8C84" strokeWidth="1.4" />
      <circle cx={x + 18} cy={y + 12} r="6" fill="none" stroke="#3E8C84" strokeWidth="0.8" opacity="0.7" />
      <circle cx={x + 42} cy={y + 8} r="4" fill="none" stroke="#3E8C84" strokeWidth="0.8" opacity="0.7" />
    </g>
  );
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="800" height="500" fill="#0a0a0a" />
      {/* Left and right stacks */}
      <g>{Array.from({ length: stackCount }, (_, i) => speaker(140, 380 - i * 36, i))}</g>
      <g>{Array.from({ length: stackCount }, (_, i) => speaker(600, 380 - i * 36, i + 100))}</g>
      {/* Hang line */}
      <line x1="170" y1="40" x2="170" y2={Math.max(40, 380 - stackCount * 36)}
            stroke="#3d3a39" strokeWidth="0.8" strokeDasharray="3 4" />
      <line x1="630" y1="40" x2="630" y2={Math.max(40, 380 - stackCount * 36)}
            stroke="#3d3a39" strokeWidth="0.8" strokeDasharray="3 4" />
      {/* SPL meter bars */}
      <g transform="translate(310, 200)">
        {Array.from({ length: 16 }, (_, i) => {
          const lit = (Math.sin(progress * 8 + i * 0.6) * 0.5 + 0.5) > (1 - t * 1.1);
          const h = 6 + i * 1.6;
          const color = i < 11 ? '#3E8C84' : i < 14 ? '#ffba00' : '#fb565b';
          return (
            <rect key={i} x={i * 11} y={120 - h} width="7" height={h}
                  fill={lit ? color : '#161616'} stroke={color} strokeOpacity={lit ? 0 : 0.3} strokeWidth="0.6" />
          );
        })}
        <text x="0" y="-8" fontFamily="ui-monospace, monospace" fontSize="10"
              fill="#8b949e" letterSpacing="1.5">SPL · 102 dBA @ FOH</text>
        <text x="180" y="-8" fontFamily="ui-monospace, monospace" fontSize="10"
              fill="#3E8C84" letterSpacing="1.5" textAnchor="end" opacity={t}>TIME-ALIGNED</text>
      </g>
      {/* Header */}
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">L-ACOUSTICS K2 · 2 × {stackCount}</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">FOH · ARMED</text>
    </svg>
  );
}

/* ===== Beat 5 — Lights rig in (cones sweep + come on) ===== */
function BeatLights({ progress }) {
  const t = clamp(progress * 1.2);
  const positions = [180, 320, 460, 600];
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <defs>
        <linearGradient id="cone" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#3E8C84" stopOpacity="0.5" />
          <stop offset="100%" stopColor="#3E8C84" stopOpacity="0" />
        </linearGradient>
      </defs>
      <rect width="800" height="500" fill="#0a0a0a" />
      {/* Top truss */}
      <line x1="120" y1="100" x2="680" y2="100" stroke="#3E8C84" strokeWidth="1.5" />
      <line x1="120" y1="112" x2="680" y2="112" stroke="#3E8C84" strokeWidth="1.5" />
      {/* Lights */}
      {positions.map((x, i) => {
        const sweep = Math.sin(progress * 4 + i * 0.7) * 30;
        const litT = clamp((t - i * 0.12) * 2);
        return (
          <g key={i} transform={`translate(${x}, 112)`}>
            <circle cx="0" cy="0" r="6" fill="#161616" stroke="#3E8C84" strokeWidth="1.2" />
            <g transform={`rotate(${sweep * litT})`}>
              <path d={`M 0 6 L -36 220 L 36 220 Z`} fill="url(#cone)" opacity={litT * 0.8} />
              <line x1="0" y1="6" x2="0" y2="220" stroke="#3E8C84" strokeWidth="0.6" opacity={litT * 0.4} />
              <circle cx="0" cy="220" r={litT * 14} fill="#3E8C84" opacity={litT * 0.18} />
            </g>
          </g>
        );
      })}
      {/* Floor */}
      <line x1="0" y1="380" x2="800" y2="380" stroke="#3d3a39" strokeWidth="1" />
      {/* Header */}
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">MOVING HEAD WASH × {positions.length} · TC LOCKED · LTC 30fps</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">CUE {Math.floor(t * 32).toString().padStart(3, '0')}</text>
    </svg>
  );
}

/* ===== Beat 6 — LED walls drop in ===== */
function BeatVideo({ progress }) {
  const t = clamp(progress * 1.2);
  const dropY = mix(-150, 110, t);
  const cellW = 16;
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="800" height="500" fill="#0a0a0a" />
      {/* Truss */}
      <line x1="100" y1="80" x2="700" y2="80" stroke="#3E8C84" strokeWidth="1.2" opacity="0.6" />
      {/* LED wall */}
      <g transform={`translate(160, ${dropY})`}>
        <rect x="0" y="0" width="480" height="240" fill="#050507" stroke="#3E8C84" strokeWidth="1.4" />
        {/* Pixel grid */}
        {Array.from({ length: 30 }, (_, c) =>
          Array.from({ length: 15 }, (_, r) => {
            const cx = c * cellW + 8;
            const cy = r * cellW + 8;
            const phase = (Math.sin(c * 0.5 + r * 0.3 + progress * 10) * 0.5 + 0.5);
            const alpha = (t - 0.4) * 1.6 * phase;
            return <rect key={c + '-' + r} x={cx - 2} y={cy - 2} width="4" height="4"
                         fill="#3E8C84" opacity={clamp(alpha)} />;
          })
        )}
        {/* Frame label */}
        <text x="240" y="-8" textAnchor="middle"
              fontFamily="ui-monospace, monospace" fontSize="10"
              fill="#8b949e" letterSpacing="1.5">P3.9 · 480 × 240 · 60Hz</text>
      </g>
      {/* Hang lines */}
      {[160, 400, 640].map((x, i) => (
        <line key={i} x1={x} y1="80" x2={x} y2={dropY} stroke="#3d3a39" strokeWidth="0.8" strokeDasharray="2 4" />
      ))}
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">LED · MAIN UPSTAGE · NOVASTAR H15</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">SIGNAL · LOCKED</text>
    </svg>
  );
}

/* ===== Beat 7 — All systems synced (waveforms aligned) ===== */
function BeatSynced({ progress }) {
  const t = clamp(progress * 1.1);
  const tracks = ['AUDIO', 'LIGHTING', 'VIDEO', 'TIMECODE'];
  const colors = ['#3E8C84', '#6DACB5', '#3E8C84', '#ffba00'];
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="800" height="500" fill="#0a0a0a" />
      {tracks.map((label, i) => {
        const y = 80 + i * 90;
        return (
          <g key={i}>
            <text x="40" y={y - 10} fontFamily="ui-monospace, monospace" fontSize="10"
                  fill="#8b949e" letterSpacing="1.5">{label}</text>
            <line x1="40" y1={y + 30} x2="760" y2={y + 30} stroke="#3d3a39" strokeWidth="0.5" />
            {/* Waveform */}
            <path d={Array.from({ length: 80 }, (_, j) => {
              const x = 40 + j * 9;
              const phase = j * 0.3 + i * 0.5 + progress * 6;
              const amp = (Math.sin(phase) * 0.5 + Math.sin(phase * 2.3) * 0.3) * 22 * t;
              return (j === 0 ? 'M' : 'L') + x + ' ' + (y + 30 - amp);
            }).join(' ')}
              fill="none" stroke={colors[i]} strokeWidth="1.4" />
            {/* Cue markers */}
            {[180, 360, 540, 680].map((x, k) => (
              <line key={k} x1={x} y1={y - 6} x2={x} y2={y + 50}
                    stroke={colors[i]} strokeOpacity={0.4 * t} strokeDasharray="1 3" />
            ))}
          </g>
        );
      })}
      {/* Playhead */}
      <line x1={mix(40, 760, t)} y1="60" x2={mix(40, 760, t)} y2="430"
            stroke="#3E8C84" strokeWidth="1.5" />
      <circle cx={mix(40, 760, t)} cy="60" r="4" fill="#3E8C84" />
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">SHOW FLOW · 4 SYSTEMS LOCKED · 30 fps</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">
        {String(Math.floor(t * 24)).padStart(2, '0')}:{String(Math.floor(t * 60 * 60) % 60).padStart(2, '0')}:00
      </text>
    </svg>
  );
}

/* ===== Beat 8 — Crowd silhouette ===== */
function BeatCrowd({ progress }) {
  const t = clamp(progress * 1.2);
  const heads = Array.from({ length: 80 }, (_, i) => {
    const row = Math.floor(i / 16);
    const col = i % 16;
    return { x: 60 + col * 46 + (row % 2) * 23, y: 320 + row * 28 };
  });
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <defs>
        <linearGradient id="stage-light" x1="0.5" y1="0" x2="0.5" y2="1">
          <stop offset="0%" stopColor="#3E8C84" stopOpacity="0.0" />
          <stop offset="50%" stopColor="#3E8C84" stopOpacity="0.5" />
          <stop offset="100%" stopColor="#3E8C84" stopOpacity="0.0" />
        </linearGradient>
      </defs>
      <rect width="800" height="500" fill="#050507" />
      {/* Stage glow */}
      <ellipse cx="400" cy="200" rx="320" ry="180" fill="url(#stage-light)" opacity={t * 0.6} />
      {/* Beam shafts */}
      {[280, 360, 440, 520].map((x, i) => (
        <path key={i} d={`M ${x} 80 L ${x - 60} 320 L ${x + 60} 320 Z`}
              fill="#3E8C84" opacity={0.05 + Math.sin(progress * 4 + i) * 0.05 * t} />
      ))}
      {/* Crowd heads */}
      {heads.map((h, i) => {
        const bob = Math.sin(progress * 6 + i * 0.4) * 2 * t;
        return (
          <circle key={i} cx={h.x} cy={h.y + bob} r="6"
                  fill="#050507" stroke="#3E8C84" strokeOpacity={0.15 + t * 0.25} strokeWidth="0.8" />
        );
      })}
      {/* Shoulders */}
      <path d={heads.map((h, i) => (i === 0 ? 'M' : 'L') + h.x + ' ' + (h.y + 12)).join(' ')}
            fill="none" stroke="#3E8C84" strokeOpacity={0.15 * t} strokeWidth="1" />
      <text x="40" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">DOORS · OPEN · {Math.floor(t * 1400)} PAX</text>
      <text x="760" y="40" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">SHOW · LIVE</text>
    </svg>
  );
}

/* ===== Beat 9 — Frozen frame (Polaroid stack of past shows) ===== */
function BeatFrame({ progress }) {
  // not animated by progress — static photographic moment
  return null;
}

/* ===== Beat 10 — Quiet stage (gentle pulse) ===== */
function BeatQuiet({ progress }) {
  const t = clamp(progress);
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="800" height="500" fill="#0a0a0a" />
      <line x1="100" y1="180" x2="700" y2="180" stroke="#3E8C84" strokeWidth="1" opacity="0.5" />
      <rect x="280" y="180" width="240" height="120" fill="none" stroke="#3d3a39" strokeWidth="1" strokeDasharray="3 4" />
      <text x="400" y="248" textAnchor="middle"
            fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">EMPTY · POST-SHOW</text>
      <circle cx="400" cy="240" r={20 + Math.sin(progress * 4) * 6} fill="none"
              stroke="#3E8C84" strokeWidth="1" opacity={0.2 + Math.sin(progress * 4) * 0.15} />
    </svg>
  );
}

/* ===== Beat 11 — Lights dim ===== */
function BeatDim({ progress }) {
  const t = 1 - clamp(progress * 1.2);
  return (
    <svg viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="800" height="500" fill="#050507" />
      {[200, 400, 600].map((x, i) => (
        <g key={i}>
          <circle cx={x} cy="120" r="6" fill="none" stroke="#3E8C84" strokeWidth="1" />
          <path d={`M ${x} 126 L ${x - 50} 360 L ${x + 50} 360 Z`}
                fill="#3E8C84" opacity={t * 0.3} />
        </g>
      ))}
      <line x1="0" y1="380" x2="800" y2="380" stroke="#3d3a39" strokeWidth="1" />
      <text x="400" y="450" textAnchor="middle"
            fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">FADE · {Math.round((1 - t) * 100)}%</text>
    </svg>
  );
}

/* =====================================================================
   Beat 08 — SHOW LIVE: the peak.
   Every layer that the prior beats covered runs simultaneously here:
     - top strip: scrolling audio waveform + live timecode ticker
     - LED wall: animated pixel grid pulsing with content
     - mid-stage: 5 light beams sweeping with phase offsets
     - audience floor: crowd heads bobbing in waves
     - ambient: dust particles drifting upward through beams
   Scroll progress drives every layer in concert, so the whole scene
   "wakes up" then handles to the next beat.
   ===================================================================== */
function BeatShowLive({ progress }) {
  const wrapperRef = useRef(null);
  const [time, setTime] = useState(0);
  const [reducedMotion, setReducedMotion] = useState(false);

  /* Autonomous animation loop: rAF ticks while mounted, gated by IntersectionObserver
     so it pauses when the beat scrolls out of viewport. prefers-reduced-motion freezes
     the loop entirely (one static peak frame at tm = 6.0). */
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
    setReducedMotion(mql.matches);
    const onMqlChange = () => setReducedMotion(mql.matches);
    mql.addEventListener('change', onMqlChange);

    if (mql.matches) {
      return () => mql.removeEventListener('change', onMqlChange);
    }

    let rafId = null;
    let prevMs = null;
    let accSec = 0;
    /* Default to running; if the IntersectionObserver tells us we're off-screen
       we'll flip this back to false and stop scheduling rAFs. */
    let running = true;

    const tick = (now) => {
      if (!running) { rafId = null; return; }
      if (prevMs === null) prevMs = now;
      const delta = (now - prevMs) / 1000;
      prevMs = now;
      accSec += delta;
      setTime(accSec);
      rafId = requestAnimationFrame(tick);
    };
    rafId = requestAnimationFrame(tick);

    let io = null;
    if (wrapperRef.current && typeof IntersectionObserver !== 'undefined') {
      io = new IntersectionObserver((entries) => {
        const entry = entries[entries.length - 1];
        if (entry.isIntersecting && !running) {
          running = true;
          prevMs = null;
          if (rafId === null) rafId = requestAnimationFrame(tick);
        } else if (!entry.isIntersecting && running) {
          running = false;
        }
      }, { threshold: 0 });
      io.observe(wrapperRef.current);
    }

    return () => {
      mql.removeEventListener('change', onMqlChange);
      if (io) io.disconnect();
      running = false;
      if (rafId) cancelAnimationFrame(rafId);
    };
  }, []);

  /* Scroll-derived fade-in intensity (matches the existing pattern for other beats). */
  const intensity = clamp(progress * 1.15);
  /* Internal animation time in seconds. Under reduced-motion, freeze at a peak frame. */
  const tm = reducedMotion ? 6.0 : time;

  /* Live timecode: real-time seconds since the beat first entered viewport.
     Pauses when out of viewport (because the rAF loop pauses) and resumes on re-entry. */
  const totalSec = Math.floor(tm);
  const HH = String(Math.floor(totalSec / 3600) + 21).padStart(2, '0'); /* show clock starts at 21:00 */
  const MM = String(Math.floor(totalSec / 60) % 60).padStart(2, '0');
  const SS = String(totalSec % 60).padStart(2, '0');

  /* Top audio waveform — 100 sample points scrolling left */
  const wavePath = Array.from({ length: 100 }, (_, j) => {
    const x = 200 + j * 6;
    const phase = j * 0.32 + tm * 14;
    const amp = (Math.sin(phase) * 0.6 + Math.sin(phase * 2.1) * 0.3 + Math.sin(phase * 4.7) * 0.15) * 10 * intensity;
    return (j === 0 ? 'M' : 'L') + x + ' ' + (24 - amp);
  }).join(' ');

  /* LED wall: 28 cols x 12 rows. Each cell's brightness is a noise-ish
     function of (col, row, time). Cells nearer center are more lit. */
  const ledCols = 28, ledRows = 12;
  const wallX = 130, wallY = 70, wallW = 540, wallH = 200;
  const cellW = wallW / ledCols, cellH = wallH / ledRows;
  const pixels = [];
  for (let r = 0; r < ledRows; r++) {
    for (let c = 0; c < ledCols; c++) {
      const cx = c / (ledCols - 1) - 0.5;
      const cy = r / (ledRows - 1) - 0.5;
      const dist = Math.sqrt(cx * cx + cy * cy);
      const wave = Math.sin(c * 0.55 + tm * 9 - r * 0.3) * 0.5 + 0.5;
      const radial = 1 - dist * 1.4;
      const a = clamp(wave * radial * intensity) * 0.85;
      pixels.push({ c, r, a });
    }
  }

  /* Seven dramatic beams along a top truss at y=55.
     Each beam swings on its own phase and flickers in brightness so the rig
     feels alive without identical-looking copies. */
  const beamOrigins = [80, 200, 320, 400, 480, 600, 720];
  const beams = beamOrigins.map((cx, i) => {
    const swing = Math.sin(tm * 1.4 + i * 0.7) * 40;
    const flicker = (Math.sin(tm * 3.2 + i * 1.7) * 0.5 + 0.5);
    return {
      topX: cx,
      bottomX: cx + swing,
      alpha: (0.5 + flicker * 0.2) * intensity,
    };
  });

  /* Crowd: 90 heads (15 cols x 6 rows) bobbing in waves.
     Front 3 rows (r = 0, 1, 2 — closest to the stage) raise their arms,
     each on its own shimmer phase so the wave ripples through the rows. */
  const heads = [];
  for (let r = 0; r < 6; r++) {
    for (let c = 0; c < 15; c++) {
      const x = 60 + c * 50 + (r % 2) * 25;
      const y = 320 + r * 28;
      const bob = Math.sin(tm * 5 + c * 0.4 + r * 0.7) * 3 * intensity;
      /* Spotlight fall-off — heads closer to stage centerline are brighter */
      const distFromCenter = Math.abs(x - 400) / 400;
      const litness = (1 - distFromCenter * 0.7) * intensity;
      const armsUp = r < 3;
      const armWave = armsUp ? (Math.sin(tm * 4 + c * 0.5 + r * 1.1) * 0.5 + 0.5) : 0;
      heads.push({ x, y: y + bob, lit: litness, armsUp, armWave });
    }
  }

  /* Ambient particles — 14 dust motes drifting upward through beams */
  const particles = Array.from({ length: 14 }, (_, i) => {
    const seed = i * 137.5;
    const xJitter = Math.sin(seed) * 250 + 400;
    const yStart = 380;
    const yEnd = 90;
    /* Each particle has its own phase so they don't move in lockstep */
    const lifeT = (tm * 0.35 + i / 14) % 1;
    return {
      x: xJitter + Math.sin(tm * 0.8 + i) * 8,
      y: yStart - (yStart - yEnd) * lifeT,
      a: Math.sin(lifeT * Math.PI) * 0.6 * intensity,
    };
  });

  /* Live indicator pulse */
  const livePulse = (Math.sin(tm * 8) * 0.5 + 0.5) * intensity;

  return (
    <svg ref={wrapperRef} viewBox="0 0 800 500" className="beat-svg" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <defs>
        <linearGradient id="bsl-beam" x1="0.5" y1="0" x2="0.5" y2="1">
          <stop offset="0%" stopColor="#6DACB5" stopOpacity="0" />
          <stop offset="20%" stopColor="#6DACB5" stopOpacity="0.85" />
          <stop offset="100%" stopColor="#6DACB5" stopOpacity="0" />
        </linearGradient>
        <radialGradient id="bsl-spot" cx="0.5" cy="0.1" r="0.8">
          <stop offset="0%" stopColor="#6DACB5" stopOpacity="0.35" />
          <stop offset="100%" stopColor="#6DACB5" stopOpacity="0" />
        </radialGradient>
        <clipPath id="bsl-wall-clip">
          <rect x={wallX} y={wallY} width={wallW} height={wallH} />
        </clipPath>
      </defs>

      {/* Canvas */}
      <rect width="800" height="500" fill="#050507" />

      {/* Stage glow above the wall */}
      <ellipse cx="400" cy="260" rx="360" ry="170" fill="url(#bsl-spot)" opacity={intensity * 0.7} />

      {/* LED wall frame */}
      <rect x={wallX - 2} y={wallY - 2} width={wallW + 4} height={wallH + 4}
            fill="none" stroke="#3d3a39" strokeWidth="1.5" />
      {/* LED pixels */}
      <g clipPath="url(#bsl-wall-clip)">
        {pixels.map((px, i) => (
          <rect key={'px-' + i}
                x={wallX + px.c * cellW + 1}
                y={wallY + px.r * cellH + 1}
                width={cellW - 2}
                height={cellH - 2}
                fill="#3E8C84"
                opacity={px.a} />
        ))}
      </g>

      {/* Stage floor line */}
      <line x1="0" y1="320" x2="800" y2="320" stroke="#3d3a39" strokeWidth="1" />

      {/* Crowd heads with stage spotlight fall-off; front 3 rows raise arms. */}
      {heads.map((h, i) => {
        let armPath = null;
        if (h.armsUp) {
          const armReach = 14 + h.armWave * 6; /* arms extend 14-20px above the head */
          const armSpread = 9;
          const ax1 = h.x - armSpread;
          const ax2 = h.x + armSpread;
          const armY = h.y - armReach;
          armPath = `M ${h.x - 5} ${h.y + 9} L ${h.x - 4} ${h.y - 1} ` +
                    `L ${ax1} ${armY + 2} L ${ax1 + 1} ${armY - 1} ` +
                    `L ${h.x - 1} ${h.y - 2} L ${h.x + 1} ${h.y - 2} ` +
                    `L ${ax2 - 1} ${armY - 1} L ${ax2} ${armY + 2} ` +
                    `L ${h.x + 4} ${h.y - 1} L ${h.x + 5} ${h.y + 9} Z`;
        }
        return (
          <g key={'h-' + i}>
            {armPath && (
              <path d={armPath}
                    fill="#020303"
                    stroke="#3E8C84"
                    strokeOpacity={0.25 + h.lit * 0.45}
                    strokeWidth="0.7" />
            )}
            <circle cx={h.x} cy={h.y} r="6"
                    fill="#050507"
                    stroke="#3E8C84"
                    strokeOpacity={0.15 + h.lit * 0.55}
                    strokeWidth="0.9" />
            {/* shoulders (rear rows only — front rows have arms covering this area) */}
            {!h.armsUp && (
              <path d={`M ${h.x - 9} ${h.y + 11} Q ${h.x} ${h.y + 6} ${h.x + 9} ${h.y + 11}`}
                    fill="none"
                    stroke="#3E8C84"
                    strokeOpacity={0.10 + h.lit * 0.35}
                    strokeWidth="0.7" />
            )}
          </g>
        );
      })}

      {/* Ambient particles */}
      {particles.map((pt, i) => (
        <circle key={'pt-' + i} cx={pt.x} cy={pt.y} r="1.4"
                fill="#6DACB5" opacity={pt.a} />
      ))}

      {/* Truss bar — anchors the beam origins. 1px line spanning the SVG,
          with 7 small fixture boxes at the beam origin X positions. */}
      <line x1="20" y1="55" x2="780" y2="55"
            stroke="#3d3a39" strokeWidth="1.5" opacity={0.6 + intensity * 0.4} />
      {beamOrigins.map((cx, i) => (
        <rect key={'fix-' + i}
              x={cx - 5} y={50}
              width="10" height="10"
              fill="#15191b"
              stroke={i === 3 ? "#3E8C84" : "#3d3a39"}
              strokeWidth={i === 3 ? "1.2" : "0.8"}
              opacity={0.7 + intensity * 0.3} />
      ))}

      {/* Light beams — top SVG layer (above LED wall, crowd, particles).
          Origins anchored to the truss at y=55; widened cones (74px each side)
          so the bright mid-stop reads at scale. */}
      {beams.map((b, i) => (
        <path key={'beam-' + i}
              d={`M ${b.topX - 5} 55 L ${b.topX + 5} 55 L ${b.bottomX + 74} 320 L ${b.bottomX - 74} 320 Z`}
              fill="url(#bsl-beam)" opacity={b.alpha}
              style={{ mixBlendMode: 'screen' }} />
      ))}

      {/* Top strip: scrolling waveform */}
      <path d={wavePath}
            fill="none" stroke="#3E8C84" strokeWidth="1.2" strokeOpacity={0.85 * intensity} />

      {/* Labels */}
      <text x="40" y="28" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#8b949e" letterSpacing="2">SHOW · LIVE</text>
      {/* LIVE indicator pulse dot */}
      <circle cx="116" cy="24" r="3.5" fill="#3E8C84" opacity={0.4 + livePulse * 0.6} />

      <text x="760" y="28" fontFamily="ui-monospace, monospace" fontSize="11"
            fill="#3E8C84" letterSpacing="2" textAnchor="end">
        TC {HH}:{MM}:{SS}
      </text>

      <text x="40" y="486" fontFamily="ui-monospace, monospace" fontSize="10"
            fill="#8b949e" letterSpacing="1.6">
        1,400 PAX · HOUSE LIGHTS · OUT
      </text>
      <text x="760" y="486" fontFamily="ui-monospace, monospace" fontSize="10"
            fill="#8b949e" letterSpacing="1.6" textAnchor="end">
        FOH · ARMED · ALL SYSTEMS GO
      </text>
    </svg>
  );
}

window.BeatVisuals = {
  BeatEmpty, BeatFloorPlan, BeatTrusses, BeatAudio, BeatLights,
  BeatVideo, BeatSynced, BeatShowLive, BeatCrowd, BeatQuiet, BeatDim
};
