/* ============================================================
SECTIONS 2 — Sobre mí, Proyectos (grid filtrable), Lightbox
============================================================ */
/* ---------- Sobre mí ---------- */
function About({ refMap }) {
return (
{ if (refMap) refMap.current['sobre'] = el; }} style={{ background: 'var(--color-surface)', padding: 'clamp(64px, 9vh, 112px) 0', borderTop: '1px solid var(--color-border)', borderBottom: '1px solid var(--color-border)' }}>
Sobre mí
«Llevo el proyecto del concepto a la entrega: diseño, presupuesto, materiales y obra, con la misma exigencia en cada fase.»
{ABOUT.map((p, i) => (
{p}
))}
Habilidades clave
{SKILLS.map((s) => {s})}
Herramientas
{TOOLS.map((s) => {s})}
);
}
/* ---------- Proyectos ---------- */
function Projects({ refMap, onOpen }) {
const [filter, setFilter] = React.useState('Todos');
const shown = PROJECTS.filter((p) => filter === 'Todos' || p.cat === filter);
return (
{ if (refMap) refMap.current['proyectos'] = el; }} style={{ background: 'var(--color-bg)', padding: 'clamp(64px, 9vh, 112px) 0' }}>
{FILTERS.map((f) => setFilter(f)} count={f === 'Todos' ? PROJECTS.length : PROJECTS.filter((p) => p.cat === f).length} />)}
{shown.map((p, i) => (
))}
);
}
function FilterChip({ label, active, onClick, count }) {
const [h, setH] = React.useState(false);
return (
);
}
function PortfolioTile({ p, i, onOpen }) {
const [h, setH] = React.useState(false);
return (
{ e.preventDefault(); onOpen(p); }} onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
style={{ display: 'block', position: 'relative', height: '100%', minHeight: 280, textDecoration: 'none', borderRadius: 'var(--radius-lg)', overflow: 'hidden',
border: '1px solid var(--color-border)', boxShadow: h ? 'var(--shadow-md)' : 'var(--shadow-xs)',
transform: h ? 'translateY(-3px)' : 'none', transition: 'box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out)' }}>
{p.cat}
{p.images && p.images.length > 1 ? (
{p.images.length}
) : null}
);
}
/* ---------- Lightbox (con galería) ---------- */
function Lightbox({ project, onClose }) {
const gallery = project ? (project.images && project.images.length ? project.images : [project.image]) : [];
const [idx, setIdx] = React.useState(0);
const multi = gallery.length > 1;
React.useEffect(() => { setIdx(0); }, [project]);
React.useEffect(() => {
if (!project) return;
const fn = (e) => {
if (e.key === 'Escape') onClose();
else if (e.key === 'ArrowRight' && multi) setIdx((i) => (i + 1) % gallery.length);
else if (e.key === 'ArrowLeft' && multi) setIdx((i) => (i - 1 + gallery.length) % gallery.length);
};
window.addEventListener('keydown', fn);
document.body.style.overflow = 'hidden';
return () => { window.removeEventListener('keydown', fn); document.body.style.overflow = ''; };
}, [project, multi, gallery.length]);
if (!project) return null;
const go = (d) => setIdx((i) => (i + d + gallery.length) % gallery.length);
return (
e.stopPropagation()} className="lb-card" style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.3fr) minmax(300px, 0.86fr)', gridTemplateRows: 'minmax(0, 1fr)', background: 'var(--color-surface)', borderRadius: 'var(--radius-xl)', overflow: 'hidden', maxWidth: 1040, width: '100%', maxHeight: '90vh', boxShadow: 'var(--shadow-lg)' }}>
{project.cat}
{multi ? (
{idx + 1} / {gallery.length}
) : null}
{multi ? (
{gallery.map((src, i) => (
))}
) : null}
{project.place}
{project.title}
{project.blurb}
{multi ?
Usa las flechas o las miniaturas para recorrer {gallery.length} imágenes.
: null}
);
}
function lbArrow(side) {
return { position: 'absolute', top: '50%', [side]: 14, transform: 'translateY(-50%)', width: 46, height: 46, borderRadius: 'var(--radius-full)', border: 'none', background: 'var(--white)', color: 'var(--color-primary)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 4px 16px rgba(10,21,29,0.35)', zIndex: 3 };
}
Object.assign(window, { About, Projects, Lightbox });