import React, { useMemo, useEffect, useState } from 'react'; import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig, Easing, Img, delayRender, continueRender, spring, Audio, staticFile, } 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 Deep Interaction Script')); useEffect(() => { if (typeof window !== 'undefined') (window as any).isRemotion = true; const timer = setTimeout(() => continueRender(handle), 500); return () => clearTimeout(timer); }, [handle]); // ---- TIMELINE CONSTANTS (1500 Frames / 25s) ---- const T = useMemo(() => ({ ENTER: 0, // Step 0: Type Selection SELECT_WEBSITE: 60, NEXT_0: 100, // Step 1: Company Profile COMPANY_TYPE_START: 150, COMPANY_TYPE_END: 250, NEXT_1: 300, // Step 2: Presence URL_TYPE_START: 350, URL_TYPE_END: 450, NEXT_2: 500, // Step 3: Scope & Services (Multi-clicks) SCOPE_CLICK_1: 550, SCOPE_CLICK_2: 600, SCOPE_CLICK_3: 650, NEXT_3: 750, // Step 4: Design Vibe VIBE_SELECT: 850, NEXT_4: 950, // Step 5: Contact Info NAME_TYPE_START: 1050, NAME_TYPE_END: 1120, EMAIL_TYPE_START: 1150, EMAIL_TYPE_END: 1250, MESSAGE_TYPE_START: 1280, MESSAGE_TYPE_END: 1400, SUBMIT: 1450, EXIT: 1500, }), []); // ---- FORM STATE LOGIC ---- const formState = useMemo(() => { const state = { ...initialState }; // Step 0: Fixed to website per request state.projectType = 'website'; // Step 1: Company if (frame > T.COMPANY_TYPE_START) { const text = "Mintel Studios"; const progress = interpolate(frame, [T.COMPANY_TYPE_START, T.COMPANY_TYPE_END], [0, text.length], { extrapolateRight: 'clamp' }); state.companyName = text.substring(0, Math.round(progress)); } // Step 2: URL if (frame > T.URL_TYPE_START) { const text = "mintel.me"; const progress = interpolate(frame, [T.URL_TYPE_START, T.URL_TYPE_END], [0, text.length], { extrapolateRight: 'clamp' }); state.existingWebsite = text.substring(0, Math.round(progress)); } // Step 3: Selections if (frame > T.SCOPE_CLICK_1) state.selectedPages = ['Home', 'About']; if (frame > T.SCOPE_CLICK_2) state.selectedPages = ['Home', 'About', 'Services']; if (frame > T.SCOPE_CLICK_3) state.features = ['blog_news']; // Step 4: Design if (frame > T.VIBE_SELECT) state.designVibe = 'tech'; // Step 5: Contact if (frame > T.NAME_TYPE_START) { const text = "Marc Mintel"; const progress = interpolate(frame, [T.NAME_TYPE_START, T.NAME_TYPE_END], [0, text.length], { extrapolateRight: 'clamp' }); state.name = text.substring(0, Math.round(progress)); } if (frame > T.EMAIL_TYPE_START) { const text = "marc@mintel.me"; const progress = interpolate(frame, [T.EMAIL_TYPE_START, T.EMAIL_TYPE_END], [0, text.length], { extrapolateRight: 'clamp' }); state.email = text.substring(0, Math.round(progress)); } if (frame > T.MESSAGE_TYPE_START) { const text = "Hi folks! Let's build something cinematic and smooth."; const progress = interpolate(frame, [T.MESSAGE_TYPE_START, T.MESSAGE_TYPE_END], [0, text.length], { extrapolateRight: 'clamp' }); state.message = text.substring(0, Math.round(progress)); } return state; }, [frame, T]); // ---- STEP NAVIGATION ---- const stepIndex = useMemo(() => { if (frame < T.NEXT_0) return 0; if (frame < T.NEXT_1) return 1; if (frame < T.NEXT_2) return 2; if (frame < T.NEXT_3) return 3; if (frame < T.NEXT_4) return 6; // Mapping depends on actual component order, let's assume standard sequence if (frame < T.SUBMIT) return 12; // Final Contact step return 13; // Success }, [frame, T]); // ---- CAMERA ANCHORS ---- const anchors = useMemo(() => ({ 'overview': { z: 0.85, x: 0, y: 0 }, 'type': { z: 1.15, x: 250, y: 150 }, 'company': { z: 1.3, x: 100, y: 50 }, 'presence': { z: 1.3, x: 100, y: -50 }, 'scope': { z: 1.1, x: -100, y: 0 }, 'design': { z: 1.15, x: 150, y: 100 }, 'contact': { z: 1.25, x: 0, y: 400 }, 'success': { z: 0.9, x: 0, y: 0 }, }), []); const activeAnchor = useMemo(() => { if (frame < T.SELECT_WEBSITE) return anchors.overview; if (frame < T.NEXT_0) return anchors.type; if (frame < T.NEXT_1) return anchors.company; if (frame < T.NEXT_2) return anchors.presence; if (frame < T.NEXT_3) return anchors.scope; if (frame < T.NEXT_4) return anchors.design; if (frame < T.SUBMIT) return anchors.contact; return anchors.success; }, [frame, anchors, T]); const camera = useMemo(() => { // Continuous organic spring follow const s = spring({ frame, fps, config: { stiffness: 45, damping: 20 }, }); // This is a simplified lerp since spring() is stateless per frame in remotion, // for true chasing we'd need a custom reducer or just accept the "settle" behavior. // Actually, we'll use interpolate for predictable transitions between keyframes. return activeAnchor; }, [frame, activeAnchor, fps]); // Simple smooth camera interpolation for the actual movement const smoothCamera = useMemo(() => { // To avoid jumpiness when anchor switches, we could use a custom useSpring alternative, // but for now let's just use the active anchor and let the frame-based spring handle the property drift if planned. // Actually, let's just use Interpolation for reliability. return activeAnchor; }, [activeAnchor]); // ---- MOUSE PATH ---- const mouse = useMemo(() => { const targets = { off: { x: width * 1.2, y: height * 1.2 }, type_website: { x: 200, y: 50 }, btn_next: { x: 450, y: 450 }, company_input: { x: 0, y: 0 }, url_input: { x: 0, y: -50 }, scope_1: { x: -300, y: -100 }, scope_2: { x: -300, y: 0 }, scope_3: { x: 0, y: 200 }, vibe_tech: { x: 250, y: 100 }, contact_name: { x: -200, y: 200 }, contact_email: { x: 200, y: 200 }, contact_msg: { x: 0, y: 400 }, btn_submit: { x: 400, y: 550 }, }; const path = [ { f: 0, ...targets.off }, { f: T.SELECT_WEBSITE, ...targets.type_website }, { f: T.NEXT_0, ...targets.btn_next }, { f: T.COMPANY_TYPE_START, ...targets.company_input }, { f: T.NEXT_1, ...targets.btn_next }, { f: T.URL_TYPE_START, ...targets.url_input }, { f: T.NEXT_2, ...targets.btn_next }, { f: T.SCOPE_CLICK_1, ...targets.scope_1 }, { f: T.SCOPE_CLICK_2, ...targets.scope_2 }, { f: T.SCOPE_CLICK_3, ...targets.scope_3 }, { f: T.NEXT_3, ...targets.btn_next }, { f: T.VIBE_SELECT, ...targets.vibe_tech }, { f: T.NEXT_4, ...targets.btn_next }, { f: T.NAME_TYPE_START, ...targets.contact_name }, { f: T.EMAIL_TYPE_START, ...targets.contact_email }, { f: T.MESSAGE_TYPE_START, ...targets.contact_msg }, { f: T.SUBMIT, ...targets.btn_submit }, { f: T.EXIT, ...targets.off }, ]; 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: 60, damping: 25 }, }); return { x: interpolate(s, [0, 1], [p1.x, p2.x]), y: interpolate(s, [0, 1], [p1.y, p2.y]), }; }, [frame, width, height, fps, T]); const isClicking = useMemo(() => { const clicks = [ T.SELECT_WEBSITE, T.NEXT_0, T.NEXT_1, T.NEXT_2, T.SCOPE_CLICK_1, T.SCOPE_CLICK_2, T.SCOPE_CLICK_3, T.NEXT_3, T.VIBE_SELECT, T.NEXT_4, T.SUBMIT ]; return clicks.some(c => frame >= c && frame < c + 8); }, [frame, T]); return (
{/* Logo HUD */}
); };