/* =========================================================== 1 A 1 — App: idioma, Tweaks (acento re-tematiza), reveal =========================================================== */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#4a8fac", "motion": true, "cursor": true, "startLang": "es" }/*EDITMODE-END*/; /* Cursor moderno — punto central instantáneo + anillo externo con retraso físico (lerp) */ function CustomCursor() { const dotRef = React.useRef(null); const ringRef = React.useRef(null); React.useEffect(() => { const mq = window.matchMedia("(hover: hover) and (pointer: fine)"); if (!mq.matches) return; const root = document.documentElement; root.classList.add("cursor-custom-active"); const dot = dotRef.current; const ring = ringRef.current; const mouse = { x: -100, y: -100 }; const dotPos = { x: -100, y: -100 }; const ringPos = { x: -100, y: -100 }; let raf; const loop = () => { // Easing rápido para el punto central (mantiene precisión) dotPos.x += (mouse.x - dotPos.x) * 0.4; dotPos.y += (mouse.y - dotPos.y) * 0.4; // Easing más lento para el anillo (efecto fluidez física) ringPos.x += (mouse.x - ringPos.x) * 0.16; ringPos.y += (mouse.y - ringPos.y) * 0.16; if (dot) { dot.style.transform = `translate(${dotPos.x}px, ${dotPos.y}px)`; } if (ring) { ring.style.transform = `translate(${ringPos.x}px, ${ringPos.y}px)`; } raf = requestAnimationFrame(loop); }; const onMove = (e) => { mouse.x = e.clientX; mouse.y = e.clientY; dot.classList.add("show"); ring.classList.add("show"); const hot = e.target.closest && e.target.closest("a,button,input,textarea,select,label,.gw-piece,.gfilter,[role=tab],image-slot,.contact-channel,.lang button"); if (hot) { dot.classList.add("hot"); ring.classList.add("hot"); } else { dot.classList.remove("hot"); ring.classList.remove("hot"); } }; const onDown = () => { dot.classList.add("down"); ring.classList.add("down"); }; const onUp = () => { dot.classList.remove("down"); ring.classList.remove("down"); }; const onOut = (e) => { if (!e.relatedTarget) { dot.classList.remove("show"); ring.classList.remove("show"); } }; window.addEventListener("mousemove", onMove, { passive: true }); window.addEventListener("mousedown", onDown); window.addEventListener("mouseup", onUp); window.addEventListener("mouseout", onOut); raf = requestAnimationFrame(loop); return () => { cancelAnimationFrame(raf); root.classList.remove("cursor-custom-active"); window.removeEventListener("mousemove", onMove); window.removeEventListener("mousedown", onDown); window.removeEventListener("mouseup", onUp); window.removeEventListener("mouseout", onOut); }; }, []); return ( ); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [lang, setLang] = React.useState(() => localStorage.getItem("1a1-lang") || TWEAK_DEFAULTS.startLang); const touched = React.useRef(false); React.useEffect(() => { localStorage.setItem("1a1-lang", lang); document.documentElement.lang = lang; }, [lang]); const onSetLang = (l) => { touched.current = true; setLang(l); }; React.useEffect(() => { if (!touched.current) setLang(t.startLang); }, [t.startLang]); // ---- Router: rutas reales por pestaña (/trabajo, /industrias, /herramientas-gratuitas) ---- const normalizePath = (p) => (p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p); const [route, setRoute] = React.useState(() => normalizePath(window.location.pathname)); React.useEffect(() => { const scrollToHashOrTop = (hash) => { requestAnimationFrame(() => requestAnimationFrame(() => { if (hash) { const el = document.querySelector(hash); if (el) { el.scrollIntoView(); return; } } window.scrollTo(0, 0); })); }; const onPop = () => { setRoute(normalizePath(window.location.pathname)); scrollToHashOrTop(window.location.hash); }; const onClick = (e) => { if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; const a = e.target.closest && e.target.closest("a"); if (!a) return; const raw = a.getAttribute("href"); if (!raw || a.target === "_blank" || a.hasAttribute("download")) return; let url; try { url = new URL(a.href, window.location.origin); } catch (_) { return; } if (url.origin !== window.location.origin) return; // enlace externo const cur = normalizePath(window.location.pathname); const dest = normalizePath(url.pathname); if (dest === cur) { if (url.hash) return; // misma página con ancla → scroll nativo e.preventDefault(); // misma página sin ancla (logo) → subir window.scrollTo({ top: 0 }); return; } e.preventDefault(); // cambio de pestaña sin recargar window.history.pushState({}, "", url.pathname + url.hash); setRoute(dest); scrollToHashOrTop(url.hash); }; window.addEventListener("popstate", onPop); document.addEventListener("click", onClick); return () => { window.removeEventListener("popstate", onPop); document.removeEventListener("click", onClick); }; }, []); // Acento = color principal de toda la página React.useEffect(() => { document.documentElement.style.setProperty("--accent", t.accent); }, [t.accent]); // Movimiento (líneas + ticker) React.useEffect(() => { document.documentElement.classList.toggle("motion-off", !t.motion); }, [t.motion]); const copy = window.SITE[lang]; // Reveal on scroll — estado base visible; se oculta para animar sólo con JS. React.useEffect(() => { let scheduled = false; const check = () => { scheduled = false; const vh = window.innerHeight || document.documentElement.clientHeight; document.querySelectorAll("[data-reveal]:not(.in), .reveal-title:not(.in)").forEach((el) => { if (el.getBoundingClientRect().top < vh * 0.92) el.classList.add("in"); }); }; const onScroll = () => { if (scheduled) return; scheduled = true; (window.requestAnimationFrame || setTimeout)(check); }; check(); document.documentElement.classList.add("reveal-on"); check(); const timers = [60, 200, 500, 1000].map((ms) => setTimeout(check, ms)); window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", onScroll, { passive: true }); return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); timers.forEach(clearTimeout); }; }, [lang, route]); return (