// MontageHero — plays a sequence of short clips with captions tied to each.
// Problem → recognition → calm. Loops once, holds on final frame.
//
// Clips live in `videos/` and are referenced by filename. The user will drop
// 30 .mp4 files into that folder. Until they do, we render a placeholder
// "scene" per beat (animated SVG) so the layout and pacing are real.

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

// ---------------------------------------------------------------------------
// SCRIPTS — each is a sequence of beats. A beat = one clip + one caption.
// duration is in seconds. `src` points to a file in videos/. If the file is
// missing, we fall back to a SVG placeholder of the same `placeholder` kind.
// ---------------------------------------------------------------------------

// Real footage. Filenames describe the clip — captions are written to fit.
const SCRIPTS = {
  // Frustration → recognition → calm
  problem_relief: {
    label: "Problem → relief",
    beats: [
      { src: "videos/01-frustrated-man-sofa.mp4",     placeholder: "squint",   caption: "You wanted to take a photo.",          dur: 2.6, anchor: "tl" },
      { src: "videos/02-frustrated-young-man.mp4",    placeholder: "fullmsg",  caption: "Storage full.",                         dur: 2.4, anchor: "tl" },
      { src: "videos/03-woman-shocked-laptop.mp4",    placeholder: "scroll",   caption: "You've seen this before.",              dur: 2.6, anchor: "tr" },
      { src: "videos/07-senior-man-phone.mp4",        placeholder: "settings", caption: "It's not the phone.",                   dur: 2.6, anchor: "tl" },
      { src: "videos/06-senior-woman-laptop.mp4",     placeholder: "kitchen",  caption: "It's that no one showed you.",          dur: 3.0, anchor: "tl" },
      { src: "videos/10-granddaughter-grandmother.mp4", placeholder: "class",  caption: "So we'll show you.",                    dur: 2.8, anchor: "tr" },
      { src: "videos/16-man-child-phone.mp4",         placeholder: "hands",    caption: "On the device you actually use.",       dur: 2.8, anchor: "tl" },
      { src: "videos/13-grandparents-laughing.mp4",   placeholder: "smile",    caption: "Patiently. In person.",                 dur: 2.8, anchor: "bl" },
      { src: "videos/22-senior-woman-phone.mp4",      placeholder: "library",  caption: "At a library down the street.",         dur: 3.0, anchor: "tr" },
      { src: "videos/19-gray-hair-bench.mp4",         placeholder: "hold",     caption: "You are not behind.",                   dur: 3.0, anchor: "tl" },
    ],
  },

  community: {
    label: "Community",
    beats: [
      { src: "videos/11-grandfather-grandson-laughing.mp4", placeholder: "library", caption: "Saturday morning.",                dur: 2.6, anchor: "tl" },
      { src: "videos/12-grandmother-child-couch.mp4",       placeholder: "kitchen", caption: "Someone you trust nearby.",        dur: 2.8, anchor: "br" },
      { src: "videos/17-man-laughing-kitchen.mp4",          placeholder: "class",   caption: "A question, asked out loud.",      dur: 2.8, anchor: "tr" },
      { src: "videos/23-two-women-phone.mp4",               placeholder: "hands",   caption: "Someone leaning in to help.",      dur: 3.0, anchor: "bl" },
      { src: "videos/14-happy-family-selfie.mp4",           placeholder: "smile",   caption: "It clicks.",                        dur: 2.6, anchor: "tl" },
      { src: "videos/15-three-gen-laughing.mp4",            placeholder: "scroll",  caption: "You laugh at how simple it was.",   dur: 2.8, anchor: "br" },
      { src: "videos/24-three-gen-selfie.mp4",              placeholder: "settings", caption: "Three generations, one phone.",    dur: 2.8, anchor: "tr" },
      { src: "videos/21-senior-young-sofa.mp4",             placeholder: "fullmsg", caption: "No product pitches.",               dur: 2.6, anchor: "bl" },
      { src: "videos/25-woman-tablet-home.mp4",             placeholder: "squint",  caption: "Just the things you wanted to ask.", dur: 3.0, anchor: "tl" },
      { src: "videos/20-senior-man-relaxing.mp4",           placeholder: "hold",    caption: "Cherokee County.",                  dur: 2.6, anchor: "mr" },
    ],
  },

  confidence: {
    label: "Confidence",
    beats: [
      { src: "videos/08-woman-photographs.mp4",   placeholder: "hands",    caption: "You already know more than you think.", dur: 3.0, anchor: "tl" },
      { src: "videos/04-man-speaking-phone.mp4",  placeholder: "settings", caption: "Settings is just a list.",              dur: 2.6, anchor: "br" },
      { src: "videos/05-woman-laptop-phone.mp4",  placeholder: "scroll",   caption: "Storage is just a drawer.",             dur: 2.8, anchor: "tr" },
      { src: "videos/09-adult-tablet-desk.mp4",   placeholder: "fullmsg",  caption: "Wi-Fi is just a signal in the air.",    dur: 3.0, anchor: "bl" },
      { src: "videos/26-woman-couch-phone.mp4",   placeholder: "kitchen",  caption: "None of this is a mystery.",            dur: 2.8, anchor: "tl" },
      { src: "videos/27-woman-couch-tablet.mp4",  placeholder: "library",  caption: "It was just never explained.",          dur: 2.8, anchor: "br" },
      { src: "videos/28-woman-phone-couch.mp4",   placeholder: "class",    caption: "We'll explain it.",                     dur: 2.4, anchor: "tr" },
      { src: "videos/29-woman-tablet-park.mp4",   placeholder: "smile",    caption: "Plainly. Without rushing.",             dur: 2.8, anchor: "bl" },
      { src: "videos/30-woman-laptop-office.mp4", placeholder: "squint",   caption: "Until it makes sense.",                 dur: 2.6, anchor: "tl" },
      { src: "videos/18-man-relaxes-sofa.mp4",    placeholder: "hold",     caption: "You've got this.",                      dur: 3.0, anchor: "mr" },
    ],
  },
};

