sites/randogoth.com/bbs/static/bbs.js

159 lines
5.6 KiB
JavaScript
Raw Normal View History

/* 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();
})();