tools(rpo-replay): v6 — round-2 critic fixes (attributed quote, Δv wording, bigger inset, label plates and keep-outs, CA framing, pacing)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0187rSCa4AKJPGw6uiuPSGyX
This commit is contained in:
Claude committed 2026-11-08 06:29:55 +00:00
1 parent bfe2e964fb
commit 7eb53463a8
5 files changed
+104 -85

No files matched your search

+19 -16
View File
@@ -29,12 +29,12 @@ export function createHud(ctx) {
]; ];
const cap = (id, title, body, extra = {}) => ({ s0: span(id)[0], s1: span(id)[1], title, body, ...extra }); const cap = (id, title, body, extra = {}) => ({ s0: span(id)[0], s1: span(id)[1], title, body, ...extra });
const CAPTIONS = [ const CAPTIONS = [
cap('hook', 'A Russian military satellite came within about half a kilometre of a U.S. radar satellite', cap('hook', `A Russian military satellite came within <span class="c-cos nw">~${CA_M} m</span> of a U.S. one`,
'Rebuilt from public tracking data.', { hook: true, s0: -1 }), 'Rebuilt from public tracking data.', { hook: true, s0: -1 }),
cap('climb', 'Climb higher to fall behind', cap('climb', 'Climb higher to fall behind',
'Two burns lift <b class="c-cos">Cosmos 2612</b> <b>47 km</b>. A higher orbit is a longer lap, so it falls behind.'), 'Two burns lift <b class="c-cos">Cosmos 2612</b> <b>47 km</b>. A higher orbit is a longer lap, so it falls behind.'),
cap('lap', 'Lapped, twice', cap('lap', 'Lapped, twice',
'Losing <b>~6,700 km a day</b>, it is lapped by <b class="c-x36">X36</b> on Oct 5 and Oct 12, while Earth\'s bulge swings the two orbits into line.'), 'Losing <b>~6,700 km a day</b>, it gets lapped by <b class="c-x36">X36</b> on Oct 5 and Oct 12. Earth\'s bulge, plus a 4 m/s nudge, swing the orbits into line.'),
cap('drop', 'Drop into the same orbit', cap('drop', 'Drop into the same orbit',
'Oct 12: from <b>54 km</b> directly overhead it dives into X36\'s orbit, landing <b>~500 km behind</b>.'), 'Oct 12: from <b>54 km</b> directly overhead it dives into X36\'s orbit, landing <b>~500 km behind</b>.'),
cap('close', 'Speed up, then brake', cap('close', 'Speed up, then brake',
@@ -46,7 +46,7 @@ export function createHud(ctx) {
cap('approach', 'Closing in, slowly', cap('approach', 'Closing in, slowly',
'Creeping in at <b>a few centimetres a second</b>, while both fly at <b>27,000 km/h</b>.'), 'Creeping in at <b>a few centimetres a second</b>, while both fly at <b>27,000 km/h</b>.'),
cap('ca', `Closest pass: <span class="c-cos nw">~${CA_M} m</span>`, cap('ca', `Closest pass: <span class="c-cos nw">~${CA_M} m</span>`,
'In Earth\'s shadow, ~01:17 UTC. LeoLabs radar: <b>~500 m</b>. Public estimates: <b>150–650 m</b>.'), 'In Earth\'s shadow, drifting slower than a tortoise. LeoLabs radar: <b>~500 m</b>. Estimates: <b>150–650 m</b>.'),
cap('sunrise', 'Then the sun comes up', cap('sunrise', 'Then the sun comes up',
'Twenty minutes later both leave Earth\'s shadow. Within the hour, Cosmos backs away.'), 'Twenty minutes later both leave Earth\'s shadow. Within the hour, Cosmos backs away.'),
cap('leave', 'Back off, and wait', cap('leave', 'Back off, and wait',
@@ -55,7 +55,7 @@ export function createHud(ctx) {
const BURN_NOTES = { const BURN_NOTES = {
'Phasing raise': 'two burns raise its orbit 46 km', 'Phasing raise': 'two burns raise its orbit 46 km',
'Plane trim': 'tilts its orbit into line', 'Plane trim': 'tilts its orbit into line',
'Descent to X36 orbit': 'drops 48 km into X36\'s orbit', 'Descent to X36 orbit': 'drops 47 km into X36\'s orbit',
'Closing burn': 'dips 2.3 km to speed up', 'Closing burn': 'dips 2.3 km to speed up',
'Braking burn': 'rises 2.5 km to stop', 'Braking burn': 'rises 2.5 km to stop',
'Back-off': 'rises 140 m and starts to drift back', 'Back-off': 'rises 140 m and starts to drift back',
@@ -224,7 +224,7 @@ export function createHud(ctx) {
else if (st.sep < 2e6) ctxLine = `${Math.abs(tt) >= 1e4 ? fmtNum(Math.abs(tt) / 1000) + ' km' : (Math.abs(tt) / 1000).toFixed(1) + ' km'} ${tt < 0 ? 'behind' : 'ahead'}${Math.abs(r) > 20e3 ? ` · ${fmtNum(Math.abs(r) / 1000)} km ${r > 0 ? 'higher' : 'lower'}` : ''}`; else if (st.sep < 2e6) ctxLine = `${Math.abs(tt) >= 1e4 ? fmtNum(Math.abs(tt) / 1000) + ' km' : (Math.abs(tt) / 1000).toFixed(1) + ' km'} ${tt < 0 ? 'behind' : 'ahead'}${Math.abs(r) > 20e3 ? ` · ${fmtNum(Math.abs(r) / 1000)} km ${r > 0 ? 'higher' : 'lower'}` : ''}`;
else ctxLine = `${Math.abs(r) > 20e3 ? `${fmtNum(Math.abs(r) / 1000)} km higher · ` : ''}${fmtNum(st.sep / 1000)} km apart in a straight line`; else ctxLine = `${Math.abs(r) > 20e3 ? `${fmtNum(Math.abs(r) / 1000)} km higher · ` : ''}${fmtNum(st.sep / 1000)} km apart in a straight line`;
if (s > caS && s < span('leave')[0] + 0.5) ctxLine += ` · closest ${CA_M} m`; if (s > caS && s < span('leave')[0] + 0.5) ctxLine += ` · closest ${CA_M} m`;
ro.querySelector('.ctx').textContent = lapMode ? `${Math.abs(r) > 20e3 ? `${fmtNum(Math.abs(r) / 1000)} km higher · ` : ''}behind X36` : ctxLine; ro.querySelector('.ctx').textContent = lapMode ? `${st.da > 20e3 ? `${Math.round(st.da / 1000)} km higher on average · ` : ''}behind X36` : ctxLine;
const relLine = ro.querySelector('.rel'); const relLine = ro.querySelector('.rel');
relLine.textContent = st.sep < 2e4 && sh.mode === 'true' ? (st.relSpeed < 0.2 ? `relative speed ~${Math.max(1, Math.round(st.relSpeed * 100))} cm/s (modelled)` : `relative speed ~${st.relSpeed.toFixed(1)} m/s (modelled)`) : ''; relLine.textContent = st.sep < 2e4 && sh.mode === 'true' ? (st.relSpeed < 0.2 ? `relative speed ~${Math.max(1, Math.round(st.relSpeed * 100))} cm/s (modelled)` : `relative speed ~${st.relSpeed.toFixed(1)} m/s (modelled)`) : '';
@@ -234,13 +234,15 @@ export function createHud(ctx) {
const trueSize = !enlarged && st.camDistX < 200; const trueSize = !enlarged && st.camDistX < 200;
magEl.style.opacity = (enlarged || trueSize) ? fade(s, climbS, END, 0.3) : 0; magEl.style.opacity = (enlarged || trueSize) ? fade(s, climbS, END, 0.3) : 0;
magEl.classList.toggle('enlarged', enlarged); magEl.classList.toggle('enlarged', enlarged);
magEl.textContent = enlarged ? `Satellites enlarged ${Math.round(st.mag)}×` : sh.id === 'ca' ? 'True size · faint bar: ±1.5 km tracking uncertainty' : 'True size'; magEl.classList.toggle('low', !sh.inset);
magEl.textContent = enlarged ? `Satellites enlarged ${Math.round(st.mag)}×` : sh.id === 'ca' ? 'True size · tracking error ±1.5 km > the gap' : 'True size';
const relChip = $('#relchip'); const relChip = $('#relchip');
relChip.style.opacity = ['drop', 'close', 'dance', 'dance-ff', 'approach', 'leave'].includes(sh.id) ? fade(s, sh.s0, sh.s1, 0.3) : 0; relChip.style.opacity = 0;
// Inset // Inset
const insA = sh.inset && s < END - 0.1 ? fade(s, Math.max(sh.s0, climbS + 0.8), END, 0.3) : 0; const insA = sh.inset && s < END - 0.1 ? fade(s, Math.max(sh.s0, climbS + 0.8), END, 0.3) : 0;
inset.draw(s, t, (st.sepG || st.sep) / 1000, { alpha: insA * live, trailHours: sh.inset || 1, cut: CUTS.some((c) => Math.abs(s - c) < 0.05) }); insetEl.style.opacity = insA * live > 0.01 ? 1 : 0;
inset.draw(s, t, (st.sepG || st.sep) / 1000, { alpha: insA * live, trailHours: sh.inset || 1, cut: CUTS.some((c) => Math.abs(s - c) < 0.05), behindNote: sh.id === 'climb' || sh.id === 'lap' });
// Timeline // Timeline
const px = timeline.x(Math.min(Math.max(t, TL.t0), TL.t1)); const px = timeline.x(Math.min(Math.max(t, TL.t0), TL.t1));
@@ -355,6 +357,10 @@ export function createHud(ctx) {
el.style.left = `${left}px`; el.style.left = `${left}px`;
el.style.top = `${top}px`; el.style.top = `${top}px`;
el.style.transform = 'none'; el.style.transform = 'none';
if (overlap({ l: left, r: left + w, t: top, b: top + h }) > 0.35 * w * h) {
el.style.opacity = 0;
return;
}
el.style.textAlign = q[0] > 0 ? 'left' : 'right'; el.style.textAlign = q[0] > 0 ? 'left' : 'right';
obstacles.push({ left, right: left + w, top, bottom: top + h }); obstacles.push({ left, right: left + w, top, bottom: top + h });
const ax = q[0] > 0 ? left - 6 * U : left + w + 6 * U; const ax = q[0] > 0 ? left - 6 * U : left + w + 6 * U;
@@ -386,14 +392,11 @@ export function createHud(ctx) {
if (!ec.dataset.built) { if (!ec.dataset.built) {
ec.dataset.built = '1'; ec.dataset.built = '1';
ec.innerHTML = `<div class="panel"> ec.innerHTML = `<div class="panel">
<div class="big">~${CA_M} m</div> <div class="kicker">Closest pass <span class="c-cos">~${CA_M} m</span> · Oct 18 · in Earth's shadow</div>
<div class="range">closest pass, Oct 18, in Earth's shadow · LeoLabs ~500 m · estimates 150–650 m</div> <div class="quote">“Consistent with on-orbit weapons operations.”</div>
<div class="stats"> <div class="who">— Thomas DiNanno, U.S. Under Secretary of State</div>
<div><b>18</b><span>days from first burn to closest pass</span></div> <div class="still">By Oct 25, Cosmos 2612 was still parked ~480 km behind, in the same orbit.</div>
<div><b>${BURNS.length}</b><span>manoeuvres</span></div> <div class="statline">18 days from first burn · ${BURNS.length} manoeuvres · ~${Math.round(D.total_dv)} m/s Δv · LeoLabs ~500 m · estimates 150–650 m</div>
<div><b>~${Math.round(D.total_dv)} m/s</b><span>of thrust spent</span></div>
</div>
<div class="why">U.S. officials called it <b>"consistent with on-orbit weapons operations."</b><br>By Oct 25 it was still parked ~480 km behind, in the same orbit.</div>
</div>`; </div>`;
} }
$('#endlogo').style.opacity = endA; $('#endlogo').style.opacity = endA;
+15 -12
View File
@@ -32,7 +32,7 @@ export function createInset({ D, U, relAt, canvas }) {
ctx.globalAlpha = opts.alpha; ctx.globalAlpha = opts.alpha;
// Zoom: half-width (km) follows the guiding separation; eased frame to frame. // Zoom: half-width (km) follows the guiding separation; eased frame to frame.
const target = Math.min(9200, Math.max(1.4, 1.6 * sepGuideKm)); const target = Math.min(9200, Math.max(1.0, 1.25 * sepGuideKm));
if (state.hw === null || Math.abs(s - state.lastS) > 0.2 || opts.cut) state.hw = target; if (state.hw === null || Math.abs(s - state.lastS) > 0.2 || opts.cut) state.hw = target;
else state.hw = Math.exp(Math.log(state.hw) + (Math.log(target) - Math.log(state.hw)) * 0.12); else state.hw = Math.exp(Math.log(state.hw) + (Math.log(target) - Math.log(state.hw)) * 0.12);
state.lastS = s; state.lastS = s;
@@ -51,13 +51,16 @@ export function createInset({ D, U, relAt, canvas }) {
ctx.roundRect(0, 0, W, H, r); ctx.roundRect(0, 0, W, H, r);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
const head = 44 * U * dpr; const head = 52 * U * dpr;
ctx.fillStyle = 'rgba(244,247,250,0.92)'; ctx.fillStyle = 'rgba(244,247,250,0.92)';
ctx.font = `800 ${22 * U * dpr}px Inter, sans-serif`; ctx.font = `800 ${26 * U * dpr}px Inter, sans-serif`;
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.fillText('SEEN FROM X36', pad, head / 2 + 4 * dpr); ctx.fillText('SEEN FROM X36', pad, head / 2 + 4 * dpr);
const tw = ctx.measureText('SEEN FROM X36 ').width;
ctx.fillStyle = '#ff5a3c';
ctx.fillText('· red = Cosmos', pad + tw, head / 2 + 4 * dpr);
ctx.fillStyle = 'rgba(244,247,250,0.6)'; ctx.fillStyle = 'rgba(244,247,250,0.6)';
ctx.font = `600 ${20 * U * dpr}px Inter, sans-serif`; ctx.font = `700 ${24 * U * dpr}px Inter, sans-serif`;
ctx.textAlign = 'right'; ctx.textAlign = 'right';
ctx.fillText('flight →', W - pad, head / 2 + 4 * dpr); ctx.fillText('flight →', W - pad, head / 2 + 4 * dpr);
ctx.textAlign = 'left'; ctx.textAlign = 'left';
@@ -133,7 +136,7 @@ export function createInset({ D, U, relAt, canvas }) {
const [x, y] = P(...toXY(relAt(tt))); const [x, y] = P(...toXY(relAt(tt)));
if (prev) { if (prev) {
ctx.strokeStyle = `rgba(255,90,60,${0.08 + 0.85 * (k / n) ** 1.5})`; ctx.strokeStyle = `rgba(255,90,60,${0.08 + 0.85 * (k / n) ** 1.5})`;
ctx.lineWidth = (1.5 + 2.5 * (k / n)) * dpr; ctx.lineWidth = (2.5 + 4 * (k / n)) * U * 2;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(prev[0], prev[1]); ctx.moveTo(prev[0], prev[1]);
ctx.lineTo(x, y); ctx.lineTo(x, y);
@@ -162,15 +165,15 @@ export function createInset({ D, U, relAt, canvas }) {
ctx.fill(); ctx.fill();
ctx.shadowBlur = 0; ctx.shadowBlur = 0;
}; };
dot(xxp, xyp, '#5ce1e6', 7 * U); dot(xxp, xyp, '#5ce1e6', 11 * U);
dot(cxp, cyp, '#ff5a3c', 7 * U); dot(cxp, cyp, '#ff5a3c', 11 * U);
ctx.font = `800 ${20 * U * dpr}px Inter, sans-serif`; ctx.font = `800 ${26 * U * dpr}px Inter, sans-serif`;
ctx.fillStyle = '#5ce1e6'; ctx.fillStyle = '#5ce1e6';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.fillText('X36', xxp, xyp - 20 * U * dpr); ctx.fillText('X36', xxp, xyp - 24 * U * dpr);
ctx.fillStyle = '#ff5a3c'; ctx.fillStyle = '#ff5a3c';
const below = cyp > xyp + 10 * dpr || Math.abs(cxp - xxp) < 40 * U * dpr; const below = cyp > xyp + 10 * dpr || Math.abs(cxp - xxp) < 40 * U * dpr;
ctx.fillText('COSMOS', Math.min(Math.max(cxp, px0 + 50 * U * dpr), px0 + pw - 50 * U * dpr), cyp + (below ? 24 : -20) * U * dpr); ctx.fillText(opts.behindNote ? '← COSMOS falling behind' : 'COSMOS', Math.min(Math.max(cxp, px0 + 150 * U * dpr), px0 + pw - 150 * U * dpr), cyp + (below ? 32 : -24) * U * dpr);
ctx.textAlign = 'left'; ctx.textAlign = 'left';
ctx.restore(); ctx.restore();
@@ -185,13 +188,13 @@ export function createInset({ D, U, relAt, canvas }) {
ctx.lineTo(pad + sbpx, by); ctx.lineTo(pad + sbpx, by);
ctx.stroke(); ctx.stroke();
ctx.fillStyle = 'rgba(244,247,250,0.85)'; ctx.fillStyle = 'rgba(244,247,250,0.85)';
ctx.font = `700 ${19 * U * dpr}px 'JetBrains Mono', monospace`; ctx.font = `700 ${24 * U * dpr}px 'JetBrains Mono', monospace`;
ctx.textBaseline = 'bottom'; ctx.textBaseline = 'bottom';
ctx.fillText(fmt(sb), pad, by - 6 * dpr); ctx.fillText(fmt(sb), pad, by - 6 * dpr);
if (exag > 1.5) { if (exag > 1.5) {
ctx.textAlign = 'right'; ctx.textAlign = 'right';
ctx.fillStyle = 'rgba(255,181,71,0.95)'; ctx.fillStyle = 'rgba(255,181,71,0.95)';
ctx.font = `700 ${19 * U * dpr}px Inter, sans-serif`; ctx.font = `700 ${24 * U * dpr}px Inter, sans-serif`;
ctx.fillText(`heights ×${Math.round(exag)}`, W - pad, by - 6 * dpr); ctx.fillText(`heights ×${Math.round(exag)}`, W - pad, by - 6 * dpr);
ctx.textAlign = 'left'; ctx.textAlign = 'left';
} }
+16 -5
View File
@@ -90,9 +90,11 @@
#readout .val small { font-size: 0.42em; color: var(--ink-2); margin-left: 0.12em; letter-spacing: 0; } #readout .val small { font-size: 0.42em; color: var(--ink-2); margin-left: 0.12em; letter-spacing: 0; }
#readout .ctx { margin-top: calc(10 * var(--u)); font: 600 calc(32 * var(--u))/1.2 Inter, sans-serif; color: var(--ink); } #readout .ctx { margin-top: calc(10 * var(--u)); font: 600 calc(32 * var(--u))/1.2 Inter, sans-serif; color: var(--ink); }
#mag { position: absolute; left: calc(56 * var(--u) + 536 * var(--u)); bottom: calc(246 * var(--u)); padding: calc(7 * var(--u)) calc(14 * var(--u)); border-radius: calc(6 * var(--u)); #mag { position: absolute; left: var(--pad); bottom: calc(590 * var(--u)); padding: calc(7 * var(--u)) calc(14 * var(--u)); border-radius: calc(6 * var(--u));
font: 800 calc(28 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.02em; color: #000; background: #fff; } font: 800 calc(28 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.02em; color: #000; background: #fff; }
#mag.enlarged { background: var(--burn); } #mag.enlarged { background: var(--burn); }
#mag.low { bottom: calc(196 * var(--u)); }
body.portrait #mag.low { bottom: calc(440 * var(--u)); }
.nw { white-space: nowrap; } .nw { white-space: nowrap; }
#clock .pills { display: flex; gap: calc(10 * var(--u)); justify-content: flex-end; margin-top: calc(16 * var(--u)); } #clock .pills { display: flex; gap: calc(10 * var(--u)); justify-content: flex-end; margin-top: calc(16 * var(--u)); }
@@ -106,7 +108,7 @@
#clock .light.night { color: #b9c8ff; } #clock .light.night i { background: #20263a; border: calc(3 * var(--u)) solid #b9c8ff; } #clock .light.night { color: #b9c8ff; } #clock .light.night i { background: #20263a; border: calc(3 * var(--u)) solid #b9c8ff; }
#readout .rel { margin-top: calc(6 * var(--u)); font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); } #readout .rel { margin-top: calc(6 * var(--u)); font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); }
/* Inset + chips */ /* Inset + chips */
#inset { position: absolute; left: var(--pad); bottom: calc(196 * var(--u)); width: calc(520 * var(--u)); height: calc(330 * var(--u)); } #inset { position: absolute; left: var(--pad); bottom: calc(196 * var(--u)); width: calc(640 * var(--u)); height: calc(380 * var(--u)); }
#relchip { position: absolute; left: calc(56 * var(--u) + 536 * var(--u)); bottom: calc(196 * var(--u)); padding: calc(7 * var(--u)) calc(14 * var(--u)); #relchip { position: absolute; left: calc(56 * var(--u) + 536 * var(--u)); bottom: calc(196 * var(--u)); padding: calc(7 * var(--u)) calc(14 * var(--u));
border-radius: calc(6 * var(--u)); font: 800 calc(26 * var(--u))/1 Inter, sans-serif; color: #fff; background: rgba(255,90,60,0.85); opacity: 0; } border-radius: calc(6 * var(--u)); font: 800 calc(26 * var(--u))/1 Inter, sans-serif; color: #fff; background: rgba(255,90,60,0.85); opacity: 0; }
.event { background: rgba(4,8,14,0.62); padding: calc(10 * var(--u)) calc(16 * var(--u)) calc(10 * var(--u)) calc(14 * var(--u)); border-radius: calc(8 * var(--u)); width: fit-content; } .event { background: rgba(4,8,14,0.62); padding: calc(10 * var(--u)) calc(16 * var(--u)) calc(10 * var(--u)) calc(14 * var(--u)); border-radius: calc(8 * var(--u)); width: fit-content; }
@@ -129,6 +131,7 @@
/* Tags + dimension */ /* Tags + dimension */
#leaders { position: absolute; inset: 0; width: 100%; height: 100%; } #leaders { position: absolute; inset: 0; width: 100%; height: 100%; }
.tag { position: absolute; white-space: nowrap; } .tag { position: absolute; white-space: nowrap; }
.tag { background: rgba(3,6,12,0.55); padding: calc(6 * var(--u)) calc(12 * var(--u)); border-radius: calc(8 * var(--u)); }
.tag .name { font: 800 calc(40 * var(--u))/1.05 Inter, sans-serif; letter-spacing: 0.01em; } .tag .name { font: 800 calc(40 * var(--u))/1.05 Inter, sans-serif; letter-spacing: 0.01em; }
.tag .meta { font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); margin-top: calc(4 * var(--u)); } .tag .meta { font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); margin-top: calc(4 * var(--u)); }
#dim { position: absolute; transform: translate(-50%, -50%); padding: calc(6 * var(--u)) calc(14 * var(--u)); white-space: nowrap; #dim { position: absolute; transform: translate(-50%, -50%); padding: calc(6 * var(--u)) calc(14 * var(--u)); white-space: nowrap;
@@ -148,6 +151,14 @@
#endcard .stats b { display: block; font: 700 calc(64 * var(--u))/1 'JetBrains Mono', monospace; letter-spacing: -0.03em; } #endcard .stats b { display: block; font: 700 calc(64 * var(--u))/1 'JetBrains Mono', monospace; letter-spacing: -0.03em; }
#endcard .stats span { display: block; margin-top: calc(10 * var(--u)); font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); } #endcard .stats span { display: block; margin-top: calc(10 * var(--u)); font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); }
#endcard .why { max-width: calc(1100 * var(--u)); margin: 0 auto; font: 500 calc(32 * var(--u))/1.35 Inter, sans-serif; color: var(--ink-2); } #endcard .why { max-width: calc(1100 * var(--u)); margin: 0 auto; font: 500 calc(32 * var(--u))/1.35 Inter, sans-serif; color: var(--ink-2); }
#endcard .kicker { font: 800 calc(30 * var(--u))/1.2 Inter, sans-serif; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-2); }
#endcard .quote { margin-top: calc(26 * var(--u)); font: 800 calc(76 * var(--u))/1.08 Inter, sans-serif; letter-spacing: -0.02em; }
#endcard .who { margin-top: calc(18 * var(--u)); font: 600 calc(32 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); }
#endcard .still { margin-top: calc(34 * var(--u)); font: 700 calc(38 * var(--u))/1.25 Inter, sans-serif; color: var(--ink); }
#endcard .statline { margin-top: calc(30 * var(--u)); font: 500 calc(22 * var(--u))/1.4 'JetBrains Mono', monospace; color: var(--ink-3); }
body.portrait #endcard .quote { font-size: calc(72 * var(--u)); }
body.portrait #endcard .still { font-size: calc(40 * var(--u)); }
body.portrait #endcard .statline { font-size: calc(24 * var(--u)); }
#endlogo { left: 50%; top: calc(70 * var(--u)); transform: translateX(-50%); } #endlogo { left: 50%; top: calc(70 * var(--u)); transform: translateX(-50%); }
#endlogo img { height: calc(96 * var(--u)); } #endlogo img { height: calc(96 * var(--u)); }
#endlogo span { font-size: calc(40 * var(--u)); } #endlogo span { font-size: calc(40 * var(--u)); }
@@ -166,11 +177,11 @@
body.portrait #events { top: calc(700 * var(--u)); width: auto; right: var(--pad); } body.portrait #events { top: calc(700 * var(--u)); width: auto; right: var(--pad); }
body.portrait #readout { right: auto; left: var(--pad); bottom: calc(420 * var(--u)); text-align: left; } body.portrait #readout { right: auto; left: var(--pad); bottom: calc(420 * var(--u)); text-align: left; }
body.portrait #readout .val { font-size: calc(104 * var(--u)); } body.portrait #readout .val { font-size: calc(104 * var(--u)); }
body.portrait #mag { left: auto; right: var(--pad); bottom: calc(912 * var(--u)); } body.portrait #mag { left: auto; right: var(--pad); bottom: calc(934 * var(--u)); }
body.portrait #tlwrap { display: none; } body.portrait #tlwrap { display: none; }
body.portrait #inset { left: auto; right: var(--pad); bottom: calc(420 * var(--u)); width: calc(560 * var(--u)); height: calc(430 * var(--u)); } body.portrait #inset { left: auto; right: var(--pad); bottom: calc(420 * var(--u)); width: calc(620 * var(--u)); height: calc(500 * var(--u)); }
body.portrait #relchip { left: auto; right: var(--pad); bottom: calc(866 * var(--u)); } body.portrait #relchip { left: auto; right: var(--pad); bottom: calc(866 * var(--u)); }
body.portrait #readout { max-width: calc(400 * var(--u)); } body.portrait #readout { max-width: calc(340 * var(--u)); }
body.portrait #readout .light { margin: 0 0 calc(18 * var(--u)); display: inline-flex; } body.portrait #readout .light { margin: 0 0 calc(18 * var(--u)); display: inline-flex; }
body.portrait .ch span { font-size: calc(24 * var(--u)); } body.portrait .ch span { font-size: calc(24 * var(--u)); }
body.portrait #interstitial .d { font-size: calc(120 * var(--u)); } body.portrait #interstitial .d { font-size: calc(120 * var(--u)); }
+12 -10
View File
@@ -195,7 +195,7 @@ function camAt(s) {
sun: lerp(a.sun || 0, b.sun || 0), sun: lerp(a.sun || 0, b.sun || 0),
vup: lerp(a.vup || 0, b.vup || 0), vup: lerp(a.vup || 0, b.vup || 0),
comp: lerp(a.comp ?? 1, b.comp ?? 1), comp: lerp(a.comp ?? 1, b.comp ?? 1),
compY: lerp(a.compY ?? 0, b.compY ?? 0), compY: lerp(a.compY ?? 0, b.compY ?? 0) + (PORTRAIT ? 0 : lerp(a.compYL ?? 0, b.compYL ?? 0)),
pairD: lerp(a.pairD || 0, b.pairD || 0), pairD: lerp(a.pairD || 0, b.pairD || 0),
pairTh: lerp(a.pairTh || 0, b.pairTh || 0), pairTh: lerp(a.pairTh || 0, b.pairTh || 0),
aimRel: [0, 1, 2].map((k) => lerp(ar[k], br[k])), aimRel: [0, 1, 2].map((k) => lerp(ar[k], br[k])),
@@ -453,15 +453,16 @@ function setState(s) {
v3.add(aim, v3.multiplyByScalar(RW, cam.aimRel[0], new v3()), aim); v3.add(aim, v3.multiplyByScalar(RW, cam.aimRel[0], new v3()), aim);
v3.add(aim, v3.multiplyByScalar(TW, cam.aimRel[1], new v3()), aim); v3.add(aim, v3.multiplyByScalar(TW, cam.aimRel[1], new v3()), aim);
v3.add(aim, v3.multiplyByScalar(NW, cam.aimRel[2], new v3()), aim); v3.add(aim, v3.multiplyByScalar(NW, cam.aimRel[2], new v3()), aim);
const dist = Math.max(cam.dist, cam.f * sepG) * (PORTRAIT && cam.earth > 0.5 ? 0.74 : 1); const dist = Math.max(cam.dist, cam.f * sepG) * (PORTRAIT && cam.earth > 0.5 ? 0.74 : 1) * (PORTRAIT && sh.id === 'hook' ? 0.6 : 1);
const az = Cesium.Math.toRadians(cam.az); const az = Cesium.Math.toRadians(cam.az);
const el = Cesium.Math.toRadians(cam.el); const el = Cesium.Math.toRadians(cam.el);
const off = new v3(); const off = new v3();
v3.add(off, v3.multiplyByScalar(cT, -Math.cos(el) * Math.cos(az), new v3()), off); v3.add(off, v3.multiplyByScalar(cT, -Math.cos(el) * Math.cos(az), new v3()), off);
v3.add(off, v3.multiplyByScalar(cN, Math.cos(el) * Math.sin(az), new v3()), off); v3.add(off, v3.multiplyByScalar(cN, Math.cos(el) * Math.sin(az), new v3()), off);
v3.add(off, v3.multiplyByScalar(cR, Math.sin(el), new v3()), off); v3.add(off, v3.multiplyByScalar(cR, Math.sin(el), new v3()), off);
if (cam.sun > 0) { const sunW8 = Math.min(0.8, cam.sun * (PORTRAIT ? 2 : 1));
v3.normalize(v3.add(v3.multiplyByScalar(off, 1 - cam.sun, off), v3.multiplyByScalar(sunW, cam.sun, new v3()), off), off); if (sunW8 > 0) {
v3.normalize(v3.add(v3.multiplyByScalar(off, 1 - sunW8, off), v3.multiplyByScalar(sunW, sunW8, new v3()), off), off);
} }
const camPos = v3.add(pivot, v3.multiplyByScalar(off, dist, new v3()), new v3()); const camPos = v3.add(pivot, v3.multiplyByScalar(off, dist, new v3()), new v3());
if (cam.pairD > 0) { if (cam.pairD > 0) {
@@ -469,7 +470,7 @@ function setState(s) {
// (sideways, in the orbit's horizontal), aimed at the bisector of the two. // (sideways, in the orbit's horizontal), aimed at the bisector of the two.
const u = v3.normalize(v3.subtract(cosW, x36W, new v3()), new v3()); const u = v3.normalize(v3.subtract(cosW, x36W, new v3()), new v3());
const perp = v3.normalize(v3.subtract(NW, v3.multiplyByScalar(u, v3.dot(NW, u), new v3()), new v3()), new v3()); const perp = v3.normalize(v3.subtract(NW, v3.multiplyByScalar(u, v3.dot(NW, u), new v3()), new v3()), new v3());
const th = Cesium.Math.toRadians(cam.pairTh * (PORTRAIT ? 1.3 : 1)); const th = Cesium.Math.toRadians(cam.pairTh * (PORTRAIT ? 0.7 : 1));
v3.add(x36W, v3.multiplyByScalar(u, -cam.pairD * Math.cos(th), new v3()), camPos); v3.add(x36W, v3.multiplyByScalar(u, -cam.pairD * Math.cos(th), new v3()), camPos);
v3.add(camPos, v3.multiplyByScalar(perp, cam.pairD * Math.sin(th), new v3()), camPos); v3.add(camPos, v3.multiplyByScalar(perp, cam.pairD * Math.sin(th), new v3()), camPos);
const a1 = v3.normalize(v3.subtract(x36W, camPos, new v3()), new v3()); const a1 = v3.normalize(v3.subtract(x36W, camPos, new v3()), new v3());
@@ -495,10 +496,11 @@ function setState(s) {
const aspect = W / H; const aspect = W / H;
const tanH = PORTRAIT ? Math.tan(fovR / 2) * aspect : Math.tan(fovR / 2); const tanH = PORTRAIT ? Math.tan(fovR / 2) * aspect : Math.tan(fovR / 2);
const tanV = PORTRAIT ? Math.tan(fovR / 2) : Math.tan(fovR / 2) / aspect; const tanV = PORTRAIT ? Math.tan(fovR / 2) : Math.tan(fovR / 2) / aspect;
const ndcX = (PORTRAIT ? 0 : 0.18) * cam.comp; const ndcX = (PORTRAIT ? 0 : 0.24) * cam.comp;
const ndcY = (PORTRAIT ? -0.04 : 0.06) * cam.comp + cam.compY; const ndcY = (PORTRAIT ? 0.14 : 0.06) * cam.comp + cam.compY;
const ndcYp = ndcY + (PORTRAIT && cam.pairD > 0 ? 0.13 : 0);
v3.normalize(v3.add(v3.add(dir, v3.multiplyByScalar(right, -ndcX * tanH, new v3()), new v3()), v3.normalize(v3.add(v3.add(dir, v3.multiplyByScalar(right, -ndcX * tanH, new v3()), new v3()),
v3.multiplyByScalar(up, -ndcY * tanV, new v3()), new v3()), dir); v3.multiplyByScalar(up, -ndcYp * tanV, new v3()), new v3()), dir);
right = v3.normalize(v3.cross(dir, upHint, new v3()), new v3()); right = v3.normalize(v3.cross(dir, upHint, new v3()), new v3());
up = v3.cross(right, dir, new v3()); up = v3.cross(right, dir, new v3());
} }
@@ -612,7 +614,7 @@ function setState(s) {
if (sh.id === 'ca') { if (sh.id === 'ca') {
const a0 = v3.add(cosW, v3.multiplyByScalar(TW, -1500, new v3()), new v3()); const a0 = v3.add(cosW, v3.multiplyByScalar(TW, -1500, new v3()), new v3());
const a1 = v3.add(cosW, v3.multiplyByScalar(TW, 1500, new v3()), new v3()); const a1 = v3.add(cosW, v3.multiplyByScalar(TW, 1500, new v3()), new v3());
polyline([a0, cosW, a1], [COL_COS.withAlpha(0), COL_COS.withAlpha(0.45), COL_COS.withAlpha(0)], 10); polyline([a0, cosW, a1], [COL_COS.withAlpha(0.25), COL_COS.withAlpha(0.6), COL_COS.withAlpha(0.25)], 22);
} }
// RIC reference rings (orbital plane) during proximity // RIC reference rings (orbital plane) during proximity
const gridA = Math.min(1, Math.max(0, (8e3 - Math.max(sep, camDistX)) / 3e3)) * (cam.mag > 0.4 ? 1 : 0.35); const gridA = Math.min(1, Math.max(0, (8e3 - Math.max(sep, camDistX)) / 3e3)) * (cam.mag > 0.4 ? 1 : 0.35);
@@ -640,7 +642,7 @@ function setState(s) {
const r0 = relAt(t - 30); const r0 = relAt(t - 30);
const relSpeed = Math.hypot(r1[0] - r0[0], r1[1] - r0[1], r1[2] - r0[2]) / 60; const relSpeed = Math.hypot(r1[0] - r0[0], r1[1] - r0[1], r1[2] - r0[2]) / 60;
return { sh, t, b, jd, x36W, cosW, RW, TW, NW, rel, sep, sepG, mag, gridA, lx, camDistX, vX: vXv, vC, relSpeed }; return { sh, t, b, jd, x36W, cosW, RW, TW, NW, rel, sep, sepG, da: smaOffsetAt(t), mag, gridA, lx, camDistX, vX: vXv, vC, relSpeed };
} }
async function frame(s) { async function frame(s) {
+42 -42
View File
@@ -39,87 +39,87 @@ export function buildStory({ sec, D }) {
], ],
}, },
{ {
id: 'lap', mode: 'ff', s0: 9.6, s1: 13.6, blur: 1, bgRate: 260, inset: 30, id: 'lap', mode: 'ff', s0: 9.6, s1: 12.6, blur: 1, bgRate: 260, inset: 30,
tau: [[9.6, iso('2026-09-30T13:00:00Z')], [13.6, iso('2026-10-11T23:40:00Z')]], tau: [[9.6, iso('2026-09-30T13:00:00Z')], [12.6, iso('2026-10-11T23:40:00Z')]],
cam: [ cam: [
{ s: 9.6, ...WIDE, dist: 2.9e7, az: 100, el: 30, atrail: 0, rtrail: 30 }, { s: 9.6, ...WIDE, dist: 2.9e7, az: 100, el: 30, atrail: 0, rtrail: 30 },
{ s: 13.6, ...WIDE, dist: 2.5e7, az: 116, el: 28, atrail: 0, rtrail: 30 }, { s: 12.6, ...WIDE, dist: 2.5e7, az: 116, el: 28, atrail: 0, rtrail: 30 },
], ],
}, },
{ {
id: 'drop', mode: 'true', s0: 13.6, s1: 18.6, blur: 3, inset: 4, id: 'drop', mode: 'true', s0: 12.6, s1: 17.6, blur: 3, inset: 4,
tau: [[13.6, iso('2026-10-12T00:00:00Z')], [18.6, iso('2026-10-12T03:30:00Z')]], tau: [[12.6, iso('2026-10-12T00:00:00Z')], [17.6, iso('2026-10-12T03:30:00Z')]],
cam: [ cam: [
{ s: 13.6, dist: 2.4e5, f: 2.6, az: 62, el: 28, look: 0.5, fov: 36, mag: 0, rtrail: 3, atrail: 20, vup: 1 }, { s: 12.6, dist: 2.4e5, f: 2.6, az: 62, el: 28, look: 0.5, fov: 36, mag: 0, rtrail: 3, atrail: 20, vup: 1 },
{ s: 16.0, dist: 3.0e5, f: 2.6, az: 48, el: 30, look: 0.5, fov: 36, mag: 0, rtrail: 4, atrail: 20, vup: 1 }, { s: 15.0, dist: 3.0e5, f: 2.6, az: 48, el: 30, look: 0.5, fov: 36, mag: 0, rtrail: 4, atrail: 20, vup: 1 },
{ s: 18.6, dist: 4.0e5, f: 2.6, az: 34, el: 32, look: 0.5, fov: 36, mag: 0, rtrail: 4, atrail: 20, vup: 1 }, { s: 17.6, dist: 4.0e5, f: 2.6, az: 34, el: 32, look: 0.5, fov: 36, mag: 0, rtrail: 4, atrail: 20, vup: 1 },
], ],
}, },
{ {
id: 'close', mode: 'ff', s0: 18.6, s1: 22.0, blur: 1, bgRate: 140, inset: 30, id: 'close', mode: 'ff', s0: 17.6, s1: 21.0, blur: 1, bgRate: 140, inset: 30,
tau: [[18.6, iso('2026-10-12T03:40:00Z')], [20.3, iso('2026-10-15T06:00:00Z')], [22.0, iso('2026-10-16T07:00:00Z')]], tau: [[17.6, iso('2026-10-12T03:40:00Z')], [19.3, iso('2026-10-15T06:00:00Z')], [21.0, iso('2026-10-16T07:00:00Z')]],
cam: [ cam: [
{ s: 18.6, dist: 1.0e6, f: 2.8, az: 30, el: 34, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 }, { s: 17.6, dist: 1.0e6, f: 2.8, az: 30, el: 34, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 },
{ s: 22.0, dist: 2.0e4, f: 2.8, az: 46, el: 34, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 }, { s: 21.0, dist: 2.0e4, f: 2.8, az: 46, el: 34, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 },
], ],
}, },
{ {
id: 'dance', mode: 'true', s0: 22.0, s1: 28.0, blur: 5, inset: 3.2, id: 'dance', mode: 'true', s0: 21.0, s1: 27.0, blur: 5, inset: 3.2,
tau: [[22.0, iso('2026-10-16T07:10:00Z')], [28.0, iso('2026-10-16T12:10:00Z')]], tau: [[21.0, iso('2026-10-16T07:10:00Z')], [27.0, iso('2026-10-16T12:10:00Z')]],
cam: [ cam: [
{ s: 22.0, dist: 5.5e3, f: 1.7, az: 62, el: 30, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 3, atrail: 1.2, vup: 1 }, { s: 21.0, dist: 5.5e3, f: 1.7, az: 62, el: 30, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 3, atrail: 1.2, vup: 1 },
{ s: 28.0, dist: 5.0e3, f: 1.7, az: 80, el: 26, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 3, atrail: 1.2, vup: 1 }, { s: 27.0, dist: 5.0e3, f: 1.7, az: 80, el: 26, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 3, atrail: 1.2, vup: 1 },
], ],
}, },
{ {
id: 'dance-ff', mode: 'ff', s0: 28.0, s1: 30.0, blur: 1, bgRate: 140, inset: 10, id: 'dance-ff', mode: 'ff', s0: 27.0, s1: 29.0, blur: 1, bgRate: 140, inset: 10,
tau: [[28.0, iso('2026-10-16T12:20:00Z')], [30.0, iso('2026-10-17T22:20:00Z')]], tau: [[27.0, iso('2026-10-16T12:20:00Z')], [29.0, iso('2026-10-17T22:20:00Z')]],
cam: [ cam: [
{ s: 28.0, dist: 4.5e3, f: 1.7, az: 72, el: 38, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 12, atrail: 0, vup: 1 }, { s: 27.0, dist: 4.5e3, f: 1.7, az: 72, el: 38, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 12, atrail: 0, vup: 1 },
{ s: 30.0, dist: 3.4e3, f: 1.7, az: 90, el: 32, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 12, atrail: 0, vup: 1 }, { s: 29.0, dist: 3.4e3, f: 1.7, az: 90, el: 32, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 12, atrail: 0, vup: 1 },
], ],
}, },
{ {
id: 'approach', mode: 'true', s0: 30.0, s1: 33.6, blur: 3, inset: 3, id: 'approach', mode: 'true', s0: 29.0, s1: 32.6, blur: 3, inset: 3,
tau: [[30.0, iso('2026-10-17T22:30:00Z')], [32.0, CA - 40 * M], [33.6, CA - 5 * M]], tau: [[29.0, iso('2026-10-17T22:30:00Z')], [31.0, CA - 40 * M], [32.6, CA - 5 * M]],
cam: [ cam: [
{ s: 30.0, dist: 3.0e3, f: 1.7, az: 96, el: 26, look: 0.5, fov: 38, mag: 1, mx: 30, rtrail: 3, atrail: 1.5, vup: 1 }, { s: 29.0, dist: 3.0e3, f: 1.7, az: 96, el: 26, look: 0.5, fov: 38, mag: 1, mx: 30, rtrail: 3, atrail: 1.5, vup: 1 },
{ s: 33.6, dist: 1.4e3, f: 1.7, az: 112, el: 18, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 1.6, atrail: 1.5, vup: 1 }, { s: 32.6, dist: 1.4e3, f: 1.7, az: 112, el: 18, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 1.6, atrail: 1.5, vup: 1 },
], ],
}, },
{ {
id: 'ca', mode: 'true', s0: 33.6, s1: 36.6, blur: 1, inset: 1, noSky: true, id: 'ca', mode: 'true', s0: 32.6, s1: 35.6, blur: 1, inset: 0, noSky: true,
tau: [[33.6, CA - 4 * M], [36.6, CA + 2 * M]], tau: [[32.6, CA - 4 * M], [35.6, CA + 2 * M]],
cam: [ cam: [
{ s: 33.6, dist: 26, az: 160, el: 0, look: 0, fov: 34, mag: 0, rtrail: 0.4, atrail: 0.6, comp: 0, pairD: 26, pairTh: 24 }, { s: 32.6, dist: 26, az: 160, el: 0, look: 0, fov: 34, mag: 0, rtrail: 0.4, atrail: 0.6, comp: 0, compYL: 0.07, pairD: 26, pairTh: 24 },
{ s: 36.6, dist: 20, az: 163, el: 0, look: 0, fov: 34, mag: 0, rtrail: 0.4, atrail: 0.6, comp: 0, pairD: 20, pairTh: 14 }, { s: 35.6, dist: 20, az: 163, el: 0, look: 0, fov: 34, mag: 0, rtrail: 0.4, atrail: 0.6, comp: 0, compYL: 0.07, pairD: 20, pairTh: 14 },
], ],
}, },
{ {
id: 'sunrise', mode: 'true', s0: 36.6, s1: 39.0, blur: 1, inset: 1.2, id: 'sunrise', mode: 'true', s0: 35.6, s1: 38.0, blur: 1, inset: 1.2,
tau: [[36.6, CA + 6 * M], [39.0, CA + 27 * M]], tau: [[35.6, CA + 6 * M], [38.0, CA + 27 * M]],
cam: [ cam: [
{ s: 36.6, dist: 1.3e3, f: 2.2, az: 96, el: 16, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 1.6, atrail: 1.5, vup: 1 }, { s: 35.6, dist: 1.3e3, f: 2.2, az: 96, el: 16, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 1.6, atrail: 1.5, vup: 1 },
{ s: 39.0, dist: 2.2e3, f: 2.4, az: 104, el: 22, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 2, atrail: 1.5, vup: 1 }, { s: 38.0, dist: 2.2e3, f: 2.4, az: 104, el: 22, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 2, atrail: 1.5, vup: 1 },
], ],
}, },
{ {
id: 'leave', mode: 'ff', s0: 39.0, s1: 42.4, blur: 1, bgRate: 140, inset: 40, id: 'leave', mode: 'ff', s0: 38.0, s1: 40.6, blur: 1, bgRate: 140, inset: 40,
tau: [[39.0, CA + 30 * M], [40.8, iso('2026-10-19T08:00:00Z')], [42.4, iso('2026-10-21T12:00:00Z')]], tau: [[38.0, CA + 30 * M], [39.3, iso('2026-10-19T08:00:00Z')], [40.6, iso('2026-10-21T12:00:00Z')]],
cam: [ cam: [
{ s: 39.0, dist: 1.2e4, f: 2.8, az: 110, el: 30, look: 0.4, fov: 36, mag: 1, mx: 40, rtrail: 8, atrail: 0, vup: 1 }, { s: 38.0, dist: 1.2e4, f: 2.8, az: 110, el: 30, look: 0.4, fov: 36, mag: 1, mx: 40, rtrail: 8, atrail: 0, vup: 1 },
{ s: 40.8, dist: 4.0e5, f: 2.8, az: 70, el: 30, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 }, { s: 39.3, dist: 4.0e5, f: 2.8, az: 70, el: 30, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 },
{ s: 42.4, dist: 1.6e6, f: 2.8, az: 44, el: 30, look: 0.5, fov: 34, mag: 0, rtrail: 60, atrail: 0, vup: 1 }, { s: 40.6, dist: 1.6e6, f: 2.8, az: 44, el: 30, look: 0.5, fov: 34, mag: 0, rtrail: 60, atrail: 0, vup: 1 },
], ],
}, },
{ {
id: 'end', mode: 'true', s0: 42.4, s1: 45.4, blur: 1, inset: 0, id: 'end', mode: 'true', s0: 40.6, s1: 43.6, blur: 1, inset: 0,
tau: [[42.4, iso('2026-10-21T12:00:00Z')], [45.4, iso('2026-10-21T13:30:00Z')]], tau: [[40.6, iso('2026-10-21T12:00:00Z')], [43.6, iso('2026-10-21T13:30:00Z')]],
cam: [ cam: [
{ s: 42.4, ...WIDE, dist: 3.4e7, az: 92, el: 28, atrail: 80, rtrail: 0 }, { s: 40.6, ...WIDE, dist: 3.4e7, az: 92, el: 28, atrail: 80, rtrail: 0 },
{ s: 45.4, ...WIDE, dist: 3.1e7, az: 100, el: 26, atrail: 80, rtrail: 0 }, { s: 43.6, ...WIDE, dist: 3.1e7, az: 100, el: 26, atrail: 80, rtrail: 0 },
], ],
}, },
]; ];
return { shots, duration: 45.4 }; return { shots, duration: 43.6 };
} }