@@ -50,9 +50,8 @@ export function CompanyStep({ state, updateState }: CompanyStepProps) {
whileTap={{ scale: 0.95 }}
type="button"
onClick={() => updateState({ employeeCount: option.id })}
- className={`p-6 rounded-[2rem] border-2 transition-all duration-300 font-bold text-lg ${
- state.employeeCount === option.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-100 bg-white hover:border-slate-300 text-slate-600'
- }`}
+ className={`p-6 rounded-[2rem] border-2 transition-all duration-300 font-bold text-lg ${state.employeeCount === option.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-100 bg-white hover:border-slate-300 text-slate-600'
+ }`}
>
{option.label}
diff --git a/src/components/ContactForm/steps/TypeStep.tsx b/src/components/ContactForm/steps/TypeStep.tsx
index b3a0d83..69b4b14 100644
--- a/src/components/ContactForm/steps/TypeStep.tsx
+++ b/src/components/ContactForm/steps/TypeStep.tsx
@@ -20,13 +20,13 @@ export function TypeStep({ state, updateState }: TypeStepProps) {
].map((type, index) => (
updateState({ projectType: type.id as ProjectType })}
- className={`w-full p-12 rounded-[4rem] border-2 text-left transition-all duration-700 focus:outline-none overflow-hidden relative group ${
- state.projectType === type.id ? 'border-slate-900 bg-slate-900 text-white shadow-2xl shadow-slate-400' : 'border-slate-100 bg-white hover:border-slate-300 hover:shadow-xl'
- }`}
+ className={`w-full p-12 rounded-[4rem] border-2 text-left transition-all duration-700 focus:outline-none overflow-hidden relative group ${state.projectType === type.id ? 'border-slate-900 bg-slate-900 text-white shadow-2xl shadow-slate-400' : 'border-slate-100 bg-white hover:border-slate-300 hover:shadow-xl'
+ }`}
>
{type.illustration}
@@ -34,7 +34,7 @@ export function TypeStep({ state, updateState }: TypeStepProps) {
Grundlage
{type.desc}
-
+
{state.projectType === type.id && (
{
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/video/components/MouseCursor.tsx b/video/components/MouseCursor.tsx
new file mode 100644
index 0000000..757ffef
--- /dev/null
+++ b/video/components/MouseCursor.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { MousePointer2 } from 'lucide-react';
+
+interface MouseCursorProps {
+ x: number;
+ y: number;
+ isClicking?: boolean;
+}
+
+export const MouseCursor: React.FC = ({ x, y, isClicking }) => {
+ return (
+
+
+
+ {isClicking && (
+
+ )}
+
+
+ );
+};
diff --git a/video/components/NextLinkMock.tsx b/video/components/NextLinkMock.tsx
new file mode 100644
index 0000000..df5755d
--- /dev/null
+++ b/video/components/NextLinkMock.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const Link: React.FC<{ href: string; children: React.ReactNode; className?: string }> = ({ children, className }) => {
+ return {children}
;
+};
+
+export default Link;
diff --git a/video/compositions/ButtonShowcase.tsx b/video/compositions/ButtonShowcase.tsx
new file mode 100644
index 0000000..8e2147e
--- /dev/null
+++ b/video/compositions/ButtonShowcase.tsx
@@ -0,0 +1,254 @@
+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 */}
+
+
+
+ {/* Default State */}
+ {!isLoading && !isSuccess && (
+
Start Verification
+ )}
+
+ {/* Loading State */}
+ {isLoading && (
+
+
+
+ )}
+
+ {/* Success State */}
+ {isSuccess && (
+
+
+ Verified
+
+ )}
+
+
+
+
+
+ {/* Toast Notification Layer - Bottom Center */}
+
+
+
+
+ {/* 3D Cursor */}
+
+
+
+
+ );
+};
diff --git a/video/compositions/ContactFormShowcase.tsx b/video/compositions/ContactFormShowcase.tsx
new file mode 100644
index 0000000..f5c067d
--- /dev/null
+++ b/video/compositions/ContactFormShowcase.tsx
@@ -0,0 +1,209 @@
+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 */}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/video/index.ts b/video/index.ts
new file mode 100644
index 0000000..91fa0f3
--- /dev/null
+++ b/video/index.ts
@@ -0,0 +1,4 @@
+import { registerRoot } from 'remotion';
+import { RemotionRoot } from './Root';
+
+registerRoot(RemotionRoot);
diff --git a/video/mocks/framer-motion.tsx b/video/mocks/framer-motion.tsx
new file mode 100644
index 0000000..e2825c8
--- /dev/null
+++ b/video/mocks/framer-motion.tsx
@@ -0,0 +1,114 @@
+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) => {
+
+ // 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]);
+ }
+
+ // 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;
+
+ return (
+
+ {children}
+
+ );
+ });
+ 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'),
+};
+
+export const m = motion;
+export const AnimatePresence = ({ children }: any) => <>{children}>;
+export const MotionConfig = ({ children }: any) => <>{children}>;
+export const LayoutGroup = ({ children }: any) => <>{children}>;
+export const LazyMotion = ({ children }: any) => <>{children}>;
+
+export const useAnimation = () => ({
+ 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 }
+});
+
+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 useIsPresent = () => true;
+export const useReducedMotion = () => true;
+export const useAnimationControls = useAnimation;
+export const usePresence = () => [true, null];
+
+export const isValidMotionProp = () => true;
+
+export default motion;
diff --git a/video/mocks/next-image.tsx b/video/mocks/next-image.tsx
new file mode 100644
index 0000000..9b4689c
--- /dev/null
+++ b/video/mocks/next-image.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+
+const Image: React.FC = ({ src, alt, ...props }) => {
+ // eslint-disable-next-line @next/next/no-img-element
+ return ;
+};
+
+export default Image;
diff --git a/video/mocks/next-navigation.tsx b/video/mocks/next-navigation.tsx
new file mode 100644
index 0000000..56c1ff5
--- /dev/null
+++ b/video/mocks/next-navigation.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+export const useRouter = () => ({
+ push: () => { },
+ replace: () => { },
+ prefetch: () => { },
+ back: () => { },
+});
+
+export const useSearchParams = () => {
+ return new URLSearchParams();
+};
+
+export const usePathname = () => '/';
+
+export const Link: React.FC<{ href: string; children: React.ReactNode; className?: string }> = ({ children, className }) => {
+ return {children}
;
+};
+
+export default Link;
diff --git a/video/mocks/reveal.tsx b/video/mocks/reveal.tsx
new file mode 100644
index 0000000..6c2a9d5
--- /dev/null
+++ b/video/mocks/reveal.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export const Reveal = ({ children }: { children: React.ReactNode }) => {
+ return <>{children}>;
+};
diff --git a/video/style.css b/video/style.css
new file mode 100644
index 0000000..334a7fa
--- /dev/null
+++ b/video/style.css
@@ -0,0 +1,44 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@font-face {
+ font-family: 'Inter';
+ src: url('https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.19') format('woff2');
+ font-weight: 400;
+}
+
+@font-face {
+ font-family: 'Inter';
+ src: url('https://rsms.me/inter/font-files/Inter-Bold.woff2?v=3.19') format('woff2');
+ font-weight: 700;
+}
+
+body {
+ font-family: 'Inter', sans-serif;
+ margin: 0;
+ padding: 0;
+ -webkit-font-smoothing: antialiased;
+}
+
+/*
+ REMOTION HARD-FREEZE
+ We must disable EVERY browser-native transition and animation.
+ These run on real-time and will always lag in frame-by-frame renders.
+*/
+* {
+ transition: none !important;
+ transition-property: none !important;
+ transition-duration: 0s !important;
+ transition-delay: 0s !important;
+ animation: none !important;
+ animation-duration: 0s !important;
+ animation-delay: 0s !important;
+ animation-iteration-count: 0 !important;
+ animation-fill-mode: none !important;
+}
+
+/* Ensure no smooth scrolling which fights Remotion */
+html {
+ scroll-behavior: auto !important;
+}
\ No newline at end of file
diff --git a/video/utils/animations.ts b/video/utils/animations.ts
new file mode 100644
index 0000000..bead0ec
--- /dev/null
+++ b/video/utils/animations.ts
@@ -0,0 +1,24 @@
+import { spring, SpringConfig } from 'remotion';
+
+export const COMPONENT_SPRING: Partial = {
+ stiffness: 200,
+ damping: 20,
+ mass: 1,
+};
+
+export const MOUSE_SPRING: Partial = {
+ stiffness: 150,
+ damping: 15,
+ mass: 0.5,
+};
+
+export const clickAnimation = (frame: number, clickFrame: number, fps: number) => {
+ return spring({
+ frame: frame - clickFrame,
+ fps,
+ config: {
+ stiffness: 300,
+ damping: 10,
+ },
+ });
+};
diff --git a/video/webpack-override.ts b/video/webpack-override.ts
new file mode 100644
index 0000000..802e0a8
--- /dev/null
+++ b/video/webpack-override.ts
@@ -0,0 +1,28 @@
+import { WebpackOverrideFn } from '@remotion/bundler';
+import path from 'path';
+
+export const webpackOverride: WebpackOverrideFn = (currentConfig) => {
+ return {
+ ...currentConfig,
+ resolve: {
+ ...currentConfig.resolve,
+ alias: {
+ ...(currentConfig.resolve?.alias ?? {}),
+ '@': path.resolve(__dirname, '..'),
+ 'next/navigation': path.resolve(__dirname, 'mocks/next-navigation.tsx'),
+ 'next/image': path.resolve(__dirname, 'mocks/next-image.tsx'),
+ 'next/link': path.resolve(__dirname, 'mocks/next-navigation.tsx'),
+
+ // SYSTEMATIC ALIASING FOR ALL ANIMATION PROXYING
+ 'framer-motion': path.resolve(__dirname, 'mocks/framer-motion.tsx'),
+ 'framer-motion/dist/framer-motion': path.resolve(__dirname, 'mocks/framer-motion.tsx'),
+
+ // Reveal Component Proxying (Deterministic Reveal)
+ '../Reveal': path.resolve(__dirname, 'mocks/reveal.tsx'),
+ '../../Reveal': path.resolve(__dirname, 'mocks/reveal.tsx'),
+ '../../../Reveal': path.resolve(__dirname, 'mocks/reveal.tsx'),
+ '@/src/components/Reveal': path.resolve(__dirname, 'mocks/reveal.tsx'),
+ },
+ },
+ };
+};