// 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 */}