"use client"; import { useCallback, useEffect, useRef, useState, useSyncExternalStore, } from "react"; import { GaussianSplatViewer } from "./GaussianSplatViewer"; import { scenes } from "./scenes"; function highlightJson(json: string): string { return json.replace( /("(?:\\.|[^"\\])*")\s*(:)|("(?:\\.|[^"\\])*")|([-+]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(\btrue\b|\bfalse\b|\bnull\b)|([{}[\]:,])/g, (_, key, colon, str, num, lit, punct) => { if (key) return `${key}${colon}`; if (str) return `${str}`; if (num) return `${num}`; if (lit) return `${lit}`; if (punct) return `${punct}`; return _; }, ); } const MOBILE_BREAKPOINT = 768; function subscribeToResize(cb: () => void) { window.addEventListener("resize", cb); return () => window.removeEventListener("resize", cb); } function getIsMobile() { return typeof window !== "undefined" ? window.innerWidth < MOBILE_BREAKPOINT : false; } function useIsMobile() { return useSyncExternalStore(subscribeToResize, getIsMobile, () => false); } const LIST_WIDTH = 220; const JSON_WIDTH = 380; const HEADER_HEIGHT = 40; const headerStyle: React.CSSProperties = { height: HEADER_HEIGHT, display: "flex", alignItems: "center", padding: "0 16px", fontSize: 11, fontWeight: 600, color: "#555", letterSpacing: "0.08em", textTransform: "uppercase", fontFamily: "ui-monospace, monospace", borderBottom: "1px solid #1e1e1e", flexShrink: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", boxSizing: "border-box", }; function SplatViewer({ sceneIndex }: { sceneIndex: number }) { const scene = scenes[sceneIndex]!; const v = scene.viewer; return ( ); } function SceneListItem({ scene, index, isSelected, onSelect, }: { scene: (typeof scenes)[number]; index: number; isSelected: boolean; onSelect: (i: number) => void; }) { return ( ); } function DesktopLayout() { const [selectedIndex, setSelectedIndex] = useState(0); const selected = scenes[selectedIndex]!; return (
{selected.description}
); } function MobileLayout() { const [selectedIndex, setSelectedIndex] = useState(0); const [showJson, setShowJson] = useState(false); const [showScenes, setShowScenes] = useState(false); const selected = scenes[selectedIndex]!; const scenePanelRef = useRef(null); const jsonPanelRef = useRef(null); const closeScenes = useCallback(() => setShowScenes(false), []); const closeJson = useCallback(() => setShowJson(false), []); // Close overlays on Escape useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { if (showScenes) closeScenes(); if (showJson) closeJson(); } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [showScenes, showJson, closeScenes, closeJson]); // Focus the panel when it opens useEffect(() => { if (showScenes) scenePanelRef.current?.focus(); }, [showScenes]); useEffect(() => { if (showJson) jsonPanelRef.current?.focus(); }, [showJson]); return (
{selected.description}
{showScenes && ( <>
Splat Scenes
{scenes.map((scene, i) => ( { setSelectedIndex(idx); closeScenes(); }} /> ))}
)} {showJson && ( <>
Spec JSON
            
)}
); } export default function Page() { const isMobile = useIsMobile(); return isMobile ? : ; }