// ---------------------------------------------------------------------------
// PLACEHOLDER SCENES — animated SVGs that stand in for missing video files.
// Each represents an actual beat from the brief (squinting at a notification,
// kitchen-table laptop, etc.). They are intentionally illustrative, not
// realistic, so they read as placeholders.
// ---------------------------------------------------------------------------

const PlaceholderScene = ({ kind, treatment, t }) => {
  // t is 0..1 within the beat — drives subtle motion so the placeholder
  // doesn't look frozen.
  const tones = treatmentTones(treatment);
  const common = { width: '100%', height: '100%', preserveAspectRatio: 'xMidYMid slice', viewBox: '0 0 800 600' };

  // Subtle ken-burns drift on every scene
  const drift = `translate(${(t - 0.5) * 12}px, ${(t - 0.5) * -6}px) scale(${1.04 + t * 0.02})`;

  const scenes = {
    squint: () => (
      <g transform={drift}>
        {/* hand holding a phone, glow on screen */}
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        <rect x="280" y="120" width="240" height="440" rx="28" fill={tones.surfaceDeep}/>
        <rect x="298" y="148" width="204" height="384" rx="14" fill={tones.surface}/>
        <rect x="320" y="180" width="160" height="14" rx="4" fill={tones.accent} opacity="0.45"/>
        <rect x="320" y="208" width="120" height="10" rx="3" fill={tones.fg} opacity="0.25"/>
        <rect x="320" y="228" width="140" height="10" rx="3" fill={tones.fg} opacity="0.18"/>
        <circle cx="400" cy="350" r={60 + t * 4} fill={tones.accent} opacity="0.08"/>
        {/* fingertip */}
        <ellipse cx="430" cy="500" rx="48" ry="22" fill={tones.skin}/>
      </g>
    ),
    fullmsg: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        <rect x="160" y="160" width="480" height="280" rx="14" fill={tones.surface}/>
        <rect x="200" y="206" width="280" height="16" rx="4" fill={tones.accent}/>
        <rect x="200" y="234" width="380" height="10" rx="3" fill={tones.fg} opacity="0.35"/>
        <rect x="200" y="252" width="320" height="10" rx="3" fill={tones.fg} opacity="0.25"/>
        <rect x="200" y="380" width="120" height="34" rx="6" fill={tones.accent}/>
        <rect x="340" y="380" width="120" height="34" rx="6" fill="transparent" stroke={tones.fg} strokeWidth="1.5"/>
      </g>
    ),
    scroll: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        {[0,1,2,3,4,5,6,7].map(i => (
          <g key={i} transform={`translate(0, ${(i * 70) - (t * 90)})`}>
            <rect x="220" y="80" width="360" height="56" rx="8" fill={tones.surface}/>
            <rect x="244" y="100" width="240" height="10" rx="3" fill={tones.fg} opacity="0.4"/>
            <rect x="244" y="116" width="160" height="8" rx="2" fill={tones.fg} opacity="0.22"/>
          </g>
        ))}
        <rect x="0" y="0" width="800" height="80" fill={tones.bg}/>
        <rect x="0" y="520" width="800" height="80" fill={tones.bg}/>
      </g>
    ),
    settings: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        <rect x="180" y="100" width="440" height="420" rx="16" fill={tones.surface}/>
        <rect x="210" y="130" width="160" height="18" rx="4" fill={tones.fg}/>
        {[0,1,2,3,4,5].map(i => (
          <g key={i} transform={`translate(0, ${i * 50})`}>
            <rect x="210" y="180" width="380" height="36" rx="6" fill={tones.bg}/>
            <circle cx="234" cy="198" r="10" fill={tones.accent} opacity={i === 2 ? 1 : 0.4}/>
            <rect x="256" y="192" width="180" height="10" rx="3" fill={tones.fg} opacity="0.6"/>
          </g>
        ))}
      </g>
    ),
    kitchen: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        {/* table */}
        <rect x="0" y="380" width="800" height="220" fill={tones.surfaceDeep} opacity="0.6"/>
        {/* laptop */}
        <rect x="240" y="220" width="320" height="200" rx="6" fill={tones.surface}/>
        <rect x="220" y="416" width="360" height="14" rx="2" fill={tones.fg} opacity="0.6"/>
        <rect x="266" y="246" width="268" height="148" rx="2" fill={tones.bg}/>
        {/* coffee */}
        <circle cx="640" cy="430" r="36" fill={tones.surface}/>
        <circle cx="640" cy="430" r="28" fill={tones.accent} opacity="0.4"/>
        {/* steam */}
        <path d={`M 632 ${390 - t*8} q 6 -10 0 -20 q -6 -10 0 -20`} fill="none" stroke={tones.fg} strokeWidth="1.5" opacity="0.3"/>
        <path d={`M 648 ${390 - t*8} q 6 -10 0 -20 q -6 -10 0 -20`} fill="none" stroke={tones.fg} strokeWidth="1.5" opacity="0.3"/>
      </g>
    ),
    class: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        {/* heads in a room */}
        {[[140,420],[260,440],[400,420],[540,440],[660,420],[200,360],[340,370],[480,360],[620,370]].map(([x,y], i) => (
          <g key={i}>
            <circle cx={x} cy={y} r="28" fill={tones.skin}/>
            <rect x={x-40} y={y+18} width="80" height="60" rx="14" fill={tones.surfaceDeep}/>
          </g>
        ))}
        {/* instructor area */}
        <rect x="320" y="120" width="160" height="120" rx="6" fill={tones.surface}/>
        <circle cx="400" cy="170" r="22" fill={tones.skin}/>
        <rect x="370" y="200" width="60" height="40" rx="8" fill={tones.accent}/>
      </g>
    ),
    hands: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        {/* two hands meeting on a tablet */}
        <rect x="220" y="200" width="360" height="240" rx="14" fill={tones.surface}/>
        <rect x="248" y="228" width="304" height="184" rx="4" fill={tones.bg}/>
        <ellipse cx="280" cy="500" rx="100" ry="40" fill={tones.skin}/>
        <ellipse cx="540" cy="500" rx="100" ry="40" fill={tones.skin} opacity="0.85"/>
        <rect x="320" y="260" width="160" height="10" rx="3" fill={tones.accent} opacity="0.5"/>
        <rect x="320" y="282" width="120" height="8" rx="2" fill={tones.fg} opacity="0.3"/>
      </g>
    ),
    smile: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        {/* head */}
        <circle cx="400" cy="280" r="140" fill={tones.skin}/>
        {/* eyes */}
        <circle cx="356" cy="270" r="6" fill={tones.fg}/>
        <circle cx="444" cy="270" r="6" fill={tones.fg}/>
        {/* mouth — slight smile */}
        <path d={`M 350 320 Q 400 ${346 + t*4} 450 320`} fill="none" stroke={tones.fg} strokeWidth="3" strokeLinecap="round"/>
        {/* shoulders */}
        <path d="M 200 600 Q 200 460 400 460 Q 600 460 600 600 Z" fill={tones.surfaceDeep}/>
      </g>
    ),
    library: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        {/* shelves */}
        {[0,1,2].map(row => (
          <g key={row} transform={`translate(0, ${row * 110})`}>
            <rect x="80" y="120" width="640" height="80" fill={tones.surfaceDeep} opacity="0.4"/>
            {Array.from({length: 14}).map((_, i) => (
              <rect key={i}
                    x={92 + i * 44}
                    y={130}
                    width="32"
                    height={50 + ((i * 7) % 16)}
                    fill={[tones.accent, tones.surface, tones.fg][i % 3]}
                    opacity={0.7}/>
            ))}
            <rect x="80" y="200" width="640" height="6" fill={tones.fg} opacity="0.3"/>
          </g>
        ))}
      </g>
    ),
    hold: () => (
      <g transform={drift}>
        <rect x="0" y="0" width="800" height="600" fill={tones.bg}/>
        {/* a quiet final frame: phone face down on a notebook on a table */}
        <rect x="0" y="380" width="800" height="220" fill={tones.surfaceDeep} opacity="0.5"/>
        <rect x="220" y="240" width="360" height="240" rx="8" fill={tones.surface}/>
        <rect x="250" y="270" width="200" height="6" rx="2" fill={tones.fg} opacity="0.4"/>
        <rect x="250" y="286" width="240" height="6" rx="2" fill={tones.fg} opacity="0.3"/>
        <rect x="250" y="302" width="160" height="6" rx="2" fill={tones.fg} opacity="0.3"/>
        <rect x="320" y="340" width="160" height="84" rx="14" fill={tones.surfaceDeep}/>
      </g>
    ),
  };

  return (
    <svg {...common}>
      {scenes[kind] ? scenes[kind]() : <rect width="800" height="600" fill={tones.bg}/>}
    </svg>
  );
};

