/* app.jsx — root: language, hash routing, active-section tracking. Loaded last. */
const { useState: aS, useEffect: aE, useCallback: aC } = React;
function parseHash() {
const h = (window.location.hash || "").replace(/^#\/?/, "");
const m = h.match(/^project\/(.+)$/);
return m ? { page: "project", id: decodeURIComponent(m[1]) } : { page: "home" };
}
function App() {
const [lang, setLang] = aS(() => localStorage.getItem("olv-lang") || "en");
const [route, setRoute] = aS(parseHash);
const [activeId, setActiveId] = aS("home");
aE(() => { localStorage.setItem("olv-lang", lang); document.documentElement.lang = lang; }, [lang]);
aE(() => {
const onHash = () => setRoute(parseHash());
window.addEventListener("hashchange", onHash);
return () => window.removeEventListener("hashchange", onHash);
}, []);
/* active-section tracking on home */
aE(() => {
if (route.page !== "home") return;
const ids = ["home", "why", "portfolio", "about", "contact"];
const secMap = { stages: "why", pillars: "why" };
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
let id = e.target.id;
id = secMap[id] || id;
if (ids.includes(id)) setActiveId(id);
}
});
}, { rootMargin: "-45% 0px -50% 0px", threshold: 0 });
["home", "why", "stages", "pillars", "portfolio", "about", "contact"]
.map((i) => document.getElementById(i)).filter(Boolean).forEach((el) => io.observe(el));
return () => io.disconnect();
}, [route.page]);
const openProject = aC((id) => { window.location.hash = "#/project/" + encodeURIComponent(id); }, []);
const backToPortfolio = aC(() => { window.location.hash = ""; setTimeout(() => window.scrollToId("portfolio"), 60); }, []);
const navigate = aC((sectionId) => {
if (route.page !== "home") {
window.location.hash = "";
setTimeout(() => window.scrollToId(sectionId), 80);
} else {
window.scrollToId(sectionId);
}
}, [route.page]);
/* audience card → jump to portfolio pre-filtered by category */
const pickCat = aC((cat) => {
const doIt = () => { window.dispatchEvent(new CustomEvent("olv-filter-cat", { detail: cat })); setTimeout(() => window.scrollToId("portfolio"), 60); };
if (route.page !== "home") { window.location.hash = ""; setTimeout(doIt, 120); } else doIt();
}, [route.page]);
return (
{route.page === "project"
?
:
}
);
}
(function mount() {
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render();
const loader = document.getElementById("loader");
if (loader) { setTimeout(() => { loader.classList.add("hide"); setTimeout(() => loader.remove(), 700); }, 300); }
})();