// ============ PROJECTS GRID + MODAL ============ const { useState: useStateP } = React; function ProjectCard({ p, onOpen }) { const ref = React.useRef(null); const onMove = (e) => { const el = ref.current; if (!el) return; const r = el.getBoundingClientRect(); const px = (e.clientX - r.left) / r.width - 0.5; const py = (e.clientY - r.top) / r.height - 0.5; el.style.transform = `perspective(900px) rotateX(${-py * 4}deg) rotateY(${px * 6}deg) translateZ(0)`; }; const onLeave = () => { if (ref.current) ref.current.style.transform = ''; }; return (
onOpen(p)} role="button" tabIndex={0} onKeyDown={(e) => e.key === 'Enter' && onOpen(p)}>
{p.id}.angelo.dev
{p.img ? {p.name} : }

{p.name}

{p.type}

{p.desc}

{p.stack.map((s, i) => {s})}
); } function ProjectModal({ p, onClose }) { React.useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); document.body.style.overflow = 'hidden'; return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = ''; }; }, [onClose]); return (
e.stopPropagation()} style={{position:'relative'}}> {p.type}

{p.name}

{p.desc}

{p.metrics.map((m, i) => (
{m.k}
{m.v}
))}
{p.img &&
{p.name}
}
STACK
{p.stack.map((s, i) => {s})}
{p.link ? ( {p.linkLabel || 'Visitar'} ) : null}
); } function Projects() { const { t } = window.useLang(); const ref = window.useReveal(); const [filter, setFilter] = useStateP('Todos'); const [open, setOpen] = useStateP(null); const map = { 'Todos': null, 'All': null, 'IA': 'IA', 'AI': 'IA', 'Robótica': 'Robótica', 'Robotics': 'Robótica', 'Web': 'Web', 'Académico': 'Académico', 'Academic': 'Académico' }; const list = window.PROJECTS.filter(p => { const m = map[filter]; return !m || p.cats.includes(m); }); return (
{t.projects.kicker}

{t.projects.h2}

{t.projects.filters.map(f => ( ))}
{list.map(p => (
))}
{open && setOpen(null)} />}
); } window.Projects = Projects;