function treatmentTones(treatment) {
  // Returns a palette appropriate to the chosen color treatment.
  const natural = {
    bg: '#F4B58E',          // sandy apricot
    surface: '#FFFDF5',
    surfaceDeep: '#A0452D',
    accent: '#A0452D',
    fg: '#333333',
    skin: '#E8B594',
  };
  const clayDuotone = {
    bg: '#A0452D',
    surface: '#F4B58E',
    surfaceDeep: '#762F1D',
    accent: '#FFFDF5',
    fg: '#FFFDF5',
    skin: '#F4B58E',
  };
  const desaturated = {
    bg: '#E8E2D4',
    surface: '#FFFDF5',
    surfaceDeep: '#8A8A8A',
    accent: '#5C5C5C',
    fg: '#333333',
    skin: '#C8B8A8',
  };
  return { natural, clay: clayDuotone, desaturated }[treatment] || natural;
}

// ---------------------------------------------------------------------------
// MontageHero — orchestrates the timeline.
// ---------------------------------------------------------------------------

const MontageHero = ({ tweaks }) => {
  const { script, treatment, speed, captionsOn, layout } = tweaks;
  const beats = SCRIPTS[script].beats;

  // We scale every beat duration by `speed`. Total runtime is dur * speed.
  const scaledBeats = useMemo(
    () => beats.map(b => ({ ...b, dur: b.dur * speed })),
    [beats, speed]
  );

  const totalDur = scaledBeats.reduce((s, b) => s + b.dur, 0);

  const [elapsed, setElapsed] = useState(0);   // seconds since start
  const [playing, setPlaying] = useState(true);
  const [muted, setMuted] = useState(true);
  const [done, setDone] = useState(false);

  const startRef = useRef(performance.now());
  const rafRef = useRef(null);

  // Reset clock when script or speed changes
  useEffect(() => {
    startRef.current = performance.now();
    setElapsed(0);
    setDone(false);
    setPlaying(true);
  }, [script, speed]);

  useEffect(() => {
    if (!playing || done) return;
    const tick = (now) => {
      const e = (now - startRef.current) / 1000;
      if (e >= totalDur) {
        setElapsed(totalDur);
        setDone(true);
        return;
      }
      setElapsed(e);
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [playing, done, totalDur]);

  // Find current beat index + local progress 0..1
  const { beatIdx, localT } = useMemo(() => {
    let acc = 0;
    for (let i = 0; i < scaledBeats.length; i++) {
      const next = acc + scaledBeats[i].dur;
      if (elapsed < next || i === scaledBeats.length - 1) {
        return { beatIdx: i, localT: Math.min(1, (elapsed - acc) / scaledBeats[i].dur) };
      }
      acc = next;
    }
    return { beatIdx: scaledBeats.length - 1, localT: 1 };
  }, [elapsed, scaledBeats]);

  const beat = scaledBeats[beatIdx];

  // Restart handler
  const restart = () => {
    startRef.current = performance.now();
    setElapsed(0);
    setDone(false);
    setPlaying(true);
  };

  // Caption fade — in for first 200ms of beat, out for last 300ms
  const captionOpacity = (() => {
    const fadeIn = Math.min(1, localT / 0.08);
    const fadeOut = Math.min(1, (1 - localT) / 0.12);
    return Math.min(fadeIn, fadeOut);
  })();

  // Color treatment applied to actual <video> via CSS filter.
  const videoFilter = {
    natural: 'none',
    clay: 'sepia(1) hue-rotate(-25deg) saturate(1.6) brightness(0.85)',
    desaturated: 'saturate(0.4) contrast(0.95) brightness(1.02)',
  }[treatment];

  const frameProps = {
    beats: scaledBeats, beatIdx, localT, captionOpacity, captionsOn,
    treatment, videoFilter, muted, done, elapsed, totalDur,
    onRestart: restart, onToggleMute: () => setMuted(m => !m), playing,
  };

  if (layout === 'fullbleed') {
    return (
      <React.Fragment>
        {/* Full-bleed montage — captions read clearly, no overlaid hero text */}
        <section style={{
          position: 'relative', width: '100%',
          height: 'min(72vh, 620px)', minHeight: 460,
          overflow: 'hidden',
          background: '#333333',
        }}>
          <div style={{ position: 'absolute', inset: 0 }}>
            <MontageFrame {...frameProps} fullbleed/>
          </div>
        </section>

        {/* Headline + copy + CTAs sit BELOW the video, on cream */}
        <section style={{ maxWidth: 1120, margin: '0 auto', padding: '56px 32px 56px' }}>
          <div style={{
            display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: 56,
            alignItems: 'start',
          }}>
            <div>
              <div style={{
                fontFamily: "'DM Sans', sans-serif", fontSize: 13, fontWeight: 600,
                letterSpacing: '0.14em', textTransform: 'uppercase',
                color: '#A0452D', marginBottom: 20,
              }}>Cherokee County · Georgia</div>
              <h1 style={{
                fontFamily: "'Fraunces', Georgia, serif", fontWeight: 400,
                fontSize: 60, lineHeight: 1.06, letterSpacing: '-0.02em',
                color: '#333333', margin: 0, maxWidth: 560, textWrap: 'balance',
              }}>You are not behind. You were never shown.</h1>
            </div>
            <div>
              <p style={{
                fontFamily: "'DM Sans', sans-serif", fontSize: 19, lineHeight: 1.6,
                color: '#5C5C5C', margin: '12px 0 28px', maxWidth: 480,
              }}>
                Patient, in-person classes on the devices you already own.
                Hosted at libraries, churches, and community centers around the county.
              </p>
              <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
                <Button>See upcoming classes</Button>
                <Button variant="tertiary">For host venues</Button>
              </div>
            </div>
          </div>
        </section>
      </React.Fragment>
    );
  }

  // Default: side-by-side
  return (
    <section style={{
      maxWidth: 1120, margin: '0 auto', padding: '64px 32px 56px',
    }}>
      <div style={{
        display: 'grid', gridTemplateColumns: '1fr 1.05fr', gap: 56,
        alignItems: 'center',
      }}>
        <div>
          <div style={{
            fontFamily: "'DM Sans', sans-serif", fontSize: 13, fontWeight: 600,
            letterSpacing: '0.14em', textTransform: 'uppercase',
            color: '#A0452D', marginBottom: 20,
          }}>Cherokee County · Georgia</div>
          <h1 style={{
            fontFamily: "'Fraunces', Georgia, serif", fontWeight: 400,
            fontSize: 60, lineHeight: 1.06, letterSpacing: '-0.02em',
            color: '#333333', margin: 0, maxWidth: 520, textWrap: 'balance',
          }}>You are not behind. You were never shown.</h1>
          <p style={{
            fontFamily: "'DM Sans', sans-serif", fontSize: 19, lineHeight: 1.6,
            color: '#5C5C5C', marginTop: 24, marginBottom: 32, maxWidth: 460,
          }}>
            Patient, in-person classes on the devices you already own.
            Hosted at libraries, churches, and community centers around the county.
          </p>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <Button>See upcoming classes</Button>
            <Button variant="tertiary">For host venues</Button>
          </div>
        </div>

        <div>
          <MontageFrame {...frameProps}/>
        </div>
      </div>
    </section>
  );
};

const MontageFrame = (props) => {
  const {
    beats, beatIdx, localT, captionOpacity, captionsOn,
    treatment, videoFilter, muted, done, elapsed, totalDur,
    onRestart, onToggleMute, playing,
  } = props;
  const beat = beats[beatIdx];

  // We try to render a real <video> if the file exists. We track whether
  // each beat's video has loaded; if not, fall back to placeholder.
  // Strategy: render video; on `error` set a flag. Until then, also render
  // the placeholder underneath so there's never a black flash.
  const [videoErrors, setVideoErrors] = useState({});

  const markError = (i) => setVideoErrors(prev => ({ ...prev, [i]: true }));

  return (
    <div style={{
      position: 'relative',
      width: '100%',
      ...(props.fullbleed ? { height: '100%' } : { aspectRatio: '4 / 3' }),
      borderRadius: props.fullbleed ? 0 : 12,
      overflow: 'hidden',
      background: '#F4B58E',
      border: props.fullbleed ? 'none' : '1px solid #E8E2D4',
    }}>
      {/* Placeholder layer (always present, behind video) */}
      <div style={{ position: 'absolute', inset: 0 }}>
        <PlaceholderScene kind={beat.placeholder} treatment={treatment} t={localT}/>
      </div>

      {/* Video layer — one per beat, only the active one is visible.
          We mount all of them so they can preload; using opacity rather than
          display:none keeps decoded frames around. */}
      {beats.map((b, i) => {
        const active = i === beatIdx;
        const errored = videoErrors[i];
        if (errored) return null;
        return (
          <video
            key={i}
            src={b.src}
            muted={muted}
            playsInline
            preload="auto"
            onError={() => markError(i)}
            style={{
              position: 'absolute', inset: 0,
              width: '100%', height: '100%',
              objectFit: 'cover',
              opacity: active ? 1 : 0,
              transition: 'opacity 240ms ease-out',
              filter: videoFilter,
              pointerEvents: 'none',
            }}
            ref={(el) => {
              if (!el) return;
              if (active && playing && !done) {
                if (el.paused) el.play().catch(() => {});
              } else {
                el.pause();
                if (!active) el.currentTime = 0;
              }
            }}
          />
        );
      })}

      {/* Caption — sits in the outer thirds (against walls/furniture, off the subject) */}
      {captionsOn && (() => {
        const anchor = beat.anchor || 'br';
        const pos = {
          // Outer-third anchors: 5–32% or 68–95% horizontally; 8–28% or 64–88% vertically
          tl: { top: '10%',  left: '5%',                          textAlign: 'left'  },
          tr: { top: '10%',  right: '5%',                         textAlign: 'right' },
          bl: { bottom: '10%', left: '5%',                        textAlign: 'left'  },
          br: { bottom: '10%', right: '5%',                       textAlign: 'right' },
          ml: { top: '50%',  left: '5%',  transform: 'translateY(-50%)', textAlign: 'left'  },
          mr: { top: '50%',  right: '5%', transform: 'translateY(-50%)', textAlign: 'right' },
        }[anchor] || { bottom: '10%', right: '5%', textAlign: 'right' };
        return (
          <div style={{
            position: 'absolute',
            ...pos,
            maxWidth: '38%',
            opacity: captionOpacity,
            transition: 'opacity 80ms linear',
            pointerEvents: 'none',
          }}>
            <div style={{
              fontFamily: "'Fraunces', Georgia, serif",
              fontWeight: 400,
              fontSize: 'clamp(34px, 4.2vw, 64px)',
              lineHeight: 1.08,
              letterSpacing: '-0.015em',
              color: '#FFFDF5',
              textWrap: 'balance',
              textShadow: '0 2px 24px rgba(0,0,0,0.6), 0 1px 4px rgba(0,0,0,0.65)',
            }}>{beat.caption}</div>
          </div>
        );
      })()}

      {/* Top-right controls */}
      <div style={{
        position: 'absolute', top: 14, right: 14,
        display: 'flex', gap: 8,
      }}>
        {done && (
          <FrameButton onClick={onRestart} title="Replay">↻</FrameButton>
        )}
      </div>

      {/* Final-frame overlay — only shows once `done` */}
      {done && (
        <div style={{
          position: 'absolute', inset: 0,
          background: 'rgba(51,51,51,0.42)',
          display: 'flex', alignItems: 'flex-end', justifyContent: 'center',
          padding: '0 32px 14%',
          animation: 'tu-fade-in 320ms ease-out',
          pointerEvents: 'none',
        }}>
          <div style={{ textAlign: 'center', maxWidth: 820 }}>
            <div style={{
              fontFamily: "'Fraunces', Georgia, serif", fontWeight: 400,
              fontSize: 'clamp(44px, 5.6vw, 84px)',
              lineHeight: 1.05, letterSpacing: '-0.02em',
              color: '#FFFDF5',
              textWrap: 'balance',
              textShadow: '0 2px 28px rgba(0,0,0,0.6), 0 1px 4px rgba(0,0,0,0.7)',
            }}>You were never shown.</div>
          </div>
        </div>
      )}
    </div>
  );
};

const FrameButton = ({ onClick, title, children }) => (
  <button
    onClick={onClick}
    title={title}
    aria-label={title}
    style={{
      width: 36, height: 36, borderRadius: 8,
      background: 'rgba(51,51,51,0.55)',
      border: '1px solid rgba(255,253,245,0.25)',
      color: '#FFFDF5', fontSize: 16,
      cursor: 'pointer',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>{children}</button>
);

const ProgressBar = ({ elapsed, totalDur, beats, onDark }) => {
  // Segmented progress — one slim bar per beat, fills as we play through.
  let acc = 0;
  return (
    <div style={{
      marginTop: onDark ? 0 : 14,
      display: 'grid', gridTemplateColumns: `repeat(${beats.length}, 1fr)`,
      gap: 4,
    }}>
      {beats.map((b, i) => {
        const start = acc; acc += b.dur;
        const end = acc;
        const pct = elapsed <= start ? 0
                  : elapsed >= end ? 1
                  : (elapsed - start) / b.dur;
        return (
          <div key={i} style={{
            height: 3,
            background: onDark ? 'rgba(255,253,245,0.22)' : '#E8E2D4',
            borderRadius: 2, overflow: 'hidden',
          }}>
            <div style={{
              width: `${pct * 100}%`, height: '100%',
              background: onDark ? '#F4B58E' : '#A0452D',
              transition: 'width 80ms linear',
            }}/>
          </div>
        );
      })}
    </div>
  );
};

Object.assign(window, { MontageHero, SCRIPTS });
