'use client';
import { cn } from '@/lib/utils';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { AnimatePresence, motion } from 'framer-motion';
import Link from 'next/link';
import React, { useState, useMemo, useRef, useEffect } from 'react';
import * as THREE from 'three';
type Uniforms = {
[key: string]: {
value: number[] | number[][] | number;
type: string;
};
};
interface ShaderProps {
source: string;
uniforms: {
[key: string]: {
value: number[] | number[][] | number;
type: string;
};
};
maxFps?: number;
}
interface SignInPageProps {
className?: string;
}
export const CanvasRevealEffect = ({
animationSpeed = 10,
opacities = [0.3, 0.3, 0.3, 0.5, 0.5, 0.5, 0.8, 0.8, 0.8, 1],
colors = [[0, 255, 255]],
containerClassName,
dotSize,
showGradient = true,
reverse = false, // This controls the direction
}: {
animationSpeed?: number;
opacities?: number[];
colors?: number[][];
containerClassName?: string;
dotSize?: number;
showGradient?: boolean;
reverse?: boolean; // This prop determines the direction
}) => {
return (
{' '}
{/* Removed bg-white */}
{showGradient && (
// Adjust gradient colors if needed based on background (was bg-white, now likely uses containerClassName bg)
// Example assuming a dark background like the SignInPage uses:
)}
);
};
interface DotMatrixProps {
colors?: number[][];
opacities?: number[];
totalSize?: number;
dotSize?: number;
shader?: string;
center?: ('x' | 'y')[];
}
const DotMatrix: React.FC = ({
colors = [[0, 0, 0]],
opacities = [0.04, 0.04, 0.04, 0.04, 0.04, 0.08, 0.08, 0.08, 0.08, 0.14],
totalSize = 20,
dotSize = 2,
shader = '', // This shader string will now contain the animation logic
center = ['x', 'y'],
}) => {
// ... uniforms calculation remains the same for colors, opacities, etc.
const uniforms = React.useMemo(() => {
let colorsArray = [colors[0], colors[0], colors[0], colors[0], colors[0], colors[0]];
if (colors.length === 2) {
colorsArray = [colors[0], colors[0], colors[0], colors[1], colors[1], colors[1]];
} else if (colors.length === 3) {
colorsArray = [colors[0], colors[0], colors[1], colors[1], colors[2], colors[2]];
}
return {
u_colors: {
value: colorsArray.map((color) => [
(color?.[0] ?? 0) / 255,
(color?.[1] ?? 0) / 255,
(color?.[2] ?? 0) / 255,
]),
type: 'uniform3fv',
},
u_opacities: {
value: opacities,
type: 'uniform1fv',
},
u_total_size: {
value: totalSize,
type: 'uniform1f',
},
u_dot_size: {
value: dotSize,
type: 'uniform1f',
},
u_reverse: {
value: shader.includes('u_reverse_active') ? 1 : 0, // Convert boolean to number (1 or 0)
type: 'uniform1i', // Use 1i for bool in WebGL1/GLSL100, or just bool for GLSL300+ if supported
},
};
}, [colors, opacities, totalSize, dotSize, shader]); // Add shader to dependencies
return (
);
};
const ShaderMaterial = ({
source,
uniforms,
}: {
source: string;
hovered?: boolean;
maxFps?: number;
uniforms: Uniforms;
}) => {
const { size } = useThree();
const ref = useRef(null);
useFrame(({ clock }) => {
if (!ref.current) return;
const timestamp = clock.getElapsedTime();
const material: any = ref.current.material;
const timeLocation = material.uniforms.u_time;
timeLocation.value = timestamp;
});
const getUniforms = () => {
const preparedUniforms: any = {};
for (const uniformName in uniforms) {
const uniform: any = uniforms[uniformName];
switch (uniform.type) {
case 'uniform1f':
preparedUniforms[uniformName] = { value: uniform.value, type: '1f' };
break;
case 'uniform1i':
preparedUniforms[uniformName] = { value: uniform.value, type: '1i' };
break;
case 'uniform3f':
preparedUniforms[uniformName] = {
value: new THREE.Vector3().fromArray(uniform.value),
type: '3f',
};
break;
case 'uniform1fv':
preparedUniforms[uniformName] = { value: uniform.value, type: '1fv' };
break;
case 'uniform3fv':
preparedUniforms[uniformName] = {
value: uniform.value.map((v: number[]) => new THREE.Vector3().fromArray(v)),
type: '3fv',
};
break;
case 'uniform2f':
preparedUniforms[uniformName] = {
value: new THREE.Vector2().fromArray(uniform.value),
type: '2f',
};
break;
default:
console.error(`Invalid uniform type for '${uniformName}'.`);
break;
}
}
preparedUniforms.u_time = { value: 0, type: '1f' };
preparedUniforms.u_resolution = {
value: new THREE.Vector2(size.width * 2, size.height * 2),
}; // Initialize u_resolution
return preparedUniforms;
};
// Shader material
const material = useMemo(() => {
const materialObject = new THREE.ShaderMaterial({
vertexShader: `
precision mediump float;
in vec2 coordinates;
uniform vec2 u_resolution;
out vec2 fragCoord;
void main(){
float x = position.x;
float y = position.y;
gl_Position = vec4(x, y, 0.0, 1.0);
fragCoord = (position.xy + vec2(1.0)) * 0.5 * u_resolution;
fragCoord.y = u_resolution.y - fragCoord.y;
}
`,
fragmentShader: source,
uniforms: getUniforms(),
glslVersion: THREE.GLSL3,
blending: THREE.CustomBlending,
blendSrc: THREE.SrcAlphaFactor,
blendDst: THREE.OneFactor,
});
return materialObject;
}, [size.width, size.height, source]);
return (
);
};
const Shader: React.FC = ({ source, uniforms, maxFps = 60 }) => {
return (
);
};
const AnimatedNavLink = ({ href, children }: { href: string; children: React.ReactNode }) => {
const defaultTextColor = 'text-gray-300';
const hoverTextColor = 'text-white';
const textSizeClass = 'text-sm';
return (
{children}
{children}
);
};
function MiniNavbar() {
const [isOpen, setIsOpen] = useState(false);
const [headerShapeClass, setHeaderShapeClass] = useState('rounded-full');
const shapeTimeoutRef = useRef(null);
const toggleMenu = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
if (shapeTimeoutRef.current) {
clearTimeout(shapeTimeoutRef.current);
}
if (isOpen) {
setHeaderShapeClass('rounded-xl');
} else {
shapeTimeoutRef.current = setTimeout(() => {
setHeaderShapeClass('rounded-full');
}, 300);
}
return () => {
if (shapeTimeoutRef.current) {
clearTimeout(shapeTimeoutRef.current);
}
};
}, [isOpen]);
const logoElement = (
);
const navLinksData = [
{ label: 'Manifesto', href: '#1' },
{ label: 'Careers', href: '#2' },
{ label: 'Discover', href: '#3' },
];
const loginButtonElement = (
LogIn
);
const signupButtonElement = (
);
return (
);
}
export const SignInPage = ({ className }: SignInPageProps) => {
const [email, setEmail] = useState('');
const [step, setStep] = useState<'email' | 'code' | 'success'>('email');
const [code, setCode] = useState(['', '', '', '', '', '']);
const codeInputRefs = useRef<(HTMLInputElement | null)[]>([]);
const [initialCanvasVisible, setInitialCanvasVisible] = useState(true);
const [reverseCanvasVisible, setReverseCanvasVisible] = useState(false);
const handleEmailSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (email) {
setStep('code');
}
};
// Focus first input when code screen appears
useEffect(() => {
if (step === 'code') {
setTimeout(() => {
codeInputRefs.current[0]?.focus();
}, 500);
}
}, [step]);
const handleCodeChange = (index: number, value: string) => {
if (value.length <= 1) {
const newCode = [...code];
newCode[index] = value;
setCode(newCode);
// Focus next input if value is entered
if (value && index < 5) {
codeInputRefs.current[index + 1]?.focus();
}
// Check if code is complete
if (index === 5 && value) {
const isComplete = newCode.every((digit) => digit.length === 1);
if (isComplete) {
// First show the new reverse canvas
setReverseCanvasVisible(true);
// Then hide the original canvas after a small delay
setTimeout(() => {
setInitialCanvasVisible(false);
}, 50);
// Transition to success screen after animation
setTimeout(() => {
setStep('success');
}, 2000);
}
}
}
};
const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
if (e.key === 'Backspace' && !code[index] && index > 0) {
codeInputRefs.current[index - 1]?.focus();
}
};
const handleBackClick = () => {
setStep('email');
setCode(['', '', '', '', '', '']);
// Reset animations if going back
setReverseCanvasVisible(false);
setInitialCanvasVisible(true);
};
return (
{/* Initial canvas (forward animation) */}
{initialCanvasVisible && (
)}
{/* Reverse canvas (appears when code is complete) */}
{reverseCanvasVisible && (
)}
{/* Content Layer */}
{/* Top navigation */}
{/* Main content container */}
{/* Left side (form) */}
{step === 'email' ? (
Welcome Developer
Your sign in component
By signing up, you agree to the{' '}
MSA
,{' '}
Product Terms
,{' '}
Policies
,{' '}
Privacy Notice
, and{' '}
Cookie Notice
.
) : step === 'code' ? (
We sent you a code
Please enter it
{code.map((digit, i) => (
{
codeInputRefs.current[i] = el;
}}
type="text"
inputMode="numeric"
pattern="[0-9]*"
maxLength={1}
value={digit}
onChange={(e) => handleCodeChange(i, e.target.value)}
onKeyDown={(e) => handleKeyDown(i, e)}
className="w-8 text-center text-xl bg-transparent text-white border-none focus:outline-none focus:ring-0 appearance-none"
style={{ caretColor: 'transparent' }}
/>
{!digit && (
0
)}
{i < 5 &&
| }
))}
Resend code
Back
d !== '')
? 'bg-white text-black border-transparent hover:bg-white/90 cursor-pointer'
: 'bg-[#111] text-white/50 border-white/10 cursor-not-allowed'
}`}
disabled={!code.every((d) => d !== '')}
>
Continue
By signing up, you agree to the{' '}
MSA
,{' '}
Product Terms
,{' '}
Policies
,{' '}
Privacy Notice
, and{' '}
Cookie Notice
.
) : (
Continue to Dashboard
)}
);
};