← back to Prestige Car Wash Video

src/Video.tsx

118 lines

import { AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig, interpolate } from 'remotion';
import { SECTIONS } from './sections';
import { Background } from './Background';
import { BrowserShot } from './BrowserShot';
import { Caption } from './Caption';
import { Card } from './Card';

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

// fade each section in/out over the persistent background
const FadeWrap: React.FC<{ dur: number; children: React.ReactNode }> = ({ dur, children }) => {
  const f = useCurrentFrame();
  const o = interpolate(f, [0, 12, dur - 10, dur], [0, 1, 1, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  return <AbsoluteFill style={{ opacity: o }}>{children}</AbsoluteFill>;
};

export const PrestigeShowcase: React.FC = () => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();

  const offsets: number[] = [];
  let acc = 0;
  for (const s of SECTIONS) { offsets.push(acc); acc += s.frames; }
  let active = 0;
  for (let i = 0; i < SECTIONS.length; i++) if (frame >= offsets[i]) active = i;
  const accent = SECTIONS[active].accent;
  const shotIndex = SECTIONS.slice(0, active + 1).filter((s) => s.kind === 'shot').length;
  const shotTotal = SECTIONS.filter((s) => s.kind === 'shot').length;

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

      {SECTIONS.map((s, i) => (
        <Sequence key={s.id} from={offsets[i]} durationInFrames={s.frames} name={s.title}>
          <FadeWrap dur={s.frames}>
            {s.kind === 'card' ? (
              <Card s={s} />
            ) : (
              <>
                <BrowserShot s={s} />
                <Caption s={s} index={i} total={SECTIONS.length} />
              </>
            )}
          </FadeWrap>
        </Sequence>
      ))}

      {/* top 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>

      {/* section counter (only during shot sections) */}
      {SECTIONS[active].kind === 'shot' && (
        <div
          style={{
            position: 'absolute',
            top: 34,
            right: 54,
            fontFamily: FONT,
            fontWeight: 700,
            fontSize: 24,
            color: '#8ea6c4',
            letterSpacing: 2,
          }}
        >
          {String(shotIndex).padStart(2, '0')}
          <span style={{ opacity: 0.4 }}> / {String(shotTotal).padStart(2, '0')}</span>
        </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,
        }}
      >
        <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>
  );
};