/* App shell + hash-based router + Tweaks + page-load delay */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "theme": "light", "accent": "#4f46e5", "density": "comfortable", "loadDelayMs": 32000, "username": "Candidate", "password": "Quantium" }/*EDITMODE-END*/; const ACCENT_OPTIONS = [ { value: "#4f46e5", label: "Indigo" }, { value: "#0ea5e9", label: "Sky" }, { value: "#16a34a", label: "Green" }, { value: "#ea580c", label: "Orange" }, { value: "#db2777", label: "Pink" }, ]; function App() { const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS); /* ---- apply theme + accent + density to ---- */ useEffect(() => { const html = document.documentElement; html.setAttribute("data-theme", tweaks.theme); html.style.setProperty("--accent", tweaks.accent); // hover = darken slightly html.style.setProperty("--accent-hover", `color-mix(in oklch, ${tweaks.accent} 85%, #000)` ); html.style.setProperty("--d", tweaks.density === "compact" ? "0.85" : tweaks.density === "spacious" ? "1.15" : "1" ); }, [tweaks.theme, tweaks.accent, tweaks.density]); /* ---- auth ---- */ const [user, setUser] = useState(() => sessionStorage.getItem("qa.user") || null); const onLogin = (u) => { sessionStorage.setItem("qa.user", u); setUser(u); setRoute({ name: "home" }); }; const onLogout = () => { sessionStorage.removeItem("qa.user"); setUser(null); setRoute({ name: "login" }); history.replaceState(null, "", "#/login"); }; /* ---- routing (simple hash-based) ---- */ const parseHash = () => { const h = window.location.hash.replace(/^#\/?/, ""); if (!h || h === "login") return { name: user ? "home" : "login" }; if (h === "home") return { name: "home" }; const m = h.match(/^topics\/(.+)$/); if (m && window.TOPICS_BY_ID[m[1]]) return { name: "topic", id: m[1] }; return { name: user ? "home" : "login" }; }; const [route, setRoute] = useState(parseHash); useEffect(() => { const onHash = () => setRoute(parseHash()); window.addEventListener("hashchange", onHash); return () => window.removeEventListener("hashchange", onHash); }, [user]); // Sync route → hash useEffect(() => { const target = route.name === "login" ? "#/login" : route.name === "home" ? "#/home" : route.name === "topic" ? `#/topics/${route.id}` : "#/home"; if (window.location.hash !== target) { history.pushState(null, "", target); } }, [route]); // Force login if not authed useEffect(() => { if (!user && route.name !== "login") { setRoute({ name: "login" }); } }, [user, route.name]); /* ---- page-load delay key ---- * Only "slow-load" topics (where waiting on page load is part of the * practice) get the long delay; every other page is instant. */ const navKey = route.name + (route.id || ""); const isSlowPage = route.name === "topic" && window.TOPICS_SLOW_IDS.has(route.id); const effectiveDelay = isSlowPage ? tweaks.loadDelayMs : 0; const loading = useArtificialDelay(navKey, effectiveDelay); /* ---- render ---- */ if (!user || route.name === "login") { return ( ); } const onOpenTopic = (id) => setRoute(id ? { name: "topic", id } : { name: "home" }); const onNavHome = () => setRoute({ name: "home" }); const onNavTopic = (id) => setRoute({ name: "topic", id }); return (
{loading && } {loading ? ( <> ) : route.name === "home" ? ( ) : ( )} setTweak("theme", v)} options={[{ value: "light", label: "Light" }, { value: "dark", label: "Dark" }]} /> setTweak("accent", v)} options={ACCENT_OPTIONS.map((o) => o.value)} /> setTweak("density", v)} options={[ { value: "compact", label: "Compact" }, { value: "comfortable", label: "Default" }, { value: "spacious", label: "Spacious" }, ]} /> setTweak("loadDelayMs", v)} />
Applied only on slow-load assignments:{" "} {[...window.TOPICS_SLOW_IDS].map((id) => window.TOPICS_BY_ID[id].title).join(", ")}
setTweak("username", v)} /> setTweak("password", v)} />
); } ReactDOM.createRoot(document.getElementById("root")).render();