import { useEffect, useState, type ReactNode } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import { TooltipProvider } from "@/components/ui/tooltip"; import { Toaster } from "@/components/ui/sonner"; import frameSvg from "@/assets/frame.themed.svg"; import browserSvg from "@/assets/browser.min.svg"; import NavMenu from "./NavMenu"; import LazyEyes from "./LazyEyes"; const TITLE = ` ▄▄▄▄███▄▄▄▄ ▄█ ▄████████ ▄████████ ▄██████▄ ███▄▄▄▄ ▄██████▄ ▄▄▄▄███▄▄▄▄ ▄█ ▄████████ ▄██████▄ ███▄▄▄▄ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ███ ███▌ ███ █▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ █▀ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ ▄███▄▄▄▄██▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ ▀▀███▀▀▀▀▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄ ▀███████████ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▀█ ███ █▀ █▀ ████████▀ ███ ███ ▀██████▀ ▀█ █▀ ▀██████▀ ▀█ ███ █▀ █▀ ████████▀ ▀██████▀ ▀█ █▀ ███ ███ `; type Theme = "terra" | "azure"; function getStoredTheme(): Theme { try { return (localStorage.getItem("micronomicon-theme") as Theme) || "terra"; } catch { return "terra"; } } function toRoman(n: number): string { const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]; const syms = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]; let r = ""; for (let i = 0; i < vals.length; i++) { while (n >= vals[i]) { r += syms[i]; n -= vals[i]; } } return r; } function RomanDrum({ value }: { value: string }) { return ( {value} ); } function RomanClock() { const [time, setTime] = useState(new Date()); useEffect(() => { const id = setInterval(() => setTime(new Date()), 1000); return () => clearInterval(id); }, []); const h = time.getHours(); const m = time.getMinutes(); const s = time.getSeconds(); return ( : : ); } // --------------------------------------------------------------------------- // Per-frame layout configs // --------------------------------------------------------------------------- interface FrameLayout { svg: string; frameHeight: number; containerMaxW: string; containerMinW: string; title: { paddingTop: number; height: number; paddingLeft: number; paddingRight: number; paddingBottom: number }; content: { marginLeft: number; marginTop: number; marginRight: number; width: number; height: number; paddingTop: number; paddingBottom: number }; nav: { top: number; left: number }; } const defaultLayout: FrameLayout = { svg: frameSvg, frameHeight: 1315, containerMaxW: "max-w-5xl", containerMinW: "min-w-5xl", title: { paddingTop: 65, height: 185, paddingLeft: 60, paddingRight: 500, paddingBottom: 25 }, content: { marginLeft: 12, marginRight: 68, width: 843, height: 1030, paddingTop: 8, paddingBottom: 20 }, nav: { top: 150, left: -210 }, }; const browseLayout: FrameLayout = { svg: browserSvg, frameHeight: 884, containerMaxW: "max-w-5xl", containerMinW: "min-w-5xl", title: { paddingTop: 65, height: 185, paddingLeft: 60, paddingRight: 500, paddingBottom: 25 }, content: { marginLeft: 268, marginTop: 63, width: 476, height: 377, paddingTop: 0, paddingBottom: 0 }, nav: { top: 150, left: -210 }, }; export default function AppShell({ children }: { children: ReactNode }) { const navigate = useNavigate(); const location = useLocation(); const [theme, setTheme] = useState(getStoredTheme); const isEditor = location.pathname.startsWith("/editor"); const isBrowse = location.pathname === "/browse"; const layout = isBrowse ? browseLayout : defaultLayout; useEffect(() => { const root = document.documentElement; // Remove all theme classes, then apply root.classList.remove("dark", "theme-azure"); if (theme === "terra") { root.classList.add("dark"); } else { root.classList.add("theme-azure"); } localStorage.setItem("micronomicon-theme", theme); }, [theme]); const toggleTheme = () => setTheme(t => t === "terra" ? "azure" : "terra"); return (
{/* Frame SVG — background */}
{/* Lazy eyes on the browse frame figure */} {isBrowse && ( )} {/* Top hole — ASCII title */}
 navigate("/")}
              >
                {TITLE}
              
{/* Bottom hole — main content */}
{children}
{/* Nav menu — anchored below the frame, left side (hidden on editor) */} {!isEditor && (
)}
Created with hubris · MMXXVI
); }