/** * 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=20260921b'; 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'; const ORBITAL = new Set(['mars', 'venus', 'jupiter', 'neptune']); export function bodyToWorld(destination) { const body = String(destination?.celestial_body || '').toLowerCase(); if (body === 'moon' || ORBITAL.has(body)) return body; return 'earth'; } export async function cinematicSceneLoader(destination) { if (!destination || typeof destination !== 'object') throw new Error('scene_unavailable'); 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) return dock.querySelector('givn-transit'); dock = document.createElement('aside'); dock.id = 'sf-vts-dock'; dock.className = 'sf-vts-dock'; dock.hidden = true; dock.setAttribute('aria-label', 'Transit board'); 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.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); root.append(dock); return board; } if (typeof window !== 'undefined') { window.SFGivnVideoListings = SFGivnVideoListings; window.SFILandmarkFromDestination = landmarkFromDestination; } export function bindCinematicVts() { const host = document.getElementById('light-journey'); if (!host) return; 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 = true; if (btn && !btn.dataset.vtsBound) { btn.dataset.vtsBound = '1'; btn.addEventListener('click', () => { const scene = window.SFJourney && window.SFJourney.scene; if (!scene) return; const open = !(typeof scene.enterPreviewVisible === 'function' && scene.enterPreviewVisible()); if (open) { if (typeof scene.openEnterPreview === 'function') scene.openEnterPreview(); else if (typeof scene.toggleEnterPreview === 'function') scene.toggleEnterPreview(true); } else if (typeof scene.closeEnterPreviewWindow === 'function') scene.closeEnterPreviewWindow(); else if (typeof scene.toggleEnterPreview === 'function') scene.toggleEnterPreview(false); btn.setAttribute('aria-pressed', String(open)); }); } } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bindCinematicVts); else bindCinematicVts(); function bindHubBridge(){ const scene=()=>window.SFJourney&&window.SFJourney.scene; const steer=(key,phase='tap')=>{ const iframe=document.querySelector('#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==='givn:hub-load'&&data.payload)cinematicSceneLoader(data.payload).catch(()=>{}); }); 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="filmstrip"]')||(t.matches&&t.matches('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();