← back to Crcp Pitch Video

src/ClientWalk.tsx

66 lines

import React from 'react';
import { AbsoluteFill, Img, staticFile, Sequence, useCurrentFrame, useVideoConfig, interpolate, spring, Easing } from 'remotion';

const SANS = '-apple-system, "Helvetica Neue", Arial, sans-serif';
const SERIF = 'Georgia, "Times New Roman", serif';
type Box = { x: number; y: number; width: number; height: number } | null;

const IntroScene: React.FC<{ head: string; sub: string; accent: string; dur: number }> = ({ head, sub, accent }) => {
  const f = useCurrentFrame(); const { fps } = useVideoConfig();
  const s = spring({ frame: f - 4, fps, config: { damping: 14 } });
  const fade = interpolate(f, [4, 24], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
  return (
    <AbsoluteFill style={{ background: 'radial-gradient(1200px 700px at 50% 22%, #11151f 0%, #0a0d13 62%)', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
      <AbsoluteFill style={{ backgroundImage: `linear-gradient(${accent}18 1px, transparent 1px), linear-gradient(90deg, ${accent}18 1px, transparent 1px)`, backgroundSize: '64px 64px', opacity: 0.4 }} />
      <div style={{ fontFamily: SANS, fontSize: 26, letterSpacing: 8, textTransform: 'uppercase', color: accent, fontWeight: 700, opacity: fade, transform: `translateY(${(1 - s) * 26}px)` }}>CRCP</div>
      <div style={{ fontFamily: SERIF, fontSize: 96, color: '#EAECEF', fontWeight: 800, marginTop: 22, opacity: fade, transform: `scale(${interpolate(s, [0, 1], [0.92, 1])})` }}>{head}</div>
      <div style={{ fontFamily: SANS, fontSize: 34, color: '#8A93A2', marginTop: 22, opacity: interpolate(f, [22, 40], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }) }}>{sub}</div>
      <div style={{ width: interpolate(f, [26, 60], [0, 320], { extrapolateRight: 'clamp' }), height: 3, background: accent, marginTop: 40, borderRadius: 3 }} />
    </AbsoluteFill>
  );
};

const StepScene: React.FC<{ shot: string; box: Box; cap: string; label: string; accent: string; dur: number }> = ({ shot, box, cap, label, accent, dur }) => {
  const f = useCurrentFrame(); const { fps } = useVideoConfig();
  const z = interpolate(f, [0, dur], [1.0, 1.035], { extrapolateRight: 'clamp' });
  const appear = interpolate(f, [0, 10], [0, 1], { extrapolateRight: 'clamp' });
  const pop = spring({ frame: f - 6, fps, config: { damping: 12, mass: 0.6 } });
  const pulse = 0.55 + 0.45 * Math.abs(Math.sin(f / 11)); const pad = 10;
  return (
    <AbsoluteFill style={{ background: '#05070b' }}>
      <AbsoluteFill style={{ transform: `scale(${z})`, transformOrigin: '50% 45%' }}>
        <Img src={staticFile(shot)} style={{ width: 1920, height: 1080 }} />
        <AbsoluteFill style={{ background: 'rgba(3,5,10,0.24)', opacity: appear }} />
        {box && (
          <div style={{ position: 'absolute', left: box.x - pad, top: box.y - pad, width: box.width + pad * 2, height: box.height + pad * 2, border: `4px solid ${accent}`, borderRadius: 10, boxShadow: `0 0 ${18 + pulse * 22}px ${accent}, inset 0 0 12px ${accent}40`, opacity: appear * (0.85 + pulse * 0.15), transform: `scale(${interpolate(pop, [0, 1], [1.12, 1])})`, transformOrigin: 'center' }} />
        )}
      </AbsoluteFill>
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 138, background: 'linear-gradient(180deg, rgba(5,7,11,0) 0%, rgba(5,7,11,0.93) 44%)', display: 'flex', alignItems: 'flex-end', padding: '0 56px 28px', transform: `translateY(${interpolate(appear, [0, 1], [40, 0])}px)`, opacity: appear }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 22 }}>
          <div style={{ fontFamily: SANS, fontSize: 21, fontWeight: 800, color: '#05070b', background: accent, borderRadius: 8, padding: '6px 14px', textTransform: 'uppercase', letterSpacing: 1 }}>{label}</div>
          <div style={{ fontFamily: SANS, fontSize: 40, fontWeight: 800, color: '#EAECEF' }}>{cap}</div>
        </div>
      </div>
    </AbsoluteFill>
  );
};

export const ClientWalk: React.FC<{ data: any }> = ({ data }) => {
  let from = 0;
  return (
    <AbsoluteFill style={{ background: '#05070b' }}>
      {data.scenes.map((s: any, i: number) => {
        const el = (
          <Sequence key={i} from={from} durationInFrames={s.durFrames}>
            {s.kind === 'intro'
              ? <IntroScene head={s.head} sub={s.sub} accent={s.accent} dur={s.durFrames} />
              : <StepScene shot={s.shot} box={s.box} cap={s.cap} label={s.label} accent={s.accent} dur={s.durFrames} />}
          </Sequence>
        );
        from += s.durFrames;
        return el;
      })}
    </AbsoluteFill>
  );
};