flux.vision, tobias.raayoni-last.com and randogoth.com. They were one site until October 2026, serving four audiences from a single dated list, which is the only format that can hold unrelated things at once. flux.vision is recovered from the Wayback Machine, where it had sat since being deleted in 2021: 90 articles, closed and maintained rather than updated. tobias.raayoni-last.com is the professional record, 97 articles back to 1999. randogoth.com is the technical work, 41 articles across six areas, served as an ANSI bulletin board. History starts here deliberately. The development history, the 266-row inventory that records why each article went where it did, the Wayback pipelines, the pruned articles, the source archives and the full-size images are kept beside the repo rather than in it, so a clone is the web presence and not the argument that produced it. REFACTOR.md is gitignored for the same reason. See README.md for where that material lives, and keep it: the sort is not reproducible from these three directories alone. Each site documents itself in its own README, including the traps that cost time and look like nothing when you get them wrong.
158 lines
5.6 KiB
JavaScript
158 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();
|
|
})();
|