import React from "react"; import { AbsoluteFill, useCurrentFrame, useVideoConfig, spring, interpolate, } from "remotion"; import { theme } from "../../components/shared/theme"; import { AppScreenshot, MockScreen } from "../../components/shared/AppScreenshot"; /** * Scene 4: Routes generated * * Route auto-generates inside phone frame. * Map with animated route line + game cards appear. * Overlay: "Real route. Real games." */ type Stop = { city: string; x: number; y: number; }; const STOPS: Stop[] = [ { city: "LA", x: 180, y: 300 }, { city: "SF", x: 140, y: 130 }, { city: "SD", x: 240, y: 400 }, { city: "PHX", x: 480, y: 340 }, ]; export const RouteScene: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // Overlay text const overlayProgress = spring({ frame: frame - 0.3 * fps, fps, config: theme.animation.snappy, }); const overlayOpacity = interpolate( frame - 0.3 * fps, [0, 0.2 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" } ); return ( {/* Overlay: "Real route. Real games." */}
Real route. Real games.
{/* Phone with route */}
); }; /** Route map + game list inside phone */ const RouteContent: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // Route draw const lineProgress = interpolate( frame, [0.15 * fps, 1.5 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" } ); const pathSegments: string[] = []; for (let i = 0; i < STOPS.length - 1; i++) { const from = STOPS[i]; const to = STOPS[i + 1]; if (i === 0) pathSegments.push(`M ${from.x} ${from.y}`); const cx = (from.x + to.x) / 2; const cy = Math.min(from.y, to.y) - 30; pathSegments.push(`Q ${cx} ${cy} ${to.x} ${to.y}`); } const fullPath = pathSegments.join(" "); // Game cards const games = [ { team: "@ Dodgers", venue: "Dodger Stadium", date: "Jun 12", color: "#005A9C" }, { team: "@ Giants", venue: "Oracle Park", date: "Jun 14", color: "#FD5A1E" }, { team: "@ Padres", venue: "Petco Park", date: "Jun 16", color: "#2F241D" }, { team: "@ D-backs", venue: "Chase Field", date: "Jun 18", color: "#A71930" }, ]; return (
{/* Map section (top 45%) */}
{STOPS.map((stop, index) => { const markerProgress = spring({ frame: frame - (0.2 + index * 0.35) * fps, fps, config: { damping: 12, stiffness: 180 }, }); const leftPct = (stop.x / 600) * 100; const topPct = (stop.y / 480) * 100; return (
{stop.city}
); })}
{/* Game cards (bottom 55%) */}
Your Games
{games.map((game, index) => { const cardDelay = 1.2 + index * 0.18; const cardProgress = spring({ frame: frame - cardDelay * fps, fps, config: theme.animation.snappy, }); const translateX = interpolate(cardProgress, [0, 1], [250, 0]); const opacity = interpolate(cardProgress, [0, 1], [0, 1]); return (
{game.date.split(" ")[1]}
{game.date.split(" ")[0]}
{game.team}
{game.venue}
); })}
); };