chore: stabilize apps/web (lint, build, typecheck fixes)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Failing after 1m27s
Build & Deploy / 🏗️ Build (push) Failing after 1m31s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-02-11 11:56:13 +01:00
parent 8ba81809b0
commit ecea90dc91
50 changed files with 5596 additions and 3456 deletions

View File

@@ -1,254 +1,298 @@
import React from 'react';
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';
AbsoluteFill,
interpolate,
useCurrentFrame,
useVideoConfig,
Easing,
Img,
spring,
} from "remotion";
import { MouseCursor } from "../components/MouseCursor";
import { Button } from "@/src/components/Button";
import { Loader2, Check, ShieldCheck } from "lucide-react";
/* eslint-disable no-unused-vars */
// 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';
import IconBlack from "@/src/assets/logo/Icon Black Transparent.svg";
const Background: React.FC<{ loadingOpacity: number }> = ({ loadingOpacity }) => {
return (
<AbsoluteFill className="bg-white">
{/* Website-Matching Grid */}
<div className="absolute inset-0">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="lightGrid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#f1f5f9" strokeWidth="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#lightGrid)" />
</svg>
</div>
{/* Subtle Gradient Overlay */}
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(255,255,255,0.8)_100%)]" />
{/* Dynamic "Processing" Rings (Background Activity during loading) */}
<div
className="absolute inset-0 flex items-center justify-center pointer-events-none"
style={{ opacity: loadingOpacity * 0.1 }}
const Background: React.FC<{ loadingOpacity: number }> = ({
loadingOpacity,
}) => {
return (
<AbsoluteFill className="bg-white">
{/* Website-Matching Grid */}
<div className="absolute inset-0">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern
id="lightGrid"
width="40"
height="40"
patternUnits="userSpaceOnUse"
>
<div className="w-[500px] h-[500px] border border-slate-900 rounded-full animate-[spin_10s_linear_infinite] opacity-20 border-t-transparent" />
<div className="absolute w-[400px] h-[400px] border border-slate-900 rounded-full animate-[spin_7s_linear_infinite_reverse] opacity-20 border-b-transparent" />
</div>
<path
d="M 40 0 L 0 0 0 40"
fill="none"
stroke="#f1f5f9"
strokeWidth="1"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#lightGrid)" />
</svg>
</div>
{/* STATIC Logo - Strictly no animation on the container */}
<div className="absolute top-12 left-12 z-0">
<div className="flex items-center gap-4">
<div className="w-14 h-14 flex items-center justify-center bg-white rounded-xl border border-slate-200 shadow-sm">
<Img src={IconBlack} className="w-8 h-8 opacity-90" />
</div>
<div className="flex flex-col opacity-80">
<span className="text-slate-900 font-sans font-bold text-lg tracking-tight leading-none">Mintel.me</span>
<span className="text-slate-400 font-serif italic text-xs mt-1">Component Library</span>
</div>
</div>
</div>
</AbsoluteFill>
);
{/* Subtle Gradient Overlay */}
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(255,255,255,0.8)_100%)]" />
{/* Dynamic "Processing" Rings (Background Activity during loading) */}
<div
className="absolute inset-0 flex items-center justify-center pointer-events-none"
style={{ opacity: loadingOpacity * 0.1 }}
>
<div className="w-[500px] h-[500px] border border-slate-900 rounded-full animate-[spin_10s_linear_infinite] opacity-20 border-t-transparent" />
<div className="absolute w-[400px] h-[400px] border border-slate-900 rounded-full animate-[spin_7s_linear_infinite_reverse] opacity-20 border-b-transparent" />
</div>
{/* STATIC Logo - Strictly no animation on the container */}
<div className="absolute top-12 left-12 z-0">
<div className="flex items-center gap-4">
<div className="w-14 h-14 flex items-center justify-center bg-white rounded-xl border border-slate-200 shadow-sm">
<Img src={IconBlack} className="w-8 h-8 opacity-90" />
</div>
<div className="flex flex-col opacity-80">
<span className="text-slate-900 font-sans font-bold text-lg tracking-tight leading-none">
Mintel.me
</span>
<span className="text-slate-400 font-serif italic text-xs mt-1">
Component Library
</span>
</div>
</div>
</div>
</AbsoluteFill>
);
};
// Toast Notification Component
const Toast: React.FC<{ show: boolean; text: string }> = ({ show, text }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
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 (
<div className="flex items-center gap-3 bg-slate-900 text-white px-6 py-4 rounded-xl shadow-2xl border border-slate-800">
<div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400">
<ShieldCheck size={18} />
</div>
<div className="flex flex-col">
<span className="font-bold text-sm tracking-wide">Authentication Successful</span>
<span className="text-slate-400 text-xs font-medium">Access granted to secure portal</span>
</div>
</div>
);
}
return (
<div className="flex items-center gap-3 bg-slate-900 text-white px-6 py-4 rounded-xl shadow-2xl border border-slate-800">
<div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400">
<ShieldCheck size={18} />
</div>
<div className="flex flex-col">
<span className="font-bold text-sm tracking-wide">
Authentication Successful
</span>
<span className="text-slate-400 text-xs font-medium">
Access granted to secure portal
</span>
</div>
</div>
);
};
export const ButtonShowcase: React.FC = () => {
const frame = useCurrentFrame();
const { width, height, fps } = useVideoConfig();
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;
// ---- 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;
// 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 };
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);
// 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
}
// Button Width Morph (Optional: Make it circle on load? Keeping it wide for consistency is safer)
// Hover
if (f < EXIT_START) {
const noise = Math.sin(f * 0.1) * 2;
return { x: targetX + noise, y: targetY + noise, vx: 0 };
}
// 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]);
// 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,
};
};
return (
<AbsoluteFill className="items-center justify-center overflow-hidden bg-white">
<Background loadingOpacity={isLoading ? 1 : 0} />
const { x: mouseX, y: mouseY, vx } = getMousePos(frame);
{/* Main Stage */}
<div style={{ perspective: '1000px', zIndex: 10 }}>
{/* Button Container */}
<div
style={{
transform: `scale(${buttonScale})`,
transition: 'transform 0.1s'
}}
>
<Button
href="#"
variant="primary"
showArrow={!isLoading && !isSuccess}
className={`
// 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;
}
// 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 (
<AbsoluteFill className="items-center justify-center overflow-hidden bg-white">
<Background loadingOpacity={isLoading ? 1 : 0} />
{/* Main Stage */}
<div style={{ perspective: "1000px", zIndex: 10 }}>
{/* Button Container */}
<div
style={{
transform: `scale(${buttonScale})`,
transition: "transform 0.1s",
}}
>
<Button
href="#"
variant="primary"
showArrow={!isLoading && !isSuccess}
className={`
!transition-all !duration-500
!px-16 !py-8 !text-2xl !font-bold
${isSuccess
? '!bg-white !text-slate-900 !border-slate-900 shadow-none' // Success: Outline/Minimal
: '!bg-slate-900 !text-white !border-none !shadow-2xl !shadow-slate-300' // Default/Load: Solid
${
isSuccess
? "!bg-white !text-slate-900 !border-slate-900 shadow-none" // Success: Outline/Minimal
: "!bg-slate-900 !text-white !border-none !shadow-2xl !shadow-slate-300" // Default/Load: Solid
}
!rounded-full
`}
>
<div className="flex items-center gap-4 min-w-[240px] justify-center text-center">
{/* Default State */}
{!isLoading && !isSuccess && (
<span className="animate-fade-in">Start Verification</span>
)}
>
<div className="flex items-center gap-4 min-w-[240px] justify-center text-center">
{/* Default State */}
{!isLoading && !isSuccess && (
<span className="animate-fade-in">Start Verification</span>
)}
{/* Loading State */}
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center">
<Loader2
className="animate-spin text-slate-400"
size={32}
style={{ transform: `rotate(${spinnerRot}deg)` }}
/>
</div>
)}
{/* Success State */}
{isSuccess && (
<div className="flex items-center gap-3 animate-slide-up text-slate-900">
<Check size={28} strokeWidth={3} />
<span>Verified</span>
</div>
)}
</div>
</Button>
{/* Loading State */}
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center">
<Loader2
className="animate-spin text-slate-400"
size={32}
style={{ transform: `rotate(${spinnerRot}deg)` }}
/>
</div>
</div>
)}
{/* Toast Notification Layer - Bottom Center */}
<div
className="absolute bottom-0 left-0 right-0 flex justify-center pb-20"
style={{
transform: `translateY(${toastY}px)`,
opacity: Math.max(0, toastOpacity)
}}
>
<Toast show={true} text="" />
{/* Success State */}
{isSuccess && (
<div className="flex items-center gap-3 animate-slide-up text-slate-900">
<Check size={28} strokeWidth={3} />
<span>Verified</span>
</div>
)}
</div>
</Button>
</div>
</div>
{/* 3D Cursor */}
<div
style={{
position: 'absolute',
top: 0, left: 0,
transform: `translate(${mouseX}px, ${mouseY}px) skewX(${cursorSkew}deg) rotateX(${clickRotate}deg)`,
zIndex: 1000,
pointerEvents: 'none'
}}
>
<MouseCursor x={0} y={0} isClicking={isClicking} />
</div>
</AbsoluteFill>
);
{/* Toast Notification Layer - Bottom Center */}
<div
className="absolute bottom-0 left-0 right-0 flex justify-center pb-20"
style={{
transform: `translateY(${toastY}px)`,
opacity: Math.max(0, toastOpacity),
}}
>
<Toast _show={true} _text="" />
</div>
{/* 3D Cursor */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
transform: `translate(${mouseX}px, ${mouseY}px) skewX(${cursorSkew}deg) rotateX(${clickRotate}deg)`,
zIndex: 1000,
pointerEvents: "none",
}}
>
<MouseCursor x={0} y={0} isClicking={isClicking} />
</div>
</AbsoluteFill>
);
};

View File

@@ -1,292 +1,339 @@
import React, { useMemo, useEffect, useState } from 'react';
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';
AbsoluteFill,
interpolate,
useCurrentFrame,
useVideoConfig,
delayRender,
continueRender,
spring,
Img,
} from "remotion";
/* eslint-disable no-unused-vars */
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';
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'));
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]);
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,
// ---- 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 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 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 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 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,
// 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,
}), []);
EXIT: 1500,
}),
[],
);
// ---- FORM STATE LOGIC ----
const formState = useMemo(() => {
const state = { ...initialState };
// ---- FORM STATE LOGIC ----
const formState = useMemo(() => {
const state = { ...initialState };
// Step 0: Fixed to website per request
state.projectType = 'website';
// 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 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 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 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 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));
}
// 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]);
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]);
// ---- 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 },
}), []);
// ---- 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 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 },
});
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]);
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]);
// Simple smooth camera interpolation for the actual movement
const smoothCamera = useMemo(() => {
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 },
};
// ---- 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 },
];
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;
}
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 p1 = path[idx];
const p2 = path[idx + 1] || p1;
const s = spring({
frame: frame - p1.f,
fps,
config: { stiffness: 60, damping: 25 },
});
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]);
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]);
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 (
<AbsoluteFill className="bg-white">
<BackgroundGrid />
return (
<AbsoluteFill className="bg-white">
<BackgroundGrid />
<style>{`
<style>{`
* { transition: none !important; animation: none !important; -webkit-font-smoothing: antialiased; }
.focus-layer { transform-style: preserve-3d; backface-visibility: hidden; will-change: transform; }
`}</style>
<div
className="absolute inset-0 flex items-center justify-center focus-layer"
style={{
transform: `translate3d(${Math.round(smoothCamera.x)}px, ${Math.round(smoothCamera.y)}px, 0) scale(${smoothCamera.z.toFixed(4)})`,
}}
>
<div style={{ transform: 'scale(0.85) translate3d(0,0,0)', width: '1200px' }} className="focus-layer">
<ContactForm initialStepIndex={stepIndex} initialState={formState} />
</div>
<div
className="absolute inset-0 flex items-center justify-center focus-layer"
style={{
transform: `translate3d(${Math.round(smoothCamera.x)}px, ${Math.round(smoothCamera.y)}px, 0) scale(${smoothCamera.z.toFixed(4)})`,
}}
>
<div
style={{
transform: "scale(0.85) translate3d(0,0,0)",
width: "1200px",
}}
className="focus-layer"
>
<ContactForm initialStepIndex={stepIndex} initialState={formState} />
</div>
<div
style={{
position: 'absolute',
top: '50%', left: '50%',
transform: `translate3d(${Math.round(mouse.x)}px, ${Math.round(mouse.y)}px, 0)`,
zIndex: 1000,
marginTop: -12, marginLeft: -6,
}}
className="focus-layer"
>
<MouseCursor isClicking={isClicking} x={0} y={0} />
</div>
</div>
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate3d(${Math.round(mouse.x)}px, ${Math.round(mouse.y)}px, 0)`,
zIndex: 1000,
marginTop: -12,
marginLeft: -6,
}}
className="focus-layer"
>
<MouseCursor isClicking={isClicking} x={0} y={0} />
</div>
</div>
{/* Logo HUD */}
<div className="absolute top-28 left-28 z-50 focus-layer">
<div className="w-40 h-40 bg-black rounded-[3.5rem] flex items-center justify-center shadow-2xl">
<Img src={IconWhite} className="w-24 h-24" />
</div>
</div>
{/* Logo HUD */}
<div className="absolute top-28 left-28 z-50 focus-layer">
<div className="w-40 h-40 bg-black rounded-[3.5rem] flex items-center justify-center shadow-2xl">
<Img src={IconWhite} className="w-24 h-24" />
</div>
</div>
<AbsoluteFill
className="bg-white pointer-events-none"
style={{ opacity: interpolate(frame, [0, 15], [1, 0], { extrapolateRight: 'clamp' }) }}
/>
</AbsoluteFill>
);
<AbsoluteFill
className="bg-white pointer-events-none"
style={{
opacity: interpolate(frame, [0, 15], [1, 0], {
extrapolateRight: "clamp",
}),
}}
/>
</AbsoluteFill>
);
};

View File

@@ -1,85 +1,113 @@
import React from 'react';
/* eslint-disable no-unused-vars */
import React from "react";
// ULTRA-CRITICAL ANIMATION KILLER
// This mock covers all possible Framer Motion V12 entry points
// and forces absolute determinism on both HTML and SVG elements.
const createMotionComponent = (Tag: string) => {
const Component = React.forwardRef(({
children, style, animate, initial, whileHover, whileTap,
transition, layout, layoutId,
variants, ...props
}: any, ref) => {
const Component = React.forwardRef(
(
{
children,
style,
animate,
initial,
_whileHover,
_whileTap,
_transition,
_layout,
_layoutId,
_variants,
...props
}: any,
ref,
) => {
// 1. Resolve State
// If animate is a string (variant), we try to find it in variants,
// but since we want to be deterministic, we just ignore variants for now
// to avoid complex logic. We assume the component state is driven by props.
// 1. Resolve State
// If animate is a string (variant), we try to find it in variants,
// but since we want to be deterministic, we just ignore variants for now
// to avoid complex logic. We assume the component state is driven by props.
// 2. Resolve Attributes (for SVG)
// Framer motion allows animating SVG attributes like 'r', 'cx' directly.
// We must spread 'animate' into the props to snap them.
const resolvedProps = { ...props };
if (typeof animate === "object" && !Array.isArray(animate)) {
Object.assign(resolvedProps, animate);
} else if (Array.isArray(animate)) {
// Handle keyframes by taking the first one
Object.assign(resolvedProps, animate[0]);
}
// 2. Resolve Attributes (for SVG)
// Framer motion allows animating SVG attributes like 'r', 'cx' directly.
// We must spread 'animate' into the props to snap them.
const resolvedProps = { ...props };
if (typeof animate === 'object' && !Array.isArray(animate)) {
Object.assign(resolvedProps, animate);
} else if (Array.isArray(animate)) {
// Handle keyframes by taking the first one
Object.assign(resolvedProps, animate[0]);
}
// 3. Resolve Style
const combinedStyle = {
...style,
...(typeof initial === "object" && !Array.isArray(initial)
? initial
: {}),
...(typeof animate === "object" && !Array.isArray(animate)
? animate
: {}),
};
// 3. Resolve Style
const combinedStyle = {
...style,
...(typeof initial === 'object' && !Array.isArray(initial) ? initial : {}),
...(typeof animate === 'object' && !Array.isArray(animate) ? animate : {})
};
// Final cleaning of motion-specific props that shouldn't leak to DOM
const {
_viewport,
transition: __t,
_onAnimationStart,
_onAnimationComplete,
_onUpdate,
_onPan,
_onPanStart,
_onPanEnd,
_onPanSessionStart,
_onTap,
_onTapStart,
_onTapCancel,
_onHoverStart,
_onHoverEnd,
...domProps
} = resolvedProps;
// Final cleaning of motion-specific props that shouldn't leak to DOM
const {
viewport, transition: _t, onAnimationStart, onAnimationComplete,
onUpdate, onPan, onPanStart, onPanEnd, onPanSessionStart,
onTap, onTapStart, onTapCancel, onHoverStart, onHoverEnd,
...domProps
} = resolvedProps;
return (
<Tag
ref={ref}
{...domProps}
style={combinedStyle}
data-framer-captured="true"
>
{children}
</Tag>
);
});
Component.displayName = `motion.${Tag}`;
return Component;
return (
<Tag
ref={ref}
{...domProps}
style={combinedStyle}
data-framer-captured="true"
>
{children}
</Tag>
);
},
);
Component.displayName = `motion.${Tag}`;
return Component;
};
export const motion: any = {
div: createMotionComponent('div'),
button: createMotionComponent('button'),
h1: createMotionComponent('h1'),
h2: createMotionComponent('h2'),
h3: createMotionComponent('h3'),
h4: createMotionComponent('h4'),
p: createMotionComponent('p'),
span: createMotionComponent('span'),
section: createMotionComponent('section'),
nav: createMotionComponent('nav'),
svg: createMotionComponent('svg'),
path: createMotionComponent('path'),
circle: createMotionComponent('circle'),
rect: createMotionComponent('rect'),
line: createMotionComponent('line'),
polyline: createMotionComponent('polyline'),
polygon: createMotionComponent('polygon'),
ellipse: createMotionComponent('ellipse'),
g: createMotionComponent('g'),
a: createMotionComponent('a'),
li: createMotionComponent('li'),
ul: createMotionComponent('ul'),
div: createMotionComponent("div"),
button: createMotionComponent("button"),
h1: createMotionComponent("h1"),
h2: createMotionComponent("h2"),
h3: createMotionComponent("h3"),
h4: createMotionComponent("h4"),
p: createMotionComponent("p"),
span: createMotionComponent("span"),
section: createMotionComponent("section"),
nav: createMotionComponent("nav"),
svg: createMotionComponent("svg"),
path: createMotionComponent("path"),
circle: createMotionComponent("circle"),
rect: createMotionComponent("rect"),
line: createMotionComponent("line"),
polyline: createMotionComponent("polyline"),
polygon: createMotionComponent("polygon"),
ellipse: createMotionComponent("ellipse"),
g: createMotionComponent("g"),
a: createMotionComponent("a"),
li: createMotionComponent("li"),
ul: createMotionComponent("ul"),
};
export const m = motion;
@@ -89,21 +117,25 @@ export const LayoutGroup = ({ children }: any) => <>{children}</>;
export const LazyMotion = ({ children }: any) => <>{children}</>;
export const useAnimation = () => ({
start: () => Promise.resolve(),
set: () => { },
stop: () => { },
mount: () => { },
start: () => Promise.resolve(),
set: () => {},
stop: () => {},
mount: () => {},
});
export const useInView = () => true;
export const useScroll = () => ({
scrollYProgress: { get: () => 0, onChange: () => () => { }, getVelocity: () => 0 },
scrollY: { get: () => 0, onChange: () => () => { }, getVelocity: () => 0 }
scrollYProgress: {
get: () => 0,
onChange: () => () => {},
getVelocity: () => 0,
},
scrollY: { get: () => 0, onChange: () => () => {}, getVelocity: () => 0 },
});
export const useTransform = (value: any, from: any[], to: any[]) => to[0];
export const useTransform = (_value: any, _from: any[], to: any[]) => to[0];
export const useSpring = (value: any) => value;
export const useCycle = (...args: any[]) => [args[0], () => { }];
export const useCycle = (...args: any[]) => [args[0], () => {}];
export const useIsPresent = () => true;
export const useReducedMotion = () => true;
export const useAnimationControls = useAnimation;