// ============ 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.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) => (
))}
{p.img &&
}
STACK
{p.stack.map((s, i) => {s})}
);
}
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 => (
))}
{open && setOpen(null)} />}
);
}
window.Projects = Projects;