release: Mark VIII 1.0

This commit is contained in:
c4ch3c4d3
2026-08-11 20:24:24 -07:00
commit 3e1805dab8
86 changed files with 20678 additions and 0 deletions
@@ -0,0 +1,158 @@
'use strict';
const Pager = (() => {
const SCREEN_WIDTH = 480;
const SCREEN_HEIGHT = 222;
const FB_STRIDE = SCREEN_WIDTH * 4;
const PAGER_WIDTH = 745;
const KEY_MAP = {
'LEFT.png': 'ArrowLeft',
'UP.png': 'ArrowUp',
'RIGHT.png': 'ArrowRight',
'DOWN.png': 'ArrowDown',
'A_Button.png': 'Enter',
'B_Button.png': 'Escape'
};
let panel = null;
let bodyEl = null;
let table = null;
let pager = null;
let canvas = null;
let screenerr = null;
let keyws = null;
let screenws = null;
function ensure() {
if (table) return;
panel = document.getElementById('pager-panel');
bodyEl = document.getElementById('pager-body');
table = document.getElementById('pager_ui');
pager = document.getElementById('pager');
canvas = document.getElementById('pager_canvas');
screenerr = document.getElementById('pager_error');
table.querySelectorAll('img.pager-btn').forEach((img) => {
const src = img.getAttribute('src').split('/').pop();
const key = KEY_MAP[src];
if (!key) return;
img.addEventListener('click', () => press(img, key));
});
const retry = document.getElementById('screen_retry');
if (retry) retry.addEventListener('click', () => connect());
}
function press(el, key) {
el.classList.add('pressed');
setTimeout(() => el.classList.remove('pressed'), 80);
sendKey(key);
}
function sendKey(k) {
if (keyws && keyws.readyState === WebSocket.OPEN) keyws.send(k);
}
function renderRGBAFrame(bytes) {
if (bytes.length < FB_STRIDE * SCREEN_HEIGHT) {
console.warn('pager frame too small for 480x222', bytes.length);
return;
}
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(SCREEN_WIDTH, SCREEN_HEIGHT);
const dst = imageData.data;
let di = 0;
for (let y = 0; y < SCREEN_HEIGHT; y++) {
const rowStart = y * FB_STRIDE;
for (let x = 0; x < SCREEN_WIDTH; x++) {
const si = rowStart + x * 4;
dst[di] = bytes[si];
dst[di + 1] = bytes[si + 1];
dst[di + 2] = bytes[si + 2];
dst[di + 3] = bytes[si + 3];
di += 4;
}
}
ctx.putImageData(imageData, 0, 0);
pager.src = canvas.toDataURL('image/png');
}
function connect() {
disconnect();
try {
const sock = new WebSocket(App.pagerScreenWs);
sock.binaryType = 'arraybuffer';
screenws = sock;
sock.onopen = () => { screenerr.hidden = true; };
sock.onmessage = (ev) => {
if (ev.data instanceof ArrayBuffer) renderRGBAFrame(new Uint8Array(ev.data));
else if (ev.data && ev.data.arrayBuffer) ev.data.arrayBuffer().then((b) => renderRGBAFrame(new Uint8Array(b)));
};
sock.onerror = () => { screenerr.hidden = false; };
sock.onclose = () => { if (screenws === sock) screenws = null; screenerr.hidden = false; };
} catch (e) {
screenerr.hidden = false;
}
try {
const sock = new WebSocket(App.pagerKeysWs);
keyws = sock;
sock.onclose = () => { if (keyws === sock) keyws = null; };
sock.onerror = () => { try { sock.close(); } catch (e2) {} };
} catch (e) {
keyws = null;
}
}
function disconnect() {
if (screenws) { try { screenws.close(); } catch (e) {} screenws = null; }
if (keyws) { try { keyws.close(); } catch (e) {} keyws = null; }
}
function applyScale() {
if (!bodyEl || !table) return;
const availableWidth = bodyEl.clientWidth - 20;
const scale = Math.min(1, availableWidth / PAGER_WIDTH);
table.style.zoom = String(scale);
table.style.marginBottom = '';
}
function toggle() {
ensure();
if (panel.classList.contains('hidden')) {
panel.classList.remove('hidden');
document.getElementById('pager-btn').classList.add('active');
applyScale();
connect();
try { pager.focus(); } catch (e) {}
} else {
panel.classList.add('hidden');
document.getElementById('pager-btn').classList.remove('active');
disconnect();
}
}
function isOpen() {
return !!panel && !panel.classList.contains('hidden');
}
function close() {
if (isOpen()) toggle();
}
window.addEventListener('resize', () => {
if (table && panel && !panel.classList.contains('hidden')) applyScale();
});
document.addEventListener('keydown', (e) => {
if (!isOpen()) return;
const t = e.target;
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT'
|| t.tagName === 'BUTTON' || t.tagName === 'A' || t.isContentEditable)) return;
if (e.ctrlKey || e.metaKey || e.altKey) return;
const keys = ['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'Enter', 'Escape'];
if (keys.indexOf(e.key) === -1) return;
if (e.key.indexOf('Arrow') === 0) e.preventDefault();
if (!e.repeat) sendKey(e.key);
});
return { toggle, close, isOpen };
})();