/* global React, LR_IMAGES, gsap, ScrollTrigger */ function PortfolioWall() { const t = window.useT(); const projectDefs = [ { key: 'aurora', folder: '01-aurora', title: t('portfolio.projects.aurora.title'), loc: t('portfolio.projects.aurora.location'), tipo: t('portfolio.projects.aurora.type'), cliente: t('portfolio.projects.aurora.client'), tier: t('portfolio.tier.essential'), servicios: [ t('portfolio.projects.aurora.services.0'), t('portfolio.projects.aurora.services.1'), t('portfolio.projects.aurora.services.2'), ], rol: t('portfolio.projects.aurora.role'), resultados: [ { value: '80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.aurora.description'), }, { key: 'verania', folder: '02-verania', title: t('portfolio.projects.verania.title'), loc: t('portfolio.projects.verania.location'), tipo: t('portfolio.projects.verania.type'), cliente: t('portfolio.projects.verania.client'), tier: t('portfolio.tier.premium'), servicios: [ t('portfolio.projects.verania.services.0'), t('portfolio.projects.verania.services.1'), t('portfolio.projects.verania.services.2'), t('portfolio.projects.verania.services.3'), ], rol: t('portfolio.projects.verania.role'), resultados: [ { value: '60-80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1-5', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.verania.description'), }, { key: 'estepona', folder: '03-torre-levante', title: t('portfolio.projects.estepona.title'), loc: t('portfolio.projects.estepona.location'), tipo: t('portfolio.projects.estepona.type'), cliente: t('portfolio.projects.estepona.client'), tier: t('portfolio.tier.commercial'), servicios: [ t('portfolio.projects.estepona.services.0'), t('portfolio.projects.estepona.services.1'), t('portfolio.projects.estepona.services.2'), ], rol: t('portfolio.projects.estepona.role'), resultados: [ { value: '60-80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1-5', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.estepona.description'), }, { key: 'lumen', folder: '04-lumen', title: t('portfolio.projects.lumen.title'), loc: t('portfolio.projects.lumen.location'), tipo: t('portfolio.projects.lumen.type'), cliente: t('portfolio.projects.lumen.client'), tier: t('portfolio.tier.essential'), servicios: [ t('portfolio.projects.lumen.services.0'), t('portfolio.projects.lumen.services.1'), t('portfolio.projects.lumen.services.2'), ], rol: t('portfolio.projects.lumen.role'), resultados: [ { value: '60-80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.lumen.description'), }, { key: 'mirador', folder: '05-mirador', title: t('portfolio.projects.mirador.title'), loc: t('portfolio.projects.mirador.location'), tipo: t('portfolio.projects.mirador.type'), cliente: t('portfolio.projects.mirador.client'), tier: t('portfolio.tier.commercial'), servicios: [ t('portfolio.projects.mirador.services.0'), t('portfolio.projects.mirador.services.1'), t('portfolio.projects.mirador.services.2'), ], rol: t('portfolio.projects.mirador.role'), resultados: [ { value: '60-80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1-5', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.mirador.description'), }, { key: 'atelier', folder: '06-atelier', title: t('portfolio.projects.atelier.title'), loc: t('portfolio.projects.atelier.location'), tipo: t('portfolio.projects.atelier.type'), cliente: t('portfolio.projects.atelier.client'), tier: t('portfolio.tier.essential'), servicios: [ t('portfolio.projects.atelier.services.0'), t('portfolio.projects.atelier.services.1'), t('portfolio.projects.atelier.services.2'), ], rol: t('portfolio.projects.atelier.role'), resultados: [ { value: '60-80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.atelier.description'), }, { key: 'altea', folder: '07-altea', title: t('portfolio.projects.altea.title'), loc: t('portfolio.projects.altea.location'), tipo: t('portfolio.projects.altea.type'), cliente: t('portfolio.projects.altea.client'), tier: t('portfolio.tier.commercial'), servicios: [ t('portfolio.projects.altea.services.0'), t('portfolio.projects.altea.services.1'), t('portfolio.projects.altea.services.2'), t('portfolio.projects.altea.services.3'), ], rol: t('portfolio.projects.altea.role'), resultados: [ { value: '60-80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1-5', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.altea.description'), }, { key: 'palms', folder: '08-palms', title: t('portfolio.projects.palms.title'), loc: t('portfolio.projects.palms.location'), tipo: t('portfolio.projects.palms.type'), cliente: t('portfolio.projects.palms.client'), tier: t('portfolio.tier.premium'), servicios: [ t('portfolio.projects.palms.services.0'), t('portfolio.projects.palms.services.1'), t('portfolio.projects.palms.services.2'), t('portfolio.projects.palms.services.3'), t('portfolio.projects.palms.services.4'), ], rol: t('portfolio.projects.palms.role'), resultados: [ { value: '60-80%', label: t('portfolio.results.0.label') }, { value: '120-160+', label: t('portfolio.results.1.label') }, { value: '18-24', label: t('portfolio.results.2.label') }, { value: '1-5', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.palms.description'), }, { key: 'nuevo', folder: '09-nuevo-proyecto', title: t('portfolio.projects.nuevo.title'), loc: t('portfolio.projects.nuevo.location'), tipo: t('portfolio.projects.nuevo.type'), cliente: t('portfolio.projects.nuevo.client'), tier: t('portfolio.tier.essential'), servicios: [ t('portfolio.projects.nuevo.services.0'), t('portfolio.projects.nuevo.services.1'), t('portfolio.projects.nuevo.services.2'), ], rol: t('portfolio.projects.nuevo.role'), resultados: [ { value: '—', label: t('portfolio.results.0.label') }, { value: '—', label: t('portfolio.results.1.label') }, { value: '—', label: t('portfolio.results.2.label') }, { value: '—', label: t('portfolio.results.3.label') }, ], desc: t('portfolio.projects.nuevo.description'), }, ]; const folderOrder = (file) => { const name = file.split('/').pop() || ''; const match = name.match(/^(\d+)/); return match ? Number(match[1]) : Number.MAX_SAFE_INTEGER; }; const galleryForFolder = (folder) => ( LR_IMAGES.all .filter((img) => img.f.startsWith(`${folder}/`)) .sort((a, b) => { const byNum = folderOrder(a.f) - folderOrder(b.f); return byNum !== 0 ? byNum : a.f.localeCompare(b.f); }) .map((img) => img.f) ); const projects = projectDefs .map((project) => ({ ...project, gallery: galleryForFolder(project.folder) })) .filter((project) => project.gallery.length > 0); const layout = [ { col: '1 / 7', row: '1 / 3', h: 480, idx: 0 }, { col: '7 / 13', row: '1 / 2', h: 230, idx: 1 }, { col: '7 / 10', row: '2 / 3', h: 230, idx: 2 }, { col: '10 / 13', row: '2 / 3', h: 230, idx: 3 }, { col: '1 / 5', row: '3 / 4', h: 280, idx: 4 }, { col: '5 / 9', row: '3 / 4', h: 280, idx: 5 }, { col: '9 / 13', row: '3 / 4', h: 280, idx: 6 }, ...(projects.length >= 9 ? [ { col: '1 / 9', row: '4 / 5', h: 360, idx: 7 }, { col: '9 / 13', row: '4 / 5', h: 360, idx: 8 }, ] : projects.length >= 8 ? [{ col: '1 / 13', row: '4 / 5', h: 360, idx: 7 }] : []), ]; const wallRef = React.useRef(null); const counterRef = window.useCounter(200, { prefix: '+', duration: 2.4 }); const [openIdx, setOpenIdx] = React.useState(null); const [slideIdx, setSlideIdx] = React.useState(0); const [imgRatio, setImgRatio] = React.useState(null); const [infoExpanded, setInfoExpanded] = React.useState(false); React.useEffect(() => { // No scroll animations — hover-only effects are handled inline. }, []); // ESC closes, arrows navigate React.useEffect(() => { if (openIdx === null) return; const onKey = (e) => { if (e.key === 'Escape') setOpenIdx(null); if (e.key === 'ArrowRight') { const g = projects[openIdx].gallery; setSlideIdx((i) => (i + 1) % g.length); } if (e.key === 'ArrowLeft') { const g = projects[openIdx].gallery; setSlideIdx((i) => (i - 1 + g.length) % g.length); } }; document.body.style.overflow = 'hidden'; window.addEventListener('keydown', onKey); return () => { document.body.style.overflow = ''; window.removeEventListener('keydown', onKey); }; }, [openIdx]); const openProject = (projectIdx) => { setOpenIdx(projectIdx); setSlideIdx(0); setInfoExpanded(false); }; const current = openIdx !== null ? projects[openIdx] : null; const currentImgFile = current ? current.gallery[slideIdx] : null; const currentImg = currentImgFile ? LR_IMAGES.pick(currentImgFile) : null; return (
{t('portfolio.header.label')}

{t('portfolio.header.title').replace(t('portfolio.header.subtitle'), '')} {t('portfolio.header.subtitle')}

+0{t('portfolio.header.stats').replace(/^\+0/, '')}
{t('portfolio.header.note')}
{layout.map((cell, i) => { const p = projects[cell.idx]; const firstImg = LR_IMAGES.pick(p.gallery[0]); return ( { e.preventDefault(); openProject(cell.idx); }} style={{ gridColumn: cell.col, gridRow: cell.row, height: cell.h, position: 'relative', overflow: 'hidden', borderRadius: 2, borderBottom: 'none', background: '#000', display: 'block', cursor: 'pointer', outline: 'none', }} onMouseEnter={(e) => { const zoom = e.currentTarget.querySelector('.bg-zoom'); if (zoom) zoom.style.transform = 'scale(1.04)'; const ov = e.currentTarget.querySelector('.ov'); if (ov) ov.style.opacity = '1'; }} onMouseLeave={(e) => { const zoom = e.currentTarget.querySelector('.bg-zoom'); if (zoom) zoom.style.transform = 'scale(1)'; const ov = e.currentTarget.querySelector('.ov'); if (ov) ov.style.opacity = '0'; }}>

{p.title}

{p.loc}
); })}
{/* Modal lightbox */} {current && (
setOpenIdx(null)} style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(27,20,16,0.92)', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'stretch', justifyContent: 'center', animation: 'fadein 280ms var(--ease-standard)', }}> {/* Floating close button — fuera del modal animado para no saltar */}
e.stopPropagation()} className="pf-modal pf-modal-outer" style={{ width: '100%', maxWidth: 1800, background: 'var(--lr-cal-blanca)', margin: '32px', overflow: 'hidden', maxHeight: 'calc(100vh - 64px)', overflowY: 'auto', animation: 'modalin 420ms var(--ease-standard)', position: 'relative', scrollBehavior: 'smooth', }}> {/* Editorial body: sticky info col + scrolling gallery col */}
{/* Sticky info column */} {/* Scrolling editorial gallery column */}
{current.gallery.map((file, gi) => { const img = LR_IMAGES.pick(file); const filename = file.split('/').pop(); const nameMatch = filename.match(/^\d+\.(.*)\.[^.]+$/); const caption = nameMatch ? nameMatch[1] : filename; return (
{`${current.title}
{String(gi + 1).padStart(2, '0')} / {String(current.gallery.length).padStart(2, '0')} · {caption} {current.loc}
); })} {/* Closing block */}
{t('portfolio.modal.closing.label')}

{t('portfolio.modal.closing.text')} {' '}{t('portfolio.modal.closing.cta')}

)}
); } window.PortfolioWall = PortfolioWall;