← back to Prestige Car Wash Video

src/StoryVideo.tsx

73 lines

import { AbsoluteFill, Sequence, Audio, staticFile, useCurrentFrame, useVideoConfig, interpolate } from 'remotion';
import { SCENES } from './story';
import { Background } from './Background';
import { BrowserShot } from './BrowserShot';
import { Caption } from './Caption';
import { Card } from './Card';
import { OptionScene } from './scenes/OptionScene';
import { FcfsScene } from './scenes/FcfsScene';
import { CamScene } from './scenes/CamScene';
import { TipsScene } from './scenes/TipsScene';

const FONT = 'Inter, "SF Pro Display", system-ui, -apple-system, sans-serif';

const FadeWrap: React.FC<{ dur: number; children: React.ReactNode }> = ({ dur, children }) => {
  const f = useCurrentFrame();
  const o = interpolate(f, [0, 10, dur - 9, dur], [0, 1, 1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
  return <AbsoluteFill style={{ opacity: o }}>{children}</AbsoluteFill>;
};

export const StoryVideo: React.FC = () => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();
  const offsets: number[] = [];
  let acc = 0;
  for (const s of SCENES) { offsets.push(acc); acc += s.frames; }
  let active = 0;
  for (let i = 0; i < SCENES.length; i++) if (frame >= offsets[i]) active = i;
  const accent = SCENES[active].accent;

  return (
    <AbsoluteFill style={{ backgroundColor: '#05090f' }}>
      <Background accent={accent} />

      {SCENES.map((s, i) => (
        <Sequence key={s.id} from={offsets[i]} durationInFrames={s.frames} name={s.title || s.name || s.id}>
          <FadeWrap dur={s.frames}>
            {s.type === 'clip' && <OptionScene s={s} />}
            {s.type === 'fcfs' && <FcfsScene s={s} />}
            {s.type === 'cam' && <CamScene s={s} />}
            {s.type === 'tips' && <TipsScene s={s} />}
            {s.type === 'shot' && (
              <>
                <BrowserShot s={s as any} />
                <Caption s={s as any} index={i} total={SCENES.length} />
              </>
            )}
            {s.type === 'card' && (
              <>
                <Card s={s as any} />
                {s.narr && <Audio src={staticFile(`audio/${s.narr}.wav`)} />}
              </>
            )}
          </FadeWrap>
        </Sequence>
      ))}

      {/* progress bar */}
      <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 6, background: 'rgba(255,255,255,0.08)' }}>
        <div style={{ height: 6, width: `${(frame / durationInFrames) * 100}%`, background: accent, boxShadow: `0 0 16px ${accent}` }} />
      </div>

      {/* persistent wordmark */}
      <div style={{ position: 'absolute', top: 30, left: 54, display: 'flex', alignItems: 'center', gap: 12,
        fontFamily: FONT, fontWeight: 800, fontSize: 24, color: '#e8f1ff', letterSpacing: 0.5,
        textShadow: '0 2px 12px rgba(0,0,0,0.6)' }}>
        <span style={{ display: 'inline-flex', width: 34, height: 34, borderRadius: 9,
          background: 'linear-gradient(135deg,#38bdf8,#2563eb)', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>🚗</span>
        Prestige<span style={{ opacity: 0.5, fontWeight: 600 }}>Car Wash</span>
      </div>
    </AbsoluteFill>
  );
};