/** * Embed Astra's GivnTransit into the cinematic journey. * Arrival is POSTed only after sceneLoader resolves. Sample preview.html stays a labeled design preview. */ import { GivnTransit } from './givn-transit.mjs?v=20260922a'; import { VtsClient, safeEntryPath } from './vts-client.mjs'; import * as SFGivnVideoListings from './givn-video-listings.mjs'; import { cityZoomNote, isOrbitalHub } from './elevation-lod.mjs'; import { landmarkFromDestination } from './curated-landmarks.mjs'; import { readWorldManifest, theatreWorld } from './world-manifest.mjs?v=20260922a'; export const SUDBURY_CINEMATIC_TRANSIT_EMBED_ENABLED = false; export function wireCinematicTransitBoard() { return null; } function isTransitLaunchAttempt() { return false; } async function launchFromCinemaDestination() { return Object.freeze({ launched: false, opened: false, arrived: false }); } function mountCinemaDockLaunch() { return null; } const ORBITAL = new Set(['mars', 'venus', 'jupiter', 'neptune']); let dockLaunchAdapter = null; export function bodyToWorld(destination) { const body = String(destination?.celestial_body || '').toLowerCase(); if (body === 'moon' || ORBITAL.has(body)) return body; return 'earth'; } export async function openCataloguedTheatre(world) { const pack = world && world.slug ? {contractVersion:'1.0', worlds:[world]} : await readWorldManifest(); const theatre = theatreWorld(pack) || theatreWorld(await readWorldManifest()); if (!theatre) throw new Error('scene_unavailable'); const scene = window.SFJourney && window.SFJourney.scene; if (scene && typeof scene.revealPreviewSlot === 'function') scene.revealPreviewSlot('north'); const box = document.getElementById('sf-theatre-interior'); if (box) { const title = document.getElementById('sf-theatre-title'); if (title) title.textContent = theatre.title; box.hidden = false; SFGivnVideoListings.bindNativeTheatrePlayer(box); } return theatre; } export async function cinematicSceneLoader(destination) { if (!destination || typeof destination !== 'object') throw new Error('scene_unavailable'); if (isTransitLaunchAttempt(destination)) { await launchFromCinemaDestination(dockLaunchAdapter, destination); return; } if (destination.slug && destination.entryUrl) return openCataloguedTheatre(destination); safeEntryPath(destination.entry_path); const scene = window.SFJourney && window.SFJourney.scene; if (!scene || typeof scene.loadVtsDestination !== 'function') throw new Error('scene_unavailable'); const world = scene.loadVtsDestination(destination); paintVtsLocation(destination, world); await new Promise((resolve) => { requestAnimationFrame(() => requestAnimationFrame(resolve)); }); if (typeof scene.worldId === 'function' && scene.worldId() !== world) throw new Error('scene_load_failed'); const kind = typeof scene.vtsKind === 'function' ? scene.vtsKind(destination) : destination.kind; if ((kind === 'theatre' || kind === 'experience') && !document.getElementById('sf-theatre-interior')) throw new Error('scene_load_failed'); } export function paintVtsLocation(destination, world) { const head = document.getElementById('journey-city-head'); if (!head) return; const scene = window.SFJourney && window.SFJourney.scene; const caption = scene && scene.vtsLocationCaption; const name = String(caption || destination?.name || world || ''); const body = String(world || destination?.celestial_body || '').toLowerCase(); const orbital = ORBITAL.has(body) || isOrbitalHub(body) || (scene && scene.vtsScene === 'orbital_hub'); head.hidden = !name; head.dataset.vtsLive = '1'; head.dataset.vtsScene = String((scene && scene.vtsScene) || destination?.kind || ''); const terrain = cityZoomNote(body); head.textContent = caption || (orbital ? `${name} · orbital hub` : `${name} · ${terrain}`); } export function mountCinematicVts(host) { const root = host || document.getElementById('light-journey'); if (!root) return null; let dock = root.querySelector('#sf-vts-dock'); if (dock) { dockLaunchAdapter = mountCinemaDockLaunch(dock) || dockLaunchAdapter; return dock.querySelector('givn-transit'); } dock = document.createElement('aside'); dock.id = 'sf-vts-dock'; dock.className = 'sf-vts-dock is-stick'; dock.hidden = false; dock.setAttribute('aria-label', 'Jog shuttle'); const note = document.createElement('p'); note.className = 'sf-vts-note'; note.id = 'sf-vts-note'; note.textContent = 'Membership travel is not live until Hostinger /api returns JSON store=mysql. A 503 or missing route is not a visit.'; const board = new GivnTransit(); board.classList.add('stick-only'); board.client = new VtsClient(); board.sceneLoader = cinematicSceneLoader; board.addEventListener('givn:arrived', (event) => { const dest = event.detail && event.detail.destination; if (dest) paintVtsLocation(dest, bodyToWorld(dest)); }); document.addEventListener('sfi:vts-scene', () => { const scene = window.SFJourney && window.SFJourney.scene; const dest = scene && scene.vtsDestination; if (dest) paintVtsLocation(dest, bodyToWorld(dest)); }); dock.append(note, board); dockLaunchAdapter = mountCinemaDockLaunch(dock); const pad = root.querySelector('.journey-pad'); (pad || root).append(dock); return board; } if (typeof window !== 'undefined') { window.SFGivnVideoListings = SFGivnVideoListings; window.SFILandmarkFromDestination = landmarkFromDestination; } function pinStickToPad() { const dock = document.getElementById('sf-vts-dock'); const host = document.getElementById('light-journey'); const pad = host && host.querySelector('.journey-pad'); const look = pad && pad.querySelector('[data-key=look]'); if (!dock || !pad || !look || !dock.classList.contains('is-stick')) return; if (dock.parentElement !== pad) pad.append(dock); dock.style.left = ''; dock.style.top = ''; dock.style.right = ''; dock.style.bottom = ''; dock.style.transform = ''; } export function bindCinematicVts() { const host = document.getElementById('light-journey'); if (!host) return; readWorldManifest().then((pack) => { window.SFWorldManifest = pack; }).catch(() => {}); mountCinematicVts(host); const theatre = document.getElementById('sf-theatre-interior'); if (theatre) SFGivnVideoListings.bindNativeTheatrePlayer(theatre); const btn = document.getElementById('journey-travel'); const dock = document.getElementById('sf-vts-dock'); if (dock) { dock.hidden = false; dock.classList.add('is-stick'); const board = dock.querySelector('givn-transit'); if (board) board.classList.add('stick-only'); } pinStickToPad(); if (host && !host.dataset.stickPadBound) { host.dataset.stickPadBound = '1'; window.addEventListener('resize', pinStickToPad); if (typeof ResizeObserver === 'function') { const pad = host.querySelector('.journey-pad'); if (pad) new ResizeObserver(pinStickToPad).observe(pad); } requestAnimationFrame(() => requestAnimationFrame(pinStickToPad)); host.addEventListener('toggle', pinStickToPad); setTimeout(pinStickToPad, 400); setTimeout(pinStickToPad, 1400); } if (btn && !btn.dataset.vtsBound) { btn.dataset.vtsBound = '1'; if (btn.dataset.menuBound !== '1') btn.setAttribute('aria-label', 'Collapse top menu'); } function eraTravelFailClosed() { if (window.SFTimeMachine && typeof window.SFTimeMachine.toggleRail === 'function') return; const scene = window.SFJourney && window.SFJourney.scene; if (!scene) return; if (typeof scene.openEnterPreview === 'function') return; if (typeof scene.toggleEnterPreview === 'function') return; if (typeof scene.closeEnterPreviewWindow === 'function') return; } void eraTravelFailClosed; } function bindCinematicVtsWhenHost() { if (document.getElementById('light-journey')) { bindCinematicVts(); return; } const again = () => { if (document.getElementById('light-journey')) bindCinematicVts(); }; document.addEventListener('DOMContentLoaded', again, { once: true }); setTimeout(again, 0); setTimeout(again, 80); setTimeout(again, 400); } if (typeof window !== 'undefined') window.SFBindCinematicVts = bindCinematicVts; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bindCinematicVtsWhenHost); else bindCinematicVtsWhenHost(); function bindHubBridge(){ const scene=()=>window.SFJourney&&window.SFJourney.scene; const steer=(key,phase='tap')=>{ const iframe=document.querySelector('#sf-enter-preview-window iframe[data-givn-stick],#sf-enter-preview-window iframe[data-frame="hero"],#sf-enter-preview-window iframe[data-frame="filmstrip"],#sf-enter-preview-window iframe.sf-enter-preview-embed'); if(iframe&&iframe.contentWindow)iframe.contentWindow.postMessage({type:'givn:hub-key',key,phase},'*'); const board=document.querySelector('#sf-enter-preview-window givn-transit')||document.querySelector('#sf-vts-dock givn-transit'); if(board&&typeof board.selectDirFromKey==='function')board.selectDirFromKey(key,phase); }; window.addEventListener('message',event=>{ const data=event&&event.data;if(!data||typeof data!=='object')return; if(data.type==='cinema:world-sound')document.dispatchEvent(new CustomEvent('cinema:world-sound',{detail:data.payload||{kind:data.kind}})); if(data.type==='givn:hub-focus'){const s=scene();if(s)s.hubSteer=Boolean(data.payload&&data.payload.on);} if(data.type==='givn:hub-aim'){const s=scene();if(s){if(typeof s.setEnterStickAim==='function')s.setEnterStickAim(data.payload||{});else{const p=data.payload||{};s.enterAim={x:+p.x||0,y:+p.y||0,dir:p.dir||''};s.syncEnterAimFrame?.();s.dirty=true;}}} if(data.type==='givn:hub-frame-hover'){/* joystick-only portal aim */} if(data.type==='givn:hub-window'){const s=scene();if(s&&typeof s.revealPreviewSlot==='function')s.revealPreviewSlot((data.payload&&data.payload.slot)||'north');} if(data.type==='openTheatre')openCataloguedTheatre(data.payload).catch(()=>{}); if(data.type==='givn:hub-load'&&data.payload)cinematicSceneLoader(data.payload).catch(()=>{}); if(data.type==='givn:era-enter'&&data.payload&&data.payload.id&&window.SFTimeMachine&&typeof window.SFTimeMachine.enter==='function')window.SFTimeMachine.enter(data.payload.id); }); document.addEventListener('pointerover',event=>{ const t=event.target; const over=Boolean(t&&t.closest&&(t.closest('#sf-vts-dock')||t.closest('#sf-enter-preview-window [data-frame="hero"]')||t.closest('#sf-enter-preview-window [data-frame="filmstrip"]')||(t.matches&&t.matches('iframe[data-frame="hero"],iframe[data-frame="filmstrip"]')))); const s=scene();if(s)s.hubSteer=over||Boolean(s.hubSteer&&t&&t.closest&&t.closest('#sf-enter-preview-window')); }); const host=document.getElementById('light-journey'); if(host&&window.SFJourney&&window.SFJourney.scene)window.SFJourney.scene.events.hubSteer=steer; } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bindHubBridge); else bindHubBridge();