import React from 'react'; import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig, Easing, Img, staticFile, spring, random, } from 'remotion'; import { MouseCursor } from '../components/MouseCursor'; import { Button } from '@/src/components/Button'; import { Loader2, Check, UserCheck, ShieldCheck } from 'lucide-react'; // Import logo using the alias setup in remotion.config.ts // We'll use the staticFile helper if it's in public, but these are in src/assets // So we can try to import them directly if the bundler allows, or move them to public. // Given Header.tsx imports them, they should be importable. // import IconWhite from '@/src/assets/logo/Icon White Transparent.svg'; // Not used in this version // Import black logo for light mode import IconBlack from '@/src/assets/logo/Icon Black Transparent.svg'; const Background: React.FC<{ loadingOpacity: number }> = ({ loadingOpacity }) => { return ( {/* Website-Matching Grid */}
{/* Subtle Gradient Overlay */}
{/* Dynamic "Processing" Rings (Background Activity during loading) */}
{/* STATIC Logo - Strictly no animation on the container */}
Mintel.me Component Library
); }; // Toast Notification Component const Toast: React.FC<{ show: boolean; text: string }> = ({ show, text }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // Animate in/out based on 'show' prop would require state tracking or precise frame logic // We'll trust the parent to mount/unmount or pass an animatable value // For video, deterministic frame-based spring is best. // We'll actually control position purely by parent for simplicity in this demo context return (
Authentication Successful Access granted to secure portal
); } export const ButtonShowcase: React.FC = () => { const frame = useCurrentFrame(); const { width, height, fps } = useVideoConfig(); // ---- SEQUENCE TIMELINE (300 frames / 5s) ---- const ENTER_START = 20; const HOVER_START = 70; const CLICK_FRAME = 90; const LOADING_START = 95; const SUCCESS_START = 180; const TOAST_START = 190; const TOAST_END = 260; const EXIT_START = 220; // 1. Mouse Animation (Bézier Path) const getMousePos = (f: number) => { const startX = width * 1.2; const startY = height * 1.2; const targetX = width / 2; const targetY = height / 2; if (f < ENTER_START) return { x: startX, y: startY, vx: 0 }; // Approach if (f < HOVER_START) { const t = interpolate(f, [ENTER_START, HOVER_START], [0, 1], { extrapolateRight: 'clamp' }); const ease = Easing.bezier(0.16, 1, 0.3, 1)(t); const x = interpolate(ease, [0, 1], [startX, targetX]); const y = interpolate(ease, [0, 1], [startY, targetY]); return { x, y, vx: -10 }; // Approximate Velocity } // Hover if (f < EXIT_START) { const noise = Math.sin(f * 0.1) * 2; return { x: targetX + noise, y: targetY + noise, vx: 0 }; } // Exit const t = interpolate(f, [EXIT_START, EXIT_START + 30], [0, 1], { extrapolateLeft: 'clamp' }); const ease = Easing.exp(t); return { x: interpolate(ease, [0, 1], [targetX, width * 0.9]), y: interpolate(ease, [0, 1], [targetY, -100]), vx: 10 }; }; const { x: mouseX, y: mouseY, vx } = getMousePos(frame); // 3D Cursor Skew const cursorSkew = interpolate(vx, [-20, 20], [20, -20], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); const isClicking = frame >= CLICK_FRAME && frame < CLICK_FRAME + 8; const clickRotate = isClicking ? 30 : 0; // 2. Button State Logic const isLoading = frame >= LOADING_START && frame < SUCCESS_START; const isSuccess = frame >= SUCCESS_START; // Loading Spinner Rotation const spinnerRot = interpolate(frame, [LOADING_START, SUCCESS_START], [0, 720]); // Button Scale Physics const pressSpring = spring({ frame: frame - CLICK_FRAME, fps, config: { stiffness: 400, damping: 20 } }); const successSpring = spring({ frame: frame - SUCCESS_START, fps, config: { stiffness: 200, damping: 15 } }); // Morph scale: Click(Compress) -> Loading(Normal) -> Success(Pop) let buttonScale = 1; if (frame >= CLICK_FRAME && frame < LOADING_START) { buttonScale = 1 - (pressSpring * 0.05); } else if (isSuccess) { buttonScale = 1 + (successSpring * 0.05); } // Button Width Morph (Optional: Make it circle on load? Keeping it wide for consistency is safer) // 3. Toast Animation const toastSpring = spring({ frame: frame - TOAST_START, fps, config: { stiffness: 100, damping: 15 } }); const toastExit = spring({ frame: frame - TOAST_END, fps, config: { stiffness: 100, damping: 20 } }); const toastY = interpolate(toastSpring, [0, 1], [100, -80]) + interpolate(toastExit, [0, 1], [0, 200]); const toastOpacity = interpolate(toastSpring, [0, 1], [0, 1]) - interpolate(toastExit, [0, 0.5], [0, 1]); return ( {/* Main Stage */}
{/* Button Container */}
{/* Toast Notification Layer - Bottom Center */}
{/* 3D Cursor */}
); };