import React, { useEffect, useMemo, useRef, useState } from "react"; import { Outlet } from "react-router-dom"; import { AnimatePresence, motion } from "motion/react"; import { ThemeConfig, ThemeContext } from "../context/ThemeContext"; import GeometricBackground from "../components/GeometricBackground"; import BackgroundElements from "../components/BackgroundElements"; import TopBar from "../components/TopBar"; import PlatformSidebar from "./PlatformSidebar"; import ModuleLaunchAnimation, { hashModuleId } from "./ModuleLaunchAnimation"; import { useLaunch } from "./LaunchContext"; import { MODULES, ModuleId } from "./registry"; /** * Nested theme provider — overrides the platform default (mint) with the * active module palette for the whole module subtree. */ function ModuleThemeProvider({ config, children }: { config: ThemeConfig; children: React.ReactNode }) { const value = useMemo(() => ({ theme: config.id, config }), [config]); return {children}; } interface ModuleShellProps { module: ModuleId; } /** * Route shell for /modules/e and /modules/s. * * Launch guard: on every mount (menu launch, hub launch, direct URL) the * module's real dashboard primitives random-reassemble in slow motion before * the workspace is revealed. Leaving via the menu plays the reverse-assembly * exit. Internal navigation never replays the animation (shell stays mounted). */ export default function ModuleShell({ module }: ModuleShellProps) { const def = MODULES[module]; const { seq, exiting, finishExit } = useLaunch(); const [launchDone, setLaunchDone] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const seedRef = useRef(0); if (seedRef.current === 0) { seedRef.current = Date.now() ^ hashModuleId(module) ^ ((seq + 1) * 0x9e3779b9); } // A fresh mount is a fresh launch — clear any stale exit flag from the // previous module. useEffect(() => { if (exiting) finishExit(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const isExiting = exiting === module; return (
{/* Mobile overlay */} {isSidebarOpen && (
setIsSidebarOpen(false)} /> )} {/* Sidebar with mobile slide transition */}
setIsSidebarOpen(false)} />
setIsSidebarOpen(true)} />
{/* Launch guard: the animation covers the workspace; pages load (and prefetch their data) behind the opaque overlay. */}
{!isExiting && ( }> )} {!launchDone && !isExiting && ( setLaunchDone(true)} accent={def.theme.accent} accentSoft={def.theme.accentText} /> )} {isExiting && ( finishExit()} accent={def.theme.accent} accentSoft={def.theme.accentText} /> )}
); }