// nav.mjs — buttons, keyboard, export, copy link const key = k => [{ type: 'key', id: 'kb', actions: [{ type: 'keyDown', value: k }, { type: 'keyUp', value: k }] }]; const st = `return window.mapViewer.view.getState();`; export const steps = [ { name: 'start at alt 2', wait: 4000, js: `window.mapViewer.view.setState({ lat: 20, lon: 0, alt: 2 });` + st, expect: v => Math.abs(v.alt - 2) < 1e-6 }, { name: 'zoom-in button (×0.6)', js: `document.getElementById('nav-in').click();` + st, expect: v => Math.abs(v.alt - 1.2) < 1e-6 }, { name: 'zoom-out button', js: `document.getElementById('nav-out').click();` + st, expect: v => Math.abs(v.alt - 2) < 1e-6 }, { name: 'ArrowRight pans east, north stays up', actions: key(''), js: st, expect: v => v.lon > 5 && v.hd === 0 }, { name: '+ key zooms in', actions: key('+'), js: st, expect: v => v.alt < 1.9 }, { name: 'Home key: whole planet', actions: key(''), js: `const s = window.mapViewer.view.getState(); window.__home = s; return s;`, expect: v => Math.abs(v.alt - 2) < 1e-6 && Math.abs(v.lat - 20) < 1e-6 }, { name: 'focus a text field', js: `const i = document.createElement('input'); i.id = 'tmp-input'; document.body.append(i); i.focus(); return document.activeElement.id;`, expect: v => v === 'tmp-input' }, { name: 'arrows while typing do nothing', actions: key(''), js: `const s = window.mapViewer.view.getState(); document.getElementById('tmp-input').remove(); return Math.abs(s.lon - window.__home.lon) < 1e-9;`, expect: v => v === true }, { name: 'export PNG', expect: v => v && v.name === 'world-relief-globe.png' && v.len > 20000, js: `let got = null; const orig = HTMLAnchorElement.prototype.click; HTMLAnchorElement.prototype.click = function () { got = { name: this.download, len: this.href.length }; }; document.getElementById('nav-export').click(); HTMLAnchorElement.prototype.click = orig; return got;` }, { name: 'copy link answers with a toast', expect: v => /^(Link copied|Copy failed)/.test(v), js: `document.getElementById('nav-link').click(); await new Promise(r => setTimeout(r, 400)); return document.getElementById('toast').textContent;` }, { name: 'Shift+Up tilts, Shift+Right turns, the compass shows it, N resets', expect: v => v[0] > 5 && v[1] === 15 && /rotate\(-15deg\)/.test(v[2]) && v[3] === 0 && v[4] === 0, js: `const a = window.mapViewer; a.view.setState({ lat: 40, lon: 5, alt: 0.01 }); const k = (key, shiftKey) => dispatchEvent(new KeyboardEvent('keydown', { key, shiftKey })); k('ArrowUp', true); k('ArrowRight', true); const s = a.view.getState(); await new Promise(r => setTimeout(r, 100)); const t = document.querySelector('#nav-north span').style.transform; document.getElementById('nav-north').click(); const u = a.view.getState(); return [s.pt, s.hd, t, u.hd, u.pt];` }, { name: 'tilt lands in the URL and comes back from it', expect: v => /pt=30/.test(v[0]) && /hd=45/.test(v[0]) && v[1] === 30, js: `const a = window.mapViewer; a.view.setState({ lat: 40, lon: 5, alt: 0.01, hd: 45, pt: 30 }); a.saveUrl(); const h = location.hash; a.view.setState({ lat: 40, lon: 5, alt: 0.01 }); a.saveUrl(); location.hash = h; await new Promise(r => setTimeout(r, 300)); return [h, a.view.getState().pt];` }, { name: 'flat map: Shift+arrows pan (no tilt there)', expect: v => v > 1, js: `const a = window.mapViewer; a.setView('equal_earth'); a.view.setState({ lat: 0, lon: 0, zoom: 4 }); dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', shiftKey: true })); const lon = a.view.getState().lon; a.setView('globe'); return lon;` }, { name: 'ground button from a flat map with 3D off: globe, 3D on, close and tilted, eye over the spot', expect: v => v.kind === 'globe' && v.terrain > 0 && v.alt * 12742 <= 3.6 && v.pt === 78 && Math.abs(v.lat + 53.4) < 0.05, js: `const a = window.mapViewer; a.setTerrain(0); a.setView('equal_earth'); a.view.setState({ lat: -53.4, lon: -43.9, zoom: 8 }); document.getElementById('nav-ground').click(); await new Promise(r => setTimeout(r, 200)); const s = a.view.getState(); return { kind: a.view.kind, terrain: a.state.terrain, alt: s.alt, pt: s.pt, lat: s.lat };` }, { name: '? shows the controls, Esc hides them', expect: v => v[0] === false && v[1] === true, js: `const k = key => dispatchEvent(new KeyboardEvent('keydown', { key, shiftKey: key === '?' })); k('?'); const shown = document.getElementById('help').hidden; k('Escape'); return [shown, document.getElementById('help').hidden];` }, ];