import { lazy, Suspense, useEffect, useState, type CSSProperties } from 'react';
import type { CompiledPage } from '../../src/contracts/page';
import type { SceneRecord } from '../../src/contracts/scene';
import { sceneAspect } from './scene-coordinate-map';
import { HotspotOverlay } from './HotspotOverlay';
import { MotionPreferenceControl, useMotionPreference } from './MotionPreferenceControl';
import { probeRenderer } from './renderer-capability';
import { useSceneNavigation } from './useSceneNavigation';
const LobbyRiveCanvas = lazy(() => import('./LobbyRiveCanvas.client'));
const DOOR_OPEN_MS = 760;
function LobbyCopyOverlay() {
return (
★Secure systems.
Stronger services.
A better tomorrow.
★
IT Services
Gov Solutions
Cyber Security
);
}
/** Poster first (prerendered), native layer only in the browser and only when a verified .riv exists; real links on top. */
export function SceneShell({ scene, page }: { scene: SceneRecord; page: CompiledPage }) {
const [mounted, setMounted] = useState(false);
const [renderer, setRenderer] = useState('pending');
const [nativeReady, setNativeReady] = useState(false);
const [nativeFailed, setNativeFailed] = useState(false);
const [focus, setFocus] = useState(null);
const [motion, setMotion] = useMotionPreference();
const [pointer, setPointer] = useState({ x: 0.5, y: 0.5, active: false });
useEffect(() => {
setMounted(true);
const verdict = probeRenderer();
setRenderer(verdict.ok ? verdict.renderer : verdict.reason);
}, []);
useEffect(() => { if (!motion) setNativeReady(false); }, [motion]);
const { hotspots, activate, leaving, leavingTarget } = useSceneNavigation({
hotspots: scene.hotspots,
pageId: page.id,
motion: motion && nativeReady,
transitionMs: hotspot => scene.id === 'lobby' && hotspot.id.startsWith('door-') ? DOOR_OPEN_MS : 0,
onRouteStart: hotspot => setFocus(hotspot.id),
});
const hardware = mounted && !/^no |^software/i.test(renderer) && renderer !== 'pending';
const showLobbyNative = Boolean(hardware && motion && scene.id === 'lobby' && scene.rive && !nativeFailed);
const caption = focus ? scene.captions[focus] ?? hotspots.find(h => h.id === focus)?.label ?? '' : scene.captionRest;
const planeStyle = { aspectRatio: sceneAspect(scene.canvas), '--scene-w': scene.canvas.width, '--scene-h': scene.canvas.height } as CSSProperties;
const sceneStyle = { ...planeStyle, '--pointer-x': pointer.x, '--pointer-y': pointer.y } as CSSProperties;
return (
{
const rect = event.currentTarget.getBoundingClientRect();
setPointer({ x: Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width)), y: Math.max(0, Math.min(1, (event.clientY - rect.top) / rect.height)), active: true });
}}
onPointerLeave={() => setPointer(pointer => ({ ...pointer, active: false }))}
>
{/* One plane in room coordinates, sized to cover the viewport: poster, native layer and hit shapes map the same way. */}

)} />
{showLobbyNative && (
setNativeReady(true)}
onError={() => { setNativeFailed(true); setNativeReady(false); }}
/>
)}
{scene.id === 'lobby' && nativeReady &&
}
{ if (!leaving) setFocus(id); }} onActivate={activate} />
{caption}
{mounted && scene.id === 'lobby' &&
}
);
}