// desidium-app.jsx — assembles the site, owns language / scroll / tweaks const { useState: useStateApp, useEffect: useEffectApp } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#c8643c", "motion": true, "columns": 3, "defaultLang": "en" }/*EDITMODE-END*/; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [lang, setLangState] = useStateApp(() => { const wp = window.desidiumWp || {}; let stored = null; try { stored = localStorage.getItem("desidium-lang"); } catch (e) {} // With Polylang the server resolves the language from the URL — trust it, except // on /models// deep-links, which carry no language prefix (there, and when // Polylang isn't active, the visitor's stored choice wins over the site locale). if (wp.pllActive && wp.lang && !wp.modelSlug) return wp.lang; return stored || wp.lang || TWEAK_DEFAULTS.defaultLang; }); const setLang = (l) => { if (window.desidiumWp && window.desidiumWp.pllActive && window.desidiumWp.languages) { const target = window.desidiumWp.languages.find((x) => x.slug === l); if (target && target.url) { window.location.href = target.url; return; } } setLangState(l); try { localStorage.setItem("desidium-lang", l); } catch (e) {} }; useEffectApp(() => { document.documentElement.lang = lang; }, [lang]); const [scrolled, setScrolled] = useStateApp(false); const [menu, setMenu] = useStateApp(false); const [route, setRoute] = useStateApp(() => { // /models/ is served by WordPress (see desidium_model_* in functions.php) and // arrives here as desidiumWp.modelSlug; deep-link straight into that profile. const slug = window.desidiumWp && window.desidiumWp.modelSlug; if (slug && MODELS.some((x) => x.id === slug)) return slug; const dub = window.desidiumWp && window.desidiumWp.dubbingSlug; if (dub && typeof DUBBING !== "undefined" && DUBBING.some((x) => x.id === dub)) return "dubbing:" + dub; // "Become a model" / join page temporarily hidden. Restore this block to re-enable ?view=join. // try { // const params = new URLSearchParams(window.location.search); // if (params.get("view") === "join") return "join"; // } catch (e) {} return "home"; }); // 'home' | model.id | 'dubbing:' // Keep the address bar in sync with model routes: /models/ on open, home URL on back. const homeUrl = (window.desidiumWp && window.desidiumWp.homeUrl) || "/"; const modelUrl = (id) => homeUrl.replace(/\/?$/, "/") + "models/" + encodeURIComponent(id) + "/"; const dubbingUrl = (id) => homeUrl.replace(/\/?$/, "/") + "dubbing/" + encodeURIComponent(id) + "/"; const isModelRoute = (r) => MODELS.some((x) => x.id === r); const dubbingRouteSlug = (r) => (r.indexOf("dubbing:") === 0 ? r.slice(8) : null); useEffectApp(() => { if (!window.history || !window.history.pushState) return; const dubSlug = dubbingRouteSlug(route); const url = isModelRoute(route) ? modelUrl(route) : dubSlug ? dubbingUrl(dubSlug) : (route === "home" ? homeUrl : null); if (!url) return; const current = window.location.pathname.replace(/\/?$/, "/"); const target = new URL(url, window.location.href).pathname.replace(/\/?$/, "/"); if (current !== target) window.history.pushState({ route }, "", url); }, [route]); useEffectApp(() => { const onPop = () => { const m = window.location.pathname.match(/\/models\/([^/]+)\/?$/); const slug = m ? decodeURIComponent(m[1]) : null; if (slug && isModelRoute(slug)) { setRoute(slug); return; } const dm = window.location.pathname.match(/\/dubbing\/([^/]+)\/?$/); const dslug = dm ? decodeURIComponent(dm[1]) : null; if (dslug && typeof DUBBING !== "undefined" && DUBBING.some((x) => x.id === dslug)) { setRoute("dubbing:" + dslug); return; } if (isModelRoute(route)) { setRoute("home"); setPendingScroll("#roster"); return; } if (dubbingRouteSlug(route)) { setRoute("home"); setPendingScroll("#cases"); } }; window.addEventListener("popstate", onPop); return () => window.removeEventListener("popstate", onPop); }, [route]); useEffectApp(() => { const model = MODELS.find((x) => x.id === route); if (model) { document.title = model.name + " · Desidium"; return; } const dubSlug = dubbingRouteSlug(route); const dub = dubSlug && typeof DUBBING !== "undefined" && DUBBING.find((x) => x.id === dubSlug); if (dub) document.title = dub.client + " · Desidium"; }, [route]); const [pendingScroll, setPendingScroll] = useStateApp(() => { const hash = window.location.hash; return hash && hash !== "#top" ? hash : null; }); useEffectApp(() => { const onScroll = () => setScrolled(window.scrollY > 40); onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); // after returning home, run any pending scroll-to-section useEffectApp(() => { if (route === "home" && pendingScroll) { const target = pendingScroll; requestAnimationFrame(() => { const el = document.querySelector(target); if (el) { const y = el.getBoundingClientRect().top + window.scrollY - 8; window.scrollTo({ top: y, behavior: "smooth" }); } setPendingScroll(null); }); } }, [route, pendingScroll]); const go = (href) => { if (href === "route:journal" && window.desidiumWp && window.desidiumWp.blogUrl) { window.location.href = window.desidiumWp.blogUrl; return; } setMenu(false); if (href.indexOf("route:") === 0) { setRoute(href.slice(6)); window.scrollTo({ top: 0, behavior: "auto" }); return; } if (route !== "home") { setRoute("home"); if (href === "#top") { window.scrollTo({ top: 0, behavior: "auto" }); } else { setPendingScroll(href); } return; } if (href === "#top") { window.scrollTo({ top: 0, behavior: "smooth" }); return; } const el = document.querySelector(href); if (el) { const y = el.getBoundingClientRect().top + window.scrollY - 8; window.scrollTo({ top: y, behavior: "smooth" }); } }; const openModel = (mdl) => { setMenu(false); setRoute(mdl.id); }; const goContact = () => go("#contact"); const goPost = (id) => { setMenu(false); setRoute("post:" + id); }; const goAuthor = (id) => { setMenu(false); setRoute("author:" + id); }; const goJournal = () => { setMenu(false); setRoute("journal"); window.scrollTo({ top: 0, behavior: "auto" }); }; const motion = !!t.motion; const cols = Number(t.columns) || 3; useEffectApp(() => { document.documentElement.style.setProperty("--terra", t.accent); }, [t.accent]); // "join" ("Become a model") temporarily hidden — re-add ["join","route:join"] to restore. const navItems = [["roster","#roster"],["studio","#studio"],["cases","#cases"],["journal","route:journal"],["contact","#contact"]]; const isPost = route.indexOf("post:") === 0; const isAuthor = route.indexOf("author:") === 0; const isDubbing = route.indexOf("dubbing:") === 0; const isModel = !isPost && !isAuthor && !isDubbing && route !== "home" && route !== "join" && route !== "journal"; return (
); } ReactDOM.createRoot(document.getElementById("root")).render();