/* ------------------------------------------------------------------ *
* App shell — routing, tweaks, mount
* ------------------------------------------------------------------ */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"theme": "paper",
"accent": "teal",
"showStripes": true,
"marqueeSpeed": 40
}/*EDITMODE-END*/;
function useRoute() {
const [route, setRoute] = useState(() => {
const h = window.location.hash.replace(/^#/, "");
return h || "/";
});
useEffect(() => {
const onHash = () => setRoute(window.location.hash.replace(/^#/, "") || "/");
window.addEventListener("hashchange", onHash);
return () => window.removeEventListener("hashchange", onHash);
}, []);
const navigate = useCallback((to) => {
window.location.hash = to;
window.scrollTo({ top: 0, behavior: "smooth" });
}, []);
return [route, navigate];
}
function App() {
const [route, navigate] = useRoute();
const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
const [quoteItems, setQuoteItems] = useState([]);
const [tradeGateOpen, setTradeGateOpen] = useState(false);
const { t } = useLang();
const addToQuote = (item) => setQuoteItems((q) => [...q, item]);
const removeQuoteItem = (i) => setQuoteItems((q) => q.filter((_, idx) => idx !== i));
// Apply tweaks
useEffect(() => {
const root = document.documentElement;
if (tweaks.theme === "ocean") {
root.style.setProperty("--bg", "#08252E");
root.style.setProperty("--fg", "#DCF2F6");
root.style.setProperty("--muted", "#7B9BA3");
root.style.setProperty("--rule", "rgba(220,242,246,0.12)");
root.style.setProperty("--rule-strong", "rgba(220,242,246,0.25)");
root.style.setProperty("--sand", "#0F2F38");
root.style.setProperty("--sand-warm", "#143A45");
root.style.setProperty("--paper", "#08252E");
} else if (tweaks.theme === "sand") {
root.style.setProperty("--bg", "#F5EDE0");
root.style.setProperty("--fg", "#0A2A33");
root.style.setProperty("--muted", "#5C6B72");
root.style.setProperty("--rule", "rgba(10,42,51,0.12)");
root.style.setProperty("--rule-strong", "rgba(10,42,51,0.25)");
root.style.setProperty("--sand", "#EFE3D0");
root.style.setProperty("--sand-warm", "#E5D7BE");
root.style.setProperty("--paper", "#F5EDE0");
} else {
// paper (default) — clear all overrides so styles.css wins
["--bg","--fg","--muted","--rule","--rule-strong","--sand","--sand-warm","--paper"]
.forEach((k) => root.style.removeProperty(k));
}
if (tweaks.accent === "ocean") {
root.style.setProperty("--teal", "#0E6E80");
root.style.setProperty("--teal-deep", "#0A4D5A");
} else if (tweaks.accent === "coral") {
root.style.setProperty("--teal", "#E08864");
root.style.setProperty("--teal-deep", "#B86A4A");
} else if (tweaks.accent === "sage") {
root.style.setProperty("--teal", "#92A88E");
root.style.setProperty("--teal-deep", "#5E7758");
} else {
// teal (default) — clear so styles.css wins
root.style.removeProperty("--teal");
root.style.removeProperty("--teal-deep");
}
}, [tweaks.theme, tweaks.accent]);
// Stripes visibility
useEffect(() => {
document.documentElement.style.setProperty(
"--stripes-display",
tweaks.showStripes ? "block" : "none"
);
document.querySelectorAll(".media__stripes").forEach(el => {
el.style.display = tweaks.showStripes ? "block" : "none";
});
}, [tweaks.showStripes, route]);
// Marquee speed
useEffect(() => {
document.querySelectorAll(".marquee__track").forEach(el => {
el.style.animationDuration = `${tweaks.marqueeSpeed}s`;
});
}, [tweaks.marqueeSpeed, route]);
// Route parser
let page = null;
let darkNav = false;
if (route === "/" || route === "") {
page = ;
darkNav = true; // hero is dark
} else if (route.startsWith("/collection")) {
page = ;
} else if (route.startsWith("/story")) {
page = ;
} else if (route.startsWith("/materials")) {
page = ;
} else if (route.startsWith("/product/")) {
const slug = route.replace("/product/", "");
page = ;
} else if (route.startsWith("/contact")) {
page = ;
} else {
page = ;
darkNav = true;
}
// Dark nav only when at top of home hero
const [atHeroTop, setAtHeroTop] = useState(true);
useEffect(() => {
if (!darkNav) { setAtHeroTop(false); return; }
const onScroll = () => setAtHeroTop(window.scrollY < window.innerHeight - 200);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, [darkNav, route]);
return (
<>
{page}