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