diff --git a/mobile/main.js b/mobile/main.js index 2ba712aa0..8ea1ce7c8 100644 --- a/mobile/main.js +++ b/mobile/main.js @@ -678,12 +678,21 @@ globeReady } const subj = s.moment?.subject; const c = subj - ? { lon: subj.lon, lat: subj.lat - 0.004, height: 4200 } + ? { lon: subj.lon, lat: subj.lat, height: 4200 } : s.camera || {}; + const height = Math.max(2500, Math.min(c.height || 3000, 6000)); + // On a phone the room sheet covers the lower half: lift the place + // into the visible top of the map so the hearts rise in view. + let lat = c.lat ?? s.lat; + if (!isTwoPane()) { + const r = stage.getBoundingClientRect(); + const mPerPx = (height * 0.364) / (Math.min(r.width, r.height) / 2); + lat -= (mPerPx * r.height * 0.27) / 111320; + } g.flyTo({ lon: c.lon ?? s.lon, - lat: c.lat ?? s.lat, - height: Math.max(2500, Math.min(c.height || 3000, 6000)), + lat, + height, pitch: -40, duration: 1.8, }); @@ -1003,7 +1012,22 @@ function buildDirector(g) { run: async () => { closeAll(); const m = await hero(); - const ok = (await callSocial('openRoom', m, { lane: 'live' })).ok; + // Show the globe first, then the Live lane over it. + const sit = + (await getSituations()).find((x) => x.momentId === m.id) || m; + if (!isTwoPane()) selectTab('live'); + await g.flyTo({ + lon: m.lon, + lat: m.lat - 0.004, + height: 4200, + duration: 1.2, + }); + const ok = ( + await callSocial('openRoom', sit, { + lane: 'live', + detent: 'medium', + }) + ).ok; if (!ok) await openRoom(m, { lane: 'live' }); for (let i = 0; i < 6; i += 1) setTimeout( diff --git a/mobile/styles/layout.css b/mobile/styles/layout.css index fd69ac28c..82e643a68 100644 --- a/mobile/styles/layout.css +++ b/mobile/styles/layout.css @@ -170,7 +170,7 @@ } .mp-pops { - width: 230px; + width: min(230px, calc(46% - 14px)); } .app.is-moment .gc-attr { @@ -178,7 +178,7 @@ } .mp-legend { - width: 250px; + width: min(250px, calc(50% - 14px)); } .mp-title { diff --git a/mobile/ui/moment.js b/mobile/ui/moment.js index 12d6e0a4e..6282a081a 100644 --- a/mobile/ui/moment.js +++ b/mobile/ui/moment.js @@ -85,6 +85,14 @@ export function normMoment(m) { media: c.media || c.kind || 'photo', live: c.live || c.kind === 'live', distanceM: c.distanceM ?? c.rangeM ?? Math.round(dist(c)), + headingDeg: Number.isFinite(c.headingDeg) + ? c.headingDeg + : (Math.atan2( + (subject.lon - c.lon) * Math.cos((c.lat * Math.PI) / 180), + subject.lat - c.lat, + ) * + 180) / + Math.PI, })), chat: (m.chat || []).map((c) => ({ ...c, handle: at(c.handle) })), posts: (m.posts || []).map((p) => ({ @@ -556,7 +564,21 @@ export function createMomentPlayer({ if (!m) return; cancelAnimationFrame(raf); raf = 0; - globe.exitMoment(); + // Stop the clock and let a frame render before the engine tears the + // Moment's primitives down (exiting mid-frame can stop Cesium's render). + globe.playback.stop(); + const leaving = m; + requestAnimationFrame(() => + requestAnimationFrame(() => { + if (m === null || m === leaving) globe.exitMoment(); + }), + ); + try { + ext?.destroy?.(); + } catch { + /* ignore */ + } + ext = null; offOwn?.(); offOwn = null; const was = m; diff --git a/mobile/ui/timeRibbon.js b/mobile/ui/timeRibbon.js index b6985d6f4..9e797048e 100644 --- a/mobile/ui/timeRibbon.js +++ b/mobile/ui/timeRibbon.js @@ -552,6 +552,7 @@ export function createTimeRibbon( function goLive() { haptic('tick'); stopPlay(); + if (clipMode) setClipMode(false); if (custom) { animateTo(custom.start, () => emit(true)); return;