// Root app: hash-based router + tweaks panel + accent color binding. const { useState, useEffect, useMemo } = React; const DEFAULT_TWEAKS = /*EDITMODE-BEGIN*/{ "accent": "#7c7cff", "heroDensity": 1.6, "glassOpacity": 5, "metagraphEnabled": false }/*EDITMODE-END*/; function useHashRoute() { const [route, setRoute] = useState(() => (window.location.hash || '#/').slice(1) || '/'); useEffect(() => { const onHash = () => setRoute((window.location.hash || '#/').slice(1) || '/'); window.addEventListener('hashchange', onHash); return () => window.removeEventListener('hashchange', onHash); }, []); const navigate = (path) => { if (!path.startsWith('/')) path = '/' + path; window.location.hash = '#' + path; window.scrollTo({ top: 0, behavior: 'instant' }); }; return [route, navigate]; } function App() { const [route, navigate] = useHashRoute(); const [tweaks, setTweak] = useTweaks(DEFAULT_TWEAKS); // Sync env flag with tweak so toggling the tweak hides the route too. useEffect(() => { window.__ENV.METAGRAPH_ENABLED = !!tweaks.metagraphEnabled; }, [tweaks.metagraphEnabled]); // Push accent to CSS vars useEffect(() => { const root = document.documentElement; root.style.setProperty('--accent', tweaks.accent); // derive a near-accent for the gradient end root.style.setProperty('--accent-2', mix(tweaks.accent, '#ffffff', 0.35)); root.style.setProperty('--accent-glow', hexToRgba(tweaks.accent, 0.35)); }, [tweaks.accent]); // Push glass tint to CSS vars. Slider goes 0–100; we map to a tint alpha // that goes from "barely there" (0.01) to "clearly visible glass" (0.20). useEffect(() => { const root = document.documentElement; const t = Math.max(0, Math.min(100, tweaks.glassOpacity ?? 30)) / 100; const tint = 0.01 + t * 0.19; // 0.01 .. 0.20 const tintLow = tint * 0.3; // gradient endpoint root.style.setProperty('--lg-tint', tint.toFixed(3)); root.style.setProperty('--lg-tint-low', tintLow.toFixed(3)); }, [tweaks.glassOpacity]); // Document title per route useEffect(() => { const titles = { '/': 'graphite · structured world knowledge', '/graphs': 'Knowledge Graphs · graphite', '/graphs/financial': 'Financial Knowledge Graph · graphite', '/metagraph': 'Metagraph · graphite', '/portal': 'API Portal · graphite', '/pricing': 'Pricing · graphite', '/admin': 'Admin · graphite', }; document.title = titles[route] || 'graphite'; }, [route]); // /admin renders full-screen (no global Nav, its own header) so we // branch on it before the rest of the routing. if (route === '/admin') { return ; } let page; if (route === '/' || route === '') { page = ; } else if (route === '/graphs') { page = ; } else if (route === '/graphs/financial') { page = ; } else if (route === '/metagraph') { if (!tweaks.metagraphEnabled) { page = ; } else { page = ; } } else if (route === '/portal') { page = ; } else if (route === '/pricing') { page = ; } else { page = ; } return ( <>