Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
278 changes: 135 additions & 143 deletions assets/css/main.css

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions assets/js/facilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// City pages: refresh the facility readings from the live Mars Open Facilities
// index. The section lists the data URLs to try in order (data-facilities).
(() => {
const root = document.querySelector('[data-facilities]');
if (!root) return;
const urls = JSON.parse(root.dataset.facilities || '[]');
const fmt = (v) => (v == null ? '–' : Number(v).toLocaleString(document.documentElement.lang, { maximumFractionDigits: 1, minimumFractionDigits: 1 }));

const apply = (index) => {
index.facilities.forEach((f) => {
const item = root.querySelector(`[data-facility="${CSS.escape(f.id)}"]`);
if (!item) return;
const status = item.querySelector('[data-status]');
if (status) {
status.dataset.status = f.status;
status.textContent = status.getAttribute(`data-label-${f.status}`) || f.status;
}
(f.kpis || []).forEach((k) => {
const v = item.querySelector(`[data-kpi="${CSS.escape(k.tag)}"] [data-value]`);
if (v) v.textContent = fmt(k.value);
});
});
};

const load = async () => {
for (const url of urls) {
try {
const r = await fetch(url, { cache: 'no-cache' });
if (r.ok) return apply(await r.json());
} catch { /* try the next source */ }
}
return undefined;
};
load();
setInterval(load, 10 * 60 * 1000);
})();
2 changes: 1 addition & 1 deletion assets/js/imagemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

const pin = L.divIcon({
className: 'map-pin',
html: '<svg viewBox="0 0 28 28" width="28" height="28" aria-hidden="true"><circle cx="14" cy="14" r="13" fill="#e5484d" stroke="#fff" stroke-width="2"/><path d="M14 5.5l2.6 5.7 6.2.7-4.6 4.2 1.3 6.1L14 19l-5.5 3.2 1.3-6.1-4.6-4.2 6.2-.7z" fill="#fff"/></svg>',
html: '<svg viewBox="0 0 28 28" width="28" height="28" aria-hidden="true"><circle cx="14" cy="14" r="13" fill="#b0441c" stroke="#fff" stroke-width="2"/><path d="M14 5.5l2.6 5.7 6.2.7-4.6 4.2 1.3 6.1L14 19l-5.5 3.2 1.3-6.1-4.6-4.2 6.2-.7z" fill="#fff"/></svg>',
iconSize: [28, 28],
iconAnchor: [14, 14],
popupAnchor: [0, -14],
Expand Down
67 changes: 47 additions & 20 deletions assets/js/main.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
// Live Mars clock on the home page (NASA Mars24 algorithm, Allison & McEwen 2000).
// Home page instrument: today's positions of Earth and Mars and when a message
// sent now reaches Isidis. The astronomy lives in the kit (window.InterImm.astro),
// which also keeps the distance and delay readings live.
(() => {
const el = document.querySelector('[data-mars-clock]');
if (!el) return;
const mtc = el.querySelector('[data-mtc]');
const msd = el.querySelector('[data-msd]');
const utc = el.querySelector('[data-utc]');
const TT_MINUS_UTC = 37 + 32.184; // TAI-UTC (leap seconds since 2017) + TT-TAI, in seconds
const pad = (n) => String(Math.floor(n)).padStart(2, '0');
const hms = (hours) => {
const s = hours * 3600;
return `${pad(s / 3600)}:${pad((s % 3600) / 60)}:${pad(s % 60)}`;
};
const tick = () => {
const root = document.querySelector('[data-instrument]');
const astro = window.InterImm && window.InterImm.astro;
if (!root || !astro) return;

const SCALE = 100; // svg units per AU; y is flipped so ecliptic north is up
const pt = ([x, y]) => [x * SCALE, -y * SCALE];

// Orbits: trace one full revolution through the mean anomaly.
['earth', 'mars'].forEach((planet) => {
const now = Date.now();
let d = '';
for (let k = 0; k <= 180; k += 1) {
const [x, y] = pt(astro.position(planet, now, k * 2 - 180));
d += `${k ? 'L' : 'M'}${x.toFixed(2)} ${y.toFixed(2)}`;
}
root.querySelector(`[data-orbit="${planet}"]`).setAttribute('d', `${d}Z`);
});

const beam = root.querySelector('[data-beam]');
const utc = root.querySelector('[data-arrive-utc]');
const mtc = root.querySelector('[data-arrive-mtc]');

const draw = () => {
const now = Date.now();
const jdTT = now / 86400000 + 2440587.5 + TT_MINUS_UTC / 86400;
const sol = (jdTT - 2405522.0028779) / 1.0274912517;
mtc.textContent = hms((((sol % 1) + 1) % 1) * 24);
msd.textContent = sol.toFixed(5);
utc.textContent = new Date(now).toISOString().slice(11, 19);
const pos = { earth: pt(astro.position('earth', now)), mars: pt(astro.position('mars', now)) };
Object.entries(pos).forEach(([planet, [x, y]]) => {
const dot = root.querySelector(`[data-planet="${planet}"]`);
dot.setAttribute('cx', x);
dot.setAttribute('cy', y);
const r = Math.hypot(x, y) || 1;
const label = root.querySelector(`[data-planet-label="${planet}"]`);
label.setAttribute('x', x + (x / r) * 10);
label.setAttribute('y', y + (y / r) * 10 + 3);
label.setAttribute('text-anchor', x >= 0 ? 'start' : 'end');
});
beam.setAttribute('x1', pos.earth[0]);
beam.setAttribute('y1', pos.earth[1]);
beam.setAttribute('x2', pos.mars[0]);
beam.setAttribute('y2', pos.mars[1]);

const arrive = now + astro.distance(now) * astro.AU_LIGHT_S * 1000;
utc.textContent = new Date(arrive).toISOString().slice(11, 19);
mtc.textContent = astro.mtc(arrive);
};
tick();
setInterval(tick, 1000);
draw();
setInterval(draw, 1000);
})();
Loading
Loading