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:
arsam
2026-09-18 23:05:52 +03:30
parent 211b543414
commit e2692431a5
+186
View File
@@ -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>
);
}