159 lines
5.6 KiB
JavaScript
159 lines
5.6 KiB
JavaScript
|
|
/* One ANSI ramp per session.
|
||
|
|
*
|
||
|
|
* Boards rotated their welcome screens; this draws a palette instead. It is
|
||
|
|
* held in sessionStorage so it stays put while you read and changes when you
|
||
|
|
* come back, rather than flickering on every page load.
|
||
|
|
*
|
||
|
|
* Every ramp is three of the sixteen EGA indices, ordered dark to bright, and
|
||
|
|
* none of them bottom out on the frame blue, which would dissolve the logo's
|
||
|
|
* feet into the chrome. Without JS the CSS defaults apply and nothing is lost.
|
||
|
|
*/
|
||
|
|
(function () {
|
||
|
|
var RAMPS = [
|
||
|
|
['#0000aa', '#00aaaa', '#55ffff'], // ice
|
||
|
|
['#0000aa', '#00aaaa', '#ffffff'], // ice/white
|
||
|
|
['#00aaaa', '#55ffff', '#ffffff'], // shallow
|
||
|
|
['#555555', '#00aaaa', '#55ffff'], // slate cyan
|
||
|
|
['#00aa00', '#55ff55', '#ffffff'], // moss
|
||
|
|
['#555555', '#00aa00', '#55ff55'], // slate green
|
||
|
|
['#00aa00', '#00aaaa', '#55ffff'], // verdigris
|
||
|
|
['#aa0000', '#aa5500', '#ffff55'], // ember
|
||
|
|
['#aa0000', '#ff5555', '#ffff55'], // fire
|
||
|
|
['#aa5500', '#ff5555', '#ffff55'], // rust
|
||
|
|
['#aa5500', '#ffff55', '#ffffff'], // amber
|
||
|
|
['#555555', '#aaaaaa', '#ffffff'], // mono
|
||
|
|
['#555555', '#aaaaaa', '#55ffff'], // cold mono
|
||
|
|
['#aa00aa', '#ff55ff', '#ffffff'], // violet
|
||
|
|
['#aa0000', '#aa00aa', '#ff55ff'] // sunset
|
||
|
|
];
|
||
|
|
var i;
|
||
|
|
try {
|
||
|
|
i = sessionStorage.getItem('ramp');
|
||
|
|
if (i === null) {
|
||
|
|
i = Math.floor(Math.random() * RAMPS.length);
|
||
|
|
sessionStorage.setItem('ramp', i);
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
i = Math.floor(Math.random() * RAMPS.length);
|
||
|
|
}
|
||
|
|
var r = RAMPS[i % RAMPS.length];
|
||
|
|
var s = document.documentElement.style;
|
||
|
|
s.setProperty('--l1', r[0]);
|
||
|
|
s.setProperty('--l2', r[1]);
|
||
|
|
s.setProperty('--l3', r[2]);
|
||
|
|
})();
|
||
|
|
|
||
|
|
/* The prompt accepts commands.
|
||
|
|
*
|
||
|
|
* Numbers mean different things depending on the screen, which is how a board
|
||
|
|
* worked: at the main menu they select an area, inside an area they select a
|
||
|
|
* message. Nothing has to disambiguate because no two things on one screen
|
||
|
|
* claim the same key. Anything typeable carries data-key and the chrome that
|
||
|
|
* marks it, so the brackets are a promise rather than decoration.
|
||
|
|
*/
|
||
|
|
(function () {
|
||
|
|
// The script sits in <head> so the ramp applies before first paint; the
|
||
|
|
// command handler has to wait for a DOM to read its keys out of.
|
||
|
|
if (document.readyState === 'loading') {
|
||
|
|
document.addEventListener('DOMContentLoaded', init);
|
||
|
|
} else {
|
||
|
|
init();
|
||
|
|
}
|
||
|
|
|
||
|
|
function init() {
|
||
|
|
var buf = '', timer;
|
||
|
|
var out = document.getElementById('typed');
|
||
|
|
if (!out) return;
|
||
|
|
var hint = document.createElement('span');
|
||
|
|
hint.className = 'hint';
|
||
|
|
out.parentNode.insertBefore(hint, out.nextSibling.nextSibling);
|
||
|
|
|
||
|
|
function keys() {
|
||
|
|
var m = {};
|
||
|
|
[].forEach.call(document.querySelectorAll('[data-key]'), function (a) {
|
||
|
|
m[String(a.dataset.key).trim().toUpperCase()] = {
|
||
|
|
href: a.getAttribute('href'),
|
||
|
|
label: a.textContent.trim().replace(/\s+/g, ' ').slice(0, 44)
|
||
|
|
};
|
||
|
|
});
|
||
|
|
return m;
|
||
|
|
}
|
||
|
|
var MAP = keys();
|
||
|
|
|
||
|
|
// Echo what the number is pointing at, so a pause is informative rather than
|
||
|
|
// just a delay: you can see it is right and wait, keep typing, or hit Enter.
|
||
|
|
function show() {
|
||
|
|
out.textContent = buf;
|
||
|
|
var hit = MAP[buf];
|
||
|
|
hint.textContent = hit ? ' ' + hit.label : '';
|
||
|
|
}
|
||
|
|
function go(k) {
|
||
|
|
var hit = MAP[k];
|
||
|
|
if (hit) { location.href = hit.href; return true; }
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
addEventListener('keydown', function (e) {
|
||
|
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||
|
|
var k = e.key;
|
||
|
|
|
||
|
|
clearTimeout(timer);
|
||
|
|
if (k === 'Enter') {
|
||
|
|
if (buf && !go(buf)) { out.textContent = ''; }
|
||
|
|
buf = ''; show(); e.preventDefault(); return;
|
||
|
|
}
|
||
|
|
if (k === 'Backspace') { buf = buf.slice(0, -1); show(); e.preventDefault(); return; }
|
||
|
|
if (k === 'Escape') { buf = ''; show(); e.preventDefault(); return; }
|
||
|
|
|
||
|
|
if (/^[0-9]$/.test(k)) {
|
||
|
|
buf += k;
|
||
|
|
show();
|
||
|
|
clearTimeout(timer);
|
||
|
|
var longer = Object.keys(MAP).some(function (x) {
|
||
|
|
return x.length > buf.length && x.indexOf(buf) === 0;
|
||
|
|
});
|
||
|
|
if (!longer) {
|
||
|
|
// Nothing longer can match, so settle it now: go, or drop what was
|
||
|
|
// typed so a mistyped number never sticks at the prompt.
|
||
|
|
if (MAP[buf]) { go(buf); } else { buf = ''; show(); }
|
||
|
|
} else if (MAP[buf]) {
|
||
|
|
// Ambiguous: "1" is an area and "12" is an entry further down the same
|
||
|
|
// screen. Terminal menus settled this on a short pause rather than
|
||
|
|
// making every selection need Enter, so this does too.
|
||
|
|
// 1200ms, because the message index runs past 40 and reaching for a
|
||
|
|
// second digit takes longer than a confirmation would. Enter commits
|
||
|
|
// at once for anyone who does not want to wait.
|
||
|
|
timer = setTimeout(function () { go(buf); }, 1200);
|
||
|
|
}
|
||
|
|
e.preventDefault();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (/^[a-zA-Z]$/.test(k)) {
|
||
|
|
if (go(k.toUpperCase())) e.preventDefault();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
})();
|
||
|
|
|
||
|
|
/* The address is stored rot13 so it is not sitting in the HTML for a scraper to
|
||
|
|
* lift. Same trick as tobias.raayoni-last.com. Without JS the fallback text
|
||
|
|
* still reads, spelled out, so nothing is lost but the click.
|
||
|
|
*/
|
||
|
|
(function () {
|
||
|
|
function rot13(s) {
|
||
|
|
return s.replace(/[a-zA-Z]/g, function (c) {
|
||
|
|
return String.fromCharCode((c <= 'Z' ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function init() {
|
||
|
|
[].forEach.call(document.querySelectorAll('.mail[data-r]'), function (el) {
|
||
|
|
var a = document.createElement('a');
|
||
|
|
a.href = 'mailto:' + rot13(el.dataset.r);
|
||
|
|
a.textContent = rot13(el.dataset.r);
|
||
|
|
el.replaceWith(a);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
||
|
|
else init();
|
||
|
|
})();
|