// app.jsx — оркестрация экранов + Tweaks const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "goldTone": "#c9a227", "typeSpeed": 22, "embers": 26, "atmosphere": true }/*EDITMODE-END*/; // gold palettes: [gold, bright, deep, dim] const GOLD_SETS = { "#c9a227": ["#c9a227", "#e8c75a", "#9a7c1e", "#6f5a23"], // classic gold "#b87333": ["#b87333", "#e0935a", "#8a4f24", "#6a3f1e"], // copper / bronze "#a8451f": ["#c25a2e", "#e08a4a", "#8a3014", "#6a2810"], // ember "#8a8f6b": ["#9aa07a", "#c4c79c", "#6a6e52", "#4f5340"], // verdigris }; function EmberField({ count }) { const motes = React.useMemo(() => Array.from({ length: count }).map((_, i) => ({ left: Math.random() * 100, dur: 9 + Math.random() * 14, delay: -Math.random() * 20, drift: (Math.random() * 80 - 40) + "px", size: 1.5 + Math.random() * 2.5, op: 0.4 + Math.random() * 0.5, })), [count]); return (
{motes.map((m, i) => ( ))}
); } function Atmosphere({ on, embers }) { return (
{on && }
); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [view, setView] = React.useState("loading"); const [authMode, setAuthMode] = React.useState("register"); const [seeker, setSeeker] = React.useState(null); // apply gold tone -> CSS variables React.useEffect(() => { const set = GOLD_SETS[t.goldTone] || GOLD_SETS["#c9a227"]; const r = document.documentElement.style; r.setProperty("--gold", set[0]); r.setProperty("--gold-bright", set[1]); r.setProperty("--gold-deep", set[2]); r.setProperty("--gold-dim", set[3]); }, [t.goldTone]); const enterAuth = (mode) => { setAuthMode(mode); setView("auth"); }; const signOut = async () => { await window.AzothApi.logout(); setSeeker(null); setView("landing"); }; React.useEffect(() => { const token = window.AzothApi.token(); if (!token) { setView("landing"); return; } window.AzothApi.me() .then(({ user }) => { setSeeker({ id: user.id, name: user.display_name || "мандрівник", email: user.email, isNew: false, disciplines: ["free", "tarot", "numero"], }); setView("chat"); }) .catch(() => { window.AzothApi.setToken(""); setView("landing"); }); }, []); // Safety net: if entrance animations are paused (hidden tab) or stripped, // force revealed state shortly after each view mounts so nothing stays blank. React.useEffect(() => { document.documentElement.classList.remove("anims-locked"); const id = setTimeout(() => document.documentElement.classList.add("anims-locked"), 1700); return () => clearTimeout(id); }, [view]); return ( {view === "loading" && (
Azoth AI
)} {view === "landing" && } {view === "auth" && ( setView("landing")} onDone={(s) => { setSeeker(s); setView(s.isNew ? "onboarding" : "chat"); }} /> )} {view === "onboarding" && ( { setSeeker(prev => ({ ...prev, ...s })); setView("chat"); }} /> )} {view === "chat" && ( )} setTweak("goldTone", v)} /> setTweak("atmosphere", v)} /> setTweak("embers", v)} /> setTweak("typeSpeed", v)} />
); } ReactDOM.createRoot(document.getElementById("root")).render();