// App root + cursor + interactions + tweaks const { useState: useSA, useEffect: useEA } = React; function App() { const [lang, setLang] = useSA(() => localStorage.getItem('lm-lang') || 'pt'); const [theme, setTheme] = useSA(() => { const stored = localStorage.getItem('lm-theme'); if (stored) return stored; return window.TWEAK_DEFAULTS?.theme || 'light'; }); const [heroVariant, setHeroVariant] = useSA(() => { return localStorage.getItem('lm-hero') || window.TWEAK_DEFAULTS?.heroVariant || 'mesh'; }); const [showTweaks, setShowTweaks] = useSA(false); const [scrolled, setScrolled] = useSA(false); const [activeSection, setActiveSection] = useSA('home'); const t = window.I18N[lang]; // persist useEA(() => { localStorage.setItem('lm-lang', lang); }, [lang]); useEA(() => { localStorage.setItem('lm-theme', theme); document.documentElement.setAttribute('data-theme', theme); }, [theme]); useEA(() => { localStorage.setItem('lm-hero', heroVariant); document.documentElement.setAttribute('data-hero', heroVariant); }, [heroVariant]); // Nav scroll state + active section useEA(() => { function onScroll() { setScrolled(window.scrollY > 40); const sections = ['home', 'services', 'about', 'process', 'contact']; for (const id of sections.reverse()) { const el = document.getElementById(id); if (el) { const rect = el.getBoundingClientRect(); if (rect.top <= 120) { setActiveSection(id); break; } } } } window.addEventListener('scroll', onScroll); onScroll(); return () => window.removeEventListener('scroll', onScroll); }, []); // Reveal observer useEA(() => { const obs = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('in'); }); }, { threshold: 0.1, rootMargin: '0px 0px -60px 0px' }); const watch = () => document.querySelectorAll('.reveal:not(.in)').forEach(el => obs.observe(el)); watch(); const mo = new MutationObserver(watch); mo.observe(document.body, { childList: true, subtree: true }); return () => { obs.disconnect(); mo.disconnect(); }; }, [lang]); // Custom cursor useEA(() => { if (window.innerWidth <= 900) return; const dot = document.createElement('div'); dot.className = 'cursor-dot'; const ring = document.createElement('div'); ring.className = 'cursor-ring'; document.body.appendChild(dot); document.body.appendChild(ring); let mx = 0, my = 0, rx = 0, ry = 0; function move(e) { mx = e.clientX; my = e.clientY; dot.style.transform = `translate(${mx}px, ${my}px) translate(-50%,-50%)`; } function loop() { rx += (mx - rx) * 0.18; ry += (my - ry) * 0.18; ring.style.transform = `translate(${rx}px, ${ry}px) translate(-50%,-50%)`; requestAnimationFrame(loop); } window.addEventListener('mousemove', move); loop(); function hoverIn() { document.body.classList.add('cursor-hover'); } function hoverOut() { document.body.classList.remove('cursor-hover'); } function attach() { document.querySelectorAll('a, button, [data-magnetic], .project, .service, .faq-q, .stack-item').forEach(el => { el.addEventListener('mouseenter', hoverIn); el.addEventListener('mouseleave', hoverOut); }); } attach(); const mo = new MutationObserver(attach); mo.observe(document.body, { childList: true, subtree: true }); return () => { window.removeEventListener('mousemove', move); dot.remove(); ring.remove(); mo.disconnect(); }; }, [lang]); // Magnetic buttons useEA(() => { const els = document.querySelectorAll('[data-magnetic]'); const handlers = []; els.forEach(el => { function move(e) { const rect = el.getBoundingClientRect(); const x = e.clientX - (rect.left + rect.width / 2); const y = e.clientY - (rect.top + rect.height / 2); el.style.transform = `translate(${x * 0.25}px, ${y * 0.25}px)`; } function leave() { el.style.transform = ''; } el.addEventListener('mousemove', move); el.addEventListener('mouseleave', leave); handlers.push([el, move, leave]); }); return () => handlers.forEach(([el, m, l]) => { el.removeEventListener('mousemove', m); el.removeEventListener('mouseleave', l); }); }, [lang]); // Tweaks listener useEA(() => { function onMsg(e) { if (e.data?.type === '__activate_edit_mode') setShowTweaks(true); else if (e.data?.type === '__deactivate_edit_mode') setShowTweaks(false); } window.addEventListener('message', onMsg); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); return () => window.removeEventListener('message', onMsg); }, []); function updateTweak(key, value) { if (key === 'theme') setTheme(value); if (key === 'heroVariant') setHeroVariant(value); window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: value } }, '*'); } const navItems = [ { id: 'services', label: t.nav.services }, { id: 'about', label: t.nav.about }, { id: 'process', label: t.nav.process }, // { id: 'projects', label: t.nav.work }, // Temporarily hidden until cases are ready { id: 'contact', label: t.nav.contact }, ]; return ( <> {/* NAV */} {/* CONTENT */}
{/* — Temporarily hidden until cases are ready */}