/* global React, ReactDOM, HomePage, PortfolioPage, AboutPage, ContactPage, Nav, Footer, WhatsAppFloat, FullMenu, TweaksPanel, TWEAK_DEFAULTS, COPY, applyPalette, PAOLA_DATA */ const { useState, useEffect, useCallback } = React; function normId(s) { return s.toLowerCase() .normalize('NFD').replace(/[̀-ͯ]/g, '') .replace(/[^a-z0-9]/g, '') .replace(/(.)\1+/g, '$1'); // colapsa letras dobles: infanntil → infantil } function folderToName(folder) { return folder .replace(/[-_]/g, ' ') .replace(/\b\w/g, c => c.toUpperCase()); } function parseHash() { const h = window.location.hash.replace('#', '') || 'home'; const [route, sub] = h.split('/'); return { route: route || 'home', sub }; } function App() { const [{ route, sub }, setRoute] = useState(parseHash()); const [menuOpen, setMenuOpen] = useState(false); const [dynGallery, setDynGallery] = useState(null); const [dynCats, setDynCats] = useState([]); const [heroCopyOverride, setHeroCopyOverride] = useState(null); const [contentReady, setContentReady] = useState(false); const [tweaks, setTweaks] = useState(() => { try { const saved = localStorage.getItem('paola-tweaks'); return saved ? { ...TWEAK_DEFAULTS, ...JSON.parse(saved) } : TWEAK_DEFAULTS; } catch { return TWEAK_DEFAULTS; } }); useEffect(() => { const onHash = () => setRoute(parseHash()); window.addEventListener('hashchange', onHash); return () => window.removeEventListener('hashchange', onHash); }, []); // Actualiza el título de la pestaña según la ruta useEffect(() => { const titles = { home: 'Paola García · Fotógrafa Materna e Infantil', portfolio: 'Portafolio · Paola García', about: 'Sobre mí · Paola García', contact: 'Contacto · Paola García', }; document.title = titles[route] || titles.home; }, [route]); useEffect(() => { applyPalette(tweaks.palette); }, [tweaks.palette]); useEffect(() => { try { localStorage.setItem('paola-tweaks', JSON.stringify(tweaks)); } catch {} }, [tweaks]); const setTweak = useCallback((k, v) => { setTweaks(t => { const next = typeof k === 'object' ? { ...t, ...k } : { ...t, [k]: v }; try { window.parent.postMessage({ type: '__edit_mode_set_keys', edits: next }, '*'); } catch {} return next; }); }, []); const go = useCallback((r, s) => { const hash = s ? `${r}/${s}` : r; window.location.hash = hash; window.scrollTo({ top: 0, behavior: 'instant' }); }, []); // Carga contenido editable desde content.json vía PHP useEffect(() => { fetch('/api/content.php') .then(r => r.json()) .then(data => { // Hero copy (frases del banner) if (data.hero?.copy?.line1) setHeroCopyOverride(data.hero.copy); // Hero slides: an empty array from the admin means no banner images. if (Array.isArray(data.hero?.slides)) { const slides = data.hero.slides.filter(Boolean); PAOLA_DATA.hero.slides = slides; PAOLA_DATA.hero.main = slides[0] || ''; PAOLA_DATA.hero.alt = slides[1] || slides[0] || ''; } // Sobre mí — fotos de Paola if (data.about?.portrait) PAOLA_DATA.about.portrait = data.about.portrait; if (data.about?.landscape) PAOLA_DATA.about.landscape = data.about.landscape; if (data.about?.bio) PAOLA_DATA.about.bio = data.about.bio; // Bloque "Experiencia" del home if (data.experience?.length) PAOLA_DATA.experience = data.experience; // Contacto if (data.contact) Object.assign(PAOLA_DATA.contact, data.contact); // Testimonios if (data.testimonials?.length) PAOLA_DATA.testimonials = data.testimonials; // Paquetes — fusionar por id de categoría if (data.packages?.length) { data.packages.forEach(pkg => { const cat = PAOLA_DATA.categories.find(c => c.id === pkg.id); if (cat) cat.packages = pkg.groups; }); } setContentReady(true); }) .catch(() => setContentReady(true)); // si falla, usa data.js como fallback }, []); // Carga galería dinámica desde categorias/ vía PHP API useEffect(() => { fetch('/api/gallery.php') .then(r => r.json()) .then(({ categories }) => { if (!categories?.length) return; const newGallery = []; const newCats = []; const existingIds = PAOLA_DATA.categories.map(c => normId(c.id)); categories.forEach(({ folder, photos }) => { if (!photos.length) return; const folderNorm = normId(folder); const matchedCat = PAOLA_DATA.categories.find(c => normId(c.id) === folderNorm) || PAOLA_DATA.categories.find(c => folderNorm.includes(normId(c.id)) || normId(c.id).includes(folderNorm)); const catId = matchedCat ? matchedCat.id : folder.toLowerCase().replace(/[\s_]+/g, '-'); const catName = matchedCat ? matchedCat.name : folderToName(folder); photos.forEach((photo, i) => { const src = typeof photo === 'object' ? photo.src : photo; const alt = typeof photo === 'object' ? (photo.alt || '') : ''; const title = typeof photo === 'object' ? (photo.title || '') : ''; newGallery.push({ src, alt, title, cat: catId, label: `${catName} · ${i + 1}` }); }); const cover0 = typeof photos[0] === 'object' ? photos[0].src : photos[0]; if (!matchedCat && !existingIds.includes(folderNorm)) { newCats.push({ id: catId, idx: String(PAOLA_DATA.categories.length + newCats.length + 1).padStart(2, '0'), name: catName, desc: '', cover: cover0, packages: [], }); } else if (matchedCat) { matchedCat.cover = cover0; } }); if (newGallery.length) setDynGallery(newGallery); if (newCats.length) setDynCats(newCats); }) .catch(() => {}); }, []); // Boot fade useEffect(() => { const boot = document.getElementById('boot'); if (!boot) return; const t = setTimeout(() => boot.classList.add('gone'), 350); const t2 = setTimeout(() => boot.remove(), 1500); return () => { clearTimeout(t); clearTimeout(t2); }; }, []); const isDarkPage = route === 'home'; const heroCopy = heroCopyOverride || COPY[tweaks.language || 'es']?.[tweaks.heroVariant || 'amor'] || COPY.es.amor; // Galería vacía hasta que el API responda — nunca muestra placeholders const gallery = dynGallery ?? []; // Solo muestra filtros de categorías que tengan al menos 1 foto real const catsWithPhotos = new Set(gallery.map(g => g.cat)); const allCats = [...PAOLA_DATA.categories, ...dynCats] .filter(c => catsWithPhotos.has(c.id)); let Page; if (route === 'portfolio') Page = ; else if (route === 'about') Page = ; else if (route === 'contact') Page = ; else Page = ; return (
); } ReactDOM.createRoot(document.getElementById('root')).render();