import { lazy, Suspense, useEffect, useState, type CSSProperties } from 'react'; import type { CompiledPage } from '../../src/contracts/page'; import type { SceneRecord } from '../../src/contracts/scene'; import { HotspotOverlay } from './HotspotOverlay'; import { ItServicesEnvironment } from './ItServicesEnvironment'; import { useMotionPreference } from './MotionPreferenceControl'; import { probeRenderer } from './renderer-capability'; import { sceneAspect } from './scene-coordinate-map'; import { useSceneNavigation } from './useSceneNavigation'; import '../styles/it-services-rive.css'; const ItServicesRiveCanvas = lazy(() => import('./ItServicesRiveCanvas.client')); const ROUTE_TRANSITION_MS = 760; /** Dedicated full-viewport IT Services room, isolated from the concurrently authored lobby shell. */ export function ItServicesSceneShell({ 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] = 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); }, []); const { hotspots, activate, leaving, leavingTarget } = useSceneNavigation({ hotspots: scene.hotspots, pageId: page.id, motion, transitionMs: ROUTE_TRANSITION_MS, onRouteStart: hotspot => setFocus(hotspot.id), }); const hardware = mounted && !/^no |^software/i.test(renderer) && renderer !== 'pending'; const showNative = Boolean(hardware && motion && scene.rive && !nativeFailed); const planeStyle = { aspectRatio: sceneAspect(scene.canvas), '--scene-w': scene.canvas.width, '--scene-h': scene.canvas.height, '--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(current => ({ ...current, active: false }))} >
{scene.posterAlt})} /> {showNative && ( setNativeReady(true)} onError={() => { setNativeFailed(true); setNativeReady(false); }} /> )} {!nativeReady && ( )} { if (!leaving) setFocus(id); }} onActivate={activate} />
); }