import React, { useMemo, useEffect, useState, useRef } from 'react'; import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig, Easing, Img, delayRender, continueRender, spring, } from 'remotion'; import { MouseCursor } from '../components/MouseCursor'; import { ContactForm } from '@/src/components/ContactForm'; import { BackgroundGrid } from '@/src/components/Layout'; import { initialState } from '@/src/components/ContactForm/constants'; // Brand Assets import IconWhite from '@/src/assets/logo/Icon White Transparent.svg'; export const ContactFormShowcase: React.FC = () => { const frame = useCurrentFrame(); const { width, height, fps } = useVideoConfig(); const [handle] = useState(() => delayRender('Initializing Smart Camera')); useEffect(() => { if (typeof window !== 'undefined') (window as any).isRemotion = true; const timer = setTimeout(() => continueRender(handle), 500); return () => clearTimeout(timer); }, [handle]); // ---- SMART ANCHOR SYSTEM ---- // We define which target the camera should follow at each frame const activeTargetId = useMemo(() => { if (frame < 60) return 'overview'; if (frame < 160) return 'focus-target-web-app'; if (frame < 260) return 'focus-target-next'; if (frame < 300) return 'overview'; if (frame < 550) return 'focus-target-company'; if (frame < 650) return 'focus-target-next'; if (frame < 800) return 'overview'; return 'overview'; }, [frame]); // Pre-calculated coordinates for common targets in the 1200px scaled form // Since real-time DOM measurements can be jittery during a render, // we use "Systematic Virtual Anchors" calibrated to the layout. const anchors = useMemo(() => ({ 'overview': { z: 0.85, x: 0, y: 0 }, 'focus-target-web-app': { z: 1.3, x: 300, y: 0 }, 'focus-target-next': { z: 1.1, x: -380, y: -280 }, 'focus-target-company': { z: 1.45, x: 50, y: 220 }, }), []); // ---- UNIFIED SPRING PHYSICS ---- const camera = useMemo(() => { // Find the target state const target = (anchors as any)[activeTargetId] || anchors.overview; // We use a "Continuous Follow" spring that always chases the activeTargetId // but we need to know when the target changed to reset the spring animation. // Actually, for smoothness, we'll just use the frame globally and let the spring settle. // Let's create a transition system const points = [ { f: 0, t: 'overview' }, { f: 80, t: 'focus-target-web-app' }, { f: 200, t: 'focus-target-next' }, { f: 280, t: 'overview' }, { f: 380, t: 'focus-target-company' }, { f: 580, t: 'focus-target-next' }, { f: 700, t: 'overview' }, ]; let idx = 0; for (let i = 0; i < points.length; i++) { if (frame >= points[i].f) idx = i; } const start = (anchors as any)[points[idx].t]; const end = (anchors as any)[(points[idx + 1] || points[idx]).t]; const s = spring({ frame: frame - points[idx].f, fps, config: { stiffness: 45, damping: 18, mass: 1 }, }); return { z: interpolate(s, [0, 1], [start.z, end.z]), x: interpolate(s, [0, 1], [start.x, end.x]), y: interpolate(s, [0, 1], [start.y, end.y]), }; }, [frame, anchors, fps]); // ---- MOUSE HANDLER (Sticks to targets) ---- const mouse = useMemo(() => { const path = [ { f: 10, x: width * 1.5, y: height * 1.2 }, // Offscreen { f: 80, x: width * 0.35, y: height * 0.45 }, // Move to Web App { f: 210, x: width * 0.85, y: height * 0.68 }, // Move to Next { f: 380, x: width * 0.50, y: height * 0.35 }, // Move to Input { f: 590, x: width * 0.85, y: height * 0.68 }, // Move to Next { f: 850, x: width * 1.5, y: height * 1.2 }, // Exit ]; let idx = 0; for (let i = 0; i < path.length; i++) { if (frame >= path[i].f) idx = i; } const p1 = path[idx]; const p2 = path[idx + 1] || p1; const s = spring({ frame: frame - p1.f, fps, config: { stiffness: 40, damping: 22 }, }); return { x: interpolate(s, [0, 1], [p1.x, p2.x]), y: interpolate(s, [0, 1], [p1.y, p2.y]), }; }, [frame, width, height, fps]); // ---- UI PROGRESSION ---- const ui = useMemo(() => { let step = 0; let type = 'website'; let name = ''; if (frame > 140) type = 'web-app'; if (frame > 260) step = 1; if (frame > 400) { const txt = "Mintel Studios"; const chars = Math.min(Math.floor((frame - 400) / 4), txt.length); name = txt.substring(0, chars); } if (frame > 620) step = 2; return { step, type, name }; }, [frame]); const formState = useMemo(() => ({ ...initialState, projectType: ui.type as any, companyName: ui.name, }), [ui]); // Click triggers const isClicking = (frame > 135 && frame < 150) || (frame > 255 && frame < 270) || (frame > 615 && frame < 630); return (
{/* Scale factor 0.85 for the base container */}
{/* Mouse Cursor */}
{/* Branding Logo HUD */}
); };