Import source: src/components/Hero
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { ArrowDown } from 'lucide-react';
|
||||
|
||||
export default function Hero() {
|
||||
const t = useTranslations('Hero');
|
||||
const locale = useLocale();
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
let animationFrameId: number;
|
||||
let particles: Array<{
|
||||
x: number;
|
||||
y: number;
|
||||
radius: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
opacity: number;
|
||||
}> = [];
|
||||
|
||||
const resizeCanvas = () => {
|
||||
canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
initParticles();
|
||||
};
|
||||
|
||||
const initParticles = () => {
|
||||
particles = [];
|
||||
const density = Math.floor((canvas.width * canvas.height) / 18000);
|
||||
const count = Math.min(Math.max(density, 15), 50); // Keep it light and subtle
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
particles.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
radius: Math.random() * 1.5 + 0.5,
|
||||
vx: (Math.random() - 0.5) * 0.15,
|
||||
vy: (Math.random() - 0.5) * 0.15,
|
||||
opacity: Math.random() * 0.35 + 0.1,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
|
||||
// Select subtle colors based on theme: sand or teal
|
||||
const particleColor = isDark ? '15, 107, 115' : '217, 93, 57'; // Teal for dark theme, terracotta for light theme
|
||||
|
||||
particles.forEach((p) => {
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(${particleColor}, ${p.opacity})`;
|
||||
ctx.fill();
|
||||
|
||||
// Update positions
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
|
||||
// Bounce off bounds
|
||||
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
|
||||
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
|
||||
});
|
||||
|
||||
animationFrameId = requestAnimationFrame(draw);
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
animationFrameId = requestAnimationFrame(draw);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
window.removeEventListener('resize', resizeCanvas);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { y: 20, opacity: 0 },
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1] as const },
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative w-full h-[calc(100vh-4rem)] min-h-[500px] flex items-center justify-center overflow-hidden bg-warm-white dark:bg-near-black transition-colors duration-300">
|
||||
{/* Subtle particle canvas */}
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="absolute inset-0 w-full h-full pointer-events-none opacity-60"
|
||||
/>
|
||||
|
||||
{/* Decorative gradient overlay */}
|
||||
<div className="absolute inset-0 bg-radial-gradient from-transparent via-warm-white/10 to-warm-white dark:to-near-black pointer-events-none" />
|
||||
|
||||
<div className="max-w-[1200px] mx-auto px-6 w-full relative z-10 flex flex-col items-center text-center">
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-6"
|
||||
>
|
||||
{/* Main Typographic Treatment */}
|
||||
<motion.h1
|
||||
variants={itemVariants}
|
||||
className={`text-5xl md:text-8xl font-black tracking-tight text-charcoal dark:text-warm-white ${locale === 'fa' ? 'font-farsi font-black' : 'font-sans'}`}
|
||||
>
|
||||
{locale === 'fa' ? 'آرسام صباغ' : 'Arsam Sabbagh'}
|
||||
</motion.h1>
|
||||
|
||||
{/* Editorial Tagline */}
|
||||
<motion.p
|
||||
variants={itemVariants}
|
||||
className={`text-lg md:text-2xl text-teal-brand dark:text-sand font-medium uppercase tracking-widest ${locale === 'fa' ? 'font-farsi font-normal' : 'font-sans'}`}
|
||||
>
|
||||
{t('tagline')}
|
||||
</motion.p>
|
||||
|
||||
{/* Action CTAs */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="flex flex-wrap items-center justify-center gap-4 pt-4"
|
||||
>
|
||||
<a
|
||||
href="#projects"
|
||||
className="px-6 py-3 rounded-full bg-teal-brand hover:bg-teal-brand/90 text-warm-white text-sm font-semibold transition-all hover:scale-102 hover:shadow-md cursor-pointer"
|
||||
>
|
||||
{t('ctaWork')}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="#writing"
|
||||
className="px-6 py-3 rounded-full border border-sand dark:border-charcoal hover:border-teal-brand hover:text-teal-brand text-charcoal dark:text-warm-white text-sm font-semibold transition-all hover:scale-102 cursor-pointer"
|
||||
>
|
||||
{t('ctaWriting')}
|
||||
</a>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1, duration: 0.8 }}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 cursor-pointer opacity-70 hover:opacity-100 transition-opacity"
|
||||
onClick={() => {
|
||||
document.getElementById('about')?.scrollIntoView({ behavior: 'smooth' });
|
||||
}}
|
||||
>
|
||||
<span className={`text-xs uppercase tracking-widest ${locale === 'fa' ? 'font-farsi' : 'font-sans'}`}>
|
||||
{t('scroll')}
|
||||
</span>
|
||||
<motion.div
|
||||
animate={{ y: [0, 6, 0] }}
|
||||
transition={{ repeat: Infinity, duration: 1.5 }}
|
||||
>
|
||||
<ArrowDown className="w-4 h-4 text-teal-brand" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user