const t = document.getElementById('t'), z = document.getElementById('z'), grid = document.getElementById('grid'), channelsBox = document.getElementById('channels'), scalesBox = document.getElementById('scales'); const CHANNELS = META.channels, TVALS = META.t, LEVELS = META.levels; const SCALES = [...META.scales].reverse(); let selected = [CHANNELS[0]], scale = SCALES[0], AR = 1; function button(label) { const b = document.createElement('button'); b.type = 'button'; b.textContent = label; return b; } const chanButtons = CHANNELS.map(ch => { const b = button(ch); b.onclick = () => { if (selected.includes(ch)) { if (selected.length > 1) selected = selected.filter(x => x !== ch); } else selected.push(ch); refreshChannels(); rebuild(); }; channelsBox.appendChild(b); return b; }); function refreshChannels() { CHANNELS.forEach((ch, i) => chanButtons[i].setAttribute('aria-pressed', selected.includes(ch))); } scalesBox.setAttribute('role', 'radiogroup'); scalesBox.setAttribute('aria-label', 'colour scale'); const scaleButtons = SCALES.map((sc, i) => { const b = button(sc); b.setAttribute('role', 'radio'); b.onclick = () => selectScale(i); b.onkeydown = e => { const d = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1 : e.key === 'ArrowLeft' || e.key === 'ArrowUp' ? -1 : 0; if (!d) return; e.preventDefault(); selectScale((i + d + SCALES.length) % SCALES.length); }; scalesBox.appendChild(b); return b; }); function refreshScales() { SCALES.forEach((sc, i) => { const on = sc === scale; scaleButtons[i].setAttribute('aria-checked', on); scaleButtons[i].tabIndex = on ? 0 : -1; }); } function selectScale(i) { scale = SCALES[i]; refreshScales(); scaleButtons[i].focus(); update(); } function update() { const frame = TVALS[+t.value], level = +z.value; grid.querySelectorAll('img').forEach(im => im.src = `cgi-bin/index.cgi?s=${scale}&c=${im.dataset.ch}&t=${frame}&z=${level}`); } function fit() { const n = selected.length; if (!n) return; const gap = parseFloat(getComputedStyle(grid).gap) || 0; const top = grid.getBoundingClientRect().top; const W = grid.clientWidth, H = window.innerHeight - top; let best = { cols: 1, w: 0 }; for (let cols = 1; cols <= n; cols++) { const rows = Math.ceil(n / cols); const w = Math.min((W - (cols - 1) * gap) / cols, ((H - (rows - 1) * gap) / rows) * AR); if (w > best.w) best = { cols, w }; } grid.style.gridTemplateColumns = `repeat(${best.cols}, ${Math.floor(best.w)}px)`; } function rebuild() { grid.replaceChildren(...selected.map(ch => { const im = document.createElement('img'); im.dataset.ch = ch; im.alt = ch; im.onerror = () => { im.alt = ch + ' — failed to load'; }; return im; })); const first = grid.querySelector('img'); if (first) first.onload = () => { AR = first.naturalWidth / first.naturalHeight; fit(); }; update(); fit(); } t.max = TVALS.length - 1; z.max = LEVELS.length - 1; t.addEventListener('input', update); z.addEventListener('input', update); window.addEventListener('resize', fit); let active = t; [t, z].forEach(s => s.addEventListener('focus', () => active = s)); addEventListener('keydown', e => { if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; if (document.activeElement && document.activeElement.tagName === 'BUTTON') return; e.preventDefault(); e.key === 'ArrowLeft' ? active.stepDown() : active.stepUp(); active.dispatchEvent(new Event('input')); }); refreshChannels(); refreshScales(); rebuild();